Compare commits

..

313 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
be810d1f09 Add shared styles and loading animation to automation/script editor mixin
Co-authored-by: wendevlin <12148533+wendevlin@users.noreply.github.com>
2026-02-26 12:45:35 +00:00
Wendelin
8e6f693c55 Simplify automation/script editor mixin signature 2026-02-26 11:15:09 +01:00
copilot-swe-agent[bot]
c9d35c0500 Fix mixin: use function-body syntax for decorators, curried generics for type safety
Co-authored-by: wendevlin <12148533+wendevlin@users.noreply.github.com>
2026-02-26 08:17:45 +00:00
copilot-swe-agent[bot]
3b2a1ed5be Changes before error encountered
Co-authored-by: wendevlin <12148533+wendevlin@users.noreply.github.com>
2026-02-26 07:43:55 +00:00
copilot-swe-agent[bot]
1c50432dd4 Initial plan 2026-02-26 07:12:09 +00:00
dependabot[bot]
3c3d8d9974 Bump rollup from 2.79.2 to 2.80.0 (#29841)
Bumps [rollup](https://github.com/rollup/rollup) from 2.79.2 to 2.80.0.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/v2.80.0/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v2.79.2...v2.80.0)

---
updated-dependencies:
- dependency-name: rollup
  dependency-version: 2.80.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-26 08:18:15 +02:00
Paul Bottein
4f39fa482d Only ask to refresh dashboard in edit mode or yaml mode (#29826) 2026-02-26 08:16:21 +02:00
renovate[bot]
5d0fe3236c Update dependency @swc/helpers to v0.5.19 (#29836)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-26 07:07:37 +01:00
renovate[bot]
b86142ae50 Update Node.js to v24.14.0 (#29831)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-25 19:25:42 +00:00
renovate[bot]
5d2f3ee5e8 Update dependency tar to v7.5.9 (#29832)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-25 19:24:58 +00:00
AlCalzone
e3f7c631a7 Rename "Z-Wave JS" to "Z-Wave" when not referring to the project/org (#29830) 2026-02-25 19:15:16 +00:00
renovate[bot]
49f9d95853 Update dependency vite-tsconfig-paths to v6.1.1 (#29829)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-25 18:53:12 +01:00
renovate[bot]
db3d7701b5 Update dependency typescript-eslint to v8.56.0 (#29828)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-25 17:35:36 +00:00
renovate[bot]
3e55acf531 Update dependency @home-assistant/webawesome to v3.2.1-ha.3 (#29810)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-25 18:26:47 +01:00
renovate[bot]
f102618d9d Update dependency eslint-plugin-wc to v3.1.0 (#29824)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-25 18:25:06 +01:00
renovate[bot]
a3c02b511d Update dependency jsdom to v28.1.0 (#29825)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-25 18:24:38 +01:00
Bram Kragten
74111d248e Fix css minifying (#29827) 2026-02-25 17:53:50 +01:00
Bram Kragten
f8161b3505 Merge branch 'rc' into dev 2026-02-25 17:13:44 +01:00
Franck Nijhof
6070c1907a Adjust brands assets to proxy brand images through local API (#29799)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2026-02-25 17:10:38 +01:00
renovate[bot]
ce5991582c Update dependency @html-eslint/eslint-plugin to v0.56.0 (#29818)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-25 17:37:34 +02:00
Paul Bottein
d17217fc90 Use show in sidebar property instead of checking title (#29815) 2026-02-25 16:37:25 +01:00
renovate[bot]
86b4bd0013 Update dependency eslint-plugin-lit to v2.2.1 (#29821)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-25 17:36:42 +02:00
renovate[bot]
108ba3abd6 Update dependency eslint-plugin-unused-imports to v4.4.1 (#29822)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-25 17:36:12 +02:00
Matthias Alphart
d38a2894c4 Remove unused properties in ha-data-table and hass-tabs-subpage-data-table (#29808) 2026-02-25 16:31:39 +01:00
Aidan Timson
4c70376a62 Cleanup old comments (#29823) 2026-02-25 15:24:00 +00:00
Wendelin
8d69bd1401 Fix button active also for icon-buttons (#29820) 2026-02-25 16:21:31 +01:00
renovate[bot]
5dfecd3693 Update dependency @octokit/plugin-retry to v8.1.0 (#29819)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-25 15:09:19 +00:00
Aidan Timson
efd51d2234 Rename more info "Attributes" to "Details", add raw state and all available attributes (#29811) 2026-02-25 15:57:27 +01:00
renovate[bot]
668299c16a Update dependency marked to v17.0.3 (#29817)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-25 16:00:51 +02:00
renovate[bot]
5e155a4030 Update dependency glob to v13.0.6 (#29816)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-25 16:00:25 +02:00
gpoitch
809fa10135 Add day of week to energy chart tooltips (#29803)
* Add day of week to energy chart tooltips

* New localization helpers
2026-02-25 13:31:00 +00:00
Petar Petrov
1cbc38f231 Water flow rate sankey chart in Now view (#29804) 2026-02-25 14:18:48 +01:00
renovate[bot]
9ed39bb523 Update dependency @rspack/core to v1.7.6 (#29812)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-25 15:10:38 +02:00
renovate[bot]
4e3d66cf40 Update dependency eslint to v9.39.3 (#29813)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-25 15:10:03 +02:00
renovate[bot]
2eaad79d1c Update dependency @codemirror/view to v6.39.15 (#29807)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-25 12:47:06 +02:00
renovate[bot]
afef7a2c0f Update dependency @bundle-stats/plugin-webpack-filter to v4.21.10 (#29806)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-25 12:46:41 +02:00
renovate[bot]
18d5224002 Update dependency @formatjs/intl-datetimeformat to v7.2.2 (#29809)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-25 12:46:08 +02:00
Johan Henkens
dbffdfeaca Add cover of device type window to the security dashboard (#29797) 2026-02-25 11:11:13 +01:00
Artur Pragacz
0a4b7917ab Update vacuum segment mapping description (#29802) 2026-02-25 08:38:37 +01:00
Simon Lamon
e1524358d9 Remove duplicated buttons (#29798) 2026-02-25 08:22:40 +01:00
Artur Pragacz
8774f9c3fc Add vacuum mapping not configured issue (#29800) 2026-02-25 08:14:38 +01:00
Wendelin
f9a9aeacab Fix app panel narrow header safe area top (#29792)
* Enhance narrow property to reflect changes and adjust header padding for safe area

* Remove safe-area-inset-top for narrow iframe

* handle kiosk mode
2026-02-24 20:26:06 +01:00
ildar170975
b798fee116 Data tables: keep "Actions" as the last column (#29364)
* Data tables: keep "Actions" as the last column

* last_fixed -> lastFixed

* last_fixed -> lastFixed

* last_fixed -> lastFixed

* last_fixed -> lastFixed

* last_fixed -> lastFixed

* last_fixed -> lastFixed

* last_fixed -> lastFixed

* last_fixed -> lastFixed

* last_fixed -> lastFixed

* last_fixed -> lastFixed

* simplify

* Update dialog-data-table-settings.ts

* narrow down a column

* blank line added

* narrow dow Assistants a bit more

* remove moveable/hideable for "actions"

* remove moveable/hideable for "actions"

* remove moveable/hideable for "actions"

* remove moveable/hideable for "actions"

* remove moveable/hideable for "actions"

* remove moveable/hideable for "actions"

* remove moveable/hideable for "actions"

* remove moveable/hideable for "actions"
2026-02-24 20:24:53 +01:00
Norbert Rittel
b25f731f0f Simplify card descriptions using "This …" instead of repeating the name (#29795)
Simplify card description using "This …" instead of repeating the name
2026-02-24 20:17:05 +01:00
Paul Bottein
26a7372c5e Don't show label for toggle all lights and align individual lights (#29794) 2026-02-24 17:28:53 +01:00
Paul Bottein
70d3409d62 Don't use navigation history when using tabs (#29791) 2026-02-24 18:03:48 +02:00
Wendelin
0711ecddab Handle selector edge case for [] (#29790) 2026-02-24 15:30:29 +00:00
Petar Petrov
bcfaa67eba Add power, water and gas current flow rate tile cards (#29788) 2026-02-24 16:01:32 +01:00
Matthias de Baat
1b60e6e04e Reorganize Zigbee settings page (#29671)
Co-authored-by: TheJulianJES <TheJulianJES@users.noreply.github.com>
Co-authored-by: Norbert Rittel <norbert@rittel.de>
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2026-02-24 15:11:36 +01:00
Petar Petrov
a1a634f6dc Add footer card support to sections view (#29620)
Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com>
Co-authored-by: Wendelin <w@pe8.at>
2026-02-24 11:00:50 +00:00
Petar Petrov
55f48fbb56 Add tabs to energy config page (#29689) 2026-02-24 09:43:02 +00:00
Norbert Rittel
ca4d66b94c Change second tab to "Electricity" in Energy dashboard (#29787) 2026-02-24 10:13:22 +01:00
Aidan Timson
51fd2eedd9 Update gallery with latest adaptive dialog changes (#29672)
* Update gallery with latest adaptive dialog changes

* Update gallery/src/pages/components/ha-adaptive-dialog.ts

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

* Format

---------

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2026-02-24 06:57:31 +01:00
ildar170975
434a7c2e93 "Numeric state" trigger editor: add a "filter_entity" context to "attribute" selector (#29778)
* add a "filter_entity" context to "attribute" selector

* remove unused variable
2026-02-24 07:55:49 +02:00
Petar Petrov
b849fecf0b Add flow rate picker to gas, water, and water device energy dialogs (#29693)
* Add flow rate picker to gas, water, and water device energy dialogs

Add optional flow rate (stat_rate) picker to gas source, water source,
and water device configuration dialogs, matching the pattern used for
power tracking in grid/solar/battery sources and energy devices.

- Add stat_rate to GasSourceTypeEnergyPreference and WaterSourceTypeEnergyPreference
- Collect gas/water stat_rate in getReferencedStatisticIdsPower()
- Add flow rate ha-statistic-picker filtered to volume_flow_rate device class
- Move entity help text to picker helper props for cleaner layout

* Apply suggestions from code review

Co-authored-by: Norbert Rittel <norbert@rittel.de>

---------

Co-authored-by: Norbert Rittel <norbert@rittel.de>
2026-02-24 06:55:35 +01:00
ildar170975
3a48e1996f hui-entities-card: fix "buttons-header-footer" margin-bottom (#29783)
* fix margin-bottom for hui-buttons-header-footer

* typo
2026-02-24 07:54:50 +02:00
ildar170975
8299386737 ha-entity-attribute-picker: add valueRenderer (#29780)
add valueRenderer
2026-02-24 06:52:10 +01:00
Raphael Hehl
5e58ff476f Re-initialize camera stream when backend finishes starting (#29752) 2026-02-23 16:59:53 +01:00
Paul Bottein
758d955053 Add configuration to built-in panels (#29572)
Co-authored-by: Norbert Rittel <norbert@rittel.de>
Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2026-02-23 16:15:54 +01:00
Kevin Stillhammer
1efd5d26f0 Show allow_negative in DurationSelector options (#29775) 2026-02-23 16:02:33 +01:00
Aidan Timson
36979f10cc Fix types for dialog hide events (#29777) 2026-02-23 15:54:36 +01:00
Aidan Timson
812c59fcb4 Add missing back path for protocol config dashboards (#29770) 2026-02-23 15:36:50 +01:00
karwosts
0c34165bcf Disallow moving a section to non-sections view (#29756) 2026-02-23 10:54:11 +00:00
Matthias de Baat
8c2bfbe9ce Reorganize Matter settings (#29708)
Co-authored-by: Norbert Rittel <norbert@rittel.de>
2026-02-23 10:31:38 +00:00
Aidan Timson
8f721d74e2 Fix swipe action bubbling up to stacked bottom drawer/sheet (#29768) 2026-02-23 10:26:10 +01:00
dependabot[bot]
63782e6ef3 Bump lodash from 4.17.21 to 4.17.23 (#29767)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-23 10:22:31 +01:00
Marie-Alice Blete
eaad2295a9 Fix ZHA config dashboard button animation targeting (#29744)
Co-authored-by: Marie-Alice Blete <malywut@users.noreply.github.com>
2026-02-23 10:18:32 +01:00
Norbert Rittel
e74eee3d34 Make all picker strings of Frontend conditions consistent (#29742) 2026-02-23 09:54:14 +01:00
karwosts
cc39010839 Fix group more-info names for not-in-registry entities (#29758) 2026-02-23 09:50:55 +01:00
Kevin Stillhammer
7f97425214 Allow to disable seconds in DurationSelector (#29760) 2026-02-23 09:30:04 +01:00
dependabot[bot]
8fac6e63de Bump actions/stale from 10.1.1 to 10.2.0 (#29765)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-23 09:22:29 +01:00
dependabot[bot]
2ac8fe2b21 Bump github/codeql-action from 4.32.3 to 4.32.4 (#29766)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-23 09:22:18 +01:00
Aidan Timson
45ca1b2cdc Fix esc closing all dialogs or sheets (close one after another) (#29732) 2026-02-23 09:13:28 +01:00
Matthias de Baat
0667f1e789 Reorganize Bluetooth settings (#29723)
* Reorganize Bluetooth settings

* Additional changes

* Updates adapter page

* Update statuses

* Fix button icon

* Update en.json

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

* Update bluetooth-adapter-info-page.ts

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

* Update text

* Show GATT message and make row clickable

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2026-02-23 06:59:09 +01:00
Norbert Rittel
db49678ccb Make descriptions of Frontend actions consistent with Core (#29740) 2026-02-20 18:03:19 +01:00
Norbert Rittel
2ca7e9f71e Make building block descriptions consistent with new conditions (#29739)
Make building block descriptions consistent with conditions
2026-02-20 18:02:41 +01:00
karwosts
8d883450a8 Add year period to stat graph card editor (#29741)
Add year period to stat graph card
2026-02-20 18:02:07 +01:00
dependabot[bot]
2c136e00f5 Bump tar from 7.5.7 to 7.5.8 (#29735)
* Bump tar from 7.5.7 to 7.5.8

Bumps [tar](https://github.com/isaacs/node-tar) from 7.5.7 to 7.5.8.
- [Release notes](https://github.com/isaacs/node-tar/releases)
- [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/isaacs/node-tar/compare/v7.5.7...v7.5.8)

---
updated-dependencies:
- dependency-name: tar
  dependency-version: 7.5.8
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>

* dedupe

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2026-02-20 07:05:32 +00:00
RoboMagus
6f82478598 Fix header tab height (#29736)
Fix header tabs to header height
2026-02-20 08:48:43 +02:00
Aidan Timson
1093bd890f Add missing helper to ha-select, remove unused attr (#29729) 2026-02-19 18:54:29 +01:00
Aidan Timson
456c638750 Use ha-scrollbar in config dashboard (#29724)
* Use ha-scrollbar in config dashboard

* Remove padding

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Add padding to bottom

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-19 18:52:32 +01:00
Aidan Timson
60ca50deb4 Add a drag handle visual indicator to bottom sheet (#29707)
* Add drag handle to bottom sheet

* Remove locks

* Fix rounded corners

* Restore original functionality, keep visual indicator

* Add padding to combo box

* Apply suggestion from @wendevlin

* Fix prettier

* Shorter height

Co-authored-by: Marcin Bauer <marcinbauer85@gmail.com>

* Half width

Co-authored-by: Marcin Bauer <marcinbauer85@gmail.com>

* Restore after rebase

* Reduce space for picker

---------

Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com>
Co-authored-by: Wendelin <w@pe8.at>
Co-authored-by: Marcin Bauer <marcinbauer85@gmail.com>
2026-02-19 14:24:09 +01:00
Matthias de Baat
2064ab4141 Reorganize Z-Wave settings page (#29697)
* Reorganize ZWave settings

* Next iteration

* Made more consistent with Zigbee settings page

* Update text

* Updates on the provisioned devices page

* Add identifier when you have multiple networks

* Update to force remove button

* Update button text

* Update rebuild text

* Update remove foreign device button text
2026-02-19 13:45:35 +02:00
karwosts
d34c42e587 Refine supported actions in button heading badge (#29718) 2026-02-19 12:49:30 +02:00
Joakim Sørensen
5da7bf6fba Add repository handling for missing addons in HaConfigAppDashboard (#29722)
* Add repository handling for missing addons in HaConfigAppDashboard

* Implement feedback

* More adjustments

* minor adjustment
2026-02-19 10:36:34 +00:00
Norbert Rittel
f05ff58d27 Replace "consumption" with "usage" for battery and grid energy (#29719)
Replace "consumption" with "usage" for battery and grid power
2026-02-19 10:59:47 +02:00
Aidan Timson
7b0a381d93 Use ha-scrollbar with history panel, fix overflow position (#29715)
Use ha-scrollbar with history panel
2026-02-18 18:04:46 +01:00
Aidan Timson
8b38e6d170 Switch dialog device registry detail to adaptive dialog (#29713) 2026-02-18 18:04:05 +01:00
Aidan Timson
6daf0eb469 Use ha-scrollbar with media browser (#29714) 2026-02-18 18:03:26 +01:00
Wendelin
6f8f849af3 Prevent bottom-sheet from closing from child elements (#29716)
Fix handling of after hide event in ha-bottom-sheet component
2026-02-18 16:19:43 +00:00
Aidan Timson
cafe0f62c6 Trigger add todo item dialog via search param (#29690)
* Fix scrim closure

* Trigger add todo item dialog via add_item=true search param

* Check supports before opening prompt

* Use in willUpdate

* Add subtitle as context using name of list
2026-02-18 16:28:20 +01:00
Wendelin
721cf46ce5 Migrate ha-icon-button to webawesome (#29622)
* Remove mwc-icon-button dependency and update ha-icon-button to use ha-button component

* --mdc-icon-button-size to --ha-icon-button-size

* Refactor ha-icon-button styles to improve encapsulation and remove redundant CSS rules

* add href functionality

* Migrate a wrapped ha-icon-button to ha-icon-button

* Update slot reference for ha-icon-button in hui-dialog-save-config

* fix overflow trigger

* Review

* fix sub icon buttons

* Fix attribute binding for href and target in ha-icon-button-next

* Fix binding for href and target properties in ha-icon-button

* Update src/layouts/hass-tabs-subpage.ts

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

* Update src/panels/lovelace/editor/hui-dialog-save-config.ts

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

* Update src/panels/lovelace/editor/badge-editor/hui-dialog-edit-badge.ts

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

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

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

* Fix icon-button slot

* Update src/components/ha-icon-button.ts

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

---------

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2026-02-18 16:18:28 +01:00
Marcin Bauer
4e087760ab Fix chip order in automation save dialog to match field order (#29710) 2026-02-18 15:18:07 +00:00
Aidan Timson
8fcfd4be84 Move scrolling for dashboards inside view container (#29444)
* Move scrolling for dashboards inside view container

* Use scrollbar styles on host

* Cleanup

* Inline

Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com>

---------

Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com>
2026-02-18 16:17:13 +01:00
Wendelin
b03680a8ab ha-automation-action-condition use generic-picker (#29702)
Refactor ha-automation-action-condition to use ha-generic-picker and improve condition rendering
2026-02-18 15:14:25 +00:00
ildar170975
7ab0622bec cloud-tts-pref: fix for language picker (#29678)
* fix styles to prevent oveflow

* use a new variable to define min-width

* pass a "minWidth" property into ha-language-picker

* use a "minWidth" property for ha-generic-picker

* Update ha-language-picker.ts

* pass empty minWidth

* do not set min-width if empty

* add a style for ha-language-picker

* remove a style for ha-language-picker

* add a style for ha-language-picker

* remove min-width

* add a style for ha-language-picker

* Update src/panels/profile/ha-pick-language-row.ts

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

* add a gap

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2026-02-18 14:05:41 +00:00
Bram Kragten
c5aad44768 Add support for vacuum segment mapping to areas (#29343)
* Add support for vacuum segment mapping to areas

* simplify, use list item

* Update ha-more-info-view-vacuum-segment-mapping.ts

* review

* review

* Update dialog-vacuum-segment-mapping.ts
2026-02-18 14:11:17 +01:00
Icecovery
20ee7e5dc7 Split antimeridian-crossing paths in ha-map (#29694)
* Add option to split antimeridian-crossing path to ha-map

and map card with related editor options

* Remove split antimeridian-crossing option in ha-map

making it the default behavior, as suggested by @karwosts. And remove the option from the map card

* Fix longitudeDifference is zero edge case
2026-02-18 12:08:03 +00:00
Petar Petrov
32fdcc708e Fix history timeline showing same color for all zones (#29700)
* Fix history timeline showing same color for all zones

For person and device_tracker entities, zone states (e.g. "Kitchen",
"Office") all resolved to --state-person-active-color because their
zone-specific CSS variables don't exist and the fallback chain always
landed on the generic active color.

Now zone states only check for an explicitly defined CSS variable
(e.g. --state-person-kitchen-color) and otherwise fall through to the
generic color handler which assigns a unique palette color per zone.

Fixes #14705

* Update src/components/chart/timeline-color.ts

Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com>

---------

Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com>
2026-02-18 10:45:15 +01:00
Aidan Timson
7dd9b3308e Switch more info dialog to adaptive dialog (#29664)
* Switch more info dialog to adaptive dialog

* Remove old attr

* Fixed height

* Add dialog styles for ha-adaptive-dialog, fixes fixed top

* Lock swipe for moveable components

* Add more components

* Add locked classes

* Refactor

* Revert "Refactor"

This reverts commit 041161715e.

* Merge for loops

* Use events to track slider interaction and prevent bottom sheet closure

* Update src/components/ha-bottom-sheet.ts

* Update src/resources/styles.ts

---------

Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com>
2026-02-18 08:55:11 +01:00
Aidan Timson
71b870be15 Fix styles for manage zha device dialog (#29684)
* Fix styles for manage zha device dialog

* Prevent scrim close
2026-02-18 09:41:45 +02:00
Simon Lamon
f08c5fa03a Assign no-stale to Tasks/Epic/Opportunity issue type (#29698)
* Enhance issue creation restrictions and labeling

Added functionality to restrict Task issue creation to organization members, authorized contributors, and integration code owners. Updated permissions and added a no-stale label for specific issue types.

* Refactor Task issue authorization checks

* Update github-script action and enhance Task issue handling
2026-02-18 09:37:31 +02:00
Wendelin
fca408ae23 Upgrade webawesome to version 3.2.1-ha.2 (#29691)
Upgrade webawesome to version 3.2.1-ha.2 and adjust animation durations to 0ms
2026-02-18 08:35:23 +02:00
Aidan Timson
f3a814e38a Cleanup dialog default width attrs (#29686) 2026-02-17 20:13:27 +01:00
Aidan Timson
7b0e4651c4 Fix iframe flash for dark theme using transition (#29685)
* Fix iframe flash for light mode using transition

* Use normal duration

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

* Use normal duration

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

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2026-02-17 16:53:06 +02:00
Petar Petrov
e5fb0e21ec Update animation duration variable names in copilot instructions (#29692)
The old `--ha-animation-base-duration` variable was split into three
separate variables: `--ha-animation-duration-fast` (150ms),
`--ha-animation-duration-normal` (250ms), and
`--ha-animation-duration-slow` (350ms).
2026-02-17 14:42:00 +01:00
Wendelin
beb4c3bf8a Fix panel safe area subpage usage (#29688) 2026-02-17 11:24:48 +00:00
Aidan Timson
ad41f91c7b Fix lovelace tab overflow fade height (#29687) 2026-02-17 13:15:49 +02:00
Wendelin
dc9c20f4ac Fix custom panel size (#29681) 2026-02-17 10:06:51 +00:00
Aidan Timson
776840a527 Add prevent-scrim-close to energy config dialogs (#29682)
Add  prevent-scrim-close to energy config dialogs
2026-02-17 11:39:39 +02:00
Aidan Timson
3568d8281a Add prevent-scrim-close to 3 form dialogs (#29683)
Add prevent-scrim-close to 3 dialogs
2026-02-17 11:39:10 +02:00
Arsène Reymond
5491b6c023 fix: invisible dashboard header section (#29679)
fix: minimum column count to 1
2026-02-17 06:42:10 +00:00
Simon Lamon
e60d8f3ca4 Unflex apps (#29675) 2026-02-16 19:38:25 +00:00
ildar170975
aa0df190ed ha-code-editor: use ha-scrollbar class (#29674)
use ha-scrollbar class
2026-02-16 19:34:53 +00:00
Wendelin
7552e91f24 Fix ha-script-field-row styles (#29658)
Refactor ha-script-field-row styles and replace haStyle with rowStyles
2026-02-16 14:53:54 +00:00
Aidan Timson
0c61304023 Finish feature parity for dialog and bottom-sheet with adaptive-dialog (#29669)
* Complete feature parity for dialog and bottom-sheet with adaptive-dialog

* Update src/components/ha-bottom-sheet.ts

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

* use render root

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

* Format

---------

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2026-02-16 15:52:39 +01:00
Petar Petrov
c61bc718c2 Move energy CSV download to period selector overflow menu (#29668)
Extract CSV dump logic from ha-panel-energy into a reusable
downloadEnergyData() utility in data/energy.ts, and add a
"Download data" button to the period selector's overflow menu.
This makes the download accessible from both the energy panel
and energy cards on regular dashboards.
2026-02-16 14:44:21 +00:00
ildar170975
2229e851be cloud-remote-pref: migrate "ha-settings-row -> ha-md-list-item" (#29667)
* ha-settings-row -> ha-md-list-item

* remove "narrow"

* remove "narrow"
2026-02-16 15:10:34 +01:00
Wendelin
32d3b854ca Fix dialogs when reduce motion is active. (#29666)
Reduce dialog and animation durations for improved accessibility
2026-02-16 14:58:19 +01:00
karwosts
f5dbb89e25 Add conditions to map card (#29614) 2026-02-16 15:38:48 +02:00
Aidan Timson
2ca47fddd3 Use prevent scrim close on settings form/editor dialogs, add cancel as secondary action (#29656)
* Use prevent scrim close on registry dialogs (forms/editors)

* Add to va pipeline

* Add cancel button

* Add cancel button

* Add cancel

* Prevent scrim close, easily closable, undesired effect

* Prevent scrim closure

* Prevent scrim closure

* Delete or cancel, not both

* Remove cancel, 2 actions per dialog

* Prevent scrim closure
2026-02-16 15:37:47 +02:00
YeonJuan
ca21658968 Fix invalid attribute values (#29639)
* Fix invalid attribute values

* Update .yarnrc.yml
2026-02-16 15:33:24 +02:00
ildar170975
d8c1fe7f4d ha-formfield: fix typo "inline -> initial" (#29662)
inline -> initial
2026-02-16 13:51:50 +01:00
Aidan Timson
a159a84228 Prevent scrim close on lovelace editor dialogs (#29653)
* Prevent scrim close on lovelace editor dialogs

* Prevent other editors
2026-02-16 14:47:48 +02:00
Florian Schweikert
5c95fa65dd Allow to start typing -0 in float field (#29665)
Prevent replacing "-0" with "0" while typing
2026-02-16 12:45:16 +00:00
Aidan Timson
20cde0ef70 Compute shown entity attributes in one place (#29655)
* Compute shown entity attributes in one place

* Remove type assertion

* Unit tests
2026-02-16 12:54:01 +01:00
Wendelin
b0d272bc3d Remove generic-picer backdrop background (#29661)
Style: Update backdrop styling for wa-popover in HaGenericPicker
2026-02-16 11:43:01 +00:00
Matthias de Baat
d844c5b894 Make energy config page consistent (#29452)
* Make energy config page consistent

* Update src/translations/en.json

Co-authored-by: Norbert Rittel <norbert@rittel.de>

* small tweaks

---------

Co-authored-by: Norbert Rittel <norbert@rittel.de>
Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2026-02-16 10:52:24 +00:00
Wendelin
2229d42429 Automation editor: Add continue on error UI for actions (#29603)
* Add continue on error functionality to automation actions

* use checkbox icons
2026-02-16 11:01:11 +01:00
Tom Carpenter
2ba0e77e73 Fix Energy Dialog Localisation (#29654)
`localizeBaseKey` was seemingly being set as an attribute not a property, and therefore failing to apply.
2026-02-16 09:58:51 +00:00
Marcin Bauer
e72facdec8 Replace filled help icon with outlined version (#29625)
* Replace filled help icon with outlined version across frontend

Updated help icon from mdiHelpCircle to mdiHelpCircleOutline for
visual consistency across the application. This change affects help
tooltips and icon buttons in configuration panels, dialogs, and
Lovelace editor components.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

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

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

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2026-02-16 09:38:59 +01:00
Petar Petrov
16bbd84962 Use custom home name in energy flow cards (#29652) 2026-02-16 10:23:13 +02:00
Petar Petrov
010eee76c5 Migrate grid connections to single objects with import/export/power (#29389)
* Migrate grid connections to single objects with import/export/power

* Fix duplicate imports in battery settings dialog

* Update src/translations/en.json

Co-authored-by: Norbert Rittel <norbert@rittel.de>

* Update src/translations/en.json

Co-authored-by: Norbert Rittel <norbert@rittel.de>

* Remove redundant power grid translation keys from en.json

* Remove redundant power charge and discharge keys from en.json

* Clean up grid keys from en.json

* Rename sell price to export

* add descriptions

* use ValueChangedEvent

* Renamed translationKeyPrefix to localizeBaseKey

* Add clarification to stat_rate in energy preference interfaces

* Add handling for external statistics in energy cost tracking

* Apply suggestion from @NoRi2909

Co-authored-by: Norbert Rittel <norbert@rittel.de>

* Update src/translations/en.json

Co-authored-by: Norbert Rittel <norbert@rittel.de>

* update comments

* Use ha-dialog instead of ha-wa-dialog

---------

Co-authored-by: Norbert Rittel <norbert@rittel.de>
2026-02-16 08:55:07 +01:00
dependabot[bot]
c138608445 Bump qs from 6.14.1 to 6.14.2 (#29651)
Bumps [qs](https://github.com/ljharb/qs) from 6.14.1 to 6.14.2.
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.14.1...v6.14.2)

---
updated-dependencies:
- dependency-name: qs
  dependency-version: 6.14.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-16 09:24:02 +02:00
dependabot[bot]
9632251a36 Bump github/codeql-action from 4.32.2 to 4.32.3 (#29650)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.32.2 to 4.32.3.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](45cbd0c69e...9e907b5e64)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.32.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-16 09:03:16 +02:00
Petar Petrov
17c3699707 Smarter floor/area grouping for energy sankey cards (#29588)
* Smarter floor/area grouping for energy sankey cards

Instead of hiding floors and areas whenever any device has an upstream
parent (included_in_stat), check whether each parent shares the same
area as its children. Only disable grouping when a mismatch is found.

* Allow missing area for parent or child

---------

Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>
2026-02-15 19:14:55 +00:00
ildar170975
681cbfdbd1 Data tables: "voice assistants" icons alignment fix (#29645)
fix alignment for icons
2026-02-15 14:13:28 +00:00
Aidan Timson
7c6e88ca3f Delete ha-dialog, rename ha-wa-dialog to ha-dialog (#29627)
* Delete ha-dialog

* Rename ha-wa-dialog to ha-dialog

* Rename component

* Rename gallery

* Rename gallery

* Update agents.md

* Rename references

* Rename component

* Format

* Fix

* Fix

* Fix

* Fix typo

* Fix typo

* Fix typo

* Fix typo

* Fix typo

* Fix typos

* Remove duplicate

* Remove

* Remove redundant header

* This should never have been here

* Remove, fixup

* Cleanup

* Remove

* Cleanup

* Give it a name again for gallery sidebar

* Fix invalid action design

* Fix gallery dialog actions to correct spec

---------

Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>
2026-02-15 11:01:31 +00:00
ildar170975
8ba7ff1705 Data table filters: fix overflow (#29646)
* fix overflow

* fix overflow

* fix overflow

* fix overflow

* fix overflow
2026-02-15 11:51:26 +01:00
karwosts
8727396e63 Virtualize notification drawer (#29640)
* Virtualize notification drawer

* update padding
2026-02-15 11:50:59 +01:00
Tom Carpenter
f03a573154 Correct Developer Tools Tab Translation Key (#29642)
Correct Dev Tools Tab Translation Key

Wrong key was selected for the dev tools page title due to caching issue.
2026-02-14 21:04:08 +01:00
Tom Carpenter
be8a7e0fa5 Fix Missing Developer Tools Page Title (#29631)
Add panel.developer_tools as an additional translation key for the developer tools in `configSections`. Otherwise the title of the page is simply Settings.
2026-02-14 10:42:12 +01:00
Paul Bottein
67e3eeb45e Fix custom value in picker with sections (#29593) 2026-02-13 16:24:40 +01:00
Aidan Timson
309e60fc4f Migrate other dialogs to wa (#29610)
Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com>
2026-02-13 16:19:48 +01:00
Aidan Timson
8edfd4d5ad Fix custom paths for navigation picker (#29619)
Fix custom paths for navigation
2026-02-13 15:13:36 +02:00
Aidan Timson
32f69c08a1 Migrate lovelace config dialogs to wa (#29607)
Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com>
2026-02-13 13:09:42 +00:00
Aidan Timson
4d7d76c9aa Migrate Z-Wave and Zigbee config dialogs to wa (#29583) 2026-02-13 14:03:08 +01:00
Tom Carpenter
9f10bc1371 Correct value update for 'ha-selector-select' elements displaying as radio buttons. (#29612)
* Correct options reset in ha-selector-select

Separate out the handling of resetting for select elements in `ha-selector-select` from the main value changed callback.

This fixes a bug that prevented `ha-selector-select` elements operating in `list` mode from updating their value due to recent changes in the reset logc.

* Split radio changed callback for consistency
2026-02-13 15:02:16 +02:00
Aidan Timson
93a0f37974 Migrate lovelace editor dialogs to wa (#29594) 2026-02-13 13:02:06 +00:00
Tom Carpenter
2ad264beaf Fix hardcoded collection keys in energy dashboard (#29623)
Correct hardcoded collection keys

Some elements in the energy overview strategy, and all in the energy view strategy were using hardcoded literals rather than the assigned collection key.
2026-02-13 14:48:53 +02:00
Aidan Timson
2eec2ded13 Migrate matter config dialogs to wa (#29605)
Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com>
2026-02-13 13:48:17 +01:00
Aidan Timson
eca535dd81 Migrate more info dialog to ha-wa-dialog (#28583) 2026-02-13 13:43:46 +01:00
Aidan Timson
33ba3f20aa Migrate voice assistant config dialogs to wa (#29606) 2026-02-13 13:30:44 +01:00
Paul Bottein
a45ef6e019 Light dashboard toggle area (#29363)
* Add toggle lights button on light dashboard

* Use not

* Use group card on desktop
2026-02-13 09:16:13 +02:00
Tom Carpenter
b256fc820d Fix compare button bug in energy-period-selector (#29611)
Fix bug in energy-period-selector

When the dropdown item for compare is disabled, the click callback still fires, allowing compare to be selected. So remove the callback for disabled buttons.

Also fix a slight visual bug in the ha-ripple border radius.
2026-02-13 09:02:43 +02:00
Wendelin
67fb7f61c2 Remove ha-divider and replace with wa-divider (#29609)
* Replace ha-divider with wa-divider in onboarding and dashboard components

* Refactor styles in onboarding-welcome component for consistency and responsiveness
2026-02-12 19:03:28 +01:00
Marcin Bauer
1b3ea3d55d Move automation help icons from sections to dialogs (#29584)
Co-authored-by: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Wendelin <w@pe8.at>
2026-02-12 15:42:44 +00:00
Steven Travers
3f724aba4f Fix kiosk mode not opening sidebar on toggle menu event (#29604) 2026-02-12 09:54:46 -05:00
Aidan Timson
7e225ed8a7 Migrate onboarding dialogs to wa (#29601)
* Migrate onboarding dialog(s) to wa

* Remove hass

* Allow hass to be uninitialised in wa dialog

* Use standard width for overflowing text
2026-02-12 15:35:32 +01:00
Wendelin
bd78800d8a Fix ha-dropdown-item selected hover (#29602)
Add hover effect for selected dropdown item
2026-02-12 15:32:35 +01:00
Aidan Timson
13d40993ef Change state card button/input_button to control button (#29367)
Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com>
2026-02-12 12:41:56 +00:00
Paul Bottein
1ad74d46d6 Improve PR template and add AI agent instructions (#29590)
* Improve PR template and add AI agent instructions

Move screenshots section closer to proposed change for better flow, and add instructions in copilot-instructions.md to ensure AI agents use the PR template when creating pull requests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Instruct AI agents to not check checklist items on behalf of the user

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Remind user to add screenshots for UI changes after creating PR

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 14:33:54 +02:00
Aidan Timson
4a128c904e Migrate enter code dialog to wa (#29592)
* Move enter code dialog migration to dedicated branch

* Fix

* Make small

* Small
2026-02-12 14:33:12 +02:00
Paul Bottein
e414bab746 Improve default dashboard confirmation dialog (#29596) 2026-02-12 12:58:34 +01:00
ildar170975
a354026780 Migrate more "ha-settings-row" to "ha-md-list-item" (#29508)
Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com>
Co-authored-by: Wendelin <w@pe8.at>
2026-02-12 09:54:49 +01:00
Matthias Alphart
24014116dc Add missing import in hass-tabs-subpage-data-table (#29591) 2026-02-12 08:49:09 +00:00
Aidan Timson
b07b604a20 Migrate zone config dialogs to wa (#29581)
* Migrate config-zone dialog(s) to wa

* Move cancel to new item, only delete if editing
2026-02-12 08:33:48 +00:00
Marcin Bauer
81525a2b14 Hide actions in attributes view (#29580)
* Hide actions in attributes view

Simplify the isDefaultView condition to hide actions when child views are shown in the more-info dialog.

Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>

* Remove keepHeader option from ChildView interface

Completely removes the keepHeader property which was used to keep the header visible in child views. This simplifies the logic and ensures actions are consistently hidden across all child views, including the attributes view.

Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
2026-02-12 09:23:45 +01:00
Paulus Schoutsen
3b6babe0be Reorganize profile localization settings into dedicated card (#29496)
Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
2026-02-12 09:06:10 +01:00
Aidan Timson
432e8fc0d7 Migrate zeroconf discovery info dialog to wa (#29579)
* Migrate config-zeroconf dialog(s) to wa

* Cleanup
2026-02-11 18:06:58 +01:00
Aidan Timson
2e350d24f5 Migrate ssdp config dialogs to wa (#29582)
* Migrate config-ssdp dialog(s) to wa

* Cleanup
2026-02-11 18:01:34 +01:00
Aidan Timson
024e953c89 Migrate thread config dialog to wa (#29578)
* Migrate config-thread dialog(s) to wa

* Cleanup
2026-02-11 17:48:03 +01:00
Aidan Timson
c29401a5a5 Migrate tags config dialog to wa (#29577)
* Migrate config-tags dialog(s) to wa

* Prevent scrim close
2026-02-11 17:28:26 +01:00
Aidan Timson
f913677dfe Migrate storage config dialogs to wa (#29576)
Migrate config-storage dialog(s) to wa
2026-02-11 17:21:31 +01:00
Aidan Timson
3b3f8f3343 Migrate integrations dialogs to wa (#29567)
Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com>
2026-02-11 12:48:41 +00:00
Aidan Timson
03917a5e1c Migrate energy config dialogs to wa (#29565)
Migrate config-energy dialog(s) to wa
2026-02-11 13:51:57 +02:00
Paul Bottein
5e3002f739 Align pull request template with core (#29573) 2026-02-11 13:50:18 +02:00
ildar170975
da4395b8b3 Entity card: allow a reversed order for "value" & "unit" (#29407)
* allow a reversed order

* formatEntityStateToParts() does not return "order"

* resolving conflicts

* fixed styles
2026-02-11 12:37:41 +01:00
ildar170975
16609053ac ha-fab: fix styles for disabled (#29446)
* Update ha-fab.ts

* fix styles for "disabled"

* fix styles

* do not change opacity

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

* revert & simplify

---------

Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
2026-02-11 12:21:22 +01:00
Aidan Timson
7beadb8108 Migrate person and user dialogs to wa (#29569) 2026-02-11 11:07:55 +00:00
Aidan Timson
8ca169fc23 Migrate hardware avaliable dialog to wa (#29566) 2026-02-11 12:07:39 +01:00
Aidan Timson
6502c14fad Migrate logs detail dialog to wa (#29568) 2026-02-11 11:52:19 +01:00
Aidan Timson
3d4b10caab Migrate cloud dialog to wa (#29563) 2026-02-11 11:40:33 +01:00
Aidan Timson
20bfe8b633 Migrate statistics dialogs to wa (#29437)
* Migrate config-developer-tools dialog(s) to wa

* Add space

* Remove duplicate method, fix variant typo

* Restore closeDialog
2026-02-11 12:01:45 +02:00
Aidan Timson
c6ecb5f217 Migrate mqtt device info dialog to wa (#29564)
Migrate config-devices dialog(s) to wa
2026-02-11 11:38:17 +02:00
Aidan Timson
938cc6a1a1 Migrate app store dialogs to wa, standardise registry actions (#29542)
* Migrate config-apps dialog(s) to wa

* Apply suggestions from code review

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

* Use standard buttons

* Use back action

* Remove extra close action

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2026-02-11 11:34:53 +02:00
Wendelin
1d1e05dbdf Migrate from ha-md-select to ha-select (#29551)
* Migrate from ha-md-select to ha-select across multiple components

* Review

* Remove --md-filled-field-content-space
2026-02-11 11:30:10 +02:00
Aidan Timson
a1b4923673 Migrate bluetooth device info dialog to wa (#29546)
Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>
2026-02-11 10:00:16 +01:00
Aidan Timson
823a42f567 Add additional config areas to quick search (#29535)
* Add network discovery browsers to quick search

* Add cloud to quick search

* Add application credentials and mqtt, remove cloud as is a redirect
2026-02-11 09:46:26 +02:00
Aidan Timson
81275a96ff Migrate main calendar dialogs to wa (#29505)
* Migrate calendar dialog(s) to wa

* Remove width (too wide)

* Prevent scrim close
2026-02-11 09:26:11 +02:00
Paul Bottein
2e372b2f8a Add repairs and updates cards to home dashboard overview (#29552)
* Add repairs and updates cards to home dashboard overview

Add two new cards to the "For You" section of the home dashboard that display
links to repairs and updates when there are active issues or available updates.
Both cards are only visible to admin users and hide when empty.

https://claude.ai/code/session_013NTgs1U9x59uaEJs1smy8i

* Fix navigation and visibility

* Reorder

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-11 09:15:37 +02:00
sebcaps
541cc7d10b Add Percentage in distribution card tooltip (#29555)
Add Percentage in tooltip
2026-02-11 09:06:56 +02:00
Aidan Timson
678ee7e82a Migrate todo dialog to wa (#29527)
Migrate todo dialog(s) to wa
2026-02-10 20:38:45 +01:00
uptimeZERO_
ee72f4818d Add guard for failed JSON parsing of localStorage entries (#29502)
* Added guard for failed json parsing of selectedTheme key

* deleting corrupted keys and logging
2026-02-10 20:30:28 +01:00
karwosts
ccbd9c1f24 Don't show hidden todolists on todo panel (#29510) 2026-02-10 19:21:53 +01:00
ildar170975
84135a9424 hui-entity-editor: fix padding-top for ha-md-list (#29537)
fix padding-top for ha-md-list
2026-02-10 19:17:10 +01:00
Petar Petrov
4ebc334298 Normalize SI unit prefixes in distribution card proportions (#29539)
* Normalize SI unit prefixes in distribution card proportions

* Extract SI prefix normalization to shared utility with tests

Moves normalizeValueBySIPrefix to src/common/number/ so it can be
reused. Replaces the inline method in the distribution card and the
switch statement in getPowerFromState (energy.ts).
2026-02-10 19:16:02 +01:00
Petar Petrov
3c4c3e39e5 Fix storage space calculations to account for reserved system space (#29540) 2026-02-10 19:13:58 +01:00
Aidan Timson
1267003b42 Migrate local backup location dialog to wa (#29543)
Migrate config-backup dialog(s) to wa
2026-02-10 19:11:44 +01:00
Aidan Timson
733359c869 Fix position of 2 extra actions for date picker dialog (#29541)
Fix position of 2 extra actions for date picker
2026-02-10 19:08:55 +01:00
Aidan Timson
50b6e07ae5 Migrate import blueprint dialog to wa (#29544)
* Migrate config-blueprint dialog(s) to wa

* Add vt
2026-02-10 19:00:58 +01:00
Wendelin
1b62a7cff8 Dropdown item add selected prop (#29553)
Refactor dropdown item selection handling to use property binding for selected state
2026-02-10 18:55:38 +01:00
Wendelin
c293cf56f6 Migrate from ha-md-menu to ha-dropdown (#29548) 2026-02-10 15:33:49 +00:00
Aidan Timson
2ec6f3615d Use ValueChangedEvent for more CustomEvents (#29547) 2026-02-10 16:26:19 +01:00
Wendelin
d3b92059e5 Re-enable autofocus for iOS in ha-wa-dialog (#29534) 2026-02-10 15:09:33 +01:00
ildar170975
72e69f6291 Entities card: add “area” for “secondary_info” (#29268)
* add "area/floor" labels for secondary-info for Entities card

* use STRINGS_SEPARATOR_DOT

* add "area/floor" for secondary-info

* add "area/floor" for secondary-info

* add "area/floor" for secondary-info

* use STRINGS_SEPARATOR_DOT

* use STRINGS_SEPARATOR_DOT

* use STRINGS_SEPARATOR_DOT

* add STRINGS_SEPARATOR_DOT

* changed an order & renamed an option

* fixed name for "area with floor"

* chaged an order & renamed an entry

* renamed "area with floor" entry

* add STRINGS_SEPARATOR_DOT

* Delete src/common/strings-separator.ts

* change import

* change import

* change import

* change import

* change import

* fix import

* fix import

* fix import

* remove "area-with-floor"

* remove "area-with-floor"

* remove "area-with-floor"

* remove unneeded comma

* remove "area-with-floor"

* clean up

* typo

* Apply suggestion from @MindFreeze

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

* move area definition into a separate method

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2026-02-10 16:06:00 +02:00
Aidan Timson
3bff97595f Migrate date picker dialog to wa (#29506) 2026-02-10 14:52:01 +01:00
renovate[bot]
19b1d03cd1 Update Node.js to v24.13.1 (#29538)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-10 13:34:20 +00:00
Petar Petrov
f04557688c Use unique SVG mask IDs in graph base to fix rendering on Samsung WebView (#29525)
* Use unique SVG mask IDs in graph base to fix rendering on Samsung WebView

* Remove unused IDs
2026-02-10 14:23:52 +01:00
Matthias de Baat
d6953ea1bc Make storage charts consistent with lifetime chart (#29526)
* Make storage charts consistent with lifetime chart

* Update src/panels/config/storage/storage-breakdown-chart.ts

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2026-02-10 13:12:53 +00:00
ildar170975
4501db18c7 Entities card editor: fix margin-top for ha-entity-picker (#29536)
* fix margin-top for ha-entity-picker

* Apply suggestion from @MindFreeze

* use ha-space var

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2026-02-10 13:10:40 +00:00
ildar170975
c68bcc5b32 Entity card: fix unit for monetary (#29406)
* fix unit for monetary

* fix for span

* formatEntityStateToParts() does not return "order"

* get unit from formatEntityStateToParts()

* resolving conflicts

* resolving conflicts

* some styling
2026-02-10 15:09:43 +02:00
Aidan Timson
3753c7d313 Migrate helper dialogs to wa (#29507)
* Migrate config-helpers dialog(s) to wa

* Stop secondary dialogs from automatically closing

* Styles

* Fix initial focus
2026-02-10 14:54:52 +02:00
Aidan Timson
4a2ef18375 Migrate media browser dialog to wa (#29529) 2026-02-10 13:28:48 +01:00
Wendelin
051da41eec Update @home-assistant/webawesome to version 3.2.1-ha.0 (#29533)
update @home-assistant/webawesome to version 3.2.1-ha.0
2026-02-10 13:25:53 +01:00
Aidan Timson
9b1a679f21 Add missing AI tasks item to quick search (#29531) 2026-02-10 13:10:35 +01:00
Aidan Timson
905a0f957c Migrate system information and startup time dialogs to wa (#29532) 2026-02-10 13:08:08 +01:00
Wendelin
d9a687b79c Hardware panel imporve chart loading UI (#29528) 2026-02-10 10:42:57 +00:00
karwosts
3b56497134 Hide more info weather forecast when unsupported (#29517) 2026-02-10 09:28:52 +00:00
Paul Bottein
ed0ec871ce Don't close automation and script sidebar on save (#29434) 2026-02-10 10:07:31 +01:00
ildar170975
29f5362182 hui-graph-header-footer: add own action handler (#29522)
* add action handler

* set action handler for ":host"
2026-02-10 11:03:31 +02:00
Matthias de Baat
5096bab26c Move AI task to its own page and change General into Home information (#29458)
* Move AI task to its own page and change General into Home information

* Fixed unused state properties for form submission states, removed unused imports and obsolete CSS, replaced hardcoded pixel values with spacing tokens, and added error handling for the map component.

* Update src/panels/config/core/ha-config-section-general.ts

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2026-02-10 06:45:26 +00:00
Yosi Levy
ce2892cab9 Various RTL fixes (#29520) 2026-02-10 06:59:18 +01:00
renovate[bot]
57748c15de Update dependency @codemirror/commands to v6.10.2 (#29518)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-10 06:58:46 +01:00
Paul Bottein
ecb6a33c86 Add color settings for entity in distribution card (#29436)
* Add color settings for entity in distribution card

* Fix name

* Fix name again...
2026-02-09 16:15:14 +01:00
ildar170975
d34921ff6d state-card-input_number: fix for narrow viewport (#29327)
state-info: fix for narrow viewport
2026-02-09 15:30:40 +01:00
ildar170975
e6c9e81082 Add formatEntityStateToParts() + use it for hui-entity-card & ha-state-label-badge (#29239)
* add formatEntityStateToParts

* add formatEntityStateToParts

* use formatEntityStateToParts

* add formatEntityStateToParts

* use formatEntityStateToParts

* add formatEntityStateToParts

* add formatEntityStateToParts

* add computeStateDisplayToParts

* update for monetary

* fix a test for monetary

* fixed test for monetary

* do not include "order" into result

* do not include "order" into result

* do not include "order" into result

* do not include "order" into result

* do not include "order" into result

* do not include "order" into result

* do not include "order" into result

* simplify

* ensure less conflicts in future merges

* Refactor monetary computing

---------

Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
2026-02-09 15:11:05 +01:00
Petar Petrov
aa13c6fa53 Add tests for energy chart functions (#29504) 2026-02-09 15:07:11 +01:00
ildar170975
d47738aa24 developer-tools-debug: migrate "ha-settings-row" to "ha-md-list-item" (#29501)
* ha-settings-row -> ha-md-list-item

* ha-settings-row -> ha-md-list-item

* ha-settings-row -> ha-md-list-item

* background: 0 -> background: none
2026-02-09 14:25:23 +01:00
ildar170975
d473ee1084 User profile: migrate toggle rows "ha-settings-row" to "ha-md-list-item" (#29497)
* ha-settings-row -> ha-md-list-item

* ha-settings-row -> ha-md-list-item

* ha-settings-row -> ha-md-list-item

* ha-settings-row -> ha-md-list-item

* ha-settings-row -> ha-md-list-item

* ha-settings-row -> ha-md-list-item

* ha-settings-row -> ha-md-list-item

* ha-settings-row -> ha-md-list-item

* remove unneeded "narrow"

* add import

* background: 0 -> background: none
2026-02-09 13:52:19 +01:00
Aidan Timson
0372ed932f Migrate new dashboard dialog to wa (#29438)
* Migrate config-dashboard dialog(s) to wa

* Restructure for scrolling
2026-02-09 14:08:04 +02:00
Aidan Timson
2baa044db5 Migrate profile dialogs to wa, refactor LL access token dialog (#29440)
* Migrate profile dialog(s) to wa

* Make sure code is entered before submit is allowed

* Refactor dialog

* Remove unused params

* Pass existing names and validate name is not already used

* Reduce cleanup on show

* Make QR image larger

* max width

* Fix

* Remove extra event fire

* Make params required

* cleanup

* Cleanup

* Fix

* Fix
2026-02-09 12:54:02 +01:00
Wendelin
3d04046bcc Migrate automation picker row to ha-dropdown (#29428)
* Update @home-assistant/webawesome to version 3.2.1 and refactor ha-dropdown integration in automation picker

* review

* revert wa update

* Update @home-assistant/webawesome to version 3.0.0-ha.2 in yarn.lock
2026-02-09 12:35:49 +01:00
Tom Carpenter
8e860cb17d Improve energy dashboard monthly/this-quarter chart time axes (#29435)
* Add splitNumber option to monthly ECharts

When there are a small number of bars (<=3) for monthly data, set the splitNumber parameter to force the date x-axis to show whole months.

* Add axis tick fomratting for short months

This ensures that the month format is consistent between 2/3 month and longer ranges.

* Avoid calling getSuggestedMax twice

* Fix another case of power chart cutting off last hour of data

The previous fix only solved the problem for 5-minute data, not hourly or daily. This should solve the issue regardless, and allows the energy chart to have other line-based plots in the future.

* Update other uses of getSuggestedMax()

* Fix statistics-chart Last Period Rendering

1. When appending the "current state" value, if the current time intersects with the final period, we can end up with the chart folding back on itself. This is fixed by ensuring for the final period we push the earlier of the statistic end time and the display end time (which is in turn limited to now).

2. Always close off the last data point at the chart end time. Otherwise for line charts, the final period doesn't get rendered.

* Remove unused monthStyle formatter.

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

* Rename getSuggestedMax function parameter in energy chart

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

* Document magic numbers in montly energy chart

* Make padding a constant for clarity.
* Explain the purpose of splitNumber.

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2026-02-09 13:05:42 +02:00
Petar Petrov
c41d7ff923 Fix history-graph card rendering stale data point on left edge (#29499)
When HistoryStream.processMessage() prunes expired history and preserves
the last expired state as a boundary marker, it updates lu (last_updated)
but not lc (last_changed). Chart components use lc preferentially, so
when lc is present the boundary point gets plotted at the original stale
timestamp far to the left of the visible window. Delete lc from the
boundary state so the chart uses the corrected lu timestamp.
2026-02-09 10:13:06 +01:00
Matthias de Baat
c22fc1021a Counter gravity effect on the Matter icon (#29459) 2026-02-09 10:05:06 +01:00
dependabot[bot]
6344233934 Bump github/codeql-action from 4.32.0 to 4.32.2 (#29498)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.32.0 to 4.32.2.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](b20883b0cd...45cbd0c69e)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.32.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-09 10:22:39 +02:00
ildar170975
23441d593b Data tables: filters: fix a placement for icon & text (#29488)
* fix padding & margin

* fix margin for icon

* fix margin for icon

* fix margin for image

* use ha-space-4

* use --ha-space-4

* use ha-space-1

* use ha-space-4
2026-02-09 08:57:00 +01:00
ildar170975
8393ed5fd4 voice-assistant-brand-icon: fixes for margin & alignment (#29493)
* fix styles

* fix a gap between logos

* fix a gap between logos

* fix right margin for logo

* fix right margin for logo

* show icons in flex

* remove unneeded style

* add right margin for logo
2026-02-09 08:54:45 +01:00
TheJulianJES
09afe9bb51 Fix ZHA dashboard using disabled and ignored config entries (#29494) 2026-02-09 08:18:43 +01:00
Benedikt Johannes
fb8d6062c5 Sugestion -> Suggestion (#29490)
* Update en.json

* Update ha-area-picker.ts

* Update ha-label-picker.ts

* Update ha-floor-picker.ts

* Update ha-category-picker.ts
2026-02-09 08:14:15 +01:00
karwosts
f93ae58b83 Fix dupl. id error in water-sankey (#29489) 2026-02-08 19:06:37 +01:00
karwosts
7626b26b2d No FAB in calendar-card (#29487) 2026-02-08 19:06:31 +01:00
renovate[bot]
a1bf30e501 Update dependency @rsdoctor/rspack-plugin to v1.5.2 (#29481)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-08 08:41:36 +00:00
ildar170975
37a45d1729 voice-assistants-expose-assistant-icon: fix tooltip (#29469)
* fix tooltip

* provide id for tooltip for assistant icon
2026-02-08 09:32:12 +01:00
karwosts
6962a915a3 Fix describe legacy triggers in traces (#29473)
* Fix describe legacy triggers in traces

* remove unnecessary type
2026-02-07 22:05:05 +01:00
renovate[bot]
de3e2bcafa Update dependency glob to v13.0.1 (#29462)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-06 21:44:45 +01:00
Matthias de Baat
e732280b70 Add safe space at the bottom (#29454)
* Add safe space at the bottom

* Move margin-bottom from ha-config-analytics component to its parent wrapper in ha-config-section-analytics
2026-02-06 17:47:40 +01:00
renovate[bot]
4fb3453f73 Update dependency ua-parser-js to v2.0.9 (#29456)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-06 16:45:18 +00:00
renovate[bot]
0f9cb9c13e Update dependency @rspack/core to v1.7.5 (#29447)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-06 12:23:27 +01:00
Matthias de Baat
7aa235c6af Fix Discord link for designers (#29393)
* Fix Discord link for designers

Updated Discord link for designers to the correct channel.

* Update Discord link for designers in home.markdown

* Update gallery/src/pages/concepts/home.markdown

Co-authored-by: Aidan Timson <aidan@timmo.dev>

---------

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
Co-authored-by: Aidan Timson <aidan@timmo.dev>
2026-02-05 21:09:28 +01:00
Aidan Timson
e8ddae8189 Migrate join beta dialog to wa (#29439)
Migrate config-updates dialog(s) to wa
2026-02-05 21:06:38 +01:00
Bram Kragten
d98e373f64 Bumped version to 20260128.6 2026-02-04 15:41:09 +01:00
Paul Bottein
649516c9fa Change default icon for blank area if not icon configured (#29394) 2026-02-04 15:40:36 +01:00
Paul Bottein
bbc4fb96b2 Load domain translation when integration page load (#29391) 2026-02-04 15:40:35 +01:00
Paul Bottein
0ae639aeb0 Remove old lovelace overview from pickers (#29390) 2026-02-04 15:40:34 +01:00
karwosts
0e7e41065e Don't shrink ha-dropdown checkboxes (#29387) 2026-02-04 15:40:33 +01:00
Paul Bottein
685843f112 Add translations for new overview dialog (#29382) 2026-02-04 15:40:32 +01:00
Paul Bottein
5e1a99d94a Use area icon for area empty state (#29371) 2026-02-04 15:40:31 +01:00
Bram Kragten
d843349865 Bumped version to 20260128.5 2026-02-03 16:58:01 +01:00
Paul Bottein
ec23164aa9 Improve other devices page in home dashboard (#29370) 2026-02-03 16:57:45 +01:00
Paul Bottein
e74ef11101 Hide edit and delete actions for YAML dashboards in config (#29368)
YAML dashboards are defined in configuration files and cannot be
modified or deleted through the UI. This change ensures the edit
and delete actions are only shown for storage-mode dashboards.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 16:57:43 +01:00
Paul Bottein
a222f6a736 Add missing danger variant in dropdown item (#29359) 2026-02-03 16:57:40 +01:00
Petar Petrov
ef3dd16d45 Move dialog scrim to pseudo-element (#29357) 2026-02-03 16:57:39 +01:00
karwosts
5d4e1d205e Fix missing imports in devtools-statistics (#29355) 2026-02-03 16:57:38 +01:00
Darryn Capes-Davis
1ee5ebbe75 Fix CSS minification issue for ha-card (#29354) 2026-02-03 16:57:37 +01:00
Bram Kragten
59d705aa3d Bumped version to 20260128.4 2026-02-02 17:17:24 +01:00
Paul Bottein
332e108dae Fix "Reload resources" menu for YAML resource mode (#29346) 2026-02-02 17:17:17 +01:00
karwosts
3c15b29d0a Entity diagnostic - handle entity not in the registry (#29344) 2026-02-02 17:17:16 +01:00
Wendelin
130c708e23 Fix dropdown width in datatables (#29340) 2026-02-02 17:17:15 +01:00
Paul Bottein
588a14a8a7 Fix type error for missing hass.themes race condition in themes mixin (#29338) 2026-02-02 17:17:14 +01:00
Petar Petrov
a1ef6ad266 Remove redundant dialog backdrop color (#29337) 2026-02-02 17:17:13 +01:00
Aidan Timson
a6c1f87730 Ensure template renderer overflows on overflow (#29335) 2026-02-02 17:17:12 +01:00
Wendelin
49252a3808 Fix missing ha-md-menu in config/labels (#29334) 2026-02-02 17:17:11 +01:00
Aidan Timson
c7877fe38f Show hint only if keyboard shortcuts is enabled (#29332)
Enabled by default, must be explicity disabled
2026-02-02 17:17:10 +01:00
Wendelin
e355a61d8f Revert "Fix automation sidebar ui supported check" (#29331) 2026-02-02 17:17:08 +01:00
Linus Rath
f2e19e51ce Update untracked consumption threshold to 1W (#29310) 2026-02-02 17:17:07 +01:00
karwosts
fd9ab8f561 Use ha-form for condition template (#29301) 2026-02-02 17:17:06 +01:00
Kristel
faa1b3c98f bugfix: add eventlistener for exposed-entities-changed to Entities page (#29299) 2026-02-02 17:17:06 +01:00
Aidan Timson
acc4a84fc9 Fix scrolling for labs page (#29287) 2026-02-02 17:17:05 +01:00
karwosts
4d723dac37 Fix areas cannot be deleted (#29285) 2026-02-02 17:17:03 +01:00
Aidan Timson
f1d4d0ef98 Fix type error for missing hass.config race condition in themes mixin (#29280) 2026-02-02 17:17:02 +01:00
Paul Bottein
88180a2708 Fix demo because of new default panel (#29279) 2026-02-02 17:17:01 +01:00
Aidan Timson
258d87e3d5 Add missing settings nav items for quick search (#29278)
* Add missing repairs quick search item

* Add voice assistants
2026-02-02 17:17:00 +01:00
Wendelin
55f22ba61a Implement fallback for dialog close event in Quick Search (#29260) 2026-02-02 17:16:59 +01:00
Aidan Timson
812f3ca8b9 Change default shortcut tip in Quick Search to mod+k, add tip to settings (#29253) 2026-02-02 17:16:58 +01:00
Marcin Bauer
7f880d11a0 Keep focus on search field when clicking filter chips (#29249)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 17:16:57 +01:00
Bram Kragten
6b2452c538 Update compress.js 2026-02-02 17:15:44 +01:00
Paul Bottein
c2cbf8bd21 Bumped version to 20260128.3 2026-01-30 10:31:26 +01:00
Wendelin
224bcece9c Fix multi select in quick search (#29272)
Add item selection state management to QuickBar component
2026-01-30 10:30:33 +01:00
Wendelin
dc84b7698f Fix --wa-color-text-normal (#29271)
Update normal text color variable in wa.globals.ts
2026-01-30 10:30:32 +01:00
Wendelin
bc22e6a9bd Fix device download diagnostic via overflow (#29269)
fix diagnostic download link handling to simplify URL signing
2026-01-30 10:30:31 +01:00
Simon Lamon
d44874783a Remove duplicated text (#29265) 2026-01-30 10:30:30 +01:00
Paul Bottein
8d1bb5c867 Fix default lovelace yaml loading (#29240) 2026-01-30 10:30:29 +01:00
Bram Kragten
da1b528eee Bumped version to 20260128.2 2026-01-29 18:04:42 +01:00
Paul Bottein
756138408a Remove default title for new dashboards (#29259) 2026-01-29 18:04:09 +01:00
Paul Bottein
3c8f112565 Prevent action in tile container (#29257) 2026-01-29 18:04:08 +01:00
Paul Bottein
2521f3dde4 Fix actions in dashboard overflow menu (#29256) 2026-01-29 18:04:07 +01:00
TheJulianJES
56390aa01a Fix Matter dashboard using disabled and ignored config entries (#29254) 2026-01-29 17:52:32 +01:00
Paul Bottein
9aac5b19da Stop click propagation when clicking item in icon overflow (#29252) 2026-01-29 17:52:31 +01:00
Wendelin
24afc3dc88 Prevent quick search to close from hot keys (#29251) 2026-01-29 17:52:30 +01:00
Paul Bottein
873c7b2947 Remove unused theme option in distribution card (#29250) 2026-01-29 17:52:29 +01:00
Aidan Timson
648db4276b Add protocols to quick search (#29248)
Add protocols to quick search, extract logic and translations
2026-01-29 17:52:28 +01:00
Aidan Timson
f86c3e7856 Remove unused "app" item from quick search (#29244) 2026-01-29 17:52:27 +01:00
Aidan Timson
1d0251cc28 Fixes for picker combo box scrolling and selection (#29242) 2026-01-29 17:52:26 +01:00
Wendelin
518cf87847 Fix quick search apps (#29238) 2026-01-29 17:52:25 +01:00
ildar170975
81a9216c44 computeGroupEntitiesState(): fix condition (#29234)
* fix condition

* fix condition

* prettier
2026-01-29 17:52:24 +01:00
Paul Bottein
f0e10e0058 Fix default yaml lovelace panel loading (#29230) 2026-01-29 17:52:23 +01:00
Paul Bottein
5df8ea4f07 Add welcome banner for new overview dashboard (#29223) 2026-01-29 17:52:22 +01:00
Aidan Timson
73f081f5cc Add meta+click/enter support to quick search (#29220)
* Allow meta+click event from combobox

* Handle new tab events for navigations

* Add mod+enter support for new tab

* Helper function
2026-01-29 17:52:21 +01:00
Petar Petrov
f0d1db1da6 Add non standard power sensor support (#28845)
* Add non standard power sensor support

* remove useless code

* GridPowerSourceInput type for grid power source saving
2026-01-29 17:52:20 +01:00
Bram Kragten
c658eb414b Bumped version to 20260128.1 2026-01-28 17:52:10 +01:00
Bram Kragten
bac493b72b dont include brotli compression 2026-01-28 17:50:19 +01:00
546 changed files with 22767 additions and 14054 deletions

View File

@@ -21,6 +21,14 @@
-->
## Screenshots
<!--
If your PR includes visual changes, please add screenshots or a short video
showing the before and after. This helps reviewers understand the impact of
your changes.
Note: Remove this section if this PR has no visual changes.
-->
## Type of change
<!--
What type of change does your PR introduce to the Home Assistant frontend?
@@ -35,16 +43,6 @@
- [ ] Breaking change (fix/feature causing existing functionality to break)
- [ ] Code quality improvements to existing code or addition of tests
## Example configuration
<!--
Supplying a configuration snippet, makes it easier for a maintainer to test
your PR.
-->
```yaml
```
## Additional information
<!--
Details are important, and help maintainers processing your PR.
@@ -54,6 +52,8 @@
- This PR fixes or closes issue: fixes #
- This PR is related to issue or discussion:
- Link to documentation pull request:
- Link to developer documentation pull request:
- Link to backend pull request:
## Checklist
<!--
@@ -61,18 +61,50 @@
creating the PR. If you're unsure about any of them, don't hesitate to ask.
We're here to help! This is simply a reminder of what we are going to look
for before merging your code.
AI tools are welcome, but contributors are responsible for *fully*
understanding the code before submitting a PR.
-->
- [ ] I understand the code I am submitting and can explain how it works.
- [ ] The code change is tested and works locally.
- [ ] There is no commented out code in this PR.
- [ ] Tests have been added to verify that the new code works.
- [ ] I have followed the [development checklist][dev-checklist]
- [ ] I have followed the [perfect PR recommendations][perfect-pr]
- [ ] Any generated code has been carefully reviewed for correctness and compliance with project standards.
If user exposed functionality or configuration variables are added/changed:
- [ ] Documentation added/updated for [www.home-assistant.io][docs-repository]
<!--
Thank you for contributing <3
This project is very active and we have a high turnover of pull requests.
Unfortunately, the number of incoming pull requests is higher than what our
reviewers can review and merge so there is a long backlog of pull requests
waiting for review. You can help here!
By reviewing another pull request, you will help raise the code quality of
that pull request and the final review will be faster. This way the general
pace of pull request reviews will go up and your wait time will go down.
When picking a pull request to review, try to choose one that hasn't yet
been reviewed.
Thanks for helping out!
-->
To help with the load of incoming pull requests:
- [ ] I have reviewed two other [open pull requests][prs] in this repository.
[prs]: https://github.com/home-assistant/frontend/pulls?q=is%3Aopen+is%3Apr+-author%3A%40me+-draft%3Atrue+sort%3Acreated-desc+review%3Anone+-status%3Afailure
<!--
Thank you for contributing <3
Below, some useful links you could explore:
-->
[dev-checklist]: https://developers.home-assistant.io/docs/development_checklist/
[docs-repository]: https://github.com/home-assistant/home-assistant.io
[perfect-pr]: https://developers.home-assistant.io/docs/review-process/#creating-the-perfect-pr

View File

@@ -194,13 +194,13 @@ The View Transitions API creates smooth animations between DOM state changes. Wh
- **Utility wrapper**: `src/common/util/view-transition.ts` - `withViewTransition()` function with graceful fallback
- **Real-world example**: `src/util/launch-screen.ts` - Launch screen fade pattern with browser support detection
- **Animation keyframes**: `src/resources/theme/animations.globals.ts` - Global `fade-in`, `fade-out`, `scale` animations
- **Animation duration**: `src/resources/theme/core.globals.ts` - `--ha-animation-base-duration` (350ms, respects `prefers-reduced-motion`)
- **Animation duration**: `src/resources/theme/core.globals.ts` - `--ha-animation-duration-fast` (150ms), `--ha-animation-duration-normal` (250ms), `--ha-animation-duration-slow` (350ms) (all respect `prefers-reduced-motion`)
**Implementation Guidelines:**
1. Always use `withViewTransition()` wrapper for automatic fallback
2. Keep transitions simple (subtle crossfades and fades work best)
3. Use `--ha-animation-base-duration` CSS variable for consistent timing
3. Use `--ha-animation-duration-*` CSS variables for consistent timing (`fast`, `normal`, `slow`)
4. Assign unique `view-transition-name` to elements (must be unique at any given time)
5. For Lit components: Override `performUpdate()` or use `::part()` for internal elements
@@ -214,13 +214,6 @@ By default, `:root` receives `view-transition-name: root`, creating a full-page
- Only one view transition can run at a time
- **Shadow DOM incompatibility**: View transitions operate at document level and do not work within Shadow DOM due to style isolation ([spec discussion](https://github.com/w3c/csswg-drafts/issues/10303)). For web components, set `view-transition-name` on the `:host` element or use document-level transitions
**Current Usage & Planned Applications:**
- Launch screen fade out (implemented)
- Automation sidebar transitions (planned - #27238)
- More info dialog content changes (planned - #27672)
- Toolbar navigation, ha-spinner transitions (planned)
**Specification & Documentation:**
For browser support, API details, and current specifications, refer to these authoritative sources (note: check publication dates as specs evolve):
@@ -246,12 +239,7 @@ For browser support, API details, and current specifications, refer to these aut
## Component Library
### Dialog Components
**Available Dialog Types:**
- `ha-wa-dialog` - Preferred for new dialogs (Web Awesome based)
- `ha-dialog` - Legacy component (still widely used)
### Dialog Component
**Opening Dialogs (Fire Event Pattern - Recommended):**
@@ -265,6 +253,7 @@ fireEvent(this, "show-dialog", {
**Dialog Implementation Requirements:**
- Use `ha-dialog` component
- Implement `HassDialog<T>` interface
- Use `@state() private _open = false` to control dialog visibility
- Set `_open = true` in `showDialog()`, `_open = false` in `closeDialog()`
@@ -280,7 +269,6 @@ fireEvent(this, "show-dialog", {
- Use `width` attribute with predefined sizes: `"small"` (320px), `"medium"` (560px - default), `"large"` (720px), or `"full"`
- Custom sizing is NOT recommended - use the standard width presets
- Example: `<ha-wa-dialog width="small">` for alert/confirmation dialogs
**Button Appearance Guidelines:**
@@ -290,17 +278,9 @@ fireEvent(this, "show-dialog", {
- **Button sizes**: Use `size="small"` (32px height) or default/medium (40px height)
- Always place primary action in `slot="primaryAction"` and secondary in `slot="secondaryAction"` within `ha-dialog-footer`
**Recent Examples:**
See these files for current patterns:
- `src/panels/config/repairs/dialog-repairs-issue.ts`
- `src/dialogs/restart/dialog-restart.ts`
- `src/panels/config/lovelace/resources/dialog-lovelace-resource-detail.ts`
**Gallery Documentation:**
- `gallery/src/pages/components/ha-wa-dialog.markdown`
- `gallery/src/pages/components/ha-dialog.markdown`
- `gallery/src/pages/components/ha-dialogs.markdown`
### Form Component (ha-form)
@@ -308,7 +288,6 @@ See these files for current patterns:
- Schema-driven using `HaFormSchema[]`
- Supports entity, device, area, target, number, boolean, time, action, text, object, select, icon, media, location selectors
- Built-in validation with error display
- Use `dialogInitialFocus` in dialogs
- Use `computeLabel`, `computeError`, `computeHelper` for translations
```typescript
@@ -393,81 +372,6 @@ export class HaPanelMyFeature extends SubscribeMixin(LitElement) {
}
```
### Creating a Dialog
```typescript
@customElement("dialog-my-feature")
export class DialogMyFeature
extends LitElement
implements HassDialog<MyDialogParams>
{
@property({ attribute: false })
hass!: HomeAssistant;
@state()
private _params?: MyDialogParams;
@state()
private _open = false;
public async showDialog(params: MyDialogParams): Promise<void> {
this._params = params;
this._open = true;
}
public closeDialog(): void {
this._open = false;
}
private _dialogClosed(): void {
this._params = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render() {
if (!this._params) {
return nothing;
}
return html`
<ha-wa-dialog
.hass=${this.hass}
.open=${this._open}
header-title=${this._params.title}
header-subtitle=${this._params.subtitle}
@closed=${this._dialogClosed}
>
<p>Dialog content</p>
<ha-dialog-footer slot="footer">
<ha-button
slot="secondaryAction"
appearance="plain"
@click=${this.closeDialog}
>
${this.hass.localize("ui.common.cancel")}
</ha-button>
<ha-button slot="primaryAction" @click=${this._submit}>
${this.hass.localize("ui.common.save")}
</ha-button>
</ha-dialog-footer>
</ha-wa-dialog>
`;
}
static styles = [haStyleDialog, css``];
}
```
### Dialog Design Guidelines
- Max width: 560px (Alert/confirmation: 320px fixed width)
- Close X-icon on top left (all screen sizes)
- Submit button grouped with cancel at bottom right
- Keep button labels short: "Save", "Delete", "Enable"
- Destructive actions use red warning button
- Always use a title (best practice)
- Strive for minimalism
#### Creating a Lovelace Card
**Purpose**: Cards allow users to tell different stories about their house (based on gallery)
@@ -558,6 +462,10 @@ this.hass.localize("ui.panel.config.updates.update_available", {
- Use HTTPS - All external resources must use HTTPS
- CSP compliance - Ensure code works with Content Security Policy
### Pull Requests
When creating a pull request, you **must** use the PR template located at `.github/PULL_REQUEST_TEMPLATE.md`. Read the template file and use its full content as the PR body, filling in each section appropriately. Do not omit, reorder, or rewrite the template sections. Do not check the checklist items on behalf of the user — those are the user's responsibility to review and check. If the PR includes UI changes, remind the user to add screenshots or a short video to the PR after creating it.
### Text and Copy Guidelines
#### Terminology Standards
@@ -715,9 +623,6 @@ this.hass.localize("ui.panel.config.automation.delete_confirm", {
### Component-Specific Checks
- [ ] Dialogs implement HassDialog interface
- [ ] Dialog styling uses haStyleDialog
- [ ] Dialog accessibility includes dialogInitialFocus
- [ ] ha-alert used correctly for messages
- [ ] ha-form uses proper schema structure
- [ ] Components handle all states (loading, error, unavailable)

View File

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

View File

@@ -5,9 +5,38 @@ on:
issues:
types: [opened]
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.event.issue.number }}
jobs:
check-authorization:
add-no-stale:
name: Add no-stale label
runs-on: ubuntu-latest
permissions:
issues: write # To add labels to issues
if: >-
github.event.issue.type.name == 'Task'
|| github.event.issue.type.name == 'Epic'
|| github.event.issue.type.name == 'Opportunity'
steps:
- name: Add no-stale label
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
labels: ['no-stale']
});
check-authorization:
name: Check authorization
runs-on: ubuntu-latest
permissions:
issues: write # To comment on, label, and close issues
# Only run if this is a Task issue type (from the issue form)
if: github.event.issue.type.name == 'Task'
steps:

View File

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

2
.nvmrc
View File

@@ -1 +1 @@
24.13.0
24.14.0

View File

@@ -14,40 +14,28 @@ export const mockEnergy = (hass: MockHomeAssistant) => {
energy_sources: [
{
type: "grid",
flow_from: [
{
stat_energy_from: "sensor.energy_consumption_tarif_1",
stat_cost: "sensor.energy_consumption_tarif_1_cost",
entity_energy_price: null,
number_energy_price: null,
},
{
stat_energy_from: "sensor.energy_consumption_tarif_2",
stat_cost: "sensor.energy_consumption_tarif_2_cost",
entity_energy_price: null,
number_energy_price: null,
},
],
flow_to: [
{
stat_energy_to: "sensor.energy_production_tarif_1",
stat_compensation:
"sensor.energy_production_tarif_1_compensation",
entity_energy_price: null,
number_energy_price: null,
},
{
stat_energy_to: "sensor.energy_production_tarif_2",
stat_compensation:
"sensor.energy_production_tarif_2_compensation",
entity_energy_price: null,
number_energy_price: null,
},
],
power: [
{ stat_rate: "sensor.power_grid" },
{ stat_rate: "sensor.power_grid_return" },
],
stat_energy_from: "sensor.energy_consumption_tarif_1",
stat_energy_to: "sensor.energy_production_tarif_1",
stat_cost: "sensor.energy_consumption_tarif_1_cost",
stat_compensation: "sensor.energy_production_tarif_1_compensation",
entity_energy_price: null,
number_energy_price: null,
entity_energy_price_export: null,
number_energy_price_export: null,
stat_rate: "sensor.power_grid",
cost_adjustment_day: 0,
},
{
type: "grid",
stat_energy_from: "sensor.energy_consumption_tarif_2",
stat_energy_to: "sensor.energy_production_tarif_2",
stat_cost: "sensor.energy_consumption_tarif_2_cost",
stat_compensation: "sensor.energy_production_tarif_2_compensation",
entity_energy_price: null,
number_energy_price: null,
entity_energy_price_export: null,
number_energy_price_export: null,
stat_rate: "sensor.power_grid_return",
cost_adjustment_day: 0,
},
{

View File

@@ -12,6 +12,7 @@ import eslintConfigPrettier from "eslint-config-prettier";
import { configs as litConfigs } from "eslint-plugin-lit";
import { configs as wcConfigs } from "eslint-plugin-wc";
import { configs as a11yConfigs } from "eslint-plugin-lit-a11y";
import html from "@html-eslint/eslint-plugin";
const _filename = fileURLToPath(import.meta.url);
const _dirname = path.dirname(_filename);
@@ -192,5 +193,13 @@ export default tseslint.config(
languageOptions: {
globals: globals.audioWorklet,
},
},
{
plugins: {
html,
},
rules: {
"html/no-invalid-attr-value": "error",
},
}
);

View File

@@ -21,8 +21,8 @@ type DialogType =
| "basic"
| "basic-subtitle-below"
| "basic-subtitle-above"
| "allow-mode-change"
| "form"
| "form-block-mode"
| "actions"
| "large"
| "small";
@@ -69,8 +69,8 @@ export class DemoHaAdaptiveDialog extends LitElement {
<ha-button @click=${this._handleOpenDialog("form")}
>Adaptive dialog with form</ha-button
>
<ha-button @click=${this._handleOpenDialog("form-block-mode")}
>Adaptive dialog with form (block mode change)</ha-button
<ha-button @click=${this._handleOpenDialog("allow-mode-change")}
>Adaptive dialog with allow mode change</ha-button
>
<ha-button @click=${this._handleOpenDialog("actions")}
>Adaptive dialog with actions</ha-button
@@ -164,27 +164,15 @@ export class DemoHaAdaptiveDialog extends LitElement {
<ha-adaptive-dialog
.hass=${this._hass}
.open=${this._openDialog === "form-block-mode"}
header-title="Adaptive dialog with form (block mode change)"
header-subtitle="This form will not reset when the viewport size changes"
block-mode-change
.allowModeChange=${this._openDialog === "allow-mode-change"}
header-title="Adaptive dialog with allow mode change"
header-subtitle="Resize the window while this dialog is open"
@closed=${this._handleClosed}
>
<ha-form autofocus .schema=${SCHEMA}></ha-form>
<ha-dialog-footer slot="footer">
<ha-button
@click=${this._handleClosed}
slot="secondaryAction"
variant="plain"
>Cancel</ha-button
>
<ha-button
@click=${this._handleClosed}
slot="primaryAction"
variant="accent"
>Submit</ha-button
>
</ha-dialog-footer>
<div>
This dialog can switch between dialog mode and bottom sheet mode
while open.
</div>
</ha-adaptive-dialog>
<ha-adaptive-dialog
@@ -215,7 +203,7 @@ export class DemoHaAdaptiveDialog extends LitElement {
<li>
<strong>Dialog mode:</strong> Used on larger screens (width &gt;
870px and height &gt; 500px). Renders as a centered dialog using
<code>ha-wa-dialog</code>.
<code>ha-dialog</code>.
</li>
<li>
<strong>Bottom sheet mode:</strong> Used on mobile devices and
@@ -225,10 +213,9 @@ export class DemoHaAdaptiveDialog extends LitElement {
</ul>
<p>
The mode is determined automatically and updates when the window is
resized. To prevent mode changes after the initial mount (useful for
preventing form resets), use the <code>block-mode-change</code>
attribute.
By default, the mode is determined at mount time and then stays fixed
while the dialog is open. To allow switching modes while the viewport
changes, use the <code>allow-mode-change</code> attribute.
</p>
<h3>Width</h3>
@@ -394,15 +381,15 @@ export class DemoHaAdaptiveDialog extends LitElement {
<p>
If you don't need responsive behavior, use
<code>ha-wa-dialog</code> directly for desktop-only dialogs or
<code>ha-dialog</code> directly for desktop-only dialogs or
<code>ha-bottom-sheet</code> for mobile-only sheets.
</p>
<p>
Use the <code>block-mode-change</code> attribute when you want to
prevent the dialog from switching modes after it's opened. This is
especially useful for forms, as it prevents form data from being lost
when users resize their browser window.
Use the <code>allow-mode-change</code> attribute when you want the
dialog to switch between modes as the viewport changes after opening.
For forms, you can keep the default behavior to avoid resetting fields
on resize.
</p>
<h3>Example usage</h3>
@@ -410,7 +397,6 @@ export class DemoHaAdaptiveDialog extends LitElement {
<pre><code>&lt;ha-adaptive-dialog
.hass=\${this.hass}
open
width="medium"
header-title="Dialog title"
header-subtitle="Dialog subtitle"
&gt;
@@ -427,27 +413,10 @@ export class DemoHaAdaptiveDialog extends LitElement {
&lt;/ha-dialog-footer&gt;
&lt;/ha-adaptive-dialog&gt;</code></pre>
<p>Example with <code>block-mode-change</code> for forms:</p>
<pre><code>&lt;ha-adaptive-dialog
.hass=\${this.hass}
open
header-title="Edit configuration"
block-mode-change
&gt;
&lt;ha-form .schema=\${schema} .data=\${data}&gt;&lt;/ha-form&gt;
&lt;ha-dialog-footer slot="footer"&gt;
&lt;ha-button slot="secondaryAction" variant="plain"
&gt;Cancel&lt;/ha-button
&gt;
&lt;ha-button slot="primaryAction" variant="accent"&gt;Save&lt;/ha-button&gt;
&lt;/ha-dialog-footer&gt;
&lt;/ha-adaptive-dialog&gt;</code></pre>
<h3>API</h3>
<p>
This component combines <code>ha-wa-dialog</code> and
This component combines <code>ha-dialog</code> and
<code>ha-bottom-sheet</code> with automatic mode switching based on
screen size.
</p>
@@ -521,12 +490,10 @@ export class DemoHaAdaptiveDialog extends LitElement {
<td></td>
</tr>
<tr>
<td><code>block-mode-change</code></td>
<td><code>allow-mode-change</code></td>
<td>
When set, the mode is determined at mount time based on the
current screen size, but subsequent mode changes are blocked.
Useful for preventing forms from resetting when the viewport
size changes.
When set, the dialog can switch between modes as the viewport
size changes while it is open.
</td>
<td><code>false</code></td>
<td><code>false</code>, <code>true</code></td>

View File

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

View File

@@ -6,7 +6,7 @@ import "../../../../src/components/ha-card";
import "../../../../src/components/ha-dialog-footer";
import "../../../../src/components/ha-form/ha-form";
import "../../../../src/components/ha-icon-button";
import "../../../../src/components/ha-wa-dialog";
import "../../../../src/components/ha-dialog";
import type { HaFormSchema } from "../../../../src/components/ha-form/types";
const SCHEMA: HaFormSchema[] = [
@@ -22,14 +22,14 @@ type DialogType =
| "form"
| "actions";
@customElement("demo-components-ha-wa-dialog")
export class DemoHaWaDialog extends LitElement {
@customElement("demo-components-ha-dialog")
export class DemoHaDialog extends LitElement {
@state() private _openDialog: DialogType = false;
protected render() {
return html`
<div class="content">
<h1>Dialog <code>&lt;ha-wa-dialog&gt;</code></h1>
<h1>Dialog <code>&lt;ha-dialog&gt;</code></h1>
<p class="subtitle">Dialog component built with WebAwesome.</p>
@@ -53,24 +53,24 @@ export class DemoHaWaDialog extends LitElement {
>
</div>
<ha-wa-dialog
<ha-dialog
.open=${this._openDialog === "basic"}
header-title="Basic dialog"
@closed=${this._handleClosed}
>
<div>Dialog content</div>
</ha-wa-dialog>
</ha-dialog>
<ha-wa-dialog
<ha-dialog
.open=${this._openDialog === "basic-subtitle-below"}
header-title="Basic dialog with subtitle"
header-subtitle="This is a basic dialog with a subtitle below"
@closed=${this._handleClosed}
>
<div>Dialog content</div>
</ha-wa-dialog>
</ha-dialog>
<ha-wa-dialog
<ha-dialog
.open=${this._openDialog === "basic-subtitle-above"}
header-title="Dialog with subtitle above"
header-subtitle="This is a basic dialog with a subtitle above"
@@ -78,9 +78,9 @@ export class DemoHaWaDialog extends LitElement {
@closed=${this._handleClosed}
>
<div>Dialog content</div>
</ha-wa-dialog>
</ha-dialog>
<ha-wa-dialog
<ha-dialog
.open=${this._openDialog === "form"}
header-title="Dialog with form"
header-subtitle="This is a dialog with a form and a footer"
@@ -91,17 +91,18 @@ export class DemoHaWaDialog extends LitElement {
<ha-dialog-footer slot="footer">
<ha-button
data-dialog="close"
appearance="plain"
slot="secondaryAction"
variant="plain"
>Cancel</ha-button
>
<ha-button data-dialog="close" slot="primaryAction" variant="accent"
>Submit</ha-button
>
Cancel
</ha-button>
<ha-button data-dialog="close" slot="primaryAction">
Submit
</ha-button>
</ha-dialog-footer>
</ha-wa-dialog>
</ha-dialog>
<ha-wa-dialog
<ha-dialog
.open=${this._openDialog === "actions"}
header-title="Dialog with actions"
header-subtitle="This is a dialog with header actions"
@@ -113,7 +114,7 @@ export class DemoHaWaDialog extends LitElement {
</div>
<div>Dialog content</div>
</ha-wa-dialog>
</ha-dialog>
<h2>Design</h2>
@@ -228,19 +229,19 @@ export class DemoHaWaDialog extends LitElement {
<tr>
<th>Slot</th>
<th>Description</th>
<th>Variant to use</th>
<th>Appearance to use</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>secondaryAction</code></td>
<td>The secondary action button(s).</td>
<td><code>plain</code></td>
<td><code>appearance="plain"</code></td>
</tr>
<tr>
<td><code>primaryAction</code></td>
<td>The primary action button(s).</td>
<td><code>accent</code></td>
<td>Default (no appearance attribute)</td>
</tr>
</tbody>
</table>
@@ -249,7 +250,7 @@ export class DemoHaWaDialog extends LitElement {
<h3>Example Usage</h3>
<pre><code>&lt;ha-wa-dialog
<pre><code>&lt;ha-dialog
open
header-title="Dialog title"
header-subtitle="Dialog subtitle"
@@ -261,12 +262,18 @@ export class DemoHaWaDialog extends LitElement {
&lt;/div&gt;
&lt;div&gt;Dialog content&lt;/div&gt;
&lt;ha-dialog-footer slot="footer"&gt;
&lt;ha-button data-dialog="close" slot="secondaryAction" variant="plain"
&gt;Cancel&lt;/ha-button
&lt;ha-button
data-dialog="close"
appearance="plain"
slot="secondaryAction"
&gt;
&lt;ha-button slot="primaryAction" variant="accent"&gt;Submit&lt;/ha-button&gt;
Cancel
&lt;/ha-button&gt;
&lt;ha-button data-dialog="close" slot="primaryAction"&gt;
Submit
&lt;/ha-button&gt;
&lt;/ha-dialog-footer&gt;
&lt;/ha-wa-dialog&gt;</code></pre>
&lt;/ha-dialog&gt;</code></pre>
<h3>API</h3>
@@ -514,6 +521,6 @@ export class DemoHaWaDialog extends LitElement {
declare global {
interface HTMLElementTagNameMap {
"demo-components-ha-wa-dialog": DemoHaWaDialog;
"demo-components-ha-dialog": DemoHaDialog;
}
}

View File

@@ -1,3 +0,0 @@
---
title: Dialog (ha-wa-dialog)
---

View File

@@ -18,7 +18,7 @@ The Home Assistant interface is based on Material Design. It's a design system c
We want to make it as easy for designers to contribute as it is for developers. Theres a lot a designer can contribute to:
- Meet us at <a href="https://www.home-assistant.io/join-chat" rel="noopener noreferrer" target="_blank">devs_ux Discord</a>. Feel free to share your designs, user test or strategic ideas.
- Meet us at <a href="https://www.home-assistant.io/join-chat-design" rel="noopener noreferrer" target="_blank">Discord #designers channel</a>. If you can't see the channel, make sure you set the correct role in Channels & Roles.
- Start designing with our <a href="https://www.figma.com/community/file/967153512097289521/Home-Assistant-DesignKit" rel="noopener noreferrer" target="_blank">Figma DesignKit</a>.
- Find the latest UX <a href="https://github.com/home-assistant/frontend/discussions?discussions_q=label%3Aux" rel="noopener noreferrer" target="_blank">discussions</a> and <a href="https://github.com/home-assistant/frontend/labels/ux" rel="noopener noreferrer" target="_blank">issues</a> on GitHub. Everyone can start a new issue or discussion!

View File

@@ -222,6 +222,9 @@ class HaLandingPage extends LandingPageBaseElement {
flex-direction: column;
gap: var(--ha-space-4);
}
ha-language-picker {
min-width: 200px;
}
ha-alert p {
text-align: unset;
}

View File

@@ -29,15 +29,15 @@
"@babel/runtime": "7.28.6",
"@braintree/sanitize-url": "7.1.2",
"@codemirror/autocomplete": "6.20.0",
"@codemirror/commands": "6.10.1",
"@codemirror/commands": "6.10.2",
"@codemirror/language": "6.12.1",
"@codemirror/legacy-modes": "6.5.2",
"@codemirror/search": "6.6.0",
"@codemirror/state": "6.5.4",
"@codemirror/view": "6.39.12",
"@codemirror/view": "6.39.15",
"@date-fns/tz": "1.4.1",
"@egjs/hammerjs": "2.0.17",
"@formatjs/intl-datetimeformat": "7.2.1",
"@formatjs/intl-datetimeformat": "7.2.2",
"@formatjs/intl-displaynames": "7.2.1",
"@formatjs/intl-durationformat": "0.10.1",
"@formatjs/intl-getcanonicallocales": "3.2.1",
@@ -52,7 +52,7 @@
"@fullcalendar/list": "6.1.20",
"@fullcalendar/luxon3": "6.1.20",
"@fullcalendar/timegrid": "6.1.20",
"@home-assistant/webawesome": "3.0.0-ha.2",
"@home-assistant/webawesome": "3.2.1-ha.3",
"@lezer/highlight": "1.2.3",
"@lit-labs/motion": "1.1.0",
"@lit-labs/observers": "2.1.0",
@@ -68,7 +68,6 @@
"@material/mwc-fab": "0.27.0",
"@material/mwc-floating-label": "0.27.0",
"@material/mwc-formfield": "patch:@material/mwc-formfield@npm%3A0.27.0#~/.yarn/patches/@material-mwc-formfield-npm-0.27.0-9528cb60f6.patch",
"@material/mwc-icon-button": "0.27.0",
"@material/mwc-linear-progress": "0.27.0",
"@material/mwc-list": "patch:@material/mwc-list@npm%3A0.27.0#~/.yarn/patches/@material-mwc-list-npm-0.27.0-5344fc9de4.patch",
"@material/mwc-radio": "0.27.0",
@@ -84,7 +83,7 @@
"@mdi/js": "7.4.47",
"@mdi/svg": "7.4.47",
"@replit/codemirror-indentation-markers": "6.5.3",
"@swc/helpers": "0.5.18",
"@swc/helpers": "0.5.19",
"@thomasloven/round-slider": "0.6.0",
"@tsparticles/engine": "3.9.1",
"@tsparticles/preset-links": "3.2.0",
@@ -119,7 +118,7 @@
"lit": "3.3.2",
"lit-html": "3.3.2",
"luxon": "3.7.2",
"marked": "17.0.1",
"marked": "17.0.3",
"memoize-one": "6.0.0",
"node-vibrant": "4.0.4",
"object-hash": "3.0.0",
@@ -132,7 +131,7 @@
"stacktrace-js": "2.0.2",
"superstruct": "2.0.2",
"tinykeys": "3.0.0",
"ua-parser-js": "2.0.8",
"ua-parser-js": "2.0.9",
"vue": "2.7.16",
"vue2-daterange-picker": "0.6.8",
"weekstart": "2.0.0",
@@ -149,13 +148,14 @@
"@babel/helper-define-polyfill-provider": "0.6.6",
"@babel/plugin-transform-runtime": "7.29.0",
"@babel/preset-env": "7.29.0",
"@bundle-stats/plugin-webpack-filter": "4.21.9",
"@bundle-stats/plugin-webpack-filter": "4.21.10",
"@html-eslint/eslint-plugin": "0.56.0",
"@lokalise/node-api": "15.6.1",
"@octokit/auth-oauth-device": "8.0.3",
"@octokit/plugin-retry": "8.0.3",
"@octokit/plugin-retry": "8.1.0",
"@octokit/rest": "22.0.1",
"@rsdoctor/rspack-plugin": "1.5.1",
"@rspack/core": "1.7.4",
"@rsdoctor/rspack-plugin": "1.5.2",
"@rspack/core": "1.7.6",
"@rspack/dev-server": "1.2.1",
"@types/babel__plugin-transform-runtime": "7.9.5",
"@types/chromecast-caf-receiver": "6.0.25",
@@ -172,7 +172,7 @@
"@types/mocha": "10.0.10",
"@types/qrcode": "1.5.6",
"@types/sortablejs": "1.15.9",
"@types/tar": "6.1.13",
"@types/tar": "7.0.87",
"@types/ua-parser-js": "0.7.39",
"@types/webspeechapi": "0.0.29",
"@vitest/coverage-v8": "4.0.18",
@@ -180,25 +180,25 @@
"babel-plugin-template-html-minifier": "4.1.0",
"browserslist-useragent-regexp": "4.1.3",
"del": "8.0.1",
"eslint": "9.39.2",
"eslint": "9.39.3",
"eslint-config-airbnb-base": "15.0.0",
"eslint-config-prettier": "10.1.8",
"eslint-import-resolver-webpack": "0.13.10",
"eslint-plugin-import": "2.32.0",
"eslint-plugin-lit": "2.1.1",
"eslint-plugin-lit": "2.2.1",
"eslint-plugin-lit-a11y": "5.1.1",
"eslint-plugin-unused-imports": "4.3.0",
"eslint-plugin-wc": "3.0.2",
"eslint-plugin-unused-imports": "4.4.1",
"eslint-plugin-wc": "3.1.0",
"fancy-log": "2.0.0",
"fs-extra": "11.3.3",
"glob": "13.0.0",
"glob": "13.0.6",
"gulp": "5.0.1",
"gulp-brotli": "3.0.0",
"gulp-json-transform": "0.5.0",
"gulp-rename": "2.1.0",
"html-minifier-terser": "7.2.0",
"husky": "9.1.7",
"jsdom": "28.0.0",
"jsdom": "28.1.0",
"jszip": "3.10.1",
"lint-staged": "16.2.7",
"lit-analyzer": "2.0.3",
@@ -210,12 +210,12 @@
"rspack-manifest-plugin": "5.2.1",
"serve": "14.2.5",
"sinon": "21.0.1",
"tar": "7.5.7",
"tar": "7.5.9",
"terser-webpack-plugin": "5.3.16",
"ts-lit-plugin": "2.0.2",
"typescript": "5.9.3",
"typescript-eslint": "8.54.0",
"vite-tsconfig-paths": "6.0.5",
"typescript-eslint": "8.56.0",
"vite-tsconfig-paths": "6.1.1",
"vitest": "4.0.18",
"webpack-stats-plugin": "1.1.3",
"webpackbar": "7.0.0",
@@ -235,6 +235,6 @@
},
"packageManager": "yarn@4.12.0",
"volta": {
"node": "24.13.0"
"node": "24.14.0"
}
}

View File

@@ -31,7 +31,7 @@ export class HaAuthFormString extends HaFormString {
right: 8px;
inset-inline-start: initial;
inset-inline-end: 8px;
--mdc-icon-button-size: 40px;
--ha-icon-button-size: 40px;
--mdc-icon-size: 20px;
color: var(--secondary-text-color);
direction: var(--direction);

View File

@@ -116,3 +116,6 @@ export const UNIT_F = "°F";
/** Entity ID of the default view. */
export const DEFAULT_VIEW_ENTITY_ID = "group.default_view";
/** String to visually separate labels on UI */
export const STRINGS_SEPARATOR_DOT = " · ";

View File

@@ -210,3 +210,39 @@ const formatDateWeekdayShortMem = memoizeOne(
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
// Mon, Aug 10
export const formatDateWeekdayVeryShortDate = (
dateObj: Date,
locale: FrontendLocaleData,
config: HassConfig
) =>
formatDateWeekdayVeryShortDateMem(locale, config.time_zone).format(dateObj);
const formatDateWeekdayVeryShortDateMem = memoizeOne(
(locale: FrontendLocaleData, serverTimeZone: string) =>
new Intl.DateTimeFormat(locale.language, {
weekday: "short",
month: "short",
day: "numeric",
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
// Mon, Aug 10, 2021
export const formatDateWeekdayShortDate = (
dateObj: Date,
locale: FrontendLocaleData,
config: HassConfig
) => formatDateWeekdayShortDateMem(locale, config.time_zone).format(dateObj);
const formatDateWeekdayShortDateMem = memoizeOne(
(locale: FrontendLocaleData, serverTimeZone: string) =>
new Intl.DateTimeFormat(locale.language, {
weekday: "short",
month: "short",
day: "numeric",
year: "numeric",
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);

View File

@@ -3,13 +3,14 @@ import { UNAVAILABLE, UNKNOWN } from "../../data/entity/entity";
import type { EntityRegistryDisplayEntry } from "../../data/entity/entity_registry";
import type { FrontendLocaleData } from "../../data/translation";
import { TimeZone } from "../../data/translation";
import type { HomeAssistant } from "../../types";
import type { HomeAssistant, ValuePart } from "../../types";
import { formatDate } from "../datetime/format_date";
import { formatDateTime } from "../datetime/format_date_time";
import { DURATION_UNITS, formatDuration } from "../datetime/format_duration";
import { formatTime } from "../datetime/format_time";
import {
formatNumber,
formatNumberToParts,
getNumberFormatOptions,
isNumericFromAttributes,
} from "../number/format_number";
@@ -51,8 +52,36 @@ export const computeStateDisplayFromEntityAttributes = (
attributes: any,
state: string
): string => {
const parts = computeStateToPartsFromEntityAttributes(
localize,
locale,
sensorNumericDeviceClasses,
config,
entity,
entityId,
attributes,
state
);
return parts.map((part) => part.value).join("");
};
const computeStateToPartsFromEntityAttributes = (
localize: LocalizeFunc,
locale: FrontendLocaleData,
sensorNumericDeviceClasses: string[],
config: HassConfig,
entity: EntityRegistryDisplayEntry | undefined,
entityId: string,
attributes: any,
state: string
): ValuePart[] => {
if (state === UNKNOWN || state === UNAVAILABLE) {
return localize(`state.default.${state}`);
return [
{
type: "value",
value: localize(`state.default.${state}`),
},
];
}
const domain = computeDomain(entityId);
@@ -73,19 +102,27 @@ export const computeStateDisplayFromEntityAttributes = (
DURATION_UNITS.includes(attributes.unit_of_measurement)
) {
try {
return formatDuration(
locale,
state,
attributes.unit_of_measurement,
entity?.display_precision
);
return [
{
type: "value",
value: formatDuration(
locale,
state,
attributes.unit_of_measurement,
entity?.display_precision
),
},
];
} catch (_err) {
// fallback to default
}
}
// state is monetary
if (attributes.device_class === "monetary") {
let parts: Record<string, string>[] = [];
try {
return formatNumber(state, locale, {
parts = formatNumberToParts(state, locale, {
style: "currency",
currency: attributes.unit_of_measurement,
minimumFractionDigits: 2,
@@ -98,8 +135,34 @@ export const computeStateDisplayFromEntityAttributes = (
} catch (_err) {
// fallback to default
}
const TYPE_MAP: Record<string, ValuePart["type"]> = {
integer: "value",
group: "value",
decimal: "value",
fraction: "value",
literal: "literal",
currency: "unit",
};
const valueParts: ValuePart[] = [];
for (const part of parts) {
const type = TYPE_MAP[part.type];
if (!type) continue;
const last = valueParts[valueParts.length - 1];
// Merge consecutive numeric parts (e.g. "1" + "," + "234" + "." + "56" → "1,234.56")
if (type === "value" && last?.type === "value") {
last.value += part.value;
} else {
valueParts.push({ type, value: part.value });
}
}
return valueParts;
}
// default processing of numeric values
const value = formatNumber(
state,
locale,
@@ -114,10 +177,14 @@ export const computeStateDisplayFromEntityAttributes = (
attributes.unit_of_measurement;
if (unit) {
return `${value}${blankBeforeUnit(unit, locale)}${unit}`;
return [
{ type: "value", value: value },
{ type: "literal", value: blankBeforeUnit(unit, locale) },
{ type: "unit", value: unit },
];
}
return value;
return [{ type: "value", value: value }];
}
if (["date", "input_datetime", "time"].includes(domain)) {
@@ -129,36 +196,51 @@ export const computeStateDisplayFromEntityAttributes = (
const components = state.split(" ");
if (components.length === 2) {
// Date and time.
return formatDateTime(
new Date(components.join("T")),
{ ...locale, time_zone: TimeZone.local },
config
);
return [
{
type: "value",
value: formatDateTime(
new Date(components.join("T")),
{ ...locale, time_zone: TimeZone.local },
config
),
},
];
}
if (components.length === 1) {
if (state.includes("-")) {
// Date only.
return formatDate(
new Date(`${state}T00:00`),
{ ...locale, time_zone: TimeZone.local },
config
);
return [
{
type: "value",
value: formatDate(
new Date(`${state}T00:00`),
{ ...locale, time_zone: TimeZone.local },
config
),
},
];
}
if (state.includes(":")) {
// Time only.
const now = new Date();
return formatTime(
new Date(`${now.toISOString().split("T")[0]}T${state}`),
{ ...locale, time_zone: TimeZone.local },
config
);
return [
{
type: "value",
value: formatTime(
new Date(`${now.toISOString().split("T")[0]}T${state}`),
{ ...locale, time_zone: TimeZone.local },
config
),
},
];
}
}
return state;
return [{ type: "value", value: state }];
} catch (_e) {
// Formatting methods may throw error if date parsing doesn't go well,
// just return the state string in that case.
return state;
return [{ type: "value", value: state }];
}
}
@@ -182,25 +264,58 @@ export const computeStateDisplayFromEntityAttributes = (
(domain === "sensor" && attributes.device_class === "timestamp")
) {
try {
return formatDateTime(new Date(state), locale, config);
return [
{
type: "value",
value: formatDateTime(new Date(state), locale, config),
},
];
} catch (_err) {
return state;
return [{ type: "value", value: state }];
}
}
return (
(entity?.translation_key &&
localize(
`component.${entity.platform}.entity.${domain}.${entity.translation_key}.state.${state}`
)) ||
// Return device class translation
(attributes.device_class &&
localize(
`component.${domain}.entity_component.${attributes.device_class}.state.${state}`
)) ||
// Return default translation
localize(`component.${domain}.entity_component._.state.${state}`) ||
// We don't know! Return the raw state.
state
return [
{
type: "value",
value:
(entity?.translation_key &&
localize(
`component.${entity.platform}.entity.${domain}.${entity.translation_key}.state.${state}`
)) ||
// Return device class translation
(attributes.device_class &&
localize(
`component.${domain}.entity_component.${attributes.device_class}.state.${state}`
)) ||
// Return default translation
localize(`component.${domain}.entity_component._.state.${state}`) ||
// We don't know! Return the raw state.
state,
},
];
};
export const computeStateToParts = (
localize: LocalizeFunc,
stateObj: HassEntity,
locale: FrontendLocaleData,
sensorNumericDeviceClasses: string[],
config: HassConfig,
entities: HomeAssistant["entities"],
state?: string
): ValuePart[] => {
const entity = entities?.[stateObj.entity_id] as
| EntityRegistryDisplayEntry
| undefined;
return computeStateToPartsFromEntityAttributes(
localize,
locale,
sensorNumericDeviceClasses,
config,
entity,
stateObj.entity_id,
stateObj.attributes,
state !== undefined ? state : stateObj.state
);
};

View File

@@ -38,6 +38,18 @@ export const getEntityContext = (
return getEntityEntryContext(entry, entities, devices, areas, floors);
};
export const getEntityAreaId = (
entityId: string,
entities: HomeAssistant["entities"],
devices: HomeAssistant["devices"]
): string | undefined => {
const entry = entities[entityId];
if (!entry) return undefined;
const deviceId = entry.device_id;
const device = deviceId ? devices[deviceId] : undefined;
return entry.area_id || device?.area_id || undefined;
};
export const getEntityEntryContext = (
entry:
| EntityRegistryDisplayEntry

View File

@@ -5,7 +5,6 @@ import type {
import type { EntityRegistryDisplayEntry } from "../../data/entity/entity_registry";
import type { FrontendLocaleData } from "../../data/translation";
import { NumberFormat } from "../../data/translation";
import { round } from "./round";
/**
* Returns true if the entity is considered numeric based on the attributes it has
@@ -52,7 +51,22 @@ export const formatNumber = (
num: string | number,
localeOptions?: FrontendLocaleData,
options?: Intl.NumberFormatOptions
): string => {
): string =>
formatNumberToParts(num, localeOptions, options)
.map((part) => part.value)
.join("");
/**
* Returns an array of objects containing the formatted number in parts
* Similar to Intl.NumberFormat.prototype.formatToParts()
*
* Input params - same as for formatNumber()
*/
export const formatNumberToParts = (
num: string | number,
localeOptions?: FrontendLocaleData,
options?: Intl.NumberFormatOptions
): any[] => {
const locale = localeOptions
? numberFormatToLocale(localeOptions)
: undefined;
@@ -71,7 +85,7 @@ export const formatNumber = (
return new Intl.NumberFormat(
locale,
getDefaultFormatOptions(num, options)
).format(Number(num));
).formatToParts(Number(num));
}
if (
@@ -86,15 +100,10 @@ export const formatNumber = (
...options,
useGrouping: false,
})
).format(Number(num));
).formatToParts(Number(num));
}
if (typeof num === "string") {
return num;
}
return `${round(num, options?.maximumFractionDigits).toString()}${
options?.style === "currency" ? ` ${options.currency}` : ""
}`;
return [{ type: "literal", value: num }];
};
/**

View File

@@ -0,0 +1,28 @@
const SI_PREFIX_MULTIPLIERS: Record<string, number> = {
T: 1e12,
G: 1e9,
M: 1e6,
k: 1e3,
m: 1e-3,
"\u00B5": 1e-6, // µ (micro sign)
"\u03BC": 1e-6, // μ (greek small letter mu)
};
/**
* Normalize a numeric value by detecting SI unit prefixes (T, G, M, k, m, µ).
* Only applies when the unit is longer than 1 character and starts with a
* recognized prefix, avoiding false positives on standalone units like "m" (meters).
*/
export const normalizeValueBySIPrefix = (
value: number,
unit: string | undefined
): number => {
if (!unit || unit.length <= 1) {
return value;
}
const prefix = unit[0];
if (prefix in SI_PREFIX_MULTIPLIERS) {
return value * SI_PREFIX_MULTIPLIERS[prefix];
}
return value;
};

View File

@@ -12,6 +12,10 @@ export type FormatEntityStateFunc = (
stateObj: HassEntity,
state?: string
) => string;
export type FormatEntityStateToPartsFunc = (
stateObj: HassEntity,
state?: string
) => ValuePart[];
export type FormatEntityAttributeValueFunc = (
stateObj: HassEntity,
attribute: string,
@@ -46,12 +50,13 @@ export const computeFormatFunctions = async (
sensorNumericDeviceClasses: string[]
): Promise<{
formatEntityState: FormatEntityStateFunc;
formatEntityStateToParts: FormatEntityStateToPartsFunc;
formatEntityAttributeValue: FormatEntityAttributeValueFunc;
formatEntityAttributeValueToParts: FormatEntityAttributeValueToPartsFunc;
formatEntityAttributeName: FormatEntityAttributeNameFunc;
formatEntityName: FormatEntityNameFunc;
}> => {
const { computeStateDisplay } =
const { computeStateDisplay, computeStateToParts } =
await import("../entity/compute_state_display");
const {
computeAttributeValueDisplay,
@@ -70,6 +75,16 @@ export const computeFormatFunctions = async (
entities,
state
),
formatEntityStateToParts: (stateObj, state) =>
computeStateToParts(
localize,
stateObj,
locale,
sensorNumericDeviceClasses,
config,
entities,
state
),
formatEntityAttributeValue: (stateObj, attribute, value) =>
computeAttributeValueDisplay(
localize,

View File

@@ -18,9 +18,11 @@ import { classMap } from "lit/directives/class-map";
import { styleMap } from "lit/directives/style-map";
import { ensureArray } from "../../common/array/ensure-array";
import { getAllGraphColors } from "../../common/color/colors";
import { fireEvent } from "../../common/dom/fire_event";
import type { HASSDomEvent } from "../../common/dom/fire_event";
import { fireEvent } from "../../common/dom/fire_event";
import { listenMediaQuery } from "../../common/dom/media_query";
import { afterNextRender } from "../../common/util/render-status";
import { filterXSS } from "../../common/util/xss";
import { themesContext } from "../../data/context";
import type { Themes } from "../../data/ws-themes";
import type { ECOption } from "../../resources/echarts/echarts";
@@ -28,8 +30,6 @@ import type { HomeAssistant } from "../../types";
import { isMac } from "../../util/is_mac";
import "../chips/ha-assist-chip";
import "../ha-icon-button";
import { afterNextRender } from "../../common/util/render-status";
import { filterXSS } from "../../common/util/xss";
import { formatTimeLabel } from "./axis-label";
import { downSampleLineData } from "./down-sample";
@@ -1115,13 +1115,13 @@ export class HaChartBase extends LitElement {
.chart-controls ::slotted(ha-icon-button) {
background: var(--card-background-color);
border-radius: var(--ha-border-radius-sm);
--mdc-icon-button-size: 32px;
--ha-icon-button-size: 32px;
color: var(--primary-color);
border: 1px solid var(--divider-color);
}
.chart-controls.small ha-icon-button,
.chart-controls.small ::slotted(ha-icon-button) {
--mdc-icon-button-size: 22px;
--ha-icon-button-size: 22px;
--mdc-icon-size: 16px;
}
.chart-controls ha-icon-button.inactive,

View File

@@ -306,7 +306,10 @@ export class StateHistoryChartLine extends LitElement {
visualMap: this._visualMap,
tooltip: {
trigger: "axis",
appendTo: document.body,
renderMode: "html",
position: "bottom",
align: "center",
confine: true,
formatter: this._renderTooltip,
},
};

View File

@@ -255,7 +255,10 @@ export class StateHistoryChartTimeline extends LitElement {
right: rtl ? labelWidth : 1,
},
tooltip: {
appendTo: document.body,
renderMode: "html",
position: "bottom",
align: "center",
confine: true,
formatter: this._renderTooltip,
},
};

View File

@@ -335,7 +335,10 @@ export class StatisticsChart extends LitElement {
},
tooltip: {
trigger: "axis",
appendTo: document.body,
renderMode: "html",
position: "bottom",
align: "center",
confine: true,
formatter: this._renderTooltip,
},
};
@@ -572,6 +575,7 @@ export class StatisticsChart extends LitElement {
let firstSum: number | null | undefined = null;
stats.forEach((stat) => {
const startDate = new Date(stat.start);
const endDate = new Date(stat.end);
if (prevDate === startDate) {
return;
}
@@ -601,10 +605,25 @@ export class StatisticsChart extends LitElement {
dataValues.push(val);
});
if (!this._hiddenStats.has(statistic_id)) {
pushData(startDate, new Date(stat.end), dataValues);
pushData(
startDate,
endDate.getTime() < endTime.getTime() ? endDate : endTime,
dataValues
);
}
});
// Close out the last stat segment at prevEndTime
const lastEndTime = prevEndTime;
const lastValues = prevValues;
if (lastEndTime && lastValues) {
statDataSets.forEach((d, i) => {
d.data!.push(
this._transformDataValue([lastEndTime, ...lastValues[i]!])
);
});
}
// Append current state if viewing recent data
const now = new Date();
// allow 10m of leeway for "now", because stats are 5 minute aggregated
@@ -619,16 +638,6 @@ export class StatisticsChart extends LitElement {
isFinite(currentValue) &&
!this._hiddenStats.has(statistic_id)
) {
// First, close out the last stat segment at prevEndTime
const lastEndTime = prevEndTime;
const lastValues = prevValues;
if (lastEndTime && lastValues) {
statDataSets.forEach((d, i) => {
d.data!.push(
this._transformDataValue([lastEndTime, ...lastValues[i]!])
);
});
}
// Then push the current state at now
statTypes.forEach((type, i) => {
const val: (number | null)[] = [];

View File

@@ -6,6 +6,7 @@ import { computeDomain } from "../../common/entity/compute_domain";
import { computeStateDomain } from "../../common/entity/compute_state_domain";
import { FIXED_DOMAIN_STATES } from "../../common/entity/get_states";
import { stateColorProperties } from "../../common/entity/state_color";
import { slugify } from "../../common/string/slugify";
import { UNAVAILABLE, UNKNOWN } from "../../data/entity/entity";
import { computeCssValue } from "../../resources/css-variables";
@@ -32,6 +33,22 @@ function computeTimelineStateColor(
return computeCssValue("--history-unknown-color", computedStyles);
}
const domain = computeDomain(stateObj.entity_id);
// Zone states for person/device_tracker don't have specific CSS color variables,
// so they all fall back to the same --state-person-active-color.
// Only use a custom CSS variable if explicitly defined (e.g. --state-person-kitchen-color),
// otherwise return undefined to get unique colors from the generic color handler.
if (
(domain === "person" || domain === "device_tracker") &&
!((FIXED_DOMAIN_STATES[domain] || []) as readonly string[]).includes(state)
) {
return computeCssValue(
`--state-${domain}-${slugify(state, "_")}-color`,
computedStyles
);
}
const properties = stateColorProperties(stateObj, state);
if (!properties) {
@@ -41,8 +58,6 @@ function computeTimelineStateColor(
const rgb = computeCssValue(properties, computedStyles);
if (!rgb) return undefined;
const domain = computeDomain(stateObj.entity_id);
const shade = DOMAIN_STATE_SHADES[domain]?.[state] as number | number;
if (!shade) {
return rgb;

View File

@@ -9,10 +9,13 @@ import { fireEvent } from "../../common/dom/fire_event";
import { haStyleDialog } from "../../resources/styles";
import type { HomeAssistant } from "../../types";
import "../ha-button";
import { createCloseHeading } from "../ha-dialog";
import "../ha-dialog-footer";
import "../ha-icon-button";
import "../ha-list";
import "../ha-list-item";
import "../ha-sortable";
import "../ha-svg-icon";
import "../ha-dialog";
import type {
DataTableColumnContainer,
DataTableColumnData,
@@ -29,17 +32,49 @@ export class DialogDataTableSettings extends LitElement {
@state() private _hiddenColumns?: string[];
private _lastFixedKeys: string[] = [];
@state() private _open = false;
public showDialog(params: DataTableSettingsDialogParams) {
this._params = params;
this._columnOrder = params.columnOrder;
this._columnOrder = this._preserveLastFixed(params.columnOrder);
this._hiddenColumns = params.hiddenColumns;
this._open = true;
}
public closeDialog() {
this._open = false;
}
private _dialogClosed() {
this._params = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
private _lastFixedCount(): number {
const lastFixedKeys = Object.keys(this._params!.columns).filter(
(col) => this._params!.columns[col].lastFixed
);
if (lastFixedKeys.length) {
this._lastFixedKeys = lastFixedKeys;
}
return lastFixedKeys.length;
}
private _preserveLastFixed(columnOrder) {
let strippedColumnOrder;
const lastFixedCount = this._lastFixedCount();
if (lastFixedCount && columnOrder) {
strippedColumnOrder = [...columnOrder];
strippedColumnOrder.splice(
columnOrder.length - lastFixedCount,
lastFixedCount
);
}
return strippedColumnOrder;
}
private _sortedColumns = memoizeOne(
(
columns: DataTableColumnContainer,
@@ -47,7 +82,7 @@ export class DialogDataTableSettings extends LitElement {
hiddenColumns: string[] | undefined
) =>
Object.keys(columns)
.filter((col) => !columns[col].hidden)
.filter((col) => !columns[col].hidden && !columns[col].lastFixed)
.sort((a, b) => {
const orderA = columnOrder?.indexOf(a) ?? -1;
const orderB = columnOrder?.indexOf(b) ?? -1;
@@ -92,12 +127,10 @@ export class DialogDataTableSettings extends LitElement {
return html`
<ha-dialog
open
@closed=${this.closeDialog}
.heading=${createCloseHeading(
this.hass,
localize("ui.components.data-table.settings.header")
)}
.hass=${this.hass}
.open=${this._open}
header-title=${localize("ui.components.data-table.settings.header")}
@closed=${this._dialogClosed}
>
<ha-sortable
@item-moved=${this._columnMoved}
@@ -152,15 +185,17 @@ export class DialogDataTableSettings extends LitElement {
)}
</ha-list>
</ha-sortable>
<ha-button
appearance="plain"
slot="secondaryAction"
@click=${this._reset}
>${localize("ui.components.data-table.settings.restore")}</ha-button
>
<ha-button slot="primaryAction" @click=${this.closeDialog}>
${localize("ui.components.data-table.settings.done")}
</ha-button>
<ha-dialog-footer slot="footer">
<ha-button
slot="secondaryAction"
appearance="plain"
@click=${this._reset}
>${localize("ui.components.data-table.settings.restore")}</ha-button
>
<ha-button slot="primaryAction" @click=${this.closeDialog}>
${localize("ui.components.data-table.settings.done")}
</ha-button>
</ha-dialog-footer>
</ha-dialog>
`;
}
@@ -185,7 +220,8 @@ export class DialogDataTableSettings extends LitElement {
this._columnOrder = columnOrder;
this._params!.onUpdate(this._columnOrder, this._hiddenColumns);
const reportedOrder = columnOrder.concat(this._lastFixedKeys);
this._params!.onUpdate(reportedOrder, this._hiddenColumns);
}
private _toggle(ev) {
@@ -266,7 +302,8 @@ export class DialogDataTableSettings extends LitElement {
this._hiddenColumns = hidden;
this._params!.onUpdate(this._columnOrder, this._hiddenColumns);
const reportedOrder = this._columnOrder.concat(this._lastFixedKeys);
this._params!.onUpdate(reportedOrder, this._hiddenColumns);
}
private _reset() {
@@ -282,21 +319,9 @@ export class DialogDataTableSettings extends LitElement {
haStyleDialog,
css`
ha-dialog {
--mdc-dialog-max-width: 500px;
--dialog-z-index: 10;
--dialog-content-padding: 0 8px;
}
@media all and (max-width: 451px) {
ha-dialog {
--vertical-align-dialog: flex-start;
--dialog-surface-margin-top: 250px;
--ha-dialog-border-radius: var(--ha-border-radius-4xl)
var(--ha-border-radius-4xl) var(--ha-border-radius-square)
var(--ha-border-radius-square);
--mdc-dialog-min-height: calc(100% - 250px);
--mdc-dialog-max-height: calc(100% - 250px);
}
}
ha-list-item {
--mdc-list-side-padding: 12px;
overflow: visible;

View File

@@ -13,6 +13,7 @@ import { classMap } from "lit/directives/class-map";
import { ifDefined } from "lit/directives/if-defined";
import { styleMap } from "lit/directives/style-map";
import memoizeOne from "memoize-one";
import { STRINGS_SEPARATOR_DOT } from "../../common/const";
import { restoreScroll } from "../../common/decorators/restore-scroll";
import { fireEvent } from "../../common/dom/fire_event";
import { stringCompare } from "../../common/string/compare";
@@ -85,6 +86,7 @@ export interface DataTableColumnData<T = any> extends DataTableSortColumnData {
flex?: number;
forceLTR?: boolean;
hidden?: boolean;
lastFixed?: boolean;
}
export type ClonedDataTableColumnData = Omit<DataTableColumnData, "title"> & {
@@ -134,9 +136,6 @@ export class HaDataTable extends LitElement {
@property({ attribute: false }) public searchLabel?: string;
@property({ type: Boolean, attribute: "no-label-float" })
public noLabelFloat? = false;
@property({ type: String }) public filter = "";
@property({ attribute: false }) public groupColumn?: string;
@@ -358,6 +357,11 @@ export class HaDataTable extends LitElement {
.sort((a, b) => {
const orderA = columnOrder!.indexOf(a);
const orderB = columnOrder!.indexOf(b);
const fixedA = Boolean(columns[a].lastFixed);
const fixedB = Boolean(columns[b].lastFixed);
if (fixedA !== fixedB) {
return fixedA ? 1 : -1;
}
if (orderA !== orderB) {
if (orderA === -1) {
return 1;
@@ -393,7 +397,6 @@ export class HaDataTable extends LitElement {
.hass=${this.hass}
@value-changed=${this._handleSearchChange}
.label=${this.searchLabel}
.noLabelFloat=${this.noLabelFloat}
></search-input>
</div>
`
@@ -427,9 +430,9 @@ export class HaDataTable extends LitElement {
<ha-checkbox
class="mdc-data-table__row-checkbox"
@change=${this._handleHeaderRowCheckboxClick}
.indeterminate=${this._checkedRows.length &&
.indeterminate=${!!this._checkedRows.length &&
this._checkedRows.length !== this._checkableRowsCount}
.checked=${this._checkedRows.length &&
.checked=${!!this._checkedRows.length &&
this._checkedRows.length === this._checkableRowsCount}
>
</ha-checkbox>
@@ -636,7 +639,7 @@ export class HaDataTable extends LitElement {
.map(
([key2, column2], i) =>
html`${i !== 0
? " · "
? STRINGS_SEPARATOR_DOT
: nothing}${column2.template
? column2.template(row)
: row[key2]}`
@@ -1086,9 +1089,12 @@ export class HaDataTable extends LitElement {
}
.mdc-data-table__row.empty-row {
height: var(
--data-table-empty-row-height,
var(--data-table-row-height, 52px)
height: max(
var(
--data-table-empty-row-height,
var(--data-table-row-height, 52px)
),
var(--safe-area-inset-bottom, 0px)
);
}
@@ -1192,6 +1198,7 @@ export class HaDataTable extends LitElement {
.mdc-data-table__cell--numeric {
text-align: var(--float-end);
direction: ltr;
}
.mdc-data-table__cell--icon {

View File

@@ -1,5 +1,5 @@
import { consume } from "@lit/context";
import { css, html, LitElement, nothing } from "lit";
import { html, LitElement, nothing } from "lit";
import { property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../common/dom/fire_event";
@@ -13,8 +13,6 @@ import {
import type { EntityRegistryEntry } from "../../data/entity/entity_registry";
import type { HomeAssistant, ValueChangedEvent } from "../../types";
import "../ha-generic-picker";
import "../ha-md-select";
import "../ha-md-select-option";
import type { PickerValueRenderer } from "../ha-picker-field";
const NO_AUTOMATION_KEY = "NO_AUTOMATION";
@@ -217,10 +215,4 @@ export abstract class HaDeviceAutomationPicker<
delete value.metadata;
fireEvent(this, "value-changed", { value });
}
static styles = css`
ha-select {
display: block;
}
`;
}

View File

@@ -6,6 +6,7 @@ import { fireEvent } from "../../common/dom/fire_event";
import type { HomeAssistant, ValueChangedEvent } from "../../types";
import "../ha-generic-picker";
import type { PickerComboBoxItem } from "../ha-picker-combo-box";
import type { PickerValueRenderer } from "../ha-picker-field";
@customElement("ha-entity-attribute-picker")
class HaEntityAttributePicker extends LitElement {
@@ -94,12 +95,19 @@ class HaEntityAttributePicker extends LitElement {
.helper=${this.helper}
.allowCustomValue=${this.allowCustomValue}
.getItems=${this._getItems}
.valueRenderer=${this._valueRenderer}
@value-changed=${this._valueChanged}
>
</ha-generic-picker>
`;
}
private _valueRenderer: PickerValueRenderer = (value: string) => {
const items = this._getItems();
const item = items.find((option) => option.id === value);
return html`<span slot="headline">${item?.primary ?? value}</span>`;
};
private _valueChanged(ev: ValueChangedEvent<string>) {
ev.stopPropagation();
const newValue = ev.detail.value;

View File

@@ -164,7 +164,7 @@ export class HaEntityToggle extends LitElement {
min-width: 38px;
}
ha-icon-button {
--mdc-icon-button-size: 40px;
--ha-icon-button-size: 40px;
color: var(--ha-icon-button-inactive-color, var(--primary-text-color));
transition: color 0.5s;
}

View File

@@ -9,16 +9,7 @@ import secondsToDuration from "../../common/datetime/seconds_to_duration";
import { computeStateDomain } from "../../common/entity/compute_state_domain";
import { computeStateName } from "../../common/entity/compute_state_name";
import { FIXED_DOMAIN_STATES } from "../../common/entity/get_states";
import {
formatNumber,
getNumberFormatOptions,
isNumericState,
} from "../../common/number/format_number";
import {
isUnavailableState,
UNAVAILABLE,
UNKNOWN,
} from "../../data/entity/entity";
import { isUnavailableState, UNAVAILABLE } from "../../data/entity/entity";
import type { EntityRegistryDisplayEntry } from "../../data/entity/entity_registry";
import { timerTimeRemaining } from "../../data/timer";
import type { HomeAssistant } from "../../types";
@@ -180,16 +171,11 @@ export class HaStateLabelBadge extends LitElement {
}
// eslint-disable-next-line: disable=no-fallthrough
default:
return entityState.state === UNKNOWN ||
entityState.state === UNAVAILABLE
return isUnavailableState(entityState.state)
? "—"
: isNumericState(entityState)
? formatNumber(
entityState.state,
this.hass!.locale,
getNumberFormatOptions(entityState, entry)
)
: this.hass!.formatEntityState(entityState);
: this.hass!.formatEntityStateToParts(entityState).find(
(part) => part.type === "value"
)?.value;
}
}
@@ -238,7 +224,11 @@ export class HaStateLabelBadge extends LitElement {
if (domain === "timer") {
return secondsToDuration(_timerTimeRemaining);
}
return entityState.attributes.unit_of_measurement || null;
return (
this.hass!.formatEntityStateToParts(entityState).find(
(part) => part.type === "unit"
)?.value || null
);
}
private _clearInterval() {

View File

@@ -1,5 +1,5 @@
import type { RenderItemFunction } from "@lit-labs/virtualizer/virtualize";
import { mdiChartLine, mdiHelpCircle, mdiShape } from "@mdi/js";
import { mdiChartLine, mdiHelpCircleOutline, mdiShape } from "@mdi/js";
import type { HassEntity } from "home-assistant-js-websocket";
import { html, LitElement, nothing, type PropertyValues } from "lit";
import { customElement, property, query } from "lit/decorators";
@@ -163,7 +163,7 @@ export class HaStatisticPicker extends LitElement {
primary: this.hass.localize(
"ui.components.statistic-picker.missing_entity"
),
icon_path: mdiHelpCircle,
icon_path: mdiHelpCircleOutline,
},
];
}

View File

@@ -15,6 +15,7 @@ import { iconColorCSS } from "../../common/style/icon_color_css";
import { cameraUrlWithWidthHeight } from "../../data/camera";
import { CLIMATE_HVAC_ACTION_TO_MODE } from "../../data/climate";
import type { HomeAssistant } from "../../types";
import { addBrandsAuth } from "../../util/brands-url";
import "../ha-state-icon";
@customElement("state-badge")
@@ -137,6 +138,7 @@ export class StateBadge extends LitElement {
let imageUrl =
stateObj.attributes.entity_picture_local ||
stateObj.attributes.entity_picture;
imageUrl = addBrandsAuth(imageUrl);
if (this.hass) {
imageUrl = this.hass.hassUrl(imageUrl);
}

View File

@@ -6,8 +6,8 @@ import type { HomeAssistant } from "../types";
import "./ha-bottom-sheet";
import "./ha-dialog-header";
import "./ha-icon-button";
import "./ha-wa-dialog";
import type { DialogWidth } from "./ha-wa-dialog";
import "./ha-dialog";
import type { DialogWidth } from "./ha-dialog";
type DialogSheetMode = "dialog" | "bottom-sheet";
@@ -18,10 +18,11 @@ type DialogSheetMode = "dialog" | "bottom-sheet";
* @extends {LitElement}
*
* @summary
* A responsive dialog component that automatically switches between a full dialog (ha-wa-dialog)
* A responsive dialog component that automatically switches between a full dialog (ha-dialog)
* and a bottom sheet (ha-bottom-sheet) based on screen size. Uses dialog mode on larger screens
* (>870px width and >500px height) and bottom sheet mode on smaller screens or mobile devices.
*
* @slot header - Replace the entire header area.
* @slot headerNavigationIcon - Leading header action (e.g. close/back button).
* @slot headerTitle - Custom title content (used when header-title is not set).
* @slot headerSubtitle - Custom subtitle content (used when header-subtitle is not set).
@@ -35,15 +36,19 @@ type DialogSheetMode = "dialog" | "bottom-sheet";
* @cssprop --ha-dialog-hide-duration - Hide animation duration (dialog mode only).
*
* @attr {boolean} open - Controls the dialog/sheet open state.
* @attr {("alert"|"standard")} type - Dialog type (dialog mode only). Defaults to "standard".
* @attr {("small"|"medium"|"large"|"full")} width - Preferred dialog width preset (dialog mode only). Defaults to "medium".
* @attr {boolean} prevent-scrim-close - Prevents closing by clicking the scrim/overlay.
* @attr {string} header-title - Header title text. If not set, the headerTitle slot is used.
* @attr {string} header-subtitle - Header subtitle text. If not set, the headerSubtitle slot is used.
* @attr {("above"|"below")} header-subtitle-position - Position of the subtitle relative to the title. Defaults to "below".
* @attr {boolean} block-mode-change - When set, the mode is determined at mount time based on the current screen size, but subsequent mode changes are blocked. Useful for preventing forms from resetting when the viewport size changes.
* @attr {boolean} flexcontent - Makes the content body a flex container.
* @attr {boolean} without-header - Hides the default header.
* @attr {boolean} allow-mode-change - When set, the component can switch between dialog and bottom-sheet modes as the viewport changes.
*
* @event opened - Fired when the dialog/sheet is shown (dialog mode only).
* @event opened - Fired when the dialog/sheet is shown.
* @event closed - Fired after the dialog/sheet is hidden.
* @event after-show - Fired after show animation completes (dialog mode only).
* @event after-show - Fired after show animation completes.
*
* @remarks
* **Responsive Behavior:**
@@ -51,9 +56,9 @@ type DialogSheetMode = "dialog" | "bottom-sheet";
* Dialog mode is used for screens wider than 870px and taller than 500px.
* Bottom sheet mode is used for mobile devices and smaller screens.
*
* When `block-mode-change` is set, the mode is determined once at mount time based on the initial
* screen size. Subsequent viewport size changes will not trigger mode switches, which is useful
* for preventing form resets or other state loss when users resize their browser window.
* By default, the mode is determined once at mount time and is then kept stable to avoid state
* loss (like form resets) during viewport changes. Set `allow-mode-change` to opt into live
* mode switching while the dialog is open.
*
* **Focus Management:**
* To automatically focus an element when opened, add the `autofocus` attribute to it.
@@ -73,9 +78,15 @@ export class HaAdaptiveDialog extends LitElement {
@property({ type: Boolean, reflect: true })
public open = false;
@property({ reflect: true })
public type: "alert" | "standard" = "standard";
@property({ type: String, reflect: true, attribute: "width" })
public width: DialogWidth = "medium";
@property({ type: Boolean, reflect: true, attribute: "prevent-scrim-close" })
public preventScrimClose = false;
@property({ attribute: "header-title" })
public headerTitle?: string;
@@ -85,12 +96,15 @@ export class HaAdaptiveDialog extends LitElement {
@property({ type: String, attribute: "header-subtitle-position" })
public headerSubtitlePosition: "above" | "below" = "below";
@property({ type: Boolean, attribute: "block-mode-change" })
public blockModeChange = false;
@property({ type: Boolean, attribute: "allow-mode-change" })
public allowModeChange = false;
@property({ type: Boolean, attribute: "without-header" })
public withoutHeader = false;
@property({ type: Boolean, reflect: true, attribute: "flexcontent" })
public flexContent = false;
@state() private _mode: DialogSheetMode = "dialog";
private _unsubMediaQuery?: () => void;
@@ -102,7 +116,7 @@ export class HaAdaptiveDialog extends LitElement {
this._unsubMediaQuery = listenMediaQuery(
"(max-width: 870px), (max-height: 500px)",
(matches) => {
if (!this._modeSet || !this.blockModeChange) {
if (!this._modeSet || this.allowModeChange) {
this._mode = matches ? "bottom-sheet" : "dialog";
this._modeSet = true;
}
@@ -120,33 +134,50 @@ export class HaAdaptiveDialog extends LitElement {
render() {
if (this._mode === "bottom-sheet") {
return html`
<ha-bottom-sheet .open=${this.open} flexcontent>
<ha-bottom-sheet
.ariaLabelledBy=${this.ariaLabelledBy ||
(this.headerTitle !== undefined ? "ha-dialog-title" : undefined)}
.ariaDescribedBy=${this.ariaDescribedBy}
.flexContent=${this.flexContent}
.hass=${this.hass}
.open=${this.open}
.preventScrimClose=${this.preventScrimClose}
>
${!this.withoutHeader
? html`<ha-dialog-header
slot="header"
.subtitlePosition=${this.headerSubtitlePosition}
>
<slot name="headerNavigationIcon" slot="navigationIcon">
<ha-icon-button
data-drawer="close"
.label=${this.hass?.localize("ui.common.close") ?? "Close"}
.path=${mdiClose}
></ha-icon-button>
? html`
<slot name="header" slot="header">
<ha-dialog-header
.subtitlePosition=${this.headerSubtitlePosition}
>
<slot name="headerNavigationIcon" slot="navigationIcon">
<ha-icon-button
data-dialog="close"
.label=${this.hass?.localize("ui.common.close") ??
"Close"}
.path=${mdiClose}
></ha-icon-button>
</slot>
${this.headerTitle !== undefined
? html`<span
slot="title"
class="title"
id="ha-dialog-title"
>
${this.headerTitle}
</span>`
: html`<slot name="headerTitle" slot="title"></slot>`}
${this.headerSubtitle !== undefined
? html`<span slot="subtitle"
>${this.headerSubtitle}</span
>`
: html`<slot
name="headerSubtitle"
slot="subtitle"
></slot>`}
<slot name="headerActionItems" slot="actionItems"></slot>
</ha-dialog-header>
</slot>
${this.headerTitle !== undefined
? html`<span
slot="title"
class="title"
id="ha-wa-dialog-title"
>
${this.headerTitle}
</span>`
: html`<slot name="headerTitle" slot="title"></slot>`}
${this.headerSubtitle !== undefined
? html`<span slot="subtitle">${this.headerSubtitle}</span>`
: html`<slot name="headerSubtitle" slot="subtitle"></slot>`}
<slot name="headerActionItems" slot="actionItems"></slot>
</ha-dialog-header>`
`
: nothing}
<slot></slot>
<slot name="footer" slot="footer"></slot>
@@ -155,16 +186,18 @@ export class HaAdaptiveDialog extends LitElement {
}
return html`
<ha-wa-dialog
<ha-dialog
.hass=${this.hass}
.open=${this.open}
.type=${this.type}
.width=${this.width}
.preventScrimClose=${this.preventScrimClose}
.ariaLabelledBy=${this.ariaLabelledBy}
.ariaDescribedBy=${this.ariaDescribedBy}
.headerTitle=${this.headerTitle}
.headerSubtitle=${this.headerSubtitle}
.headerSubtitlePosition=${this.headerSubtitlePosition}
flexcontent
.flexContent=${this.flexContent}
.withoutHeader=${this.withoutHeader}
>
<slot name="headerNavigationIcon" slot="headerNavigationIcon">
@@ -179,7 +212,7 @@ export class HaAdaptiveDialog extends LitElement {
<slot name="headerActionItems" slot="headerActionItems"></slot>
<slot></slot>
<slot name="footer" slot="footer"></slot>
</ha-wa-dialog>
</ha-dialog>
`;
}
@@ -187,10 +220,17 @@ export class HaAdaptiveDialog extends LitElement {
return [
css`
ha-bottom-sheet {
--ha-bottom-sheet-border-radius: var(--ha-border-radius-2xl);
--ha-bottom-sheet-surface-background: var(
--ha-dialog-surface-background,
var(--card-background-color, var(--ha-color-surface-default))
);
--ha-bottom-sheet-padding: 0 var(--safe-area-inset-right)
var(--safe-area-inset-bottom) var(--safe-area-inset-left);
--ha-bottom-sheet-content-padding: var(
--dialog-content-padding,
0 var(--ha-space-6) var(--ha-space-6) var(--ha-space-6)
);
}
`,
];

View File

@@ -135,7 +135,7 @@ class HaAlert extends LitElement {
}
.action ha-icon-button {
--mdc-theme-primary: var(--primary-text-color);
--mdc-icon-button-size: 36px;
--ha-icon-button-size: 36px;
}
.issue-type.info > .icon {
color: var(--info-color);

View File

@@ -5,7 +5,7 @@ import { fireEvent } from "../common/dom/fire_event";
import type { LocalizeFunc } from "../common/translations/localize";
import type { Analytics, AnalyticsPreferences } from "../data/analytics";
import { haStyle } from "../resources/styles";
import "./ha-settings-row";
import "./ha-md-list-item";
import "./ha-switch";
import "./ha-tooltip";
import type { HaSwitch } from "./ha-switch";
@@ -33,105 +33,80 @@ export class HaAnalytics extends LitElement {
const baseEnabled = !loading && this.analytics!.preferences.base;
return html`
<ha-settings-row>
<span slot="heading" data-for="base">
${this.localize(
<ha-md-list-item>
<span slot="headline"
>${this.localize(
`ui.panel.${this.translationKeyPanel}.analytics.preferences.base.title`
)}
</span>
<span slot="description" data-for="base">
${this.localize(
)}</span
>
<span slot="supporting-text"
>${this.localize(
`ui.panel.${this.translationKeyPanel}.analytics.preferences.base.description`
)}
</span>
)}</span
>
<ha-switch
slot="end"
@change=${this._handleRowClick}
.checked=${!!baseEnabled}
.preference=${"base"}
.disabled=${loading}
name="base"
>
</ha-switch>
</ha-settings-row>
></ha-switch>
</ha-md-list-item>
${ADDITIONAL_PREFERENCES.map(
(preference) => html`
<ha-settings-row>
<span slot="heading" data-for=${preference}>
${this.localize(
<ha-md-list-item>
<span slot="headline"
>${this.localize(
`ui.panel.${this.translationKeyPanel}.analytics.preferences.${preference}.title`
)}
</span>
<span slot="description" data-for=${preference}>
${this.localize(
)}</span
>
<span slot="supporting-text"
>${this.localize(
`ui.panel.${this.translationKeyPanel}.analytics.preferences.${preference}.description`
)}
</span>
<span>
<ha-switch
.id="switch-${preference}"
@change=${this._handleRowClick}
.checked=${!!this.analytics?.preferences[preference]}
.preference=${preference}
name=${preference}
>
</ha-switch>
${baseEnabled
? nothing
: html`<ha-tooltip
.for="switch-${preference}"
placement="right"
>
${this.localize(
`ui.panel.${this.translationKeyPanel}.analytics.need_base_enabled`
)}
</ha-tooltip>`}
</span>
</ha-settings-row>
)}</span
>
<ha-switch
slot="end"
.id="switch-${preference}"
@change=${this._handleRowClick}
.checked=${!!this.analytics?.preferences[preference]}
.preference=${preference}
name=${preference}
></ha-switch>
${baseEnabled
? nothing
: html`<ha-tooltip .for="switch-${preference}" placement="right">
${this.localize(
`ui.panel.${this.translationKeyPanel}.analytics.need_base_enabled`
)}
</ha-tooltip>`}
</ha-md-list-item>
`
)}
<ha-settings-row>
<span slot="heading" data-for="diagnostics">
${this.localize(
<ha-md-list-item>
<span slot="headline"
>${this.localize(
`ui.panel.${this.translationKeyPanel}.analytics.preferences.diagnostics.title`
)}
</span>
<span slot="description" data-for="diagnostics">
${this.localize(
)}</span
>
<span slot="supporting-text"
>${this.localize(
`ui.panel.${this.translationKeyPanel}.analytics.preferences.diagnostics.description`
)}
</span>
)}</span
>
<ha-switch
slot="end"
@change=${this._handleRowClick}
.checked=${!!this.analytics?.preferences.diagnostics}
.preference=${"diagnostics"}
.disabled=${loading}
name="diagnostics"
>
</ha-switch>
</ha-settings-row>
></ha-switch>
</ha-md-list-item>
`;
}
protected updated(changedProps) {
super.updated(changedProps);
this.shadowRoot!.querySelectorAll("*[data-for]").forEach((el) => {
const forEl = (el as HTMLElement).dataset.for;
delete (el as HTMLElement).dataset.for;
el.addEventListener("click", () => {
const toFocus = this.shadowRoot!.querySelector(
`*[name=${forEl}]`
) as HTMLElement | null;
if (toFocus) {
toFocus.focus();
toFocus.click();
}
});
});
}
private _handleRowClick(ev: Event) {
const target = ev.currentTarget as HaSwitch;
const preference = (target as any).preference;
@@ -164,13 +139,10 @@ export class HaAnalytics extends LitElement {
color: var(--error-color);
}
ha-settings-row {
padding: 0;
}
span[slot="heading"],
span[slot="description"] {
cursor: pointer;
ha-md-list-item {
--md-list-item-leading-space: 0;
--md-list-item-trailing-space: 0;
--md-item-overflow: visible;
}
`,
];

View File

@@ -163,7 +163,7 @@ export class HaAreaPicker extends LitElement {
{
id: ADD_NEW_ID + searchString,
primary: this.hass.localize(
"ui.components.area-picker.add_new_sugestion",
"ui.components.area-picker.add_new_suggestion",
{
name: searchString,
}

View File

@@ -1,8 +1,9 @@
import { mdiAlertCircle, mdiMicrophone, mdiSend } from "@mdi/js";
import type { PropertyValues, TemplateResult } from "lit";
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { haStyleScrollbar } from "../resources/styles";
import { supportsFeature } from "../common/entity/supports-feature";
import {
runAssistPipeline,
@@ -114,7 +115,7 @@ export class HaAssistChat extends LitElement {
const supportsSTT = this.pipeline?.stt_engine && !this.disableSpeech;
return html`
<div class="messages">
<div class="messages ha-scrollbar">
${controlHA
? nothing
: html`
@@ -585,154 +586,167 @@ export class HaAssistChat extends LitElement {
return progress;
}
static styles = css`
:host {
flex: 1;
display: flex;
flex-direction: column;
}
ha-alert {
margin-bottom: 8px;
}
ha-textfield {
display: block;
}
.messages {
flex: 1;
display: block;
box-sizing: border-box;
overflow-y: auto;
max-height: 100%;
display: flex;
flex-direction: column;
padding: 0 12px 16px;
}
.spacer {
flex: 1;
}
.message {
font-size: var(--ha-font-size-l);
clear: both;
max-width: -webkit-fill-available;
overflow-wrap: break-word;
scroll-margin-top: 24px;
margin: 8px 0;
padding: 8px;
border-radius: var(--ha-border-radius-xl);
}
@media all and (max-width: 450px), all and (max-height: 500px) {
.message {
font-size: var(--ha-font-size-l);
}
}
.message.user {
margin-left: 24px;
margin-inline-start: 24px;
margin-inline-end: initial;
align-self: flex-end;
border-bottom-right-radius: 0px;
--markdown-link-color: var(--text-primary-color);
background-color: var(--chat-background-color-user, var(--primary-color));
color: var(--text-primary-color);
direction: var(--direction);
}
.message.hass {
margin-right: 24px;
margin-inline-end: 24px;
margin-inline-start: initial;
align-self: flex-start;
border-bottom-left-radius: 0px;
background-color: var(
--chat-background-color-hass,
var(--secondary-background-color)
);
static get styles(): CSSResultGroup {
return [
haStyleScrollbar,
css`
:host {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
}
ha-alert {
margin-bottom: var(--ha-space-2);
}
ha-textfield {
display: block;
}
.messages {
flex: 1 1 400px;
display: block;
box-sizing: border-box;
overflow-y: auto;
min-height: 0;
max-height: 100%;
display: flex;
flex-direction: column;
padding: 0 var(--ha-space-3) var(--ha-space-4);
}
.input {
padding: var(--ha-space-1) var(--ha-space-4) var(--ha-space-6);
}
.spacer {
flex: 1;
}
.message {
font-size: var(--ha-font-size-l);
clear: both;
max-width: -webkit-fill-available;
overflow-wrap: break-word;
scroll-margin-top: var(--ha-space-6);
margin: var(--ha-space-2) 0;
padding: var(--ha-space-2);
border-radius: var(--ha-border-radius-xl);
}
@media all and (max-width: 450px), all and (max-height: 500px) {
.message {
font-size: var(--ha-font-size-l);
}
}
.message.user {
margin-left: var(--ha-space-6);
margin-inline-start: var(--ha-space-6);
margin-inline-end: initial;
align-self: flex-end;
border-bottom-right-radius: 0px;
--markdown-link-color: var(--text-primary-color);
background-color: var(
--chat-background-color-user,
var(--primary-color)
);
color: var(--text-primary-color);
direction: var(--direction);
}
.message.hass {
margin-right: var(--ha-space-6);
margin-inline-end: var(--ha-space-6);
margin-inline-start: initial;
align-self: flex-start;
border-bottom-left-radius: 0px;
background-color: var(
--chat-background-color-hass,
var(--secondary-background-color)
);
color: var(--primary-text-color);
direction: var(--direction);
}
.message.error {
background-color: var(--error-color);
color: var(--text-primary-color);
}
ha-markdown {
--markdown-image-border-radius: calc(var(--ha-border-radius-xl) / 2);
--markdown-table-border-color: var(--divider-color);
--markdown-code-background-color: var(--primary-background-color);
--markdown-code-text-color: var(--primary-text-color);
--markdown-list-indent: 1.15em;
&:not(:has(ha-markdown-element)) {
min-height: 1lh;
min-width: 1lh;
flex-shrink: 0;
}
}
.bouncer {
width: 48px;
height: 48px;
position: absolute;
}
.double-bounce1,
.double-bounce2 {
width: 48px;
height: 48px;
border-radius: var(--ha-border-radius-circle);
background-color: var(--primary-color);
opacity: 0.2;
position: absolute;
top: 0;
left: 0;
-webkit-animation: sk-bounce 2s infinite ease-in-out;
animation: sk-bounce 2s infinite ease-in-out;
}
.double-bounce2 {
-webkit-animation-delay: -1s;
animation-delay: -1s;
}
@-webkit-keyframes sk-bounce {
0%,
100% {
-webkit-transform: scale(0);
}
50% {
-webkit-transform: scale(1);
}
}
@keyframes sk-bounce {
0%,
100% {
transform: scale(0);
-webkit-transform: scale(0);
}
50% {
transform: scale(1);
-webkit-transform: scale(1);
}
}
color: var(--primary-text-color);
direction: var(--direction);
}
.message.error {
background-color: var(--error-color);
color: var(--text-primary-color);
}
ha-markdown {
--markdown-image-border-radius: calc(var(--ha-border-radius-xl) / 2);
--markdown-table-border-color: var(--divider-color);
--markdown-code-background-color: var(--primary-background-color);
--markdown-code-text-color: var(--primary-text-color);
--markdown-list-indent: 1.15em;
}
ha-markdown:not(:has(ha-markdown-element)) {
min-height: 1lh;
min-width: 1lh;
flex-shrink: 0;
}
.bouncer {
width: 48px;
height: 48px;
position: absolute;
}
.double-bounce1,
.double-bounce2 {
width: 48px;
height: 48px;
border-radius: var(--ha-border-radius-circle);
background-color: var(--primary-color);
opacity: 0.2;
position: absolute;
top: 0;
left: 0;
-webkit-animation: sk-bounce 2s infinite ease-in-out;
animation: sk-bounce 2s infinite ease-in-out;
}
.double-bounce2 {
-webkit-animation-delay: -1s;
animation-delay: -1s;
}
@-webkit-keyframes sk-bounce {
0%,
100% {
-webkit-transform: scale(0);
}
50% {
-webkit-transform: scale(1);
}
}
@keyframes sk-bounce {
0%,
100% {
transform: scale(0);
-webkit-transform: scale(0);
}
50% {
transform: scale(1);
-webkit-transform: scale(1);
}
}
.listening-icon {
position: relative;
color: var(--secondary-text-color);
margin-right: -24px;
margin-inline-end: -24px;
margin-inline-start: initial;
direction: var(--direction);
transform: scaleX(var(--scale-direction));
}
.listening-icon {
position: relative;
color: var(--secondary-text-color);
margin-right: -24px;
margin-inline-end: -24px;
margin-inline-start: initial;
direction: var(--direction);
transform: scaleX(var(--scale-direction));
}
.listening-icon[active] {
color: var(--primary-color);
}
.listening-icon[active] {
color: var(--primary-color);
}
.unsupported {
color: var(--error-color);
position: absolute;
--mdc-icon-size: 16px;
right: 5px;
inset-inline-end: 5px;
inset-inline-start: initial;
top: 0px;
}
`;
.unsupported {
color: var(--error-color);
position: absolute;
--mdc-icon-size: 16px;
right: 5px;
inset-inline-end: 5px;
inset-inline-start: initial;
top: 0px;
}
`,
];
}
}
declare global {

View File

@@ -4,10 +4,10 @@ import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import { fireEvent } from "../common/dom/fire_event";
import type { HaSelectSelectEvent } from "./ha-select";
import "./ha-icon-button";
import "./ha-input-helper-text";
import "./ha-select";
import type { HaSelectSelectEvent } from "./ha-select";
import "./ha-textfield";
import type { HaTextField } from "./ha-textfield";
@@ -368,7 +368,7 @@ export class HaBaseTimeInput extends LitElement {
}
ha-icon-button {
position: relative;
--mdc-icon-button-size: 36px;
--ha-icon-button-size: 36px;
--mdc-icon-size: 20px;
color: var(--secondary-text-color);
direction: var(--direction);

View File

@@ -1,21 +1,52 @@
import "@home-assistant/webawesome/dist/components/drawer/drawer";
import type WaDrawer from "@home-assistant/webawesome/dist/components/drawer/drawer";
import { css, html, LitElement, type PropertyValues } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import type { HASSDomEvent } from "../common/dom/fire_event";
import { fireEvent } from "../common/dom/fire_event";
import { SwipeGestureRecognizer } from "../common/util/swipe-gesture-recognizer";
import { ScrollableFadeMixin } from "../mixins/scrollable-fade-mixin";
import { haStyleScrollbar } from "../resources/styles";
import type { HomeAssistant } from "../types";
import { isIosApp } from "../util/is_ios";
export const BOTTOM_SHEET_ANIMATION_DURATION_MS = 300;
const SWIPE_LOCKED_COMPONENTS = new Set([
"ha-control-slider",
"ha-slider",
"ha-control-switch",
"ha-control-circular-slider",
"ha-hs-color-picker",
"ha-map",
"ha-more-info-control-select-container",
"ha-filter-chip",
]);
const SWIPE_LOCKED_CLASSES = new Set(["volume-slider-container", "forecast"]);
@customElement("ha-bottom-sheet")
export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: "aria-labelledby" })
public ariaLabelledBy?: string;
@property({ attribute: "aria-describedby" })
public ariaDescribedBy?: string;
@property({ type: Boolean }) public open = false;
@property({ type: Boolean, reflect: true, attribute: "flexcontent" })
public flexContent = false;
@property({ type: Boolean, reflect: true, attribute: "prevent-scrim-close" })
public preventScrimClose = false;
@state() private _drawerOpen = false;
@state() private _sliderInteractionActive = false;
@query("#drawer") private _drawer!: HTMLElement;
@query("#body") private _bodyElement!: HTMLDivElement;
@@ -28,14 +59,124 @@ export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
private _isDragging = false;
private _handleAfterHide(afterHideEvent: Event) {
afterHideEvent.stopPropagation();
this.open = false;
const ev = new Event("closed", {
bubbles: true,
composed: true,
private _escapePressed = false;
private _handleShow = async () => {
this._drawerOpen = true;
this.open = true;
fireEvent(this, "opened");
await this.updateComplete;
requestAnimationFrame(() => {
if (this.hass && isIosApp(this.hass)) {
const element = this.renderRoot.querySelector("[autofocus]");
if (element !== null) {
if (!element.id) {
element.id = "ha-bottom-sheet-autofocus";
}
this.hass.auth.external?.fireMessage({
type: "focus_element",
payload: {
element_id: element.id,
},
});
}
return;
}
(
this.renderRoot.querySelector("[autofocus]") as HTMLElement | null
)?.focus();
});
this.dispatchEvent(ev);
};
private _handleAfterShow = () => {
fireEvent(this, "after-show");
};
private _handleSliderInteractionStart = () => {
this._sliderInteractionActive = true;
};
private _handleSliderInteractionStop = () => {
this._sliderInteractionActive = false;
};
private _handleAfterHide = (ev: CustomEvent<{ source: Element }>) => {
if (this._sliderInteractionActive) {
this._drawerOpen = true;
this.open = true;
return;
}
if (ev.eventPhase === Event.AT_TARGET) {
this.open = false;
this._drawerOpen = false;
fireEvent(this, "closed");
}
};
private _handleHide = (ev: CustomEvent<{ source: Element }>) => {
// Ignore bubbled wa-hide events from nested drawers (e.g., picker bottom sheet)
if (ev.eventPhase !== Event.AT_TARGET) {
return;
}
const sourceIsDrawer = ev.detail.source === (ev.target as WaDrawer).drawer;
if (this._sliderInteractionActive) {
ev.preventDefault();
this._drawerOpen = true;
this.open = true;
this._escapePressed = false;
return;
}
if (this.preventScrimClose && this._escapePressed && sourceIsDrawer) {
ev.preventDefault();
}
this._escapePressed = false;
};
private _handleKeyDown = (ev: KeyboardEvent) => {
if (ev.key === "Escape") {
this._escapePressed = true;
ev.stopPropagation();
(ev.currentTarget as WaDrawer).open = false;
}
};
private _handleCloseAction = (ev: Event) => {
const shouldClose = ev
.composedPath()
.some(
(node) =>
node instanceof HTMLElement &&
(node.getAttribute("data-dialog") === "close" ||
node.getAttribute("data-drawer") === "close")
);
if (shouldClose) {
this._drawerOpen = false;
}
};
connectedCallback() {
super.connectedCallback();
this.addEventListener(
"slider-interaction-start",
this._handleSliderInteractionStart,
{
capture: true,
}
);
this.addEventListener(
"slider-interaction-stop",
this._handleSliderInteractionStop,
{
capture: true,
}
);
}
protected updated(changedProperties: PropertyValues): void {
@@ -51,10 +192,21 @@ export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
id="drawer"
placement="bottom"
.open=${this._drawerOpen}
.lightDismiss=${!this.preventScrimClose}
.ariaLabelledby=${this.ariaLabelledBy}
.ariaDescribedby=${this.ariaDescribedBy}
@keydown=${this._handleKeyDown}
@wa-show=${this._handleShow}
@wa-after-show=${this._handleAfterShow}
@wa-hide=${this._handleHide}
@wa-after-hide=${this._handleAfterHide}
@click=${this._handleCloseAction}
without-header
@touchstart=${this._handleTouchStart}
>
<div class="handle-wrapper" aria-hidden="true">
<div class="handle"></div>
</div>
<slot name="header"></slot>
<div class="content-wrapper">
<div id="body" class="body ha-scrollbar">
@@ -68,17 +220,37 @@ export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
}
private _handleTouchStart = (ev: TouchEvent) => {
// Check if any element inside drawer in the composed path has scrollTop > 0
for (const path of ev.composedPath()) {
const el = path as HTMLElement;
if (el === this._drawer) {
if (this.preventScrimClose) {
return;
}
const path = ev.composedPath();
for (const target of path) {
if (target === this._drawer) {
break;
}
if (el.scrollTop > 0) {
if (!(target instanceof HTMLElement)) {
continue;
}
if (
// Check if any element inside drawer in the composed path has scrollTop > 0 (list)
target.scrollTop > 0 ||
// Check if the element is a swipe locked component or has a swipe locked class
SWIPE_LOCKED_COMPONENTS.has(target.localName) ||
Array.from(target.classList).some((cls) =>
SWIPE_LOCKED_CLASSES.has(cls)
)
) {
return;
}
}
// Stop propagation so parent bottom sheets don't also start tracking
// this gesture (same pattern as _handleKeyDown for Escape)
ev.stopPropagation();
this._startResizing(ev.touches[0].clientY);
};
@@ -174,6 +346,20 @@ export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
disconnectedCallback() {
super.disconnectedCallback();
this.removeEventListener(
"slider-interaction-start",
this._handleSliderInteractionStart,
{
capture: true,
}
);
this.removeEventListener(
"slider-interaction-stop",
this._handleSliderInteractionStop,
{
capture: true,
}
);
this._unregisterResizeHandlers();
this._isDragging = false;
}
@@ -199,6 +385,7 @@ export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
wa-drawer::part(body) {
max-width: var(--ha-bottom-sheet-max-width);
width: 100%;
position: relative;
border-top-left-radius: var(
--ha-bottom-sheet-border-radius,
var(--ha-dialog-border-radius, var(--ha-border-radius-2xl))
@@ -221,6 +408,35 @@ export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
display: flex;
flex-direction: column;
}
:host([prevent-scrim-close]) .handle-wrapper {
display: none;
}
.handle-wrapper {
position: absolute;
top: 0;
inset-inline-start: 0;
width: 100%;
padding-bottom: 2px;
display: flex;
justify-content: center;
align-items: center;
pointer-events: none;
z-index: 1;
}
.handle-wrapper .handle {
height: 16px;
width: 200px;
display: flex;
justify-content: center;
align-items: center;
}
.handle-wrapper .handle::after {
content: "";
border-radius: var(--ha-border-radius-md);
height: 4px;
background: var(--ha-bottom-sheet-handle-color, var(--divider-color));
width: 40px;
}
.content-wrapper {
position: relative;
flex: 1;
@@ -228,16 +444,24 @@ export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
flex-direction: column;
min-height: 0;
}
.body {
padding: var(--ha-bottom-sheet-content-padding, 0);
box-sizing: border-box;
}
:host([flexcontent]) .body {
flex: 1;
max-width: 100%;
display: flex;
flex-direction: column;
padding: var(
--ha-bottom-sheet-padding,
0 var(--safe-area-inset-right) var(--safe-area-inset-bottom)
var(--safe-area-inset-left)
--ha-bottom-sheet-content-padding,
var(
--ha-bottom-sheet-padding,
0 var(--safe-area-inset-right) var(--safe-area-inset-bottom)
var(--safe-area-inset-left)
)
);
box-sizing: border-box;
}
slot[name="footer"] {
display: block;
@@ -262,6 +486,18 @@ export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
}
declare global {
interface HASSDomEvents {
"slider-interaction-start": undefined;
"slider-interaction-stop": undefined;
}
interface HTMLElementEventMap {
"slider-interaction-start": HASSDomEvent<
HASSDomEvents["slider-interaction-start"]
>;
"slider-interaction-stop": HASSDomEvent<
HASSDomEvents["slider-interaction-stop"]
>;
}
interface HTMLElementTagNameMap {
"ha-bottom-sheet": HaBottomSheet;
}

View File

@@ -42,7 +42,7 @@ export class HaButton extends Button {
Button.styles,
css`
:host {
--wa-form-control-padding-inline: 16px;
--wa-form-control-padding-inline: var(--ha-space-4);
--wa-font-weight-action: var(--ha-font-weight-medium);
--wa-form-control-border-radius: var(
--ha-button-border-radius,
@@ -68,7 +68,7 @@ export class HaButton extends Button {
var(--button-height, 32px)
);
font-size: var(--wa-font-size-s, var(--ha-font-size-m));
--wa-form-control-padding-inline: 12px;
--wa-form-control-padding-inline: var(--ha-space-3);
}
:host([variant="brand"]) {
@@ -84,6 +84,9 @@ export class HaButton extends Button {
--button-color-fill-loud-hover: var(
--ha-color-fill-primary-loud-hover
);
--button-color-fill-quiet-active: var(
--ha-color-fill-primary-quiet-active
);
}
:host([variant="neutral"]) {
@@ -99,6 +102,9 @@ export class HaButton extends Button {
--button-color-fill-loud-hover: var(
--ha-color-fill-neutral-loud-hover
);
--button-color-fill-quiet-active: var(
--ha-color-fill-neutral-normal-active
);
}
:host([variant="success"]) {
@@ -114,6 +120,9 @@ export class HaButton extends Button {
--button-color-fill-loud-hover: var(
--ha-color-fill-success-loud-hover
);
--button-color-fill-quiet-active: var(
--ha-color-fill-success-quiet-active
);
}
:host([variant="warning"]) {
@@ -129,6 +138,9 @@ export class HaButton extends Button {
--button-color-fill-loud-hover: var(
--ha-color-fill-warning-loud-hover
);
--button-color-fill-quiet-active: var(
--ha-color-fill-warning-quiet-active
);
}
:host([variant="danger"]) {
@@ -144,6 +156,9 @@ export class HaButton extends Button {
--button-color-fill-loud-hover: var(
--ha-color-fill-danger-loud-hover
);
--button-color-fill-quiet-active: var(
--ha-color-fill-danger-quiet-active
);
}
:host([appearance~="plain"]) .button {
@@ -187,6 +202,10 @@ export class HaButton extends Button {
background-color: var(--ha-color-fill-disabled-normal-resting);
color: var(--ha-color-on-disabled-normal);
}
:host([appearance~="plain"])
.button:not(.disabled):not(.loading):active {
background-color: var(--button-color-fill-quiet-active);
}
:host([appearance~="accent"]) .button {
background-color: var(
@@ -212,17 +231,17 @@ export class HaButton extends Button {
}
slot[name="start"]::slotted(*) {
margin-inline-end: 4px;
margin-inline-end: var(--ha-space-1);
}
slot[name="end"]::slotted(*) {
margin-inline-start: 4px;
margin-inline-start: var(--ha-space-1);
}
.button.has-start {
padding-inline-start: 8px;
padding-inline-start: var(--ha-space-2);
}
.button.has-end {
padding-inline-end: 8px;
padding-inline-end: var(--ha-space-2);
}
.label {

View File

@@ -2,6 +2,7 @@ import { css, html, LitElement, nothing, type PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators";
import { repeat } from "lit/directives/repeat";
import { styleMap } from "lit/directives/style-map";
import { STATE_RUNNING } from "home-assistant-js-websocket";
import memoizeOne from "memoize-one";
import { computeStateName } from "../common/entity/compute_state_name";
import { supportsFeature } from "../common/entity/supports-feature";
@@ -58,12 +59,22 @@ export class HaCameraStream extends LitElement {
@state() private _webRtcStreams?: { hasAudio: boolean; hasVideo: boolean };
public willUpdate(changedProps: PropertyValues): void {
if (
const entityChanged =
changedProps.has("stateObj") &&
this.stateObj &&
(changedProps.get("stateObj") as CameraEntity | undefined)?.entity_id !==
this.stateObj.entity_id
) {
this.stateObj.entity_id;
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
const backendStarted =
changedProps.has("hass") &&
this.hass &&
this.stateObj &&
oldHass &&
this.hass.config.state === STATE_RUNNING &&
oldHass.config?.state !== STATE_RUNNING;
if (entityChanged || backendStarted) {
this._getCapabilities();
this._getPosterUrl();
}

View File

@@ -20,7 +20,7 @@ import {
mdiUndo,
} from "@mdi/js";
import type { HassEntities } from "home-assistant-js-websocket";
import type { PropertyValues } from "lit";
import type { CSSResultGroup, PropertyValues } from "lit";
import { css, html, ReactiveElement, render } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
@@ -28,6 +28,7 @@ import { fireEvent } from "../common/dom/fire_event";
import { stopPropagation } from "../common/dom/stop_propagation";
import { getEntityContext } from "../common/entity/context/get_entity_context";
import { copyToClipboard } from "../common/util/copy-clipboard";
import { haStyleScrollbar } from "../resources/styles";
import type { HomeAssistant } from "../types";
import { showToast } from "../util/toast";
import "./ha-code-editor-completion-items";
@@ -310,6 +311,11 @@ export class HaCodeEditor extends ReactiveElement {
});
this._canCopy = this._value?.length > 0;
const cmScroller = this.codemirror.dom.querySelector(".cm-scroller");
if (cmScroller) {
cmScroller.classList.add("ha-scrollbar");
}
// Update the toolbar. Creating it if required
this._updateToolbar();
}
@@ -802,100 +808,105 @@ export class HaCodeEditor extends ReactiveElement {
return [];
};
static styles = css`
:host {
position: relative;
display: block;
--code-editor-toolbar-height: 28px;
}
static get styles(): CSSResultGroup {
return [
haStyleScrollbar,
css`
:host {
position: relative;
display: block;
--code-editor-toolbar-height: 28px;
}
:host(.error-state) .cm-gutters {
border-color: var(--error-state-color, var(--error-color)) !important;
}
:host(.error-state) .cm-gutters {
border-color: var(--error-state-color, var(--error-color)) !important;
}
:host(.hasToolbar) .cm-gutters {
padding-top: 0;
}
:host(.hasToolbar) .cm-gutters {
padding-top: 0;
}
:host(.hasToolbar) .cm-focused .cm-gutters {
padding-top: 1px;
}
:host(.hasToolbar) .cm-focused .cm-gutters {
padding-top: 1px;
}
:host(.error-state) .cm-content {
border-color: var(--error-state-color, var(--error-color)) !important;
}
:host(.error-state) .cm-content {
border-color: var(--error-state-color, var(--error-color)) !important;
}
:host(.hasToolbar) .cm-content {
border: none;
border-top: 1px solid var(--secondary-text-color);
}
:host(.hasToolbar) .cm-content {
border: none;
border-top: 1px solid var(--secondary-text-color);
}
:host(.hasToolbar) .cm-focused .cm-content {
border-top: 2px solid var(--primary-color);
padding-top: 15px;
}
:host(.hasToolbar) .cm-focused .cm-content {
border-top: 2px solid var(--primary-color);
padding-top: 15px;
}
:host(.fullscreen) {
position: fixed !important;
top: calc(var(--header-height, 56px) + 8px) !important;
left: 8px !important;
right: 8px !important;
bottom: 8px !important;
z-index: 6;
border-radius: var(--ha-border-radius-lg) !important;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3) !important;
overflow: hidden !important;
background-color: var(
--code-editor-background-color,
var(--card-background-color)
) !important;
margin: 0 !important;
padding-top: var(--safe-area-inset-top) !important;
padding-left: var(--safe-area-inset-left) !important;
padding-right: var(--safe-area-inset-right) !important;
padding-bottom: var(--safe-area-inset-bottom) !important;
box-sizing: border-box !important;
display: block !important;
}
:host(.fullscreen) {
position: fixed !important;
top: calc(var(--header-height, 56px) + 8px) !important;
left: 8px !important;
right: 8px !important;
bottom: 8px !important;
z-index: 6;
border-radius: var(--ha-border-radius-lg) !important;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3) !important;
overflow: hidden !important;
background-color: var(
--code-editor-background-color,
var(--card-background-color)
) !important;
margin: 0 !important;
padding-top: var(--safe-area-inset-top) !important;
padding-left: var(--safe-area-inset-left) !important;
padding-right: var(--safe-area-inset-right) !important;
padding-bottom: var(--safe-area-inset-bottom) !important;
box-sizing: border-box !important;
display: block !important;
}
:host(.hasToolbar) .cm-editor {
padding-top: var(--code-editor-toolbar-height);
}
:host(.hasToolbar) .cm-editor {
padding-top: var(--code-editor-toolbar-height);
}
:host(.fullscreen) .cm-editor {
height: 100% !important;
max-height: 100% !important;
border-radius: var(--ha-border-radius-square) !important;
}
:host(.fullscreen) .cm-editor {
height: 100% !important;
max-height: 100% !important;
border-radius: var(--ha-border-radius-square) !important;
}
:host(:not(.hasToolbar)) .code-editor-toolbar {
display: none !important;
}
:host(:not(.hasToolbar)) .code-editor-toolbar {
display: none !important;
}
.code-editor-toolbar {
--icon-button-toolbar-height: var(--code-editor-toolbar-height);
--icon-button-toolbar-color: var(
--code-editor-gutter-color,
var(--secondary-background-color, whitesmoke)
);
border-top-left-radius: var(--ha-border-radius-sm);
border-top-right-radius: var(--ha-border-radius-sm);
}
.code-editor-toolbar {
--icon-button-toolbar-height: var(--code-editor-toolbar-height);
--icon-button-toolbar-color: var(
--code-editor-gutter-color,
var(--secondary-background-color, whitesmoke)
);
border-top-left-radius: var(--ha-border-radius-sm);
border-top-right-radius: var(--ha-border-radius-sm);
}
.completion-info {
display: grid;
gap: 3px;
padding: 8px;
}
.completion-info {
display: grid;
gap: 3px;
padding: 8px;
}
/* Hide completion info on narrow screens */
@media (max-width: 600px) {
.cm-completionInfo,
.completion-info {
display: none;
}
}
`;
/* Hide completion info on narrow screens */
@media (max-width: 600px) {
.cm-completionInfo,
.completion-info {
display: none;
}
}
`,
];
}
}
declare global {

View File

@@ -6,7 +6,7 @@ import memoizeOne from "memoize-one";
import { computeCssColor, THEME_COLORS } from "../common/color/compute-color";
import { fireEvent } from "../common/dom/fire_event";
import type { LocalizeKeys } from "../common/translations/localize";
import type { HomeAssistant } from "../types";
import type { HomeAssistant, ValueChangedEvent } from "../types";
import "./ha-generic-picker";
import type { PickerComboBoxItem } from "./ha-picker-combo-box";
import type { PickerValueRenderer } from "./ha-picker-field";
@@ -224,7 +224,7 @@ export class HaColorPicker extends LitElement {
`;
}
private _valueChanged(ev: CustomEvent<{ value?: string }>) {
private _valueChanged(ev: ValueChangedEvent<string | undefined>) {
ev.stopPropagation();
const selected = ev.detail.value;
const normalized =

View File

@@ -89,7 +89,7 @@ export class HaControlSelectMenu extends LitElement {
private _renderOption = (option: SelectOption) =>
html`<ha-dropdown-item
.value=${option.value}
class=${this.value === option.value ? "selected" : ""}
.selected=${this.value === option.value}
>${option.iconPath
? html`<ha-svg-icon slot="icon" .path=${option.iconPath}></ha-svg-icon>`
: option.icon
@@ -263,15 +263,6 @@ export class HaControlSelectMenu extends LitElement {
cursor: not-allowed;
color: var(--disabled-color);
}
ha-dropdown-item.selected {
font-weight: var(--ha-font-weight-medium);
color: var(--primary-color);
background-color: var(--ha-color-fill-primary-quiet-resting);
--icon-primary-color: var(--primary-color);
}
ha-dropdown-item.selected:hover {
background-color: var(--ha-color-fill-primary-quiet-hover);
}
ha-dropdown::part(menu) {
min-width: var(--control-select-menu-width);

View File

@@ -95,7 +95,7 @@ export class HaCopyTextfield extends LitElement {
right: 8px;
inset-inline-start: initial;
inset-inline-end: 8px;
--mdc-icon-button-size: 40px;
--ha-icon-button-size: 40px;
--mdc-icon-size: 20px;
color: var(--secondary-text-color);
direction: var(--direction);

View File

@@ -7,8 +7,9 @@ import { nextRender } from "../common/util/render-status";
import { haStyleDialog } from "../resources/styles";
import type { HomeAssistant } from "../types";
import type { DatePickerDialogParams } from "./ha-date-input";
import "./ha-dialog";
import "./ha-button";
import "./ha-dialog-footer";
import "./ha-dialog";
@customElement("ha-dialog-date-picker")
export class HaDialogDatePicker extends LitElement {
@@ -22,6 +23,8 @@ export class HaDialogDatePicker extends LitElement {
@state() private _params?: DatePickerDialogParams;
@state() private _open = false;
@state() private _value?: string;
public async showDialog(params: DatePickerDialogParams): Promise<void> {
@@ -30,9 +33,14 @@ export class HaDialogDatePicker extends LitElement {
await nextRender();
this._params = params;
this._value = params.value;
this._open = true;
}
public closeDialog() {
this._open = false;
}
private _dialogClosed() {
this._params = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
@@ -41,7 +49,13 @@ export class HaDialogDatePicker extends LitElement {
if (!this._params) {
return nothing;
}
return html`<ha-dialog open @closed=${this.closeDialog}>
return html`<ha-dialog
.hass=${this.hass}
.open=${this._open}
width="small"
without-header
@closed=${this._dialogClosed}
>
<app-datepicker
.value=${this._value}
.min=${this._params.min}
@@ -50,34 +64,39 @@ export class HaDialogDatePicker extends LitElement {
@datepicker-value-updated=${this._valueChanged}
.firstDayOfWeek=${this._params.firstWeekday}
></app-datepicker>
${this._params.canClear
? html`<ha-button
slot="secondaryAction"
@click=${this._clear}
variant="danger"
appearance="plain"
>
${this.hass.localize("ui.dialogs.date-picker.clear")}
</ha-button>`
: nothing}
<ha-button
appearance="plain"
slot="secondaryAction"
@click=${this._setToday}
>
${this.hass.localize("ui.dialogs.date-picker.today")}
</ha-button>
<ha-button
appearance="plain"
slot="primaryAction"
dialogaction="cancel"
class="cancel-btn"
>
${this.hass.localize("ui.common.cancel")}
</ha-button>
<ha-button slot="primaryAction" @click=${this._setValue}>
${this.hass.localize("ui.common.ok")}
</ha-button>
<div class="bottom-actions">
${this._params.canClear
? html`<ha-button
slot="secondaryAction"
@click=${this._clear}
variant="danger"
appearance="plain"
>
${this.hass.localize("ui.dialogs.date-picker.clear")}
</ha-button>`
: nothing}
<ha-button
appearance="plain"
slot="secondaryAction"
@click=${this._setToday}
>
${this.hass.localize("ui.dialogs.date-picker.today")}
</ha-button>
</div>
<ha-dialog-footer slot="footer">
<ha-button
appearance="plain"
slot="secondaryAction"
@click=${this.closeDialog}
>
${this.hass.localize("ui.common.cancel")}
</ha-button>
<ha-button slot="primaryAction" @click=${this._setValue}>
${this.hass.localize("ui.common.ok")}
</ha-button>
</ha-dialog-footer>
</ha-dialog>`;
}
@@ -110,9 +129,18 @@ export class HaDialogDatePicker extends LitElement {
css`
ha-dialog {
--dialog-content-padding: 0;
--justify-action-buttons: space-between;
}
.bottom-actions {
display: flex;
gap: var(--ha-space-4);
justify-content: center;
align-items: center;
width: 100%;
margin-bottom: var(--ha-space-1);
}
app-datepicker {
display: block;
margin-inline: auto;
--app-datepicker-accent-color: var(--primary-color);
--app-datepicker-bg-color: transparent;
--app-datepicker-color: var(--primary-text-color);
@@ -129,11 +157,6 @@ export class HaDialogDatePicker extends LitElement {
app-datepicker::part(body) {
direction: ltr;
}
@media all and (min-width: 450px) {
ha-dialog {
--mdc-dialog-min-width: 300px;
}
}
@media all and (max-width: 450px), all and (max-height: 500px) {
app-datepicker {
width: 100%;

View File

@@ -9,7 +9,7 @@ import { customElement } from "lit/decorators";
*
* @summary
* A simple footer container for dialog actions,
* typically used as the `footer` slot in `ha-wa-dialog`.
* typically used as the `footer` slot in `ha-dialog`.
*
* @slot primaryAction - Primary action button(s), aligned to the end.
* @slot secondaryAction - Secondary action button(s), placed before the primary action.

View File

@@ -1,193 +1,450 @@
import { DialogBase } from "@material/mwc-dialog/mwc-dialog-base";
import { styles } from "@material/mwc-dialog/mwc-dialog.css";
import "@home-assistant/webawesome/dist/components/dialog/dialog";
import type WaDialog from "@home-assistant/webawesome/dist/components/dialog/dialog";
import { mdiClose } from "@mdi/js";
import type { TemplateResult } from "lit";
import { css, html } from "lit";
import { customElement } from "lit/decorators";
import { FOCUS_TARGET } from "../dialogs/make-dialog-manager";
import { css, html, LitElement, nothing } from "lit";
import {
customElement,
eventOptions,
property,
query,
state,
} from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import { fireEvent } from "../common/dom/fire_event";
import { ScrollableFadeMixin } from "../mixins/scrollable-fade-mixin";
import { haStyleScrollbar } from "../resources/styles";
import type { HomeAssistant } from "../types";
import { isIosApp } from "../util/is_ios";
import "./ha-dialog-header";
import "./ha-icon-button";
const SUPPRESS_DEFAULT_PRESS_SELECTOR = ["button", "ha-list-item"];
export type DialogWidth = "small" | "medium" | "large" | "full";
export const createCloseHeading = (
hass: HomeAssistant | undefined,
title: string | TemplateResult
) => html`
<div class="header_title">
<ha-icon-button
.label=${hass?.localize("ui.common.close") ?? "Close"}
.path=${mdiClose}
dialogAction="close"
class="header_button"
></ha-icon-button>
<span>${title}</span>
</div>
`;
type DialogHideEvent = CustomEvent<{ source?: Element }>;
/**
* Home Assistant dialog component
*
* @element ha-dialog
* @extends {LitElement}
*
* @summary
* A stylable dialog built using the `wa-dialog` component, providing a standardized header (ha-dialog-header),
* body, and footer (preferably using `ha-dialog-footer`) with slots
*
* You can open and close the dialog declaratively by using the `data-dialog="close"` attribute.
* @see https://webawesome.com/docs/components/dialog/#opening-and-closing-dialogs-declaratively
*
* @slot header - Replace the entire header area.
* @slot headerNavigationIcon - Leading header action (e.g. close/back button).
* @slot headerTitle - Custom title content (used when header-title is not set).
* @slot headerSubtitle - Custom subtitle content (used when header-subtitle is not set).
* @slot headerActionItems - Trailing header actions (e.g. buttons, menus).
* @slot - Dialog content body.
* @slot footer - Dialog footer content.
*
* @csspart dialog - The dialog surface.
* @csspart header - The header container.
* @csspart body - The scrollable body container.
* @csspart footer - The footer container.
*
* @cssprop --dialog-content-padding - Padding for the dialog content sections.
* @cssprop --ha-dialog-show-duration - Show animation duration.
* @cssprop --ha-dialog-hide-duration - Hide animation duration.
* @cssprop --ha-dialog-surface-background - Dialog background color.
* @cssprop --ha-dialog-border-radius - Border radius of the dialog surface.
* @cssprop --dialog-surface-margin-top - Top margin for the dialog surface.
*
* @attr {boolean} open - Controls the dialog open state.
* @attr {("alert"|"standard")} type - Dialog type. Defaults to "standard".
* @attr {("small"|"medium"|"large"|"full")} width - Preferred dialog width preset. Defaults to "medium".
* @attr {boolean} prevent-scrim-close - Prevents closing the dialog by clicking the scrim/overlay. Defaults to false.
* @attr {string} header-title - Header title text. If not set, the headerTitle slot is used.
* @attr {string} header-subtitle - Header subtitle text. If not set, the headerSubtitle slot is used.
* @attr {("above"|"below")} header-subtitle-position - Position of the subtitle relative to the title. Defaults to "below".
* @attr {boolean} flexcontent - Makes the dialog body a flex container for flexible layouts.
*
* @event opened - Fired when the dialog is shown.
* @event closed - Fired after the dialog is hidden.
*
* @remarks
* **Focus Management:**
* To automatically focus an element when the dialog opens, add the `autofocus` attribute to it.
* Components with `delegatesFocus: true` (like `ha-form`) will forward focus to their first focusable child.
* Example: `<ha-form autofocus .schema=${schema}></ha-form>`
*
* @see https://github.com/home-assistant/frontend/issues/27143
*/
@customElement("ha-dialog")
export class HaDialog extends DialogBase {
protected readonly [FOCUS_TARGET];
export class HaDialog extends ScrollableFadeMixin(LitElement) {
@property({ attribute: false }) public hass?: HomeAssistant;
public scrollToPos(x: number, y: number) {
this.contentElement?.scrollTo(x, y);
@property({ attribute: "aria-labelledby" })
public ariaLabelledBy?: string;
@property({ attribute: "aria-describedby" })
public ariaDescribedBy?: string;
@property({ type: Boolean, reflect: true })
public open = false;
@property({ reflect: true })
public type: "alert" | "standard" = "standard";
@property({ type: String, reflect: true, attribute: "width" })
public width: DialogWidth = "medium";
@property({ type: Boolean, reflect: true, attribute: "prevent-scrim-close" })
public preventScrimClose = false;
@property({ attribute: "header-title" })
public headerTitle?: string;
@property({ attribute: "header-subtitle" })
public headerSubtitle?: string;
@property({ type: String, attribute: "header-subtitle-position" })
public headerSubtitlePosition: "above" | "below" = "below";
@property({ type: Boolean, reflect: true, attribute: "flexcontent" })
public flexContent = false;
@property({ type: Boolean, attribute: "without-header" })
public withoutHeader = false;
@state()
private _open = false;
@query(".body") public bodyContainer!: HTMLDivElement;
@state()
private _bodyScrolled = false;
private _escapePressed = false;
protected get scrollableElement(): HTMLElement | null {
return this.bodyContainer;
}
protected renderHeading() {
return html`<slot name="heading"> ${super.renderHeading()} </slot>`;
protected updated(
changedProperties: Map<string | number | symbol, unknown>
): void {
super.updated(changedProperties);
if (changedProperties.has("open")) {
this._open = this.open;
}
}
protected firstUpdated(): void {
super.firstUpdated();
this.suppressDefaultPressSelector = [
this.suppressDefaultPressSelector,
SUPPRESS_DEFAULT_PRESS_SELECTOR,
].join(", ");
this._updateScrolledAttribute();
this.contentElement?.addEventListener("scroll", this._onScroll, {
passive: true,
protected render() {
return html`
<wa-dialog
.open=${this._open}
.lightDismiss=${!this.preventScrimClose}
without-header
aria-labelledby=${ifDefined(
this.ariaLabelledBy ||
(this.headerTitle !== undefined ? "ha-dialog-title" : undefined)
)}
aria-describedby=${ifDefined(this.ariaDescribedBy)}
@keydown=${this._handleKeyDown}
@wa-hide=${this._handleHide}
@wa-show=${this._handleShow}
@wa-after-show=${this._handleAfterShow}
@wa-after-hide=${this._handleAfterHide}
>
${!this.withoutHeader
? html` <slot name="header">
<ha-dialog-header
.subtitlePosition=${this.headerSubtitlePosition}
.showBorder=${this._bodyScrolled}
>
<slot name="headerNavigationIcon" slot="navigationIcon">
<ha-icon-button
data-dialog="close"
.label=${this.hass?.localize("ui.common.close") ?? "Close"}
.path=${mdiClose}
></ha-icon-button>
</slot>
${this.headerTitle !== undefined
? html`<span slot="title" class="title" id="ha-dialog-title">
${this.headerTitle}
</span>`
: html`<slot name="headerTitle" slot="title"></slot>`}
${this.headerSubtitle !== undefined
? html`<span slot="subtitle">${this.headerSubtitle}</span>`
: html`<slot name="headerSubtitle" slot="subtitle"></slot>`}
<slot name="headerActionItems" slot="actionItems"></slot>
</ha-dialog-header>
</slot>`
: nothing}
<div class="content-wrapper">
<div class="body ha-scrollbar" @scroll=${this._handleBodyScroll}>
<slot></slot>
</div>
${this.renderScrollableFades()}
</div>
<slot name="footer" slot="footer"></slot>
</wa-dialog>
`;
}
private _handleShow = async () => {
this._open = true;
fireEvent(this, "opened");
await this.updateComplete;
requestAnimationFrame(() => {
if (this.hass && isIosApp(this.hass)) {
const element = this.querySelector("[autofocus]");
if (element !== null) {
if (!element.id) {
element.id = "ha-dialog-autofocus";
}
this.hass?.auth.external?.fireMessage({
type: "focus_element",
payload: {
element_id: element.id,
},
});
}
return;
}
(this.querySelector("[autofocus]") as HTMLElement | null)?.focus();
});
}
disconnectedCallback(): void {
super.disconnectedCallback();
this.contentElement.removeEventListener("scroll", this._onScroll);
}
private _onScroll = () => {
this._updateScrolledAttribute();
};
private _updateScrolledAttribute() {
if (!this.contentElement) return;
this.toggleAttribute("scrolled", this.contentElement.scrollTop !== 0);
private _handleAfterShow = () => {
fireEvent(this, "after-show");
};
private _handleAfterHide = (ev: DialogHideEvent) => {
if (ev.eventPhase === Event.AT_TARGET) {
this._open = false;
fireEvent(this, "closed");
}
};
public disconnectedCallback(): void {
super.disconnectedCallback();
this._open = false;
}
static override styles = [
styles,
css`
:host([scrolled]) ::slotted(ha-dialog-header) {
border-bottom: 1px solid
var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
}
.mdc-dialog {
--mdc-dialog-scroll-divider-color: var(
--dialog-scroll-divider-color,
var(--divider-color)
);
z-index: var(--dialog-z-index, 8);
--mdc-dialog-box-shadow: var(--dialog-box-shadow, none);
--mdc-typography-headline6-font-weight: var(--ha-font-weight-normal);
--mdc-typography-headline6-font-size: 1.574rem;
}
.mdc-dialog::before {
content: "";
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
pointer-events: none;
-webkit-backdrop-filter: var(
--ha-dialog-scrim-backdrop-filter,
var(--dialog-backdrop-filter, none)
);
backdrop-filter: var(
--ha-dialog-scrim-backdrop-filter,
var(--dialog-backdrop-filter, none)
);
}
.mdc-dialog .mdc-dialog__scrim {
background-color: var(--mdc-dialog-scrim-color, none);
}
.mdc-dialog__actions {
justify-content: var(--justify-action-buttons, flex-end);
padding: var(--ha-space-3) var(--ha-space-4) var(--ha-space-4)
var(--ha-space-4);
}
.mdc-dialog__actions span:nth-child(1) {
flex: var(--secondary-action-button-flex, unset);
}
.mdc-dialog__actions span:nth-child(2) {
flex: var(--primary-action-button-flex, unset);
}
.mdc-dialog__container {
align-items: var(--vertical-align-dialog, center);
padding: var(--dialog-container-padding, 0);
}
.mdc-dialog__title {
padding: var(--ha-space-4) var(--ha-space-4) 0 var(--ha-space-4);
}
.mdc-dialog__title:has(span) {
padding: var(--ha-space-3) var(--ha-space-3) 0;
}
.mdc-dialog__title::before {
content: unset;
}
.mdc-dialog .mdc-dialog__content {
position: var(--dialog-content-position, relative);
padding: var(--dialog-content-padding, var(--ha-space-6));
}
:host([hideactions]) .mdc-dialog .mdc-dialog__content {
padding-bottom: var(--dialog-content-padding, var(--ha-space-6));
}
.mdc-dialog .mdc-dialog__surface {
position: var(--dialog-surface-position, relative);
top: var(--dialog-surface-top);
margin-top: var(--dialog-surface-margin-top);
min-width: var(--mdc-dialog-min-width, auto);
min-height: var(--mdc-dialog-min-height, auto);
border-radius: var(
--ha-dialog-border-radius,
var(--ha-border-radius-3xl)
);
-webkit-backdrop-filter: var(--ha-dialog-surface-backdrop-filter, none);
backdrop-filter: var(--ha-dialog-surface-backdrop-filter, none);
background: var(
--ha-dialog-surface-background,
var(--mdc-theme-surface, #fff)
);
padding: var(--dialog-surface-padding, 0);
}
:host([flexContent]) .mdc-dialog .mdc-dialog__content {
display: flex;
flex-direction: column;
}
@eventOptions({ passive: true })
private _handleBodyScroll(ev: Event) {
this._bodyScrolled = (ev.target as HTMLDivElement).scrollTop > 0;
}
.header_title {
display: flex;
align-items: center;
direction: var(--direction);
}
.header_title span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: block;
padding-left: var(--ha-space-1);
padding-right: var(--ha-space-1);
margin-right: var(--ha-space-3);
margin-inline-end: var(--ha-space-3);
margin-inline-start: initial;
}
.header_button {
text-decoration: none;
color: inherit;
inset-inline-start: initial;
inset-inline-end: calc(var(--ha-space-3) * -1);
direction: var(--direction);
}
.dialog-actions {
inset-inline-start: initial !important;
inset-inline-end: 0 !important;
direction: var(--direction);
}
`,
];
private _handleKeyDown(ev: KeyboardEvent) {
if (ev.key === "Escape") {
this._escapePressed = true;
ev.stopPropagation();
(ev.currentTarget as WaDialog).open = false;
}
}
private _handleHide(ev: DialogHideEvent) {
const sourceIsDialog = ev.detail?.source === (ev.target as WaDialog).dialog;
if (this.preventScrimClose && this._escapePressed && sourceIsDialog) {
ev.preventDefault();
}
this._escapePressed = false;
}
static get styles() {
return [
...super.styles,
haStyleScrollbar,
css`
wa-dialog {
--full-width: var(
--ha-dialog-width-full,
min(95vw, var(--safe-width))
);
--width: min(var(--ha-dialog-width-md, 580px), var(--full-width));
--spacing: var(--dialog-content-padding, var(--ha-space-6));
--show-duration: var(--ha-dialog-show-duration, 200ms);
--hide-duration: var(--ha-dialog-hide-duration, 200ms);
--ha-dialog-surface-background: var(
--card-background-color,
var(--ha-color-surface-default)
);
--wa-color-surface-raised: var(
--ha-dialog-surface-background,
var(--card-background-color, var(--ha-color-surface-default))
);
--wa-panel-border-radius: var(
--ha-dialog-border-radius,
var(--ha-border-radius-3xl)
);
max-width: var(--ha-dialog-max-width, var(--safe-width));
}
@media (prefers-reduced-motion: reduce) {
wa-dialog {
--show-duration: 0ms;
--hide-duration: 0ms;
}
}
:host([width="small"]) wa-dialog {
--width: min(var(--ha-dialog-width-sm, 320px), var(--full-width));
}
:host([width="large"]) wa-dialog {
--width: min(var(--ha-dialog-width-lg, 1024px), var(--full-width));
}
:host([width="full"]) wa-dialog {
--width: var(--full-width);
}
wa-dialog::part(dialog) {
color: var(--primary-text-color);
min-width: var(--width, var(--full-width));
max-width: var(--width, var(--full-width));
max-height: var(
--ha-dialog-max-height,
calc(var(--safe-height) - var(--ha-space-20))
);
min-height: var(--ha-dialog-min-height);
margin-top: var(--dialog-surface-margin-top, auto);
/* Used to offset the dialog from the safe areas when space is limited */
transform: translate(
calc(
var(--safe-area-offset-left, 0px) - var(
--safe-area-offset-right,
0px
)
),
calc(
var(--safe-area-offset-top, 0px) - var(
--safe-area-offset-bottom,
0px
)
)
);
display: flex;
flex-direction: column;
overflow: hidden;
}
@media all and (max-width: 450px), all and (max-height: 500px) {
:host([type="standard"]) {
--ha-dialog-border-radius: 0;
}
:host([type="standard"]) wa-dialog {
/* Make the container fill the whole screen width and not the safe width */
--full-width: var(--ha-dialog-width-full, 100vw);
--width: var(--full-width);
}
:host([type="standard"]) wa-dialog::part(dialog) {
/* Make the dialog fill the whole screen height and not the safe height */
min-height: var(--ha-dialog-min-height, 100vh);
min-height: var(--ha-dialog-min-height, 100dvh);
max-height: var(--ha-dialog-max-height, 100vh);
max-height: var(--ha-dialog-max-height, 100dvh);
margin-top: 0;
margin-bottom: 0;
/* Use safe area as padding instead of the container size */
padding-top: var(--safe-area-inset-top);
padding-bottom: var(--safe-area-inset-bottom);
padding-left: var(--safe-area-inset-left);
padding-right: var(--safe-area-inset-right);
/* Reset the transform to center the dialog */
transform: none;
}
}
.header-title-container {
display: flex;
align-items: center;
}
.header-title {
margin: 0;
margin-bottom: 0;
color: var(--ha-dialog-header-title-color, var(--primary-text-color));
font-size: var(
--ha-dialog-header-title-font-size,
var(--ha-font-size-2xl)
);
line-height: var(
--ha-dialog-header-title-line-height,
var(--ha-line-height-condensed)
);
font-weight: var(
--ha-dialog-header-title-font-weight,
var(--ha-font-weight-normal)
);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: var(--ha-space-3);
}
wa-dialog::part(body) {
padding: 0;
display: flex;
flex-direction: column;
max-width: 100%;
overflow: hidden;
}
.content-wrapper {
position: relative;
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
}
.body {
position: var(--dialog-content-position, relative);
padding: var(
--dialog-content-padding,
0 var(--ha-space-6) var(--ha-space-6) var(--ha-space-6)
);
overflow: auto;
flex-grow: 1;
}
:host([flexcontent]) .body {
max-width: 100%;
flex: 1;
display: flex;
flex-direction: column;
}
wa-dialog::part(footer) {
padding: 0;
}
::slotted([slot="footer"]) {
display: flex;
padding: var(--ha-space-3) var(--ha-space-4) var(--ha-space-4)
var(--ha-space-4);
gap: var(--ha-space-3);
justify-content: flex-end;
align-items: center;
width: 100%;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-dialog": HaDialog;
}
interface HASSDomEvents {
opened: undefined;
"after-show": undefined;
closed: undefined;
}
}

View File

@@ -1,50 +0,0 @@
import { css, html, LitElement, nothing } from "lit";
import { ifDefined } from "lit/directives/if-defined";
import { customElement, property } from "lit/decorators";
@customElement("ha-divider")
export class HaMdDivider extends LitElement {
@property() public label?: string;
public render() {
return html`
<div
role=${ifDefined(this.label ? "separator" : undefined)}
aria-label=${ifDefined(this.label)}
>
<span class="line"></span>
${this.label
? html`
<span class="label">${this.label}</span>
<span class="line"></span>
`
: nothing}
</div>
`;
}
static styles = css`
:host {
width: var(--ha-divider-width, 100%);
}
div {
display: flex;
align-items: center;
justify-content: center;
}
.label {
padding: var(--ha-divider-label-padding, 0 16px);
}
.line {
flex: 1;
background-color: var(--divider-color);
height: var(--ha-divider-line-height, 1px);
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"ha-divider": HaMdDivider;
}
}

View File

@@ -2,7 +2,7 @@ import DropdownItem from "@home-assistant/webawesome/dist/components/dropdown-it
import "@home-assistant/webawesome/dist/components/icon/icon";
import { mdiCheckboxBlankOutline, mdiCheckboxMarked } from "@mdi/js";
import { css, type CSSResultGroup, html } from "lit";
import { customElement } from "lit/decorators";
import { customElement, property } from "lit/decorators";
import "./ha-svg-icon";
/**
@@ -17,6 +17,8 @@ import "./ha-svg-icon";
*/
@customElement("ha-dropdown-item")
export class HaDropdownItem extends DropdownItem {
@property({ type: Boolean, reflect: true }) selected = false;
protected renderCheckboxIcon() {
return html`
<ha-svg-icon
@@ -47,6 +49,16 @@ export class HaDropdownItem extends DropdownItem {
:host([variant="danger"]) #icon ::slotted(*) {
color: var(--ha-color-on-danger-quiet);
}
:host([selected]) {
font-weight: var(--ha-font-weight-medium);
color: var(--primary-color);
background-color: var(--ha-color-fill-primary-quiet-resting);
--icon-primary-color: var(--primary-color);
}
:host([selected]:hover) {
background-color: var(--ha-color-fill-primary-quiet-hover);
}
`,
];
}

View File

@@ -2,6 +2,7 @@ import Dropdown from "@home-assistant/webawesome/dist/components/dropdown/dropdo
import { css, type CSSResultGroup } from "lit";
import { customElement, property } from "lit/decorators";
import type { HaDropdownItem } from "./ha-dropdown-item";
import type { HaIconButton } from "./ha-icon-button";
/**
* Event type for the ha-dropdown component when an item is selected.
@@ -22,11 +23,68 @@ export type HaDropdownSelectEvent<T = string> = CustomEvent<{
*
*/
@customElement("ha-dropdown")
// @ts-ignore Allow to set an alternative anchor element
export class HaDropdown extends Dropdown {
@property({ attribute: false }) dropdownTag = "ha-dropdown";
@property({ attribute: false }) dropdownItemTag = "ha-dropdown-item";
public get anchorElement(): HTMLButtonElement | HaIconButton | undefined {
// @ts-ignore Allow to set an anchor element on popup
return this.popup?.anchor as HTMLButtonElement | HaIconButton | undefined;
}
public set anchorElement(
element: HTMLButtonElement | HaIconButton | undefined
) {
// @ts-ignore Allow to get the current anchor element from popup
if (!this.popup) {
return;
}
// @ts-ignore
if (this.popup.anchor && this.popup.anchor.localName === "ha-icon-button") {
// @ts-ignore
(this.popup.anchor as HaIconButton).selected = false;
}
// @ts-ignore Allow to get the current anchor element from popup
this.popup.anchor = element;
}
/** Get the slotted trigger button, a <wa-button> or <button> element */
// @ts-ignore Override parent method to be able to use alternative anchor
// eslint-disable-next-line @typescript-eslint/naming-convention
private override getTrigger(): HTMLButtonElement | HaIconButton | null {
if (this.anchorElement) {
return this.anchorElement;
}
// @ts-ignore fallback to default trigger slot if no anchorElement is set
return super.getTrigger();
}
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/naming-convention
private override async showMenu() {
// @ts-ignore
await super.showMenu();
const triggerElement = this.getTrigger();
if (triggerElement && triggerElement.localName === "ha-icon-button") {
(triggerElement as HaIconButton).selected = true;
}
}
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/naming-convention
private override async hideMenu() {
const triggerElement = this.getTrigger();
if (triggerElement && triggerElement.localName === "ha-icon-button") {
(triggerElement as HaIconButton).selected = false;
}
// @ts-ignore
await super.hideMenu();
}
static get styles(): CSSResultGroup {
return [
Dropdown.styles,

View File

@@ -6,6 +6,7 @@ import { fireEvent } from "../common/dom/fire_event";
import "./ha-base-time-input";
import type { TimeChangedEvent } from "./ha-base-time-input";
import "./ha-button-toggle-group";
import type { ValueChangedEvent } from "../types";
export interface HaDurationData {
days?: number;
@@ -36,6 +37,9 @@ class HaDurationInput extends LitElement {
@property({ attribute: "allow-negative", type: Boolean })
public allowNegative = false;
@property({ attribute: "enable-second", type: Boolean })
public enableSecond = true;
@property({ type: Boolean }) public disabled = false;
private _toggleNegative = false;
@@ -64,7 +68,7 @@ class HaDurationInput extends LitElement {
.autoValidate=${this.required}
.disabled=${this.disabled}
errorMessage="Required"
enable-second
.enableSecond=${this.enableSecond}
.enableMillisecond=${this.enableMillisecond}
.enableDay=${this.enableDay}
format="24"
@@ -152,16 +156,18 @@ class HaDurationInput extends LitElement {
: NaN;
}
private _durationChanged(ev: CustomEvent<{ value?: TimeChangedEvent }>) {
private _durationChanged(
ev: ValueChangedEvent<TimeChangedEvent | undefined>
) {
ev.stopPropagation();
const value = ev.detail.value ? { ...ev.detail.value } : undefined;
if (value) {
value.hours ||= 0;
value.minutes ||= 0;
value.seconds ||= 0;
if ("days" in value) value.days ||= 0;
if ("seconds" in value) value.seconds ||= 0;
if ("milliseconds" in value) value.milliseconds ||= 0;
if (this.allowNegative) {
@@ -180,8 +186,11 @@ class HaDurationInput extends LitElement {
value.milliseconds %= 1000;
}
if (value.seconds > 59) {
value.minutes += Math.floor(value.seconds / 60);
if (!this.enableSecond && !value.seconds) {
// @ts-ignore
delete value.seconds;
} else if (this.enableSecond && value.seconds > 59) {
value.minutes = (value.minutes ?? 0) + Math.floor(value.seconds / 60);
value.seconds %= 60;
}

View File

@@ -39,7 +39,7 @@ export class HaFab extends FabBase {
}
:disabled {
--mdc-theme-secondary: var(--disabled-text-color);
pointer-events: none;
cursor: not-allowed !important;
}
`,
// safari workaround - must be explicit

View File

@@ -4,14 +4,14 @@ import type { PropertyValues, TemplateResult } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { ensureArray } from "../common/array/ensure-array";
import { fireEvent } from "../common/dom/fire_event";
import { blankBeforePercent } from "../common/translations/blank_before_percent";
import type { LocalizeFunc } from "../common/translations/localize";
import type { HomeAssistant } from "../types";
import { bytesToString } from "../util/bytes-to-string";
import "./ha-button";
import "./ha-icon-button";
import { blankBeforePercent } from "../common/translations/blank_before_percent";
import { ensureArray } from "../common/array/ensure-array";
import { bytesToString } from "../util/bytes-to-string";
import type { LocalizeFunc } from "../common/translations/localize";
declare global {
interface HASSDomEvents {
@@ -317,7 +317,7 @@ export class HaFileUpload extends LitElement {
}
ha-button {
--mdc-button-outline-color: var(--primary-color);
--mdc-icon-button-size: 24px;
--ha-icon-button-size: 24px;
}
mwc-linear-progress {
width: 100%;

View File

@@ -26,12 +26,12 @@ import { showCategoryRegistryDetailDialog } from "../panels/config/category/show
import { haStyleScrollbar } from "../resources/styles";
import type { HomeAssistant } from "../types";
import "./ha-dropdown";
import type { HaDropdownSelectEvent } from "./ha-dropdown";
import "./ha-dropdown-item";
import "./ha-expansion-panel";
import "./ha-icon";
import "./ha-list";
import "./ha-list-item";
import type { HaDropdownSelectEvent } from "./ha-dropdown";
@customElement("ha-filter-categories")
export class HaFilterCategories extends SubscribeMixin(LitElement) {
@@ -315,8 +315,12 @@ export class HaFilterCategories extends SubscribeMixin(LitElement) {
}
ha-list {
--mdc-list-item-meta-size: auto;
--mdc-list-side-padding-right: 4px;
--mdc-icon-button-size: 36px;
--mdc-list-side-padding-right: var(--ha-space-1);
--mdc-list-side-padding-left: var(--ha-space-4);
--ha-icon-button-size: 36px;
}
ha-list-item {
--mdc-list-item-graphic-margin: var(--ha-space-4);
}
ha-dropdown-item {
font-size: var(--ha-font-size-m);

View File

@@ -122,7 +122,10 @@ export class HaFilterDevices extends LitElement {
setTimeout(() => {
if (!this.expanded) return;
this.renderRoot.querySelector("ha-list")!.style.height =
`${this.clientHeight - 49 - 32}px`; // 32px is the height of the search input
`${this.clientHeight - 49 - 4 - 32}px`;
// 49px - height of a header + 1px
// 4px - padding-top of the search-input
// 32px - height of the search input
}, 300);
}
}

View File

@@ -110,7 +110,10 @@ export class HaFilterDomains extends LitElement {
setTimeout(() => {
if (!this.expanded) return;
this.renderRoot.querySelector("ha-list")!.style.height =
`${this.clientHeight - 49 - 32}px`; // 32px is the height of the search input
`${this.clientHeight - 49 - 4 - 32}px`;
// 49px - height of a header + 1px
// 4px - padding-top of the search-input
// 32px - height of the search input
}, 300);
}
}
@@ -179,6 +182,9 @@ export class HaFilterDomains extends LitElement {
margin-inline-start: initial;
margin-inline-end: 8px;
}
ha-check-list-item {
--mdc-list-item-graphic-margin: var(--ha-space-4);
}
.badge {
display: inline-block;
margin-left: 8px;

View File

@@ -101,7 +101,10 @@ export class HaFilterEntities extends LitElement {
setTimeout(() => {
if (!this.expanded) return;
this.renderRoot.querySelector("ha-list")!.style.height =
`${this.clientHeight - 49 - 32}px`; // 32px is the height of the search input
`${this.clientHeight - 49 - 4 - 32}px`;
// 49px - height of a header + 1px
// 4px - padding-top of the search-input
// 32px - height of the search input
}, 300);
}
}

View File

@@ -99,7 +99,10 @@ export class HaFilterIntegrations extends LitElement {
setTimeout(() => {
if (!this.expanded) return;
this.renderRoot.querySelector("ha-list")!.style.height =
`${this.clientHeight - 49 - 32}px`; // 32px is the height of the search input
`${this.clientHeight - 49 - 4 - 32}px`;
// 49px - height of a header + 1px
// 4px - padding-top of the search-input
// 32px - height of the search input
}, 300);
}
}
@@ -199,6 +202,9 @@ export class HaFilterIntegrations extends LitElement {
margin-inline-start: auto;
margin-inline-end: 8px;
}
ha-check-list-item {
--mdc-list-item-graphic-margin: var(--ha-space-4);
}
.badge {
display: inline-block;
margin-left: 8px;

View File

@@ -145,7 +145,11 @@ export class HaFilterLabels extends SubscribeMixin(LitElement) {
setTimeout(() => {
if (!this.expanded) return;
this.renderRoot.querySelector("ha-list")!.style.height =
`${this.clientHeight - (49 + 48 + 32)}px`;
`${this.clientHeight - (49 + 48 + 32 + 4)}px`;
// 49px - height of a header + 1px
// 4px - padding-top of the search-input
// 32px - height of the search input
// 48px - height of ha-list-item
}, 300);
}
}

View File

@@ -164,6 +164,9 @@ export class HaFilterVoiceAssistants extends LitElement {
margin-inline-start: auto;
margin-inline-end: 8px;
}
ha-check-list-item {
--mdc-list-item-graphic-margin: var(--ha-space-4);
}
.badge {
display: inline-block;
margin-left: 8px;

View File

@@ -359,7 +359,7 @@ export class HaFloorPicker extends LitElement {
{
id: ADD_NEW_ID + searchString,
primary: this.hass.localize(
"ui.components.floor-picker.add_new_sugestion",
"ui.components.floor-picker.add_new_suggestion",
{
name: searchString,
}

View File

@@ -76,6 +76,11 @@ export class HaFormFloat extends LitElement implements HaFormElement {
return;
}
// Allow user to start typing a negative zero
if (rawValue === "-0") {
return;
}
if (rawValue !== "") {
value = parseFloat(rawValue);
if (isNaN(value)) {

View File

@@ -3,6 +3,10 @@ import type { PropertyValues, TemplateResult } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import type {
LocalizeFunc,
LocalizeKeys,
} from "../../common/translations/localize";
import "../ha-icon-button";
import "../ha-textfield";
import type { HaTextField } from "../ha-textfield";
@@ -11,10 +15,6 @@ import type {
HaFormStringData,
HaFormStringSchema,
} from "./types";
import type {
LocalizeFunc,
LocalizeKeys,
} from "../../common/translations/localize";
const MASKED_FIELDS = ["password", "secret", "token"];
@@ -148,7 +148,7 @@ export class HaFormString extends LitElement implements HaFormElement {
right: 8px;
inset-inline-start: initial;
inset-inline-end: 8px;
--mdc-icon-button-size: 40px;
--ha-icon-button-size: 40px;
--mdc-icon-size: 20px;
color: var(--secondary-text-color);
direction: var(--direction);

View File

@@ -53,7 +53,7 @@ export class HaFormfield extends FormfieldBase {
:host(:not([alignEnd])) ::slotted(ha-switch) {
margin-right: 10px;
margin-inline-end: 10px;
margin-inline-start: inline;
margin-inline-start: initial;
}
.mdc-form-field {
align-items: var(--ha-formfield-align-items, center);

View File

@@ -194,7 +194,6 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
.image=${this.image}
.label=${label}
.placeholder=${this.placeholder}
.helper=${this.helper}
.value=${this.value}
.valueRenderer=${this.valueRenderer}
.required=${this.required}
@@ -434,6 +433,10 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
--wa-space-l: 0;
}
wa-popover::part(dialog)::backdrop {
background: none;
}
wa-popover::part(body) {
width: max(var(--body-width), 250px);
max-width: var(

View File

@@ -1,14 +1,14 @@
import { mdiRestore } from "@mdi/js";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import "../panels/lovelace/editor/card-editor/ha-grid-layout-slider";
import "./ha-icon-button";
import { mdiRestore } from "@mdi/js";
import { styleMap } from "lit/directives/style-map";
import { fireEvent } from "../common/dom/fire_event";
import { conditionalClamp } from "../common/number/clamp";
import type { CardGridSize } from "../panels/lovelace/common/compute-card-grid-size";
import { DEFAULT_GRID_SIZE } from "../panels/lovelace/common/compute-card-grid-size";
import "../panels/lovelace/editor/card-editor/ha-grid-layout-slider";
import type { HomeAssistant } from "../types";
import "./ha-icon-button";
@customElement("ha-grid-size-picker")
export class HaGridSizeEditor extends LitElement {
@@ -245,7 +245,7 @@ export class HaGridSizeEditor extends LitElement {
}
.reset {
grid-area: reset;
--mdc-icon-button-size: 36px;
--ha-icon-button-size: 36px;
}
.preview {
position: relative;

View File

@@ -1,4 +1,4 @@
import { mdiHelpCircle } from "@mdi/js";
import { mdiHelpCircleOutline } from "@mdi/js";
import type { TemplateResult } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
@@ -25,7 +25,7 @@ export class HaHelpTooltip extends LitElement {
protected render(): TemplateResult {
return html`
<ha-svg-icon id="svg-icon" .path=${mdiHelpCircle}></ha-svg-icon>
<ha-svg-icon id="svg-icon" .path=${mdiHelpCircleOutline}></ha-svg-icon>
<ha-tooltip for="svg-icon" .placement=${this.position}>
${this.label}
</ha-tooltip>

View File

@@ -14,6 +14,14 @@ export class HaIconButtonArrowPrev extends LitElement {
@property() public label?: string;
@property() href?: string;
@property() target?: "_blank" | "_parent" | "_self" | "_top";
@property() rel?: string;
@property() download?: string;
@state() private _icon =
mainWindow.document.dir === "rtl" ? mdiArrowRight : mdiArrowLeft;
@@ -23,6 +31,10 @@ export class HaIconButtonArrowPrev extends LitElement {
.disabled=${this.disabled}
.label=${this.label || this.hass?.localize("ui.common.back") || "Back"}
.path=${this._icon}
.href=${this.href}
.target=${this.target}
.rel=${this.rel}
.download=${this.download}
></ha-icon-button>
`;
}

View File

@@ -14,6 +14,14 @@ export class HaIconButtonNext extends LitElement {
@property() public label?: string;
@property() href?: string;
@property() target?: "_blank" | "_parent" | "_self" | "_top";
@property() rel?: string;
@property() download?: string;
@state() private _icon =
mainWindow.document.dir === "rtl" ? mdiChevronLeft : mdiChevronRight;
@@ -23,6 +31,10 @@ export class HaIconButtonNext extends LitElement {
.disabled=${this.disabled}
.label=${this.label || this.hass?.localize("ui.common.next") || "Next"}
.path=${this._icon}
.href=${this.href}
.target=${this.target}
.rel=${this.rel}
.download=${this.download}
></ha-icon-button>
`;
}

View File

@@ -14,6 +14,14 @@ export class HaIconButtonPrev extends LitElement {
@property() public label?: string;
@property() href?: string;
@property() target?: "_blank" | "_parent" | "_self" | "_top";
@property() rel?: string;
@property() download?: string;
@state() private _icon =
mainWindow.document.dir === "rtl" ? mdiChevronRight : mdiChevronLeft;
@@ -23,6 +31,10 @@ export class HaIconButtonPrev extends LitElement {
.disabled=${this.disabled}
.label=${this.label || this.hass?.localize("ui.common.back") || "Back"}
.path=${this._icon}
.href=${this.href}
.target=${this.target}
.rel=${this.rel}
.download=${this.download}
></ha-icon-button>
`;
}

View File

@@ -1,3 +1,4 @@
import type { CSSResultGroup } from "lit";
import { css } from "lit";
import { customElement, property } from "lit/decorators";
import { HaIconButton } from "./ha-icon-button";
@@ -6,41 +7,48 @@ import { HaIconButton } from "./ha-icon-button";
export class HaIconButtonToggle extends HaIconButton {
@property({ type: Boolean, reflect: true }) selected = false;
static styles = css`
:host {
position: relative;
}
mwc-icon-button {
position: relative;
transition: color 180ms ease-in-out;
}
mwc-icon-button::before {
opacity: 0;
transition: opacity 180ms ease-in-out;
background-color: var(--primary-text-color);
border-radius: var(--ha-border-radius-2xl);
height: 40px;
width: 40px;
content: "";
position: absolute;
top: -10px;
left: -10px;
bottom: -10px;
right: -10px;
margin: auto;
box-sizing: border-box;
}
:host([border-only]) mwc-icon-button::before {
background-color: transparent;
border: 2px solid var(--primary-text-color);
}
:host([selected]) mwc-icon-button {
color: var(--primary-background-color);
}
:host([selected]:not([disabled])) mwc-icon-button::before {
opacity: 1;
}
`;
static styles: CSSResultGroup = [
HaIconButton.styles,
css`
:host {
position: relative;
}
ha-button::part(base) {
position: relative;
transition: color 180ms ease-in-out;
}
ha-button::part(base)::before {
opacity: 0;
transition: opacity 180ms ease-in-out;
background-color: var(--primary-text-color);
border-radius: var(--ha-border-radius-2xl);
height: 40px;
width: 40px;
content: "";
position: absolute;
top: -10px;
left: -10px;
bottom: -10px;
right: -10px;
margin: auto;
box-sizing: border-box;
}
:host([border-only]) ha-button::part(base)::before {
background-color: transparent;
border: 2px solid var(--primary-text-color);
}
:host([selected]) ha-button::part(base) {
color: var(--primary-background-color);
background-color: unset;
}
:host([selected]:not([disabled])) ha-button::part(base)::before {
opacity: 1;
}
::slotted(*) {
display: block;
}
`,
];
}
declare global {

View File

@@ -109,7 +109,7 @@ export class HaIconButtonToolbar extends LitElement {
.icon-toolbar-button {
color: var(--secondary-text-color);
--mdc-icon-button-size: var(--icon-button-toolbar-button);
--ha-icon-button-size: var(--icon-button-toolbar-button);
--mdc-icon-size: var(--icon-button-toolbar-icon);
/* Ensure button is clickable on iOS */
cursor: pointer;

View File

@@ -1,9 +1,8 @@
import "@material/mwc-icon-button";
import type { IconButton } from "@material/mwc-icon-button";
import type { TemplateResult } from "lit";
import type { CSSResultGroup, TemplateResult } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property, query } from "lit/decorators";
import { customElement, property } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import "./ha-button";
import "./ha-svg-icon";
@customElement("ha-icon-button")
@@ -19,15 +18,19 @@ export class HaIconButton extends LitElement {
// These should always be set as properties, not attributes,
// so that only the <button> element gets the attribute
@property({ type: String, attribute: "aria-haspopup" })
override ariaHasPopup!: IconButton["ariaHasPopup"];
ariaHasPopup!: "false" | "true" | "menu" | "listbox" | "tree" | "grid";
@property({ attribute: "hide-title", type: Boolean }) hideTitle = false;
@query("mwc-icon-button", true) private _button?: IconButton;
@property({ type: Boolean, reflect: true }) selected = false;
public override focus() {
this._button?.focus();
}
@property() href?: string;
@property() target?: "_blank" | "_parent" | "_self" | "_top";
@property() rel?: string;
@property() download?: string;
static shadowRootOptions: ShadowRootInit = {
mode: "open",
@@ -36,30 +39,68 @@ export class HaIconButton extends LitElement {
protected render(): TemplateResult {
return html`
<mwc-icon-button
<ha-button
appearance="plain"
variant="neutral"
aria-label=${ifDefined(this.label)}
title=${ifDefined(this.hideTitle ? undefined : this.label)}
aria-haspopup=${ifDefined(this.ariaHasPopup)}
.disabled=${this.disabled}
.iconTag=${this.path ? "ha-svg-icon" : "span"}
.href=${this.href}
.target=${this.target}
.rel=${this.rel}
.download=${this.download}
>
${this.path
? html`<ha-svg-icon .path=${this.path}></ha-svg-icon>`
: html`<slot></slot>`}
</mwc-icon-button>
: html`<span><slot></slot></span>`}
</ha-button>
`;
}
static styles = css`
static styles: CSSResultGroup = css`
:host {
display: inline-block;
outline: none;
--ha-button-height: var(--ha-icon-button-size, 48px);
}
:host([disabled]) {
ha-button {
position: relative;
isolation: isolate;
--wa-form-control-padding-inline: var(
--ha-icon-button-padding-inline,
--ha-space-2
);
--wa-color-on-normal: currentColor;
--wa-color-fill-quiet: transparent;
}
ha-button::after {
content: "";
position: absolute;
inset: 0;
z-index: -1;
border-radius: 50%;
background-color: currentColor;
opacity: 0;
pointer-events: none;
}
mwc-icon-button {
--mdc-theme-on-primary: currentColor;
--mdc-theme-text-disabled-on-light: var(--disabled-text-color);
ha-button::part(base) {
width: var(--wa-form-control-height);
aspect-ratio: 1;
outline-offset: -4px;
}
ha-button::part(label) {
display: flex;
}
:host([selected]) ha-button::after {
opacity: 0.1;
}
@media (hover: hover) {
:host(:hover:not([disabled])) ha-button::after {
opacity: 0.1;
}
}
`;
}

View File

@@ -9,7 +9,6 @@ import type { HomeAssistant } from "../types";
import "./ha-dropdown";
import "./ha-dropdown-item";
import "./ha-icon-button";
import "./ha-md-divider";
import "./ha-svg-icon";
import "./ha-tooltip";

View File

@@ -182,7 +182,7 @@ export class HaLabelPicker extends SubscribeMixin(LitElement) {
{
id: ADD_NEW_ID + searchString,
primary: this.hass.localize(
"ui.components.label-picker.add_new_sugestion",
"ui.components.label-picker.add_new_suggestion",
{
name: searchString,
}

View File

@@ -191,7 +191,6 @@ export class HaLanguagePicker extends LitElement {
static styles = css`
ha-generic-picker {
width: 100%;
min-width: 200px;
display: block;
}
`;

View File

@@ -84,13 +84,11 @@ export class HaMarkdown extends LitElement {
ha-markdown-element > :is(ol, ul) {
padding-inline-start: var(--markdown-list-indent, revert);
}
li {
&:has(input[type="checkbox"]) {
list-style: none;
& > input[type="checkbox"] {
margin-left: 0;
}
}
li:has(input[type="checkbox"]) {
list-style: none;
}
li:has(input[type="checkbox"]) > input[type="checkbox"] {
margin-left: 0;
}
svg {
background-color: var(--markdown-svg-background-color, none);
@@ -137,10 +135,10 @@ export class HaMarkdown extends LitElement {
--markdown-table-border-width: 0;
--markdown-table-padding-inline: 0;
--markdown-table-padding-block: 0;
th,
td {
vertical-align: middle;
}
}
table[role="presentation"] th,
table[role="presentation"] td {
vertical-align: middle;
}
table[role="presentation"] td[valign="top"],
table[role="presentation"] th[valign="top"] {

View File

@@ -1,22 +0,0 @@
import { Divider } from "@material/web/divider/internal/divider";
import { styles } from "@material/web/divider/internal/divider-styles";
import { css } from "lit";
import { customElement } from "lit/decorators";
@customElement("ha-md-divider")
export class HaMdDivider extends Divider {
static override styles = [
styles,
css`
:host {
--md-divider-color: var(--divider-color);
}
`,
];
}
declare global {
interface HTMLElementTagNameMap {
"ha-md-divider": HaMdDivider;
}
}

View File

@@ -1,52 +0,0 @@
import { MenuItemEl } from "@material/web/menu/internal/menuitem/menu-item";
import { styles } from "@material/web/menu/internal/menuitem/menu-item-styles";
import { css } from "lit";
import { customElement, property } from "lit/decorators";
@customElement("ha-md-menu-item")
export class HaMdMenuItem extends MenuItemEl {
@property({ attribute: false }) clickAction?: (item?: HTMLElement) => void;
static override styles = [
styles,
css`
:host {
--ha-icon-display: block;
--md-sys-color-primary: var(--primary-text-color);
--md-sys-color-on-primary: var(--primary-text-color);
--md-sys-color-secondary: var(--secondary-text-color);
--md-sys-color-surface: var(--card-background-color);
--md-sys-color-on-surface: var(--primary-text-color);
--md-sys-color-on-surface-variant: var(--secondary-text-color);
--md-sys-color-secondary-container: rgba(
var(--rgb-primary-color),
0.15
);
--md-sys-color-on-secondary-container: var(--text-primary-color);
--mdc-icon-size: 16px;
--md-sys-color-on-primary-container: var(--primary-text-color);
--md-sys-color-on-secondary-container: var(--primary-text-color);
--md-menu-item-label-text-font: Roboto, sans-serif;
}
:host(.warning) {
--md-menu-item-label-text-color: var(--error-color);
--md-menu-item-leading-icon-color: var(--error-color);
}
::slotted([slot="headline"]) {
text-wrap: nowrap;
}
:host([disabled]) {
opacity: 1;
--md-menu-item-label-text-color: var(--disabled-text-color);
--md-menu-item-leading-icon-color: var(--disabled-text-color);
}
`,
];
}
declare global {
interface HTMLElementTagNameMap {
"ha-md-menu-item": HaMdMenuItem;
}
}

View File

@@ -1,47 +0,0 @@
import { Menu } from "@material/web/menu/internal/menu";
import { styles } from "@material/web/menu/internal/menu-styles";
import type { CloseMenuEvent } from "@material/web/menu/menu";
import {
CloseReason,
KeydownCloseKey,
} from "@material/web/menu/internal/controllers/shared";
import { css } from "lit";
import { customElement } from "lit/decorators";
import type { HaMdMenuItem } from "./ha-md-menu-item";
@customElement("ha-md-menu")
export class HaMdMenu extends Menu {
connectedCallback(): void {
super.connectedCallback();
this.addEventListener("close-menu", this._handleCloseMenu);
}
private _handleCloseMenu(ev: CloseMenuEvent) {
if (
ev.detail.reason.kind === CloseReason.KEYDOWN &&
ev.detail.reason.key === KeydownCloseKey.ESCAPE
) {
return;
}
(ev.detail.initiator as HaMdMenuItem).clickAction?.(ev.detail.initiator);
}
static override styles = [
styles,
css`
:host {
--md-sys-color-surface-container: var(--card-background-color);
}
`,
];
}
declare global {
interface HTMLElementTagNameMap {
"ha-md-menu": HaMdMenu;
}
interface HTMLElementEventMap {
"close-menu": CloseMenuEvent;
}
}

View File

@@ -1,27 +0,0 @@
import { SelectOptionEl } from "@material/web/select/internal/selectoption/select-option";
import { styles } from "@material/web/menu/internal/menuitem/menu-item-styles";
import { css } from "lit";
import { customElement } from "lit/decorators";
@customElement("ha-md-select-option")
export class HaMdSelectOption extends SelectOptionEl {
static override styles = [
styles,
css`
:host {
--ha-icon-display: block;
--md-sys-color-primary: var(--primary-text-color);
--md-sys-color-secondary: var(--secondary-text-color);
--md-sys-color-surface: var(--card-background-color);
--md-sys-color-on-surface: var(--primary-text-color);
--md-sys-color-on-surface-variant: var(--secondary-text-color);
}
`,
];
}
declare global {
interface HTMLElementTagNameMap {
"ha-md-select-option": HaMdSelectOption;
}
}

View File

@@ -1,36 +0,0 @@
import { FilledSelect } from "@material/web/select/internal/filled-select";
import { styles as sharedStyles } from "@material/web/select/internal/shared-styles";
import { styles } from "@material/web/select/internal/filled-select-styles";
import { css } from "lit";
import { customElement } from "lit/decorators";
@customElement("ha-md-select")
export class HaMdSelect extends FilledSelect {
static override styles = [
sharedStyles,
styles,
css`
:host {
--ha-icon-display: block;
--md-sys-color-primary: var(--primary-text-color);
--md-sys-color-secondary: var(--secondary-text-color);
--md-sys-color-surface: var(--card-background-color);
--md-sys-color-on-surface-variant: var(--secondary-text-color);
--md-sys-color-surface-container-highest: var(--input-fill-color);
--md-sys-color-on-surface: var(--input-ink-color);
--md-sys-color-surface-container: var(--input-fill-color);
--md-sys-color-on-secondary-container: var(--primary-text-color);
--md-sys-color-secondary-container: var(--input-fill-color);
--md-menu-container-color: var(--card-background-color);
}
`,
];
}
declare global {
interface HTMLElementTagNameMap {
"ha-md-select": HaMdSelect;
}
}

View File

@@ -1,5 +1,5 @@
import Fuse from "fuse.js";
import { mdiDevices, mdiTextureBox } from "@mdi/js";
import { mdiDevices, mdiPlus, mdiTextureBox } from "@mdi/js";
import { html, LitElement, nothing, type PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
@@ -237,6 +237,22 @@ export class HaNavigationPicker extends LitElement {
addGroup("views", views);
addGroup("other_routes", otherRoutes);
if (
searchString &&
!this._navigationItems.some((navItem) => navItem.id === searchString)
) {
items.push({
id: searchString,
primary: this.hass.localize(
"ui.components.navigation-picker.add_custom_path"
),
secondary: `"${searchString}"`,
icon_path: mdiPlus,
sorting_label: searchString,
group: "other_routes",
});
}
return items;
};

View File

@@ -196,7 +196,7 @@ export class HaPasswordField extends LitElement {
right: 8px;
inset-inline-start: initial;
inset-inline-end: 8px;
--mdc-icon-button-size: 40px;
--ha-icon-button-size: 40px;
--mdc-icon-size: 20px;
color: var(--secondary-text-color);
direction: var(--direction);

View File

@@ -362,6 +362,18 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
const additionalItems = this._getAdditionalItems();
items.push(...additionalItems);
if (this.allowCustomValue && this._search) {
items.push({
id: this._search,
primary:
this.customValueLabel ??
this.hass?.localize("ui.components.combo-box.add_custom_item") ??
"Add custom item",
secondary: `"${this._search}"`,
icon_path: mdiPlus,
});
}
if (this.mode === "dialog") {
items.push({ id: PADDING_ID, primary: "" }); // padding for safe area inset
}
@@ -784,7 +796,7 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
:host {
display: flex;
flex-direction: column;
padding-top: var(--ha-space-3);
padding-top: var(--ha-space-4);
flex: 1;
}

View File

@@ -126,6 +126,7 @@ export class HaPickerField extends PickerMixin(LitElement) {
);
}
ha-combo-box-item {
position: relative;
background-color: var(--mdc-text-field-fill-color, whitesmoke);
border-radius: var(--ha-border-radius-sm);
border-end-end-radius: 0;
@@ -184,8 +185,8 @@ export class HaPickerField extends PickerMixin(LitElement) {
.clear {
margin: 0 -8px;
--mdc-icon-button-size: 32px;
--mdc-icon-size: 20px;
--ha-icon-button-size: 32px;
--ha-icon-button-padding-inline: var(--ha-space-1);
}
.arrow {
--mdc-icon-size: 20px;

View File

@@ -135,9 +135,7 @@ class HaQrScanner extends LitElement {
(camera) => html`
<ha-dropdown-item
.value=${camera.id}
class=${this._selectedCamera === camera.id
? "selected"
: ""}
.selected=${this._selectedCamera === camera.id}
>
${camera.label}
</ha-dropdown-item>
@@ -380,9 +378,6 @@ class HaQrScanner extends LitElement {
color: white;
border-radius: var(--ha-border-radius-circle);
}
ha-dropdown-item.selected {
font-weight: var(--ha-font-weight-bold);
}
.row {
display: flex;
align-items: center;

View File

@@ -61,6 +61,7 @@ class HaSegmentedBar extends LitElement {
: html`
<ha-tooltip for="segment-${index}" placement="top">
${segment.label}
(${((segment.value / totalValue) * 100).toFixed(1)}%)
</ha-tooltip>
`}
<div

View File

@@ -5,6 +5,7 @@ import memoizeOne from "memoize-one";
import { fireEvent } from "../common/dom/fire_event";
import "./ha-dropdown";
import "./ha-dropdown-item";
import "./ha-input-helper-text";
import "./ha-picker-field";
import type { HaPickerField } from "./ha-picker-field";
import "./ha-svg-icon";
@@ -75,7 +76,7 @@ export class HaSelect extends LitElement {
protected override render() {
if (this.disabled) {
return this._renderField();
return html`${this._renderField()}${this._renderHelper()}`;
}
return html`
@@ -94,10 +95,8 @@ export class HaSelect extends LitElement {
.disabled=${typeof option === "string"
? false
: (option.disabled ?? false)}
class=${this.value ===
(typeof option === "string" ? option : option.value)
? "selected"
: ""}
.selected=${this.value ===
(typeof option === "string" ? option : option.value)}
>
${option.iconPath
? html`<ha-svg-icon
@@ -118,6 +117,7 @@ export class HaSelect extends LitElement {
)
: html`<slot></slot>`}
</ha-dropdown>
${this._renderHelper()}
`;
}
@@ -133,7 +133,6 @@ export class HaSelect extends LitElement {
aria-label=${ifDefined(this.label)}
@clear=${this._clearValue}
.label=${this.label}
.helper=${this.helper}
.value=${valueLabel}
.required=${this.required}
.disabled=${this.disabled}
@@ -146,6 +145,14 @@ export class HaSelect extends LitElement {
`;
}
private _renderHelper() {
return this.helper
? html`<ha-input-helper-text .disabled=${this.disabled}
>${this.helper}</ha-input-helper-text
>`
: nothing;
}
private _handleSelect(ev: CustomEvent<{ item: { value: string } }>) {
ev.stopPropagation();
const value = ev.detail.item.value;
@@ -182,10 +189,6 @@ export class HaSelect extends LitElement {
ha-picker-field.opened {
--mdc-text-field-idle-line-color: var(--primary-color);
}
ha-dropdown-item.selected:hover {
background-color: var(--ha-color-fill-primary-quiet-hover);
}
ha-dropdown-item .content {
display: flex;
gap: var(--ha-space-1);
@@ -201,12 +204,9 @@ export class HaSelect extends LitElement {
min-width: var(--select-menu-width);
}
:host ::slotted(ha-dropdown-item.selected),
ha-dropdown-item.selected {
font-weight: var(--ha-font-weight-medium);
color: var(--primary-color);
background-color: var(--ha-color-fill-primary-quiet-resting);
--icon-primary-color: var(--primary-color);
ha-input-helper-text {
display: block;
margin: var(--ha-space-2) 0 0;
}
`;
}

View File

@@ -66,6 +66,7 @@ export class HaTimeDuration extends LitElement {
.enableDay=${this.selector.duration?.enable_day}
.enableMillisecond=${this.selector.duration?.enable_millisecond}
.allowNegative=${this.selector.duration?.allow_negative}
.enableSecond=${this.selector.duration?.enable_second ?? true}
></ha-duration-input>
`;
}

View File

@@ -64,7 +64,7 @@ export class HaEntitySelector extends LitElement {
if (!this.selector.entity?.multiple) {
return html`<ha-entity-picker
.hass=${this.hass}
.value=${this.value}
.value=${typeof this.value === "string" ? this.value : ""}
.label=${this.label}
.placeholder=${this.placeholder}
.helper=${this.helper}

View File

@@ -13,7 +13,11 @@ import {
} from "../../data/media-player";
import type { MediaSelector, MediaSelectorValue } from "../../data/selector";
import type { HomeAssistant } from "../../types";
import { brandsUrl, extractDomainFromBrandUrl } from "../../util/brands-url";
import {
brandsUrl,
extractDomainFromBrandUrl,
isBrandUrl,
} from "../../util/brands-url";
import "../ha-alert";
import "../ha-form/ha-form";
import type { SchemaUnion } from "../ha-form/types";
@@ -72,16 +76,7 @@ export class HaMediaSelector extends LitElement {
if (thumbnail === oldThumbnail) {
return;
}
if (thumbnail && thumbnail.startsWith("/")) {
this._thumbnailUrl = undefined;
// Thumbnails served by local API require authentication
getSignedPath(this.hass, thumbnail).then((signedPath) => {
this._thumbnailUrl = signedPath.path;
});
} else if (
thumbnail &&
thumbnail.startsWith("https://brands.home-assistant.io")
) {
if (thumbnail && isBrandUrl(thumbnail)) {
// The backend is not aware of the theme used by the users,
// so we rewrite the URL to show a proper icon
this._thumbnailUrl = brandsUrl({
@@ -89,6 +84,12 @@ export class HaMediaSelector extends LitElement {
type: "icon",
darkOptimized: this.hass.themes?.darkMode,
});
} else if (thumbnail && thumbnail.startsWith("/")) {
this._thumbnailUrl = undefined;
// Thumbnails served by local API require authentication
getSignedPath(this.hass, thumbnail).then((signedPath) => {
this._thumbnailUrl = signedPath.path;
});
} else {
this._thumbnailUrl = thumbnail;
}

View File

@@ -93,7 +93,7 @@ export class HaSelectSelector extends LitElement {
<ha-select-box
.options=${options}
.value=${this.value as string | undefined}
@value-changed=${this._valueChanged}
@value-changed=${this._selectChanged}
.maxColumns=${this.selector.select?.box_max_columns}
.hass=${this.hass}
></ha-select-box>
@@ -120,7 +120,7 @@ export class HaSelectSelector extends LitElement {
.checked=${item.value === this.value}
.value=${item.value}
.disabled=${item.disabled || this.disabled}
@change=${this._valueChanged}
@change=${this._radioChanged}
></ha-radio>
</ha-formfield>
`
@@ -221,7 +221,7 @@ export class HaSelectSelector extends LitElement {
.disabled=${this.disabled}
.required=${this.required}
.getItems=${this._getItems(options)}
.value=${this.value as string | undefined}
.value=${typeof this.value === "string" ? this.value : undefined}
@value-changed=${this._comboBoxValueChanged}
allow-custom-value
></ha-generic-picker>
@@ -231,12 +231,12 @@ export class HaSelectSelector extends LitElement {
return html`
<ha-select
.label=${this.label ?? ""}
.value=${(this.value as string) ?? ""}
.value=${typeof this.value === "string" ? this.value : ""}
.helper=${this.helper ?? ""}
.disabled=${this.disabled}
.required=${this.required}
clearable
@selected=${this._valueChanged}
@selected=${this._selectChanged}
.options=${options}
>
</ha-select>
@@ -282,16 +282,24 @@ export class HaSelectSelector extends LitElement {
);
}
private _valueChanged(ev) {
private _radioChanged(ev) {
ev.stopPropagation();
this._valueChanged(ev);
}
private _selectChanged(ev) {
ev.stopPropagation();
// Additional handling for reset of select elements
if (ev.detail?.value === undefined && this.value !== undefined) {
fireEvent(this, "value-changed", {
value: undefined,
});
return;
}
this._valueChanged(ev);
}
private _valueChanged(ev) {
const value = ev.detail?.value || ev.target.value;
if (this.disabled || value === undefined || value === (this.value ?? "")) {
return;

View File

@@ -64,6 +64,15 @@ const SELECTOR_SCHEMAS = {
name: "enable_millisecond",
selector: { boolean: {} },
},
{
name: "enable_second",
default: true,
selector: { boolean: {} },
},
{
name: "allow_negative",
selector: { boolean: {} },
},
] as const,
entity: [
{

View File

@@ -141,7 +141,7 @@ export class HaTextSelector extends LitElement {
right: 8px;
inset-inline-start: initial;
inset-inline-end: 8px;
--mdc-icon-button-size: 40px;
--ha-icon-button-size: 40px;
--mdc-icon-size: 20px;
color: var(--secondary-text-color);
direction: var(--direction);

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