mirror of
https://github.com/home-assistant/frontend.git
synced 2026-03-10 09:17:44 +00:00
Compare commits
373 Commits
gen-dashbo
...
copilot/re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d68532bf5 | ||
|
|
42f630f8aa | ||
|
|
48a7dca81f | ||
|
|
71a5a1d3b4 | ||
|
|
ad91d360f8 | ||
|
|
a88f492c05 | ||
|
|
0fc85a52a0 | ||
|
|
e01dc89a48 | ||
|
|
52c36db5d4 | ||
|
|
f217dab8d4 | ||
|
|
b89b02e044 | ||
|
|
daff323606 | ||
|
|
356bceb1cc | ||
|
|
acb5af2f41 | ||
|
|
17314fe62b | ||
|
|
3826a69465 | ||
|
|
a68acdf9dd | ||
|
|
bb56bbb1ba | ||
|
|
76aef60c05 | ||
|
|
23af40743b | ||
|
|
c4326b4f3a | ||
|
|
d248f5614f | ||
|
|
a4da7b26ea | ||
|
|
3c49cdf3c0 | ||
|
|
26af81d1a4 | ||
|
|
2a08f2d79b | ||
|
|
a5be02b743 | ||
|
|
4228871f00 | ||
|
|
9a7a8fd377 | ||
|
|
8b82882e15 | ||
|
|
2701015eda | ||
|
|
1991a9e493 | ||
|
|
2b72c54194 | ||
|
|
a7cb2fe7a7 | ||
|
|
51ea0c8201 | ||
|
|
ead7081bc6 | ||
|
|
ee982b1899 | ||
|
|
e8b100a39e | ||
|
|
50c361db62 | ||
|
|
e7a8d15a13 | ||
|
|
fbd0409837 | ||
|
|
a0d100611f | ||
|
|
a969bf1065 | ||
|
|
a153330610 | ||
|
|
bd2f1ca3a8 | ||
|
|
3263034416 | ||
|
|
82b28b547a | ||
|
|
61c2c750b4 | ||
|
|
117690ee70 | ||
|
|
e753de85eb | ||
|
|
a240019968 | ||
|
|
0bdf4b8777 | ||
|
|
6337828ed8 | ||
|
|
b8e5af652b | ||
|
|
e4ae29e8b5 | ||
|
|
08231dbbb0 | ||
|
|
0ca656933d | ||
|
|
b23cf8eba4 | ||
|
|
61b546415d | ||
|
|
4e1b709303 | ||
|
|
34e65b302d | ||
|
|
336d0e1b9d | ||
|
|
58d4cf8d84 | ||
|
|
d3453aff37 | ||
|
|
64ff2e414c | ||
|
|
2ca25c980f | ||
|
|
73d93bc601 | ||
|
|
5ca6a8aced | ||
|
|
7ff4993e0b | ||
|
|
4e6fbacccc | ||
|
|
2958d49e36 | ||
|
|
92289dc7ea | ||
|
|
f6c1a890e4 | ||
|
|
d06321ed43 | ||
|
|
3c3d8d9974 | ||
|
|
4f39fa482d | ||
|
|
5d0fe3236c | ||
|
|
b86142ae50 | ||
|
|
5d2f3ee5e8 | ||
|
|
e3f7c631a7 | ||
|
|
49f9d95853 | ||
|
|
db3d7701b5 | ||
|
|
3e55acf531 | ||
|
|
f102618d9d | ||
|
|
a3c02b511d | ||
|
|
74111d248e | ||
|
|
f8161b3505 | ||
|
|
6070c1907a | ||
|
|
ce5991582c | ||
|
|
d17217fc90 | ||
|
|
86b4bd0013 | ||
|
|
108ba3abd6 | ||
|
|
d38a2894c4 | ||
|
|
4c70376a62 | ||
|
|
8d69bd1401 | ||
|
|
5dfecd3693 | ||
|
|
efd51d2234 | ||
|
|
668299c16a | ||
|
|
5e155a4030 | ||
|
|
809fa10135 | ||
|
|
1cbc38f231 | ||
|
|
9ed39bb523 | ||
|
|
4e3d66cf40 | ||
|
|
2eaad79d1c | ||
|
|
afef7a2c0f | ||
|
|
18d5224002 | ||
|
|
dbffdfeaca | ||
|
|
0a4b7917ab | ||
|
|
e1524358d9 | ||
|
|
8774f9c3fc | ||
|
|
f9a9aeacab | ||
|
|
b798fee116 | ||
|
|
b25f731f0f | ||
|
|
26a7372c5e | ||
|
|
70d3409d62 | ||
|
|
0711ecddab | ||
|
|
bcfaa67eba | ||
|
|
1b60e6e04e | ||
|
|
a1a634f6dc | ||
|
|
55f48fbb56 | ||
|
|
ca4d66b94c | ||
|
|
51fd2eedd9 | ||
|
|
434a7c2e93 | ||
|
|
b849fecf0b | ||
|
|
3a48e1996f | ||
|
|
8299386737 | ||
|
|
5e58ff476f | ||
|
|
758d955053 | ||
|
|
1efd5d26f0 | ||
|
|
36979f10cc | ||
|
|
812c59fcb4 | ||
|
|
0c34165bcf | ||
|
|
8c2bfbe9ce | ||
|
|
8f721d74e2 | ||
|
|
63782e6ef3 | ||
|
|
eaad2295a9 | ||
|
|
e74eee3d34 | ||
|
|
cc39010839 | ||
|
|
7f97425214 | ||
|
|
8fac6e63de | ||
|
|
2ac8fe2b21 | ||
|
|
45ca1b2cdc | ||
|
|
0667f1e789 | ||
|
|
db49678ccb | ||
|
|
2ca7e9f71e | ||
|
|
8d883450a8 | ||
|
|
2c136e00f5 | ||
|
|
6f82478598 | ||
|
|
1093bd890f | ||
|
|
456c638750 | ||
|
|
60ca50deb4 | ||
|
|
2064ab4141 | ||
|
|
d34c42e587 | ||
|
|
5da7bf6fba | ||
|
|
f05ff58d27 | ||
|
|
7b0a381d93 | ||
|
|
8b38e6d170 | ||
|
|
6daf0eb469 | ||
|
|
6f8f849af3 | ||
|
|
cafe0f62c6 | ||
|
|
721cf46ce5 | ||
|
|
4e087760ab | ||
|
|
8fcfd4be84 | ||
|
|
b03680a8ab | ||
|
|
7ab0622bec | ||
|
|
c5aad44768 | ||
|
|
20ee7e5dc7 | ||
|
|
32fdcc708e | ||
|
|
7dd9b3308e | ||
|
|
71b870be15 | ||
|
|
f08c5fa03a | ||
|
|
fca408ae23 | ||
|
|
f3a814e38a | ||
|
|
7b0e4651c4 | ||
|
|
e5fb0e21ec | ||
|
|
beb4c3bf8a | ||
|
|
ad41f91c7b | ||
|
|
dc9c20f4ac | ||
|
|
776840a527 | ||
|
|
3568d8281a | ||
|
|
5491b6c023 | ||
|
|
e60d8f3ca4 | ||
|
|
aa0df190ed | ||
|
|
7552e91f24 | ||
|
|
0c61304023 | ||
|
|
c61bc718c2 | ||
|
|
2229e851be | ||
|
|
32d3b854ca | ||
|
|
f5dbb89e25 | ||
|
|
2ca47fddd3 | ||
|
|
ca21658968 | ||
|
|
d8c1fe7f4d | ||
|
|
a159a84228 | ||
|
|
5c95fa65dd | ||
|
|
20cde0ef70 | ||
|
|
b0d272bc3d | ||
|
|
d844c5b894 | ||
|
|
2229d42429 | ||
|
|
2ba0e77e73 | ||
|
|
e72facdec8 | ||
|
|
16bbd84962 | ||
|
|
010eee76c5 | ||
|
|
c138608445 | ||
|
|
9632251a36 | ||
|
|
17c3699707 | ||
|
|
681cbfdbd1 | ||
|
|
7c6e88ca3f | ||
|
|
8ba7ff1705 | ||
|
|
8727396e63 | ||
|
|
f03a573154 | ||
|
|
be8a7e0fa5 | ||
|
|
67e3eeb45e | ||
|
|
309e60fc4f | ||
|
|
8edfd4d5ad | ||
|
|
32f69c08a1 | ||
|
|
4d7d76c9aa | ||
|
|
9f10bc1371 | ||
|
|
93a0f37974 | ||
|
|
2ad264beaf | ||
|
|
2eec2ded13 | ||
|
|
eca535dd81 | ||
|
|
33ba3f20aa | ||
|
|
a45ef6e019 | ||
|
|
b256fc820d | ||
|
|
67fb7f61c2 | ||
|
|
1b3ea3d55d | ||
|
|
3f724aba4f | ||
|
|
7e225ed8a7 | ||
|
|
bd78800d8a | ||
|
|
13d40993ef | ||
|
|
1ad74d46d6 | ||
|
|
4a128c904e | ||
|
|
e414bab746 | ||
|
|
a354026780 | ||
|
|
24014116dc | ||
|
|
b07b604a20 | ||
|
|
81525a2b14 | ||
|
|
3b6babe0be | ||
|
|
432e8fc0d7 | ||
|
|
2e350d24f5 | ||
|
|
024e953c89 | ||
|
|
c29401a5a5 | ||
|
|
f913677dfe | ||
|
|
3b3f8f3343 | ||
|
|
03917a5e1c | ||
|
|
5e3002f739 | ||
|
|
da4395b8b3 | ||
|
|
16609053ac | ||
|
|
7beadb8108 | ||
|
|
8ca169fc23 | ||
|
|
6502c14fad | ||
|
|
3d4b10caab | ||
|
|
20bfe8b633 | ||
|
|
c6ecb5f217 | ||
|
|
938cc6a1a1 | ||
|
|
1d1e05dbdf | ||
|
|
a1b4923673 | ||
|
|
823a42f567 | ||
|
|
81275a96ff | ||
|
|
2e372b2f8a | ||
|
|
541cc7d10b | ||
|
|
678ee7e82a | ||
|
|
ee72f4818d | ||
|
|
ccbd9c1f24 | ||
|
|
84135a9424 | ||
|
|
4ebc334298 | ||
|
|
3c4c3e39e5 | ||
|
|
1267003b42 | ||
|
|
733359c869 | ||
|
|
50b6e07ae5 | ||
|
|
1b62a7cff8 | ||
|
|
c293cf56f6 | ||
|
|
2ec6f3615d | ||
|
|
d3b92059e5 | ||
|
|
72e69f6291 | ||
|
|
3bff97595f | ||
|
|
19b1d03cd1 | ||
|
|
f04557688c | ||
|
|
d6953ea1bc | ||
|
|
4501db18c7 | ||
|
|
c68bcc5b32 | ||
|
|
3753c7d313 | ||
|
|
4a2ef18375 | ||
|
|
051da41eec | ||
|
|
9b1a679f21 | ||
|
|
905a0f957c | ||
|
|
d9a687b79c | ||
|
|
3b56497134 | ||
|
|
ed0ec871ce | ||
|
|
29f5362182 | ||
|
|
5096bab26c | ||
|
|
ce2892cab9 | ||
|
|
57748c15de | ||
|
|
ecb6a33c86 | ||
|
|
d34921ff6d | ||
|
|
e6c9e81082 | ||
|
|
aa13c6fa53 | ||
|
|
d47738aa24 | ||
|
|
d473ee1084 | ||
|
|
0372ed932f | ||
|
|
2baa044db5 | ||
|
|
3d04046bcc | ||
|
|
8e860cb17d | ||
|
|
c41d7ff923 | ||
|
|
c22fc1021a | ||
|
|
6344233934 | ||
|
|
23441d593b | ||
|
|
8393ed5fd4 | ||
|
|
09afe9bb51 | ||
|
|
fb8d6062c5 | ||
|
|
f93ae58b83 | ||
|
|
7626b26b2d | ||
|
|
d98e373f64 | ||
|
|
649516c9fa | ||
|
|
bbc4fb96b2 | ||
|
|
0ae639aeb0 | ||
|
|
0e7e41065e | ||
|
|
685843f112 | ||
|
|
5e1a99d94a | ||
|
|
d843349865 | ||
|
|
ec23164aa9 | ||
|
|
e74ef11101 | ||
|
|
a222f6a736 | ||
|
|
ef3dd16d45 | ||
|
|
5d4e1d205e | ||
|
|
1ee5ebbe75 | ||
|
|
59d705aa3d | ||
|
|
332e108dae | ||
|
|
3c15b29d0a | ||
|
|
130c708e23 | ||
|
|
588a14a8a7 | ||
|
|
a1ef6ad266 | ||
|
|
a6c1f87730 | ||
|
|
49252a3808 | ||
|
|
c7877fe38f | ||
|
|
e355a61d8f | ||
|
|
f2e19e51ce | ||
|
|
fd9ab8f561 | ||
|
|
faa1b3c98f | ||
|
|
acc4a84fc9 | ||
|
|
4d723dac37 | ||
|
|
f1d4d0ef98 | ||
|
|
88180a2708 | ||
|
|
258d87e3d5 | ||
|
|
55f22ba61a | ||
|
|
812f3ca8b9 | ||
|
|
7f880d11a0 | ||
|
|
6b2452c538 | ||
|
|
c2cbf8bd21 | ||
|
|
224bcece9c | ||
|
|
dc84b7698f | ||
|
|
bc22e6a9bd | ||
|
|
d44874783a | ||
|
|
8d1bb5c867 | ||
|
|
da1b528eee | ||
|
|
756138408a | ||
|
|
3c8f112565 | ||
|
|
2521f3dde4 | ||
|
|
56390aa01a | ||
|
|
9aac5b19da | ||
|
|
24afc3dc88 | ||
|
|
873c7b2947 | ||
|
|
648db4276b | ||
|
|
f86c3e7856 | ||
|
|
1d0251cc28 | ||
|
|
518cf87847 | ||
|
|
81a9216c44 | ||
|
|
f0e10e0058 | ||
|
|
5df8ea4f07 | ||
|
|
73f081f5cc | ||
|
|
f0d1db1da6 | ||
|
|
c658eb414b | ||
|
|
bac493b72b |
56
.github/PULL_REQUEST_TEMPLATE.md
vendored
56
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -21,6 +21,14 @@
|
||||
-->
|
||||
|
||||
|
||||
## Screenshots
|
||||
<!--
|
||||
If your PR includes visual changes, please add screenshots or a short video
|
||||
showing the before and after. This helps reviewers understand the impact of
|
||||
your changes.
|
||||
Note: Remove this section if this PR has no visual changes.
|
||||
-->
|
||||
|
||||
## Type of change
|
||||
<!--
|
||||
What type of change does your PR introduce to the Home Assistant frontend?
|
||||
@@ -35,16 +43,6 @@
|
||||
- [ ] Breaking change (fix/feature causing existing functionality to break)
|
||||
- [ ] Code quality improvements to existing code or addition of tests
|
||||
|
||||
## Example configuration
|
||||
<!--
|
||||
Supplying a configuration snippet, makes it easier for a maintainer to test
|
||||
your PR.
|
||||
-->
|
||||
|
||||
```yaml
|
||||
|
||||
```
|
||||
|
||||
## Additional information
|
||||
<!--
|
||||
Details are important, and help maintainers processing your PR.
|
||||
@@ -54,6 +52,8 @@
|
||||
- This PR fixes or closes issue: fixes #
|
||||
- This PR is related to issue or discussion:
|
||||
- Link to documentation pull request:
|
||||
- Link to developer documentation pull request:
|
||||
- Link to backend pull request:
|
||||
|
||||
## Checklist
|
||||
<!--
|
||||
@@ -61,18 +61,50 @@
|
||||
creating the PR. If you're unsure about any of them, don't hesitate to ask.
|
||||
We're here to help! This is simply a reminder of what we are going to look
|
||||
for before merging your code.
|
||||
|
||||
AI tools are welcome, but contributors are responsible for *fully*
|
||||
understanding the code before submitting a PR.
|
||||
-->
|
||||
|
||||
- [ ] I understand the code I am submitting and can explain how it works.
|
||||
- [ ] The code change is tested and works locally.
|
||||
- [ ] There is no commented out code in this PR.
|
||||
- [ ] Tests have been added to verify that the new code works.
|
||||
- [ ] I have followed the [development checklist][dev-checklist]
|
||||
- [ ] I have followed the [perfect PR recommendations][perfect-pr]
|
||||
- [ ] Any generated code has been carefully reviewed for correctness and compliance with project standards.
|
||||
|
||||
If user exposed functionality or configuration variables are added/changed:
|
||||
|
||||
- [ ] Documentation added/updated for [www.home-assistant.io][docs-repository]
|
||||
|
||||
<!--
|
||||
Thank you for contributing <3
|
||||
This project is very active and we have a high turnover of pull requests.
|
||||
|
||||
Unfortunately, the number of incoming pull requests is higher than what our
|
||||
reviewers can review and merge so there is a long backlog of pull requests
|
||||
waiting for review. You can help here!
|
||||
|
||||
By reviewing another pull request, you will help raise the code quality of
|
||||
that pull request and the final review will be faster. This way the general
|
||||
pace of pull request reviews will go up and your wait time will go down.
|
||||
|
||||
When picking a pull request to review, try to choose one that hasn't yet
|
||||
been reviewed.
|
||||
|
||||
Thanks for helping out!
|
||||
-->
|
||||
|
||||
To help with the load of incoming pull requests:
|
||||
|
||||
- [ ] I have reviewed two other [open pull requests][prs] in this repository.
|
||||
|
||||
[prs]: https://github.com/home-assistant/frontend/pulls?q=is%3Aopen+is%3Apr+-author%3A%40me+-draft%3Atrue+sort%3Acreated-desc+review%3Anone+-status%3Afailure
|
||||
|
||||
<!--
|
||||
Thank you for contributing <3
|
||||
|
||||
Below, some useful links you could explore:
|
||||
-->
|
||||
[dev-checklist]: https://developers.home-assistant.io/docs/development_checklist/
|
||||
[docs-repository]: https://github.com/home-assistant/home-assistant.io
|
||||
[perfect-pr]: https://developers.home-assistant.io/docs/review-process/#creating-the-perfect-pr
|
||||
|
||||
113
.github/copilot-instructions.md
vendored
113
.github/copilot-instructions.md
vendored
@@ -194,13 +194,13 @@ The View Transitions API creates smooth animations between DOM state changes. Wh
|
||||
- **Utility wrapper**: `src/common/util/view-transition.ts` - `withViewTransition()` function with graceful fallback
|
||||
- **Real-world example**: `src/util/launch-screen.ts` - Launch screen fade pattern with browser support detection
|
||||
- **Animation keyframes**: `src/resources/theme/animations.globals.ts` - Global `fade-in`, `fade-out`, `scale` animations
|
||||
- **Animation duration**: `src/resources/theme/core.globals.ts` - `--ha-animation-base-duration` (350ms, respects `prefers-reduced-motion`)
|
||||
- **Animation duration**: `src/resources/theme/core.globals.ts` - `--ha-animation-duration-fast` (150ms), `--ha-animation-duration-normal` (250ms), `--ha-animation-duration-slow` (350ms) (all respect `prefers-reduced-motion`)
|
||||
|
||||
**Implementation Guidelines:**
|
||||
|
||||
1. Always use `withViewTransition()` wrapper for automatic fallback
|
||||
2. Keep transitions simple (subtle crossfades and fades work best)
|
||||
3. Use `--ha-animation-base-duration` CSS variable for consistent timing
|
||||
3. Use `--ha-animation-duration-*` CSS variables for consistent timing (`fast`, `normal`, `slow`)
|
||||
4. Assign unique `view-transition-name` to elements (must be unique at any given time)
|
||||
5. For Lit components: Override `performUpdate()` or use `::part()` for internal elements
|
||||
|
||||
@@ -214,13 +214,6 @@ By default, `:root` receives `view-transition-name: root`, creating a full-page
|
||||
- Only one view transition can run at a time
|
||||
- **Shadow DOM incompatibility**: View transitions operate at document level and do not work within Shadow DOM due to style isolation ([spec discussion](https://github.com/w3c/csswg-drafts/issues/10303)). For web components, set `view-transition-name` on the `:host` element or use document-level transitions
|
||||
|
||||
**Current Usage & Planned Applications:**
|
||||
|
||||
- Launch screen fade out (implemented)
|
||||
- Automation sidebar transitions (planned - #27238)
|
||||
- More info dialog content changes (planned - #27672)
|
||||
- Toolbar navigation, ha-spinner transitions (planned)
|
||||
|
||||
**Specification & Documentation:**
|
||||
|
||||
For browser support, API details, and current specifications, refer to these authoritative sources (note: check publication dates as specs evolve):
|
||||
@@ -246,12 +239,7 @@ For browser support, API details, and current specifications, refer to these aut
|
||||
|
||||
## Component Library
|
||||
|
||||
### Dialog Components
|
||||
|
||||
**Available Dialog Types:**
|
||||
|
||||
- `ha-wa-dialog` - Preferred for new dialogs (Web Awesome based)
|
||||
- `ha-dialog` - Legacy component (still widely used)
|
||||
### Dialog Component
|
||||
|
||||
**Opening Dialogs (Fire Event Pattern - Recommended):**
|
||||
|
||||
@@ -265,6 +253,7 @@ fireEvent(this, "show-dialog", {
|
||||
|
||||
**Dialog Implementation Requirements:**
|
||||
|
||||
- Use `ha-dialog` component
|
||||
- Implement `HassDialog<T>` interface
|
||||
- Use `@state() private _open = false` to control dialog visibility
|
||||
- Set `_open = true` in `showDialog()`, `_open = false` in `closeDialog()`
|
||||
@@ -280,7 +269,6 @@ fireEvent(this, "show-dialog", {
|
||||
|
||||
- Use `width` attribute with predefined sizes: `"small"` (320px), `"medium"` (560px - default), `"large"` (720px), or `"full"`
|
||||
- Custom sizing is NOT recommended - use the standard width presets
|
||||
- Example: `<ha-wa-dialog width="small">` for alert/confirmation dialogs
|
||||
|
||||
**Button Appearance Guidelines:**
|
||||
|
||||
@@ -290,17 +278,9 @@ fireEvent(this, "show-dialog", {
|
||||
- **Button sizes**: Use `size="small"` (32px height) or default/medium (40px height)
|
||||
- Always place primary action in `slot="primaryAction"` and secondary in `slot="secondaryAction"` within `ha-dialog-footer`
|
||||
|
||||
**Recent Examples:**
|
||||
|
||||
See these files for current patterns:
|
||||
|
||||
- `src/panels/config/repairs/dialog-repairs-issue.ts`
|
||||
- `src/dialogs/restart/dialog-restart.ts`
|
||||
- `src/panels/config/lovelace/resources/dialog-lovelace-resource-detail.ts`
|
||||
|
||||
**Gallery Documentation:**
|
||||
|
||||
- `gallery/src/pages/components/ha-wa-dialog.markdown`
|
||||
- `gallery/src/pages/components/ha-dialog.markdown`
|
||||
- `gallery/src/pages/components/ha-dialogs.markdown`
|
||||
|
||||
### Form Component (ha-form)
|
||||
@@ -308,7 +288,6 @@ See these files for current patterns:
|
||||
- Schema-driven using `HaFormSchema[]`
|
||||
- Supports entity, device, area, target, number, boolean, time, action, text, object, select, icon, media, location selectors
|
||||
- Built-in validation with error display
|
||||
- Use `dialogInitialFocus` in dialogs
|
||||
- Use `computeLabel`, `computeError`, `computeHelper` for translations
|
||||
|
||||
```typescript
|
||||
@@ -393,81 +372,6 @@ export class HaPanelMyFeature extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
```
|
||||
|
||||
### Creating a Dialog
|
||||
|
||||
```typescript
|
||||
@customElement("dialog-my-feature")
|
||||
export class DialogMyFeature
|
||||
extends LitElement
|
||||
implements HassDialog<MyDialogParams>
|
||||
{
|
||||
@property({ attribute: false })
|
||||
hass!: HomeAssistant;
|
||||
|
||||
@state()
|
||||
private _params?: MyDialogParams;
|
||||
|
||||
@state()
|
||||
private _open = false;
|
||||
|
||||
public async showDialog(params: MyDialogParams): Promise<void> {
|
||||
this._params = params;
|
||||
this._open = true;
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._open = false;
|
||||
}
|
||||
|
||||
private _dialogClosed(): void {
|
||||
this._params = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._params) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-wa-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
header-title=${this._params.title}
|
||||
header-subtitle=${this._params.subtitle}
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
<p>Dialog content</p>
|
||||
<ha-dialog-footer slot="footer">
|
||||
<ha-button
|
||||
slot="secondaryAction"
|
||||
appearance="plain"
|
||||
@click=${this.closeDialog}
|
||||
>
|
||||
${this.hass.localize("ui.common.cancel")}
|
||||
</ha-button>
|
||||
<ha-button slot="primaryAction" @click=${this._submit}>
|
||||
${this.hass.localize("ui.common.save")}
|
||||
</ha-button>
|
||||
</ha-dialog-footer>
|
||||
</ha-wa-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = [haStyleDialog, css``];
|
||||
}
|
||||
```
|
||||
|
||||
### Dialog Design Guidelines
|
||||
|
||||
- Max width: 560px (Alert/confirmation: 320px fixed width)
|
||||
- Close X-icon on top left (all screen sizes)
|
||||
- Submit button grouped with cancel at bottom right
|
||||
- Keep button labels short: "Save", "Delete", "Enable"
|
||||
- Destructive actions use red warning button
|
||||
- Always use a title (best practice)
|
||||
- Strive for minimalism
|
||||
|
||||
#### Creating a Lovelace Card
|
||||
|
||||
**Purpose**: Cards allow users to tell different stories about their house (based on gallery)
|
||||
@@ -558,6 +462,10 @@ this.hass.localize("ui.panel.config.updates.update_available", {
|
||||
- Use HTTPS - All external resources must use HTTPS
|
||||
- CSP compliance - Ensure code works with Content Security Policy
|
||||
|
||||
### Pull Requests
|
||||
|
||||
When creating a pull request, you **must** use the PR template located at `.github/PULL_REQUEST_TEMPLATE.md`. Read the template file and use its full content as the PR body, filling in each section appropriately. Do not omit, reorder, or rewrite the template sections. Do not check the checklist items on behalf of the user — those are the user's responsibility to review and check. If the PR includes UI changes, remind the user to add screenshots or a short video to the PR after creating it.
|
||||
|
||||
### Text and Copy Guidelines
|
||||
|
||||
#### Terminology Standards
|
||||
@@ -715,9 +623,6 @@ this.hass.localize("ui.panel.config.automation.delete_confirm", {
|
||||
|
||||
### Component-Specific Checks
|
||||
|
||||
- [ ] Dialogs implement HassDialog interface
|
||||
- [ ] Dialog styling uses haStyleDialog
|
||||
- [ ] Dialog accessibility includes dialogInitialFocus
|
||||
- [ ] ha-alert used correctly for messages
|
||||
- [ ] ha-form uses proper schema structure
|
||||
- [ ] Components handle all states (loading, error, unavailable)
|
||||
|
||||
4
.github/workflows/ci.yaml
vendored
4
.github/workflows/ci.yaml
vendored
@@ -89,13 +89,13 @@ jobs:
|
||||
env:
|
||||
IS_TEST: "true"
|
||||
- name: Upload bundle stats
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: frontend-bundle-stats
|
||||
path: build/stats/*.json
|
||||
if-no-files-found: error
|
||||
- name: Upload frontend build
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: frontend-build
|
||||
path: hass_frontend/
|
||||
|
||||
6
.github/workflows/codeql-analysis.yml
vendored
6
.github/workflows/codeql-analysis.yml
vendored
@@ -36,14 +36,14 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0
|
||||
uses: github/codeql-action/init@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0
|
||||
uses: github/codeql-action/autobuild@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -57,4 +57,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0
|
||||
uses: github/codeql-action/analyze@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
|
||||
|
||||
4
.github/workflows/nightly.yaml
vendored
4
.github/workflows/nightly.yaml
vendored
@@ -57,14 +57,14 @@ jobs:
|
||||
run: tar -czvf translations.tar.gz translations
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: wheels
|
||||
path: dist/home_assistant_frontend*.whl
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload translations
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: translations
|
||||
path: translations.tar.gz
|
||||
|
||||
31
.github/workflows/restrict-task-creation.yml
vendored
31
.github/workflows/restrict-task-creation.yml
vendored
@@ -5,9 +5,38 @@ on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.issue.number }}
|
||||
|
||||
jobs:
|
||||
check-authorization:
|
||||
add-no-stale:
|
||||
name: Add no-stale label
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write # To add labels to issues
|
||||
if: >-
|
||||
github.event.issue.type.name == 'Task'
|
||||
|| github.event.issue.type.name == 'Epic'
|
||||
|| github.event.issue.type.name == 'Opportunity'
|
||||
steps:
|
||||
- name: Add no-stale label
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
script: |
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
labels: ['no-stale']
|
||||
});
|
||||
|
||||
check-authorization:
|
||||
name: Check authorization
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write # To comment on, label, and close issues
|
||||
# Only run if this is a Task issue type (from the issue form)
|
||||
if: github.event.issue.type.name == 'Task'
|
||||
steps:
|
||||
|
||||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 90 days stale policy
|
||||
uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1
|
||||
uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-stale: 90
|
||||
|
||||
@@ -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,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,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,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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -14,40 +14,28 @@ export const mockEnergy = (hass: MockHomeAssistant) => {
|
||||
energy_sources: [
|
||||
{
|
||||
type: "grid",
|
||||
flow_from: [
|
||||
{
|
||||
stat_energy_from: "sensor.energy_consumption_tarif_1",
|
||||
stat_cost: "sensor.energy_consumption_tarif_1_cost",
|
||||
entity_energy_price: null,
|
||||
number_energy_price: null,
|
||||
},
|
||||
{
|
||||
stat_energy_from: "sensor.energy_consumption_tarif_2",
|
||||
stat_cost: "sensor.energy_consumption_tarif_2_cost",
|
||||
entity_energy_price: null,
|
||||
number_energy_price: null,
|
||||
},
|
||||
],
|
||||
flow_to: [
|
||||
{
|
||||
stat_energy_to: "sensor.energy_production_tarif_1",
|
||||
stat_compensation:
|
||||
"sensor.energy_production_tarif_1_compensation",
|
||||
entity_energy_price: null,
|
||||
number_energy_price: null,
|
||||
},
|
||||
{
|
||||
stat_energy_to: "sensor.energy_production_tarif_2",
|
||||
stat_compensation:
|
||||
"sensor.energy_production_tarif_2_compensation",
|
||||
entity_energy_price: null,
|
||||
number_energy_price: null,
|
||||
},
|
||||
],
|
||||
power: [
|
||||
{ stat_rate: "sensor.power_grid" },
|
||||
{ stat_rate: "sensor.power_grid_return" },
|
||||
],
|
||||
stat_energy_from: "sensor.energy_consumption_tarif_1",
|
||||
stat_energy_to: "sensor.energy_production_tarif_1",
|
||||
stat_cost: "sensor.energy_consumption_tarif_1_cost",
|
||||
stat_compensation: "sensor.energy_production_tarif_1_compensation",
|
||||
entity_energy_price: null,
|
||||
number_energy_price: null,
|
||||
entity_energy_price_export: null,
|
||||
number_energy_price_export: null,
|
||||
stat_rate: "sensor.power_grid",
|
||||
cost_adjustment_day: 0,
|
||||
},
|
||||
{
|
||||
type: "grid",
|
||||
stat_energy_from: "sensor.energy_consumption_tarif_2",
|
||||
stat_energy_to: "sensor.energy_production_tarif_2",
|
||||
stat_cost: "sensor.energy_consumption_tarif_2_cost",
|
||||
stat_compensation: "sensor.energy_production_tarif_2_compensation",
|
||||
entity_energy_price: null,
|
||||
number_energy_price: null,
|
||||
entity_energy_price_export: null,
|
||||
number_energy_price_export: null,
|
||||
stat_rate: "sensor.power_grid_return",
|
||||
cost_adjustment_day: 0,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -12,6 +12,7 @@ import eslintConfigPrettier from "eslint-config-prettier";
|
||||
import { configs as litConfigs } from "eslint-plugin-lit";
|
||||
import { configs as wcConfigs } from "eslint-plugin-wc";
|
||||
import { configs as a11yConfigs } from "eslint-plugin-lit-a11y";
|
||||
import html from "@html-eslint/eslint-plugin";
|
||||
|
||||
const _filename = fileURLToPath(import.meta.url);
|
||||
const _dirname = path.dirname(_filename);
|
||||
@@ -192,5 +193,13 @@ export default tseslint.config(
|
||||
languageOptions: {
|
||||
globals: globals.audioWorklet,
|
||||
},
|
||||
},
|
||||
{
|
||||
plugins: {
|
||||
html,
|
||||
},
|
||||
rules: {
|
||||
"html/no-invalid-attr-value": "error",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -21,8 +21,8 @@ type DialogType =
|
||||
| "basic"
|
||||
| "basic-subtitle-below"
|
||||
| "basic-subtitle-above"
|
||||
| "allow-mode-change"
|
||||
| "form"
|
||||
| "form-block-mode"
|
||||
| "actions"
|
||||
| "large"
|
||||
| "small";
|
||||
@@ -69,8 +69,8 @@ export class DemoHaAdaptiveDialog extends LitElement {
|
||||
<ha-button @click=${this._handleOpenDialog("form")}
|
||||
>Adaptive dialog with form</ha-button
|
||||
>
|
||||
<ha-button @click=${this._handleOpenDialog("form-block-mode")}
|
||||
>Adaptive dialog with form (block mode change)</ha-button
|
||||
<ha-button @click=${this._handleOpenDialog("allow-mode-change")}
|
||||
>Adaptive dialog with allow mode change</ha-button
|
||||
>
|
||||
<ha-button @click=${this._handleOpenDialog("actions")}
|
||||
>Adaptive dialog with actions</ha-button
|
||||
@@ -164,27 +164,15 @@ export class DemoHaAdaptiveDialog extends LitElement {
|
||||
|
||||
<ha-adaptive-dialog
|
||||
.hass=${this._hass}
|
||||
.open=${this._openDialog === "form-block-mode"}
|
||||
header-title="Adaptive dialog with form (block mode change)"
|
||||
header-subtitle="This form will not reset when the viewport size changes"
|
||||
block-mode-change
|
||||
.allowModeChange=${this._openDialog === "allow-mode-change"}
|
||||
header-title="Adaptive dialog with allow mode change"
|
||||
header-subtitle="Resize the window while this dialog is open"
|
||||
@closed=${this._handleClosed}
|
||||
>
|
||||
<ha-form autofocus .schema=${SCHEMA}></ha-form>
|
||||
<ha-dialog-footer slot="footer">
|
||||
<ha-button
|
||||
@click=${this._handleClosed}
|
||||
slot="secondaryAction"
|
||||
variant="plain"
|
||||
>Cancel</ha-button
|
||||
>
|
||||
<ha-button
|
||||
@click=${this._handleClosed}
|
||||
slot="primaryAction"
|
||||
variant="accent"
|
||||
>Submit</ha-button
|
||||
>
|
||||
</ha-dialog-footer>
|
||||
<div>
|
||||
This dialog can switch between dialog mode and bottom sheet mode
|
||||
while open.
|
||||
</div>
|
||||
</ha-adaptive-dialog>
|
||||
|
||||
<ha-adaptive-dialog
|
||||
@@ -215,7 +203,7 @@ export class DemoHaAdaptiveDialog extends LitElement {
|
||||
<li>
|
||||
<strong>Dialog mode:</strong> Used on larger screens (width >
|
||||
870px and height > 500px). Renders as a centered dialog using
|
||||
<code>ha-wa-dialog</code>.
|
||||
<code>ha-dialog</code>.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Bottom sheet mode:</strong> Used on mobile devices and
|
||||
@@ -225,10 +213,9 @@ export class DemoHaAdaptiveDialog extends LitElement {
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
The mode is determined automatically and updates when the window is
|
||||
resized. To prevent mode changes after the initial mount (useful for
|
||||
preventing form resets), use the <code>block-mode-change</code>
|
||||
attribute.
|
||||
By default, the mode is determined at mount time and then stays fixed
|
||||
while the dialog is open. To allow switching modes while the viewport
|
||||
changes, use the <code>allow-mode-change</code> attribute.
|
||||
</p>
|
||||
|
||||
<h3>Width</h3>
|
||||
@@ -394,15 +381,15 @@ export class DemoHaAdaptiveDialog extends LitElement {
|
||||
|
||||
<p>
|
||||
If you don't need responsive behavior, use
|
||||
<code>ha-wa-dialog</code> directly for desktop-only dialogs or
|
||||
<code>ha-dialog</code> directly for desktop-only dialogs or
|
||||
<code>ha-bottom-sheet</code> for mobile-only sheets.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Use the <code>block-mode-change</code> attribute when you want to
|
||||
prevent the dialog from switching modes after it's opened. This is
|
||||
especially useful for forms, as it prevents form data from being lost
|
||||
when users resize their browser window.
|
||||
Use the <code>allow-mode-change</code> attribute when you want the
|
||||
dialog to switch between modes as the viewport changes after opening.
|
||||
For forms, you can keep the default behavior to avoid resetting fields
|
||||
on resize.
|
||||
</p>
|
||||
|
||||
<h3>Example usage</h3>
|
||||
@@ -410,7 +397,6 @@ export class DemoHaAdaptiveDialog extends LitElement {
|
||||
<pre><code><ha-adaptive-dialog
|
||||
.hass=\${this.hass}
|
||||
open
|
||||
width="medium"
|
||||
header-title="Dialog title"
|
||||
header-subtitle="Dialog subtitle"
|
||||
>
|
||||
@@ -427,27 +413,10 @@ export class DemoHaAdaptiveDialog extends LitElement {
|
||||
</ha-dialog-footer>
|
||||
</ha-adaptive-dialog></code></pre>
|
||||
|
||||
<p>Example with <code>block-mode-change</code> for forms:</p>
|
||||
|
||||
<pre><code><ha-adaptive-dialog
|
||||
.hass=\${this.hass}
|
||||
open
|
||||
header-title="Edit configuration"
|
||||
block-mode-change
|
||||
>
|
||||
<ha-form .schema=\${schema} .data=\${data}></ha-form>
|
||||
<ha-dialog-footer slot="footer">
|
||||
<ha-button slot="secondaryAction" variant="plain"
|
||||
>Cancel</ha-button
|
||||
>
|
||||
<ha-button slot="primaryAction" variant="accent">Save</ha-button>
|
||||
</ha-dialog-footer>
|
||||
</ha-adaptive-dialog></code></pre>
|
||||
|
||||
<h3>API</h3>
|
||||
|
||||
<p>
|
||||
This component combines <code>ha-wa-dialog</code> and
|
||||
This component combines <code>ha-dialog</code> and
|
||||
<code>ha-bottom-sheet</code> with automatic mode switching based on
|
||||
screen size.
|
||||
</p>
|
||||
@@ -521,12 +490,10 @@ export class DemoHaAdaptiveDialog extends LitElement {
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>block-mode-change</code></td>
|
||||
<td><code>allow-mode-change</code></td>
|
||||
<td>
|
||||
When set, the mode is determined at mount time based on the
|
||||
current screen size, but subsequent mode changes are blocked.
|
||||
Useful for preventing forms from resetting when the viewport
|
||||
size changes.
|
||||
When set, the dialog can switch between modes as the viewport
|
||||
size changes while it is open.
|
||||
</td>
|
||||
<td><code>false</code></td>
|
||||
<td><code>false</code>, <code>true</code></td>
|
||||
@@ -548,6 +515,14 @@ export class DemoHaAdaptiveDialog extends LitElement {
|
||||
<td><code>--ha-dialog-surface-background</code></td>
|
||||
<td>Dialog/sheet background color.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--ha-dialog-surface-backdrop-filter</code></td>
|
||||
<td>Dialog/sheet surface backdrop filter.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--dialog-box-shadow</code></td>
|
||||
<td>Dialog surface box shadow (dialog mode only).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--ha-dialog-border-radius</code></td>
|
||||
<td>Border radius of the dialog surface (dialog mode only).</td>
|
||||
@@ -560,6 +535,34 @@ export class DemoHaAdaptiveDialog extends LitElement {
|
||||
<td><code>--ha-dialog-hide-duration</code></td>
|
||||
<td>Hide animation duration (dialog mode only).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--ha-dialog-scrim-backdrop-filter</code></td>
|
||||
<td>Dialog/sheet scrim backdrop filter.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--dialog-backdrop-filter</code></td>
|
||||
<td>Dialog/sheet scrim backdrop filter (legacy fallback).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--mdc-dialog-scrim-color</code></td>
|
||||
<td>Dialog/sheet scrim color (legacy compatibility).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--ha-bottom-sheet-surface-background</code></td>
|
||||
<td>Bottom sheet background color (sheet mode only).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--ha-bottom-sheet-surface-backdrop-filter</code></td>
|
||||
<td>Bottom sheet surface backdrop filter (sheet mode only).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--ha-bottom-sheet-scrim-backdrop-filter</code></td>
|
||||
<td>Bottom sheet scrim backdrop filter (sheet mode only).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--ha-bottom-sheet-scrim-color</code></td>
|
||||
<td>Bottom sheet scrim color (sheet mode only).</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
3
gallery/src/pages/components/ha-dialog.markdown
Normal file
3
gallery/src/pages/components/ha-dialog.markdown
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
title: Dialog (ha-dialog)
|
||||
---
|
||||
@@ -6,7 +6,7 @@ import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-dialog-footer";
|
||||
import "../../../../src/components/ha-form/ha-form";
|
||||
import "../../../../src/components/ha-icon-button";
|
||||
import "../../../../src/components/ha-wa-dialog";
|
||||
import "../../../../src/components/ha-dialog";
|
||||
import type { HaFormSchema } from "../../../../src/components/ha-form/types";
|
||||
|
||||
const SCHEMA: HaFormSchema[] = [
|
||||
@@ -22,14 +22,14 @@ type DialogType =
|
||||
| "form"
|
||||
| "actions";
|
||||
|
||||
@customElement("demo-components-ha-wa-dialog")
|
||||
export class DemoHaWaDialog extends LitElement {
|
||||
@customElement("demo-components-ha-dialog")
|
||||
export class DemoHaDialog extends LitElement {
|
||||
@state() private _openDialog: DialogType = false;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<div class="content">
|
||||
<h1>Dialog <code><ha-wa-dialog></code></h1>
|
||||
<h1>Dialog <code><ha-dialog></code></h1>
|
||||
|
||||
<p class="subtitle">Dialog component built with WebAwesome.</p>
|
||||
|
||||
@@ -53,24 +53,24 @@ export class DemoHaWaDialog extends LitElement {
|
||||
>
|
||||
</div>
|
||||
|
||||
<ha-wa-dialog
|
||||
<ha-dialog
|
||||
.open=${this._openDialog === "basic"}
|
||||
header-title="Basic dialog"
|
||||
@closed=${this._handleClosed}
|
||||
>
|
||||
<div>Dialog content</div>
|
||||
</ha-wa-dialog>
|
||||
</ha-dialog>
|
||||
|
||||
<ha-wa-dialog
|
||||
<ha-dialog
|
||||
.open=${this._openDialog === "basic-subtitle-below"}
|
||||
header-title="Basic dialog with subtitle"
|
||||
header-subtitle="This is a basic dialog with a subtitle below"
|
||||
@closed=${this._handleClosed}
|
||||
>
|
||||
<div>Dialog content</div>
|
||||
</ha-wa-dialog>
|
||||
</ha-dialog>
|
||||
|
||||
<ha-wa-dialog
|
||||
<ha-dialog
|
||||
.open=${this._openDialog === "basic-subtitle-above"}
|
||||
header-title="Dialog with subtitle above"
|
||||
header-subtitle="This is a basic dialog with a subtitle above"
|
||||
@@ -78,9 +78,9 @@ export class DemoHaWaDialog extends LitElement {
|
||||
@closed=${this._handleClosed}
|
||||
>
|
||||
<div>Dialog content</div>
|
||||
</ha-wa-dialog>
|
||||
</ha-dialog>
|
||||
|
||||
<ha-wa-dialog
|
||||
<ha-dialog
|
||||
.open=${this._openDialog === "form"}
|
||||
header-title="Dialog with form"
|
||||
header-subtitle="This is a dialog with a form and a footer"
|
||||
@@ -91,17 +91,18 @@ export class DemoHaWaDialog extends LitElement {
|
||||
<ha-dialog-footer slot="footer">
|
||||
<ha-button
|
||||
data-dialog="close"
|
||||
appearance="plain"
|
||||
slot="secondaryAction"
|
||||
variant="plain"
|
||||
>Cancel</ha-button
|
||||
>
|
||||
<ha-button data-dialog="close" slot="primaryAction" variant="accent"
|
||||
>Submit</ha-button
|
||||
>
|
||||
Cancel
|
||||
</ha-button>
|
||||
<ha-button data-dialog="close" slot="primaryAction">
|
||||
Submit
|
||||
</ha-button>
|
||||
</ha-dialog-footer>
|
||||
</ha-wa-dialog>
|
||||
</ha-dialog>
|
||||
|
||||
<ha-wa-dialog
|
||||
<ha-dialog
|
||||
.open=${this._openDialog === "actions"}
|
||||
header-title="Dialog with actions"
|
||||
header-subtitle="This is a dialog with header actions"
|
||||
@@ -113,7 +114,7 @@ export class DemoHaWaDialog extends LitElement {
|
||||
</div>
|
||||
|
||||
<div>Dialog content</div>
|
||||
</ha-wa-dialog>
|
||||
</ha-dialog>
|
||||
|
||||
<h2>Design</h2>
|
||||
|
||||
@@ -228,19 +229,19 @@ export class DemoHaWaDialog extends LitElement {
|
||||
<tr>
|
||||
<th>Slot</th>
|
||||
<th>Description</th>
|
||||
<th>Variant to use</th>
|
||||
<th>Appearance to use</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>secondaryAction</code></td>
|
||||
<td>The secondary action button(s).</td>
|
||||
<td><code>plain</code></td>
|
||||
<td><code>appearance="plain"</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>primaryAction</code></td>
|
||||
<td>The primary action button(s).</td>
|
||||
<td><code>accent</code></td>
|
||||
<td>Default (no appearance attribute)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -249,7 +250,7 @@ export class DemoHaWaDialog extends LitElement {
|
||||
|
||||
<h3>Example Usage</h3>
|
||||
|
||||
<pre><code><ha-wa-dialog
|
||||
<pre><code><ha-dialog
|
||||
open
|
||||
header-title="Dialog title"
|
||||
header-subtitle="Dialog subtitle"
|
||||
@@ -261,12 +262,18 @@ export class DemoHaWaDialog extends LitElement {
|
||||
</div>
|
||||
<div>Dialog content</div>
|
||||
<ha-dialog-footer slot="footer">
|
||||
<ha-button data-dialog="close" slot="secondaryAction" variant="plain"
|
||||
>Cancel</ha-button
|
||||
<ha-button
|
||||
data-dialog="close"
|
||||
appearance="plain"
|
||||
slot="secondaryAction"
|
||||
>
|
||||
<ha-button slot="primaryAction" variant="accent">Submit</ha-button>
|
||||
Cancel
|
||||
</ha-button>
|
||||
<ha-button data-dialog="close" slot="primaryAction">
|
||||
Submit
|
||||
</ha-button>
|
||||
</ha-dialog-footer>
|
||||
</ha-wa-dialog></code></pre>
|
||||
</ha-dialog></code></pre>
|
||||
|
||||
<h3>API</h3>
|
||||
|
||||
@@ -373,13 +380,29 @@ export class DemoHaWaDialog extends LitElement {
|
||||
<td><code>--ha-dialog-surface-background</code></td>
|
||||
<td>Dialog background color.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--ha-dialog-surface-backdrop-filter</code></td>
|
||||
<td>Backdrop filter applied to the dialog surface.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--dialog-box-shadow</code></td>
|
||||
<td>Dialog surface box shadow.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--ha-dialog-border-radius</code></td>
|
||||
<td>Border radius of the dialog surface.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--dialog-z-index</code></td>
|
||||
<td>Z-index for the dialog.</td>
|
||||
<td><code>--ha-dialog-scrim-backdrop-filter</code></td>
|
||||
<td>Backdrop filter applied to the dialog scrim.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--dialog-backdrop-filter</code></td>
|
||||
<td>Legacy fallback for the dialog scrim backdrop filter.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--mdc-dialog-scrim-color</code></td>
|
||||
<td>Dialog scrim color (legacy compatibility).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--dialog-surface-margin-top</code></td>
|
||||
@@ -514,6 +537,6 @@ export class DemoHaWaDialog extends LitElement {
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-components-ha-wa-dialog": DemoHaWaDialog;
|
||||
"demo-components-ha-dialog": DemoHaDialog;
|
||||
}
|
||||
}
|
||||
@@ -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[] = [
|
||||
|
||||
@@ -17,32 +17,55 @@ import type { DeviceRegistryEntry } from "../../../../src/data/device/device_reg
|
||||
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 { 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[] = [
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
---
|
||||
title: Dialog (ha-wa-dialog)
|
||||
---
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -222,6 +222,9 @@ class HaLandingPage extends LandingPageBaseElement {
|
||||
flex-direction: column;
|
||||
gap: var(--ha-space-4);
|
||||
}
|
||||
ha-language-picker {
|
||||
min-width: 200px;
|
||||
}
|
||||
ha-alert p {
|
||||
text-align: unset;
|
||||
}
|
||||
|
||||
53
package.json
53
package.json
@@ -29,15 +29,15 @@
|
||||
"@babel/runtime": "7.28.6",
|
||||
"@braintree/sanitize-url": "7.1.2",
|
||||
"@codemirror/autocomplete": "6.20.0",
|
||||
"@codemirror/commands": "6.10.1",
|
||||
"@codemirror/language": "6.12.1",
|
||||
"@codemirror/commands": "6.10.2",
|
||||
"@codemirror/language": "6.12.2",
|
||||
"@codemirror/legacy-modes": "6.5.2",
|
||||
"@codemirror/search": "6.6.0",
|
||||
"@codemirror/state": "6.5.4",
|
||||
"@codemirror/view": "6.39.12",
|
||||
"@codemirror/view": "6.39.15",
|
||||
"@date-fns/tz": "1.4.1",
|
||||
"@egjs/hammerjs": "2.0.17",
|
||||
"@formatjs/intl-datetimeformat": "7.2.1",
|
||||
"@formatjs/intl-datetimeformat": "7.2.2",
|
||||
"@formatjs/intl-displaynames": "7.2.1",
|
||||
"@formatjs/intl-durationformat": "0.10.1",
|
||||
"@formatjs/intl-getcanonicallocales": "3.2.1",
|
||||
@@ -52,7 +52,7 @@
|
||||
"@fullcalendar/list": "6.1.20",
|
||||
"@fullcalendar/luxon3": "6.1.20",
|
||||
"@fullcalendar/timegrid": "6.1.20",
|
||||
"@home-assistant/webawesome": "3.0.0-ha.2",
|
||||
"@home-assistant/webawesome": "3.2.1-ha.3",
|
||||
"@lezer/highlight": "1.2.3",
|
||||
"@lit-labs/motion": "1.1.0",
|
||||
"@lit-labs/observers": "2.1.0",
|
||||
@@ -68,12 +68,10 @@
|
||||
"@material/mwc-fab": "0.27.0",
|
||||
"@material/mwc-floating-label": "0.27.0",
|
||||
"@material/mwc-formfield": "patch:@material/mwc-formfield@npm%3A0.27.0#~/.yarn/patches/@material-mwc-formfield-npm-0.27.0-9528cb60f6.patch",
|
||||
"@material/mwc-icon-button": "0.27.0",
|
||||
"@material/mwc-linear-progress": "0.27.0",
|
||||
"@material/mwc-list": "patch:@material/mwc-list@npm%3A0.27.0#~/.yarn/patches/@material-mwc-list-npm-0.27.0-5344fc9de4.patch",
|
||||
"@material/mwc-radio": "0.27.0",
|
||||
"@material/mwc-select": "0.27.0",
|
||||
"@material/mwc-snackbar": "0.27.0",
|
||||
"@material/mwc-switch": "0.27.0",
|
||||
"@material/mwc-textarea": "0.27.0",
|
||||
"@material/mwc-textfield": "0.27.0",
|
||||
@@ -84,7 +82,7 @@
|
||||
"@mdi/js": "7.4.47",
|
||||
"@mdi/svg": "7.4.47",
|
||||
"@replit/codemirror-indentation-markers": "6.5.3",
|
||||
"@swc/helpers": "0.5.18",
|
||||
"@swc/helpers": "0.5.19",
|
||||
"@thomasloven/round-slider": "0.6.0",
|
||||
"@tsparticles/engine": "3.9.1",
|
||||
"@tsparticles/preset-links": "3.2.0",
|
||||
@@ -93,7 +91,7 @@
|
||||
"@webcomponents/scoped-custom-element-registry": "0.0.10",
|
||||
"@webcomponents/webcomponentsjs": "2.8.0",
|
||||
"app-datepicker": "5.1.1",
|
||||
"barcode-detector": "3.0.8",
|
||||
"barcode-detector": "3.1.0",
|
||||
"color-name": "2.1.0",
|
||||
"comlink": "4.4.2",
|
||||
"core-js": "3.48.0",
|
||||
@@ -107,7 +105,7 @@
|
||||
"element-internals-polyfill": "3.0.2",
|
||||
"fuse.js": "7.1.0",
|
||||
"google-timezones-json": "1.2.0",
|
||||
"gulp-zopfli-green": "6.0.2",
|
||||
"gulp-zopfli-green": "7.0.0",
|
||||
"hls.js": "1.6.15",
|
||||
"home-assistant-js-websocket": "9.6.0",
|
||||
"idb-keyval": "6.2.2",
|
||||
@@ -119,7 +117,7 @@
|
||||
"lit": "3.3.2",
|
||||
"lit-html": "3.3.2",
|
||||
"luxon": "3.7.2",
|
||||
"marked": "17.0.1",
|
||||
"marked": "17.0.3",
|
||||
"memoize-one": "6.0.0",
|
||||
"node-vibrant": "4.0.4",
|
||||
"object-hash": "3.0.0",
|
||||
@@ -149,13 +147,14 @@
|
||||
"@babel/helper-define-polyfill-provider": "0.6.6",
|
||||
"@babel/plugin-transform-runtime": "7.29.0",
|
||||
"@babel/preset-env": "7.29.0",
|
||||
"@bundle-stats/plugin-webpack-filter": "4.21.9",
|
||||
"@bundle-stats/plugin-webpack-filter": "4.21.10",
|
||||
"@html-eslint/eslint-plugin": "0.57.1",
|
||||
"@lokalise/node-api": "15.6.1",
|
||||
"@octokit/auth-oauth-device": "8.0.3",
|
||||
"@octokit/plugin-retry": "8.0.3",
|
||||
"@octokit/plugin-retry": "8.1.0",
|
||||
"@octokit/rest": "22.0.1",
|
||||
"@rsdoctor/rspack-plugin": "1.5.2",
|
||||
"@rspack/core": "1.7.5",
|
||||
"@rspack/core": "1.7.6",
|
||||
"@rspack/dev-server": "1.2.1",
|
||||
"@types/babel__plugin-transform-runtime": "7.9.5",
|
||||
"@types/chromecast-caf-receiver": "6.0.25",
|
||||
@@ -172,7 +171,7 @@
|
||||
"@types/mocha": "10.0.10",
|
||||
"@types/qrcode": "1.5.6",
|
||||
"@types/sortablejs": "1.15.9",
|
||||
"@types/tar": "6.1.13",
|
||||
"@types/tar": "7.0.87",
|
||||
"@types/ua-parser-js": "0.7.39",
|
||||
"@types/webspeechapi": "0.0.29",
|
||||
"@vitest/coverage-v8": "4.0.18",
|
||||
@@ -180,27 +179,27 @@
|
||||
"babel-plugin-template-html-minifier": "4.1.0",
|
||||
"browserslist-useragent-regexp": "4.1.3",
|
||||
"del": "8.0.1",
|
||||
"eslint": "9.39.2",
|
||||
"eslint": "9.39.3",
|
||||
"eslint-config-airbnb-base": "15.0.0",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"eslint-import-resolver-webpack": "0.13.10",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
"eslint-plugin-lit": "2.1.1",
|
||||
"eslint-plugin-lit": "2.2.1",
|
||||
"eslint-plugin-lit-a11y": "5.1.1",
|
||||
"eslint-plugin-unused-imports": "4.3.0",
|
||||
"eslint-plugin-wc": "3.0.2",
|
||||
"eslint-plugin-unused-imports": "4.4.1",
|
||||
"eslint-plugin-wc": "3.1.0",
|
||||
"fancy-log": "2.0.0",
|
||||
"fs-extra": "11.3.3",
|
||||
"glob": "13.0.1",
|
||||
"glob": "13.0.6",
|
||||
"gulp": "5.0.1",
|
||||
"gulp-brotli": "3.0.0",
|
||||
"gulp-json-transform": "0.5.0",
|
||||
"gulp-rename": "2.1.0",
|
||||
"html-minifier-terser": "7.2.0",
|
||||
"husky": "9.1.7",
|
||||
"jsdom": "28.0.0",
|
||||
"jsdom": "28.1.0",
|
||||
"jszip": "3.10.1",
|
||||
"lint-staged": "16.2.7",
|
||||
"lint-staged": "16.3.1",
|
||||
"lit-analyzer": "2.0.3",
|
||||
"lodash.merge": "4.6.2",
|
||||
"lodash.template": "4.5.0",
|
||||
@@ -210,12 +209,12 @@
|
||||
"rspack-manifest-plugin": "5.2.1",
|
||||
"serve": "14.2.5",
|
||||
"sinon": "21.0.1",
|
||||
"tar": "7.5.7",
|
||||
"tar": "7.5.9",
|
||||
"terser-webpack-plugin": "5.3.16",
|
||||
"ts-lit-plugin": "2.0.2",
|
||||
"typescript": "5.9.3",
|
||||
"typescript-eslint": "8.54.0",
|
||||
"vite-tsconfig-paths": "6.0.5",
|
||||
"typescript-eslint": "8.56.1",
|
||||
"vite-tsconfig-paths": "6.1.1",
|
||||
"vitest": "4.0.18",
|
||||
"webpack-stats-plugin": "1.1.3",
|
||||
"webpackbar": "7.0.0",
|
||||
@@ -228,13 +227,13 @@
|
||||
"clean-css": "5.3.3",
|
||||
"@lit/reactive-element": "2.1.2",
|
||||
"@fullcalendar/daygrid": "6.1.20",
|
||||
"globals": "17.3.0",
|
||||
"globals": "17.4.0",
|
||||
"tslib": "2.8.1",
|
||||
"@material/mwc-list@^0.27.0": "patch:@material/mwc-list@npm%3A0.27.0#~/.yarn/patches/@material-mwc-list-npm-0.27.0-5344fc9de4.patch",
|
||||
"glob@^10.2.2": "^10.5.0"
|
||||
},
|
||||
"packageManager": "yarn@4.12.0",
|
||||
"volta": {
|
||||
"node": "24.13.0"
|
||||
"node": "24.14.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { genClientId } from "home-assistant-js-websocket";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { keyed } from "lit/directives/keyed";
|
||||
import type { LocalizeFunc } from "../common/translations/localize";
|
||||
import "../components/ha-alert";
|
||||
@@ -23,6 +23,7 @@ import type {
|
||||
DataEntryFlowStepForm,
|
||||
} from "../data/data_entry_flow";
|
||||
import "./ha-auth-form";
|
||||
import type { HaAuthForm } from "./ha-auth-form";
|
||||
|
||||
type State = "loading" | "error" | "step";
|
||||
|
||||
@@ -52,6 +53,8 @@ export class HaAuthFlow extends LitElement {
|
||||
|
||||
@state() private _submitting = false;
|
||||
|
||||
@query("ha-auth-form") private _form?: HaAuthForm;
|
||||
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
@@ -179,7 +182,7 @@ export class HaAuthFlow extends LitElement {
|
||||
<div class="action">
|
||||
<ha-button
|
||||
@click=${this._handleSubmit}
|
||||
.disabled=${this._submitting}
|
||||
.loading=${this._submitting}
|
||||
>
|
||||
${this.step.type === "form"
|
||||
? this.localize("ui.panel.page-authorize.form.next")
|
||||
@@ -370,6 +373,11 @@ export class HaAuthFlow extends LitElement {
|
||||
this._providerChanged(this.authProvider);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._form?.reportValidity()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._submitting = true;
|
||||
|
||||
const postData = { ...this._stepData, client_id: this.clientId };
|
||||
|
||||
@@ -12,6 +12,10 @@ export class HaAuthFormString extends HaFormString {
|
||||
return this;
|
||||
}
|
||||
|
||||
public reportValidity(): boolean {
|
||||
return this.querySelector("ha-auth-textfield")?.reportValidity() ?? true;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<style>
|
||||
@@ -31,7 +35,7 @@ export class HaAuthFormString extends HaFormString {
|
||||
right: 8px;
|
||||
inset-inline-start: initial;
|
||||
inset-inline-end: 8px;
|
||||
--mdc-icon-button-size: 40px;
|
||||
--ha-icon-button-size: 40px;
|
||||
--mdc-icon-size: 20px;
|
||||
color: var(--secondary-text-color);
|
||||
direction: var(--direction);
|
||||
|
||||
@@ -116,3 +116,6 @@ export const UNIT_F = "°F";
|
||||
|
||||
/** Entity ID of the default view. */
|
||||
export const DEFAULT_VIEW_ENTITY_ID = "group.default_view";
|
||||
|
||||
/** String to visually separate labels on UI */
|
||||
export const STRINGS_SEPARATOR_DOT = " · ";
|
||||
|
||||
@@ -210,3 +210,39 @@ const formatDateWeekdayShortMem = memoizeOne(
|
||||
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
|
||||
})
|
||||
);
|
||||
|
||||
// Mon, Aug 10
|
||||
export const formatDateWeekdayVeryShortDate = (
|
||||
dateObj: Date,
|
||||
locale: FrontendLocaleData,
|
||||
config: HassConfig
|
||||
) =>
|
||||
formatDateWeekdayVeryShortDateMem(locale, config.time_zone).format(dateObj);
|
||||
|
||||
const formatDateWeekdayVeryShortDateMem = memoizeOne(
|
||||
(locale: FrontendLocaleData, serverTimeZone: string) =>
|
||||
new Intl.DateTimeFormat(locale.language, {
|
||||
weekday: "short",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
|
||||
})
|
||||
);
|
||||
|
||||
// Mon, Aug 10, 2021
|
||||
export const formatDateWeekdayShortDate = (
|
||||
dateObj: Date,
|
||||
locale: FrontendLocaleData,
|
||||
config: HassConfig
|
||||
) => formatDateWeekdayShortDateMem(locale, config.time_zone).format(dateObj);
|
||||
|
||||
const formatDateWeekdayShortDateMem = memoizeOne(
|
||||
(locale: FrontendLocaleData, serverTimeZone: string) =>
|
||||
new Intl.DateTimeFormat(locale.language, {
|
||||
weekday: "short",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
|
||||
})
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,13 +3,14 @@ import { UNAVAILABLE, UNKNOWN } from "../../data/entity/entity";
|
||||
import type { EntityRegistryDisplayEntry } from "../../data/entity/entity_registry";
|
||||
import type { FrontendLocaleData } from "../../data/translation";
|
||||
import { TimeZone } from "../../data/translation";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { HomeAssistant, ValuePart } from "../../types";
|
||||
import { formatDate } from "../datetime/format_date";
|
||||
import { formatDateTime } from "../datetime/format_date_time";
|
||||
import { DURATION_UNITS, formatDuration } from "../datetime/format_duration";
|
||||
import { formatTime } from "../datetime/format_time";
|
||||
import {
|
||||
formatNumber,
|
||||
formatNumberToParts,
|
||||
getNumberFormatOptions,
|
||||
isNumericFromAttributes,
|
||||
} from "../number/format_number";
|
||||
@@ -51,8 +52,36 @@ export const computeStateDisplayFromEntityAttributes = (
|
||||
attributes: any,
|
||||
state: string
|
||||
): string => {
|
||||
const parts = computeStateToPartsFromEntityAttributes(
|
||||
localize,
|
||||
locale,
|
||||
sensorNumericDeviceClasses,
|
||||
config,
|
||||
entity,
|
||||
entityId,
|
||||
attributes,
|
||||
state
|
||||
);
|
||||
return parts.map((part) => part.value).join("");
|
||||
};
|
||||
|
||||
const computeStateToPartsFromEntityAttributes = (
|
||||
localize: LocalizeFunc,
|
||||
locale: FrontendLocaleData,
|
||||
sensorNumericDeviceClasses: string[],
|
||||
config: HassConfig,
|
||||
entity: EntityRegistryDisplayEntry | undefined,
|
||||
entityId: string,
|
||||
attributes: any,
|
||||
state: string
|
||||
): ValuePart[] => {
|
||||
if (state === UNKNOWN || state === UNAVAILABLE) {
|
||||
return localize(`state.default.${state}`);
|
||||
return [
|
||||
{
|
||||
type: "value",
|
||||
value: localize(`state.default.${state}`),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const domain = computeDomain(entityId);
|
||||
@@ -73,19 +102,27 @@ export const computeStateDisplayFromEntityAttributes = (
|
||||
DURATION_UNITS.includes(attributes.unit_of_measurement)
|
||||
) {
|
||||
try {
|
||||
return formatDuration(
|
||||
locale,
|
||||
state,
|
||||
attributes.unit_of_measurement,
|
||||
entity?.display_precision
|
||||
);
|
||||
return [
|
||||
{
|
||||
type: "value",
|
||||
value: formatDuration(
|
||||
locale,
|
||||
state,
|
||||
attributes.unit_of_measurement,
|
||||
entity?.display_precision
|
||||
),
|
||||
},
|
||||
];
|
||||
} catch (_err) {
|
||||
// fallback to default
|
||||
}
|
||||
}
|
||||
|
||||
// state is monetary
|
||||
if (attributes.device_class === "monetary") {
|
||||
let parts: Record<string, string>[] = [];
|
||||
try {
|
||||
return formatNumber(state, locale, {
|
||||
parts = formatNumberToParts(state, locale, {
|
||||
style: "currency",
|
||||
currency: attributes.unit_of_measurement,
|
||||
minimumFractionDigits: 2,
|
||||
@@ -96,10 +133,37 @@ export const computeStateDisplayFromEntityAttributes = (
|
||||
),
|
||||
});
|
||||
} catch (_err) {
|
||||
// fallback to default
|
||||
// fallback to default numeric formatting below
|
||||
}
|
||||
|
||||
if (parts.length) {
|
||||
const TYPE_MAP: Record<string, ValuePart["type"]> = {
|
||||
integer: "value",
|
||||
group: "value",
|
||||
decimal: "value",
|
||||
fraction: "value",
|
||||
literal: "literal",
|
||||
currency: "unit",
|
||||
};
|
||||
|
||||
const valueParts: ValuePart[] = [];
|
||||
|
||||
for (const part of parts) {
|
||||
const type = TYPE_MAP[part.type];
|
||||
if (!type) continue;
|
||||
const last = valueParts[valueParts.length - 1];
|
||||
// Merge consecutive numeric parts (e.g. "1" + "," + "234" + "." + "56" → "1,234.56")
|
||||
if (type === "value" && last?.type === "value") {
|
||||
last.value += part.value;
|
||||
} else {
|
||||
valueParts.push({ type, value: part.value });
|
||||
}
|
||||
}
|
||||
return valueParts;
|
||||
}
|
||||
}
|
||||
|
||||
// default processing of numeric values
|
||||
const value = formatNumber(
|
||||
state,
|
||||
locale,
|
||||
@@ -114,10 +178,14 @@ export const computeStateDisplayFromEntityAttributes = (
|
||||
attributes.unit_of_measurement;
|
||||
|
||||
if (unit) {
|
||||
return `${value}${blankBeforeUnit(unit, locale)}${unit}`;
|
||||
return [
|
||||
{ type: "value", value: value },
|
||||
{ type: "literal", value: blankBeforeUnit(unit, locale) },
|
||||
{ type: "unit", value: unit },
|
||||
];
|
||||
}
|
||||
|
||||
return value;
|
||||
return [{ type: "value", value: value }];
|
||||
}
|
||||
|
||||
if (["date", "input_datetime", "time"].includes(domain)) {
|
||||
@@ -129,36 +197,51 @@ export const computeStateDisplayFromEntityAttributes = (
|
||||
const components = state.split(" ");
|
||||
if (components.length === 2) {
|
||||
// Date and time.
|
||||
return formatDateTime(
|
||||
new Date(components.join("T")),
|
||||
{ ...locale, time_zone: TimeZone.local },
|
||||
config
|
||||
);
|
||||
return [
|
||||
{
|
||||
type: "value",
|
||||
value: formatDateTime(
|
||||
new Date(components.join("T")),
|
||||
{ ...locale, time_zone: TimeZone.local },
|
||||
config
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
if (components.length === 1) {
|
||||
if (state.includes("-")) {
|
||||
// Date only.
|
||||
return formatDate(
|
||||
new Date(`${state}T00:00`),
|
||||
{ ...locale, time_zone: TimeZone.local },
|
||||
config
|
||||
);
|
||||
return [
|
||||
{
|
||||
type: "value",
|
||||
value: formatDate(
|
||||
new Date(`${state}T00:00`),
|
||||
{ ...locale, time_zone: TimeZone.local },
|
||||
config
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
if (state.includes(":")) {
|
||||
// Time only.
|
||||
const now = new Date();
|
||||
return formatTime(
|
||||
new Date(`${now.toISOString().split("T")[0]}T${state}`),
|
||||
{ ...locale, time_zone: TimeZone.local },
|
||||
config
|
||||
);
|
||||
return [
|
||||
{
|
||||
type: "value",
|
||||
value: formatTime(
|
||||
new Date(`${now.toISOString().split("T")[0]}T${state}`),
|
||||
{ ...locale, time_zone: TimeZone.local },
|
||||
config
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
return state;
|
||||
return [{ type: "value", value: state }];
|
||||
} catch (_e) {
|
||||
// Formatting methods may throw error if date parsing doesn't go well,
|
||||
// just return the state string in that case.
|
||||
return state;
|
||||
return [{ type: "value", value: state }];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,25 +265,58 @@ export const computeStateDisplayFromEntityAttributes = (
|
||||
(domain === "sensor" && attributes.device_class === "timestamp")
|
||||
) {
|
||||
try {
|
||||
return formatDateTime(new Date(state), locale, config);
|
||||
return [
|
||||
{
|
||||
type: "value",
|
||||
value: formatDateTime(new Date(state), locale, config),
|
||||
},
|
||||
];
|
||||
} catch (_err) {
|
||||
return state;
|
||||
return [{ type: "value", value: state }];
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
(entity?.translation_key &&
|
||||
localize(
|
||||
`component.${entity.platform}.entity.${domain}.${entity.translation_key}.state.${state}`
|
||||
)) ||
|
||||
// Return device class translation
|
||||
(attributes.device_class &&
|
||||
localize(
|
||||
`component.${domain}.entity_component.${attributes.device_class}.state.${state}`
|
||||
)) ||
|
||||
// Return default translation
|
||||
localize(`component.${domain}.entity_component._.state.${state}`) ||
|
||||
// We don't know! Return the raw state.
|
||||
state
|
||||
return [
|
||||
{
|
||||
type: "value",
|
||||
value:
|
||||
(entity?.translation_key &&
|
||||
localize(
|
||||
`component.${entity.platform}.entity.${domain}.${entity.translation_key}.state.${state}`
|
||||
)) ||
|
||||
// Return device class translation
|
||||
(attributes.device_class &&
|
||||
localize(
|
||||
`component.${domain}.entity_component.${attributes.device_class}.state.${state}`
|
||||
)) ||
|
||||
// Return default translation
|
||||
localize(`component.${domain}.entity_component._.state.${state}`) ||
|
||||
// We don't know! Return the raw state.
|
||||
state,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export const computeStateToParts = (
|
||||
localize: LocalizeFunc,
|
||||
stateObj: HassEntity,
|
||||
locale: FrontendLocaleData,
|
||||
sensorNumericDeviceClasses: string[],
|
||||
config: HassConfig,
|
||||
entities: HomeAssistant["entities"],
|
||||
state?: string
|
||||
): ValuePart[] => {
|
||||
const entity = entities?.[stateObj.entity_id] as
|
||||
| EntityRegistryDisplayEntry
|
||||
| undefined;
|
||||
return computeStateToPartsFromEntityAttributes(
|
||||
localize,
|
||||
locale,
|
||||
sensorNumericDeviceClasses,
|
||||
config,
|
||||
entity,
|
||||
stateObj.entity_id,
|
||||
stateObj.attributes,
|
||||
state !== undefined ? state : stateObj.state
|
||||
);
|
||||
};
|
||||
|
||||
@@ -38,6 +38,18 @@ export const getEntityContext = (
|
||||
return getEntityEntryContext(entry, entities, devices, areas, floors);
|
||||
};
|
||||
|
||||
export const getEntityAreaId = (
|
||||
entityId: string,
|
||||
entities: HomeAssistant["entities"],
|
||||
devices: HomeAssistant["devices"]
|
||||
): string | undefined => {
|
||||
const entry = entities[entityId];
|
||||
if (!entry) return undefined;
|
||||
const deviceId = entry.device_id;
|
||||
const device = deviceId ? devices[deviceId] : undefined;
|
||||
return entry.area_id || device?.area_id || undefined;
|
||||
};
|
||||
|
||||
export const getEntityEntryContext = (
|
||||
entry:
|
||||
| EntityRegistryDisplayEntry
|
||||
|
||||
@@ -5,7 +5,6 @@ import type {
|
||||
import type { EntityRegistryDisplayEntry } from "../../data/entity/entity_registry";
|
||||
import type { FrontendLocaleData } from "../../data/translation";
|
||||
import { NumberFormat } from "../../data/translation";
|
||||
import { round } from "./round";
|
||||
|
||||
/**
|
||||
* Returns true if the entity is considered numeric based on the attributes it has
|
||||
@@ -52,7 +51,22 @@ export const formatNumber = (
|
||||
num: string | number,
|
||||
localeOptions?: FrontendLocaleData,
|
||||
options?: Intl.NumberFormatOptions
|
||||
): string => {
|
||||
): string =>
|
||||
formatNumberToParts(num, localeOptions, options)
|
||||
.map((part) => part.value)
|
||||
.join("");
|
||||
|
||||
/**
|
||||
* Returns an array of objects containing the formatted number in parts
|
||||
* Similar to Intl.NumberFormat.prototype.formatToParts()
|
||||
*
|
||||
* Input params - same as for formatNumber()
|
||||
*/
|
||||
export const formatNumberToParts = (
|
||||
num: string | number,
|
||||
localeOptions?: FrontendLocaleData,
|
||||
options?: Intl.NumberFormatOptions
|
||||
): any[] => {
|
||||
const locale = localeOptions
|
||||
? numberFormatToLocale(localeOptions)
|
||||
: undefined;
|
||||
@@ -71,7 +85,7 @@ export const formatNumber = (
|
||||
return new Intl.NumberFormat(
|
||||
locale,
|
||||
getDefaultFormatOptions(num, options)
|
||||
).format(Number(num));
|
||||
).formatToParts(Number(num));
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -86,15 +100,10 @@ export const formatNumber = (
|
||||
...options,
|
||||
useGrouping: false,
|
||||
})
|
||||
).format(Number(num));
|
||||
).formatToParts(Number(num));
|
||||
}
|
||||
|
||||
if (typeof num === "string") {
|
||||
return num;
|
||||
}
|
||||
return `${round(num, options?.maximumFractionDigits).toString()}${
|
||||
options?.style === "currency" ? ` ${options.currency}` : ""
|
||||
}`;
|
||||
return [{ type: "literal", value: num }];
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
28
src/common/number/normalize-by-si-prefix.ts
Normal file
28
src/common/number/normalize-by-si-prefix.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
const SI_PREFIX_MULTIPLIERS: Record<string, number> = {
|
||||
T: 1e12,
|
||||
G: 1e9,
|
||||
M: 1e6,
|
||||
k: 1e3,
|
||||
m: 1e-3,
|
||||
"\u00B5": 1e-6, // µ (micro sign)
|
||||
"\u03BC": 1e-6, // μ (greek small letter mu)
|
||||
};
|
||||
|
||||
/**
|
||||
* Normalize a numeric value by detecting SI unit prefixes (T, G, M, k, m, µ).
|
||||
* Only applies when the unit is longer than 1 character and starts with a
|
||||
* recognized prefix, avoiding false positives on standalone units like "m" (meters).
|
||||
*/
|
||||
export const normalizeValueBySIPrefix = (
|
||||
value: number,
|
||||
unit: string | undefined
|
||||
): number => {
|
||||
if (!unit || unit.length <= 1) {
|
||||
return value;
|
||||
}
|
||||
const prefix = unit[0];
|
||||
if (prefix in SI_PREFIX_MULTIPLIERS) {
|
||||
return value * SI_PREFIX_MULTIPLIERS[prefix];
|
||||
}
|
||||
return value;
|
||||
};
|
||||
@@ -12,6 +12,10 @@ export type FormatEntityStateFunc = (
|
||||
stateObj: HassEntity,
|
||||
state?: string
|
||||
) => string;
|
||||
export type FormatEntityStateToPartsFunc = (
|
||||
stateObj: HassEntity,
|
||||
state?: string
|
||||
) => ValuePart[];
|
||||
export type FormatEntityAttributeValueFunc = (
|
||||
stateObj: HassEntity,
|
||||
attribute: string,
|
||||
@@ -46,12 +50,13 @@ export const computeFormatFunctions = async (
|
||||
sensorNumericDeviceClasses: string[]
|
||||
): Promise<{
|
||||
formatEntityState: FormatEntityStateFunc;
|
||||
formatEntityStateToParts: FormatEntityStateToPartsFunc;
|
||||
formatEntityAttributeValue: FormatEntityAttributeValueFunc;
|
||||
formatEntityAttributeValueToParts: FormatEntityAttributeValueToPartsFunc;
|
||||
formatEntityAttributeName: FormatEntityAttributeNameFunc;
|
||||
formatEntityName: FormatEntityNameFunc;
|
||||
}> => {
|
||||
const { computeStateDisplay } =
|
||||
const { computeStateDisplay, computeStateToParts } =
|
||||
await import("../entity/compute_state_display");
|
||||
const {
|
||||
computeAttributeValueDisplay,
|
||||
@@ -70,6 +75,16 @@ export const computeFormatFunctions = async (
|
||||
entities,
|
||||
state
|
||||
),
|
||||
formatEntityStateToParts: (stateObj, state) =>
|
||||
computeStateToParts(
|
||||
localize,
|
||||
stateObj,
|
||||
locale,
|
||||
sensorNumericDeviceClasses,
|
||||
config,
|
||||
entities,
|
||||
state
|
||||
),
|
||||
formatEntityAttributeValue: (stateObj, attribute, value) =>
|
||||
computeAttributeValueDisplay(
|
||||
localize,
|
||||
|
||||
@@ -1,3 +1,24 @@
|
||||
import { deepActiveElement } from "../dom/deep-active-element";
|
||||
|
||||
const getClipboardFallbackRoot = (): HTMLElement => {
|
||||
const activeElement = deepActiveElement();
|
||||
if (activeElement instanceof HTMLElement) {
|
||||
let root: Node = activeElement.getRootNode();
|
||||
let host: HTMLElement | null = null;
|
||||
|
||||
while (root instanceof ShadowRoot && root.host instanceof HTMLElement) {
|
||||
host = root.host;
|
||||
root = root.host.getRootNode();
|
||||
}
|
||||
|
||||
if (host) {
|
||||
return host;
|
||||
}
|
||||
}
|
||||
|
||||
return document.body;
|
||||
};
|
||||
|
||||
export const copyToClipboard = async (str, rootEl?: HTMLElement) => {
|
||||
if (navigator.clipboard) {
|
||||
try {
|
||||
@@ -8,10 +29,15 @@ export const copyToClipboard = async (str, rootEl?: HTMLElement) => {
|
||||
}
|
||||
}
|
||||
|
||||
const root = rootEl ?? document.body;
|
||||
const root = rootEl || getClipboardFallbackRoot();
|
||||
|
||||
const el = document.createElement("textarea");
|
||||
el.value = str;
|
||||
el.setAttribute("readonly", "");
|
||||
el.style.position = "fixed";
|
||||
el.style.top = "0";
|
||||
el.style.left = "0";
|
||||
el.style.opacity = "0";
|
||||
root.appendChild(el);
|
||||
el.select();
|
||||
document.execCommand("copy");
|
||||
|
||||
@@ -18,9 +18,11 @@ import { classMap } from "lit/directives/class-map";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { ensureArray } from "../../common/array/ensure-array";
|
||||
import { getAllGraphColors } from "../../common/color/colors";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { HASSDomEvent } from "../../common/dom/fire_event";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { listenMediaQuery } from "../../common/dom/media_query";
|
||||
import { afterNextRender } from "../../common/util/render-status";
|
||||
import { filterXSS } from "../../common/util/xss";
|
||||
import { themesContext } from "../../data/context";
|
||||
import type { Themes } from "../../data/ws-themes";
|
||||
import type { ECOption } from "../../resources/echarts/echarts";
|
||||
@@ -28,8 +30,6 @@ import type { HomeAssistant } from "../../types";
|
||||
import { isMac } from "../../util/is_mac";
|
||||
import "../chips/ha-assist-chip";
|
||||
import "../ha-icon-button";
|
||||
import { afterNextRender } from "../../common/util/render-status";
|
||||
import { filterXSS } from "../../common/util/xss";
|
||||
import { formatTimeLabel } from "./axis-label";
|
||||
import { downSampleLineData } from "./down-sample";
|
||||
|
||||
@@ -1115,13 +1115,13 @@ export class HaChartBase extends LitElement {
|
||||
.chart-controls ::slotted(ha-icon-button) {
|
||||
background: var(--card-background-color);
|
||||
border-radius: var(--ha-border-radius-sm);
|
||||
--mdc-icon-button-size: 32px;
|
||||
--ha-icon-button-size: 32px;
|
||||
color: var(--primary-color);
|
||||
border: 1px solid var(--divider-color);
|
||||
}
|
||||
.chart-controls.small ha-icon-button,
|
||||
.chart-controls.small ::slotted(ha-icon-button) {
|
||||
--mdc-icon-button-size: 22px;
|
||||
--ha-icon-button-size: 22px;
|
||||
--mdc-icon-size: 16px;
|
||||
}
|
||||
.chart-controls ha-icon-button.inactive,
|
||||
|
||||
@@ -306,7 +306,10 @@ export class StateHistoryChartLine extends LitElement {
|
||||
visualMap: this._visualMap,
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
appendTo: document.body,
|
||||
renderMode: "html",
|
||||
position: "bottom",
|
||||
align: "center",
|
||||
confine: true,
|
||||
formatter: this._renderTooltip,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -255,7 +255,10 @@ export class StateHistoryChartTimeline extends LitElement {
|
||||
right: rtl ? labelWidth : 1,
|
||||
},
|
||||
tooltip: {
|
||||
appendTo: document.body,
|
||||
renderMode: "html",
|
||||
position: "bottom",
|
||||
align: "center",
|
||||
confine: true,
|
||||
formatter: this._renderTooltip,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -335,7 +335,10 @@ export class StatisticsChart extends LitElement {
|
||||
},
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
appendTo: document.body,
|
||||
renderMode: "html",
|
||||
position: "bottom",
|
||||
align: "center",
|
||||
confine: true,
|
||||
formatter: this._renderTooltip,
|
||||
},
|
||||
};
|
||||
@@ -572,6 +575,7 @@ export class StatisticsChart extends LitElement {
|
||||
let firstSum: number | null | undefined = null;
|
||||
stats.forEach((stat) => {
|
||||
const startDate = new Date(stat.start);
|
||||
const endDate = new Date(stat.end);
|
||||
if (prevDate === startDate) {
|
||||
return;
|
||||
}
|
||||
@@ -601,10 +605,25 @@ export class StatisticsChart extends LitElement {
|
||||
dataValues.push(val);
|
||||
});
|
||||
if (!this._hiddenStats.has(statistic_id)) {
|
||||
pushData(startDate, new Date(stat.end), dataValues);
|
||||
pushData(
|
||||
startDate,
|
||||
endDate.getTime() < endTime.getTime() ? endDate : endTime,
|
||||
dataValues
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Close out the last stat segment at prevEndTime
|
||||
const lastEndTime = prevEndTime;
|
||||
const lastValues = prevValues;
|
||||
if (lastEndTime && lastValues) {
|
||||
statDataSets.forEach((d, i) => {
|
||||
d.data!.push(
|
||||
this._transformDataValue([lastEndTime, ...lastValues[i]!])
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Append current state if viewing recent data
|
||||
const now = new Date();
|
||||
// allow 10m of leeway for "now", because stats are 5 minute aggregated
|
||||
@@ -619,16 +638,6 @@ export class StatisticsChart extends LitElement {
|
||||
isFinite(currentValue) &&
|
||||
!this._hiddenStats.has(statistic_id)
|
||||
) {
|
||||
// First, close out the last stat segment at prevEndTime
|
||||
const lastEndTime = prevEndTime;
|
||||
const lastValues = prevValues;
|
||||
if (lastEndTime && lastValues) {
|
||||
statDataSets.forEach((d, i) => {
|
||||
d.data!.push(
|
||||
this._transformDataValue([lastEndTime, ...lastValues[i]!])
|
||||
);
|
||||
});
|
||||
}
|
||||
// Then push the current state at now
|
||||
statTypes.forEach((type, i) => {
|
||||
const val: (number | null)[] = [];
|
||||
|
||||
@@ -6,6 +6,7 @@ import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||
import { FIXED_DOMAIN_STATES } from "../../common/entity/get_states";
|
||||
import { stateColorProperties } from "../../common/entity/state_color";
|
||||
import { slugify } from "../../common/string/slugify";
|
||||
import { UNAVAILABLE, UNKNOWN } from "../../data/entity/entity";
|
||||
import { computeCssValue } from "../../resources/css-variables";
|
||||
|
||||
@@ -32,6 +33,22 @@ function computeTimelineStateColor(
|
||||
return computeCssValue("--history-unknown-color", computedStyles);
|
||||
}
|
||||
|
||||
const domain = computeDomain(stateObj.entity_id);
|
||||
|
||||
// Zone states for person/device_tracker don't have specific CSS color variables,
|
||||
// so they all fall back to the same --state-person-active-color.
|
||||
// Only use a custom CSS variable if explicitly defined (e.g. --state-person-kitchen-color),
|
||||
// otherwise return undefined to get unique colors from the generic color handler.
|
||||
if (
|
||||
(domain === "person" || domain === "device_tracker") &&
|
||||
!((FIXED_DOMAIN_STATES[domain] || []) as readonly string[]).includes(state)
|
||||
) {
|
||||
return computeCssValue(
|
||||
`--state-${domain}-${slugify(state, "_")}-color`,
|
||||
computedStyles
|
||||
);
|
||||
}
|
||||
|
||||
const properties = stateColorProperties(stateObj, state);
|
||||
|
||||
if (!properties) {
|
||||
@@ -41,8 +58,6 @@ function computeTimelineStateColor(
|
||||
const rgb = computeCssValue(properties, computedStyles);
|
||||
|
||||
if (!rgb) return undefined;
|
||||
|
||||
const domain = computeDomain(stateObj.entity_id);
|
||||
const shade = DOMAIN_STATE_SHADES[domain]?.[state] as number | number;
|
||||
if (!shade) {
|
||||
return rgb;
|
||||
|
||||
@@ -9,10 +9,13 @@ import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../ha-button";
|
||||
import { createCloseHeading } from "../ha-dialog";
|
||||
import "../ha-dialog-footer";
|
||||
import "../ha-icon-button";
|
||||
import "../ha-list";
|
||||
import "../ha-list-item";
|
||||
import "../ha-sortable";
|
||||
import "../ha-svg-icon";
|
||||
import "../ha-dialog";
|
||||
import type {
|
||||
DataTableColumnContainer,
|
||||
DataTableColumnData,
|
||||
@@ -29,17 +32,49 @@ export class DialogDataTableSettings extends LitElement {
|
||||
|
||||
@state() private _hiddenColumns?: string[];
|
||||
|
||||
private _lastFixedKeys: string[] = [];
|
||||
|
||||
@state() private _open = false;
|
||||
|
||||
public showDialog(params: DataTableSettingsDialogParams) {
|
||||
this._params = params;
|
||||
this._columnOrder = params.columnOrder;
|
||||
this._columnOrder = this._preserveLastFixed(params.columnOrder);
|
||||
this._hiddenColumns = params.hiddenColumns;
|
||||
this._open = true;
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
this._open = false;
|
||||
}
|
||||
|
||||
private _dialogClosed() {
|
||||
this._params = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
private _lastFixedCount(): number {
|
||||
const lastFixedKeys = Object.keys(this._params!.columns).filter(
|
||||
(col) => this._params!.columns[col].lastFixed
|
||||
);
|
||||
if (lastFixedKeys.length) {
|
||||
this._lastFixedKeys = lastFixedKeys;
|
||||
}
|
||||
return lastFixedKeys.length;
|
||||
}
|
||||
|
||||
private _preserveLastFixed(columnOrder) {
|
||||
let strippedColumnOrder;
|
||||
const lastFixedCount = this._lastFixedCount();
|
||||
if (lastFixedCount && columnOrder) {
|
||||
strippedColumnOrder = [...columnOrder];
|
||||
strippedColumnOrder.splice(
|
||||
columnOrder.length - lastFixedCount,
|
||||
lastFixedCount
|
||||
);
|
||||
}
|
||||
return strippedColumnOrder;
|
||||
}
|
||||
|
||||
private _sortedColumns = memoizeOne(
|
||||
(
|
||||
columns: DataTableColumnContainer,
|
||||
@@ -47,7 +82,7 @@ export class DialogDataTableSettings extends LitElement {
|
||||
hiddenColumns: string[] | undefined
|
||||
) =>
|
||||
Object.keys(columns)
|
||||
.filter((col) => !columns[col].hidden)
|
||||
.filter((col) => !columns[col].hidden && !columns[col].lastFixed)
|
||||
.sort((a, b) => {
|
||||
const orderA = columnOrder?.indexOf(a) ?? -1;
|
||||
const orderB = columnOrder?.indexOf(b) ?? -1;
|
||||
@@ -92,12 +127,10 @@ export class DialogDataTableSettings extends LitElement {
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
@closed=${this.closeDialog}
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
localize("ui.components.data-table.settings.header")
|
||||
)}
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
header-title=${localize("ui.components.data-table.settings.header")}
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
<ha-sortable
|
||||
@item-moved=${this._columnMoved}
|
||||
@@ -152,15 +185,17 @@ export class DialogDataTableSettings extends LitElement {
|
||||
)}
|
||||
</ha-list>
|
||||
</ha-sortable>
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
slot="secondaryAction"
|
||||
@click=${this._reset}
|
||||
>${localize("ui.components.data-table.settings.restore")}</ha-button
|
||||
>
|
||||
<ha-button slot="primaryAction" @click=${this.closeDialog}>
|
||||
${localize("ui.components.data-table.settings.done")}
|
||||
</ha-button>
|
||||
<ha-dialog-footer slot="footer">
|
||||
<ha-button
|
||||
slot="secondaryAction"
|
||||
appearance="plain"
|
||||
@click=${this._reset}
|
||||
>${localize("ui.components.data-table.settings.restore")}</ha-button
|
||||
>
|
||||
<ha-button slot="primaryAction" @click=${this.closeDialog}>
|
||||
${localize("ui.components.data-table.settings.done")}
|
||||
</ha-button>
|
||||
</ha-dialog-footer>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
@@ -185,7 +220,8 @@ export class DialogDataTableSettings extends LitElement {
|
||||
|
||||
this._columnOrder = columnOrder;
|
||||
|
||||
this._params!.onUpdate(this._columnOrder, this._hiddenColumns);
|
||||
const reportedOrder = columnOrder.concat(this._lastFixedKeys);
|
||||
this._params!.onUpdate(reportedOrder, this._hiddenColumns);
|
||||
}
|
||||
|
||||
private _toggle(ev) {
|
||||
@@ -266,7 +302,8 @@ export class DialogDataTableSettings extends LitElement {
|
||||
|
||||
this._hiddenColumns = hidden;
|
||||
|
||||
this._params!.onUpdate(this._columnOrder, this._hiddenColumns);
|
||||
const reportedOrder = this._columnOrder.concat(this._lastFixedKeys);
|
||||
this._params!.onUpdate(reportedOrder, this._hiddenColumns);
|
||||
}
|
||||
|
||||
private _reset() {
|
||||
@@ -282,21 +319,9 @@ export class DialogDataTableSettings extends LitElement {
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-dialog {
|
||||
--mdc-dialog-max-width: 500px;
|
||||
--dialog-z-index: 10;
|
||||
--dialog-content-padding: 0 8px;
|
||||
}
|
||||
@media all and (max-width: 451px) {
|
||||
ha-dialog {
|
||||
--vertical-align-dialog: flex-start;
|
||||
--dialog-surface-margin-top: 250px;
|
||||
--ha-dialog-border-radius: var(--ha-border-radius-4xl)
|
||||
var(--ha-border-radius-4xl) var(--ha-border-radius-square)
|
||||
var(--ha-border-radius-square);
|
||||
--mdc-dialog-min-height: calc(100% - 250px);
|
||||
--mdc-dialog-max-height: calc(100% - 250px);
|
||||
}
|
||||
}
|
||||
ha-list-item {
|
||||
--mdc-list-side-padding: 12px;
|
||||
overflow: visible;
|
||||
|
||||
@@ -13,6 +13,7 @@ import { classMap } from "lit/directives/class-map";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { STRINGS_SEPARATOR_DOT } from "../../common/const";
|
||||
import { restoreScroll } from "../../common/decorators/restore-scroll";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { stringCompare } from "../../common/string/compare";
|
||||
@@ -85,6 +86,7 @@ export interface DataTableColumnData<T = any> extends DataTableSortColumnData {
|
||||
flex?: number;
|
||||
forceLTR?: boolean;
|
||||
hidden?: boolean;
|
||||
lastFixed?: boolean;
|
||||
}
|
||||
|
||||
export type ClonedDataTableColumnData = Omit<DataTableColumnData, "title"> & {
|
||||
@@ -116,8 +118,6 @@ export class HaDataTable extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public clickable = false;
|
||||
|
||||
@property({ attribute: "has-fab", type: Boolean }) public hasFab = false;
|
||||
|
||||
/**
|
||||
* Add an extra row at the bottom of the data table
|
||||
* @type {TemplateResult}
|
||||
@@ -134,9 +134,6 @@ export class HaDataTable extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public searchLabel?: string;
|
||||
|
||||
@property({ type: Boolean, attribute: "no-label-float" })
|
||||
public noLabelFloat? = false;
|
||||
|
||||
@property({ type: String }) public filter = "";
|
||||
|
||||
@property({ attribute: false }) public groupColumn?: string;
|
||||
@@ -358,6 +355,11 @@ export class HaDataTable extends LitElement {
|
||||
.sort((a, b) => {
|
||||
const orderA = columnOrder!.indexOf(a);
|
||||
const orderB = columnOrder!.indexOf(b);
|
||||
const fixedA = Boolean(columns[a].lastFixed);
|
||||
const fixedB = Boolean(columns[b].lastFixed);
|
||||
if (fixedA !== fixedB) {
|
||||
return fixedA ? 1 : -1;
|
||||
}
|
||||
if (orderA !== orderB) {
|
||||
if (orderA === -1) {
|
||||
return 1;
|
||||
@@ -393,7 +395,6 @@ export class HaDataTable extends LitElement {
|
||||
.hass=${this.hass}
|
||||
@value-changed=${this._handleSearchChange}
|
||||
.label=${this.searchLabel}
|
||||
.noLabelFloat=${this.noLabelFloat}
|
||||
></search-input>
|
||||
</div>
|
||||
`
|
||||
@@ -427,9 +428,9 @@ export class HaDataTable extends LitElement {
|
||||
<ha-checkbox
|
||||
class="mdc-data-table__row-checkbox"
|
||||
@change=${this._handleHeaderRowCheckboxClick}
|
||||
.indeterminate=${this._checkedRows.length &&
|
||||
.indeterminate=${!!this._checkedRows.length &&
|
||||
this._checkedRows.length !== this._checkableRowsCount}
|
||||
.checked=${this._checkedRows.length &&
|
||||
.checked=${!!this._checkedRows.length &&
|
||||
this._checkedRows.length === this._checkableRowsCount}
|
||||
>
|
||||
</ha-checkbox>
|
||||
@@ -516,7 +517,6 @@ export class HaDataTable extends LitElement {
|
||||
this._filteredData,
|
||||
localize,
|
||||
this.appendRow,
|
||||
this.hasFab,
|
||||
this.groupColumn,
|
||||
this.groupOrder,
|
||||
this._collapsedGroups,
|
||||
@@ -636,7 +636,7 @@ export class HaDataTable extends LitElement {
|
||||
.map(
|
||||
([key2, column2], i) =>
|
||||
html`${i !== 0
|
||||
? " · "
|
||||
? STRINGS_SEPARATOR_DOT
|
||||
: nothing}${column2.template
|
||||
? column2.template(row)
|
||||
: row[key2]}`
|
||||
@@ -713,14 +713,13 @@ export class HaDataTable extends LitElement {
|
||||
data: DataTableRowData[],
|
||||
localize: LocalizeFunc,
|
||||
appendRow,
|
||||
hasFab: boolean,
|
||||
groupColumn: string | undefined,
|
||||
groupOrder: string[] | undefined,
|
||||
collapsedGroups: string[],
|
||||
sortColumn: string | undefined,
|
||||
sortDirection: SortingDirection
|
||||
) => {
|
||||
if (appendRow || hasFab || groupColumn) {
|
||||
if (appendRow || groupColumn) {
|
||||
let items = [...data];
|
||||
|
||||
if (groupColumn) {
|
||||
@@ -810,13 +809,11 @@ export class HaDataTable extends LitElement {
|
||||
items.push({ append: true, selectable: false, content: appendRow });
|
||||
}
|
||||
|
||||
if (hasFab) {
|
||||
items.push({ empty: true });
|
||||
}
|
||||
items.push({ empty: true });
|
||||
|
||||
return items;
|
||||
}
|
||||
return data;
|
||||
return [...data, { empty: true }];
|
||||
}
|
||||
);
|
||||
|
||||
@@ -868,7 +865,6 @@ export class HaDataTable extends LitElement {
|
||||
this._filteredData,
|
||||
this.localizeFunc || this.hass.localize,
|
||||
this.appendRow,
|
||||
this.hasFab,
|
||||
this.groupColumn,
|
||||
this.groupOrder,
|
||||
this._collapsedGroups,
|
||||
@@ -1088,7 +1084,7 @@ export class HaDataTable extends LitElement {
|
||||
.mdc-data-table__row.empty-row {
|
||||
height: var(
|
||||
--data-table-empty-row-height,
|
||||
var(--data-table-row-height, 52px)
|
||||
var(--safe-area-inset-bottom, 0px)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1192,6 +1188,7 @@ export class HaDataTable extends LitElement {
|
||||
|
||||
.mdc-data-table__cell--numeric {
|
||||
text-align: var(--float-end);
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.mdc-data-table__cell--icon {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { consume } from "@lit/context";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
@@ -13,8 +13,6 @@ import {
|
||||
import type { EntityRegistryEntry } from "../../data/entity/entity_registry";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../types";
|
||||
import "../ha-generic-picker";
|
||||
import "../ha-md-select";
|
||||
import "../ha-md-select-option";
|
||||
import type { PickerValueRenderer } from "../ha-picker-field";
|
||||
|
||||
const NO_AUTOMATION_KEY = "NO_AUTOMATION";
|
||||
@@ -217,10 +215,4 @@ export abstract class HaDeviceAutomationPicker<
|
||||
delete value.metadata;
|
||||
fireEvent(this, "value-changed", { value });
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-select {
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../types";
|
||||
import "../ha-generic-picker";
|
||||
import type { PickerComboBoxItem } from "../ha-picker-combo-box";
|
||||
import type { PickerValueRenderer } from "../ha-picker-field";
|
||||
|
||||
@customElement("ha-entity-attribute-picker")
|
||||
class HaEntityAttributePicker extends LitElement {
|
||||
@@ -94,12 +95,19 @@ class HaEntityAttributePicker extends LitElement {
|
||||
.helper=${this.helper}
|
||||
.allowCustomValue=${this.allowCustomValue}
|
||||
.getItems=${this._getItems}
|
||||
.valueRenderer=${this._valueRenderer}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-generic-picker>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueRenderer: PickerValueRenderer = (value: string) => {
|
||||
const items = this._getItems();
|
||||
const item = items.find((option) => option.id === value);
|
||||
return html`<span slot="headline">${item?.primary ?? value}</span>`;
|
||||
};
|
||||
|
||||
private _valueChanged(ev: ValueChangedEvent<string>) {
|
||||
ev.stopPropagation();
|
||||
const newValue = ev.detail.value;
|
||||
|
||||
@@ -164,7 +164,7 @@ export class HaEntityToggle extends LitElement {
|
||||
min-width: 38px;
|
||||
}
|
||||
ha-icon-button {
|
||||
--mdc-icon-button-size: 40px;
|
||||
--ha-icon-button-size: 40px;
|
||||
color: var(--ha-icon-button-inactive-color, var(--primary-text-color));
|
||||
transition: color 0.5s;
|
||||
}
|
||||
|
||||
@@ -9,16 +9,7 @@ import secondsToDuration from "../../common/datetime/seconds_to_duration";
|
||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { FIXED_DOMAIN_STATES } from "../../common/entity/get_states";
|
||||
import {
|
||||
formatNumber,
|
||||
getNumberFormatOptions,
|
||||
isNumericState,
|
||||
} from "../../common/number/format_number";
|
||||
import {
|
||||
isUnavailableState,
|
||||
UNAVAILABLE,
|
||||
UNKNOWN,
|
||||
} from "../../data/entity/entity";
|
||||
import { isUnavailableState, UNAVAILABLE } from "../../data/entity/entity";
|
||||
import type { EntityRegistryDisplayEntry } from "../../data/entity/entity_registry";
|
||||
import { timerTimeRemaining } from "../../data/timer";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
@@ -180,16 +171,11 @@ export class HaStateLabelBadge extends LitElement {
|
||||
}
|
||||
// eslint-disable-next-line: disable=no-fallthrough
|
||||
default:
|
||||
return entityState.state === UNKNOWN ||
|
||||
entityState.state === UNAVAILABLE
|
||||
return isUnavailableState(entityState.state)
|
||||
? "—"
|
||||
: isNumericState(entityState)
|
||||
? formatNumber(
|
||||
entityState.state,
|
||||
this.hass!.locale,
|
||||
getNumberFormatOptions(entityState, entry)
|
||||
)
|
||||
: this.hass!.formatEntityState(entityState);
|
||||
: this.hass!.formatEntityStateToParts(entityState).find(
|
||||
(part) => part.type === "value"
|
||||
)?.value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,7 +224,11 @@ export class HaStateLabelBadge extends LitElement {
|
||||
if (domain === "timer") {
|
||||
return secondsToDuration(_timerTimeRemaining);
|
||||
}
|
||||
return entityState.attributes.unit_of_measurement || null;
|
||||
return (
|
||||
this.hass!.formatEntityStateToParts(entityState).find(
|
||||
(part) => part.type === "unit"
|
||||
)?.value || null
|
||||
);
|
||||
}
|
||||
|
||||
private _clearInterval() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { RenderItemFunction } from "@lit-labs/virtualizer/virtualize";
|
||||
import { mdiChartLine, mdiHelpCircle, mdiShape } from "@mdi/js";
|
||||
import { mdiChartLine, mdiHelpCircleOutline, mdiShape } from "@mdi/js";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { html, LitElement, nothing, type PropertyValues } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
@@ -163,7 +163,7 @@ export class HaStatisticPicker extends LitElement {
|
||||
primary: this.hass.localize(
|
||||
"ui.components.statistic-picker.missing_entity"
|
||||
),
|
||||
icon_path: mdiHelpCircle,
|
||||
icon_path: mdiHelpCircleOutline,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import { iconColorCSS } from "../../common/style/icon_color_css";
|
||||
import { cameraUrlWithWidthHeight } from "../../data/camera";
|
||||
import { CLIMATE_HVAC_ACTION_TO_MODE } from "../../data/climate";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { addBrandsAuth } from "../../util/brands-url";
|
||||
import "../ha-state-icon";
|
||||
|
||||
@customElement("state-badge")
|
||||
@@ -140,6 +141,7 @@ export class StateBadge extends LitElement {
|
||||
if (this.hass) {
|
||||
imageUrl = this.hass.hassUrl(imageUrl);
|
||||
}
|
||||
imageUrl = addBrandsAuth(imageUrl, this.hass?.auth.data.hassUrl);
|
||||
if (domain === "camera") {
|
||||
imageUrl = cameraUrlWithWidthHeight(imageUrl, 80, 80);
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ import type { HomeAssistant } from "../types";
|
||||
import "./ha-bottom-sheet";
|
||||
import "./ha-dialog-header";
|
||||
import "./ha-icon-button";
|
||||
import "./ha-wa-dialog";
|
||||
import type { DialogWidth } from "./ha-wa-dialog";
|
||||
import "./ha-dialog";
|
||||
import type { DialogWidth } from "./ha-dialog";
|
||||
|
||||
type DialogSheetMode = "dialog" | "bottom-sheet";
|
||||
|
||||
@@ -18,10 +18,11 @@ type DialogSheetMode = "dialog" | "bottom-sheet";
|
||||
* @extends {LitElement}
|
||||
*
|
||||
* @summary
|
||||
* A responsive dialog component that automatically switches between a full dialog (ha-wa-dialog)
|
||||
* A responsive dialog component that automatically switches between a full dialog (ha-dialog)
|
||||
* and a bottom sheet (ha-bottom-sheet) based on screen size. Uses dialog mode on larger screens
|
||||
* (>870px width and >500px height) and bottom sheet mode on smaller screens or mobile devices.
|
||||
*
|
||||
* @slot header - Replace the entire header area.
|
||||
* @slot headerNavigationIcon - Leading header action (e.g. close/back button).
|
||||
* @slot headerTitle - Custom title content (used when header-title is not set).
|
||||
* @slot headerSubtitle - Custom subtitle content (used when header-subtitle is not set).
|
||||
@@ -30,20 +31,33 @@ type DialogSheetMode = "dialog" | "bottom-sheet";
|
||||
* @slot footer - Dialog/sheet footer content.
|
||||
*
|
||||
* @cssprop --ha-dialog-surface-background - Dialog/sheet background color.
|
||||
* @cssprop --ha-dialog-surface-backdrop-filter - Dialog/sheet backdrop filter.
|
||||
* @cssprop --dialog-box-shadow - Dialog box shadow (dialog mode only).
|
||||
* @cssprop --ha-dialog-border-radius - Border radius of the dialog surface (dialog mode only).
|
||||
* @cssprop --ha-dialog-show-duration - Show animation duration (dialog mode only).
|
||||
* @cssprop --ha-dialog-hide-duration - Hide animation duration (dialog mode only).
|
||||
* @cssprop --ha-dialog-scrim-backdrop-filter - Dialog/sheet scrim backdrop filter.
|
||||
* @cssprop --dialog-backdrop-filter - Dialog/sheet scrim backdrop filter (legacy).
|
||||
* @cssprop --mdc-dialog-scrim-color - Dialog/sheet scrim color (legacy).
|
||||
* @cssprop --ha-bottom-sheet-surface-background - Bottom sheet background color (sheet mode only).
|
||||
* @cssprop --ha-bottom-sheet-surface-backdrop-filter - Bottom sheet backdrop filter (sheet mode only).
|
||||
* @cssprop --ha-bottom-sheet-scrim-backdrop-filter - Bottom sheet scrim backdrop filter (sheet mode only).
|
||||
* @cssprop --ha-bottom-sheet-scrim-color - Bottom sheet scrim color (sheet mode only).
|
||||
*
|
||||
* @attr {boolean} open - Controls the dialog/sheet open state.
|
||||
* @attr {("alert"|"standard")} type - Dialog type (dialog mode only). Defaults to "standard".
|
||||
* @attr {("small"|"medium"|"large"|"full")} width - Preferred dialog width preset (dialog mode only). Defaults to "medium".
|
||||
* @attr {boolean} prevent-scrim-close - Prevents closing by clicking the scrim/overlay.
|
||||
* @attr {string} header-title - Header title text. If not set, the headerTitle slot is used.
|
||||
* @attr {string} header-subtitle - Header subtitle text. If not set, the headerSubtitle slot is used.
|
||||
* @attr {("above"|"below")} header-subtitle-position - Position of the subtitle relative to the title. Defaults to "below".
|
||||
* @attr {boolean} block-mode-change - When set, the mode is determined at mount time based on the current screen size, but subsequent mode changes are blocked. Useful for preventing forms from resetting when the viewport size changes.
|
||||
* @attr {boolean} flexcontent - Makes the content body a flex container.
|
||||
* @attr {boolean} without-header - Hides the default header.
|
||||
* @attr {boolean} allow-mode-change - When set, the component can switch between dialog and bottom-sheet modes as the viewport changes.
|
||||
*
|
||||
* @event opened - Fired when the dialog/sheet is shown (dialog mode only).
|
||||
* @event opened - Fired when the dialog/sheet is shown.
|
||||
* @event closed - Fired after the dialog/sheet is hidden.
|
||||
* @event after-show - Fired after show animation completes (dialog mode only).
|
||||
* @event after-show - Fired after show animation completes.
|
||||
*
|
||||
* @remarks
|
||||
* **Responsive Behavior:**
|
||||
@@ -51,9 +65,9 @@ type DialogSheetMode = "dialog" | "bottom-sheet";
|
||||
* Dialog mode is used for screens wider than 870px and taller than 500px.
|
||||
* Bottom sheet mode is used for mobile devices and smaller screens.
|
||||
*
|
||||
* When `block-mode-change` is set, the mode is determined once at mount time based on the initial
|
||||
* screen size. Subsequent viewport size changes will not trigger mode switches, which is useful
|
||||
* for preventing form resets or other state loss when users resize their browser window.
|
||||
* By default, the mode is determined once at mount time and is then kept stable to avoid state
|
||||
* loss (like form resets) during viewport changes. Set `allow-mode-change` to opt into live
|
||||
* mode switching while the dialog is open.
|
||||
*
|
||||
* **Focus Management:**
|
||||
* To automatically focus an element when opened, add the `autofocus` attribute to it.
|
||||
@@ -73,9 +87,15 @@ export class HaAdaptiveDialog extends LitElement {
|
||||
@property({ type: Boolean, reflect: true })
|
||||
public open = false;
|
||||
|
||||
@property({ reflect: true })
|
||||
public type: "alert" | "standard" = "standard";
|
||||
|
||||
@property({ type: String, reflect: true, attribute: "width" })
|
||||
public width: DialogWidth = "medium";
|
||||
|
||||
@property({ type: Boolean, reflect: true, attribute: "prevent-scrim-close" })
|
||||
public preventScrimClose = false;
|
||||
|
||||
@property({ attribute: "header-title" })
|
||||
public headerTitle?: string;
|
||||
|
||||
@@ -85,12 +105,15 @@ export class HaAdaptiveDialog extends LitElement {
|
||||
@property({ type: String, attribute: "header-subtitle-position" })
|
||||
public headerSubtitlePosition: "above" | "below" = "below";
|
||||
|
||||
@property({ type: Boolean, attribute: "block-mode-change" })
|
||||
public blockModeChange = false;
|
||||
@property({ type: Boolean, attribute: "allow-mode-change" })
|
||||
public allowModeChange = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "without-header" })
|
||||
public withoutHeader = false;
|
||||
|
||||
@property({ type: Boolean, reflect: true, attribute: "flexcontent" })
|
||||
public flexContent = false;
|
||||
|
||||
@state() private _mode: DialogSheetMode = "dialog";
|
||||
|
||||
private _unsubMediaQuery?: () => void;
|
||||
@@ -102,7 +125,7 @@ export class HaAdaptiveDialog extends LitElement {
|
||||
this._unsubMediaQuery = listenMediaQuery(
|
||||
"(max-width: 870px), (max-height: 500px)",
|
||||
(matches) => {
|
||||
if (!this._modeSet || !this.blockModeChange) {
|
||||
if (!this._modeSet || this.allowModeChange) {
|
||||
this._mode = matches ? "bottom-sheet" : "dialog";
|
||||
this._modeSet = true;
|
||||
}
|
||||
@@ -120,33 +143,50 @@ export class HaAdaptiveDialog extends LitElement {
|
||||
render() {
|
||||
if (this._mode === "bottom-sheet") {
|
||||
return html`
|
||||
<ha-bottom-sheet .open=${this.open} flexcontent>
|
||||
<ha-bottom-sheet
|
||||
.ariaLabelledBy=${this.ariaLabelledBy ||
|
||||
(this.headerTitle !== undefined ? "ha-dialog-title" : undefined)}
|
||||
.ariaDescribedBy=${this.ariaDescribedBy}
|
||||
.flexContent=${this.flexContent}
|
||||
.hass=${this.hass}
|
||||
.open=${this.open}
|
||||
.preventScrimClose=${this.preventScrimClose}
|
||||
>
|
||||
${!this.withoutHeader
|
||||
? html`<ha-dialog-header
|
||||
slot="header"
|
||||
.subtitlePosition=${this.headerSubtitlePosition}
|
||||
>
|
||||
<slot name="headerNavigationIcon" slot="navigationIcon">
|
||||
<ha-icon-button
|
||||
data-drawer="close"
|
||||
.label=${this.hass?.localize("ui.common.close") ?? "Close"}
|
||||
.path=${mdiClose}
|
||||
></ha-icon-button>
|
||||
? html`
|
||||
<slot name="header" slot="header">
|
||||
<ha-dialog-header
|
||||
.subtitlePosition=${this.headerSubtitlePosition}
|
||||
>
|
||||
<slot name="headerNavigationIcon" slot="navigationIcon">
|
||||
<ha-icon-button
|
||||
data-dialog="close"
|
||||
.label=${this.hass?.localize("ui.common.close") ??
|
||||
"Close"}
|
||||
.path=${mdiClose}
|
||||
></ha-icon-button>
|
||||
</slot>
|
||||
${this.headerTitle !== undefined
|
||||
? html`<span
|
||||
slot="title"
|
||||
class="title"
|
||||
id="ha-dialog-title"
|
||||
>
|
||||
${this.headerTitle}
|
||||
</span>`
|
||||
: html`<slot name="headerTitle" slot="title"></slot>`}
|
||||
${this.headerSubtitle !== undefined
|
||||
? html`<span slot="subtitle"
|
||||
>${this.headerSubtitle}</span
|
||||
>`
|
||||
: html`<slot
|
||||
name="headerSubtitle"
|
||||
slot="subtitle"
|
||||
></slot>`}
|
||||
<slot name="headerActionItems" slot="actionItems"></slot>
|
||||
</ha-dialog-header>
|
||||
</slot>
|
||||
${this.headerTitle !== undefined
|
||||
? html`<span
|
||||
slot="title"
|
||||
class="title"
|
||||
id="ha-wa-dialog-title"
|
||||
>
|
||||
${this.headerTitle}
|
||||
</span>`
|
||||
: html`<slot name="headerTitle" slot="title"></slot>`}
|
||||
${this.headerSubtitle !== undefined
|
||||
? html`<span slot="subtitle">${this.headerSubtitle}</span>`
|
||||
: html`<slot name="headerSubtitle" slot="subtitle"></slot>`}
|
||||
<slot name="headerActionItems" slot="actionItems"></slot>
|
||||
</ha-dialog-header>`
|
||||
`
|
||||
: nothing}
|
||||
<slot></slot>
|
||||
<slot name="footer" slot="footer"></slot>
|
||||
@@ -155,16 +195,18 @@ export class HaAdaptiveDialog extends LitElement {
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-wa-dialog
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this.open}
|
||||
.type=${this.type}
|
||||
.width=${this.width}
|
||||
.preventScrimClose=${this.preventScrimClose}
|
||||
.ariaLabelledBy=${this.ariaLabelledBy}
|
||||
.ariaDescribedBy=${this.ariaDescribedBy}
|
||||
.headerTitle=${this.headerTitle}
|
||||
.headerSubtitle=${this.headerSubtitle}
|
||||
.headerSubtitlePosition=${this.headerSubtitlePosition}
|
||||
flexcontent
|
||||
.flexContent=${this.flexContent}
|
||||
.withoutHeader=${this.withoutHeader}
|
||||
>
|
||||
<slot name="headerNavigationIcon" slot="headerNavigationIcon">
|
||||
@@ -179,7 +221,7 @@ export class HaAdaptiveDialog extends LitElement {
|
||||
<slot name="headerActionItems" slot="headerActionItems"></slot>
|
||||
<slot></slot>
|
||||
<slot name="footer" slot="footer"></slot>
|
||||
</ha-wa-dialog>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -187,10 +229,17 @@ export class HaAdaptiveDialog extends LitElement {
|
||||
return [
|
||||
css`
|
||||
ha-bottom-sheet {
|
||||
--ha-bottom-sheet-border-radius: var(--ha-border-radius-2xl);
|
||||
--ha-bottom-sheet-surface-background: var(
|
||||
--ha-dialog-surface-background,
|
||||
var(--card-background-color, var(--ha-color-surface-default))
|
||||
);
|
||||
--ha-bottom-sheet-padding: 0 var(--safe-area-inset-right)
|
||||
var(--safe-area-inset-bottom) var(--safe-area-inset-left);
|
||||
--ha-bottom-sheet-content-padding: var(
|
||||
--dialog-content-padding,
|
||||
0 var(--ha-space-6) var(--ha-space-6) var(--ha-space-6)
|
||||
);
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -135,7 +135,7 @@ class HaAlert extends LitElement {
|
||||
}
|
||||
.action ha-icon-button {
|
||||
--mdc-theme-primary: var(--primary-text-color);
|
||||
--mdc-icon-button-size: 36px;
|
||||
--ha-icon-button-size: 36px;
|
||||
}
|
||||
.issue-type.info > .icon {
|
||||
color: var(--info-color);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { fireEvent } from "../common/dom/fire_event";
|
||||
import type { LocalizeFunc } from "../common/translations/localize";
|
||||
import type { Analytics, AnalyticsPreferences } from "../data/analytics";
|
||||
import { haStyle } from "../resources/styles";
|
||||
import "./ha-settings-row";
|
||||
import "./ha-md-list-item";
|
||||
import "./ha-switch";
|
||||
import "./ha-tooltip";
|
||||
import type { HaSwitch } from "./ha-switch";
|
||||
@@ -33,105 +33,80 @@ export class HaAnalytics extends LitElement {
|
||||
const baseEnabled = !loading && this.analytics!.preferences.base;
|
||||
|
||||
return html`
|
||||
<ha-settings-row>
|
||||
<span slot="heading" data-for="base">
|
||||
${this.localize(
|
||||
<ha-md-list-item>
|
||||
<span slot="headline"
|
||||
>${this.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.analytics.preferences.base.title`
|
||||
)}
|
||||
</span>
|
||||
<span slot="description" data-for="base">
|
||||
${this.localize(
|
||||
)}</span
|
||||
>
|
||||
<span slot="supporting-text"
|
||||
>${this.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.analytics.preferences.base.description`
|
||||
)}
|
||||
</span>
|
||||
)}</span
|
||||
>
|
||||
<ha-switch
|
||||
slot="end"
|
||||
@change=${this._handleRowClick}
|
||||
.checked=${!!baseEnabled}
|
||||
.preference=${"base"}
|
||||
.disabled=${loading}
|
||||
name="base"
|
||||
>
|
||||
</ha-switch>
|
||||
</ha-settings-row>
|
||||
></ha-switch>
|
||||
</ha-md-list-item>
|
||||
${ADDITIONAL_PREFERENCES.map(
|
||||
(preference) => html`
|
||||
<ha-settings-row>
|
||||
<span slot="heading" data-for=${preference}>
|
||||
${this.localize(
|
||||
<ha-md-list-item>
|
||||
<span slot="headline"
|
||||
>${this.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.analytics.preferences.${preference}.title`
|
||||
)}
|
||||
</span>
|
||||
<span slot="description" data-for=${preference}>
|
||||
${this.localize(
|
||||
)}</span
|
||||
>
|
||||
<span slot="supporting-text"
|
||||
>${this.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.analytics.preferences.${preference}.description`
|
||||
)}
|
||||
</span>
|
||||
<span>
|
||||
<ha-switch
|
||||
.id="switch-${preference}"
|
||||
@change=${this._handleRowClick}
|
||||
.checked=${!!this.analytics?.preferences[preference]}
|
||||
.preference=${preference}
|
||||
name=${preference}
|
||||
>
|
||||
</ha-switch>
|
||||
${baseEnabled
|
||||
? nothing
|
||||
: html`<ha-tooltip
|
||||
.for="switch-${preference}"
|
||||
placement="right"
|
||||
>
|
||||
${this.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.analytics.need_base_enabled`
|
||||
)}
|
||||
</ha-tooltip>`}
|
||||
</span>
|
||||
</ha-settings-row>
|
||||
)}</span
|
||||
>
|
||||
<ha-switch
|
||||
slot="end"
|
||||
.id="switch-${preference}"
|
||||
@change=${this._handleRowClick}
|
||||
.checked=${!!this.analytics?.preferences[preference]}
|
||||
.preference=${preference}
|
||||
name=${preference}
|
||||
></ha-switch>
|
||||
${baseEnabled
|
||||
? nothing
|
||||
: html`<ha-tooltip .for="switch-${preference}" placement="right">
|
||||
${this.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.analytics.need_base_enabled`
|
||||
)}
|
||||
</ha-tooltip>`}
|
||||
</ha-md-list-item>
|
||||
`
|
||||
)}
|
||||
<ha-settings-row>
|
||||
<span slot="heading" data-for="diagnostics">
|
||||
${this.localize(
|
||||
<ha-md-list-item>
|
||||
<span slot="headline"
|
||||
>${this.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.analytics.preferences.diagnostics.title`
|
||||
)}
|
||||
</span>
|
||||
<span slot="description" data-for="diagnostics">
|
||||
${this.localize(
|
||||
)}</span
|
||||
>
|
||||
<span slot="supporting-text"
|
||||
>${this.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.analytics.preferences.diagnostics.description`
|
||||
)}
|
||||
</span>
|
||||
)}</span
|
||||
>
|
||||
<ha-switch
|
||||
slot="end"
|
||||
@change=${this._handleRowClick}
|
||||
.checked=${!!this.analytics?.preferences.diagnostics}
|
||||
.preference=${"diagnostics"}
|
||||
.disabled=${loading}
|
||||
name="diagnostics"
|
||||
>
|
||||
</ha-switch>
|
||||
</ha-settings-row>
|
||||
></ha-switch>
|
||||
</ha-md-list-item>
|
||||
`;
|
||||
}
|
||||
|
||||
protected updated(changedProps) {
|
||||
super.updated(changedProps);
|
||||
|
||||
this.shadowRoot!.querySelectorAll("*[data-for]").forEach((el) => {
|
||||
const forEl = (el as HTMLElement).dataset.for;
|
||||
delete (el as HTMLElement).dataset.for;
|
||||
|
||||
el.addEventListener("click", () => {
|
||||
const toFocus = this.shadowRoot!.querySelector(
|
||||
`*[name=${forEl}]`
|
||||
) as HTMLElement | null;
|
||||
|
||||
if (toFocus) {
|
||||
toFocus.focus();
|
||||
toFocus.click();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private _handleRowClick(ev: Event) {
|
||||
const target = ev.currentTarget as HaSwitch;
|
||||
const preference = (target as any).preference;
|
||||
@@ -164,13 +139,10 @@ export class HaAnalytics extends LitElement {
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
||||
ha-settings-row {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
span[slot="heading"],
|
||||
span[slot="description"] {
|
||||
cursor: pointer;
|
||||
ha-md-list-item {
|
||||
--md-list-item-leading-space: 0;
|
||||
--md-list-item-trailing-space: 0;
|
||||
--md-item-overflow: visible;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -163,7 +163,7 @@ export class HaAreaPicker extends LitElement {
|
||||
{
|
||||
id: ADD_NEW_ID + searchString,
|
||||
primary: this.hass.localize(
|
||||
"ui.components.area-picker.add_new_sugestion",
|
||||
"ui.components.area-picker.add_new_suggestion",
|
||||
{
|
||||
name: searchString,
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { mdiAlertCircle, mdiMicrophone, mdiSend } from "@mdi/js";
|
||||
import type { PropertyValues, TemplateResult } from "lit";
|
||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
import { supportsFeature } from "../common/entity/supports-feature";
|
||||
import {
|
||||
runAssistPipeline,
|
||||
@@ -114,7 +115,7 @@ export class HaAssistChat extends LitElement {
|
||||
const supportsSTT = this.pipeline?.stt_engine && !this.disableSpeech;
|
||||
|
||||
return html`
|
||||
<div class="messages">
|
||||
<div class="messages ha-scrollbar">
|
||||
${controlHA
|
||||
? nothing
|
||||
: html`
|
||||
@@ -585,154 +586,167 @@ export class HaAssistChat extends LitElement {
|
||||
return progress;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
ha-alert {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
ha-textfield {
|
||||
display: block;
|
||||
}
|
||||
.messages {
|
||||
flex: 1;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
max-height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0 12px 16px;
|
||||
}
|
||||
.spacer {
|
||||
flex: 1;
|
||||
}
|
||||
.message {
|
||||
font-size: var(--ha-font-size-l);
|
||||
clear: both;
|
||||
max-width: -webkit-fill-available;
|
||||
overflow-wrap: break-word;
|
||||
scroll-margin-top: 24px;
|
||||
margin: 8px 0;
|
||||
padding: 8px;
|
||||
border-radius: var(--ha-border-radius-xl);
|
||||
}
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
.message {
|
||||
font-size: var(--ha-font-size-l);
|
||||
}
|
||||
}
|
||||
.message.user {
|
||||
margin-left: 24px;
|
||||
margin-inline-start: 24px;
|
||||
margin-inline-end: initial;
|
||||
align-self: flex-end;
|
||||
border-bottom-right-radius: 0px;
|
||||
--markdown-link-color: var(--text-primary-color);
|
||||
background-color: var(--chat-background-color-user, var(--primary-color));
|
||||
color: var(--text-primary-color);
|
||||
direction: var(--direction);
|
||||
}
|
||||
.message.hass {
|
||||
margin-right: 24px;
|
||||
margin-inline-end: 24px;
|
||||
margin-inline-start: initial;
|
||||
align-self: flex-start;
|
||||
border-bottom-left-radius: 0px;
|
||||
background-color: var(
|
||||
--chat-background-color-hass,
|
||||
var(--secondary-background-color)
|
||||
);
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyleScrollbar,
|
||||
css`
|
||||
:host {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
ha-alert {
|
||||
margin-bottom: var(--ha-space-2);
|
||||
}
|
||||
ha-textfield {
|
||||
display: block;
|
||||
}
|
||||
.messages {
|
||||
flex: 1 1 400px;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
min-height: 0;
|
||||
max-height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0 var(--ha-space-3) var(--ha-space-4);
|
||||
}
|
||||
.input {
|
||||
padding: var(--ha-space-1) var(--ha-space-4) var(--ha-space-6);
|
||||
}
|
||||
.spacer {
|
||||
flex: 1;
|
||||
}
|
||||
.message {
|
||||
font-size: var(--ha-font-size-l);
|
||||
clear: both;
|
||||
max-width: -webkit-fill-available;
|
||||
overflow-wrap: break-word;
|
||||
scroll-margin-top: var(--ha-space-6);
|
||||
margin: var(--ha-space-2) 0;
|
||||
padding: var(--ha-space-2);
|
||||
border-radius: var(--ha-border-radius-xl);
|
||||
}
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
.message {
|
||||
font-size: var(--ha-font-size-l);
|
||||
}
|
||||
}
|
||||
.message.user {
|
||||
margin-left: var(--ha-space-6);
|
||||
margin-inline-start: var(--ha-space-6);
|
||||
margin-inline-end: initial;
|
||||
align-self: flex-end;
|
||||
border-bottom-right-radius: 0px;
|
||||
--markdown-link-color: var(--text-primary-color);
|
||||
background-color: var(
|
||||
--chat-background-color-user,
|
||||
var(--primary-color)
|
||||
);
|
||||
color: var(--text-primary-color);
|
||||
direction: var(--direction);
|
||||
}
|
||||
.message.hass {
|
||||
margin-right: var(--ha-space-6);
|
||||
margin-inline-end: var(--ha-space-6);
|
||||
margin-inline-start: initial;
|
||||
align-self: flex-start;
|
||||
border-bottom-left-radius: 0px;
|
||||
background-color: var(
|
||||
--chat-background-color-hass,
|
||||
var(--secondary-background-color)
|
||||
);
|
||||
|
||||
color: var(--primary-text-color);
|
||||
direction: var(--direction);
|
||||
}
|
||||
.message.error {
|
||||
background-color: var(--error-color);
|
||||
color: var(--text-primary-color);
|
||||
}
|
||||
ha-markdown {
|
||||
--markdown-image-border-radius: calc(var(--ha-border-radius-xl) / 2);
|
||||
--markdown-table-border-color: var(--divider-color);
|
||||
--markdown-code-background-color: var(--primary-background-color);
|
||||
--markdown-code-text-color: var(--primary-text-color);
|
||||
--markdown-list-indent: 1.15em;
|
||||
&:not(:has(ha-markdown-element)) {
|
||||
min-height: 1lh;
|
||||
min-width: 1lh;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
.bouncer {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
position: absolute;
|
||||
}
|
||||
.double-bounce1,
|
||||
.double-bounce2 {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: var(--ha-border-radius-circle);
|
||||
background-color: var(--primary-color);
|
||||
opacity: 0.2;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
-webkit-animation: sk-bounce 2s infinite ease-in-out;
|
||||
animation: sk-bounce 2s infinite ease-in-out;
|
||||
}
|
||||
.double-bounce2 {
|
||||
-webkit-animation-delay: -1s;
|
||||
animation-delay: -1s;
|
||||
}
|
||||
@-webkit-keyframes sk-bounce {
|
||||
0%,
|
||||
100% {
|
||||
-webkit-transform: scale(0);
|
||||
}
|
||||
50% {
|
||||
-webkit-transform: scale(1);
|
||||
}
|
||||
}
|
||||
@keyframes sk-bounce {
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(0);
|
||||
-webkit-transform: scale(0);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1);
|
||||
-webkit-transform: scale(1);
|
||||
}
|
||||
}
|
||||
color: var(--primary-text-color);
|
||||
direction: var(--direction);
|
||||
}
|
||||
.message.error {
|
||||
background-color: var(--error-color);
|
||||
color: var(--text-primary-color);
|
||||
}
|
||||
ha-markdown {
|
||||
--markdown-image-border-radius: calc(var(--ha-border-radius-xl) / 2);
|
||||
--markdown-table-border-color: var(--divider-color);
|
||||
--markdown-code-background-color: var(--primary-background-color);
|
||||
--markdown-code-text-color: var(--primary-text-color);
|
||||
--markdown-list-indent: 1.15em;
|
||||
}
|
||||
ha-markdown:not(:has(ha-markdown-element)) {
|
||||
min-height: 1lh;
|
||||
min-width: 1lh;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.bouncer {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
position: absolute;
|
||||
}
|
||||
.double-bounce1,
|
||||
.double-bounce2 {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: var(--ha-border-radius-circle);
|
||||
background-color: var(--primary-color);
|
||||
opacity: 0.2;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
-webkit-animation: sk-bounce 2s infinite ease-in-out;
|
||||
animation: sk-bounce 2s infinite ease-in-out;
|
||||
}
|
||||
.double-bounce2 {
|
||||
-webkit-animation-delay: -1s;
|
||||
animation-delay: -1s;
|
||||
}
|
||||
@-webkit-keyframes sk-bounce {
|
||||
0%,
|
||||
100% {
|
||||
-webkit-transform: scale(0);
|
||||
}
|
||||
50% {
|
||||
-webkit-transform: scale(1);
|
||||
}
|
||||
}
|
||||
@keyframes sk-bounce {
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(0);
|
||||
-webkit-transform: scale(0);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1);
|
||||
-webkit-transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.listening-icon {
|
||||
position: relative;
|
||||
color: var(--secondary-text-color);
|
||||
margin-right: -24px;
|
||||
margin-inline-end: -24px;
|
||||
margin-inline-start: initial;
|
||||
direction: var(--direction);
|
||||
transform: scaleX(var(--scale-direction));
|
||||
}
|
||||
.listening-icon {
|
||||
position: relative;
|
||||
color: var(--secondary-text-color);
|
||||
margin-right: -24px;
|
||||
margin-inline-end: -24px;
|
||||
margin-inline-start: initial;
|
||||
direction: var(--direction);
|
||||
transform: scaleX(var(--scale-direction));
|
||||
}
|
||||
|
||||
.listening-icon[active] {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.listening-icon[active] {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.unsupported {
|
||||
color: var(--error-color);
|
||||
position: absolute;
|
||||
--mdc-icon-size: 16px;
|
||||
right: 5px;
|
||||
inset-inline-end: 5px;
|
||||
inset-inline-start: initial;
|
||||
top: 0px;
|
||||
}
|
||||
`;
|
||||
.unsupported {
|
||||
color: var(--error-color);
|
||||
position: absolute;
|
||||
--mdc-icon-size: 16px;
|
||||
right: 5px;
|
||||
inset-inline-end: 5px;
|
||||
inset-inline-start: initial;
|
||||
top: 0px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { mdiClose } from "@mdi/js";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement, property, queryAll } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import type { HaSelectSelectEvent } from "./ha-select";
|
||||
import "./ha-icon-button";
|
||||
import "./ha-input-helper-text";
|
||||
import "./ha-select";
|
||||
import type { HaSelectSelectEvent } from "./ha-select";
|
||||
import "./ha-textfield";
|
||||
import type { HaTextField } from "./ha-textfield";
|
||||
|
||||
@@ -133,6 +133,17 @@ export class HaBaseTimeInput extends LitElement {
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public clearable?: boolean;
|
||||
|
||||
@queryAll("ha-textfield") private _inputs?: HaTextField[];
|
||||
|
||||
static shadowRootOptions = {
|
||||
...LitElement.shadowRootOptions,
|
||||
delegatesFocus: true,
|
||||
};
|
||||
|
||||
public reportValidity(): boolean {
|
||||
return this._inputs?.every((input) => input.reportValidity()) ?? true;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
${this.label
|
||||
@@ -368,7 +379,7 @@ export class HaBaseTimeInput extends LitElement {
|
||||
}
|
||||
ha-icon-button {
|
||||
position: relative;
|
||||
--mdc-icon-button-size: 36px;
|
||||
--ha-icon-button-size: 36px;
|
||||
--mdc-icon-size: 20px;
|
||||
color: var(--secondary-text-color);
|
||||
direction: var(--direction);
|
||||
|
||||
@@ -1,21 +1,73 @@
|
||||
import "@home-assistant/webawesome/dist/components/drawer/drawer";
|
||||
import type WaDrawer from "@home-assistant/webawesome/dist/components/drawer/drawer";
|
||||
import { css, html, LitElement, type PropertyValues } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import type { HASSDomEvent } from "../common/dom/fire_event";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { SwipeGestureRecognizer } from "../common/util/swipe-gesture-recognizer";
|
||||
import { ScrollableFadeMixin } from "../mixins/scrollable-fade-mixin";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { isIosApp } from "../util/is_ios";
|
||||
|
||||
export const BOTTOM_SHEET_ANIMATION_DURATION_MS = 300;
|
||||
|
||||
const SWIPE_LOCKED_COMPONENTS = new Set([
|
||||
"ha-control-slider",
|
||||
"ha-slider",
|
||||
"ha-control-switch",
|
||||
"ha-control-circular-slider",
|
||||
"ha-hs-color-picker",
|
||||
"ha-map",
|
||||
"ha-more-info-control-select-container",
|
||||
"ha-filter-chip",
|
||||
]);
|
||||
|
||||
const SWIPE_LOCKED_CLASSES = new Set(["volume-slider-container", "forecast"]);
|
||||
|
||||
/**
|
||||
* Home Assistant bottom sheet component.
|
||||
*
|
||||
* @element ha-bottom-sheet
|
||||
* @extends {LitElement}
|
||||
*
|
||||
* @cssprop --ha-bottom-sheet-height - Preferred height of the bottom sheet.
|
||||
* @cssprop --ha-bottom-sheet-max-height - Maximum height of the bottom sheet.
|
||||
* @cssprop --ha-bottom-sheet-max-width - Maximum width of the bottom sheet.
|
||||
* @cssprop --ha-bottom-sheet-border-radius - Top border radius of the bottom sheet.
|
||||
* @cssprop --ha-bottom-sheet-surface-background - Bottom sheet background color.
|
||||
* @cssprop --ha-bottom-sheet-surface-backdrop-filter - Bottom sheet surface backdrop filter.
|
||||
* @cssprop --ha-bottom-sheet-scrim-backdrop-filter - Bottom sheet scrim backdrop filter.
|
||||
* @cssprop --ha-bottom-sheet-scrim-color - Bottom sheet scrim color.
|
||||
*
|
||||
* @cssprop --ha-dialog-surface-background - Bottom sheet background color fallback.
|
||||
* @cssprop --ha-dialog-surface-backdrop-filter - Bottom sheet surface backdrop filter fallback.
|
||||
* @cssprop --ha-dialog-scrim-backdrop-filter - Bottom sheet scrim backdrop filter fallback.
|
||||
* @cssprop --dialog-backdrop-filter - Bottom sheet scrim backdrop filter legacy fallback.
|
||||
* @cssprop --mdc-dialog-scrim-color - Bottom sheet scrim color legacy fallback.
|
||||
*/
|
||||
@customElement("ha-bottom-sheet")
|
||||
export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ attribute: "aria-labelledby" })
|
||||
public ariaLabelledBy?: string;
|
||||
|
||||
@property({ attribute: "aria-describedby" })
|
||||
public ariaDescribedBy?: string;
|
||||
|
||||
@property({ type: Boolean }) public open = false;
|
||||
|
||||
@property({ type: Boolean, reflect: true, attribute: "flexcontent" })
|
||||
public flexContent = false;
|
||||
|
||||
@property({ type: Boolean, reflect: true, attribute: "prevent-scrim-close" })
|
||||
public preventScrimClose = false;
|
||||
|
||||
@state() private _drawerOpen = false;
|
||||
|
||||
@state() private _sliderInteractionActive = false;
|
||||
|
||||
@query("#drawer") private _drawer!: HTMLElement;
|
||||
|
||||
@query("#body") private _bodyElement!: HTMLDivElement;
|
||||
@@ -28,14 +80,127 @@ export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
|
||||
|
||||
private _isDragging = false;
|
||||
|
||||
private _handleAfterHide(afterHideEvent: Event) {
|
||||
afterHideEvent.stopPropagation();
|
||||
this.open = false;
|
||||
const ev = new Event("closed", {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
private _escapePressed = false;
|
||||
|
||||
private _handleShow = async () => {
|
||||
this._drawerOpen = true;
|
||||
this.open = true;
|
||||
fireEvent(this, "opened");
|
||||
|
||||
await this.updateComplete;
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
if (this.hass && isIosApp(this.hass)) {
|
||||
const element = this.renderRoot.querySelector("[autofocus]");
|
||||
if (element !== null) {
|
||||
if (!element.id) {
|
||||
element.id = "ha-bottom-sheet-autofocus";
|
||||
}
|
||||
this.hass.auth.external?.fireMessage({
|
||||
type: "focus_element",
|
||||
payload: {
|
||||
element_id: element.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
(
|
||||
this.renderRoot.querySelector("[autofocus]") as HTMLElement | null
|
||||
)?.focus();
|
||||
});
|
||||
this.dispatchEvent(ev);
|
||||
};
|
||||
|
||||
private _handleAfterShow = () => {
|
||||
fireEvent(this, "after-show");
|
||||
};
|
||||
|
||||
private _handleSliderInteractionStart = () => {
|
||||
this._sliderInteractionActive = true;
|
||||
};
|
||||
|
||||
private _handleSliderInteractionStop = () => {
|
||||
this._sliderInteractionActive = false;
|
||||
};
|
||||
|
||||
private _handleAfterHide = (ev: CustomEvent<{ source: Element }>) => {
|
||||
if (this._sliderInteractionActive) {
|
||||
this._drawerOpen = true;
|
||||
this.open = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (ev.eventPhase === Event.AT_TARGET) {
|
||||
this.open = false;
|
||||
this._drawerOpen = false;
|
||||
fireEvent(this, "closed");
|
||||
}
|
||||
};
|
||||
|
||||
private _handleHide = (ev: CustomEvent<{ source: Element }>) => {
|
||||
// Ignore bubbled wa-hide events from nested drawers (e.g., picker bottom sheet)
|
||||
if (ev.eventPhase !== Event.AT_TARGET) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sourceIsDrawer = ev.detail.source === (ev.target as WaDrawer).drawer;
|
||||
|
||||
if (this._sliderInteractionActive) {
|
||||
ev.preventDefault();
|
||||
this._drawerOpen = true;
|
||||
this.open = true;
|
||||
this._escapePressed = false;
|
||||
return;
|
||||
}
|
||||
if (this.preventScrimClose && this._escapePressed && sourceIsDrawer) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
this._escapePressed = false;
|
||||
};
|
||||
|
||||
private _handleKeyDown = (ev: KeyboardEvent) => {
|
||||
if (ev.key === "Escape") {
|
||||
this._escapePressed = true;
|
||||
if (this.preventScrimClose) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
ev.stopPropagation();
|
||||
(ev.currentTarget as WaDrawer).open = false;
|
||||
}
|
||||
};
|
||||
|
||||
private _handleCloseAction = (ev: Event) => {
|
||||
const shouldClose = ev
|
||||
.composedPath()
|
||||
.some(
|
||||
(node) =>
|
||||
node instanceof HTMLElement &&
|
||||
(node.getAttribute("data-dialog") === "close" ||
|
||||
node.getAttribute("data-drawer") === "close")
|
||||
);
|
||||
|
||||
if (shouldClose) {
|
||||
this._drawerOpen = false;
|
||||
}
|
||||
};
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.addEventListener(
|
||||
"slider-interaction-start",
|
||||
this._handleSliderInteractionStart,
|
||||
{
|
||||
capture: true,
|
||||
}
|
||||
);
|
||||
this.addEventListener(
|
||||
"slider-interaction-stop",
|
||||
this._handleSliderInteractionStop,
|
||||
{
|
||||
capture: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues): void {
|
||||
@@ -51,10 +216,21 @@ export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
|
||||
id="drawer"
|
||||
placement="bottom"
|
||||
.open=${this._drawerOpen}
|
||||
.lightDismiss=${!this.preventScrimClose}
|
||||
.ariaLabelledby=${this.ariaLabelledBy}
|
||||
.ariaDescribedby=${this.ariaDescribedBy}
|
||||
@keydown=${this._handleKeyDown}
|
||||
@wa-show=${this._handleShow}
|
||||
@wa-after-show=${this._handleAfterShow}
|
||||
@wa-hide=${this._handleHide}
|
||||
@wa-after-hide=${this._handleAfterHide}
|
||||
@click=${this._handleCloseAction}
|
||||
without-header
|
||||
@touchstart=${this._handleTouchStart}
|
||||
>
|
||||
<div class="handle-wrapper" aria-hidden="true">
|
||||
<div class="handle"></div>
|
||||
</div>
|
||||
<slot name="header"></slot>
|
||||
<div class="content-wrapper">
|
||||
<div id="body" class="body ha-scrollbar">
|
||||
@@ -68,17 +244,37 @@ export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
|
||||
}
|
||||
|
||||
private _handleTouchStart = (ev: TouchEvent) => {
|
||||
// Check if any element inside drawer in the composed path has scrollTop > 0
|
||||
for (const path of ev.composedPath()) {
|
||||
const el = path as HTMLElement;
|
||||
if (el === this._drawer) {
|
||||
if (this.preventScrimClose) {
|
||||
return;
|
||||
}
|
||||
|
||||
const path = ev.composedPath();
|
||||
|
||||
for (const target of path) {
|
||||
if (target === this._drawer) {
|
||||
break;
|
||||
}
|
||||
if (el.scrollTop > 0) {
|
||||
|
||||
if (!(target instanceof HTMLElement)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
// Check if any element inside drawer in the composed path has scrollTop > 0 (list)
|
||||
target.scrollTop > 0 ||
|
||||
// Check if the element is a swipe locked component or has a swipe locked class
|
||||
SWIPE_LOCKED_COMPONENTS.has(target.localName) ||
|
||||
Array.from(target.classList).some((cls) =>
|
||||
SWIPE_LOCKED_CLASSES.has(cls)
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Stop propagation so parent bottom sheets don't also start tracking
|
||||
// this gesture (same pattern as _handleKeyDown for Escape)
|
||||
ev.stopPropagation();
|
||||
this._startResizing(ev.touches[0].clientY);
|
||||
};
|
||||
|
||||
@@ -174,6 +370,20 @@ export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this.removeEventListener(
|
||||
"slider-interaction-start",
|
||||
this._handleSliderInteractionStart,
|
||||
{
|
||||
capture: true,
|
||||
}
|
||||
);
|
||||
this.removeEventListener(
|
||||
"slider-interaction-stop",
|
||||
this._handleSliderInteractionStop,
|
||||
{
|
||||
capture: true,
|
||||
}
|
||||
);
|
||||
this._unregisterResizeHandlers();
|
||||
this._isDragging = false;
|
||||
}
|
||||
@@ -191,14 +401,54 @@ 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);
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
wa-drawer {
|
||||
--wa-color-surface-raised: transparent;
|
||||
--spacing: 0;
|
||||
--size: var(--ha-bottom-sheet-height, auto);
|
||||
--show-duration: 1ms;
|
||||
--hide-duration: 1ms;
|
||||
}
|
||||
wa-drawer::part(dialog) {
|
||||
transition: 1ms;
|
||||
}
|
||||
}
|
||||
wa-drawer::part(dialog)::backdrop {
|
||||
-webkit-backdrop-filter: var(
|
||||
--ha-bottom-sheet-scrim-backdrop-filter,
|
||||
var(
|
||||
--ha-dialog-scrim-backdrop-filter,
|
||||
var(--dialog-backdrop-filter, none)
|
||||
)
|
||||
);
|
||||
backdrop-filter: var(
|
||||
--ha-bottom-sheet-scrim-backdrop-filter,
|
||||
var(
|
||||
--ha-dialog-scrim-backdrop-filter,
|
||||
var(--dialog-backdrop-filter, none)
|
||||
)
|
||||
);
|
||||
background-color: var(
|
||||
--ha-bottom-sheet-scrim-color,
|
||||
var(--mdc-dialog-scrim-color, none)
|
||||
);
|
||||
}
|
||||
wa-drawer::part(body) {
|
||||
max-width: var(--ha-bottom-sheet-max-width);
|
||||
width: 100%;
|
||||
position: relative;
|
||||
border-top-left-radius: var(
|
||||
--ha-bottom-sheet-border-radius,
|
||||
var(--ha-dialog-border-radius, var(--ha-border-radius-2xl))
|
||||
@@ -209,7 +459,18 @@ export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
|
||||
);
|
||||
background-color: var(
|
||||
--ha-bottom-sheet-surface-background,
|
||||
var(--ha-dialog-surface-background, var(--mdc-theme-surface, #fff)),
|
||||
var(
|
||||
--ha-dialog-surface-background,
|
||||
var(--card-background-color, var(--ha-color-surface-default))
|
||||
)
|
||||
);
|
||||
-webkit-backdrop-filter: var(
|
||||
--ha-bottom-sheet-surface-backdrop-filter,
|
||||
var(--ha-dialog-surface-backdrop-filter, none)
|
||||
);
|
||||
backdrop-filter: var(
|
||||
--ha-bottom-sheet-surface-backdrop-filter,
|
||||
var(--ha-dialog-surface-backdrop-filter, none)
|
||||
);
|
||||
padding: var(
|
||||
--ha-bottom-sheet-padding,
|
||||
@@ -221,6 +482,35 @@ export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
:host([prevent-scrim-close]) .handle-wrapper {
|
||||
display: none;
|
||||
}
|
||||
.handle-wrapper {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
inset-inline-start: 0;
|
||||
width: 100%;
|
||||
padding-bottom: 2px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
.handle-wrapper .handle {
|
||||
height: 16px;
|
||||
width: 200px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.handle-wrapper .handle::after {
|
||||
content: "";
|
||||
border-radius: var(--ha-border-radius-md);
|
||||
height: 4px;
|
||||
background: var(--ha-bottom-sheet-handle-color, var(--divider-color));
|
||||
width: 40px;
|
||||
}
|
||||
.content-wrapper {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
@@ -228,16 +518,24 @@ export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
.body {
|
||||
padding: var(--ha-bottom-sheet-content-padding, 0);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
:host([flexcontent]) .body {
|
||||
flex: 1;
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: var(
|
||||
--ha-bottom-sheet-padding,
|
||||
0 var(--safe-area-inset-right) var(--safe-area-inset-bottom)
|
||||
var(--safe-area-inset-left)
|
||||
--ha-bottom-sheet-content-padding,
|
||||
var(
|
||||
--ha-bottom-sheet-padding,
|
||||
0 var(--safe-area-inset-right) var(--safe-area-inset-bottom)
|
||||
var(--safe-area-inset-left)
|
||||
)
|
||||
);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
slot[name="footer"] {
|
||||
display: block;
|
||||
@@ -262,6 +560,18 @@ export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
"slider-interaction-start": undefined;
|
||||
"slider-interaction-stop": undefined;
|
||||
}
|
||||
interface HTMLElementEventMap {
|
||||
"slider-interaction-start": HASSDomEvent<
|
||||
HASSDomEvents["slider-interaction-start"]
|
||||
>;
|
||||
"slider-interaction-stop": HASSDomEvent<
|
||||
HASSDomEvents["slider-interaction-stop"]
|
||||
>;
|
||||
}
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-bottom-sheet": HaBottomSheet;
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ export class HaButton extends Button {
|
||||
Button.styles,
|
||||
css`
|
||||
:host {
|
||||
--wa-form-control-padding-inline: 16px;
|
||||
--wa-form-control-padding-inline: var(--ha-space-4);
|
||||
--wa-font-weight-action: var(--ha-font-weight-medium);
|
||||
--wa-form-control-border-radius: var(
|
||||
--ha-button-border-radius,
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -68,7 +69,7 @@ export class HaButton extends Button {
|
||||
var(--button-height, 32px)
|
||||
);
|
||||
font-size: var(--wa-font-size-s, var(--ha-font-size-m));
|
||||
--wa-form-control-padding-inline: 12px;
|
||||
--wa-form-control-padding-inline: var(--ha-space-3);
|
||||
}
|
||||
|
||||
:host([variant="brand"]) {
|
||||
@@ -84,6 +85,9 @@ export class HaButton extends Button {
|
||||
--button-color-fill-loud-hover: var(
|
||||
--ha-color-fill-primary-loud-hover
|
||||
);
|
||||
--button-color-fill-quiet-active: var(
|
||||
--ha-color-fill-primary-quiet-active
|
||||
);
|
||||
}
|
||||
|
||||
:host([variant="neutral"]) {
|
||||
@@ -99,6 +103,9 @@ export class HaButton extends Button {
|
||||
--button-color-fill-loud-hover: var(
|
||||
--ha-color-fill-neutral-loud-hover
|
||||
);
|
||||
--button-color-fill-quiet-active: var(
|
||||
--ha-color-fill-neutral-normal-active
|
||||
);
|
||||
}
|
||||
|
||||
:host([variant="success"]) {
|
||||
@@ -114,6 +121,9 @@ export class HaButton extends Button {
|
||||
--button-color-fill-loud-hover: var(
|
||||
--ha-color-fill-success-loud-hover
|
||||
);
|
||||
--button-color-fill-quiet-active: var(
|
||||
--ha-color-fill-success-quiet-active
|
||||
);
|
||||
}
|
||||
|
||||
:host([variant="warning"]) {
|
||||
@@ -129,6 +139,9 @@ export class HaButton extends Button {
|
||||
--button-color-fill-loud-hover: var(
|
||||
--ha-color-fill-warning-loud-hover
|
||||
);
|
||||
--button-color-fill-quiet-active: var(
|
||||
--ha-color-fill-warning-quiet-active
|
||||
);
|
||||
}
|
||||
|
||||
:host([variant="danger"]) {
|
||||
@@ -144,6 +157,9 @@ export class HaButton extends Button {
|
||||
--button-color-fill-loud-hover: var(
|
||||
--ha-color-fill-danger-loud-hover
|
||||
);
|
||||
--button-color-fill-quiet-active: var(
|
||||
--ha-color-fill-danger-quiet-active
|
||||
);
|
||||
}
|
||||
|
||||
:host([appearance~="plain"]) .button {
|
||||
@@ -187,6 +203,10 @@ export class HaButton extends Button {
|
||||
background-color: var(--ha-color-fill-disabled-normal-resting);
|
||||
color: var(--ha-color-on-disabled-normal);
|
||||
}
|
||||
:host([appearance~="plain"])
|
||||
.button:not(.disabled):not(.loading):active {
|
||||
background-color: var(--button-color-fill-quiet-active);
|
||||
}
|
||||
|
||||
:host([appearance~="accent"]) .button {
|
||||
background-color: var(
|
||||
@@ -212,21 +232,21 @@ export class HaButton extends Button {
|
||||
}
|
||||
|
||||
slot[name="start"]::slotted(*) {
|
||||
margin-inline-end: 4px;
|
||||
margin-inline-end: var(--ha-space-1);
|
||||
}
|
||||
slot[name="end"]::slotted(*) {
|
||||
margin-inline-start: 4px;
|
||||
margin-inline-start: var(--ha-space-1);
|
||||
}
|
||||
|
||||
.button.has-start {
|
||||
padding-inline-start: 8px;
|
||||
padding-inline-start: var(--ha-space-2);
|
||||
}
|
||||
.button.has-end {
|
||||
padding-inline-end: 8px;
|
||||
padding-inline-end: var(--ha-space-2);
|
||||
}
|
||||
|
||||
.label {
|
||||
overflow: hidden;
|
||||
overflow: var(--ha-button-label-overflow, hidden);
|
||||
text-overflow: ellipsis;
|
||||
padding: var(--ha-space-1) 0;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { css, html, LitElement, nothing, type PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { STATE_RUNNING } from "home-assistant-js-websocket";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { computeStateName } from "../common/entity/compute_state_name";
|
||||
import { supportsFeature } from "../common/entity/supports-feature";
|
||||
@@ -58,12 +59,22 @@ export class HaCameraStream extends LitElement {
|
||||
@state() private _webRtcStreams?: { hasAudio: boolean; hasVideo: boolean };
|
||||
|
||||
public willUpdate(changedProps: PropertyValues): void {
|
||||
if (
|
||||
const entityChanged =
|
||||
changedProps.has("stateObj") &&
|
||||
this.stateObj &&
|
||||
(changedProps.get("stateObj") as CameraEntity | undefined)?.entity_id !==
|
||||
this.stateObj.entity_id
|
||||
) {
|
||||
this.stateObj.entity_id;
|
||||
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
const backendStarted =
|
||||
changedProps.has("hass") &&
|
||||
this.hass &&
|
||||
this.stateObj &&
|
||||
oldHass &&
|
||||
this.hass.config.state === STATE_RUNNING &&
|
||||
oldHass.config?.state !== STATE_RUNNING;
|
||||
|
||||
if (entityChanged || backendStarted) {
|
||||
this._getCapabilities();
|
||||
this._getPosterUrl();
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
mdiUndo,
|
||||
} from "@mdi/js";
|
||||
import type { HassEntities } from "home-assistant-js-websocket";
|
||||
import type { PropertyValues } from "lit";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { css, html, ReactiveElement, render } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
@@ -28,6 +28,7 @@ import { fireEvent } from "../common/dom/fire_event";
|
||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||
import { getEntityContext } from "../common/entity/context/get_entity_context";
|
||||
import { copyToClipboard } from "../common/util/copy-clipboard";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { showToast } from "../util/toast";
|
||||
import "./ha-code-editor-completion-items";
|
||||
@@ -83,6 +84,9 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
@property({ type: Boolean, attribute: "disable-fullscreen" })
|
||||
public disableFullscreen = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "in-dialog" })
|
||||
public inDialog = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "has-toolbar" })
|
||||
public hasToolbar = true;
|
||||
|
||||
@@ -131,6 +135,7 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.classList.toggle("in-dialog", this.inDialog);
|
||||
// Force update on reconnection so editor is recreated
|
||||
if (this.hasUpdated) {
|
||||
this.requestUpdate();
|
||||
@@ -149,6 +154,7 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
fireEvent(this, "dialog-set-fullscreen", false);
|
||||
super.disconnectedCallback();
|
||||
this.removeEventListener("keydown", stopPropagation);
|
||||
this.removeEventListener("keydown", this._handleKeyDown);
|
||||
@@ -215,6 +221,9 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
if (changedProps.has("error")) {
|
||||
this.classList.toggle("error-state", this.error);
|
||||
}
|
||||
if (changedProps.has("inDialog")) {
|
||||
this.classList.toggle("in-dialog", this.inDialog);
|
||||
}
|
||||
if (changedProps.has("_isFullscreen")) {
|
||||
this.classList.toggle("fullscreen", this._isFullscreen);
|
||||
this._updateToolbarButtons();
|
||||
@@ -310,6 +319,11 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
});
|
||||
this._canCopy = this._value?.length > 0;
|
||||
|
||||
const cmScroller = this.codemirror.dom.querySelector(".cm-scroller");
|
||||
if (cmScroller) {
|
||||
cmScroller.classList.add("ha-scrollbar");
|
||||
}
|
||||
|
||||
// Update the toolbar. Creating it if required
|
||||
this._updateToolbar();
|
||||
}
|
||||
@@ -428,10 +442,19 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
private _updateFullscreenState(
|
||||
fullscreen: boolean = this._isFullscreen
|
||||
): boolean {
|
||||
const previousFullscreen = this._isFullscreen;
|
||||
|
||||
this.classList.toggle("in-dialog", this.inDialog);
|
||||
|
||||
// Update the current fullscreen state based on selected value. If fullscreen
|
||||
// is disabled, or we have no toolbar, ensure we are not in fullscreen mode.
|
||||
this._isFullscreen =
|
||||
fullscreen && !this.disableFullscreen && this.hasToolbar;
|
||||
|
||||
if (previousFullscreen !== this._isFullscreen) {
|
||||
fireEvent(this, "dialog-set-fullscreen", this._isFullscreen);
|
||||
}
|
||||
|
||||
// Return whether successfully in requested state
|
||||
return this._isFullscreen === fullscreen;
|
||||
}
|
||||
@@ -802,100 +825,116 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
return [];
|
||||
};
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
position: relative;
|
||||
display: block;
|
||||
--code-editor-toolbar-height: 28px;
|
||||
}
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyleScrollbar,
|
||||
css`
|
||||
:host {
|
||||
position: relative;
|
||||
display: block;
|
||||
--code-editor-toolbar-height: 28px;
|
||||
}
|
||||
|
||||
:host(.error-state) .cm-gutters {
|
||||
border-color: var(--error-state-color, var(--error-color)) !important;
|
||||
}
|
||||
:host(.error-state) .cm-gutters {
|
||||
border-color: var(--error-state-color, var(--error-color)) !important;
|
||||
}
|
||||
|
||||
:host(.hasToolbar) .cm-gutters {
|
||||
padding-top: 0;
|
||||
}
|
||||
:host(.hasToolbar) .cm-gutters {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
:host(.hasToolbar) .cm-focused .cm-gutters {
|
||||
padding-top: 1px;
|
||||
}
|
||||
:host(.hasToolbar) .cm-focused .cm-gutters {
|
||||
padding-top: 1px;
|
||||
}
|
||||
|
||||
:host(.error-state) .cm-content {
|
||||
border-color: var(--error-state-color, var(--error-color)) !important;
|
||||
}
|
||||
:host(.error-state) .cm-content {
|
||||
border-color: var(--error-state-color, var(--error-color)) !important;
|
||||
}
|
||||
|
||||
:host(.hasToolbar) .cm-content {
|
||||
border: none;
|
||||
border-top: 1px solid var(--secondary-text-color);
|
||||
}
|
||||
:host(.hasToolbar) .cm-content {
|
||||
border: none;
|
||||
border-top: 1px solid var(--secondary-text-color);
|
||||
}
|
||||
|
||||
:host(.hasToolbar) .cm-focused .cm-content {
|
||||
border-top: 2px solid var(--primary-color);
|
||||
padding-top: 15px;
|
||||
}
|
||||
:host(.hasToolbar) .cm-focused .cm-content {
|
||||
border-top: 2px solid var(--primary-color);
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
:host(.fullscreen) {
|
||||
position: fixed !important;
|
||||
top: calc(var(--header-height, 56px) + 8px) !important;
|
||||
left: 8px !important;
|
||||
right: 8px !important;
|
||||
bottom: 8px !important;
|
||||
z-index: 6;
|
||||
border-radius: var(--ha-border-radius-lg) !important;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3) !important;
|
||||
overflow: hidden !important;
|
||||
background-color: var(
|
||||
--code-editor-background-color,
|
||||
var(--card-background-color)
|
||||
) !important;
|
||||
margin: 0 !important;
|
||||
padding-top: var(--safe-area-inset-top) !important;
|
||||
padding-left: var(--safe-area-inset-left) !important;
|
||||
padding-right: var(--safe-area-inset-right) !important;
|
||||
padding-bottom: var(--safe-area-inset-bottom) !important;
|
||||
box-sizing: border-box !important;
|
||||
display: block !important;
|
||||
}
|
||||
:host(.fullscreen) {
|
||||
position: fixed !important;
|
||||
top: calc(var(--header-height, 56px) + var(--ha-space-2)) !important;
|
||||
left: var(--ha-space-2) !important;
|
||||
right: var(--ha-space-2) !important;
|
||||
bottom: var(--ha-space-2) !important;
|
||||
z-index: 6;
|
||||
border-radius: var(--ha-border-radius-lg) !important;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3) !important;
|
||||
overflow: hidden !important;
|
||||
background-color: var(
|
||||
--code-editor-background-color,
|
||||
var(--card-background-color)
|
||||
) !important;
|
||||
margin: 0 !important;
|
||||
padding-top: var(--safe-area-inset-top) !important;
|
||||
padding-left: var(--safe-area-inset-left) !important;
|
||||
padding-right: var(--safe-area-inset-right) !important;
|
||||
padding-bottom: var(--safe-area-inset-bottom) !important;
|
||||
box-sizing: border-box !important;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
:host(.hasToolbar) .cm-editor {
|
||||
padding-top: var(--code-editor-toolbar-height);
|
||||
}
|
||||
:host(.in-dialog.fullscreen) {
|
||||
position: absolute !important;
|
||||
top: 0 !important;
|
||||
left: 0 !important;
|
||||
right: 0 !important;
|
||||
bottom: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
box-shadow: none !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
:host(.fullscreen) .cm-editor {
|
||||
height: 100% !important;
|
||||
max-height: 100% !important;
|
||||
border-radius: var(--ha-border-radius-square) !important;
|
||||
}
|
||||
:host(.hasToolbar) .cm-editor {
|
||||
padding-top: var(--code-editor-toolbar-height);
|
||||
}
|
||||
|
||||
:host(:not(.hasToolbar)) .code-editor-toolbar {
|
||||
display: none !important;
|
||||
}
|
||||
:host(.fullscreen) .cm-editor {
|
||||
height: 100% !important;
|
||||
max-height: 100% !important;
|
||||
border-radius: var(--ha-border-radius-square) !important;
|
||||
}
|
||||
|
||||
.code-editor-toolbar {
|
||||
--icon-button-toolbar-height: var(--code-editor-toolbar-height);
|
||||
--icon-button-toolbar-color: var(
|
||||
--code-editor-gutter-color,
|
||||
var(--secondary-background-color, whitesmoke)
|
||||
);
|
||||
border-top-left-radius: var(--ha-border-radius-sm);
|
||||
border-top-right-radius: var(--ha-border-radius-sm);
|
||||
}
|
||||
:host(:not(.hasToolbar)) .code-editor-toolbar {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.completion-info {
|
||||
display: grid;
|
||||
gap: 3px;
|
||||
padding: 8px;
|
||||
}
|
||||
.code-editor-toolbar {
|
||||
--icon-button-toolbar-height: var(--code-editor-toolbar-height);
|
||||
--icon-button-toolbar-color: var(
|
||||
--code-editor-gutter-color,
|
||||
var(--secondary-background-color, whitesmoke)
|
||||
);
|
||||
border-top-left-radius: var(--ha-border-radius-sm);
|
||||
border-top-right-radius: var(--ha-border-radius-sm);
|
||||
}
|
||||
|
||||
/* Hide completion info on narrow screens */
|
||||
@media (max-width: 600px) {
|
||||
.cm-completionInfo,
|
||||
.completion-info {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
`;
|
||||
.completion-info {
|
||||
display: grid;
|
||||
gap: 3px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
/* Hide completion info on narrow screens */
|
||||
@media (max-width: 600px) {
|
||||
.cm-completionInfo,
|
||||
.completion-info {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -6,7 +6,7 @@ import memoizeOne from "memoize-one";
|
||||
import { computeCssColor, THEME_COLORS } from "../common/color/compute-color";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import type { LocalizeKeys } from "../common/translations/localize";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../types";
|
||||
import "./ha-generic-picker";
|
||||
import type { PickerComboBoxItem } from "./ha-picker-combo-box";
|
||||
import type { PickerValueRenderer } from "./ha-picker-field";
|
||||
@@ -224,7 +224,7 @@ export class HaColorPicker extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent<{ value?: string }>) {
|
||||
private _valueChanged(ev: ValueChangedEvent<string | undefined>) {
|
||||
ev.stopPropagation();
|
||||
const selected = ev.detail.value;
|
||||
const normalized =
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user