Compare commits

..

87 Commits

Author SHA1 Message Date
Paul Bottein
31603ea7b2 20251203.1 (#28383) 2025-12-05 20:53:17 +01:00
Paul Bottein
17c1043cfc Bumped version to 20251203.1 2025-12-05 20:51:48 +01:00
Timothy
da255dce40 Add add to button in more info topbar for non admin users (#28365) 2025-12-05 20:51:20 +01:00
Paul Bottein
0c68072f8f Use non-admin endpoint to subscribe to one lab feature (#28352) 2025-12-05 20:51:19 +01:00
Petar Petrov
d197fd8f76 Fix calendar card not showing different colors for multiple calendars (#28338) 2025-12-05 20:51:18 +01:00
Paul Bottein
a961a87872 Move reorder areas and floors to floor overflow (#28335) 2025-12-05 20:51:17 +01:00
Petar Petrov
cc96c707b9 Fix markdown sections and styling (#28333) 2025-12-05 20:51:16 +01:00
Petar Petrov
4b73713f2a Fix gauge severity using entity state instead of attribute value (#28331) 2025-12-05 20:51:15 +01:00
Petar Petrov
c001102f15 Append current state to power-sources-graph (#28330) 2025-12-05 20:51:14 +01:00
Preet Patel
c1e5e0bfcb Fix energy dashboard redirect for device-consumption-only configs (#28322)
When users configure energy with only device consumption (no
grid/solar/battery/gas/water sources), the dashboard would redirect
to /config/energy instead of displaying. This occurred because
_generateLovelaceConfig() returned an empty views array.

The fix adds hasDeviceConsumption check and includes ENERGY_VIEW
when device consumption is configured, since energy-view-strategy
already supports device consumption cards.
2025-12-05 20:51:13 +01:00
Bram Kragten
a1412e90fd Add more info to the energy demo (#28316)
* Add more info to the energy demo

* Also add battery power
2025-12-05 20:51:12 +01:00
Petar Petrov
f6f40c1679 Always show energy-sources-table in overview (#28315) 2025-12-05 20:48:59 +01:00
Bram Kragten
d77bebe96b Bumped version to 20251203.0 2025-12-03 15:38:49 +01:00
Bram Kragten
1260af0b45 Fix add matter device my link (#28313) 2025-12-03 15:36:05 +01:00
Petar Petrov
1d37eec411 Fix label filter losing selections when searching (#28312) 2025-12-03 15:36:04 +01:00
Bram Kragten
5a52f83358 Fix sticky headers in TCA dialog when target is selected (#28310) 2025-12-03 15:36:03 +01:00
Aidan Timson
60724eb952 Add subscribeLabFeature function (#28309)
* Add subscribe to lab feature function

* Add docstrings to exported functions
2025-12-03 15:36:02 +01:00
Aidan Timson
de5778079e Add small rotation to snowflakes (#28308) 2025-12-03 15:36:01 +01:00
Wendelin
f3710650f2 Hide disabled devices in automation target tree (#28307) 2025-12-03 15:36:00 +01:00
Paul Bottein
feb35dbc4f Use svg for snowflakes (#28306) 2025-12-03 15:35:59 +01:00
Paul Bottein
ee9e101fa6 Rename unassigned areas to other areas (#28305) 2025-12-03 15:35:58 +01:00
Paul Bottein
24b16360a6 Use core area sorting everywhere (#28304) 2025-12-03 15:35:57 +01:00
Wendelin
109c81a00d Revert "Migrate updates dropdown to ha-dropdown" (#28303)
Revert "Migrate updates dropdown to ha-dropdown (#28039)"

This reverts commit ba9bab38c9.
2025-12-03 15:35:56 +01:00
Wendelin
eaa1ddbf61 Fix filtering of floors in getAreasAndFloorsItems function (#28302) 2025-12-03 15:35:55 +01:00
Paul Bottein
b11cb57a1e Always set ha-wa-dialog position to fixed (#28301) 2025-12-03 15:35:55 +01:00
Petar Petrov
87b5f58779 Add Y-axis label formatter to energy charts (#28298) 2025-12-03 15:35:53 +01:00
Petar Petrov
8dac53c672 Fix binary sensor history timeline not rendering properly (#28297) 2025-12-03 15:35:52 +01:00
Petar Petrov
d0966bf35a Hide empty System message in assist debug view (#28296) 2025-12-03 15:35:51 +01:00
Paul Bottein
6ba4fc0808 Handle not existing panels in dashboard config (#28292) 2025-12-03 15:35:50 +01:00
ildar170975
bd582ff816 computeLovelaceEntityName(): allow "number" names to be processed (#28231)
* allow "number" names to be processed

* Apply suggestion from @MindFreeze

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2025-12-03 15:35:49 +01:00
Bram Kragten
d34bf83da0 Bumped version to 20251202.0 2025-12-02 16:02:32 +01:00
Wendelin
b0cfb31bf3 Automation add TCA: fix narrow subtitles & icons (#28291) 2025-12-02 16:02:25 +01:00
Wendelin
6c39e5d2c5 Use history to manage back button click in automations add TCA (#28289) 2025-12-02 16:02:24 +01:00
Paul Bottein
7b51e71092 Only show current weather in home overview (#28288) 2025-12-02 16:02:23 +01:00
Paul Bottein
8a82483685 Fix container alignment in section view (#28287) 2025-12-02 16:02:23 +01:00
Bram Kragten
bb691fa7a2 fix paste in add tca dialog (#28286) 2025-12-02 16:02:22 +01:00
Petar Petrov
2232db9c0f Update Energy dashboard layout (#28283) 2025-12-02 16:02:21 +01:00
Petar Petrov
5375665dc6 Fix index value for grid return in power sankey card (#28281) 2025-12-02 16:02:20 +01:00
Silas Krause
480122f40a Revert custom markdown styles (#28277) 2025-12-02 16:02:18 +01:00
karwosts
ee5c54030a Safer lookup of description_placeholders when service is invalid (#28273) 2025-12-02 16:02:17 +01:00
Paul Bottein
b73f50e864 Add dialog to reorder areas and floors (#28272) 2025-12-02 16:02:16 +01:00
eringerli
b9836073b7 fix stacking of multiple power sources (#28243) 2025-12-02 16:02:15 +01:00
Bram Kragten
a40512e0b5 Bumped version to 20251201.0 2025-12-01 16:35:54 +01:00
Paul Bottein
b2122570fb Clean reference to floor compare (#28269)
Fix floor compare
2025-12-01 16:35:34 +01:00
Paul Bottein
885f9333d2 Add helper for floor level (#28268)
* Add helper for floor level

* Update src/translations/en.json

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2025-12-01 16:35:33 +01:00
Aidan Timson
f812e7e9fb Match more-info-update backup preferences (#28266) 2025-12-01 16:35:32 +01:00
Wendelin
64dad39f6e Fix automation trigger ha icon (#28265) 2025-12-01 16:35:31 +01:00
Simon Lamon
df0fb423ed Include background in light, climate and security views (#28264)
* Include background

* Remove background key

* Add imports
2025-12-01 16:35:30 +01:00
Wendelin
4c3156f290 Respect system area sort in automation target tree (#28263) 2025-12-01 16:35:29 +01:00
Petar Petrov
ecdf374902 Reduce the duration of init animation for charts to 500ms (#28262)
Reduce the duration of init animation for charts
2025-12-01 16:35:29 +01:00
Aidan Timson
3e924e0cde Add missing key for labs to show in quick bar (#28261) 2025-12-01 16:35:27 +01:00
Bram Kragten
6fb71e12c8 Use name instead of description_configured for triggers and conditions (#28260) 2025-12-01 16:35:27 +01:00
Wendelin
6138aa5489 Fix ha-bottom-sheet closed event (#28257) 2025-12-01 16:35:26 +01:00
Aidan Timson
61e865d3a6 Fix 1px padding for subpage titles (#28256) 2025-12-01 16:35:24 +01:00
Aidan Timson
febcbf6242 Make labs toolbar icon use default color (#28255) 2025-12-01 16:35:23 +01:00
Petar Petrov
6a2fac6a9e Fix refresh in energy panel subviews (#28252) 2025-12-01 16:35:22 +01:00
karwosts
b60c5467fc Add water devices to energy data download (#28242) 2025-12-01 16:35:21 +01:00
Petar Petrov
ecd563406e Add power view and restructure energy dashboard layout (#28240) 2025-12-01 16:35:19 +01:00
Silas Krause
d5b66315e2 Fix markdown rendering for cached html (#28229)
* Render markdown table in wrapper.

* Fix markdown styles

* Fix formatting

* fix rendering for cache
2025-12-01 16:35:18 +01:00
karwosts
5b1719fc6e Add missing helper to language selector (#28218) 2025-12-01 16:35:17 +01:00
Silas Krause
add22cf2e9 Fix markdown styles regression (#28202)
* Render markdown table in wrapper.

* Fix markdown styles

* Fix formatting
2025-12-01 16:35:16 +01:00
Paul Bottein
21509191fa Fix ha icon size (#28201) 2025-12-01 16:35:15 +01:00
Paul Bottein
1a73cccf0d Fix safe area for sidebar section views in Android (#28194) 2025-12-01 16:35:14 +01:00
Aidan Timson
407d68250a Fix ha-wa-dialog fullscreen and make alerts not fullscreen (#28175) 2025-12-01 16:35:13 +01:00
Bram Kragten
38b7bd18bb Bumped version to 20251127.0 2025-11-27 17:06:57 +01:00
Wendelin
a00e944a35 Add TCA by target sort like item collections (#28192) 2025-11-27 17:06:30 +01:00
Petar Petrov
481569804e Fix water sankey calculation to include total supply from sources (#28191) 2025-11-27 17:06:29 +01:00
Paul Bottein
a1d7e270ff Add hint to reorder areas and floors (#28189) 2025-11-27 17:06:28 +01:00
Wendelin
225ccf1d2f Fix lab automations icons and sidebar width (#28184)
Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2025-11-27 17:06:27 +01:00
Wendelin
4a5e1f9f3f "Add TCA" dialog desktop height to 800px (#28182) 2025-11-27 17:06:26 +01:00
Wendelin
b27b7210fd Show hidden entities in target tree (#28181)
* Show hidden entities in target tree

* Fix types
2025-11-27 17:06:25 +01:00
Petar Petrov
acd5181449 Fix sankey chart resizing (#28180) 2025-11-27 17:06:24 +01:00
Bram Kragten
b6b2d03a80 Always store token when using develop and serve (#28179) 2025-11-27 17:06:22 +01:00
Paul Bottein
7aee2b7cb7 Fix labs back button (#28174) 2025-11-27 17:06:21 +01:00
Paul Bottein
df1914cb7a Fix disabled dashboard picker when no custom dashboard (#28172) 2025-11-27 17:06:20 +01:00
Paul Bottein
6706d5904d Fix box shadow for sidebar tabs (#28170) 2025-11-27 17:06:19 +01:00
Wendelin
221aefd764 Fix automation add TCA autofocus (#28168)
Fix automation add tca autofocus
2025-11-27 17:06:18 +01:00
Paul Bottein
670057e8e6 Restore sidebar view when clicking back (#28167) 2025-11-27 17:06:17 +01:00
Wendelin
427e46201c Fix add condition default tab and blank styles (#28166) 2025-11-27 17:06:16 +01:00
Petar Petrov
fd1240f335 Refactor power sankey hierarchy to handle devices with not power sensor (#28164) 2025-11-27 17:06:15 +01:00
Petar Petrov
aa7670cb59 Disable axis pointer on the energy devices bar chart to fix refresh issues on touch devices (#28163) 2025-11-27 17:06:14 +01:00
Petar Petrov
468139229c Handle grouping by floor and area in power sankey card (#28162) 2025-11-27 17:06:13 +01:00
Simon Lamon
39752f0e3f Don't show more info for untracked consumption (#28151) 2025-11-27 17:06:12 +01:00
Petar Petrov
4d850d067f Replace gauges with energy usage graph in energy overview (#28150) 2025-11-27 17:06:10 +01:00
Paul Bottein
bcae64df88 Use hui-root for panel energy (#28149)
* Use hui-root for panel energy

* Review feedback

* Set empty prefs
2025-11-27 17:06:09 +01:00
Iván Pereira
690fd5a061 Fix hide sidebar tooltip on touchend events (#28042)
* fix: hide sidebar tooltip on touchend events

* Add a comment recommended by Copilot

* Clear timeouts id in disconnectedCallback
2025-11-27 17:06:08 +01:00
Bram Kragten
ac56c6df9a Bumped version to 20251126.0 2025-11-26 16:11:20 +01:00
165 changed files with 2541 additions and 3912 deletions

View File

@@ -21,12 +21,12 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
ref: dev
- name: Setup Node
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version-file: ".nvmrc"
cache: yarn
@@ -56,12 +56,12 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
ref: master
- name: Setup Node
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version-file: ".nvmrc"
cache: yarn

View File

@@ -24,9 +24,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Setup Node
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version-file: ".nvmrc"
cache: yarn
@@ -58,9 +58,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Setup Node
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version-file: ".nvmrc"
cache: yarn
@@ -76,9 +76,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Setup Node
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version-file: ".nvmrc"
cache: yarn
@@ -100,9 +100,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Setup Node
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version-file: ".nvmrc"
cache: yarn

View File

@@ -23,7 +23,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
@@ -36,14 +36,14 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4.31.7
uses: github/codeql-action/init@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
with:
languages: ${{ matrix.language }}
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4.31.7
uses: github/codeql-action/autobuild@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -57,4 +57,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4.31.7
uses: github/codeql-action/analyze@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4

View File

@@ -22,12 +22,12 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
ref: dev
- name: Setup Node
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version-file: ".nvmrc"
cache: yarn
@@ -57,12 +57,12 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
ref: master
- name: Setup Node
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version-file: ".nvmrc"
cache: yarn

View File

@@ -16,10 +16,10 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Setup Node
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version-file: ".nvmrc"
cache: yarn

View File

@@ -21,10 +21,10 @@ jobs:
if: github.repository == 'home-assistant/frontend' && contains(github.event.pull_request.labels.*.name, 'needs design preview')
steps:
- name: Check out files from GitHub
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Setup Node
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version-file: ".nvmrc"
cache: yarn

View File

@@ -20,15 +20,15 @@ jobs:
contents: write
steps:
- name: Checkout the repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Setup Node
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version-file: ".nvmrc"
cache: yarn

View File

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

View File

@@ -23,10 +23,10 @@ jobs:
contents: write # Required to upload release assets
steps:
- name: Checkout the repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: ${{ env.PYTHON_VERSION }}
@@ -34,7 +34,7 @@ jobs:
uses: home-assistant/actions/helpers/verify-version@master
- name: Setup Node
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version-file: ".nvmrc"
cache: yarn
@@ -55,7 +55,7 @@ jobs:
script/release
- name: Upload release assets
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2
with:
files: |
dist/*.whl
@@ -75,7 +75,7 @@ jobs:
# home-assistant/wheels doesn't support SHA pinning
- name: Build wheels
uses: home-assistant/wheels@2025.12.0
uses: home-assistant/wheels@2025.10.0
with:
abi: cp313
tag: musllinux_1_2
@@ -91,9 +91,9 @@ jobs:
contents: write # Required to upload release assets
steps:
- name: Checkout the repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Setup Node
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version-file: ".nvmrc"
cache: yarn
@@ -108,7 +108,7 @@ jobs:
- name: Tar folder
run: tar -czf landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz -C landing-page/dist .
- name: Upload release asset
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2
with:
files: landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz
@@ -120,9 +120,9 @@ jobs:
contents: write # Required to upload release assets
steps:
- name: Checkout the repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Setup Node
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version-file: ".nvmrc"
cache: yarn
@@ -137,6 +137,6 @@ jobs:
- name: Tar folder
run: tar -czf hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz -C hassio/build .
- name: Upload release asset
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2
with:
files: hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz

View File

@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 90 days stale policy
uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1
uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 90

View File

@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Upload Translations
run: |

View File

@@ -305,8 +305,9 @@ export class HcMain extends HassElement {
await llColl.refresh();
this._unsubLovelace = llColl.subscribe(async (rawConfig) => {
if (isStrategyDashboard(rawConfig)) {
const { generateLovelaceDashboardStrategy } =
await import("../../../../src/panels/lovelace/strategies/get-strategy");
const { generateLovelaceDashboardStrategy } = await import(
"../../../../src/panels/lovelace/strategies/get-strategy"
);
const config = await generateLovelaceDashboardStrategy(
rawConfig,
this.hass!
@@ -346,8 +347,9 @@ export class HcMain extends HassElement {
}
private async _generateDefaultLovelaceConfig() {
const { generateLovelaceDashboardStrategy } =
await import("../../../../src/panels/lovelace/strategies/get-strategy");
const { generateLovelaceDashboardStrategy } = await import(
"../../../../src/panels/lovelace/strategies/get-strategy"
);
this._handleNewLovelaceConfig(
await generateLovelaceDashboardStrategy(DEFAULT_CONFIG, this.hass!)
);

View File

@@ -1,3 +0,0 @@
---
title: Adaptive dialog (ha-adaptive-dialog)
---

View File

@@ -1,732 +0,0 @@
import { css, html, LitElement } from "lit";
import { customElement, state } from "lit/decorators";
import { mdiCog, mdiHelp } from "@mdi/js";
import "../../../../src/components/ha-button";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-dialog-footer";
import "../../../../src/components/ha-adaptive-dialog";
import "../../../../src/components/ha-form/ha-form";
import "../../../../src/components/ha-icon-button";
import type { HaFormSchema } from "../../../../src/components/ha-form/types";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import type { HomeAssistant } from "../../../../src/types";
const SCHEMA: HaFormSchema[] = [
{ type: "string", name: "Name", default: "", autofocus: true },
{ type: "string", name: "Email", default: "" },
];
type DialogType =
| false
| "basic"
| "basic-subtitle-below"
| "basic-subtitle-above"
| "form"
| "form-block-mode"
| "actions"
| "large"
| "small";
@customElement("demo-components-ha-adaptive-dialog")
export class DemoHaAdaptiveDialog extends LitElement {
@state() private _openDialog: DialogType = false;
@state() private _hass?: HomeAssistant;
protected firstUpdated() {
const hass = provideHass(this);
this._hass = hass;
}
protected render() {
return html`
<div class="content">
<h1>Adaptive dialog <code>&lt;ha-adaptive-dialog&gt;</code></h1>
<p class="subtitle">
Responsive dialog component that automatically switches between a full
dialog and bottom sheet based on screen size.
</p>
<h2>Demos</h2>
<div class="buttons">
<ha-button @click=${this._handleOpenDialog("basic")}
>Basic adaptive dialog</ha-button
>
<ha-button @click=${this._handleOpenDialog("basic-subtitle-below")}
>Adaptive dialog with subtitle below</ha-button
>
<ha-button @click=${this._handleOpenDialog("basic-subtitle-above")}
>Adaptive dialog with subtitle above</ha-button
>
<ha-button @click=${this._handleOpenDialog("small")}
>Small width adaptive dialog</ha-button
>
<ha-button @click=${this._handleOpenDialog("large")}
>Large width adaptive dialog</ha-button
>
<ha-button @click=${this._handleOpenDialog("form")}
>Adaptive dialog with form</ha-button
>
<ha-button @click=${this._handleOpenDialog("form-block-mode")}
>Adaptive dialog with form (block mode change)</ha-button
>
<ha-button @click=${this._handleOpenDialog("actions")}
>Adaptive dialog with actions</ha-button
>
</div>
<ha-card>
<div class="card-content">
<p>
<strong>Tip:</strong> Resize your browser window to see the
responsive behavior. The dialog automatically switches to a bottom
sheet on narrow screens (&lt;870px width) or short screens
(&lt;500px height).
</p>
</div>
</ha-card>
<ha-adaptive-dialog
.hass=${this._hass}
.open=${this._openDialog === "basic"}
header-title="Basic adaptive dialog"
@closed=${this._handleClosed}
>
<div>Adaptive dialog content</div>
</ha-adaptive-dialog>
<ha-adaptive-dialog
.hass=${this._hass}
.open=${this._openDialog === "basic-subtitle-below"}
header-title="Adaptive dialog with subtitle"
header-subtitle="This is an adaptive dialog with a subtitle below"
@closed=${this._handleClosed}
>
<div>Adaptive dialog content</div>
</ha-adaptive-dialog>
<ha-adaptive-dialog
.hass=${this._hass}
.open=${this._openDialog === "basic-subtitle-above"}
header-title="Adaptive dialog with subtitle above"
header-subtitle="This is an adaptive dialog with a subtitle above"
header-subtitle-position="above"
@closed=${this._handleClosed}
>
<div>Adaptive dialog content</div>
</ha-adaptive-dialog>
<ha-adaptive-dialog
.hass=${this._hass}
.open=${this._openDialog === "small"}
width="small"
header-title="Small adaptive dialog"
@closed=${this._handleClosed}
>
<div>This dialog uses the small width preset (320px).</div>
</ha-adaptive-dialog>
<ha-adaptive-dialog
.hass=${this._hass}
.open=${this._openDialog === "large"}
width="large"
header-title="Large adaptive dialog"
@closed=${this._handleClosed}
>
<div>This dialog uses the large width preset (1024px).</div>
</ha-adaptive-dialog>
<ha-adaptive-dialog
.hass=${this._hass}
.open=${this._openDialog === "form"}
header-title="Adaptive dialog with form"
header-subtitle="This is an adaptive dialog with a form"
@closed=${this._handleClosed}
>
<ha-form autofocus .schema=${SCHEMA}></ha-form>
<ha-dialog-footer slot="footer">
<ha-button
@click=${this._handleClosed}
slot="secondaryAction"
variant="plain"
>Cancel</ha-button
>
<ha-button
@click=${this._handleClosed}
slot="primaryAction"
variant="accent"
>Submit</ha-button
>
</ha-dialog-footer>
</ha-adaptive-dialog>
<ha-adaptive-dialog
.hass=${this._hass}
.open=${this._openDialog === "form-block-mode"}
header-title="Adaptive dialog with form (block mode change)"
header-subtitle="This form will not reset when the viewport size changes"
block-mode-change
@closed=${this._handleClosed}
>
<ha-form autofocus .schema=${SCHEMA}></ha-form>
<ha-dialog-footer slot="footer">
<ha-button
@click=${this._handleClosed}
slot="secondaryAction"
variant="plain"
>Cancel</ha-button
>
<ha-button
@click=${this._handleClosed}
slot="primaryAction"
variant="accent"
>Submit</ha-button
>
</ha-dialog-footer>
</ha-adaptive-dialog>
<ha-adaptive-dialog
.hass=${this._hass}
.open=${this._openDialog === "actions"}
header-title="Adaptive dialog with actions"
header-subtitle="This is an adaptive dialog with header actions"
@closed=${this._handleClosed}
>
<div slot="headerActionItems">
<ha-icon-button label="Settings" path=${mdiCog}></ha-icon-button>
<ha-icon-button label="Help" path=${mdiHelp}></ha-icon-button>
</div>
<div>Adaptive dialog content</div>
</ha-adaptive-dialog>
<h2>Design</h2>
<h3>Responsive behavior</h3>
<p>
The <code>ha-adaptive-dialog</code> component automatically switches
between two modes based on screen size:
</p>
<ul>
<li>
<strong>Dialog mode:</strong> Used on larger screens (width &gt;
870px and height &gt; 500px). Renders as a centered dialog using
<code>ha-wa-dialog</code>.
</li>
<li>
<strong>Bottom sheet mode:</strong> Used on mobile devices and
smaller screens (width ≤ 870px or height ≤ 500px). Renders as a
drawer from the bottom using <code>ha-bottom-sheet</code>.
</li>
</ul>
<p>
The mode is determined automatically and updates when the window is
resized. To prevent mode changes after the initial mount (useful for
preventing form resets), use the <code>block-mode-change</code>
attribute.
</p>
<h3>Width</h3>
<p>
In dialog mode, there are multiple width presets available. These are
ignored in bottom sheet mode.
</p>
<table>
<thead>
<tr>
<th>Name</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>small</code></td>
<td><code>min(320px, var(--full-width))</code></td>
</tr>
<tr>
<td><code>medium</code></td>
<td><code>min(580px, var(--full-width))</code></td>
</tr>
<tr>
<td><code>large</code></td>
<td><code>min(1024px, var(--full-width))</code></td>
</tr>
<tr>
<td><code>full</code></td>
<td><code>var(--full-width)</code></td>
</tr>
</tbody>
</table>
<p>Adaptive dialogs have a default width of <code>medium</code>.</p>
<h3>Header</h3>
<p>
The header contains a navigation icon, title, subtitle, and action
items.
</p>
<table>
<thead>
<tr>
<th>Slot</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>headerNavigationIcon</code></td>
<td>
Leading header action (e.g., close/back button). In bottom sheet
mode, defaults to a close button if not provided.
</td>
</tr>
<tr>
<td><code>headerTitle</code></td>
<td>The header title content.</td>
</tr>
<tr>
<td><code>headerSubtitle</code></td>
<td>The header subtitle content.</td>
</tr>
<tr>
<td><code>headerActionItems</code></td>
<td>Trailing header actions (e.g., icon buttons, menus).</td>
</tr>
</tbody>
</table>
<h4>Header title</h4>
<p>
The header title can be set using the <code>header-title</code>
attribute or by providing custom content in the
<code>headerTitle</code> slot.
</p>
<h4>Header subtitle</h4>
<p>
The header subtitle can be set using the
<code>header-subtitle</code> attribute or by providing custom content
in the <code>headerSubtitle</code> slot. The subtitle position
relative to the title can be controlled with the
<code>header-subtitle-position</code> attribute.
</p>
<h4>Header navigation icon</h4>
<p>
In bottom sheet mode, a close button is automatically provided if no
custom navigation icon is specified. In dialog mode, the dialog can be
closed via the standard dialog close button.
</p>
<h4>Header action items</h4>
<p>
The header action items usually contain icon buttons and/or menu
buttons.
</p>
<h3>Body</h3>
<p>The body is the content of the adaptive dialog.</p>
<h3>Footer</h3>
<p>The footer is the footer of the adaptive dialog.</p>
<p>
It is recommended to use the <code>ha-dialog-footer</code> component
for the footer and to style the buttons inside the footer as follows:
</p>
<table>
<thead>
<tr>
<th>Slot</th>
<th>Description</th>
<th>Variant to use</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>secondaryAction</code></td>
<td>The secondary action button(s).</td>
<td><code>plain</code></td>
</tr>
<tr>
<td><code>primaryAction</code></td>
<td>The primary action button(s).</td>
<td><code>accent</code></td>
</tr>
</tbody>
</table>
<h2>Implementation</h2>
<h3>When to use</h3>
<p>
Use <code>ha-adaptive-dialog</code> when you need a dialog that should
adapt to different screen sizes automatically. This is particularly
useful for:
</p>
<ul>
<li>Forms and data entry that need to work well on mobile devices</li>
<li>
Content that benefits from full-screen presentation on small devices
</li>
<li>
Interfaces that need consistent behavior across desktop and mobile
</li>
</ul>
<p>
If you don't need responsive behavior, use
<code>ha-wa-dialog</code> directly for desktop-only dialogs or
<code>ha-bottom-sheet</code> for mobile-only sheets.
</p>
<p>
Use the <code>block-mode-change</code> attribute when you want to
prevent the dialog from switching modes after it's opened. This is
especially useful for forms, as it prevents form data from being lost
when users resize their browser window.
</p>
<h3>Example usage</h3>
<pre><code>&lt;ha-adaptive-dialog
.hass=\${this.hass}
open
width="medium"
header-title="Dialog title"
header-subtitle="Dialog subtitle"
&gt;
&lt;div slot="headerActionItems"&gt;
&lt;ha-icon-button label="Settings" path="mdiCog"&gt;&lt;/ha-icon-button&gt;
&lt;ha-icon-button label="Help" path="mdiHelp"&gt;&lt;/ha-icon-button&gt;
&lt;/div&gt;
&lt;div&gt;Dialog content&lt;/div&gt;
&lt;ha-dialog-footer slot="footer"&gt;
&lt;ha-button slot="secondaryAction" variant="plain"
&gt;Cancel&lt;/ha-button
&gt;
&lt;ha-button slot="primaryAction" variant="accent"&gt;Submit&lt;/ha-button&gt;
&lt;/ha-dialog-footer&gt;
&lt;/ha-adaptive-dialog&gt;</code></pre>
<p>Example with <code>block-mode-change</code> for forms:</p>
<pre><code>&lt;ha-adaptive-dialog
.hass=\${this.hass}
open
header-title="Edit configuration"
block-mode-change
&gt;
&lt;ha-form .schema=\${schema} .data=\${data}&gt;&lt;/ha-form&gt;
&lt;ha-dialog-footer slot="footer"&gt;
&lt;ha-button slot="secondaryAction" variant="plain"
&gt;Cancel&lt;/ha-button
&gt;
&lt;ha-button slot="primaryAction" variant="accent"&gt;Save&lt;/ha-button&gt;
&lt;/ha-dialog-footer&gt;
&lt;/ha-adaptive-dialog&gt;</code></pre>
<h3>API</h3>
<p>
This component combines <code>ha-wa-dialog</code> and
<code>ha-bottom-sheet</code> with automatic mode switching based on
screen size.
</p>
<h4>Attributes</h4>
<table>
<thead>
<tr>
<th>Attribute</th>
<th>Description</th>
<th>Default</th>
<th>Options</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>open</code></td>
<td>Controls the adaptive dialog open state.</td>
<td><code>false</code></td>
<td><code>false</code>, <code>true</code></td>
</tr>
<tr>
<td><code>width</code></td>
<td>
Preferred dialog width preset (dialog mode only, ignored in
bottom sheet mode).
</td>
<td><code>medium</code></td>
<td>
<code>small</code>, <code>medium</code>, <code>large</code>,
<code>full</code>
</td>
</tr>
<tr>
<td><code>header-title</code></td>
<td>Header title text when no custom title slot is provided.</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>header-subtitle</code></td>
<td>
Header subtitle text when no custom subtitle slot is provided.
</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>header-subtitle-position</code></td>
<td>Position of the subtitle relative to the title.</td>
<td><code>below</code></td>
<td><code>above</code>, <code>below</code></td>
</tr>
<tr>
<td><code>aria-labelledby</code></td>
<td>
The ID of the element that labels the dialog (for
accessibility).
</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>aria-describedby</code></td>
<td>
The ID of the element that describes the dialog (for
accessibility).
</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>block-mode-change</code></td>
<td>
When set, the mode is determined at mount time based on the
current screen size, but subsequent mode changes are blocked.
Useful for preventing forms from resetting when the viewport
size changes.
</td>
<td><code>false</code></td>
<td><code>false</code>, <code>true</code></td>
</tr>
</tbody>
</table>
<h4>CSS custom properties</h4>
<table>
<thead>
<tr>
<th>CSS Property</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>--ha-dialog-surface-background</code></td>
<td>Dialog/sheet background color.</td>
</tr>
<tr>
<td><code>--ha-dialog-border-radius</code></td>
<td>Border radius of the dialog surface (dialog mode only).</td>
</tr>
<tr>
<td><code>--ha-dialog-show-duration</code></td>
<td>Show animation duration (dialog mode only).</td>
</tr>
<tr>
<td><code>--ha-dialog-hide-duration</code></td>
<td>Hide animation duration (dialog mode only).</td>
</tr>
</tbody>
</table>
<h4>Events</h4>
<table>
<thead>
<tr>
<th>Event</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>opened</code></td>
<td>
Fired when the adaptive dialog is shown (dialog mode only).
</td>
</tr>
<tr>
<td><code>closed</code></td>
<td>
Fired after the adaptive dialog is hidden (dialog mode only).
</td>
</tr>
<tr>
<td><code>after-show</code></td>
<td>Fired after show animation completes (dialog mode only).</td>
</tr>
</tbody>
</table>
<h3>Focus management</h3>
<p>
To automatically focus an element when the adaptive dialog opens, add
the
<code>autofocus</code> attribute to it. Components with
<code>delegatesFocus: true</code> (like <code>ha-form</code>) will
forward focus to their first focusable child.
</p>
<p>Example:</p>
<pre><code>&lt;ha-adaptive-dialog .hass=\${this.hass} open&gt;
&lt;ha-form autofocus .schema=\${schema}&gt;&lt;/ha-form&gt;
&lt;/ha-adaptive-dialog&gt;</code></pre>
</div>
`;
}
private _handleOpenDialog = (dialog: DialogType) => () => {
this._openDialog = dialog;
};
private _handleClosed = () => {
this._openDialog = false;
};
static styles = [
css`
:host {
display: block;
padding: var(--ha-space-4);
}
.content {
max-width: 1000px;
margin: 0 auto;
}
h1 {
margin-top: 0;
margin-bottom: var(--ha-space-2);
}
h2 {
margin-top: var(--ha-space-6);
margin-bottom: var(--ha-space-3);
}
h3,
h4 {
margin-top: var(--ha-space-4);
margin-bottom: var(--ha-space-2);
}
p {
margin: var(--ha-space-2) 0;
line-height: 1.6;
}
ul {
margin: var(--ha-space-2) 0;
padding-left: var(--ha-space-5);
}
li {
margin: var(--ha-space-1) 0;
line-height: 1.6;
}
.subtitle {
color: var(--secondary-text-color);
font-size: 1.1em;
margin-bottom: var(--ha-space-4);
}
table {
width: 100%;
border-collapse: collapse;
margin: var(--ha-space-3) 0;
}
th,
td {
text-align: left;
padding: var(--ha-space-2);
border-bottom: 1px solid var(--divider-color);
}
th {
font-weight: 500;
}
code {
background-color: var(--secondary-background-color);
padding: 2px 6px;
border-radius: 4px;
font-family: monospace;
font-size: 0.9em;
}
pre {
background-color: var(--secondary-background-color);
padding: var(--ha-space-3);
border-radius: 8px;
overflow-x: auto;
margin: var(--ha-space-3) 0;
}
pre code {
background-color: transparent;
padding: 0;
}
.buttons {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: var(--ha-space-2);
margin: var(--ha-space-4) 0;
}
.card-content {
padding: var(--ha-space-3);
}
a {
color: var(--primary-color);
}
`,
];
}
declare global {
interface HTMLElementTagNameMap {
"demo-components-ha-adaptive-dialog": DemoHaAdaptiveDialog;
}
}

View File

@@ -139,7 +139,7 @@ export class DemoHaWaDialog extends LitElement {
</tr>
<tr>
<td><code>large</code></td>
<td><code>min(1024px, var(--full-width))</code></td>
<td><code>min(720px, var(--full-width))</code></td>
</tr>
<tr>
<td><code>full</code></td>

View File

@@ -88,8 +88,8 @@ class HassioRegistriesDialog extends LitElement {
<ha-button
?disabled=${Boolean(
!this._input.registry ||
!this._input.username ||
!this._input.password
!this._input.username ||
!this._input.password
)}
@click=${this._addNewRegistry}
appearance="filled"

View File

@@ -52,7 +52,7 @@
"@fullcalendar/list": "6.1.19",
"@fullcalendar/luxon3": "6.1.19",
"@fullcalendar/timegrid": "6.1.19",
"@home-assistant/webawesome": "3.0.0-ha.1",
"@home-assistant/webawesome": "3.0.0-ha.0",
"@lezer/highlight": "1.2.3",
"@lit-labs/motion": "1.0.9",
"@lit-labs/observers": "2.0.6",
@@ -89,8 +89,8 @@
"@thomasloven/round-slider": "0.6.0",
"@tsparticles/engine": "3.9.1",
"@tsparticles/preset-links": "3.2.0",
"@vaadin/combo-box": "24.9.6",
"@vaadin/vaadin-themable-mixin": "24.9.6",
"@vaadin/combo-box": "24.9.5",
"@vaadin/vaadin-themable-mixin": "24.9.5",
"@vibrant/color": "4.0.0",
"@vue/web-component-wrapper": "1.3.0",
"@webcomponents/scoped-custom-element-registry": "0.0.10",
@@ -152,13 +152,13 @@
"@babel/helper-define-polyfill-provider": "0.6.5",
"@babel/plugin-transform-runtime": "7.28.5",
"@babel/preset-env": "7.28.5",
"@bundle-stats/plugin-webpack-filter": "4.21.7",
"@bundle-stats/plugin-webpack-filter": "4.21.6",
"@lokalise/node-api": "15.4.0",
"@octokit/auth-oauth-device": "8.0.3",
"@octokit/plugin-retry": "8.0.3",
"@octokit/rest": "22.0.1",
"@rsdoctor/rspack-plugin": "1.3.12",
"@rspack/core": "1.6.6",
"@rsdoctor/rspack-plugin": "1.3.11",
"@rspack/core": "1.6.4",
"@rspack/dev-server": "1.1.4",
"@types/babel__plugin-transform-runtime": "7.9.5",
"@types/chromecast-caf-receiver": "6.0.22",
@@ -178,7 +178,7 @@
"@types/tar": "6.1.13",
"@types/ua-parser-js": "0.7.39",
"@types/webspeechapi": "0.0.29",
"@vitest/coverage-v8": "4.0.15",
"@vitest/coverage-v8": "4.0.13",
"babel-loader": "10.0.0",
"babel-plugin-template-html-minifier": "4.1.0",
"browserslist-useragent-regexp": "4.1.3",
@@ -194,7 +194,7 @@
"eslint-plugin-wc": "3.0.2",
"fancy-log": "2.0.0",
"fs-extra": "11.3.2",
"glob": "13.0.0",
"glob": "12.0.0",
"gulp": "5.0.1",
"gulp-brotli": "3.0.0",
"gulp-json-transform": "0.5.0",
@@ -209,17 +209,17 @@
"lodash.template": "4.5.0",
"map-stream": "0.0.7",
"pinst": "3.0.0",
"prettier": "3.7.4",
"prettier": "3.6.2",
"rspack-manifest-plugin": "5.2.0",
"serve": "14.2.5",
"sinon": "21.0.0",
"tar": "7.5.2",
"terser-webpack-plugin": "5.3.15",
"terser-webpack-plugin": "5.3.14",
"ts-lit-plugin": "2.0.2",
"typescript": "5.9.3",
"typescript-eslint": "8.48.1",
"typescript-eslint": "8.47.0",
"vite-tsconfig-paths": "5.1.4",
"vitest": "4.0.15",
"vitest": "4.0.13",
"webpack-stats-plugin": "1.1.3",
"webpackbar": "7.0.0",
"workbox-build": "patch:workbox-build@npm%3A7.1.1#~/.yarn/patches/workbox-build-npm-7.1.1-a854f3faae.patch"

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "home-assistant-frontend"
version = "20251203.0"
version = "20251203.1"
license = "Apache-2.0"
license-files = ["LICENSE*"]
description = "The Home Assistant frontend"

View File

@@ -45,8 +45,9 @@ export const computeFormatFunctions = async (
formatEntityAttributeName: FormatEntityAttributeNameFunc;
formatEntityName: FormatEntityNameFunc;
}> => {
const { computeStateDisplay } =
await import("../entity/compute_state_display");
const { computeStateDisplay } = await import(
"../entity/compute_state_display"
);
const { computeAttributeValueDisplay, computeAttributeNameDisplay } =
await import("../entity/compute_attribute_display");

View File

@@ -1,6 +1,6 @@
import type { PropertyValues } from "lit";
import { html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { property, state } from "lit/decorators";
import type { VisualMapComponentOption } from "echarts/components";
import type { LineSeriesOption } from "echarts/charts";
import type { YAXisOption } from "echarts/types/dist/shared";
@@ -27,7 +27,6 @@ const safeParseFloat = (value) => {
return isFinite(parsed) ? parsed : null;
};
@customElement("state-history-chart-line")
export class StateHistoryChartLine extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -796,6 +795,7 @@ export class StateHistoryChartLine extends LitElement {
return Math.abs(value) < 1 ? value : roundingFn(value);
}
}
customElements.define("state-history-chart-line", StateHistoryChartLine);
declare global {
interface HTMLElementTagNameMap {

View File

@@ -838,10 +838,10 @@ export class HaDataTable extends LitElement {
} else if (this.sortDirection === "asc") {
this.sortDirection = "desc";
} else {
this.sortDirection = "asc";
this.sortDirection = null;
}
this.sortColumn = columnId;
this.sortColumn = this.sortDirection === null ? undefined : columnId;
fireEvent(this, "sorting-changed", {
column: columnId,

View File

@@ -2,7 +2,7 @@ import { mdiAlert } from "@mdi/js";
import type { HassEntity } from "home-assistant-js-websocket";
import type { CSSResultGroup, PropertyValues } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { property, state } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import { styleMap } from "lit/directives/style-map";
import { computeDomain } from "../../common/entity/compute_domain";
@@ -17,7 +17,6 @@ import { CLIMATE_HVAC_ACTION_TO_MODE } from "../../data/climate";
import type { HomeAssistant } from "../../types";
import "../ha-state-icon";
@customElement("state-badge")
export class StateBadge extends LitElement {
public hass?: HomeAssistant;
@@ -266,3 +265,5 @@ declare global {
"state-badge": StateBadge;
}
}
customElements.define("state-badge", StateBadge);

View File

@@ -1,188 +0,0 @@
import { mdiClose } from "@mdi/js";
import { css, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import type { HomeAssistant } from "../types";
import { listenMediaQuery } from "../common/dom/media_query";
import "./ha-bottom-sheet";
import "./ha-dialog-header";
import "./ha-icon-button";
import "./ha-wa-dialog";
import type { DialogWidth } from "./ha-wa-dialog";
type DialogSheetMode = "dialog" | "bottom-sheet";
/**
* Home Assistant adaptive dialog component
*
* @element ha-adaptive-dialog
* @extends {LitElement}
*
* @summary
* A responsive dialog component that automatically switches between a full dialog (ha-wa-dialog)
* and a bottom sheet (ha-bottom-sheet) based on screen size. Uses dialog mode on larger screens
* (>870px width and >500px height) and bottom sheet mode on smaller screens or mobile devices.
*
* @slot headerNavigationIcon - Leading header action (e.g. close/back button).
* @slot headerTitle - Custom title content (used when header-title is not set).
* @slot headerSubtitle - Custom subtitle content (used when header-subtitle is not set).
* @slot headerActionItems - Trailing header actions (e.g. buttons, menus).
* @slot - Dialog/sheet content body.
* @slot footer - Dialog/sheet footer content.
*
* @cssprop --ha-dialog-surface-background - Dialog/sheet background color.
* @cssprop --ha-dialog-border-radius - Border radius of the dialog surface (dialog mode only).
* @cssprop --ha-dialog-show-duration - Show animation duration (dialog mode only).
* @cssprop --ha-dialog-hide-duration - Hide animation duration (dialog mode only).
*
* @attr {boolean} open - Controls the dialog/sheet open state.
* @attr {("small"|"medium"|"large"|"full")} width - Preferred dialog width preset (dialog mode only). Defaults to "medium".
* @attr {string} header-title - Header title text. If not set, the headerTitle slot is used.
* @attr {string} header-subtitle - Header subtitle text. If not set, the headerSubtitle slot is used.
* @attr {("above"|"below")} header-subtitle-position - Position of the subtitle relative to the title. Defaults to "below".
* @attr {boolean} block-mode-change - When set, the mode is determined at mount time based on the current screen size, but subsequent mode changes are blocked. Useful for preventing forms from resetting when the viewport size changes.
*
* @event opened - Fired when the dialog/sheet is shown (dialog mode only).
* @event closed - Fired after the dialog/sheet is hidden.
* @event after-show - Fired after show animation completes (dialog mode only).
*
* @remarks
* **Responsive Behavior:**
* The component automatically switches between dialog and bottom sheet modes based on viewport size.
* Dialog mode is used for screens wider than 870px and taller than 500px.
* Bottom sheet mode is used for mobile devices and smaller screens.
*
* When `block-mode-change` is set, the mode is determined once at mount time based on the initial
* screen size. Subsequent viewport size changes will not trigger mode switches, which is useful
* for preventing form resets or other state loss when users resize their browser window.
*
* **Focus Management:**
* To automatically focus an element when opened, add the `autofocus` attribute to it.
* Components with `delegatesFocus: true` (like `ha-form`) will forward focus to their first focusable child.
* Example: `<ha-form autofocus .schema=${schema}></ha-form>`
*/
@customElement("ha-adaptive-dialog")
export class HaAdaptiveDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: "aria-labelledby" })
public ariaLabelledBy?: string;
@property({ attribute: "aria-describedby" })
public ariaDescribedBy?: string;
@property({ type: Boolean, reflect: true })
public open = false;
@property({ type: String, reflect: true, attribute: "width" })
public width: DialogWidth = "medium";
@property({ attribute: "header-title" })
public headerTitle?: string;
@property({ attribute: "header-subtitle" })
public headerSubtitle?: string;
@property({ type: String, attribute: "header-subtitle-position" })
public headerSubtitlePosition: "above" | "below" = "below";
@property({ type: Boolean, attribute: "block-mode-change" })
public blockModeChange = false;
@state() private _mode: DialogSheetMode = "dialog";
private _unsubMediaQuery?: () => void;
private _modeSet = false;
connectedCallback() {
super.connectedCallback();
this._unsubMediaQuery = listenMediaQuery(
"(max-width: 870px), (max-height: 500px)",
(matches) => {
if (!this._modeSet || !this.blockModeChange) {
this._mode = matches ? "bottom-sheet" : "dialog";
this._modeSet = true;
}
}
);
}
disconnectedCallback() {
super.disconnectedCallback();
this._unsubMediaQuery?.();
this._unsubMediaQuery = undefined;
this._modeSet = false;
}
render() {
if (this._mode === "bottom-sheet") {
return html`
<ha-bottom-sheet .open=${this.open} flexcontent>
<ha-dialog-header
slot="header"
.subtitlePosition=${this.headerSubtitlePosition}
>
<slot name="headerNavigationIcon" slot="navigationIcon">
<ha-icon-button
data-drawer="close"
.label=${this.hass?.localize("ui.common.close") ?? "Close"}
.path=${mdiClose}
></ha-icon-button>
</slot>
${this.headerTitle !== undefined
? html`<span slot="title" class="title" id="ha-wa-dialog-title">
${this.headerTitle}
</span>`
: html`<slot name="headerTitle" slot="title"></slot>`}
${this.headerSubtitle !== undefined
? html`<span slot="subtitle">${this.headerSubtitle}</span>`
: html`<slot name="headerSubtitle" slot="subtitle"></slot>`}
<slot name="headerActionItems" slot="actionItems"></slot>
</ha-dialog-header>
<slot></slot>
<slot name="footer" slot="footer"></slot>
</ha-bottom-sheet>
`;
}
return html`
<ha-wa-dialog
.hass=${this.hass}
.open=${this.open}
.width=${this.width}
.ariaLabelledBy=${this.ariaLabelledBy}
.ariaDescribedBy=${this.ariaDescribedBy}
.headerTitle=${this.headerTitle}
.headerSubtitle=${this.headerSubtitle}
.headerSubtitlePosition=${this.headerSubtitlePosition}
flexcontent
>
<slot name="headerNavigationIcon" slot="headerNavigationIcon"></slot>
<slot name="headerTitle" slot="headerTitle"></slot>
<slot name="headerSubtitle" slot="headerSubtitle"></slot>
<slot name="headerActionItems" slot="headerActionItems"></slot>
<slot></slot>
<slot name="footer" slot="footer"></slot>
</ha-wa-dialog>
`;
}
static get styles() {
return [
css`
ha-bottom-sheet {
--ha-bottom-sheet-surface-background: var(
--ha-dialog-surface-background,
var(--card-background-color, var(--ha-color-surface-default))
);
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-adaptive-dialog": HaAdaptiveDialog;
}
}

View File

@@ -2,13 +2,12 @@ import "@home-assistant/webawesome/dist/components/drawer/drawer";
import { css, html, LitElement, type PropertyValues } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { SwipeGestureRecognizer } from "../common/util/swipe-gesture-recognizer";
import { ScrollableFadeMixin } from "../mixins/scrollable-fade-mixin";
import { haStyleScrollbar } from "../resources/styles";
export const BOTTOM_SHEET_ANIMATION_DURATION_MS = 300;
@customElement("ha-bottom-sheet")
export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
export class HaBottomSheet extends LitElement {
@property({ type: Boolean }) public open = false;
@property({ type: Boolean, reflect: true, attribute: "flexcontent" })
@@ -18,12 +17,6 @@ export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
@query("#drawer") private _drawer!: HTMLElement;
@query("#body") private _bodyElement!: HTMLDivElement;
protected get scrollableElement(): HTMLElement | null {
return this._bodyElement;
}
private _gestureRecognizer = new SwipeGestureRecognizer();
private _isDragging = false;
@@ -56,13 +49,9 @@ export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
@touchstart=${this._handleTouchStart}
>
<slot name="header"></slot>
<div class="content-wrapper">
<div id="body" class="body ha-scrollbar">
<slot></slot>
</div>
${this.renderScrollableFades()}
<div id="body" class="body ha-scrollbar">
<slot></slot>
</div>
<slot name="footer"></slot>
</wa-drawer>
`;
}
@@ -178,87 +167,60 @@ export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
this._isDragging = false;
}
static get styles() {
return [
...super.styles,
haStyleScrollbar,
css`
wa-drawer {
--wa-color-surface-raised: transparent;
--spacing: 0;
--size: var(--ha-bottom-sheet-height, auto);
--show-duration: ${BOTTOM_SHEET_ANIMATION_DURATION_MS}ms;
--hide-duration: ${BOTTOM_SHEET_ANIMATION_DURATION_MS}ms;
}
wa-drawer::part(dialog) {
max-height: var(--ha-bottom-sheet-max-height, 90vh);
align-items: center;
transform: var(--dialog-transform);
transition: var(--dialog-transition);
}
wa-drawer::part(body) {
max-width: var(--ha-bottom-sheet-max-width);
width: 100%;
border-top-left-radius: var(
--ha-bottom-sheet-border-radius,
var(--ha-dialog-border-radius, var(--ha-border-radius-2xl))
);
border-top-right-radius: var(
--ha-bottom-sheet-border-radius,
var(--ha-dialog-border-radius, var(--ha-border-radius-2xl))
);
background-color: var(
--ha-bottom-sheet-surface-background,
var(--ha-dialog-surface-background, var(--mdc-theme-surface, #fff)),
);
padding: var(
--ha-bottom-sheet-padding,
0 var(--safe-area-inset-right) var(--safe-area-inset-bottom)
var(--safe-area-inset-left)
);
}
:host([flexcontent]) wa-drawer::part(body) {
display: flex;
flex-direction: column;
}
.content-wrapper {
position: relative;
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
}
:host([flexcontent]) .body {
flex: 1;
max-width: 100%;
display: flex;
flex-direction: column;
padding: var(
--ha-bottom-sheet-padding,
0 var(--safe-area-inset-right) var(--safe-area-inset-bottom)
var(--safe-area-inset-left)
);
}
slot[name="footer"] {
display: block;
padding: var(--ha-space-0);
}
::slotted([slot="footer"]) {
display: flex;
padding: var(--ha-space-3) var(--ha-space-4) var(--ha-space-4)
var(--ha-space-4);
gap: var(--ha-space-3);
justify-content: flex-end;
align-items: center;
width: 100%;
box-sizing: border-box;
}
:host([flexcontent]) slot[name="footer"] {
flex-shrink: 0;
}
`,
];
}
static styles = [
haStyleScrollbar,
css`
wa-drawer {
--wa-color-surface-raised: transparent;
--spacing: 0;
--size: var(--ha-bottom-sheet-height, auto);
--show-duration: ${BOTTOM_SHEET_ANIMATION_DURATION_MS}ms;
--hide-duration: ${BOTTOM_SHEET_ANIMATION_DURATION_MS}ms;
}
wa-drawer::part(dialog) {
max-height: var(--ha-bottom-sheet-max-height, 90vh);
align-items: center;
transform: var(--dialog-transform);
transition: var(--dialog-transition);
}
wa-drawer::part(body) {
max-width: var(--ha-bottom-sheet-max-width);
width: 100%;
border-top-left-radius: var(
--ha-bottom-sheet-border-radius,
var(--ha-dialog-border-radius, var(--ha-border-radius-2xl))
);
border-top-right-radius: var(
--ha-bottom-sheet-border-radius,
var(--ha-dialog-border-radius, var(--ha-border-radius-2xl))
);
background-color: var(
--ha-bottom-sheet-surface-background,
var(--ha-dialog-surface-background, var(--mdc-theme-surface, #fff)),
);
padding: var(
--ha-bottom-sheet-padding,
0 var(--safe-area-inset-right) var(--safe-area-inset-bottom)
var(--safe-area-inset-left)
);
}
:host([flexcontent]) wa-drawer::part(body) {
display: flex;
flex-direction: column;
}
:host([flexcontent]) .body {
flex: 1;
max-width: 100%;
display: flex;
flex-direction: column;
padding: var(
--ha-bottom-sheet-padding,
0 var(--safe-area-inset-right) var(--safe-area-inset-bottom)
var(--safe-area-inset-left)
);
}
`,
];
}
declare global {

View File

@@ -32,12 +32,6 @@ export class HaGridSizeEditor extends LitElement {
@property({ attribute: false }) public step = 1;
@property({ type: Boolean, attribute: "rows-disabled" })
public rowsDisabled?: boolean;
@property({ type: Boolean, attribute: "columns-disabled" })
public columnsDisabled?: boolean;
@state() public _localValue?: CardGridSize = { rows: 1, columns: 1 };
protected willUpdate(changedProperties) {
@@ -48,11 +42,9 @@ export class HaGridSizeEditor extends LitElement {
protected render() {
const disabledColumns =
this.columnsDisabled ||
(this.columnMin !== undefined && this.columnMin === this.columnMax);
this.columnMin !== undefined && this.columnMin === this.columnMax;
const disabledRows =
this.rowsDisabled ||
(this.rowMin !== undefined && this.rowMin === this.rowMax);
this.rowMin !== undefined && this.rowMin === this.rowMax;
const autoHeight = this._localValue?.rows === "auto";
const fullWidth = this._localValue?.columns === "full";
@@ -80,7 +72,7 @@ export class HaGridSizeEditor extends LitElement {
@value-changed=${this._valueChanged}
@slider-moved=${this._sliderMoved}
.disabled=${disabledColumns}
tooltip-mode=${disabledColumns ? "never" : "always"}
tooltip-mode="always"
></ha-grid-layout-slider>
<ha-grid-layout-slider
@@ -96,7 +88,7 @@ export class HaGridSizeEditor extends LitElement {
@value-changed=${this._valueChanged}
@slider-moved=${this._sliderMoved}
.disabled=${disabledRows}
tooltip-mode=${disabledRows ? "never" : "always"}
tooltip-mode="always"
></ha-grid-layout-slider>
${!this.isDefault
? html`

View File

@@ -14,7 +14,6 @@ import memoizeOne from "memoize-one";
import { tinykeys } from "tinykeys";
import { fireEvent } from "../common/dom/fire_event";
import { caseInsensitiveStringCompare } from "../common/string/compare";
import { ScrollableFadeMixin } from "../mixins/scrollable-fade-mixin";
import { HaFuse } from "../resources/fuse";
import { haStyleScrollbar } from "../resources/styles";
import { loadVirtualizer } from "../resources/virtualizer";
@@ -60,7 +59,7 @@ export type PickerComboBoxSearchFn<T extends PickerComboBoxItem> = (
) => T[];
@customElement("ha-picker-combo-box")
export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
export class HaPickerComboBox extends LitElement {
@property({ attribute: false }) public hass?: HomeAssistant;
// eslint-disable-next-line lit/no-native-attributes
@@ -127,10 +126,6 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
@state() private _items: (PickerComboBoxItem | string)[] = [];
protected get scrollableElement(): HTMLElement | null {
return this._virtualizerElement as HTMLElement | null;
}
@state() private _sectionTitle?: string;
private _allItems: (PickerComboBoxItem | string)[] = [];
@@ -185,22 +180,19 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
</div>
`
: nothing}
<div class="virtualizer-wrapper">
<lit-virtualizer
.keyFunction=${this._keyFunction}
tabindex="0"
scroller
.items=${this._items}
.renderItem=${this._renderItem}
style="min-height: 36px;"
class=${this._listScrolled ? "scrolled" : ""}
@scroll=${this._onScrollList}
@focus=${this._focusList}
@visibilityChanged=${this._visibilityChanged}
>
</lit-virtualizer>
${this.renderScrollableFades()}
</div>`;
<lit-virtualizer
.keyFunction=${this._keyFunction}
tabindex="0"
scroller
.items=${this._items}
.renderItem=${this._renderItem}
style="min-height: 36px;"
class=${this._listScrolled ? "scrolled" : ""}
@scroll=${this._onScrollList}
@focus=${this._focusList}
@visibilityChanged=${this._visibilityChanged}
>
</lit-virtualizer>`;
}
private _renderSectionButtons() {
@@ -592,167 +584,156 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
private _keyFunction = (item: PickerComboBoxItem | string) =>
typeof item === "string" ? item : item.id;
static get styles() {
return [
...super.styles,
haStyleScrollbar,
css`
:host {
display: flex;
flex-direction: column;
padding-top: var(--ha-space-3);
flex: 1;
}
static styles = [
haStyleScrollbar,
css`
:host {
display: flex;
flex-direction: column;
padding-top: var(--ha-space-3);
flex: 1;
}
ha-textfield {
padding: 0 var(--ha-space-3);
margin-bottom: var(--ha-space-3);
}
ha-textfield {
padding: 0 var(--ha-space-3);
margin-bottom: var(--ha-space-3);
}
:host([mode="dialog"]) ha-textfield {
padding: 0 var(--ha-space-4);
}
:host([mode="dialog"]) ha-textfield {
padding: 0 var(--ha-space-4);
}
ha-combo-box-item {
width: 100%;
}
ha-combo-box-item {
width: 100%;
}
ha-combo-box-item.selected {
background-color: var(--ha-color-fill-neutral-quiet-hover);
}
@media (prefers-color-scheme: dark) {
ha-combo-box-item.selected {
background-color: var(--ha-color-fill-neutral-quiet-hover);
background-color: var(--ha-color-fill-neutral-normal-hover);
}
}
@media (prefers-color-scheme: dark) {
ha-combo-box-item.selected {
background-color: var(--ha-color-fill-neutral-normal-hover);
}
}
lit-virtualizer {
flex: 1;
}
.virtualizer-wrapper {
position: relative;
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
}
lit-virtualizer:focus-visible {
outline: none;
}
lit-virtualizer {
flex: 1;
}
lit-virtualizer.scrolled {
border-top: 1px solid var(--ha-color-border-neutral-quiet);
}
lit-virtualizer:focus-visible {
outline: none;
}
.bottom-padding {
height: max(var(--safe-area-inset-bottom, 0px), var(--ha-space-8));
width: 100%;
}
lit-virtualizer.scrolled {
border-top: 1px solid var(--ha-color-border-neutral-quiet);
}
.empty {
text-align: center;
}
.bottom-padding {
height: max(var(--safe-area-inset-bottom, 0px), var(--ha-space-8));
width: 100%;
}
.combo-box-row {
display: flex;
width: 100%;
align-items: center;
box-sizing: border-box;
min-height: 36px;
}
.combo-box-row.current-value {
background-color: var(--ha-color-fill-primary-quiet-resting);
}
.empty {
text-align: center;
}
.combo-box-row {
display: flex;
width: 100%;
align-items: center;
box-sizing: border-box;
min-height: 36px;
}
.combo-box-row.current-value {
background-color: var(--ha-color-fill-primary-quiet-resting);
}
.combo-box-row.selected {
background-color: var(--ha-color-fill-neutral-quiet-hover);
}
@media (prefers-color-scheme: dark) {
.combo-box-row.selected {
background-color: var(--ha-color-fill-neutral-quiet-hover);
background-color: var(--ha-color-fill-neutral-normal-hover);
}
}
@media (prefers-color-scheme: dark) {
.combo-box-row.selected {
background-color: var(--ha-color-fill-neutral-normal-hover);
}
}
.sections {
display: flex;
flex-wrap: nowrap;
gap: var(--ha-space-2);
padding: var(--ha-space-3) var(--ha-space-3);
overflow: auto;
}
.sections {
display: flex;
flex-wrap: nowrap;
gap: var(--ha-space-2);
padding: var(--ha-space-3) var(--ha-space-3);
overflow: auto;
}
:host([mode="dialog"]) .sections {
padding: var(--ha-space-3) var(--ha-space-4);
}
:host([mode="dialog"]) .sections {
padding: var(--ha-space-3) var(--ha-space-4);
}
.sections ha-filter-chip {
flex-shrink: 0;
--md-filter-chip-selected-container-color: var(
--ha-color-fill-primary-normal-hover
);
color: var(--primary-color);
}
.sections ha-filter-chip {
flex-shrink: 0;
--md-filter-chip-selected-container-color: var(
--ha-color-fill-primary-normal-hover
);
color: var(--primary-color);
}
.sections .separator {
height: var(--ha-space-8);
width: 0;
border: 1px solid var(--ha-color-border-neutral-quiet);
}
.sections .separator {
height: var(--ha-space-8);
width: 0;
border: 1px solid var(--ha-color-border-neutral-quiet);
}
.section-title,
.title {
background-color: var(--ha-color-fill-neutral-quiet-resting);
padding: var(--ha-space-1) var(--ha-space-2);
font-weight: var(--ha-font-weight-bold);
color: var(--secondary-text-color);
min-height: var(--ha-space-6);
display: flex;
align-items: center;
}
.section-title,
.title {
background-color: var(--ha-color-fill-neutral-quiet-resting);
padding: var(--ha-space-1) var(--ha-space-2);
font-weight: var(--ha-font-weight-bold);
color: var(--secondary-text-color);
min-height: var(--ha-space-6);
display: flex;
align-items: center;
}
.title {
width: 100%;
}
.title {
width: 100%;
}
:host([mode="dialog"]) .title {
padding: var(--ha-space-1) var(--ha-space-4);
}
:host([mode="dialog"]) .title {
padding: var(--ha-space-1) var(--ha-space-4);
}
:host([mode="dialog"]) ha-textfield {
padding: 0 var(--ha-space-4);
}
:host([mode="dialog"]) ha-textfield {
padding: 0 var(--ha-space-4);
}
.section-title-wrapper {
height: 0;
position: relative;
}
.section-title-wrapper {
height: 0;
position: relative;
}
.section-title {
opacity: 0;
position: absolute;
top: 1px;
width: calc(100% - var(--ha-space-8));
}
.section-title {
opacity: 0;
position: absolute;
top: 1px;
width: calc(100% - var(--ha-space-8));
}
.section-title.show {
opacity: 1;
z-index: 1;
}
.section-title.show {
opacity: 1;
z-index: 1;
}
.empty-search {
display: flex;
width: 100%;
flex-direction: column;
align-items: center;
padding: var(--ha-space-3);
}
`,
];
}
.empty-search {
display: flex;
width: 100%;
flex-direction: column;
align-items: center;
padding: var(--ha-space-3);
}
`,
];
}
declare global {

View File

@@ -103,8 +103,8 @@ export class HaPickerField extends LitElement {
--md-list-item-two-line-container-height: 56px;
--md-list-item-top-space: 0px;
--md-list-item-bottom-space: 0px;
--md-list-item-leading-space: var(--ha-space-4);
--md-list-item-trailing-space: var(--ha-space-2);
--md-list-item-leading-space: 8px;
--md-list-item-trailing-space: 8px;
--ha-md-list-item-gap: var(--ha-space-2);
/* Remove the default focus ring */
--md-focus-ring-width: 0px;

View File

@@ -450,7 +450,7 @@ export class HaServiceControl extends LitElement {
const hasOptional = Boolean(
!shouldRenderServiceDataYaml &&
serviceData?.flatFields.some((field) => showOptionalToggle(field))
serviceData?.flatFields.some((field) => showOptionalToggle(field))
);
const targetEntities = this._getTargetedEntities(

File diff suppressed because one or more lines are too long

View File

@@ -10,7 +10,6 @@ import {
} from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import { fireEvent } from "../common/dom/fire_event";
import { ScrollableFadeMixin } from "../mixins/scrollable-fade-mixin";
import { haStyleScrollbar } from "../resources/styles";
import type { HomeAssistant } from "../types";
import "./ha-dialog-header";
@@ -73,7 +72,7 @@ export type DialogWidth = "small" | "medium" | "large" | "full";
* @see https://github.com/home-assistant/frontend/issues/27143
*/
@customElement("ha-wa-dialog")
export class HaWaDialog extends ScrollableFadeMixin(LitElement) {
export class HaWaDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: "aria-labelledby" })
@@ -114,10 +113,6 @@ export class HaWaDialog extends ScrollableFadeMixin(LitElement) {
@state()
private _bodyScrolled = false;
protected get scrollableElement(): HTMLElement | null {
return this.bodyContainer;
}
protected updated(
changedProperties: Map<string | number | symbol, unknown>
): void {
@@ -166,11 +161,8 @@ export class HaWaDialog extends ScrollableFadeMixin(LitElement) {
<slot name="headerActionItems" slot="actionItems"></slot>
</ha-dialog-header>
</slot>
<div class="content-wrapper">
<div class="body ha-scrollbar" @scroll=${this._handleBodyScroll}>
<slot></slot>
</div>
${this.renderScrollableFades()}
<div class="body ha-scrollbar" @scroll=${this._handleBodyScroll}>
<slot></slot>
</div>
<slot name="footer" slot="footer"></slot>
</wa-dialog>
@@ -207,180 +199,165 @@ export class HaWaDialog extends ScrollableFadeMixin(LitElement) {
this._bodyScrolled = (ev.target as HTMLDivElement).scrollTop > 0;
}
static get styles() {
return [
...super.styles,
haStyleScrollbar,
css`
wa-dialog {
--full-width: var(
--ha-dialog-width-full,
min(95vw, var(--safe-width))
);
--width: min(var(--ha-dialog-width-md, 580px), var(--full-width));
--spacing: var(--dialog-content-padding, var(--ha-space-6));
--show-duration: var(--ha-dialog-show-duration, 200ms);
--hide-duration: var(--ha-dialog-hide-duration, 200ms);
--ha-dialog-surface-background: var(
--card-background-color,
var(--ha-color-surface-default)
);
--wa-color-surface-raised: var(
--ha-dialog-surface-background,
var(--card-background-color, var(--ha-color-surface-default))
);
--wa-panel-border-radius: var(
--ha-dialog-border-radius,
var(--ha-border-radius-3xl)
);
max-width: var(--ha-dialog-max-width, var(--safe-width));
}
static styles = [
haStyleScrollbar,
css`
wa-dialog {
--full-width: var(--ha-dialog-width-full, min(95vw, var(--safe-width)));
--width: min(var(--ha-dialog-width-md, 580px), var(--full-width));
--spacing: var(--dialog-content-padding, var(--ha-space-6));
--show-duration: var(--ha-dialog-show-duration, 200ms);
--hide-duration: var(--ha-dialog-hide-duration, 200ms);
--ha-dialog-surface-background: var(
--card-background-color,
var(--ha-color-surface-default)
);
--wa-color-surface-raised: var(
--ha-dialog-surface-background,
var(--card-background-color, var(--ha-color-surface-default))
);
--wa-panel-border-radius: var(
--ha-dialog-border-radius,
var(--ha-border-radius-3xl)
);
max-width: var(--ha-dialog-max-width, var(--safe-width));
}
:host([width="small"]) wa-dialog {
--width: min(var(--ha-dialog-width-sm, 320px), var(--full-width));
}
:host([width="small"]) wa-dialog {
--width: min(var(--ha-dialog-width-sm, 320px), var(--full-width));
}
:host([width="large"]) wa-dialog {
--width: min(var(--ha-dialog-width-lg, 1024px), var(--full-width));
}
:host([width="large"]) wa-dialog {
--width: min(var(--ha-dialog-width-lg, 1024px), var(--full-width));
}
:host([width="full"]) wa-dialog {
--width: var(--full-width);
}
:host([width="full"]) wa-dialog {
--width: var(--full-width);
}
wa-dialog::part(dialog) {
min-width: var(--width, var(--full-width));
max-width: var(--width, var(--full-width));
max-height: var(
--ha-dialog-max-height,
calc(var(--safe-height) - var(--ha-space-20))
);
min-height: var(--ha-dialog-min-height);
margin-top: var(--dialog-surface-margin-top, auto);
/* Used to offset the dialog from the safe areas when space is limited */
transform: translate(
calc(
var(--safe-area-offset-left, var(--ha-space-0)) - var(
--safe-area-offset-right,
var(--ha-space-0)
)
),
calc(
var(--safe-area-offset-top, var(--ha-space-0)) - var(
--safe-area-offset-bottom,
var(--ha-space-0)
)
)
);
display: flex;
flex-direction: column;
overflow: hidden;
}
wa-dialog::part(dialog) {
min-width: var(--width, var(--full-width));
max-width: var(--width, var(--full-width));
max-height: var(
--ha-dialog-max-height,
calc(var(--safe-height) - var(--ha-space-20))
);
min-height: var(--ha-dialog-min-height);
margin-top: var(--dialog-surface-margin-top, auto);
/* Used to offset the dialog from the safe areas when space is limited */
transform: translate(
calc(
var(--safe-area-offset-left, var(--ha-space-0)) - var(
--safe-area-offset-right,
var(--ha-space-0)
)
),
calc(
var(--safe-area-offset-top, var(--ha-space-0)) - var(
--safe-area-offset-bottom,
var(--ha-space-0)
)
)
);
display: flex;
flex-direction: column;
overflow: hidden;
}
@media all and (max-width: 450px), all and (max-height: 500px) {
:host([type="standard"]) {
--ha-dialog-border-radius: var(--ha-space-0);
@media all and (max-width: 450px), all and (max-height: 500px) {
:host([type="standard"]) {
--ha-dialog-border-radius: var(--ha-space-0);
wa-dialog {
/* Make the container fill the whole screen width and not the safe width */
--full-width: var(--ha-dialog-width-full, 100vw);
--width: var(--full-width);
}
wa-dialog {
/* Make the container fill the whole screen width and not the safe width */
--full-width: var(--ha-dialog-width-full, 100vw);
--width: var(--full-width);
}
wa-dialog::part(dialog) {
/* Make the dialog fill the whole screen height and not the safe height */
min-height: var(--ha-dialog-min-height, 100vh);
min-height: var(--ha-dialog-min-height, 100dvh);
max-height: var(--ha-dialog-max-height, 100vh);
max-height: var(--ha-dialog-max-height, 100dvh);
margin-top: 0;
margin-bottom: 0;
/* Use safe area as padding instead of the container size */
padding-top: var(--safe-area-inset-top);
padding-bottom: var(--safe-area-inset-bottom);
padding-left: var(--safe-area-inset-left);
padding-right: var(--safe-area-inset-right);
/* Reset the transform to center the dialog */
transform: none;
}
wa-dialog::part(dialog) {
/* Make the dialog fill the whole screen height and not the safe height */
min-height: var(--ha-dialog-min-height, 100vh);
min-height: var(--ha-dialog-min-height, 100dvh);
max-height: var(--ha-dialog-max-height, 100vh);
max-height: var(--ha-dialog-max-height, 100dvh);
margin-top: 0;
margin-bottom: 0;
/* Use safe area as padding instead of the container size */
padding-top: var(--safe-area-inset-top);
padding-bottom: var(--safe-area-inset-bottom);
padding-left: var(--safe-area-inset-left);
padding-right: var(--safe-area-inset-right);
/* Reset the transform to center the dialog */
transform: none;
}
}
}
.header-title-container {
display: flex;
align-items: center;
}
.header-title-container {
display: flex;
align-items: center;
}
.header-title {
margin: 0;
margin-bottom: 0;
color: var(--ha-dialog-header-title-color, var(--primary-text-color));
font-size: var(
--ha-dialog-header-title-font-size,
var(--ha-font-size-2xl)
);
line-height: var(
--ha-dialog-header-title-line-height,
var(--ha-line-height-condensed)
);
font-weight: var(
--ha-dialog-header-title-font-weight,
var(--ha-font-weight-normal)
);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: var(--ha-space-3);
}
.header-title {
margin: 0;
margin-bottom: 0;
color: var(--ha-dialog-header-title-color, var(--primary-text-color));
font-size: var(
--ha-dialog-header-title-font-size,
var(--ha-font-size-2xl)
);
line-height: var(
--ha-dialog-header-title-line-height,
var(--ha-line-height-condensed)
);
font-weight: var(
--ha-dialog-header-title-font-weight,
var(--ha-font-weight-normal)
);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: var(--ha-space-3);
}
wa-dialog::part(body) {
padding: 0;
display: flex;
flex-direction: column;
max-width: 100%;
overflow: hidden;
}
wa-dialog::part(body) {
padding: 0;
display: flex;
flex-direction: column;
max-width: 100%;
overflow: hidden;
}
.content-wrapper {
position: relative;
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
}
.body {
position: var(--dialog-content-position, relative);
padding: 0 var(--dialog-content-padding, var(--ha-space-6))
var(--dialog-content-padding, var(--ha-space-6))
var(--dialog-content-padding, var(--ha-space-6));
overflow: auto;
flex-grow: 1;
}
:host([flexcontent]) .body {
max-width: 100%;
flex: 1;
display: flex;
flex-direction: column;
}
.body {
position: var(--dialog-content-position, relative);
padding: var(
--dialog-content-padding,
0 var(--ha-space-6) var(--ha-space-6) var(--ha-space-6)
);
overflow: auto;
flex-grow: 1;
}
:host([flexcontent]) .body {
max-width: 100%;
flex: 1;
display: flex;
flex-direction: column;
}
wa-dialog::part(footer) {
padding: var(--ha-space-0);
}
wa-dialog::part(footer) {
padding: var(--ha-space-0);
}
::slotted([slot="footer"]) {
display: flex;
padding: var(--ha-space-3) var(--ha-space-4) var(--ha-space-4)
var(--ha-space-4);
gap: var(--ha-space-3);
justify-content: flex-end;
align-items: center;
width: 100%;
}
`,
];
}
::slotted([slot="footer"]) {
display: flex;
padding: var(--ha-space-3) var(--ha-space-4) var(--ha-space-4)
var(--ha-space-4);
gap: var(--ha-space-3);
justify-content: flex-end;
align-items: center;
width: 100%;
}
`,
];
}
declare global {

View File

@@ -1,11 +1,10 @@
import { LitElement, html, css } from "lit";
import { customElement, property } from "lit/decorators";
import { property } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import type { HomeAssistant } from "../../types";
import { fireEvent } from "../../common/dom/fire_event";
import "../ha-state-icon";
@customElement("ha-entity-marker")
class HaEntityMarker extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -90,6 +89,8 @@ class HaEntityMarker extends LitElement {
`;
}
customElements.define("ha-entity-marker", HaEntityMarker);
declare global {
interface HTMLElementTagNameMap {
"ha-entity-marker": HaEntityMarker;

View File

@@ -47,7 +47,8 @@ export interface HassioFullBackupCreateParams {
confirm_password?: string;
background?: boolean;
}
export interface HassioPartialBackupCreateParams extends HassioFullBackupCreateParams {
export interface HassioPartialBackupCreateParams
extends HassioFullBackupCreateParams {
folders?: string[];
addons?: string[];
homeassistant?: boolean;

View File

@@ -18,7 +18,8 @@ export const enum LawnMowerEntityFeature {
}
interface LawnMowerEntityAttributes
extends HassEntityAttributeBase, Record<string, any> {}
extends HassEntityAttributeBase,
Record<string, any> {}
export interface LawnMowerEntity extends HassEntityBase {
attributes: LawnMowerEntityAttributes;

View File

@@ -18,7 +18,8 @@ export interface LovelaceSectionConfig extends LovelaceBaseSectionConfig {
cards?: LovelaceCardConfig[];
}
export interface LovelaceStrategySectionConfig extends LovelaceBaseSectionConfig {
export interface LovelaceStrategySectionConfig
extends LovelaceBaseSectionConfig {
strategy: LovelaceStrategyConfig;
}

View File

@@ -11,7 +11,8 @@ export interface LovelaceConfig extends LovelaceDashboardBaseConfig {
views: LovelaceViewRawConfig[];
}
export interface LovelaceDashboardStrategyConfig extends LovelaceDashboardBaseConfig {
export interface LovelaceDashboardStrategyConfig
extends LovelaceDashboardBaseConfig {
strategy: LovelaceStrategyConfig;
}

View File

@@ -29,7 +29,8 @@ export interface LovelaceDashboardMutableParams {
title: string;
}
export interface LovelaceDashboardCreateParams extends LovelaceDashboardMutableParams {
export interface LovelaceDashboardCreateParams
extends LovelaceDashboardMutableParams {
url_path: string;
mode: "storage";
}

View File

@@ -220,12 +220,12 @@ const tryDescribeAction = <T extends ActionType>(
if (config.action) {
const [domain, serviceName] = config.action.split(".", 2);
const descriptionPlaceholders =
hass.services[domain]?.[serviceName]?.description_placeholders;
hass.services[domain][serviceName].description_placeholders;
const service =
hass.localize(
`component.${domain}.services.${serviceName}.name`,
descriptionPlaceholders
) || hass.services[domain]?.[serviceName]?.name;
) || hass.services[domain][serviceName]?.name;
if (config.metadata) {
return hass.localize(

View File

@@ -106,7 +106,8 @@ export interface AutomationTrace extends BaseTrace {
}
export interface AutomationTraceExtended
extends AutomationTrace, BaseTraceExtended {
extends AutomationTrace,
BaseTraceExtended {
config: ManualAutomationConfig;
blueprint_inputs?: BlueprintAutomationConfig;
}

View File

@@ -82,12 +82,6 @@ export interface WeatherEntity extends HassEntityBase {
attributes: WeatherEntityAttributes;
}
export const WEATHER_TEMPERATURE_ATTRIBUTES = new Set<string>([
"temperature",
"apparent_temperature",
"dew_point",
]);
export const weatherSVGs = new Set<string>([
"clear-night",
"cloudy",
@@ -262,15 +256,9 @@ export const getWeatherUnit = (
export const getSecondaryWeatherAttribute = (
hass: HomeAssistant,
stateObj: WeatherEntity,
forecast: ForecastAttribute[],
temperatureFractionDigits?: number
forecast: ForecastAttribute[]
): TemplateResult | undefined => {
const extrema = getWeatherExtrema(
hass,
stateObj,
forecast,
temperatureFractionDigits
);
const extrema = getWeatherExtrema(hass, stateObj, forecast);
if (extrema) {
return extrema;
@@ -310,8 +298,7 @@ export const getSecondaryWeatherAttribute = (
const getWeatherExtrema = (
hass: HomeAssistant,
stateObj: WeatherEntity,
forecast: ForecastAttribute[],
temperatureFractionDigits?: number
forecast: ForecastAttribute[]
): TemplateResult | undefined => {
if (!forecast?.length) {
return undefined;
@@ -326,22 +313,13 @@ const getWeatherExtrema = (
break;
}
if (!tempHigh || fc.temperature > tempHigh) {
tempHigh =
temperatureFractionDigits === undefined
? fc.temperature
: round(fc.temperature, temperatureFractionDigits);
tempHigh = fc.temperature;
}
if (fc.templow !== undefined && (!tempLow || fc.templow < tempLow)) {
tempLow =
temperatureFractionDigits === undefined
? fc.templow
: round(fc.templow, temperatureFractionDigits);
if (!tempLow || (fc.templow && fc.templow < tempLow)) {
tempLow = fc.templow;
}
if (!fc.templow && (!tempLow || fc.temperature < tempLow)) {
tempLow =
temperatureFractionDigits === undefined
? fc.temperature
: round(fc.temperature, temperatureFractionDigits);
tempLow = fc.temperature;
}
}

View File

@@ -32,7 +32,8 @@ export interface PromptDialogParams extends BaseDialogBoxParams {
}
export interface DialogBoxParams
extends ConfirmationDialogParams, PromptDialogParams {
extends ConfirmationDialogParams,
PromptDialogParams {
confirm?: (out?: string) => void;
confirmation?: boolean;
prompt?: boolean;

View File

@@ -19,9 +19,8 @@ declare global {
}
}
export interface HassDialog<
T = HASSDomEvents[ValidHassDomEvent],
> extends HTMLElement {
export interface HassDialog<T = HASSDomEvents[ValidHassDomEvent]>
extends HTMLElement {
showDialog(params: T);
closeDialog?: (historyState?: any) => boolean;
}

View File

@@ -1,5 +1,5 @@
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { property, state } from "lit/decorators";
import { slugify } from "../../../common/string/slugify";
import "../../../components/buttons/ha-progress-button";
import "../../../components/ha-camera-stream";
@@ -9,7 +9,6 @@ import type { HomeAssistant } from "../../../types";
import { fileDownload } from "../../../util/file_download";
import { showToast } from "../../../util/toast";
@customElement("more-info-camera")
class MoreInfoCamera extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -113,6 +112,8 @@ class MoreInfoCamera extends LitElement {
`;
}
customElements.define("more-info-camera", MoreInfoCamera);
declare global {
interface HTMLElementTagNameMap {
"more-info-camera": MoreInfoCamera;

View File

@@ -7,7 +7,7 @@ import {
} from "@mdi/js";
import type { CSSResultGroup, PropertyValues } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { property, state } from "lit/decorators";
import { stopPropagation } from "../../../common/dom/stop_propagation";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-attribute-icon";
@@ -32,7 +32,6 @@ import { moreInfoControlStyle } from "../components/more-info-control-style";
type MainControl = "temperature" | "humidity";
@customElement("more-info-climate")
class MoreInfoClimate extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -568,6 +567,8 @@ class MoreInfoClimate extends LitElement {
}
}
customElements.define("more-info-climate", MoreInfoClimate);
declare global {
interface HTMLElementTagNameMap {
"more-info-climate": MoreInfoClimate;

View File

@@ -1,7 +1,7 @@
import type { HassEntity } from "home-assistant-js-websocket";
import type { CSSResultGroup, PropertyValues } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { property, state } from "lit/decorators";
import { dynamicElement } from "../../../common/dom/dynamic-element-directive";
import type { GroupEntity } from "../../../data/group";
import { computeGroupDomain } from "../../../data/group";
@@ -13,7 +13,6 @@ import {
importMoreInfoControl,
} from "../state_more_info_control";
@customElement("more-info-group")
class MoreInfoGroup extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -107,6 +106,8 @@ class MoreInfoGroup extends LitElement {
}
}
customElements.define("more-info-group", MoreInfoGroup);
declare global {
interface HTMLElementTagNameMap {
"more-info-group": MoreInfoGroup;

View File

@@ -1,7 +1,7 @@
import { mdiPower, mdiTuneVariant } from "@mdi/js";
import type { CSSResultGroup, PropertyValues } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { property, state } from "lit/decorators";
import { stopPropagation } from "../../../common/dom/stop_propagation";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-control-select-menu";
@@ -15,7 +15,6 @@ import type { HomeAssistant } from "../../../types";
import "../components/ha-more-info-control-select-container";
import { moreInfoControlStyle } from "../components/more-info-control-style";
@customElement("more-info-humidifier")
class MoreInfoHumidifier extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -250,6 +249,8 @@ class MoreInfoHumidifier extends LitElement {
}
}
customElements.define("more-info-humidifier", MoreInfoHumidifier);
declare global {
interface HTMLElementTagNameMap {
"more-info-humidifier": MoreInfoHumidifier;

View File

@@ -14,9 +14,8 @@ import {
import type { HassEntity } from "home-assistant-js-websocket";
import type { PropertyValues } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { customElement, property, state } from "lit/decorators";
import { cache } from "lit/directives/cache";
import { classMap } from "lit/directives/class-map";
import { keyed } from "lit/directives/keyed";
import { dynamicElement } from "../../common/dom/dynamic-element-directive";
import { fireEvent } from "../../common/dom/fire_event";
@@ -51,12 +50,7 @@ import { lightSupportsFavoriteColors } from "../../data/light";
import type { ItemType } from "../../data/search";
import { SearchableDomains } from "../../data/search";
import { getSensorNumericDeviceClasses } from "../../data/sensor";
import { ScrollableFadeMixin } from "../../mixins/scrollable-fade-mixin";
import {
haStyleDialog,
haStyleDialogFixedTop,
haStyleScrollbar,
} from "../../resources/styles";
import { haStyleDialog, haStyleDialogFixedTop } from "../../resources/styles";
import "../../state-summary/state-card-content";
import type { HomeAssistant } from "../../types";
import {
@@ -102,15 +96,13 @@ declare global {
const DEFAULT_VIEW: View = "info";
@customElement("ha-more-info-dialog")
export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
export class MoreInfoDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean, reflect: true }) public large = false;
@state() private _parentEntityIds: string[] = [];
@query(".content") private _contentElement?: HTMLDivElement;
@state() private _entityId?: string | null;
@state() private _data?: Record<string, any>;
@@ -129,12 +121,6 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
@state() private _sensorNumericDeviceClasses?: string[] = [];
protected scrollFadeThreshold = 24;
protected get scrollableElement(): HTMLElement | null {
return this._contentElement || null;
}
public showDialog(params: MoreInfoDialogParams) {
this._entityId = params.entityId;
if (!this._entityId) {
@@ -608,86 +594,78 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
`
: nothing}
</ha-dialog-header>
<div
class=${classMap({
"content-wrapper": true,
"settings-view": this._currView === "settings",
})}
>
${keyed(
this._entityId,
html`
<div
class="content ha-scrollbar"
tabindex="-1"
dialogInitialFocus
@show-child-view=${this._showChildView}
@entity-entry-updated=${this._entryUpdated}
@toggle-edit-mode=${this._handleToggleInfoEditModeEvent}
@hass-more-info=${this._handleMoreInfoEvent}
>
${cache(
this._childView
${keyed(
this._entityId,
html`
<div
class="content"
tabindex="-1"
dialogInitialFocus
@show-child-view=${this._showChildView}
@entity-entry-updated=${this._entryUpdated}
@toggle-edit-mode=${this._handleToggleInfoEditModeEvent}
@hass-more-info=${this._handleMoreInfoEvent}
>
${cache(
this._childView
? html`
<div class="child-view">
${dynamicElement(this._childView.viewTag, {
hass: this.hass,
entry: this._entry,
params: this._childView.viewParams,
})}
</div>
`
: this._currView === "info"
? html`
<div class="child-view">
${dynamicElement(this._childView.viewTag, {
hass: this.hass,
entry: this._entry,
params: this._childView.viewParams,
})}
</div>
<ha-more-info-info
dialogInitialFocus
.hass=${this.hass}
.entityId=${this._entityId}
.entry=${this._entry}
.editMode=${this._infoEditMode}
.data=${this._data}
></ha-more-info-info>
`
: this._currView === "info"
: this._currView === "history"
? html`
<ha-more-info-info
dialogInitialFocus
<ha-more-info-history-and-logbook
.hass=${this.hass}
.entityId=${this._entityId}
.entry=${this._entry}
.editMode=${this._infoEditMode}
.data=${this._data}
></ha-more-info-info>
></ha-more-info-history-and-logbook>
`
: this._currView === "history"
: this._currView === "settings"
? html`
<ha-more-info-history-and-logbook
<ha-more-info-settings
.hass=${this.hass}
.entityId=${this._entityId}
></ha-more-info-history-and-logbook>
.entry=${this._entry}
></ha-more-info-settings>
`
: this._currView === "settings"
: this._currView === "related"
? html`
<ha-more-info-settings
<ha-related-items
.hass=${this.hass}
.entityId=${this._entityId}
.entry=${this._entry}
></ha-more-info-settings>
.itemId=${entityId}
.itemType=${SearchableDomains.has(domain)
? (domain as ItemType)
: "entity"}
></ha-related-items>
`
: this._currView === "related"
: this._currView === "add_to"
? html`
<ha-related-items
<ha-more-info-add-to
.hass=${this.hass}
.itemId=${entityId}
.itemType=${SearchableDomains.has(domain)
? (domain as ItemType)
: "entity"}
></ha-related-items>
.entityId=${entityId}
@add-to-action-selected=${this._goBack}
></ha-more-info-add-to>
`
: this._currView === "add_to"
? html`
<ha-more-info-add-to
.hass=${this.hass}
.entityId=${entityId}
@add-to-action-selected=${this._goBack}
></ha-more-info-add-to>
`
: nothing
)}
</div>
`
)}
${this.renderScrollableFades()}
</div>
: nothing
)}
</div>
`
)}
</ha-dialog>
`;
}
@@ -742,31 +720,18 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
static get styles() {
return [
...super.styles,
haStyleDialog,
haStyleDialogFixedTop,
haStyleScrollbar,
css`
ha-dialog {
--dialog-content-padding: 0;
}
.content-wrapper {
flex: 1 1 auto;
min-height: 0;
position: relative;
.content {
display: flex;
flex-direction: column;
}
.content {
outline: none;
flex: 1;
overflow: auto;
}
.content-wrapper.settings-view .fade-bottom {
bottom: var(--ha-space-18);
}
.child-view {

View File

@@ -15,7 +15,7 @@ import "../../components/ha-alert";
import "../../components/ha-expansion-panel";
import "../../components/ha-fade-in";
import "../../components/ha-icon-next";
import "../../components/ha-adaptive-dialog";
import "../../components/ha-wa-dialog";
import "../../components/ha-md-list";
import "../../components/ha-md-list-item";
import "../../components/ha-spinner";
@@ -109,7 +109,7 @@ class DialogRestart extends LitElement {
const dialogTitle = this.hass.localize("ui.dialogs.restart.heading");
return html`
<ha-adaptive-dialog
<ha-wa-dialog
.hass=${this.hass}
.open=${this._dialogOpen}
header-title=${dialogTitle}
@@ -257,7 +257,7 @@ class DialogRestart extends LitElement {
</ha-expansion-panel>
`}
</div>
</ha-adaptive-dialog>
</ha-wa-dialog>
`;
}
@@ -405,7 +405,7 @@ class DialogRestart extends LitElement {
haStyle,
haStyleDialog,
css`
ha-adaptive-dialog {
ha-wa-dialog {
--dialog-content-padding: 0;
}

View File

@@ -1,12 +1,11 @@
import "@material/mwc-linear-progress/mwc-linear-progress";
import { mdiDotsVertical, mdiRestart } from "@mdi/js";
import { css, html, LitElement, type TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { mdiClose, mdiDotsVertical, mdiRestart } from "@mdi/js";
import { css, html, LitElement, nothing, type TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../common/dom/fire_event";
import "../../components/ha-alert";
import "../../components/ha-button";
import "../../components/ha-dialog-footer";
import "../../components/ha-dialog-header";
import "../../components/ha-fade-in";
import "../../components/ha-icon-button";
import "../../components/ha-items-display-editor";
@@ -15,8 +14,9 @@ import type {
DisplayValue,
} from "../../components/ha-items-display-editor";
import "../../components/ha-md-button-menu";
import "../../components/ha-md-dialog";
import type { HaMdDialog } from "../../components/ha-md-dialog";
import "../../components/ha-md-menu-item";
import "../../components/ha-wa-dialog";
import { computePanels } from "../../components/ha-sidebar";
import "../../components/ha-spinner";
import "../../components/ha-svg-icon";
@@ -40,6 +40,8 @@ class DialogEditSidebar extends LitElement {
@state() private _open = false;
@query("ha-md-dialog") private _dialog?: HaMdDialog;
@state() private _order?: string[];
@state() private _hidden?: string[];
@@ -53,6 +55,7 @@ class DialogEditSidebar extends LitElement {
public async showDialog(): Promise<void> {
this._open = true;
this._getData();
}
@@ -84,7 +87,7 @@ class DialogEditSidebar extends LitElement {
}
public closeDialog(): void {
this._open = false;
this._dialog?.close();
}
private _panels = memoizeOne((panels: HomeAssistant["panels"]) =>
@@ -164,52 +167,57 @@ class DialogEditSidebar extends LitElement {
}
protected render() {
if (!this._open) {
return nothing;
}
const dialogTitle = this.hass.localize("ui.sidebar.edit_sidebar");
return html`
<ha-wa-dialog
.hass=${this.hass}
.open=${this._open}
header-title=${dialogTitle}
header-subtitle=${!this._migrateToUserData
? this.hass.localize("ui.sidebar.edit_subtitle")
: ""}
@closed=${this._dialogClosed}
>
<ha-md-button-menu
slot="headerActionItems"
positioning="popover"
anchor-corner="end-end"
menu-corner="start-end"
>
<ha-md-dialog open @closed=${this._dialogClosed}>
<ha-dialog-header slot="headline">
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-md-menu-item .clickAction=${this._resetToDefaults}>
<ha-svg-icon slot="start" .path=${mdiRestart}></ha-svg-icon>
${this.hass.localize("ui.sidebar.reset_to_defaults")}
</ha-md-menu-item>
</ha-md-button-menu>
<div class="content">${this._renderContent()}</div>
<ha-dialog-footer slot="footer">
<ha-button
slot="secondaryAction"
appearance="plain"
slot="navigationIcon"
.label=${this.hass.localize("ui.common.close") ?? "Close"}
.path=${mdiClose}
@click=${this.closeDialog}
></ha-icon-button>
<span slot="title" .title=${dialogTitle}>${dialogTitle}</span>
${!this._migrateToUserData
? html`<span slot="subtitle"
>${this.hass.localize("ui.sidebar.edit_subtitle")}</span
>`
: nothing}
<ha-md-button-menu
slot="actionItems"
positioning="popover"
anchor-corner="end-end"
menu-corner="start-end"
>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-md-menu-item .clickAction=${this._resetToDefaults}>
<ha-svg-icon slot="start" .path=${mdiRestart}></ha-svg-icon>
${this.hass.localize("ui.sidebar.reset_to_defaults")}
</ha-md-menu-item>
</ha-md-button-menu>
</ha-dialog-header>
<div slot="content" class="content">${this._renderContent()}</div>
<div slot="actions">
<ha-button appearance="plain" @click=${this.closeDialog}>
${this.hass.localize("ui.common.cancel")}
</ha-button>
<ha-button
slot="primaryAction"
.disabled=${!this._order || !this._hidden}
@click=${this._save}
>
${this.hass.localize("ui.common.save")}
</ha-button>
</ha-dialog-footer>
</ha-wa-dialog>
</div>
</ha-md-dialog>
`;
}
@@ -264,13 +272,15 @@ class DialogEditSidebar extends LitElement {
}
static styles = css`
ha-wa-dialog {
ha-md-dialog {
min-width: 600px;
max-height: 90%;
--dialog-content-padding: var(--ha-space-2) var(--ha-space-6);
--dialog-content-padding: 8px 24px;
}
@media all and (max-width: 580px), all and (max-height: 500px) {
ha-wa-dialog {
@media all and (max-width: 600px), all and (max-height: 500px) {
ha-md-dialog {
--md-dialog-container-shape: 0;
min-width: 100%;
min-height: 100%;
}

View File

@@ -1,10 +1,9 @@
import type { PropertyValues } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { property, state } from "lit/decorators";
import "../components/ha-spinner";
import "../components/ha-button";
@customElement("ha-init-page")
class HaInitPage extends LitElement {
@property({ type: Boolean }) public error = false;
@@ -121,6 +120,8 @@ class HaInitPage extends LitElement {
`;
}
customElements.define("ha-init-page", HaInitPage);
declare global {
interface HTMLElementTagNameMap {
"ha-init-page": HaInitPage;

View File

@@ -128,8 +128,6 @@ class HassSubpage extends LitElement {
ha-menu-button,
ha-icon-button-arrow-prev,
::slotted([slot="toolbar-icon"]) {
display: flex;
align-items: center;
pointer-events: auto;
color: var(--sidebar-icon-color);
}

View File

@@ -621,9 +621,9 @@ export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) {
} else if (this._sortDirection === "asc") {
this._sortDirection = "desc";
} else {
this._sortDirection = "asc";
this._sortDirection = null;
}
this._sortColumn = columnId;
this._sortColumn = this._sortDirection === null ? undefined : columnId;
fireEvent(this, "sorting-changed", {
column: columnId,

View File

@@ -13,7 +13,6 @@ import "../components/ha-svg-icon";
import "../components/ha-tab";
import { haStyleScrollbar } from "../resources/styles";
import type { HomeAssistant, Route } from "../types";
import { withViewTransition } from "../common/util/view-transition";
export interface PageNavigation {
path: string;
@@ -113,11 +112,9 @@ class HassTabsSubpage extends LitElement {
public willUpdate(changedProperties: PropertyValues) {
if (changedProperties.has("route")) {
withViewTransition(() => {
this._activeTab = this.tabs.find((tab) =>
`${this.route.prefix}${this.route.path}`.includes(tab.path)
);
});
this._activeTab = this.tabs.find((tab) =>
`${this.route.prefix}${this.route.path}`.includes(tab.path)
);
}
super.willUpdate(changedProperties);
}

View File

@@ -1,6 +1,6 @@
import { mdiClose } from "@mdi/js";
import { html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { property, query, state } from "lit/decorators";
import type { LocalizeKeys } from "../common/translations/localize";
import "../components/ha-button";
import "../components/ha-icon-button";
@@ -26,7 +26,6 @@ export interface ToastActionParams {
| { translationKey: LocalizeKeys; args?: Record<string, string> };
}
@customElement("notification-manager")
class NotificationManager extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -116,6 +115,8 @@ class NotificationManager extends LitElement {
}
}
customElements.define("notification-manager", NotificationManager);
declare global {
interface HTMLElementTagNameMap {
"notification-manager": NotificationManager;

View File

@@ -13,27 +13,19 @@ import type { Constructor } from "../types";
const stylesArray = (styles?: CSSResultGroup | CSSResultGroup[]) =>
styles === undefined ? [] : Array.isArray(styles) ? styles : [styles];
/**
* Mixin that adds top and bottom fade overlays for scrollable content.
* @param superClass - The LitElement class to extend.
* @returns Extended class with scrollable fade functionality.
*/
export const ScrollableFadeMixin = <T extends Constructor<LitElement>>(
superClass: T
) => {
class ScrollableFadeClass extends superClass {
/** Whether content has scrolled past the threshold. Controls top fade visibility. */
@state() protected _contentScrolled = false;
/** Whether content extends beyond the viewport. Controls bottom fade visibility. */
@state() protected _contentScrollable = false;
private _scrollTarget?: HTMLElement | null;
private _onScroll = (ev: Event) => {
const target = ev.currentTarget as HTMLElement;
this._contentScrolled =
(target.scrollTop ?? 0) > this.scrollFadeThreshold;
this._contentScrolled = (target.scrollTop ?? 0) > 0;
this._updateScrollableState(target);
};
@@ -47,26 +39,15 @@ export const ScrollableFadeMixin = <T extends Constructor<LitElement>>(
},
});
/**
* Safe area padding in pixels for the scrollable element.
*/
protected scrollFadeSafeAreaPadding = 16;
private static readonly DEFAULT_SAFE_AREA_PADDING = 16;
/**
* Scroll threshold in pixels for showing the fades.
*/
protected scrollFadeThreshold = 4;
/**
* Default scrollable element value.
*/
private static readonly DEFAULT_SCROLLABLE_ELEMENT: HTMLElement | null =
null;
/**
* Element to observe for scroll and resize events. Override with a getter to specify target.
* Kept as a getter to allow subclasses to return query results.
*/
protected get scrollFadeSafeAreaPadding() {
return ScrollableFadeClass.DEFAULT_SAFE_AREA_PADDING;
}
protected get scrollableElement(): HTMLElement | null {
return ScrollableFadeClass.DEFAULT_SCROLLABLE_ELEMENT;
}
@@ -86,11 +67,6 @@ export const ScrollableFadeMixin = <T extends Constructor<LitElement>>(
super.disconnectedCallback();
}
/**
* Renders top and bottom fade overlays. Call in render method.
* @param rounded - Whether to apply rounded corners.
* @returns Template containing fade elements.
*/
protected renderScrollableFades(rounded = false): TemplateResult {
return html`
<div
@@ -134,6 +110,7 @@ export const ScrollableFadeMixin = <T extends Constructor<LitElement>>(
transparent
);
border-radius: var(--ha-border-radius-square);
z-index: 100;
opacity: 0;
}
.fade-top {

View File

@@ -90,7 +90,9 @@ class OnboardingRestoreBackupCloudLogin extends LitElement {
this._email = this._cloudLoginElement.emailField.value;
}
await import("../../panels/config/cloud/forgot-password/cloud-forgot-password-card");
await import(
"../../panels/config/cloud/forgot-password/cloud-forgot-password-card"
);
this._view = "forgot-password";
}

View File

@@ -3,7 +3,7 @@ import { TZDate } from "@date-fns/tz";
import { addDays, isSameDay } from "date-fns";
import type { CSSResultGroup } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { property, state } from "lit/decorators";
import { formatDate } from "../../common/datetime/format_date";
import { formatDateTime } from "../../common/datetime/format_date_time";
import { formatTime } from "../../common/datetime/format_time";
@@ -26,7 +26,6 @@ import type { CalendarEventDetailDialogParams } from "./show-dialog-calendar-eve
import { showCalendarEventEditDialog } from "./show-dialog-calendar-event-editor";
import { resolveTimeZone } from "../../common/datetime/resolve-time-zone";
@customElement("dialog-calendar-event-detail")
class DialogCalendarEventDetail extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -272,3 +271,8 @@ declare global {
"dialog-calendar-event-detail": DialogCalendarEventDetail;
}
}
customElements.define(
"dialog-calendar-event-detail",
DialogCalendarEventDetail
);

View File

@@ -1,7 +1,7 @@
import type { HassEntity } from "home-assistant-js-websocket";
import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { property, state } from "lit/decorators";
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/entity/ha-entity-picker";
import type { HaEntityPicker } from "../../../components/entity/ha-entity-picker";
@@ -40,7 +40,6 @@ const SENSOR_DOMAINS = ["sensor"];
const TEMPERATURE_DEVICE_CLASSES = [SENSOR_DEVICE_CLASS_TEMPERATURE];
const HUMIDITY_DEVICE_CLASSES = [SENSOR_DEVICE_CLASS_HUMIDITY];
@customElement("dialog-area-registry-detail")
class DialogAreaDetail extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -427,3 +426,5 @@ declare global {
"dialog-area-registry-detail": DialogAreaDetail;
}
}
customElements.define("dialog-area-registry-detail", DialogAreaDetail);

View File

@@ -1,7 +1,7 @@
import { mdiTextureBox } from "@mdi/js";
import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { property, state } from "lit/decorators";
import { repeat } from "lit/directives/repeat";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../common/dom/fire_event";
@@ -27,7 +27,6 @@ import type { HomeAssistant } from "../../../types";
import { showAreaRegistryDetailDialog } from "./show-dialog-area-registry-detail";
import type { FloorRegistryDetailDialogParams } from "./show-dialog-floor-registry-detail";
@customElement("dialog-floor-registry-detail")
class DialogFloorDetail extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -362,3 +361,5 @@ declare global {
"dialog-floor-registry-detail": DialogFloorDetail;
}
}
customElements.define("dialog-floor-registry-detail", DialogFloorDetail);

View File

@@ -1,4 +1,3 @@
import "@home-assistant/webawesome/dist/components/divider/divider";
import { consume } from "@lit/context";
import {
mdiAlertCircleCheck,
@@ -33,11 +32,11 @@ import { copyToClipboard } from "../../../../common/util/copy-clipboard";
import "../../../../components/ha-automation-row";
import type { HaAutomationRow } from "../../../../components/ha-automation-row";
import "../../../../components/ha-card";
import "../../../../components/ha-dropdown";
import "../../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
import "../../../../components/ha-expansion-panel";
import "../../../../components/ha-icon-button";
import "../../../../components/ha-md-button-menu";
import "../../../../components/ha-md-divider";
import "../../../../components/ha-md-menu-item";
import "../../../../components/ha-service-icon";
import "../../../../components/ha-tooltip";
import {
@@ -289,12 +288,15 @@ export default class HaAutomationActionRow extends LitElement {
</ha-tooltip>`
: nothing}
<ha-dropdown
<ha-md-button-menu
quick
slot="icons"
@click=${preventDefaultStopPropagation}
@keydown=${stopPropagation}
@wa-select=${this._handleDropdownSelect}
placement="bottom-end"
@closed=${stopPropagation}
positioning="fixed"
anchor-corner="end-end"
menu-corner="start-end"
>
<ha-icon-button
slot="trigger"
@@ -302,24 +304,30 @@ export default class HaAutomationActionRow extends LitElement {
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-dropdown-item value="run">
<ha-svg-icon slot="icon" .path=${mdiPlay}></ha-svg-icon>
<ha-md-menu-item .clickAction=${this._runAction}>
<ha-svg-icon slot="start" .path=${mdiPlay}></ha-svg-icon>
${this._renderOverflowLabel(
this.hass.localize("ui.panel.config.automation.editor.actions.run")
)}
</ha-dropdown-item>
<ha-dropdown-item value="rename" .disabled=${this.disabled}>
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
</ha-md-menu-item>
<ha-md-menu-item
.clickAction=${this._renameAction}
.disabled=${this.disabled}
>
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
${this._renderOverflowLabel(
this.hass.localize(
"ui.panel.config.automation.editor.triggers.rename"
)
)}
</ha-dropdown-item>
<wa-divider></wa-divider>
<ha-dropdown-item value="duplicate" .disabled=${this.disabled}>
</ha-md-menu-item>
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
<ha-md-menu-item
.clickAction=${this._duplicateAction}
.disabled=${this.disabled}
>
<ha-svg-icon
slot="icon"
slot="start"
.path=${mdiPlusCircleMultipleOutline}
></ha-svg-icon>
@@ -328,10 +336,13 @@ export default class HaAutomationActionRow extends LitElement {
"ui.panel.config.automation.editor.actions.duplicate"
)
)}
</ha-dropdown-item>
</ha-md-menu-item>
<ha-dropdown-item value="copy" .disabled=${this.disabled}>
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon>
<ha-md-menu-item
.clickAction=${this._copyAction}
.disabled=${this.disabled}
>
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
${this._renderOverflowLabel(
this.hass.localize(
"ui.panel.config.automation.editor.triggers.copy"
@@ -340,6 +351,7 @@ export default class HaAutomationActionRow extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -350,10 +362,13 @@ export default class HaAutomationActionRow extends LitElement {
<span>C</span>
</span>`
)}
</ha-dropdown-item>
</ha-md-menu-item>
<ha-dropdown-item value="cut" .disabled=${this.disabled}>
<ha-svg-icon slot="icon" .path=${mdiContentCut}></ha-svg-icon>
<ha-md-menu-item
.clickAction=${this._cutAction}
.disabled=${this.disabled}
>
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
${this._renderOverflowLabel(
this.hass.localize(
"ui.panel.config.automation.editor.triggers.cut"
@@ -362,6 +377,7 @@ export default class HaAutomationActionRow extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -372,48 +388,51 @@ export default class HaAutomationActionRow extends LitElement {
<span>X</span>
</span>`
)}
</ha-dropdown-item>
</ha-md-menu-item>
${!this.optionsInSidebar
? html`
<ha-dropdown-item
value="move_up"
<ha-md-menu-item
.clickAction=${this._moveUp}
.disabled=${this.disabled || !!this.first}
>
${this.hass.localize(
"ui.panel.config.automation.editor.move_up"
)}
<ha-svg-icon slot="icon" .path=${mdiArrowUp}></ha-svg-icon
></ha-dropdown-item>
<ha-dropdown-item
value="move_down"
<ha-svg-icon slot="start" .path=${mdiArrowUp}></ha-svg-icon
></ha-md-menu-item>
<ha-md-menu-item
.clickAction=${this._moveDown}
.disabled=${this.disabled || !!this.last}
>
${this.hass.localize(
"ui.panel.config.automation.editor.move_down"
)}
<ha-svg-icon slot="icon" .path=${mdiArrowDown}></ha-svg-icon
></ha-dropdown-item>
<ha-svg-icon slot="start" .path=${mdiArrowDown}></ha-svg-icon
></ha-md-menu-item>
`
: nothing}
<ha-dropdown-item
value="toggle_yaml_mode"
<ha-md-menu-item
.clickAction=${this._toggleYamlMode}
.disabled=${!this._uiModeAvailable || !!this._warnings}
>
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
${this._renderOverflowLabel(
this.hass.localize(
`ui.panel.config.automation.editor.edit_${!this._yamlMode ? "yaml" : "ui"}`
)
)}
</ha-dropdown-item>
</ha-md-menu-item>
<wa-divider></wa-divider>
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
<ha-dropdown-item value="disable" .disabled=${this.disabled}>
<ha-md-menu-item
.clickAction=${this._onDisable}
.disabled=${this.disabled}
>
<ha-svg-icon
slot="icon"
slot="start"
.path=${this.action.enabled === false
? mdiPlayCircleOutline
: mdiStopCircleOutline}
@@ -424,15 +443,15 @@ export default class HaAutomationActionRow extends LitElement {
`ui.panel.config.automation.editor.actions.${this.action.enabled === false ? "enable" : "disable"}`
)
)}
</ha-dropdown-item>
<ha-dropdown-item
value="delete"
variant="danger"
</ha-md-menu-item>
<ha-md-menu-item
class="warning"
.clickAction=${this._onDelete}
.disabled=${this.disabled}
>
<ha-svg-icon
class="warning"
slot="icon"
slot="start"
.path=${mdiDelete}
></ha-svg-icon>
@@ -444,6 +463,7 @@ export default class HaAutomationActionRow extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -458,8 +478,8 @@ export default class HaAutomationActionRow extends LitElement {
>
</span>`
)}
</ha-dropdown-item>
</ha-dropdown>
</ha-md-menu-item>
</ha-md-button-menu>
${!this.optionsInSidebar
? html`${this._warnings
@@ -870,47 +890,6 @@ export default class HaAutomationActionRow extends LitElement {
this._automationRowElement?.focus();
}
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
return;
}
switch (action) {
case "run":
this._runAction();
break;
case "rename":
this._renameAction();
break;
case "duplicate":
this._duplicateAction();
break;
case "copy":
this._copyAction();
break;
case "cut":
this._cutAction();
break;
case "move_up":
this._moveUp();
break;
case "move_down":
this._moveDown();
break;
case "toggle_yaml_mode":
this._toggleYamlMode(ev.target as HTMLElement);
break;
case "disable":
this._onDisable();
break;
case "delete":
this._onDelete();
break;
}
}
static styles = [rowStyles, overflowStyles];
}

View File

@@ -64,15 +64,8 @@ export class HaStopAction extends LitElement implements ActionElement {
private _responseChanged(ev: Event) {
ev.stopPropagation();
const newAction = { ...this.action };
const newValue = (ev.target as any).value;
if (newValue) {
newAction.response_variable = newValue;
} else {
delete newAction.response_variable;
}
fireEvent(this, "value-changed", {
value: newAction,
value: { ...this.action, response_variable: (ev.target as any).value },
});
}

View File

@@ -1,4 +1,3 @@
import "@home-assistant/webawesome/dist/components/divider/divider";
import { consume } from "@lit/context";
import {
mdiAppleKeyboardCommand,
@@ -34,11 +33,11 @@ import "../../../../components/ha-automation-row";
import type { HaAutomationRow } from "../../../../components/ha-automation-row";
import "../../../../components/ha-card";
import "../../../../components/ha-condition-icon";
import "../../../../components/ha-dropdown";
import "../../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
import "../../../../components/ha-expansion-panel";
import "../../../../components/ha-icon-button";
import "../../../../components/ha-md-button-menu";
import "../../../../components/ha-md-divider";
import "../../../../components/ha-md-menu-item";
import type {
AutomationClipboard,
Condition,
@@ -195,12 +194,15 @@ export default class HaAutomationConditionRow extends LitElement {
<slot name="icons" slot="icons"></slot>
<ha-dropdown
<ha-md-button-menu
quick
slot="icons"
@click=${preventDefaultStopPropagation}
@keydown=${stopPropagation}
@wa-select=${this._handleDropdownSelect}
placement="bottom-end"
@closed=${stopPropagation}
positioning="fixed"
anchor-corner="end-end"
menu-corner="start-end"
>
<ha-icon-button
slot="trigger"
@@ -209,28 +211,34 @@ export default class HaAutomationConditionRow extends LitElement {
>
</ha-icon-button>
<ha-dropdown-item value="test">
<ha-svg-icon slot="icon" .path=${mdiFlask}></ha-svg-icon>
<ha-md-menu-item .clickAction=${this._testCondition}>
<ha-svg-icon slot="start" .path=${mdiFlask}></ha-svg-icon>
${this._renderOverflowLabel(
this.hass.localize(
"ui.panel.config.automation.editor.conditions.test"
)
)}
</ha-dropdown-item>
<ha-dropdown-item value="rename" .disabled=${this.disabled}>
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
</ha-md-menu-item>
<ha-md-menu-item
.clickAction=${this._renameCondition}
.disabled=${this.disabled}
>
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
${this._renderOverflowLabel(
this.hass.localize(
"ui.panel.config.automation.editor.conditions.rename"
)
)}
</ha-dropdown-item>
</ha-md-menu-item>
<wa-divider></wa-divider>
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
<ha-dropdown-item value="duplicate" .disabled=${this.disabled}>
<ha-md-menu-item
.clickAction=${this._duplicateCondition}
.disabled=${this.disabled}
>
<ha-svg-icon
slot="icon"
slot="start"
.path=${mdiPlusCircleMultipleOutline}
></ha-svg-icon>
${this._renderOverflowLabel(
@@ -238,10 +246,13 @@ export default class HaAutomationConditionRow extends LitElement {
"ui.panel.config.automation.editor.actions.duplicate"
)
)}
</ha-dropdown-item>
</ha-md-menu-item>
<ha-dropdown-item value="copy" .disabled=${this.disabled}>
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon
<ha-md-menu-item
.clickAction=${this._copyCondition}
.disabled=${this.disabled}
>
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon
>${this._renderOverflowLabel(
this.hass.localize(
"ui.panel.config.automation.editor.triggers.copy"
@@ -250,6 +261,7 @@ export default class HaAutomationConditionRow extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -260,10 +272,13 @@ export default class HaAutomationConditionRow extends LitElement {
<span>C</span>
</span>`
)}
</ha-dropdown-item>
</ha-md-menu-item>
<ha-dropdown-item value="cut" .disabled=${this.disabled}>
<ha-svg-icon slot="icon" .path=${mdiContentCut}></ha-svg-icon
<ha-md-menu-item
.clickAction=${this._cutCondition}
.disabled=${this.disabled}
>
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon
>${this._renderOverflowLabel(
this.hass.localize(
"ui.panel.config.automation.editor.triggers.cut"
@@ -272,6 +287,7 @@ export default class HaAutomationConditionRow extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -282,45 +298,48 @@ export default class HaAutomationConditionRow extends LitElement {
<span>X</span>
</span>`
)}
</ha-dropdown-item>
</ha-md-menu-item>
${!this.optionsInSidebar
? html`
<ha-dropdown-item
value="move_up"
.disabled=${this.disabled || !!this.first}
<ha-md-menu-item
.clickAction=${this._moveUp}
.disabled=${this.disabled || this.first}
>
${this.hass.localize(
"ui.panel.config.automation.editor.move_up"
)}
<ha-svg-icon slot="icon" .path=${mdiArrowUp}></ha-svg-icon
></ha-dropdown-item>
<ha-dropdown-item
value="move_down"
.disabled=${this.disabled || !!this.last}
<ha-svg-icon slot="start" .path=${mdiArrowUp}></ha-svg-icon
></ha-md-menu-item>
<ha-md-menu-item
.clickAction=${this._moveDown}
.disabled=${this.disabled || this.last}
>
${this.hass.localize(
"ui.panel.config.automation.editor.move_down"
)}
<ha-svg-icon slot="icon" .path=${mdiArrowDown}></ha-svg-icon
></ha-dropdown-item>
<ha-svg-icon slot="start" .path=${mdiArrowDown}></ha-svg-icon
></ha-md-menu-item>
`
: nothing}
<ha-dropdown-item value="toggle_yaml_mode">
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
<ha-md-menu-item .clickAction=${this._toggleYamlMode}>
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
${this._renderOverflowLabel(
this.hass.localize(
`ui.panel.config.automation.editor.edit_${!this._yamlMode ? "yaml" : "ui"}`
)
)}
</ha-dropdown-item>
</ha-md-menu-item>
<wa-divider></wa-divider>
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
<ha-dropdown-item value="disable" .disabled=${this.disabled}>
<ha-md-menu-item
.clickAction=${this._onDisable}
.disabled=${this.disabled}
>
<ha-svg-icon
slot="icon"
slot="start"
.path=${this.condition.enabled === false
? mdiPlayCircleOutline
: mdiStopCircleOutline}
@@ -331,15 +350,15 @@ export default class HaAutomationConditionRow extends LitElement {
`ui.panel.config.automation.editor.actions.${this.condition.enabled === false ? "enable" : "disable"}`
)
)}
</ha-dropdown-item>
<ha-dropdown-item
variant="danger"
value="delete"
</ha-md-menu-item>
<ha-md-menu-item
class="warning"
.clickAction=${this._onDelete}
.disabled=${this.disabled}
>
<ha-svg-icon
class="warning"
slot="icon"
slot="start"
.path=${mdiDelete}
></ha-svg-icon>
${this._renderOverflowLabel(
@@ -350,6 +369,7 @@ export default class HaAutomationConditionRow extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -364,8 +384,8 @@ export default class HaAutomationConditionRow extends LitElement {
>
</span>`
)}
</ha-dropdown-item>
</ha-dropdown>
</ha-md-menu-item>
</ha-md-button-menu>
${!this.optionsInSidebar
? html`${this._warnings
? html`<ha-automation-editor-warning
@@ -817,47 +837,6 @@ export default class HaAutomationConditionRow extends LitElement {
this._automationRowElement?.focus();
}
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
return;
}
switch (action) {
case "test":
this._testCondition();
break;
case "rename":
this._renameCondition();
break;
case "duplicate":
this._duplicateCondition();
break;
case "copy":
this._copyCondition();
break;
case "cut":
this._cutCondition();
break;
case "move_up":
this._moveUp();
break;
case "move_down":
this._moveDown();
break;
case "toggle_yaml_mode":
this._toggleYamlMode(ev.target as HTMLElement);
break;
case "disable":
this._onDisable();
break;
case "delete":
this._onDelete();
break;
}
}
static get styles(): CSSResultGroup {
return [
rowStyles,

View File

@@ -124,9 +124,9 @@ export class HaPlatformCondition extends LitElement {
const hasOptional = Boolean(
conditionDesc?.fields &&
Object.values(conditionDesc.fields).some((field) =>
showOptionalToggle(field)
)
Object.values(conditionDesc.fields).some((field) =>
showOptionalToggle(field)
)
);
return html`

View File

@@ -1,4 +1,3 @@
import "@home-assistant/webawesome/dist/components/divider/divider";
import { consume } from "@lit/context";
import {
mdiAppleKeyboardCommand,
@@ -24,22 +23,20 @@ import {
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { UndoRedoController } from "../../../common/controllers/undo-redo-controller";
import { transform } from "../../../common/decorators/transform";
import { fireEvent } from "../../../common/dom/fire_event";
import { goBack, navigate } from "../../../common/navigate";
import { promiseTimeout } from "../../../common/util/promise-timeout";
import { afterNextRender } from "../../../common/util/render-status";
import "../../../components/ha-button";
import "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
import "../../../components/ha-button-menu";
import "../../../components/ha-fab";
import "../../../components/ha-fade-in";
import "../../../components/ha-icon";
import "../../../components/ha-icon-button";
import "../../../components/ha-list-item";
import "../../../components/ha-spinner";
import "../../../components/ha-svg-icon";
import "../../../components/ha-yaml-editor";
@@ -77,6 +74,7 @@ import { showMoreInfoDialog } from "../../../dialogs/more-info/show-ha-more-info
import "../../../layouts/hass-subpage";
import { KeyboardShortcutMixin } from "../../../mixins/keyboard-shortcut-mixin";
import { PreventUnsavedMixin } from "../../../mixins/prevent-unsaved-mixin";
import { UndoRedoController } from "../../../common/controllers/undo-redo-controller";
import { haStyle } from "../../../resources/styles";
import type { Entries, HomeAssistant, Route } from "../../../types";
import { isMac } from "../../../util/is_mac";
@@ -88,10 +86,10 @@ import {
type EntityRegistryUpdate,
showAutomationSaveDialog,
} from "./automation-save-dialog/show-dialog-automation-save";
import { showAutomationSaveTimeoutDialog } from "./automation-save-timeout-dialog/show-dialog-automation-save-timeout";
import "./blueprint-automation-editor";
import "./manual-automation-editor";
import type { HaManualAutomationEditor } from "./manual-automation-editor";
import { showAutomationSaveTimeoutDialog } from "./automation-save-timeout-dialog/show-dialog-automation-save-timeout";
declare global {
interface HTMLElementTagNameMap {
@@ -114,7 +112,6 @@ declare global {
}
}
@customElement("ha-automation-editor")
export class HaAutomationEditor extends PreventUnsavedMixin(
KeyboardShortcutMixin(LitElement)
) {
@@ -294,10 +291,7 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
</ha-button>
`
: ""}
<ha-dropdown
slot="toolbar-icon"
@wa-select=${this._handleDropdownSelect}
>
<ha-button-menu slot="toolbar-icon">
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
@@ -305,73 +299,99 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
></ha-icon-button>
${this._mode === "gui" && this.narrow
? html`<ha-dropdown-item
value="undo"
? html`<ha-list-item
graphic="icon"
@click=${this._undo}
.disabled=${!this._undoRedoController.canUndo}
>
${this.hass.localize("ui.common.undo")}
<ha-svg-icon slot="icon" .path=${mdiUndo}></ha-svg-icon>
</ha-dropdown-item>
<ha-dropdown-item
value="redo"
<ha-svg-icon slot="graphic" .path=${mdiUndo}></ha-svg-icon>
</ha-list-item>
<ha-list-item
graphic="icon"
@click=${this._redo}
.disabled=${!this._undoRedoController.canRedo}
>
${this.hass.localize("ui.common.redo")}
<ha-svg-icon slot="icon" .path=${mdiRedo}></ha-svg-icon>
</ha-dropdown-item>`
<ha-svg-icon slot="graphic" .path=${mdiRedo}></ha-svg-icon>
</ha-list-item>`
: nothing}
<ha-dropdown-item .disabled=${!stateObj} value="info">
<ha-list-item
graphic="icon"
.disabled=${!stateObj}
@click=${this._showInfo}
>
${this.hass.localize("ui.panel.config.automation.editor.show_info")}
<ha-svg-icon
slot="icon"
slot="graphic"
.path=${mdiInformationOutline}
></ha-svg-icon>
</ha-dropdown-item>
</ha-list-item>
<ha-dropdown-item .disabled=${!stateObj} value="settings">
<ha-list-item
graphic="icon"
.disabled=${!stateObj}
@click=${this._showSettings}
>
${this.hass.localize(
"ui.panel.config.automation.picker.show_settings"
)}
<ha-svg-icon slot="icon" .path=${mdiCog}></ha-svg-icon>
</ha-dropdown-item>
<ha-svg-icon slot="graphic" .path=${mdiCog}></ha-svg-icon>
</ha-list-item>
<ha-dropdown-item .disabled=${!stateObj} value="category">
<ha-list-item
graphic="icon"
.disabled=${!stateObj}
@click=${this._editCategory}
>
${this.hass.localize(
`ui.panel.config.scene.picker.${this._registryEntry?.categories?.automation ? "edit_category" : "assign_category"}`
)}
<ha-svg-icon slot="icon" .path=${mdiTag}></ha-svg-icon>
</ha-dropdown-item>
<ha-svg-icon slot="graphic" .path=${mdiTag}></ha-svg-icon>
</ha-list-item>
<ha-dropdown-item .disabled=${!stateObj} value="run">
<ha-list-item
graphic="icon"
.disabled=${!stateObj}
@click=${this._runActions}
>
${this.hass.localize("ui.panel.config.automation.editor.run")}
<ha-svg-icon slot="icon" .path=${mdiPlay}></ha-svg-icon>
</ha-dropdown-item>
<ha-svg-icon slot="graphic" .path=${mdiPlay}></ha-svg-icon>
</ha-list-item>
${stateObj && this.narrow
? html`<ha-dropdown-item value="trace">
${this.hass.localize(
"ui.panel.config.automation.editor.show_trace"
)}
<ha-svg-icon
slot="icon"
.path=${mdiTransitConnection}
></ha-svg-icon>
</ha-dropdown-item>`
? html`<a
href="/config/automation/trace/${encodeURIComponent(
this._config.id!
)}"
>
<ha-list-item graphic="icon">
${this.hass.localize(
"ui.panel.config.automation.editor.show_trace"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiTransitConnection}
></ha-svg-icon>
</ha-list-item>
</a>`
: nothing}
<ha-dropdown-item
value="rename"
<ha-list-item
graphic="icon"
@click=${this._promptAutomationAlias}
.disabled=${this._readOnly ||
!this.automationId ||
this._mode === "yaml"}
>
${this.hass.localize("ui.panel.config.automation.editor.rename")}
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
</ha-dropdown-item>
<ha-svg-icon slot="graphic" .path=${mdiRenameBox}></ha-svg-icon>
</ha-list-item>
${!useBlueprint
? html`
<ha-dropdown-item
<ha-list-item
graphic="icon"
@click=${this._promptAutomationMode}
.disabled=${this._readOnly || this._mode === "yaml"}
>
@@ -379,17 +399,18 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
"ui.panel.config.automation.editor.change_mode"
)}
<ha-svg-icon
slot="icon"
slot="graphic"
.path=${mdiDebugStepOver}
></ha-svg-icon>
</ha-dropdown-item>
</ha-list-item>
`
: nothing}
<ha-dropdown-item
.disabled=${!!this._blueprintConfig ||
<ha-list-item
.disabled=${this._blueprintConfig ||
(!this._readOnly && !this.automationId)}
value="duplicate"
graphic="icon"
@click=${this._duplicate}
>
${this.hass.localize(
this._readOnly
@@ -397,60 +418,74 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
: "ui.panel.config.automation.editor.duplicate"
)}
<ha-svg-icon
slot="icon"
slot="graphic"
.path=${mdiPlusCircleMultipleOutline}
></ha-svg-icon>
</ha-dropdown-item>
</ha-list-item>
${useBlueprint
? html`
<ha-dropdown-item
value="take_control"
<ha-list-item
graphic="icon"
@click=${this._takeControl}
.disabled=${this._readOnly}
>
${this.hass.localize(
"ui.panel.config.automation.editor.take_control"
)}
<ha-svg-icon slot="icon" .path=${mdiFileEdit}></ha-svg-icon>
</ha-dropdown-item>
<ha-svg-icon
slot="graphic"
.path=${mdiFileEdit}
></ha-svg-icon>
</ha-list-item>
`
: nothing}
<ha-dropdown-item value="toggle_yaml_mode">
<ha-list-item
graphic="icon"
@click=${this._mode === "gui"
? this._switchYamlMode
: this._switchUiMode}
>
${this.hass.localize(
`ui.panel.config.automation.editor.edit_${this._mode === "gui" ? "yaml" : "ui"}`
)}
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
</ha-dropdown-item>
<ha-svg-icon slot="graphic" .path=${mdiPlaylistEdit}></ha-svg-icon>
</ha-list-item>
<wa-divider></wa-divider>
<li divider role="separator"></li>
<ha-dropdown-item .disabled=${!stateObj} value="disable">
<ha-list-item
graphic="icon"
.disabled=${!stateObj}
@click=${this._toggle}
>
${stateObj?.state === "off"
? this.hass.localize("ui.panel.config.automation.editor.enable")
: this.hass.localize("ui.panel.config.automation.editor.disable")}
<ha-svg-icon
slot="icon"
slot="graphic"
.path=${stateObj?.state === "off"
? mdiPlayCircleOutline
: mdiStopCircleOutline}
></ha-svg-icon>
</ha-dropdown-item>
</ha-list-item>
<ha-dropdown-item
<ha-list-item
.disabled=${!this.automationId}
.variant=${this.automationId ? "danger" : "default"}
value="delete"
class=${classMap({ warning: Boolean(this.automationId) })}
graphic="icon"
@click=${this._deleteConfirm}
>
${this.hass.localize("ui.panel.config.automation.picker.delete")}
<ha-svg-icon
class=${classMap({ warning: Boolean(this.automationId) })}
slot="icon"
slot="graphic"
.path=${mdiDelete}
>
</ha-svg-icon>
</ha-dropdown-item>
</ha-dropdown>
</ha-list-item>
</ha-button-menu>
<div
class=${this._mode === "yaml" ? "yaml-mode" : ""}
@subscribe-automation-config=${this._subscribeAutomationConfig}
@@ -1213,63 +1248,6 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
this._undoRedoController.redo();
}
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
return;
}
switch (action) {
case "undo":
this._undo();
break;
case "redo":
this._redo();
break;
case "info":
this._showInfo();
break;
case "settings":
this._showSettings();
break;
case "category":
this._editCategory();
break;
case "run":
this._runActions();
break;
case "rename":
this._promptAutomationAlias();
break;
case "change_mode":
this._promptAutomationMode();
break;
case "duplicate":
this._duplicate();
break;
case "take_control":
this._takeControl();
break;
case "toggle_yaml_mode":
if (this._mode === "gui") {
this._switchYamlMode();
break;
}
this._switchUiMode();
break;
case "disable":
this._toggle();
break;
case "delete":
this._deleteConfirm();
break;
case "trace":
this._showTrace();
break;
}
}
static get styles(): CSSResultGroup {
return [
haStyle,
@@ -1322,6 +1300,13 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
margin-inline-end: 8px;
margin-inline-start: initial;
}
li[role="separator"] {
border-bottom-color: var(--divider-color);
}
ha-button-menu a {
text-decoration: none;
color: var(--primary-color);
}
h1 {
margin: 0;
}
@@ -1354,3 +1339,5 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
];
}
}
customElements.define("ha-automation-editor", HaAutomationEditor);

View File

@@ -35,8 +35,6 @@ import type { HASSDomEvent } from "../../../common/dom/fire_event";
import { fireEvent } from "../../../common/dom/fire_event";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { navigate } from "../../../common/navigate";
import { slugify } from "../../../common/string/slugify";
import "../../../components/ha-tooltip";
import type { LocalizeFunc } from "../../../common/translations/localize";
import {
hasRejectedItems,
@@ -329,19 +327,14 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
const date = new Date(automation.last_triggered);
const now = new Date();
const dayDifference = differenceInDays(now, date);
const formattedTime = formatShortDateTimeWithConditionalYear(
date,
this.hass.locale,
this.hass.config
);
const elementId = "last-triggered-" + slugify(automation.entity_id);
return html`
${dayDifference > 3
? formattedTime
: html`
<ha-tooltip for=${elementId}>${formattedTime}</ha-tooltip>
<span id=${elementId}>${relativeTime(date, locale)}</span>
`}
? formatShortDateTimeWithConditionalYear(
date,
this.hass.locale,
this.hass.config
)
: relativeTime(date, locale)}
`;
},
},

View File

@@ -1,4 +1,3 @@
import "@home-assistant/webawesome/dist/components/divider/divider";
import {
mdiDotsVertical,
mdiDownload,
@@ -16,13 +15,11 @@ import { repeat } from "lit/directives/repeat";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { formatDateTimeWithSeconds } from "../../../common/datetime/format_date_time";
import { fireEvent } from "../../../common/dom/fire_event";
import { navigate } from "../../../common/navigate";
import { computeRTL } from "../../../common/util/compute_rtl";
import "../../../components/ha-button";
import "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
import "../../../components/ha-button-menu";
import "../../../components/ha-icon-button";
import "../../../components/ha-list-item";
import "../../../components/trace/ha-trace-blueprint-config";
import "../../../components/trace/ha-trace-config";
import "../../../components/trace/ha-trace-logbook";
@@ -107,7 +104,9 @@ export class HaAutomationTrace extends LitElement {
appearance="plain"
size="small"
class="trace-link"
@click=${this._navigateToAutomation}
href="/config/automation/edit/${encodeURIComponent(
stateObj.attributes.id
)}"
slot="toolbar-icon"
>
${this.hass.localize(
@@ -115,50 +114,65 @@ export class HaAutomationTrace extends LitElement {
)}
</ha-button>
`
: nothing}
<ha-dropdown
slot="toolbar-icon"
@wa-select=${this._handleDropdownSelect}
>
: ""}
<ha-button-menu slot="toolbar-icon">
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-dropdown-item .disabled=${!stateObj} value="show_info">
<ha-list-item
graphic="icon"
.disabled=${!stateObj}
@click=${this._showInfo}
>
${this.hass.localize("ui.panel.config.automation.editor.show_info")}
<ha-svg-icon
slot="icon"
slot="graphic"
.path=${mdiInformationOutline}
></ha-svg-icon>
</ha-dropdown-item>
</ha-list-item>
${stateObj?.attributes.id && this.narrow
? html`
<ha-dropdown-item value="edit_automation">
${this.hass.localize(
"ui.panel.config.automation.trace.edit_automation"
)}
<ha-svg-icon slot="icon" .path=${mdiPencil}></ha-svg-icon>
</ha-dropdown-item>
<a
class="trace-link"
href="/config/automation/edit/${encodeURIComponent(
stateObj.attributes.id
)}"
>
<ha-list-item graphic="icon">
${this.hass.localize(
"ui.panel.config.automation.trace.edit_automation"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiPencil}
></ha-svg-icon>
</ha-list-item>
</a>
`
: nothing}
: ""}
<wa-divider></wa-divider>
<li divider role="separator"></li>
<ha-dropdown-item value="refresh">
<ha-list-item graphic="icon" @click=${this._refreshTraces}>
${this.hass.localize("ui.panel.config.automation.trace.refresh")}
<ha-svg-icon slot="icon" .path=${mdiRefresh}></ha-svg-icon>
</ha-dropdown-item>
<ha-svg-icon slot="graphic" .path=${mdiRefresh}></ha-svg-icon>
</ha-list-item>
<ha-dropdown-item .disabled=${!this._trace} value="download_trace">
<ha-list-item
graphic="icon"
.disabled=${!this._trace}
@click=${this._downloadTrace}
>
${this.hass.localize(
"ui.panel.config.automation.trace.download_trace"
)}
<ha-svg-icon slot="icon" .path=${mdiDownload}></ha-svg-icon>
</ha-dropdown-item>
</ha-dropdown>
<ha-svg-icon slot="graphic" .path=${mdiDownload}></ha-svg-icon>
</ha-list-item>
</ha-button-menu>
<div class="toolbar">
${this._traces && this._traces.length > 0
@@ -506,37 +520,6 @@ export class HaAutomationTrace extends LitElement {
fireEvent(this, "hass-more-info", { entityId: this._entityId });
}
private _navigateToAutomation() {
if (this._entityId && this.hass.states[this._entityId]) {
navigate(
`/config/automation/edit/${encodeURIComponent(this.hass.states[this._entityId].attributes.id)}`
);
}
}
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
return;
}
switch (action) {
case "show_info":
this._showInfo();
break;
case "refresh":
this._refreshTraces();
break;
case "download_trace":
this._downloadTrace();
break;
case "edit_automation":
this._navigateToAutomation();
break;
}
}
static get styles(): CSSResultGroup {
return [
haStyle,

View File

@@ -20,11 +20,10 @@ import { capitalizeFirstLetter } from "../../../../common/string/capitalize-firs
import "../../../../components/ha-automation-row";
import type { HaAutomationRow } from "../../../../components/ha-automation-row";
import "../../../../components/ha-card";
import "../../../../components/ha-dropdown";
import "../../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
import "../../../../components/ha-expansion-panel";
import "../../../../components/ha-icon-button";
import "../../../../components/ha-md-button-menu";
import "../../../../components/ha-md-menu-item";
import "../../../../components/ha-svg-icon";
import type {
Condition,
@@ -37,7 +36,6 @@ import type { Action, Option } from "../../../../data/script";
import { showPromptDialog } from "../../../../dialogs/generic/show-dialog-box";
import type { HomeAssistant } from "../../../../types";
import { isMac } from "../../../../util/is_mac";
import { showToast } from "../../../../util/toast";
import "../action/ha-automation-action";
import type HaAutomationAction from "../action/ha-automation-action";
import "../condition/ha-automation-condition";
@@ -48,6 +46,7 @@ import {
overflowStyles,
rowStyles,
} from "../styles";
import { showToast } from "../../../../util/toast";
@customElement("ha-automation-option-row")
export default class HaAutomationOptionRow extends LitElement {
@@ -156,12 +155,15 @@ export default class HaAutomationOptionRow extends LitElement {
${this.option
? html`
<ha-dropdown
<ha-md-button-menu
quick
slot="icons"
@click=${preventDefaultStopPropagation}
@closed=${stopPropagation}
@keydown=${stopPropagation}
@wa-select=${this._handleDropdownSelect}
placement="bottom-end"
positioning="fixed"
anchor-corner="end-end"
menu-corner="start-end"
>
<ha-icon-button
slot="trigger"
@@ -169,18 +171,24 @@ export default class HaAutomationOptionRow extends LitElement {
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-dropdown-item value="rename" .disabled=${this.disabled}>
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
<ha-md-menu-item
@click=${this._renameOption}
.disabled=${this.disabled}
>
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
${this._renderOverflowLabel(
this.hass.localize(
"ui.panel.config.automation.editor.triggers.rename"
)
)}
</ha-dropdown-item>
</ha-md-menu-item>
<ha-dropdown-item value="duplicate" .disabled=${this.disabled}>
<ha-md-menu-item
@click=${this._duplicateOption}
.disabled=${this.disabled}
>
<ha-svg-icon
slot="icon"
slot="start"
.path=${mdiPlusCircleMultipleOutline}
></ha-svg-icon>
@@ -189,42 +197,45 @@ export default class HaAutomationOptionRow extends LitElement {
"ui.panel.config.automation.editor.actions.duplicate"
)
)}
</ha-dropdown-item>
</ha-md-menu-item>
${!this.optionsInSidebar
? html`
<ha-dropdown-item
value="move_up"
<ha-md-menu-item
.clickAction=${this._moveUp}
.disabled=${this.disabled || !!this.first}
>
${this.hass.localize(
"ui.panel.config.automation.editor.move_up"
)}
<ha-svg-icon slot="icon" .path=${mdiArrowUp}></ha-svg-icon
></ha-dropdown-item>
<ha-dropdown-item
value="move_down"
<ha-svg-icon
slot="start"
.path=${mdiArrowUp}
></ha-svg-icon
></ha-md-menu-item>
<ha-md-menu-item
.clickAction=${this._moveDown}
.disabled=${this.disabled || !!this.last}
>
${this.hass.localize(
"ui.panel.config.automation.editor.move_down"
)}
<ha-svg-icon
slot="icon"
slot="start"
.path=${mdiArrowDown}
></ha-svg-icon
></ha-dropdown-item>
></ha-md-menu-item>
`
: nothing}
<ha-dropdown-item
value="delete"
variant="danger"
<ha-md-menu-item
@click=${this._removeOption}
class="warning"
.disabled=${this.disabled}
>
<ha-svg-icon
class="warning"
slot="icon"
slot="start"
.path=${mdiDelete}
></ha-svg-icon>
${this._renderOverflowLabel(
@@ -235,6 +246,7 @@ export default class HaAutomationOptionRow extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -249,8 +261,8 @@ export default class HaAutomationOptionRow extends LitElement {
>
</span>`
)}
</ha-dropdown-item>
</ha-dropdown>
</ha-md-menu-item>
</ha-md-button-menu>
`
: nothing}
${!this.optionsInSidebar ? this._renderContent() : nothing}
@@ -349,32 +361,6 @@ export default class HaAutomationOptionRow extends LitElement {
fireEvent(this, "move-down");
}
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
return;
}
switch (action) {
case "rename":
this._renameOption();
break;
case "delete":
this._removeOption();
break;
case "duplicate":
this._duplicateOption();
break;
case "move_up":
this._moveUp();
break;
case "move_down":
this._moveDown();
break;
}
}
private _removeOption = () => {
if (this.option) {
fireEvent(this, "value-changed", {
@@ -527,6 +513,9 @@ export default class HaAutomationOptionRow extends LitElement {
overflowStyles,
indentStyle,
css`
li[role="separator"] {
border-bottom-color: var(--divider-color);
}
h4 {
color: var(--ha-color-text-secondary);
}

View File

@@ -1,4 +1,3 @@
import "@home-assistant/webawesome/dist/components/divider/divider";
import {
mdiAppleKeyboardCommand,
mdiContentCopy,
@@ -17,8 +16,8 @@ import { keyed } from "lit/directives/keyed";
import { fireEvent } from "../../../../common/dom/fire_event";
import { handleStructError } from "../../../../common/structs/handle-errors";
import type { LocalizeKeys } from "../../../../common/translations/localize";
import "../../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
import "../../../../components/ha-md-divider";
import "../../../../components/ha-md-menu-item";
import { ACTION_BUILDING_BLOCKS } from "../../../../data/action";
import type { ActionSidebarConfig } from "../../../../data/automation";
import { domainToName } from "../../../../data/integration";
@@ -98,9 +97,9 @@ export default class HaAutomationSidebarAction extends LitElement {
title = `${domainToName(this.hass.localize, domain)}: ${
this.hass.localize(
`component.${domain}.services.${service}.name`,
this.hass.services[domain]?.[service]?.description_placeholders
this.hass.services[domain][service].description_placeholders
) ||
this.hass.services[domain]?.[service]?.name ||
this.hass.services[domain][service]?.name ||
title
}`;
}
@@ -117,7 +116,6 @@ export default class HaAutomationSidebarAction extends LitElement {
.yamlMode=${this.yamlMode}
.warnings=${this._warnings}
.narrow=${this.narrow}
@wa-select=${this._handleDropdownSelect}
>
<span slot="title">${title}</span>
<span slot="subtitle"
@@ -128,35 +126,38 @@ export default class HaAutomationSidebarAction extends LitElement {
: ""}</span
>
<ha-dropdown-item slot="menu-items" value="run">
<ha-svg-icon slot="icon" .path=${mdiPlay}></ha-svg-icon>
<ha-md-menu-item slot="menu-items" .clickAction=${this.config.run}>
<ha-svg-icon slot="start" .path=${mdiPlay}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize("ui.panel.config.automation.editor.actions.run")}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>
<ha-dropdown-item
</ha-md-menu-item>
<ha-md-menu-item
slot="menu-items"
value="rename"
.clickAction=${this.config.rename}
.disabled=${this.disabled}
>
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.rename"
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>
<wa-divider slot="menu-items"></wa-divider>
<ha-dropdown-item
</ha-md-menu-item>
<ha-md-divider
slot="menu-items"
value="duplicate"
role="separator"
tabindex="-1"
></ha-md-divider>
<ha-md-menu-item
slot="menu-items"
.clickAction=${this.config.duplicate}
.disabled=${this.disabled}
>
<ha-svg-icon
slot="icon"
slot="start"
.path=${mdiPlusCircleMultipleOutline}
></ha-svg-icon>
<div class="overflow-label">
@@ -165,9 +166,9 @@ export default class HaAutomationSidebarAction extends LitElement {
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>
<ha-dropdown-item slot="menu-items" value="copy">
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon>
</ha-md-menu-item>
<ha-md-menu-item slot="menu-items" .clickAction=${this.config.copy}>
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.copy"
@@ -177,6 +178,7 @@ export default class HaAutomationSidebarAction extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -188,13 +190,13 @@ export default class HaAutomationSidebarAction extends LitElement {
</span>`
: nothing}
</div>
</ha-dropdown-item>
<ha-dropdown-item
</ha-md-menu-item>
<ha-md-menu-item
slot="menu-items"
value="cut"
.clickAction=${this.config.cut}
.disabled=${this.disabled}
>
<ha-svg-icon slot="icon" .path=${mdiContentCut}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.cut"
@@ -204,6 +206,7 @@ export default class HaAutomationSidebarAction extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -215,29 +218,32 @@ export default class HaAutomationSidebarAction extends LitElement {
</span>`
: nothing}
</div>
</ha-dropdown-item>
<ha-dropdown-item
</ha-md-menu-item>
<ha-md-menu-item
slot="menu-items"
value="toggle_yaml_mode"
.clickAction=${this._toggleYamlMode}
.disabled=${!this.config.uiSupported || !!this._warnings}
>
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>
<wa-divider slot="menu-items"></wa-divider>
<ha-dropdown-item
</ha-md-menu-item>
<ha-md-divider
slot="menu-items"
value="disable"
role="separator"
tabindex="-1"
></ha-md-divider>
<ha-md-menu-item
slot="menu-items"
.clickAction=${this.config.disable}
.disabled=${this.disabled}
>
<ha-svg-icon
slot="icon"
slot="start"
.path=${rowDisabled ? mdiPlayCircleOutline : mdiStopCircleOutline}
></ha-svg-icon>
<div class="overflow-label">
@@ -246,14 +252,14 @@ export default class HaAutomationSidebarAction extends LitElement {
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>
<ha-dropdown-item
</ha-md-menu-item>
<ha-md-menu-item
slot="menu-items"
value="delete"
variant="danger"
.clickAction=${this.config.delete}
.disabled=${this.disabled}
class="warning"
>
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete"
@@ -263,6 +269,7 @@ export default class HaAutomationSidebarAction extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -278,7 +285,7 @@ export default class HaAutomationSidebarAction extends LitElement {
</span>`
: nothing}
</div>
</ha-dropdown-item>
</ha-md-menu-item>
${description && !this.yamlMode
? html`<div class="description">${description}</div>`
: keyed(
@@ -334,41 +341,6 @@ export default class HaAutomationSidebarAction extends LitElement {
fireEvent(this, "toggle-yaml-mode");
};
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
return;
}
switch (action) {
case "rename":
this.config.rename();
break;
case "run":
this.config.run();
break;
case "duplicate":
this.config.duplicate();
break;
case "copy":
this.config.copy();
break;
case "cut":
this.config.cut();
break;
case "toggle_yaml_mode":
this._toggleYamlMode();
break;
case "disable":
this.config.disable();
break;
case "delete":
this.config.delete();
break;
}
}
static styles = [sidebarEditorStyles, overflowStyles];
}

View File

@@ -3,16 +3,16 @@ import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { fireEvent } from "../../../../common/dom/fire_event";
import { preventDefaultStopPropagation } from "../../../../common/dom/prevent_default_stop_propagation";
import { stopPropagation } from "../../../../common/dom/stop_propagation";
import "../../../../components/ha-card";
import "../../../../components/ha-dialog-header";
import "../../../../components/ha-dropdown";
import "../../../../components/ha-icon-button";
import { ScrollableFadeMixin } from "../../../../mixins/scrollable-fade-mixin";
import "../../../../components/ha-md-button-menu";
import "../../../../components/ha-md-divider";
import { haStyleScrollbar } from "../../../../resources/styles";
import type { HomeAssistant } from "../../../../types";
import "../ha-automation-editor-warning";
import { ScrollableFadeMixin } from "../../../../mixins/scrollable-fade-mixin";
export interface SidebarOverflowMenuEntry {
clickAction: () => void;
@@ -36,10 +36,6 @@ export default class HaAutomationSidebarCard extends ScrollableFadeMixin(
@property({ attribute: false }) public warnings?: string[];
@property({ attribute: false }) public handleDropdownSelect!: (
ev: CustomEvent
) => void;
@property({ type: Boolean }) public narrow = false;
@query(".card-content") private _contentElement!: HTMLDivElement;
@@ -67,10 +63,14 @@ export default class HaAutomationSidebarCard extends ScrollableFadeMixin(
<slot slot="title" name="title"></slot>
<slot slot="subtitle" name="subtitle"></slot>
<slot name="overflow-menu" slot="actionItems">
<ha-dropdown
@click=${preventDefaultStopPropagation}
<ha-md-button-menu
quick
@click=${this._openOverflowMenu}
@keydown=${stopPropagation}
placement="bottom-end"
@closed=${stopPropagation}
.positioning=${this.narrow ? "absolute" : "fixed"}
anchor-corner="end-end"
menu-corner="start-end"
>
<ha-icon-button
slot="trigger"
@@ -78,7 +78,7 @@ export default class HaAutomationSidebarCard extends ScrollableFadeMixin(
.path=${mdiDotsVertical}
></ha-icon-button>
<slot name="menu-items"></slot>
</ha-dropdown>
</ha-md-button-menu>
</slot>
</ha-dialog-header>
${this.warnings
@@ -100,6 +100,11 @@ export default class HaAutomationSidebarCard extends ScrollableFadeMixin(
fireEvent(this, "close-sidebar");
}
private _openOverflowMenu(ev: MouseEvent) {
ev.stopPropagation();
ev.preventDefault();
}
static get styles() {
return [
...super.styles,

View File

@@ -1,4 +1,3 @@
import "@home-assistant/webawesome/dist/components/divider/divider";
import {
mdiAppleKeyboardCommand,
mdiContentCopy,
@@ -17,11 +16,9 @@ import { classMap } from "lit/directives/class-map";
import { keyed } from "lit/directives/keyed";
import { fireEvent } from "../../../../common/dom/fire_event";
import { handleStructError } from "../../../../common/structs/handle-errors";
import "../../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
import type {
ConditionSidebarConfig,
LegacyCondition,
ConditionSidebarConfig,
} from "../../../../data/automation";
import { testCondition } from "../../../../data/automation";
import {
@@ -120,7 +117,6 @@ export default class HaAutomationSidebarCondition extends LitElement {
.yamlMode=${this.yamlMode}
.warnings=${this._warnings}
.narrow=${this.narrow}
@wa-select=${this._handleDropdownSelect}
>
<span slot="title">${title}</span>
<span slot="subtitle"
@@ -128,38 +124,42 @@ export default class HaAutomationSidebarCondition extends LitElement {
? ` (${this.hass.localize("ui.panel.config.automation.editor.actions.disabled")})`
: ""}</span
>
<ha-dropdown-item slot="menu-items" value="test">
<ha-svg-icon slot="icon" .path=${mdiFlask}></ha-svg-icon>
<ha-md-menu-item slot="menu-items" .clickAction=${this._testCondition}>
<ha-svg-icon slot="start" .path=${mdiFlask}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.conditions.test"
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>
<ha-dropdown-item
</ha-md-menu-item>
<ha-md-menu-item
slot="menu-items"
value="rename"
.clickAction=${this.config.rename}
.disabled=${this.disabled}
>
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.rename"
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>
</ha-md-menu-item>
<wa-divider slot="menu-items"></wa-divider>
<ha-dropdown-item
<ha-md-divider
slot="menu-items"
value="duplicate"
role="separator"
tabindex="-1"
></ha-md-divider>
<ha-md-menu-item
slot="menu-items"
.clickAction=${this.config.duplicate}
.disabled=${this.disabled}
>
<ha-svg-icon
slot="icon"
slot="start"
.path=${mdiPlusCircleMultipleOutline}
></ha-svg-icon>
<div class="overflow-label">
@@ -168,10 +168,10 @@ export default class HaAutomationSidebarCondition extends LitElement {
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>
</ha-md-menu-item>
<ha-dropdown-item slot="menu-items" value="copy">
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon>
<ha-md-menu-item slot="menu-items" .clickAction=${this.config.copy}>
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.copy"
@@ -181,6 +181,7 @@ export default class HaAutomationSidebarCondition extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -192,14 +193,14 @@ export default class HaAutomationSidebarCondition extends LitElement {
</span>`
: nothing}
</div>
</ha-dropdown-item>
</ha-md-menu-item>
<ha-dropdown-item
<ha-md-menu-item
slot="menu-items"
value="cut"
.clickAction=${this.config.cut}
.disabled=${this.disabled}
>
<ha-svg-icon slot="icon" .path=${mdiContentCut}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.cut"
@@ -209,6 +210,7 @@ export default class HaAutomationSidebarCondition extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -220,29 +222,32 @@ export default class HaAutomationSidebarCondition extends LitElement {
</span>`
: nothing}
</div>
</ha-dropdown-item>
<ha-dropdown-item
</ha-md-menu-item>
<ha-md-menu-item
slot="menu-items"
value="toggle_yaml_mode"
.clickAction=${this._toggleYamlMode}
.disabled=${!this.config.uiSupported || !!this._warnings}
>
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>
<wa-divider slot="menu-items"></wa-divider>
<ha-dropdown-item
</ha-md-menu-item>
<ha-md-divider
slot="menu-items"
value="disable"
role="separator"
tabindex="-1"
></ha-md-divider>
<ha-md-menu-item
slot="menu-items"
.clickAction=${this.config.disable}
.disabled=${this.disabled}
>
<ha-svg-icon
slot="icon"
slot="start"
.path=${rowDisabled ? mdiPlayCircleOutline : mdiStopCircleOutline}
></ha-svg-icon>
<div class="overflow-label">
@@ -251,14 +256,14 @@ export default class HaAutomationSidebarCondition extends LitElement {
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>
<ha-dropdown-item
</ha-md-menu-item>
<ha-md-menu-item
slot="menu-items"
value="delete"
variant="danger"
.clickAction=${this.config.delete}
.disabled=${this.disabled}
class="warning"
>
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete"
@@ -268,6 +273,7 @@ export default class HaAutomationSidebarCondition extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -283,7 +289,7 @@ export default class HaAutomationSidebarCondition extends LitElement {
</span>`
: nothing}
</div>
</ha-dropdown-item>
</ha-md-menu-item>
${description && !this.yamlMode
? html`<div class="description">${description}</div>`
: keyed(
@@ -413,41 +419,6 @@ export default class HaAutomationSidebarCondition extends LitElement {
fireEvent(this, "toggle-yaml-mode");
};
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
return;
}
switch (action) {
case "rename":
this.config.rename();
break;
case "test":
this._testCondition();
break;
case "duplicate":
this.config.duplicate();
break;
case "copy":
this.config.copy();
break;
case "cut":
this.config.cut();
break;
case "toggle_yaml_mode":
this._toggleYamlMode();
break;
case "disable":
this.config.disable();
break;
case "delete":
this.config.delete();
break;
}
}
static styles = [
sidebarEditorStyles,
overflowStyles,

View File

@@ -1,4 +1,3 @@
import "@home-assistant/webawesome/dist/components/divider/divider";
import {
mdiAppleKeyboardCommand,
mdiDelete,
@@ -7,9 +6,8 @@ import {
} from "@mdi/js";
import { html, LitElement, nothing } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
import "../../../../components/ha-md-divider";
import "../../../../components/ha-md-menu-item";
import "../../../../components/ha-svg-icon";
import type { OptionSidebarConfig } from "../../../../data/automation";
import type { HomeAssistant } from "../../../../types";
@@ -52,34 +50,33 @@ export default class HaAutomationSidebarOption extends LitElement {
.hass=${this.hass}
.isWide=${this.isWide}
.narrow=${this.narrow}
@wa-select=${this._handleDropdownSelect}
>
<span slot="title">${title}</span>
<span slot="subtitle">${subtitle}</span>
${this.config.defaultOption
? html`<span slot="overflow-menu"></span>`
: html`
<ha-dropdown-item
<ha-md-menu-item
slot="menu-items"
value="rename"
.clickAction=${this.config.rename}
.disabled=${!!disabled}
>
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.rename"
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>
</ha-md-menu-item>
<ha-dropdown-item
<ha-md-menu-item
slot="menu-items"
value="duplicate"
@click=${this.config.duplicate}
.disabled=${this.disabled}
>
<ha-svg-icon
slot="icon"
slot="start"
.path=${mdiPlusCircleMultipleOutline}
></ha-svg-icon>
<div class="overflow-label">
@@ -88,15 +85,19 @@ export default class HaAutomationSidebarOption extends LitElement {
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>
<wa-divider slot="menu-items"></wa-divider>
<ha-dropdown-item
</ha-md-menu-item>
<ha-md-divider
slot="menu-items"
value="delete"
role="separator"
tabindex="-1"
></ha-md-divider>
<ha-md-menu-item
slot="menu-items"
.clickAction=${this.config.delete}
.disabled=${this.disabled}
variant="danger"
class="warning"
>
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.type.choose.remove_option"
@@ -122,33 +123,13 @@ export default class HaAutomationSidebarOption extends LitElement {
</span>`
: nothing}
</div>
</ha-dropdown-item>
</ha-md-menu-item>
`}
<div class="description">${description}</div>
</ha-automation-sidebar-card>`;
}
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
return;
}
switch (action) {
case "rename":
this.config.rename();
break;
case "duplicate":
this.config.duplicate();
break;
case "delete":
this.config.delete();
break;
}
}
static styles = [sidebarEditorStyles, overflowStyles];
}

View File

@@ -4,8 +4,6 @@ import { customElement, property, query, state } from "lit/decorators";
import { keyed } from "lit/directives/keyed";
import { fireEvent } from "../../../../common/dom/fire_event";
import type { LocalizeKeys } from "../../../../common/translations/localize";
import "../../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
import type { ScriptFieldSidebarConfig } from "../../../../data/automation";
import type { HomeAssistant } from "../../../../types";
import { isMac } from "../../../../util/is_mac";
@@ -64,30 +62,29 @@ export default class HaAutomationSidebarScriptFieldSelector extends LitElement {
.yamlMode=${this.yamlMode}
.warnings=${this._warnings}
.narrow=${this.narrow}
@wa-select=${this._handleDropdownSelect}
>
<span slot="title">${title}</span>
<span slot="subtitle">${subtitle}</span>
<ha-dropdown-item
<ha-md-menu-item
slot="menu-items"
value="toggle_yaml_mode"
.clickAction=${this._toggleYamlMode}
.disabled=${!!this._warnings}
>
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>
<ha-dropdown-item
</ha-md-menu-item>
<ha-md-menu-item
slot="menu-items"
value="delete"
.clickAction=${this.config.delete}
.disabled=${this.disabled}
variant="danger"
class="warning"
>
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete"
@@ -97,6 +94,7 @@ export default class HaAutomationSidebarScriptFieldSelector extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -112,7 +110,7 @@ export default class HaAutomationSidebarScriptFieldSelector extends LitElement {
</span>`
: nothing}
</div>
</ha-dropdown-item>
</ha-md-menu-item>
${keyed(
this.sidebarKey,
html`<ha-script-field-selector-editor
@@ -162,23 +160,6 @@ export default class HaAutomationSidebarScriptFieldSelector extends LitElement {
fireEvent(this, "toggle-yaml-mode");
};
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
return;
}
switch (action) {
case "toggle_yaml_mode":
this._toggleYamlMode();
break;
case "delete":
this.config.delete();
break;
}
}
static styles = sidebarEditorStyles;
}

View File

@@ -3,8 +3,6 @@ import { html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { keyed } from "lit/directives/keyed";
import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
import type { ScriptFieldSidebarConfig } from "../../../../data/automation";
import type { HomeAssistant } from "../../../../types";
import { isMac } from "../../../../util/is_mac";
@@ -58,29 +56,28 @@ export default class HaAutomationSidebarScriptField extends LitElement {
.yamlMode=${this.yamlMode}
.warnings=${this._warnings}
.narrow=${this.narrow}
@wa-select=${this._handleDropdownSelect}
>
<span slot="title">${title}</span>
<ha-dropdown-item
<ha-md-menu-item
slot="menu-items"
value="toggle_yaml_mode"
.clickAction=${this._toggleYamlMode}
.disabled=${!!this._warnings}
>
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>
<ha-dropdown-item
</ha-md-menu-item>
<ha-md-menu-item
slot="menu-items"
value="delete"
.clickAction=${this.config.delete}
.disabled=${this.disabled}
variant="danger"
class="warning"
>
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete"
@@ -90,6 +87,7 @@ export default class HaAutomationSidebarScriptField extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -105,7 +103,7 @@ export default class HaAutomationSidebarScriptField extends LitElement {
</span>`
: nothing}
</div>
</ha-dropdown-item>
</ha-md-menu-item>
${keyed(
this.sidebarKey,
html`<ha-script-field-editor
@@ -156,23 +154,6 @@ export default class HaAutomationSidebarScriptField extends LitElement {
fireEvent(this, "toggle-yaml-mode");
};
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
return;
}
switch (action) {
case "toggle_yaml_mode":
this._toggleYamlMode();
break;
case "delete":
this.config.delete();
break;
}
}
static styles = [sidebarEditorStyles, overflowStyles];
}

View File

@@ -1,4 +1,3 @@
import "@home-assistant/webawesome/dist/components/divider/divider";
import {
mdiAppleKeyboardCommand,
mdiContentCopy,
@@ -16,8 +15,6 @@ import { customElement, property, query, state } from "lit/decorators";
import { keyed } from "lit/directives/keyed";
import { fireEvent } from "../../../../common/dom/fire_event";
import { handleStructError } from "../../../../common/structs/handle-errors";
import "../../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
import type {
LegacyTrigger,
TriggerSidebarConfig,
@@ -102,7 +99,6 @@ export default class HaAutomationSidebarTrigger extends LitElement {
.yamlMode=${this.yamlMode}
.warnings=${this._warnings}
.narrow=${this.narrow}
@wa-select=${this._handleDropdownSelect}
>
<span slot="title">${title}</span>
<span slot="subtitle"
@@ -110,56 +106,60 @@ export default class HaAutomationSidebarTrigger extends LitElement {
? ` (${this.hass.localize("ui.panel.config.automation.editor.actions.disabled")})`
: ""}</span
>
<ha-dropdown-item
<ha-md-menu-item
slot="menu-items"
value="rename"
.clickAction=${this.config.rename}
.disabled=${this.disabled || type === "list"}
>
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.rename"
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>
</ha-md-menu-item>
${!this.yamlMode &&
!("id" in this.config.config) &&
!this._requestShowId
? html`<ha-dropdown-item
? html`<ha-md-menu-item
slot="menu-items"
value="show_id"
.clickAction=${this._showTriggerId}
.disabled=${this.disabled || type === "list"}
>
<ha-svg-icon slot="icon" .path=${mdiIdentifier}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiIdentifier}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.edit_id"
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>`
</ha-md-menu-item>`
: nothing}
<wa-divider slot="menu-items"></wa-divider>
<ha-dropdown-item
<ha-md-divider
slot="menu-items"
value="duplicate"
role="separator"
tabindex="-1"
></ha-md-divider>
<ha-md-menu-item
slot="menu-items"
.clickAction=${this.config.duplicate}
.disabled=${this.disabled}
>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.duplicate"
)}
<ha-svg-icon
slot="icon"
slot="start"
.path=${mdiPlusCircleMultipleOutline}
></ha-svg-icon>
</ha-dropdown-item>
</ha-md-menu-item>
<ha-dropdown-item slot="menu-items" value="copy">
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon>
<ha-md-menu-item slot="menu-items" .clickAction=${this.config.copy}>
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.copy"
@@ -169,6 +169,7 @@ export default class HaAutomationSidebarTrigger extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -180,14 +181,14 @@ export default class HaAutomationSidebarTrigger extends LitElement {
</span>`
: nothing}
</div>
</ha-dropdown-item>
</ha-md-menu-item>
<ha-dropdown-item
<ha-md-menu-item
slot="menu-items"
value="cut"
.clickAction=${this.config.cut}
.disabled=${this.disabled}
>
<ha-svg-icon slot="icon" .path=${mdiContentCut}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.cut"
@@ -197,6 +198,7 @@ export default class HaAutomationSidebarTrigger extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -208,28 +210,32 @@ export default class HaAutomationSidebarTrigger extends LitElement {
</span>`
: nothing}
</div>
</ha-dropdown-item>
<ha-dropdown-item
</ha-md-menu-item>
<ha-md-menu-item
slot="menu-items"
value="toggle_yaml_mode"
.clickAction=${this._toggleYamlMode}
.disabled=${!this.config.uiSupported || !!this._warnings}
>
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>
<wa-divider slot="menu-items"></wa-divider>
<ha-dropdown-item
</ha-md-menu-item>
<ha-md-divider
slot="menu-items"
value="disable"
role="separator"
tabindex="-1"
></ha-md-divider>
<ha-md-menu-item
slot="menu-items"
.clickAction=${this.config.disable}
.disabled=${this.disabled || type === "list"}
>
<ha-svg-icon
slot="icon"
slot="start"
.path=${rowDisabled ? mdiPlayCircleOutline : mdiStopCircleOutline}
></ha-svg-icon>
<div class="overflow-label">
@@ -238,14 +244,14 @@ export default class HaAutomationSidebarTrigger extends LitElement {
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>
<ha-dropdown-item
</ha-md-menu-item>
<ha-md-menu-item
slot="menu-items"
value="delete"
.clickAction=${this.config.delete}
.disabled=${this.disabled}
variant="danger"
class="warning"
>
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete"
@@ -255,6 +261,7 @@ export default class HaAutomationSidebarTrigger extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -270,7 +277,7 @@ export default class HaAutomationSidebarTrigger extends LitElement {
</span>`
: nothing}
</div>
</ha-dropdown-item>
</ha-md-menu-item>
${keyed(
this.sidebarKey,
html`<ha-automation-trigger-editor
@@ -328,41 +335,6 @@ export default class HaAutomationSidebarTrigger extends LitElement {
this._requestShowId = true;
};
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
return;
}
switch (action) {
case "rename":
this.config.rename();
break;
case "show_id":
this._showTriggerId();
break;
case "duplicate":
this.config.duplicate();
break;
case "copy":
this.config.copy();
break;
case "cut":
this.config.cut();
break;
case "toggle_yaml_mode":
this._toggleYamlMode();
break;
case "disable":
this.config.disable();
break;
case "delete":
this.config.delete();
break;
}
}
static styles = [sidebarEditorStyles, overflowStyles];
}

View File

@@ -1,4 +1,3 @@
import "@home-assistant/webawesome/dist/components/divider/divider";
import { consume } from "@lit/context";
import {
mdiAppleKeyboardCommand,
@@ -35,11 +34,11 @@ import "../../../../components/ha-alert";
import "../../../../components/ha-automation-row";
import type { HaAutomationRow } from "../../../../components/ha-automation-row";
import "../../../../components/ha-card";
import "../../../../components/ha-dropdown";
import "../../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
import "../../../../components/ha-expansion-panel";
import "../../../../components/ha-icon-button";
import "../../../../components/ha-md-button-menu";
import "../../../../components/ha-md-divider";
import "../../../../components/ha-md-menu-item";
import "../../../../components/ha-svg-icon";
import { TRIGGER_ICONS } from "../../../../components/ha-trigger-icon";
import type {
@@ -209,35 +208,41 @@ export default class HaAutomationTriggerRow extends LitElement {
<slot name="icons" slot="icons"></slot>
<ha-dropdown
<ha-md-button-menu
quick
slot="icons"
@click=${preventDefaultStopPropagation}
@keydown=${stopPropagation}
@wa-select=${this._handleDropdownSelect}
placement="bottom-end"
@closed=${stopPropagation}
positioning="fixed"
anchor-corner="end-end"
menu-corner="start-end"
>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-dropdown-item
value="rename"
<ha-md-menu-item
.clickAction=${this._renameTrigger}
.disabled=${this.disabled || type === "list"}
>
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
${this._renderOverflowLabel(
this.hass.localize(
"ui.panel.config.automation.editor.triggers.rename"
)
)}
</ha-dropdown-item>
</ha-md-menu-item>
<wa-divider></wa-divider>
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
<ha-dropdown-item value="duplicate" .disabled=${this.disabled}>
<ha-md-menu-item
.clickAction=${this._duplicateTrigger}
.disabled=${this.disabled}
>
<ha-svg-icon
slot="icon"
slot="start"
.path=${mdiPlusCircleMultipleOutline}
></ha-svg-icon>
@@ -246,10 +251,13 @@ export default class HaAutomationTriggerRow extends LitElement {
"ui.panel.config.automation.editor.actions.duplicate"
)
)}
</ha-dropdown-item>
</ha-md-menu-item>
<ha-dropdown-item value="copy" .disabled=${this.disabled}>
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon>
<ha-md-menu-item
.clickAction=${this._copyTrigger}
.disabled=${this.disabled}
>
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
${this._renderOverflowLabel(
this.hass.localize(
"ui.panel.config.automation.editor.triggers.copy"
@@ -258,6 +266,7 @@ export default class HaAutomationTriggerRow extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -268,10 +277,13 @@ export default class HaAutomationTriggerRow extends LitElement {
<span>C</span>
</span>`
)}
</ha-dropdown-item>
</ha-md-menu-item>
<ha-dropdown-item value="cut" .disabled=${this.disabled}>
<ha-svg-icon slot="icon" .path=${mdiContentCut}></ha-svg-icon>
<ha-md-menu-item
.clickAction=${this._cutTrigger}
.disabled=${this.disabled}
>
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
${this._renderOverflowLabel(
this.hass.localize(
"ui.panel.config.automation.editor.triggers.cut"
@@ -280,6 +292,7 @@ export default class HaAutomationTriggerRow extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -290,51 +303,51 @@ export default class HaAutomationTriggerRow extends LitElement {
<span>X</span>
</span>`
)}
</ha-dropdown-item>
</ha-md-menu-item>
${!this.optionsInSidebar
? html`
<ha-dropdown-item
value="move_up"
<ha-md-menu-item
.clickAction=${this._moveUp}
.disabled=${this.disabled || !!this.first}
>
${this.hass.localize(
"ui.panel.config.automation.editor.move_up"
)}
<ha-svg-icon slot="icon" .path=${mdiArrowUp}></ha-svg-icon
></ha-dropdown-item>
<ha-dropdown-item
value="move_down"
<ha-svg-icon slot="start" .path=${mdiArrowUp}></ha-svg-icon
></ha-md-menu-item>
<ha-md-menu-item
.clickAction=${this._moveDown}
.disabled=${this.disabled || !!this.last}
>
${this.hass.localize(
"ui.panel.config.automation.editor.move_down"
)}
<ha-svg-icon slot="icon" .path=${mdiArrowDown}></ha-svg-icon
></ha-dropdown-item>
<ha-svg-icon slot="start" .path=${mdiArrowDown}></ha-svg-icon
></ha-md-menu-item>
`
: nothing}
<ha-dropdown-item
value="toggle_yaml_mode"
<ha-md-menu-item
.clickAction=${this._toggleYamlMode}
.disabled=${!supported || !!this._warnings}
>
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
${this._renderOverflowLabel(
this.hass.localize(
`ui.panel.config.automation.editor.edit_${!yamlMode ? "yaml" : "ui"}`
)
)}
</ha-dropdown-item>
</ha-md-menu-item>
<wa-divider></wa-divider>
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
<ha-dropdown-item
value="disable"
<ha-md-menu-item
.clickAction=${this._onDisable}
.disabled=${this.disabled || type === "list"}
>
<ha-svg-icon
slot="icon"
slot="start"
.path=${"enabled" in this.trigger && this.trigger.enabled === false
? mdiPlayCircleOutline
: mdiStopCircleOutline}
@@ -345,15 +358,15 @@ export default class HaAutomationTriggerRow extends LitElement {
`ui.panel.config.automation.editor.actions.${"enabled" in this.trigger && this.trigger.enabled === false ? "enable" : "disable"}`
)
)}
</ha-dropdown-item>
<ha-dropdown-item
value="delete"
variant="danger"
</ha-md-menu-item>
<ha-md-menu-item
.clickAction=${this._onDelete}
class="warning"
.disabled=${this.disabled}
>
<ha-svg-icon
class="warning"
slot="icon"
slot="start"
.path=${mdiDelete}
></ha-svg-icon>
${this._renderOverflowLabel(
@@ -364,6 +377,7 @@ export default class HaAutomationTriggerRow extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -378,8 +392,8 @@ export default class HaAutomationTriggerRow extends LitElement {
>
</span>`
)}
</ha-dropdown-item>
</ha-dropdown>
</ha-md-menu-item>
</ha-md-button-menu>
${!this.optionsInSidebar
? html`${this._warnings
? html`<ha-automation-editor-warning
@@ -790,44 +804,6 @@ export default class HaAutomationTriggerRow extends LitElement {
this._automationRowElement?.focus();
}
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
return;
}
switch (action) {
case "rename":
this._renameTrigger();
break;
case "duplicate":
this._duplicateTrigger();
break;
case "copy":
this._copyTrigger();
break;
case "cut":
this._cutTrigger();
break;
case "move_up":
this._moveUp();
break;
case "move_down":
this._moveDown();
break;
case "toggle_yaml_mode":
this._toggleYamlMode(ev.target as HTMLElement);
break;
case "disable":
this._onDisable();
break;
case "delete":
this._onDelete();
break;
}
}
static get styles(): CSSResultGroup {
return [
rowStyles,

View File

@@ -160,9 +160,9 @@ export class HaPlatformTrigger extends LitElement {
const hasOptional = Boolean(
triggerDesc?.fields &&
Object.values(triggerDesc.fields).some((field) =>
showOptionalToggle(field)
)
Object.values(triggerDesc.fields).some((field) =>
showOptionalToggle(field)
)
);
return html`

View File

@@ -1,14 +1,15 @@
import { mdiClose, mdiContentCopy, mdiDownload } from "@mdi/js";
import type { CSSResultGroup } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { customElement, property, query, state } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event";
import { copyToClipboard } from "../../../../common/util/copy-clipboard";
import "../../../../components/ha-button";
import "../../../../components/ha-dialog-footer";
import "../../../../components/ha-dialog-header";
import "../../../../components/ha-icon-button";
import "../../../../components/ha-icon-button-prev";
import "../../../../components/ha-wa-dialog";
import "../../../../components/ha-md-dialog";
import type { HaMdDialog } from "../../../../components/ha-md-dialog";
import "../../../../components/ha-md-list";
import "../../../../components/ha-md-list-item";
import "../../../../components/ha-password-field";
@@ -30,18 +31,20 @@ type Step = (typeof STEPS)[number];
class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _open = false;
@state() private _opened = false;
@state() private _step?: Step;
@state() private _params?: ChangeBackupEncryptionKeyDialogParams;
@query("ha-md-dialog") private _dialog!: HaMdDialog;
@state() private _newEncryptionKey?: string;
public showDialog(params: ChangeBackupEncryptionKeyDialogParams): void {
this._params = params;
this._step = STEPS[0];
this._open = true;
this._opened = true;
this._newEncryptionKey = generateEncryptionKey();
}
@@ -49,10 +52,10 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
if (this._params!.cancel) {
this._params!.cancel();
}
if (this._open) {
if (this._opened) {
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
this._open = false;
this._opened = false;
this._step = undefined;
this._params = undefined;
this._newEncryptionKey = undefined;
@@ -61,7 +64,7 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
private _done() {
this._params?.submit!(true);
this.closeDialog();
this._dialog.close();
}
private _previousStep() {
@@ -81,6 +84,10 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
}
protected render() {
if (!this._opened || !this._params) {
return nothing;
}
const dialogTitle =
this._step === "current" || this._step === "new"
? this.hass.localize(
@@ -89,40 +96,36 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
: "";
return html`
<ha-wa-dialog
.hass=${this.hass}
.open=${this._open}
header-title=${dialogTitle}
prevent-scrim-close
@closed=${this.closeDialog}
>
${this._step === "new"
? html`
<ha-icon-button-prev
slot="headerNavigationIcon"
@click=${this._previousStep}
></ha-icon-button-prev>
`
: html`
<ha-icon-button
slot="headerNavigationIcon"
data-dialog="close"
.label=${this.hass.localize("ui.common.close")}
.path=${mdiClose}
></ha-icon-button>
`}
${this._renderStepContent()}
<ha-dialog-footer slot="footer">
<ha-md-dialog disable-cancel-action open @closed=${this.closeDialog}>
<ha-dialog-header slot="headline">
${this._step === "new"
? html`
<ha-icon-button-prev
slot="navigationIcon"
@click=${this._previousStep}
></ha-icon-button-prev>
`
: html`
<ha-icon-button
slot="navigationIcon"
.label=${this.hass.localize("ui.common.close")}
.path=${mdiClose}
@click=${this.closeDialog}
></ha-icon-button>
`}
<span slot="title">${dialogTitle}</span>
</ha-dialog-header>
<div slot="content">${this._renderStepContent()}</div>
<div slot="actions">
${this._step === "current"
? html`
<ha-button slot="primaryAction" @click=${this._nextStep}>
<ha-button @click=${this._nextStep}>
${this.hass.localize("ui.common.next")}
</ha-button>
`
: this._step === "new"
? html`
<ha-button
slot="primaryAction"
@click=${this._submit}
.disabled=${!this._newEncryptionKey}
variant="danger"
@@ -133,14 +136,14 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
</ha-button>
`
: html`
<ha-button slot="primaryAction" @click=${this._done}>
<ha-button @click=${this._done}>
${this.hass.localize(
"ui.panel.config.backup.dialogs.change_encryption_key.actions.done"
)}
</ha-button>
`}
</ha-dialog-footer>
</ha-wa-dialog>
</div>
</ha-md-dialog>
`;
}
@@ -284,8 +287,10 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
haStyle,
haStyleDialog,
css`
ha-wa-dialog {
--dialog-content-padding: var(--ha-space-2) var(--ha-space-6);
ha-md-dialog {
width: 90vw;
max-width: 560px;
--dialog-content-padding: 8px 24px;
}
ha-md-list {
background: none;
@@ -316,7 +321,14 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
flex: none;
margin: -16px;
}
@media all and (max-width: 450px), all and (max-height: 500px) {
ha-md-dialog {
max-width: none;
}
div[slot="content"] {
margin-top: 0;
}
}
p {
margin-top: 0;
}

View File

@@ -1,14 +1,15 @@
import { mdiClose, mdiContentCopy, mdiDownload } from "@mdi/js";
import type { CSSResultGroup } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { customElement, property, query, state } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event";
import { copyToClipboard } from "../../../../common/util/copy-clipboard";
import "../../../../components/ha-button";
import "../../../../components/ha-dialog-footer";
import "../../../../components/ha-dialog-header";
import "../../../../components/ha-icon-button";
import "../../../../components/ha-icon-button-prev";
import "../../../../components/ha-wa-dialog";
import "../../../../components/ha-md-dialog";
import type { HaMdDialog } from "../../../../components/ha-md-dialog";
import "../../../../components/ha-md-list";
import "../../../../components/ha-md-list-item";
import "../../../../components/ha-password-field";
@@ -23,82 +24,89 @@ import type { ShowBackupEncryptionKeyDialogParams } from "./show-dialog-show-bac
class DialogShowBackupEncryptionKey extends LitElement implements HassDialog {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _open = false;
@state() private _params?: ShowBackupEncryptionKeyDialogParams;
@query("ha-md-dialog") private _dialog!: HaMdDialog;
public showDialog(params: ShowBackupEncryptionKeyDialogParams): void {
this._params = params;
this._open = true;
}
public closeDialog() {
if (this._open) {
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
this._open = false;
this._params = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
return true;
}
private _closeDialog() {
this._dialog.close();
}
protected render() {
if (!this._params) {
return nothing;
}
return html`
<ha-wa-dialog
.hass=${this.hass}
.open=${this._open}
header-title=${this.hass.localize(
"ui.panel.config.backup.dialogs.show_encryption_key.title"
)}
@closed=${this.closeDialog}
>
<ha-icon-button
slot="headerNavigationIcon"
data-dialog="close"
.label=${this.hass.localize("ui.common.close")}
.path=${mdiClose}
></ha-icon-button>
<p>
${this.hass.localize(
"ui.panel.config.backup.dialogs.show_encryption_key.description"
)}
</p>
<div class="encryption-key">
<p>${this._params?.currentKey}</p>
<ha-md-dialog disable-cancel-action open @closed=${this.closeDialog}>
<ha-dialog-header slot="headline">
<ha-icon-button
.path=${mdiContentCopy}
@click=${this._copyKeyToClipboard}
slot="navigationIcon"
.label=${this.hass.localize("ui.common.close")}
.path=${mdiClose}
@click=${this._closeDialog}
></ha-icon-button>
<span slot="title">
${this.hass.localize(
"ui.panel.config.backup.dialogs.show_encryption_key.title"
)}
</span>
</ha-dialog-header>
<div slot="content">
<p>
${this.hass.localize(
"ui.panel.config.backup.dialogs.show_encryption_key.description"
)}
</p>
<div class="encryption-key">
<p>${this._params?.currentKey}</p>
<ha-icon-button
.path=${mdiContentCopy}
@click=${this._copyKeyToClipboard}
></ha-icon-button>
</div>
<ha-md-list>
<ha-md-list-item>
<span slot="headline">
${this.hass.localize(
"ui.panel.config.backup.encryption_key.download_emergency_kit"
)}
</span>
<span slot="supporting-text">
${this.hass.localize(
"ui.panel.config.backup.encryption_key.download_emergency_kit_description"
)}
</span>
<ha-button
size="small"
appearance="plain"
slot="end"
@click=${this._download}
>
<ha-svg-icon .path=${mdiDownload} slot="start"></ha-svg-icon>
${this.hass.localize(
"ui.panel.config.backup.encryption_key.download_emergency_kit_action"
)}
</ha-button>
</ha-md-list-item>
</ha-md-list>
</div>
<ha-md-list>
<ha-md-list-item>
<span slot="headline">
${this.hass.localize(
"ui.panel.config.backup.encryption_key.download_emergency_kit"
)}
</span>
<span slot="supporting-text">
${this.hass.localize(
"ui.panel.config.backup.encryption_key.download_emergency_kit_description"
)}
</span>
<ha-button slot="end" @click=${this._download}>
<ha-svg-icon .path=${mdiDownload} slot="start"></ha-svg-icon>
${this.hass.localize(
"ui.panel.config.backup.encryption_key.download_emergency_kit_action"
)}
</ha-button>
</ha-md-list-item>
</ha-md-list>
<ha-dialog-footer slot="footer">
<ha-button slot="primaryAction" @click=${this.closeDialog}>
<div slot="actions">
<ha-button @click=${this._closeDialog}>
${this.hass.localize("ui.common.close")}
</ha-button>
</ha-dialog-footer>
</ha-wa-dialog>
</div>
</ha-md-dialog>
`;
}
@@ -127,8 +135,10 @@ class DialogShowBackupEncryptionKey extends LitElement implements HassDialog {
haStyle,
haStyleDialog,
css`
ha-wa-dialog {
--dialog-content-padding: var(--ha-space-2) var(--ha-space-6);
ha-md-dialog {
width: 90vw;
max-width: 560px;
--dialog-content-padding: 8px 24px;
}
ha-md-list {
background: none;
@@ -159,7 +169,14 @@ class DialogShowBackupEncryptionKey extends LitElement implements HassDialog {
flex: none;
margin: -16px;
}
@media all and (max-width: 450px), all and (max-height: 500px) {
ha-md-dialog {
max-width: none;
}
div[slot="content"] {
margin-top: 0;
}
}
p {
margin-top: 0;
}

View File

@@ -1,7 +1,7 @@
import { mdiOpenInNew } from "@mdi/js";
import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, state } from "lit/decorators";
import { state } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event";
import { createCloseHeading } from "../../../../components/ha-dialog";
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
@@ -13,7 +13,6 @@ import type { WebhookDialogParams } from "./show-dialog-manage-cloudhook";
import "../../../../components/ha-button";
import "../../../../components/ha-copy-textfield";
@customElement("dialog-manage-cloudhook")
export class DialogManageCloudhook extends LitElement {
protected hass?: HomeAssistant;
@@ -156,3 +155,5 @@ declare global {
"dialog-manage-cloudhook": DialogManageCloudhook;
}
}
customElements.define("dialog-manage-cloudhook", DialogManageCloudhook);

View File

@@ -1138,20 +1138,23 @@ export class HaConfigDevicePage extends LitElement {
}
if (domains.includes("mqtt")) {
const mqtt =
await import("./device-detail/integration-elements/mqtt/device-actions");
const mqtt = await import(
"./device-detail/integration-elements/mqtt/device-actions"
);
const actions = mqtt.getMQTTDeviceActions(this, device);
deviceActions.push(...actions);
}
if (domains.includes("zha")) {
const zha =
await import("./device-detail/integration-elements/zha/device-actions");
const zha = await import(
"./device-detail/integration-elements/zha/device-actions"
);
const actions = await zha.getZHADeviceActions(this, this.hass, device);
deviceActions.push(...actions);
}
if (domains.includes("zwave_js")) {
const zwave =
await import("./device-detail/integration-elements/zwave_js/device-actions");
const zwave = await import(
"./device-detail/integration-elements/zwave_js/device-actions"
);
const actions = await zwave.getZwaveDeviceActions(
this,
this.hass,
@@ -1160,8 +1163,9 @@ export class HaConfigDevicePage extends LitElement {
deviceActions.push(...actions);
}
if (domains.includes("esphome")) {
const esphome =
await import("./device-detail/integration-elements/esphome/device-actions");
const esphome = await import(
"./device-detail/integration-elements/esphome/device-actions"
);
const actions = await esphome.getESPHomeDeviceActions(
this,
this.hass,
@@ -1170,8 +1174,9 @@ export class HaConfigDevicePage extends LitElement {
deviceActions.push(...actions);
}
if (domains.includes("matter")) {
const matter =
await import("./device-detail/integration-elements/matter/device-actions");
const matter = await import(
"./device-detail/integration-elements/matter/device-actions"
);
const defaultActions = matter.getMatterDeviceDefaultActions(
this,
this.hass,
@@ -1215,8 +1220,9 @@ export class HaConfigDevicePage extends LitElement {
).map((int) => int.domain);
if (domains.includes("zwave_js")) {
const zwave =
await import("./device-detail/integration-elements/zwave_js/device-alerts");
const zwave = await import(
"./device-detail/integration-elements/zwave_js/device-alerts"
);
const alerts = await zwave.getZwaveDeviceAlerts(this.hass, device);
deviceAlerts.push(...alerts);
@@ -1298,7 +1304,9 @@ export class HaConfigDevicePage extends LitElement {
`);
}
if (domains.includes("zwave_js")) {
import("./device-detail/integration-elements/zwave_js/ha-device-info-zwave_js");
import(
"./device-detail/integration-elements/zwave_js/ha-device-info-zwave_js"
);
deviceInfo.push(html`
<ha-device-info-zwave_js
.hass=${this.hass}
@@ -1307,7 +1315,9 @@ export class HaConfigDevicePage extends LitElement {
`);
}
if (domains.includes("matter")) {
import("./device-detail/integration-elements/matter/ha-device-info-matter");
import(
"./device-detail/integration-elements/matter/ha-device-info-matter"
);
deviceInfo.push(html`
<ha-device-info-matter
.hass=${this.hass}

View File

@@ -9,7 +9,6 @@ import "../../../../components/ha-dialog";
import "../../../../components/ha-formfield";
import "../../../../components/ha-radio";
import "../../../../components/ha-button";
import "../../../../components/ha-markdown";
import type { HaRadio } from "../../../../components/ha-radio";
import "../../../../components/ha-textfield";
import type { GasSourceTypeEnergyPreference } from "../../../../data/energy";
@@ -110,15 +109,6 @@ export class DialogEnergyGasSettings
? `${this.hass.config.currency}/${this._pickedDisplayUnit}`
: undefined;
const pickedUnitClass =
this._pickedDisplayUnit &&
this._energy_units?.includes(this._pickedDisplayUnit)
? "energy"
: this._pickedDisplayUnit &&
this._gas_units?.includes(this._pickedDisplayUnit)
? "volume"
: undefined;
const externalSource =
this._source.stat_energy_from &&
isExternalStatistic(this._source.stat_energy_from);
@@ -223,33 +213,9 @@ export class DialogEnergyGasSettings
.hass=${this.hass}
include-domains='["sensor", "input_number"]'
.value=${this._source.entity_energy_price}
.label=${this.hass.localize(
.label=${`${this.hass.localize(
"ui.panel.config.energy.gas.dialog.cost_entity_input"
)}
.helper=${pickedUnitClass
? html`<ha-markdown
.content=${this.hass.localize(
"ui.panel.config.energy.gas.dialog.cost_entity_helper",
pickedUnitClass === "energy"
? {
currency: this.hass.config.currency,
class: this.hass.localize(
"ui.panel.config.energy.gas.dialog.cost_entity_helper_energy"
),
unit1: "kWh",
unit2: "Wh",
}
: {
currency: this.hass.config.currency,
class: this.hass.localize(
"ui.panel.config.energy.gas.dialog.cost_entity_helper_volume"
),
unit1: "m³",
unit2: "ft³",
}
)}
></ha-markdown>`
: nothing}
)} ${unitPrice ? ` (${unitPrice})` : ""}`}
@value-changed=${this._priceEntityChanged}
></ha-entity-picker>`
: ""}

View File

@@ -9,7 +9,6 @@ import "../../../../components/ha-dialog";
import "../../../../components/ha-button";
import "../../../../components/ha-formfield";
import "../../../../components/ha-radio";
import "../../../../components/ha-markdown";
import type { HaRadio } from "../../../../components/ha-radio";
import "../../../../components/ha-textfield";
import type { WaterSourceTypeEnergyPreference } from "../../../../data/energy";
@@ -17,7 +16,11 @@ import {
emptyWaterEnergyPreference,
energyStatisticHelpUrl,
} from "../../../../data/energy";
import { isExternalStatistic } from "../../../../data/recorder";
import {
getDisplayUnit,
getStatisticMetadata,
isExternalStatistic,
} from "../../../../data/recorder";
import { getSensorDeviceClassConvertibleUnits } from "../../../../data/sensor";
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
import { haStyle, haStyleDialog } from "../../../../resources/styles";
@@ -37,6 +40,8 @@ export class DialogEnergyWaterSettings
@state() private _costs?: "no-costs" | "number" | "entity" | "statistic";
@state() private _pickedDisplayUnit?: string | null;
@state() private _water_units?: string[];
@state() private _error?: string;
@@ -50,6 +55,11 @@ export class DialogEnergyWaterSettings
this._source = params.source
? { ...params.source }
: emptyWaterEnergyPreference();
this._pickedDisplayUnit = getDisplayUnit(
this.hass,
params.source?.stat_energy_from,
params.metadata
);
this._costs = this._source.entity_energy_price
? "entity"
: this._source.number_energy_price
@@ -69,6 +79,7 @@ export class DialogEnergyWaterSettings
this._params = undefined;
this._source = undefined;
this._error = undefined;
this._pickedDisplayUnit = undefined;
this._excludeList = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
return true;
@@ -81,6 +92,10 @@ export class DialogEnergyWaterSettings
const pickableUnit = this._water_units?.join(", ") || "";
const unitPriceSensor = this._pickedDisplayUnit
? `${this.hass.config.currency}/${this._pickedDisplayUnit}`
: undefined;
const unitPriceFixed = `${this.hass.config.currency}/${
this.hass.config.unit_system.volume === "gal" ? "gal" : "m³"
}`;
@@ -187,15 +202,9 @@ export class DialogEnergyWaterSettings
.hass=${this.hass}
include-domains='["sensor", "input_number"]'
.value=${this._source.entity_energy_price}
.label=${this.hass.localize(
.label=${`${this.hass.localize(
"ui.panel.config.energy.water.dialog.cost_entity_input"
)}
.helper=${html`<ha-markdown
.content=${this.hass.localize(
"ui.panel.config.energy.water.dialog.cost_entity_helper",
{ currency: this.hass.config.currency }
)}
></ha-markdown>`}
)}${unitPriceSensor ? ` (${unitPriceSensor})` : ""}`}
@value-changed=${this._priceEntityChanged}
></ha-entity-picker>`
: ""}
@@ -278,6 +287,16 @@ export class DialogEnergyWaterSettings
}
private async _statisticChanged(ev: CustomEvent<{ value: string }>) {
if (ev.detail.value) {
const metadata = await getStatisticMetadata(this.hass, [ev.detail.value]);
this._pickedDisplayUnit = getDisplayUnit(
this.hass,
ev.detail.value,
metadata[0]
);
} else {
this._pickedDisplayUnit = undefined;
}
if (isExternalStatistic(ev.detail.value) && this._costs !== "statistic") {
this._costs = "no-costs";
}

View File

@@ -1012,6 +1012,7 @@ export class EntityRegistrySettingsEditor extends LitElement {
? html`<ha-area-picker
.hass=${this.hass}
.value=${this._areaId}
.placeholder=${this._device?.area_id}
.disabled=${this.disabled}
@value-changed=${this._areaPicked}
></ha-area-picker>`
@@ -1525,17 +1526,17 @@ export class EntityRegistrySettingsEditor extends LitElement {
}
ha-textfield.entityId ha-icon-button {
position: relative;
right: calc(var(--ha-space-2) * -1);
right: -8px;
--mdc-icon-button-size: 36px;
--mdc-icon-size: 20px;
color: var(--secondary-text-color);
inset-inline-start: initial;
inset-inline-end: calc(var(--ha-space-2) * -1);
inset-inline-end: -8px;
direction: var(--direction);
}
ha-switch {
margin-right: var(--ha-space-4);
margin-inline-end: var(--ha-space-4);
margin-right: 16px;
margin-inline-end: 16px;
margin-inline-start: initial;
}
ha-settings-row ha-switch {
@@ -1548,7 +1549,7 @@ export class EntityRegistrySettingsEditor extends LitElement {
ha-select,
ha-area-picker {
display: block;
margin: var(--ha-space-2) 0;
margin: 8px 0;
width: 100%;
}
li[divider] {

View File

@@ -244,15 +244,14 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
display: block;
}
.container {
padding: var(--ha-space-2) var(--ha-space-6) var(--ha-space-5)
var(--ha-space-6);
padding: 8px 24px 20px 24px;
}
.buttons {
box-sizing: border-box;
display: flex;
padding: var(--ha-space-4);
padding: 16px;
justify-content: space-between;
padding-bottom: max(var(--safe-area-inset-bottom), var(--ha-space-4));
padding-bottom: max(var(--safe-area-inset-bottom), 16px);
background-color: var(--mdc-theme-surface, #fff);
border-top: 1px solid var(--divider-color);
position: sticky;

View File

@@ -116,10 +116,8 @@ import { showAddIntegrationDialog } from "../integrations/show-add-integration-d
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
import { slugify } from "../../../common/string/slugify";
export interface StateEntity extends Omit<
EntityRegistryEntry,
"id" | "unique_id"
> {
export interface StateEntity
extends Omit<EntityRegistryEntry, "id" | "unique_id"> {
readonly?: boolean;
selectable?: boolean;
id?: string;

View File

@@ -530,7 +530,9 @@ class HaPanelConfig extends SubscribeMixin(HassRouterPage) {
zha: {
tag: "zha-config-dashboard-router",
load: () =>
import("./integrations/integration-panels/zha/zha-config-dashboard-router"),
import(
"./integrations/integration-panels/zha/zha-config-dashboard-router"
),
},
mqtt: {
tag: "mqtt-config-panel",
@@ -540,22 +542,30 @@ class HaPanelConfig extends SubscribeMixin(HassRouterPage) {
zwave_js: {
tag: "zwave_js-config-router",
load: () =>
import("./integrations/integration-panels/zwave_js/zwave_js-config-router"),
import(
"./integrations/integration-panels/zwave_js/zwave_js-config-router"
),
},
matter: {
tag: "matter-config-panel",
load: () =>
import("./integrations/integration-panels/matter/matter-config-panel"),
import(
"./integrations/integration-panels/matter/matter-config-panel"
),
},
thread: {
tag: "thread-config-panel",
load: () =>
import("./integrations/integration-panels/thread/thread-config-panel"),
import(
"./integrations/integration-panels/thread/thread-config-panel"
),
},
bluetooth: {
tag: "bluetooth-config-dashboard-router",
load: () =>
import("./integrations/integration-panels/bluetooth/bluetooth-config-dashboard-router"),
import(
"./integrations/integration-panels/bluetooth/bluetooth-config-dashboard-router"
),
},
dhcp: {
tag: "dhcp-config-panel",
@@ -570,7 +580,9 @@ class HaPanelConfig extends SubscribeMixin(HassRouterPage) {
zeroconf: {
tag: "zeroconf-config-panel",
load: () =>
import("./integrations/integration-panels/zeroconf/zeroconf-config-panel"),
import(
"./integrations/integration-panels/zeroconf/zeroconf-config-panel"
),
},
application_credentials: {
tag: "ha-config-application-credentials",

View File

@@ -1,7 +1,7 @@
import type { CSSResultGroup } from "lit";
import { html, LitElement, nothing } from "lit";
import memoizeOne from "memoize-one";
import { customElement, property, state } from "lit/decorators";
import { property, state } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event";
import { createCloseHeading } from "../../../../components/ha-dialog";
import "../../../../components/ha-form/ha-form";
@@ -14,7 +14,6 @@ import type {
} from "./show-dialog-schedule-block-info";
import type { SchemaUnion } from "../../../../components/ha-form/types";
@customElement("dialog-schedule-block-info")
class DialogScheduleBlockInfo extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -157,3 +156,5 @@ declare global {
"dialog-schedule-block-info": DialogScheduleBlockInfo;
}
}
customElements.define("dialog-schedule-block-info", DialogScheduleBlockInfo);

View File

@@ -336,7 +336,9 @@ class HaConfigIntegrationsDashboard extends KeyboardShortcutMixin(
super.firstUpdated(changed);
this._fetchManifests();
this._fetchEntitySources();
this._handleRouteChanged();
if (this.route.path === "/add") {
this._handleAdd();
}
this._scanUSBDevices();
this._scanImprovDevices();
@@ -353,9 +355,6 @@ class HaConfigIntegrationsDashboard extends KeyboardShortcutMixin(
protected updated(changed: PropertyValues) {
super.updated(changed);
if (changed.has("route")) {
this._handleRouteChanged();
}
if (
(this._searchParms.has("config_entry") ||
this._searchParms.has("domain")) &&
@@ -814,13 +813,10 @@ class HaConfigIntegrationsDashboard extends KeyboardShortcutMixin(
}
}
private async _handleRouteChanged() {
if (this.route?.path !== "/add") {
return;
}
private async _handleAdd() {
const brand = extractSearchParam("brand");
const domain = extractSearchParam("domain");
navigate("/config/integrations/dashboard/", { replace: true });
navigate("/config/integrations", { replace: true });
if (brand) {
showAddIntegrationDialog(this, {

View File

@@ -1,6 +1,6 @@
import type { CSSResultGroup, PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { property, state } from "lit/decorators";
import { stopPropagation } from "../../../../../common/dom/stop_propagation";
import "../../../../../components/buttons/ha-call-service-button";
import "../../../../../components/ha-card";
@@ -15,7 +15,6 @@ import type { HomeAssistant } from "../../../../../types";
import { formatAsPaddedHex } from "./functions";
import type { IssueCommandServiceData } from "./types";
@customElement("zha-cluster-commands")
export class ZHAClusterCommands extends LitElement {
@property({ attribute: false }) public hass?: HomeAssistant;
@@ -260,3 +259,5 @@ declare global {
"zha-cluster-commands": ZHAClusterCommands;
}
}
customElements.define("zha-cluster-commands", ZHAClusterCommands);

View File

@@ -192,9 +192,9 @@ export class ZHAGroupBindingControl extends LitElement {
private get _canBind(): boolean {
return Boolean(
this._groupToBind &&
this._clustersToBind &&
this._clustersToBind?.length > 0 &&
this.device
this._clustersToBind &&
this._clustersToBind?.length > 0 &&
this.device
);
}

View File

@@ -1,7 +1,7 @@
import { mdiClose, mdiContentCopy } from "@mdi/js";
import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { property, state } from "lit/decorators";
import { fireEvent } from "../../../common/dom/fire_event";
import { copyToClipboard } from "../../../common/util/copy-clipboard";
import "../../../components/ha-alert";
@@ -26,7 +26,6 @@ import { showToast } from "../../../util/toast";
import type { SystemLogDetailDialogParams } from "./show-dialog-system-log-detail";
import { formatSystemLogTime } from "./util";
@customElement("dialog-system-log-detail")
class DialogSystemLogDetail extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -273,3 +272,5 @@ declare global {
"dialog-system-log-detail": DialogSystemLogDetail;
}
}
customElements.define("dialog-system-log-detail", DialogSystemLogDetail);

View File

@@ -1,7 +1,7 @@
import { mdiPencil } from "@mdi/js";
import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/entity/ha-entities-picker";
@@ -43,7 +43,6 @@ const cropOptions: CropOptions = {
aspectRatio: 1,
};
@customElement("dialog-person-detail")
class DialogPersonDetail extends LitElement implements HassDialog {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -568,3 +567,5 @@ declare global {
"dialog-person-detail": DialogPersonDetail;
}
}
customElements.define("dialog-person-detail", DialogPersonDetail);

View File

@@ -24,7 +24,7 @@ import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { computeCssColor } from "../../../common/color/compute-color";
import { formatShortDateTimeWithConditionalYear } from "../../../common/datetime/format_date_time";
import { formatShortDateTime } from "../../../common/datetime/format_date_time";
import { relativeTime } from "../../../common/datetime/relative_time";
import { storage } from "../../../common/decorators/storage";
import type { HASSDomEvent } from "../../../common/dom/fire_event";
@@ -301,21 +301,10 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
const date = new Date(scene.state);
const now = new Date();
const dayDifference = differenceInDays(now, date);
const formattedTime = formatShortDateTimeWithConditionalYear(
date,
this.hass.locale,
this.hass.config
);
const elementId = "last-activated-" + slugify(scene.entity_id);
return html`
${dayDifference > 3
? formattedTime
: html`
<ha-tooltip for=${elementId}>${formattedTime}</ha-tooltip>
<span id=${elementId}
>${relativeTime(date, this.hass.locale)}</span
>
`}
? formatShortDateTime(date, this.hass.locale, this.hass.config)
: relativeTime(date, this.hass.locale)}
`;
},
},

View File

@@ -1,4 +1,3 @@
import "@home-assistant/webawesome/dist/components/divider/divider";
import { consume } from "@lit/context";
import {
mdiAppleKeyboardCommand,
@@ -22,21 +21,20 @@ import {
} from "@mdi/js";
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { UndoRedoController } from "../../../common/controllers/undo-redo-controller";
import { transform } from "../../../common/decorators/transform";
import { fireEvent } from "../../../common/dom/fire_event";
import { goBack, navigate } from "../../../common/navigate";
import { slugify } from "../../../common/string/slugify";
import { promiseTimeout } from "../../../common/util/promise-timeout";
import { afterNextRender } from "../../../common/util/render-status";
import "../../../components/ha-button";
import "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
import "../../../components/ha-button-menu";
import "../../../components/ha-fab";
import { transform } from "../../../common/decorators/transform";
import "../../../components/ha-icon-button";
import "../../../components/ha-list-item";
import "../../../components/ha-svg-icon";
import "../../../components/ha-yaml-editor";
import { substituteBlueprint } from "../../../data/blueprint";
@@ -67,6 +65,7 @@ import "../../../layouts/hass-subpage";
import { KeyboardShortcutMixin } from "../../../mixins/keyboard-shortcut-mixin";
import { PreventUnsavedMixin } from "../../../mixins/prevent-unsaved-mixin";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { UndoRedoController } from "../../../common/controllers/undo-redo-controller";
import { haStyle } from "../../../resources/styles";
import type { Entries, HomeAssistant, Route } from "../../../types";
import { isMac } from "../../../util/is_mac";
@@ -74,13 +73,12 @@ import { showToast } from "../../../util/toast";
import { showAutomationModeDialog } from "../automation/automation-mode-dialog/show-dialog-automation-mode";
import type { EntityRegistryUpdate } from "../automation/automation-save-dialog/show-dialog-automation-save";
import { showAutomationSaveDialog } from "../automation/automation-save-dialog/show-dialog-automation-save";
import { showAutomationSaveTimeoutDialog } from "../automation/automation-save-timeout-dialog/show-dialog-automation-save-timeout";
import { showAssignCategoryDialog } from "../category/show-dialog-assign-category";
import "./blueprint-script-editor";
import "./manual-script-editor";
import type { HaManualScriptEditor } from "./manual-script-editor";
import { showAutomationSaveTimeoutDialog } from "../automation/automation-save-timeout-dialog/show-dialog-automation-save-timeout";
@customElement("ha-script-editor")
export class HaScriptEditor extends SubscribeMixin(
PreventUnsavedMixin(KeyboardShortcutMixin(LitElement))
) {
@@ -242,10 +240,7 @@ export class HaScriptEditor extends SubscribeMixin(
</ha-button>
`
: ""}
<ha-dropdown
slot="toolbar-icon"
@wa-select=${this._handleDropdownSelect}
>
<ha-button-menu slot="toolbar-icon">
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
@@ -253,107 +248,133 @@ export class HaScriptEditor extends SubscribeMixin(
></ha-icon-button>
${this._mode === "gui" && this.narrow
? html`<ha-dropdown-item
value="undo"
? html`<ha-list-item
graphic="icon"
@click=${this._undo}
.disabled=${!this._undoRedoController.canUndo}
>
${this.hass.localize("ui.common.undo")}
<ha-svg-icon slot="icon" .path=${mdiUndo}></ha-svg-icon>
</ha-dropdown-item>
<ha-dropdown-item
value="redo"
<ha-svg-icon slot="graphic" .path=${mdiUndo}></ha-svg-icon>
</ha-list-item>
<ha-list-item
graphic="icon"
@click=${this._redo}
.disabled=${!this._undoRedoController.canRedo}
>
${this.hass.localize("ui.common.redo")}
<ha-svg-icon slot="icon" .path=${mdiRedo}></ha-svg-icon>
</ha-dropdown-item>`
<ha-svg-icon slot="graphic" .path=${mdiRedo}></ha-svg-icon>
</ha-list-item>`
: nothing}
<ha-dropdown-item .disabled=${!this.scriptId} value="info">
<ha-list-item
graphic="icon"
.disabled=${!this.scriptId}
@click=${this._showInfo}
>
${this.hass.localize("ui.panel.config.script.editor.show_info")}
<ha-svg-icon
slot="icon"
slot="graphic"
.path=${mdiInformationOutline}
></ha-svg-icon>
</ha-dropdown-item>
</ha-list-item>
<ha-dropdown-item .disabled=${!stateObj} value="settings">
<ha-list-item
graphic="icon"
.disabled=${!stateObj}
@click=${this._showSettings}
>
${this.hass.localize(
"ui.panel.config.automation.picker.show_settings"
)}
<ha-svg-icon slot="icon" .path=${mdiCog}></ha-svg-icon>
</ha-dropdown-item>
<ha-svg-icon slot="graphic" .path=${mdiCog}></ha-svg-icon>
</ha-list-item>
<ha-dropdown-item .disabled=${!stateObj} value="category">
<ha-list-item
graphic="icon"
.disabled=${!stateObj}
@click=${this._editCategory}
>
${this.hass.localize(
`ui.panel.config.scene.picker.${this._registryEntry?.categories?.script ? "edit_category" : "assign_category"}`
)}
<ha-svg-icon slot="icon" .path=${mdiTag}></ha-svg-icon>
</ha-dropdown-item>
<ha-svg-icon slot="graphic" .path=${mdiTag}></ha-svg-icon>
</ha-list-item>
<ha-dropdown-item .disabled=${!this.scriptId} value="run">
<ha-list-item
graphic="icon"
.disabled=${!this.scriptId}
@click=${this._runScript}
>
${this.hass.localize("ui.panel.config.script.picker.run_script")}
<ha-svg-icon slot="icon" .path=${mdiPlay}></ha-svg-icon>
</ha-dropdown-item>
<ha-svg-icon slot="graphic" .path=${mdiPlay}></ha-svg-icon>
</ha-list-item>
${this.scriptId && this.narrow
? html`<ha-dropdown-item value="trace">
${this.hass.localize(
"ui.panel.config.automation.editor.show_trace"
)}
<ha-svg-icon
slot="icon"
.path=${mdiTransitConnection}
></ha-svg-icon>
</ha-dropdown-item>`
? html`
<a href="/config/script/trace/${this.scriptId}">
<ha-list-item graphic="icon">
${this.hass.localize(
"ui.panel.config.script.editor.show_trace"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiTransitConnection}
></ha-svg-icon>
</ha-list-item>
</a>
`
: nothing}
${!useBlueprint && !("fields" in this._config)
? html`
<ha-dropdown-item
<ha-list-item
graphic="icon"
.disabled=${this._readOnly || this._mode === "yaml"}
value="add_fields"
@click=${this._addFields}
>
${this.hass.localize(
"ui.panel.config.script.editor.field.add_fields"
)}
<ha-svg-icon
slot="icon"
slot="graphic"
.path=${mdiFormTextbox}
></ha-svg-icon>
</ha-dropdown-item>
</ha-list-item>
`
: nothing}
<ha-dropdown-item
value="rename"
<ha-list-item
graphic="icon"
@click=${this._promptScriptAlias}
.disabled=${!this.scriptId ||
this._readOnly ||
this._mode === "yaml"}
>
${this.hass.localize("ui.panel.config.script.editor.rename")}
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
</ha-dropdown-item>
<ha-svg-icon slot="graphic" .path=${mdiRenameBox}></ha-svg-icon>
</ha-list-item>
${!useBlueprint
? html`
<ha-dropdown-item
value="change_mode"
<ha-list-item
graphic="icon"
@click=${this._promptScriptMode}
.disabled=${this._readOnly || this._mode === "yaml"}
>
${this.hass.localize(
"ui.panel.config.script.editor.change_mode"
)}
<ha-svg-icon
slot="icon"
slot="graphic"
.path=${mdiDebugStepOver}
></ha-svg-icon>
</ha-dropdown-item>
</ha-list-item>
`
: nothing}
<ha-dropdown-item
.disabled=${!!this._blueprintConfig ||
<ha-list-item
.disabled=${this._blueprintConfig ||
(!this._readOnly && !this.scriptId)}
value="duplicate"
graphic="icon"
@click=${this._duplicate}
>
${this.hass.localize(
this._readOnly
@@ -361,48 +382,58 @@ export class HaScriptEditor extends SubscribeMixin(
: "ui.panel.config.script.editor.duplicate"
)}
<ha-svg-icon
slot="icon"
slot="graphic"
.path=${mdiPlusCircleMultipleOutline}
></ha-svg-icon>
</ha-dropdown-item>
</ha-list-item>
${useBlueprint
? html`
<ha-dropdown-item
value="take_control"
<ha-list-item
graphic="icon"
@click=${this._takeControl}
.disabled=${this._readOnly}
>
${this.hass.localize(
"ui.panel.config.script.editor.take_control"
)}
<ha-svg-icon slot="icon" .path=${mdiFileEdit}></ha-svg-icon>
</ha-dropdown-item>
<ha-svg-icon
slot="graphic"
.path=${mdiFileEdit}
></ha-svg-icon>
</ha-list-item>
`
: nothing}
<ha-dropdown-item value="toggle_yaml_mode">
<ha-list-item
graphic="icon"
@click=${this._mode === "gui"
? this._switchYamlMode
: this._switchUiMode}
>
${this.hass.localize(
`ui.panel.config.automation.editor.edit_${this._mode === "gui" ? "yaml" : "ui"}`
)}
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
</ha-dropdown-item>
<ha-svg-icon slot="graphic" .path=${mdiPlaylistEdit}></ha-svg-icon>
</ha-list-item>
<wa-divider></wa-divider>
<li divider role="separator"></li>
<ha-dropdown-item
<ha-list-item
.disabled=${this._readOnly || !this.scriptId}
value="delete"
.variant=${this.scriptId ? "danger" : "default"}
class=${classMap({ warning: Boolean(this.scriptId) })}
graphic="icon"
@click=${this._deleteConfirm}
>
${this.hass.localize("ui.panel.config.script.picker.delete")}
<ha-svg-icon
class=${classMap({ warning: Boolean(this.scriptId) })}
slot="icon"
slot="graphic"
.path=${mdiDelete}
>
</ha-svg-icon>
</ha-dropdown-item>
</ha-dropdown>
</ha-list-item>
</ha-button-menu>
<div class=${this._mode === "yaml" ? "yaml-mode" : ""}>
${this._mode === "gui"
? html`
@@ -656,7 +687,9 @@ export class HaScriptEditor extends SubscribeMixin(
this._dirty = true;
}
private async _runScript() {
private async _runScript(ev: CustomEvent) {
ev.stopPropagation();
if (hasScriptFields(this.hass, this._entityId!)) {
showMoreInfoDialog(this, {
entityId: this._entityId!,
@@ -1121,63 +1154,6 @@ export class HaScriptEditor extends SubscribeMixin(
this._undoRedoController.redo();
}
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
return;
}
switch (action) {
case "undo":
this._undo();
break;
case "redo":
this._redo();
break;
case "info":
this._showInfo();
break;
case "settings":
this._showSettings();
break;
case "category":
this._editCategory();
break;
case "run":
this._runScript();
break;
case "add_fields":
this._addFields();
break;
case "rename":
this._promptScriptAlias();
break;
case "change_mode":
this._promptScriptMode();
break;
case "duplicate":
this._duplicate();
break;
case "take_control":
this._takeControl();
break;
case "toggle_yaml_mode":
if (this._mode === "gui") {
this._switchYamlMode();
break;
}
this._switchUiMode();
break;
case "delete":
this._deleteConfirm();
break;
case "trace":
this._showTrace();
break;
}
}
static get styles(): CSSResultGroup {
return [
haStyle,
@@ -1268,6 +1244,9 @@ export class HaScriptEditor extends SubscribeMixin(
ha-fab.dirty {
bottom: calc(16px + var(--safe-area-inset-bottom, 0px));
}
li[role="separator"] {
border-bottom-color: var(--divider-color);
}
.header {
display: flex;
margin: 16px 0;
@@ -1281,6 +1260,10 @@ export class HaScriptEditor extends SubscribeMixin(
.header a {
color: var(--secondary-text-color);
}
ha-button-menu a {
text-decoration: none;
color: var(--primary-color);
}
ha-tooltip ha-svg-icon {
width: 12px;
}
@@ -1295,6 +1278,8 @@ export class HaScriptEditor extends SubscribeMixin(
}
}
customElements.define("ha-script-editor", HaScriptEditor);
declare global {
interface HTMLElementTagNameMap {
"ha-script-editor": HaScriptEditor;

View File

@@ -15,19 +15,18 @@ import type { LocalizeKeys } from "../../../common/translations/localize";
import "../../../components/ha-automation-row";
import type { HaAutomationRow } from "../../../components/ha-automation-row";
import "../../../components/ha-card";
import "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
import "../../../components/ha-md-button-menu";
import "../../../components/ha-md-menu-item";
import type { ScriptFieldSidebarConfig } from "../../../data/automation";
import type { Field } from "../../../data/script";
import { SELECTOR_SELECTOR_BUILDING_BLOCKS } from "../../../data/selector/selector_selector";
import { haStyle } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types";
import { isMac } from "../../../util/is_mac";
import { showToast } from "../../../util/toast";
import { indentStyle, overflowStyles } from "../automation/styles";
import "./ha-script-field-selector-editor";
import type HaScriptFieldSelectorEditor from "./ha-script-field-selector-editor";
import { showToast } from "../../../util/toast";
@customElement("ha-script-field-row")
export default class HaScriptFieldRow extends LitElement {
@@ -80,33 +79,36 @@ export default class HaScriptFieldRow extends LitElement {
.highlight=${this.highlight}
@delete-row=${this._onDelete}
>
<ha-dropdown
<ha-md-button-menu
quick
slot="icons"
@click=${preventDefaultStopPropagation}
@keydown=${stopPropagation}
@wa-select=${this._handleDropdownSelect}
placement="bottom-end"
@closed=${stopPropagation}
positioning="fixed"
anchor-corner="end-end"
menu-corner="start-end"
>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-dropdown-item value="toggle_yaml_mode">
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
<ha-md-menu-item .clickAction=${this._toggleYamlMode}>
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
`ui.panel.config.automation.editor.edit_${!this._yamlMode ? "yaml" : "ui"}`
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>
<ha-dropdown-item
value="delete"
</ha-md-menu-item>
<ha-md-menu-item
.clickAction=${this._onDelete}
.disabled=${this.disabled}
variant="danger"
class="warning"
>
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete"
@@ -116,6 +118,7 @@ export default class HaScriptFieldRow extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -131,8 +134,8 @@ export default class HaScriptFieldRow extends LitElement {
</span>`
: nothing}
</div>
</ha-dropdown-item>
</ha-dropdown>
</ha-md-menu-item>
</ha-md-button-menu>
<h3 slot="header">${this.key}</h3>
@@ -167,21 +170,27 @@ export default class HaScriptFieldRow extends LitElement {
"ui.panel.config.script.editor.field.selector"
)}
</h3>
<ha-dropdown
<ha-md-button-menu
quick
slot="icons"
@click=${preventDefaultStopPropagation}
@keydown=${stopPropagation}
@wa-select=${this._handleDropdownSelect}
placement="bottom-end"
@closed=${stopPropagation}
positioning="fixed"
anchor-corner="end-end"
menu-corner="start-end"
>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-dropdown-item value="toggle_yaml_mode" selector-row>
<ha-md-menu-item
.clickAction=${this._toggleYamlMode}
selector-row
>
<ha-svg-icon
slot="icon"
slot="start"
.path=${mdiPlaylistEdit}
></ha-svg-icon>
<div class="overflow-label">
@@ -192,13 +201,16 @@ export default class HaScriptFieldRow extends LitElement {
class="shortcut-placeholder ${isMac ? "mac" : ""}"
></span>
</div>
</ha-dropdown-item>
<ha-dropdown-item
value="delete"
</ha-md-menu-item>
<ha-md-menu-item
.clickAction=${this._onDelete}
.disabled=${this.disabled}
variant="danger"
class="warning"
>
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
<ha-svg-icon
slot="start"
.path=${mdiDelete}
></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete"
@@ -208,6 +220,7 @@ export default class HaScriptFieldRow extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -223,8 +236,8 @@ export default class HaScriptFieldRow extends LitElement {
</span>`
: nothing}
</div>
</ha-dropdown-item>
</ha-dropdown>
</ha-md-menu-item>
</ha-md-button-menu>
</ha-automation-row>
</ha-card>
${typeof this.field.selector === "object" &&
@@ -407,23 +420,6 @@ export default class HaScriptFieldRow extends LitElement {
this._selectorRowElement?.focus();
}
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
return;
}
switch (action) {
case "toggle_yaml_mode":
this._toggleYamlMode(ev.target as HTMLElement);
break;
case "delete":
this._onDelete();
break;
}
}
static get styles(): CSSResultGroup {
return [
haStyle,
@@ -481,6 +477,9 @@ export default class HaScriptFieldRow extends LitElement {
.selected_menu_item {
color: var(--primary-color);
}
li[role="separator"] {
border-bottom-color: var(--divider-color);
}
.selector-row {
padding-top: 12px;
padding-bottom: 16px;

View File

@@ -33,8 +33,6 @@ import type { HASSDomEvent } from "../../../common/dom/fire_event";
import { fireEvent } from "../../../common/dom/fire_event";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { navigate } from "../../../common/navigate";
import { slugify } from "../../../common/string/slugify";
import "../../../components/ha-tooltip";
import type { LocalizeFunc } from "../../../common/translations/localize";
import {
hasRejectedItems,
@@ -304,27 +302,19 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
sortable: true,
title: localize("ui.card.automation.last_triggered"),
template: (script) => {
if (!script.last_triggered) {
return this.hass.localize("ui.components.relative_time.never");
}
const date = new Date(script.last_triggered);
const now = new Date();
const dayDifference = differenceInDays(now, date);
const formattedTime = formatShortDateTimeWithConditionalYear(
date,
this.hass.locale,
this.hass.config
);
const elementId = "last-triggered-" + slugify(script.entity_id);
return html`
${dayDifference > 3
? formattedTime
: html`
<ha-tooltip for=${elementId}>${formattedTime}</ha-tooltip>
<span id=${elementId}
>${relativeTime(date, this.hass.locale)}</span
>
`}
${script.last_triggered
? dayDifference > 3
? formatShortDateTimeWithConditionalYear(
date,
this.hass.locale,
this.hass.config
)
: relativeTime(date, this.hass.locale)
: this.hass.localize("ui.components.relative_time.never")}
`;
},
},

View File

@@ -1,4 +1,3 @@
import "@home-assistant/webawesome/dist/components/divider/divider";
import {
mdiDotsVertical,
mdiDownload,
@@ -9,19 +8,17 @@ import {
mdiRefresh,
} from "@mdi/js";
import type { CSSResultGroup, TemplateResult } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { repeat } from "lit/directives/repeat";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { formatDateTimeWithSeconds } from "../../../common/datetime/format_date_time";
import { fireEvent } from "../../../common/dom/fire_event";
import { navigate } from "../../../common/navigate";
import "../../../components/ha-button-menu";
import "../../../components/ha-button";
import "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
import "../../../components/ha-icon-button";
import "../../../components/ha-list-item";
import "../../../components/trace/ha-trace-blueprint-config";
import "../../../components/trace/ha-trace-config";
import "../../../components/trace/ha-trace-logbook";
@@ -107,7 +104,7 @@ export class HaScriptTrace extends LitElement {
? html`
<ha-button
class="trace-link"
@click=${this._navigateToScript}
href="/config/script/edit/${this.scriptId}"
slot="toolbar-icon"
appearance="plain"
>
@@ -116,49 +113,64 @@ export class HaScriptTrace extends LitElement {
)}
</ha-button>
`
: nothing}
: ""}
<ha-dropdown
slot="toolbar-icon"
@wa-select=${this._handleDropdownSelect}
>
<ha-button-menu slot="toolbar-icon">
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-dropdown-item .disabled=${!stateObj} value="show_info">
<ha-list-item
graphic="icon"
.disabled=${!stateObj}
@click=${this._showInfo}
>
${this.hass.localize("ui.panel.config.script.editor.show_info")}
<ha-svg-icon
slot="icon"
slot="graphic"
.path=${mdiInformationOutline}
></ha-svg-icon>
</ha-dropdown-item>
</ha-list-item>
${this.narrow && this.scriptId
? html`<ha-dropdown-item value="edit_script">
${this.hass.localize(
"ui.panel.config.script.trace.edit_script"
)}
<ha-svg-icon slot="icon" .path=${mdiPencil}></ha-svg-icon>
</ha-dropdown-item> `
: nothing}
? html`
<a
class="trace-link"
href="/config/script/edit/${this.scriptId}"
>
<ha-list-item graphic="icon">
${this.hass.localize(
"ui.panel.config.script.trace.edit_script"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiPencil}
></ha-svg-icon>
</ha-list-item>
</a>
`
: ""}
<wa-divider></wa-divider>
<li divider role="separator"></li>
<ha-dropdown-item value="refresh">
<ha-list-item graphic="icon" @click=${this._refreshTraces}>
${this.hass.localize("ui.panel.config.automation.trace.refresh")}
<ha-svg-icon slot="icon" .path=${mdiRefresh}></ha-svg-icon>
</ha-dropdown-item>
<ha-svg-icon slot="graphic" .path=${mdiRefresh}></ha-svg-icon>
</ha-list-item>
<ha-dropdown-item .disabled=${!this._trace} value="download_trace">
<ha-list-item
graphic="icon"
.disabled=${!this._trace}
@click=${this._downloadTrace}
>
${this.hass.localize(
"ui.panel.config.automation.trace.download_trace"
)}
<ha-svg-icon slot="icon" .path=${mdiDownload}></ha-svg-icon>
</ha-dropdown-item>
</ha-dropdown>
<ha-svg-icon slot="graphic" .path=${mdiDownload}></ha-svg-icon>
</ha-list-item>
</ha-button-menu>
<div class="toolbar">
${this._traces && this._traces.length > 0
@@ -518,35 +530,6 @@ export class HaScriptTrace extends LitElement {
fireEvent(this, "hass-more-info", { entityId: this._entityId });
}
private _navigateToScript() {
if (this.scriptId) {
navigate(`/config/script/edit/${this.scriptId}`);
}
}
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
return;
}
switch (action) {
case "show_info":
this._showInfo();
break;
case "refresh":
this._refreshTraces();
break;
case "download_trace":
this._downloadTrace();
break;
case "edit_script":
this._navigateToScript();
break;
}
}
static get styles(): CSSResultGroup {
return [
haStyle,

View File

@@ -1,6 +1,6 @@
import { mdiHelpCircle } from "@mdi/js";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../common/dom/fire_event";
import { isEmptyEntityDomainFilter } from "../../../common/entity/entity_domain_filter";
@@ -20,7 +20,6 @@ import {
import type { HomeAssistant } from "../../../types";
import { brandsUrl } from "../../../util/brands-url";
@customElement("cloud-alexa-pref")
export class CloudAlexaPref extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -298,3 +297,5 @@ declare global {
"cloud-alexa-pref": CloudAlexaPref;
}
}
customElements.define("cloud-alexa-pref", CloudAlexaPref);

View File

@@ -1,6 +1,6 @@
import { mdiHelpCircle } from "@mdi/js";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../common/dom/fire_event";
import { isEmptyEntityDomainFilter } from "../../../common/entity/entity_domain_filter";
@@ -23,7 +23,6 @@ import type { HomeAssistant } from "../../../types";
import { brandsUrl } from "../../../util/brands-url";
import { showSaveSuccessToast } from "../../../util/toast-saved-success";
@customElement("cloud-google-pref")
export class CloudGooglePref extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -368,3 +367,5 @@ declare global {
"cloud-google-pref": CloudGooglePref;
}
}
customElements.define("cloud-google-pref", CloudGooglePref);

View File

@@ -1,6 +1,6 @@
import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../common/dom/fire_event";
import { createCloseHeading } from "../../../components/ha-dialog";
@@ -19,7 +19,6 @@ const SCHEMA = [
},
];
@customElement("dialog-home-zone-detail")
class DialogHomeZoneDetail extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -152,3 +151,5 @@ declare global {
"dialog-home-zone-detail": DialogHomeZoneDetail;
}
}
customElements.define("dialog-home-zone-detail", DialogHomeZoneDetail);

View File

@@ -1,6 +1,6 @@
import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../common/dom/fire_event";
import { addDistanceToCoord } from "../../../common/location/add_distance_to_coord";
@@ -14,7 +14,6 @@ import { haStyleDialog } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types";
import type { ZoneDetailDialogParams } from "./show-dialog-zone-detail";
@customElement("dialog-zone-detail")
class DialogZoneDetail extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -242,3 +241,5 @@ declare global {
"dialog-zone-detail": DialogZoneDetail;
}
}
customElements.define("dialog-zone-detail", DialogZoneDetail);

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