Compare commits

..

102 Commits

Author SHA1 Message Date
Bram Kragten
5b0bd9d577 Merge branch 'rc' 2025-12-19 17:05:32 +01:00
Bram Kragten
d839152fd1 Bumped version to 20251203.3 2025-12-19 17:05:16 +01:00
Petar Petrov
407cb79805 Fix power sources graph ordering with multiple sources (#28549) 2025-12-19 17:04:50 +01:00
karwosts
7817ebe983 Home strategy: don't link non-admin to config pages (#28512) 2025-12-19 17:04:49 +01:00
Wendelin
7e58cedd49 Fix ha-toast z-index (#28491) 2025-12-19 17:04:48 +01:00
Wendelin
06334a039c Fix automation add TCA search icons (#28490)
Fix automation add TCA seach icons
2025-12-19 17:04:47 +01:00
Silas Krause
6e5853a1c0 Support legacy table styles in markdown (#28488)
* Remove unnecessary assist styles

* Fix list styles

* Remove table styles for role="presentation"
2025-12-19 17:04:46 +01:00
Wendelin
f4f4520773 Fix target picker area in history/activity (#28474)
* Add max target picker width for history and activity

* Fix target picker  area selection in history and activity
2025-12-19 17:04:45 +01:00
karwosts
94453dfba5 Fix markdown card image sizing (#28449) 2025-12-19 17:04:44 +01:00
Paul Bottein
0ce0247a2c 20251203.2 (#28443) 2025-12-08 17:30:04 +01:00
Paul Bottein
ce8cabbad9 Bumped version to 20251203.2 2025-12-08 17:29:01 +01:00
karwosts
0802841606 More unsafe description_placeholders fixes (#28416) 2025-12-08 17:28:52 +01:00
Nils Schönwald
cb93e1b741 Update snowflake to 6 sides (#28406) 2025-12-08 17:28:51 +01:00
dcapslock
30c383a2fc Energy strategies to refresh energy collection which allows to be used in custom dashboards (#28400)
* Energy strategies to refresh energy collection which allows to be used in custom dashboards

* Update src/panels/energy/strategies/energy-overview-view-strategy.ts

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

* Only refresh if no prefs

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2025-12-08 17:28:50 +01:00
karwosts
73ee235fef Fix for undefined description_placeholders (#28395)
Another fix for undefined description_placeholders
2025-12-08 17:28:49 +01:00
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
157 changed files with 1639 additions and 1078 deletions

View File

@@ -36,14 +36,14 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5 uses: github/codeql-action/init@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # 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) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5 uses: github/codeql-action/autobuild@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
# Command-line programs to run using the OS shell. # Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl # 📚 https://git.io/JvXDl
@@ -57,4 +57,4 @@ jobs:
# make release # make release
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5 uses: github/codeql-action/analyze@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4

View File

@@ -23,7 +23,7 @@ jobs:
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Set up Python ${{ env.PYTHON_VERSION }} - name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6 uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6
with: with:
python-version: ${{ env.PYTHON_VERSION }} python-version: ${{ env.PYTHON_VERSION }}

View File

@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Send bundle stats and build information to RelativeCI - 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: with:
key: ${{ secrets[format('RELATIVE_CI_KEY_{0}_{1}', matrix.bundle, matrix.build)] }} key: ${{ secrets[format('RELATIVE_CI_KEY_{0}_{1}', matrix.bundle, matrix.build)] }}
token: ${{ github.token }} token: ${{ github.token }}

View File

@@ -26,7 +26,7 @@ jobs:
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Set up Python ${{ env.PYTHON_VERSION }} - name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with: with:
python-version: ${{ env.PYTHON_VERSION }} python-version: ${{ env.PYTHON_VERSION }}
@@ -55,7 +55,7 @@ jobs:
script/release script/release
- name: Upload release assets - name: Upload release assets
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0 uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2
with: with:
files: | files: |
dist/*.whl dist/*.whl
@@ -75,7 +75,7 @@ jobs:
# home-assistant/wheels doesn't support SHA pinning # home-assistant/wheels doesn't support SHA pinning
- name: Build wheels - name: Build wheels
uses: home-assistant/wheels@2025.11.0 uses: home-assistant/wheels@2025.10.0
with: with:
abi: cp313 abi: cp313
tag: musllinux_1_2 tag: musllinux_1_2
@@ -108,7 +108,7 @@ jobs:
- name: Tar folder - name: Tar folder
run: tar -czf landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz -C landing-page/dist . run: tar -czf landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz -C landing-page/dist .
- name: Upload release asset - name: Upload release asset
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0 uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2
with: with:
files: landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz files: landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz
@@ -137,6 +137,6 @@ jobs:
- name: Tar folder - name: Tar folder
run: tar -czf hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz -C hassio/build . run: tar -czf hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz -C hassio/build .
- name: Upload release asset - name: Upload release asset
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0 uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2
with: with:
files: hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz files: hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz

View File

@@ -156,7 +156,9 @@ const createTestTranslation = () =>
*/ */
const createMasterTranslation = () => const createMasterTranslation = () =>
gulp gulp
.src([EN_SRC, ...(mergeBackend ? [`${inBackendDir}/en.json`] : [])]) .src([EN_SRC, ...(mergeBackend ? [`${inBackendDir}/en.json`] : [])], {
allowEmpty: true,
})
.pipe(new CustomJSON(lokaliseTransform)) .pipe(new CustomJSON(lokaliseTransform))
.pipe(new MergeJSON("en")) .pipe(new MergeJSON("en"))
.pipe(gulp.dest(workDir)); .pipe(gulp.dest(workDir));

View File

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

View File

@@ -44,18 +44,24 @@ export const mockEnergy = (hass: MockHomeAssistant) => {
number_energy_price: null, number_energy_price: null,
}, },
], ],
power: [
{ stat_rate: "sensor.power_grid" },
{ stat_rate: "sensor.power_grid_return" },
],
cost_adjustment_day: 0, cost_adjustment_day: 0,
}, },
{ {
type: "solar", type: "solar",
stat_energy_from: "sensor.solar_production", stat_energy_from: "sensor.solar_production",
stat_rate: "sensor.power_solar",
config_entry_solar_forecast: ["solar_forecast"], config_entry_solar_forecast: ["solar_forecast"],
}, },
/* { {
type: "battery", type: "battery",
stat_energy_from: "sensor.battery_output", stat_energy_from: "sensor.battery_output",
stat_energy_to: "sensor.battery_input", stat_energy_to: "sensor.battery_input",
}, */ stat_rate: "sensor.power_battery",
},
{ {
type: "gas", type: "gas",
stat_energy_from: "sensor.energy_gas", stat_energy_from: "sensor.energy_gas",
@@ -63,28 +69,48 @@ export const mockEnergy = (hass: MockHomeAssistant) => {
entity_energy_price: null, entity_energy_price: null,
number_energy_price: null, number_energy_price: null,
}, },
{
type: "water",
stat_energy_from: "sensor.energy_water",
stat_cost: "sensor.energy_water_cost",
entity_energy_price: null,
number_energy_price: null,
},
], ],
device_consumption: [ device_consumption: [
{ {
stat_consumption: "sensor.energy_car", stat_consumption: "sensor.energy_car",
stat_rate: "sensor.power_car",
}, },
{ {
stat_consumption: "sensor.energy_ac", stat_consumption: "sensor.energy_ac",
stat_rate: "sensor.power_ac",
}, },
{ {
stat_consumption: "sensor.energy_washing_machine", stat_consumption: "sensor.energy_washing_machine",
stat_rate: "sensor.power_washing_machine",
}, },
{ {
stat_consumption: "sensor.energy_dryer", stat_consumption: "sensor.energy_dryer",
stat_rate: "sensor.power_dryer",
}, },
{ {
stat_consumption: "sensor.energy_heat_pump", stat_consumption: "sensor.energy_heat_pump",
stat_rate: "sensor.power_heat_pump",
}, },
{ {
stat_consumption: "sensor.energy_boiler", stat_consumption: "sensor.energy_boiler",
stat_rate: "sensor.power_boiler",
},
],
device_consumption_water: [
{
stat_consumption: "sensor.water_kitchen",
},
{
stat_consumption: "sensor.water_garden",
}, },
], ],
device_consumption_water: [],
}) })
); );
hass.mockWS( hass.mockWS(

View File

@@ -154,6 +154,38 @@ export const energyEntities = () =>
unit_of_measurement: "EUR", unit_of_measurement: "EUR",
}, },
}, },
"sensor.power_grid": {
entity_id: "sensor.power_grid",
state: "500",
attributes: {
state_class: "measurement",
unit_of_measurement: "W",
},
},
"sensor.power_grid_return": {
entity_id: "sensor.power_grid_return",
state: "-100",
attributes: {
state_class: "measurement",
unit_of_measurement: "W",
},
},
"sensor.power_solar": {
entity_id: "sensor.power_solar",
state: "200",
attributes: {
state_class: "measurement",
unit_of_measurement: "W",
},
},
"sensor.power_battery": {
entity_id: "sensor.power_battery",
state: "100",
attributes: {
state_class: "measurement",
unit_of_measurement: "W",
},
},
"sensor.energy_gas_cost": { "sensor.energy_gas_cost": {
entity_id: "sensor.energy_gas_cost", entity_id: "sensor.energy_gas_cost",
state: "2", state: "2",
@@ -171,6 +203,15 @@ export const energyEntities = () =>
unit_of_measurement: "m³", unit_of_measurement: "m³",
}, },
}, },
"sensor.energy_water": {
entity_id: "sensor.energy_water",
state: "4000",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Water",
unit_of_measurement: "L",
},
},
"sensor.energy_car": { "sensor.energy_car": {
entity_id: "sensor.energy_car", entity_id: "sensor.energy_car",
state: "4", state: "4",
@@ -225,4 +266,58 @@ export const energyEntities = () =>
unit_of_measurement: "kWh", unit_of_measurement: "kWh",
}, },
}, },
"sensor.power_car": {
entity_id: "sensor.power_car",
state: "40",
attributes: {
state_class: "measurement",
friendly_name: "Electric car",
unit_of_measurement: "W",
},
},
"sensor.power_ac": {
entity_id: "sensor.power_ac",
state: "30",
attributes: {
state_class: "measurement",
friendly_name: "Air conditioning",
unit_of_measurement: "W",
},
},
"sensor.power_washing_machine": {
entity_id: "sensor.power_washing_machine",
state: "60",
attributes: {
state_class: "measurement",
friendly_name: "Washing machine",
unit_of_measurement: "W",
},
},
"sensor.power_dryer": {
entity_id: "sensor.power_dryer",
state: "55",
attributes: {
state_class: "measurement",
friendly_name: "Dryer",
unit_of_measurement: "W",
},
},
"sensor.power_heat_pump": {
entity_id: "sensor.power_heat_pump",
state: "60",
attributes: {
state_class: "measurement",
friendly_name: "Heat pump",
unit_of_measurement: "W",
},
},
"sensor.power_boiler": {
entity_id: "sensor.power_boiler",
state: "70",
attributes: {
state_class: "measurement",
friendly_name: "Boiler",
unit_of_measurement: "W",
},
},
}); });

