Compare commits

..

98 Commits

Author SHA1 Message Date
Paul Bottein
962e941ec9 Merge branch 'master' into dev
# Conflicts:
#	build-scripts/gulp/download-translations.js
#	pyproject.toml
#	src/components/chart/ha-network-graph.ts
#	src/components/date-picker/date-range-picker.ts
#	src/components/date-picker/styles.ts
#	src/components/entity/ha-entity-name-picker.ts
#	src/components/ha-gauge.ts
#	src/components/ha-selector/ha-selector-numeric-threshold.ts
#	src/components/ha-toast.ts
#	src/components/input/ha-input.ts
#	src/data/icons.ts
#	src/dialogs/make-dialog-manager.ts
#	src/panels/config/automation/action/ha-automation-action-row.ts
#	src/panels/config/automation/add-automation-element-dialog.ts
#	src/panels/config/automation/condition/ha-automation-condition-row.ts
#	src/panels/config/automation/condition/types/ha-automation-condition-platform.ts
#	src/panels/config/automation/target/ha-automation-row-targets.ts
#	src/panels/config/automation/trigger/ha-automation-trigger-row.ts
#	src/panels/config/automation/trigger/types/ha-automation-trigger-platform.ts
#	src/panels/config/developer-tools/action/developer-tools-action.ts
#	src/panels/config/devices/ha-config-device-page.ts
#	src/panels/config/entities/entity-registry-settings-editor.ts
#	src/panels/config/ha-panel-config.ts
#	src/panels/config/integrations/integration-panels/zha/zha-device-card.ts
#	src/panels/lovelace/card-features/hui-trend-graph-card-feature.ts
#	src/panels/lovelace/cards/hui-gauge-card.ts
#	src/panels/lovelace/cards/hui-history-graph-card.ts
#	src/panels/lovelace/cards/hui-map-card.ts
#	src/panels/lovelace/sections/hui-section.ts
#	src/panels/lovelace/views/hui-view-footer.ts
#	src/state/quick-bar-mixin.ts
#	yarn.lock
2026-04-29 16:30:58 +02:00
Paul Bottein
31495b2de9 Bumped version to 20260429.0 2026-04-29 16:17:10 +02:00
Bram Kragten
f71dcaeac1 Add automation behavior selector (#30322)
* Add automation behavior selector

* Use mode option instead

* update design

* remove label

* 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>

---------

Co-authored-by: Wendelin <w@pe8.at>
Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com>
Co-authored-by: Norbert Rittel <norbert@rittel.de>
2026-04-29 16:12:17 +02:00
Wendelin
26b1bfbe20 Automations: Flatten triggers/conditions list in pickers (#51785)
* Flat add trigger condition collections

* fix list order

Co-authored-by: Copilot <copilot@github.com>

---------

Co-authored-by: Copilot <copilot@github.com>
2026-04-29 16:11:42 +02:00
Bram Kragten
b95fce5f24 Bumped version to 20260325.8 2026-04-24 17:26:41 +02:00
karwosts
f6abbe80a2 Remove spurious map in original-states view (#51696) 2026-04-24 17:24:44 +02:00
Wendelin
71301ef5be Remove allow-mode-change in quick search (#51634)
Remove allow-mode-change attribute from ha-adaptive-dialog in QuickBar component
2026-04-24 17:24:43 +02:00
ildar170975
4af618ac6f Gauge card: fix a height in Horizontal stack (#51626)
remove styles for ":host"
2026-04-24 17:24:42 +02:00
Simon Lamon
5a21ef67cd Adjust gauge again (#51613) 2026-04-24 17:24:41 +02:00
karwosts
251b9a1b94 Fix gauge missing label on load (#51584) 2026-04-24 17:24:40 +02:00
karwosts
cadaa47bf0 Fix gauge segmentLabels, cleanup sorting (#51583) 2026-04-24 17:24:39 +02:00
Jan Čermák
7ec864dc6d Fix rendering of select with multiple options in SupervisorAppConfig (#51585)
If the app config contains a schema field like this one:

```
privileges:
  - "list(ALTER|CREATE|...|UPDATE)?"
```

it was rendered incorrectly as a drop-down where only one item can be
selected - but this is wrong because of the preceding `-` denoting it
should be a list containing the listed values. Supervisor translated
this to an entry of type `select` with `multiple: true`. The `multiple`
flag wasn't passed along, with the flag set the field renders as
expected.

Fixes #51533
2026-04-24 17:08:12 +02:00
Wendelin
680ceb73e9 ha-select allow number values (#51564) 2026-04-24 17:08:11 +02:00
Wendelin
ab31771055 Improve view footer card visibility handling (#51549) 2026-04-24 17:08:10 +02:00
Wendelin
07b9a6e287 Hide footer if card is not visible (#51544)
* Fix footer visibility logic in render method

* use card-visibility-changed
2026-04-24 17:08:09 +02:00
Wendelin
be2c90cd1c Fix register admin quick search shortcuts (#51540) 2026-04-24 17:08:08 +02:00
Raphael Hehl
2af4ff7c8f Fix history/sensor cards stuck loading after backend restart (#51531)
* Fix history/sensor cards stuck loading after backend restart

- Add { resubscribe: false } to history subscriptions to prevent
  corrupt HistoryStream state on auto-resubscription
- Add connection-status handlers to re-subscribe on reconnect
- Add sentinel pattern to prevent re-entrant async subscriptions
- Add shouldUpdate/updated retry when components become available
- Clear sensor device classes cache on WS error
- Clear _error on reconnect so cards can retry
- Add .catch() on unsubscribe to handle dead subscriptions

* Fix type annotation for callWS in getSensorNumericDeviceClasses

* Address review: type connection-status handlers, add reconnect to history panel

- Use HASSDomEvent<ConnectionStatus> instead of (ev as CustomEvent).detail
  for proper type safety on all connection-status handlers
- Add connection-status handler to ha-panel-history so it re-subscribes
  after backend restart (addresses concern about resubscribe: false)

* Address review: sentinel pattern, reconnect handling, stale data reset

- Add sentinel pattern to ha-more-info-history, ha-panel-history,
  hui-history-graph-card to prevent re-entrant subscription races
- Refactor hui-trend-graph-card-feature from SubscribeMixin to manual
  subscription management with connection-status reconnect support
- Reset stale history/statistics data on reconnect in
  hui-history-graph-card and hui-map-card before re-subscribing
- Wrap fetchStatistics and getSensorNumericDeviceClasses calls in
  ha-panel-history with try/catch to handle errors gracefully
- Chain .catch directly on subscribeHistoryStatesTimeWindow in
  hui-trend-graph-card-feature to avoid detached-promise race condition

* Centralize history stream reconnect handling in data layer

Move the reconnect logic from every consumer into `subscribeHistoryStream`
in data/history.ts. The helper listens to the connection's `ready` event
itself, and on reconnect creates a fresh `HistoryStream` and rebuilds
params (so `start_time` for the time-window variant is re-anchored to
"now"). `resubscribe: false` stays as an internal implementation detail.

Removes the duplicated `_handleConnectionStatus` boilerplate and
`connection-status` window listeners from all six history consumers.

* Render subscription errors and make _error reactive

`_error` was declared as a plain string field in hui-graph-header-footer
and ha-more-info-history (non-reactive) and typed as Error/string while
being assigned the WS error object. hui-trend-graph-card-feature had it
reactive but never rendered it.

Align all three with the hui-history-graph-card pattern: reactive
`{ code, message }` and a user-visible error branch in render(). Without
this, a failed subscription would leave the component stuck on a spinner
forever.

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2026-04-24 17:08:07 +02:00
Bram Kragten
f7e92b484a Bumped version to 20260325.7 2026-04-10 17:44:15 +02:00
Aidan Timson
9fab7bafdb Allow quick search for non-admins, while hiding inaccessible areas (#51456)
Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com>
2026-04-10 17:40:25 +02:00
Petar Petrov
0dabb02007 Fix dialog show animation broken by connectedCallback _open sync (#51450) 2026-04-10 17:38:06 +02:00
Petar Petrov
5b73e86786 Fix toast race condition causing stuck notifications (#51447) 2026-04-10 17:38:05 +02:00
Timothy
144d7c5c3f Android externalAppV2 (#51446) 2026-04-10 17:37:08 +02:00
Petar Petrov
8b396dc640 Preserve browser back/forward keyboard shortcuts in tab group (#51439) 2026-04-10 17:33:34 +02:00
Aidan Timson
9bf48d30ab Handle lazy loaded entity registry when editing scripts from more info (#51438)
* Handle lazy loaded entity registry when editing scripts from more info

* Remove extra check

* Fix type of mixin
2026-04-10 17:33:33 +02:00
GeorgeZ83
35fee46f5b Fix media browser dialog window (#51423)
Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com>
2026-04-10 17:33:32 +02:00
Niccolò Betto
9ac6636029 Fix code input dialog undefined value concatenation (#51399) 2026-04-10 17:33:31 +02:00
Simon Lamon
136462114d Increase gauge thickness for accessibility reasons (#51382)
Increase thickness for accessability reasons
2026-04-10 17:33:30 +02:00
Bram Kragten
cf542197e0 Bumped version to 20260325.6 2026-04-03 13:01:51 +02:00
Bram Kragten
5c2627624a Always add options object to triggers and conditions (#51394) 2026-04-03 13:01:42 +02:00
Petar Petrov
698ded9d85 Only use inflight map for pending fragment translation loads (#51393) 2026-04-03 13:01:42 +02:00
Tim Ittermann
9a7fb96873 fix: null value error on ha-form-integer (#51385)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2026-04-03 13:01:41 +02:00
Petar Petrov
204c5b5e14 Fix fragment translation race condition returning stale localize (#51381) 2026-04-03 13:01:40 +02:00
Petar Petrov
8ea3acfa98 Load energy translations in dashboard strategy before generating view titles (#51376) 2026-04-03 13:01:39 +02:00
Wendelin
306739773e Fix login on legacy browsers (#51373) 2026-04-03 13:01:38 +02:00
Petar Petrov
8b3fa3adac Fix next_flow dialog closing immediately after rendering (#51369) 2026-04-03 13:01:37 +02:00
Petar Petrov
37a1d59a24 Fix statistics-graph card not rendering self-imported stats (#51367) 2026-04-03 13:01:37 +02:00
Trevin Chow
6812884e00 Guard against orphaned label references in device list (#51359) 2026-04-03 13:01:36 +02:00
Trevin Chow
bf7ef1f7ae Fix TypeError in Voice Assistants expose page with manual entity filters (#51357) 2026-04-03 13:01:35 +02:00
Paul Bottein
fe57f601ba Fix device page entity names not refreshing after device rename (#51355) 2026-04-03 13:01:34 +02:00
Wendelin
c89d478440 Fix input hint height (#51351) 2026-04-03 13:01:33 +02:00
Petar Petrov
fa27d26e5f Fix history-graph card not showing first value (#51350) 2026-04-03 13:01:32 +02:00
Wendelin
18f411ef53 Fix generic picker filter section padding (#51334)
Fix padding in picker section for improved layout
2026-04-03 13:01:31 +02:00
Wendelin
24826e92f0 Fix picker search padding (#51331) 2026-04-03 13:01:30 +02:00
Wendelin
ea9d369d88 Fix date input field shrink (#51330) 2026-04-03 13:01:29 +02:00
Bram Kragten
a9b026d0ef Bumped version to 20260325.5 2026-04-01 11:15:10 +02:00
Petar Petrov
35339906ec Fix layout of compare card in water/gas views (#51329) 2026-04-01 11:14:50 +02:00
Wendelin
ce23f716cc Improve dialog open logic (#51328) 2026-04-01 11:14:49 +02:00
Petar Petrov
aaf8fa199f Await energy translation fragment before generating dashboard strategy (#51327) 2026-04-01 11:14:48 +02:00
Aidan Timson
fba430d507 Fix target item loading error (#51326) 2026-04-01 11:14:47 +02:00
Petar Petrov
59361cbd38 Fix ZHA device count not including devices without entities (#51322) 2026-04-01 11:14:46 +02:00
Petar Petrov
b558117d8c Use ha-card-border-color for integration cards instead of divider-color (#51321) 2026-04-01 11:14:45 +02:00
Petar Petrov
a7c8347751 Fix Fill example data inserting incorrect datetime format (#51320) 2026-04-01 11:14:44 +02:00
Wendelin
31ca9c849a Remove target description (#51315) 2026-04-01 11:14:43 +02:00
Bram Kragten
6252d7e8f5 Bumped version to 20260325.4 2026-03-31 15:36:46 +02:00
Bram Kragten
f42986adf6 Make translation downloading async (#51314) 2026-03-31 15:36:31 +02:00
Bram Kragten
9e70ea3723 Bumped version to 20260325.3 2026-03-31 14:58:38 +02:00
Bram Kragten
de3b7bf513 Fix has target check for actions (#51309) 2026-03-31 14:58:19 +02:00
Petar Petrov
2c5f491c9e Use boundaryFilter data zoom mode only for line charts (#51307) 2026-03-31 14:58:18 +02:00
Wendelin
1ef13c5100 Fix automation add TCA dialog sometimes not opening (#51306) 2026-03-31 14:58:17 +02:00
Aidan Timson
c166335aca Fix above/below numeric state entity formatting (#51298) 2026-03-31 14:51:11 +02:00
Petar Petrov
c64ec21eca Fix x-axis labels for statistics graph month/year periods (#51295) 2026-03-31 14:51:10 +02:00
Norbert Rittel
8d62056f4a Change picker descriptions of triggers to match new style (#51294) 2026-03-31 14:51:09 +02:00
Bram Kragten
62e73608b6 Triggers/conditions Add usage and grouping to new multi domains (#51287) 2026-03-31 14:51:08 +02:00
Wendelin
aa66d8891c Improve date-range-picker mobile presets (#51285) 2026-03-31 14:51:07 +02:00
Paul Bottein
494a96c635 Hide section when all cards are hidden (#51281) 2026-03-31 14:51:06 +02:00
Petar Petrov
36d77f54ce Disable physics by default for large networks (#51277) 2026-03-31 14:51:05 +02:00
Wendelin
12fec9f580 Fix ha-dropdown z-index for legacy browsers (#51276) 2026-03-31 14:51:04 +02:00
Bram Kragten
5f1f55448a Numeric threshold selector: remove duplicate uom from input (#51275) 2026-03-31 14:51:03 +02:00
Paul Bottein
837e345ecf Reduce heading button badge font size and fix alignement (#51274)
Title: Reduce heading button badge font size and fix alignement
2026-03-31 14:51:02 +02:00
Wendelin
0929d7d18a Remove mobile-specific styles for date-range-picker (#51273)
Remove mobile-specific styles for date-picker component
2026-03-31 14:51:01 +02:00
Aidan Timson
70991d3c1e Limit ha-toast width to window, refactor CSS (#51272)
* Limit `ha-toast` width to window and use safe width

* Query assigned slots to stop actions display

* Constrain max-width

* Increase start/end padding
2026-03-31 14:51:00 +02:00
Wendelin
82e5bd62a1 Fix time input background (#51270)
Fix input color tokens
2026-03-31 14:50:59 +02:00
Wendelin
b8adf4e866 Fix date-range-picker preset selection (#51269) 2026-03-31 14:50:58 +02:00
Tom Carpenter
111be984e0 Add date range picker time validation (#51267)
* Fix base time inputs reportValidity() function

The queryAll selector returns a NodeList not not an array. Need to spread it to an array before we can use every().

* Validate the date range picker time inputs

Enable auto validation to get the nice red underline on invalid values, and then check validity before accepting the input.

* Fix automatic 24hr value conversion in AM/PM format

When using AM/PM, entering a 24 hour value will automatically convert the first time. For example 15 will become 3. However if you then enter 15 again it will stay as 15 and not update.
To fix this, make sure we trigger an update of the input field once the current update cycle is complete.

* Validate time inputs on save not value update

In the value changed callback, the update 24->12hr input correction will not have been updated and therefore they will report invalid.
2026-03-31 14:50:57 +02:00
Tom Carpenter
78a2cbb532 Fix new date-range-picker rendering on small screens (#51257) 2026-03-31 14:50:56 +02:00
ildar170975
34b09b140b Map card editor: use context in attribute selector (#30393)
use context in attribute selector
2026-03-31 14:50:55 +02:00
Simon Lamon
f173f901c5 Gauge improvements (#30368)
* Gauge last improvements

* Change needle

* Fixup

* Feedback comments

* Update src/components/ha-gauge.ts

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2026-03-31 14:50:55 +02:00
Paul Bottein
ebb6ac8d8b Bumped version to 20260325.2 2026-03-27 22:09:10 +01:00
Wendelin
abe214a33a Fix picker field disabled background (#30385) 2026-03-27 22:08:51 +01:00
Paul Bottein
248332ae27 Revert entity naming change (#30384) 2026-03-27 22:08:50 +01:00
Wendelin
82fc2fccdc Automation add TCA: Fix classMap usage (#30380) 2026-03-27 22:08:49 +01:00
Marcin Bauer
c8f30a7ee4 Use dedicated tab copy in automation add dialogs (#30378)
Co-authored-by: Wendelin <w@pe8.at>
2026-03-27 22:08:48 +01:00
Norbert Rittel
77f48d91cd Shorten collection_key_description to fit available space (#30376) 2026-03-27 22:08:47 +01:00
Paul Bottein
caa707a7b1 Only display entity name instead of friendly name in state info (#30365) 2026-03-27 22:08:46 +01:00
Petar Petrov
0bed0fa37e Fix negative currency display on sensor card (#30359) 2026-03-27 22:08:46 +01:00
Bram Kragten
5b6309d984 Numeric threshold selector fixes (#30350)
* Update numeric threshold

* Update ha-selector-numeric-threshold.ts
2026-03-27 22:08:45 +01:00
Aidan Timson
264818bc70 Fix floating ha-toast (#30344) 2026-03-27 22:08:44 +01:00
Bram Kragten
d664ab6836 Bumped version to 20260325.1 2026-03-26 17:08:11 +01:00
Bram Kragten
a6c4184054 Replace ua-parser-js with simple regexs (#30355) 2026-03-26 17:07:45 +01:00
karwosts
cb6985eb7c Stabilize map colors (#30354) 2026-03-26 17:07:44 +01:00
Bram Kragten
d466ab63bd Add target error badge if target is missing (#30352)
* Add target error badge if target is missing

* Don't show for newly added items
2026-03-26 17:07:40 +01:00
Paul Bottein
1132cdb364 Replace computeLovelaceEntityName with hass.formatEntityName (#30351) 2026-03-26 17:07:39 +01:00
Paul Bottein
0f9d48a03d Use hardcoded label for temperature and humidity sensor in climate dashboard (#30348)
* Only use entity name for climate view sensors

* Use hardcoded text
2026-03-26 17:07:38 +01:00
Paul Bottein
7e085d9b08 Fix stack card scrollbar clipping box-shadows (#30346)
* Fix stack card scrollbar clipping box-shadows

* Remove grid options

* Remove scrollbar
2026-03-26 17:07:37 +01:00
Timothy
1a62c7296c Set tap highlight color to transparent for button (#30340) 2026-03-26 17:07:36 +01:00
Petar Petrov
be1921229c Fix energy pie chart legend showing raw data instead of formatted values (#30339) 2026-03-26 17:07:34 +01:00
Paul Bottein
640558ad35 Add composed/text mode toggle to entity name picker (#30337) 2026-03-26 17:07:33 +01:00
sir-Unknown
99636c9719 Fix calendar event description not preserving line breaks (#30329)
Add `white-space: pre-line` to the event description style so that
newlines in the calendar event description are rendered correctly
instead of being collapsed into a single line.
2026-03-26 17:07:32 +01:00
19 changed files with 456 additions and 123 deletions

View File

@@ -0,0 +1,17 @@
<svg width="268" height="28" viewBox="0 0 268 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="88" height="28" rx="8" fill="white"/>
<rect x="0.5" y="0.5" width="87" height="27" rx="7.5" stroke="black" stroke-opacity="0.12"/>
<rect x="8" y="8" width="12" height="12" rx="3" fill="black" fill-opacity="0.12"/>
<rect x="28" y="8" width="12" height="12" rx="3" fill="#009AC7"/>
<rect x="48" y="8" width="12" height="12" rx="3" fill="black" fill-opacity="0.12"/>
<rect x="68" y="8" width="12" height="12" rx="3" fill="black" fill-opacity="0.12"/>
<path d="M107.667 18.1167V14.7833H100.233L100.208 13.1083H107.667V9.78333L111.833 13.95L107.667 18.1167Z" fill="#B1B1B1"/>
<rect x="124" width="88" height="28" rx="8" fill="white"/>
<rect x="124.5" y="0.5" width="87" height="27" rx="7.5" stroke="black" stroke-opacity="0.12"/>
<rect x="132" y="8" width="12" height="12" rx="3" fill="black" fill-opacity="0.12"/>
<rect x="152" y="8" width="12" height="12" rx="3" fill="#009AC7"/>
<rect x="172" y="8" width="12" height="12" rx="3" fill="black" fill-opacity="0.12"/>
<rect x="192" y="8" width="12" height="12" rx="3" fill="#009AC7"/>
<path d="M237.5 13.1667H222.5V11.5H237.5V13.1667ZM237.5 14.8333H222.5V16.5H237.5V14.8333Z" fill="#B1B1B1"/>
<path d="M257.167 16.5H253L258.833 4.83337V11.5H263L257.167 23.1667V16.5Z" fill="#5E5E5E"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,17 @@
<svg width="268" height="28" viewBox="0 0 268 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 8C0 3.58172 3.58172 0 8 0H80C84.4183 0 88 3.58172 88 8V20C88 24.4183 84.4183 28 80 28H8C3.58172 28 0 24.4183 0 20V8Z" fill="#1C1C1C"/>
<path d="M8 0.5H80C84.1421 0.5 87.5 3.85786 87.5 8V20C87.5 24.1421 84.1421 27.5 80 27.5H8C3.85786 27.5 0.5 24.1421 0.5 20V8C0.5 3.85786 3.85786 0.5 8 0.5Z" stroke="white" stroke-opacity="0.24"/>
<rect x="8" y="8" width="12" height="12" rx="3" fill="white" fill-opacity="0.24"/>
<rect x="28" y="8" width="12" height="12" rx="3" fill="#009AC7"/>
<rect x="48" y="8" width="12" height="12" rx="3" fill="white" fill-opacity="0.24"/>
<rect x="68" y="8" width="12" height="12" rx="3" fill="white" fill-opacity="0.24"/>
<path d="M109.333 16.45C108.718 17.065 107.667 16.6294 107.667 15.7596C107.667 15.2204 107.23 14.7833 106.69 14.7833H101.058C100.601 14.7833 100.228 14.4159 100.221 13.9583C100.214 13.4909 100.591 13.1083 101.058 13.1083H106.693C107.231 13.1083 107.667 12.6723 107.667 12.1345C107.667 11.2668 108.716 10.8323 109.329 11.4458L110.613 12.7296C111.287 13.4036 111.287 14.4964 110.613 15.1704L109.333 16.45Z" fill="white" fill-opacity="0.48"/>
<path d="M124 8C124 3.58172 127.582 0 132 0H204C208.418 0 212 3.58172 212 8V20C212 24.4183 208.418 28 204 28H132C127.582 28 124 24.4183 124 20V8Z" fill="#1C1C1C"/>
<path d="M132 0.5H204C208.142 0.5 211.5 3.85786 211.5 8V20C211.5 24.1421 208.142 27.5 204 27.5H132C127.858 27.5 124.5 24.1421 124.5 20V8C124.5 3.85786 127.858 0.5 132 0.5Z" stroke="white" stroke-opacity="0.24"/>
<rect x="132" y="8" width="12" height="12" rx="3" fill="white" fill-opacity="0.24"/>
<rect x="152" y="8" width="12" height="12" rx="3" fill="#009AC7"/>
<rect x="172" y="8" width="12" height="12" rx="3" fill="white" fill-opacity="0.24"/>
<rect x="192" y="8" width="12" height="12" rx="3" fill="#009AC7"/>
<path d="M237.5 12.3333C237.5 12.7936 237.127 13.1667 236.667 13.1667H223.333C222.873 13.1667 222.5 12.7936 222.5 12.3333C222.5 11.8731 222.873 11.5 223.333 11.5H236.667C237.127 11.5 237.5 11.8731 237.5 12.3333ZM237.5 15.6667C237.5 15.2064 237.127 14.8333 236.667 14.8333H223.333C222.873 14.8333 222.5 15.2064 222.5 15.6667C222.5 16.1269 222.873 16.5 223.333 16.5H236.667C237.127 16.5 237.5 16.1269 237.5 15.6667Z" fill="white" fill-opacity="0.48"/>
<path d="M257.167 16.5H253L258.833 4.83337V11.5H263L257.167 23.1667V16.5Z" fill="white" fill-opacity="0.24"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,17 @@
<svg width="268" height="28" viewBox="0 0 268 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="88" height="28" rx="8" fill="white"/>
<rect x="0.5" y="0.5" width="87" height="27" rx="7.5" stroke="black" stroke-opacity="0.12"/>
<rect x="8" y="8" width="12" height="12" rx="3" fill="black" fill-opacity="0.12"/>
<rect x="28" y="8" width="12" height="12" rx="3" fill="black" fill-opacity="0.12"/>
<rect x="48" y="8" width="12" height="12" rx="3" fill="black" fill-opacity="0.12"/>
<rect x="68" y="8" width="12" height="12" rx="3" fill="black" fill-opacity="0.12"/>
<path d="M107.667 18.1167V14.7833H100.233L100.208 13.1083H107.667V9.78333L111.833 13.95L107.667 18.1167Z" fill="#B1B1B1"/>
<rect x="124" width="88" height="28" rx="8" fill="white"/>
<rect x="124.5" y="0.5" width="87" height="27" rx="7.5" stroke="black" stroke-opacity="0.12"/>
<rect x="132" y="8" width="12" height="12" rx="3" fill="black" fill-opacity="0.12"/>
<rect x="152" y="8" width="12" height="12" rx="3" fill="black" fill-opacity="0.12"/>
<rect x="172" y="8" width="12" height="12" rx="3" fill="#009AC7"/>
<rect x="192" y="8" width="12" height="12" rx="3" fill="black" fill-opacity="0.12"/>
<path d="M237.5 13.1667H222.5V11.5H237.5V13.1667ZM237.5 14.8333H222.5V16.5H237.5V14.8333Z" fill="#B1B1B1"/>
<path d="M257.167 16.5H253L258.833 4.83337V11.5H263L257.167 23.1667V16.5Z" fill="#5E5E5E"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,17 @@
<svg width="268" height="28" viewBox="0 0 268 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 8C0 3.58172 3.58172 0 8 0H80C84.4183 0 88 3.58172 88 8V20C88 24.4183 84.4183 28 80 28H8C3.58172 28 0 24.4183 0 20V8Z" fill="#1C1C1C"/>
<path d="M8 0.5H80C84.1421 0.5 87.5 3.85786 87.5 8V20C87.5 24.1421 84.1421 27.5 80 27.5H8C3.85786 27.5 0.5 24.1421 0.5 20V8C0.5 3.85786 3.85786 0.5 8 0.5Z" stroke="white" stroke-opacity="0.24"/>
<rect x="8" y="8" width="12" height="12" rx="3" fill="white" fill-opacity="0.24"/>
<rect x="28" y="8" width="12" height="12" rx="3" fill="white" fill-opacity="0.24"/>
<rect x="48" y="8" width="12" height="12" rx="3" fill="white" fill-opacity="0.24"/>
<rect x="68" y="8" width="12" height="12" rx="3" fill="white" fill-opacity="0.24"/>
<path d="M109.333 16.45C108.718 17.065 107.667 16.6294 107.667 15.7596C107.667 15.2204 107.23 14.7833 106.69 14.7833H101.058C100.601 14.7833 100.228 14.4159 100.221 13.9583C100.214 13.4909 100.591 13.1083 101.058 13.1083H106.693C107.231 13.1083 107.667 12.6723 107.667 12.1345C107.667 11.2668 108.716 10.8323 109.329 11.4458L110.613 12.7296C111.287 13.4036 111.287 14.4964 110.613 15.1704L109.333 16.45Z" fill="white" fill-opacity="0.48"/>
<path d="M124 8C124 3.58172 127.582 0 132 0H204C208.418 0 212 3.58172 212 8V20C212 24.4183 208.418 28 204 28H132C127.582 28 124 24.4183 124 20V8Z" fill="#1C1C1C"/>
<path d="M132 0.5H204C208.142 0.5 211.5 3.85786 211.5 8V20C211.5 24.1421 208.142 27.5 204 27.5H132C127.858 27.5 124.5 24.1421 124.5 20V8C124.5 3.85786 127.858 0.5 132 0.5Z" stroke="white" stroke-opacity="0.24"/>
<rect x="132" y="8" width="12" height="12" rx="3" fill="white" fill-opacity="0.24"/>
<rect x="152" y="8" width="12" height="12" rx="3" fill="white" fill-opacity="0.24"/>
<rect x="172" y="8" width="12" height="12" rx="3" fill="#009AC7"/>
<rect x="192" y="8" width="12" height="12" rx="3" fill="white" fill-opacity="0.24"/>
<path d="M237.5 12.3333C237.5 12.7936 237.127 13.1667 236.667 13.1667H223.333C222.873 13.1667 222.5 12.7936 222.5 12.3333C222.5 11.8731 222.873 11.5 223.333 11.5H236.667C237.127 11.5 237.5 11.8731 237.5 12.3333ZM237.5 15.6667C237.5 15.2064 237.127 14.8333 236.667 14.8333H223.333C222.873 14.8333 222.5 15.2064 222.5 15.6667C222.5 16.1269 222.873 16.5 223.333 16.5H236.667C237.127 16.5 237.5 16.1269 237.5 15.6667Z" fill="white" fill-opacity="0.48"/>
<path d="M257.167 16.5H253L258.833 4.83337V11.5H263L257.167 23.1667V16.5Z" fill="white" fill-opacity="0.24"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,17 @@
<svg width="268" height="28" viewBox="0 0 268 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="88" height="28" rx="8" fill="white"/>
<rect x="0.5" y="0.5" width="87" height="27" rx="7.5" stroke="black" stroke-opacity="0.12"/>
<rect x="8" y="8" width="12" height="12" rx="3" fill="#009AC7"/>
<rect x="28" y="8" width="12" height="12" rx="3" fill="#009AC7"/>
<rect x="48" y="8" width="12" height="12" rx="3" fill="black" fill-opacity="0.12"/>
<rect x="68" y="8" width="12" height="12" rx="3" fill="#009AC7"/>
<path d="M107.667 18.1167V14.7833H100.233L100.208 13.1083H107.667V9.78333L111.833 13.95L107.667 18.1167Z" fill="#B1B1B1"/>
<rect x="124" width="88" height="28" rx="8" fill="white"/>
<rect x="124.5" y="0.5" width="87" height="27" rx="7.5" stroke="black" stroke-opacity="0.12"/>
<rect x="132" y="8" width="12" height="12" rx="3" fill="#009AC7"/>
<rect x="152" y="8" width="12" height="12" rx="3" fill="#009AC7"/>
<rect x="172" y="8" width="12" height="12" rx="3" fill="#009AC7"/>
<rect x="192" y="8" width="12" height="12" rx="3" fill="#009AC7"/>
<path d="M237.5 13.1667H222.5V11.5H237.5V13.1667ZM237.5 14.8333H222.5V16.5H237.5V14.8333Z" fill="#B1B1B1"/>
<path d="M257.167 16.5H253L258.833 4.83337V11.5H263L257.167 23.1667V16.5Z" fill="#5E5E5E"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,17 @@
<svg width="268" height="28" viewBox="0 0 268 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 8C0 3.58172 3.58172 0 8 0H80C84.4183 0 88 3.58172 88 8V20C88 24.4183 84.4183 28 80 28H8C3.58172 28 0 24.4183 0 20V8Z" fill="#1C1C1C"/>
<path d="M8 0.5H80C84.1421 0.5 87.5 3.85786 87.5 8V20C87.5 24.1421 84.1421 27.5 80 27.5H8C3.85786 27.5 0.5 24.1421 0.5 20V8C0.5 3.85786 3.85786 0.5 8 0.5Z" stroke="white" stroke-opacity="0.24"/>
<rect x="8" y="8" width="12" height="12" rx="3" fill="#009AC7"/>
<rect x="28" y="8" width="12" height="12" rx="3" fill="#009AC7"/>
<rect x="48" y="8" width="12" height="12" rx="3" fill="white" fill-opacity="0.24"/>
<rect x="68" y="8" width="12" height="12" rx="3" fill="#009AC7"/>
<path d="M109.333 16.45C108.718 17.065 107.667 16.6294 107.667 15.7596C107.667 15.2204 107.23 14.7833 106.69 14.7833H101.058C100.601 14.7833 100.228 14.4159 100.221 13.9583C100.214 13.4909 100.591 13.1083 101.058 13.1083H106.693C107.231 13.1083 107.667 12.6723 107.667 12.1345C107.667 11.2668 108.716 10.8323 109.329 11.4458L110.613 12.7296C111.287 13.4036 111.287 14.4964 110.613 15.1704L109.333 16.45Z" fill="white" fill-opacity="0.48"/>
<path d="M124 8C124 3.58172 127.582 0 132 0H204C208.418 0 212 3.58172 212 8V20C212 24.4183 208.418 28 204 28H132C127.582 28 124 24.4183 124 20V8Z" fill="#1C1C1C"/>
<path d="M132 0.5H204C208.142 0.5 211.5 3.85786 211.5 8V20C211.5 24.1421 208.142 27.5 204 27.5H132C127.858 27.5 124.5 24.1421 124.5 20V8C124.5 3.85786 127.858 0.5 132 0.5Z" stroke="white" stroke-opacity="0.24"/>
<rect x="132" y="8" width="12" height="12" rx="3" fill="#009AC7"/>
<rect x="152" y="8" width="12" height="12" rx="3" fill="#009AC7"/>
<rect x="172" y="8" width="12" height="12" rx="3" fill="#009AC7"/>
<rect x="192" y="8" width="12" height="12" rx="3" fill="#009AC7"/>
<path d="M237.5 12.3333C237.5 12.7936 237.127 13.1667 236.667 13.1667H223.333C222.873 13.1667 222.5 12.7936 222.5 12.3333C222.5 11.8731 222.873 11.5 223.333 11.5H236.667C237.127 11.5 237.5 11.8731 237.5 12.3333ZM237.5 15.6667C237.5 15.2064 237.127 14.8333 236.667 14.8333H223.333C222.873 14.8333 222.5 15.2064 222.5 15.6667C222.5 16.1269 222.873 16.5 223.333 16.5H236.667C237.127 16.5 237.5 16.1269 237.5 15.6667Z" fill="white" fill-opacity="0.48"/>
<path d="M257.167 16.5H253L258.833 4.83337V11.5H263L257.167 23.1667V16.5Z" fill="white" fill-opacity="0.24"/>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

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

View File

@@ -36,6 +36,9 @@ export class HaSelectBox extends LitElement {
@property({ type: Number, attribute: "max_columns" })
public maxColumns?: number;
@property({ type: Boolean, attribute: "stacked_image" })
public stackedImage = false;
render() {
const maxColumns = this.maxColumns ?? 3;
const columns = Math.min(maxColumns, this.options.length);
@@ -48,7 +51,8 @@ export class HaSelectBox extends LitElement {
}
private _renderOption(option: SelectBoxOption) {
const horizontal = this.maxColumns === 1;
const horizontal = this.maxColumns === 1 && !this.stackedImage;
const stacked = this.maxColumns === 1 && this.stackedImage;
const disabled = option.disabled || this.disabled || false;
const selected = option.value === this.value;
@@ -66,6 +70,7 @@ export class HaSelectBox extends LitElement {
<label
class="option ${classMap({
horizontal: horizontal,
stacked: stacked,
selected: selected,
})}"
?disabled=${disabled}
@@ -187,6 +192,16 @@ export class HaSelectBox extends LitElement {
margin: 0;
}
.option.stacked {
align-items: stretch;
}
.option.stacked img {
max-width: 100%;
max-height: var(--ha-select-box-image-size, 96px);
margin: 0;
}
.option:before {
content: "";
display: block;

View File

@@ -0,0 +1,123 @@
import { LitElement, css, html, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import type { LocalizeKeys } from "../../common/translations/localize";
import type {
AutomationBehavior,
AutomationBehaviorConditionMode,
AutomationBehaviorSelector,
AutomationBehaviorTriggerMode,
} from "../../data/selector";
import type { HomeAssistant } from "../../types";
import "../ha-input-helper-text";
import type { SelectBoxOption } from "../ha-select-box";
import "../ha-select-box";
const TRIGGER_BEHAVIORS: AutomationBehaviorTriggerMode[] = [
"any",
"first",
"last",
];
const CONDITION_BEHAVIORS: AutomationBehaviorConditionMode[] = ["any", "all"];
@customElement("ha-selector-automation_behavior")
export class HaSelectorAutomationBehavior extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false })
public selector!: AutomationBehaviorSelector;
@property() public value?: AutomationBehavior;
@property() public helper?: string;
@property({ attribute: false })
public localizeValue?: (key: string) => string;
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) public required = true;
protected render() {
const { mode } = this.selector.automation_behavior ?? {};
const modeKey = mode ?? "trigger";
const isTrigger = modeKey === "trigger";
const options = this._behaviors().map<SelectBoxOption>((behavior) => ({
value: behavior,
label: this._localizeOption(behavior, "label"),
description: this._localizeOption(behavior, "description"),
disabled: this.disabled,
...(isTrigger && {
image: {
src: `/static/images/form/automation_behavior_trigger_${behavior}.svg`,
src_dark: `/static/images/form/automation_behavior_trigger_${behavior}_dark.svg`,
},
}),
}));
return html`
<ha-select-box
.hass=${this.hass}
.options=${options}
.value=${this.value ?? ""}
max_columns="1"
?stacked_image=${isTrigger}
@value-changed=${this._valueChanged}
></ha-select-box>
${this.helper
? html`<ha-input-helper-text .disabled=${this.disabled}
>${this.helper}</ha-input-helper-text
>`
: nothing}
`;
}
private _behaviors(): AutomationBehavior[] {
const mode = this.selector.automation_behavior?.mode;
return mode === "condition" ? CONDITION_BEHAVIORS : TRIGGER_BEHAVIORS;
}
private _localizeOption(
behavior: AutomationBehavior,
field: "label" | "description"
): string {
const { translation_key: translationKey, mode } =
this.selector.automation_behavior ?? {};
if (this.localizeValue && translationKey) {
const translated = this.localizeValue(
`${translationKey}.options.${behavior}.${field}`
);
if (translated) {
return translated;
}
}
return this.hass.localize(
`ui.components.selectors.automation_behavior.${mode ?? "trigger"}.options.${behavior}.${field}` as LocalizeKeys
);
}
private _valueChanged(ev: CustomEvent) {
ev.stopPropagation();
const value = ev.detail.value as AutomationBehavior;
if (this.disabled || value === this.value) {
return;
}
fireEvent(this, "value-changed", { value });
}
static styles = css`
ha-select-box {
--ha-select-box-image-size: 28px;
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"ha-selector-automation_behavior": HaSelectorAutomationBehavior;
}
}

View File

@@ -13,6 +13,7 @@ import type { HomeAssistant } from "../../types";
const LOAD_ELEMENTS = {
action: () => import("./ha-selector-action"),
addon: () => import("./ha-selector-addon"),
automation_behavior: () => import("./ha-selector-automation-behavior"),
app: () => import("./ha-selector-app"),
area: () => import("./ha-selector-area"),
areas_display: () => import("./ha-selector-areas-display"),

View File

@@ -322,6 +322,7 @@ export interface ShorthandNotCondition extends ShorthandBaseCondition {
export interface AutomationElementGroupCollection {
titleKey?: LocalizeKeys;
generic?: boolean;
groups: AutomationElementGroup;
}

View File

@@ -8,28 +8,31 @@ import type { Selector, TargetSelector } from "./selector";
export const CONDITION_COLLECTIONS: AutomationElementGroupCollection[] = [
{
groups: {
device: {},
dynamicGroups: {},
entity: { icon: mdiShape, members: { state: {}, numeric_state: {} } },
time_location: {
icon: mdiMapClock,
members: { sun: {}, time: {}, zone: {} },
},
helpers: {},
template: {},
trigger: {},
other: {},
},
},
{
titleKey:
"ui.panel.config.automation.editor.conditions.groups.helpers.label",
"ui.panel.config.automation.editor.conditions.groups.generic.label",
generic: true,
groups: {
helpers: {},
device: {},
entity: { icon: mdiShape, members: { state: {}, numeric_state: {} } },
},
},
{
titleKey: "ui.panel.config.automation.editor.conditions.groups.other.label",
titleKey:
"ui.panel.config.automation.editor.conditions.groups.custom_integrations.label",
groups: {
template: {},
trigger: {},
other: {},
customDynamicGroups: {},
},
},
] as const;

View File

@@ -31,6 +31,7 @@ export type Selector =
| AreaSelector
| AreasDisplaySelector
| AttributeSelector
| AutomationBehaviorSelector
| BooleanSelector
| ButtonToggleSelector
| ChooseSelector
@@ -124,6 +125,21 @@ export interface BooleanSelector {
boolean: {} | null;
}
export type AutomationBehaviorTriggerMode = "first" | "last" | "any";
export type AutomationBehaviorConditionMode = "all" | "any";
export type AutomationBehavior =
| AutomationBehaviorTriggerMode
| AutomationBehaviorConditionMode;
export interface AutomationBehaviorSelector {
automation_behavior: {
mode: "trigger" | "condition";
translation_key?: string;
} | null;
}
export interface ButtonToggleSelector {
button_toggle: {
options: readonly string[] | readonly SelectOption[];

View File

@@ -13,9 +13,7 @@ import type { Selector, TargetSelector } from "./selector";
export const TRIGGER_COLLECTIONS: AutomationElementGroupCollection[] = [
{
groups: {
device: {},
dynamicGroups: {},
entity: { icon: mdiShape, members: { state: {}, numeric_state: {} } },
time_location: {
icon: mdiMapClock,
members: {
@@ -26,17 +24,6 @@ export const TRIGGER_COLLECTIONS: AutomationElementGroupCollection[] = [
zone: {},
},
},
},
},
{
titleKey: "ui.panel.config.automation.editor.triggers.groups.helpers.label",
groups: {
helpers: {},
},
},
{
titleKey: "ui.panel.config.automation.editor.triggers.groups.other.label",
groups: {
event: {},
geo_location: {},
homeassistant: {},
@@ -45,9 +32,25 @@ export const TRIGGER_COLLECTIONS: AutomationElementGroupCollection[] = [
template: {},
webhook: {},
persistent_notification: {},
helpers: {},
other: {},
},
},
{
titleKey: "ui.panel.config.automation.editor.triggers.groups.generic.label",
generic: true,
groups: {
device: {},
entity: { icon: mdiShape, members: { state: {}, numeric_state: {} } },
},
},
{
titleKey:
"ui.panel.config.automation.editor.triggers.groups.custom_integrations.label",
groups: {
customDynamicGroups: {},
},
},
] as const;
export const isTriggerList = (trigger: Trigger): trigger is TriggerList =>

View File

@@ -146,6 +146,7 @@ const TYPES = {
export interface CollectionGroup {
collectionIndex: number;
titleKey?: LocalizeKeys;
generic?: boolean;
groups: AddAutomationElementListItem[];
}
@@ -176,9 +177,16 @@ const ENTITY_DOMAINS_OTHER = new Set([
const ENTITY_DOMAINS_MAIN = new Set(["notify"]);
const DYNAMIC_KEYWORDS = ["dynamicGroups", "helpers", "other"];
const DYNAMIC_KEYWORDS = [
"dynamicGroups",
"helpers",
"other",
"customDynamicGroups",
];
const GENERIC_GROUPS = new Set(["device", "entity", `${DYNAMIC_PREFIX}event`]);
const DYNAMIC_TO_GENERIC = new Set([`${DYNAMIC_PREFIX}event`]);
type CollectionGroupType = "helper" | "other" | "dynamic" | "customDynamic";
@customElement("add-automation-element-dialog")
class DialogAddAutomationElement
@@ -1086,10 +1094,27 @@ class DialogAddAutomationElement
): CollectionGroup[] => {
const generatedCollections: CollectionGroup[] = [];
let genericCollectionIndex = -1;
let dynamicCollectionIndex = -1;
collections.forEach((collection, index) => {
let collectionGroups = Object.entries(collection.groups);
const groups: AddAutomationElementListItem[] = [];
const types: CollectionGroupType[] = [];
if (collection.groups.dynamicGroups) {
types.push("dynamic");
}
if (collection.groups.helpers) {
types.push("helper");
}
if (collection.groups.other) {
types.push("other");
}
if (collection.groups.customDynamicGroups) {
types.push("customDynamic");
}
if (
type === "trigger" &&
Object.keys(collection.groups).some((item) =>
@@ -1102,11 +1127,7 @@ class DialogAddAutomationElement
triggerDescriptions,
manifests,
domains,
collection.groups.dynamicGroups
? undefined
: collection.groups.helpers
? "helper"
: "other"
types
)
);
@@ -1125,11 +1146,7 @@ class DialogAddAutomationElement
conditionDescriptions,
manifests,
domains,
collection.groups.dynamicGroups
? undefined
: collection.groups.helpers
? "helper"
: "other"
types
)
);
@@ -1167,55 +1184,45 @@ class DialogAddAutomationElement
)
);
generatedCollections.push({
collectionIndex: index,
titleKey: collection.titleKey,
groups: groups.sort((a, b) => {
// make sure device is always on top
if (a.key === "device" || a.key === "device_id") {
return -1;
}
if (b.key === "device" || b.key === "device_id") {
return 1;
}
return stringCompare(a.name, b.name, this.hass.locale.language);
}),
});
if (groups.length) {
if (collection.generic) {
genericCollectionIndex = index;
}
if (collection.groups.dynamicGroups) {
dynamicCollectionIndex = index;
}
generatedCollections.push({
collectionIndex: index,
titleKey: collection.titleKey,
generic: collection.generic,
groups: groups.sort((a, b) => {
return stringCompare(a.name, b.name, this.hass.locale.language);
}),
});
}
});
return !["trigger", "condition"].includes(type)
? generatedCollections
: generatedCollections.flatMap(
(collection: CollectionGroup): CollectionGroup[] => {
const genericGroups = collection.groups.filter((group) =>
GENERIC_GROUPS.has(group.key)
);
// move groups from dynamic to generic
if (genericCollectionIndex !== -1 && dynamicCollectionIndex !== -1) {
const groupsToMove =
generatedCollections[dynamicCollectionIndex].groups.filter((group) =>
DYNAMIC_TO_GENERIC.has(group.key)
) || [];
generatedCollections[dynamicCollectionIndex].groups =
generatedCollections[dynamicCollectionIndex].groups.filter(
(group) => !DYNAMIC_TO_GENERIC.has(group.key)
) || [];
const mainGroups = collection.groups.filter(
(group) => !GENERIC_GROUPS.has(group.key)
);
generatedCollections[genericCollectionIndex].groups = [
...(generatedCollections[genericCollectionIndex].groups || []),
...groupsToMove,
].sort((a, b) =>
stringCompare(a.name, b.name, this.hass.locale.language)
);
}
return [
...(mainGroups.length
? [
{
...collection,
groups: mainGroups,
},
]
: []),
...(genericGroups.length
? [
{
collectionIndex: collection.collectionIndex,
titleKey: "ui.panel.config.automation.editor.generic",
groups: genericGroups,
} satisfies CollectionGroup,
]
: []),
];
}
);
return generatedCollections;
}
);
@@ -1363,34 +1370,31 @@ class DialogAddAutomationElement
domain: string,
manifest: DomainManifestLookup[string] | undefined,
domainUsed: boolean,
type: "helper" | "other" | undefined
types: CollectionGroupType[]
): boolean {
if (type === undefined) {
return (
ENTITY_DOMAINS_MAIN.has(domain) ||
const matchDynamic =
((types.includes("dynamic") && (!manifest || manifest.is_built_in)) ||
(types.includes("customDynamic") &&
!(manifest?.is_built_in ?? true))) &&
(ENTITY_DOMAINS_MAIN.has(domain) ||
(manifest?.integration_type === "entity" &&
!ENTITY_DOMAINS_OTHER.has(domain) &&
(domainUsed || (this._systemDomains?.active.has(domain) ?? false))) ||
(manifest?.integration_type === "system" &&
(this._systemDomains?.active.has(domain) ?? false))
);
}
if (type === "helper") {
return manifest?.integration_type === "helper";
}
// type === "other"
return (
(this._systemDomains?.active.has(domain) ?? false)));
const matchHelper =
types.includes("helper") && manifest?.integration_type === "helper";
const matchOther =
types.includes("other") &&
!ENTITY_DOMAINS_MAIN.has(domain) &&
(ENTITY_DOMAINS_OTHER.has(domain) ||
(!domainUsed &&
manifest?.integration_type === "entity" &&
!(this._systemDomains?.active.has(domain) ?? false)) ||
(manifest?.integration_type === "system" &&
!(this._systemDomains?.active.has(domain) ?? false)) ||
!["helper", "entity", "system"].includes(
manifest?.integration_type || ""
))
);
));
return matchDynamic || matchHelper || matchOther;
}
private _triggerGroups = (
@@ -1398,7 +1402,7 @@ class DialogAddAutomationElement
triggers: TriggerDescriptions,
manifests: DomainManifestLookup | undefined,
domains: Set<string> | undefined,
type: "helper" | "other" | undefined
types: CollectionGroupType[]
): AddAutomationElementListItem[] => {
if (!triggers || !manifests) {
return [];
@@ -1416,7 +1420,7 @@ class DialogAddAutomationElement
const manifest = manifests[domain];
const domainUsed = !domains ? true : domains.has(domain);
if (this._domainMatchesGroupType(domain, manifest, domainUsed, type)) {
if (this._domainMatchesGroupType(domain, manifest, domainUsed, types)) {
result.push({
icon: html`
<ha-domain-icon .domain=${domain} brand-fallback></ha-domain-icon>
@@ -1470,7 +1474,7 @@ class DialogAddAutomationElement
conditions: ConditionDescriptions,
manifests: DomainManifestLookup | undefined,
domains: Set<string> | undefined,
type: "helper" | "other" | undefined
types: CollectionGroupType[]
): AddAutomationElementListItem[] => {
if (!conditions || !manifests) {
return [];
@@ -1488,7 +1492,7 @@ class DialogAddAutomationElement
const manifest = manifests[domain];
const domainUsed = !domains ? true : domains.has(domain);
if (this._domainMatchesGroupType(domain, manifest, domainUsed, type)) {
if (this._domainMatchesGroupType(domain, manifest, domainUsed, types)) {
result.push({
icon: html`
<ha-domain-icon .domain=${domain} brand-fallback></ha-domain-icon>

View File

@@ -100,7 +100,8 @@ export class HaPlatformCondition extends LitElement {
field.default !== undefined &&
updatedOptions[key] === undefined &&
!(
key === "behavior" &&
field.selector &&
"automation_behavior" in field.selector &&
this.description?.target &&
!this.condition?.target
)
@@ -227,7 +228,7 @@ export class HaPlatformCondition extends LitElement {
}
if (
fieldName === "behavior" &&
"automation_behavior" in selector &&
this.description?.target &&
(!this.condition?.target ||
(this._resolvedTargetEntityCount !== undefined &&
@@ -437,17 +438,32 @@ export class HaPlatformCondition extends LitElement {
) {
this._resolvedTargetEntityCount = getTargetEntityCount(target);
const behaviorFieldEntry = Object.entries(
this.description?.fields ?? {}
).find(
([, field]) => field.selector && "automation_behavior" in field.selector
);
if (!behaviorFieldEntry) {
return;
}
const [behaviorFieldName, behaviorField] = behaviorFieldEntry;
if (
target &&
this._resolvedTargetEntityCount > 1 &&
this.condition.options?.behavior === undefined
this.condition.options?.[behaviorFieldName] === undefined
) {
const behaviorDefault = this.description?.fields?.behavior?.default;
const behaviorDefault = behaviorField.default;
if (behaviorDefault !== undefined) {
fireEvent(this, "value-changed", {
value: {
...this.condition,
options: { ...this.condition.options, behavior: behaviorDefault },
options: {
...this.condition.options,
[behaviorFieldName]: behaviorDefault,
},
},
});
}

View File

@@ -135,7 +135,8 @@ export class HaPlatformTrigger extends LitElement {
field.default !== undefined &&
updatedOptions[key] === undefined &&
!(
key === "behavior" &&
field.selector &&
"automation_behavior" in field.selector &&
this.description?.target &&
!this.trigger?.target
)
@@ -262,7 +263,7 @@ export class HaPlatformTrigger extends LitElement {
}
if (
fieldName === "behavior" &&
"automation_behavior" in selector &&
this.description?.target &&
(!this.trigger?.target ||
(this._resolvedTargetEntityCount !== undefined &&
@@ -470,17 +471,32 @@ export class HaPlatformTrigger extends LitElement {
private _updateResolvedTargetEntityCount(target: PlatformTrigger["target"]) {
this._resolvedTargetEntityCount = getTargetEntityCount(target);
const behaviorFieldEntry = Object.entries(
this.description?.fields ?? {}
).find(
([, field]) => field.selector && "automation_behavior" in field.selector
);
if (!behaviorFieldEntry) {
return;
}
const [behaviorFieldName, behaviorField] = behaviorFieldEntry;
if (
target &&
this._resolvedTargetEntityCount > 1 &&
this.trigger.options?.behavior === undefined
this.trigger.options?.[behaviorFieldName] === undefined
) {
const behaviorDefault = this.description?.fields?.behavior?.default;
const behaviorDefault = behaviorField.default;
if (behaviorDefault !== undefined) {
fireEvent(this, "value-changed", {
value: {
...this.trigger,
options: { ...this.trigger.options, behavior: behaviorDefault },
options: {
...this.trigger.options,
[behaviorFieldName]: behaviorDefault,
},
},
});
}

View File

@@ -669,6 +669,9 @@ class HaPanelDevAction extends LitElement {
}
.button-row {
padding: var(--ha-space-2) var(--ha-space-4);
border-top: 1px solid var(--divider-color);
border-bottom: 1px solid var(--divider-color);
background: var(--card-background-color);
position: sticky;
bottom: 0;
box-sizing: border-box;

View File

@@ -623,6 +623,36 @@
"between": "In range",
"outside": "Outside range"
}
},
"automation_behavior": {
"trigger": {
"options": {
"any": {
"label": "Each",
"description": "Triggers once for every target that changes"
},
"first": {
"label": "First",
"description": "Triggers the first time any target changes"
},
"last": {
"label": "All",
"description": "Triggers once all targets have changed"
}
}
},
"condition": {
"options": {
"any": {
"label": "Any",
"description": "Is true if any target matches the desired state"
},
"all": {
"label": "All",
"description": "Is true if all targets match the desired state"
}
}
}
}
},
"logbook": {
@@ -5079,14 +5109,14 @@
"entity": {
"label": "Entity"
},
"helpers": {
"label": "Helpers"
},
"time_location": {
"label": "Time and location"
},
"other": {
"label": "Other triggers"
"generic": {
"label": "Generic"
},
"custom_integrations": {
"label": "Custom integrations"
}
},
"type": {
@@ -5354,14 +5384,14 @@
"time_location": {
"label": "Time and location"
},
"helpers": {
"label": "Helpers"
},
"other": {
"label": "Other conditions"
"generic": {
"label": "Generic"
},
"building_blocks": {
"label": "Building blocks"
},
"custom_integrations": {
"label": "Custom integrations"
}
},
"type": {