Compare commits

...
Author SHA1 Message Date
copilot-swe-agent[bot]andtimmo001 564a2e7301 Group device and entity automation items under Generic
Co-authored-by: timmo001 <[email protected]>
2026-03-13 11:04:19 +00:00
copilot-swe-agent[bot] 054b98dfc5 Initial plan 2026-03-13 10:53:13 +00: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 CarpenterandPetar Petrov 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 <[email protected]>

* 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 <[email protected]>
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 RosenandCopilot 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 <[email protected]>
2026-03-13 08:28:58 +02:00
Logan RosenandCopilot 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 <[email protected]>
2026-03-13 08:27:39 +02:00
Logan RosenandCopilot 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 <[email protected]>
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
5dc2c51251 Remove Z-Wave Installer panel (#30115)
* Initial plan

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

Co-authored-by: AlCalzone <[email protected]>

---------

Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: AlCalzone <[email protected]>
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
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 <[email protected]>

* 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 <[email protected]>

* 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 <[email protected]>

* 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 <[email protected]>

* 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 <[email protected]>

---------

Co-authored-by: Petar Petrov <[email protected]>
Co-authored-by: Norbert Rittel <[email protected]>
Co-authored-by: karwosts <[email protected]>
Co-authored-by: Simon Lamon <[email protected]>
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
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 <[email protected]>

* 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 <[email protected]>

* 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 <[email protected]>

---------

Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: wendevlin <[email protected]>
Co-authored-by: MindFreeze <[email protected]>
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 CarpenterandPetar Petrov 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 <[email protected]>

---------

Co-authored-by: Petar Petrov <[email protected]>
2026-03-12 07:40:50 +00:00
Josef ZweckandPetar Petrov 5ddb31ce7b Add "cleaning_up" backup stage (#30106)
* Add "cleaning_up" backup stage

* Update ha-backup-overview-progress.ts

Co-authored-by: Petar Petrov <[email protected]>

* prettier

---------

Co-authored-by: Petar Petrov <[email protected]>
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 RosenandCopilot 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 <[email protected]>
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
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 <[email protected]>

* 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 <[email protected]>

* Make not-scrollable more specific.

Co-authored-by: Petar Petrov <[email protected]>

---------

Co-authored-by: Claude Sonnet 4.6 <[email protected]>
Co-authored-by: Petar Petrov <[email protected]>
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
karwostsandPetar Petrov 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 <[email protected]>

* Apply suggestion

Co-authored-by: Petar Petrov <[email protected]>

* Apply suggestion from @MindFreeze

---------

Co-authored-by: Petar Petrov <[email protected]>
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 KragtenandWendelin 6957e1f4d9 Introduce Lazy context provider (#29988)
Co-authored-by: Wendelin <[email protected]>
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](https://github.com/release-drafter/release-drafter/compare/6db134d15f3909ccc9eefd369f02bd1e9cffdf97...6a93d829887aa2e0748befe2e808c66c0ec6e4c7)

---
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] <[email protected]>
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](https://github.com/actions/setup-node/compare/6044e13b5dc448c55e2357c09f80417699197238...53b83947a5a98c8d113130e565377fae1a50d02f)

---
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] <[email protected]>
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](https://github.com/github/codeql-action/compare/89a39a4e59826350b863aa6b6252a07ad50cf83e...0d579ffd059c29b07949a3cce3983f0780820c98)

---
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] <[email protected]>
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
e47bf22798 Add click-to-replace interaction for selected targets in ha-target-picker (#29864)
Co-authored-by: timmo001 <[email protected]>
Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: Aidan Timson <[email protected]>
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 TimsonandPetar Petrov 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 <[email protected]>
2026-03-05 07:31:31 +00:00
7612ec1979 Refactor tooltip CSS tokens to use ha- prefix (#29978)
Co-authored-by: Claude <[email protected]>
Co-authored-by: Wendelin <[email protected]>
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
Copilotandwendevlin 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 <[email protected]>

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

Co-authored-by: wendevlin <[email protected]>

* Throw not implemented error in renderContent() base method

Co-authored-by: wendevlin <[email protected]>

* Throw not implemented error in handlePaste and saveConfig base methods

Co-authored-by: wendevlin <[email protected]>

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

Co-authored-by: wendevlin <[email protected]>

---------

Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: wendevlin <[email protected]>
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
Copilotandwendevlin 52c36db5d4 Fix plant status card not respecting sensor display precision (#29968)
Co-authored-by: wendevlin <[email protected]>
Co-authored-by: copilot-swe-agent[bot] <[email protected]>
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 BauerandWendelin 17314fe62b Add animation duration tokens and button transition update (#29965)
Co-authored-by: Wendelin <[email protected]>
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 BaatandAlCalzone a68acdf9dd Update delete Z-Wave device dialog (#29956)
* Update text and spacing

* Update text

* Update src/translations/en.json

Co-authored-by: AlCalzone <[email protected]>

---------

Co-authored-by: AlCalzone <[email protected]>
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
320 changed files with 12645 additions and 7365 deletions
+8 -1
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
+2 -2
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
+3 -3
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
@@ -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
+3 -3
View File
@@ -36,14 +36,14 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
uses: github/codeql-action/init@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
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@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
uses: github/codeql-action/autobuild@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
# ️ 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@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
uses: github/codeql-action/analyze@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
+2 -2
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
+1 -1
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
+1 -1
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
+1 -1
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
+1 -1
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@6a93d829887aa2e0748befe2e808c66c0ec6e4c7 # v6.4.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+2 -2
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
@@ -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
+6
View File
@@ -57,6 +57,12 @@ const runDevServer = async ({
directory: contentBase,
watch: true,
},
client: {
overlay: {
runtimeErrors: (error) =>
!error?.message?.includes("ResizeObserver loop"),
},
},
proxy,
},
compiler
+3 -4
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",
+1 -2
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",
+1 -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",
+1 -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",
+5 -6
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,
+1 -2
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",
+2 -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;
}
+2 -4
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",
+251 -173
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,
},
},
];
+77 -67
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",
}),
},
];
@@ -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 = [
@@ -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[] = [
@@ -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 = [
+42 -23
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[] = [
+52 -24
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
);
};
@@ -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)
+29 -14
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 = [
+68 -33
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
+28 -13
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 = [
+245 -138
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 = [
@@ -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 = [
+106 -63
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 = [
+32 -12
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: `
+78 -51
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 = [
@@ -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 = [
+33 -18
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 = [
+76 -49
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 = [
+8 -5
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 = [
@@ -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 = [
@@ -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 = [
@@ -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 = [
+112 -89
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 = [
+148 -97
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 = [
+15 -8
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 = [
+124 -97
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>
`;
}
+179 -128
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>
`;
}
+13 -10
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>
`;
}
+28 -17
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>
`;
}
+27 -20
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>
`;
}
+9 -6
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>
`;
}
+162 -127
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>
`;
}
+16 -9
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>
`;
}
+1 -1
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>
`;
}
+53 -38
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>
`;
}
+16 -9
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>
`;
}
+9 -6
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>
`;
}
+203 -120
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>
`;
}
+12 -9
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>
`;
}
+36 -29
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>
`;
}
+1 -1
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");
+24 -25
View File
@@ -28,31 +28,31 @@
"dependencies": {
"@babel/runtime": "7.28.6",
"@braintree/sanitize-url": "7.1.2",
"@codemirror/autocomplete": "6.20.0",
"@codemirror/autocomplete": "6.20.1",
"@codemirror/commands": "6.10.2",
"@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.15",
"@codemirror/view": "6.39.17",
"@date-fns/tz": "1.4.1",
"@egjs/hammerjs": "2.0.17",
"@formatjs/intl-datetimeformat": "7.2.2",
"@formatjs/intl-displaynames": "7.2.1",
"@formatjs/intl-datetimeformat": "7.2.5",
"@formatjs/intl-displaynames": "7.2.2",
"@formatjs/intl-durationformat": "0.10.1",
"@formatjs/intl-getcanonicallocales": "3.2.1",
"@formatjs/intl-listformat": "8.2.1",
"@formatjs/intl-listformat": "8.2.2",
"@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-numberformat": "9.2.3",
"@formatjs/intl-pluralrules": "6.2.3",
"@formatjs/intl-relativetimeformat": "12.2.3",
"@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.3",
"@home-assistant/webawesome": "3.3.1",
"@lezer/highlight": "1.2.3",
"@lit-labs/motion": "1.1.0",
"@lit-labs/observers": "2.1.0",
@@ -72,7 +72,6 @@
"@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",
@@ -92,7 +91,7 @@
"@webcomponents/scoped-custom-element-registry": "0.0.10",
"@webcomponents/webcomponentsjs": "2.8.0",
"app-datepicker": "5.1.1",
"barcode-detector": "3.1.0",
"barcode-detector": "3.1.1",
"color-name": "2.1.0",
"comlink": "4.4.2",
"core-js": "3.48.0",
@@ -118,7 +117,7 @@
"lit": "3.3.2",
"lit-html": "3.3.2",
"luxon": "3.7.2",
"marked": "17.0.3",
"marked": "17.0.4",
"memoize-one": "6.0.0",
"node-vibrant": "4.0.4",
"object-hash": "3.0.0",
@@ -145,17 +144,17 @@
},
"devDependencies": {
"@babel/core": "7.29.0",
"@babel/helper-define-polyfill-provider": "0.6.6",
"@babel/helper-define-polyfill-provider": "0.6.7",
"@babel/plugin-transform-runtime": "7.29.0",
"@babel/preset-env": "7.29.0",
"@bundle-stats/plugin-webpack-filter": "4.21.10",
"@html-eslint/eslint-plugin": "0.57.1",
"@html-eslint/eslint-plugin": "0.58.1",
"@lokalise/node-api": "15.6.1",
"@octokit/auth-oauth-device": "8.0.3",
"@octokit/plugin-retry": "8.1.0",
"@octokit/rest": "22.0.1",
"@rsdoctor/rspack-plugin": "1.5.2",
"@rspack/core": "1.7.6",
"@rspack/core": "1.7.8",
"@rspack/dev-server": "1.2.1",
"@types/babel__plugin-transform-runtime": "7.9.5",
"@types/chromecast-caf-receiver": "6.0.25",
@@ -176,11 +175,11 @@
"@types/ua-parser-js": "0.7.39",
"@types/webspeechapi": "0.0.29",
"@vitest/coverage-v8": "4.0.18",
"babel-loader": "10.0.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.3",
"eslint": "9.39.4",
"eslint-config-airbnb-base": "15.0.0",
"eslint-config-prettier": "10.1.8",
"eslint-import-resolver-webpack": "0.13.10",
@@ -190,7 +189,7 @@
"eslint-plugin-unused-imports": "4.4.1",
"eslint-plugin-wc": "3.1.0",
"fancy-log": "2.0.0",
"fs-extra": "11.3.3",
"fs-extra": "11.3.4",
"glob": "13.0.6",
"gulp": "5.0.1",
"gulp-brotli": "3.0.0",
@@ -200,7 +199,7 @@
"husky": "9.1.7",
"jsdom": "28.1.0",
"jszip": "3.10.1",
"lint-staged": "16.3.0",
"lint-staged": "16.3.3",
"lit-analyzer": "2.0.3",
"lodash.merge": "4.6.2",
"lodash.template": "4.5.0",
@@ -208,13 +207,13 @@
"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.9",
"terser-webpack-plugin": "5.3.16",
"serve": "14.2.6",
"sinon": "21.0.2",
"tar": "7.5.11",
"terser-webpack-plugin": "5.3.17",
"ts-lit-plugin": "2.0.2",
"typescript": "5.9.3",
"typescript-eslint": "8.56.1",
"typescript-eslint": "8.57.0",
"vite-tsconfig-paths": "6.1.1",
"vitest": "4.0.18",
"webpack-stats-plugin": "1.1.3",
@@ -228,7 +227,7 @@
"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"
+10 -2
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;
}
+7 -2
View File
@@ -93,8 +93,13 @@ export const calcDateRange = (
];
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),
hass.locale,
hass.config
),
calcDate(today, endOfMonth, hass.locale, hass.config),
];
case "now-1h":
return [
+15
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,
+5 -5
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;
}
+24 -10
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;
};
+14 -4
View File
@@ -877,10 +877,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,
+5 -5
View File
@@ -507,7 +507,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
@@ -640,11 +639,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 &&
@@ -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(
+16 -10
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}
-2
View File
@@ -15,7 +15,6 @@ import { iconColorCSS } from "../../common/style/icon_color_css";
import { cameraUrlWithWidthHeight } from "../../data/camera";
import { CLIMATE_HVAC_ACTION_TO_MODE } from "../../data/climate";
import type { HomeAssistant } from "../../types";
import { addBrandsAuth } from "../../util/brands-url";
import "../ha-state-icon";
@customElement("state-badge")
@@ -138,7 +137,6 @@ export class StateBadge extends LitElement {
let imageUrl =
stateObj.attributes.entity_picture_local ||
stateObj.attributes.entity_picture;
imageUrl = addBrandsAuth(imageUrl);
if (this.hass) {
imageUrl = this.hass.hassUrl(imageUrl);
}
+64 -20
View File
@@ -1,3 +1,4 @@
import { mdiDragHorizontalVariant } from "@mdi/js";
import type { HassEntity } from "home-assistant-js-websocket";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
@@ -6,6 +7,7 @@ import { SubscribeMixin } from "../mixins/subscribe-mixin";
import type { HomeAssistant } from "../types";
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
import "./ha-area-picker";
import "./ha-sortable";
@customElement("ha-areas-picker")
export class HaAreasPicker extends SubscribeMixin(LitElement) {
@@ -62,6 +64,8 @@ export class HaAreasPicker extends SubscribeMixin(LitElement) {
@property({ type: Boolean }) public required = false;
@property({ type: Boolean }) public reorder = false;
protected render() {
if (!this.hass) {
return nothing;
@@ -69,26 +73,42 @@ export class HaAreasPicker extends SubscribeMixin(LitElement) {
const currentAreas = this._currentAreas;
return html`
${currentAreas.map(
(area) => html`
<div>
<ha-area-picker
.curValue=${area}
.noAdd=${this.noAdd}
.hass=${this.hass}
.value=${area}
.label=${this.pickedAreaLabel}
.includeDomains=${this.includeDomains}
.excludeDomains=${this.excludeDomains}
.includeDeviceClasses=${this.includeDeviceClasses}
.deviceFilter=${this.deviceFilter}
.entityFilter=${this.entityFilter}
.disabled=${this.disabled}
@value-changed=${this._areaChanged}
></ha-area-picker>
</div>
`
)}
<ha-sortable
.disabled=${!this.reorder || this.disabled}
handle-selector=".area-handle"
@item-moved=${this._areaMoved}
>
<div class="list">
${currentAreas.map(
(area) => html`
<div class="area">
<ha-area-picker
.curValue=${area}
.noAdd=${this.noAdd}
.hass=${this.hass}
.value=${area}
.label=${this.pickedAreaLabel}
.includeDomains=${this.includeDomains}
.excludeDomains=${this.excludeDomains}
.includeDeviceClasses=${this.includeDeviceClasses}
.deviceFilter=${this.deviceFilter}
.entityFilter=${this.entityFilter}
.disabled=${this.disabled}
@value-changed=${this._areaChanged}
></ha-area-picker>
${this.reorder
? html`
<ha-svg-icon
class="area-handle"
.path=${mdiDragHorizontalVariant}
></ha-svg-icon>
`
: nothing}
</div>
`
)}
</div>
</ha-sortable>
<div>
<ha-area-picker
.noAdd=${this.noAdd}
@@ -110,6 +130,17 @@ export class HaAreasPicker extends SubscribeMixin(LitElement) {
`;
}
private _areaMoved(e: CustomEvent) {
e.stopPropagation();
const { oldIndex, newIndex } = e.detail;
const currentAreas = this._currentAreas;
const movedArea = currentAreas[oldIndex];
const newAreas = [...currentAreas];
newAreas.splice(oldIndex, 1);
newAreas.splice(newIndex, 0, movedArea);
this._updateAreas(newAreas);
}
private get _currentAreas(): string[] {
return this.value || [];
}
@@ -159,6 +190,19 @@ export class HaAreasPicker extends SubscribeMixin(LitElement) {
div {
margin-top: 8px;
}
.area {
display: flex;
flex-direction: row;
align-items: center;
}
.area ha-area-picker {
flex: 1;
}
.area-handle {
padding: 8px;
cursor: move; /* fallback if grab cursor is unsupported */
cursor: grab;
}
`;
}
+5 -1
View File
@@ -360,7 +360,11 @@ export class HaBaseTimeInput extends LitElement {
text-align: center;
--mdc-shape-small: 0;
--text-field-appearance: none;
--text-field-padding: 0 4px;
--text-field-padding-top: 0;
--text-field-padding-bottom: 0;
--text-field-padding-start: 4px;
--text-field-padding-end: 4px;
--text-field-suffix-padding-left: 2px;
--text-field-suffix-padding-right: 0;
--text-field-text-align: center;
+9 -2
View File
@@ -90,7 +90,7 @@ export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
await this.updateComplete;
requestAnimationFrame(() => {
if (this.hass && isIosApp(this.hass)) {
if (this.hass && isIosApp(this.hass.auth.external)) {
const element = this.renderRoot.querySelector("[autofocus]");
if (element !== null) {
if (!element.id) {
@@ -401,7 +401,14 @@ export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
--hide-duration: ${BOTTOM_SHEET_ANIMATION_DURATION_MS}ms;
}
wa-drawer::part(dialog) {
max-height: var(--ha-bottom-sheet-max-height, 90vh);
max-height: min(
var(--ha-bottom-sheet-max-height, 90vh),
calc(100vh - max(var(--safe-area-inset-top), 48px))
);
max-height: min(
var(--ha-bottom-sheet-max-height, 90dvh),
calc(100dvh - max(var(--safe-area-inset-top), 48px))
);
align-items: center;
transform: var(--dialog-transform);
transition: var(--dialog-transition);
+2 -1
View File
@@ -58,7 +58,8 @@ export class HaButton extends Button {
font-size: var(--ha-font-size-m);
line-height: 1;
transition: background-color 0.15s ease-in-out;
transition: background-color var(--ha-animation-duration-fast)
ease-out;
text-wrap: wrap;
}
+200 -3
View File
@@ -1,3 +1,5 @@
import "@home-assistant/webawesome/dist/components/popup/popup";
import type WaPopup from "@home-assistant/webawesome/dist/components/popup/popup";
import type {
Completion,
CompletionContext,
@@ -110,6 +112,18 @@ export class HaCodeEditor extends ReactiveElement {
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
private _loadedCodeMirror?: typeof import("../resources/codemirror");
private _completionInfoPopover?: WaPopup;
private _completionInfoContainer?: HTMLDivElement;
private _completionInfoDestroy?: () => void;
private _completionInfoRequest = 0;
private _completionInfoKey?: string;
private _completionInfoFrame?: number;
private _editorToolbar?: HaIconButtonToolbar;
private _iconList?: Completion[];
@@ -155,6 +169,14 @@ export class HaCodeEditor extends ReactiveElement {
public disconnectedCallback() {
fireEvent(this, "dialog-set-fullscreen", false);
this._clearCompletionInfo();
if (this._completionInfoFrame !== undefined) {
cancelAnimationFrame(this._completionInfoFrame);
this._completionInfoFrame = undefined;
}
this._completionInfoPopover?.remove();
this._completionInfoPopover = undefined;
this._completionInfoContainer = undefined;
super.disconnectedCallback();
this.removeEventListener("keydown", stopPropagation);
this.removeEventListener("keydown", this._handleKeyDown);
@@ -288,6 +310,9 @@ export class HaCodeEditor extends ReactiveElement {
this._loadedCodeMirror.foldingCompartment.of(
this._getFoldingExtensions()
),
this._loadedCodeMirror.tooltips({
position: "absolute",
}),
...(this.placeholder ? [placeholder(this.placeholder)] : []),
];
@@ -595,6 +620,157 @@ export class HaCodeEditor extends ReactiveElement {
return completionInfo;
};
private _getCompletionInfo = (
completion: Completion
): CompletionInfo | Promise<CompletionInfo> | null => {
if (this.hass && completion.label in this.hass.states) {
return this._renderInfo(completion);
}
if (completion.label.startsWith("mdi:")) {
return renderIcon(completion);
}
return null;
};
private _ensureCompletionInfoPopover(): WaPopup {
if (!this._completionInfoPopover) {
this._completionInfoPopover = document.createElement(
"wa-popup"
) as WaPopup;
this._completionInfoPopover.classList.add("completion-info-popover");
this._completionInfoPopover.placement = "right-start";
this._completionInfoPopover.distance = 4;
this._completionInfoPopover.flip = true;
this._completionInfoPopover.flipFallbackPlacements =
"left-start bottom-start top-start";
this._completionInfoPopover.shift = true;
this._completionInfoPopover.shiftPadding = 8;
this._completionInfoPopover.autoSize = "both";
this._completionInfoPopover.autoSizePadding = 8;
this._completionInfoContainer = document.createElement("div");
this._completionInfoPopover.appendChild(this._completionInfoContainer);
this.renderRoot.appendChild(this._completionInfoPopover);
}
return this._completionInfoPopover;
}
private _clearCompletionInfo() {
this._completionInfoRequest += 1;
this._completionInfoKey = undefined;
this._completionInfoDestroy?.();
this._completionInfoDestroy = undefined;
this._completionInfoContainer?.replaceChildren();
if (this._completionInfoPopover?.active) {
this._completionInfoPopover.active = false;
}
}
private _renderCompletionInfoContent(info: CompletionInfo) {
this._completionInfoDestroy?.();
this._completionInfoDestroy = undefined;
if (!this._completionInfoContainer) {
return;
}
if (info === null) {
this._completionInfoContainer.replaceChildren();
return;
}
if ("nodeType" in info) {
this._completionInfoContainer.replaceChildren(info);
return;
}
this._completionInfoContainer.replaceChildren(info.dom);
this._completionInfoDestroy = info.destroy;
}
private _syncCompletionInfoPopover = () => {
if (this._completionInfoFrame !== undefined) {
cancelAnimationFrame(this._completionInfoFrame);
}
this._completionInfoFrame = requestAnimationFrame(() => {
this._completionInfoFrame = undefined;
this._syncCompletionInfoPopoverNow();
});
};
private _syncCompletionInfoPopoverNow = () => {
if (!this.codemirror || !this._loadedCodeMirror) {
return;
}
if (window.matchMedia("(max-width: 600px)").matches) {
this._clearCompletionInfo();
return;
}
const completion = this._loadedCodeMirror.selectedCompletion(
this.codemirror.state
);
const selectedOption = this.codemirror.dom.querySelector(
".cm-tooltip-autocomplete li[aria-selected]"
) as HTMLElement | null;
if (!completion || !selectedOption) {
this._clearCompletionInfo();
return;
}
const infoResult = this._getCompletionInfo(completion);
if (!infoResult) {
this._clearCompletionInfo();
return;
}
const requestId = ++this._completionInfoRequest;
const infoKey = completion.label;
const popover = this._ensureCompletionInfoPopover();
popover.anchor = selectedOption;
const showPopover = async (info: CompletionInfo) => {
if (requestId !== this._completionInfoRequest) {
if (info && typeof info === "object" && "destroy" in info) {
info.destroy?.();
}
return;
}
if (infoKey !== this._completionInfoKey) {
this._renderCompletionInfoContent(info);
this._completionInfoKey = infoKey;
}
await popover.updateComplete;
popover.active = true;
popover.reposition();
};
if ("then" in infoResult) {
infoResult.then(showPopover).catch(() => {
if (requestId === this._completionInfoRequest) {
this._clearCompletionInfo();
}
});
return;
}
showPopover(infoResult).catch(() => {
if (requestId === this._completionInfoRequest) {
this._clearCompletionInfo();
}
});
};
private _getStates = memoizeOne((states: HassEntities): Completion[] => {
if (!states) {
return [];
@@ -604,7 +780,6 @@ export class HaCodeEditor extends ReactiveElement {
type: "variable",
label: key,
detail: states[key].attributes.friendly_name,
info: this._renderInfo,
}));
return options;
@@ -778,7 +953,6 @@ export class HaCodeEditor extends ReactiveElement {
type: "variable",
label: `mdi:${icon.name}`,
detail: icon.keywords.join(", "),
info: renderIcon,
}));
}
@@ -806,6 +980,7 @@ export class HaCodeEditor extends ReactiveElement {
private _onUpdate = (update: ViewUpdate): void => {
this._canUndo = !this.readOnly && undoDepth(update.state) > 0;
this._canRedo = !this.readOnly && redoDepth(update.state) > 0;
this._syncCompletionInfoPopover();
if (!update.docChanged) {
return;
}
@@ -925,9 +1100,31 @@ export class HaCodeEditor extends ReactiveElement {
padding: 8px;
}
wa-popup.completion-info-popover {
--auto-size-available-width: min(
420px,
calc(var(--safe-width) - var(--ha-space-8))
);
}
wa-popup.completion-info-popover::part(popup) {
padding: 0;
color: var(--primary-text-color);
background-color: var(
--code-editor-background-color,
var(--card-background-color)
);
border: 1px solid var(--divider-color);
border-radius: var(--mdc-shape-medium, 4px);
box-shadow:
0px 5px 5px -3px rgb(0 0 0 / 20%),
0px 8px 10px 1px rgb(0 0 0 / 14%),
0px 3px 14px 2px rgb(0 0 0 / 12%);
}
/* Hide completion info on narrow screens */
@media (max-width: 600px) {
.cm-completionInfo,
wa-popup.completion-info-popover,
.completion-info {
display: none;
}
+20 -16
View File
@@ -1,20 +1,20 @@
import { consume, type ContextType } from "@lit/context";
import { mdiInvertColorsOff, mdiPalette } from "@mdi/js";
import { html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { customElement, property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import memoizeOne from "memoize-one";
import { computeCssColor, THEME_COLORS } from "../common/color/compute-color";
import { fireEvent } from "../common/dom/fire_event";
import type { LocalizeKeys } from "../common/translations/localize";
import type { HomeAssistant, ValueChangedEvent } from "../types";
import { localizeContext } from "../data/context";
import type { ValueChangedEvent } from "../types";
import "./ha-generic-picker";
import type { PickerComboBoxItem } from "./ha-picker-combo-box";
import type { PickerValueRenderer } from "./ha-picker-field";
@customElement("ha-color-picker")
export class HaColorPicker extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public label?: string;
@property() public helper?: string;
@@ -34,12 +34,15 @@ export class HaColorPicker extends LitElement {
@property({ type: Boolean }) public required = false;
@state()
@consume({ context: localizeContext, subscribe: true })
private localize!: ContextType<typeof localizeContext>;
render() {
const effectiveValue = this.value ?? this.defaultColor ?? "";
return html`
<ha-generic-picker
.hass=${this.hass}
.disabled=${this.disabled}
.required=${this.required}
.hideClearIcon=${!this.value && !!this.defaultColor}
@@ -50,7 +53,7 @@ export class HaColorPicker extends LitElement {
.rowRenderer=${this._rowRenderer}
.valueRenderer=${this._valueRenderer}
@value-changed=${this._valueChanged}
.notFoundLabel=${this.hass.localize(
.notFoundLabel=${this.localize?.(
"ui.components.color-picker.no_colors_found"
)}
.getAdditionalItems=${this._getAdditionalItems}
@@ -78,7 +81,9 @@ export class HaColorPicker extends LitElement {
return [
{
id: searchString,
primary: this.hass.localize("ui.components.color-picker.custom_color"),
primary:
this.localize?.("ui.components.color-picker.custom_color") ||
"Custom color",
secondary: searchString,
},
];
@@ -101,16 +106,15 @@ export class HaColorPicker extends LitElement {
): PickerComboBoxItem[] => {
const items: PickerComboBoxItem[] = [];
const defaultSuffix = this.hass.localize(
"ui.components.color-picker.default"
);
const defaultSuffix =
this.localize?.("ui.components.color-picker.default") || "Default";
const addDefaultSuffix = (label: string, isDefault: boolean) =>
isDefault && defaultSuffix ? `${label} (${defaultSuffix})` : label;
if (includeNone) {
const noneLabel =
this.hass.localize("ui.components.color-picker.none") || "None";
this.localize?.("ui.components.color-picker.none") || "None";
items.push({
id: "none",
primary: addDefaultSuffix(noneLabel, defaultColor === "none"),
@@ -120,7 +124,7 @@ export class HaColorPicker extends LitElement {
if (includeState) {
const stateLabel =
this.hass.localize("ui.components.color-picker.state") || "State";
this.localize?.("ui.components.color-picker.state") || "State";
items.push({
id: "state",
primary: addDefaultSuffix(stateLabel, defaultColor === "state"),
@@ -130,7 +134,7 @@ export class HaColorPicker extends LitElement {
Array.from(THEME_COLORS).forEach((color) => {
const themeLabel =
this.hass.localize(
this.localize?.(
`ui.components.color-picker.colors.${color}` as LocalizeKeys
) || color;
items.push({
@@ -184,7 +188,7 @@ export class HaColorPicker extends LitElement {
return html`
<ha-svg-icon slot="start" .path=${mdiInvertColorsOff}></ha-svg-icon>
<span slot="headline">
${this.hass.localize("ui.components.color-picker.none")}
${this.localize?.("ui.components.color-picker.none") || "None"}
</span>
`;
}
@@ -192,7 +196,7 @@ export class HaColorPicker extends LitElement {
return html`
<ha-svg-icon slot="start" .path=${mdiPalette}></ha-svg-icon>
<span slot="headline">
${this.hass.localize("ui.components.color-picker.state")}
${this.localize?.("ui.components.color-picker.state") || "State"}
</span>
`;
}
@@ -200,7 +204,7 @@ export class HaColorPicker extends LitElement {
return html`
<span slot="start">${this._renderColorCircle(value)}</span>
<span slot="headline">
${this.hass.localize(
${this.localize?.(
`ui.components.color-picker.colors.${value}` as LocalizeKeys
) || value}
</span>
+13 -6
View File
@@ -1,5 +1,6 @@
import "@home-assistant/webawesome/dist/components/dialog/dialog";
import type WaDialog from "@home-assistant/webawesome/dist/components/dialog/dialog";
import { consume, type ContextType } from "@lit/context";
import { mdiClose } from "@mdi/js";
import { css, html, LitElement, nothing } from "lit";
import {
@@ -13,9 +14,9 @@ import { ifDefined } from "lit/directives/if-defined";
import type { HASSDomEvent } from "../common/dom/fire_event";
import { fireEvent } from "../common/dom/fire_event";
import { withViewTransition } from "../common/util/view-transition";
import { authContext, localizeContext } from "../data/context";
import { ScrollableFadeMixin } from "../mixins/scrollable-fade-mixin";
import { haStyleScrollbar } from "../resources/styles";
import type { HomeAssistant } from "../types";
import { isIosApp } from "../util/is_ios";
import "./ha-dialog-header";
import "./ha-icon-button";
@@ -84,8 +85,6 @@ type DialogHideEvent = CustomEvent<{ source?: Element }>;
*/
@customElement("ha-dialog")
export class HaDialog extends ScrollableFadeMixin(LitElement) {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: "aria-labelledby" })
public ariaLabelledBy?: string;
@@ -124,6 +123,14 @@ export class HaDialog extends ScrollableFadeMixin(LitElement) {
@query(".body") public bodyContainer!: HTMLDivElement;
@state()
@consume({ context: localizeContext, subscribe: true })
private localize!: ContextType<typeof localizeContext>;
@state()
@consume({ context: authContext, subscribe: true })
private auth?: ContextType<typeof authContext>;
@state()
private _bodyScrolled = false;
@@ -177,7 +184,7 @@ export class HaDialog extends ScrollableFadeMixin(LitElement) {
<slot name="headerNavigationIcon" slot="navigationIcon">
<ha-icon-button
data-dialog="close"
.label=${this.hass?.localize("ui.common.close") ?? "Close"}
.label=${this.localize?.("ui.common.close") ?? "Close"}
.path=${mdiClose}
></ha-icon-button>
</slot>
@@ -214,13 +221,13 @@ export class HaDialog extends ScrollableFadeMixin(LitElement) {
await this.updateComplete;
requestAnimationFrame(() => {
if (this.hass && isIosApp(this.hass)) {
if (this.auth?.external && isIosApp(this.auth.external)) {
const element = this.querySelector("[autofocus]");
if (element !== null) {
if (!element.id) {
element.id = "ha-dialog-autofocus";
}
this.hass?.auth.external?.fireMessage({
this.auth.external.fireMessage({
type: "focus_element",
payload: {
element_id: element.id,
+8 -5
View File
@@ -61,11 +61,14 @@ export class HaDomainIcon extends LitElement {
`;
}
if (this.brandFallback) {
const image = brandsUrl({
domain: this.domain!,
type: "icon",
darkOptimized: this.hass.themes?.darkMode,
});
const image = brandsUrl(
{
domain: this.domain!,
type: "icon",
darkOptimized: this.hass.themes?.darkMode,
},
this.hass.auth.data.hassUrl
);
return html`
<img
alt=""
+12 -15
View File
@@ -1,6 +1,6 @@
import { consume } from "@lit/context";
import type { SelectedDetail } from "@material/mwc-list";
import { mdiCog, mdiFilterVariantRemove } from "@mdi/js";
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
import type { CSSResultGroup } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
@@ -11,8 +11,7 @@ import { fireEvent } from "../common/dom/fire_event";
import { navigate } from "../common/navigate";
import { stringCompare } from "../common/string/compare";
import type { LabelRegistryEntry } from "../data/label/label_registry";
import { subscribeLabelRegistry } from "../data/label/label_registry";
import { SubscribeMixin } from "../mixins/subscribe-mixin";
import { labelsContext } from "../data/context";
import { haStyleScrollbar } from "../resources/styles";
import type { HomeAssistant } from "../types";
import "./ha-check-list-item";
@@ -25,7 +24,7 @@ import "./ha-list-item";
import "./search-input-outlined";
@customElement("ha-filter-labels")
export class HaFilterLabels extends SubscribeMixin(LitElement) {
export class HaFilterLabels extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public value?: string[];
@@ -34,20 +33,14 @@ export class HaFilterLabels extends SubscribeMixin(LitElement) {
@property({ type: Boolean, reflect: true }) public expanded = false;
@state() private _labels: LabelRegistryEntry[] = [];
@consume({ context: labelsContext, subscribe: true })
@state()
private _labels?: LabelRegistryEntry[];
@state() private _shouldRender = false;
@state() private _filter?: string;
protected hassSubscribe(): (UnsubscribeFunc | Promise<UnsubscribeFunc>)[] {
return [
subscribeLabelRegistry(this.hass.connection, (labels) => {
this._labels = labels;
}),
];
}
private _filteredLabels = memoizeOne(
// `_value` used to recalculate the memoization when the selection changes
(labels: LabelRegistryEntry[], filter: string | undefined, _value) =>
@@ -98,7 +91,11 @@ export class HaFilterLabels extends SubscribeMixin(LitElement) {
multi
>
${repeat(
this._filteredLabels(this._labels, this._filter, this.value),
this._filteredLabels(
this._labels || [],
this._filter,
this.value
),
(label) => label.label_id,
(label) => {
const color = label.color
@@ -172,7 +169,7 @@ export class HaFilterLabels extends SubscribeMixin(LitElement) {
private async _labelSelected(ev: CustomEvent<SelectedDetail<Set<number>>>) {
const filteredLabels = this._filteredLabels(
this._labels,
this._labels || [],
this._filter,
this.value
);
+134 -96
View File
@@ -44,16 +44,16 @@ export class HaGauge extends LitElement {
@state() private _updated = false;
@state() private _segment_label? = "";
@state() private _segment_label?: string = "";
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
// Wait for the first render for the initial animation to work
afterNextRender(() => {
this._updated = true;
this._angle = getAngle(this.value, this.min, this.max);
if (this.needle) {
this._angle = getAngle(this.value, this.min, this.max);
}
this._segment_label = this._getSegmentLabel();
this._rescaleSvg();
});
}
@@ -70,70 +70,121 @@ export class HaGauge extends LitElement {
}
this._angle = getAngle(this.value, this.min, this.max);
this._segment_label = this._getSegmentLabel();
this._rescaleSvg();
}
protected render() {
const arcRadius = 40;
const arcLength = Math.PI * arcRadius;
const valueAngle = getAngle(this.value, this.min, this.max);
const strokeOffset = arcLength * (1 - valueAngle / 180);
return svg`
<svg viewBox="-50 -50 100 50" class="gauge">
${
!this.needle || !this.levels
? svg`<path
class="dial"
<svg viewBox="-50 -50 100 60" class="gauge">
<path
class="levels-base"
d="M -40 0 A 40 40 0 0 1 40 0"
></path>`
: ""
}
/>
${
this.levels
? [...this.levels]
.sort((a, b) => a.level - b.level)
.map((level, i, arr) => {
const startLevel = i === 0 ? this.min : arr[i].level;
const endLevel = i + 1 < arr.length ? arr[i + 1].level : this.max;
const startAngle = getAngle(startLevel, this.min, this.max);
const endAngle = getAngle(endLevel, this.min, this.max);
const largeArc = endAngle - startAngle > 180 ? 1 : 0;
const x1 = -arcRadius * Math.cos((startAngle * Math.PI) / 180);
const y1 = -arcRadius * Math.sin((startAngle * Math.PI) / 180);
const x2 = -arcRadius * Math.cos((endAngle * Math.PI) / 180);
const y2 = -arcRadius * Math.sin((endAngle * Math.PI) / 180);
const firstSegment = i === 0;
const lastSegment = i === arr.length - 1;
const paths: TemplateResult[] = [];
if (firstSegment) {
paths.push(svg`
<path
class="level"
stroke="${level.stroke}"
style="stroke-linecap: round"
d="M ${x1} ${y1} A ${arcRadius} ${arcRadius} 0 ${largeArc} 1 ${x2} ${y2}"
/>
`);
} else if (lastSegment) {
const offsetAngle = 0.5;
const midAngle = endAngle - offsetAngle;
const xm = -arcRadius * Math.cos((midAngle * Math.PI) / 180);
const ym = -arcRadius * Math.sin((midAngle * Math.PI) / 180);
paths.push(svg`
<path
class="level"
stroke="${level.stroke}"
style="stroke-linecap: butt"
d="M ${x1} ${y1} A ${arcRadius} ${arcRadius} 0 ${largeArc} 1 ${xm} ${ym}"
/>
`);
paths.push(svg`
<path
class="level"
stroke="${level.stroke}"
style="stroke-linecap: round"
d="M ${xm} ${ym} A ${arcRadius} ${arcRadius} 0 0 1 ${x2} ${y2}"
/>
`);
} else {
paths.push(svg`
<path
class="level"
stroke="${level.stroke}"
style="stroke-linecap: butt"
d="M ${x1} ${y1} A ${arcRadius} ${arcRadius} 0 ${largeArc} 1 ${x2} ${y2}"
/>
`);
}
return paths;
})
: ""
}
${
this.levels
? this.levels
.sort((a, b) => a.level - b.level)
.map((level, idx) => {
let firstPath: TemplateResult | undefined;
if (idx === 0 && level.level !== this.min) {
const angle = getAngle(this.min, this.min, this.max);
firstPath = svg`<path
stroke="var(--info-color)"
class="level"
d="M
${0 - 40 * Math.cos((angle * Math.PI) / 180)}
${0 - 40 * Math.sin((angle * Math.PI) / 180)}
A 40 40 0 0 1 40 0
"
></path>`;
}
const angle = getAngle(level.level, this.min, this.max);
return svg`${firstPath}<path
stroke="${level.stroke}"
class="level"
d="M
${0 - 40 * Math.cos((angle * Math.PI) / 180)}
${0 - 40 * Math.sin((angle * Math.PI) / 180)}
A 40 40 0 0 1 40 0
"
></path>`;
})
: ""
}
${
this.needle
? svg`<path
class="needle"
d="M -25 -2.5 L -47.5 0 L -25 2.5 z"
style=${styleMap({ transform: `rotate(${this._angle}deg)` })}
>
? svg`
<line
class="needle"
x1="-35.0"
y1="0"
x2="-45.0"
y2="0"
style=${styleMap({ transform: `rotate(${this._angle}deg)` })}
/>
`
: svg`
<path
class="value"
d="M -40 0 A 40 40 0 0 1 40 0"
stroke-dasharray="${arcLength}"
style=${styleMap({ strokeDashoffset: `${strokeOffset}` })}
/>
`
: svg`<path
class="value"
d="M -40 0 A 40 40 0 1 0 40 0"
style=${styleMap({ transform: `rotate(${this._angle}deg)` })}
>`
}
</path>
</svg>
<svg class="text">
<text class="value-text">
<text
class="value-text"
x="0"
y="-10"
dominant-baseline="middle"
text-anchor="middle"
>
${
this._segment_label
? this._segment_label
@@ -147,24 +198,13 @@ export class HaGauge extends LitElement {
: ` ${this.label}`
}
</text>
</svg>`;
}
private _rescaleSvg() {
// Set the viewbox of the SVG containing the value to perfectly
// fit the text
// That way it will auto-scale correctly
const svgRoot = this.shadowRoot!.querySelector(".text")!;
const box = svgRoot.querySelector("text")!.getBBox()!;
svgRoot.setAttribute(
"viewBox",
`${box.x} ${box!.y} ${box.width} ${box.height}`
);
</svg>
`;
}
private _getSegmentLabel() {
if (this.levels) {
this.levels.sort((a, b) => a.level - b.level);
[...this.levels].sort((a, b) => a.level - b.level);
for (let i = this.levels.length - 1; i >= 0; i--) {
if (this.value >= this.levels[i].level) {
return this.levels[i].label;
@@ -178,40 +218,38 @@ export class HaGauge extends LitElement {
:host {
position: relative;
}
.dial {
.levels-base {
fill: none;
stroke: var(--primary-background-color);
stroke-width: 15;
}
.value {
fill: none;
stroke-width: 15;
stroke: var(--gauge-color);
transition: all 1s ease 0s;
}
.needle {
fill: var(--primary-text-color);
transition: all 1s ease 0s;
stroke-width: 10;
stroke-linecap: round;
}
.level {
fill: none;
stroke-width: 15;
stroke-width: 10;
stroke-linecap: butt;
}
.gauge {
display: block;
.value {
fill: none;
stroke-width: 10;
stroke: var(--gauge-color);
stroke-linecap: round;
transition: all 1s ease 0s;
}
.text {
position: absolute;
max-height: 40%;
max-width: 55%;
left: 50%;
bottom: -6%;
transform: translate(-50%, 0%);
.needle {
stroke: var(--primary-text-color);
stroke-width: 2;
stroke-linecap: round;
transform-origin: 0 0;
transition: all 1s ease 0s;
}
.value-text {
font-size: 50px;
fill: var(--primary-text-color);
text-anchor: middle;
direction: ltr;
}
`;
+28 -21
View File
@@ -1,5 +1,6 @@
import "@home-assistant/webawesome/dist/components/popover/popover";
import type { RenderItemFunction } from "@lit-labs/virtualizer/virtualize";
import { consume, type ContextType } from "@lit/context";
import { mdiPlaylistPlus } from "@mdi/js";
import {
css,
@@ -13,10 +14,9 @@ import { customElement, property, query, state } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import { tinykeys } from "tinykeys";
import { fireEvent } from "../common/dom/fire_event";
import { throttle } from "../common/util/throttle";
import { authContext } from "../data/context";
import { PickerMixin } from "../mixins/picker-mixin";
import type { FuseWeightedKey } from "../resources/fuseMultiTerm";
import type { HomeAssistant } from "../types";
import { isIosApp } from "../util/is_ios";
import "./ha-bottom-sheet";
import "./ha-button";
@@ -33,8 +33,6 @@ import "./ha-svg-icon";
@customElement("ha-generic-picker")
export class HaGenericPicker extends PickerMixin(LitElement) {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ type: Boolean, attribute: "allow-custom-value" })
public allowCustomValue;
@@ -103,6 +101,8 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
@property({ attribute: "selected-section" }) public selectedSection?: string;
@property({ attribute: false }) public popoverAnchor?: Element | null;
@property({ type: Boolean, attribute: "use-top-label" })
public useTopLabel = false;
@@ -113,6 +113,10 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
@query("ha-picker-combo-box") private _comboBox?: HaPickerComboBox;
@state()
@consume({ context: authContext, subscribe: true })
private auth?: ContextType<typeof authContext>;
@state() private _opened = false;
@state() private _pickerWrapperOpen = false;
@@ -123,6 +127,8 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
@state() private _unknownValue = false;
@state() private _selectedValue?: string;
static shadowRootOptions = {
...LitElement.shadowRootOptions,
delegatesFocus: true,
@@ -142,10 +148,6 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
protected willUpdate(changedProperties: PropertyValues) {
if (changedProperties.has("value")) {
this._setUnknownValue();
return;
}
if (changedProperties.has("hass")) {
this._throttleUnknownValue();
}
}
@@ -227,7 +229,8 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
without-arrow
distance="-4"
.placement=${this.popoverPlacement}
for="picker"
.for=${this.popoverAnchor ? null : "picker"}
.anchor=${this.popoverAnchor ?? null}
auto-size="vertical"
auto-size-padding="16"
@wa-after-show=${this._dialogOpened}
@@ -252,10 +255,9 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
return html`
<ha-picker-combo-box
id="combo-box"
.hass=${this.hass}
.allowCustomValue=${this.allowCustomValue}
.label=${this.searchLabel}
.value=${this.value}
.value=${this._selectedValue ?? this.value}
@value-changed=${this._valueChanged}
.rowRenderer=${this.rowRenderer}
.notFoundLabel=${this.notFoundLabel}
@@ -291,13 +293,6 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
);
};
private _throttleUnknownValue = throttle(
this._setUnknownValue,
1000,
true,
false
);
private _renderHelper() {
const showError = this.invalid && this.errorMessage;
const showHelper = !showError && this.helper;
@@ -321,8 +316,8 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
this._comboBox?.setFieldValue(this._initialFieldValue);
this._initialFieldValue = undefined;
}
if (this.hass && isIosApp(this.hass)) {
this.hass.auth.external!.fireMessage({
if (this.auth?.external && isIosApp(this.auth.external)) {
this.auth.external.fireMessage({
type: "focus_element",
payload: {
element_id: "combo-box",
@@ -344,7 +339,9 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
this._opened = false;
this._pickerWrapperOpen = false;
this._selectedValue = undefined;
this._unsubscribeTinyKeys?.();
fireEvent(this, "picker-closed");
}
private _valueChanged(ev: CustomEvent) {
@@ -367,11 +364,17 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
fireEvent(this, "value-changed", { value });
}
public async open(ev?: Event) {
public async open(
ev?: Event,
options?: {
selectedValue?: string;
}
) {
ev?.stopPropagation();
if (this.disabled) {
return;
}
this._selectedValue = options?.selectedValue;
this._openedNarrow = this._narrow;
this._popoverWidth = this._containerElement?.offsetWidth || 250;
this._pickerWrapperOpen = true;
@@ -476,4 +479,8 @@ declare global {
interface HTMLElementTagNameMap {
"ha-generic-picker": HaGenericPicker;
}
interface HASSDomEvents {
"picker-closed": undefined;
}
}
+1 -4
View File
@@ -5,7 +5,7 @@ import { customElement, property } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../common/dom/fire_event";
import { customIcons } from "../data/custom_icons";
import type { HomeAssistant, ValueChangedEvent } from "../types";
import type { ValueChangedEvent } from "../types";
import "./ha-combo-box-item";
import "./ha-generic-picker";
import "./ha-icon";
@@ -88,8 +88,6 @@ const rowRenderer: RenderItemFunction<PickerComboBoxItem> = (item) => html`
@customElement("ha-icon-picker")
export class HaIconPicker extends LitElement {
@property({ attribute: false }) public hass?: HomeAssistant;
@property() public value?: string;
@property() public label?: string;
@@ -111,7 +109,6 @@ export class HaIconPicker extends LitElement {
protected render(): TemplateResult {
return html`
<ha-generic-picker
.hass=${this.hass}
allow-custom-value
.getItems=${this._getIconPickerItems}
.helper=${this.helper}
+17 -16
View File
@@ -1,5 +1,7 @@
import type { RenderItemFunction } from "@lit-labs/virtualizer/virtualize";
import { consume } from "@lit/context";
import { mdiPlus } from "@mdi/js";
import type { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
import type { HassEntity } from "home-assistant-js-websocket";
import type { TemplateResult } from "lit";
import { LitElement, html, nothing } from "lit";
import {
@@ -10,10 +12,10 @@ import {
state,
} from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import type { RenderItemFunction } from "@lit-labs/virtualizer/virtualize";
import memoizeOne from "memoize-one";
import { computeCssColor } from "../common/color/compute-color";
import { fireEvent } from "../common/dom/fire_event";
import { labelsContext } from "../data/context";
import {
getLabels,
labelComboBoxKeys,
@@ -21,19 +23,17 @@ import {
} from "../data/label/label_picker";
import {
createLabelRegistryEntry,
subscribeLabelRegistry,
type LabelRegistryEntry,
} from "../data/label/label_registry";
import { showAlertDialog } from "../dialogs/generic/show-dialog-box";
import { SubscribeMixin } from "../mixins/subscribe-mixin";
import { showLabelDetailDialog } from "../panels/config/labels/show-dialog-label-detail";
import type { HomeAssistant, ValueChangedEvent } from "../types";
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
import "./ha-generic-picker";
import type { HaGenericPicker } from "./ha-generic-picker";
import {
type PickerComboBoxItem,
DEFAULT_ROW_RENDERER_CONTENT,
type PickerComboBoxItem,
} from "./ha-picker-combo-box";
import "./ha-svg-icon";
@@ -52,7 +52,7 @@ export const renderLabelColorBadge = (color: string | undefined) =>
></div>`;
@customElement("ha-label-picker")
export class HaLabelPicker extends SubscribeMixin(LitElement) {
export class HaLabelPicker extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public label?: string;
@@ -108,7 +108,9 @@ export class HaLabelPicker extends SubscribeMixin(LitElement) {
@property({ type: Boolean }) public required = false;
@state() private _labels?: LabelRegistryEntry[];
@consume({ context: labelsContext, subscribe: true })
@state()
private _labels?: LabelRegistryEntry[];
@queryAssignedElements({ flatten: true })
private _slotNodes?: NodeListOf<HTMLElement>;
@@ -120,14 +122,6 @@ export class HaLabelPicker extends SubscribeMixin(LitElement) {
await this._picker?.open();
}
protected hassSubscribe(): (UnsubscribeFunc | Promise<UnsubscribeFunc>)[] {
return [
subscribeLabelRegistry(this.hass.connection, (labels) => {
this._labels = labels;
}),
];
}
private _rowRenderer: RenderItemFunction<LabelComboBoxItem> = (item) =>
html`<ha-combo-box-item type="button" compact>
${DEFAULT_ROW_RENDERER_CONTENT(item)}
@@ -226,7 +220,10 @@ export class HaLabelPicker extends SubscribeMixin(LitElement) {
.searchKeys=${labelComboBoxKeys}
@value-changed=${this._valueChanged}
>
<slot .slot=${this._slotNodes?.length ? "field" : undefined}></slot>
<slot
@slotchange=${this._handleSlotChange}
.slot=${this._slotNodes?.length ? "field" : undefined}
></slot>
</ha-generic-picker>
`;
}
@@ -280,6 +277,10 @@ export class HaLabelPicker extends SubscribeMixin(LitElement) {
this.hass.localize("ui.components.label-picker.no_match", {
term: html`<b>${search}</b>`,
});
private _handleSlotChange() {
this.requestUpdate();
}
}
declare global {
+16 -22
View File
@@ -1,5 +1,6 @@
import { consume } from "@lit/context";
import { mdiPlaylistPlus } from "@mdi/js";
import type { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
import type { HassEntity } from "home-assistant-js-websocket";
import type { TemplateResult } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
@@ -8,12 +9,9 @@ import memoizeOne from "memoize-one";
import { computeCssColor } from "../common/color/compute-color";
import { fireEvent } from "../common/dom/fire_event";
import { stringCompare } from "../common/string/compare";
import { labelsContext } from "../data/context";
import type { LabelRegistryEntry } from "../data/label/label_registry";
import {
subscribeLabelRegistry,
updateLabelRegistryEntry,
} from "../data/label/label_registry";
import { SubscribeMixin } from "../mixins/subscribe-mixin";
import { updateLabelRegistryEntry } from "../data/label/label_registry";
import { showLabelDetailDialog } from "../panels/config/labels/show-dialog-label-detail";
import type { HomeAssistant, ValueChangedEvent } from "../types";
import "./chips/ha-chip-set";
@@ -24,7 +22,7 @@ import type { HaLabelPicker } from "./ha-label-picker";
import "./ha-tooltip";
@customElement("ha-labels-picker")
export class HaLabelsPicker extends SubscribeMixin(LitElement) {
export class HaLabelsPicker extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public label?: string;
@@ -80,7 +78,9 @@ export class HaLabelsPicker extends SubscribeMixin(LitElement) {
@property({ type: Boolean }) public required = false;
@state() private _labels?: Record<string, LabelRegistryEntry>;
@consume({ context: labelsContext, subscribe: true })
@state()
private _labels?: LabelRegistryEntry[];
@query("ha-label-picker", true) public labelPicker!: HaLabelPicker;
@@ -94,26 +94,20 @@ export class HaLabelsPicker extends SubscribeMixin(LitElement) {
await this.labelPicker?.focus();
}
protected hassSubscribe(): (UnsubscribeFunc | Promise<UnsubscribeFunc>)[] {
return [
subscribeLabelRegistry(this.hass.connection, (labels) => {
const lookUp = {};
labels.forEach((label) => {
lookUp[label.label_id] = label;
});
this._labels = lookUp;
}),
];
}
private _sortedLabels = memoizeOne(
(
value: string[] | undefined,
labels: Record<string, LabelRegistryEntry> | undefined,
labels: LabelRegistryEntry[] | undefined,
language: string
) =>
value
?.map((id) => labels?.[id])
?.map(
(id) =>
labels?.find((label) => label.label_id === id) || {
label_id: id,
name: id,
}
)
.sort((a, b) => stringCompare(a?.name || "", b?.name || "", language))
);
+7 -1
View File
@@ -38,6 +38,8 @@ class HaMultiTextField extends LitElement {
@property({ attribute: "item-index", type: Boolean })
public itemIndex = false;
@property({ type: Number }) public max?: number;
protected render() {
return html`
${this._items.map((item, index) => {
@@ -77,7 +79,8 @@ class HaMultiTextField extends LitElement {
size="small"
appearance="filled"
@click=${this._addItem}
.disabled=${this.disabled}
.disabled=${this.disabled ||
(this.max != null && this._items.length >= this.max)}
>
<ha-svg-icon slot="start" .path=${mdiPlus}></ha-svg-icon>
${this.addLabel ??
@@ -102,6 +105,9 @@ class HaMultiTextField extends LitElement {
}
private async _addItem() {
if (this.max != null && this._items.length >= this.max) {
return;
}
const items = [...this._items, ""];
this._fireChanged(items);
await this.updateComplete;
+23 -17
View File
@@ -1,5 +1,6 @@
import type { LitVirtualizer } from "@lit-labs/virtualizer";
import type { RenderItemFunction } from "@lit-labs/virtualizer/virtualize";
import { consume, type ContextType } from "@lit/context";
import { mdiClose, mdiMagnify, mdiMinusBoxOutline, mdiPlus } from "@mdi/js";
import Fuse from "fuse.js";
import { css, html, LitElement, nothing } from "lit";
@@ -14,6 +15,7 @@ import memoizeOne from "memoize-one";
import { tinykeys } from "tinykeys";
import { fireEvent } from "../common/dom/fire_event";
import { caseInsensitiveStringCompare } from "../common/string/compare";
import { localeContext, localizeContext } from "../data/context";
import { ScrollableFadeMixin } from "../mixins/scrollable-fade-mixin";
import {
multiTermSortedSearch,
@@ -21,7 +23,6 @@ import {
} from "../resources/fuseMultiTerm";
import { haStyleScrollbar } from "../resources/styles";
import { loadVirtualizer } from "../resources/virtualizer";
import type { HomeAssistant } from "../types";
import { isTouch } from "../util/is_touch";
import "./chips/ha-chip-set";
import "./chips/ha-filter-chip";
@@ -90,8 +91,6 @@ export type PickerComboBoxSearchFn<T extends PickerComboBoxItem> = (
@customElement("ha-picker-combo-box")
export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
@property({ attribute: false }) public hass?: HomeAssistant;
// eslint-disable-next-line lit/no-native-attributes
@property({ type: Boolean }) public autofocus = false;
@@ -162,6 +161,14 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
@query("ha-textfield") private _searchFieldElement?: HaTextField;
@state()
@consume({ context: localizeContext, subscribe: true })
private localize!: ContextType<typeof localizeContext>;
@state()
@consume({ context: localeContext, subscribe: true })
private locale!: ContextType<typeof localeContext>;
@state() private _items: PickerComboBoxItem[] = [];
@state() private _selectedSection?: string;
@@ -215,9 +222,9 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
const searchLabel =
this.label ??
(this.allowCustomValue
? (this.hass?.localize("ui.components.combo-box.search_or_custom") ??
? (this.localize?.("ui.components.combo-box.search_or_custom") ??
"Search | Add custom value")
: (this.hass?.localize("ui.common.search") ?? "Search"));
: (this.localize?.("ui.common.search") ?? "Search"));
return html`<ha-textfield
.label=${searchLabel}
@@ -228,7 +235,7 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
<ha-icon-button
@click=${this._clearSearch}
slot="trailingIcon"
.label=${this.hass?.localize("ui.common.clear") || "Clear"}
.label=${this.localize?.("ui.common.clear") || "Clear"}
.path=${mdiClose}
></ha-icon-button>
</ha-textfield>
@@ -350,7 +357,7 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
return caseInsensitiveStringCompare(
sortLabelA,
sortLabelB,
this.hass?.locale.language ?? navigator.language
this.locale?.language ?? navigator.language
);
});
}
@@ -367,7 +374,7 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
id: this._search,
primary:
this.customValueLabel ??
this.hass?.localize("ui.components.combo-box.add_custom_item") ??
this.localize?.("ui.components.combo-box.add_custom_item") ??
"Add custom item",
secondary: `"${this._search}"`,
icon_path: mdiPlus,
@@ -401,10 +408,10 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
? typeof this.notFoundLabel === "function"
? this.notFoundLabel(this._search)
: this.notFoundLabel ||
this.hass?.localize("ui.components.combo-box.no_match") ||
this.localize?.("ui.components.combo-box.no_match") ||
"No matching items found"
: this.emptyLabel ||
this.hass?.localize("ui.components.combo-box.no_items") ||
this.localize?.("ui.components.combo-box.no_items") ||
"No items available"}</span
>
</ha-combo-box-item>
@@ -418,7 +425,7 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
const renderer = this.rowRenderer || DEFAULT_ROW_RENDERER;
return html`<div
id=${`list-item-${index}`}
class="combo-box-row ${this._value === item.id ? "current-value" : ""}"
class="combo-box-row ${this.value === item.id ? "current-value" : ""}"
.value=${item.id}
.index=${index}
@click=${this._valueSelected}
@@ -433,10 +440,6 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
this._listScrolled = top > 0;
}
private get _value() {
return this.value || "";
}
private _valueSelected = (ev: MouseEvent) => {
ev.stopPropagation();
const value = (ev.currentTarget as any).value as string;
@@ -507,7 +510,7 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
id: searchString,
primary:
this.customValueLabel ??
this.hass?.localize("ui.components.combo-box.add_custom_item") ??
this.localize?.("ui.components.combo-box.add_custom_item") ??
"Add custom item",
secondary: `"${searchString}"`,
icon_path: mdiPlus,
@@ -801,7 +804,10 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
}
:host([clearable]) {
--text-field-padding: 0 0 0 var(--ha-space-4);
--text-field-padding-top: 0;
--text-field-padding-bottom: 0;
--text-field-padding-start: var(--ha-space-4);
--text-field-padding-end: 0;
}
ha-textfield {
+16 -10
View File
@@ -220,11 +220,14 @@ export class HaRelatedItems extends LitElement {
>
<ha-list-item hasMeta graphic="icon">
<img
.src=${brandsUrl({
domain: entry.domain,
type: "icon",
darkOptimized: this.hass.themes?.darkMode,
})}
.src=${brandsUrl(
{
domain: entry.domain,
type: "icon",
darkOptimized: this.hass.themes?.darkMode,
},
this.hass.auth.data.hassUrl
)}
crossorigin="anonymous"
referrerpolicy="no-referrer"
alt=${entry.domain}
@@ -245,11 +248,14 @@ export class HaRelatedItems extends LitElement {
>
<ha-list-item hasMeta graphic="icon">
<img
.src=${brandsUrl({
domain: integration,
type: "icon",
darkOptimized: this.hass.themes?.darkMode,
})}
.src=${brandsUrl(
{
domain: integration,
type: "icon",
darkOptimized: this.hass.themes?.darkMode,
},
this.hass.auth.data.hassUrl
)}
crossorigin="anonymous"
referrerpolicy="no-referrer"
alt=${integration}
+8 -2
View File
@@ -214,8 +214,14 @@ export class HaResizableBottomSheet extends LitElement {
}
dialog {
height: var(--height, auto);
max-height: var(--max-height, 70vh);
max-height: var(--max-height, 70dvh);
max-height: min(
var(--max-height, 70vh),
calc(100vh - var(--safe-area-inset-top))
);
max-height: min(
var(--max-height, 70dvh),
calc(100dvh - var(--safe-area-inset-top))
);
min-height: var(--min-height, 30vh);
min-height: var(--min-height, 30dvh);
background-color: var(
@@ -1,23 +1,18 @@
import { consume, ContextProvider } from "@lit/context";
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
import { consume } from "@lit/context";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fullEntitiesContext } from "../../data/context";
import {
subscribeEntityRegistry,
type EntityRegistryEntry,
} from "../../data/entity/entity_registry";
import type { EntityRegistryEntry } from "../../data/entity/entity_registry";
import type { Action } from "../../data/script";
import { migrateAutomationAction } from "../../data/script";
import type { ActionSelector } from "../../data/selector";
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import "../../panels/config/automation/action/ha-automation-action";
import type HaAutomationAction from "../../panels/config/automation/action/ha-automation-action";
import type { HomeAssistant } from "../../types";
@customElement("ha-selector-action")
export class HaActionSelector extends SubscribeMixin(LitElement) {
export class HaActionSelector extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean }) public narrow = false;
@@ -34,13 +29,9 @@ export class HaActionSelector extends SubscribeMixin(LitElement) {
@consume({ context: fullEntitiesContext, subscribe: true })
_entityReg: EntityRegistryEntry[] | undefined;
@state() private _entitiesContext;
@query("ha-automation-action")
private _actionElement?: HaAutomationAction;
protected hassSubscribeRequiredHostProps = ["_entitiesContext"];
private _actions = memoizeOne((action: Action | undefined) => {
if (!action) {
return [];
@@ -48,23 +39,6 @@ export class HaActionSelector extends SubscribeMixin(LitElement) {
return migrateAutomationAction(action);
});
protected firstUpdated() {
if (!this._entityReg) {
this._entitiesContext = new ContextProvider(this, {
context: fullEntitiesContext,
initialValue: [],
});
}
}
public hassSubscribe(): UnsubscribeFunc[] {
return [
subscribeEntityRegistry(this.hass.connection!, (entities) => {
this._entitiesContext.setValue(entities);
}),
];
}
public expandAll() {
this._actionElement?.expandAll();
}
@@ -123,6 +123,7 @@ export class HaAreaSelector extends LitElement {
: undefined}
.disabled=${this.disabled}
.required=${this.required}
.reorder=${this.selector.area?.reorder ?? false}
></ha-areas-picker>
`;
}
@@ -51,7 +51,10 @@ export class HaColorRGBSelector extends LitElement {
align-items: center;
}
ha-textfield {
--text-field-padding: 8px;
--text-field-padding-top: 8px;
--text-field-padding-bottom: 8px;
--text-field-padding-start: 8px;
--text-field-padding-end: 8px;
min-width: 75px;
flex-grow: 1;
margin: 0 4px;
@@ -79,11 +79,14 @@ export class HaMediaSelector extends LitElement {
if (thumbnail && isBrandUrl(thumbnail)) {
// The backend is not aware of the theme used by the users,
// so we rewrite the URL to show a proper icon
this._thumbnailUrl = brandsUrl({
domain: extractDomainFromBrandUrl(thumbnail),
type: "icon",
darkOptimized: this.hass.themes?.darkMode,
});
this._thumbnailUrl = brandsUrl(
{
domain: extractDomainFromBrandUrl(thumbnail),
type: "icon",
darkOptimized: this.hass.themes?.darkMode,
},
this.hass.auth.data.hassUrl
);
} else if (thumbnail && thumbnail.startsWith("/")) {
this._thumbnailUrl = undefined;
// Thumbnails served by local API require authentication
+17 -4
View File
@@ -24,8 +24,14 @@ export class HaSlider extends Slider {
--marker-width: calc(var(--ha-slider-track-size, 4px) / 2);
--wa-color-surface-default: var(--card-background-color);
--wa-color-neutral-fill-normal: var(--disabled-color);
--wa-tooltip-background-color: var(--secondary-background-color);
--wa-tooltip-color: var(--primary-text-color);
--wa-tooltip-background-color: var(
--ha-tooltip-background-color,
var(--secondary-background-color)
);
--wa-tooltip-content-color: var(
--ha-tooltip-text-color,
var(--primary-text-color)
);
--wa-tooltip-font-family: var(
--ha-tooltip-font-family,
var(--ha-font-family-body)
@@ -42,18 +48,25 @@ export class HaSlider extends Slider {
--ha-tooltip-line-height,
var(--ha-line-height-condensed)
);
--wa-tooltip-padding: 8px;
--wa-tooltip-padding: var(--ha-tooltip-padding, var(--ha-space-2));
--wa-tooltip-border-radius: var(
--ha-tooltip-border-radius,
var(--ha-border-radius-sm)
);
--wa-tooltip-arrow-size: var(--ha-tooltip-arrow-size, 8px);
--wa-z-index-tooltip: var(--ha-tooltip-z-index, 1000);
--wa-tooltip-border-width: 0px;
--wa-z-index-tooltip: 1000;
min-width: 100px;
min-inline-size: 100px;
width: 200px;
}
/* Expand slider touch target to 32px */
#slider {
padding-block: 14px;
margin-block: -14px;
}
#thumb {
border: none;
background-color: var(--ha-slider-thumb-color, var(--primary-color));
+198 -45
View File
@@ -7,10 +7,11 @@ import Fuse from "fuse.js";
import type { HassServiceTarget } from "home-assistant-js-websocket";
import type { CSSResultGroup, PropertyValues } from "lit";
import { LitElement, css, html, nothing, unsafeCSS } from "lit";
import { customElement, property, state } from "lit/decorators";
import { customElement, property, query, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import memoizeOne from "memoize-one";
import { ensureArray } from "../common/array/ensure-array";
import type { HASSDomEvent } from "../common/dom/fire_event";
import { fireEvent } from "../common/dom/fire_event";
import { isValidEntityId } from "../common/entity/valid_entity_id";
import { caseInsensitiveStringCompare } from "../common/string/compare";
@@ -42,6 +43,7 @@ import {
deviceMeetsFilter,
entityRegMeetsFilter,
getTargetComboBoxItemType,
type TargetItem,
type TargetType,
type TargetTypeFloorless,
} from "../data/target";
@@ -57,6 +59,7 @@ import type { HomeAssistant, ValueChangedEvent } from "../types";
import { brandsUrl } from "../util/brands-url";
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
import "./ha-generic-picker";
import type { HaGenericPicker } from "./ha-generic-picker";
import type { PickerComboBoxItem } from "./ha-picker-combo-box";
import "./ha-svg-icon";
import "./ha-tree-indicator";
@@ -65,6 +68,12 @@ import "./target-picker/ha-target-picker-value-chip";
const SEPARATOR = "________";
const CREATE_ID = "___create-new-entity___";
const isTargetType = (value: string): value is TargetType =>
value === "entity" ||
value === "device" ||
value === "area" ||
value === "label" ||
value === "floor";
@customElement("ha-target-picker")
export class HaTargetPicker extends SubscribeMixin(LitElement) {
@@ -106,13 +115,19 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
@state() private _selectedSection?: TargetTypeFloorless;
@state() private _replaceTarget?: TargetItem;
@state() private _replaceTargetAnchor?: HTMLElement;
@state() private _configEntryLookup: Record<string, ConfigEntry> = {};
@state()
@consume({ context: labelsContext, subscribe: true })
private _labelRegistry!: LabelRegistryEntry[];
private _newTarget?: { type: TargetType; id: string };
@query("ha-generic-picker") private _picker?: HaGenericPicker;
private _newTarget?: TargetItem;
private _getDevicesMemoized = memoizeOne(getDevices);
@@ -286,6 +301,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
? html`
<ha-target-picker-item-group
@remove-target-item=${this._handleRemove}
@replace-target-item=${this._handleReplace}
type="entity"
.hass=${this.hass}
.items=${{ entity: entityIds }}
@@ -301,6 +317,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
? html`
<ha-target-picker-item-group
@remove-target-item=${this._handleRemove}
@replace-target-item=${this._handleReplace}
type="device"
.hass=${this.hass}
.items=${{ device: deviceIds }}
@@ -316,6 +333,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
? html`
<ha-target-picker-item-group
@remove-target-item=${this._handleRemove}
@replace-target-item=${this._handleReplace}
type="area"
.hass=${this.hass}
.items=${{
@@ -334,6 +352,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
? html`
<ha-target-picker-item-group
@remove-target-item=${this._handleRemove}
@replace-target-item=${this._handleReplace}
type="label"
.hass=${this.hass}
.items=${{ label: labelIds }}
@@ -390,9 +409,11 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
)}
.sectionTitleFunction=${this._sectionTitleFunction}
.selectedSection=${this._selectedSection}
.popoverAnchor=${this._replaceTargetAnchor}
.rowRenderer=${this._renderRow}
.getItems=${this._getItems}
@value-changed=${this._targetPicked}
@picker-closed=${this._handlePickerClosed}
.addButtonLabel=${this.hass.localize(
"ui.components.target-picker.add_target"
)}
@@ -411,34 +432,42 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
return;
}
const [type, id] = ev.detail.value.split(SEPARATOR);
this._addTarget(id, type as TargetType);
const [rawType, id] = value.split(SEPARATOR);
if (!id || !isTargetType(rawType)) {
return;
}
if (this._replaceTarget) {
this._replaceTargetItem(this._replaceTarget, { type: rawType, id });
return;
}
this._addTarget(id, rawType);
}
private _replaceTargetItem(currentTarget: TargetItem, newTarget: TargetItem) {
const value = this._replaceTargetInValue(
this.value,
currentTarget,
newTarget
);
if (value === this.value) {
return;
}
fireEvent(this, "value-changed", { value });
}
private _addTarget(id: string, type: TargetType) {
const typeId = `${type}_id`;
const value = this._addTargetToValue(this.value, { type, id });
if (typeId === "entity_id" && !isValidEntityId(id)) {
if (value === this.value) {
return;
}
if (
this.value &&
this.value[typeId] &&
ensureArray(this.value[typeId]).includes(id)
) {
return;
}
fireEvent(this, "value-changed", {
value: this.value
? {
...this.value,
[typeId]: this.value[typeId]
? [...ensureArray(this.value[typeId]), id]
: id,
}
: { [typeId]: id },
});
fireEvent(this, "value-changed", { value });
this.shadowRoot
?.querySelector(
@@ -447,6 +476,52 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
?.removeAttribute("collapsed");
}
private _replaceTargetInValue(
value: this["value"],
currentTarget: TargetItem,
newTarget: TargetItem
): this["value"] {
if (
!value ||
(currentTarget.type === newTarget.type &&
currentTarget.id === newTarget.id)
) {
return value;
}
const valueWithoutCurrent = this._removeItem(
value,
currentTarget.type,
currentTarget.id
);
return this._addTargetToValue(valueWithoutCurrent, newTarget);
}
private _addTargetToValue(
value: this["value"],
target: TargetItem
): this["value"] {
const typeId = `${target.type}_id`;
if (typeId === "entity_id" && !isValidEntityId(target.id)) {
return value;
}
if (value?.[typeId] && ensureArray(value[typeId]).includes(target.id)) {
return value;
}
return value
? {
...value,
[typeId]: value[typeId]
? [...ensureArray(value[typeId]), target.id]
: target.id,
}
: { [typeId]: target.id };
}
private _createNewDomainElement = (domain: string) => {
showHelperDetailDialog(this, {
domain,
@@ -461,14 +536,14 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
});
};
private _handleRemove(ev) {
private _handleRemove(ev: HASSDomEvent<HASSDomEvents["remove-target-item"]>) {
const { type, id } = ev.detail;
fireEvent(this, "value-changed", {
value: this._removeItem(this.value, type, id),
});
}
private _handleExpand(ev) {
private _handleExpand(ev: HASSDomEvent<HASSDomEvents["expand-target-item"]>) {
const type = ev.detail.type;
const itemId = ev.detail.id;
const newAreas: string[] = [];
@@ -614,6 +689,45 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
fireEvent(this, "value-changed", { value });
}
private _handleReplace(
ev: HASSDomEvent<HASSDomEvents["replace-target-item"]>
) {
ev.stopPropagation();
this._replaceTargetAnchor = ev
.composedPath()
.find(
(node): node is HTMLElement =>
node instanceof HTMLElement &&
node.tagName === "HA-TARGET-PICKER-ITEM-ROW"
);
const type = ev.detail.type;
if (type === "floor") {
this._selectedSection = "area";
} else if (
type === "entity" ||
type === "device" ||
type === "area" ||
type === "label"
) {
this._selectedSection = type;
} else {
return;
}
this._replaceTarget = { type, id: ev.detail.id };
this._picker?.open(undefined, {
selectedValue: `${type}${SEPARATOR}${ev.detail.id}`,
});
}
private _handlePickerClosed() {
if (this._replaceTarget) {
this._selectedSection = undefined;
}
this._replaceTarget = undefined;
this._replaceTargetAnchor = undefined;
}
private _addItems(
value: this["value"],
type: string,
@@ -704,6 +818,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
this.includeDomains,
this.includeDeviceClasses,
this.value,
this._replaceTarget,
searchString,
this._configEntryLookup,
this._selectedSection
@@ -718,10 +833,22 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
includeDomains: this["includeDomains"],
includeDeviceClasses: this["includeDeviceClasses"],
targetValue: this["value"],
replaceTarget: TargetItem | undefined,
searchTerm: string,
configEntryLookup: Record<string, ConfigEntry>,
filterType?: TargetTypeFloorless
) => {
const replacingEntityId =
replaceTarget?.type === "entity" ? replaceTarget.id : undefined;
const replacingDeviceId =
replaceTarget?.type === "device" ? replaceTarget.id : undefined;
const replacingAreaId =
replaceTarget?.type === "area" ? replaceTarget.id : undefined;
const replacingFloorId =
replaceTarget?.type === "floor" ? replaceTarget.id : undefined;
const replacingLabelId =
replaceTarget?.type === "label" ? replaceTarget.id : undefined;
const items: (
| string
| FloorComboBoxItem
@@ -739,9 +866,15 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
undefined,
undefined,
targetValue?.entity_id
? ensureArray(targetValue.entity_id)
? replacingEntityId
? ensureArray(targetValue.entity_id).filter(
(entityId) => entityId !== replacingEntityId
)
: ensureArray(targetValue.entity_id)
: undefined,
replacingEntityId
? `entity${SEPARATOR}${replacingEntityId}`
: undefined,
undefined,
`entity${SEPARATOR}`
).sort(this._sortBySortingLabel);
@@ -772,9 +905,13 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
deviceFilter,
entityFilter,
targetValue?.device_id
? ensureArray(targetValue.device_id)
? replacingDeviceId
? ensureArray(targetValue.device_id).filter(
(deviceId) => deviceId !== replacingDeviceId
)
: ensureArray(targetValue.device_id)
: undefined,
undefined,
replacingDeviceId,
`device${SEPARATOR}`
).sort(this._sortBySortingLabel);
@@ -810,8 +947,20 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
includeDeviceClasses,
deviceFilter,
entityFilter,
targetValue?.area_id ? ensureArray(targetValue.area_id) : undefined,
targetValue?.floor_id ? ensureArray(targetValue.floor_id) : undefined
targetValue?.area_id
? replacingAreaId
? ensureArray(targetValue.area_id).filter(
(areaId) => areaId !== replacingAreaId
)
: ensureArray(targetValue.area_id)
: undefined,
targetValue?.floor_id
? replacingFloorId
? ensureArray(targetValue.floor_id).filter(
(floorId) => floorId !== replacingFloorId
)
: ensureArray(targetValue.floor_id)
: undefined
);
if (searchTerm) {
@@ -860,7 +1009,13 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
includeDeviceClasses,
deviceFilter,
entityFilter,
targetValue?.label_id ? ensureArray(targetValue.label_id) : undefined,
targetValue?.label_id
? replacingLabelId
? ensureArray(targetValue.label_id).filter(
(labelId) => labelId !== replacingLabelId
)
: ensureArray(targetValue.label_id)
: undefined,
`label${SEPARATOR}`
).sort(this._sortBySortingLabel);
@@ -1022,11 +1177,14 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
alt=""
crossorigin="anonymous"
referrerpolicy="no-referrer"
src=${brandsUrl({
domain: (item as DevicePickerItem).domain!,
type: "icon",
darkOptimized: this.hass.themes.darkMode,
})}
src=${brandsUrl(
{
domain: (item as DevicePickerItem).domain!,
type: "icon",
darkOptimized: this.hass.themes.darkMode,
},
this.hass.auth.data.hassUrl
)}
/>
`
: type === "floor"
@@ -1111,14 +1269,9 @@ declare global {
}
interface HASSDomEvents {
"remove-target-item": {
type: string;
id: string;
};
"expand-target-item": {
type: string;
id: string;
};
"remove-target-item": TargetItem;
"expand-target-item": TargetItem;
"replace-target-item": TargetItem;
"remove-target-group": string;
}
}
+8 -7
View File
@@ -95,11 +95,12 @@ export class HaTextField extends TextFieldBase {
width: var(--ha-textfield-input-width, 100%);
}
.mdc-text-field:not(.mdc-text-field--with-leading-icon) {
padding: var(--text-field-padding, 0px 16px);
padding-top: var(--text-field-padding-top, 0px);
padding-bottom: var(--text-field-padding-bottom, 0px);
padding-inline-start: var(--text-field-padding-start, 16px);
padding-inline-end: var(--text-field-padding-end, 16px);
}
.mdc-text-field__affix--suffix {
padding-left: var(--text-field-suffix-padding-left, 12px);
padding-right: var(--text-field-suffix-padding-right, 0px);
padding-inline-start: var(--text-field-suffix-padding-left, 12px);
padding-inline-end: var(--text-field-suffix-padding-right, 0px);
direction: ltr;
@@ -110,12 +111,12 @@ export class HaTextField extends TextFieldBase {
direction: var(--direction);
}
.mdc-text-field--with-leading-icon.mdc-text-field--with-trailing-icon {
padding-left: var(--text-field-suffix-padding-left, 0px);
padding-right: var(--text-field-suffix-padding-right, 0px);
padding-inline-start: var(--text-field-suffix-padding-left, 0px);
.mdc-text-field--with-trailing-icon {
padding-inline-start: var(--text-field-suffix-padding-left, 16px);
padding-inline-end: var(--text-field-suffix-padding-right, 0px);
direction: var(--direction);
}
.mdc-text-field:not(.mdc-text-field--disabled)
.mdc-text-field__affix--suffix {
color: var(--secondary-text-color);
+154 -42
View File
@@ -1,56 +1,168 @@
import { Snackbar } from "@material/mwc-snackbar/mwc-snackbar";
import { styles } from "@material/mwc-snackbar/mwc-snackbar.css";
import { css } from "lit";
import { customElement } from "lit/decorators";
import "@home-assistant/webawesome/dist/components/popover/popover";
import type WaPopover from "@home-assistant/webawesome/dist/components/popover/popover";
import { css, html, LitElement } from "lit";
import { customElement, property, query } from "lit/decorators";
export type ToastCloseReason =
| "dismiss"
| "action"
| "timeout"
| "programmatic";
export interface ToastClosedEventDetail {
reason: ToastCloseReason;
}
@customElement("ha-toast")
export class HaToast extends Snackbar {
static override styles = [
styles,
css`
.mdc-snackbar--leading {
justify-content: center;
}
export class HaToast extends LitElement {
@property({ attribute: "label-text" }) public labelText = "";
.mdc-snackbar {
z-index: 10;
margin: 8px;
right: calc(8px + var(--safe-area-inset-right));
bottom: calc(8px + var(--safe-area-inset-bottom));
left: calc(8px + var(--safe-area-inset-left));
}
@property({ type: Number, attribute: "timeout-ms" }) public timeoutMs = 4000;
.mdc-snackbar__surface {
min-width: 350px;
max-width: 650px;
}
@query("wa-popover")
private _popover?: WaPopover;
.mdc-snackbar__actions {
color: rgba(255, 255, 255, 0.87);
}
private _dismissTimer?: ReturnType<typeof setTimeout>;
/* Revert the default styles set by mwc-snackbar */
@media (max-width: 480px), (max-width: 344px) {
.mdc-snackbar__surface {
min-width: inherit;
}
}
private _closeReason: ToastCloseReason = "programmatic";
@media all and (max-width: 450px), all and (max-height: 500px) {
.mdc-snackbar {
right: var(--safe-area-inset-right);
bottom: var(--safe-area-inset-bottom);
left: var(--safe-area-inset-left);
}
.mdc-snackbar__surface {
min-width: 100%;
}
public disconnectedCallback(): void {
clearTimeout(this._dismissTimer);
super.disconnectedCallback();
}
public async show(): Promise<void> {
await this.updateComplete;
await this._popover?.show();
clearTimeout(this._dismissTimer);
if (this.timeoutMs > 0) {
this._dismissTimer = setTimeout(() => {
this.hide("timeout");
}, this.timeoutMs);
}
}
public async hide(reason: ToastCloseReason = "programmatic"): Promise<void> {
clearTimeout(this._dismissTimer);
this._closeReason = reason;
await this._popover?.hide();
}
public close(reason: ToastCloseReason = "programmatic"): void {
this.hide(reason);
}
private _handleAfterHide() {
this.dispatchEvent(
new CustomEvent<ToastClosedEventDetail>("toast-closed", {
detail: { reason: this._closeReason },
bubbles: true,
composed: true,
})
);
this._closeReason = "programmatic";
}
protected render() {
return html`
<div id="toast-anchor" aria-hidden="true"></div>
<wa-popover
for="toast-anchor"
placement="top"
distance="16"
skidding="0"
without-arrow
@wa-after-hide=${this._handleAfterHide}
>
<div class="toast" role="status" aria-live="polite">
<span class="message">${this.labelText}</span>
<div class="actions">
<slot name="action"></slot>
<slot name="dismiss"></slot>
</div>
</div>
</wa-popover>
`;
}
static override styles = css`
#toast-anchor {
position: fixed;
bottom: calc(8px + var(--safe-area-inset-bottom));
inset-inline-start: 50%;
transform: translateX(-50%);
width: 1px;
height: 1px;
opacity: 0;
pointer-events: none;
}
wa-popover {
--arrow-size: 0;
--max-width: min(
650px,
calc(
100vw -
16px - var(--safe-area-inset-left) - var(--safe-area-inset-right)
)
);
--show-duration: var(--ha-animation-duration-fast, 150ms);
--hide-duration: var(--ha-animation-duration-fast, 150ms);
}
wa-popover::part(body) {
padding: 0;
border-radius: 4px;
}
.toast {
box-sizing: border-box;
min-width: min(
350px,
calc(
100vw -
16px - var(--safe-area-inset-left) - var(--safe-area-inset-right)
)
);
max-width: 650px;
min-height: 48px;
display: flex;
align-items: center;
gap: var(--ha-space-2);
padding: var(--ha-space-2) var(--ha-space-3);
color: var(--inverse-primary-text-color);
background-color: var(--inverse-surface-color);
}
.message {
flex: 1;
min-width: 0;
}
.actions {
display: flex;
align-items: center;
gap: var(--ha-space-2);
color: rgba(255, 255, 255, 0.87);
}
@media all and (max-width: 450px), all and (max-height: 500px) {
.toast {
min-width: calc(
100vw - var(--safe-area-inset-left) - var(--safe-area-inset-right)
);
border-radius: 0;
}
`,
];
}
`;
}
declare global {
interface HTMLElementEventMap {
"toast-closed": CustomEvent<ToastClosedEventDetail>;
}
interface HTMLElementTagNameMap {
"ha-toast": HaToast;
}
+11 -4
View File
@@ -16,8 +16,14 @@ export class HaTooltip extends Tooltip {
Tooltip.styles,
css`
:host {
--wa-tooltip-background-color: var(--secondary-background-color);
--wa-tooltip-content-color: var(--primary-text-color);
--wa-tooltip-background-color: var(
--ha-tooltip-background-color,
var(--secondary-background-color)
);
--wa-tooltip-content-color: var(
--ha-tooltip-text-color,
var(--primary-text-color)
);
--wa-tooltip-font-family: var(
--ha-tooltip-font-family,
var(--ha-font-family-body)
@@ -34,13 +40,14 @@ export class HaTooltip extends Tooltip {
--ha-tooltip-line-height,
var(--ha-line-height-condensed)
);
--wa-tooltip-padding: 8px;
--wa-tooltip-padding: var(--ha-tooltip-padding, var(--ha-space-2));
--wa-tooltip-border-radius: var(
--ha-tooltip-border-radius,
var(--ha-border-radius-sm)
);
--wa-tooltip-arrow-size: var(--ha-tooltip-arrow-size, 8px);
--wa-z-index-tooltip: var(--ha-tooltip-z-index, 1000);
--wa-tooltip-border-width: 0px;
--wa-z-index-tooltip: 1000;
}
`,
];
+36 -10
View File
@@ -1,6 +1,7 @@
import type { CSSResultGroup, PropertyValues } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { mdiArrowRightThin } from "@mdi/js";
import { fireEvent } from "../common/dom/fire_event";
import type { Segment } from "../data/vacuum";
import { getVacuumSegments } from "../data/vacuum";
@@ -8,8 +9,7 @@ import { haStyle } from "../resources/styles";
import type { HomeAssistant } from "../types";
import "./ha-alert";
import "./ha-area-picker";
import "./ha-md-list";
import "./ha-md-list-item";
import "./ha-svg-icon";
type AreaSegmentMapping = Record<string, string[]>; // area ID -> segment IDs
@@ -80,9 +80,7 @@ export class HaVacuumSegmentAreaMapper extends LitElement {
${Object.entries(groupedSegments).map(
([groupName, segments]) => html`
${groupName ? html`<h2>${groupName}</h2>` : nothing}
<ha-md-list>
${segments.map((segment) => this._renderSegment(segment))}
</ha-md-list>
${segments.map((segment) => this._renderSegment(segment))}
`
)}
`;
@@ -106,10 +104,10 @@ export class HaVacuumSegmentAreaMapper extends LitElement {
const mappedAreas = this._getSegmentAreas(segment.id);
return html`
<ha-md-list-item>
<span slot="headline">${segment.name}</span>
<div class="segment-row">
<span class="segment-name">${segment.name}</span>
<ha-svg-icon class="arrow" .path=${mdiArrowRightThin}></ha-svg-icon>
<ha-area-picker
slot="end"
.hass=${this.hass}
.value=${mappedAreas}
.label=${this.hass.localize(
@@ -118,7 +116,7 @@ export class HaVacuumSegmentAreaMapper extends LitElement {
@value-changed=${this._handleAreaChanged}
data-segment-id=${segment.id}
></ha-area-picker>
</ha-md-list-item>
</div>
`;
}
@@ -179,8 +177,36 @@ export class HaVacuumSegmentAreaMapper extends LitElement {
display: block;
}
ha-area-picker {
.segment-row {
display: flex;
align-items: center;
gap: var(--ha-space-4);
padding: var(--ha-space-2) var(--ha-space-4);
}
.segment-name {
flex: 1;
font: var(--ha-font-body-l);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.arrow {
flex-shrink: 0;
color: var(--secondary-text-color);
}
@media (max-width: 600px) {
.arrow {
display: none;
}
}
ha-area-picker {
flex: 2;
min-width: 0;
max-width: 300px;
}
h2 {

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