View File

@@ -17,17 +17,15 @@ const generateMeanStatistics = (
end: Date, end: Date,
// eslint-disable-next-line default-param-last // eslint-disable-next-line default-param-last
period: "5minute" | "hour" | "day" | "month" = "hour", period: "5minute" | "hour" | "day" | "month" = "hour",
initValue: number,
maxDiff: number maxDiff: number
): StatisticValue[] => { ): StatisticValue[] => {
const statistics: StatisticValue[] = []; const statistics: StatisticValue[] = [];
let currentDate = new Date(start); let currentDate = new Date(start);
currentDate.setMinutes(0, 0, 0); currentDate.setMinutes(0, 0, 0);
let lastVal = initValue;
const now = new Date(); const now = new Date();
while (end > currentDate && currentDate < now) { while (end > currentDate && currentDate < now) {
const delta = Math.random() * maxDiff; const delta = Math.random() * maxDiff;
const mean = lastVal + delta; const mean = delta;
statistics.push({ statistics.push({
start: currentDate.getTime(), start: currentDate.getTime(),
end: currentDate.getTime(), end: currentDate.getTime(),
@@ -38,7 +36,6 @@ const generateMeanStatistics = (
state: mean, state: mean,
sum: null, sum: null,
}); });
lastVal = mean;
currentDate = currentDate =
period === "day" period === "day"
? addDays(currentDate, 1) ? addDays(currentDate, 1)
@@ -336,7 +333,6 @@ export const mockRecorder = (mockHass: MockHomeAssistant) => {
start, start,
end, end,
period, period,
state,
state * (state > 80 ? 0.05 : 0.1) state * (state > 80 ? 0.05 : 0.1)
); );
} }

View File

@@ -381,10 +381,6 @@ export class DemoHaWaDialog extends LitElement {
<td><code>--dialog-z-index</code></td> <td><code>--dialog-z-index</code></td>
<td>Z-index for the dialog.</td> <td>Z-index for the dialog.</td>
</tr> </tr>
<tr>
<td><code>--dialog-surface-position</code></td>
<td>CSS position of the dialog surface.</td>
</tr>
<tr> <tr>
<td><code>--dialog-surface-margin-top</code></td> <td><code>--dialog-surface-margin-top</code></td>
<td>Top margin for the dialog surface.</td> <td>Top margin for the dialog surface.</td>

View File

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

View File

@@ -89,8 +89,8 @@
"@thomasloven/round-slider": "0.6.0", "@thomasloven/round-slider": "0.6.0",
"@tsparticles/engine": "3.9.1", "@tsparticles/engine": "3.9.1",
"@tsparticles/preset-links": "3.2.0", "@tsparticles/preset-links": "3.2.0",
"@vaadin/combo-box": "24.9.6", "@vaadin/combo-box": "24.9.5",
"@vaadin/vaadin-themable-mixin": "24.9.6", "@vaadin/vaadin-themable-mixin": "24.9.5",
"@vibrant/color": "4.0.0", "@vibrant/color": "4.0.0",
"@vue/web-component-wrapper": "1.3.0", "@vue/web-component-wrapper": "1.3.0",
"@webcomponents/scoped-custom-element-registry": "0.0.10", "@webcomponents/scoped-custom-element-registry": "0.0.10",
@@ -152,13 +152,13 @@
"@babel/helper-define-polyfill-provider": "0.6.5", "@babel/helper-define-polyfill-provider": "0.6.5",
"@babel/plugin-transform-runtime": "7.28.5", "@babel/plugin-transform-runtime": "7.28.5",
"@babel/preset-env": "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", "@lokalise/node-api": "15.4.0",
"@octokit/auth-oauth-device": "8.0.3", "@octokit/auth-oauth-device": "8.0.3",
"@octokit/plugin-retry": "8.0.3", "@octokit/plugin-retry": "8.0.3",
"@octokit/rest": "22.0.1", "@octokit/rest": "22.0.1",
"@rsdoctor/rspack-plugin": "1.3.11", "@rsdoctor/rspack-plugin": "1.3.11",
"@rspack/core": "1.6.5", "@rspack/core": "1.6.4",
"@rspack/dev-server": "1.1.4", "@rspack/dev-server": "1.1.4",
"@types/babel__plugin-transform-runtime": "7.9.5", "@types/babel__plugin-transform-runtime": "7.9.5",
"@types/chromecast-caf-receiver": "6.0.22", "@types/chromecast-caf-receiver": "6.0.22",
@@ -178,7 +178,7 @@
"@types/tar": "6.1.13", "@types/tar": "6.1.13",
"@types/ua-parser-js": "0.7.39", "@types/ua-parser-js": "0.7.39",
"@types/webspeechapi": "0.0.29", "@types/webspeechapi": "0.0.29",
"@vitest/coverage-v8": "4.0.14", "@vitest/coverage-v8": "4.0.13",
"babel-loader": "10.0.0", "babel-loader": "10.0.0",
"babel-plugin-template-html-minifier": "4.1.0", "babel-plugin-template-html-minifier": "4.1.0",
"browserslist-useragent-regexp": "4.1.3", "browserslist-useragent-regexp": "4.1.3",
@@ -194,7 +194,7 @@
"eslint-plugin-wc": "3.0.2", "eslint-plugin-wc": "3.0.2",
"fancy-log": "2.0.0", "fancy-log": "2.0.0",
"fs-extra": "11.3.2", "fs-extra": "11.3.2",
"glob": "13.0.0", "glob": "12.0.0",
"gulp": "5.0.1", "gulp": "5.0.1",
"gulp-brotli": "3.0.0", "gulp-brotli": "3.0.0",
"gulp-json-transform": "0.5.0", "gulp-json-transform": "0.5.0",
@@ -209,7 +209,7 @@
"lodash.template": "4.5.0", "lodash.template": "4.5.0",
"map-stream": "0.0.7", "map-stream": "0.0.7",
"pinst": "3.0.0", "pinst": "3.0.0",
"prettier": "3.7.2", "prettier": "3.6.2",
"rspack-manifest-plugin": "5.2.0", "rspack-manifest-plugin": "5.2.0",
"serve": "14.2.5", "serve": "14.2.5",
"sinon": "21.0.0", "sinon": "21.0.0",
@@ -217,9 +217,9 @@
"terser-webpack-plugin": "5.3.14", "terser-webpack-plugin": "5.3.14",
"ts-lit-plugin": "2.0.2", "ts-lit-plugin": "2.0.2",
"typescript": "5.9.3", "typescript": "5.9.3",
"typescript-eslint": "8.48.0", "typescript-eslint": "8.47.0",
"vite-tsconfig-paths": "5.1.4", "vite-tsconfig-paths": "5.1.4",
"vitest": "4.0.14", "vitest": "4.0.13",
"webpack-stats-plugin": "1.1.3", "webpack-stats-plugin": "1.1.3",
"webpackbar": "7.0.0", "webpackbar": "7.0.0",
"workbox-build": "patch:workbox-build@npm%3A7.1.1#~/.yarn/patches/workbox-build-npm-7.1.1-a854f3faae.patch" "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] [project]
name = "home-assistant-frontend" name = "home-assistant-frontend"
version = "20251029.0" version = "20251203.3"
license = "Apache-2.0" license = "Apache-2.0"
license-files = ["LICENSE*"] license-files = ["LICENSE*"]
description = "The Home Assistant frontend" description = "The Home Assistant frontend"

View File

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

View File

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

View File

@@ -373,6 +373,7 @@ export class StateHistoryChartTimeline extends LitElement {
itemName: 3, itemName: 3,
}, },
renderItem: this._renderItem, renderItem: this._renderItem,
progressive: 0,
}); });
}); });

View File

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

View File

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

View File

