Compare commits

...
Author SHA1 Message Date
Paul Bottein 90e807009d Improve energy settings confirmation dialogs 2026-03-17 18:50:07 +01:00
karwostsandGitHub 6e3cf3e42f Fix tag dialog (#30191) 2026-03-17 16:15:35 +01:00
Jan-Philipp BeneckeandGitHub fe53656c7e Enhance create new automation/script dialog with search and adaptive dialog (#30188)
* Enhance create new automation/script dialog with search and adaptive dialog

* Always show tip

* Address review comments

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

* Setup cover favorites

* Fix

* Make generic

* Remove

* Move

* Add type for keys

* Remove

* Move

* Types

* Types

* Changes for tilt position

* Cleanup

* Add missing options

* Replace cover preset features with favorites features

Editor points to more info instead of allowing cusomisation of feature

* Fix drag

* Remove learn more

* Add domains with favorites shared data

* Support recent additions: reset and copy favorites

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

* Await all

* Refactor

* Use copy for primary action

* Allow empty lists

* Rename

* Use better ally labels

* Move favorites options above and use divider

* Move

* Use proper DOM types

* Use proper type

* Use proper types

* Cleanup

* Type from param

* Center align label

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

* Clean device page

* Remove rename device logic

* Remove rename device logic in zwavejs

* Fix tests

* Fix entity name fallback

* Add test

* Fix type name

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

* Remove app-datepicker styles from date picker dialog

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

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

* Review

* Apply suggestion from @MindFreeze

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

* Fix date parsing in HaDialogDatePicker to handle ISO string format

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

---------

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

* Show stage in app store repository view

* Increase size of cards to accomadate badges

* Add stage to installed apps, update style

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

* Remove show advanced mode logic for app visibility

* Add margin to .addition

* Remove [deprecated] from app page title also

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

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

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

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

* prettier

---------

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

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

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

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

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

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

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

* Initial implementation of Matter lock pin management and events.

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

* Initial implementation of Matter lock pin management and events.

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

* - Copilot fixes

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

* Updates to support new PR for backend

* Update as per PR comments

* Fixes to align to new backend design.

* Fixes for user deletion

* Fixes for PR comments

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

* Remove unused code

* Updates with review feedback

* PR Comments

* Fixed linting error

* Fixes for new dialog changes

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

---------

Co-authored-by: Wendelin <[email protected]>
Co-authored-by: Petar Petrov <[email protected]>
2026-03-13 16:20:21 +00:00
Tom CarpenterandGitHub e21f7baa93 Support Energy Collections in Statistics Graph Card Visual Editor (#29628)
* Add date selection keys to statistics-graph editor

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

* Add missing option to stats card editor

Was missing the expand_legend config option in the visual editor.

* Tidy statistics graph editor arrangement

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

* Add "auto" option for statistics-chart period

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

* Correct hiding days to show

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

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

* Swap date selection and collection key order

This keeps the toggle in a consistent location.

* Remove duplicate config key

There is a title? key now in EnergyCardBaseConfig.

* Correct energy_date_selection translation

* Improve collection key description

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

* Remove extra margin for header

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

* Inline
2026-03-13 12:59:27 +02:00
Aidan TimsonandGitHub 814106e4aa Prevent scrim close on edit home overview dialog (#30139) 2026-03-13 12:59:01 +02:00
Aidan TimsonandGitHub a2254eb048 Add my link for home overview (#30137)
Add my link for overview
2026-03-13 11:48:33 +01:00
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]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
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]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
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 BotteinandGitHub d851a5bdf3 Fix entities not updated in device page (#30136) 2026-03-13 09:11:11 +00:00
ildar170975andGitHub 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]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
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]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
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]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
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]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
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
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
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
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 TimsonandGitHub 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]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
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
xtymmms1021andGitHub 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 BotteinandGitHub 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 LamonandGitHub a356c153f8 Demo fixes (#30105) 2026-03-12 08:53:25 +01:00
Matthias de BaatandGitHub b60dc94196 Add protocol logos (#30104) 2026-03-12 08:52:37 +01:00
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
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 RittelandGitHub 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]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
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
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 BaatandGitHub 268c0a60f0 Faster load Zigbee numbers (#30096) 2026-03-11 15:47:41 +00:00
ildar170975andGitHub 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 TimsonandGitHub 0844d9c2c0 Highlight linked config entry again (#30095)
* Highlight linked config entry again

* Fix export
2026-03-11 15:05:58 +02:00
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 PragaczandGitHub 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 HattonandGitHub 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 LamonandGitHub 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 PetrovandGitHub d61de4d2df Disable smoothMonotone in line graphs (#30093) 2026-03-11 09:01:37 +00:00
Bram KragtenandGitHub 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 CarpenterandGitHub 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 CarpenterandGitHub 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 TimsonandGitHub 2650c1c0b2 Fix code editor autocomplete using wa popup (#30081) 2026-03-10 17:20:40 +01:00
6957e1f4d9 Introduce Lazy context provider (#29988)
Co-authored-by: Wendelin <[email protected]>
2026-03-10 16:19:48 +01:00
Yosi LevyandGitHub 32be26320d Support for ESC in notification drawer (#29521) 2026-03-10 15:47:35 +01:00
Paul BotteinandGitHub cdbaa85beb Fix debounce with immediate firing twice on single call (#30082) 2026-03-10 13:39:44 +01:00
Bram KragtenandGitHub b58fc68ea8 Add warning when importing blueprint from unknown source (#30076) 2026-03-10 09:05:39 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
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 BotteinandGitHub eeae66e026 Add default state/numeric_state options to automation triggers and conditions (#30052) 2026-03-10 08:54:00 +00:00
renovate[bot]andGitHub 60b4b1c4e2 Update dependency eslint to v9.39.4 (#30079) 2026-03-10 07:42:05 +01:00
Aidan TimsonandGitHub 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 ZweckandGitHub 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]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
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 BotteinandGitHub 052909ea01 Make bar-gauge card feature non-clickable (#30068) 2026-03-09 10:52:09 +00:00
Petar PetrovandGitHub 5e3c80ef6e Fix hasReturn check to scan all grid sources in energy view strategy (#30062) 2026-03-09 10:51:28 +00:00
Bram KragtenandGitHub 6e2fdbb396 Add reorder support to area selector (#30056) 2026-03-09 09:24:20 +01:00
Tom CarpenterandGitHub 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 CarpenterandGitHub 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]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
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]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
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]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
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]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
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]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
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
WendelinandGitHub 872d1c684f Simplify dialogs (#29848) 2026-03-08 16:27:04 +01:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
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]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
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]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
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 PetrovandGitHub b7d6652e5c Add back energy distribution card to electricity tab (#30049) 2026-03-08 09:34:28 +01:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
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
karwostsandGitHub 9f87257f41 Support copying light favorites to other lights (#30034) 2026-03-07 11:14:48 +01:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
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]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
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 PetrovandGitHub 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 LevyandGitHub 697a945056 RTL textfield fixes for quick search (#30013)
textfield fixes for quick search
2026-03-06 19:42:21 +01:00
Paul BotteinandGitHub 67dbacce52 Improve mock for cover, alarm, lock, lawn mower, valve and vacuum (#29999) 2026-03-06 19:41:12 +01:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
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]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
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]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
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
karwostsandGitHub 1ca1ef3c32 Fix favorite color dragging unintended buttons (#30028) 2026-03-06 19:34:18 +01:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
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
karwostsandGitHub b3b35e4299 Menu item to reset light favorite colors (#30004)
reset-light-favorites
2026-03-05 20:46:44 +01:00
Paul BotteinandGitHub 59364d05ec Improve pull request instructions for AI agents (#30001) 2026-03-05 18:38:25 +01:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
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 TimsonandGitHub 8f704d8c0a Cover tilt presets card feature (#29989) 2026-03-05 17:23:28 +02:00
Petar PetrovandGitHub aeddeb3bd7 Fix sensor card graph time axis not progressing when value is unchanged (#29976) 2026-03-05 15:13:05 +01:00
Paul BotteinandGitHub 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 BotteinandGitHub 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
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 BotteinandGitHub 71a5a1d3b4 Add arrow and fix footer for vacuum segment mapper (#29975) 2026-03-04 14:58:34 +01:00
WendelinandGitHub 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
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 TimsonandGitHub 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 CarpenterandGitHub e01dc89a48 Prevent keyboard shortcuts firing in dropdowns (#29972) 2026-03-04 12:32:53 +00:00
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 TimsonandGitHub 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
WendelinandGitHub b89b02e044 Open quick search quicker (#29967) 2026-03-04 11:13:13 +01:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
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 BotteinandGitHub 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]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
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
17314fe62b Add animation duration tokens and button transition update (#29965)
Co-authored-by: Wendelin <[email protected]>
2026-03-04 08:36:37 +00:00
sevorlandGitHub 3826a69465 Use ha-form for wait_for_trigger timeout (#29944) 2026-03-04 08:48:57 +01:00
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 BotteinandGitHub bb56bbb1ba Align heading button font-size with other heading entity badge (#29958) 2026-03-04 08:08:22 +02:00
Bram KragtenandGitHub 76aef60c05 Add hass url to brand images (#29961) 2026-03-03 21:00:43 +01:00
407 changed files with 18544 additions and 10842 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@3a7fb5c85b80b1dda66e1ccb94009adbbd32fce3 # v7.0.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+4 -4
View File
@@ -37,7 +37,7 @@ jobs:
uses: home-assistant/actions/helpers/verify-version@master
- name: Setup Node
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version-file: ".nvmrc"
cache: yarn
@@ -62,7 +62,7 @@ jobs:
skip-existing: true
- name: Upload release assets
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
with:
files: |
dist/*.whl
@@ -100,7 +100,7 @@ jobs:
- name: Checkout the repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version-file: ".nvmrc"
cache: yarn
@@ -115,6 +115,6 @@ jobs:
- name: Tar folder
run: tar -czf landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz -C landing-page/dist .
- name: Upload release asset
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
with:
files: landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz
@@ -31,7 +31,7 @@ index 8795ddcaa77aea7b0356417e4bc4b19e2b3f860c..fcdc68342d9ac53936c9ed40a9ccfc2f
@@ -129,7 +129,10 @@ export async function injectManifest(
searchString: options.injectionPoint!,
});
- filesToWrite[options.swDest] = source;
+ filesToWrite[options.swDest] = source.replace(
+ url!,
+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");
+31 -33
View File
@@ -28,31 +28,31 @@
"dependencies": {
"@babel/runtime": "7.28.6",
"@braintree/sanitize-url": "7.1.2",
"@codemirror/autocomplete": "6.20.0",
"@codemirror/commands": "6.10.2",
"@codemirror/autocomplete": "6.20.1",
"@codemirror/commands": "6.10.3",
"@codemirror/language": "6.12.2",
"@codemirror/legacy-modes": "6.5.2",
"@codemirror/search": "6.6.0",
"@codemirror/state": "6.5.4",
"@codemirror/view": "6.39.15",
"@codemirror/state": "6.6.0",
"@codemirror/view": "6.40.0",
"@date-fns/tz": "1.4.1",
"@egjs/hammerjs": "2.0.17",
"@formatjs/intl-datetimeformat": "7.2.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-ha.0",
"@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",
@@ -91,8 +90,8 @@
"@vue/web-component-wrapper": "1.3.0",
"@webcomponents/scoped-custom-element-registry": "0.0.10",
"@webcomponents/webcomponentsjs": "2.8.0",
"app-datepicker": "5.1.1",
"barcode-detector": "3.1.0",
"barcode-detector": "3.1.1",
"cally": "0.9.2",
"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",
"@bundle-stats/plugin-webpack-filter": "4.22.0",
"@html-eslint/eslint-plugin": "0.58.1",
"@lokalise/node-api": "15.6.1",
"@octokit/auth-oauth-device": "8.0.3",
"@octokit/plugin-retry": "8.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",
@@ -175,12 +174,12 @@
"@types/tar": "7.0.87",
"@types/ua-parser-js": "0.7.39",
"@types/webspeechapi": "0.0.29",
"@vitest/coverage-v8": "4.0.18",
"babel-loader": "10.0.0",
"@vitest/coverage-v8": "4.1.0",
"babel-loader": "10.1.1",
"babel-plugin-template-html-minifier": "4.1.0",
"browserslist-useragent-regexp": "4.1.3",
"del": "8.0.1",
"eslint": "9.39.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.4.0",
"lit-analyzer": "2.0.3",
"lodash.merge": "4.6.2",
"lodash.template": "4.5.0",
@@ -208,27 +207,26 @@
"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.4.0",
"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",
"vitest": "4.1.0",
"webpack-stats-plugin": "1.1.3",
"webpackbar": "7.0.0",
"workbox-build": "patch:workbox-build@npm%3A7.1.1#~/.yarn/patches/workbox-build-npm-7.1.1-a854f3faae.patch"
"workbox-build": "patch:workbox-build@npm%3A7.4.0#~/.yarn/patches/workbox-build-npm-7.4.0-c84561662c.patch"
},
"resolutions": {
"@material/mwc-button@^0.25.3": "^0.27.0",
"lit": "3.3.2",
"lit-html": "3.3.2",
"clean-css": "5.3.3",
"@lit/reactive-element": "2.1.2",
"@fullcalendar/daygrid": "6.1.20",
"globals": "17.3.0",
"globals": "17.4.0",
"tslib": "2.8.1",
"@material/mwc-list@^0.27.0": "patch:@material/mwc-list@npm%3A0.27.0#~/.yarn/patches/@material-mwc-list-npm-0.27.0-5344fc9de4.patch",
"glob@^10.2.2": "^10.5.0"
+4 -61
View File
@@ -1,10 +1,7 @@
/* eslint-disable lit/prefer-static-styles */
import type { TemplateResult } from "lit";
import { html } from "lit";
import { customElement } from "lit/decorators";
import { HaFormString } from "../components/ha-form/ha-form-string";
import "../components/ha-icon-button";
import "./ha-auth-textfield";
import "../components/input/ha-input";
@customElement("ha-auth-form-string")
export class HaAuthFormString extends HaFormString {
@@ -12,63 +9,9 @@ export class HaAuthFormString extends HaFormString {
return this;
}
public reportValidity(): boolean {
return this.querySelector("ha-auth-textfield")?.reportValidity() ?? true;
}
protected render(): TemplateResult {
return html`
<style>
ha-auth-form-string {
display: block;
position: relative;
}
ha-auth-form-string[own-margin] {
margin-bottom: 5px;
}
ha-auth-form-string ha-auth-textfield {
display: block !important;
}
ha-auth-form-string ha-icon-button {
position: absolute;
top: 8px;
right: 8px;
inset-inline-start: initial;
inset-inline-end: 8px;
--ha-icon-button-size: 40px;
--mdc-icon-size: 20px;
color: var(--secondary-text-color);
direction: var(--direction);
}
</style>
<ha-auth-textfield
.type=${!this.isPassword
? this.stringType
: this.unmaskedPassword
? "text"
: "password"}
.label=${this.label}
.value=${this.data || ""}
.helper=${this.helper}
helperPersistent
.disabled=${this.disabled}
.required=${this.schema.required}
.autoValidate=${this.schema.required}
.name=${this.schema.name}
.autocomplete=${this.schema.autocomplete}
?autofocus=${this.schema.autofocus}
.suffix=${this.isPassword
? // reserve some space for the icon.
html`<div style="width: 24px"></div>`
: this.schema.description?.suffix}
.validationMessage=${this.schema.required
? this.localize?.("ui.panel.page-authorize.form.error_required")
: undefined}
@input=${this._valueChanged}
@change=${this._valueChanged}
></ha-auth-textfield>
${this.renderIcon()}
`;
public connectedCallback(): void {
super.connectedCallback();
this.style.position = "relative";
}
}
+4 -1
View File
@@ -1,9 +1,9 @@
/* eslint-disable lit/prefer-static-styles */
import { html } from "lit";
import { customElement, property } from "lit/decorators";
import type { LocalizeFunc } from "../common/translations/localize";
import { HaForm } from "../components/ha-form/ha-form";
import "./ha-auth-form-string";
import type { LocalizeFunc } from "../common/translations/localize";
const localizeBaseKey = "ui.panel.page-authorize.form";
@@ -34,6 +34,9 @@ export class HaAuthForm extends HaForm {
protected render() {
return html`
<style>
ha-auth-form {
--ha-input-required-marker: "";
}
ha-auth-form .root > * {
display: block;
}
-264
View File
@@ -1,264 +0,0 @@
/* eslint-disable lit/value-after-constraints */
/* eslint-disable lit/prefer-static-styles */
import { floatingLabel } from "@material/mwc-floating-label/mwc-floating-label-directive";
import type { TemplateResult } from "lit";
import { html } from "lit";
import { customElement } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import { live } from "lit/directives/live";
import { HaTextField } from "../components/ha-textfield";
@customElement("ha-auth-textfield")
export class HaAuthTextField extends HaTextField {
protected renderLabel(): TemplateResult | string {
return !this.label
? ""
: html`
<span
.floatingLabelFoundation=${floatingLabel(
this.label
) as unknown as any}
.id=${this.name}
>${this.label}</span
>
`;
}
protected renderInput(shouldRenderHelperText: boolean): TemplateResult {
const minOrUndef = this.minLength === -1 ? undefined : this.minLength;
const maxOrUndef = this.maxLength === -1 ? undefined : this.maxLength;
const autocapitalizeOrUndef = this.autocapitalize
? (this.autocapitalize as
| "off"
| "none"
| "on"
| "sentences"
| "words"
| "characters")
: undefined;
const showValidationMessage = this.validationMessage && !this.isUiValid;
const ariaLabelledbyOrUndef = this.label ? this.name : undefined;
const ariaControlsOrUndef = shouldRenderHelperText
? "helper-text"
: undefined;
const ariaDescribedbyOrUndef =
this.focused || this.helperPersistent || showValidationMessage
? "helper-text"
: undefined;
// TODO: live() directive needs casting for lit-analyzer
// https://github.com/runem/lit-analyzer/pull/91/files
// TODO: lit-analyzer labels min/max as (number|string) instead of string
return html`<input
aria-labelledby=${ifDefined(ariaLabelledbyOrUndef)}
aria-controls=${ifDefined(ariaControlsOrUndef)}
aria-describedby=${ifDefined(ariaDescribedbyOrUndef)}
class="mdc-text-field__input"
type=${this.type}
.value=${live(this.value) as unknown as string}
?disabled=${this.disabled}
placeholder=${this.placeholder}
?required=${this.required}
?readonly=${this.readOnly}
minlength=${ifDefined(minOrUndef)}
maxlength=${ifDefined(maxOrUndef)}
pattern=${ifDefined(this.pattern ? this.pattern : undefined)}
min=${ifDefined(this.min === "" ? undefined : (this.min as number))}
max=${ifDefined(this.max === "" ? undefined : (this.max as number))}
step=${ifDefined(this.step === null ? undefined : (this.step as number))}
size=${ifDefined(this.size === null ? undefined : this.size)}
name=${ifDefined(this.name === "" ? undefined : this.name)}
inputmode=${ifDefined(this.inputMode)}
autocapitalize=${ifDefined(autocapitalizeOrUndef)}
?autofocus=${this.autofocus}
@input=${this.handleInputChange}
@focus=${this.onInputFocus}
@blur=${this.onInputBlur}
/>`;
}
public render() {
return html`
<style>
ha-auth-textfield {
display: inline-flex;
flex-direction: column;
outline: none;
}
ha-auth-textfield:not([disabled]):hover
:not(.mdc-text-field--invalid):not(.mdc-text-field--focused)
mwc-notched-outline {
--mdc-notched-outline-border-color: var(
--mdc-text-field-outlined-hover-border-color,
rgba(0, 0, 0, 0.87)
);
}
ha-auth-textfield:not([disabled])
.mdc-text-field:not(.mdc-text-field--outlined) {
background-color: var(--mdc-text-field-fill-color, whitesmoke);
}
ha-auth-textfield:not([disabled])
.mdc-text-field.mdc-text-field--invalid
mwc-notched-outline {
--mdc-notched-outline-border-color: var(
--mdc-text-field-error-color,
var(--mdc-theme-error, #b00020)
);
}
ha-auth-textfield:not([disabled])
.mdc-text-field.mdc-text-field--invalid
+ .mdc-text-field-helper-line
.mdc-text-field-character-counter,
ha-auth-textfield:not([disabled])
.mdc-text-field.mdc-text-field--invalid
.mdc-text-field__icon {
color: var(
--mdc-text-field-error-color,
var(--mdc-theme-error, #b00020)
);
}
ha-auth-textfield:not([disabled])
.mdc-text-field:not(.mdc-text-field--invalid):not(
.mdc-text-field--focused
)
.mdc-floating-label,
ha-auth-textfield:not([disabled])
.mdc-text-field:not(.mdc-text-field--invalid):not(
.mdc-text-field--focused
)
.mdc-floating-label::after {
color: var(--mdc-text-field-label-ink-color, rgba(0, 0, 0, 0.6));
}
ha-auth-textfield:not([disabled])
.mdc-text-field.mdc-text-field--focused
mwc-notched-outline {
--mdc-notched-outline-stroke-width: 2px;
}
ha-auth-textfield:not([disabled])
.mdc-text-field.mdc-text-field--focused:not(.mdc-text-field--invalid)
mwc-notched-outline {
--mdc-notched-outline-border-color: var(
--mdc-text-field-focused-label-color,
var(--mdc-theme-primary, rgba(98, 0, 238, 0.87))
);
}
ha-auth-textfield:not([disabled])
.mdc-text-field.mdc-text-field--focused:not(.mdc-text-field--invalid)
.mdc-floating-label {
color: #6200ee;
color: var(--mdc-theme-primary, #6200ee);
}
ha-auth-textfield:not([disabled])
.mdc-text-field
.mdc-text-field__input {
color: var(--mdc-text-field-ink-color, rgba(0, 0, 0, 0.87));
}
ha-auth-textfield:not([disabled])
.mdc-text-field
.mdc-text-field__input::placeholder {
color: var(--mdc-text-field-label-ink-color, rgba(0, 0, 0, 0.6));
}
ha-auth-textfield:not([disabled])
.mdc-text-field-helper-line
.mdc-text-field-helper-text:not(
.mdc-text-field-helper-text--validation-msg
),
ha-auth-textfield:not([disabled])
.mdc-text-field-helper-line:not(.mdc-text-field--invalid)
.mdc-text-field-character-counter {
color: var(--mdc-text-field-label-ink-color, rgba(0, 0, 0, 0.6));
}
ha-auth-textfield[disabled]
.mdc-text-field:not(.mdc-text-field--outlined) {
background-color: var(--mdc-text-field-disabled-fill-color, #fafafa);
}
ha-auth-textfield[disabled]
.mdc-text-field.mdc-text-field--outlined
mwc-notched-outline {
--mdc-notched-outline-border-color: var(
--mdc-text-field-outlined-disabled-border-color,
rgba(0, 0, 0, 0.06)
);
}
ha-auth-textfield[disabled]
.mdc-text-field:not(.mdc-text-field--invalid):not(
.mdc-text-field--focused
)
.mdc-floating-label,
ha-auth-textfield[disabled]
.mdc-text-field:not(.mdc-text-field--invalid):not(
.mdc-text-field--focused
)
.mdc-floating-label::after {
color: var(--mdc-text-field-disabled-ink-color, rgba(0, 0, 0, 0.38));
}
ha-auth-textfield[disabled] .mdc-text-field .mdc-text-field__input,
ha-auth-textfield[disabled]
.mdc-text-field
.mdc-text-field__input::placeholder {
color: var(--mdc-text-field-disabled-ink-color, rgba(0, 0, 0, 0.38));
}
ha-auth-textfield[disabled]
.mdc-text-field-helper-line
.mdc-text-field-helper-text,
ha-auth-textfield[disabled]
.mdc-text-field-helper-line
.mdc-text-field-character-counter {
color: var(--mdc-text-field-disabled-ink-color, rgba(0, 0, 0, 0.38));
}
ha-auth-textfield:not([disabled])
.mdc-text-field.mdc-text-field--focused:not(.mdc-text-field--invalid)
.mdc-floating-label {
color: var(--mdc-theme-primary, #6200ee);
}
ha-auth-textfield[no-spinner] input::-webkit-outer-spin-button,
ha-auth-textfield[no-spinner] input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* Firefox */
ha-auth-textfield[no-spinner] input[type="number"] {
-moz-appearance: textfield;
}
</style>
${super.render()}
`;
}
protected createRenderRoot() {
// add parent style to light dom
const style = document.createElement("style");
style.textContent = HaTextField.elementStyles as unknown as string;
this.append(style);
return this;
}
public firstUpdated() {
super.firstUpdated();
if (this.autofocus) {
this.focus();
}
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-auth-textfield": HaAuthTextField;
}
}
+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;
}
+10 -40
View File
@@ -4,14 +4,11 @@ import type {
EntityRegistryEntry,
} from "../../data/entity/entity_registry";
import type { HomeAssistant } from "../../types";
import { computeDeviceName } from "./compute_device_name";
import { computeStateName } from "./compute_state_name";
import { stripPrefixFromEntityName } from "./strip_prefix_from_entity_name";
export const computeEntityName = (
stateObj: HassEntity,
entities: HomeAssistant["entities"],
devices: HomeAssistant["devices"]
entities: HomeAssistant["entities"]
): string | undefined => {
const entry = entities[stateObj.entity_id] as
| EntityRegistryDisplayEntry
@@ -21,49 +18,22 @@ export const computeEntityName = (
// Fall back to state name if not in the entity registry (friendly name)
return computeStateName(stateObj);
}
return computeEntityEntryName(entry, devices);
return computeEntityEntryName(entry);
};
export const computeEntityEntryName = (
entry: EntityRegistryDisplayEntry | EntityRegistryEntry,
devices: HomeAssistant["devices"],
fallbackStateObj?: HassEntity
entry: EntityRegistryDisplayEntry | EntityRegistryEntry
): string | undefined => {
const name =
entry.name ||
("original_name" in entry && entry.original_name != null
? String(entry.original_name)
: undefined);
const device = entry.device_id ? devices[entry.device_id] : undefined;
if (!device) {
if (name) {
return name;
}
if (fallbackStateObj) {
return computeStateName(fallbackStateObj);
}
return undefined;
if (entry.name != null) {
return entry.name;
}
const deviceName = computeDeviceName(device);
// If the device name is the same as the entity name, consider empty entity name
if (deviceName === name) {
return undefined;
if ("original_name" in entry && entry.original_name != null) {
return String(entry.original_name);
}
// Remove the device name from the entity name if it starts with it
if (deviceName && name) {
return stripPrefixFromEntityName(name, deviceName) || name;
}
return name;
return undefined;
};
export const entityUseDeviceName = (
stateObj: HassEntity,
entities: HomeAssistant["entities"],
devices: HomeAssistant["devices"]
): boolean => !computeEntityName(stateObj, entities, devices);
entities: HomeAssistant["entities"]
): boolean => !computeEntityName(stateObj, entities);
@@ -45,7 +45,7 @@ export const computeEntityNameDisplay = (
return items.map((item) => item.text).join(separator);
}
const useDeviceName = entityUseDeviceName(stateObj, entities, devices);
const useDeviceName = entityUseDeviceName(stateObj, entities);
// If entity uses device name, and device is not already included, replace it with device name
if (useDeviceName) {
@@ -91,7 +91,7 @@ export const computeEntityNameList = (
const names = name.map((item) => {
switch (item.type) {
case "entity":
return computeEntityName(stateObj, entities, devices);
return computeEntityName(stateObj, entities);
case "device":
return device ? computeDeviceName(device) : undefined;
case "area":
@@ -144,6 +144,7 @@ const computeStateToPartsFromEntityAttributes = (
fraction: "value",
literal: "literal",
currency: "unit",
minusSign: "value",
};
const valueParts: ValuePart[] = [];
+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,
+7 -7
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
@@ -613,10 +612,10 @@ export class StatisticsChart extends LitElement {
}
});
// Close out the last stat segment at prevEndTime
// For line charts, close out the last stat segment at prevEndTime
const lastEndTime = prevEndTime;
const lastValues = prevValues;
if (lastEndTime && lastValues) {
if (this.chartType === "line" && lastEndTime && lastValues) {
statDataSets.forEach((d, i) => {
d.data!.push(
this._transformDataValue([lastEndTime, ...lastValues[i]!])
@@ -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}
+9 -11
View File
@@ -156,17 +156,15 @@ export class HaStatisticPicker extends LitElement {
this.value
);
private _getAdditionalItems(): StatisticComboBoxItem[] {
return [
{
id: MISSING_ID,
primary: this.hass.localize(
"ui.components.statistic-picker.missing_entity"
),
icon_path: mdiHelpCircleOutline,
},
];
}
private _getAdditionalItems = (): StatisticComboBoxItem[] => [
{
id: MISSING_ID,
primary: this.hass.localize(
"ui.components.statistic-picker.missing_entity"
),
icon_path: mdiHelpCircleOutline,
},
];
private _getStatisticsItems = memoizeOne(
(
-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;
}
`;
}
+105 -73
View File
@@ -8,8 +8,8 @@ import "./ha-icon-button";
import "./ha-input-helper-text";
import "./ha-select";
import type { HaSelectSelectEvent } from "./ha-select";
import "./ha-textfield";
import type { HaTextField } from "./ha-textfield";
import "./input/ha-input";
import type { HaInput } from "./input/ha-input";
export interface TimeChangedEvent {
days?: number;
@@ -133,7 +133,7 @@ export class HaBaseTimeInput extends LitElement {
@property({ type: Boolean, reflect: true }) public clearable?: boolean;
@queryAll("ha-textfield") private _inputs?: HaTextField[];
@queryAll("ha-input") private _inputs?: HaInput[];
static shadowRootOptions = {
...LitElement.shadowRootOptions,
@@ -153,7 +153,7 @@ export class HaBaseTimeInput extends LitElement {
<div class="time-input-wrap">
${this.enableDay
? html`
<ha-textfield
<ha-input
id="day"
type="number"
inputmode="numeric"
@@ -162,19 +162,18 @@ export class HaBaseTimeInput extends LitElement {
name="days"
@change=${this._valueChanged}
@focusin=${this._onFocus}
no-spinner
without-spin-buttons
.required=${this.required}
.autoValidate=${this.autoValidate}
min="0"
.disabled=${this.disabled}
suffix=":"
class="hasSuffix"
>
</ha-textfield>
</ha-input>
<div class="time-separator">:</div>
`
: nothing}
<ha-textfield
<ha-input
id="hour"
type="number"
inputmode="numeric"
@@ -183,18 +182,17 @@ export class HaBaseTimeInput extends LitElement {
name="hours"
@change=${this._valueChanged}
@focusin=${this._onFocus}
no-spinner
without-spin-buttons
.required=${this.required}
.autoValidate=${this.autoValidate}
maxlength="2"
max=${ifDefined(this._hourMax)}
min="0"
.disabled=${this.disabled}
suffix=":"
class="hasSuffix"
>
</ha-textfield>
<ha-textfield
</ha-input>
<div class="time-separator">:</div>
<ha-input
id="min"
type="number"
inputmode="numeric"
@@ -203,41 +201,43 @@ export class HaBaseTimeInput extends LitElement {
@change=${this._valueChanged}
@focusin=${this._onFocus}
name="minutes"
no-spinner
without-spin-buttons
.required=${this.required}
.autoValidate=${this.autoValidate}
maxlength="2"
max="59"
min="0"
.disabled=${this.disabled}
.suffix=${this.enableSecond ? ":" : ""}
class=${this.enableSecond ? "has-suffix" : ""}
>
</ha-textfield>
</ha-input>
${this.enableSecond
? html`<ha-textfield
id="sec"
type="number"
inputmode="decimal"
step="any"
.value=${this._formatValue(this.seconds)}
.label=${this.secLabel}
@change=${this._valueChanged}
@focusin=${this._onFocus}
name="seconds"
no-spinner
.required=${this.required}
.autoValidate=${this.autoValidate}
max="59"
min="0"
.disabled=${this.disabled}
.suffix=${this.enableMillisecond ? ":" : ""}
class=${this.enableMillisecond ? "has-suffix" : ""}
>
</ha-textfield>`
? html`<div class="time-separator">:</div>`
: nothing}
${this.enableSecond
? html`<ha-input
id="sec"
type="number"
inputmode="decimal"
step="any"
.value=${this._formatValue(this.seconds)}
.label=${this.secLabel}
@change=${this._valueChanged}
@focusin=${this._onFocus}
name="seconds"
without-spin-buttons
.required=${this.required}
.autoValidate=${this.autoValidate}
max="59"
min="0"
.disabled=${this.disabled}
>
</ha-input>
${this.enableMillisecond
? html`<div class="time-separator">:</div>`
: nothing}`
: nothing}
${this.enableMillisecond
? html`<ha-textfield
? html`<ha-input
id="millisec"
type="number"
.value=${this._formatValue(this.milliseconds, 3)}
@@ -245,7 +245,7 @@ export class HaBaseTimeInput extends LitElement {
@change=${this._valueChanged}
@focusin=${this._onFocus}
name="milliseconds"
no-spinner
without-spin-buttons
.required=${this.required}
.autoValidate=${this.autoValidate}
maxlength="3"
@@ -253,8 +253,19 @@ export class HaBaseTimeInput extends LitElement {
min="0"
.disabled=${this.disabled}
>
</ha-textfield>`
</ha-input>`
: nothing}
${this.format === 24
? nothing
: html`<ha-select
.required=${this.required}
.value=${this.amPm}
.disabled=${this.disabled}
.name=${"amPm"}
@selected=${this._valueChanged}
.options=${["AM", "PM"]}
>
</ha-select>`}
${this.clearable && !this.required && !this.disabled
? html`<ha-icon-button
label="clear"
@@ -263,18 +274,6 @@ export class HaBaseTimeInput extends LitElement {
></ha-icon-button>`
: nothing}
</div>
${this.format === 24
? nothing
: html`<ha-select
.required=${this.required}
.value=${this.amPm}
.disabled=${this.disabled}
.name=${"amPm"}
@selected=${this._valueChanged}
.options=${["AM", "PM"]}
>
</ha-select>`}
</div>
${this.helper
? html`<ha-input-helper-text .disabled=${this.disabled}
@@ -289,8 +288,8 @@ export class HaBaseTimeInput extends LitElement {
}
private _valueChanged(ev: InputEvent | HaSelectSelectEvent): void {
const textField = ev.currentTarget as HaTextField;
this[textField.name] =
const textField = ev.currentTarget as HaInput;
this[textField.name || ""] =
textField.name === "amPm"
? (ev as HaSelectSelectEvent).detail.value
: Number(textField.value);
@@ -312,7 +311,7 @@ export class HaBaseTimeInput extends LitElement {
}
private _onFocus(ev: FocusEvent) {
(ev.currentTarget as HaTextField).select();
(ev.currentTarget as HaInput).select();
}
/**
@@ -354,41 +353,74 @@ export class HaBaseTimeInput extends LitElement {
direction: ltr;
padding-right: 3px;
}
ha-textfield {
ha-input {
height: 56px;
padding: 0;
width: 60px;
flex-grow: 1;
text-align: center;
--mdc-shape-small: 0;
--text-field-appearance: none;
--text-field-padding: 0 4px;
--text-field-suffix-padding-left: 2px;
--text-field-suffix-padding-right: 0;
--text-field-text-align: center;
}
ha-textfield.hasSuffix {
--text-field-padding: 0 0 0 4px;
}
ha-textfield:first-child {
ha-input:first-child {
--text-field-border-top-left-radius: var(--mdc-shape-medium);
}
ha-textfield:last-child {
ha-input:last-child {
--text-field-border-top-right-radius: var(--mdc-shape-medium);
}
ha-input::part(wa-base) {
padding: var(--ha-space-1);
}
ha-input:first-child::part(wa-base) {
padding-inline-start: var(--ha-space-4);
}
ha-input:last-child::part(wa-base) {
padding-inline-end: var(--ha-space-4);
}
ha-input::part(wa-hint) {
height: 0;
}
ha-input::part(wa-input) {
text-align: center;
}
.time-separator,
ha-icon-button {
background-color: var(--ha-color-fill-neutral-quiet-resting);
color: var(--ha-color-text-secondary);
border-bottom: 1px solid var(--ha-color-border-neutral-loud);
box-sizing: border-box;
height: 56px;
margin-inline-start: calc(var(--ha-space-1) * -1);
}
.time-separator {
width: 12px;
margin-inline-end: calc(var(--ha-space-1) * -1);
display: flex;
align-items: center;
justify-content: center;
}
:host([clearable]) .mdc-select__anchor {
padding-inline-end: var(--select-selected-text-padding-end, 12px);
}
ha-icon-button {
position: relative;
--ha-icon-button-size: 36px;
border-start-end-radius: var(--ha-border-radius-sm);
--mdc-icon-size: 20px;
color: var(--secondary-text-color);
direction: var(--direction);
display: flex;
align-items: center;
background-color: var(--mdc-text-field-fill-color, whitesmoke);
border-bottom-style: solid;
border-bottom-width: 1px;
}
ha-select {
margin-inline: calc(var(--ha-space-1) * -1);
}
label {
-moz-osx-font-smoothing: var(--ha-moz-osx-font-smoothing);
-webkit-font-smoothing: var(--ha-font-smoothing);
+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);
+3 -2
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;
}
@@ -67,7 +68,7 @@ export class HaButton extends Button {
--ha-button-height,
var(--button-height, 32px)
);
font-size: var(--wa-font-size-s, var(--ha-font-size-m));
font-size: var(--ha-font-size-m);
--wa-form-control-padding-inline: var(--ha-space-3);
}
+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>
+3 -2
View File
@@ -11,6 +11,7 @@ import "./ha-svg-icon";
export interface ControlSelectOption {
value: string;
label?: string;
ariaLabel?: string;
icon?: TemplateResult;
path?: string;
}
@@ -161,8 +162,8 @@ export class HaControlSelect extends LitElement {
tabindex=${isSelected ? "0" : "-1"}
.value=${option.value}
aria-checked=${isSelected ? "true" : "false"}
aria-label=${ifDefined(option.label)}
title=${ifDefined(option.label)}
aria-label=${ifDefined(option.ariaLabel ?? option.label)}
title=${ifDefined(option.ariaLabel ?? option.label)}
@click=${this._handleOptionClick}
@focus=${this._handleOptionFocus}
@mousedown=${this._handleOptionMouseDown}
+219 -112
View File
@@ -1,117 +1,189 @@
import "app-datepicker";
import { format } from "date-fns";
import "@home-assistant/webawesome/dist/components/divider/divider";
import { consume, type ContextType } from "@lit/context";
import { mdiBackspace, mdiCalendarToday } from "@mdi/js";
import "cally";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
import { nextRender } from "../common/util/render-status";
import { haStyleDialog } from "../resources/styles";
import type { HomeAssistant } from "../types";
import type { DatePickerDialogParams } from "./ha-date-input";
import { customElement, state } from "lit/decorators";
import {
formatDateMonth,
formatDateShort,
formatDateYear,
} from "../common/datetime/format_date";
import { configContext, localeContext, localizeContext } from "../data/context";
import { DialogMixin } from "../dialogs/dialog-mixin";
import "./ha-button";
import "./ha-dialog-footer";
import type { DatePickerDialogParams } from "./ha-date-input";
import "./ha-dialog";
import "./ha-dialog-footer";
import "./ha-icon-button";
import "./ha-icon-button-next";
import "./ha-icon-button-prev";
type CalendarDate = HTMLElementTagNameMap["calendar-date"];
/**
* A date picker dialog component that displays a calendar for selecting dates.
* Uses the `cally` library for calendar rendering and supports localization,
* min/max date constraints, and optional clearing of the selected date.
*
* @element ha-dialog-date-picker
* Uses {@link DialogMixin} with {@link DatePickerDialogParams} to manage dialog state and parameters.
*/
@customElement("ha-dialog-date-picker")
export class HaDialogDatePicker extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
export class HaDialogDatePicker extends DialogMixin<DatePickerDialogParams>(
LitElement
) {
@state()
@consume({ context: localizeContext, subscribe: true })
private localize!: ContextType<typeof localizeContext>;
@property() public value?: string;
@state()
@consume({ context: localeContext, subscribe: true })
private locale!: ContextType<typeof localeContext>;
@property({ type: Boolean }) public disabled = false;
@state()
@consume({ context: configContext, subscribe: true })
private hassConfig!: ContextType<typeof configContext>;
@property() public label?: string;
@state() private _value?: {
year: string;
title: string;
dateString: string;
};
@state() private _params?: DatePickerDialogParams;
/** used to show month in calendar-date header */
@state() private _pickerMonth?: string;
@state() private _open = false;
/** used to show year in calendar-date header */
@state() private _pickerYear?: string;
@state() private _value?: string;
/** used for today to navigate focus in cally-calendar-date */
@state() private _focusDate?: string;
public async showDialog(params: DatePickerDialogParams): Promise<void> {
// app-datepicker has a bug, that it removes its handlers when disconnected, but doesn't add them back when reconnected.
// So we need to wait for the next render to make sure the element is removed and re-created so the handlers are added.
await nextRender();
this._params = params;
this._value = params.value;
this._open = true;
}
public connectedCallback() {
super.connectedCallback();
public closeDialog() {
this._open = false;
}
if (this.params) {
const date = this.params.value
? new Date(`${this.params.value.split("T")[0]}T00:00:00`)
: new Date();
private _dialogClosed() {
this._params = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
this._pickerYear = formatDateYear(date, this.locale, this.hassConfig);
this._pickerMonth = formatDateMonth(date, this.locale, this.hassConfig);
this._value = this.params.value
? {
year: this._pickerYear,
title: formatDateShort(date, this.locale, this.hassConfig),
dateString: this.params.value.substring(0, 10),
}
: undefined;
}
}
render() {
if (!this._params) {
if (!this.params) {
return nothing;
}
return html`<ha-dialog
.hass=${this.hass}
.open=${this._open}
open
width="small"
without-header
@closed=${this._dialogClosed}
.headerTitle=${this._value?.title ||
this.localize("ui.dialogs.date-picker.title")}
.headerSubtitle=${this._value?.year}
header-subtitle-position="above"
>
<app-datepicker
.value=${this._value}
.min=${this._params.min}
.max=${this._params.max}
.locale=${this._params.locale}
@datepicker-value-updated=${this._valueChanged}
.firstDayOfWeek=${this._params.firstWeekday}
></app-datepicker>
<div class="bottom-actions">
${this._params.canClear
? html`<ha-button
slot="secondaryAction"
${this.params.canClear
? html`
<ha-icon-button
.path=${mdiBackspace}
.label=${this.localize("ui.dialogs.date-picker.clear")}
slot="headerActionItems"
@click=${this._clear}
variant="danger"
appearance="plain"
>
${this.hass.localize("ui.dialogs.date-picker.clear")}
</ha-button>`
: nothing}
<ha-button
appearance="plain"
slot="secondaryAction"
@click=${this._setToday}
>
${this.hass.localize("ui.dialogs.date-picker.today")}
</ha-button>
</div>
></ha-icon-button>
`
: nothing}
<wa-divider></wa-divider>
<calendar-date
.value=${this._value?.dateString}
.min=${this.params.min}
.max=${this.params.max}
.locale=${this.params.locale}
.firstDayOfWeek=${this.params.firstWeekday}
.focusedDate=${this._focusDate}
@change=${this._valueChanged}
@focusday=${this._focusChanged}
>
<ha-icon-button-prev
tabindex="-1"
slot="previous"
></ha-icon-button-prev>
<div class="heading" slot="heading">
<span class="month-year"
>${this._pickerMonth} ${this._pickerYear}</span
>
<ha-icon-button
@click=${this._setToday}
.path=${mdiCalendarToday}
.label=${this.localize("ui.dialogs.date-picker.today")}
></ha-icon-button>
</div>
<ha-icon-button-next tabindex="-1" slot="next"></ha-icon-button-next>
<calendar-month></calendar-month>
</calendar-date>
<ha-dialog-footer slot="footer">
<ha-button
appearance="plain"
slot="secondaryAction"
@click=${this.closeDialog}
>
${this.hass.localize("ui.common.cancel")}
${this.localize("ui.common.cancel")}
</ha-button>
<ha-button slot="primaryAction" @click=${this._setValue}>
${this.hass.localize("ui.common.ok")}
${this.localize("ui.common.ok")}
</ha-button>
</ha-dialog-footer>
</ha-dialog>`;
}
private _valueChanged(ev: CustomEvent) {
this._value = ev.detail.value;
private _valueChanged(ev: Event) {
const dateElement = ev.target as CalendarDate;
if (dateElement.value) {
this._updateValue(dateElement.value);
}
}
private _updateValue(value?: string, setFocusDay = false) {
const date = value
? new Date(`${value.split("T")[0]}T00:00:00`)
: new Date();
this._value = {
year: formatDateYear(date, this.locale, this.hassConfig),
title: formatDateShort(date, this.locale, this.hassConfig),
dateString: value || date.toISOString().substring(0, 10),
};
if (setFocusDay) {
this._focusDate = this._value.dateString;
this._pickerMonth = formatDateMonth(date, this.locale, this.hassConfig);
this._pickerYear = formatDateYear(date, this.locale, this.hassConfig);
}
}
private _focusChanged(ev: CustomEvent<Date>) {
const date = ev.detail;
this._pickerMonth = formatDateMonth(date, this.locale, this.hassConfig);
this._pickerYear = formatDateYear(date, this.locale, this.hassConfig);
this._focusDate = undefined;
}
private _clear() {
this._params?.onChange(undefined);
this.params?.onChange(undefined);
this.closeDialog();
}
private _setToday() {
const today = new Date();
this._value = format(today, "yyyy-MM-dd");
this._updateValue(undefined, true);
}
private _setValue() {
@@ -120,50 +192,85 @@ export class HaDialogDatePicker extends LitElement {
// without changing the date, should return todays date, not undefined.
this._setToday();
}
this._params?.onChange(this._value!);
this.params?.onChange(this._value?.dateString);
this.closeDialog();
}
static styles = [
haStyleDialog,
css`
ha-dialog {
--dialog-content-padding: 0;
}
.bottom-actions {
display: flex;
gap: var(--ha-space-4);
justify-content: center;
align-items: center;
width: 100%;
margin-bottom: var(--ha-space-1);
}
app-datepicker {
display: block;
margin-inline: auto;
--app-datepicker-accent-color: var(--primary-color);
--app-datepicker-bg-color: transparent;
--app-datepicker-color: var(--primary-text-color);
--app-datepicker-disabled-day-color: var(--disabled-text-color);
--app-datepicker-focused-day-color: var(--text-primary-color);
--app-datepicker-focused-year-bg-color: var(--primary-color);
--app-datepicker-selector-color: var(--secondary-text-color);
--app-datepicker-separator-color: var(--divider-color);
--app-datepicker-weekday-color: var(--secondary-text-color);
}
app-datepicker::part(calendar-day):focus {
outline: none;
}
app-datepicker::part(body) {
direction: ltr;
}
@media all and (max-width: 450px), all and (max-height: 500px) {
app-datepicker {
width: 100%;
}
}
`,
];
static styles = css`
ha-dialog {
--dialog-content-padding: 0;
}
calendar-date {
width: 100%;
}
calendar-date::part(button) {
border: none;
background-color: unset;
border-radius: var(--ha-border-radius-circle);
outline-offset: -2px;
outline-color: var(--ha-color-neutral-60);
}
calendar-month {
width: 100%;
margin: 0 auto;
min-height: calc(42px * 7);
}
calendar-month::part(heading) {
display: none;
}
calendar-month::part(day) {
color: var(--disabled-text-color);
font-size: var(--ha-font-size-m);
font-family: var(--ha-font-body);
}
calendar-month::part(button),
calendar-month::part(selected):focus-visible {
color: var(--primary-text-color);
height: 32px;
width: 32px;
margin: var(--ha-space-1);
border-radius: var(--ha-border-radius-circle);
}
calendar-month::part(button):focus-visible {
background-color: inherit;
outline: 1px solid var(--ha-color-neutral-60);
outline-offset: 2px;
}
calendar-month::part(button):hover {
background-color: var(--ha-color-fill-primary-quiet-hover);
}
calendar-month::part(today) {
color: var(--primary-color);
}
calendar-month::part(selected),
calendar-month::part(selected):hover {
color: var(--text-primary-color);
background-color: var(--primary-color);
height: 40px;
width: 40px;
margin: 0;
}
calendar-month::part(selected):focus-visible {
background-color: var(--primary-color);
color: var(--text-primary-color);
}
.heading {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
font-size: var(--ha-font-size-m);
font-weight: var(--ha-font-weight-medium);
}
.month-year {
flex: 1;
text-align: center;
margin-left: 48px;
}
`;
}
declare global {
+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
);
+20 -64
View File
@@ -1,15 +1,11 @@
import { mdiEye, mdiEyeOff } from "@mdi/js";
import type { PropertyValues, TemplateResult } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { customElement, property, query } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import type {
LocalizeFunc,
LocalizeKeys,
} from "../../common/translations/localize";
import type { LocalizeFunc } from "../../common/translations/localize";
import "../ha-icon-button";
import "../ha-textfield";
import type { HaTextField } from "../ha-textfield";
import "../input/ha-input";
import type { HaInput } from "../input/ha-input";
import type {
HaFormElement,
HaFormStringData,
@@ -35,9 +31,7 @@ export class HaFormString extends LitElement implements HaFormElement {
@property({ type: Boolean }) public disabled = false;
@state() protected unmaskedPassword = false;
@query("ha-textfield", true) private _input?: HaTextField;
@query("ha-input") private _input?: HaInput;
static shadowRootOptions = {
...LitElement.shadowRootOptions,
@@ -50,48 +44,28 @@ export class HaFormString extends LitElement implements HaFormElement {
protected render(): TemplateResult {
return html`
<ha-textfield
.type=${!this.isPassword
? this.stringType
: this.unmaskedPassword
? "text"
: "password"}
<ha-input
.passwordToggle=${this.isPassword}
.type=${!this.isPassword ? this.stringType : "password"}
.label=${this.label}
.value=${this.data || ""}
.helper=${this.helper}
helperPersistent
.hint=${this.helper}
.disabled=${this.disabled}
.required=${this.schema.required}
.autoValidate=${this.schema.required}
.required=${!!this.schema.required}
.autoValidate=${!!this.schema.required}
.name=${this.schema.name}
.autofocus=${this.schema.autofocus}
.autofocus=${!!this.schema.autofocus}
.autocomplete=${this.schema.autocomplete}
.suffix=${this.isPassword
? // reserve some space for the icon.
html`<div style="width: 24px"></div>`
: this.schema.description?.suffix}
.validationMessage=${this.schema.required
? this.localize?.("ui.common.error_required")
: undefined}
@input=${this._valueChanged}
@change=${this._valueChanged}
></ha-textfield>
${this.renderIcon()}
`;
}
protected renderIcon() {
if (!this.isPassword) return nothing;
return html`
<ha-icon-button
.label=${this.localize?.(
`${this.localizeBaseKey}.${
this.unmaskedPassword ? "hide_password" : "show_password"
}` as LocalizeKeys
)}
@click=${this.toggleUnmaskedPassword}
.path=${this.unmaskedPassword ? mdiEyeOff : mdiEye}
></ha-icon-button>
>
${this.schema.description?.suffix
? html`<span slot="end">${this.schema.description.suffix}</span>`
: nothing}
</ha-input>
`;
}
@@ -101,12 +75,8 @@ export class HaFormString extends LitElement implements HaFormElement {
}
}
protected toggleUnmaskedPassword(): void {
this.unmaskedPassword = !this.unmaskedPassword;
}
protected _valueChanged(ev: Event): void {
let value: string | undefined = (ev.target as HaTextField).value;
let value: string | undefined = (ev.target as HaInput).value;
if (this.data === value) {
return;
}
@@ -118,10 +88,10 @@ export class HaFormString extends LitElement implements HaFormElement {
});
}
protected get stringType(): string {
protected get stringType(): "email" | "url" | "text" {
if (this.schema.format) {
if (["email", "url"].includes(this.schema.format)) {
return this.schema.format;
return this.schema.format as "email" | "url";
}
if (this.schema.format === "fqdnurl") {
return "url";
@@ -142,20 +112,6 @@ export class HaFormString extends LitElement implements HaFormElement {
:host([own-margin]) {
margin-bottom: 5px;
}
ha-textfield {
display: block;
}
ha-icon-button {
position: absolute;
top: 8px;
right: 8px;
inset-inline-start: initial;
inset-inline-end: 8px;
--ha-icon-button-size: 40px;
--mdc-icon-size: 20px;
color: var(--secondary-text-color);
direction: var(--direction);
}
`;
}
+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))
);
-34
View File
@@ -1,34 +0,0 @@
import { styles } from "@material/web/textfield/internal/filled-styles";
import { FilledTextField } from "@material/web/textfield/internal/filled-text-field";
import { styles as sharedStyles } from "@material/web/textfield/internal/shared-styles";
import { css } from "lit";
import { customElement } from "lit/decorators";
@customElement("ha-md-textfield")
export class HaMdTextfield extends FilledTextField {
static override styles = [
sharedStyles,
styles,
css`
:host {
--ha-icon-display: block;
--md-sys-color-primary: var(--primary-text-color);
--md-sys-color-secondary: var(--secondary-text-color);
--md-sys-color-surface: var(--card-background-color);
--md-sys-color-on-surface-variant: var(--secondary-text-color);
--md-sys-color-surface-container-highest: var(--input-fill-color);
--md-sys-color-on-surface: var(--input-ink-color);
--md-sys-color-surface-container: var(--input-fill-color);
--md-sys-color-secondary-container: var(--input-fill-color);
}
`,
];
}
declare global {
interface HTMLElementTagNameMap {
"ha-md-textfield": HaMdTextfield;
}
}
+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;
-211
View File
@@ -1,211 +0,0 @@
import type { TextAreaCharCounter } from "@material/mwc-textfield/mwc-textfield-base";
import { mdiEye, mdiEyeOff } from "@mdi/js";
import { LitElement, css, html } from "lit";
import {
customElement,
eventOptions,
property,
query,
state,
} from "lit/decorators";
import type { HomeAssistant } from "../types";
import "./ha-icon-button";
import "./ha-textfield";
import type { HaTextField } from "./ha-textfield";
@customElement("ha-password-field")
export class HaPasswordField extends LitElement {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ type: Boolean }) public invalid?: boolean;
@property({ attribute: "error-message" }) public errorMessage?: string;
@property({ type: Boolean }) public icon = false;
// eslint-disable-next-line lit/attribute-names
@property({ type: Boolean }) public iconTrailing = false;
@property() public autocomplete?: string;
@property({ type: Boolean }) public autocorrect = true;
@property({ attribute: "input-spellcheck" })
public inputSpellcheck?: string;
@property({ type: String }) value = "";
@property({ type: String }) placeholder = "";
@property({ type: String }) label = "";
@property({ type: Boolean, reflect: true }) disabled = false;
@property({ type: Boolean }) required = false;
// eslint-disable-next-line lit/attribute-names
@property({ type: Number }) minLength = -1;
// eslint-disable-next-line lit/attribute-names
@property({ type: Number }) maxLength = -1;
@property({ type: Boolean, reflect: true }) outlined = false;
@property({ type: String }) helper = "";
// eslint-disable-next-line lit/attribute-names
@property({ type: Boolean }) validateOnInitialRender = false;
// eslint-disable-next-line lit/attribute-names
@property({ type: String }) validationMessage = "";
// eslint-disable-next-line lit/attribute-names
@property({ type: Boolean }) autoValidate = false;
@property({ type: String }) pattern = "";
@property({ type: Number }) size: number | null = null;
// eslint-disable-next-line lit/attribute-names
@property({ type: Boolean }) helperPersistent = false;
// eslint-disable-next-line lit/attribute-names
@property({ type: Boolean }) charCounter: boolean | TextAreaCharCounter =
false;
// eslint-disable-next-line lit/attribute-names
@property({ type: Boolean }) endAligned = false;
@property({ type: String }) prefix = "";
@property({ type: String }) suffix = "";
@property({ type: String }) name = "";
@property({ type: String, attribute: "input-mode" })
inputMode!: string;
// eslint-disable-next-line lit/attribute-names
@property({ type: Boolean }) readOnly = false;
// eslint-disable-next-line lit/no-native-attributes
@property({ attribute: false }) autocapitalize = "";
@state() private _unmaskedPassword = false;
@query("ha-textfield") private _textField!: HaTextField;
protected render() {
return html`<ha-textfield
.invalid=${this.invalid}
.errorMessage=${this.errorMessage}
.icon=${this.icon}
.iconTrailing=${this.iconTrailing}
.autocomplete=${this.autocomplete}
.autocorrect=${this.autocorrect}
.inputSpellcheck=${this.inputSpellcheck}
.value=${this.value}
.placeholder=${this.placeholder}
.label=${this.label}
.disabled=${this.disabled}
.required=${this.required}
.minLength=${this.minLength}
.maxLength=${this.maxLength}
.outlined=${this.outlined}
.helper=${this.helper}
.validateOnInitialRender=${this.validateOnInitialRender}
.validationMessage=${this.validationMessage}
.autoValidate=${this.autoValidate}
.pattern=${this.pattern}
.size=${this.size}
.helperPersistent=${this.helperPersistent}
.charCounter=${this.charCounter}
.endAligned=${this.endAligned}
.prefix=${this.prefix}
.name=${this.name}
.inputMode=${this.inputMode}
.readOnly=${this.readOnly}
.autocapitalize=${this.autocapitalize}
.type=${this._unmaskedPassword ? "text" : "password"}
.suffix=${html`<div style="width: 24px"></div>`}
@input=${this._handleInputEvent}
@change=${this._handleChangeEvent}
></ha-textfield>
<ha-icon-button
.label=${this.hass?.localize(
this._unmaskedPassword
? "ui.components.selectors.text.hide_password"
: "ui.components.selectors.text.show_password"
) || (this._unmaskedPassword ? "Hide password" : "Show password")}
@click=${this._toggleUnmaskedPassword}
.path=${this._unmaskedPassword ? mdiEyeOff : mdiEye}
></ha-icon-button>`;
}
public focus(): void {
this._textField.focus();
}
public checkValidity(): boolean {
return this._textField.checkValidity();
}
public reportValidity(): boolean {
return this._textField.reportValidity();
}
public setCustomValidity(message: string): void {
return this._textField.setCustomValidity(message);
}
public layout(): Promise<void> {
return this._textField.layout();
}
private _toggleUnmaskedPassword(): void {
this._unmaskedPassword = !this._unmaskedPassword;
}
@eventOptions({ passive: true })
private _handleInputEvent(ev) {
this.value = ev.target.value;
}
@eventOptions({ passive: true })
private _handleChangeEvent(ev) {
this.value = ev.target.value;
this._reDispatchEvent(ev);
}
private _reDispatchEvent(oldEvent: Event) {
const newEvent = new Event(oldEvent.type, oldEvent);
this.dispatchEvent(newEvent);
}
static styles = css`
:host {
display: block;
position: relative;
}
ha-textfield {
width: 100%;
}
ha-icon-button {
position: absolute;
top: 8px;
right: 8px;
inset-inline-start: initial;
inset-inline-end: 8px;
--ha-icon-button-size: 40px;
--mdc-icon-size: 20px;
color: var(--secondary-text-color);
direction: var(--direction);
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"ha-password-field": HaPasswordField;
}
}

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