Compare commits

...

475 Commits

Author SHA1 Message Date
Aidan Timson
b5d61d4041 Format 2026-03-24 10:50:30 +00:00
Aidan Timson
79780b111c Remove unnecessary copy
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-24 10:50:30 +00:00
Aidan Timson
a592a5f222 Dont force ltr 2026-03-24 10:50:30 +00:00
Aidan Timson
2ac8bf9179 Cache date formatting 2026-03-24 10:50:30 +00:00
Aidan Timson
fd21dd2fd4 Reflect property for style usage 2026-03-24 10:50:30 +00:00
Aidan Timson
370ccd95da Remove JS tick for analog clock 2026-03-24 10:50:30 +00:00
Aidan Timson
ed2161effd Inline 2026-03-24 10:50:30 +00:00
Aidan Timson
19d902afc6 Improve typing 2026-03-24 10:50:30 +00:00
Aidan Timson
672d235c3e Remove modern/legacy support diff 2026-03-24 10:50:30 +00:00
Aidan Timson
5246ce3f72 Fix 2026-03-24 10:50:29 +00:00
Aidan Timson
c83924efa7 Refactor 2026-03-24 10:50:29 +00:00
Aidan Timson
bdbcec4d90 Update description 2026-03-24 10:50:29 +00:00
Aidan Timson
a074c80ec3 Fix value render bug 2026-03-24 10:50:29 +00:00
Aidan Timson
3795ad1253 Cleanup 2026-03-24 10:50:29 +00:00
Aidan Timson
7bb466a75b Remove conditional render on seperators 2026-03-24 10:50:29 +00:00
Aidan Timson
bff2514eed Format 2026-03-24 10:50:29 +00:00
Aidan Timson
602d41b31d Move section 2026-03-24 10:50:29 +00:00
Aidan Timson
85d10cf982 Fix 1st value as array 2026-03-24 10:50:29 +00:00
Aidan Timson
a3ff3346db Split date and clock to avoid layout shifts 2026-03-24 10:50:29 +00:00
Aidan Timson
38a314ced4 Margin 2026-03-24 10:50:29 +00:00
Aidan Timson
2cf7452ed1 Reduce line height 2026-03-24 10:50:29 +00:00
Aidan Timson
ae97cc1c8d Use singular section name 2026-03-24 10:50:29 +00:00
Aidan Timson
65bba30266 Add new line seperator 2026-03-24 10:50:29 +00:00
Aidan Timson
8ee3544a32 Swap section in picker values 2026-03-24 10:50:29 +00:00
Aidan Timson
fcddf8f548 Rename 2026-03-24 10:50:29 +00:00
Aidan Timson
c7824d4059 Section in picker values 2026-03-24 10:50:29 +00:00
Aidan Timson
8c4f5206b1 Preview in secondary 2026-03-24 10:50:29 +00:00
Aidan Timson
cc2a7972fc Remove section from labels 2026-03-24 10:50:29 +00:00
Aidan Timson
33079bb12c Group sections 2026-03-24 10:50:29 +00:00
Aidan Timson
34152e522e Date formatter C 2026-03-24 10:50:29 +00:00
Aidan Timson
a0dc331056 Date formatter B 2026-03-24 10:47:32 +00:00
Aidan Timson
4a56c1404f Date formatter A 2026-03-24 10:47:32 +00:00
Aidan Timson
7e7845853d Use local date var 2026-03-24 10:47:32 +00:00
Aidan Timson
f8fe7a7d82 Cleanup old translation 2026-03-24 10:47:32 +00:00
Aidan Timson
8b40b55324 Scale small based on date length 2026-03-24 10:47:32 +00:00
Aidan Timson
ab55d1fdde Sizing (CSS Impl) 2026-03-24 10:47:32 +00:00
Aidan Timson
597099f153 Sizing (JS Impl) 2026-03-24 10:47:32 +00:00
Aidan Timson
40ba2ade58 Format 2026-03-24 10:47:32 +00:00
Aidan Timson
901fa4cdda Add 2026-03-24 10:47:32 +00:00
Aidan Timson
edf007718a Improve 2026-03-24 10:47:32 +00:00
Aidan Timson
5abaeea1f9 Type 2026-03-24 10:47:32 +00:00
Aidan Timson
1ce0a7eab2 Setup 2026-03-24 10:47:32 +00:00
Aidan Timson
c0c02eb548 Match 2026-03-24 10:47:32 +00:00
Aidan Timson
18d5b84a02 Setup analog clock 2026-03-24 10:47:32 +00:00
Aidan Timson
ebc58f025a Add date to digital clock 2026-03-24 10:47:32 +00:00
Aidan Timson
cb2758d868 Setup 2026-03-24 10:47:32 +00:00
Aidan Timson
5bbfa36228 Support more-info-view query param (#30282)
* Support more-info-view query param

* Remove unintended subview logic and add details view

* Reset childView
2026-03-24 11:26:08 +02:00
Bram Kragten
a8070b322c Add numeric threshold selector (#30284)
* wip

* add numeric threshold selector

* clean up

* review optimize
2026-03-24 10:59:07 +02:00
Jan-Philipp Benecke
9cbc44123e Enhance delete entity confirmation dialog with detailed information (#30293) 2026-03-24 09:43:46 +01:00
Wendelin
c8f4c892f9 Migrate ha-multi-textfield, ha-selector-text to ha-input and update to use new input components (#30280)
* Migrate ha-multi-textfield to ha-input-multi and update ha-selector-text to use new input components

* Review

* Update src/components/input/ha-input-multi.ts

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2026-03-24 07:04:39 +00:00
Simon Lamon
40b9f9dccb Gauge card improvements (#30149)
* Gauge card improvements

* Decrease stroke width

* Feedback

* Remove show more, move text to bottom

* Remove _handleMoreInfo method from hui-gauge-card

Removed the _handleMoreInfo method from hui-gauge-card.

* Remove unused fireEvent import from hui-gauge-card

Removed unused import for fireEvent.

* Fix up padding

* Better alignment from previous card

* Decrease padding

* Apply suggestions from code review

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

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2026-03-24 06:56:29 +00:00
Aidan Timson
823c222a55 Fix app info descriptions and metrics (#30287)
* Improve app info descriptions

* Space tokens on rest of files

* Don't display empty value
2026-03-23 18:36:29 +01:00
Paul Bottein
02acd2996c Allow boolean option to section background (#30289) 2026-03-23 18:32:53 +01:00
renovate[bot]
c462fc0639 Update formatjs monorepo (#30256)
* Update formatjs monorepo

* Fixup convert locale script

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>
2026-03-23 17:46:08 +01:00
renovate[bot]
903553dab9 Update dependency marked to v17.0.5 (#30286)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-23 16:12:59 +00:00
Paul Bottein
25a1c14523 Refactor light color favorites card feature and button (#30281) 2026-03-23 16:29:48 +01:00
karwosts
f03eee6cb2 Fix select-entity-row timeout (#30249)
* Fix select-entity-row timeout

* fix possible undefined exception
2026-03-23 16:07:50 +02:00
Tom Carpenter
984b50bac7 Fix water/gas total badge unit when sensor value is zero (#30279)
Fix water/gas badge when rate is 0

If there is only one flow rate sensor, and it has a value <=0, the badge would be displayed without a unit because the sensor gets skipped.

Instead of skipping <=0 values, continue to process sensor, but clamp the value to a minimum of 0. This ensures for single sensors we have a unit, and for multiple sensors the chosen unit is consistent even if the first sensor value drops to zero.
2026-03-23 13:36:15 +02:00
Wendelin
09e4355451 Migrate copy-textfield to input-copy (#30276) 2026-03-23 10:59:52 +00:00
Petar Petrov
7ee76538ae Fix trace tree left side unreachable on mobile (#30277) 2026-03-23 09:53:33 +00:00
Maarten Lakerveld
3bbc3403d6 Validate external and internal URL on network tab (#30267) 2026-03-23 09:01:05 +00:00
Petar Petrov
9979bb13ea Fix hasReturnToGrid only checking first grid source in energy distribution card (#30273) 2026-03-23 08:55:22 +00:00
renovate[bot]
8ac831679d Update dependency @rsdoctor/rspack-plugin to v1.5.5 (#30275)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-23 08:46:07 +00:00
karwosts
db05b07997 Use form instead of schema for element-sub-editor (#30210)
* Use form instead of schema for element-sub-editor

* fix after merge dev
2026-03-23 10:28:07 +02:00
Tom Carpenter
dba8cefa67 Fix statistics graph card units when using energy collections. (#30263)
* Use same now() time for all entity state values

Otherwise if there is a slight time lag while each statistic is processed, some of the points go missing from the tooltip. There end up being to very closely spaced time points for each different entity.

* Fix initial statistics graph card metadata loading

When using energy collection mode, the metadata for statistic entities was not always being loaded when the charts were first created. This could be seen in the graph units when they had differing unit (e.g. kW and W mixed).
2026-03-23 10:22:23 +02:00
Maarten Lakerveld
6935c55c3c Fix validation hint styling for ha-input (#30266) 2026-03-23 06:52:59 +00:00
renovate[bot]
635a1185a3 Update Yarn to v4.13.0 (#30258)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-23 07:16:43 +01:00
renovate[bot]
585c894c5a Update dependency tar to v7.5.12 (#30269)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-23 07:16:04 +01:00
renovate[bot]
f9d052a818 Update dependency jsdom to v29.0.1 (#30268)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-23 07:15:49 +01:00
dependabot[bot]
a29132441d Bump release-drafter/release-drafter from 7.0.0 to 7.1.1 (#30270)
Bumps [release-drafter/release-drafter](https://github.com/release-drafter/release-drafter) from 7.0.0 to 7.1.1.
- [Release notes](https://github.com/release-drafter/release-drafter/releases)
- [Commits](3a7fb5c85b...139054aeaa)

---
updated-dependencies:
- dependency-name: release-drafter/release-drafter
  dependency-version: 7.1.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-23 07:15:32 +01:00
dependabot[bot]
479d52bf1d Bump actions/cache from 5.0.3 to 5.0.4 (#30271)
Bumps [actions/cache](https://github.com/actions/cache) from 5.0.3 to 5.0.4.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](cdf6c1fa76...668228422a)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: 5.0.4
  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-03-23 07:15:19 +01:00
dependabot[bot]
d96d78d6f6 Bump github/codeql-action from 4.32.6 to 4.34.1 (#30272)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.32.6 to 4.34.1.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](0d579ffd05...3869755554)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-23 07:15:00 +01:00
Timothy
f80cba341f Add new configuration for current assist current device external link (#29979)
* Add new configuration for current assist current device external link

* Adjust CSS

* Adjust naming
2026-03-22 10:47:05 +01:00
Petar Petrov
77ee966442 Propagate schema changes to existing form editor element (#30200)
* Re-create form editor when schema changes in hui-form-element-editor

When a parent component passes a new schema to hui-form-element-editor
(e.g. with updated disabled flags after an entity change), the internal
hui-form-editor was not rebuilt because loadConfigElement() short-circuits
once the config element exists. This meant dynamic schema changes were
silently ignored.

Override updated() to detect schema changes after initial load, tear down
the stale config element via unloadConfigElement(), and re-set the value
so the normal load path recreates the form with the current schema.

Fixes #29776

* Fix prettier formatting in hui-map-card.ts

Pre-existing formatting issue on dev branch.

* Propagate schema changes to existing form editor element

When the schema property on hui-form-element-editor changes (e.g. because
the selected entity changed and disabled flags need updating), directly
update the schema on the already-created hui-form-editor config element.

Previously, unloadConfigElement() was tried but caused a visible flash
(editor going blank then reappearing), and re-setting value with a spread
object was tried but deepEqual considers it unchanged, short-circuiting
the reload.

The fix is minimal: make _configElement protected in HuiElementEditor so
the subclass can reach it, then in the updated() hook push the new schema
directly onto the existing element — no teardown, no re-creation.
2026-03-22 10:40:31 +01:00
Niklas Wagner
2fec5a497e Add translation support for nested app configuration schemas (#30121) 2026-03-22 10:38:36 +01:00
Sergio
ed75d96d3c Clarify Matter iOS version requirement in add-device fallback (#30129)
Improve Matter add-device guidance for older iOS devices
2026-03-22 10:23:02 +01:00
balloob-travel
0fac47992b Update cloud promo styling in network settings (#30246)
Update cloud promo styling

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2026-03-22 08:17:09 +00:00
Wendelin
91a608c4c5 fix ha-input styles (#30225) 2026-03-22 09:13:59 +01:00
renovate[bot]
df61953ed4 Update dependency @rspack/core to v1.7.9 (#30243)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-20 20:56:13 +01:00
dependabot[bot]
2cda46b4bb Bump flatted from 3.3.3 to 3.4.1 (#30241)
Bumps [flatted](https://github.com/WebReflection/flatted) from 3.3.3 to 3.4.1.
- [Commits](https://github.com/WebReflection/flatted/compare/v3.3.3...v3.4.1)

---
updated-dependencies:
- dependency-name: flatted
  dependency-version: 3.4.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-20 07:51:41 +02:00
renovate[bot]
037190a393 Update babel monorepo to v7.29.2 (#30238)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-20 06:42:56 +01:00
renovate[bot]
ebe0154e32 Update dependency typescript-eslint to v8.57.1 (#30235)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-20 06:42:37 +01:00
renovate[bot]
efa73067f6 Update dependency core-js to v3.49.0 (#30236)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-20 06:42:20 +01:00
renovate[bot]
fdb40c9d01 Update dependency @babel/helper-define-polyfill-provider to v0.6.8 (#30239)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-20 06:42:06 +01:00
Paul Bottein
5a7ddb4972 Add background color option to dashboard sections (#30228) 2026-03-19 16:55:21 +01:00
Aidan Timson
dbe46d3b3f Remove advanced mode usages for apps area (#30232) 2026-03-19 16:31:04 +01:00
Petar Petrov
eb43d85439 Convert energy panel to use a dashboard strategy (#30170)
* Convert energy panel to use a dashboard strategy

Move the view-selection logic from ha-panel-energy into a dedicated
energy dashboard strategy, consistent with how home/areas/map dashboards
work. The strategy decides which view strategies (electricity, gas,
water, power, overview) to show based on energy preferences.

Extract shared constants into a separate module to avoid circular
dependencies between the panel and view strategies.

* Remove unused energy collection constants from ha-panel-energy
2026-03-19 16:16:56 +01:00
Aidan Timson
1bbfb79ddb Sort disabled and ignored integrations by name, translate disabled domains (#30230)
* Sort Disabled and Ignored integrations by name

* Localise disabled integration domains
2026-03-19 17:03:30 +02:00
Wendelin
cf50db350f Temporarily disable "focus_element" implementation for iOS app (#30226) 2026-03-19 13:51:37 +00:00
Wendelin
e04a0ec7dc Prevent time-input to close dialogs, popups and bottom-sheets (#30227) 2026-03-19 13:47:06 +00:00
renovate[bot]
e08576a6dc Update dependency sinon to v21.0.3 (#30224)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-19 13:40:50 +00:00
Petar Petrov
a7831f86ee Use --ha-box-shadow-l for footer (#30221) 2026-03-19 13:01:23 +01:00
Petar Petrov
c66e5b379b Show current entity value in history chart legend (#30222) 2026-03-19 11:16:47 +00:00
Aidan Timson
e819c30151 Hide behavior selector if no targets are populated (#30219) 2026-03-19 11:48:18 +01:00
Wendelin
e278e33375 date-range-picker with cally (#30193)
* date-range-picker with cally

* fix timePicker

* Review: backdrop transition

* fix comments

* Add formatCallyDateRange

* Refactor date formatting in date range picker and remove unused styles

* time-input without label

* review
2026-03-19 10:30:15 +00:00
renovate[bot]
8313be8e7e Update dependency @rsdoctor/rspack-plugin to v1.5.4 (#30220)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-19 11:44:40 +02:00
Tom Carpenter
6d95a59ca0 Skip plotting state value on statistic graph if units mismatch (#30214)
* Use isExternalStatistic helper for consistency

* Remove redundant if condition

We have `band  = drawBands && ...`, so there is no point checking if `drawBands` is true inside `if (band && ...)`.

* Skip plotting state value on statistic graph if units mismatch

For example plotting a *F sensor on a *C chart - statistic data will be converted to *C, but the state value will still be in *F so the displayed point is wrong. Similarly if plotting a kW sensor on a W chart, the same is true - statistics get converted to W by recorder, but the state value would still be in kW. In other words the plotted state point is complete nonsense.

If the units of the statistic state don't match the units of the graph, we should not be displaying the value on the graph.

* Remove redundant this.unit check

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

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2026-03-19 11:33:51 +02:00
Paul Bottein
a498ad3d06 Refactor device entities card to use Lit directive (#30138)
* Refactor device entities card to use Lit directive for entity rows

Replace the imperative pattern (shouldUpdate hack, _entityRows array,
_renderEntity pushing elements) with a declarative approach using a
reusable Lit directive and repeat for stable keying.

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

* Fix function name and padding issue

* Recreate on domain change, otherwise update

* Prettier

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 09:23:02 +02:00
renovate[bot]
c4a2229baa Update formatjs monorepo (#30215)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-19 08:20:34 +02:00
ildar170975
15245af52d Markdown card: add support of actions (#28951)
* add actions

* add actions

* add actions to MarkdownCardConfig

* add "actions_warning" for Markdown

* Add a "warning" label

* process a default handler if none action is defined

* check for config=undefined

* fix attributes

* prettier

* Update src/translations/en.json

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

* add ripple

* Fix interactive and add missing import for ripple

* Fix translation

---------

Co-authored-by: Norbert Rittel <norbert@rittel.de>
Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
2026-03-18 17:22:28 +00:00
Wendelin
c697735e46 Thread dashboard - replace fab with card (#30212)
Add UI to import Thread credentials
2026-03-18 17:47:39 +02:00
Paul Bottein
ddec792ae3 Add entity name alias toggle and drag-to-reorder aliases in voice settings (#30201)
* Add entity name alias toggle and drag-to-reorder aliases in voice settings

* Fix reorder

* Update src/panels/config/voice-assistants/entity-voice-settings.ts

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

* Use map instead of repeat

* Improve key

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2026-03-18 17:42:54 +02:00
Aidan Timson
cfd0e72609 Fix disabled entity ID in helper settings (#30213) 2026-03-18 15:42:19 +00:00
Aidan Timson
8a5bcd67ab Hide behavior selector for single target in triggers and conditions (labs) (#30145) 2026-03-18 16:26:34 +01:00
Paul Bottein
a794a80228 Add scrollbar support for cards with fixed grid row height (#30209)
* Add scrollbar support for cards with fixed grid row height

* Fix default sizing

* Add warning for card that doesn't support resizing well

* Remove not used explanation
2026-03-18 16:11:29 +01:00
Bram Kragten
41ed7d2877 Add uom filter to entity selector (#30211) 2026-03-18 16:00:34 +01:00
Jan-Philipp Benecke
b0b86e7ba8 Gate blueprint search behind minimum blueprint amount (#30207)
* Gate blueprint search behind minimum blueprint amount

* Remove top padding from search
2026-03-18 15:10:49 +02:00
Paul Bottein
e67c4842d4 Add an auto height toggle in card layout editor (#30182)
* Add auto height toggle in grid card editor

* Improve toggle

* Prettier
2026-03-18 13:51:38 +01:00
Petar Petrov
d9c39640e0 Preserve entity unit in gas and water flow rate badges (#30116)
* Preserve entity unit_of_measurement in gas and water flow rate badges

The gas and water total badges on the energy dashboard Now tab previously
converted all flow rate values to L/min and then formatted them as either
L/min or gal/min based on the unit system. This meant entities reporting
in m³/h or other units always displayed incorrectly.

Now the badges preserve the unit_of_measurement from the entities. If all
entities share the same unit, the raw values are summed directly. If they
differ, values are converted through L/min as an intermediate and displayed
in the first entity's unit.

* Extract shared computeTotalFlowRate to energy.ts
2026-03-18 13:00:17 +01:00
Wendelin
a8478ab346 Fix copy-to-clipboard in unsecure context (#30204) 2026-03-18 11:56:35 +00:00
Petar Petrov
3ac2434b6f Rescale Y-axis on chart zoom via custom AxisProxy filterMode (#30192)
* Rescale Y-axis on chart zoom via custom AxisProxy filterMode

Patch ECharts' AxisProxy.filterData to support a "boundaryFilter" mode
that keeps the nearest data point outside each zoom boundary while
filtering distant points. This lets ECharts natively rescale the Y-axis
to the visible data range without causing line gaps at the zoom edges.

* Add tests for ECharts AxisProxy patch internals

Verify that the ECharts internals our boundaryFilter patch relies on
still exist (filterData, getTargetSeriesModels on AxisProxy prototype),
and test the patch behavior: delegation for other filterModes, early
return for non-matching models, and correct boundary-preserving filtering.

* Update comment
2026-03-18 10:58:51 +00:00
Aidan Timson
f2f1044992 Format map card (#30202) 2026-03-18 12:52:06 +02:00
ildar170975
53bc66883a Map card editor: add more options (#29759)
* optimize ThemeMode

* add more map card options

* fix a type for label_mode

* enhance EntitySelectorFilter

* fix a type for label_mode

* fix classes for Map card

* add "required" flag

* add more options

* remove leading space

* simplify a bit

* Update src/panels/lovelace/editor/config-elements/hui-map-card-editor.ts

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

* fix labels & simplify _deleteLabelModeOptions()

* move translations to generic

* resolving conflicts

* revert

* remove filterFunc

* use include_entities

* add a missing line

* prettier

* prettier

* Update src/panels/lovelace/editor/config-elements/hui-map-card-editor.ts

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

* simplify _computeLabelCallback()

* Update src/panels/lovelace/editor/config-elements/hui-map-card-editor.ts

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

* set location entiites in firstUpdated()

* add "disabled"

* disable "color" opton for a "zone" entity

* Update src/panels/lovelace/editor/config-elements/hui-map-card-editor.ts

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

* Update src/panels/lovelace/editor/config-elements/hui-map-card-editor.ts

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

* move MapCardMarkerLabelMode to ha-map

* move MapCardMarkerLabelMode to ha-map

* move MapCardMarkerLabelMode to ha-map

* move MAP_CARD_MARKER_LABEL_MODES to ha-map

* little fix

* fix import

* typo in import

* linter

* linter

* rename _disabledOptions -> _shouldDisableOptions

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2026-03-18 09:08:45 +00:00
Aidan Timson
d795bd1f61 Valve favorites (#30190)
* Setup valve favorites

* Setup card feature

* Fix styles, match covers

* Merge numeric favorites card features

* Merge favorites handlers in more info favorites

* Use correct key

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

* Add translation

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2026-03-18 11:03:59 +02:00
Norbert Rittel
869e1d32b3 Replace remaining occurrences of "grid return" with "grid export" (#30199) 2026-03-18 10:53:31 +02:00
Aidan Timson
3370bfa9dd Move Device and Entity triggers and conditions to Generic group (#30185)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-03-18 09:07:27 +01:00
Paul Bottein
b1921d1b66 Use explicit default name in entity name picker and lovelace cards (#30189) 2026-03-18 09:02:04 +01:00
renovate[bot]
c2a2b382e9 Update dependency jsdom to v29 (#30198)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-18 08:57:11 +01:00
Qusai Ismael
7d95c2b6cb Fix missing conversation language picker in new pipeline dialog (#30194) 2026-03-18 08:56:04 +01:00
Allen Porter
67536a8a64 Display thinking steps and tool calling in the assist dialog (#29680)
Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2026-03-18 08:47:17 +01:00
Joe Julian
3d89ad4f91 calendar: move to "today" daily at midnight (#30177)
calendar: move to today daily at midnight
2026-03-18 08:50:42 +02:00
Paul Bottein
36e08367d9 Use domain-specific label for Edit button in more info dialog (#30195) 2026-03-17 19:07:14 +01:00
karwosts
6e3cf3e42f Fix tag dialog (#30191) 2026-03-17 16:15:35 +01:00
Jan-Philipp Benecke
fe53656c7e Enhance create new automation/script dialog with search and adaptive dialog (#30188)
* Enhance create new automation/script dialog with search and adaptive dialog

* Always show tip

* Address review comments

* Use multiTermSearch
2026-03-17 16:11:26 +02:00
Wendelin
14615191f4 Introduce ha-input (#29878) 2026-03-17 14:45:07 +01:00
Christopher Fenner
86ca8ebf71 Change battery border color to energy-battery-out (#30181) 2026-03-17 14:17:35 +02:00
Aidan Timson
16c1db5346 Cover favorites (#29997)
* Make favorites UI reusable

* Setup cover favorites

* Fix

* Make generic

* Remove

* Move

* Add type for keys

* Remove

* Move

* Types

* Types

* Changes for tilt position

* Cleanup

* Add missing options

* Replace cover preset features with favorites features

Editor points to more info instead of allowing cusomisation of feature

* Fix drag

* Remove learn more

* Add domains with favorites shared data

* Support recent additions: reset and copy favorites

* Move favorites logic into new file, futureproof for new domains

* Await all

* Refactor

* Use copy for primary action

* Allow empty lists

* Rename

* Use better ally labels

* Move favorites options above and use divider

* Move

* Use proper DOM types

* Use proper type

* Use proper types

* Cleanup

* Type from param

* Center align label

* Only show labels if both show
2026-03-17 14:14:47 +02:00
Aidan Timson
b9568c079e Use ha-scrollbar in add automation item dialog (#30187)
Use ha-scrollbar for add automation item dialog
2026-03-17 13:11:23 +01:00
Norbert Rittel
0f9d90c217 Use "export" and "import" in Grid neutrality gauge card (#30183) 2026-03-17 11:11:31 +00:00
renovate[bot]
ad092df9e0 Update dependency @bundle-stats/plugin-webpack-filter to v4.22.0 (#30186)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-17 11:40:02 +01:00
renovate[bot]
484473080e Update dependency lint-staged to v16.4.0 (#30184)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-17 11:27:11 +01:00
Paul Bottein
97dcc62ae7 Simplify entity name computation (#30147)
* WIP: start entity naming cleanup

* Clean device page

* Remove rename device logic

* Remove rename device logic in zwavejs

* Fix tests

* Fix entity name fallback

* Add test

* Fix type name

* Update name field in entity registry editor
2026-03-17 11:11:38 +01:00
Simon Lamon
d12d4811b4 Update workbox-build (#30172) 2026-03-17 09:10:56 +00:00
renovate[bot]
3747abe1a9 Update dependency lint-staged to v16.3.4 (#30180)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-17 09:25:05 +01:00
Wendelin
8541494b3d Migrate date picker to cally calendar-date (#29994)
* Switch date picker to cally calendar-date

* Remove app-datepicker styles from date picker dialog

Clean up unused CSS overrides in ha-dialog-date-picker.

Remove custom properties, focus/body rules, and responsive media
queries for app-datepicker and calendar-date so the component uses
upstream defaults and avoids duplicate styling

* Review

* Apply suggestion from @MindFreeze

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

* Fix date parsing in HaDialogDatePicker to handle ISO string format

* Update src/components/ha-dialog-date-picker.ts

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2026-03-17 10:18:18 +02:00
Petar Petrov
2d1e211034 Fix negative monetary values displayed as positive (#30178) 2026-03-17 09:11:15 +01:00
Tom Carpenter
147a21db27 Remove duplicate final point in bar statistics-chart (#30175)
For bar charts, we don't need to close out the final segment. All this does is produce a duplicate final bar.
2026-03-17 08:25:23 +02:00
karwosts
e2e5feb8d5 Move loadConfig to common mixin (#30171) 2026-03-16 17:34:41 +02:00
Aidan Timson
619c75ac8b Update apps filtering and styles to show stage and remove advanced mode filters (#30165)
* Remove filter for unstable app stages

* Show stage in app store repository view

* Increase size of cards to accomadate badges

* Add stage to installed apps, update style

* Make "Search" consistent, "Search apps" not needed in this context

* Remove show advanced mode logic for app visibility

* Add margin to .addition

* Remove [deprecated] from app page title also

* Use common helper
2026-03-16 16:12:03 +02:00
Joakim Sørensen
5b09101903 Fix passing click handler to ha-switch in cloudhooks section (#30166)
Fix passing clickhandler to ha-switch in cloudhooks section
2026-03-16 15:27:44 +02:00
Aidan Timson
408efd2e2c Fix event entity row propagation (#30163)
* Stop event entity row value propagation

* Catch interaction
2026-03-16 14:32:50 +02:00
karwosts
fd0b503a21 Show errors loading automation config (#30158)
* Show errors loading automation config

* Update src/panels/config/automation/ha-automation-editor.ts

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

* prettier

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2026-03-16 14:27:15 +02:00
Norbert Rittel
1e9a1eaa04 Fix duplicated "shows" and excessive comma in user-facing string (#30161) 2026-03-16 08:37:32 +01:00
dependabot[bot]
b42d52b854 Bump release-drafter/release-drafter from 6.4.0 to 7.0.0 (#30160)
Bumps [release-drafter/release-drafter](https://github.com/release-drafter/release-drafter) from 6.4.0 to 7.0.0.
- [Release notes](https://github.com/release-drafter/release-drafter/releases)
- [Commits](6a93d82988...3a7fb5c85b)

---
updated-dependencies:
- dependency-name: release-drafter/release-drafter
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-16 08:30:45 +02:00
dependabot[bot]
21a5e643ac Bump softprops/action-gh-release from 2.5.0 to 2.6.1 (#30159)
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.5.0 to 2.6.1.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](a06a81a03e...153bb8e044)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-version: 2.6.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-16 08:30:00 +02:00
renovate[bot]
52ac052baf Update vitest monorepo to v4.1.0 (#30155)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-15 16:33:02 +01:00
renovate[bot]
a13ffa3c35 Update CodeMirror (#30154)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-15 16:22:17 +01:00
Tom Carpenter
c51535eb79 Fix Statistic Entity Picker showing no entities/uncaught exception (#30144)
Correct Statistic Picker _getAdditionalItems

This should be a lambda function saved to a property, not a method. Otherwise when called  the "this" is the caller not the statistic picker. This was causing the Statistic Card entity picker to load blank.
2026-03-14 09:16:39 +02:00
renovate[bot]
5ea05768a3 Update dependency terser-webpack-plugin to v5.4.0 (#30148)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-13 18:16:06 +01:00
Alex Brown
d10376e9d8 Matter lock manager (#28672)
* Initial implementation of Matter lock pin management and events.

- Implement lock codes
- Implement lock events
- Implement lock schedules and guest codes

* Initial implementation of Matter lock pin management and events.

- Implement lock codes
- Implement lock events
- Implement lock schedules and guest codes

* Initial implementation of Matter lock pin management and events.

- Implement lock codes
- Implement lock events
- Implement lock schedules and guest codes

* - Copilot fixes

* - Requested improvements on how the UI screens render including:
   - Cancel button location
   - Alignment of delete icons and buttons

* Updates to support new PR for backend

* Update as per PR comments

* Fixes to align to new backend design.

* Fixes for user deletion

* Fixes for PR comments

* Delete test/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json

* Remove unused code

* Updates with review feedback

* PR Comments

* Fixed linting error

* Fixes for new dialog changes

* Added debugging for errors, aligning to other areas where this is used.

---------

Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com>
Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2026-03-13 16:20:21 +00:00
Tom Carpenter
e21f7baa93 Support Energy Collections in Statistics Graph Card Visual Editor (#29628)
* Add date selection keys to statistics-graph editor

Add support for the `energy_date_selection` and `collection_key` entires in the visual statistics-graph card editor, along with validation of the collection key in the statistics-graph's setConfig.

* Add missing option to stats card editor

Was missing the expand_legend config option in the visual editor.

* Tidy statistics graph editor arrangement

1. Group the various settings cleanly.
2. Auto-hide the collections key if energy date picker selection is disabled
3. Auto-hide days to show if a collection is linked

* Add "auto" option for statistics-chart period

When using the energy date picker option, enable the ability to select a period of "auto" which will enable use of the energy systems automatic period selection function.

* Correct hiding days to show

Should be hidden whenever energy_date_selection is true, regardless of collection key to cope with upgrading existing cards for which no key has been set.

* Hide "auto" period on statistics graph card editor
When not using energy, hide the auto option to avoid confusion.

* Swap date selection and collection key order

This keeps the toggle in a consistent location.

* Remove duplicate config key

There is a title? key now in EnergyCardBaseConfig.

* Correct energy_date_selection translation

* Improve collection key description

* Improve terminology for energy card collection
2026-03-13 18:15:53 +02:00
Aidan Timson
14098e0d7e Remove extra spacing for automation editor content (#30084)
* Remove extra spacing for automation editor content

* Remove extra margin for header

* Restore some padding
2026-03-13 15:53:40 +02:00
Joakim Sørensen
bd8c407690 Fix formatting of ha-switch in cloud remote preferences panel (#30143) 2026-03-13 13:47:32 +00:00
Aidan Timson
c452acf6cf Prevent scrim close on settings view for more info (#30140)
* Prevent scrim close on settings view for more info

* Inline
2026-03-13 12:59:27 +02:00
Aidan Timson
814106e4aa Prevent scrim close on edit home overview dialog (#30139) 2026-03-13 12:59:01 +02:00
Aidan Timson
a2254eb048 Add my link for home overview (#30137)
Add my link for overview
2026-03-13 11:48:33 +01:00
Tom Carpenter
fa5e0bb0c7 Support Energy Collections in Statistic Card Visual Editor (#29629)
* Improve energy support for statistics card

Rather than setting the period to "energy_date_selection", add an energy_date_selection key to the config to be more consistent. The original configuration option is still supported for backwards compatibility

Update the statistic card visual editor to support energy collection key selection.

* Add statistics card collection key validation

* Apply suggestions from code review

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

* Remove title from EnergyCardBaseConfig

This was added when the energy card visual editors were created, but not all cards using a collection key need a title. Using Omit to remove it seems to lose the extend of LovelaceCardConfig.

Instead add EnergyCardConfig which is EnergyCardBaseConfig with the title field. This is used for a number of cards to allow them to share the same visual editor without having to list out every one.

* Mark Statistic Period calendar.offset as optional

It is already handled in core as an optional key (defaults to 0), and the statistic card/editor was explicitly omiting the key even though it was declaring it as required.

This removes the need for an error masking cast when converting the deprecated PERIOD_ENERGY to STATISTIC_CARD_DEFAULT_PERIOD.

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2026-03-13 12:47:37 +02:00
renovate[bot]
e8e62a0aa0 Update dependency @codemirror/view to v6.39.17 (#30133)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-13 09:12:15 +00:00
renovate[bot]
4893b631a5 Update dependency @rspack/core to v1.7.8 (#30134)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-13 09:12:08 +00:00
Paul Bottein
d851a5bdf3 Fix entities not updated in device page (#30136) 2026-03-13 09:11:11 +00:00
ildar170975
148d01b05b hui-entities-card-row-editor: add margin-bottom for "add-entity" & prevent a clipping (#30123) 2026-03-13 08:38:17 +00:00
renovate[bot]
171875c65c Update dependency lint-staged to v16.3.3 (#30127)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-13 08:35:58 +02:00
renovate[bot]
6a61186507 Update dependency babel-loader to v10.1.1 (#30120)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-13 08:30:13 +02:00
renovate[bot]
1690991726 Update dependency typescript-eslint to v8.57.0 (#30119)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-13 08:29:53 +02:00
renovate[bot]
4904566460 Update dependency barcode-detector to v3.1.1 (#30118)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-13 08:29:28 +02:00
Logan Rosen
b3b8f1a38a Filter ResizeObserver loop errors from dev server overlay (#30125)
The @rspack/dev-server overlay catches ResizeObserver loop errors as
runtime crashes, showing a full-screen error overlay that blocks
development. These errors are benign — they originate from
lit-virtualizer (used by ha-data-table) and simply mean not all
resize observations could be delivered in a single animation frame.

Add a client.overlay.runtimeErrors filter to the dev server config
that suppresses ResizeObserver loop messages while still surfacing
all real runtime errors.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-13 08:28:58 +02:00
Logan Rosen
3bb870f52a Fix gallery integration card crash from invalid mock hassUrl (#30126)
The gallery mock set hass.auth.data.hassUrl to an empty string, which
caused brandsUrl() to throw when constructing a URL via new URL(base, '').
In production hassUrl is always a valid URL, so the mock now uses
location.origin to match real behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-13 08:27:39 +02:00
Logan Rosen
95a7b91ea5 Fix media-player mock attrs for gallery seek slider (#30124)
Fix media-player mock state attributes for gallery seek UI

Expose media-player capability and state attributes via entity hooks so gallery music mocks render position bars. Keep the domain-hook model and null state attrs when off to match other mocks.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-13 08:26:44 +02:00
Aidan Timson
29b3303f94 Center justify more info controls (sliders, etc) (#30117) 2026-03-12 16:46:31 +01:00
AlCalzone
5dc2c51251 Remove Z-Wave Installer panel (#30115)
* Initial plan

* Remove Z-Wave expert panel (installer mode) references

Co-authored-by: AlCalzone <17641229+AlCalzone@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: AlCalzone <17641229+AlCalzone@users.noreply.github.com>
2026-03-12 16:22:47 +02:00
renovate[bot]
571fccf4a0 Update formatjs monorepo (#30114)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-12 15:27:23 +02:00
Tom Carpenter
8f958147dd Add Visual Editors for Energy Dashboard Cards (#29483)
* Add common API for energy collection keys

These allow so far for:
1. Creating a valid collection key
2. Validating that a collection key is useable
3. Getting a list of all currently active collection keys

Currently the validation/creation is simply checking the prefix. In time this could be extended to add more validation - e.g. sanitising punctuation/spaces/etc.

Cards configured using a visual editor will in the future ican use these APIs to distinguish between collection keys created manually in YAML/internally by HA from those created by the visual editor.

* Create visual editor for energy date selector

Allow creating date selection cards directly from the lovelace card dialog rather than requiring manual creation.

To aid in this, a date selection editor card has been created to allow using the visual editor. This includes options for setting the vertical opening direction, whether comparison is enabled/disabled, and creating a collection key.

To keep things hopefully more accessible to users, the `energy_` prefix for the collection key can be omitted from the text field. It will be added automatically if necessary. The prefix must be added if editing in the YAML editor.

Validation of the collection key is also performed, providing an error message if the key is invalid.

* Allow visual editors only for UI collection keys

Prevent the visual editors from being used if the collection key doesn't have the correct prefix. This is to avoid breaking existing user dashboards that predate the visual editor.

Additionally use the helper in energy dashboard for stripping the UI prefix.

* Create visual editor for sankey cards

Allow creating energy/water/power sankey cards directly from the lovelace card dialog rather than requiring manual creation.

A sankey chart editor card has been created to allow using the visual editor. This includes setting of all common options for sankey cards including layout, groups, and selection of a collection key.

To keep things simple for users, the visual editor displays the collection_key field as a drop-down menu from which all active collection keys may be selected.

* Create visual editor for energy graph cards

Allow creating energy/solar/gas/water usage graph cards directly from the lovelace card dialog rather than requiring manual creation.

A common graph editor card has been created to allow using the visual editor. This provides a basic set of common properties - currently just title and collection key.

To keep things simple for users, the visual editor displays the collection_key field as a drop-down menu from which all active collection keys may be selected.

* Assign visual editor to compare card

This is not assigned to the lovelace card selector as its not strictly a card, but rather an alert. However it might as well be assigned to the visual editor for consistency.

* Add Power Sources Graph

This was missed when adding other graphs. Currently it shares the same editor which means the optional show_legend key cannot yet be set visually. In time this could be updated.

* Update energy card descriptions

Update descriptions and names of cards in the lovelace card selector to be consistent with the energy card documentation.

* Create visual editor for energy device graph cards

Allow creating device and detailed device graph cards directly from the lovelace card dialog rather than requiring manual creation.

* Pass config type to devices schema

Rather than converting to a boolean, pass in the type. This will make expanding easier if future devices cards are added.

* Add missing fields to Energy Graph Card Editor

Add options to set `show_legend` and `link_dashboard` config fields.

* Add energy gauge visual editing

These need no special configuration, so might as well reuse the generic energy graph card editor.

* Add option to ignore onInput for ha-form-string

For the ha-form-string element on e.g. the card editor dialog, it would be useful for some types of input to not send value changed events for incomplete changes.

Currently the form triggers the event whenever a character is typed/deleted by forwarding onInput event. To avoid this where undesireable, add a new optional schema entry to ignore these events, which if defined and true sees updating only in response to the onChange callback.

For the energy date picker, this will avoid unnecessary creation of collection keys and by extension energy API connections when using the visual editor improving efficiency.

* Disable preview of energy cards in card picker
To avoid excess querying of the backend for energy data when loading the card picker dialog.

* Set default energy collection key to dashboard name

This changes the behaviour so that by default any component without a collection key will be grouped with other elements on their dashboard, but not with elements on other dashboards.

* Add stub config for date picker

To auto-populate a default picker configuration, including current dashboard name as the collection key.

* Add Stub Configs for other Energy Cards

Auto-populate the default parameters and the current dashboards default collection key.

* Add isActive Getter to Energy Collection

* Add findEnergyDataCollection

This will look for an existing data collection but will not create a new one if it doesn't exist.

To help with this, move the logic for creating the connection key name into a function shared between the new findEnergyDataCollection and the existing getEnergyDataCollection.

* Convert energyCollectionKeys to a Set

All entries should be unique, so use set behaviour.

* Fix capitalisation in translations

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

* Revert unintentionally committed change

* Revert "Add option to ignore onInput for ha-form-string"

This reverts commit 1cae35db25.

* Remove hiding of collection key prefix

For now keep things simple and show the whole prefix as creation will probably be overhauled in the future.

To assist the user, in the date picker, automatically add the required prefix when the user types into the collection key box.

* Use panelUrl directly for default collection keys

* Update src/translations/en.json

Fix typo

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

* Ensure energy cards appear at end of card picker

Add a new `sortAtEnd` option which will cause cards with this sort parameter to sink to the bottom of the card picker.

* Remove collection key from stubConfig
Empty is fine, will convert to same default key anyway.

* Make all collection keys text fields
Remove the active energy collection dropdown.

* Revert "Ensure energy cards appear at end of card picker"

This reverts commit 28834aaa55.

* Add (hidden) energy cards to card picker

Merge in changes originally proposed in #23499.

* Resolve Merge Conflicts

* Fix Lint Error

* Update energy card descriptions

Make them consistent with other card descriptions which start with "The <name of card> card shows ...".

* Change Energy Card Descriptions
Fix the descriptions to use `This card` not `The ... card`.

* Change hide_compound_stats Description
Change to 'upstream devices' to match energy config page descriptions.

* Remove superfluous translation
The type field was not intended to be added to the label callback.

* Remove unused collection key exports

* Remove createEnergyCollectionKey

Was helpful when tying into the collection_key field to auto-add the prefix, but so be it.

* Add visual editor to water flow sankey
Can use the same sankey editor as the others. Update the description of energy sankey to better describe what is being displayed and to match the description of water vs water flow sankey.

* Add getStubConfig to water flow sankey

* Add missing water-flow-sankey to validation

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

* Fix Date Picker Collection Key Localise

* Move Collection Key to end of Visual Editor
Downplay its significance - move it to the end of the list of options.

* Fix default collection key check
Intended behaviour was to check that the panel URL was not empty - defaultKey will always be truthy!

* Add Opening Direction to Date Picker Config

* Omit "inline" opening direction

This breaks the visual editor as the date selection window suddenly popus up in the preview and is uncloseable.

* Update src/panels/lovelace/editor/lovelace-cards.ts

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

* Add visual editor for energy-sources-table

The only energy card now missing a visual editor, create the editor for completeness.

* Add name/description for Energy Compare card

* Add preview mode to energy-compare-card

Otherwise it just appears blank in the card and dashboard editors. In preview it will just use the current date for start/end and not provide the dismiss option just to give an impression of how it will look.

* Add type check in energy-graph-card-editor

* Add documentation link for energy-compare-card

* Add documentation links for energy badges

* Fix value lost in dev merge

* Remove dismiss from compare card preview

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

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
Co-authored-by: Norbert Rittel <norbert@rittel.de>
Co-authored-by: karwosts <karwosts@gmail.com>
Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>
2026-03-12 15:17:41 +02:00
xtymmms1021
41753e48bf Guard discovered devices flow subscription for non-admin users (#30110)
* Guard discovered devices flow subscription for non-admin users

* Address review feedback on hass non-null assertions
2026-03-12 14:46:59 +02:00
Paul Bottein
d496b2a6fe Add token for brands url in hassUrl helper (#30111) 2026-03-12 10:27:43 +00:00
Copilot
3260c48130 Extract AutomationSortableListMixin to deduplicate automation list components (#29977)
* Initial plan

* Extract shared automation rows logic into AutomationRowsMixin

Create ha-automation-rows-mixin.ts with common list manipulation methods
(moveUp, moveDown, move, itemMoved, itemAdded, itemRemoved, itemChanged,
duplicateItem, insertAfter, handleDragKeydown, stopSortSelection, getKey)
and shared properties (hass, disabled, narrow, optionsInSidebar,
_rowSortSelected, _clipboard).

Refactor ha-automation-trigger, ha-automation-condition,
ha-automation-action, and ha-automation-option to use the mixin,
eliminating significant code duplication.

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

* Address review feedback: rename to AutomationSortableListMixin, remove _ prefix from protected members, throw in setHighlightedItems

- Rename AutomationRowsMixin to AutomationSortableListMixin
- Rename file to ha-automation-sortable-list-mixin.ts
- Remove _ prefix from all protected methods/properties
- Make setHighlightedItems throw Not implemented error
- Update all 4 component files with new references

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

* Add missing this.items = items in itemChanged to avoid UI jump

The original _conditionChanged had this.conditions = conditions before
fireEvent to update local state and avoid UI jumps. This was lost during
the mixin extraction.

Co-authored-by: MindFreeze <5219205+MindFreeze@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: wendevlin <12148533+wendevlin@users.noreply.github.com>
Co-authored-by: MindFreeze <5219205+MindFreeze@users.noreply.github.com>
2026-03-12 09:19:57 +00:00
Simon Lamon
a356c153f8 Demo fixes (#30105) 2026-03-12 08:53:25 +01:00
Matthias de Baat
b60dc94196 Add protocol logos (#30104) 2026-03-12 08:52:37 +01:00
Tom Carpenter
c5cb196aa1 Use whole months for "Last 12 Months" on Energy Date Picker (#30091)
* Make "now-12m" use month boundaries

Currently the "Last 12 Months" date function calculates precisely 12 months from the current day. So today is March 10, then it would retrieve April 11 to March 10.

However logically the name implies whole months - i.e. "now" would be "March", so the last 12 months ought to be Apr 1 to March 31.

* Let energy date picker detect 12 Month range

With now-12m changed to mean whole months rather than partial months, we can make the energy date picker nicely detect and render any month range, and also fix the now button so that it can pick a "now-12m" range.

* Use Short Month for Displayed Range
To avoid making the displayed range too large to fit on small screens.

* Move subMonths into calcDate fn not input

Perform both startOfMonth and subMonths as a custom function in calcDate rather than performing subMonths on the input to avoid any wierd timezone issues.

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

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2026-03-12 07:40:50 +00:00
Josef Zweck
5ddb31ce7b Add "cleaning_up" backup stage (#30106)
* Add "cleaning_up" backup stage

* Update ha-backup-overview-progress.ts

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

* prettier

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2026-03-12 09:25:24 +02:00
Norbert Rittel
7465c9874c Improve "The … is disabled by …" message in Device info (#30107)
- remove the trailing period as it's shown in a box
- sentence-case "user", "integration" and "config entry"
2026-03-12 09:09:14 +02:00
renovate[bot]
bbe81ca8f1 Update dependency tar to v7.5.11 [SECURITY] (#30108)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-12 09:08:31 +02:00
Logan Rosen
51ecdb0851 Expand ha-slider touch target for volume slider usability (#30109)
Add padding to the #slider element in ha-slider to expand its touch
target to 32px, making sliders easier to interact with on touch devices.

Remove the pointer-events: none workaround on the volume slider in the
media player more-info dialog. This workaround disabled touch interaction
entirely on coarse pointer devices, which is no longer needed now that
the slider has an adequately sized touch target.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-12 07:03:26 +00:00
Matthias de Baat
268c0a60f0 Faster load Zigbee numbers (#30096) 2026-03-11 15:47:41 +00:00
ildar170975
8745cfa1ed Distribution card: fix height & layout settings (#29712)
fix height & fixed_rows
2026-03-11 14:58:47 +01:00
Johan Henkens
8bcccb5792 Allow trace graph to scroll independently of the step-details tab (#29906)
* Allow trace graph to scroll independently

* Apply independent trace graph scrolling to script trace

Port the independent column scrolling and sticky nav overlay from
ha-automation-trace to ha-script-trace, and add scroll-to-active-node
behavior to hat-script-graph.

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

* Re-encapsulate nav buttons inside hat-script-graph

Move the up/down navigation buttons back into hat-script-graph where they
belong, restoring _previousTrackedNode/_nextTrackedNode as private. Wrap
the graph content in a div.graph-scroll so it scrolls independently while
the button column stays fixed — the same flex-row layout the parent was
using, now self-contained inside the shadow DOM.

This removes the duplicated nav overlay markup and disabled-state logic
from ha-automation-trace and ha-script-trace, and removes the unnecessary
public surface on HatScriptGraph.

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

* Make not-scrollable more specific.

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2026-03-11 15:08:10 +02:00
Aidan Timson
0844d9c2c0 Highlight linked config entry again (#30095)
* Highlight linked config entry again

* Fix export
2026-03-11 15:05:58 +02:00
karwosts
9d3aafe219 Light favorite color card feature (#29995)
* Light favorite color card feature

* rewrite resizeObserver

* code review

* Apply suggestions from code review

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

* Apply suggestion

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

* Apply suggestion from @MindFreeze

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2026-03-11 12:59:01 +00:00
Artur Pragacz
0cf3e34956 Use group entities attribute (#30094)
* Use group entities attribute

* Include min_max

* Remove domain check
2026-03-11 14:54:45 +02:00
Brooke Hatton
7d20b04469 Filter hidden entities in common controls section (#29871)
Filter hidden entities in common controls section strategy

Match filtering behavior of area/room view strategies by excluding entities
marked as hidden (via hidden_by: user/integration/device) from the common
controls section. This ensures consistency across all auto-generated
dashboard strategies and respects user preferences for entity visibility.
2026-03-11 12:10:45 +01:00
Simon Lamon
6f6ba71e61 Redesign gauge card (#29981)
* Redesign gauge card

* Fix calculation

* New design, code needs optimization (ai)
2026-03-11 11:22:44 +02:00
Petar Petrov
d61de4d2df Disable smoothMonotone in line graphs (#30093) 2026-03-11 09:01:37 +00:00
Bram Kragten
f8dcee6867 Allow to assign a color to a map entity in the map card (#30088)
* Allow to assign a color to a map entity in the map card

* Update en.json
2026-03-11 08:31:12 +02:00
Tom Carpenter
0f975acca1 Show energy date picker previous/next on smaller screens (#30092)
* Fix missed spacing token usage

* Reduce Date Picker Collapse Button Width

Remove unnecessary padding between date icon button and date text and allow next/prev to be shown for 20px narrower screens.

* Reduce calendar icon size for narrow screens

Allows use of screens down to 325px wide before the next/prev buttons are lost.

Given most android screens are >=360px this is a big improvement as before this change the hide limit was 370px.
2026-03-11 08:27:29 +02:00
Tom Carpenter
9e57dacb3c Fix energy dashboard date picker opening direction (#30090)
* Add Opening Direction to Date Picker Config

* Force date picker opening direction on energy dash
2026-03-11 08:12:22 +02:00
Aidan Timson
2650c1c0b2 Fix code editor autocomplete using wa popup (#30081) 2026-03-10 17:20:40 +01:00
Bram Kragten
6957e1f4d9 Introduce Lazy context provider (#29988)
Co-authored-by: Wendelin <w@pe8.at>
2026-03-10 16:19:48 +01:00
Yosi Levy
32be26320d Support for ESC in notification drawer (#29521) 2026-03-10 15:47:35 +01:00
Paul Bottein
cdbaa85beb Fix debounce with immediate firing twice on single call (#30082) 2026-03-10 13:39:44 +01:00
Bram Kragten
b58fc68ea8 Add warning when importing blueprint from unknown source (#30076) 2026-03-10 09:05:39 +00:00
renovate[bot]
4792659c61 Update dependency @babel/helper-define-polyfill-provider to v0.6.7 (#30075)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-10 08:59:26 +00:00
Paul Bottein
eeae66e026 Add default state/numeric_state options to automation triggers and conditions (#30052) 2026-03-10 08:54:00 +00:00
renovate[bot]
60b4b1c4e2 Update dependency eslint to v9.39.4 (#30079) 2026-03-10 07:42:05 +01:00
Aidan Timson
877c5e8d16 Validate min-max with dialog box (#30071)
* Validate min-max for dialog box

* Use check

* Remove redundant call
2026-03-09 17:17:28 +02:00
Josef Zweck
11fd10a011 Report progress for backup upload (#29748)
* Add progress bar for backup uploads

* add sort

* visual fixes

* fix sorting

* react to event

* cleanup

* remove log

* remove fom union type

* different progress bar

* styling fixes

* guard against empty space in other backup states

* cleanup

* xleanup

* add checkmark on completion

* remove progress bar

* remove spinner during upload

* remove spinner, animate

* add subtext

* prettier

* review comments

* linesbreaks
2026-03-09 17:12:54 +02:00
renovate[bot]
623baea59d Update dependency babel-loader to v10.1.0 (#30069)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-09 11:00:53 +00:00
Paul Bottein
052909ea01 Make bar-gauge card feature non-clickable (#30068) 2026-03-09 10:52:09 +00:00
Petar Petrov
5e3c80ef6e Fix hasReturn check to scan all grid sources in energy view strategy (#30062) 2026-03-09 10:51:28 +00:00
Bram Kragten
6e2fdbb396 Add reorder support to area selector (#30056) 2026-03-09 09:24:20 +01:00
Tom Carpenter
5b981062f6 Add missing webawesome tooltip CSS variable (#30057)
* Correct missing ha-tooltip CSS variable

We were missing a default for the `--wa-tooltip-border-width` variable which meant the arrow from the tooltip disappeared in WA 3.3.1.

* Fix tooltip in ha-slider
2026-03-09 09:25:21 +02:00
Tom Carpenter
5a2d7f100a Don't include "null" data point in stat graph (#30058)
When displaying the "now" value on statistics graphs, don't include a "null" data point for sum/change type graphs, just skip entirely.

Otherwise for you get a messy null data point in the tooltip.
2026-03-09 09:02:00 +02:00
dependabot[bot]
b62e6c9328 Bump release-drafter/release-drafter from 6.2.0 to 6.4.0 (#30059)
Bumps [release-drafter/release-drafter](https://github.com/release-drafter/release-drafter) from 6.2.0 to 6.4.0.
- [Release notes](https://github.com/release-drafter/release-drafter/releases)
- [Commits](6db134d15f...6a93d82988)

---
updated-dependencies:
- dependency-name: release-drafter/release-drafter
  dependency-version: 6.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-09 08:50:31 +02:00
dependabot[bot]
537ab2debb Bump actions/setup-node from 6.2.0 to 6.3.0 (#30061)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 6.2.0 to 6.3.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](6044e13b5d...53b83947a5)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: 6.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-09 08:50:00 +02:00
dependabot[bot]
04d31e4ea9 Bump github/codeql-action from 4.32.4 to 4.32.6 (#30060)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.32.4 to 4.32.6.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](89a39a4e59...0d579ffd05)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.32.6
  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-03-09 08:49:05 +02:00
renovate[bot]
3d0c7b6aab Update dependency @html-eslint/eslint-plugin to v0.58.1 (#30054)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-08 16:53:13 +01:00
renovate[bot]
87d3d06e79 Update dependency @home-assistant/webawesome to v3.3.1 (#30055)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-08 16:38:56 +01:00
Wendelin
872d1c684f Simplify dialogs (#29848) 2026-03-08 16:27:04 +01:00
renovate[bot]
d9d2ebfb9d Update dependency tar to v7.5.10 [SECURITY] (#30002)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-08 08:42:53 +00:00
renovate[bot]
e26315c4d2 Update dependency @html-eslint/eslint-plugin to v0.58.0 (#30047)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-08 09:35:44 +01:00
renovate[bot]
d9a2e1cbee Update dependency marked to v17.0.4 (#30046)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-08 09:34:54 +01:00
Petar Petrov
b7d6652e5c Add back energy distribution card to electricity tab (#30049) 2026-03-08 09:34:28 +01:00
renovate[bot]
b188da0b5c Update dependency serve to v14.2.6 (#30030)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-07 10:24:46 +00:00
karwosts
9f87257f41 Support copying light favorites to other lights (#30034) 2026-03-07 11:14:48 +01:00
renovate[bot]
f4e409f157 Update dependency sinon to v21.0.2 (#30041)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-07 11:13:53 +01:00
renovate[bot]
44d727763c Update dependency lint-staged to v16.3.2 (#30029)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-06 19:44:50 +01:00
Petar Petrov
2f87614c4c Fix stale data point in history-graph cards with sub-hour windows (#29998)
Skip fetching hourly statistics when hours_to_show < 1 since hourly
aggregates produce stale outlier points in sub-hour chart windows
(e.g. hours_to_show: 0.1 or 0.05).

Also fix Date object handling in ha-chart-base downsampling bounds
extraction.
2026-03-06 19:42:52 +01:00
Yosi Levy
697a945056 RTL textfield fixes for quick search (#30013)
textfield fixes for quick search
2026-03-06 19:42:21 +01:00
Paul Bottein
67dbacce52 Improve mock for cover, alarm, lock, lawn mower, valve and vacuum (#29999) 2026-03-06 19:41:12 +01:00
renovate[bot]
e891ae6bdd Update dependency terser-webpack-plugin to v5.3.17 (#30026)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-06 19:39:16 +01:00
renovate[bot]
a11d1cfecf Update dependency @formatjs/intl-datetimeformat to v7.2.4 (#30021)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-06 19:38:32 +01:00
renovate[bot]
933bb4a2cc Update dependency fs-extra to v11.3.4 (#30025)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-06 19:34:52 +01:00
karwosts
1ca1ef3c32 Fix favorite color dragging unintended buttons (#30028) 2026-03-06 19:34:18 +01:00
renovate[bot]
f529a349b9 Update dependency @rspack/core to v1.7.7 (#30018)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-06 10:54:23 +01:00
karwosts
b3b35e4299 Menu item to reset light favorite colors (#30004)
reset-light-favorites
2026-03-05 20:46:44 +01:00
Paul Bottein
59364d05ec Improve pull request instructions for AI agents (#30001) 2026-03-05 18:38:25 +01:00
renovate[bot]
6444c0df15 Update CodeMirror (#29987)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-05 17:26:18 +02:00
Aidan Timson
8f704d8c0a Cover tilt presets card feature (#29989) 2026-03-05 17:23:28 +02:00
Petar Petrov
aeddeb3bd7 Fix sensor card graph time axis not progressing when value is unchanged (#29976) 2026-03-05 15:13:05 +01:00
Paul Bottein
1377e3a363 Improve light, climate, water heater, humidifier and fan mock (#29980) 2026-03-05 14:11:23 +01:00
Copilot
e47bf22798 Add click-to-replace interaction for selected targets in ha-target-picker (#29864)
Co-authored-by: timmo001 <28114703+timmo001@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Aidan Timson <aidan@timmo.dev>
2026-03-05 13:51:33 +01:00
Paul Bottein
b3f1ba9433 Simplify icon loading (#29953) 2026-03-05 12:36:36 +01:00
Bram Kragten
829c5ca4df Update ha-device-automation-picker.ts 2026-03-05 10:19:03 +01:00
Aidan Timson
fbd4fc6d5f Cover presets card feature (#29959)
* Add cover presets features

* Max 6 items

* Remove tilt preset

* Fix clamping

* Filter

* Apply suggestion from @MindFreeze

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2026-03-05 07:31:31 +00:00
Paul Bottein
7612ec1979 Refactor tooltip CSS tokens to use ha- prefix (#29978)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com>
2026-03-04 15:02:09 +00:00
Paul Bottein
71a5a1d3b4 Add arrow and fix footer for vacuum segment mapper (#29975) 2026-03-04 14:58:34 +01:00
Wendelin
ad91d360f8 Respect safe area top for bottom sheet max height (#29974)
Respect safe area insets for bottom sheet maximum height
2026-03-04 15:45:44 +02:00
Copilot
a88f492c05 Create ManualEditorMixin to deduplicate manual-automation-editor and manual-script-editor (#29955)
* Initial plan

* Create ManualEditorMixin to share code between manual-automation-editor and manual-script-editor

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

* Add PropertyValues type annotation to updated() in manual-script-editor

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

* Throw not implemented error in renderContent() base method

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

* Throw not implemented error in handlePaste and saveConfig base methods

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

* Fix lint:types error: add explicit Promise<void> return type to handlePaste

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

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: wendevlin <12148533+wendevlin@users.noreply.github.com>
2026-03-04 15:19:22 +02:00
Aidan Timson
0fc85a52a0 Create dedicated app store repositories and registries pages (#29931)
* Update design of app store repositories dialog

* Cleanup

* Move to new page

* Move to new page

* Update label

* Migrate translations

* Add loading state to dialog box

* Create dedicated app store registries page
2026-03-04 15:08:51 +02:00
Tom Carpenter
e01dc89a48 Prevent keyboard shortcuts firing in dropdowns (#29972) 2026-03-04 12:32:53 +00:00
Copilot
52c36db5d4 Fix plant status card not respecting sensor display precision (#29968)
Co-authored-by: wendevlin <12148533+wendevlin@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-03-04 13:30:51 +01:00
Aidan Timson
f217dab8d4 Migrate ha-toast to webawesome popover (#29952)
* Migrate ha-toast to webawesome popover

* Update closed event to new API

* Use component

* Use div

* Fix import
2026-03-04 14:21:18 +02:00
Wendelin
b89b02e044 Open quick search quicker (#29967) 2026-03-04 11:13:13 +01:00
renovate[bot]
daff323606 Update dependency lint-staged to v16.3.1 (#29970)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-04 11:35:06 +02:00
Paul Bottein
356bceb1cc Simplify mock entities in demo (#29960)
* Simplify mock entities in demo

* Clean unused file
2026-03-04 11:22:35 +02:00
renovate[bot]
acb5af2f41 Update dependency globals to v17.4.0 (#29966)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-04 09:10:02 +00:00
Marcin Bauer
17314fe62b Add animation duration tokens and button transition update (#29965)
Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com>
2026-03-04 08:36:37 +00:00
sevorl
3826a69465 Use ha-form for wait_for_trigger timeout (#29944) 2026-03-04 08:48:57 +01:00
Matthias de Baat
a68acdf9dd Update delete Z-Wave device dialog (#29956)
* Update text and spacing

* Update text

* Update src/translations/en.json

Co-authored-by: AlCalzone <d.griesel@gmx.net>

---------

Co-authored-by: AlCalzone <d.griesel@gmx.net>
2026-03-04 08:35:41 +02:00
Paul Bottein
bb56bbb1ba Align heading button font-size with other heading entity badge (#29958) 2026-03-04 08:08:22 +02:00
Bram Kragten
76aef60c05 Add hass url to brand images (#29961) 2026-03-03 21:00:43 +01:00
renovate[bot]
23af40743b Update dependency lint-staged to v16.3.0 (#29954)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-03 14:36:48 +00:00
Paul Bottein
c4326b4f3a Use max width for dashboard footer (#29947) 2026-03-03 14:57:57 +01:00
Paul Bottein
d248f5614f Add label for toggle button in area strategy (#29949) 2026-03-03 13:34:05 +01:00
Aidan Timson
a4da7b26ea Fix copy to clipboard for wa dialogs (#29951)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com>
2026-03-03 13:09:30 +01:00
Wendelin
3c49cdf3c0 ha-bottom-sheet reduce motion support (#29950) 2026-03-03 11:06:23 +00:00
Petar Petrov
26af81d1a4 Use net battery power in power sankey card (#29940) 2026-03-03 11:53:54 +01:00
Aidan Timson
2a08f2d79b Add tooltip for config dashboard action button in toolbar (#29948) 2026-03-03 10:33:37 +01:00
Marcin Bauer
a5be02b743 Add tooltip for Lovelace dropdown action button in top app bar (#29933) 2026-03-03 09:15:37 +00:00
sevorl
4228871f00 Fix missing slot attribute on wa-divider in automation sidebar action (#29942) 2026-03-03 08:56:33 +00:00
Wendelin
9a7a8fd377 Add reportValidity in ha-form (#29884)
* Add validation for required fields in ha-auth-flow before submission

* Add reportValidity methods to form components for improved validation handling

* Remove async reportValidity funcs

* Review
2026-03-03 09:05:49 +02:00
Wendelin
8b82882e15 ha-authorize fix rtl check (#29937)
Add RTL direction handling in updated lifecycle method
2026-03-02 18:22:06 +01:00
Matthias Alphart
2701015eda Fix data-table content bottom margin (#29805)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-02 17:02:31 +01:00
Aidan Timson
1991a9e493 Code editor fullscreen in dialogs (#29882)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2026-03-02 17:01:13 +01:00
Petar Petrov
2b72c54194 Migrate Energy date selector to new footer (#29867) 2026-03-02 17:00:34 +01:00
Paul Bottein
a7cb2fe7a7 Fix updates, discovered devices and repairs cards flickering (#29935) 2026-03-02 13:50:42 +00:00
Paul Bottein
51ea0c8201 Fix sidebar not closing when reduced motion is enabled (#29934) 2026-03-02 13:19:26 +00:00
Wendelin
ead7081bc6 Dialog: Add show event target check (#29927)
Add event phase check in _handleShow and _handleAfterShow methods
2026-03-02 11:41:52 +00:00
Wendelin
ee982b1899 Add error translation for loading energy preferences (#29924) 2026-03-02 11:49:46 +02:00
Aidan Timson
e8b100a39e Remove cache to fix re-add repo issue (#29926)
Remove cache to fix readd repo issue
2026-03-02 11:49:19 +02:00
Copilot
50c361db62 Add mixin to remove code duplication in automation/script editors (#29842)
* Initial plan

* Changes before error encountered

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

* Fix mixin: use function-body syntax for decorators, curried generics for type safety

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

* Simplify automation/script editor mixin signature

* Add shared styles and loading animation to automation/script editor mixin

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

* Remove underscore prefix from protected members per style guide

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

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: wendevlin <12148533+wendevlin@users.noreply.github.com>
Co-authored-by: Wendelin <w@pe8.at>
2026-03-02 11:44:04 +02:00
sevorl
e7a8d15a13 Use ha-duration-input for wait_template timeout (#29862) 2026-03-02 09:07:51 +01:00
karwosts
fbd0409837 Init ha-form expansion elements to undefined instead of null (#29900)
* Init ha-form expansion elements to undefined instead of null

* revert change to error/warning
2026-03-02 09:29:06 +02:00
karwosts
a0d100611f Fix distribution card stub error (#29915)
* Fix distribution card stub error

* unit check not required
2026-03-02 09:06:10 +02:00
dependabot[bot]
a969bf1065 Bump actions/upload-artifact from 6.0.0 to 7.0.0 (#29922)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6.0.0 to 7.0.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](b7c566a772...bbbca2ddaa)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-02 06:10:19 +00:00
renovate[bot]
a153330610 Update dependency gulp-zopfli-green to v7 (#29919)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-02 07:06:41 +01:00
renovate[bot]
bd2f1ca3a8 Update dependency @html-eslint/eslint-plugin to v0.57.1 (#29905)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-28 20:08:37 +01:00
renovate[bot]
3263034416 Update dependency @codemirror/language to v6.12.2 (#29904)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-28 20:07:43 +01:00
Paul Bottein
82b28b547a Fix control select menu color in ios (#29892) 2026-02-27 17:26:04 +01:00
Bram Kragten
61c2c750b4 Fix overflow for icon buttons (#29891) 2026-02-27 15:44:21 +00:00
Petar Petrov
117690ee70 Fix sensor card graph not updating when value is unchanged (#29889) 2026-02-27 15:41:54 +00:00
Petar Petrov
e753de85eb Make hui-sections-view always fill the screen so footer is at the bottom (#29890) 2026-02-27 15:39:21 +00:00
Paul Bottein
a240019968 Add render icon property to ha-control-select-menu (#29881) 2026-02-27 16:23:58 +01:00
Petar Petrov
0bdf4b8777 Fix monetary device class state display with non-ISO 4217 currency symbols (#29887) 2026-02-27 14:59:14 +01:00
renovate[bot]
6337828ed8 Update dependency barcode-detector to v3.1.0 (#29886)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-27 15:46:29 +02:00
Aidan Timson
b8e5af652b Add audits and yaml mode to more info details (#29854)
* Add audits and yaml mode to more info details

* Reset yaml mode on back

* Use mapped array for state entries

* Typo

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

* Memoize

* Rename

* Fix

* Format audits in normal mode

* Refactor, dont pass hass

---------

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2026-02-27 14:45:55 +01:00
Petar Petrov
e4ae29e8b5 Fix energy compare tooltip showing wrong year (#29885) 2026-02-27 14:37:52 +01:00
Aidan Timson
08231dbbb0 Use large width on system log dialogs (#29879) 2026-02-27 12:46:10 +01:00
Paul Bottein
0ca656933d Revert "Add render icon property to ha-control-select-menu"
This reverts commit b23cf8eba4.
2026-02-27 12:21:23 +01:00
Paul Bottein
b23cf8eba4 Add render icon property to ha-control-select-menu 2026-02-27 12:20:52 +01:00
Robert Resch
61b546415d Revert "Add vacuum mapping not configured issue" (#29876) 2026-02-27 11:18:49 +01:00
Brandon Chen
4e1b709303 Fix YAML content invisible in dark mode for conversation debug result… (#29874) 2026-02-27 09:11:28 +01:00
renovate[bot]
34e65b302d Update dependency typescript-eslint to v8.56.1 (#29868)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-26 18:08:04 +00:00
renovate[bot]
336d0e1b9d Update dependency @html-eslint/eslint-plugin to v0.57.0 (#29863)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-26 17:25:10 +01:00
Paul Bottein
58d4cf8d84 Fix scrollbar in 2026.3 (#29865) 2026-02-26 16:44:12 +01:00
Aidan Timson
d3453aff37 Add missing theming variable support to dialog and bottom sheet (#29857) 2026-02-26 16:43:20 +01:00
Aidan Timson
64ff2e414c Add thread configuration my link (#29861) 2026-02-26 15:06:46 +00:00
Wendelin
2ca25c980f Fix quick search icon size (#29858) 2026-02-26 15:59:27 +01:00
Aidan Timson
73d93bc601 Add matter configuration my link (#29859) 2026-02-26 14:41:43 +00:00
Wendelin
5ca6a8aced Fix ha-icon-button-toggle selected style (#29856) 2026-02-26 13:02:12 +00:00
Aidan Timson
7ff4993e0b Fix esc closing dialogs with prevent scrim close (#29851) 2026-02-26 13:20:05 +02:00
Norbert Rittel
4e6fbacccc Remove trailing periods from "Learn more" etc. links / tooltips (#29835) 2026-02-26 10:38:54 +00:00
Petar Petrov
2958d49e36 Convert Energy Now tiles to badges (#29845) 2026-02-26 10:38:01 +00:00
Norbert Rittel
92289dc7ea Improve "Create a new … helper" option in entity picker (#29853) 2026-02-26 10:34:42 +00:00
Petar Petrov
f6c1a890e4 Dynamically calculate the date range picker's vertical opening direction (#29850) 2026-02-26 09:33:34 +00:00
Wendelin
d06321ed43 Fix protocols dashboards fab padding (#29847) 2026-02-26 10:31:50 +02: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
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
719 changed files with 38647 additions and 19214 deletions

View File

@@ -464,7 +464,14 @@ this.hass.localize("ui.panel.config.updates.update_available", {
### 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.
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
- Check the appropriate "Type of change" box based on the changes
- 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
- Be simple and user friendly — explain what the change does, not implementation details
- Use markdown so the user can copy it
### Text and Copy Guidelines

View File

@@ -26,7 +26,7 @@ jobs:
ref: dev
- name: Setup Node
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version-file: ".nvmrc"
cache: yarn
@@ -61,7 +61,7 @@ jobs:
ref: master
- name: Setup Node
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version-file: ".nvmrc"
cache: yarn

View File

@@ -26,7 +26,7 @@ jobs:
- name: Check out files from GitHub
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version-file: ".nvmrc"
cache: yarn
@@ -37,7 +37,7 @@ jobs:
- name: Build resources
run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data gather-gallery-pages
- name: Setup lint cache
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: |
node_modules/.cache/prettier
@@ -60,7 +60,7 @@ jobs:
- name: Check out files from GitHub
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version-file: ".nvmrc"
cache: yarn
@@ -78,7 +78,7 @@ jobs:
- name: Check out files from GitHub
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version-file: ".nvmrc"
cache: yarn
@@ -89,13 +89,13 @@ jobs:
env:
IS_TEST: "true"
- name: Upload bundle stats
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: frontend-bundle-stats
path: build/stats/*.json
if-no-files-found: error
- name: Upload frontend build
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: frontend-build
path: hass_frontend/

View File

@@ -36,14 +36,14 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
uses: github/codeql-action/init@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
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@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
uses: github/codeql-action/autobuild@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
# 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@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
uses: github/codeql-action/analyze@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1

View File

@@ -27,7 +27,7 @@ jobs:
ref: dev
- name: Setup Node
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version-file: ".nvmrc"
cache: yarn
@@ -62,7 +62,7 @@ jobs:
ref: master
- name: Setup Node
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version-file: ".nvmrc"
cache: yarn

View File

@@ -19,7 +19,7 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version-file: ".nvmrc"
cache: yarn

View File

@@ -24,7 +24,7 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version-file: ".nvmrc"
cache: yarn

View File

@@ -28,7 +28,7 @@ jobs:
python-version: ${{ env.PYTHON_VERSION }}
- name: Setup Node
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version-file: ".nvmrc"
cache: yarn
@@ -57,14 +57,14 @@ jobs:
run: tar -czvf translations.tar.gz translations
- name: Upload build artifacts
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: wheels
path: dist/home_assistant_frontend*.whl
if-no-files-found: error
- name: Upload translations
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: translations
path: translations.tar.gz

View File

@@ -18,6 +18,6 @@ jobs:
pull-requests: read
runs-on: ubuntu-latest
steps:
- uses: release-drafter/release-drafter@6db134d15f3909ccc9eefd369f02bd1e9cffdf97 # v6.2.0
- uses: release-drafter/release-drafter@139054aeaa9adc52ab36ddf67437541f039b88e2 # v7.1.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -37,7 +37,7 @@ jobs:
uses: home-assistant/actions/helpers/verify-version@master
- name: Setup Node
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version-file: ".nvmrc"
cache: yarn
@@ -62,7 +62,7 @@ jobs:
skip-existing: true
- name: Upload release assets
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
with:
files: |
dist/*.whl
@@ -100,7 +100,7 @@ jobs:
- name: Checkout the repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version-file: ".nvmrc"
cache: yarn
@@ -115,6 +115,6 @@ jobs:
- name: Tar folder
run: tar -czf landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz -C landing-page/dist .
- name: Upload release asset
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
with:
files: landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz

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.1
24.14.0

View File

@@ -31,7 +31,7 @@ index 8795ddcaa77aea7b0356417e4bc4b19e2b3f860c..fcdc68342d9ac53936c9ed40a9ccfc2f
@@ -129,7 +129,10 @@ export async function injectManifest(
searchString: options.injectionPoint!,
});
- filesToWrite[options.swDest] = source;
+ filesToWrite[options.swDest] = source.replace(
+ url!,

File diff suppressed because one or more lines are too long

940
.yarn/releases/yarn-4.13.0.cjs vendored Executable file

File diff suppressed because one or more lines are too long

View File

@@ -8,4 +8,4 @@ enableGlobalCache: false
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-4.12.0.cjs
yarnPath: .yarn/releases/yarn-4.13.0.cjs

View File

@@ -40,18 +40,24 @@ const convertToJSON = async (
throw e;
}
// Convert to JSON
const parts = localeData.split("} else {");
const firstBlock = parts[0];
const obj = INTL_POLYFILLS[pkg];
const dataRegex = new RegExp(
`Intl\\.${obj}\\.${addFunc}\\((?<data>.*)\\)`,
"s"
);
localeData = localeData.match(dataRegex)?.groups?.data;
localeData = firstBlock.match(dataRegex)?.groups?.data;
if (!localeData) {
throw Error(`Failed to extract data for language ${lang} from ${pkg}`);
}
// Parse to validate JSON, then stringify to minify
localeData = JSON.stringify(JSON.parse(localeData));
await writeFile(join(outDir, `${pkg}/${lang}.json`), localeData);
try {
localeData = JSON.stringify(JSON.parse(localeData));
await writeFile(join(outDir, `${pkg}/${lang}.json`), localeData);
} catch (e) {
throw Error(`Failed to parse JSON for language ${lang} from ${pkg}: ${e}`);
}
};
gulp.task("clean-locale-data", async () => deleteSync([outDir]));

View File

@@ -57,6 +57,12 @@ const runDevServer = async ({
directory: contentBase,
watch: true,
},
client: {
overlay: {
runtimeErrors: (error) =>
!error?.message?.includes("ResizeObserver loop"),
},
},
proxy,
},
compiler

View File

@@ -1,8 +1,7 @@
import type { Entity } from "../../../../src/fake_data/entity";
import { convertEntities } from "../../../../src/fake_data/entity";
import type { EntityInput } from "../../../../src/fake_data/entities/types";
export const castDemoEntities: () => Entity[] = () =>
convertEntities({
export const castDemoEntities: () => EntityInput[] = () =>
Object.values({
"light.reading_light": {
entity_id: "light.reading_light",
state: "on",

View File

@@ -1,8 +1,7 @@
import { convertEntities } from "../../../../src/fake_data/entity";
import type { DemoConfig } from "../types";
export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
convertEntities({
Object.values({
"todo.shopping_list": {
entity_id: "todo.shopping_list",
state: "2",

View File

@@ -1,8 +1,7 @@
import { convertEntities } from "../../../../src/fake_data/entity";
import type { DemoConfig } from "../types";
export const demoEntitiesJimpower: DemoConfig["entities"] = () =>
convertEntities({
Object.values({
"todo.shopping_list": {
entity_id: "todo.shopping_list",
state: "2",

View File

@@ -1,8 +1,7 @@
import { convertEntities } from "../../../../src/fake_data/entity";
import type { DemoConfig } from "../types";
export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
convertEntities({
Object.values({
"todo.shopping_list": {
entity_id: "todo.shopping_list",
state: "2",

View File

@@ -1,8 +1,7 @@
import { convertEntities } from "../../../../src/fake_data/entity";
import type { DemoConfig } from "../types";
export const demoEntitiesSections: DemoConfig["entities"] = (localize) =>
convertEntities({
Object.values({
"cover.living_room_garden_shutter": {
entity_id: "cover.living_room_garden_shutter",
state: "open",
@@ -142,7 +141,7 @@ export const demoEntitiesSections: DemoConfig["entities"] = (localize) =>
},
},
"device_tracker.car": {
entity_id: "sensor.outdoor_humidity",
entity_id: "device_tracker.car",
state: "not_home",
attributes: {
friendly_name: "Car",
@@ -200,7 +199,7 @@ export const demoEntitiesSections: DemoConfig["entities"] = (localize) =>
},
},
"binary_sensor.kitchen_motion": {
entity_id: "light.kitchen_motion",
entity_id: "binary_sensor.kitchen_motion",
state: "on",
attributes: {
device_class: "motion",
@@ -336,7 +335,7 @@ export const demoEntitiesSections: DemoConfig["entities"] = (localize) =>
},
},
"sensor.rain": {
entity_id: "sensor.moon_phase",
entity_id: "sensor.rain",
state: "7.2",
attributes: {
state_class: "total_increasing",
@@ -566,7 +565,7 @@ export const demoEntitiesSections: DemoConfig["entities"] = (localize) =>
},
},
"update.home_assistant_core_update": {
entity_id: "update.home_assistant_supervisor_update",
entity_id: "update.home_assistant_core_update",
state: "off",
attributes: {
auto_update: false,

View File

@@ -1,8 +1,7 @@
import { convertEntities } from "../../../../src/fake_data/entity";
import type { DemoConfig } from "../types";
export const demoEntitiesTeachingbirds: DemoConfig["entities"] = () =>
convertEntities({
Object.values({
"todo.shopping_list": {
entity_id: "todo.shopping_list",
state: "2",

View File

@@ -1,7 +1,7 @@
import type { TemplateResult } from "lit";
import type { LocalizeFunc } from "../../../src/common/translations/localize";
import type { LovelaceConfig } from "../../../src/data/lovelace/config/types";
import type { Entity } from "../../../src/fake_data/entity";
import type { EntityInput } from "../../../src/fake_data/entities/types";
export interface DemoConfig {
index?: number;
@@ -12,6 +12,6 @@ export interface DemoConfig {
| string
| ((localize: LocalizeFunc) => string | TemplateResult<1>);
lovelace: (localize: LocalizeFunc) => LovelaceConfig;
entities: (localize: LocalizeFunc) => Entity[];
entities: (localize: LocalizeFunc) => EntityInput[];
theme: () => Record<string, string> | null;
}

View File

@@ -1,7 +1,5 @@
import { convertEntities } from "../../../src/fake_data/entity";
export const mapEntities = () =>
convertEntities({
Object.values({
"zone.home": {
entity_id: "zone.home",
state: "zoning",
@@ -51,7 +49,7 @@ export const mapEntities = () =>
});
export const energyEntities = () =>
convertEntities({
Object.values({
"sensor.grid_fossil_fuel_percentage": {
entity_id: "sensor.grid_fossil_fuel_percentage",
state: "88.6",

View File

@@ -1,175 +1,253 @@
import { getEntity } from "../../../src/fake_data/entity";
export const createMediaPlayerEntities = () => [
getEntity("media_player", "music_paused", "paused", {
friendly_name: "Pausing The Music",
media_content_type: "music",
media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)",
media_artist: "Technohead",
// Pause + Seek + Volume Set + Volume Mute + Previous Track + Next Track + Play Media +
// Select Source + Stop + Clear + Play + Shuffle Set
supported_features: 64063,
entity_picture: "/images/album_cover_2.jpg",
media_duration: 300,
media_position: 50,
media_position_updated_at: new Date(
// 23 seconds in
new Date().getTime() - 23000
).toISOString(),
volume_level: 0.5,
source_list: ["AirPlay", "Blu-Ray", "TV", "USB", "iPod (USB)"],
source: "AirPlay",
sound_mode_list: ["Movie", "Music", "Game", "Pure Audio"],
sound_mode: "Music",
}),
getEntity("media_player", "music_playing", "playing", {
friendly_name: "Playing The Music",
media_content_type: "music",
media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)",
media_artist: "Technohead",
// Pause + Seek + Volume Set + Volume Mute + Previous Track + Next Track + Play Media +
// Select Source + Stop + Clear + Play + Shuffle Set + Browse Media + Grouping
supported_features: 784959,
entity_picture: "/images/album_cover.jpg",
media_duration: 300,
media_position: 0,
media_position_updated_at: new Date(
// 23 seconds in
new Date().getTime() - 23000
).toISOString(),
volume_level: 0.5,
sound_mode_list: ["Movie", "Music", "Game", "Pure Audio"],
sound_mode: "Music",
group_members: ["media_player.playing", "media_player.stream_playing"],
}),
getEntity("media_player", "stream_playing", "playing", {
friendly_name: "Playing the Stream",
media_content_type: "movie",
media_title: "Epic sax guy 10 hours",
app_name: "YouTube",
entity_picture: "/images/frenck.jpg",
// Pause + Next Track + Play + Browse Media
supported_features: 147489,
}),
getEntity("media_player", "stream_paused", "paused", {
friendly_name: "Paused the Stream",
media_content_type: "movie",
media_title: "Epic sax guy 10 hours",
app_name: "YouTube",
entity_picture: "/images/frenck.jpg",
// Pause + Next Track + Play
supported_features: 16417,
}),
getEntity("media_player", "stream_playing_previous", "playing", {
friendly_name: 'Playing the Stream (with "previous" support)',
media_content_type: "movie",
media_title: "Epic sax guy 10 hours",
app_name: "YouTube",
entity_picture: "/images/frenck.jpg",
// Pause + Previous Track + Play
supported_features: 16401,
}),
getEntity("media_player", "tv_playing", "playing", {
friendly_name: "Playing non-skip TV Show",
media_content_type: "tvshow",
media_title: "Chapter 1",
media_series_title: "House of Cards",
app_name: "Netflix",
entity_picture: "/images/netflix.jpg",
// Pause
supported_features: 1,
}),
getEntity("media_player", "sonos_idle", "idle", {
friendly_name: "Sonos Idle",
// Pause + Seek + Volume Set + Volume Mute + Previous Track + Next Track + Play Media +
// Select Source + Stop + Clear + Play + Shuffle Set
supported_features: 64063,
volume_level: 0.33,
is_volume_muted: true,
}),
getEntity("media_player", "idle_browse_media", "idle", {
friendly_name: "Idle waiting for Browse Media (e.g. Spotify)",
// Pause + Seek + Volume Set + Previous Track + Next Track + Play Media +
// Select Source + Play + Shuffle Set + Browse Media
supported_features: 182839,
volume_level: 0.79,
}),
getEntity("media_player", "theater_off", "off", {
friendly_name: "TV Off",
// On + Off + Play + Next + Pause
supported_features: 16801,
}),
getEntity("media_player", "theater_on", "on", {
friendly_name: "TV On",
// On + Off + Play + Next + Pause
supported_features: 16801,
}),
getEntity("media_player", "theater_off_static", "off", {
friendly_name: "TV Off (cannot be switched on)",
// Off + Next + Pause
supported_features: 289,
}),
getEntity("media_player", "theater_on_static", "on", {
friendly_name: "TV On (cannot be switched off)",
// On + Next + Pause
supported_features: 161,
}),
getEntity("media_player", "android_cast", "playing", {
friendly_name: "Casting App (no supported features)",
media_title: "Android Screen Casting",
app_name: "Screen Mirroring",
}),
getEntity("media_player", "image_display", "playing", {
friendly_name: "Digital Picture Frame",
media_content_type: "image",
media_title: "Famous Painting",
media_artist: "Famous Artist",
entity_picture: "/images/sunflowers.jpg",
// On + Off + Browse Media
supported_features: 131456,
}),
getEntity("media_player", "unavailable", "unavailable", {
friendly_name: "Player Unavailable",
// Pause + Volume Set + Volume Mute + Previous Track + Next Track +
// Play Media + Stop + Play
supported_features: 21437,
}),
getEntity("media_player", "unknown", "unknown", {
friendly_name: "Player Unknown",
// Pause + Volume Set + Volume Mute + Previous Track + Next Track +
// Play Media + Stop + Play
supported_features: 21437,
}),
getEntity("media_player", "playing", "playing", {
friendly_name: "Player Playing (no Pause support)",
// Volume Set + Volume Mute + Previous Track + Next Track +
// Play Media + Stop + Play
supported_features: 21436,
volume_level: 1,
}),
getEntity("media_player", "idle", "idle", {
friendly_name: "Player Idle",
// Pause + Volume Set + Volume Mute + Previous Track + Next Track +
// Play Media + Stop + Play
supported_features: 21437,
volume_level: 0,
}),
getEntity("media_player", "receiver_on", "on", {
source_list: ["AirPlay", "Blu-Ray", "TV", "USB", "iPod (USB)"],
sound_mode_list: ["Movie", "Music", "Game", "Pure Audio"],
volume_level: 0.63,
is_volume_muted: false,
source: "TV",
sound_mode: "Movie",
friendly_name: "Receiver (selectable sources)",
// Volume Set + Volume Mute + On + Off + Select Source + Play + Sound Mode
supported_features: 84364,
}),
getEntity("media_player", "receiver_off", "off", {
source_list: ["AirPlay", "Blu-Ray", "TV", "USB", "iPod (USB)"],
sound_mode_list: ["Movie", "Music", "Game", "Pure Audio"],
friendly_name: "Receiver (selectable sources)",
// Volume Set + Volume Mute + On + Off + Select Source + Play + Sound Mode
supported_features: 84364,
}),
{
entity_id: "media_player.music_paused",
state: "paused",
attributes: {
friendly_name: "Pausing The Music",
media_content_type: "music",
media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)",
media_artist: "Technohead",
// Pause + Seek + Volume Set + Volume Mute + Previous Track + Next Track + Play Media +
// Select Source + Stop + Clear + Play + Shuffle Set
supported_features: 64063,
entity_picture: "/images/album_cover_2.jpg",
media_duration: 300,
media_position: 50,
media_position_updated_at: new Date(
// 23 seconds in
new Date().getTime() - 23000
).toISOString(),
volume_level: 0.5,
source_list: ["AirPlay", "Blu-Ray", "TV", "USB", "iPod (USB)"],
source: "AirPlay",
sound_mode_list: ["Movie", "Music", "Game", "Pure Audio"],
sound_mode: "Music",
},
},
{
entity_id: "media_player.music_playing",
state: "playing",
attributes: {
friendly_name: "Playing The Music",
media_content_type: "music",
media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)",
media_artist: "Technohead",
// Pause + Seek + Volume Set + Volume Mute + Previous Track + Next Track + Play Media +
// Select Source + Stop + Clear + Play + Shuffle Set + Browse Media + Grouping
supported_features: 784959,
entity_picture: "/images/album_cover.jpg",
media_duration: 300,
media_position: 0,
media_position_updated_at: new Date(
// 23 seconds in
new Date().getTime() - 23000
).toISOString(),
volume_level: 0.5,
sound_mode_list: ["Movie", "Music", "Game", "Pure Audio"],
sound_mode: "Music",
group_members: ["media_player.playing", "media_player.stream_playing"],
},
},
{
entity_id: "media_player.stream_playing",
state: "playing",
attributes: {
friendly_name: "Playing the Stream",
media_content_type: "movie",
media_title: "Epic sax guy 10 hours",
app_name: "YouTube",
entity_picture: "/images/frenck.jpg",
// Pause + Next Track + Play + Browse Media
supported_features: 147489,
},
},
{
entity_id: "media_player.stream_paused",
state: "paused",
attributes: {
friendly_name: "Paused the Stream",
media_content_type: "movie",
media_title: "Epic sax guy 10 hours",
app_name: "YouTube",
entity_picture: "/images/frenck.jpg",
// Pause + Next Track + Play
supported_features: 16417,
},
},
{
entity_id: "media_player.stream_playing_previous",
state: "playing",
attributes: {
friendly_name: 'Playing the Stream (with "previous" support)',
media_content_type: "movie",
media_title: "Epic sax guy 10 hours",
app_name: "YouTube",
entity_picture: "/images/frenck.jpg",
// Pause + Previous Track + Play
supported_features: 16401,
},
},
{
entity_id: "media_player.tv_playing",
state: "playing",
attributes: {
friendly_name: "Playing non-skip TV Show",
media_content_type: "tvshow",
media_title: "Chapter 1",
media_series_title: "House of Cards",
app_name: "Netflix",
entity_picture: "/images/netflix.jpg",
// Pause
supported_features: 1,
},
},
{
entity_id: "media_player.sonos_idle",
state: "idle",
attributes: {
friendly_name: "Sonos Idle",
// Pause + Seek + Volume Set + Volume Mute + Previous Track + Next Track + Play Media +
// Select Source + Stop + Clear + Play + Shuffle Set
supported_features: 64063,
volume_level: 0.33,
is_volume_muted: true,
},
},
{
entity_id: "media_player.idle_browse_media",
state: "idle",
attributes: {
friendly_name: "Idle waiting for Browse Media (e.g. Spotify)",
// Pause + Seek + Volume Set + Previous Track + Next Track + Play Media +
// Select Source + Play + Shuffle Set + Browse Media
supported_features: 182839,
volume_level: 0.79,
},
},
{
entity_id: "media_player.theater_off",
state: "off",
attributes: {
friendly_name: "TV Off",
// On + Off + Play + Next + Pause
supported_features: 16801,
},
},
{
entity_id: "media_player.theater_on",
state: "on",
attributes: {
friendly_name: "TV On",
// On + Off + Play + Next + Pause
supported_features: 16801,
},
},
{
entity_id: "media_player.theater_off_static",
state: "off",
attributes: {
friendly_name: "TV Off (cannot be switched on)",
// Off + Next + Pause
supported_features: 289,
},
},
{
entity_id: "media_player.theater_on_static",
state: "on",
attributes: {
friendly_name: "TV On (cannot be switched off)",
// On + Next + Pause
supported_features: 161,
},
},
{
entity_id: "media_player.android_cast",
state: "playing",
attributes: {
friendly_name: "Casting App (no supported features)",
media_title: "Android Screen Casting",
app_name: "Screen Mirroring",
},
},
{
entity_id: "media_player.image_display",
state: "playing",
attributes: {
friendly_name: "Digital Picture Frame",
media_content_type: "image",
media_title: "Famous Painting",
media_artist: "Famous Artist",
entity_picture: "/images/sunflowers.jpg",
// On + Off + Browse Media
supported_features: 131456,
},
},
{
entity_id: "media_player.unavailable",
state: "unavailable",
attributes: {
friendly_name: "Player Unavailable",
// Pause + Volume Set + Volume Mute + Previous Track + Next Track +
// Play Media + Stop + Play
supported_features: 21437,
},
},
{
entity_id: "media_player.unknown",
state: "unknown",
attributes: {
friendly_name: "Player Unknown",
// Pause + Volume Set + Volume Mute + Previous Track + Next Track +
// Play Media + Stop + Play
supported_features: 21437,
},
},
{
entity_id: "media_player.playing",
state: "playing",
attributes: {
friendly_name: "Player Playing (no Pause support)",
// Volume Set + Volume Mute + Previous Track + Next Track +
// Play Media + Stop + Play
supported_features: 21436,
volume_level: 1,
},
},
{
entity_id: "media_player.idle",
state: "idle",
attributes: {
friendly_name: "Player Idle",
// Pause + Volume Set + Volume Mute + Previous Track + Next Track +
// Play Media + Stop + Play
supported_features: 21437,
volume_level: 0,
},
},
{
entity_id: "media_player.receiver_on",
state: "on",
attributes: {
source_list: ["AirPlay", "Blu-Ray", "TV", "USB", "iPod (USB)"],
sound_mode_list: ["Movie", "Music", "Game", "Pure Audio"],
volume_level: 0.63,
is_volume_muted: false,
source: "TV",
sound_mode: "Movie",
friendly_name: "Receiver (selectable sources)",
// Volume Set + Volume Mute + On + Off + Select Source + Play + Sound Mode
supported_features: 84364,
},
},
{
entity_id: "media_player.receiver_off",
state: "off",
attributes: {
source_list: ["AirPlay", "Blu-Ray", "TV", "USB", "iPod (USB)"],
sound_mode_list: ["Movie", "Music", "Game", "Pure Audio"],
friendly_name: "Receiver (selectable sources)",
// Volume Set + Volume Mute + On + Off + Select Source + Play + Sound Mode
supported_features: 84364,
},
},
];

View File

@@ -1,72 +1,82 @@
import { getEntity } from "../../../src/fake_data/entity";
export const createPlantEntities = () => [
getEntity("plant", "lemon_tree", "ok", {
problem: "none",
sensors: {
moisture: "sensor.lemon_tree_moisture",
battery: "sensor.lemon_tree_battery",
temperature: "sensor.lemon_tree_temperature",
conductivity: "sensor.lemon_tree_conductivity",
brightness: "sensor.lemon_tree_brightness",
{
entity_id: "plant.lemon_tree",
state: "ok",
attributes: {
problem: "none",
sensors: {
moisture: "sensor.lemon_tree_moisture",
battery: "sensor.lemon_tree_battery",
temperature: "sensor.lemon_tree_temperature",
conductivity: "sensor.lemon_tree_conductivity",
brightness: "sensor.lemon_tree_brightness",
},
unit_of_measurement_dict: {
temperature: "°C",
moisture: "%",
brightness: "lx",
battery: "%",
conductivity: "μS/cm",
},
moisture: 54,
battery: 95,
temperature: 15.6,
conductivity: 1,
brightness: 12,
max_brightness: 20,
friendly_name: "Lemon Tree",
},
unit_of_measurement_dict: {
temperature: "°C",
moisture: "%",
brightness: "lx",
battery: "%",
conductivity: "μS/cm",
},
{
entity_id: "plant.apple_tree",
state: "ok",
attributes: {
problem: "brightness",
sensors: {
moisture: "sensor.apple_tree_moisture",
battery: "sensor.apple_tree_battery",
temperature: "sensor.apple_tree_temperature",
conductivity: "sensor.apple_tree_conductivity",
brightness: "sensor.apple_tree_brightness",
},
unit_of_measurement_dict: {
temperature: "°C",
moisture: "%",
brightness: "lx",
battery: "%",
conductivity: "μS/cm",
},
moisture: 54,
battery: 2,
temperature: 15.6,
conductivity: 1,
brightness: 25,
max_brightness: 20,
friendly_name: "Apple Tree",
},
moisture: 54,
battery: 95,
temperature: 15.6,
conductivity: 1,
brightness: 12,
max_brightness: 20,
friendly_name: "Lemon Tree",
}),
getEntity("plant", "apple_tree", "ok", {
problem: "brightness",
sensors: {
moisture: "sensor.apple_tree_moisture",
battery: "sensor.apple_tree_battery",
temperature: "sensor.apple_tree_temperature",
conductivity: "sensor.apple_tree_conductivity",
brightness: "sensor.apple_tree_brightness",
},
{
entity_id: "plant.sunflowers",
state: "ok",
attributes: {
problem: "moisture, temperature, conductivity",
sensors: {
moisture: "sensor.sunflowers_moisture",
temperature: "sensor.sunflowers_temperature",
conductivity: "sensor.sunflowers_conductivity",
brightness: "sensor.sunflowers_brightness",
},
unit_of_measurement_dict: {
temperature: "°C",
moisture: "%",
brightness: "lx",
conductivity: "μS/cm",
},
moisture: 54,
temperature: 15.6,
conductivity: 1,
brightness: 25,
entity_picture: "/images/sunflowers.jpg",
},
unit_of_measurement_dict: {
temperature: "°C",
moisture: "%",
brightness: "lx",
battery: "%",
conductivity: "μS/cm",
},
moisture: 54,
battery: 2,
temperature: 15.6,
conductivity: 1,
brightness: 25,
max_brightness: 20,
friendly_name: "Apple Tree",
}),
getEntity("plant", "sunflowers", "ok", {
problem: "moisture, temperature, conductivity",
sensors: {
moisture: "sensor.sunflowers_moisture",
temperature: "sensor.sunflowers_temperature",
conductivity: "sensor.sunflowers_conductivity",
brightness: "sensor.sunflowers_brightness",
},
unit_of_measurement_dict: {
temperature: "°C",
moisture: "%",
brightness: "lx",
conductivity: "μS/cm",
},
moisture: 54,
temperature: 15.6,
conductivity: 1,
brightness: 25,
entity_picture: "/images/sunflowers.jpg",
}),
},
];

View File

@@ -5,17 +5,24 @@ import "../../../../src/components/ha-card";
import "../../../../src/components/ha-yaml-editor";
import type { Action } from "../../../../src/data/script";
import { describeAction } from "../../../../src/data/script_i18n";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import type { HomeAssistant } from "../../../../src/types";
const ENTITIES = [
getEntity("scene", "kitchen_morning", "scening", {
friendly_name: "Kitchen Morning",
}),
getEntity("media_player", "kitchen", "playing", {
friendly_name: "Sonos Kitchen",
}),
{
entity_id: "scene.kitchen_morning",
state: "scening",
attributes: {
friendly_name: "Kitchen Morning",
},
},
{
entity_id: "media_player.kitchen",
state: "playing",
attributes: {
friendly_name: "Sonos Kitchen",
},
},
];
const ACTIONS = [

View File

@@ -5,20 +5,31 @@ import "../../../../src/components/ha-card";
import "../../../../src/components/ha-yaml-editor";
import type { Condition } from "../../../../src/data/automation";
import { describeCondition } from "../../../../src/data/automation_i18n";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import type { HomeAssistant } from "../../../../src/types";
const ENTITIES = [
getEntity("light", "kitchen", "on", {
friendly_name: "Kitchen Light",
}),
getEntity("device_tracker", "person", "home", {
friendly_name: "Person",
}),
getEntity("zone", "home", "", {
friendly_name: "Home",
}),
{
entity_id: "light.kitchen",
state: "on",
attributes: {
friendly_name: "Kitchen Light",
},
},
{
entity_id: "device_tracker.person",
state: "home",
attributes: {
friendly_name: "Person",
},
},
{
entity_id: "zone.home",
state: "",
attributes: {
friendly_name: "Home",
},
},
];
const conditions: Condition[] = [

View File

@@ -5,20 +5,31 @@ import "../../../../src/components/ha-card";
import "../../../../src/components/ha-yaml-editor";
import type { LegacyTrigger } from "../../../../src/data/automation";
import { describeTrigger } from "../../../../src/data/automation_i18n";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import type { HomeAssistant } from "../../../../src/types";
const ENTITIES = [
getEntity("light", "kitchen", "on", {
friendly_name: "Kitchen Light",
}),
getEntity("person", "person", "", {
friendly_name: "Person",
}),
getEntity("zone", "home", "", {
friendly_name: "Home",
}),
{
entity_id: "light.kitchen",
state: "on",
attributes: {
friendly_name: "Kitchen Light",
},
},
{
entity_id: "person.person",
state: "",
attributes: {
friendly_name: "Person",
},
},
{
entity_id: "zone.home",
state: "",
attributes: {
friendly_name: "Home",
},
},
];
const triggers = [

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
@@ -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>
@@ -399,10 +386,10 @@ export class DemoHaAdaptiveDialog extends LitElement {
</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,23 +413,6 @@ 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>
@@ -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>
@@ -548,6 +515,14 @@ export class DemoHaAdaptiveDialog extends LitElement {
<td><code>--ha-dialog-surface-background</code></td>
<td>Dialog/sheet background color.</td>
</tr>
<tr>
<td><code>--ha-dialog-surface-backdrop-filter</code></td>
<td>Dialog/sheet surface backdrop filter.</td>
</tr>
<tr>
<td><code>--dialog-box-shadow</code></td>
<td>Dialog surface box shadow (dialog mode only).</td>
</tr>
<tr>
<td><code>--ha-dialog-border-radius</code></td>
<td>Border radius of the dialog surface (dialog mode only).</td>
@@ -560,6 +535,34 @@ export class DemoHaAdaptiveDialog extends LitElement {
<td><code>--ha-dialog-hide-duration</code></td>
<td>Hide animation duration (dialog mode only).</td>
</tr>
<tr>
<td><code>--ha-dialog-scrim-backdrop-filter</code></td>
<td>Dialog/sheet scrim backdrop filter.</td>
</tr>
<tr>
<td><code>--dialog-backdrop-filter</code></td>
<td>Dialog/sheet scrim backdrop filter (legacy fallback).</td>
</tr>
<tr>
<td><code>--mdc-dialog-scrim-color</code></td>
<td>Dialog/sheet scrim color (legacy compatibility).</td>
</tr>
<tr>
<td><code>--ha-bottom-sheet-surface-background</code></td>
<td>Bottom sheet background color (sheet mode only).</td>
</tr>
<tr>
<td><code>--ha-bottom-sheet-surface-backdrop-filter</code></td>
<td>Bottom sheet surface backdrop filter (sheet mode only).</td>
</tr>
<tr>
<td><code>--ha-bottom-sheet-scrim-backdrop-filter</code></td>
<td>Bottom sheet scrim backdrop filter (sheet mode only).</td>
</tr>
<tr>
<td><code>--ha-bottom-sheet-scrim-color</code></td>
<td>Bottom sheet scrim color (sheet mode only).</td>
</tr>
</tbody>
</table>

View File

@@ -380,13 +380,29 @@ export class DemoHaDialog extends LitElement {
<td><code>--ha-dialog-surface-background</code></td>
<td>Dialog background color.</td>
</tr>
<tr>
<td><code>--ha-dialog-surface-backdrop-filter</code></td>
<td>Backdrop filter applied to the dialog surface.</td>
</tr>
<tr>
<td><code>--dialog-box-shadow</code></td>
<td>Dialog surface box shadow.</td>
</tr>
<tr>
<td><code>--ha-dialog-border-radius</code></td>
<td>Border radius of the dialog surface.</td>
</tr>
<tr>
<td><code>--dialog-z-index</code></td>
<td>Z-index for the dialog.</td>
<td><code>--ha-dialog-scrim-backdrop-filter</code></td>
<td>Backdrop filter applied to the dialog scrim.</td>
</tr>
<tr>
<td><code>--dialog-backdrop-filter</code></td>
<td>Legacy fallback for the dialog scrim backdrop filter.</td>
</tr>
<tr>
<td><code>--mdc-dialog-scrim-color</code></td>
<td>Dialog scrim color (legacy compatibility).</td>
</tr>
<tr>
<td><code>--dialog-surface-margin-top</code></td>

View File

@@ -12,34 +12,53 @@ import "../../../../src/components/ha-form/ha-form";
import type { HaFormSchema } from "../../../../src/components/ha-form/types";
import type { AreaRegistryEntry } from "../../../../src/data/area/area_registry";
import type { DeviceRegistryEntry } from "../../../../src/data/device/device_registry";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import type { HomeAssistant } from "../../../../src/types";
import "../../components/demo-black-white-row";
const ENTITIES = [
getEntity("alarm_control_panel", "alarm", "disarmed", {
friendly_name: "Alarm",
}),
getEntity("media_player", "livingroom", "playing", {
friendly_name: "Livingroom",
media_content_type: "music",
device_class: "tv",
}),
getEntity("media_player", "lounge", "idle", {
friendly_name: "Lounge",
supported_features: 444983,
device_class: "speaker",
}),
getEntity("light", "bedroom", "on", {
friendly_name: "Bedroom",
effect: "colorloop",
effect_list: ["colorloop", "random"],
}),
getEntity("switch", "coffee", "off", {
friendly_name: "Coffee",
device_class: "switch",
}),
{
entity_id: "alarm_control_panel.alarm",
state: "disarmed",
attributes: {
friendly_name: "Alarm",
},
},
{
entity_id: "media_player.livingroom",
state: "playing",
attributes: {
friendly_name: "Livingroom",
media_content_type: "music",
device_class: "tv",
},
},
{
entity_id: "media_player.lounge",
state: "idle",
attributes: {
friendly_name: "Lounge",
supported_features: 444983,
device_class: "speaker",
},
},
{
entity_id: "light.bedroom",
state: "on",
attributes: {
friendly_name: "Bedroom",
effect: "colorloop",
effect_list: ["colorloop", "random"],
},
},
{
entity_id: "switch.coffee",
state: "off",
attributes: {
friendly_name: "Coffee",
device_class: "switch",
},
},
];
const DEVICES: DeviceRegistryEntry[] = [
@@ -461,6 +480,12 @@ const SCHEMAS: {
},
{ type: "string", name: "path", default: "/" },
{ type: "boolean", name: "ssl", default: false },
{
type: "string",
name: "comments",
default: "disabled field",
disabled: true,
},
],
},
];

View File

@@ -8,6 +8,7 @@ import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
import { mockFloorRegistry } from "../../../../demo/src/stubs/floor_registry";
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
import { mockLabelRegistry } from "../../../../demo/src/stubs/label_registry";
import type { HASSDomEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-formfield";
import "../../../../src/components/ha-selector/ha-selector";
import "../../../../src/components/ha-settings-row";
@@ -16,33 +17,59 @@ import type { BlueprintInput } from "../../../../src/data/blueprint";
import type { DeviceRegistryEntry } from "../../../../src/data/device/device_registry";
import type { FloorRegistryEntry } from "../../../../src/data/floor_registry";
import type { LabelRegistryEntry } from "../../../../src/data/label/label_registry";
import { showDialog } from "../../../../src/dialogs/make-dialog-manager";
import { getEntity } from "../../../../src/fake_data/entity";
import {
showDialog,
type ShowDialogParams,
} from "../../../../src/dialogs/make-dialog-manager";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import type { ProvideHassElement } from "../../../../src/mixins/provide-hass-lit-mixin";
import type { HomeAssistant } from "../../../../src/types";
import "../../components/demo-black-white-row";
const ENTITIES = [
getEntity("alarm_control_panel", "alarm", "disarmed", {
friendly_name: "Alarm",
}),
getEntity("media_player", "livingroom", "playing", {
friendly_name: "Livingroom",
}),
getEntity("media_player", "lounge", "idle", {
friendly_name: "Lounge",
supported_features: 444983,
}),
getEntity("light", "bedroom", "on", {
friendly_name: "Bedroom",
}),
getEntity("switch", "coffee", "off", {
friendly_name: "Coffee",
}),
getEntity("number", "number", 5, {
friendly_name: "Number",
}),
{
entity_id: "alarm_control_panel.alarm",
state: "disarmed",
attributes: {
friendly_name: "Alarm",
},
},
{
entity_id: "media_player.livingroom",
state: "playing",
attributes: {
friendly_name: "Livingroom",
},
},
{
entity_id: "media_player.lounge",
state: "idle",
attributes: {
friendly_name: "Lounge",
supported_features: 444983,
},
},
{
entity_id: "light.bedroom",
state: "on",
attributes: {
friendly_name: "Bedroom",
},
},
{
entity_id: "switch.coffee",
state: "off",
attributes: {
friendly_name: "Coffee",
},
},
{
entity_id: "number.number",
state: "5",
attributes: {
friendly_name: "Number",
},
},
];
const DEVICES: DeviceRegistryEntry[] = [
@@ -611,14 +638,15 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
};
};
private _dialogManager = (e) => {
const { dialogTag, dialogImport, dialogParams, addHistory } = e.detail;
private _dialogManager = (e: HASSDomEvent<ShowDialogParams<unknown>>) => {
const { dialogTag, dialogImport, dialogParams, addHistory, parentElement } =
e.detail;
showDialog(
this,
this.shadowRoot!,
dialogTag,
dialogParams,
dialogImport,
parentElement,
addHistory
);
};

View File

@@ -28,9 +28,12 @@ This element is based on webawesome `wa-tooltip` it only sets some css tokens an
In your theme settings use this without the prefixed `--`.
- `--ha-tooltip-border-radius` (Default: 4px)
- `--ha-tooltip-arrow-size` (Default: 8px)
- `--wa-tooltip-font-family` (Default: `var(--ha-font-family-body)`)
- `--ha-tooltip-background-color` (Default: `var(--secondary-background-color)`)
- `--ha-tooltip-text-color` (Default: `var(--primary-text-color)`)
- `--ha-tooltip-font-family` (Default: `var(--ha-font-family-body)`)
- `--ha-tooltip-font-size` (Default: `var(--ha-font-size-s)`)
- `--wa-tooltip-font-weight` (Default: `var(--ha-font-weight-normal)`)
- `--wa-tooltip-line-height` (Default: `var(--ha-line-height-condensed)`)
- `--ha-tooltip-font-weight` (Default: `var(--ha-font-weight-normal)`)
- `--ha-tooltip-line-height` (Default: `var(--ha-line-height-condensed)`)
- `--ha-tooltip-padding` (Default: 8px)
- `--ha-tooltip-border-radius` (Default: `var(--ha-border-radius-sm)`)
- `--ha-tooltip-arrow-size` (Default: 8px)

View File

@@ -1,25 +1,40 @@
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
getEntity("alarm_control_panel", "alarm", "disarmed", {
friendly_name: "Alarm",
}),
getEntity("alarm_control_panel", "alarm_armed", "armed_home", {
friendly_name: "Alarm",
}),
getEntity("alarm_control_panel", "unavailable", "unavailable", {
friendly_name: "Alarm",
}),
getEntity("alarm_control_panel", "alarm_code", "disarmed", {
friendly_name: "Alarm",
code_format: "number",
}),
{
entity_id: "alarm_control_panel.alarm",
state: "disarmed",
attributes: {
friendly_name: "Alarm",
},
},
{
entity_id: "alarm_control_panel.alarm_armed",
state: "armed_home",
attributes: {
friendly_name: "Alarm",
},
},
{
entity_id: "alarm_control_panel.unavailable",
state: "unavailable",
attributes: {
friendly_name: "Alarm",
},
},
{
entity_id: "alarm_control_panel.alarm_code",
state: "disarmed",
attributes: {
friendly_name: "Alarm",
code_format: "number",
},
},
];
const CONFIGS = [

View File

@@ -1,44 +1,79 @@
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
getEntity("light", "bed_light", "on", {
friendly_name: "Bed Light",
}),
getEntity("switch", "bed_ac", "on", {
friendly_name: "Ecobee",
}),
getEntity("sensor", "bed_temp", "72", {
friendly_name: "Bedroom Temp",
device_class: "temperature",
unit_of_measurement: "°F",
}),
getEntity("light", "living_room_light", "off", {
friendly_name: "Living Room Light",
}),
getEntity("fan", "living_room", "on", {
friendly_name: "Living Room Fan",
}),
getEntity("sensor", "office_humidity", "73", {
friendly_name: "Office Humidity",
device_class: "humidity",
unit_of_measurement: "%",
}),
getEntity("light", "office", "on", {
friendly_name: "Office Light",
}),
getEntity("fan", "kitchen", "on", {
friendly_name: "Kitchen Fan",
}),
getEntity("binary_sensor", "kitchen_door", "on", {
friendly_name: "Office Door",
device_class: "door",
}),
{
entity_id: "light.bed_light",
state: "on",
attributes: {
friendly_name: "Bed Light",
},
},
{
entity_id: "switch.bed_ac",
state: "on",
attributes: {
friendly_name: "Ecobee",
},
},
{
entity_id: "sensor.bed_temp",
state: "72",
attributes: {
friendly_name: "Bedroom Temp",
device_class: "temperature",
unit_of_measurement: "°F",
},
},
{
entity_id: "light.living_room_light",
state: "off",
attributes: {
friendly_name: "Living Room Light",
},
},
{
entity_id: "fan.living_room",
state: "on",
attributes: {
friendly_name: "Living Room Fan",
},
},
{
entity_id: "sensor.office_humidity",
state: "73",
attributes: {
friendly_name: "Office Humidity",
device_class: "humidity",
unit_of_measurement: "%",
},
},
{
entity_id: "light.office",
state: "on",
attributes: {
friendly_name: "Office Light",
},
},
{
entity_id: "fan.kitchen",
state: "on",
attributes: {
friendly_name: "Kitchen Fan",
},
},
{
entity_id: "binary_sensor.kitchen_door",
state: "on",
attributes: {
friendly_name: "Office Door",
device_class: "door",
},
},
];
// TODO: Update image here

View File

@@ -1,24 +1,39 @@
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
getEntity("light", "controller_1", "on", {
friendly_name: "Controller 1",
}),
getEntity("light", "controller_2", "on", {
friendly_name: "Controller 2",
}),
getEntity("light", "floor", "off", {
friendly_name: "Floor light",
}),
getEntity("light", "kitchen", "on", {
friendly_name: "Kitchen light",
}),
{
entity_id: "light.controller_1",
state: "on",
attributes: {
friendly_name: "Controller 1",
},
},
{
entity_id: "light.controller_2",
state: "on",
attributes: {
friendly_name: "Controller 2",
},
},
{
entity_id: "light.floor",
state: "off",
attributes: {
friendly_name: "Floor light",
},
},
{
entity_id: "light.kitchen",
state: "on",
attributes: {
friendly_name: "Kitchen light",
},
},
];
const CONFIGS = [

View File

@@ -1,150 +1,257 @@
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
getEntity("light", "bed_light", "on", {
friendly_name: "Bed Light",
}),
getEntity("group", "kitchen", "on", {
entity_id: ["light.bed_light"],
order: 8,
friendly_name: "Kitchen Group",
}),
getEntity("lock", "kitchen_door", "locked", {
friendly_name: "Kitchen Lock",
}),
getEntity("cover", "kitchen_window", "open", {
friendly_name: "Kitchen Window",
supported_features: 11,
}),
getEntity("scene", "romantic_lights", "scening", {
entity_id: ["light.bed_light", "light.ceiling_lights"],
friendly_name: "Romantic Scene",
}),
getEntity("device_tracker", "demo_paulus", "home", {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 71,
friendly_name: "Paulus",
}),
getEntity("climate", "ecobee", "auto", {
current_temperature: 73,
min_temp: 45,
max_temp: 95,
temperature: null,
target_temp_high: 75,
target_temp_low: 70,
fan_mode: "Auto Low",
fan_list: ["On Low", "On High", "Auto Low", "Auto High", "Off"],
operation_mode: "auto",
operation_list: ["heat", "cool", "auto", "off"],
hold_mode: "home",
swing_mode: "Auto",
swing_list: ["Auto", "1", "2", "3", "Off"],
unit_of_measurement: "°F",
friendly_name: "Ecobee",
supported_features: 1014,
}),
getEntity("input_number", "number", 5, {
min: 0,
max: 10,
step: 1,
mode: "slider",
unit_of_measurement: "dB",
friendly_name: "Number",
icon: "mdi:bell-ring",
}),
getEntity("input_boolean", "toggle", "on", {
friendly_name: "Toggle",
}),
getEntity("input_datetime", "date_and_time", "2022-01-10 00:00:00", {
has_date: true,
has_time: true,
editable: true,
year: 2022,
month: 1,
day: 10,
hour: 0,
minute: 0,
second: 0,
timestamp: 1641801600,
friendly_name: "Date and Time",
}),
getEntity("sensor", "humidity", "23.2", {
friendly_name: "Humidity",
unit_of_measurement: "%",
}),
getEntity("input_select", "dropdown", "Soda", {
friendly_name: "Dropdown",
options: ["Soda", "Beer", "Wine"],
}),
getEntity("input_text", "text", "Inspiration", {
friendly_name: "Text",
mode: "text",
}),
getEntity("timer", "timer", "idle", {
friendly_name: "Timer",
duration: "0:05:00",
}),
getEntity("counter", "counter", "3", {
friendly_name: "Counter",
initial: 0,
step: 1,
minimum: 0,
maximum: 10,
}),
getEntity("text", "message", "Hello!", {
friendly_name: "Message",
}),
{
entity_id: "light.bed_light",
state: "on",
attributes: {
friendly_name: "Bed Light",
},
},
{
entity_id: "group.kitchen",
state: "on",
attributes: {
entity_id: ["light.bed_light"],
order: 8,
friendly_name: "Kitchen Group",
},
},
{
entity_id: "lock.kitchen_door",
state: "locked",
attributes: {
friendly_name: "Kitchen Lock",
},
},
{
entity_id: "cover.kitchen_window",
state: "open",
attributes: {
friendly_name: "Kitchen Window",
supported_features: 11,
},
},
{
entity_id: "scene.romantic_lights",
state: "scening",
attributes: {
entity_id: ["light.bed_light", "light.ceiling_lights"],
friendly_name: "Romantic Scene",
},
},
{
entity_id: "device_tracker.demo_paulus",
state: "home",
attributes: {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 71,
friendly_name: "Paulus",
},
},
{
entity_id: "climate.ecobee",
state: "auto",
attributes: {
current_temperature: 73,
min_temp: 45,
max_temp: 95,
temperature: null,
target_temp_high: 75,
target_temp_low: 70,
fan_mode: "Auto Low",
fan_list: ["On Low", "On High", "Auto Low", "Auto High", "Off"],
operation_mode: "auto",
operation_list: ["heat", "cool", "auto", "off"],
hold_mode: "home",
swing_mode: "Auto",
swing_list: ["Auto", "1", "2", "3", "Off"],
unit_of_measurement: "°F",
friendly_name: "Ecobee",
supported_features: 1014,
},
},
{
entity_id: "input_number.number",
state: "5",
attributes: {
min: 0,
max: 10,
step: 1,
mode: "slider",
unit_of_measurement: "dB",
friendly_name: "Number",
icon: "mdi:bell-ring",
},
},
{
entity_id: "input_boolean.toggle",
state: "on",
attributes: {
friendly_name: "Toggle",
},
},
{
entity_id: "input_datetime.date_and_time",
state: "2022-01-10 00:00:00",
attributes: {
has_date: true,
has_time: true,
editable: true,
year: 2022,
month: 1,
day: 10,
hour: 0,
minute: 0,
second: 0,
timestamp: 1641801600,
friendly_name: "Date and Time",
},
},
{
entity_id: "sensor.humidity",
state: "23.2",
attributes: {
friendly_name: "Humidity",
unit_of_measurement: "%",
},
},
{
entity_id: "input_select.dropdown",
state: "Soda",
attributes: {
friendly_name: "Dropdown",
options: ["Soda", "Beer", "Wine"],
},
},
{
entity_id: "input_text.text",
state: "Inspiration",
attributes: {
friendly_name: "Text",
mode: "text",
},
},
{
entity_id: "timer.timer",
state: "idle",
attributes: {
friendly_name: "Timer",
duration: "0:05:00",
},
},
{
entity_id: "counter.counter",
state: "3",
attributes: {
friendly_name: "Counter",
initial: 0,
step: 1,
minimum: 0,
maximum: 10,
},
},
{
entity_id: "text.message",
state: "Hello!",
attributes: {
friendly_name: "Message",
},
},
getEntity("light", "unavailable", "unavailable", {
friendly_name: "Bed Light",
}),
getEntity("lock", "unavailable", "unavailable", {
friendly_name: "Kitchen Door",
}),
getEntity("cover", "unavailable", "unavailable", {
friendly_name: "Kitchen Window",
supported_features: 11,
}),
getEntity("scene", "unavailable", "unavailable", {
friendly_name: "Romantic Scene",
}),
getEntity("device_tracker", "unavailable", "unavailable", {
friendly_name: "Paulus",
}),
getEntity("climate", "unavailable", "unavailable", {
unit_of_measurement: "°F",
friendly_name: "Ecobee",
supported_features: 1014,
}),
getEntity("input_number", "unavailable", "unavailable", {
friendly_name: "Allowed Noise",
icon: "mdi:bell-ring",
}),
getEntity("input_select", "unavailable", "unavailable", {
unit_of_measurement: "dB",
friendly_name: "Who cooks",
icon: "mdi:cheff",
}),
getEntity("text", "unavailable", "unavailable", {
friendly_name: "Message",
}),
getEntity("event", "unavailable", "unavailable", {
friendly_name: "Empty remote",
}),
getEntity("event", "doorbell", "2023-07-17T21:26:11.615+00:00", {
friendly_name: "Doorbell",
device_class: "doorbell",
event_type: "Ding-Dong",
}),
{
entity_id: "light.unavailable",
state: "unavailable",
attributes: {
friendly_name: "Bed Light",
},
},
{
entity_id: "lock.unavailable",
state: "unavailable",
attributes: {
friendly_name: "Kitchen Door",
},
},
{
entity_id: "cover.unavailable",
state: "unavailable",
attributes: {
friendly_name: "Kitchen Window",
supported_features: 11,
},
},
{
entity_id: "scene.unavailable",
state: "unavailable",
attributes: {
friendly_name: "Romantic Scene",
},
},
{
entity_id: "device_tracker.unavailable",
state: "unavailable",
attributes: {
friendly_name: "Paulus",
},
},
{
entity_id: "climate.unavailable",
state: "unavailable",
attributes: {
unit_of_measurement: "°F",
friendly_name: "Ecobee",
supported_features: 1014,
},
},
{
entity_id: "input_number.unavailable",
state: "unavailable",
attributes: {
friendly_name: "Allowed Noise",
icon: "mdi:bell-ring",
},
},
{
entity_id: "input_select.unavailable",
state: "unavailable",
attributes: {
unit_of_measurement: "dB",
friendly_name: "Who cooks",
icon: "mdi:cheff",
},
},
{
entity_id: "text.unavailable",
state: "unavailable",
attributes: {
friendly_name: "Message",
},
},
{
entity_id: "event.unavailable",
state: "unavailable",
attributes: {
friendly_name: "Empty remote",
},
},
{
entity_id: "event.doorbell",
state: "2023-07-17T21:26:11.615+00:00",
attributes: {
friendly_name: "Doorbell",
device_class: "doorbell",
event_type: "Ding-Dong",
},
},
];
const CONFIGS = [

View File

@@ -1,15 +1,18 @@
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
getEntity("light", "bed_light", "on", {
friendly_name: "Bed Light",
}),
{
entity_id: "light.bed_light",
state: "on",
attributes: {
friendly_name: "Bed Light",
},
},
];
const CONFIGS = [

View File

@@ -1,74 +1,117 @@
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
getEntity("device_tracker", "demo_paulus", "work", {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 25,
friendly_name: "Paulus",
}),
getEntity("device_tracker", "demo_anne_therese", "school", {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 50,
friendly_name: "Anne Therese",
}),
getEntity("device_tracker", "demo_home_boy", "home", {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 75,
friendly_name: "Home Boy",
}),
getEntity("light", "bed_light", "on", {
friendly_name: "Bed Light",
}),
getEntity("light", "kitchen_lights", "on", {
friendly_name: "Kitchen Lights",
}),
getEntity("light", "ceiling_lights", "off", {
friendly_name: "Ceiling Lights",
}),
getEntity("sensor", "battery_1", 20, {
device_class: "battery",
friendly_name: "Battery 1",
unit_of_measurement: "%",
}),
getEntity("sensor", "battery_2", 35, {
device_class: "battery",
friendly_name: "Battery 2",
unit_of_measurement: "%",
}),
getEntity("sensor", "battery_3", 40, {
device_class: "battery",
friendly_name: "Battery 3",
unit_of_measurement: "%",
}),
getEntity("sensor", "battery_4", 80, {
device_class: "battery",
friendly_name: "Battery 4",
unit_of_measurement: "%",
}),
getEntity("input_number", "min_battery_level", 30, {
mode: "slider",
step: 10,
min: 0,
max: 100,
icon: "mdi:battery-alert-variant",
friendly_name: "Minimum Battery Level",
unit_of_measurement: "%",
}),
{
entity_id: "device_tracker.demo_paulus",
state: "work",
attributes: {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 25,
friendly_name: "Paulus",
},
},
{
entity_id: "device_tracker.demo_anne_therese",
state: "school",
attributes: {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 50,
friendly_name: "Anne Therese",
},
},
{
entity_id: "device_tracker.demo_home_boy",
state: "home",
attributes: {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 75,
friendly_name: "Home Boy",
},
},
{
entity_id: "light.bed_light",
state: "on",
attributes: {
friendly_name: "Bed Light",
},
},
{
entity_id: "light.kitchen_lights",
state: "on",
attributes: {
friendly_name: "Kitchen Lights",
},
},
{
entity_id: "light.ceiling_lights",
state: "off",
attributes: {
friendly_name: "Ceiling Lights",
},
},
{
entity_id: "sensor.battery_1",
state: "20",
attributes: {
device_class: "battery",
friendly_name: "Battery 1",
unit_of_measurement: "%",
},
},
{
entity_id: "sensor.battery_2",
state: "35",
attributes: {
device_class: "battery",
friendly_name: "Battery 2",
unit_of_measurement: "%",
},
},
{
entity_id: "sensor.battery_3",
state: "40",
attributes: {
device_class: "battery",
friendly_name: "Battery 3",
unit_of_measurement: "%",
},
},
{
entity_id: "sensor.battery_4",
state: "80",
attributes: {
device_class: "battery",
friendly_name: "Battery 4",
unit_of_measurement: "%",
},
},
{
entity_id: "input_number.min_battery_level",
state: "30",
attributes: {
mode: "slider",
step: 10,
min: 0,
max: 100,
icon: "mdi:battery-alert-variant",
friendly_name: "Minimum Battery Level",
unit_of_measurement: "%",
},
},
];
const CONFIGS = [

View File

@@ -1,23 +1,30 @@
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
getEntity("sensor", "brightness", "12", {}),
getEntity("sensor", "brightness_medium", "53", {}),
getEntity("sensor", "brightness_high", "87", {}),
getEntity("plant", "bonsai", "ok", {}),
getEntity("sensor", "not_working", "unavailable", {}),
getEntity("sensor", "outside_humidity", "54", {
unit_of_measurement: "%",
}),
getEntity("sensor", "outside_temperature", "15.6", {
unit_of_measurement: "°C",
}),
{ entity_id: "sensor.brightness", state: "12", attributes: {} },
{ entity_id: "sensor.brightness_medium", state: "53", attributes: {} },
{ entity_id: "sensor.brightness_high", state: "87", attributes: {} },
{ entity_id: "plant.bonsai", state: "ok", attributes: {} },
{ entity_id: "sensor.not_working", state: "unavailable", attributes: {} },
{
entity_id: "sensor.outside_humidity",
state: "54",
attributes: {
unit_of_measurement: "%",
},
},
{
entity_id: "sensor.outside_temperature",
state: "15.6",
attributes: {
unit_of_measurement: "°C",
},
},
];
const CONFIGS = [
@@ -47,6 +54,19 @@ const CONFIGS = [
needle: true
`,
},
{
heading: "Rendering needle and severity levels",
config: `
- type: gauge
entity: sensor.brightness_high
name: Brightness High
needle: true
severity:
red: 75
green: 0
yellow: 50
`,
},
{
heading: "Setting severity levels",
config: `

View File

@@ -1,62 +1,89 @@
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
getEntity("device_tracker", "demo_paulus", "home", {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 71,
friendly_name: "Paulus",
}),
getEntity("media_player", "living_room", "playing", {
volume_level: 1,
is_volume_muted: false,
media_content_id: "eyU3bRy2x44",
media_content_type: "movie",
media_duration: 300,
media_position: 45.017773,
media_position_updated_at: "2018-07-19T10:44:45.919514+00:00",
media_title: "♥♥ The Best Fireplace Video (3 hours)",
app_name: "YouTube",
sound_mode: "Dummy Music",
sound_mode_list: ["Dummy Music", "Dummy Movie"],
shuffle: false,
friendly_name: "Living Room",
entity_picture:
"/api/media_player_proxy/media_player.living_room?token=e925f8db7f7bd1f317e4524dcb8333d60f6019219a3799a22604b5787f243567&cache=bc2ffb49c4f67034",
supported_features: 115597,
}),
getEntity("sun", "sun", "below_horizon", {
next_dawn: "2018-07-19T20:48:47+00:00",
next_dusk: "2018-07-20T11:46:06+00:00",
next_midnight: "2018-07-19T16:17:28+00:00",
next_noon: "2018-07-20T04:17:26+00:00",
next_rising: "2018-07-19T21:16:31+00:00",
next_setting: "2018-07-20T11:18:22+00:00",
elevation: 67.69,
azimuth: 338.55,
friendly_name: "Sun",
}),
getEntity("cover", "kitchen_window", "open", {
friendly_name: "Kitchen Window",
supported_features: 11,
}),
getEntity("light", "kitchen_lights", "on", {
friendly_name: "Kitchen Lights",
}),
getEntity("light", "ceiling_lights", "off", {
friendly_name: "Ceiling Lights",
}),
getEntity("lock", "kitchen_door", "locked", {
friendly_name: "Kitchen Door",
}),
{
entity_id: "device_tracker.demo_paulus",
state: "home",
attributes: {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 71,
friendly_name: "Paulus",
},
},
{
entity_id: "media_player.living_room",
state: "playing",
attributes: {
volume_level: 1,
is_volume_muted: false,
media_content_id: "eyU3bRy2x44",
media_content_type: "movie",
media_duration: 300,
media_position: 45.017773,
media_position_updated_at: "2018-07-19T10:44:45.919514+00:00",
media_title: "♥♥ The Best Fireplace Video (3 hours)",
app_name: "YouTube",
sound_mode: "Dummy Music",
sound_mode_list: ["Dummy Music", "Dummy Movie"],
shuffle: false,
friendly_name: "Living Room",
entity_picture:
"/api/media_player_proxy/media_player.living_room?token=e925f8db7f7bd1f317e4524dcb8333d60f6019219a3799a22604b5787f243567&cache=bc2ffb49c4f67034",
supported_features: 115597,
},
},
{
entity_id: "sun.sun",
state: "below_horizon",
attributes: {
next_dawn: "2018-07-19T20:48:47+00:00",
next_dusk: "2018-07-20T11:46:06+00:00",
next_midnight: "2018-07-19T16:17:28+00:00",
next_noon: "2018-07-20T04:17:26+00:00",
next_rising: "2018-07-19T21:16:31+00:00",
next_setting: "2018-07-20T11:18:22+00:00",
elevation: 67.69,
azimuth: 338.55,
friendly_name: "Sun",
},
},
{
entity_id: "cover.kitchen_window",
state: "open",
attributes: {
friendly_name: "Kitchen Window",
supported_features: 11,
},
},
{
entity_id: "light.kitchen_lights",
state: "on",
attributes: {
friendly_name: "Kitchen Lights",
},
},
{
entity_id: "light.ceiling_lights",
state: "off",
attributes: {
friendly_name: "Ceiling Lights",
},
},
{
entity_id: "lock.kitchen_door",
state: "locked",
attributes: {
friendly_name: "Kitchen Door",
},
},
];
const CONFIGS = [

View File

@@ -2,46 +2,69 @@ import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators";
import { mockHistory } from "../../../../demo/src/stubs/history";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
getEntity("light", "kitchen_lights", "on", {
friendly_name: "Kitchen Lights",
}),
getEntity("light", "bed_light", "on", {
friendly_name: "Bed Lights",
}),
getEntity("device_tracker", "demo_paulus", "work", {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 71,
friendly_name: "Paulus",
}),
getEntity("device_tracker", "demo_anne_therese", "school", {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 71,
friendly_name: "Anne Therese",
}),
getEntity("device_tracker", "demo_home_boy", "home", {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 71,
friendly_name: "Home Boy",
}),
getEntity("sensor", "illumination", "23", {
friendly_name: "Illumination",
unit_of_measurement: "lx",
}),
{
entity_id: "light.kitchen_lights",
state: "on",
attributes: {
friendly_name: "Kitchen Lights",
},
},
{
entity_id: "light.bed_light",
state: "on",
attributes: {
friendly_name: "Bed Lights",
},
},
{
entity_id: "device_tracker.demo_paulus",
state: "work",
attributes: {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 71,
friendly_name: "Paulus",
},
},
{
entity_id: "device_tracker.demo_anne_therese",
state: "school",
attributes: {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 71,
friendly_name: "Anne Therese",
},
},
{
entity_id: "device_tracker.demo_home_boy",
state: "home",
attributes: {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 71,
friendly_name: "Home Boy",
},
},
{
entity_id: "sensor.illumination",
state: "23",
attributes: {
friendly_name: "Illumination",
unit_of_measurement: "lx",
},
},
];
const CONFIGS = [

View File

@@ -1,29 +1,44 @@
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
getEntity("light", "bed_light", "on", {
friendly_name: "Bed Light",
brightness: 255,
}),
getEntity("light", "dim_on", "on", {
friendly_name: "Dining Room",
supported_features: 1,
brightness: 100,
}),
getEntity("light", "dim_off", "off", {
friendly_name: "Dining Room",
supported_features: 1,
}),
getEntity("light", "unavailable", "unavailable", {
friendly_name: "Lost Light",
supported_features: 1,
}),
{
entity_id: "light.bed_light",
state: "on",
attributes: {
friendly_name: "Bed Light",
brightness: 255,
},
},
{
entity_id: "light.dim_on",
state: "on",
attributes: {
friendly_name: "Dining Room",
supported_features: 1,
brightness: 100,
},
},
{
entity_id: "light.dim_off",
state: "off",
attributes: {
friendly_name: "Dining Room",
supported_features: 1,
},
},
{
entity_id: "light.unavailable",
state: "unavailable",
attributes: {
friendly_name: "Lost Light",
supported_features: 1,
},
},
];
const CONFIGS = [

View File

@@ -1,59 +1,86 @@
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
const ENTITIES = [
getEntity("device_tracker", "demo_paulus", "not_home", {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 71,
friendly_name: "Paulus",
}),
getEntity("device_tracker", "demo_home_boy", "home", {
source_type: "gps",
latitude: 32.87334,
longitude: 117.22745,
gps_accuracy: 20,
battery: 53,
friendly_name: "Home Boy",
}),
getEntity("zone", "home", "zoning", {
latitude: 32.87354,
longitude: 117.22765,
radius: 100,
friendly_name: "Home",
icon: "mdi:home",
}),
getEntity("zone", "bushfire", "zoning", {
latitude: -33.8611,
longitude: 151.203,
radius: 35000,
friendly_name: "Bushfire Zone",
icon: "mdi:home",
}),
getEntity("geo_location", "nelsons_creek", "15", {
source: "bushfire_demo",
latitude: -34.07792,
longitude: 151.03219,
friendly_name: "Nelsons Creek",
}),
getEntity("geo_location", "forest_rd_nowra_hill", "8", {
source: "bushfire_demo",
latitude: -33.69452,
longitude: 151.19577,
friendly_name: "Forest Rd, Nowra Hill",
}),
getEntity("geo_location", "stoney_ridge_rd_kremnos", "20", {
source: "bushfire_demo",
latitude: -33.66584,
longitude: 150.97209,
friendly_name: "Stoney Ridge Rd, Kremnos",
}),
{
entity_id: "device_tracker.demo_paulus",
state: "not_home",
attributes: {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 71,
friendly_name: "Paulus",
},
},
{
entity_id: "device_tracker.demo_home_boy",
state: "home",
attributes: {
source_type: "gps",
latitude: 32.87334,
longitude: 117.22745,
gps_accuracy: 20,
battery: 53,
friendly_name: "Home Boy",
},
},
{
entity_id: "zone.home",
state: "zoning",
attributes: {
latitude: 32.87354,
longitude: 117.22765,
radius: 100,
friendly_name: "Home",
icon: "mdi:home",
},
},
{
entity_id: "zone.bushfire",
state: "zoning",
attributes: {
latitude: -33.8611,
longitude: 151.203,
radius: 35000,
friendly_name: "Bushfire Zone",
icon: "mdi:home",
},
},
{
entity_id: "geo_location.nelsons_creek",
state: "15",
attributes: {
source: "bushfire_demo",
latitude: -34.07792,
longitude: 151.03219,
friendly_name: "Nelsons Creek",
},
},
{
entity_id: "geo_location.forest_rd_nowra_hill",
state: "8",
attributes: {
source: "bushfire_demo",
latitude: -33.69452,
longitude: 151.19577,
friendly_name: "Forest Rd, Nowra Hill",
},
},
{
entity_id: "geo_location.stoney_ridge_rd_kremnos",
state: "20",
attributes: {
source: "bushfire_demo",
latitude: -33.66584,
longitude: 150.97209,
friendly_name: "Stoney Ridge Rd, Kremnos",
},
},
];
const CONFIGS = [

View File

@@ -1,16 +1,19 @@
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
getEntity("person", "paulus", "home", {
friendly_name: "Paulus",
entity_picture: "/images/paulus.jpg",
}),
{
entity_id: "person.paulus",
state: "home",
attributes: {
friendly_name: "Paulus",
entity_picture: "/images/paulus.jpg",
},
},
];
const CONFIGS = [

View File

@@ -1,40 +1,63 @@
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
getEntity("light", "bed_light", "on", {
friendly_name: "Bed Light",
}),
getEntity("group", "all_lights", "on", {
entity_id: ["light.bed_light"],
order: 8,
friendly_name: "All Lights",
}),
getEntity("camera", "demo_camera", "idle", {
access_token:
"2f5bb163fb91cd8770a9494fa5e7eab172d8d34f4aba806eb6b59411b8c720b8",
friendly_name: "Demo camera",
entity_picture:
"/api/camera_proxy/camera.demo_camera?token=2f5bb163fb91cd8770a9494fa5e7eab172d8d34f4aba806eb6b59411b8c720b8",
}),
getEntity("binary_sensor", "movement_backyard", "on", {
friendly_name: "Movement Backyard",
device_class: "motion",
}),
getEntity("person", "paulus", "home", {
friendly_name: "Paulus",
entity_picture: "/images/paulus.jpg",
}),
getEntity("sensor", "battery", 35, {
device_class: "battery",
friendly_name: "Battery",
unit_of_measurement: "%",
}),
{
entity_id: "light.bed_light",
state: "on",
attributes: {
friendly_name: "Bed Light",
},
},
{
entity_id: "group.all_lights",
state: "on",
attributes: {
entity_id: ["light.bed_light"],
order: 8,
friendly_name: "All Lights",
},
},
{
entity_id: "camera.demo_camera",
state: "idle",
attributes: {
access_token:
"2f5bb163fb91cd8770a9494fa5e7eab172d8d34f4aba806eb6b59411b8c720b8",
friendly_name: "Demo camera",
entity_picture:
"/api/camera_proxy/camera.demo_camera?token=2f5bb163fb91cd8770a9494fa5e7eab172d8d34f4aba806eb6b59411b8c720b8",
},
},
{
entity_id: "binary_sensor.movement_backyard",
state: "on",
attributes: {
friendly_name: "Movement Backyard",
device_class: "motion",
},
},
{
entity_id: "person.paulus",
state: "home",
attributes: {
friendly_name: "Paulus",
entity_picture: "/images/paulus.jpg",
},
},
{
entity_id: "sensor.battery",
state: "35",
attributes: {
device_class: "battery",
friendly_name: "Battery",
unit_of_measurement: "%",
},
},
];
const CONFIGS = [

View File

@@ -1,22 +1,33 @@
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
getEntity("light", "kitchen_lights", "on", {
friendly_name: "Kitchen Lights",
}),
getEntity("light", "bed_light", "off", {
friendly_name: "Bed Light",
}),
getEntity("person", "paulus", "home", {
friendly_name: "Paulus",
entity_picture: "/images/paulus.jpg",
}),
{
entity_id: "light.kitchen_lights",
state: "on",
attributes: {
friendly_name: "Kitchen Lights",
},
},
{
entity_id: "light.bed_light",
state: "off",
attributes: {
friendly_name: "Bed Light",
},
},
{
entity_id: "person.paulus",
state: "home",
attributes: {
friendly_name: "Paulus",
entity_picture: "/images/paulus.jpg",
},
},
];
const CONFIGS = [

View File

@@ -1,35 +1,58 @@
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
getEntity("switch", "decorative_lights", "on", {
friendly_name: "Decorative Lights",
}),
getEntity("light", "ceiling_lights", "on", {
friendly_name: "Ceiling Lights",
}),
getEntity("binary_sensor", "movement_backyard", "on", {
friendly_name: "Movement Backyard",
device_class: "moving",
}),
getEntity("binary_sensor", "basement_floor_wet", "off", {
friendly_name: "Basement Floor Wet",
device_class: "moisture",
}),
getEntity("person", "paulus", "home", {
friendly_name: "Paulus",
entity_picture: "/images/paulus.jpg",
}),
getEntity("sensor", "battery", 35, {
device_class: "battery",
friendly_name: "Battery",
unit_of_measurement: "%",
}),
{
entity_id: "switch.decorative_lights",
state: "on",
attributes: {
friendly_name: "Decorative Lights",
},
},
{
entity_id: "light.ceiling_lights",
state: "on",
attributes: {
friendly_name: "Ceiling Lights",
},
},
{
entity_id: "binary_sensor.movement_backyard",
state: "on",
attributes: {
friendly_name: "Movement Backyard",
device_class: "moving",
},
},
{
entity_id: "binary_sensor.basement_floor_wet",
state: "off",
attributes: {
friendly_name: "Basement Floor Wet",
device_class: "moisture",
},
},
{
entity_id: "person.paulus",
state: "home",
attributes: {
friendly_name: "Paulus",
entity_picture: "/images/paulus.jpg",
},
},
{
entity_id: "sensor.battery",
state: "35",
attributes: {
device_class: "battery",
friendly_name: "Battery",
unit_of_measurement: "%",
},
},
];
const CONFIGS = [

View File

@@ -1,100 +1,123 @@
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
getEntity("climate", "ecobee", "auto", {
current_temperature: 73,
min_temp: 45,
max_temp: 95,
temperature: null,
target_temp_high: 75,
target_temp_low: 70,
fan_mode: "Auto Low",
fan_modes: ["On Low", "On High", "Auto Low", "Auto High", "Off"],
hvac_modes: ["heat", "cool", "auto", "off"],
swing_mode: "Auto",
swing_modes: ["Auto", "1", "2", "3", "Off"],
friendly_name: "Ecobee",
supported_features: 59,
preset_mode: "eco",
preset_modes: ["away", "eco"],
}),
getEntity("climate", "nest", "heat", {
current_temperature: 17,
min_temp: 15,
max_temp: 25,
temperature: 19,
fan_mode: "Auto Low",
fan_modes: ["On Low", "On High", "Auto Low", "Auto High", "Off"],
hvac_modes: ["heat", "cool", "auto", "off"],
swing_mode: "Auto",
swing_modes: ["Auto", "1", "2", "3", "Off"],
friendly_name: "Nest",
supported_features: 43,
}),
getEntity("climate", "overkiz_radiator", "heat", {
current_temperature: 18,
min_temp: 7,
max_temp: 35,
temperature: 20,
hvac_modes: ["heat", "auto", "off"],
friendly_name: "Overkiz radiator",
supported_features: 17,
preset_mode: "comfort",
preset_modes: [
"none",
"frost_protection",
"eco",
"comfort",
"comfort-1",
"comfort-2",
"auto",
"boost",
"external",
"prog",
],
}),
getEntity("climate", "overkiz_towel_dryer", "heat", {
current_temperature: null,
min_temp: 7,
max_temp: 35,
hvac_modes: ["heat", "off"],
friendly_name: "Overkiz towel dryer",
supported_features: 16,
preset_mode: "eco",
preset_modes: [
"none",
"frost_protection",
"eco",
"comfort",
"comfort-1",
"comfort-2",
],
}),
getEntity("climate", "sensibo", "fan_only", {
current_temperature: null,
temperature: null,
min_temp: 0,
max_temp: 1,
target_temp_step: 1,
hvac_modes: ["fan_only", "off"],
friendly_name: "Sensibo purifier",
fan_modes: ["low", "high"],
fan_mode: "low",
swing_modes: ["both", "rangefull", "off"],
swing_mode: "rangefull",
swing_horizontal_modes: ["both", "rangefull", "off"],
swing_horizontal_mode: "both",
supported_features: 553,
}),
getEntity("climate", "unavailable", "unavailable", {
supported_features: 43,
}),
{
entity_id: "climate.ecobee",
state: "auto",
attributes: {
current_temperature: 73,
min_temp: 45,
max_temp: 95,
temperature: null,
target_temp_high: 75,
target_temp_low: 70,
fan_mode: "Auto Low",
fan_modes: ["On Low", "On High", "Auto Low", "Auto High", "Off"],
hvac_modes: ["heat", "cool", "auto", "off"],
swing_mode: "Auto",
swing_modes: ["Auto", "1", "2", "3", "Off"],
friendly_name: "Ecobee",
supported_features: 59,
preset_mode: "eco",
preset_modes: ["away", "eco"],
},
},
{
entity_id: "climate.nest",
state: "heat",
attributes: {
current_temperature: 17,
min_temp: 15,
max_temp: 25,
temperature: 19,
fan_mode: "Auto Low",
fan_modes: ["On Low", "On High", "Auto Low", "Auto High", "Off"],
hvac_modes: ["heat", "cool", "auto", "off"],
swing_mode: "Auto",
swing_modes: ["Auto", "1", "2", "3", "Off"],
friendly_name: "Nest",
supported_features: 43,
},
},
{
entity_id: "climate.overkiz_radiator",
state: "heat",
attributes: {
current_temperature: 18,
min_temp: 7,
max_temp: 35,
temperature: 20,
hvac_modes: ["heat", "auto", "off"],
friendly_name: "Overkiz radiator",
supported_features: 17,
preset_mode: "comfort",
preset_modes: [
"none",
"frost_protection",
"eco",
"comfort",
"comfort-1",
"comfort-2",
"auto",
"boost",
"external",
"prog",
],
},
},
{
entity_id: "climate.overkiz_towel_dryer",
state: "heat",
attributes: {
current_temperature: null,
min_temp: 7,
max_temp: 35,
hvac_modes: ["heat", "off"],
friendly_name: "Overkiz towel dryer",
supported_features: 16,
preset_mode: "eco",
preset_modes: [
"none",
"frost_protection",
"eco",
"comfort",
"comfort-1",
"comfort-2",
],
},
},
{
entity_id: "climate.sensibo",
state: "fan_only",
attributes: {
current_temperature: null,
temperature: null,
min_temp: 0,
max_temp: 1,
target_temp_step: 1,
hvac_modes: ["fan_only", "off"],
friendly_name: "Sensibo purifier",
fan_modes: ["low", "high"],
fan_mode: "low",
swing_modes: ["both", "rangefull", "off"],
swing_mode: "rangefull",
swing_horizontal_modes: ["both", "rangefull", "off"],
swing_horizontal_mode: "both",
supported_features: 553,
},
},
{
entity_id: "climate.unavailable",
state: "unavailable",
attributes: {
supported_features: 43,
},
},
];
const CONFIGS = [

View File

@@ -6,7 +6,6 @@ import { LightColorMode } from "../../../../src/data/light";
import { LockEntityFeature } from "../../../../src/data/lock";
import { MediaPlayerEntityFeature } from "../../../../src/data/media-player";
import { VacuumEntityFeature } from "../../../../src/data/vacuum";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
@@ -14,102 +13,154 @@ import { ClimateEntityFeature } from "../../../../src/data/climate";
import { FanEntityFeature } from "../../../../src/data/fan";
const ENTITIES = [
getEntity("switch", "tv_outlet", "on", {
friendly_name: "TV outlet",
device_class: "outlet",
}),
getEntity("light", "bed_light", "on", {
friendly_name: "Bed Light",
supported_color_modes: [LightColorMode.HS, LightColorMode.COLOR_TEMP],
}),
getEntity("light", "unavailable", "unavailable", {
friendly_name: "Unavailable entity",
}),
getEntity("lock", "front_door", "locked", {
friendly_name: "Front Door Lock",
device_class: "lock",
supported_features: LockEntityFeature.OPEN,
}),
getEntity("media_player", "living_room", "playing", {
friendly_name: "Living room speaker",
supported_features: MediaPlayerEntityFeature.VOLUME_SET,
}),
getEntity("climate", "thermostat", "heat", {
current_temperature: 73,
min_temp: 45,
max_temp: 95,
temperature: 80,
hvac_modes: ["heat", "cool", "auto", "off"],
friendly_name: "Thermostat",
hvac_action: "heating",
}),
getEntity("person", "paulus", "home", {
friendly_name: "Paulus",
}),
getEntity("vacuum", "first_floor_vacuum", "docked", {
friendly_name: "First floor vacuum",
supported_features:
VacuumEntityFeature.START +
VacuumEntityFeature.STOP +
VacuumEntityFeature.RETURN_HOME,
}),
getEntity("cover", "kitchen_shutter", "open", {
friendly_name: "Kitchen shutter",
device_class: "shutter",
supported_features:
CoverEntityFeature.CLOSE +
CoverEntityFeature.OPEN +
CoverEntityFeature.STOP,
}),
getEntity("cover", "pergola_roof", "open", {
friendly_name: "Pergola Roof",
supported_features:
CoverEntityFeature.CLOSE_TILT +
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT,
}),
getEntity("input_number", "counter", "1.0", {
friendly_name: "Counter",
initial: 0,
min: 0,
max: 100,
step: 1,
mode: "slider",
}),
getEntity("climate", "dual_thermostat", "heat/cool", {
friendly_name: "Dual thermostat",
hvac_modes: ["off", "cool", "heat_cool", "auto", "dry", "fan_only"],
min_temp: 7,
max_temp: 35,
fan_modes: ["on_low", "on_high", "auto_low", "auto_high", "off"],
preset_modes: ["home", "eco", "away"],
swing_modes: ["auto", "1", "2", "3", "off"],
switch_horizontal_modes: ["auto", "4", "5", "6", "off"],
current_temperature: 23,
target_temp_high: 24,
target_temp_low: 21,
fan_mode: "auto_low",
preset_mode: "home",
swing_mode: "auto",
swing_horizontal_mode: "off",
supported_features:
ClimateEntityFeature.TURN_ON +
ClimateEntityFeature.TURN_OFF +
ClimateEntityFeature.SWING_MODE +
ClimateEntityFeature.SWING_HORIZONTAL_MODE +
ClimateEntityFeature.PRESET_MODE +
ClimateEntityFeature.FAN_MODE +
ClimateEntityFeature.TARGET_TEMPERATURE_RANGE,
}),
getEntity("fan", "fan_demo", "on", {
friendly_name: "Ceiling fan",
device_class: "fan",
direction: "reverse",
supported_features:
FanEntityFeature.DIRECTION +
FanEntityFeature.SET_SPEED +
FanEntityFeature.OSCILLATE,
}),
{
entity_id: "switch.tv_outlet",
state: "on",
attributes: {
friendly_name: "TV outlet",
device_class: "outlet",
},
},
{
entity_id: "light.bed_light",
state: "on",
attributes: {
friendly_name: "Bed Light",
supported_color_modes: [LightColorMode.HS, LightColorMode.COLOR_TEMP],
},
},
{
entity_id: "light.unavailable",
state: "unavailable",
attributes: {
friendly_name: "Unavailable entity",
},
},
{
entity_id: "lock.front_door",
state: "locked",
attributes: {
friendly_name: "Front Door Lock",
device_class: "lock",
supported_features: LockEntityFeature.OPEN,
},
},
{
entity_id: "media_player.living_room",
state: "playing",
attributes: {
friendly_name: "Living room speaker",
supported_features: MediaPlayerEntityFeature.VOLUME_SET,
},
},
{
entity_id: "climate.thermostat",
state: "heat",
attributes: {
current_temperature: 73,
min_temp: 45,
max_temp: 95,
temperature: 80,
hvac_modes: ["heat", "cool", "auto", "off"],
friendly_name: "Thermostat",
hvac_action: "heating",
},
},
{
entity_id: "person.paulus",
state: "home",
attributes: {
friendly_name: "Paulus",
},
},
{
entity_id: "vacuum.first_floor_vacuum",
state: "docked",
attributes: {
friendly_name: "First floor vacuum",
supported_features:
VacuumEntityFeature.START +
VacuumEntityFeature.STOP +
VacuumEntityFeature.RETURN_HOME,
},
},
{
entity_id: "cover.kitchen_shutter",
state: "open",
attributes: {
friendly_name: "Kitchen shutter",
device_class: "shutter",
supported_features:
CoverEntityFeature.CLOSE +
CoverEntityFeature.OPEN +
CoverEntityFeature.STOP,
},
},
{
entity_id: "cover.pergola_roof",
state: "open",
attributes: {
friendly_name: "Pergola Roof",
supported_features:
CoverEntityFeature.CLOSE_TILT +
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT,
},
},
{
entity_id: "input_number.counter",
state: "1.0",
attributes: {
friendly_name: "Counter",
initial: 0,
min: 0,
max: 100,
step: 1,
mode: "slider",
},
},
{
entity_id: "climate.dual_thermostat",
state: "heat/cool",
attributes: {
friendly_name: "Dual thermostat",
hvac_modes: ["off", "cool", "heat_cool", "auto", "dry", "fan_only"],
min_temp: 7,
max_temp: 35,
fan_modes: ["on_low", "on_high", "auto_low", "auto_high", "off"],
preset_modes: ["home", "eco", "away"],
swing_modes: ["auto", "1", "2", "3", "off"],
switch_horizontal_modes: ["auto", "4", "5", "6", "off"],
current_temperature: 23,
target_temp_high: 24,
target_temp_low: 21,
fan_mode: "auto_low",
preset_mode: "home",
swing_mode: "auto",
swing_horizontal_mode: "off",
supported_features:
ClimateEntityFeature.TURN_ON +
ClimateEntityFeature.TURN_OFF +
ClimateEntityFeature.SWING_MODE +
ClimateEntityFeature.SWING_HORIZONTAL_MODE +
ClimateEntityFeature.PRESET_MODE +
ClimateEntityFeature.FAN_MODE +
ClimateEntityFeature.TARGET_TEMPERATURE_RANGE,
},
},
{
entity_id: "fan.fan_demo",
state: "on",
attributes: {
friendly_name: "Ceiling fan",
device_class: "fan",
direction: "reverse",
supported_features:
FanEntityFeature.DIRECTION +
FanEntityFeature.SET_SPEED +
FanEntityFeature.OSCILLATE,
},
},
];
const CONFIGS = [

View File

@@ -3,18 +3,25 @@ import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators";
import { mockIcons } from "../../../../demo/src/stubs/icons";
import { mockTodo } from "../../../../demo/src/stubs/todo";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
const ENTITIES = [
getEntity("todo", "shopping_list", "2", {
friendly_name: "Shopping List",
supported_features: 15,
}),
getEntity("todo", "read_only", "2", {
friendly_name: "Read only",
}),
{
entity_id: "todo.shopping_list",
state: "2",
attributes: {
friendly_name: "Shopping List",
supported_features: 15,
},
},
{
entity_id: "todo.read_only",
state: "2",
attributes: {
friendly_name: "Read only",
},
},
];
const CONFIGS = [

View File

@@ -3,108 +3,135 @@ import { html, LitElement } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import type { MockHomeAssistant } from "../../../../src/fake_data/provide_hass";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
import { ClimateEntityFeature } from "../../../../src/data/climate";
const ENTITIES = [
getEntity("climate", "radiator", "heat", {
friendly_name: "Basic heater",
hvac_modes: ["heat", "off"],
hvac_mode: "heat",
current_temperature: 18,
temperature: 20,
min_temp: 10,
max_temp: 30,
supported_features: ClimateEntityFeature.TARGET_TEMPERATURE,
}),
getEntity("climate", "ac", "cool", {
friendly_name: "Basic air conditioning",
hvac_modes: ["cool", "off"],
hvac_mode: "cool",
current_temperature: 18,
temperature: 20,
min_temp: 10,
max_temp: 30,
supported_features: ClimateEntityFeature.TARGET_TEMPERATURE,
}),
getEntity("climate", "fan", "fan_only", {
friendly_name: "Basic fan",
hvac_modes: ["fan_only", "off"],
hvac_mode: "fan_only",
fan_modes: ["low", "high"],
fan_mode: "low",
current_temperature: null,
temperature: null,
min_temp: 0,
max_temp: 1,
target_temp_step: 1,
supported_features:
// eslint-disable-next-line no-bitwise
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE,
}),
getEntity("climate", "hvac", "auto", {
friendly_name: "Basic hvac",
hvac_modes: ["auto", "off"],
hvac_mode: "auto",
current_temperature: 18,
min_temp: 10,
max_temp: 30,
target_temp_step: 1,
supported_features: ClimateEntityFeature.TARGET_TEMPERATURE_RANGE,
target_temp_low: 20,
target_temp_high: 25,
}),
getEntity("climate", "advanced", "auto", {
friendly_name: "Advanced hvac",
supported_features:
// eslint-disable-next-line no-bitwise
ClimateEntityFeature.TARGET_TEMPERATURE_RANGE |
ClimateEntityFeature.TARGET_HUMIDITY |
ClimateEntityFeature.PRESET_MODE,
hvac_modes: ["auto", "off"],
hvac_mode: "auto",
preset_modes: ["eco", "comfort", "boost"],
preset_mode: "eco",
current_temperature: 18,
min_temp: 10,
max_temp: 30,
target_temp_step: 1,
target_temp_low: 20,
target_temp_high: 25,
current_humidity: 40,
min_humidity: 0,
max_humidity: 100,
humidity: 50,
}),
getEntity("climate", "towel_dryer", "heat", {
friendly_name: "Preset only heater",
hvac_modes: ["heat", "off"],
hvac_mode: "heat",
preset_modes: [
"none",
"frost_protection",
"eco",
"comfort",
"comfort-1",
"comfort-2",
],
preset_mode: "eco",
current_temperature: null,
min_temp: 7,
max_temp: 35,
supported_features: ClimateEntityFeature.PRESET_MODE,
}),
getEntity("climate", "unavailable", "unavailable", {
friendly_name: "Unavailable heater",
hvac_modes: ["heat", "off"],
hvac_mode: "heat",
min_temp: 10,
max_temp: 30,
supported_features: ClimateEntityFeature.TARGET_TEMPERATURE,
}),
{
entity_id: "climate.radiator",
state: "heat",
attributes: {
friendly_name: "Basic heater",
hvac_modes: ["heat", "off"],
hvac_mode: "heat",
current_temperature: 18,
temperature: 20,
min_temp: 10,
max_temp: 30,
supported_features: ClimateEntityFeature.TARGET_TEMPERATURE,
},
},
{
entity_id: "climate.ac",
state: "cool",
attributes: {
friendly_name: "Basic air conditioning",
hvac_modes: ["cool", "off"],
hvac_mode: "cool",
current_temperature: 18,
temperature: 20,
min_temp: 10,
max_temp: 30,
supported_features: ClimateEntityFeature.TARGET_TEMPERATURE,
},
},
{
entity_id: "climate.fan",
state: "fan_only",
attributes: {
friendly_name: "Basic fan",
hvac_modes: ["fan_only", "off"],
hvac_mode: "fan_only",
fan_modes: ["low", "high"],
fan_mode: "low",
current_temperature: null,
temperature: null,
min_temp: 0,
max_temp: 1,
target_temp_step: 1,
supported_features:
// eslint-disable-next-line no-bitwise
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE,
},
},
{
entity_id: "climate.hvac",
state: "auto",
attributes: {
friendly_name: "Basic hvac",
hvac_modes: ["auto", "off"],
hvac_mode: "auto",
current_temperature: 18,
min_temp: 10,
max_temp: 30,
target_temp_step: 1,
supported_features: ClimateEntityFeature.TARGET_TEMPERATURE_RANGE,
target_temp_low: 20,
target_temp_high: 25,
},
},
{
entity_id: "climate.advanced",
state: "auto",
attributes: {
friendly_name: "Advanced hvac",
supported_features:
// eslint-disable-next-line no-bitwise
ClimateEntityFeature.TARGET_TEMPERATURE_RANGE |
ClimateEntityFeature.TARGET_HUMIDITY |
ClimateEntityFeature.PRESET_MODE,
hvac_modes: ["auto", "off"],
hvac_mode: "auto",
preset_modes: ["eco", "comfort", "boost"],
preset_mode: "eco",
current_temperature: 18,
min_temp: 10,
max_temp: 30,
target_temp_step: 1,
target_temp_low: 20,
target_temp_high: 25,
current_humidity: 40,
min_humidity: 0,
max_humidity: 100,
humidity: 50,
},
},
{
entity_id: "climate.towel_dryer",
state: "heat",
attributes: {
friendly_name: "Preset only heater",
hvac_modes: ["heat", "off"],
hvac_mode: "heat",
preset_modes: [
"none",
"frost_protection",
"eco",
"comfort",
"comfort-1",
"comfort-2",
],
preset_mode: "eco",
current_temperature: null,
min_temp: 7,
max_temp: 35,
supported_features: ClimateEntityFeature.PRESET_MODE,
},
},
{
entity_id: "climate.unavailable",
state: "unavailable",
attributes: {
friendly_name: "Unavailable heater",
hvac_modes: ["heat", "off"],
hvac_mode: "heat",
min_temp: 10,
max_temp: 30,
supported_features: ClimateEntityFeature.TARGET_TEMPERATURE,
},
},
];
@customElement("demo-more-info-climate")
@@ -117,7 +144,7 @@ class DemoMoreInfoClimate extends LitElement {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entityId)}
.entities=${ENTITIES.map((ent) => ent.entity_id)}
></demo-more-infos>
`;
}

View File

@@ -4,138 +4,189 @@ import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import { CoverEntityFeature } from "../../../../src/data/cover";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import type { MockHomeAssistant } from "../../../../src/fake_data/provide_hass";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
const ENTITIES = [
getEntity("cover", "position_buttons", "on", {
friendly_name: "Position Buttons",
supported_features:
CoverEntityFeature.OPEN +
CoverEntityFeature.STOP +
CoverEntityFeature.CLOSE,
}),
getEntity("cover", "position_slider_half", "on", {
friendly_name: "Position Half-Open",
supported_features:
CoverEntityFeature.OPEN +
CoverEntityFeature.STOP +
CoverEntityFeature.CLOSE +
CoverEntityFeature.SET_POSITION,
current_position: 50,
}),
getEntity("cover", "position_slider_open", "on", {
friendly_name: "Position Open",
supported_features:
CoverEntityFeature.OPEN +
CoverEntityFeature.STOP +
CoverEntityFeature.CLOSE +
CoverEntityFeature.SET_POSITION,
current_position: 100,
}),
getEntity("cover", "position_slider_closed", "on", {
friendly_name: "Position Closed",
supported_features:
CoverEntityFeature.OPEN +
CoverEntityFeature.STOP +
CoverEntityFeature.CLOSE +
CoverEntityFeature.SET_POSITION,
current_position: 0,
}),
getEntity("cover", "tilt_buttons", "on", {
friendly_name: "Tilt Buttons",
supported_features:
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT +
CoverEntityFeature.CLOSE_TILT,
}),
getEntity("cover", "tilt_slider_half", "on", {
friendly_name: "Tilt Half-Open",
supported_features:
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT +
CoverEntityFeature.CLOSE_TILT +
CoverEntityFeature.SET_TILT_POSITION,
current_tilt_position: 50,
}),
getEntity("cover", "tilt_slider_open", "on", {
friendly_name: "Tilt Open",
supported_features:
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT +
CoverEntityFeature.CLOSE_TILT +
CoverEntityFeature.SET_TILT_POSITION,
current_tilt_position: 100,
}),
getEntity("cover", "tilt_slider_closed", "on", {
friendly_name: "Tilt Closed",
supported_features:
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT +
CoverEntityFeature.CLOSE_TILT +
CoverEntityFeature.SET_TILT_POSITION,
current_tilt_position: 0,
}),
getEntity("cover", "position_slider_tilt_slider", "on", {
friendly_name: "Both Sliders",
supported_features:
CoverEntityFeature.OPEN +
CoverEntityFeature.STOP +
CoverEntityFeature.CLOSE +
CoverEntityFeature.SET_POSITION +
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT +
CoverEntityFeature.CLOSE_TILT +
CoverEntityFeature.SET_TILT_POSITION,
current_position: 30,
current_tilt_position: 70,
}),
getEntity("cover", "position_tilt_slider", "on", {
friendly_name: "Position & Tilt Slider",
supported_features:
CoverEntityFeature.OPEN +
CoverEntityFeature.STOP +
CoverEntityFeature.CLOSE +
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT +
CoverEntityFeature.CLOSE_TILT +
CoverEntityFeature.SET_TILT_POSITION,
current_tilt_position: 70,
}),
getEntity("cover", "position_slider_tilt", "on", {
friendly_name: "Position Slider & Tilt",
supported_features:
CoverEntityFeature.OPEN +
CoverEntityFeature.STOP +
CoverEntityFeature.CLOSE +
CoverEntityFeature.SET_POSITION +
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT +
CoverEntityFeature.CLOSE_TILT,
current_position: 30,
}),
getEntity("cover", "position_slider_only_tilt_slider", "on", {
friendly_name: "Position Slider Only & Tilt Buttons",
supported_features:
CoverEntityFeature.SET_POSITION +
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT +
CoverEntityFeature.CLOSE_TILT,
current_position: 30,
}),
getEntity("cover", "position_slider_only_tilt", "on", {
friendly_name: "Position Slider Only & Tilt",
supported_features:
CoverEntityFeature.SET_POSITION +
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT +
CoverEntityFeature.CLOSE_TILT +
CoverEntityFeature.SET_TILT_POSITION,
current_position: 30,
current_tilt_position: 70,
}),
{
entity_id: "cover.position_buttons",
state: "on",
attributes: {
friendly_name: "Position Buttons",
supported_features:
CoverEntityFeature.OPEN +
CoverEntityFeature.STOP +
CoverEntityFeature.CLOSE,
},
},
{
entity_id: "cover.position_slider_half",
state: "on",
attributes: {
friendly_name: "Position Half-Open",
supported_features:
CoverEntityFeature.OPEN +
CoverEntityFeature.STOP +
CoverEntityFeature.CLOSE +
CoverEntityFeature.SET_POSITION,
current_position: 50,
},
},
{
entity_id: "cover.position_slider_open",
state: "on",
attributes: {
friendly_name: "Position Open",
supported_features:
CoverEntityFeature.OPEN +
CoverEntityFeature.STOP +
CoverEntityFeature.CLOSE +
CoverEntityFeature.SET_POSITION,
current_position: 100,
},
},
{
entity_id: "cover.position_slider_closed",
state: "on",
attributes: {
friendly_name: "Position Closed",
supported_features:
CoverEntityFeature.OPEN +
CoverEntityFeature.STOP +
CoverEntityFeature.CLOSE +
CoverEntityFeature.SET_POSITION,
current_position: 0,
},
},
{
entity_id: "cover.tilt_buttons",
state: "on",
attributes: {
friendly_name: "Tilt Buttons",
supported_features:
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT +
CoverEntityFeature.CLOSE_TILT,
},
},
{
entity_id: "cover.tilt_slider_half",
state: "on",
attributes: {
friendly_name: "Tilt Half-Open",
supported_features:
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT +
CoverEntityFeature.CLOSE_TILT +
CoverEntityFeature.SET_TILT_POSITION,
current_tilt_position: 50,
},
},
{
entity_id: "cover.tilt_slider_open",
state: "on",
attributes: {
friendly_name: "Tilt Open",
supported_features:
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT +
CoverEntityFeature.CLOSE_TILT +
CoverEntityFeature.SET_TILT_POSITION,
current_tilt_position: 100,
},
},
{
entity_id: "cover.tilt_slider_closed",
state: "on",
attributes: {
friendly_name: "Tilt Closed",
supported_features:
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT +
CoverEntityFeature.CLOSE_TILT +
CoverEntityFeature.SET_TILT_POSITION,
current_tilt_position: 0,
},
},
{
entity_id: "cover.position_slider_tilt_slider",
state: "on",
attributes: {
friendly_name: "Both Sliders",
supported_features:
CoverEntityFeature.OPEN +
CoverEntityFeature.STOP +
CoverEntityFeature.CLOSE +
CoverEntityFeature.SET_POSITION +
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT +
CoverEntityFeature.CLOSE_TILT +
CoverEntityFeature.SET_TILT_POSITION,
current_position: 30,
current_tilt_position: 70,
},
},
{
entity_id: "cover.position_tilt_slider",
state: "on",
attributes: {
friendly_name: "Position & Tilt Slider",
supported_features:
CoverEntityFeature.OPEN +
CoverEntityFeature.STOP +
CoverEntityFeature.CLOSE +
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT +
CoverEntityFeature.CLOSE_TILT +
CoverEntityFeature.SET_TILT_POSITION,
current_tilt_position: 70,
},
},
{
entity_id: "cover.position_slider_tilt",
state: "on",
attributes: {
friendly_name: "Position Slider & Tilt",
supported_features:
CoverEntityFeature.OPEN +
CoverEntityFeature.STOP +
CoverEntityFeature.CLOSE +
CoverEntityFeature.SET_POSITION +
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT +
CoverEntityFeature.CLOSE_TILT,
current_position: 30,
},
},
{
entity_id: "cover.position_slider_only_tilt_slider",
state: "on",
attributes: {
friendly_name: "Position Slider Only & Tilt Buttons",
supported_features:
CoverEntityFeature.SET_POSITION +
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT +
CoverEntityFeature.CLOSE_TILT,
current_position: 30,
},
},
{
entity_id: "cover.position_slider_only_tilt",
state: "on",
attributes: {
friendly_name: "Position Slider Only & Tilt",
supported_features:
CoverEntityFeature.SET_POSITION +
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT +
CoverEntityFeature.CLOSE_TILT +
CoverEntityFeature.SET_TILT_POSITION,
current_position: 30,
current_tilt_position: 70,
},
},
];
@customElement("demo-more-info-cover")
@@ -148,7 +199,7 @@ class DemoMoreInfoCover extends LitElement {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entityId)}
.entities=${ENTITIES.map((ent) => ent.entity_id)}
></demo-more-infos>
`;
}

View File

@@ -3,21 +3,24 @@ import { html, LitElement } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import type { MockHomeAssistant } from "../../../../src/fake_data/provide_hass";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
import { FanEntityFeature } from "../../../../src/data/fan";
const ENTITIES = [
getEntity("fan", "fan", "on", {
friendly_name: "Fan",
device_class: "fan",
supported_features:
FanEntityFeature.OSCILLATE +
FanEntityFeature.DIRECTION +
FanEntityFeature.SET_SPEED,
}),
{
entity_id: "fan.fan",
state: "on",
attributes: {
friendly_name: "Fan",
device_class: "fan",
supported_features:
FanEntityFeature.OSCILLATE +
FanEntityFeature.DIRECTION +
FanEntityFeature.SET_SPEED,
},
},
];
@customElement("demo-more-info-fan")
@@ -30,7 +33,7 @@ class DemoMoreInfoFan extends LitElement {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entityId)}
.entities=${ENTITIES.map((ent) => ent.entity_id)}
></demo-more-infos>
`;
}

View File

@@ -3,27 +3,38 @@ import { html, LitElement } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import type { MockHomeAssistant } from "../../../../src/fake_data/provide_hass";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
const ENTITIES = [
getEntity("humidifier", "humidifier", "on", {
friendly_name: "Humidifier",
device_class: "humidifier",
current_humidity: 50,
humidity: 30,
}),
getEntity("humidifier", "dehumidifier", "on", {
friendly_name: "Dehumidifier",
device_class: "dehumidifier",
current_humidity: 50,
humidity: 30,
}),
getEntity("humidifier", "unavailable", "unavailable", {
friendly_name: "Unavailable humidifier",
}),
{
entity_id: "humidifier.humidifier",
state: "on",
attributes: {
friendly_name: "Humidifier",
device_class: "humidifier",
current_humidity: 50,
humidity: 30,
},
},
{
entity_id: "humidifier.dehumidifier",
state: "on",
attributes: {
friendly_name: "Dehumidifier",
device_class: "dehumidifier",
current_humidity: 50,
humidity: 30,
},
},
{
entity_id: "humidifier.unavailable",
state: "unavailable",
attributes: {
friendly_name: "Unavailable humidifier",
},
},
];
@customElement("demo-more-info-humidifier")
@@ -36,7 +47,7 @@ class DemoMoreInfoHumidifier extends LitElement {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entityId)}
.entities=${ENTITIES.map((ent) => ent.entity_id)}
></demo-more-infos>
`;
}

View File

@@ -3,30 +3,37 @@ import { html, LitElement } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import type { MockHomeAssistant } from "../../../../src/fake_data/provide_hass";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
const ENTITIES = [
getEntity("input_number", "box1", 0, {
friendly_name: "Box1",
min: 0,
max: 100,
step: 1,
initial: 0,
mode: "box",
unit_of_measurement: "items",
}),
getEntity("input_number", "slider1", 0, {
friendly_name: "Slider1",
min: 0,
max: 100,
step: 1,
initial: 0,
mode: "slider",
unit_of_measurement: "items",
}),
{
entity_id: "input_number.box1",
state: "0",
attributes: {
friendly_name: "Box1",
min: 0,
max: 100,
step: 1,
initial: 0,
mode: "box",
unit_of_measurement: "items",
},
},
{
entity_id: "input_number.slider1",
state: "0",
attributes: {
friendly_name: "Slider1",
min: 0,
max: 100,
step: 1,
initial: 0,
mode: "slider",
unit_of_measurement: "items",
},
},
];
@customElement("demo-more-info-input-number")
@@ -39,7 +46,7 @@ class DemoMoreInfoInputNumber extends LitElement {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entityId)}
.entities=${ENTITIES.map((ent) => ent.entity_id)}
></demo-more-infos>
`;
}

View File

@@ -3,16 +3,19 @@ import { html, LitElement } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import type { MockHomeAssistant } from "../../../../src/fake_data/provide_hass";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
const ENTITIES = [
getEntity("input_text", "text", "Inspiration", {
friendly_name: "Text",
mode: "text",
}),
{
entity_id: "input_text.text",
state: "Inspiration",
attributes: {
friendly_name: "Text",
mode: "text",
},
},
];
@customElement("demo-more-info-input-text")
@@ -25,7 +28,7 @@ class DemoMoreInfoInputText extends LitElement {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entityId)}
.entities=${ENTITIES.map((ent) => ent.entity_id)}
></demo-more-infos>
`;
}

View File

@@ -4,137 +4,172 @@ import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import { LightColorMode, LightEntityFeature } from "../../../../src/data/light";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import type { MockHomeAssistant } from "../../../../src/fake_data/provide_hass";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
const ENTITIES = [
getEntity("light", "bed_light", "on", {
friendly_name: "Basic Light",
}),
getEntity("light", "kitchen_light", "on", {
friendly_name: "Brightness Light",
brightness: 200,
supported_color_modes: [LightColorMode.BRIGHTNESS],
color_mode: LightColorMode.BRIGHTNESS,
}),
getEntity("light", "color_temperature_light", "on", {
friendly_name: "White Color Temperature Light",
brightness: 128,
color_temp: 75,
min_mireds: 30,
max_mireds: 150,
supported_color_modes: [
LightColorMode.BRIGHTNESS,
LightColorMode.COLOR_TEMP,
],
color_mode: LightColorMode.COLOR_TEMP,
}),
getEntity("light", "color_hs_light", "on", {
friendly_name: "Color HS Light",
brightness: 255,
hs_color: [30, 100],
rgb_color: [30, 100, 255],
min_mireds: 30,
max_mireds: 150,
supported_features:
LightEntityFeature.EFFECT +
LightEntityFeature.FLASH +
LightEntityFeature.TRANSITION,
supported_color_modes: [
LightColorMode.BRIGHTNESS,
LightColorMode.COLOR_TEMP,
LightColorMode.HS,
],
color_mode: LightColorMode.HS,
effect_list: ["random", "colorloop"],
}),
getEntity("light", "color_rgb_ct_light", "on", {
friendly_name: "Color RGB + CT Light",
brightness: 255,
color_temp: 75,
min_mireds: 30,
max_mireds: 150,
supported_features:
LightEntityFeature.EFFECT +
LightEntityFeature.FLASH +
LightEntityFeature.TRANSITION,
supported_color_modes: [
LightColorMode.BRIGHTNESS,
LightColorMode.COLOR_TEMP,
LightColorMode.RGB,
],
color_mode: LightColorMode.COLOR_TEMP,
effect_list: ["random", "colorloop"],
}),
getEntity("light", "color_RGB_light", "on", {
friendly_name: "Color Effects Light",
brightness: 255,
rgb_color: [30, 100, 255],
supported_features:
LightEntityFeature.EFFECT +
LightEntityFeature.FLASH +
LightEntityFeature.TRANSITION,
supported_color_modes: [LightColorMode.BRIGHTNESS, LightColorMode.RGB],
color_mode: LightColorMode.RGB,
effect_list: ["random", "colorloop"],
}),
getEntity("light", "color_rgbw_light", "on", {
friendly_name: "Color RGBW Light",
brightness: 255,
rgbw_color: [30, 100, 255, 125],
min_mireds: 30,
max_mireds: 150,
supported_features:
LightEntityFeature.EFFECT +
LightEntityFeature.FLASH +
LightEntityFeature.TRANSITION,
supported_color_modes: [
LightColorMode.BRIGHTNESS,
LightColorMode.COLOR_TEMP,
LightColorMode.RGBW,
],
color_mode: LightColorMode.RGBW,
effect_list: ["random", "colorloop"],
}),
getEntity("light", "color_rgbww_light", "on", {
friendly_name: "Color RGBWW Light",
brightness: 255,
rgbww_color: [30, 100, 255, 125, 10],
min_mireds: 30,
max_mireds: 150,
supported_features:
LightEntityFeature.EFFECT +
LightEntityFeature.FLASH +
LightEntityFeature.TRANSITION,
supported_color_modes: [
LightColorMode.BRIGHTNESS,
LightColorMode.COLOR_TEMP,
LightColorMode.RGBWW,
],
color_mode: LightColorMode.RGBWW,
effect_list: ["random", "colorloop"],
}),
getEntity("light", "color_xy_light", "on", {
friendly_name: "Color XY Light",
brightness: 255,
xy_color: [30, 100],
rgb_color: [30, 100, 255],
min_mireds: 30,
max_mireds: 150,
supported_features:
LightEntityFeature.EFFECT +
LightEntityFeature.FLASH +
LightEntityFeature.TRANSITION,
supported_color_modes: [
LightColorMode.BRIGHTNESS,
LightColorMode.COLOR_TEMP,
LightColorMode.XY,
],
color_mode: LightColorMode.XY,
effect_list: ["random", "colorloop"],
}),
{
entity_id: "light.bed_light",
state: "on",
attributes: {
friendly_name: "Basic Light",
},
},
{
entity_id: "light.kitchen_light",
state: "on",
attributes: {
friendly_name: "Brightness Light",
brightness: 200,
supported_color_modes: [LightColorMode.BRIGHTNESS],
color_mode: LightColorMode.BRIGHTNESS,
},
},
{
entity_id: "light.color_temperature_light",
state: "on",
attributes: {
friendly_name: "White Color Temperature Light",
brightness: 128,
color_temp: 75,
min_mireds: 30,
max_mireds: 150,
supported_color_modes: [
LightColorMode.BRIGHTNESS,
LightColorMode.COLOR_TEMP,
],
color_mode: LightColorMode.COLOR_TEMP,
},
},
{
entity_id: "light.color_hs_light",
state: "on",
attributes: {
friendly_name: "Color HS Light",
brightness: 255,
hs_color: [30, 100],
rgb_color: [30, 100, 255],
min_mireds: 30,
max_mireds: 150,
supported_features:
LightEntityFeature.EFFECT +
LightEntityFeature.FLASH +
LightEntityFeature.TRANSITION,
supported_color_modes: [
LightColorMode.BRIGHTNESS,
LightColorMode.COLOR_TEMP,
LightColorMode.HS,
],
color_mode: LightColorMode.HS,
effect_list: ["random", "colorloop"],
},
},
{
entity_id: "light.color_rgb_ct_light",
state: "on",
attributes: {
friendly_name: "Color RGB + CT Light",
brightness: 255,
color_temp: 75,
min_mireds: 30,
max_mireds: 150,
supported_features:
LightEntityFeature.EFFECT +
LightEntityFeature.FLASH +
LightEntityFeature.TRANSITION,
supported_color_modes: [
LightColorMode.BRIGHTNESS,
LightColorMode.COLOR_TEMP,
LightColorMode.RGB,
],
color_mode: LightColorMode.COLOR_TEMP,
effect_list: ["random", "colorloop"],
},
},
{
entity_id: "light.color_RGB_light",
state: "on",
attributes: {
friendly_name: "Color Effects Light",
brightness: 255,
rgb_color: [30, 100, 255],
supported_features:
LightEntityFeature.EFFECT +
LightEntityFeature.FLASH +
LightEntityFeature.TRANSITION,
supported_color_modes: [LightColorMode.BRIGHTNESS, LightColorMode.RGB],
color_mode: LightColorMode.RGB,
effect_list: ["random", "colorloop"],
},
},
{
entity_id: "light.color_rgbw_light",
state: "on",
attributes: {
friendly_name: "Color RGBW Light",
brightness: 255,
rgbw_color: [30, 100, 255, 125],
min_mireds: 30,
max_mireds: 150,
supported_features:
LightEntityFeature.EFFECT +
LightEntityFeature.FLASH +
LightEntityFeature.TRANSITION,
supported_color_modes: [
LightColorMode.BRIGHTNESS,
LightColorMode.COLOR_TEMP,
LightColorMode.RGBW,
],
color_mode: LightColorMode.RGBW,
effect_list: ["random", "colorloop"],
},
},
{
entity_id: "light.color_rgbww_light",
state: "on",
attributes: {
friendly_name: "Color RGBWW Light",
brightness: 255,
rgbww_color: [30, 100, 255, 125, 10],
min_mireds: 30,
max_mireds: 150,
supported_features:
LightEntityFeature.EFFECT +
LightEntityFeature.FLASH +
LightEntityFeature.TRANSITION,
supported_color_modes: [
LightColorMode.BRIGHTNESS,
LightColorMode.COLOR_TEMP,
LightColorMode.RGBWW,
],
color_mode: LightColorMode.RGBWW,
effect_list: ["random", "colorloop"],
},
},
{
entity_id: "light.color_xy_light",
state: "on",
attributes: {
friendly_name: "Color XY Light",
brightness: 255,
xy_color: [30, 100],
rgb_color: [30, 100, 255],
min_mireds: 30,
max_mireds: 150,
supported_features:
LightEntityFeature.EFFECT +
LightEntityFeature.FLASH +
LightEntityFeature.TRANSITION,
supported_color_modes: [
LightColorMode.BRIGHTNESS,
LightColorMode.COLOR_TEMP,
LightColorMode.XY,
],
color_mode: LightColorMode.XY,
effect_list: ["random", "colorloop"],
},
},
];
@customElement("demo-more-info-light")
@@ -147,7 +182,7 @@ class DemoMoreInfoLight extends LitElement {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entityId)}
.entities=${ENTITIES.map((ent) => ent.entity_id)}
></demo-more-infos>
`;
}

View File

@@ -3,19 +3,26 @@ import { html, LitElement } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import type { MockHomeAssistant } from "../../../../src/fake_data/provide_hass";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
const ENTITIES = [
getEntity("lock", "lock", "locked", {
friendly_name: "Lock",
device_class: "lock",
}),
getEntity("lock", "unavailable", "unavailable", {
friendly_name: "Unavailable lock",
}),
{
entity_id: "lock.lock",
state: "locked",
attributes: {
friendly_name: "Lock",
device_class: "lock",
},
},
{
entity_id: "lock.unavailable",
state: "unavailable",
attributes: {
friendly_name: "Unavailable lock",
},
},
];
@customElement("demo-more-info-lock")
@@ -28,7 +35,7 @@ class DemoMoreInfoLock extends LitElement {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entityId)}
.entities=${ENTITIES.map((ent) => ent.entity_id)}
></demo-more-infos>
`;
}

View File

@@ -20,7 +20,7 @@ class DemoMoreInfoMediaPlayer extends LitElement {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entityId)}
.entities=${ENTITIES.map((ent) => ent.entity_id)}
></demo-more-infos>
`;
}

View File

@@ -3,48 +3,63 @@ import { html, LitElement } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import type { MockHomeAssistant } from "../../../../src/fake_data/provide_hass";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
const ENTITIES = [
getEntity("number", "box1", 0, {
friendly_name: "Box1",
min: 0,
max: 100,
step: 1,
initial: 0,
mode: "box",
unit_of_measurement: "items",
}),
getEntity("number", "slider1", 0, {
friendly_name: "Slider1",
min: 0,
max: 100,
step: 1,
initial: 0,
mode: "slider",
unit_of_measurement: "items",
}),
getEntity("number", "auto1", 0, {
friendly_name: "Auto1",
min: 0,
max: 1000,
step: 1,
initial: 0,
mode: "auto",
unit_of_measurement: "items",
}),
getEntity("number", "auto2", 0, {
friendly_name: "Auto2",
min: 0,
max: 100,
step: 1,
initial: 0,
mode: "auto",
unit_of_measurement: "items",
}),
{
entity_id: "number.box1",
state: "0",
attributes: {
friendly_name: "Box1",
min: 0,
max: 100,
step: 1,
initial: 0,
mode: "box",
unit_of_measurement: "items",
},
},
{
entity_id: "number.slider1",
state: "0",
attributes: {
friendly_name: "Slider1",
min: 0,
max: 100,
step: 1,
initial: 0,
mode: "slider",
unit_of_measurement: "items",
},
},
{
entity_id: "number.auto1",
state: "0",
attributes: {
friendly_name: "Auto1",
min: 0,
max: 1000,
step: 1,
initial: 0,
mode: "auto",
unit_of_measurement: "items",
},
},
{
entity_id: "number.auto2",
state: "0",
attributes: {
friendly_name: "Auto2",
min: 0,
max: 100,
step: 1,
initial: 0,
mode: "auto",
unit_of_measurement: "items",
},
},
];
@customElement("demo-more-info-number")
@@ -57,7 +72,7 @@ class DemoMoreInfoNumber extends LitElement {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entityId)}
.entities=${ENTITIES.map((ent) => ent.entity_id)}
></demo-more-infos>
`;
}

View File

@@ -3,19 +3,26 @@ import { html, LitElement } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import type { MockHomeAssistant } from "../../../../src/fake_data/provide_hass";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
const ENTITIES = [
getEntity("scene", "romantic_lights", "scening", {
entity_id: ["light.bed_light", "light.ceiling_lights"],
friendly_name: "Romantic Scene",
}),
getEntity("scene", "unavailable", "unavailable", {
friendly_name: "Romantic Scene",
}),
{
entity_id: "scene.romantic_lights",
state: "scening",
attributes: {
entity_id: ["light.bed_light", "light.ceiling_lights"],
friendly_name: "Romantic Scene",
},
},
{
entity_id: "scene.unavailable",
state: "unavailable",
attributes: {
friendly_name: "Romantic Scene",
},
},
];
@customElement("demo-more-info-scene")
@@ -28,7 +35,7 @@ class DemoMoreInfoScene extends LitElement {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entityId)}
.entities=${ENTITIES.map((ent) => ent.entity_id)}
></demo-more-infos>
`;
}

View File

@@ -3,16 +3,19 @@ import { html, LitElement } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import type { MockHomeAssistant } from "../../../../src/fake_data/provide_hass";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
const ENTITIES = [
getEntity("timer", "timer", "idle", {
friendly_name: "Timer",
duration: "0:05:00",
}),
{
entity_id: "timer.timer",
state: "idle",
attributes: {
friendly_name: "Timer",
duration: "0:05:00",
},
},
];
@customElement("demo-more-info-timer")
@@ -25,7 +28,7 @@ class DemoMoreInfoTimer extends LitElement {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entityId)}
.entities=${ENTITIES.map((ent) => ent.entity_id)}
></demo-more-infos>
`;
}

View File

@@ -3,7 +3,6 @@ import { html, LitElement } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import type { MockHomeAssistant } from "../../../../src/fake_data/provide_hass";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
@@ -23,124 +22,208 @@ const base_attributes = {
};
const ENTITIES = [
getEntity("update", "update1", "on", {
...base_attributes,
friendly_name: "Update",
}),
getEntity("update", "update2", "on", {
...base_attributes,
title: null,
friendly_name: "Update without title",
}),
getEntity("update", "update3", "on", {
...base_attributes,
release_url: null,
friendly_name: "Update without release_url",
}),
getEntity("update", "update4", "on", {
...base_attributes,
release_summary: null,
friendly_name: "Update without release_summary",
}),
getEntity("update", "update5", "off", {
...base_attributes,
installed_version: "1.2.3",
friendly_name: "No update",
}),
getEntity("update", "update6", "off", {
...base_attributes,
skipped_version: "1.2.3",
friendly_name: "Skipped version",
}),
getEntity("update", "update7", "on", {
...base_attributes,
supported_features:
base_attributes.supported_features + UpdateEntityFeature.BACKUP,
friendly_name: "With backup support",
}),
getEntity("update", "update8", "on", {
...base_attributes,
in_progress: true,
friendly_name: "With true in_progress",
}),
getEntity("update", "update9", "on", {
...base_attributes,
in_progress: 25,
supported_features:
base_attributes.supported_features + UpdateEntityFeature.PROGRESS,
friendly_name: "With 25 in_progress",
}),
getEntity("update", "update10", "on", {
...base_attributes,
in_progress: 50,
supported_features:
base_attributes.supported_features + UpdateEntityFeature.PROGRESS,
friendly_name: "With 50 in_progress",
}),
getEntity("update", "update11", "on", {
...base_attributes,
in_progress: 75,
supported_features:
base_attributes.supported_features + UpdateEntityFeature.PROGRESS,
friendly_name: "With 75 in_progress",
}),
getEntity("update", "update12", "unavailable", {
...base_attributes,
in_progress: 50,
friendly_name: "Unavailable",
}),
getEntity("update", "update13", "on", {
...base_attributes,
supported_features: 0,
friendly_name: "No install support",
}),
getEntity("update", "update14", "off", {
...base_attributes,
installed_version: null,
friendly_name: "Update without installed_version",
}),
getEntity("update", "update15", "off", {
...base_attributes,
latest_version: null,
friendly_name: "Update without latest_version",
}),
getEntity("update", "update16", "off", {
...base_attributes,
friendly_name: "Update with release notes",
supported_features:
base_attributes.supported_features + UpdateEntityFeature.RELEASE_NOTES,
}),
getEntity("update", "update17", "off", {
...base_attributes,
friendly_name: "Update with release notes error",
supported_features:
base_attributes.supported_features + UpdateEntityFeature.RELEASE_NOTES,
}),
getEntity("update", "update18", "off", {
...base_attributes,
friendly_name: "Update with release notes loading",
supported_features:
base_attributes.supported_features + UpdateEntityFeature.RELEASE_NOTES,
}),
getEntity("update", "update19", "on", {
...base_attributes,
friendly_name: "Update with auto update",
auto_update: true,
}),
getEntity("update", "update20", "on", {
...base_attributes,
in_progress: true,
title: undefined,
friendly_name: "Installing without title",
}),
getEntity("update", "update21", "on", {
...base_attributes,
in_progress: true,
friendly_name:
"Update with in_progress true and UpdateEntityFeature.PROGRESS",
supported_features:
base_attributes.supported_features + UpdateEntityFeature.PROGRESS,
}),
{
entity_id: "update.update1",
state: "on",
attributes: {
...base_attributes,
friendly_name: "Update",
},
},
{
entity_id: "update.update2",
state: "on",
attributes: {
...base_attributes,
title: null,
friendly_name: "Update without title",
},
},
{
entity_id: "update.update3",
state: "on",
attributes: {
...base_attributes,
release_url: null,
friendly_name: "Update without release_url",
},
},
{
entity_id: "update.update4",
state: "on",
attributes: {
...base_attributes,
release_summary: null,
friendly_name: "Update without release_summary",
},
},
{
entity_id: "update.update5",
state: "off",
attributes: {
...base_attributes,
installed_version: "1.2.3",
friendly_name: "No update",
},
},
{
entity_id: "update.update6",
state: "off",
attributes: {
...base_attributes,
skipped_version: "1.2.3",
friendly_name: "Skipped version",
},
},
{
entity_id: "update.update7",
state: "on",
attributes: {
...base_attributes,
supported_features:
base_attributes.supported_features + UpdateEntityFeature.BACKUP,
friendly_name: "With backup support",
},
},
{
entity_id: "update.update8",
state: "on",
attributes: {
...base_attributes,
in_progress: true,
friendly_name: "With true in_progress",
},
},
{
entity_id: "update.update9",
state: "on",
attributes: {
...base_attributes,
in_progress: 25,
supported_features:
base_attributes.supported_features + UpdateEntityFeature.PROGRESS,
friendly_name: "With 25 in_progress",
},
},
{
entity_id: "update.update10",
state: "on",
attributes: {
...base_attributes,
in_progress: 50,
supported_features:
base_attributes.supported_features + UpdateEntityFeature.PROGRESS,
friendly_name: "With 50 in_progress",
},
},
{
entity_id: "update.update11",
state: "on",
attributes: {
...base_attributes,
in_progress: 75,
supported_features:
base_attributes.supported_features + UpdateEntityFeature.PROGRESS,
friendly_name: "With 75 in_progress",
},
},
{
entity_id: "update.update12",
state: "unavailable",
attributes: {
...base_attributes,
in_progress: 50,
friendly_name: "Unavailable",
},
},
{
entity_id: "update.update13",
state: "on",
attributes: {
...base_attributes,
supported_features: 0,
friendly_name: "No install support",
},
},
{
entity_id: "update.update14",
state: "off",
attributes: {
...base_attributes,
installed_version: null,
friendly_name: "Update without installed_version",
},
},
{
entity_id: "update.update15",
state: "off",
attributes: {
...base_attributes,
latest_version: null,
friendly_name: "Update without latest_version",
},
},
{
entity_id: "update.update16",
state: "off",
attributes: {
...base_attributes,
friendly_name: "Update with release notes",
supported_features:
base_attributes.supported_features + UpdateEntityFeature.RELEASE_NOTES,
},
},
{
entity_id: "update.update17",
state: "off",
attributes: {
...base_attributes,
friendly_name: "Update with release notes error",
supported_features:
base_attributes.supported_features + UpdateEntityFeature.RELEASE_NOTES,
},
},
{
entity_id: "update.update18",
state: "off",
attributes: {
...base_attributes,
friendly_name: "Update with release notes loading",
supported_features:
base_attributes.supported_features + UpdateEntityFeature.RELEASE_NOTES,
},
},
{
entity_id: "update.update19",
state: "on",
attributes: {
...base_attributes,
friendly_name: "Update with auto update",
auto_update: true,
},
},
{
entity_id: "update.update20",
state: "on",
attributes: {
...base_attributes,
in_progress: true,
title: undefined,
friendly_name: "Installing without title",
},
},
{
entity_id: "update.update21",
state: "on",
attributes: {
...base_attributes,
in_progress: true,
friendly_name:
"Update with in_progress true and UpdateEntityFeature.PROGRESS",
supported_features:
base_attributes.supported_features + UpdateEntityFeature.PROGRESS,
},
},
];
@customElement("demo-more-info-update")
@@ -153,7 +236,7 @@ class DemoMoreInfoUpdate extends LitElement {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entityId)}
.entities=${ENTITIES.map((ent) => ent.entity_id)}
></demo-more-infos>
`;
}

View File

@@ -3,20 +3,23 @@ import { html, LitElement } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import type { MockHomeAssistant } from "../../../../src/fake_data/provide_hass";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
import { VacuumEntityFeature } from "../../../../src/data/vacuum";
const ENTITIES = [
getEntity("vacuum", "first_floor_vacuum", "docked", {
friendly_name: "First floor vacuum",
supported_features:
VacuumEntityFeature.START +
VacuumEntityFeature.STOP +
VacuumEntityFeature.RETURN_HOME,
}),
{
entity_id: "vacuum.first_floor_vacuum",
state: "docked",
attributes: {
friendly_name: "First floor vacuum",
supported_features:
VacuumEntityFeature.START +
VacuumEntityFeature.STOP +
VacuumEntityFeature.RETURN_HOME,
},
},
];
@customElement("demo-more-info-vacuum")
@@ -29,7 +32,7 @@ class DemoMoreInfoVacuum extends LitElement {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entityId)}
.entities=${ENTITIES.map((ent) => ent.entity_id)}
></demo-more-infos>
`;
}

View File

@@ -4,39 +4,46 @@ import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import { WaterHeaterEntityFeature } from "../../../../src/data/water_heater";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import type { MockHomeAssistant } from "../../../../src/fake_data/provide_hass";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
const ENTITIES = [
getEntity("water_heater", "basic", "eco", {
friendly_name: "Basic heater",
operation_list: ["heat_pump", "eco", "performance", "off"],
operation_mode: "eco",
away_mode: "off",
target_temp_step: 1,
current_temperature: 55,
temperature: 60,
min_temp: 20,
max_temp: 70,
supported_features:
// eslint-disable-next-line no-bitwise
WaterHeaterEntityFeature.TARGET_TEMPERATURE |
WaterHeaterEntityFeature.OPERATION_MODE |
WaterHeaterEntityFeature.AWAY_MODE,
}),
getEntity("water_heater", "unavailable", "unavailable", {
friendly_name: "Unavailable heater",
operation_list: ["heat_pump", "eco", "performance", "off"],
operation_mode: "off",
min_temp: 20,
max_temp: 70,
supported_features:
// eslint-disable-next-line no-bitwise
WaterHeaterEntityFeature.TARGET_TEMPERATURE |
WaterHeaterEntityFeature.OPERATION_MODE,
}),
{
entity_id: "water_heater.basic",
state: "eco",
attributes: {
friendly_name: "Basic heater",
operation_list: ["heat_pump", "eco", "performance", "off"],
operation_mode: "eco",
away_mode: "off",
target_temp_step: 1,
current_temperature: 55,
temperature: 60,
min_temp: 20,
max_temp: 70,
supported_features:
// eslint-disable-next-line no-bitwise
WaterHeaterEntityFeature.TARGET_TEMPERATURE |
WaterHeaterEntityFeature.OPERATION_MODE |
WaterHeaterEntityFeature.AWAY_MODE,
},
},
{
entity_id: "water_heater.unavailable",
state: "unavailable",
attributes: {
friendly_name: "Unavailable heater",
operation_list: ["heat_pump", "eco", "performance", "off"],
operation_mode: "off",
min_temp: 20,
max_temp: 70,
supported_features:
// eslint-disable-next-line no-bitwise
WaterHeaterEntityFeature.TARGET_TEMPERATURE |
WaterHeaterEntityFeature.OPERATION_MODE,
},
},
];
@customElement("demo-more-info-water-heater")
@@ -49,7 +56,7 @@ class DemoMoreInfoWaterHeater extends LitElement {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entityId)}
.entities=${ENTITIES.map((ent) => ent.entity_id)}
></demo-more-infos>
`;
}

View File

@@ -118,7 +118,7 @@ class HaLandingPage extends LandingPageBaseElement {
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
makeDialogManager(this, this.shadowRoot!);
makeDialogManager(this);
if (window.innerWidth > 450) {
import("../../src/resources/particles");
@@ -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

@@ -26,33 +26,33 @@
"license": "Apache-2.0",
"type": "module",
"dependencies": {
"@babel/runtime": "7.28.6",
"@babel/runtime": "7.29.2",
"@braintree/sanitize-url": "7.1.2",
"@codemirror/autocomplete": "6.20.0",
"@codemirror/commands": "6.10.2",
"@codemirror/language": "6.12.1",
"@codemirror/autocomplete": "6.20.1",
"@codemirror/commands": "6.10.3",
"@codemirror/language": "6.12.2",
"@codemirror/legacy-modes": "6.5.2",
"@codemirror/search": "6.6.0",
"@codemirror/state": "6.5.4",
"@codemirror/view": "6.39.12",
"@codemirror/state": "6.6.0",
"@codemirror/view": "6.40.0",
"@date-fns/tz": "1.4.1",
"@egjs/hammerjs": "2.0.17",
"@formatjs/intl-datetimeformat": "7.2.1",
"@formatjs/intl-displaynames": "7.2.1",
"@formatjs/intl-durationformat": "0.10.1",
"@formatjs/intl-getcanonicallocales": "3.2.1",
"@formatjs/intl-listformat": "8.2.1",
"@formatjs/intl-locale": "5.2.1",
"@formatjs/intl-numberformat": "9.2.2",
"@formatjs/intl-pluralrules": "6.2.2",
"@formatjs/intl-relativetimeformat": "12.2.2",
"@formatjs/intl-datetimeformat": "7.3.1",
"@formatjs/intl-displaynames": "7.3.1",
"@formatjs/intl-durationformat": "0.10.3",
"@formatjs/intl-getcanonicallocales": "3.2.2",
"@formatjs/intl-listformat": "8.3.1",
"@formatjs/intl-locale": "5.3.1",
"@formatjs/intl-numberformat": "9.3.1",
"@formatjs/intl-pluralrules": "6.3.1",
"@formatjs/intl-relativetimeformat": "12.3.1",
"@fullcalendar/core": "6.1.20",
"@fullcalendar/daygrid": "6.1.20",
"@fullcalendar/interaction": "6.1.20",
"@fullcalendar/list": "6.1.20",
"@fullcalendar/luxon3": "6.1.20",
"@fullcalendar/timegrid": "6.1.20",
"@home-assistant/webawesome": "3.2.1-ha.0",
"@home-assistant/webawesome": "3.3.1-ha.0",
"@lezer/highlight": "1.2.3",
"@lit-labs/motion": "1.1.0",
"@lit-labs/observers": "2.1.0",
@@ -68,12 +68,10 @@
"@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",
"@material/mwc-select": "0.27.0",
"@material/mwc-snackbar": "0.27.0",
"@material/mwc-switch": "0.27.0",
"@material/mwc-textarea": "0.27.0",
"@material/mwc-textfield": "0.27.0",
@@ -84,19 +82,18 @@
"@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",
"@vibrant/color": "4.0.4",
"@vue/web-component-wrapper": "1.3.0",
"@webcomponents/scoped-custom-element-registry": "0.0.10",
"@webcomponents/webcomponentsjs": "2.8.0",
"app-datepicker": "5.1.1",
"barcode-detector": "3.0.8",
"barcode-detector": "3.1.1",
"cally": "0.9.2",
"color-name": "2.1.0",
"comlink": "4.4.2",
"core-js": "3.48.0",
"core-js": "3.49.0",
"cropperjs": "1.6.2",
"culori": "4.0.2",
"date-fns": "4.1.0",
@@ -107,11 +104,11 @@
"element-internals-polyfill": "3.0.2",
"fuse.js": "7.1.0",
"google-timezones-json": "1.2.0",
"gulp-zopfli-green": "6.0.2",
"gulp-zopfli-green": "7.0.0",
"hls.js": "1.6.15",
"home-assistant-js-websocket": "9.6.0",
"idb-keyval": "6.2.2",
"intl-messageformat": "11.1.2",
"intl-messageformat": "11.2.0",
"js-yaml": "4.1.1",
"leaflet": "1.9.4",
"leaflet-draw": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch",
@@ -119,7 +116,7 @@
"lit": "3.3.2",
"lit-html": "3.3.2",
"luxon": "3.7.2",
"marked": "17.0.1",
"marked": "17.0.5",
"memoize-one": "6.0.0",
"node-vibrant": "4.0.4",
"object-hash": "3.0.0",
@@ -133,8 +130,6 @@
"superstruct": "2.0.2",
"tinykeys": "3.0.0",
"ua-parser-js": "2.0.9",
"vue": "2.7.16",
"vue2-daterange-picker": "0.6.8",
"weekstart": "2.0.0",
"workbox-cacheable-response": "7.4.0",
"workbox-core": "7.4.0",
@@ -146,17 +141,17 @@
},
"devDependencies": {
"@babel/core": "7.29.0",
"@babel/helper-define-polyfill-provider": "0.6.6",
"@babel/helper-define-polyfill-provider": "0.6.8",
"@babel/plugin-transform-runtime": "7.29.0",
"@babel/preset-env": "7.29.0",
"@bundle-stats/plugin-webpack-filter": "4.21.9",
"@html-eslint/eslint-plugin": "0.55.0",
"@babel/preset-env": "7.29.2",
"@bundle-stats/plugin-webpack-filter": "4.22.0",
"@html-eslint/eslint-plugin": "0.58.1",
"@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.2",
"@rspack/core": "1.7.5",
"@rsdoctor/rspack-plugin": "1.5.5",
"@rspack/core": "1.7.9",
"@rspack/dev-server": "1.2.1",
"@types/babel__plugin-transform-runtime": "7.9.5",
"@types/chromecast-caf-receiver": "6.0.25",
@@ -173,35 +168,35 @@
"@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",
"babel-loader": "10.0.0",
"@vitest/coverage-v8": "4.1.0",
"babel-loader": "10.1.1",
"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.4",
"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.1",
"fs-extra": "11.3.4",
"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": "29.0.1",
"jszip": "3.10.1",
"lint-staged": "16.2.7",
"lint-staged": "16.4.0",
"lit-analyzer": "2.0.3",
"lodash.merge": "4.6.2",
"lodash.template": "4.5.0",
@@ -209,33 +204,32 @@
"pinst": "3.0.0",
"prettier": "3.8.1",
"rspack-manifest-plugin": "5.2.1",
"serve": "14.2.5",
"sinon": "21.0.1",
"tar": "7.5.7",
"terser-webpack-plugin": "5.3.16",
"serve": "14.2.6",
"sinon": "21.0.3",
"tar": "7.5.12",
"terser-webpack-plugin": "5.4.0",
"ts-lit-plugin": "2.0.2",
"typescript": "5.9.3",
"typescript-eslint": "8.54.0",
"vite-tsconfig-paths": "6.0.5",
"vitest": "4.0.18",
"typescript-eslint": "8.57.1",
"vite-tsconfig-paths": "6.1.1",
"vitest": "4.1.0",
"webpack-stats-plugin": "1.1.3",
"webpackbar": "7.0.0",
"workbox-build": "patch:workbox-build@npm%3A7.1.1#~/.yarn/patches/workbox-build-npm-7.1.1-a854f3faae.patch"
"workbox-build": "patch:workbox-build@npm%3A7.4.0#~/.yarn/patches/workbox-build-npm-7.4.0-c84561662c.patch"
},
"resolutions": {
"@material/mwc-button@^0.25.3": "^0.27.0",
"lit": "3.3.2",
"lit-html": "3.3.2",
"clean-css": "5.3.3",
"@lit/reactive-element": "2.1.2",
"@fullcalendar/daygrid": "6.1.20",
"globals": "17.3.0",
"globals": "17.4.0",
"tslib": "2.8.1",
"@material/mwc-list@^0.27.0": "patch:@material/mwc-list@npm%3A0.27.0#~/.yarn/patches/@material-mwc-list-npm-0.27.0-5344fc9de4.patch",
"glob@^10.2.2": "^10.5.0"
},
"packageManager": "yarn@4.12.0",
"packageManager": "yarn@4.13.0",
"volta": {
"node": "24.13.1"
"node": "24.14.0"
}
}

View File

@@ -24,11 +24,6 @@
"extends": ["monorepo:material-components-web"],
"enabled": false
},
{
"description": "Vue is only used by date range which is only v2",
"matchPackageNames": ["vue"],
"allowedVersions": "< 3"
},
{
"description": "Group MDI packages",
"groupName": "Material Design Icons",

View File

@@ -2,7 +2,7 @@
import { genClientId } from "home-assistant-js-websocket";
import type { PropertyValues } from "lit";
import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { customElement, property, query, state } from "lit/decorators";
import { keyed } from "lit/directives/keyed";
import type { LocalizeFunc } from "../common/translations/localize";
import "../components/ha-alert";
@@ -23,6 +23,7 @@ import type {
DataEntryFlowStepForm,
} from "../data/data_entry_flow";
import "./ha-auth-form";
import type { HaAuthForm } from "./ha-auth-form";
type State = "loading" | "error" | "step";
@@ -52,6 +53,8 @@ export class HaAuthFlow extends LitElement {
@state() private _submitting = false;
@query("ha-auth-form") private _form?: HaAuthForm;
createRenderRoot() {
return this;
}
@@ -179,7 +182,7 @@ export class HaAuthFlow extends LitElement {
<div class="action">
<ha-button
@click=${this._handleSubmit}
.disabled=${this._submitting}
.loading=${this._submitting}
>
${this.step.type === "form"
? this.localize("ui.panel.page-authorize.form.next")
@@ -370,6 +373,11 @@ export class HaAuthFlow extends LitElement {
this._providerChanged(this.authProvider);
return;
}
if (!this._form?.reportValidity()) {
return;
}
this._submitting = true;
const postData = { ...this._stepData, client_id: this.clientId };

View File

@@ -1,10 +1,7 @@
/* eslint-disable lit/prefer-static-styles */
import type { TemplateResult } from "lit";
import { html } from "lit";
import { customElement } from "lit/decorators";
import { HaFormString } from "../components/ha-form/ha-form-string";
import "../components/ha-icon-button";
import "./ha-auth-textfield";
import "../components/input/ha-input";
@customElement("ha-auth-form-string")
export class HaAuthFormString extends HaFormString {
@@ -12,59 +9,9 @@ export class HaAuthFormString extends HaFormString {
return this;
}
protected render(): TemplateResult {
return html`
<style>
ha-auth-form-string {
display: block;
position: relative;
}
ha-auth-form-string[own-margin] {
margin-bottom: 5px;
}
ha-auth-form-string ha-auth-textfield {
display: block !important;
}
ha-auth-form-string ha-icon-button {
position: absolute;
top: 8px;
right: 8px;
inset-inline-start: initial;
inset-inline-end: 8px;
--mdc-icon-button-size: 40px;
--mdc-icon-size: 20px;
color: var(--secondary-text-color);
direction: var(--direction);
}
</style>
<ha-auth-textfield
.type=${!this.isPassword
? this.stringType
: this.unmaskedPassword
? "text"
: "password"}
.label=${this.label}
.value=${this.data || ""}
.helper=${this.helper}
helperPersistent
.disabled=${this.disabled}
.required=${this.schema.required}
.autoValidate=${this.schema.required}
.name=${this.schema.name}
.autocomplete=${this.schema.autocomplete}
?autofocus=${this.schema.autofocus}
.suffix=${this.isPassword
? // reserve some space for the icon.
html`<div style="width: 24px"></div>`
: this.schema.description?.suffix}
.validationMessage=${this.schema.required
? this.localize?.("ui.panel.page-authorize.form.error_required")
: undefined}
@input=${this._valueChanged}
@change=${this._valueChanged}
></ha-auth-textfield>
${this.renderIcon()}
`;
public connectedCallback(): void {
super.connectedCallback();
this.style.position = "relative";
}
}

View File

@@ -1,9 +1,9 @@
/* eslint-disable lit/prefer-static-styles */
import { html } from "lit";
import { customElement, property } from "lit/decorators";
import type { LocalizeFunc } from "../common/translations/localize";
import { HaForm } from "../components/ha-form/ha-form";
import "./ha-auth-form-string";
import type { LocalizeFunc } from "../common/translations/localize";
const localizeBaseKey = "ui.panel.page-authorize.form";
@@ -34,6 +34,9 @@ export class HaAuthForm extends HaForm {
protected render() {
return html`
<style>
ha-auth-form {
--ha-input-required-marker: "";
}
ha-auth-form .root > * {
display: block;
}

View File

@@ -1,264 +0,0 @@
/* eslint-disable lit/value-after-constraints */
/* eslint-disable lit/prefer-static-styles */
import { floatingLabel } from "@material/mwc-floating-label/mwc-floating-label-directive";
import type { TemplateResult } from "lit";
import { html } from "lit";
import { customElement } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import { live } from "lit/directives/live";
import { HaTextField } from "../components/ha-textfield";
@customElement("ha-auth-textfield")
export class HaAuthTextField extends HaTextField {
protected renderLabel(): TemplateResult | string {
return !this.label
? ""
: html`
<span
.floatingLabelFoundation=${floatingLabel(
this.label
) as unknown as any}
.id=${this.name}
>${this.label}</span
>
`;
}
protected renderInput(shouldRenderHelperText: boolean): TemplateResult {
const minOrUndef = this.minLength === -1 ? undefined : this.minLength;
const maxOrUndef = this.maxLength === -1 ? undefined : this.maxLength;
const autocapitalizeOrUndef = this.autocapitalize
? (this.autocapitalize as
| "off"
| "none"
| "on"
| "sentences"
| "words"
| "characters")
: undefined;
const showValidationMessage = this.validationMessage && !this.isUiValid;
const ariaLabelledbyOrUndef = this.label ? this.name : undefined;
const ariaControlsOrUndef = shouldRenderHelperText
? "helper-text"
: undefined;
const ariaDescribedbyOrUndef =
this.focused || this.helperPersistent || showValidationMessage
? "helper-text"
: undefined;
// TODO: live() directive needs casting for lit-analyzer
// https://github.com/runem/lit-analyzer/pull/91/files
// TODO: lit-analyzer labels min/max as (number|string) instead of string
return html`<input
aria-labelledby=${ifDefined(ariaLabelledbyOrUndef)}
aria-controls=${ifDefined(ariaControlsOrUndef)}
aria-describedby=${ifDefined(ariaDescribedbyOrUndef)}
class="mdc-text-field__input"
type=${this.type}
.value=${live(this.value) as unknown as string}
?disabled=${this.disabled}
placeholder=${this.placeholder}
?required=${this.required}
?readonly=${this.readOnly}
minlength=${ifDefined(minOrUndef)}
maxlength=${ifDefined(maxOrUndef)}
pattern=${ifDefined(this.pattern ? this.pattern : undefined)}
min=${ifDefined(this.min === "" ? undefined : (this.min as number))}
max=${ifDefined(this.max === "" ? undefined : (this.max as number))}
step=${ifDefined(this.step === null ? undefined : (this.step as number))}
size=${ifDefined(this.size === null ? undefined : this.size)}
name=${ifDefined(this.name === "" ? undefined : this.name)}
inputmode=${ifDefined(this.inputMode)}
autocapitalize=${ifDefined(autocapitalizeOrUndef)}
?autofocus=${this.autofocus}
@input=${this.handleInputChange}
@focus=${this.onInputFocus}
@blur=${this.onInputBlur}
/>`;
}
public render() {
return html`
<style>
ha-auth-textfield {
display: inline-flex;
flex-direction: column;
outline: none;
}
ha-auth-textfield:not([disabled]):hover
:not(.mdc-text-field--invalid):not(.mdc-text-field--focused)
mwc-notched-outline {
--mdc-notched-outline-border-color: var(
--mdc-text-field-outlined-hover-border-color,
rgba(0, 0, 0, 0.87)
);
}
ha-auth-textfield:not([disabled])
.mdc-text-field:not(.mdc-text-field--outlined) {
background-color: var(--mdc-text-field-fill-color, whitesmoke);
}
ha-auth-textfield:not([disabled])
.mdc-text-field.mdc-text-field--invalid
mwc-notched-outline {
--mdc-notched-outline-border-color: var(
--mdc-text-field-error-color,
var(--mdc-theme-error, #b00020)
);
}
ha-auth-textfield:not([disabled])
.mdc-text-field.mdc-text-field--invalid
+ .mdc-text-field-helper-line
.mdc-text-field-character-counter,
ha-auth-textfield:not([disabled])
.mdc-text-field.mdc-text-field--invalid
.mdc-text-field__icon {
color: var(
--mdc-text-field-error-color,
var(--mdc-theme-error, #b00020)
);
}
ha-auth-textfield:not([disabled])
.mdc-text-field:not(.mdc-text-field--invalid):not(
.mdc-text-field--focused
)
.mdc-floating-label,
ha-auth-textfield:not([disabled])
.mdc-text-field:not(.mdc-text-field--invalid):not(
.mdc-text-field--focused
)
.mdc-floating-label::after {
color: var(--mdc-text-field-label-ink-color, rgba(0, 0, 0, 0.6));
}
ha-auth-textfield:not([disabled])
.mdc-text-field.mdc-text-field--focused
mwc-notched-outline {
--mdc-notched-outline-stroke-width: 2px;
}
ha-auth-textfield:not([disabled])
.mdc-text-field.mdc-text-field--focused:not(.mdc-text-field--invalid)
mwc-notched-outline {
--mdc-notched-outline-border-color: var(
--mdc-text-field-focused-label-color,
var(--mdc-theme-primary, rgba(98, 0, 238, 0.87))
);
}
ha-auth-textfield:not([disabled])
.mdc-text-field.mdc-text-field--focused:not(.mdc-text-field--invalid)
.mdc-floating-label {
color: #6200ee;
color: var(--mdc-theme-primary, #6200ee);
}
ha-auth-textfield:not([disabled])
.mdc-text-field
.mdc-text-field__input {
color: var(--mdc-text-field-ink-color, rgba(0, 0, 0, 0.87));
}
ha-auth-textfield:not([disabled])
.mdc-text-field
.mdc-text-field__input::placeholder {
color: var(--mdc-text-field-label-ink-color, rgba(0, 0, 0, 0.6));
}
ha-auth-textfield:not([disabled])
.mdc-text-field-helper-line
.mdc-text-field-helper-text:not(
.mdc-text-field-helper-text--validation-msg
),
ha-auth-textfield:not([disabled])
.mdc-text-field-helper-line:not(.mdc-text-field--invalid)
.mdc-text-field-character-counter {
color: var(--mdc-text-field-label-ink-color, rgba(0, 0, 0, 0.6));
}
ha-auth-textfield[disabled]
.mdc-text-field:not(.mdc-text-field--outlined) {
background-color: var(--mdc-text-field-disabled-fill-color, #fafafa);
}
ha-auth-textfield[disabled]
.mdc-text-field.mdc-text-field--outlined
mwc-notched-outline {
--mdc-notched-outline-border-color: var(
--mdc-text-field-outlined-disabled-border-color,
rgba(0, 0, 0, 0.06)
);
}
ha-auth-textfield[disabled]
.mdc-text-field:not(.mdc-text-field--invalid):not(
.mdc-text-field--focused
)
.mdc-floating-label,
ha-auth-textfield[disabled]
.mdc-text-field:not(.mdc-text-field--invalid):not(
.mdc-text-field--focused
)
.mdc-floating-label::after {
color: var(--mdc-text-field-disabled-ink-color, rgba(0, 0, 0, 0.38));
}
ha-auth-textfield[disabled] .mdc-text-field .mdc-text-field__input,
ha-auth-textfield[disabled]
.mdc-text-field
.mdc-text-field__input::placeholder {
color: var(--mdc-text-field-disabled-ink-color, rgba(0, 0, 0, 0.38));
}
ha-auth-textfield[disabled]
.mdc-text-field-helper-line
.mdc-text-field-helper-text,
ha-auth-textfield[disabled]
.mdc-text-field-helper-line
.mdc-text-field-character-counter {
color: var(--mdc-text-field-disabled-ink-color, rgba(0, 0, 0, 0.38));
}
ha-auth-textfield:not([disabled])
.mdc-text-field.mdc-text-field--focused:not(.mdc-text-field--invalid)
.mdc-floating-label {
color: var(--mdc-theme-primary, #6200ee);
}
ha-auth-textfield[no-spinner] input::-webkit-outer-spin-button,
ha-auth-textfield[no-spinner] input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* Firefox */
ha-auth-textfield[no-spinner] input[type="number"] {
-moz-appearance: textfield;
}
</style>
${super.render()}
`;
}
protected createRenderRoot() {
// add parent style to light dom
const style = document.createElement("style");
style.textContent = HaTextField.elementStyles as unknown as string;
this.append(style);
return this;
}
public firstUpdated() {
super.firstUpdated();
if (this.autofocus) {
this.focus();
}
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-auth-textfield": HaAuthTextField;
}
}

View File

@@ -32,9 +32,17 @@ const YAML_ONLY_THEMES_COLORS = new Set([
"disabled",
]);
export function computeCssColor(color: string): string {
export function computeCssVariableName(color: string): string {
if (THEME_COLORS.has(color) || YAML_ONLY_THEMES_COLORS.has(color)) {
return `var(--${color}-color)`;
return `--${color}-color`;
}
return color;
}
export function computeCssColor(color: string): string {
const cssVarName = computeCssVariableName(color);
if (cssVarName !== color) {
return `var(${cssVarName})`;
}
return color;
}

View File

@@ -1,17 +1,17 @@
import {
addDays,
subHours,
endOfDay,
endOfMonth,
endOfQuarter,
endOfWeek,
endOfYear,
startOfDay,
startOfMonth,
startOfQuarter,
startOfWeek,
startOfYear,
startOfQuarter,
endOfQuarter,
subDays,
subHours,
subMonths,
} from "date-fns";
import type { HomeAssistant } from "../../types";
@@ -33,83 +33,89 @@ export type DateRange =
| "now-24h";
export const calcDateRange = (
hass: HomeAssistant,
locale: HomeAssistant["locale"],
hassConfig: HomeAssistant["config"],
range: DateRange
): [Date, Date] => {
const today = new Date();
const weekStartsOn = firstWeekdayIndex(hass.locale);
const weekStartsOn = firstWeekdayIndex(locale);
switch (range) {
case "today":
return [
calcDate(today, startOfDay, hass.locale, hass.config, {
calcDate(today, startOfDay, locale, hassConfig, {
weekStartsOn,
}),
calcDate(today, endOfDay, hass.locale, hass.config, {
calcDate(today, endOfDay, locale, hassConfig, {
weekStartsOn,
}),
];
case "yesterday":
return [
calcDate(addDays(today, -1), startOfDay, hass.locale, hass.config, {
calcDate(addDays(today, -1), startOfDay, locale, hassConfig, {
weekStartsOn,
}),
calcDate(addDays(today, -1), endOfDay, hass.locale, hass.config, {
calcDate(addDays(today, -1), endOfDay, locale, hassConfig, {
weekStartsOn,
}),
];
case "this_week":
return [
calcDate(today, startOfWeek, hass.locale, hass.config, {
calcDate(today, startOfWeek, locale, hassConfig, {
weekStartsOn,
}),
calcDate(today, endOfWeek, hass.locale, hass.config, {
calcDate(today, endOfWeek, locale, hassConfig, {
weekStartsOn,
}),
];
case "this_month":
return [
calcDate(today, startOfMonth, hass.locale, hass.config),
calcDate(today, endOfMonth, hass.locale, hass.config),
calcDate(today, startOfMonth, locale, hassConfig),
calcDate(today, endOfMonth, locale, hassConfig),
];
case "this_quarter":
return [
calcDate(today, startOfQuarter, hass.locale, hass.config),
calcDate(today, endOfQuarter, hass.locale, hass.config),
calcDate(today, startOfQuarter, locale, hassConfig),
calcDate(today, endOfQuarter, locale, hassConfig),
];
case "this_year":
return [
calcDate(today, startOfYear, hass.locale, hass.config),
calcDate(today, endOfYear, hass.locale, hass.config),
calcDate(today, startOfYear, locale, hassConfig),
calcDate(today, endOfYear, locale, hassConfig),
];
case "now-7d":
return [
calcDate(today, subDays, hass.locale, hass.config, 7),
calcDate(today, subDays, hass.locale, hass.config, 0),
calcDate(today, subDays, locale, hassConfig, 7),
calcDate(today, subDays, locale, hassConfig, 0),
];
case "now-30d":
return [
calcDate(today, subDays, hass.locale, hass.config, 30),
calcDate(today, subDays, hass.locale, hass.config, 0),
calcDate(today, subDays, locale, hassConfig, 30),
calcDate(today, subDays, locale, hassConfig, 0),
];
case "now-12m":
return [
calcDate(today, subMonths, hass.locale, hass.config, 12),
calcDate(today, subMonths, hass.locale, hass.config, 0),
calcDate(
today,
(date) => subMonths(startOfMonth(date), 11),
locale,
hassConfig
),
calcDate(today, endOfMonth, locale, hassConfig),
];
case "now-1h":
return [
calcDate(today, subHours, hass.locale, hass.config, 1),
calcDate(today, subHours, hass.locale, hass.config, 0),
calcDate(today, subHours, locale, hassConfig, 1),
calcDate(today, subHours, locale, hassConfig, 0),
];
case "now-12h":
return [
calcDate(today, subHours, hass.locale, hass.config, 12),
calcDate(today, subHours, hass.locale, hass.config, 0),
calcDate(today, subHours, locale, hassConfig, 12),
calcDate(today, subHours, locale, hassConfig, 0),
];
case "now-24h":
return [
calcDate(today, subHours, hass.locale, hass.config, 24),
calcDate(today, subHours, hass.locale, hass.config, 0),
calcDate(today, subHours, locale, hassConfig, 24),
calcDate(today, subHours, locale, hassConfig, 0),
];
}
return [today, today];

View File

@@ -166,6 +166,21 @@ const formatDateMonthMem = memoizeOne(
})
);
// Aug
export const formatDateMonthShort = (
dateObj: Date,
locale: FrontendLocaleData,
config: HassConfig
) => formatDateMonthShortMem(locale, config.time_zone).format(dateObj);
const formatDateMonthShortMem = memoizeOne(
(locale: FrontendLocaleData, serverTimeZone: string) =>
new Intl.DateTimeFormat(locale.language, {
month: "short",
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
// 2021
export const formatDateYear = (
dateObj: Date,
@@ -210,3 +225,72 @@ 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),
})
);
/**
* Format a date as YYYY-MM-DD. Uses "en-CA" because it's the only
* Intl locale that natively outputs ISO 8601 date format.
* Locale/config are only used to resolve the time zone.
*/
export const formatISODateOnly = (
dateObj: Date,
locale: FrontendLocaleData,
config: HassConfig
) => {
const timeZone = resolveTimeZone(locale.time_zone, config.time_zone);
const formatter = new Intl.DateTimeFormat("en-CA", {
year: "numeric",
month: "2-digit",
day: "2-digit",
timeZone,
});
return formatter.format(dateObj);
};
// 2026-08-10/2026-08-15
export const formatCallyDateRange = (
start: Date,
end: Date,
locale: FrontendLocaleData,
config: HassConfig
) => {
const startDate = formatISODateOnly(start, locale, config);
const endDate = formatISODateOnly(end, locale, config);
return `${startDate}/${endDate}`;
};

View File

@@ -11,11 +11,11 @@ export const canOverrideAlphanumericInput = (composedPath: EventTarget[]) => {
const el = composedPath[0] as Element;
if (el.tagName === "TEXTAREA") {
return false;
}
if (el.parentElement?.tagName === "HA-SELECT") {
if (
el.tagName === "TEXTAREA" ||
el.parentElement?.tagName === "HA-SELECT" ||
el.parentElement?.tagName === "HA-DROPDOWN"
) {
return false;
}

View File

@@ -4,14 +4,11 @@ import type {
EntityRegistryEntry,
} from "../../data/entity/entity_registry";
import type { HomeAssistant } from "../../types";
import { computeDeviceName } from "./compute_device_name";
import { computeStateName } from "./compute_state_name";
import { stripPrefixFromEntityName } from "./strip_prefix_from_entity_name";
export const computeEntityName = (
stateObj: HassEntity,
entities: HomeAssistant["entities"],
devices: HomeAssistant["devices"]
entities: HomeAssistant["entities"]
): string | undefined => {
const entry = entities[stateObj.entity_id] as
| EntityRegistryDisplayEntry
@@ -21,49 +18,22 @@ export const computeEntityName = (
// Fall back to state name if not in the entity registry (friendly name)
return computeStateName(stateObj);
}
return computeEntityEntryName(entry, devices);
return computeEntityEntryName(entry);
};
export const computeEntityEntryName = (
entry: EntityRegistryDisplayEntry | EntityRegistryEntry,
devices: HomeAssistant["devices"],
fallbackStateObj?: HassEntity
entry: EntityRegistryDisplayEntry | EntityRegistryEntry
): string | undefined => {
const name =
entry.name ||
("original_name" in entry && entry.original_name != null
? String(entry.original_name)
: undefined);
const device = entry.device_id ? devices[entry.device_id] : undefined;
if (!device) {
if (name) {
return name;
}
if (fallbackStateObj) {
return computeStateName(fallbackStateObj);
}
return undefined;
if (entry.name != null) {
return entry.name;
}
const deviceName = computeDeviceName(device);
// If the device name is the same as the entity name, consider empty entity name
if (deviceName === name) {
return undefined;
if ("original_name" in entry && entry.original_name != null) {
return String(entry.original_name);
}
// Remove the device name from the entity name if it starts with it
if (deviceName && name) {
return stripPrefixFromEntityName(name, deviceName) || name;
}
return name;
return undefined;
};
export const entityUseDeviceName = (
stateObj: HassEntity,
entities: HomeAssistant["entities"],
devices: HomeAssistant["devices"]
): boolean => !computeEntityName(stateObj, entities, devices);
entities: HomeAssistant["entities"]
): boolean => !computeEntityName(stateObj, entities);

View File

@@ -45,7 +45,7 @@ export const computeEntityNameDisplay = (
return items.map((item) => item.text).join(separator);
}
const useDeviceName = entityUseDeviceName(stateObj, entities, devices);
const useDeviceName = entityUseDeviceName(stateObj, entities);
// If entity uses device name, and device is not already included, replace it with device name
if (useDeviceName) {
@@ -91,7 +91,7 @@ export const computeEntityNameList = (
const names = name.map((item) => {
switch (item.type) {
case "entity":
return computeEntityName(stateObj, entities, devices);
return computeEntityName(stateObj, entities);
case "device":
return device ? computeDeviceName(device) : undefined;
case "area":

View File

@@ -133,33 +133,35 @@ const computeStateToPartsFromEntityAttributes = (
),
});
} catch (_err) {
// fallback to default
// fallback to default numeric formatting below
}
const TYPE_MAP: Record<string, ValuePart["type"]> = {
integer: "value",
group: "value",
decimal: "value",
fraction: "value",
literal: "literal",
currency: "unit",
};
if (parts.length) {
const TYPE_MAP: Record<string, ValuePart["type"]> = {
integer: "value",
group: "value",
decimal: "value",
fraction: "value",
literal: "literal",
currency: "unit",
minusSign: "value",
};
const valueParts: ValuePart[] = [];
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 });
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;
}
return valueParts;
}
// default processing of numeric values

View File

@@ -1,3 +1,5 @@
import { deepActiveElement } from "../dom/deep-active-element";
export const copyToClipboard = async (str, rootEl?: HTMLElement) => {
if (navigator.clipboard) {
try {
@@ -8,10 +10,15 @@ export const copyToClipboard = async (str, rootEl?: HTMLElement) => {
}
}
const root = rootEl ?? document.body;
const root = rootEl || deepActiveElement()?.getRootNode() || document.body;
const el = document.createElement("textarea");
el.value = str;
el.setAttribute("readonly", "");
el.style.position = "fixed";
el.style.top = "0";
el.style.left = "0";
el.style.opacity = "0";
root.appendChild(el);
el.select();
document.execCommand("copy");

View File

@@ -1,9 +1,8 @@
// From: https://davidwalsh.name/javascript-debounce-function
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge and on the trailing.
// leading edge. The trailing edge only fires if there were additional calls
// during the wait period.
export const debounce = <T extends any[]>(
func: (...args: T) => void,
@@ -11,20 +10,35 @@ export const debounce = <T extends any[]>(
immediate = false
) => {
let timeout: number | undefined;
let trailingArgs: T | undefined;
const debouncedFunc = (...args: T): void => {
const later = () => {
timeout = undefined;
func(...args);
};
const callNow = immediate && !timeout;
const isLeading = immediate && !timeout;
if (timeout) {
trailingArgs = args;
}
clearTimeout(timeout);
timeout = window.setTimeout(later, wait);
if (callNow) {
timeout = window.setTimeout(() => {
timeout = undefined;
if (trailingArgs) {
func(...trailingArgs);
trailingArgs = undefined;
} else if (!immediate) {
func(...args);
}
}, wait);
if (isLeading) {
func(...args);
}
};
debouncedFunc.cancel = () => {
clearTimeout(timeout);
trailingArgs = undefined;
};
return debouncedFunc;
};

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";
@@ -44,6 +44,7 @@ export type CustomLegendOption = ECOption["legend"] & {
id?: string;
secondaryIds?: string[]; // Other dataset IDs that should be controlled by this legend item.
name: string;
value?: string; // Current value to display next to the name in the legend.
itemStyle?: Record<string, any>;
}[];
};
@@ -333,12 +334,14 @@ export class HaChartBase extends LitElement {
let itemStyle: Record<string, any> = {};
let name = "";
let id = "";
let value = "";
if (typeof item === "string") {
name = item;
id = item;
} else {
name = item.name ?? "";
id = item.id ?? name;
value = item.value ?? "";
itemStyle = item.itemStyle ?? {};
}
const dataset =
@@ -365,6 +368,7 @@ export class HaChartBase extends LitElement {
})}
></div>
<div class="label">${name}</div>
${value ? html`<div class="value">${value}</div>` : nothing}
</li>`;
})}
${items.length > overflowLimit
@@ -578,7 +582,10 @@ export class HaChartBase extends LitElement {
id: "dataZoom",
type: "inside",
orient: "horizontal",
filterMode: "none",
// "boundaryFilter" is a custom mode added via axis-proxy-patch.ts.
// It rescales the Y-axis to the visible data while keeping one point
// just outside each boundary to avoid line gaps at the zoom edges.
filterMode: "boundaryFilter" as any,
xAxisIndex: 0,
moveOnMouseMove: !this._isTouchDevice || this._isZoomed,
preventDefaultMouseMove: !this._isTouchDevice || this._isZoomed,
@@ -877,10 +884,20 @@ export class HaChartBase extends LitElement {
};
}
if (s.sampling === "minmax") {
const minX =
xAxis?.min && typeof xAxis.min === "number" ? xAxis.min : undefined;
const maxX =
xAxis?.max && typeof xAxis.max === "number" ? xAxis.max : undefined;
const minX = xAxis?.min
? xAxis.min instanceof Date
? xAxis.min.getTime()
: typeof xAxis.min === "number"
? xAxis.min
: undefined
: undefined;
const maxX = xAxis?.max
? xAxis.max instanceof Date
? xAxis.max.getTime()
: typeof xAxis.max === "number"
? xAxis.max
: undefined
: undefined;
return {
...s,
sampling: undefined,
@@ -1115,13 +1132,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,
@@ -1156,6 +1173,9 @@ export class HaChartBase extends LitElement {
.chart-legend.multiple-items li {
max-width: 220px;
}
.chart-legend.multiple-items li:has(.value) {
max-width: 300px;
}
.chart-legend .hidden {
color: var(--secondary-text-color);
}
@@ -1164,6 +1184,12 @@ export class HaChartBase extends LitElement {
white-space: nowrap;
overflow: hidden;
}
.chart-legend .value {
color: var(--secondary-text-color);
margin-inline-start: var(--ha-space-1);
flex-shrink: 0;
white-space: nowrap;
}
.chart-legend .bullet {
border-width: 1px;
border-style: solid;

View File

@@ -239,7 +239,9 @@ export class StateHistoryChartLine extends LitElement {
changedProps.has("fitYData") ||
changedProps.has("paddingYAxis") ||
changedProps.has("_visualMap") ||
changedProps.has("_yWidth")
changedProps.has("_yWidth") ||
(changedProps.has("hass") &&
this._hasEntityStatesChanged(changedProps.get("hass")))
) {
const rtl = computeRTL(this.hass);
let minYAxis: number | ((values: { min: number }) => number) | undefined =
@@ -296,6 +298,19 @@ export class StateHistoryChartLine extends LitElement {
legend: {
type: "custom",
show: this.showNames,
data: this._chartData
.map((d, i) => ({ dataset: d, entityId: this._entityIds[i] }))
.filter((item) => !(item.dataset as LineSeriesOption).areaStyle)
.map((item) => {
const stateObj = this.hass.states[item.entityId];
return {
id: item.dataset.id as string,
name: item.dataset.name as string,
value: stateObj
? this.hass.formatEntityState(stateObj)
: undefined,
};
}),
},
grid: {
top: 15,
@@ -316,6 +331,13 @@ export class StateHistoryChartLine extends LitElement {
}
}
private _hasEntityStatesChanged(oldHass: HomeAssistant): boolean {
return this._entityIds.some(
(entityId) =>
this.hass.states[entityId]?.state !== oldHass.states[entityId]?.state
);
}
private _generateData() {
let colorIndex = 0;
const computedStyles = getComputedStyle(this);

View File

@@ -27,6 +27,7 @@ import {
getDisplayUnit,
getStatisticLabel,
getStatisticMetadata,
isExternalStatistic,
statisticsHaveType,
} from "../../data/recorder";
import type { ECOption } from "../../resources/echarts/echarts";
@@ -398,7 +399,31 @@ export class StatisticsChart extends LitElement {
endTime = new Date();
}
let unit: string | undefined | null;
// Check if we need to display most recent data. Allow 10m of leeway for "now",
// because stats are 5 minute aggregated.
// Use same now point for all statistics even if processing time means the
// state value is actually from a slightly later time. Otherwise the points
// end up separated slightly and disappear from the tooltips.
const now = new Date();
const displayCurrentState = now.getTime() - endTime.getTime() <= 600000;
// Try to determine chart unit if it has not already been set explicitly
if (!this.unit) {
let unit: string | undefined | null;
statisticsData.forEach(([statistic_id, _stats]) => {
const meta = statisticsMetaData?.[statistic_id];
const statisticUnit = getDisplayUnit(this.hass, statistic_id, meta);
if (unit === undefined) {
unit = statisticUnit;
} else if (unit !== null && unit !== statisticUnit) {
// Clear unit if not all statistics have same unit
unit = null;
}
});
if (unit) {
this.unit = unit;
}
}
const names = this.names || {};
statisticsData.forEach(([statistic_id, stats]) => {
@@ -408,18 +433,6 @@ export class StatisticsChart extends LitElement {
name = getStatisticLabel(this.hass, statistic_id, meta);
}
if (!this.unit) {
if (unit === undefined) {
unit = getDisplayUnit(this.hass, statistic_id, meta);
} else if (
unit !== null &&
unit !== getDisplayUnit(this.hass, statistic_id, meta)
) {
// Clear unit if not all statistics have same unit
unit = null;
}
}
// array containing [value1, value2, etc]
let prevValues: (number | null)[][] | null = null;
let prevEndTime: Date | undefined;
@@ -507,7 +520,6 @@ export class StatisticsChart extends LitElement {
id: `${statistic_id}-${type}`,
type: this.chartType,
smooth: this.chartType === "line" ? 0.4 : false,
smoothMonotone: "x",
cursor: "default",
data: [],
name: name
@@ -544,7 +556,7 @@ export class StatisticsChart extends LitElement {
(series as LineSeriesOption).areaStyle = undefined;
} else {
series.stackOrder = "seriesAsc";
if (drawBands && type === bandTop) {
if (type === bandTop) {
(series as LineSeriesOption).areaStyle = {
color: color + "3F",
};
@@ -613,10 +625,10 @@ export class StatisticsChart extends LitElement {
}
});
// Close out the last stat segment at prevEndTime
// For line charts, close out the last stat segment at prevEndTime
const lastEndTime = prevEndTime;
const lastValues = prevValues;
if (lastEndTime && lastValues) {
if (this.chartType === "line" && lastEndTime && lastValues) {
statDataSets.forEach((d, i) => {
d.data!.push(
this._transformDataValue([lastEndTime, ...lastValues[i]!])
@@ -624,13 +636,14 @@ export class StatisticsChart extends LitElement {
});
}
// Append current state if viewing recent data
const now = new Date();
// allow 10m of leeway for "now", because stats are 5 minute aggregated
const isUpToNow = now.getTime() - endTime.getTime() <= 600000;
if (isUpToNow) {
// Skip external statistics (they have ":" in the ID)
if (!statistic_id.includes(":")) {
// Show current state if required, and units match (or are unknown)
const statisticUnit = getDisplayUnit(this.hass, statistic_id, meta);
if (
displayCurrentState &&
(!this.unit || !statisticUnit || this.unit === statisticUnit)
) {
// Skip external statistics
if (!isExternalStatistic(statistic_id)) {
const stateObj = this.hass.states[statistic_id];
if (stateObj) {
const currentValue = parseFloat(stateObj.state);
@@ -640,11 +653,12 @@ export class StatisticsChart extends LitElement {
) {
// Then push the current state at now
statTypes.forEach((type, i) => {
const val: (number | null)[] = [];
if (type === "sum" || type === "change") {
// Skip cumulative types - need special calculation
val.push(null);
} else if (
// Skip cumulative types - need special calculation.
return;
}
const val: (number | null)[] = [];
if (
type === bandTop &&
this.chartType === "line" &&
drawBands &&
@@ -670,10 +684,6 @@ export class StatisticsChart extends LitElement {
Array.prototype.push.apply(legendData, statLegendData);
});
if (unit) {
this.unit = unit;
}
legendData.forEach(({ id, name, color, borderColor }) => {
// Add an empty series for the legend
totalDataSets.push({

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

@@ -32,11 +32,13 @@ 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;
}
@@ -50,6 +52,29 @@ export class DialogDataTableSettings extends LitElement {
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,
@@ -57,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;
@@ -195,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) {
@@ -276,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() {

View File

@@ -86,6 +86,7 @@ export interface DataTableColumnData<T = any> extends DataTableSortColumnData {
flex?: number;
forceLTR?: boolean;
hidden?: boolean;
lastFixed?: boolean;
}
export type ClonedDataTableColumnData = Omit<DataTableColumnData, "title"> & {
@@ -117,8 +118,6 @@ export class HaDataTable extends LitElement {
@property({ type: Boolean }) public clickable = false;
@property({ attribute: "has-fab", type: Boolean }) public hasFab = false;
/**
* Add an extra row at the bottom of the data table
* @type {TemplateResult}
@@ -135,9 +134,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;
@@ -359,6 +355,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;
@@ -394,7 +395,6 @@ export class HaDataTable extends LitElement {
.hass=${this.hass}
@value-changed=${this._handleSearchChange}
.label=${this.searchLabel}
.noLabelFloat=${this.noLabelFloat}
></search-input>
</div>
`
@@ -428,9 +428,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>
@@ -517,7 +517,6 @@ export class HaDataTable extends LitElement {
this._filteredData,
localize,
this.appendRow,
this.hasFab,
this.groupColumn,
this.groupOrder,
this._collapsedGroups,
@@ -714,14 +713,13 @@ export class HaDataTable extends LitElement {
data: DataTableRowData[],
localize: LocalizeFunc,
appendRow,
hasFab: boolean,
groupColumn: string | undefined,
groupOrder: string[] | undefined,
collapsedGroups: string[],
sortColumn: string | undefined,
sortDirection: SortingDirection
) => {
if (appendRow || hasFab || groupColumn) {
if (appendRow || groupColumn) {
let items = [...data];
if (groupColumn) {
@@ -811,13 +809,11 @@ export class HaDataTable extends LitElement {
items.push({ append: true, selectable: false, content: appendRow });
}
if (hasFab) {
items.push({ empty: true });
}
items.push({ empty: true });
return items;
}
return data;
return [...data, { empty: true }];
}
);
@@ -869,7 +865,6 @@ export class HaDataTable extends LitElement {
this._filteredData,
this.localizeFunc || this.hass.localize,
this.appendRow,
this.hasFab,
this.groupColumn,
this.groupOrder,
this._collapsedGroups,
@@ -1087,11 +1082,8 @@ export class HaDataTable extends LitElement {
}
.mdc-data-table__row.empty-row {
height: max(
var(
--data-table-empty-row-height,
var(--data-table-row-height, 52px)
),
height: var(
--data-table-empty-row-height,
var(--safe-area-inset-bottom, 0px)
);
}

View File

@@ -0,0 +1,348 @@
import { TZDate } from "@date-fns/tz";
import { consume, type ContextType } from "@lit/context";
import type { ActionDetail } from "@material/mwc-list";
import { mdiCalendarToday } from "@mdi/js";
import "cally";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { firstWeekdayIndex } from "../../common/datetime/first_weekday";
import {
formatCallyDateRange,
formatDateMonth,
formatDateYear,
formatISODateOnly,
} from "../../common/datetime/format_date";
import { fireEvent } from "../../common/dom/fire_event";
import {
configContext,
localeContext,
localizeContext,
} from "../../data/context";
import { TimeZone } from "../../data/translation";
import type { ValueChangedEvent } from "../../types";
import type { HaBaseTimeInput } from "../ha-base-time-input";
import "../ha-icon-button";
import "../ha-icon-button-next";
import "../ha-icon-button-prev";
import "../ha-list";
import "../ha-list-item";
import "../ha-time-input";
import type { DateRangePickerRanges } from "./ha-date-range-picker";
import { datePickerStyles, dateRangePickerStyles } from "./styles";
@customElement("date-range-picker")
export class DateRangePicker extends LitElement {
@property({ attribute: false }) public ranges?: DateRangePickerRanges | false;
@property({ attribute: false }) public startDate?: Date;
@property({ attribute: false }) public endDate?: Date;
@property({ attribute: "time-picker", type: Boolean })
public timePicker = false;
@state()
@consume({ context: localizeContext, subscribe: true })
private localize!: ContextType<typeof localizeContext>;
@state()
@consume({ context: localeContext, subscribe: true })
private locale!: ContextType<typeof localeContext>;
@state()
@consume({ context: configContext, subscribe: true })
private hassConfig!: ContextType<typeof configContext>;
/** used to show month in calendar-range header */
@state() private _pickerMonth?: string;
/** used to show year in calendar-date header */
@state() private _pickerYear?: string;
/** used for today to navigate focus in calendar-range */
@state() private _focusDate?: string;
@state() private _dateValue?: string;
@state() private _timeValue = {
from: { hours: 0, minutes: 0 },
to: { hours: 23, minutes: 59 },
};
public connectedCallback() {
super.connectedCallback();
const date = this.startDate || new Date();
this._dateValue =
this.startDate && this.endDate
? formatCallyDateRange(
this.startDate,
this.endDate,
this.locale,
this.hassConfig
)
: undefined;
this._pickerMonth = formatDateMonth(date, this.locale, this.hassConfig);
this._pickerYear = formatDateYear(date, this.locale, this.hassConfig);
if (this.timePicker && this.startDate && this.endDate) {
this._timeValue = {
from: {
hours: this.startDate.getHours(),
minutes: this.startDate.getMinutes(),
},
to: {
hours: this.endDate.getHours(),
minutes: this.endDate.getMinutes(),
},
};
}
}
render() {
return html`<div class="picker">
${this.ranges !== false && this.ranges
? html`<div class="date-range-ranges">
<ha-list @action=${this._setDateRange} activatable>
${Object.keys(this.ranges).map(
(name) => html`<ha-list-item>${name}</ha-list-item>`
)}
</ha-list>
</div>`
: nothing}
<div class="range">
<calendar-range
.value=${this._dateValue}
.locale=${this.locale.language}
.focusedDate=${this._focusDate}
@focusday=${this._focusChanged}
@change=${this._handleChange}
show-outside-days
.firstDayOfWeek=${firstWeekdayIndex(this.locale)}
>
<ha-icon-button-prev
tabindex="-1"
slot="previous"
></ha-icon-button-prev>
<div class="heading" slot="heading">
<span class="month-year"
>${this._pickerMonth} ${this._pickerYear}</span
>
<ha-icon-button
@click=${this._focusToday}
.path=${mdiCalendarToday}
.label=${this.localize("ui.dialogs.date-picker.today")}
></ha-icon-button>
</div>
<ha-icon-button-next
tabindex="-1"
slot="next"
></ha-icon-button-next>
<calendar-month></calendar-month>
</calendar-range>
${this.timePicker
? html`
<div class="times">
<ha-time-input
.value=${`${this._timeValue.from.hours}:${this._timeValue.from.minutes}`}
.locale=${this.locale}
@value-changed=${this._handleChangeTime}
.label=${this.localize(
"ui.components.date-range-picker.time_from"
)}
id="from"
placeholder-labels
></ha-time-input>
<ha-time-input
.value=${`${this._timeValue.to.hours}:${this._timeValue.to.minutes}`}
.locale=${this.locale}
@value-changed=${this._handleChangeTime}
.label=${this.localize(
"ui.components.date-range-picker.time_to"
)}
id="to"
placeholder-labels
></ha-time-input>
</div>
`
: nothing}
</div>
</div>
<div class="footer">
<ha-button appearance="plain" @click=${this._cancel}
>${this.localize("ui.common.cancel")}</ha-button
>
<ha-button .disabled=${!this._dateValue} @click=${this._save}
>${this.localize("ui.components.date-range-picker.select")}</ha-button
>
</div>`;
}
private _focusToday() {
const date = new Date();
this._focusDate = formatISODateOnly(date, this.locale, this.hassConfig);
this._pickerMonth = formatDateMonth(date, this.locale, this.hassConfig);
this._pickerYear = formatDateYear(date, this.locale, this.hassConfig);
}
private _cancel() {
fireEvent(this, "cancel-date-picker");
}
private _save() {
if (!this._dateValue) {
return;
}
const dates = this._dateValue.split("/");
let startDate = new Date(`${dates[0]}T00:00:00`);
let endDate = new Date(`${dates[1]}T23:59:00`);
if (this.timePicker) {
startDate.setHours(this._timeValue.from.hours);
startDate.setMinutes(this._timeValue.from.minutes);
endDate.setHours(this._timeValue.to.hours);
endDate.setMinutes(this._timeValue.to.minutes);
startDate.setSeconds(0);
startDate.setMilliseconds(0);
endDate.setSeconds(0);
endDate.setMilliseconds(0);
if (endDate <= startDate) {
endDate.setDate(startDate.getDate() + 1);
}
}
if (this.locale.time_zone === TimeZone.server) {
startDate = new Date(
new TZDate(startDate, this.hassConfig.time_zone).getTime()
);
endDate = new Date(
new TZDate(endDate, this.hassConfig.time_zone).getTime()
);
}
if (
startDate.getHours() !== this._timeValue.from.hours ||
startDate.getMinutes() !== this._timeValue.from.minutes ||
endDate.getHours() !== this._timeValue.to.hours ||
endDate.getMinutes() !== this._timeValue.to.minutes
) {
this._timeValue.from.hours = startDate.getHours();
this._timeValue.from.minutes = startDate.getMinutes();
this._timeValue.to.hours = endDate.getHours();
this._timeValue.to.minutes = endDate.getMinutes();
}
fireEvent(this, "value-changed", {
value: {
startDate,
endDate,
},
});
}
private _focusChanged(ev: CustomEvent<Date>) {
const date = ev.detail;
this._pickerMonth = formatDateMonth(date, this.locale, this.hassConfig);
this._pickerYear = formatDateYear(date, this.locale, this.hassConfig);
this._focusDate = undefined;
}
private _handleChange(ev: CustomEvent) {
const dateElement = ev.target as HTMLElementTagNameMap["calendar-range"];
this._dateValue = dateElement.value;
this._focusDate = undefined;
}
private _setDateRange(ev: CustomEvent<ActionDetail>) {
const dateRange: [Date, Date] = Object.values(this.ranges!)[
ev.detail.index
];
this._dateValue = formatCallyDateRange(
dateRange[0],
dateRange[1],
this.locale,
this.hassConfig
);
fireEvent(this, "value-changed", {
value: {
startDate: dateRange[0],
endDate: dateRange[1],
},
});
fireEvent(this, "preset-selected", {
index: ev.detail.index,
});
}
private _handleChangeTime(ev: ValueChangedEvent<string>) {
ev.stopPropagation();
const time = ev.detail.value;
const type = (ev.target as HaBaseTimeInput).id;
if (time) {
if (!this._timeValue) {
this._timeValue = {
from: { hours: 0, minutes: 0 },
to: { hours: 23, minutes: 59 },
};
}
const [hours, minutes] = time.split(":").map(Number);
this._timeValue[type].hours = hours;
this._timeValue[type].minutes = minutes;
}
}
static styles = [
datePickerStyles,
dateRangePickerStyles,
css`
.picker {
display: flex;
}
.date-range-ranges {
border-right: 1px solid var(--divider-color);
}
.range {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
padding: var(--ha-space-3);
}
.times {
display: flex;
flex-direction: column;
gap: var(--ha-space-2);
}
.footer {
display: flex;
justify-content: flex-end;
padding: var(--ha-space-2);
border-top: 1px solid var(--divider-color);
}
@media only screen and (max-width: 500px) {
.date-range-ranges {
max-width: 30%;
}
}
`,
];
}
declare global {
interface HTMLElementTagNameMap {
"date-range-picker": DateRangePicker;
}
interface HASSDomEvents {
"cancel-date-picker": undefined;
"preset-selected": { index: number };
}
}

View File

@@ -0,0 +1,406 @@
import "@home-assistant/webawesome/dist/components/popover/popover";
import { consume, type ContextType } from "@lit/context";
import { mdiCalendar } from "@mdi/js";
import "cally";
import { isThisYear } from "date-fns";
import type { TemplateResult } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { tinykeys } from "tinykeys";
import { shiftDateRange } from "../../common/datetime/calc_date";
import type { DateRange } from "../../common/datetime/calc_date_range";
import { calcDateRange } from "../../common/datetime/calc_date_range";
import {
formatShortDateTime,
formatShortDateTimeWithYear,
} from "../../common/datetime/format_date_time";
import { fireEvent } from "../../common/dom/fire_event";
import {
configContext,
localeContext,
localizeContext,
} from "../../data/context";
import "../ha-bottom-sheet";
import "../ha-icon-button";
import "../ha-icon-button-next";
import "../ha-icon-button-prev";
import "../ha-textarea";
import "./date-range-picker";
export type DateRangePickerRanges = Record<string, [Date, Date]>;
const RANGE_KEYS: DateRange[] = ["today", "yesterday", "this_week"];
const EXTENDED_RANGE_KEYS: DateRange[] = [
"this_month",
"this_year",
"now-1h",
"now-12h",
"now-24h",
"now-7d",
"now-30d",
];
@customElement("ha-date-range-picker")
export class HaDateRangePicker extends LitElement {
@state()
@consume({ context: localizeContext, subscribe: true })
private localize!: ContextType<typeof localizeContext>;
@state()
@consume({ context: localeContext, subscribe: true })
private locale!: ContextType<typeof localeContext>;
@state()
@consume({ context: configContext, subscribe: true })
private hassConfig!: ContextType<typeof configContext>;
@property({ attribute: false }) public startDate!: Date;
@property({ attribute: false }) public endDate!: Date;
@property({ attribute: false }) public ranges?: DateRangePickerRanges | false;
@state() private _ranges?: DateRangePickerRanges;
@property({ attribute: "time-picker", type: Boolean })
public timePicker = false;
@property({ type: Boolean, reflect: true })
public backdrop = false;
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) public minimal = false;
@property({ attribute: "extended-presets", type: Boolean })
public extendedPresets = false;
@property({ attribute: "popover-placement" })
public popoverPlacement:
| "bottom"
| "top"
| "left"
| "right"
| "top-start"
| "top-end"
| "right-start"
| "right-end"
| "bottom-start"
| "bottom-end"
| "left-start"
| "left-end" = "bottom-start";
@state() private _opened = false;
@state() private _pickerWrapperOpen = false;
@state() private _openedNarrow = false;
@state() private _popoverWidth = 0;
@query(".container") private _containerElement?: HTMLDivElement;
private _narrow = false;
private _unsubscribeTinyKeys?: () => void;
public connectedCallback() {
super.connectedCallback();
this._handleResize();
window.addEventListener("resize", this._handleResize);
const rangeKeys = this.extendedPresets
? [...RANGE_KEYS, ...EXTENDED_RANGE_KEYS]
: RANGE_KEYS;
this._ranges = {};
rangeKeys.forEach((key) => {
this._ranges![
this.localize(`ui.components.date-range-picker.ranges.${key}`)
] = calcDateRange(this.locale, this.hassConfig, key);
});
}
public open(): void {
this._openPicker();
}
protected render(): TemplateResult {
return html`
<div class="container">
<div class="date-range-inputs">
${!this.minimal
? html`<ha-textarea
id="field"
mobile-multiline
@click=${this._openPicker}
@keydown=${this._handleKeydown}
.value=${(isThisYear(this.startDate)
? formatShortDateTime(
this.startDate,
this.locale,
this.hassConfig
)
: formatShortDateTimeWithYear(
this.startDate,
this.locale,
this.hassConfig
)) +
(window.innerWidth >= 459 ? " - " : " - \n") +
(isThisYear(this.endDate)
? formatShortDateTime(
this.endDate,
this.locale,
this.hassConfig
)
: formatShortDateTimeWithYear(
this.endDate,
this.locale,
this.hassConfig
))}
.label=${this.localize(
"ui.components.date-range-picker.start_date"
) +
" - " +
this.localize("ui.components.date-range-picker.end_date")}
.disabled=${this.disabled}
readonly
></ha-textarea>
<ha-icon-button-prev
.label=${this.localize("ui.common.previous")}
@click=${this._handlePrev}
>
</ha-icon-button-prev>
<ha-icon-button-next
.label=${this.localize("ui.common.next")}
@click=${this._handleNext}
>
</ha-icon-button-next>`
: html`<ha-icon-button
@click=${this._openPicker}
.disabled=${this.disabled}
id="field"
.label=${this.localize(
"ui.components.date-range-picker.select_date_range"
)}
.path=${mdiCalendar}
></ha-icon-button>`}
</div>
${this._pickerWrapperOpen || this._opened
? this._openedNarrow
? html`
<ha-bottom-sheet
flexcontent
.open=${this._pickerWrapperOpen}
@wa-after-show=${this._dialogOpened}
@closed=${this._hidePicker}
>
${this._renderPicker()}
</ha-bottom-sheet>
`
: html`
<wa-popover
.open=${this._pickerWrapperOpen}
style="--body-width: ${this._popoverWidth}px;"
class=${this._opened ? "open" : ""}
without-arrow
distance="0"
.placement=${this.popoverPlacement}
for="field"
auto-size="vertical"
auto-size-padding="16"
@wa-after-show=${this._dialogOpened}
@wa-hide=${this._handlePopoverHide}
@wa-after-hide=${this._hidePicker}
trap-focus
>
${this._renderPicker()}
</wa-popover>
`
: nothing}
</div>
`;
}
private _renderPicker() {
if (!this._opened) {
return nothing;
}
return html`
<date-range-picker
.ranges=${this.ranges === false ? false : this.ranges || this._ranges}
.startDate=${this.startDate}
.endDate=${this.endDate}
.timePicker=${this.timePicker}
@cancel-date-picker=${this._closePicker}
@value-changed=${this._closePicker}
>
</date-range-picker>
`;
}
private _hidePicker(ev: Event) {
ev.stopPropagation();
this._opened = false;
this._pickerWrapperOpen = false;
this._unsubscribeTinyKeys?.();
fireEvent(this, "picker-closed");
}
public disconnectedCallback() {
super.disconnectedCallback();
window.removeEventListener("resize", this._handleResize);
this._unsubscribeTinyKeys?.();
}
private _handleResize = () => {
this._narrow =
window.matchMedia("(max-width: 870px)").matches ||
window.matchMedia("(max-height: 500px)").matches;
if (!this._openedNarrow && this._pickerWrapperOpen) {
this._popoverWidth = this._containerElement?.offsetWidth || 250;
}
};
private _dialogOpened = () => {
this._opened = true;
this._setTextareaFocusStyle(true);
};
private _handlePopoverHide = () => {
this._opened = false;
};
private _handleNext(ev: MouseEvent): void {
if (ev && ev.stopPropagation) ev.stopPropagation();
this._shift(true);
}
private _handlePrev(ev: MouseEvent): void {
if (ev && ev.stopPropagation) ev.stopPropagation();
this._shift(false);
}
private _shift(forward: boolean) {
if (!this.startDate) return;
const { start, end } = shiftDateRange(
this.startDate,
this.endDate,
forward,
this.locale,
this.hassConfig
);
this.startDate = start;
this.endDate = end;
fireEvent(this, "value-changed", {
value: {
startDate: this.startDate,
endDate: this.endDate,
},
});
}
private _closePicker() {
this._pickerWrapperOpen = false;
}
private _openPicker(ev?: Event) {
if (this.disabled) {
return;
}
if (this._pickerWrapperOpen) {
ev?.stopImmediatePropagation();
return;
}
this._openedNarrow = this._narrow;
this._popoverWidth = this._containerElement?.offsetWidth || 250;
this._pickerWrapperOpen = true;
this._unsubscribeTinyKeys = tinykeys(this, {
Escape: this._handleEscClose,
});
}
private _handleKeydown(ev: KeyboardEvent) {
if (ev.key === "Enter" || ev.key === " ") {
ev.stopPropagation();
this._openPicker(ev);
}
}
private _handleEscClose = (ev: KeyboardEvent) => {
ev.stopPropagation();
};
private _setTextareaFocusStyle(focused: boolean) {
const textarea = this.renderRoot.querySelector("ha-textarea");
if (textarea) {
const foundation = (textarea as any).mdcFoundation;
if (foundation) {
if (focused) {
foundation.activateFocus();
} else {
foundation.deactivateFocus();
}
}
}
}
static styles = [
css`
ha-icon-button {
direction: var(--direction);
}
.date-range-inputs {
display: flex;
align-items: center;
gap: var(--ha-space-2);
}
ha-textarea {
display: inline-block;
width: 340px;
}
@media only screen and (max-width: 460px) {
ha-textarea {
width: 100%;
}
}
wa-popover {
--wa-space-l: 0;
}
wa-popover::part(dialog)::backdrop {
opacity: 0;
transition: opacity var(--ha-animation-duration-normal) ease-out;
}
wa-popover.open::part(dialog)::backdrop {
opacity: 1;
}
:host(:not([backdrop])) wa-popover::part(dialog)::backdrop {
background: none;
}
wa-popover::part(body) {
min-width: max(var(--body-width), 250px);
max-width: calc(
100vw - var(--safe-area-inset-left) - var(
--safe-area-inset-right
) - var(--ha-space-8)
);
overflow: hidden;
}
`,
];
}
declare global {
interface HTMLElementTagNameMap {
"ha-date-range-picker": HaDateRangePicker;
}
}

View File

@@ -0,0 +1,220 @@
import "@home-assistant/webawesome/dist/components/divider/divider";
import { consume, type ContextType } from "@lit/context";
import { mdiBackspace, mdiCalendarToday } from "@mdi/js";
import "cally";
import { css, html, LitElement, nothing } from "lit";
import { customElement, state } from "lit/decorators";
import {
formatDateMonth,
formatDateShort,
formatDateYear,
formatISODateOnly,
} from "../../common/datetime/format_date";
import {
configContext,
localeContext,
localizeContext,
} from "../../data/context";
import { DialogMixin } from "../../dialogs/dialog-mixin";
import "../ha-button";
import type { DatePickerDialogParams } from "../ha-date-input";
import "../ha-dialog";
import "../ha-dialog-footer";
import "../ha-icon-button";
import "../ha-icon-button-next";
import "../ha-icon-button-prev";
import { datePickerStyles } from "./styles";
type CalendarDate = HTMLElementTagNameMap["calendar-date"];
/**
* A date picker dialog component that displays a calendar for selecting dates.
* Uses the `cally` library for calendar rendering and supports localization,
* min/max date constraints, and optional clearing of the selected date.
*
* @element ha-dialog-date-picker
* Uses {@link DialogMixin} with {@link DatePickerDialogParams} to manage dialog state and parameters.
*/
@customElement("ha-dialog-date-picker")
export class HaDialogDatePicker extends DialogMixin<DatePickerDialogParams>(
LitElement
) {
@state()
@consume({ context: localizeContext, subscribe: true })
private localize!: ContextType<typeof localizeContext>;
@state()
@consume({ context: localeContext, subscribe: true })
private locale!: ContextType<typeof localeContext>;
@state()
@consume({ context: configContext, subscribe: true })
private hassConfig!: ContextType<typeof configContext>;
@state() private _value?: {
year: string;
title: string;
dateString: string;
};
/** used to show month in calendar-date header */
@state() private _pickerMonth?: string;
/** used to show year in calendar-date header */
@state() private _pickerYear?: string;
/** used for today to navigate focus in cally-calendar-date */
@state() private _focusDate?: string;
public connectedCallback() {
super.connectedCallback();
if (this.params) {
const date = this.params.value
? new Date(`${this.params.value.split("T")[0]}T00:00:00`)
: new Date();
this._pickerYear = formatDateYear(date, this.locale, this.hassConfig);
this._pickerMonth = formatDateMonth(date, this.locale, this.hassConfig);
this._value = this.params.value
? {
year: this._pickerYear,
title: formatDateShort(date, this.locale, this.hassConfig),
dateString: formatISODateOnly(date, this.locale, this.hassConfig),
}
: undefined;
}
}
render() {
if (!this.params) {
return nothing;
}
return html`<ha-dialog
open
width="small"
.headerTitle=${this._value?.title ||
this.localize("ui.dialogs.date-picker.title")}
.headerSubtitle=${this._value?.year}
header-subtitle-position="above"
>
${this.params.canClear
? html`
<ha-icon-button
.path=${mdiBackspace}
.label=${this.localize("ui.dialogs.date-picker.clear")}
slot="headerActionItems"
@click=${this._clear}
></ha-icon-button>
`
: nothing}
<wa-divider></wa-divider>
<calendar-date
.value=${this._value?.dateString}
.min=${this.params.min}
.max=${this.params.max}
.locale=${this.params.locale}
.firstDayOfWeek=${this.params.firstWeekday}
.focusedDate=${this._focusDate}
@change=${this._valueChanged}
@focusday=${this._focusChanged}
>
<ha-icon-button-prev
tabindex="-1"
slot="previous"
></ha-icon-button-prev>
<div class="heading" slot="heading">
<span class="month-year"
>${this._pickerMonth} ${this._pickerYear}</span
>
<ha-icon-button
@click=${this._setToday}
.path=${mdiCalendarToday}
.label=${this.localize("ui.dialogs.date-picker.today")}
></ha-icon-button>
</div>
<ha-icon-button-next tabindex="-1" slot="next"></ha-icon-button-next>
<calendar-month></calendar-month>
</calendar-date>
<ha-dialog-footer slot="footer">
<ha-button
appearance="plain"
slot="secondaryAction"
@click=${this.closeDialog}
>
${this.localize("ui.common.cancel")}
</ha-button>
<ha-button slot="primaryAction" @click=${this._setValue}>
${this.localize("ui.common.ok")}
</ha-button>
</ha-dialog-footer>
</ha-dialog>`;
}
private _valueChanged(ev: Event) {
const dateElement = ev.target as CalendarDate;
if (dateElement.value) {
this._updateValue(dateElement.value);
}
}
private _updateValue(value?: string, setFocusDay = false) {
const date = value
? new Date(`${value.split("T")[0]}T00:00:00`)
: new Date();
this._value = {
year: formatDateYear(date, this.locale, this.hassConfig),
title: formatDateShort(date, this.locale, this.hassConfig),
dateString:
value || formatISODateOnly(date, this.locale, this.hassConfig),
};
if (setFocusDay) {
this._focusDate = this._value.dateString;
this._pickerMonth = formatDateMonth(date, this.locale, this.hassConfig);
this._pickerYear = formatDateYear(date, this.locale, this.hassConfig);
}
}
private _focusChanged(ev: CustomEvent<Date>) {
const date = ev.detail;
this._pickerMonth = formatDateMonth(date, this.locale, this.hassConfig);
this._pickerYear = formatDateYear(date, this.locale, this.hassConfig);
this._focusDate = undefined;
}
private _clear() {
this.params?.onChange(undefined);
this.closeDialog();
}
private _setToday() {
this._updateValue(undefined, true);
}
private _setValue() {
if (!this._value) {
// Date picker opens to today if value is undefined. If user click OK
// without changing the date, should return todays date, not undefined.
this._setToday();
}
this.params?.onChange(this._value?.dateString);
this.closeDialog();
}
static styles = [
datePickerStyles,
css`
ha-dialog {
--dialog-content-padding: 0;
}
`,
];
}
declare global {
interface HTMLElementTagNameMap {
"ha-dialog-date-picker": HaDialogDatePicker;
}
}

View File

@@ -0,0 +1,144 @@
import { css } from "lit";
export const datePickerStyles = css`
calendar-range,
calendar-date {
width: 100%;
min-width: 300px;
}
calendar-date::part(button),
calendar-range::part(button) {
border: none;
background-color: unset;
border-radius: var(--ha-border-radius-circle);
outline-offset: -2px;
outline-color: var(--ha-color-neutral-60);
}
calendar-month {
width: calc(40px * 7);
margin: 0 auto;
min-height: calc(42px * 7);
}
calendar-month::part(heading) {
display: none;
}
calendar-month::part(day) {
color: var(--disabled-text-color);
font-size: var(--ha-font-size-m);
font-family: var(--ha-font-body);
}
calendar-month::part(button) {
color: var(--primary-text-color);
height: 32px;
width: 32px;
margin: var(--ha-space-1);
border-radius: var(--ha-border-radius-circle);
}
calendar-month::part(button):focus-visible {
background-color: inherit;
outline: 2px solid var(--accent-color);
outline-offset: 2px;
}
calendar-month::part(button):hover {
background-color: var(--ha-color-fill-primary-quiet-hover);
}
calendar-month::part(today) {
color: var(--primary-color);
}
calendar-month::part(range-inner),
calendar-month::part(range-start),
calendar-month::part(range-end),
calendar-month::part(selected),
calendar-month::part(selected):hover {
color: var(--text-primary-color);
background-color: var(--primary-color);
height: 40px;
width: 40px;
margin: 0;
}
calendar-month::part(selected):focus-visible {
background-color: var(--primary-color);
color: var(--text-primary-color);
}
calendar-month::part(outside) {
cursor: pointer;
}
.heading {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
font-size: var(--ha-font-size-m);
font-weight: var(--ha-font-weight-medium);
}
.month-year {
flex: 1;
text-align: center;
margin-left: 48px;
}
@media only screen and (max-width: 500px) {
calendar-month {
min-height: calc(34px * 7);
}
calendar-month::part(day) {
font-size: var(--ha-font-size-s);
}
calendar-month::part(button) {
height: 26px;
width: 26px;
}
calendar-month::part(range-inner),
calendar-month::part(range-start),
calendar-month::part(range-end),
calendar-month::part(selected),
calendar-month::part(selected):hover {
height: 34px;
width: 34px;
}
.heading {
font-size: var(--ha-font-size-s);
}
.month-year {
margin-left: 40px;
}
}
`;
export const dateRangePickerStyles = css`
calendar-month::part(selected):focus-visible {
background-color: var(--primary-color);
color: var(--text-primary-color);
}
calendar-month::part(range-inner),
calendar-month::part(range-start),
calendar-month::part(range-end),
calendar-month::part(range-inner):hover,
calendar-month::part(range-start):hover,
calendar-month::part(range-end):hover {
color: var(--text-primary-color);
background-color: var(--primary-color);
border-radius: var(--ha-border-radius-square);
display: block;
margin: 0;
}
calendar-month::part(range-start),
calendar-month::part(range-start):hover {
border-top-left-radius: var(--ha-border-radius-circle);
border-bottom-left-radius: var(--ha-border-radius-circle);
}
calendar-month::part(range-end),
calendar-month::part(range-end):hover {
border-top-right-radius: var(--ha-border-radius-circle);
border-bottom-right-radius: var(--ha-border-radius-circle);
}
calendar-month::part(range-start):hover,
calendar-month::part(range-end):hover,
calendar-month::part(range-inner):hover {
color: var(--primary-text-color);
}
`;

View File

@@ -1,359 +0,0 @@
import wrap from "@vue/web-component-wrapper";
import { customElement } from "lit/decorators";
import Vue from "vue";
import DateRangePicker from "vue2-daterange-picker";
// @ts-ignore
import dateRangePickerStyles from "vue2-daterange-picker/dist/vue2-daterange-picker.css";
import {
localizeMonths,
localizeWeekdays,
} from "../common/datetime/localize_date";
import { fireEvent } from "../common/dom/fire_event";
import { mainWindow } from "../common/dom/get_main_window";
// eslint-disable-next-line @typescript-eslint/naming-convention
const CustomDateRangePicker = Vue.extend({
mixins: [DateRangePicker],
methods: {
// Set the current date to the left picker instead of the right picker because the right is hidden
selectMonthDate() {
const dt: Date = this.end || new Date();
// @ts-ignore
this.changeLeftMonth({
year: dt.getFullYear(),
month: dt.getMonth() + 1,
});
},
// Fix the start/end date calculation when selecting a date range. The
// original code keeps track of the first clicked date (in_selection) but it
// never sets it to either the start or end date variables, so if the
// in_selection date is between the start and end date that were set by the
// hover the selection will enter a broken state that's counter-intuitive
// when hovering between weeks and leads to a random date when selecting a
// range across months. This bug doesn't seem to be present on v0.6.7 of the
// lib
hoverDate(value: Date) {
if (this.readonly) return;
if (this.in_selection) {
const pickA = this.in_selection as Date;
const pickB = value;
this.start = this.normalizeDatetime(
Math.min(pickA.valueOf(), pickB.valueOf()),
this.start
);
this.end = this.normalizeDatetime(
Math.max(pickA.valueOf(), pickB.valueOf()),
this.end
);
}
this.$emit("hover-date", value);
},
},
});
// eslint-disable-next-line @typescript-eslint/naming-convention
const Component = Vue.extend({
props: {
timePicker: {
type: Boolean,
default: true,
},
twentyfourHours: {
type: Boolean,
default: true,
},
openingDirection: {
type: String,
default: "right",
},
disabled: {
type: Boolean,
default: false,
},
ranges: {
type: Boolean,
default: true,
},
startDate: {
type: [String, Date],
default() {
return new Date();
},
},
endDate: {
type: [String, Date],
default() {
return new Date();
},
},
firstDay: {
type: Number,
default: 1,
},
autoApply: {
type: Boolean,
default: false,
},
language: {
type: String,
default: "en",
},
opensVertical: {
type: String,
default: undefined,
},
},
render(createElement) {
// @ts-expect-error
return createElement(CustomDateRangePicker, {
props: {
"time-picker": this.timePicker,
"auto-apply": this.autoApply,
opens: this.openingDirection,
"show-dropdowns": false,
"time-picker24-hour": this.twentyfourHours,
disabled: this.disabled,
ranges: this.ranges ? {} : false,
"locale-data": {
firstDay: this.firstDay,
daysOfWeek: localizeWeekdays(this.language, true),
monthNames: localizeMonths(this.language, false),
},
},
model: {
value: {
startDate: this.startDate,
endDate: this.endDate,
},
callback: (value) => {
fireEvent(this.$el as HTMLElement, "change", value);
},
expression: "dateRange",
},
on: {
toggle: (open: boolean) => {
fireEvent(this.$el as HTMLElement, "toggle", { open });
},
},
scopedSlots: {
input() {
return createElement("slot", {
domProps: { name: "input" },
});
},
header() {
return createElement("slot", {
domProps: { name: "header" },
});
},
ranges() {
return createElement("slot", {
domProps: { name: "ranges" },
});
},
footer() {
return createElement("slot", {
domProps: { name: "footer" },
});
},
},
});
},
});
// Assertion corrects HTMLElement type from package
// eslint-disable-next-line @typescript-eslint/naming-convention
const WrappedElement = wrap(
Vue,
Component
) as unknown as CustomElementConstructor;
@customElement("date-range-picker")
class DateRangePickerElement extends WrappedElement {
constructor() {
super();
const style = document.createElement("style");
style.innerHTML = `
${dateRangePickerStyles}
.calendars {
display: flex;
flex-wrap: nowrap !important;
}
.daterangepicker {
top: auto;
box-shadow: var(--ha-card-box-shadow, none);
background-color: var(--card-background-color);
border-radius: var(--ha-card-border-radius, var(--ha-border-radius-lg));
border-width: var(--ha-card-border-width, 1px);
border-style: solid;
border-color: var(
--ha-card-border-color,
var(--divider-color, #e0e0e0)
);
color: var(--primary-text-color);
min-width: initial !important;
max-height: var(--date-range-picker-max-height);
overflow-y: auto;
}
.daterangepicker:before {
display: none;
}
.daterangepicker:after {
border-bottom: 6px solid var(--card-background-color);
}
.daterangepicker .calendar-table {
background-color: var(--card-background-color);
border: none;
}
.daterangepicker .calendar-table td,
.daterangepicker .calendar-table th {
background-color: transparent;
color: var(--secondary-text-color);
border-radius: var(--ha-border-radius-square);
outline: none;
min-width: 32px;
height: 32px;
}
.daterangepicker td.off,
.daterangepicker td.off.end-date,
.daterangepicker td.off.in-range,
.daterangepicker td.off.start-date {
background-color: var(--secondary-background-color);
color: var(--disabled-text-color);
}
.daterangepicker td.in-range {
background-color: var(--light-primary-color);
color: var(--text-light-primary-color, var(--primary-text-color));
}
.daterangepicker td.active,
.daterangepicker td.active:hover {
background-color: var(--primary-color);
color: var(--text-primary-color);
}
.daterangepicker td.start-date.end-date {
border-radius: var(--ha-border-radius-circle);
}
.daterangepicker td.start-date {
border-radius: var(--ha-border-radius-circle) var(--ha-border-radius-square) var(--ha-border-radius-square) var(--ha-border-radius-circle);
}
.daterangepicker td.end-date {
border-radius: var(--ha-border-radius-square) var(--ha-border-radius-circle) var(--ha-border-radius-circle) var(--ha-border-radius-square);
}
.reportrange-text {
background: none !important;
padding: 0 !important;
border: none !important;
}
.daterangepicker .calendar-table .next span,
.daterangepicker .calendar-table .prev span {
border: solid var(--primary-text-color);
border-width: 0 2px 2px 0;
}
.daterangepicker .ranges li {
outline: none;
}
.daterangepicker .ranges li:hover {
background-color: var(--secondary-background-color);
}
.daterangepicker .ranges li.active {
background-color: var(--primary-color);
color: var(--text-primary-color);
}
.daterangepicker select.ampmselect,
.daterangepicker select.hourselect,
.daterangepicker select.minuteselect,
.daterangepicker select.secondselect {
background: var(--card-background-color);
border: 1px solid var(--divider-color);
color: var(--primary-color);
}
.daterangepicker .drp-buttons .btn {
border: 1px solid var(--primary-color);
background-color: transparent;
color: var(--primary-color);
border-radius: var(--ha-border-radius-sm);
padding: 8px;
cursor: pointer;
}
.calendars-container {
flex-direction: column;
align-items: center;
}
.drp-calendar.col.right .calendar-table {
display: none;
}
.daterangepicker.show-ranges .drp-calendar.left {
border-left: 0px;
}
.daterangepicker .drp-calendar.left {
padding: 8px;
width: unset;
max-width: unset;
min-width: 270px;
}
.daterangepicker.show-calendar .ranges {
margin-top: 0;
padding-top: 8px;
border-right: 1px solid var(--divider-color);
}
@media only screen and (max-width: 800px) {
.calendars {
flex-direction: column;
}
}
.calendar-table {
padding: 0 !important;
}
.calendar-time {
direction: ltr;
}
.daterangepicker.ltr {
direction: var(--direction);
text-align: var(--float-start);
}
.vue-daterange-picker{
min-width: unset !important;
display: block !important;
}
:host([opens-vertical="up"]) .daterangepicker {
bottom: 100%;
top: auto !important;
}
`;
if (mainWindow.document.dir === "rtl") {
style.innerHTML += `
.daterangepicker .calendar-table .next span {
transform: rotate(135deg);
-webkit-transform: rotate(135deg);
}
.daterangepicker .calendar-table .prev span {
transform: rotate(-45deg);
-webkit-transform: rotate(-45deg);
}
.daterangepicker td.start-date {
border-radius: var(--ha-border-radius-square) var(--ha-border-radius-circle) var(--ha-border-radius-circle) var(--ha-border-radius-square);
}
.daterangepicker td.end-date {
border-radius: var(--ha-border-radius-circle) var(--ha-border-radius-square) var(--ha-border-radius-square) var(--ha-border-radius-circle);
}
`;
}
const shadowRoot = this.shadowRoot!;
shadowRoot.appendChild(style);
// Stop click events from reaching the document, otherwise it will close the picker immediately.
shadowRoot.addEventListener("click", (ev) => ev.stopPropagation());
}
}
declare global {
interface HTMLElementTagNameMap {
"date-range-picker": DateRangePickerElement;
}
interface HASSDomEvents {
toggle: { open: boolean };
}
}

View File

@@ -36,7 +36,7 @@ export abstract class HaDeviceAutomationPicker<
@state()
@consume({ context: fullEntitiesContext, subscribe: true })
_entityReg!: EntityRegistryEntry[];
_entityReg: EntityRegistryEntry[] = [];
protected get NO_AUTOMATION_TEXT() {
return this.hass.localize(

View File

@@ -173,11 +173,14 @@ export class HaDevicePicker extends LitElement {
alt=""
crossorigin="anonymous"
referrerpolicy="no-referrer"
src=${brandsUrl({
domain: configEntry.domain,
type: "icon",
darkOptimized: this.hass.themes?.darkMode,
})}
src=${brandsUrl(
{
domain: configEntry.domain,
type: "icon",
darkOptimized: this.hass.themes?.darkMode,
},
this.hass.auth.data.hassUrl
)}
/>`
: nothing}
<span slot="headline">${primary}</span>
@@ -195,11 +198,14 @@ export class HaDevicePicker extends LitElement {
alt=""
crossorigin="anonymous"
referrerpolicy="no-referrer"
src=${brandsUrl({
domain: item.domain,
type: "icon",
darkOptimized: this.hass.themes.darkMode,
})}
src=${brandsUrl(
{
domain: item.domain,
type: "icon",
darkOptimized: this.hass.themes.darkMode,
},
this.hass.auth.data.hassUrl
)}
/>
`
: nothing}

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;

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