@@ -4,7 +4,6 @@ import { LitElement, html } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event"; import { fireEvent } from "../common/dom/fire_event";
import { getAreaContext } from "../common/entity/context/get_area_context"; import { getAreaContext } from "../common/entity/context/get_area_context";
import { areaCompare } from "../data/area_registry";
import type { HomeAssistant } from "../types"; import type { HomeAssistant } from "../types";
import "./ha-expansion-panel"; import "./ha-expansion-panel";
import "./ha-items-display-editor"; import "./ha-items-display-editor";
@@ -37,11 +36,7 @@ export class HaAreasDisplayEditor extends LitElement {
public showNavigationButton = false; public showNavigationButton = false;
protected render(): TemplateResult { protected render(): TemplateResult {
const compare = areaCompare(this.hass.areas); const areas = Object.values(this.hass.areas);
const areas = Object.values(this.hass.areas).sort((areaA, areaB) =>
compare(areaA.area_id, areaB.area_id)
);
const items: DisplayItem[] = areas.map((area) => { const items: DisplayItem[] = areas.map((area) => {
const { floor } = getAreaContext(area, this.hass.floors); const { floor } = getAreaContext(area, this.hass.floors);

View File

@@ -7,7 +7,6 @@ import memoizeOne from "memoize-one";
import { fireEvent } from "../common/dom/fire_event"; import { fireEvent } from "../common/dom/fire_event";
import { computeFloorName } from "../common/entity/compute_floor_name"; import { computeFloorName } from "../common/entity/compute_floor_name";
import { getAreaContext } from "../common/entity/context/get_area_context"; import { getAreaContext } from "../common/entity/context/get_area_context";
import { areaCompare } from "../data/area_registry";
import type { FloorRegistryEntry } from "../data/floor_registry"; import type { FloorRegistryEntry } from "../data/floor_registry";
import { getFloors } from "../panels/lovelace/strategies/areas/helpers/areas-strategy-helper"; import { getFloors } from "../panels/lovelace/strategies/areas/helpers/areas-strategy-helper";
import type { HomeAssistant } from "../types"; import type { HomeAssistant } from "../types";
@@ -131,11 +130,8 @@ export class HaAreasFloorsDisplayEditor extends LitElement {
// update items if floors change // update items if floors change
_hassFloors: HomeAssistant["floors"] _hassFloors: HomeAssistant["floors"]
): Record<string, DisplayItem[]> => { ): Record<string, DisplayItem[]> => {
const compare = areaCompare(hassAreas); const areas = Object.values(hassAreas);
const areas = Object.values(hassAreas).sort((areaA, areaB) =>
compare(areaA.area_id, areaB.area_id)
);
const groupedItems: Record<string, DisplayItem[]> = areas.reduce( const groupedItems: Record<string, DisplayItem[]> = areas.reduce(
(acc, area) => { (acc, area) => {
const { floor } = getAreaContext(area, this.hass.floors); const { floor } = getAreaContext(area, this.hass.floors);

View File

@@ -659,7 +659,7 @@ export class HaAssistChat extends LitElement {
--markdown-table-border-color: var(--divider-color); --markdown-table-border-color: var(--divider-color);
--markdown-code-background-color: var(--primary-background-color); --markdown-code-background-color: var(--primary-background-color);
--markdown-code-text-color: var(--primary-text-color); --markdown-code-text-color: var(--primary-text-color);
--markdown-list-indent: 1rem; --markdown-list-indent: 1.15em;
&:not(:has(ha-markdown-element)) { &:not(:has(ha-markdown-element)) {
min-height: 1lh; min-height: 1lh;
min-width: 1lh; min-width: 1lh;

View File

@@ -167,30 +167,33 @@ export class HaFilterLabels extends SubscribeMixin(LitElement) {
} }
private async _labelSelected(ev: CustomEvent<SelectedDetail<Set<number>>>) { private async _labelSelected(ev: CustomEvent<SelectedDetail<Set<number>>>) {
if (!ev.detail.index.size) {
fireEvent(this, "data-table-filter-changed", {
value: [],
items: undefined,
});
this.value = [];
return;
}
const value: string[] = [];
const filteredLabels = this._filteredLabels( const filteredLabels = this._filteredLabels(
this._labels, this._labels,
this._filter, this._filter,
this.value this.value
); );
const filteredLabelIds = new Set(filteredLabels.map((l) => l.label_id));
// Keep previously selected labels that are not in the current filtered view
const preservedLabels = (this.value || []).filter(
(id) => !filteredLabelIds.has(id)
);
// Build the new selection from the filtered labels based on selected indices
const newlySelectedLabels: string[] = [];
for (const index of ev.detail.index) { for (const index of ev.detail.index) {
const labelId = filteredLabels[index].label_id; const labelId = filteredLabels[index]?.label_id;
value.push(labelId); if (labelId) {
newlySelectedLabels.push(labelId);
}
} }
this.value = value;
const value = [...preservedLabels, ...newlySelectedLabels];
this.value = value.length ? value : [];
fireEvent(this, "data-table-filter-changed", { fireEvent(this, "data-table-filter-changed", {
value, value: value.length ? value : undefined,
items: undefined, items: undefined,
}); });
} }

View File

@@ -344,7 +344,10 @@ export class HaGenericPicker extends LitElement {
wa-popover::part(body) { wa-popover::part(body) {
width: max(var(--body-width), 250px); width: max(var(--body-width), 250px);
max-width: max(var(--body-width), 250px); max-width: var(
--ha-generic-picker-max-width,
max(var(--body-width), 250px)
);
max-height: 500px; max-height: 500px;
height: 70vh; height: 70vh;
overflow: hidden; overflow: hidden;

View File

@@ -99,10 +99,7 @@ class HaMarkdownElement extends ReactiveElement {
} }
); );
render( render(h(unsafeHTML(elements.join(""))), this.renderRoot);
elements.map((e) => h(unsafeHTML(e))),
this.renderRoot
);
this._resize(); this._resize();

View File

@@ -25,11 +25,11 @@ export class HaMarkdown extends LitElement {
@property({ type: Boolean }) public cache = false; @property({ type: Boolean }) public cache = false;
@query("ha-markdown-element") private _markdownElement!: ReactiveElement; @query("ha-markdown-element") private _markdownElement?: ReactiveElement;
protected async getUpdateComplete() { protected async getUpdateComplete() {
const result = await super.getUpdateComplete(); const result = await super.getUpdateComplete();
await this._markdownElement.updateComplete; await this._markdownElement?.updateComplete;
return result; return result;
} }
@@ -74,9 +74,6 @@ export class HaMarkdown extends LitElement {
background-color: var(--markdown-image-background-color); background-color: var(--markdown-image-background-color);
border-radius: var(--markdown-image-border-radius); border-radius: var(--markdown-image-border-radius);
max-width: 100%; max-width: 100%;
height: auto;
width: auto;
transition: height 0.2s ease-in-out;
} }
p:first-child > img:first-child { p:first-child > img:first-child {
vertical-align: top; vertical-align: top;
@@ -84,8 +81,7 @@ export class HaMarkdown extends LitElement {
p:first-child > img:last-child { p:first-child > img:last-child {
vertical-align: top; vertical-align: top;
} }
:host > ul, ha-markdown-element > :is(ol, ul) {
:host > ol {
padding-inline-start: var(--markdown-list-indent, revert); padding-inline-start: var(--markdown-list-indent, revert);
} }
li { li {
@@ -136,6 +132,18 @@ export class HaMarkdown extends LitElement {
border-bottom: none; border-bottom: none;
margin: var(--ha-space-4) 0; margin: var(--ha-space-4) 0;
} }
table[role="presentation"] {
--markdown-table-border-collapse: separate;
--markdown-table-border-width: attr(border, 0);
--markdown-table-padding-inline: 0;
--markdown-table-padding-block: 0;
th {
vertical-align: attr(align, center);
}
td {
vertical-align: attr(align, left);
}
}
table { table {
border-collapse: var(--markdown-table-border-collapse, collapse); border-collapse: var(--markdown-table-border-collapse, collapse);
} }
@@ -143,14 +151,15 @@ export class HaMarkdown extends LitElement {
overflow: auto; overflow: auto;
} }
th { th {
text-align: start; text-align: var(--markdown-table-text-align, start);
} }
td, td,
th { th {
border-width: var(--markdown-table-border-width, 1px); border-width: var(--markdown-table-border-width, 1px);
border-style: var(--markdown-table-border-style, solid); border-style: var(--markdown-table-border-style, solid);
border-color: var(--markdown-table-border-color, var(--divider-color)); border-color: var(--markdown-table-border-color, var(--divider-color));
padding: 0.25em 0.5em; padding-inline: var(--markdown-table-padding-inline, 0.5em);
padding-block: var(--markdown-table-padding-block, 0.25em);
} }
blockquote { blockquote {
border-left: 4px solid var(--divider-color); border-left: 4px solid var(--divider-color);

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
import { css, html, LitElement, nothing } from "lit"; import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import type { HomeAssistant } from "../types"; import type { HomeAssistant } from "../types";
import { subscribeLabFeatures } from "../data/labs"; import { subscribeLabFeature } from "../data/labs";
import { SubscribeMixin } from "../mixins/subscribe-mixin"; import { SubscribeMixin } from "../mixins/subscribe-mixin";
interface Snowflake { interface Snowflake {
@@ -10,7 +10,7 @@ interface Snowflake {
size: number; size: number;
duration: number; duration: number;
delay: number; delay: number;
blur: number; rotation: number;
} }
@customElement("ha-snowflakes") @customElement("ha-snowflakes")
@@ -27,13 +27,14 @@ export class HaSnowflakes extends SubscribeMixin(LitElement) {
public hassSubscribe() { public hassSubscribe() {
return [ return [
subscribeLabFeatures(this.hass!.connection, (features) => { subscribeLabFeature(
this._enabled = this.hass!.connection,
features.find( "frontend",
(f) => "winter_mode",
f.domain === "frontend" && f.preview_feature === "winter_mode" (feature) => {
)?.enabled ?? false; this._enabled = feature.enabled;
}), }
),
]; ];
} }
@@ -51,7 +52,7 @@ export class HaSnowflakes extends SubscribeMixin(LitElement) {
size: Math.random() * 12 + 8, // Random size between 8-20px size: Math.random() * 12 + 8, // Random size between 8-20px
duration: Math.random() * 8 + 8, // Random duration between 8-16s duration: Math.random() * 8 + 8, // Random duration between 8-16s
delay: Math.random() * 8, // Random delay between 0-8s delay: Math.random() * 8, // Random delay between 0-8s
blur: Math.random() * 1, // Random blur between 0-1px rotation: Math.random() * 720 - 360, // Random starting rotation -360 to 360deg
}); });
} }
this._snowflakes = snowflakes; this._snowflakes = snowflakes;
@@ -75,20 +76,27 @@ export class HaSnowflakes extends SubscribeMixin(LitElement) {
<div class="snowflakes ${isDark ? "dark" : "light"}" aria-hidden="true"> <div class="snowflakes ${isDark ? "dark" : "light"}" aria-hidden="true">
${this._snowflakes.map( ${this._snowflakes.map(
(flake) => html` (flake) => html`
<div <svg
class="snowflake ${this.narrow && flake.id >= 30 class="snowflake ${this.narrow && flake.id >= 30
? "hide-narrow" ? "hide-narrow"
: ""}" : ""}"
style=" style="
left: ${flake.left}%; left: ${flake.left}%;
font-size: ${flake.size}px; width: ${flake.size}px;
height: ${flake.size}px;
animation-duration: ${flake.duration}s; animation-duration: ${flake.duration}s;
animation-delay: ${flake.delay}s; animation-delay: ${flake.delay}s;
filter: blur(${flake.blur}px); --rotation: ${flake.rotation}deg;
" "
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
> >
<path
</div> d="M7.991 0a.644.644 0 0 1 .283 1.221v2.553l.986-.988a.645.645 0 0 1 .612-.839.644.644 0 1 1-.222 1.247l-1.376 1.38V7.52l1.65-.954.466-1.879a.645.645 0 0 1 .1-1.042.643.643 0 1 1 .445 1.189l-.363 1.356 3.145-1.82a.643.643 0 1 1 .282.49l-2.205 1.277 1.347.361a.643.643 0 1 1-.158.543l-1.88-.505L8.573 8l1.632.945 1.858-.535a.64.64 0 0 1 .95-.434.643.643 0 1 1-.805.98l-1.354.364L14 11.14a.641.641 0 0 1 .914.855.643.643 0 0 1-1.197-.366l-2.205-1.276.36 1.35a.642.642 0 0 1 .419.95.643.643 0 1 1-.967-.816l-.503-1.884L8.273 8.48v1.909l1.39 1.344a.644.644 0 1 1 .208 1.252.644.644 0 0 1-.606-.852l-.991-.994v3.64A.644.644 0 0 1 7.99 16a.644.644 0 0 1-.282-1.221v-2.553l-.986.988a.645.645 0 0 1-.612.839.644.644 0 1 1 .222-1.247l1.376-1.38V8.5l-1.632.945-.467 1.879q.079.068.134.163a.643.643 0 1 1-.68-.31l.364-1.357-3.145 1.82A.643.643 0 1 1 2 11.15l2.205-1.276-1.347-.361a.643.643 0 1 1 .158-.543l1.88.505L7.444 8l-1.65-.954-1.857.534a.64.64 0 0 1-.95.434.643.643 0 1 1 .805-.98l1.354-.364L2 4.85a.641.641 0 0 1-.914-.855.643.643 0 0 1 1.197.366l2.205 1.276-.36-1.35a.642.642 0 0 1-.419-.95.643.643 0 1 1 .967.816l.503 1.884L7.71 7.5V5.611L6.32 4.267a.644.644 0 1 1-.208-1.252.644.644 0 0 1 .607.852l.991.994V1.22A.644.644 0 0 1 7.991 0"
fill="currentColor"
/>
</svg>
` `
)} )}
</div> </div>
@@ -128,16 +136,10 @@ export class HaSnowflakes extends SubscribeMixin(LitElement) {
.light .snowflake { .light .snowflake {
color: #00bcd4; color: #00bcd4;
text-shadow:
0 0 5px #00bcd4,
0 0 10px #00e5ff;
} }
.dark .snowflake { .dark .snowflake {
color: #fff; color: #fff;
text-shadow:
0 0 5px rgba(255, 255, 255, 0.8),
0 0 10px rgba(255, 255, 255, 0.5);
} }
.snowflake.hide-narrow { .snowflake.hide-narrow {
@@ -146,19 +148,23 @@ export class HaSnowflakes extends SubscribeMixin(LitElement) {
@keyframes fall { @keyframes fall {
0% { 0% {
transform: translateY(-10vh) translateX(0); transform: translateY(-10vh) translateX(0) rotate(var(--rotation));
} }
25% { 25% {
transform: translateY(30vh) translateX(10px); transform: translateY(30vh) translateX(10px)
rotate(calc(var(--rotation) + 25deg));
} }
50% { 50% {
transform: translateY(60vh) translateX(-10px); transform: translateY(60vh) translateX(-10px)
rotate(calc(var(--rotation) + 50deg));
} }
75% { 75% {
transform: translateY(85vh) translateX(10px); transform: translateY(85vh) translateX(10px)
rotate(calc(var(--rotation) + 75deg));
} }
100% { 100% {
transform: translateY(120vh) translateX(0); transform: translateY(120vh) translateX(0)
rotate(calc(var(--rotation) + 100deg));
} }
} }

View File

@@ -952,10 +952,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
let hasFloor = false; let hasFloor = false;
let rtl = false; let rtl = false;
let showEntityId = false; let showEntityId = false;
if (type === "area" || type === "floor") { if (type === "area" || type === "floor") {
item.id = item[type]?.[`${type}_id`];
rtl = computeRTL(this.hass); rtl = computeRTL(this.hass);
hasFloor = hasFloor =
type === "area" && !!(item as FloorComboBoxItem).area?.floor_id; type === "area" && !!(item as FloorComboBoxItem).area?.floor_id;

View File

@@ -13,6 +13,7 @@ export class HaToast extends Snackbar {
} }
.mdc-snackbar { .mdc-snackbar {
z-index: 10;
margin: 8px; margin: 8px;
right: calc(8px + var(--safe-area-inset-right)); right: calc(8px + var(--safe-area-inset-right));
bottom: calc(8px + var(--safe-area-inset-bottom)); bottom: calc(8px + var(--safe-area-inset-bottom));

View File

@@ -49,7 +49,6 @@ export type DialogWidth = "small" | "medium" | "large" | "full";
* @cssprop --ha-dialog-surface-background - Dialog background color. * @cssprop --ha-dialog-surface-background - Dialog background color.
* @cssprop --ha-dialog-border-radius - Border radius of the dialog surface. * @cssprop --ha-dialog-border-radius - Border radius of the dialog surface.
* @cssprop --dialog-z-index - Z-index for the dialog. * @cssprop --dialog-z-index - Z-index for the dialog.
* @cssprop --dialog-surface-position - CSS position of the dialog surface.
* @cssprop --dialog-surface-margin-top - Top margin for the dialog surface. * @cssprop --dialog-surface-margin-top - Top margin for the dialog surface.
* *
* @attr {boolean} open - Controls the dialog open state. * @attr {boolean} open - Controls the dialog open state.
@@ -244,7 +243,6 @@ export class HaWaDialog extends LitElement {
calc(var(--safe-height) - var(--ha-space-20)) calc(var(--safe-height) - var(--ha-space-20))
); );
min-height: var(--ha-dialog-min-height); min-height: var(--ha-dialog-min-height);
position: var(--dialog-surface-position, relative);
margin-top: var(--dialog-surface-margin-top, auto); margin-top: var(--dialog-surface-margin-top, auto);
/* Used to offset the dialog from the safe areas when space is limited */ /* Used to offset the dialog from the safe areas when space is limited */
transform: translate( transform: translate(

View File

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

View File

@@ -223,6 +223,7 @@ const getAreasAndFloorsItems = (
} }
let outputAreas = areas; let outputAreas = areas;
let outputFloors = floors;
let areaIds: string[] | undefined; let areaIds: string[] | undefined;
@@ -254,9 +255,29 @@ const getAreasAndFloorsItems = (
outputAreas = outputAreas.filter( outputAreas = outputAreas.filter(
(area) => !area.floor_id || !excludeFloors!.includes(area.floor_id) (area) => !area.floor_id || !excludeFloors!.includes(area.floor_id)
); );
outputFloors = outputFloors.filter(
(floor) => !excludeFloors!.includes(floor.floor_id)
);
} }
const hierarchy = getAreasFloorHierarchy(floors, outputAreas); if (
entityFilter ||
deviceFilter ||
includeDomains ||
excludeDomains ||
includeDeviceClasses
) {
// Ensure we only include floors that have areas with the filtered entities/devices
const validFloorIds = new Set(
outputAreas.map((area) => area.floor_id).filter((id) => id)
);
outputFloors = outputFloors.filter((floor) =>
validFloorIds.has(floor.floor_id)
);
}
const hierarchy = getAreasFloorHierarchy(outputFloors, outputAreas);
const items: ( const items: (
| FloorComboBoxItem | FloorComboBoxItem

View File

@@ -1,4 +1,3 @@
import { stringCompare } from "../common/string/compare";
import type { HomeAssistant } from "../types"; import type { HomeAssistant } from "../types";
import type { DeviceRegistryEntry } from "./device_registry"; import type { DeviceRegistryEntry } from "./device_registry";
import type { import type {
@@ -105,22 +104,3 @@ export const getAreaDeviceLookup = (
} }
return areaDeviceLookup; return areaDeviceLookup;
}; };
export const areaCompare =
(entries?: HomeAssistant["areas"], order?: string[]) =>
(a: string, b: string) => {
const indexA = order ? order.indexOf(a) : -1;
const indexB = order ? order.indexOf(b) : -1;
if (indexA === -1 && indexB === -1) {
const nameA = entries?.[a]?.name ?? a;
const nameB = entries?.[b]?.name ?? b;
return stringCompare(nameA, nameB);
}
if (indexA === -1) {
return 1;
}
if (indexB === -1) {
return -1;
}
return indexA - indexB;
};

View File

@@ -11,7 +11,7 @@ import {
isLastDayOfMonth, isLastDayOfMonth,
addYears, addYears,
} from "date-fns"; } from "date-fns";
import type { Collection } from "home-assistant-js-websocket"; import type { Collection, HassEntity } from "home-assistant-js-websocket";
import { getCollection } from "home-assistant-js-websocket"; import { getCollection } from "home-assistant-js-websocket";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { import {
@@ -1361,3 +1361,37 @@ export const calculateSolarConsumedGauge = (
} }
return undefined; return undefined;
}; };
/**
* Get current power value from entity state, normalized to kW
* @param stateObj - The entity state object to get power value from
* @returns Power value in kW, or 0 if entity not found or invalid
*/
export const getPowerFromState = (stateObj: HassEntity): number | undefined => {
if (!stateObj) {
return undefined;
}
const value = parseFloat(stateObj.state);
if (isNaN(value)) {
return undefined;
}
// Normalize to kW based on unit of measurement (case-sensitive)
// Supported units: GW, kW, MW, mW, TW, W
const unit = stateObj.attributes.unit_of_measurement;
switch (unit) {
case "W":
return value / 1000;
case "mW":
return value / 1000000;
case "MW":
return value * 1000;
case "GW":
return value * 1000000;
case "TW":
return value * 1000000000;
default:
// Assume kW if no unit or unit is kW
return value;
}
};

View File

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

View File

@@ -18,6 +18,11 @@ export interface LabPreviewFeaturesResponse {
features: LabPreviewFeature[]; features: LabPreviewFeature[];
} }
/**
* Fetch all lab features
* @param hass - The Home Assistant instance
* @returns A promise to fetch the lab features
*/
export const fetchLabFeatures = async ( export const fetchLabFeatures = async (
hass: HomeAssistant hass: HomeAssistant
): Promise<LabPreviewFeature[]> => { ): Promise<LabPreviewFeature[]> => {
@@ -27,6 +32,15 @@ export const fetchLabFeatures = async (
return response.features; return response.features;
}; };
/**
* Update a specific lab feature
* @param hass - The Home Assistant instance
* @param domain - The domain of the lab feature
* @param preview_feature - The preview feature of the lab feature
* @param enabled - Whether the lab feature is enabled
* @param create_backup - Whether to create a backup of the lab feature
* @returns A promise to update the lab feature
*/
export const labsUpdatePreviewFeature = ( export const labsUpdatePreviewFeature = (
hass: HomeAssistant, hass: HomeAssistant,
domain: string, domain: string,
@@ -65,6 +79,12 @@ const subscribeLabUpdates = (
"labs_updated" "labs_updated"
); );
/**
* Subscribe to a collection of lab features
* @param conn - The connection to the Home Assistant instance
* @param onChange - The function to call when the lab features change
* @returns The unsubscribe function
*/
export const subscribeLabFeatures = ( export const subscribeLabFeatures = (
conn: Connection, conn: Connection,
onChange: (features: LabPreviewFeature[]) => void onChange: (features: LabPreviewFeature[]) => void
@@ -76,3 +96,23 @@ export const subscribeLabFeatures = (
conn, conn,
onChange onChange
); );
/**
* Subscribe to a specific lab feature
* @param conn - The connection to the Home Assistant instance
* @param domain - The domain of the lab feature
* @param previewFeature - The preview feature identifier
* @param onChange - The function to call when the lab feature changes
* @returns A promise that resolves to the unsubscribe function
*/
export const subscribeLabFeature = (
conn: Connection,
domain: string,
previewFeature: string,
onChange: (feature: LabPreviewFeature) => void
): Promise<() => void> =>
conn.subscribeMessage<LabPreviewFeature>(onChange, {
type: "labs/subscribe",
domain,
preview_feature: previewFeature,
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -302,7 +302,9 @@ export class MoreInfoDialog extends LitElement {
} }
private _goToAddEntityTo(ev) { private _goToAddEntityTo(ev) {
if (!shouldHandleRequestSelectedEvent(ev)) return; // Only check for request-selected events (from menu items), not regular clicks (from icon button)
if (ev.type === "request-selected" && !shouldHandleRequestSelectedEvent(ev))
return;
this._setView("add_to"); this._setView("add_to");
} }
@@ -550,7 +552,18 @@ export class MoreInfoDialog extends LitElement {
: nothing} : nothing}
</ha-button-menu> </ha-button-menu>
` `
: nothing} : !__DEMO__ && this._shouldShowAddEntityTo()
? html`
<ha-icon-button
slot="actionItems"
.label=${this.hass.localize(
"ui.dialogs.more_info_control.add_entity_to"
)}
.path=${mdiPlusBoxMultipleOutline}
@click=${this._goToAddEntityTo}
></ha-icon-button>
`
: nothing}
` `
: isSpecificInitialView : isSpecificInitialView
? html` ? html`

View File

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

View File

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

View File

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

View File

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

View File

@@ -90,7 +90,9 @@ class OnboardingRestoreBackupCloudLogin extends LitElement {
this._email = this._cloudLoginElement.emailField.value; 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"; this._view = "forgot-password";
} }

View File

@@ -3,7 +3,7 @@ import { TZDate } from "@date-fns/tz";
import { addDays, isSameDay } from "date-fns"; import { addDays, isSameDay } from "date-fns";
import type { CSSResultGroup } from "lit"; import type { CSSResultGroup } from "lit";
import { LitElement, css, html, nothing } 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 { formatDate } from "../../common/datetime/format_date";
import { formatDateTime } from "../../common/datetime/format_date_time"; import { formatDateTime } from "../../common/datetime/format_date_time";
import { formatTime } from "../../common/datetime/format_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 { showCalendarEventEditDialog } from "./show-dialog-calendar-event-editor";
import { resolveTimeZone } from "../../common/datetime/resolve-time-zone"; import { resolveTimeZone } from "../../common/datetime/resolve-time-zone";
@customElement("dialog-calendar-event-detail")
class DialogCalendarEventDetail extends LitElement { class DialogCalendarEventDetail extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@@ -272,3 +271,8 @@ declare global {
"dialog-calendar-event-detail": DialogCalendarEventDetail; "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 { HassEntity } from "home-assistant-js-websocket";
import type { CSSResultGroup } from "lit"; import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } 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 { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/entity/ha-entity-picker"; import "../../../components/entity/ha-entity-picker";
import type { HaEntityPicker } from "../../../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 TEMPERATURE_DEVICE_CLASSES = [SENSOR_DEVICE_CLASS_TEMPERATURE];
const HUMIDITY_DEVICE_CLASSES = [SENSOR_DEVICE_CLASS_HUMIDITY]; const HUMIDITY_DEVICE_CLASSES = [SENSOR_DEVICE_CLASS_HUMIDITY];
@customElement("dialog-area-registry-detail")
class DialogAreaDetail extends LitElement { class DialogAreaDetail extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@@ -427,3 +426,5 @@ declare global {
"dialog-area-registry-detail": DialogAreaDetail; "dialog-area-registry-detail": DialogAreaDetail;
} }
} }
customElements.define("dialog-area-registry-detail", DialogAreaDetail);

View File

@@ -81,8 +81,11 @@ class DialogAreasFloorsOrder extends LitElement {
return nothing; return nothing;
} }
const hasFloors = this._hierarchy.floors.length > 0;
const dialogTitle = this.hass.localize( const dialogTitle = this.hass.localize(
"ui.panel.config.areas.dialog.reorder_title" hasFloors
? "ui.panel.config.areas.dialog.reorder_floors_areas_title"
: "ui.panel.config.areas.dialog.reorder_areas_title"
); );
return html` return html`
@@ -172,7 +175,7 @@ class DialogAreasFloorsOrder extends LitElement {
? html`<div class="floor-header"> ? html`<div class="floor-header">
<span class="floor-name"> <span class="floor-name">
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.areas.dialog.unassigned_areas" "ui.panel.config.areas.dialog.other_areas"
)} )}
</span> </span>
</div>` </div>`
@@ -418,7 +421,6 @@ class DialogAreasFloorsOrder extends LitElement {
} }
.floor.unassigned { .floor.unassigned {
border-style: dashed;
margin-top: 16px; margin-top: 16px;
} }

View File

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

View File

@@ -175,21 +175,12 @@ export class HaConfigAreasDashboard extends LitElement {
.route=${this.route} .route=${this.route}
has-fab has-fab
> >
<ha-button-menu slot="toolbar-icon"> <ha-icon-button
<ha-icon-button slot="toolbar-icon"
slot="trigger" .label=${this.hass.localize("ui.common.help")}
.label=${this.hass.localize("ui.common.menu")} .path=${mdiHelpCircle}
.path=${mdiDotsVertical} @click=${this._showHelp}
></ha-icon-button> ></ha-icon-button>
<ha-list-item graphic="icon" @click=${this._showReorderDialog}>
<ha-svg-icon .path=${mdiSort} slot="graphic"></ha-svg-icon>
${this.hass.localize("ui.panel.config.areas.picker.reorder")}
</ha-list-item>
<ha-list-item graphic="icon" @click=${this._showHelp}>
<ha-svg-icon .path=${mdiHelpCircle} slot="graphic"></ha-svg-icon>
${this.hass.localize("ui.common.help")}
</ha-list-item>
</ha-button-menu>
<div class="container"> <div class="container">
<div class="floors"> <div class="floors">
${this._hierarchy.floors.map(({ areas, id }) => { ${this._hierarchy.floors.map(({ areas, id }) => {
@@ -213,6 +204,16 @@ export class HaConfigAreasDashboard extends LitElement {
slot="trigger" slot="trigger"
.path=${mdiDotsVertical} .path=${mdiDotsVertical}
></ha-icon-button> ></ha-icon-button>
<ha-list-item graphic="icon"
><ha-svg-icon
.path=${mdiSort}
slot="graphic"
></ha-svg-icon
>${this.hass.localize(
"ui.panel.config.areas.picker.reorder"
)}</ha-list-item
>
<li divider role="separator"></li>
<ha-list-item graphic="icon" <ha-list-item graphic="icon"
><ha-svg-icon ><ha-svg-icon
.path=${mdiPencil} .path=${mdiPencil}
@@ -266,9 +267,30 @@ export class HaConfigAreasDashboard extends LitElement {
<div class="header"> <div class="header">
<h2> <h2>
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.areas.picker.unassigned_areas" this._hierarchy.floors.length
? "ui.panel.config.areas.picker.other_areas"
: "ui.panel.config.areas.picker.header"
)} )}
</h2> </h2>
<div class="actions">
<ha-button-menu
@action=${this._handleUnassignedAreasAction}
>
<ha-icon-button
slot="trigger"
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-list-item graphic="icon"
><ha-svg-icon
.path=${mdiSort}
slot="graphic"
></ha-svg-icon
>${this.hass.localize(
"ui.panel.config.areas.picker.reorder"
)}</ha-list-item
>
</ha-button-menu>
</div>
</div> </div>
<ha-sortable <ha-sortable
handle-selector="a" handle-selector="a"
@@ -515,14 +537,23 @@ export class HaConfigAreasDashboard extends LitElement {
const floor = (ev.currentTarget as any).floor; const floor = (ev.currentTarget as any).floor;
switch (ev.detail.index) { switch (ev.detail.index) {
case 0: case 0:
this._editFloor(floor); this._showReorderDialog();
break; break;
case 1: case 1:
this._editFloor(floor);
break;
case 2:
this._deleteFloor(floor); this._deleteFloor(floor);
break; break;
} }
} }
private _handleUnassignedAreasAction(ev: CustomEvent<ActionDetail>) {
if (ev.detail.index === 0) {
this._showReorderDialog();
}
}
private _createFloor() { private _createFloor() {
this._openFloorDialog(); this._openFloorDialog();
} }

View File

@@ -97,7 +97,7 @@ import {
fetchIntegrationManifests, fetchIntegrationManifests,
} from "../../../data/integration"; } from "../../../data/integration";
import type { LabelRegistryEntry } from "../../../data/label_registry"; import type { LabelRegistryEntry } from "../../../data/label_registry";
import { subscribeLabFeatures } from "../../../data/labs"; import { subscribeLabFeature } from "../../../data/labs";
import { import {
TARGET_SEPARATOR, TARGET_SEPARATOR,
getConditionsForTarget, getConditionsForTarget,
@@ -228,7 +228,7 @@ class DialogAddAutomationElement
private _unsub?: Promise<UnsubscribeFunc>; private _unsub?: Promise<UnsubscribeFunc>;
private _unsubscribeLabFeatures?: UnsubscribeFunc; private _unsubscribeLabFeatures?: Promise<UnsubscribeFunc>;
private _configEntryLookup: Record<string, ConfigEntry> = {}; private _configEntryLookup: Record<string, ConfigEntry> = {};
@@ -281,15 +281,12 @@ class DialogAddAutomationElement
this._fetchManifests(); this._fetchManifests();
this._calculateUsedDomains(); this._calculateUsedDomains();
this._unsubscribeLabFeatures = subscribeLabFeatures( this._unsubscribeLabFeatures = subscribeLabFeature(
this.hass.connection, this.hass.connection,
(features) => { "automation",
this._newTriggersAndConditions = "new_triggers_conditions",
features.find( (feature) => {
(feature) => this._newTriggersAndConditions = feature.enabled;
feature.domain === "automation" &&
feature.preview_feature === "new_triggers_conditions"
)?.enabled ?? false;
this._tab = this._newTriggersAndConditions ? "targets" : "groups"; this._tab = this._newTriggersAndConditions ? "targets" : "groups";
} }
); );
@@ -425,7 +422,7 @@ class DialogAddAutomationElement
this._unsub = undefined; this._unsub = undefined;
} }
if (this._unsubscribeLabFeatures) { if (this._unsubscribeLabFeatures) {
this._unsubscribeLabFeatures(); this._unsubscribeLabFeatures.then((unsub) => unsub());
this._unsubscribeLabFeatures = undefined; this._unsubscribeLabFeatures = undefined;
} }
} }
@@ -686,6 +683,7 @@ class DialogAddAutomationElement
<ha-automation-add-items <ha-automation-add-items
.hass=${this.hass} .hass=${this.hass}
.items=${this._getItems()} .items=${this._getItems()}
.scrollable=${!this._narrow}
.error=${this._tab === "targets" && this._loadItemsError .error=${this._tab === "targets" && this._loadItemsError
? this.hass.localize( ? this.hass.localize(
"ui.panel.config.automation.editor.load_target_items_failed" "ui.panel.config.automation.editor.load_target_items_failed"
@@ -2146,7 +2144,7 @@ class DialogAddAutomationElement
min-height: 160px; min-height: 160px;
} }
.content.column ha-automation-add-from-target { .content.column ha-automation-add-from-target {
overflow: hidden; overflow: clip;
} }
ha-wa-dialog ha-automation-add-items { ha-wa-dialog ha-automation-add-items {

View File

@@ -911,6 +911,10 @@ export default class HaAutomationAddFromTarget extends LitElement {
const services: Record<string, Level3Entries> = {}; const services: Record<string, Level3Entries> = {};
unassignedDevices.forEach(({ id: deviceId, entry_type }) => { unassignedDevices.forEach(({ id: deviceId, entry_type }) => {
const device = this.devices[deviceId];
if (!device || device.disabled_by) {
return;
}
const deviceEntry = { const deviceEntry = {
open: false, open: false,
entities: entities:
@@ -1012,6 +1016,10 @@ export default class HaAutomationAddFromTarget extends LitElement {
const devices: Record<string, Level3Entries> = {}; const devices: Record<string, Level3Entries> = {};
referenced_devices.forEach(({ id: deviceId }) => { referenced_devices.forEach(({ id: deviceId }) => {
const device = this.devices[deviceId];
if (!device || device.disabled_by) {
return;
}
devices[deviceId] = { devices[deviceId] = {
open: false, open: false,
entities: entities:

View File

@@ -60,6 +60,8 @@ export class HaAutomationAddItems extends LitElement {
@property({ type: Boolean, attribute: "tooltip-description" }) @property({ type: Boolean, attribute: "tooltip-description" })
public tooltipDescription = false; public tooltipDescription = false;
@property({ type: Boolean, reflect: true }) scrollable = false;
@state() private _itemsScrolled = false; @state() private _itemsScrolled = false;
@query(".items") @query(".items")
@@ -260,11 +262,12 @@ export class HaAutomationAddItems extends LitElement {
:host { :host {
display: flex; display: flex;
} }
:host([scrollable]) .items {
overflow: auto;
}
.items { .items {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: auto;
flex: 1; flex: 1;
} }
.items.blank { .items.blank {

View File

@@ -321,40 +321,39 @@ export class HaAutomationAddSearch extends LitElement {
></ha-tree-indicator> ></ha-tree-indicator>
` `
: nothing} : nothing}
${item.icon ${(item as AutomationItemComboBoxItem).renderedIcon
? html`<ha-icon slot="start" .icon=${item.icon}></ha-icon>` ? html`<div slot="start">
: item.icon_path ${(item as AutomationItemComboBoxItem).renderedIcon}
? html`<ha-svg-icon </div>`
slot="start" : item.icon
.path=${item.icon_path} ? html`<ha-icon slot="start" .icon=${item.icon}></ha-icon>`
></ha-svg-icon>` : item.icon_path || type === "area"
: type === "entity" && (item as EntityComboBoxItem).stateObj ? html`<ha-svg-icon
? html` slot="start"
<state-badge .path=${item.icon_path || mdiTextureBox}
slot="start" ></ha-svg-icon>`
.stateObj=${(item as EntityComboBoxItem).stateObj} : type === "entity" && (item as EntityComboBoxItem).stateObj
.hass=${this.hass}
></state-badge>
`
: type === "device" && (item as DevicePickerItem).domain
? html` ? html`
<ha-domain-icon <state-badge
slot="start" slot="start"
.stateObj=${(item as EntityComboBoxItem).stateObj}
.hass=${this.hass} .hass=${this.hass}
.domain=${(item as DevicePickerItem).domain!} ></state-badge>
brand-fallback
></ha-domain-icon>
` `
: type === "floor" : type === "device" && (item as DevicePickerItem).domain
? html`<ha-floor-icon ? html`
slot="start" <ha-domain-icon
.floor=${(item as FloorComboBoxItem).floor!}
></ha-floor-icon>`
: type === "area"
? html`<ha-svg-icon
slot="start" slot="start"
.path=${item.icon_path || mdiTextureBox} .hass=${this.hass}
></ha-svg-icon>` .domain=${(item as DevicePickerItem).domain!}
brand-fallback
></ha-domain-icon>
`
: type === "floor"
? html`<ha-floor-icon
slot="start"
.floor=${(item as FloorComboBoxItem).floor!}
></ha-floor-icon>`
: nothing} : nothing}
<span slot="headline">${item.primary}</span> <span slot="headline">${item.primary}</span>
${item.secondary ${item.secondary
@@ -784,7 +783,7 @@ export class HaAutomationAddSearch extends LitElement {
id: key, id: key,
primary: name, primary: name,
secondary: description, secondary: description,
iconPath, icon_path: iconPath,
renderedIcon: icon, renderedIcon: icon,
type, type,
search_labels: [key, name, description], search_labels: [key, name, description],

View File

@@ -28,7 +28,7 @@ import {
CONDITION_BUILDING_BLOCKS, CONDITION_BUILDING_BLOCKS,
subscribeConditions, subscribeConditions,
} from "../../../../data/condition"; } from "../../../../data/condition";
import { subscribeLabFeatures } from "../../../../data/labs"; import { subscribeLabFeature } from "../../../../data/labs";
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin"; import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
import type { HomeAssistant } from "../../../../types"; import type { HomeAssistant } from "../../../../types";
import { import {
@@ -90,14 +90,14 @@ export default class HaAutomationCondition extends SubscribeMixin(LitElement) {
protected hassSubscribe() { protected hassSubscribe() {
return [ return [
subscribeLabFeatures(this.hass!.connection, (features) => { subscribeLabFeature(
this._newTriggersAndConditions = this.hass!.connection,
features.find( "automation",
(feature) => "new_triggers_conditions",
feature.domain === "automation" && (feature) => {
feature.preview_feature === "new_triggers_conditions" this._newTriggersAndConditions = feature.enabled;
)?.enabled ?? false; }
}), ),
]; ];
} }

View File

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

View File

@@ -23,7 +23,7 @@ import {
import type { UnsubscribeFunc } from "home-assistant-js-websocket"; import type { UnsubscribeFunc } from "home-assistant-js-websocket";
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit"; import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import { css, html, LitElement, nothing } 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 { classMap } from "lit/directives/class-map";
import { transform } from "../../../common/decorators/transform"; import { transform } from "../../../common/decorators/transform";
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
@@ -112,7 +112,6 @@ declare global {
} }
} }
@customElement("ha-automation-editor")
export class HaAutomationEditor extends PreventUnsavedMixin( export class HaAutomationEditor extends PreventUnsavedMixin(
KeyboardShortcutMixin(LitElement) KeyboardShortcutMixin(LitElement)
) { ) {
@@ -1340,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 { fireEvent } from "../../../common/dom/fire_event";
import { computeStateName } from "../../../common/entity/compute_state_name"; import { computeStateName } from "../../../common/entity/compute_state_name";
import { navigate } from "../../../common/navigate"; import { navigate } from "../../../common/navigate";
import { slugify } from "../../../common/string/slugify";
import "../../../components/ha-tooltip";
import type { LocalizeFunc } from "../../../common/translations/localize"; import type { LocalizeFunc } from "../../../common/translations/localize";
import { import {
hasRejectedItems, hasRejectedItems,
@@ -329,19 +327,14 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
const date = new Date(automation.last_triggered); const date = new Date(automation.last_triggered);
const now = new Date(); const now = new Date();
const dayDifference = differenceInDays(now, 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` return html`
${dayDifference > 3 ${dayDifference > 3
? formattedTime ? formatShortDateTimeWithConditionalYear(
: html` date,
<ha-tooltip for=${elementId}>${formattedTime}</ha-tooltip> this.hass.locale,
<span id=${elementId}>${relativeTime(date, locale)}</span> this.hass.config
`} )
: relativeTime(date, locale)}
`; `;
}, },
}, },

View File

@@ -97,9 +97,9 @@ export default class HaAutomationSidebarAction extends LitElement {
title = `${domainToName(this.hass.localize, domain)}: ${ title = `${domainToName(this.hass.localize, domain)}: ${
this.hass.localize( this.hass.localize(
`component.${domain}.services.${service}.name`, `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 title
}`; }`;
} }

View File

@@ -24,7 +24,7 @@ import {
type Trigger, type Trigger,
type TriggerList, type TriggerList,
} from "../../../../data/automation"; } from "../../../../data/automation";
import { subscribeLabFeatures } from "../../../../data/labs"; import { subscribeLabFeature } from "../../../../data/labs";
import type { TriggerDescriptions } from "../../../../data/trigger"; import type { TriggerDescriptions } from "../../../../data/trigger";
import { isTriggerList, subscribeTriggers } from "../../../../data/trigger"; import { isTriggerList, subscribeTriggers } from "../../../../data/trigger";
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin"; import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
@@ -85,14 +85,14 @@ export default class HaAutomationTrigger extends SubscribeMixin(LitElement) {
protected hassSubscribe() { protected hassSubscribe() {
return [ return [
subscribeLabFeatures(this.hass!.connection, (features) => { subscribeLabFeature(
this._newTriggersAndConditions = this.hass!.connection,
features.find( "automation",
(feature) => "new_triggers_conditions",
feature.domain === "automation" && (feature) => {
feature.preview_feature === "new_triggers_conditions" this._newTriggersAndConditions = feature.enabled;
)?.enabled ?? false; }
}), ),
]; ];
} }

View File

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

View File

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

View File

@@ -1,20 +1,18 @@
import { import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
mdiCheckboxBlankOutline, import { mdiDotsVertical, mdiRefresh } from "@mdi/js";
mdiCheckboxMarked,
mdiDotsVertical,
mdiLocationEnter,
mdiLocationExit,
mdiRefresh,
} from "@mdi/js";
import type { HassEntities } from "home-assistant-js-websocket"; import type { HassEntities } from "home-assistant-js-websocket";
import type { TemplateResult } from "lit"; import type { TemplateResult } from "lit";
import { LitElement, css, html } from "lit"; import { LitElement, css, html } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event";
import "../../../components/ha-alert"; import "../../../components/ha-alert";
import "../../../components/ha-bar"; import "../../../components/ha-bar";
import "../../../components/ha-button-menu";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/ha-check-list-item";
import "../../../components/ha-list-item";
import "../../../components/ha-metric"; import "../../../components/ha-metric";
import { extractApiErrorMessage } from "../../../data/hassio/common"; import { extractApiErrorMessage } from "../../../data/hassio/common";
import type { import type {
@@ -35,9 +33,6 @@ import "../../../layouts/hass-subpage";
import type { HomeAssistant } from "../../../types"; import type { HomeAssistant } from "../../../types";
import "../dashboard/ha-config-updates"; import "../dashboard/ha-config-updates";
import { showJoinBetaDialog } from "./updates/show-dialog-join-beta"; import { showJoinBetaDialog } from "./updates/show-dialog-join-beta";
import "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import "@home-assistant/webawesome/dist/components/divider/divider";
@customElement("ha-config-section-updates") @customElement("ha-config-section-updates")
class HaConfigSectionUpdates extends LitElement { class HaConfigSectionUpdates extends LitElement {
@@ -78,44 +73,35 @@ class HaConfigSectionUpdates extends LitElement {
.path=${mdiRefresh} .path=${mdiRefresh}
@click=${this._checkUpdates} @click=${this._checkUpdates}
></ha-icon-button> ></ha-icon-button>
<ha-dropdown @wa-select=${this._handleOverflowAction}> <ha-button-menu multi>
<ha-icon-button <ha-icon-button
slot="trigger" slot="trigger"
.label=${this.hass.localize("ui.common.menu")} .label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical} .path=${mdiDotsVertical}
></ha-icon-button> ></ha-icon-button>
<ha-check-list-item
<ha-dropdown-item value="show_skipped"> left
<ha-svg-icon @request-selected=${this._toggleSkipped}
.path=${this._showSkipped .selected=${this._showSkipped}
? mdiCheckboxMarked >
: mdiCheckboxBlankOutline}
slot="icon"
></ha-svg-icon>
${this.hass.localize("ui.panel.config.updates.show_skipped")} ${this.hass.localize("ui.panel.config.updates.show_skipped")}
</ha-dropdown-item> </ha-check-list-item>
${this._supervisorInfo ${this._supervisorInfo
? html` ? html`
<wa-divider></wa-divider> <li divider role="separator"></li>
<ha-dropdown-item <ha-list-item
value="toggle_beta" @request-selected=${this._toggleBeta}
.disabled=${this._supervisorInfo.channel === "dev"} .disabled=${this._supervisorInfo.channel === "dev"}
> >
<ha-svg-icon
.path=${this._supervisorInfo.channel === "stable"
? mdiLocationEnter
: mdiLocationExit}
slot="icon"
></ha-svg-icon>
${this._supervisorInfo.channel === "stable" ${this._supervisorInfo.channel === "stable"
? this.hass.localize("ui.panel.config.updates.join_beta") ? this.hass.localize("ui.panel.config.updates.join_beta")
: this.hass.localize( : this.hass.localize(
"ui.panel.config.updates.leave_beta" "ui.panel.config.updates.leave_beta"
)} )}
</ha-dropdown-item> </ha-list-item>
` `
: ""} : ""}
</ha-dropdown> </ha-button-menu>
</div> </div>
<div class="content"> <div class="content">
<ha-card outlined> <ha-card outlined>
@@ -147,19 +133,27 @@ class HaConfigSectionUpdates extends LitElement {
this._supervisorInfo = await fetchHassioSupervisorInfo(this.hass); this._supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
} }
private async _handleOverflowAction( private _toggleSkipped(ev: CustomEvent<RequestSelectedDetail>): void {
ev: CustomEvent<{ item: { value: string } }> if (ev.detail.source !== "property") {
return;
}
this._showSkipped = !this._showSkipped;
}
private async _toggleBeta(
ev: CustomEvent<RequestSelectedDetail>
): Promise<void> { ): Promise<void> {
if (ev.detail.item.value === "toggle_beta") { if (!shouldHandleRequestSelectedEvent(ev)) {
if (this._supervisorInfo!.channel === "stable") { return;
showJoinBetaDialog(this, { }
join: async () => this._setChannel("beta"),
}); if (this._supervisorInfo!.channel === "stable") {
} else { showJoinBetaDialog(this, {
this._setChannel("stable"); join: async () => this._setChannel("beta"),
} });
} else if (ev.detail.item.value === "show_skipped") { } else {
this._showSkipped = !this._showSkipped; this._setChannel("stable");
} }
} }

View File

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

View File

@@ -1012,6 +1012,7 @@ export class EntityRegistrySettingsEditor extends LitElement {
? html`<ha-area-picker ? html`<ha-area-picker
.hass=${this.hass} .hass=${this.hass}
.value=${this._areaId} .value=${this._areaId}
.placeholder=${this._device?.area_id}
.disabled=${this.disabled} .disabled=${this.disabled}
@value-changed=${this._areaPicked} @value-changed=${this._areaPicked}
></ha-area-picker>` ></ha-area-picker>`

View File

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

View File

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

View File

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

View File

@@ -9,10 +9,10 @@ export class MatterAddDevice extends HTMLElement {
public hass!: HomeAssistant; public hass!: HomeAssistant;
connectedCallback() { connectedCallback() {
showMatterAddDeviceDialog(this); navigate("/config/devices/dashboard", {
navigate(`/config/devices`, {
replace: true, replace: true,
}); });
showMatterAddDeviceDialog(this);
} }
} }

View File

@@ -1,6 +1,6 @@
import type { CSSResultGroup, PropertyValues } from "lit"; import type { CSSResultGroup, PropertyValues } from "lit";
import { css, html, LitElement, nothing } 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 { stopPropagation } from "../../../../../common/dom/stop_propagation";
import "../../../../../components/buttons/ha-call-service-button"; import "../../../../../components/buttons/ha-call-service-button";
import "../../../../../components/ha-card"; import "../../../../../components/ha-card";
@@ -15,7 +15,6 @@ import type { HomeAssistant } from "../../../../../types";
import { formatAsPaddedHex } from "./functions"; import { formatAsPaddedHex } from "./functions";
import type { IssueCommandServiceData } from "./types"; import type { IssueCommandServiceData } from "./types";
@customElement("zha-cluster-commands")
export class ZHAClusterCommands extends LitElement { export class ZHAClusterCommands extends LitElement {
@property({ attribute: false }) public hass?: HomeAssistant; @property({ attribute: false }) public hass?: HomeAssistant;
@@ -260,3 +259,5 @@ declare global {
"zha-cluster-commands": ZHAClusterCommands; "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 { private get _canBind(): boolean {
return Boolean( return Boolean(
this._groupToBind && this._groupToBind &&
this._clustersToBind && this._clustersToBind &&
this._clustersToBind?.length > 0 && this._clustersToBind?.length > 0 &&
this.device this.device
); );
} }

View File

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

View File

@@ -1,7 +1,7 @@
import { mdiPencil } from "@mdi/js"; import { mdiPencil } from "@mdi/js";
import type { CSSResultGroup } from "lit"; import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } 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 memoizeOne from "memoize-one";
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/entity/ha-entities-picker"; import "../../../components/entity/ha-entities-picker";
@@ -43,7 +43,6 @@ const cropOptions: CropOptions = {
aspectRatio: 1, aspectRatio: 1,
}; };
@customElement("dialog-person-detail")
class DialogPersonDetail extends LitElement implements HassDialog { class DialogPersonDetail extends LitElement implements HassDialog {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@@ -568,3 +567,5 @@ declare global {
"dialog-person-detail": DialogPersonDetail; "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 { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { computeCssColor } from "../../../common/color/compute-color"; 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 { relativeTime } from "../../../common/datetime/relative_time";
import { storage } from "../../../common/decorators/storage"; import { storage } from "../../../common/decorators/storage";
import type { HASSDomEvent } from "../../../common/dom/fire_event"; import type { HASSDomEvent } from "../../../common/dom/fire_event";
@@ -301,21 +301,10 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
const date = new Date(scene.state); const date = new Date(scene.state);
const now = new Date(); const now = new Date();
const dayDifference = differenceInDays(now, 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` return html`
${dayDifference > 3 ${dayDifference > 3
? formattedTime ? formatShortDateTime(date, this.hass.locale, this.hass.config)
: html` : relativeTime(date, this.hass.locale)}
<ha-tooltip for=${elementId}>${formattedTime}</ha-tooltip>
<span id=${elementId}
>${relativeTime(date, this.hass.locale)}</span
>
`}
`; `;
}, },
}, },

View File

@@ -21,7 +21,7 @@ import {
} from "@mdi/js"; } from "@mdi/js";
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit"; import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import { LitElement, css, html, nothing } 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 { classMap } from "lit/directives/class-map";
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import { goBack, navigate } from "../../../common/navigate"; import { goBack, navigate } from "../../../common/navigate";
@@ -79,7 +79,6 @@ import "./manual-script-editor";
import type { HaManualScriptEditor } from "./manual-script-editor"; import type { HaManualScriptEditor } from "./manual-script-editor";
import { showAutomationSaveTimeoutDialog } from "../automation/automation-save-timeout-dialog/show-dialog-automation-save-timeout"; import { showAutomationSaveTimeoutDialog } from "../automation/automation-save-timeout-dialog/show-dialog-automation-save-timeout";
@customElement("ha-script-editor")
export class HaScriptEditor extends SubscribeMixin( export class HaScriptEditor extends SubscribeMixin(
PreventUnsavedMixin(KeyboardShortcutMixin(LitElement)) PreventUnsavedMixin(KeyboardShortcutMixin(LitElement))
) { ) {
@@ -1279,6 +1278,8 @@ export class HaScriptEditor extends SubscribeMixin(
} }
} }
customElements.define("ha-script-editor", HaScriptEditor);
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
"ha-script-editor": HaScriptEditor; "ha-script-editor": HaScriptEditor;

View File

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

View File

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

View File

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

View File

@@ -215,57 +215,62 @@ export class AssistPipelineDebug extends LitElement {
? html` ? html`
<div class="messages"> <div class="messages">
${messages.map((content) => ${messages.map((content) =>
content.role === "system" || content.role === "tool_result" content.role === "system"
? html` ? content.content
<ha-expansion-panel ? html`
class="content-expansion ${content.role}" <ha-expansion-panel
> class="content-expansion ${content.role}"
<div slot="header"> >
${content.role === "system" <div slot="header">System</div>
? "System" <pre>${content.content}</pre>
: `Result for ${content.tool_name}`} </ha-expansion-panel>
</div> `
${content.role === "system" : nothing
? html`<pre>${content.content}</pre>` : content.role === "tool_result"
: html` ? html`
<ha-yaml-editor <ha-expansion-panel
read-only class="content-expansion ${content.role}"
auto-update >
.value=${content} <div slot="header">
></ha-yaml-editor> Result for ${content.tool_name}
`} </div>
</ha-expansion-panel> <ha-yaml-editor
` read-only
: html` auto-update
${content.content .value=${content}
? html` ></ha-yaml-editor>
<div class=${`message ${content.role}`}> </ha-expansion-panel>
${content.content} `
</div> : html`
` ${content.content
: nothing} ? html`
${content.role === "assistant" && <div class=${`message ${content.role}`}>
content.tool_calls?.length ${content.content}
? html` </div>
<ha-expansion-panel `
class="content-expansion assistant" : nothing}
> ${content.role === "assistant" &&
<span slot="header"> content.tool_calls?.length
Call ? html`
${content.tool_calls.length === 1 <ha-expansion-panel
? content.tool_calls[0].tool_name class="content-expansion assistant"
: `${content.tool_calls.length} tools`} >
</span> <span slot="header">
Call
${content.tool_calls.length === 1
? content.tool_calls[0].tool_name
: `${content.tool_calls.length} tools`}
</span>
<ha-yaml-editor <ha-yaml-editor
read-only read-only
auto-update auto-update
.value=${content.tool_calls} .value=${content.tool_calls}
></ha-yaml-editor> ></ha-yaml-editor>
</ha-expansion-panel> </ha-expansion-panel>
` `
: nothing} : nothing}
` `
)} )}
</div> </div>
<div style="clear:both"></div> <div style="clear:both"></div>

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
import type { PropertyValues } from "lit"; import type { PropertyValues } from "lit";
import { ReactiveElement } from "lit"; import { ReactiveElement } from "lit";
import { customElement, property } from "lit/decorators"; import { property } from "lit/decorators";
import type { NavigateOptions } from "../../common/navigate"; import type { NavigateOptions } from "../../common/navigate";
import { navigate } from "../../common/navigate"; import { navigate } from "../../common/navigate";
import { deepEqual } from "../../common/util/deep-equal"; import { deepEqual } from "../../common/util/deep-equal";
@@ -22,7 +22,6 @@ declare global {
} }
} }
@customElement("ha-panel-custom")
export class HaPanelCustom extends ReactiveElement { export class HaPanelCustom extends ReactiveElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@@ -172,3 +171,5 @@ export class HaPanelCustom extends ReactiveElement {
iframeDoc.close(); iframeDoc.close();
} }
} }
customElements.define("ha-panel-custom", HaPanelCustom);

View File

@@ -1,7 +1,7 @@
import { mdiHelpCircle } from "@mdi/js"; import { mdiHelpCircle } from "@mdi/js";
import type { HassService } from "home-assistant-js-websocket"; import type { HassService } from "home-assistant-js-websocket";
import { ERR_CONNECTION_LOST } from "home-assistant-js-websocket"; import { ERR_CONNECTION_LOST } from "home-assistant-js-websocket";
import { dump, load } from "js-yaml"; import { load } from "js-yaml";
import type { CSSResultGroup, TemplateResult } from "lit"; import type { CSSResultGroup, TemplateResult } from "lit";
import { css, html, LitElement, nothing } from "lit"; import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
@@ -320,10 +320,7 @@ class HaPanelDevAction extends LitElement {
${this.hass.localize( ${this.hass.localize(
`component.${domain}.services.${serviceName}.fields.${field.key}.example`, `component.${domain}.services.${serviceName}.fields.${field.key}.example`,
descriptionPlaceholders descriptionPlaceholders
) || ) || field.example}
(typeof field.example === "object"
? html`<pre>${dump(field.example)}</pre>`
: field.example)}
</td> </td>
</tr>` </tr>`
)} )}

View File

@@ -547,9 +547,9 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
} else if (this._sortDirection === "asc") { } else if (this._sortDirection === "asc") {
this._sortDirection = "desc"; this._sortDirection = "desc";
} else { } else {
this._sortDirection = "asc"; this._sortDirection = null;
} }
this._sortColumn = columnId; this._sortColumn = this._sortDirection === null ? undefined : columnId;
} }
private _handleGroupBy(ev) { private _handleGroupBy(ev) {

View File

@@ -268,8 +268,10 @@ class PanelEnergy extends LitElement {
(source) => source.type === "gas" (source) => source.type === "gas"
); );
const hasDeviceConsumption = this._prefs.device_consumption.length > 0;
const views: LovelaceViewConfig[] = []; const views: LovelaceViewConfig[] = [];
if (hasEnergy) { if (hasEnergy || hasDeviceConsumption) {
views.push(ENERGY_VIEW); views.push(ENERGY_VIEW);
} }
if (hasGas) { if (hasGas) {

View File

@@ -2,20 +2,12 @@ import { ReactiveElement } from "lit";
import { customElement } from "lit/decorators"; import { customElement } from "lit/decorators";
import type { GridSourceTypeEnergyPreference } from "../../../data/energy"; import type { GridSourceTypeEnergyPreference } from "../../../data/energy";
import { getEnergyDataCollection } from "../../../data/energy"; import { getEnergyDataCollection } from "../../../data/energy";
import type { HomeAssistant } from "../../../types";
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy";
import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section"; import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section";
import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy";
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
import type { HomeAssistant } from "../../../types";
import { DEFAULT_ENERGY_COLLECTION_KEY } from "../ha-panel-energy"; import { DEFAULT_ENERGY_COLLECTION_KEY } from "../ha-panel-energy";
const sourceHasCost = (source: Record<string, any>): boolean =>
Boolean(
source.stat_cost ||
source.stat_compensation ||
source.entity_energy_price ||
source.number_energy_price
);
@customElement("energy-overview-view-strategy") @customElement("energy-overview-view-strategy")
export class EnergyOverviewViewStrategy extends ReactiveElement { export class EnergyOverviewViewStrategy extends ReactiveElement {
static async generate( static async generate(
@@ -35,6 +27,9 @@ export class EnergyOverviewViewStrategy extends ReactiveElement {
const energyCollection = getEnergyDataCollection(hass, { const energyCollection = getEnergyDataCollection(hass, {
key: collectionKey, key: collectionKey,
}); });
if (!energyCollection.prefs) {
await energyCollection.refresh();
}
const prefs = energyCollection.prefs; const prefs = energyCollection.prefs;
// No energy sources available // No energy sources available
@@ -68,13 +63,6 @@ export class EnergyOverviewViewStrategy extends ReactiveElement {
(source.type === "battery" && source.stat_rate) || (source.type === "battery" && source.stat_rate) ||
(source.type === "grid" && source.power?.length) (source.type === "grid" && source.power?.length)
); );
const hasCost = prefs.energy_sources.some(
(source) =>
sourceHasCost(source) ||
(source.type === "grid" &&
(source.flow_from?.some(sourceHasCost) ||
source.flow_to?.some(sourceHasCost)))
);
const overviewSection: LovelaceSectionConfig = { const overviewSection: LovelaceSectionConfig = {
type: "grid", type: "grid",
@@ -95,7 +83,7 @@ export class EnergyOverviewViewStrategy extends ReactiveElement {
} }
view.sections!.push(overviewSection); view.sections!.push(overviewSection);
if (hasCost) { if (prefs.energy_sources.length) {
view.sections!.push({ view.sections!.push({
type: "grid", type: "grid",
cards: [ cards: [

View File

@@ -21,6 +21,9 @@ export class EnergyViewStrategy extends ReactiveElement {
const energyCollection = getEnergyDataCollection(hass, { const energyCollection = getEnergyDataCollection(hass, {
key: collectionKey, key: collectionKey,
}); });
if (!energyCollection.prefs) {
await energyCollection.refresh();
}
const prefs = energyCollection.prefs; const prefs = energyCollection.prefs;
// No energy sources available // No energy sources available

View File

@@ -24,6 +24,9 @@ export class GasViewStrategy extends ReactiveElement {
const energyCollection = getEnergyDataCollection(hass, { const energyCollection = getEnergyDataCollection(hass, {
key: collectionKey, key: collectionKey,
}); });
if (!energyCollection.prefs) {
await energyCollection.refresh();
}
const prefs = energyCollection.prefs; const prefs = energyCollection.prefs;
const hasGasSources = prefs?.energy_sources.some( const hasGasSources = prefs?.energy_sources.some(

View File

@@ -24,6 +24,9 @@ export class WaterViewStrategy extends ReactiveElement {
const energyCollection = getEnergyDataCollection(hass, { const energyCollection = getEnergyDataCollection(hass, {
key: collectionKey, key: collectionKey,
}); });
if (!energyCollection.prefs) {
await energyCollection.refresh();
}
const prefs = energyCollection.prefs; const prefs = energyCollection.prefs;
const hasWaterSources = prefs?.energy_sources.some( const hasWaterSources = prefs?.energy_sources.some(

View File

@@ -12,7 +12,7 @@ import type {
} from "home-assistant-js-websocket/dist/types"; } from "home-assistant-js-websocket/dist/types";
import type { PropertyValues } from "lit"; import type { PropertyValues } from "lit";
import { LitElement, css, html } from "lit"; import { LitElement, css, html } from "lit";
import { customElement, property, query, state } from "lit/decorators"; import { property, query, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { ensureArray } from "../../common/array/ensure-array"; import { ensureArray } from "../../common/array/ensure-array";
import { storage } from "../../common/decorators/storage"; import { storage } from "../../common/decorators/storage";
@@ -52,7 +52,6 @@ import type { HomeAssistant } from "../../types";
import { fileDownload } from "../../util/file_download"; import { fileDownload } from "../../util/file_download";
import { addEntitiesToLovelaceView } from "../lovelace/editor/add-entities-to-view"; import { addEntitiesToLovelaceView } from "../lovelace/editor/add-entities-to-view";
@customElement("ha-panel-history")
class HaPanelHistory extends LitElement { class HaPanelHistory extends LitElement {
@property({ attribute: false }) hass!: HomeAssistant; @property({ attribute: false }) hass!: HomeAssistant;
@@ -632,6 +631,7 @@ class HaPanelHistory extends LitElement {
:host([virtualize]) { :host([virtualize]) {
height: 100%; height: 100%;
--ha-generic-picker-max-width: 400px;
} }
.progress-wrapper { .progress-wrapper {
@@ -680,6 +680,8 @@ class HaPanelHistory extends LitElement {
} }
} }
customElements.define("ha-panel-history", HaPanelHistory);
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
"ha-panel-history": HaPanelHistory; "ha-panel-history": HaPanelHistory;

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