Compare commits

...

153 Commits

Author SHA1 Message Date
Zack
14de223ffc Update for other editors 2022-03-22 10:21:26 -05:00
Zack
1290336cc5 update style 2022-03-18 13:18:46 -05:00
Zack
abaf5dd0f2 Stack Action Inputs in the Button Editor 2022-03-18 13:15:55 -05:00
Zack Barett
9c1d1cb6f6 Merge pull request #12069 from matthiasdebaat/patch-2 2022-03-18 07:32:14 -05:00
Matthias de Baat
470225abde Update logo.markdown 2022-03-18 11:26:19 +01:00
Matthias de Baat
ee230b86c1 Update gallery/src/pages/brand/logo.markdown
Co-authored-by: Zack Barett <zackbarett@hey.com>
2022-03-18 11:25:41 +01:00
Matthias de Baat
f927fc64a9 Update gallery/src/pages/brand/logo.markdown
Co-authored-by: Zack Barett <zackbarett@hey.com>
2022-03-18 11:25:34 +01:00
Zack Barett
03677c33f7 Bumped version to 20220317.0 (#12074) 2022-03-17 15:27:27 -07:00
Zack Barett
bc36a206da Merge pull request #11984 from Mariusthvdb/patch-4 2022-03-17 17:15:41 -05:00
Zack Barett
af06ab1e2d Merge pull request #12073 from home-assistant/bump-haws-701 2022-03-17 17:11:53 -05:00
Zack Barett
3e2135a485 Add radio Form Logic to Select Selector (#12063) 2022-03-17 15:04:59 -07:00
Zack Barett
2e7f8fb46f Add Date Time Selector (#12070) 2022-03-17 15:01:08 -07:00
Paulus Schoutsen
102568c4bd Update lock 2022-03-17 14:58:45 -07:00
Paulus Schoutsen
4fcdae842e Bump HAWS to 7.0.1 2022-03-17 14:50:16 -07:00
Paulus Schoutsen
ea19740f5a Ignore diagnostics not found exceptions (#12066) 2022-03-17 13:44:06 -07:00
Matthias de Baat
3e0942b631 Add files via upload 2022-03-17 14:19:28 +01:00
Matthias de Baat
0261cea796 Create new Logo page 2022-03-17 14:14:13 +01:00
Paulus Schoutsen
5247b2813f Bump HAWS to 7.0.0 (#12067) 2022-03-16 19:54:25 -10:00
Zack Barett
8a5090684e Merge pull request #12064 from home-assistant/20220316.0 2022-03-16 17:59:26 -05:00
Zack Barett
1784ba5e68 Merge pull request #12061 from home-assistant/Add-Date-Selector 2022-03-16 17:52:40 -05:00
Zack Barett
4fbe9a7b10 Merge pull request #12049 from home-assistant/hide-hidden-entities 2022-03-16 17:49:20 -05:00
Zack
1ca9c7838a Bumped version to 20220316.0 2022-03-16 17:46:32 -05:00
Zack
4fc2c3ef05 Remvoe redunency 2022-03-16 17:42:11 -05:00
Zack Barett
73ff8e28a8 Add Devices Picker (#12056) 2022-03-16 15:40:34 -07:00
Zack
dde1c5e03c Entity Status 2022-03-16 17:38:38 -05:00
Zack
01eed22592 clean up 2022-03-16 17:34:09 -05:00
Zack
94ebb63589 add to basic editor and update advanced style 2022-03-16 17:25:08 -05:00
Zack
29119db5ce Add translation 2022-03-16 17:05:52 -05:00
Paulus Schoutsen
9908162ac2 Add support for menu data entry flow option (#12055) 2022-03-16 14:14:38 -07:00
Paulus Schoutsen
1e929ae78a Revamp URL form (#12060) 2022-03-16 14:14:25 -07:00
Paulus Schoutsen
ab5df0fe6e test condition (#11925) 2022-03-16 14:13:13 -07:00
Paulus Schoutsen
d5010dda9e Add ha-form context (#12062) 2022-03-16 14:12:10 -07:00
Zack
4ac097f32b Add Date Selector 2022-03-16 14:20:45 -05:00
Zack Barett
5d3d15072f Merge pull request #12054 from home-assistant/Add-image-to-design-docs 2022-03-16 13:24:03 -05:00
Zack Barett
5c53bc4225 Add Color RGB Selector (#12039) 2022-03-15 15:34:02 -07:00
Zack
d5a307f8f4 Add icons and buttons 2022-03-15 15:00:35 -05:00
Zack
a27dd1e7f1 Add Description of chosen 2022-03-15 14:47:15 -05:00
Zack
c86ed1fb3e remove 1 2022-03-15 14:33:11 -05:00
Zack
7fa7a48072 Disabled by 2022-03-15 14:32:49 -05:00
Zack
4e0fc8ee08 Update Translations 2022-03-15 14:26:21 -05:00
Zack
5f6490e54e Add HA to public folder and show in markdown 2022-03-15 14:17:24 -05:00
Matthias de Baat
db78b046a2 Add Brand folder and Our story page (#11978)
Co-authored-by: Zack Barett <zackbarett@hey.com>
2022-03-15 09:01:42 -05:00
Zack
c37fe1e7ff add to demo 2022-03-14 20:39:03 -05:00
Zack
f1ec479d41 Reviews 2022-03-14 20:37:37 -05:00
Zack
e01cb3ca82 Utilize Hide Hidden Entities 2022-03-14 14:22:45 -05:00
Zack Barett
b8d3c68a7a Add Color Temp Selector (#12041) 2022-03-14 11:07:15 -07:00
Matthias de Baat
641003bb2a Rename Lovelace Dashboard to just Dashboard (#12044)
Co-authored-by: Zack Barett <zackbarett@hey.com>
2022-03-14 11:05:44 -07:00
Zack Barett
3358fc2b18 Add all cover device classes (#12042) 2022-03-14 10:45:12 -07:00
Zack Barett
dcf50e055b Fix @changed where using ev.detail (#12043) 2022-03-14 16:11:46 +00:00
Zack Barett
1fa04baa16 Fix: Changing Blueprint Automation Name (#12036) 2022-03-14 08:33:49 -07:00
jpearl
84ffa2369a Add shade to device class overrides (#11874) 2022-03-14 10:19:43 -05:00
Paulus Schoutsen
cc27ddb362 Bumped version to 20220312.0 2022-03-12 13:47:05 -08:00
Paulus Schoutsen
c4dc6bfb0d Bumped version to 20220301.2 2022-03-12 13:45:34 -08:00
Paulus Schoutsen
4fbcc30a37 Merge remote-tracking branch 'origin/master' into dev 2022-03-12 13:43:33 -08:00
Paulus Schoutsen
4916527e5f Bumped version to 20220301.1 2022-03-12 13:42:42 -08:00
Paulus Schoutsen
fad8a27232 HAWS 6.1 (#12016) 2022-03-12 09:56:25 -10:00
Zack Barett
a993d3a753 Script ID update with Alias (#12008) 2022-03-11 21:25:09 -08:00
Zack Barett
5dfe17a43a Fix: Allow for deleting Input_select options (#12007) 2022-03-11 17:07:56 -08:00
Zack Barett
9b6c935ffb Fix For Selecting Device Class (#12010) 2022-03-11 09:39:04 -08:00
Zack Barett
f4e28da0a3 Fix Dashboard Editing (#12011) 2022-03-11 09:38:18 -08:00
Zack Barett
294a69d7e4 Fix changing cost number in energy settings (#12009) 2022-03-11 09:37:22 -08:00
Charles Garwood
f89b8cffcf Fix zwave_js set config dropdown default value (#11974)
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2022-03-10 09:21:04 -06:00
Raman Gupta
99fd3a1b6f Fix zwave_js 'add/remove device' disabled bug (#12000)
* Fix zwave_js 'add/remove device' disabled bug

* revert extra change
2022-03-10 08:45:12 -05:00
Emil Stjerneman
246e426182 #11971 Change order of alarm panel buttons (#11998) 2022-03-09 19:54:40 -06:00
Paulus Schoutsen
9f1e9b43fe Use entities-picker in entity selector (#11990) 2022-03-08 21:33:23 -06:00
Marius
8301ae262c change icon to mimic physical device
and follow comments
2022-03-08 21:40:42 +01:00
Zack Barett
d968fe41ee Update Style of Design Page (#11982) 2022-03-08 10:19:18 -08:00
Bram Kragten
db830e9014 Fix theme setting (#11977) 2022-03-08 10:13:08 -08:00
Paulus Schoutsen
fc6b594a27 Allow selecting multiple entities (#11986) 2022-03-08 10:09:45 -08:00
Marius
86dbf99ebe replace default switch icon
to  make it stand out against a power entity which uses the same mdiFlash https://github.com/home-assistant/core/issues/67620#issuecomment-1061949527

suggest the Outline version, so create a subtle difference with the on/off icons.
2022-03-08 17:41:32 +01:00
Joakim Sørensen
68e7ce1883 Add systemd_resolved unsupported reason (#11971) 2022-03-07 17:42:49 +01:00
Bram Kragten
e9003ac35e Merge pull request #11969 from home-assistant/patch-release 2022-03-07 17:08:58 +01:00
Bram Kragten
1dd5214b42 Bumped version to 20220301.1 2022-03-07 16:51:41 +01:00
Bram Kragten
96738350bb Add location selector, convert zone editor (#11902) 2022-03-07 16:51:20 +01:00
Bram Kragten
5bdecf57cf Convert file upload to mdc (#11906) 2022-03-07 16:51:02 +01:00
Bram Kragten
ec12282f8c A11y expansion panel (#11967) 2022-03-07 16:50:42 +01:00
Zack Barett
552dbca201 Fix for Statistics Editor (#11942)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2022-03-07 16:50:29 +01:00
Bram Kragten
0bbc0ebb3c Make min width of select configurable (#11965) 2022-03-07 16:50:14 +01:00
Bram Kragten
ac7acc5802 Fix humidifier more info mode dropdown (#11964) 2022-03-07 16:49:59 +01:00
Philip Allgaier
64e1d160d1 Correct media upload error + add file name (#11949)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2022-03-07 16:49:40 +01:00
Bram Kragten
8e51878b6d Convert inputs (#11907)
* Convert inputs

* Update dialog-thingtalk.ts

* imports
2022-03-07 16:49:20 +01:00
Paulus Schoutsen
7c94ced303 Guard setting up config flow for an unsupported domain (#11937) 2022-03-07 16:48:57 +01:00
Bram Kragten
a040e1d5e0 Convert lovelace config dialogs to ha-form (#11910) 2022-03-07 16:48:36 +01:00
Bram Kragten
87c7407857 Convert objects to string in config flow error (#11908) 2022-03-07 16:48:16 +01:00
Bram Kragten
d0d0c44ec7 Fix quickbar overlaying, fix click handling (#11900) 2022-03-07 16:47:56 +01:00
Bram Kragten
4cdff3faea Add location selector, convert zone editor (#11902) 2022-03-07 08:47:20 -06:00
Bram Kragten
0dac10aa23 Convert file upload to mdc (#11906) 2022-03-07 08:42:40 -06:00
Bram Kragten
4b8b14a69d A11y expansion panel (#11967) 2022-03-07 08:40:19 -06:00
Zack Barett
9d28df31bd Fix for Statistics Editor (#11942)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2022-03-07 15:29:00 +01:00
Bram Kragten
8258641443 Make min width of select configurable (#11965) 2022-03-07 14:55:44 +01:00
Bram Kragten
dfcb0f6ba0 Fix humidifier more info mode dropdown (#11964) 2022-03-07 07:25:38 -06:00
Philip Allgaier
2e10eb04b6 Correct media upload error + add file name (#11949)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2022-03-07 12:08:54 +00:00
Raman Gupta
b4b52d3872 Remove some additional old zwave code (#11941) 2022-03-07 12:49:51 +01:00
Bram Kragten
3873203721 Convert inputs (#11907)
* Convert inputs

* Update dialog-thingtalk.ts

* imports
2022-03-07 12:45:39 +01:00
Paulus Schoutsen
ccb91e0b49 Allow marking YAML editor as read only (#11960) 2022-03-07 12:39:16 +01:00
Paulus Schoutsen
bd20c15a55 Show triggered vars on click (#11924) 2022-03-04 23:24:31 -08:00
Paulus Schoutsen
0936fd9ae4 Guard setting up config flow for an unsupported domain (#11937) 2022-03-04 14:31:11 -08:00
Bram Kragten
adefc7a4e2 Convert lovelace config dialogs to ha-form (#11910) 2022-03-04 23:15:10 +01:00
Bram Kragten
8f8017ecff Remove zwave and ozw panels (#11911)
Remove zwave and ozw panels
2022-03-04 14:10:44 -08:00
Robin Wittebol
604b79696e Always show tab labels (#11919) 2022-03-03 19:46:14 +01:00
Robin Wittebol
8c445f6409 Fix datepicker triangle (#11920) 2022-03-03 19:45:03 +01:00
Bram Kragten
797c871137 Convert objects to string in config flow error (#11908) 2022-03-03 13:55:40 +01:00
Steve Repsher
24829bd903 Supervisor mobile click accessibility (#11915) 2022-03-03 10:15:22 +01:00
Bram Kragten
add92a559d Fix quickbar overlaying, fix click handling (#11900) 2022-03-02 17:50:01 +01:00
Paulus Schoutsen
7f086c0900 Merge pull request #11899 from home-assistant/dev 2022-03-01 15:06:56 -08:00
Paulus Schoutsen
17018c0f26 Bumped version to 20220301.0 2022-03-01 14:48:41 -08:00
Joakim Sørensen
cd6a478130 Better handle brands URL in media thumbnails (#11890) 2022-03-01 14:32:56 -08:00
Zack Barett
4f6d7ca5c9 Fix for Entity SElector when supplying multiple domains (#11887)
Fix for Entity SElector when supplying multiple domains
2022-02-28 16:45:33 -08:00
Joakim Sørensen
c2994343b4 Remove unused attributes from search-input (#11889) 2022-02-28 18:27:07 -06:00
Joakim Sørensen
e5f77c35d4 Remove autofocus form log panel search (#11888) 2022-03-01 00:31:09 +01:00
Steve Repsher
a9e5a5dd44 Add a few button labels (#11885)
* Add label to remove addon repository button

* Add label to delete energy device button

* Add label to quick bar button
2022-02-28 23:53:39 +01:00
Bram Kragten
1159798b8d Energy: Wait with subscribe for _config to be set (#11884) 2022-02-28 12:32:36 -06:00
Paulus Schoutsen
437de42c55 Handle resolve media and cannot play errors (#11878) 2022-02-28 12:30:49 +01:00
Bram Kragten
89e0bb3f16 Fix date input in Safari (#11871)
* Fix date input in Safari

* helper
2022-02-28 12:29:53 +01:00
Robin Wittebol
28c9631b6c Remove white border on date-range-picker triangle (#11877) 2022-02-28 12:29:07 +01:00
Paulus Schoutsen
8882624618 Merge pull request #11867 from home-assistant/dev 2022-02-26 13:41:41 -08:00
Paulus Schoutsen
a769f84755 Bumped version to 20220226.0 2022-02-26 13:28:52 -08:00
Bram Kragten
7abf9c2473 Fix condition time (#11866)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
Co-authored-by: Zack Barett <zackbarett@hey.com>
2022-02-26 21:18:33 +00:00
Paulus Schoutsen
298296a81f Fix iOS audio (#11863) 2022-02-26 15:08:42 -06:00
Marc Mueller
6907fa5c8e Add py.typed (#11865) 2022-02-26 13:03:12 -08:00
Zack Barett
546461b70f Fix Render Pane on Mobile (#11856) 2022-02-25 11:51:55 -08:00
Joakim Sørensen
4031009c26 Only set tip once (#11853) 2022-02-25 10:03:55 -06:00
Paulus Schoutsen
91e4557625 Guard controls in more info media player (#11851) 2022-02-25 01:31:59 -06:00
Paulus Schoutsen
f0c4b92dbb Small fixes for actions (#11850) 2022-02-24 21:48:54 -08:00
Paulus Schoutsen
ffac3d055e Merge pull request #11848 from home-assistant/dev 2022-02-24 16:37:06 -08:00
Zack Barett
04ae8c9d14 Bumped version to 20220224.0 (#11847) 2022-02-24 16:22:09 -08:00
Zack Barett
0158610d42 Finishing up the editors (#11846) 2022-02-24 23:35:28 +00:00
Bram Kragten
5ab6121581 Fix quickbar showing on ha-select (#11845) 2022-02-24 14:24:32 -08:00
Bram Kragten
3d9c31aef9 Allow to clear integration filter on mobile (#11844) 2022-02-24 14:21:25 -08:00
Paulus Schoutsen
acfeea5c92 Show browse media even if media player has no controls (#11843) 2022-02-24 14:17:33 -08:00
Zack Barett
75e8e17073 Statistics Graph Editor to Ha Form (#11820) 2022-02-24 22:49:43 +01:00
Zack Barett
976fd4b32d weather forecast editor to HA form (#11823) 2022-02-24 22:49:20 +01:00
Bram Kragten
49beafbe5f Fix dev states filter field on iOS (#11839) 2022-02-24 21:57:20 +01:00
Bram Kragten
151f8d5524 Fix time trigger (#11841) 2022-02-24 14:36:18 -06:00
Bram Kragten
48355aa98e Fix icon color in select (#11842) 2022-02-24 14:35:09 -06:00
Paulus Schoutsen
fc31929f41 Fix saving home name (#11838) 2022-02-24 18:51:56 +00:00
Paulus Schoutsen
b7c149fcc1 Fix timer entity exception (#11837) 2022-02-24 10:30:45 -08:00
Paulus Schoutsen
02d058561b Lovelace: Datetime and text to match look select (#11836) 2022-02-24 18:16:42 +00:00
Zack Barett
4e57fb1ec1 Fix for Sidebar view rendering issue (#11835) 2022-02-24 10:15:34 -08:00
Patrick ZAJDA
30f79c5a46 Ask confirmation before logging out from Home Assistant Cloud (#11833)
Co-authored-by: Zack Barett <zackbarett@hey.com>
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2022-02-24 10:13:19 -08:00
Zack Barett
30f7252d84 Update Media Browser Bar to show image on mobile but a bit less (#11818) 2022-02-24 11:56:20 -06:00
Bram Kragten
8af795a7ce Make automation name field wide (#11832) 2022-02-24 09:52:40 -06:00
Joakim Sørensen
8576eeae41 Add tip rotation on dashboard (#11826)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2022-02-24 09:07:32 -06:00
Paulus Schoutsen
cd740ed135 Fix cast receiver background (#11825)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2022-02-24 10:52:12 +00:00
Zack Barett
892f774792 Picture Glance to HA Form (#11821) 2022-02-23 21:40:04 -08:00
Zack Barett
aa504fe1f8 Shopping list to MWC (#11822) 2022-02-23 21:36:58 -08:00
Paulus Schoutsen
be491451d5 Add run action to dropdown (#11817) 2022-02-23 21:36:25 -08:00
Zack Barett
bad184210d Control where the tip breaks (#11819) 2022-02-23 19:42:32 -06:00
Bram Kragten
a43b3b64b3 Link to filtered logs (#11816) 2022-02-23 23:14:16 +01:00
Bram Kragten
aa831a9adf bump marked and flatmap (#11814) 2022-02-23 22:11:13 +01:00
Bram Kragten
43d4f55392 Update workbox (#11813)
* Update workbox

* dedupe
2022-02-23 22:10:16 +01:00
Bram Kragten
130c66fb24 Update yarn (#11812)
* Update yarn

* Update yarn.lock

* dedupe
2022-02-23 22:08:43 +01:00
Zack Barett
684c232c8c Sensor Card Editor to Ha Form (#11810)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2022-02-23 21:46:52 +01:00
214 changed files with 5696 additions and 10478 deletions

File diff suppressed because one or more lines are too long

785
.yarn/releases/yarn-3.2.0.cjs vendored Executable file

File diff suppressed because one or more lines are too long

View File

@@ -6,4 +6,4 @@ plugins:
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
spec: "@yarnpkg/plugin-interactive-tools"
yarnPath: .yarn/releases/yarn-3.0.2.cjs
yarnPath: .yarn/releases/yarn-3.2.0.cjs

View File

@@ -3,7 +3,7 @@
const gulp = require("gulp");
const fs = require("fs");
const path = require("path");
const marked = require("marked");
const { marked } = require("marked");
const glob = require("glob");
const yaml = require("js-yaml");

View File

@@ -7,7 +7,7 @@ const source = require("vinyl-source-stream");
const vinylBuffer = require("vinyl-buffer");
const gulp = require("gulp");
const fs = require("fs");
const foreach = require("gulp-foreach");
const flatmap = require("gulp-flatmap");
const merge = require("gulp-merge-json");
const rename = require("gulp-rename");
const transform = require("gulp-json-transform");
@@ -183,7 +183,7 @@ gulp.task("build-merged-translations", () =>
})
.pipe(transform((data, file) => lokaliseTransform(data, data, file)))
.pipe(
foreach((stream, file) => {
flatmap((stream, file) => {
// For each language generate a merged json file. It begins with the master
// translation as a failsafe for untranslated strings, and merges all parent
// tags into one file for each specific subtag

View File

@@ -1,5 +1,5 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import { customElement, property, query } from "lit/decorators";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import { LovelaceConfig } from "../../../../src/data/lovelace";
import { Lovelace } from "../../../../src/panels/lovelace/types";
@@ -20,6 +20,8 @@ class HcLovelace extends LitElement {
@property() public urlPath: string | null = null;
@query("hui-view") private _huiView?: HTMLElement;
protected render(): TemplateResult {
const index = this._viewIndex;
if (index === undefined) {
@@ -78,12 +80,12 @@ class HcLovelace extends LitElement {
this.lovelaceConfig.background;
if (configBackground) {
(this.shadowRoot!.querySelector(
"hui-view"
) as HTMLElement)!.style.setProperty(
this._huiView!.style.setProperty(
"--lovelace-background",
configBackground
);
} else {
this._huiView!.style.removeProperty("--lovelace-background");
}
}
}
@@ -116,6 +118,9 @@ class HcLovelace extends LitElement {
:host > * {
flex: 1;
}
hui-view {
background: var(--lovelace-background, var(--primary-background-color));
}
`;
}
}

View File

@@ -1,4 +1,3 @@
import "web-animations-js/web-animations-next-lite.min";
import "../../../src/resources/ha-style";
import "../../../src/resources/roboto";
import "./layout/hc-lovelace";

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -36,6 +36,10 @@ module.exports = [
category: "misc",
header: "Miscelaneous",
},
{
category: "brand",
header: "Brand",
},
{
category: "user-test",
header: "User Tests",

View File

@@ -78,6 +78,9 @@ class DemoCards extends LitElement {
ha-formfield {
margin-right: 16px;
}
#container {
background-color: var(--primary-background-color);
}
`;
}

View File

@@ -12,7 +12,14 @@ class PageDescription extends HaMarkdown {
if (!PAGES[this.page].description) {
return html``;
}
return html`
<div class="heading">
<div class="title">
${PAGES[this.page].metadata.title || this.page.split("/")[1]}
</div>
<div class="subtitle">${PAGES[this.page].metadata.subtitle}</div>
</div>
${until(
PAGES[this.page]
.description()
@@ -25,9 +32,22 @@ class PageDescription extends HaMarkdown {
static styles = [
HaMarkdown.styles,
css`
.heading {
padding: 16px;
border-bottom: 1px solid var(--secondary-background-color);
}
.title {
font-size: 42px;
line-height: 56px;
padding-bottom: 8px;
}
.subtitle {
font-size: 18px;
line-height: 24px;
}
.root {
max-width: 800px;
margin: 0 auto;
margin: 16px auto;
}
.root > *:first-child {
margin-top: 0;

View File

@@ -5,6 +5,7 @@ import { html, css, LitElement, PropertyValues } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../src/components/ha-icon-button";
import "../../src/managers/notification-manager";
import "../../src/components/ha-expansion-panel";
import { haStyle } from "../../src/resources/styles";
import { PAGES, SIDEBAR } from "../build/import-pages";
import { dynamicElement } from "../../src/common/dom/dynamic-element-directive";
@@ -53,10 +54,9 @@ class HaGallery extends LitElement {
sidebar.push(
group.header
? html`
<details>
<summary class="section">${group.header}</summary>
<ha-expansion-panel .header=${group.header}>
${links}
</details>
</ha-expansion-panel>
`
: links
);
@@ -92,27 +92,34 @@ class HaGallery extends LitElement {
${dynamicElement(`demo-${this._page.replace("/", "-")}`)}
</div>
<div class="page-footer">
${PAGES[this._page].description ||
Object.keys(PAGES[this._page].metadata).length > 0
? html`
<a
href=${`${GITHUB_DEMO_URL}${this._page}.markdown`}
target="_blank"
>
Edit text
</a>
`
: ""}
${PAGES[this._page].demo
? html`
<a
href=${`${GITHUB_DEMO_URL}${this._page}.ts`}
target="_blank"
>
Edit demo
</a>
`
: ""}
<div class="header">Help us to improve our documentation</div>
<div class="secondary">
Suggest an edit to this page, or provide/view feedback for this
page.
</div>
<div>
${PAGES[this._page].description ||
Object.keys(PAGES[this._page].metadata).length > 0
? html`
<a
href=${`${GITHUB_DEMO_URL}${this._page}.markdown`}
target="_blank"
>
Edit text
</a>
`
: ""}
${PAGES[this._page].demo
? html`
<a
href=${`${GITHUB_DEMO_URL}${this._page}.ts`}
target="_blank"
>
Edit demo
</a>
`
: ""}
</div>
</div>
</div>
</mwc-drawer>
@@ -186,27 +193,16 @@ class HaGallery extends LitElement {
padding: 4px;
}
.sidebar details {
margin-top: 1em;
margin-left: 1em;
}
.sidebar summary {
cursor: pointer;
font-weight: bold;
margin-bottom: 8px;
}
.sidebar a {
color: var(--primary-text-color);
display: block;
padding: 4px 12px;
padding: 12px;
text-decoration: none;
position: relative;
}
.sidebar a[active]::before {
border-radius: 4px;
border-radius: 12px;
position: absolute;
top: 0;
right: 2px;
@@ -237,14 +233,32 @@ class HaGallery extends LitElement {
.page-footer {
text-align: center;
margin: 16px 0;
padding-top: 16px;
border-top: 1px solid rgba(0, 0, 0, 0.12);
margin: 16px;
padding: 16px;
border-radius: 12px;
background-color: var(--primary-background-color);
}
.page-footer div {
margin-top: 4px;
}
.page-footer .header {
font-size: 16px;
font-weight: 500;
line-height: 28px;
text-align: center;
}
.page-footer .secondary {
line-height: 23px;
text-align: center;
}
.page-footer a {
display: inline-block;
margin: 0 8px;
text-decoration: none;
}
`,
];

View File

@@ -0,0 +1,34 @@
---
title: "Logo"
---
![Using our logo](/images/using-our-logo.png)
# Using our logo
As a community, we are proud of our logo. Follow these guidelines to ensure it always looks its best. Our logo follows Google's material design spec and uses the blue interface color.
[Download Logo](https://github.com/home-assistant/assets/tree/master/logo)
![Logo](/images/logo.png)
## Using the icon
Our icon is a shorter and most used version of our logo. The icon can exist without the wordmark, the wordmark should never exist without the icon.
![Logo variants](/images/logo-variants.png)
## Using the right variant
The pretty blue logo with a background shadow, pictured top left, is our primary logo. It should only be used with black, white, and non-duotone photography.
When needed you can use our logo without a shadow, as seen as the second variant.
The outlined logo should only be used on packaging.
## Exclusion zone
The logo needs some personal space. It's exclusion zone is equal to a quarter the height of the icon.
![Clearspace](/images/clearspace.png)

View File

@@ -0,0 +1,41 @@
---
title: "Our story"
---
## Open source home automation that puts local control and privacy first
Home Assistant is a free and open-source software for home automation that is designed to be the central control system for smart home devices with a focus on local control and privacy. It can be accessed via a web-based user interface, via apps for Android and iOS, or using voice commands via a supported virtual assistant like Google Assistant and Amazon Alexa.
IoT devices and services are supported by modular support for controlling proprietary ecosystems if they provide public access via an Open API for third-party integrations and protocols like Bluetooth, MQTT, Zigbee, and Z-Wave, After the Home Assistant software application is installed as a computer appliance it will act as a central control system for home automation. Information from all entities it sees can be used and controlled from within scripts trigger automations using scheduling and "blueprint" subroutines, e.g. for controlling lighting, climate, entertainment systems, and appliances.
# Open Home
The Open Home is our vision for the smart home. It defines the values that we put at the heart of every decision we make at Home Assistant. Its woven into our architecture, licensing, community, and everything else.
The Open Home is about privacy, choice, and durability.
## Privacy
Your home should be your safe space. A place where you can be your true self without having to bother about what the world thinks of you. A place where you dont need to act differently to avoid an algorithm categorizing your behavior. Privacy for the Open Home means that devices need to work locally. No one else needs to know if you turn on a light bulb or change the thermostat.
It is okay for a product to offer a cloud connection, but it should be extra and opt-in.
## Choice
Devices in your home gather data about themselves and their surroundings. Your data. Vendors shouldnt be able to limit your access to your data or limit the interoperability of your devices with the rest of your smart home.
Choice for the Open Home means that devices need to make the gathered data available through local APIs. This avoids vendor lock-in and allows users to create their own smart home with devices from different manufacturers.
## Durability
If there is one thing that technology firms are very good at, it is launching new products. However, maintaining the products and making sure they keep working is an afterthought for most. The result is that vendors can decide to no longer support your device, crippling its features or even preventing it from working at all. As we install more and more devices in our home, durability is becoming more and more important. We shouldnt have to buy everything new every couple of years because the manufacturer decided to move on.
Durability for the Open Home means that devices are designed and built to keep working. Not just this year, but for the next decade.
# Our history
The project was started as a Python application by Paulus Schoutsen in September 2013 and first published publicly on GitHub in November 2013. In July 2017, a managed operating system called Hass.io was initially introduced to make it easier use to use Home Assistant on single-board computers like the Raspberry Pi series. Its bundled "supervisor" management system allowed users to manage, backup, and update the local installation and introduced the option to extend the functionality of the software with add-ons.
An optional subscription service was introduced in December 2017 for $5/month to solve the complexities associated with secured remote access, as well as linking to Amazon Alexa and Google Assistant. Nabu Casa, Inc. was formed in September 2018 to take over the subscription service. The company's funding is based solely on revenue from the subscription service. It is used to finance the project's infrastructure and to pay for full-time employees contributing to the project.
In January 2020, branding was adjusted to make it easier to refer to different parts of the project. The main piece of software was renamed to Home Assistant Core, while the full suite of software with the embedded operating system and bundled "supervisor" management system was renamed to Home Assistant.

View File

@@ -1,5 +1,6 @@
---
title: Alerts
subtitle: An alert displays a short, important message in a way that attracts the user's attention without interrupting the user's task.
---
# Alert `<ha-alert>`

View File

@@ -12,6 +12,98 @@ import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import { HomeAssistant } from "../../../../src/types";
import { getEntity } from "../../../../src/fake_data/entity";
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",
}),
];
const DEVICES = [
{
area_id: "bedroom",
configuration_url: null,
config_entries: ["config_entry_1"],
connections: [],
disabled_by: null,
entry_type: null,
id: "device_1",
identifiers: [["demo", "volume1"] as [string, string]],
manufacturer: null,
model: null,
name_by_user: null,
name: "Dishwasher",
sw_version: null,
hw_version: null,
via_device_id: null,
},
{
area_id: "backyard",
configuration_url: null,
config_entries: ["config_entry_2"],
connections: [],
disabled_by: null,
entry_type: null,
id: "device_2",
identifiers: [["demo", "pwm1"] as [string, string]],
manufacturer: null,
model: null,
name_by_user: null,
name: "Lamp",
sw_version: null,
hw_version: null,
via_device_id: null,
},
{
area_id: null,
configuration_url: null,
config_entries: ["config_entry_3"],
connections: [],
disabled_by: null,
entry_type: null,
id: "device_3",
identifiers: [["demo", "pwm1"] as [string, string]],
manufacturer: null,
model: null,
name_by_user: "User name",
name: "Technical name",
sw_version: null,
hw_version: null,
via_device_id: null,
},
];
const AREAS = [
{
area_id: "backyard",
name: "Backyard",
picture: null,
},
{
area_id: "bedroom",
name: "Bedroom",
picture: null,
},
{
area_id: "livingroom",
name: "Livingroom",
picture: null,
},
];
const SCHEMAS: {
title: string;
@@ -38,6 +130,8 @@ const SCHEMAS: {
select: "Select",
icon: "Icon",
media: "Media",
location: "Location",
entities: "Entities",
},
schema: [
{ name: "addon", selector: { addon: {} } },
@@ -45,6 +139,7 @@ const SCHEMAS: {
{
name: "Attribute",
selector: { attribute: { entity_id: "" } },
context: { filter_entity: "entity" },
},
{ name: "Device", selector: { device: {} } },
{ name: "Duration", selector: { duration: {} } },
@@ -75,6 +170,14 @@ const SCHEMAS: {
media: {},
},
},
{
name: "location",
selector: { location: { radius: true, icon: "mdi:home" } },
},
{
name: "entities",
selector: { entity: { multiple: true } },
},
],
},
{
@@ -315,9 +418,10 @@ class DemoHaForm extends LitElement {
const hass = provideHass(this);
hass.updateTranslations(null, "en");
hass.updateTranslations("config", "en");
hass.addEntities(ENTITIES);
mockEntityRegistry(hass);
mockDeviceRegistry(hass);
mockAreaRegistry(hass);
mockDeviceRegistry(hass, DEVICES);
mockAreaRegistry(hass, AREAS);
mockHassioSupervisor(hass);
}

View File

@@ -146,6 +146,8 @@ const SCHEMAS: {
},
boolean: { name: "Boolean", selector: { boolean: {} } },
time: { name: "Time", selector: { time: {} } },
date: { name: "Date", selector: { date: {} } },
datetime: { name: "Date Time", selector: { datetime: {} } },
action: { name: "Action", selector: { action: {} } },
text: {
name: "Text",
@@ -162,12 +164,44 @@ const SCHEMAS: {
},
},
object: { name: "Object", selector: { object: {} } },
select_radio: {
name: "Select (Radio)",
selector: { select: { options: ["Option 1", "Option 2"] } },
},
select: {
name: "Select",
selector: { select: { options: ["Option 1", "Option 2"] } },
selector: {
select: {
options: [
"Option 1",
"Option 2",
"Option 3",
"Option 4",
"Option 5",
"Option 6",
],
},
},
},
icon: { name: "Icon", selector: { icon: {} } },
media: { name: "Media", selector: { media: {} } },
location: { name: "Location", selector: { location: {} } },
location_radius: {
name: "Location with radius",
selector: { location: { radius: true, icon: "mdi:home" } },
},
color_temp: {
name: "Color Temperature",
selector: { color_temp: {} },
},
color_rgb: { name: "Color", selector: { color_rgb: {} } },
},
},
{
name: "Multiples",
input: {
entity: { name: "Entity", selector: { entity: { multiple: true } } },
device: { name: "Device", selector: { device: { multiple: true } } },
},
},
];
@@ -279,7 +313,7 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
can_play: true,
can_expand: false,
children_media_class: null,
thumbnail: null,
thumbnail: "https://brands.home-assistant.io/_/image/logo.png",
},
{
title: "movie.mp4",

View File

@@ -2,6 +2,8 @@
title: Editing design.home-assistant.io
---
![Home Assistant Logo](/images/logo-with-text.png)
# How to edit design.home-assistant.io
All pages are stored in [the pages folder][pages-folder] on GitHub. Pages are grouped in a folder per sidebar section. Each page can contain a `<page name>.markdown` description file, a `<page name>.ts` demo file or both. If both are defined the description is rendered first. The description can contain metadata to specify the title of the page.
@@ -41,15 +43,12 @@ import { html, css, LitElement } from "lit";
import { customElement } from "lit/decorators";
import "../../../../src/components/ha-card";
@customElement("demo-user-experience-usability")
export class DemoUserExperienceUsability extends LitElement {
protected render() {
return html`
<ha-card>
<div class="card-content">
Hello world!
</div>
<div class="card-content">Hello world!</div>
</ha-card>
`;
}

View File

@@ -188,6 +188,7 @@ const createEntityRegistryEntries = (
device_id: "mock-device-id",
area_id: null,
disabled_by: null,
hidden_by: null,
entity_category: null,
entity_id: "binary_sensor.updater",
name: null,

View File

@@ -14,7 +14,7 @@ import memoizeOne from "memoize-one";
import { atLeastVersion } from "../../../src/common/config/version";
import { fireEvent } from "../../../src/common/dom/fire_event";
import { navigate } from "../../../src/common/navigate";
import "../../../src/common/search/search-input";
import "../../../src/components/search-input";
import { extractSearchParam } from "../../../src/common/url/search-params";
import "../../../src/components/ha-button-menu";
import "../../../src/components/ha-icon-button";
@@ -110,8 +110,6 @@ class HassioAddonStore extends LitElement {
<div class="search">
<search-input
.hass=${this.hass}
no-label-float
no-underline
.filter=${this._filter}
@value-changed=${this._filterChanged}
></search-input>

View File

@@ -1,5 +1,4 @@
import { mdiFolderUpload } from "@mdi/js";
import "@polymer/paper-input/paper-input-container";
import { html, LitElement, TemplateResult } from "lit";
import { customElement, state } from "lit/decorators";
import { fireEvent } from "../../../src/common/dom/fire_event";

View File

@@ -3,7 +3,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/common/search/search-input";
import "../../../../src/components/search-input";
import { stringCompare } from "../../../../src/common/string/compare";
import "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-expansion-panel";
@@ -80,8 +80,6 @@ class HassioHardwareDialog extends LitElement {
></ha-icon-button>
<search-input
.hass=${this.hass}
dialogInitialFocus
no-label-float
.filter=${this._filter}
@value-changed=${this._handleSearchChange}
.label=${this._dialogParams.supervisor.localize(

View File

@@ -106,6 +106,9 @@ class HassioRepositoriesDialog extends LitElement {
</paper-item-body>
<div class="delete">
<ha-icon-button
.label=${this._dialogParams!.supervisor.localize(
"dialog.repositories.remove"
)}
.disabled=${usedRepositories.includes(repo.slug)}
.slug=${repo.slug}
.path=${usedRepositories.includes(repo.slug)

View File

@@ -1,9 +1,12 @@
// Compat needs to be first import
import "../../src/resources/compatibility";
import { setCancelSyntheticClickEvents } from "@polymer/polymer/lib/utils/settings";
import "../../src/resources/roboto";
import "../../src/resources/safari-14-attachshadow-patch";
import "./hassio-main";
setCancelSyntheticClickEvents(false);
const styleEl = document.createElement("style");
styleEl.innerHTML = `
body {

View File

@@ -121,7 +121,8 @@ export class HassioMain extends SupervisorBaseElement {
this.parentElement,
this.hass.themes,
themeName,
themeSettings
themeSettings,
true
);
}
}

View File

@@ -79,7 +79,6 @@
"@polymer/iron-icon": "^3.0.1",
"@polymer/iron-input": "^3.0.1",
"@polymer/iron-resizable-behavior": "^3.0.1",
"@polymer/paper-dropdown-menu": "^3.2.0",
"@polymer/paper-input": "^3.2.1",
"@polymer/paper-item": "^3.0.1",
"@polymer/paper-listbox": "^3.0.1",
@@ -109,7 +108,7 @@
"fuse.js": "^6.0.0",
"google-timezones-json": "^1.0.2",
"hls.js": "^1.1.5",
"home-assistant-js-websocket": "^6.0.1",
"home-assistant-js-websocket": "^7.0.1",
"idb-keyval": "^5.1.3",
"intl-messageformat": "^9.9.1",
"js-yaml": "^4.1.0",
@@ -117,7 +116,7 @@
"leaflet-draw": "^1.0.4",
"lit": "^2.1.2",
"lit-vaadin-helpers": "^0.3.0",
"marked": "^3.0.2",
"marked": "^4.0.12",
"memoize-one": "^5.2.1",
"node-vibrant": "3.2.1-alpha.1",
"proxy-polyfill": "^0.3.2",
@@ -136,13 +135,12 @@
"vis-network": "^8.5.4",
"vue": "^2.6.12",
"vue2-daterange-picker": "^0.5.1",
"web-animations-js": "^2.3.2",
"workbox-cacheable-response": "^6.1.5",
"workbox-core": "^6.1.5",
"workbox-expiration": "^6.1.5",
"workbox-precaching": "^6.1.5",
"workbox-routing": "^6.1.5",
"workbox-strategies": "^6.1.5",
"workbox-cacheable-response": "^6.4.2",
"workbox-core": "^6.4.2",
"workbox-expiration": "^6.4.2",
"workbox-precaching": "^6.4.2",
"workbox-routing": "^6.4.2",
"workbox-strategies": "^6.4.2",
"xss": "^1.0.9"
},
"devDependencies": {
@@ -171,7 +169,7 @@
"@types/js-yaml": "^4",
"@types/leaflet": "^1",
"@types/leaflet-draw": "^1",
"@types/marked": "^2",
"@types/marked": "^4",
"@types/mocha": "^8",
"@types/qrcode": "^1.4.2",
"@types/sortablejs": "^1",
@@ -198,7 +196,7 @@
"fs-extra": "^7.0.1",
"glob": "^7.2.0",
"gulp": "^4.0.2",
"gulp-foreach": "^0.1.0",
"gulp-flatmap": "^1.0.2",
"gulp-json-transform": "^0.4.6",
"gulp-merge-json": "^1.3.1",
"gulp-rename": "^2.0.0",
@@ -235,7 +233,7 @@
"webpack-dev-server": "^4.3.0",
"webpack-manifest-plugin": "^4.0.2",
"webpackbar": "^5.0.0-3",
"workbox-build": "^6.1.5"
"workbox-build": "^6.4.2"
},
"_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch",
"resolutions": {
@@ -255,5 +253,6 @@
"prettier": {
"trailingComma": "es5",
"arrowParens": "always"
}
},
"packageManager": "yarn@3.2.0"
}

View File

@@ -2,6 +2,6 @@
from pathlib import Path
def where():
def where() -> Path:
"""Return path to the frontend."""
return Path(__file__).parent

0
public/py.typed Normal file
View File

View File

@@ -1,6 +1,6 @@
[metadata]
name = home-assistant-frontend
version = 20220223.0
version = 20220317.0
author = The Home Assistant Authors
author_email = hello@home-assistant.io
license = Apache-2.0
@@ -19,3 +19,8 @@ python_requires = >= 3.4.0
[options.packages.find]
include =
hass_frontend*
[mypy]
python_version = 3.4
show_error_codes = True
strict = True

View File

@@ -101,13 +101,19 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
this._fetchAuthProviders();
if (matchMedia("(prefers-color-scheme: dark)").matches) {
applyThemesOnElement(document.documentElement, {
default_theme: "default",
default_dark_theme: null,
themes: {},
darkMode: true,
theme: "default",
});
applyThemesOnElement(
document.documentElement,
{
default_theme: "default",
default_dark_theme: null,
themes: {},
darkMode: true,
theme: "default",
},
undefined,
undefined,
true
);
}
if (!this.redirectUri) {

View File

@@ -31,11 +31,12 @@ export const applyThemesOnElement = (
element,
themes: HomeAssistant["themes"],
selectedTheme?: string,
themeSettings?: Partial<HomeAssistant["selectedTheme"]>
themeSettings?: Partial<HomeAssistant["selectedTheme"]>,
main?: boolean
) => {
// If there is no explicitly desired theme provided, we automatically
// If there is no explicitly desired theme provided, and the element is the main element we automatically
// use the active one from `themes`.
const themeToApply = selectedTheme || themes.theme;
const themeToApply = selectedTheme || (main ? themes.theme : undefined);
// If there is no explicitly desired dark mode provided, we automatically
// use the active one from `themes`.
@@ -47,7 +48,7 @@ export const applyThemesOnElement = (
let cacheKey = themeToApply;
let themeRules: Partial<ThemeVars> = {};
if (darkMode) {
if (themeToApply && darkMode) {
cacheKey = `${cacheKey}__dark`;
themeRules = { ...darkStyles };
}

View File

@@ -9,11 +9,10 @@ import {
mdiCast,
mdiCastConnected,
mdiClock,
mdiEmoticonDead,
mdiFlash,
mdiGestureTapButton,
mdiLanConnect,
mdiLanDisconnect,
mdiLightSwitch,
mdiLock,
mdiLockAlert,
mdiLockClock,
@@ -22,14 +21,11 @@ import {
mdiPowerPlug,
mdiPowerPlugOff,
mdiRestart,
mdiSleep,
mdiTimerSand,
mdiToggleSwitch,
mdiToggleSwitchOff,
mdiCheckCircleOutline,
mdiCloseCircleOutline,
mdiWeatherNight,
mdiZWave,
} from "@mdi/js";
import { HassEntity } from "home-assistant-js-websocket";
/**
@@ -112,19 +108,7 @@ export const domainIcon = (
case "switch":
return compareState === "on" ? mdiToggleSwitch : mdiToggleSwitchOff;
default:
return mdiFlash;
}
case "zwave":
switch (compareState) {
case "dead":
return mdiEmoticonDead;
case "sleeping":
return mdiSleep;
case "initializing":
return mdiTimerSand;
default:
return mdiZWave;
return mdiLightSwitch;
}
case "sensor": {

View File

@@ -0,0 +1,4 @@
const regexp =
/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
export const isIPAddress = (input: string): boolean => regexp.test(input);

View File

@@ -70,9 +70,6 @@ export const iconColorCSS = css`
}
ha-state-icon[data-domain="plant"][data-state="problem"],
ha-state-icon[data-domain="zwave"][data-state="dead"] {
color: var(--state-icon-error-color);
}
/* Color the icon if unavailable */
ha-state-icon[data-state="unavailable"] {

View File

@@ -1,8 +1,9 @@
import "@material/mwc-button";
import type { Button } from "@material/mwc-button";
import { mdiAlertOctagram, mdiCheckBold } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, query } from "lit/decorators";
import { customElement, property, state } from "lit/decorators";
import "../ha-circular-progress";
import "../ha-svg-icon";
@customElement("ha-progress-button")
export class HaProgressButton extends LitElement {
@@ -12,38 +13,53 @@ export class HaProgressButton extends LitElement {
@property({ type: Boolean }) public raised = false;
@query("mwc-button", true) private _button?: Button;
@state() private _result?: "success" | "error";
public render(): TemplateResult {
const overlay = this._result || this.progress;
return html`
<mwc-button
?raised=${this.raised}
.disabled=${this.disabled || this.progress}
@click=${this._buttonTapped}
class=${this._result || ""}
>
<slot></slot>
</mwc-button>
${this.progress
? html`<div class="progress">
<ha-circular-progress size="small" active></ha-circular-progress>
</div>`
: ""}
${!overlay
? ""
: html`
<div class="progress">
${this._result === "success"
? html`<ha-svg-icon .path=${mdiCheckBold}></ha-svg-icon>`
: this._result === "error"
? html`<ha-svg-icon .path=${mdiAlertOctagram}></ha-svg-icon>`
: this.progress
? html`
<ha-circular-progress
size="small"
active
></ha-circular-progress>
`
: ""}
</div>
`}
`;
}
public actionSuccess(): void {
this._tempClass("success");
this._setResult("success");
}
public actionError(): void {
this._tempClass("error");
this._setResult("error");
}
private _tempClass(className: string): void {
this._button!.classList.add(className);
private _setResult(result: "success" | "error"): void {
this._result = result;
setTimeout(() => {
this._button!.classList.remove(className);
}, 1000);
this._result = undefined;
}, 2000);
}
private _buttonTapped(ev: Event): void {
@@ -69,6 +85,7 @@ export class HaProgressButton extends LitElement {
background-color: var(--success-color);
transition: none;
border-radius: 4px;
pointer-events: none;
}
mwc-button[raised].success {
@@ -81,6 +98,7 @@ export class HaProgressButton extends LitElement {
background-color: var(--error-color);
transition: none;
border-radius: 4px;
pointer-events: none;
}
mwc-button[raised].error {
@@ -89,13 +107,21 @@ export class HaProgressButton extends LitElement {
}
.progress {
bottom: 0;
margin-top: 4px;
bottom: 4px;
position: absolute;
text-align: center;
top: 0;
top: 4px;
width: 100%;
}
ha-svg-icon {
color: white;
}
mwc-button.success slot,
mwc-button.error slot {
visibility: hidden;
}
`;
}
}

View File

@@ -21,7 +21,7 @@ import { styleMap } from "lit/directives/style-map";
import memoizeOne from "memoize-one";
import { restoreScroll } from "../../common/decorators/restore-scroll";
import { fireEvent } from "../../common/dom/fire_event";
import "../../common/search/search-input";
import "../search-input";
import { debounce } from "../../common/util/debounce";
import { nextRender } from "../../common/util/render-status";
import { haStyleScrollbar } from "../../resources/styles";

View File

@@ -115,6 +115,9 @@ class DateRangePickerElement extends WrappedElement {
color: var(--primary-text-color);
min-width: initial !important;
}
.daterangepicker:before {
display: none;
}
.daterangepicker:after {
border-bottom: 6px solid var(--card-background-color);
}

View File

@@ -1,4 +1,4 @@
import { html, LitElement, TemplateResult } from "lit";
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import { PolymerChangedEvent } from "../../polymer-types";
@@ -116,6 +116,12 @@ class HaDevicesPicker extends LitElement {
this._updateDevices([...currentDevices, toAdd]);
}
static override styles = css`
div {
margin-top: 8px;
}
`;
}
declare global {

View File

@@ -51,6 +51,8 @@ class HaEntitiesPickerLight extends LitElement {
@property({ attribute: "pick-entity-label" }) public pickEntityLabel?: string;
@property() public entityFilter?: HaEntityPickerEntityFilterFunc;
protected render(): TemplateResult {
if (!this.hass) {
return html``;
@@ -94,7 +96,9 @@ class HaEntitiesPickerLight extends LitElement {
private _entityFilter: HaEntityPickerEntityFilterFunc = (
stateObj: HassEntity
) => !this.value || !this.value.includes(stateObj.entity_id);
) =>
(!this.value || !this.value.includes(stateObj.entity_id)) &&
(!this.entityFilter || this.entityFilter(stateObj));
private get _currentEntities() {
return this.value || [];

View File

@@ -15,18 +15,21 @@ import "../ha-icon-button";
import "../ha-svg-icon";
import "./state-badge";
interface HassEntityWithCachedName extends HassEntity {
friendly_name: string;
}
export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
// eslint-disable-next-line lit/prefer-static-styles
const rowRenderer: ComboBoxLitRenderer<HassEntity & { friendly_name: string }> =
(item) =>
html`<mwc-list-item graphic="avatar" .twoline=${!!item.entity_id}>
${item.state
? html`<state-badge slot="graphic" .stateObj=${item}></state-badge>`
: ""}
<span>${item.friendly_name}</span>
<span slot="secondary">${item.entity_id}</span>
</mwc-list-item>`;
const rowRenderer: ComboBoxLitRenderer<HassEntityWithCachedName> = (item) =>
html`<mwc-list-item graphic="avatar" .twoline=${!!item.entity_id}>
${item.state
? html`<state-badge slot="graphic" .stateObj=${item}></state-badge>`
: ""}
<span>${item.friendly_name}</span>
<span slot="secondary">${item.entity_id}</span>
</mwc-list-item>`;
@customElement("ha-entity-picker")
export class HaEntityPicker extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -96,7 +99,7 @@ export class HaEntityPicker extends LitElement {
private _initedStates = false;
private _states: HassEntity[] = [];
private _states: HassEntityWithCachedName[] = [];
private _getStates = memoizeOne(
(
@@ -107,8 +110,8 @@ export class HaEntityPicker extends LitElement {
entityFilter: this["entityFilter"],
includeDeviceClasses: this["includeDeviceClasses"],
includeUnitOfMeasurement: this["includeUnitOfMeasurement"]
) => {
let states: HassEntity[] = [];
): HassEntityWithCachedName[] => {
let states: HassEntityWithCachedName[] = [];
if (!hass) {
return [];
@@ -122,7 +125,7 @@ export class HaEntityPicker extends LitElement {
state: "",
last_changed: "",
last_updated: "",
context: { id: "", user_id: null },
context: { id: "", user_id: null, parent_id: null },
friendly_name: this.hass!.localize(
"ui.components.entity.entity-picker.no_entities"
),
@@ -190,7 +193,7 @@ export class HaEntityPicker extends LitElement {
state: "",
last_changed: "",
last_updated: "",
context: { id: "", user_id: null },
context: { id: "", user_id: null, parent_id: null },
friendly_name: this.hass!.localize(
"ui.components.entity.entity-picker.no_match"
),

View File

@@ -41,7 +41,7 @@ export class HaDateInput extends LitElement {
return html`<ha-textfield
.label=${this.label}
.disabled=${this.disabled}
iconTrailing="calendar"
iconTrailing
@click=${this._openDialog}
.value=${this.value
? formatDateNumeric(new Date(this.value), this.locale)

View File

@@ -1,6 +1,13 @@
import { mdiChevronDown } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, query } from "lit/decorators";
import {
css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { fireEvent } from "../common/dom/fire_event";
import { nextRender } from "../common/util/render-status";
@@ -16,11 +23,21 @@ class HaExpansionPanel extends LitElement {
@property() secondary?: string;
@state() _showContent = this.expanded;
@query(".container") private _container!: HTMLDivElement;
protected render(): TemplateResult {
return html`
<div class="summary" @click=${this._toggleContainer}>
<div
id="summary"
@click=${this._toggleContainer}
@keydown=${this._toggleContainer}
role="button"
tabindex="0"
aria-expanded=${this.expanded}
aria-controls="sect1"
>
<slot class="header" name="header">
${this.header}
<slot class="secondary" name="secondary">${this.secondary}</slot>
@@ -33,21 +50,37 @@ class HaExpansionPanel extends LitElement {
<div
class="container ${classMap({ expanded: this.expanded })}"
@transitionend=${this._handleTransitionEnd}
role="region"
aria-labelledby="summary"
aria-hidden=${!this.expanded}
tabindex="-1"
>
<slot></slot>
${this._showContent ? html`<slot></slot>` : ""}
</div>
`;
}
private _handleTransitionEnd() {
this._container.style.removeProperty("height");
protected willUpdate(changedProps: PropertyValues) {
if (changedProps.has("expanded") && this.expanded) {
this._showContent = this.expanded;
}
}
private async _toggleContainer(): Promise<void> {
private _handleTransitionEnd() {
this._container.style.removeProperty("height");
this._showContent = this.expanded;
}
private async _toggleContainer(ev): Promise<void> {
if (ev.type === "keydown" && ev.key !== "Enter" && ev.key !== " ") {
return;
}
ev.preventDefault();
const newExpanded = !this.expanded;
fireEvent(this, "expanded-will-change", { expanded: newExpanded });
if (newExpanded) {
this._showContent = true;
// allow for dynamic content to be rendered
await nextRender();
}
@@ -80,17 +113,21 @@ class HaExpansionPanel extends LitElement {
var(--divider-color, #e0e0e0)
);
border-radius: var(--ha-card-border-radius, 4px);
padding: 0 8px;
}
.summary {
#summary {
display: flex;
padding: var(--expansion-panel-summary-padding, 0);
padding: var(--expansion-panel-summary-padding, 0 8px);
min-height: 48px;
align-items: center;
cursor: pointer;
overflow: hidden;
font-weight: 500;
outline: none;
}
#summary:focus {
background: var(--input-fill-color);
}
.summary-icon {
@@ -103,6 +140,7 @@ class HaExpansionPanel extends LitElement {
}
.container {
padding: var(--expansion-panel-content-padding, 0 8px);
overflow: hidden;
transition: height 300ms cubic-bezier(0.4, 0, 0.2, 1);
height: 0px;

View File

@@ -1,6 +1,5 @@
import { styles } from "@material/mwc-textfield/mwc-textfield.css";
import { mdiClose } from "@mdi/js";
import "@polymer/iron-input/iron-input";
import "@polymer/paper-input/paper-input-container";
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
@@ -21,7 +20,7 @@ export class HaFileUpload extends LitElement {
@property() public accept!: string;
@property() public icon!: string;
@property() public icon?: string;
@property() public label!: string;
@@ -39,15 +38,7 @@ export class HaFileUpload extends LitElement {
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
if (this.autoOpenFileDialog) {
this._input?.click();
}
}
protected updated(changedProperties: PropertyValues) {
if (changedProperties.has("_drag") && !this.uploading) {
(
this.shadowRoot!.querySelector("paper-input-container") as any
)._setFocused(this._drag);
this._openFilePicker();
}
}
@@ -60,51 +51,75 @@ export class HaFileUpload extends LitElement {
active
></ha-circular-progress>`
: html`
<label for="input">
<paper-input-container
.alwaysFloatLabel=${Boolean(this.value)}
@drop=${this._handleDrop}
@dragenter=${this._handleDragStart}
@dragover=${this._handleDragStart}
@dragleave=${this._handleDragEnd}
@dragend=${this._handleDragEnd}
class=${classMap({
dragged: this._drag,
})}
<label
for="input"
class="mdc-text-field mdc-text-field--filled ${classMap({
"mdc-text-field--focused": this._drag,
"mdc-text-field--with-leading-icon": Boolean(this.icon),
"mdc-text-field--with-trailing-icon": Boolean(this.value),
})}"
@drop=${this._handleDrop}
@dragenter=${this._handleDragStart}
@dragover=${this._handleDragStart}
@dragleave=${this._handleDragEnd}
@dragend=${this._handleDragEnd}
>
<span class="mdc-text-field__ripple"></span>
<span
class="mdc-floating-label ${this.value || this._drag
? "mdc-floating-label--float-above"
: ""}"
id="label"
>${this.label}</span
>
<label for="input" slot="label"> ${this.label} </label>
<iron-input slot="input">
<input
id="input"
type="file"
class="file"
accept=${this.accept}
@change=${this._handleFilePicked}
/>
${this.value}
</iron-input>
${this.value
? html`
<ha-icon-button
slot="suffix"
@click=${this._clearValue}
.label=${this.hass?.localize("ui.common.close") ||
"close"}
.path=${mdiClose}
></ha-icon-button>
`
: html`
<ha-icon-button
slot="suffix"
.path=${this.icon}
></ha-icon-button>
`}
</paper-input-container>
${this.icon
? html`<span
class="mdc-text-field__icon mdc-text-field__icon--leading"
tabindex="-1"
>
<ha-icon-button
@click=${this._openFilePicker}
.path=${this.icon}
></ha-icon-button>
</span>`
: ""}
<div class="value">${this.value}</div>
<input
id="input"
type="file"
class="mdc-text-field__input file"
accept=${this.accept}
@change=${this._handleFilePicked}
aria-labelledby="label"
/>
${this.value
? html`<span
class="mdc-text-field__icon mdc-text-field__icon--trailing"
tabindex="1"
>
<ha-icon-button
slot="suffix"
@click=${this._clearValue}
.label=${this.hass?.localize("ui.common.close") ||
"close"}
.path=${mdiClose}
></ha-icon-button>
</span>`
: ""}
<span
class="mdc-line-ripple ${this._drag
? "mdc-line-ripple--active"
: ""}"
></span>
</label>
`}
`;
}
private _openFilePicker() {
this._input?.click();
}
private _handleDrop(ev: DragEvent) {
ev.preventDefault();
ev.stopPropagation();
@@ -137,40 +152,66 @@ export class HaFileUpload extends LitElement {
}
static get styles() {
return css`
paper-input-container {
position: relative;
padding: 8px;
margin: 0 -8px;
}
paper-input-container.dragged:before {
position: var(--layout-fit_-_position);
top: var(--layout-fit_-_top);
right: var(--layout-fit_-_right);
bottom: var(--layout-fit_-_bottom);
left: var(--layout-fit_-_left);
background: currentColor;
content: "";
opacity: var(--dark-divider-opacity);
pointer-events: none;
border-radius: 4px;
}
input.file {
display: none;
}
img {
max-width: 125px;
max-height: 125px;
}
ha-icon-button {
--mdc-icon-button-size: 24px;
--mdc-icon-size: 20px;
}
ha-circular-progress {
display: block;
text-align-last: center;
}
`;
return [
styles,
css`
:host {
display: block;
}
.mdc-text-field--filled {
height: auto;
padding-top: 16px;
cursor: pointer;
}
.mdc-text-field--filled.mdc-text-field--with-trailing-icon {
padding-top: 28px;
}
.mdc-text-field:not(.mdc-text-field--disabled) .mdc-text-field__icon {
color: var(--secondary-text-color);
}
.mdc-text-field--filled.mdc-text-field--with-trailing-icon
.mdc-text-field__icon {
align-self: flex-end;
}
.mdc-text-field__icon--leading {
margin-bottom: 12px;
}
.mdc-text-field--filled .mdc-floating-label--float-above {
transform: scale(0.75);
top: 8px;
}
.dragged:before {
position: var(--layout-fit_-_position);
top: var(--layout-fit_-_top);
right: var(--layout-fit_-_right);
bottom: var(--layout-fit_-_bottom);
left: var(--layout-fit_-_left);
background: currentColor;
content: "";
opacity: var(--dark-divider-opacity);
pointer-events: none;
border-radius: 4px;
}
.value {
width: 100%;
}
input.file {
display: none;
}
img {
max-width: 100%;
max-height: 125px;
}
ha-icon-button {
--mdc-icon-button-size: 24px;
--mdc-icon-size: 20px;
}
ha-circular-progress {
display: block;
text-align-last: center;
}
`,
];
}
}

View File

@@ -9,7 +9,9 @@ export class HaFormConstant extends LitElement implements HaFormElement {
@property() public label!: string;
protected render(): TemplateResult {
return html`<span class="label">${this.label}</span>: ${this.schema.value}`;
return html`<span class="label">${this.label}</span>${this.schema.value
? `: ${this.schema.value}`
: ""}`;
}
static get styles(): CSSResultGroup {

View File

@@ -1,16 +1,20 @@
import "@material/mwc-list/mwc-list-item";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, query } from "lit/decorators";
import memoizeOne from "memoize-one";
import { html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import { stopPropagation } from "../../common/dom/stop_propagation";
import "../ha-radio";
import type { HaRadio } from "../ha-radio";
import "../ha-select";
import type { HaSelect } from "../ha-select";
import { HaFormElement, HaFormSelectData, HaFormSelectSchema } from "./types";
import type { HomeAssistant } from "../../types";
import type {
HaFormElement,
HaFormSelectData,
HaFormSelectSchema,
} from "./types";
import type { SelectSelector } from "../../data/selector";
import "../ha-selector/ha-selector-select";
@customElement("ha-form-select")
export class HaFormSelect extends LitElement implements HaFormElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public schema!: HaFormSelectSchema;
@property() public data!: HaFormSelectData;
@@ -19,60 +23,35 @@ export class HaFormSelect extends LitElement implements HaFormElement {
@property({ type: Boolean }) public disabled = false;
@query("ha-select", true) private _input?: HTMLElement;
public focus() {
if (this._input) {
this._input.focus();
}
}
private _selectSchema = memoizeOne(
(options): SelectSelector => ({
select: {
options: options.map((option) => ({
value: option[0],
label: option[1],
})),
},
})
);
protected render(): TemplateResult {
if (this.schema.required && this.schema.options!.length < 6) {
return html`
<div>
${this.label}
${this.schema.options.map(
([value, label]) => html`
<mwc-formfield .label=${label}>
<ha-radio
.checked=${value === this.data}
.value=${value}
.disabled=${this.disabled}
@change=${this._valueChanged}
></ha-radio>
</mwc-formfield>
`
)}
</div>
`;
}
return html`
<ha-select
fixedMenuPosition
naturalMenuWidth
.label=${this.label}
<ha-selector-select
.hass=${this.hass}
.schema=${this.schema}
.value=${this.data}
.label=${this.label}
.disabled=${this.disabled}
@closed=${stopPropagation}
@selected=${this._valueChanged}
>
${!this.schema.required
? html`<mwc-list-item value=""></mwc-list-item>`
: ""}
${this.schema.options!.map(
([value, label]) => html`
<mwc-list-item .value=${value}>${label}</mwc-list-item>
`
)}
</ha-select>
.required=${this.schema.required}
.selector=${this._selectSchema(this.schema.options)}
@value-changed=${this._valueChanged}
></ha-selector-select>
`;
}
private _valueChanged(ev: CustomEvent) {
ev.stopPropagation();
let value: string | undefined = (ev.target as HaSelect | HaRadio).value;
let value: string | undefined = ev.detail.value;
if (value === this.data) {
return;
@@ -86,15 +65,6 @@ export class HaFormSelect extends LitElement implements HaFormElement {
value,
});
}
static get styles(): CSSResultGroup {
return css`
ha-select,
mwc-formfield {
display: block;
}
`;
}
}
declare global {

View File

@@ -25,6 +25,8 @@ import { HomeAssistant } from "../../types";
const getValue = (obj, item) =>
obj ? (!item.name ? obj : obj[item.name]) : null;
const getError = (obj, item) => (obj && item.name ? obj[item.name] : null);
let selectorImported = false;
@customElement("ha-form")
@@ -84,7 +86,7 @@ export class HaForm extends LitElement implements HaFormElement {
`
: ""}
${this.schema.map((item) => {
const error = getValue(this.error, item);
const error = getError(this.error, item);
return html`
${error
@@ -104,6 +106,7 @@ export class HaForm extends LitElement implements HaFormElement {
.disabled=${this.disabled}
.helper=${this._computeHelper(item)}
.required=${item.required || false}
.context=${this._generateContext(item)}
></ha-selector>`
: dynamicElement(`ha-form-${item.type}`, {
schema: item,
@@ -113,6 +116,7 @@ export class HaForm extends LitElement implements HaFormElement {
hass: this.hass,
computeLabel: this.computeLabel,
computeHelper: this.computeHelper,
context: this._generateContext(item),
})}
`;
})}
@@ -120,6 +124,20 @@ export class HaForm extends LitElement implements HaFormElement {
`;
}
private _generateContext(
schema: HaFormSchema
): Record<string, any> | undefined {
if (!schema.context) {
return undefined;
}
const context = {};
for (const [context_key, data_key] of Object.entries(schema.context)) {
context[context_key] = this.data[data_key];
}
return context;
}
protected createRenderRoot() {
const root = super.createRenderRoot();
// attach it as soon as possible to make sure we fetch all events.

View File

@@ -24,6 +24,7 @@ export interface HaFormBaseSchema {
// This value will be set initially when form is loaded
suggested_value?: HaFormData;
};
context?: Record<string, string>;
}
export interface HaFormGridSchema extends HaFormBaseSchema {
@@ -40,7 +41,7 @@ export interface HaFormSelector extends HaFormBaseSchema {
export interface HaFormConstantSchema extends HaFormBaseSchema {
type: "constant";
value: string;
value?: string;
}
export interface HaFormIntegerSchema extends HaFormBaseSchema {

View File

@@ -1,6 +1,6 @@
import { SelectBase } from "@material/mwc-select/mwc-select-base";
import { styles } from "@material/mwc-select/mwc-select.css";
import { html, nothing } from "lit";
import { css, html, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { debounce } from "../common/util/debounce";
import { nextRender } from "../common/util/render-status";
@@ -20,8 +20,6 @@ export class HaSelect extends SelectBase {
></span>`;
}
static override styles = [styles];
connectedCallback() {
super.connectedCallback();
window.addEventListener("translations-updated", this._translationsUpdated);
@@ -39,6 +37,18 @@ export class HaSelect extends SelectBase {
await nextRender();
this.layoutOptions();
}, 500);
static override styles = [
styles,
css`
.mdc-select:not(.mdc-select--disabled) .mdc-select__icon {
color: var(--secondary-text-color);
}
.mdc-select__anchor {
width: var(--ha-select-min-width, 200px);
}
`,
];
}
declare global {
interface HTMLElementTagNameMap {

View File

@@ -1,9 +1,10 @@
import "../entity/ha-entity-attribute-picker";
import { html, LitElement } from "lit";
import { html, LitElement, PropertyValues } from "lit";
import { customElement, property } from "lit/decorators";
import { AttributeSelector } from "../../data/selector";
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import { HomeAssistant } from "../../types";
import { fireEvent } from "../../common/dom/fire_event";
@customElement("ha-selector-attribute")
export class HaSelectorAttribute extends SubscribeMixin(LitElement) {
@@ -17,11 +18,16 @@ export class HaSelectorAttribute extends SubscribeMixin(LitElement) {
@property({ type: Boolean }) public disabled = false;
@property() public context?: {
filter_entity?: string;
};
protected render() {
return html`
<ha-entity-attribute-picker
.hass=${this.hass}
.entityId=${this.selector.attribute.entity_id}
.entityId=${this.selector.attribute.entity_id ||
this.context?.filter_entity}
.value=${this.value}
.label=${this.label}
.disabled=${this.disabled}
@@ -29,6 +35,47 @@ export class HaSelectorAttribute extends SubscribeMixin(LitElement) {
></ha-entity-attribute-picker>
`;
}
protected updated(changedProps: PropertyValues): void {
super.updated(changedProps);
if (
// No need to filter value if no value
!this.value ||
// Only adjust value if we used the context
this.selector.attribute.entity_id ||
// Only check if context has changed
!changedProps.has("context")
) {
return;
}
const oldContext = changedProps.get("context") as this["context"];
if (
!this.context ||
oldContext?.filter_entity === this.context.filter_entity
) {
return;
}
// Validate that that the attribute is still valid for this entity, else unselect.
let invalid = false;
if (this.context.filter_entity) {
const stateObj = this.hass.states[this.context.filter_entity];
if (!(stateObj && this.value in stateObj.attributes)) {
invalid = true;
}
} else {
invalid = this.value !== undefined;
}
if (invalid) {
fireEvent(this, "value-changed", {
value: undefined,
});
}
}
}
declare global {

View File

@@ -0,0 +1,58 @@
import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import type { HomeAssistant } from "../../types";
import type { ColorRGBSelector } from "../../data/selector";
import { fireEvent } from "../../common/dom/fire_event";
import { hex2rgb, rgb2hex } from "../../common/color/convert-color";
import "../ha-textfield";
@customElement("ha-selector-color_rgb")
export class HaColorRGBSelector extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public selector!: ColorRGBSelector;
@property() public value?: string;
@property() public label?: string;
@property({ type: Boolean, reflect: true }) public disabled = false;
protected render() {
return html`
<ha-textfield
type="color"
.value=${this.value ? rgb2hex(this.value as any) : ""}
.label=${this.label || ""}
@change=${this._valueChanged}
></ha-textfield>
`;
}
private _valueChanged(ev: CustomEvent) {
const value = (ev.target as any).value;
fireEvent(this, "value-changed", {
value: hex2rgb(value),
});
}
static styles = css`
:host {
display: flex;
justify-content: flex-end;
align-items: center;
}
ha-textfield {
--text-field-padding: 8px;
min-width: 75px;
flex-grow: 1;
margin: 0 4px;
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"ha-selector-color_rgb": HaColorRGBSelector;
}
}

View File

@@ -0,0 +1,58 @@
import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import type { HomeAssistant } from "../../types";
import type { ColorTempSelector } from "../../data/selector";
import { fireEvent } from "../../common/dom/fire_event";
import "../ha-labeled-slider";
@customElement("ha-selector-color_temp")
export class HaColorTempSelector extends LitElement {
@property() public hass!: HomeAssistant;
@property() public selector!: ColorTempSelector;
@property() public value?: string;
@property() public label?: string;
@property({ type: Boolean, reflect: true }) public disabled = false;
protected render() {
return html`
<ha-labeled-slider
pin
icon="hass:thermometer"
.caption=${this.label}
.min=${this.selector.color_temp.min_mireds ?? 153}
.max=${this.selector.color_temp.max_mireds ?? 500}
.value=${this.value}
@change=${this._valueChanged}
></ha-labeled-slider>
`;
}
private _valueChanged(ev: CustomEvent) {
fireEvent(this, "value-changed", {
value: Number((ev.target as any).value),
});
}
static styles = css`
ha-labeled-slider {
--ha-slider-background: -webkit-linear-gradient(
right,
rgb(255, 160, 0) 0%,
white 50%,
rgb(166, 209, 255) 100%
);
/* The color temp minimum value shouldn't be rendered differently. It's not "off". */
--paper-slider-knob-start-border-color: var(--primary-color);
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"ha-selector-color_temp": HaColorTempSelector;
}
}

View File

@@ -0,0 +1,36 @@
import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import type { HomeAssistant } from "../../types";
import type { DateSelector } from "../../data/selector";
import "../ha-date-input";
@customElement("ha-selector-date")
export class HaDateSelector extends LitElement {
@property() public hass!: HomeAssistant;
@property() public selector!: DateSelector;
@property() public value?: string;
@property() public label?: string;
@property({ type: Boolean, reflect: true }) public disabled = false;
protected render() {
return html`
<ha-date-input
.label=${this.label}
.locale=${this.hass.locale}
.disabled=${this.disabled}
.value=${this.value}
>
</ha-date-input>
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-selector-date": HaDateSelector;
}
}

View File

@@ -0,0 +1,73 @@
import { css, html, LitElement } from "lit";
import { customElement, property, query } from "lit/decorators";
import type { HomeAssistant } from "../../types";
import type { DateTimeSelector } from "../../data/selector";
import type { HaDateInput } from "../ha-date-input";
import type { HaTimeInput } from "../ha-time-input";
import { fireEvent } from "../../common/dom/fire_event";
import "../ha-date-input";
import "../ha-time-input";
@customElement("ha-selector-datetime")
export class HaDateTimeSelector extends LitElement {
@property() public hass!: HomeAssistant;
@property() public selector!: DateTimeSelector;
@property() public value?: string;
@property() public label?: string;
@property({ type: Boolean, reflect: true }) public disabled = false;
@query("ha-date-input") private _dateInput!: HaDateInput;
@query("ha-time-input") private _timeInput!: HaTimeInput;
protected render() {
const values = this.value?.split(" ");
return html`
<ha-date-input
.label=${this.label}
.locale=${this.hass.locale}
.disabled=${this.disabled}
.value=${values?.[0]}
@value-changed=${this._valueChanged}
>
</ha-date-input>
<ha-time-input
enable-second
.value=${values?.[1] || "00:00:00"}
.locale=${this.hass.locale}
.disabled=${this.disabled}
@value-changed=${this._valueChanged}
></ha-time-input>
`;
}
private _valueChanged(ev: CustomEvent): void {
ev.stopPropagation();
fireEvent(this, "value-changed", {
value: `${this._dateInput.value} ${this._timeInput.value}`,
});
}
static styles = css`
:host {
display: flex;
align-items: center;
flex-direction: row;
}
ha-date-input {
min-width: 150px;
margin-right: 4px;
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"ha-selector-datetime": HaDateTimeSelector;
}
}

View File

@@ -1,10 +1,11 @@
import { html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { ConfigEntry, getConfigEntries } from "../../data/config_entries";
import { DeviceRegistryEntry } from "../../data/device_registry";
import { DeviceSelector } from "../../data/selector";
import { HomeAssistant } from "../../types";
import type { DeviceRegistryEntry } from "../../data/device_registry";
import type { DeviceSelector } from "../../data/selector";
import type { HomeAssistant } from "../../types";
import "../device/ha-device-picker";
import "../device/ha-devices-picker";
@customElement("ha-selector-device")
export class HaDeviceSelector extends LitElement {
@@ -30,20 +31,36 @@ export class HaDeviceSelector extends LitElement {
}
protected render() {
return html`<ha-device-picker
.hass=${this.hass}
.value=${this.value}
.label=${this.label}
.deviceFilter=${this._filterDevices}
.includeDeviceClasses=${this.selector.device.entity?.device_class
? [this.selector.device.entity.device_class]
: undefined}
.includeDomains=${this.selector.device.entity?.domain
? [this.selector.device.entity.domain]
: undefined}
.disabled=${this.disabled}
allow-custom-entity
></ha-device-picker>`;
if (!this.selector.device.multiple) {
return html`<ha-device-picker
.hass=${this.hass}
.value=${this.value}
.label=${this.label}
.deviceFilter=${this._filterDevices}
.includeDeviceClasses=${this.selector.device.entity?.device_class
? [this.selector.device.entity.device_class]
: undefined}
.includeDomains=${this.selector.device.entity?.domain
? [this.selector.device.entity.domain]
: undefined}
.disabled=${this.disabled}
allow-custom-entity
></ha-device-picker> `;
}
return html`
${this.label ? html`<label>${this.label}</label>` : ""}
<ha-devices-picker
.hass=${this.hass}
.value=${this.value}
.includeDeviceClasses=${this.selector.device.entity?.device_class
? [this.selector.device.entity.device_class]
: undefined}
.includeDomains=${this.selector.device.entity?.domain
? [this.selector.device.entity.domain]
: undefined}
></ha-devices-picker>
`;
}
private _filterDevices = (device: DeviceRegistryEntry): boolean => {

View File

@@ -7,6 +7,7 @@ import { EntitySelector } from "../../data/selector";
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import { HomeAssistant } from "../../types";
import "../entity/ha-entity-picker";
import "../entity/ha-entities-picker";
@customElement("ha-selector-entity")
export class HaEntitySelector extends SubscribeMixin(LitElement) {
@@ -23,14 +24,25 @@ export class HaEntitySelector extends SubscribeMixin(LitElement) {
@property({ type: Boolean }) public disabled = false;
protected render() {
return html`<ha-entity-picker
.hass=${this.hass}
.value=${this.value}
.label=${this.label}
.entityFilter=${this._filterEntities}
.disabled=${this.disabled}
allow-custom-entity
></ha-entity-picker>`;
if (!this.selector.entity.multiple) {
return html`<ha-entity-picker
.hass=${this.hass}
.value=${this.value}
.label=${this.label}
.entityFilter=${this._filterEntities}
.disabled=${this.disabled}
allow-custom-entity
></ha-entity-picker>`;
}
return html`
${this.label ? html`<label>${this.label}</label>` : ""}
<ha-entities-picker
.hass=${this.hass}
.value=${this.value}
.entityFilter=${this._filterEntities}
></ha-entities-picker>
`;
}
public hassSubscribe(): UnsubscribeFunc[] {
@@ -51,10 +63,11 @@ export class HaEntitySelector extends SubscribeMixin(LitElement) {
private _filterEntities = (entity: HassEntity): boolean => {
if (this.selector.entity?.domain) {
const filterDomain = this.selector.entity.domain;
const filterDomainIsArray = Array.isArray(filterDomain);
const entityDomain = computeStateDomain(entity);
if (
(Array.isArray(filterDomain) && !filterDomain.includes(entityDomain)) ||
entityDomain !== filterDomain
(filterDomainIsArray && !filterDomain.includes(entityDomain)) ||
(!filterDomainIsArray && entityDomain !== filterDomain)
) {
return false;
}

View File

@@ -0,0 +1,80 @@
import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../common/dom/fire_event";
import type {
LocationSelector,
LocationSelectorValue,
} from "../../data/selector";
import "../../panels/lovelace/components/hui-theme-select-editor";
import type { HomeAssistant } from "../../types";
import type { MarkerLocation } from "../map/ha-locations-editor";
import "../map/ha-locations-editor";
@customElement("ha-selector-location")
export class HaLocationSelector extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public selector!: LocationSelector;
@property() public value?: LocationSelectorValue;
@property() public label?: string;
@property({ type: Boolean, reflect: true }) public disabled = false;
protected render() {
return html`
<ha-locations-editor
class="flex"
.hass=${this.hass}
.locations=${this._location(this.selector, this.value)}
@location-updated=${this._locationChanged}
@radius-updated=${this._radiusChanged}
></ha-locations-editor>
`;
}
private _location = memoizeOne(
(
selector: LocationSelector,
value?: LocationSelectorValue
): MarkerLocation[] => {
const computedStyles = getComputedStyle(this);
const zoneRadiusColor = selector.location.radius
? computedStyles.getPropertyValue("--zone-radius-color") ||
computedStyles.getPropertyValue("--accent-color")
: undefined;
return [
{
id: "location",
latitude: value?.latitude || this.hass.config.latitude,
longitude: value?.longitude || this.hass.config.longitude,
radius: selector.location.radius ? value?.radius || 1000 : undefined,
radius_color: zoneRadiusColor,
icon: selector.location.icon,
location_editable: true,
radius_editable: true,
},
];
}
);
private _locationChanged(ev: CustomEvent) {
const [latitude, longitude] = ev.detail.location;
fireEvent(this, "value-changed", {
value: { ...this.value, latitude, longitude },
});
}
private _radiusChanged(ev: CustomEvent) {
const radius = ev.detail.radius;
fireEvent(this, "value-changed", { value: { ...this.value, radius } });
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-selector-location": HaLocationSelector;
}
}

View File

@@ -12,6 +12,7 @@ import {
} from "../../data/media-player";
import type { MediaSelector, MediaSelectorValue } from "../../data/selector";
import type { HomeAssistant } from "../../types";
import { brandsUrl, extractDomainFromBrandUrl } from "../../util/brands-url";
import "../ha-alert";
import "../ha-form/ha-form";
import type { HaFormSchema } from "../ha-form/types";
@@ -50,6 +51,18 @@ export class HaMediaSelector extends LitElement {
getSignedPath(this.hass, thumbnail).then((signedPath) => {
this._thumbnailUrl = signedPath.path;
});
} else if (
thumbnail &&
thumbnail.startsWith("https://brands.home-assistant.io")
) {
// The backend is not aware of the theme used by the users,
// so we rewrite the URL to show a proper icon
this._thumbnailUrl = brandsUrl({
domain: extractDomainFromBrandUrl(thumbnail),
type: "icon",
useFallback: true,
darkOptimized: this.hass.themes?.darkMode,
});
} else {
this._thumbnailUrl = thumbnail;
}

View File

@@ -1,17 +1,19 @@
import "@material/mwc-formfield/mwc-formfield";
import "@material/mwc-list/mwc-list-item";
import { css, CSSResultGroup, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import { stopPropagation } from "../../common/dom/stop_propagation";
import { SelectOption, SelectSelector } from "../../data/selector";
import { HomeAssistant } from "../../types";
import type { SelectOption, SelectSelector } from "../../data/selector";
import type { HomeAssistant } from "../../types";
import "../ha-select";
import "../ha-radio";
@customElement("ha-selector-select")
export class HaSelectSelector extends LitElement {
@property() public hass!: HomeAssistant;
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public selector!: SelectSelector;
@property({ attribute: false }) public selector!: SelectSelector;
@property() public value?: string;
@@ -21,24 +23,51 @@ export class HaSelectSelector extends LitElement {
@property({ type: Boolean }) public disabled = false;
protected render() {
return html`<ha-select
fixedMenuPosition
naturalMenuWidth
.label=${this.label}
.value=${this.value}
.helper=${this.helper}
.disabled=${this.disabled}
@closed=${stopPropagation}
@selected=${this._valueChanged}
>
${this.selector.select.options.map((item: string | SelectOption) => {
const value = typeof item === "object" ? item.value : item;
const label = typeof item === "object" ? item.label : item;
@property({ type: Boolean }) public required = true;
return html`<mwc-list-item .value=${value}>${label}</mwc-list-item>`;
})}
</ha-select>`;
protected render() {
if (this.required && this.selector.select.options!.length < 6) {
return html`
<div>
${this.label}
${this.selector.select.options.map((item: string | SelectOption) => {
const value = typeof item === "object" ? item.value : item;
const label = typeof item === "object" ? item.label : item;
return html`
<mwc-formfield .label=${label}>
<ha-radio
.checked=${value === this.value}
.value=${value}
.disabled=${this.disabled}
@change=${this._valueChanged}
></ha-radio>
</mwc-formfield>
`;
})}
</div>
`;
}
return html`
<ha-select
fixedMenuPosition
naturalMenuWidth
.label=${this.label}
.value=${this.value}
.helper=${this.helper}
.disabled=${this.disabled}
@closed=${stopPropagation}
@selected=${this._valueChanged}
>
${this.selector.select.options.map((item: string | SelectOption) => {
const value = typeof item === "object" ? item.value : item;
const label = typeof item === "object" ? item.label : item;
return html`<mwc-list-item .value=${value}>${label}</mwc-list-item>`;
})}
</ha-select>
`;
}
private _valueChanged(ev) {
@@ -56,6 +85,9 @@ export class HaSelectSelector extends LitElement {
ha-select {
width: 100%;
}
mwc-formfield {
display: block;
}
`;
}
}

View File

@@ -8,6 +8,9 @@ import "./ha-selector-addon";
import "./ha-selector-area";
import "./ha-selector-attribute";
import "./ha-selector-boolean";
import "./ha-selector-color-rgb";
import "./ha-selector-date";
import "./ha-selector-datetime";
import "./ha-selector-device";
import "./ha-selector-duration";
import "./ha-selector-entity";
@@ -20,6 +23,8 @@ import "./ha-selector-time";
import "./ha-selector-icon";
import "./ha-selector-media";
import "./ha-selector-theme";
import "./ha-selector-location";
import "./ha-selector-color-temp";
@customElement("ha-selector")
export class HaSelector extends LitElement {
@@ -39,6 +44,8 @@ export class HaSelector extends LitElement {
@property({ type: Boolean }) public required = true;
@property() public context?: Record<string, any>;
public focus() {
this.shadowRoot?.getElementById("selector")?.focus();
}
@@ -58,6 +65,7 @@ export class HaSelector extends LitElement {
disabled: this.disabled,
required: this.required,
helper: this.helper,
context: this.context,
id: "selector",
})}
`;

View File

@@ -42,9 +42,7 @@ export class HaTab extends LitElement {
@keydown=${this._handleKeyDown}
>
${this.narrow ? html`<slot name="icon"></slot>` : ""}
${!this.narrow || this.active
? html`<span class="name">${this.name}</span>`
: ""}
<span class="name">${this.name}</span>
${this._shouldRenderRipple ? html`<mwc-ripple></mwc-ripple>` : ""}
</div>
`;

View File

@@ -9,6 +9,12 @@ export class HaTextField extends TextFieldBase {
@property({ attribute: "error-message" }) public errorMessage?: string;
// @ts-ignore
@property({ type: Boolean }) public icon?: boolean;
// @ts-ignore
@property({ type: Boolean }) public iconTrailing?: boolean;
override updated(changedProperties: PropertyValues) {
super.updated(changedProperties);
if (
@@ -53,6 +59,15 @@ export class HaTextField extends TextFieldBase {
padding-right: var(--text-field-suffix-padding-right, 0px);
}
.mdc-text-field:not(.mdc-text-field--disabled)
.mdc-text-field__affix--suffix {
color: var(--secondary-text-color);
}
.mdc-text-field__icon {
color: var(--secondary-text-color);
}
input {
text-align: var(--text-field-text-align);
}

View File

@@ -31,6 +31,8 @@ export class HaYamlEditor extends LitElement {
@property() public label?: string;
@property({ type: Boolean }) public readOnly = false;
@state() private _yaml = "";
public setValue(value): void {
@@ -61,6 +63,7 @@ export class HaYamlEditor extends LitElement {
<ha-code-editor
.hass=${this.hass}
.value=${this._yaml}
.readOnly=${this.readOnly}
mode="yaml"
autocomplete-entities
.error=${this.isValid === false}

View File

@@ -34,23 +34,24 @@ import {
MediaPickedEvent,
MediaPlayerBrowseAction,
} from "../../data/media-player";
import { browseLocalMediaPlayer } from "../../data/media_source";
import { isTTSMediaSource } from "../../data/tts";
import { showAlertDialog } from "../../dialogs/generic/show-dialog-box";
import { installResizeObserver } from "../../panels/lovelace/common/install-resize-observer";
import { haStyle } from "../../resources/styles";
import type { HomeAssistant } from "../../types";
import { brandsUrl, extractDomainFromBrandUrl } from "../../util/brands-url";
import { documentationUrl } from "../../util/documentation-url";
import "../entity/ha-entity-picker";
import "../ha-button-menu";
import "../ha-card";
import type { HaCard } from "../ha-card";
import "../ha-circular-progress";
import "../ha-fab";
import "../ha-icon-button";
import "../ha-svg-icon";
import "../ha-fab";
import { browseLocalMediaPlayer } from "../../data/media_source";
import { isTTSMediaSource } from "../../data/tts";
import type { TtsMediaPickedEvent } from "./ha-browse-media-tts";
import "./ha-browse-media-tts";
import type { TtsMediaPickedEvent } from "./ha-browse-media-tts";
declare global {
interface HASSDomEvents {
@@ -681,6 +682,17 @@ export class HaMediaPlayerBrowse extends LitElement {
// Thumbnails served by local API require authentication
const signedPath = await getSignedPath(this.hass, thumbnailUrl);
thumbnailUrl = signedPath.path;
} else if (
thumbnailUrl.startsWith("https://brands.home-assistant.io")
) {
// The backend is not aware of the theme used by the users,
// so we rewrite the URL to show a proper icon
thumbnailUrl = brandsUrl({
domain: extractDomainFromBrandUrl(thumbnailUrl),
type: "icon",
useFallback: true,
darkOptimized: this.hass.themes?.darkMode,
});
}
thumbnailCard.style.backgroundImage = `url(${thumbnailUrl})`;
observer.unobserve(thumbnailCard); // loaded, so no need to observe anymore

View File

@@ -1,12 +1,12 @@
import { mdiClose, mdiMagnify } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../components/ha-icon-button";
import "../../components/ha-svg-icon";
import "../../components/ha-textfield";
import type { HaTextField } from "../../components/ha-textfield";
import { HomeAssistant } from "../../types";
import { fireEvent } from "../dom/fire_event";
import "./ha-icon-button";
import "./ha-svg-icon";
import "./ha-textfield";
import type { HaTextField } from "./ha-textfield";
import { HomeAssistant } from "../types";
import { fireEvent } from "../common/dom/fire_event";
@customElement("search-input")
class SearchInput extends LitElement {
@@ -35,7 +35,7 @@ class SearchInput extends LitElement {
.autofocus=${this.autofocus}
.label=${this.label || "Search"}
.value=${this.filter || ""}
.icon=${true}
icon
.iconTrailing=${this.filter || this.suffix}
@input=${this._filterInputChanged}
>

View File

@@ -6,6 +6,7 @@ import { AutomationConfig } from "./automation";
interface CloudStatusNotLoggedIn {
logged_in: false;
cloud: "disconnected" | "connecting" | "connected";
http_use_ssl: boolean;
}
export interface GoogleEntityConfig {
@@ -59,6 +60,7 @@ export interface CloudStatusLoggedIn {
remote_connected: boolean;
remote_certificate: undefined | CertificateInformation;
http_use_ssl: boolean;
active_subscription: boolean;
}
export type CloudStatus = CloudStatusNotLoggedIn | CloudStatusLoggedIn;

View File

@@ -1,8 +1,13 @@
import { HomeAssistant } from "../types";
interface ValidationResult {
valid: boolean;
error: string | null;
interface ValidConfig {
valid: true;
error: null;
}
interface InvalidConfig {
valid: false;
error: string;
}
type ValidKeys = "trigger" | "action" | "condition";
@@ -12,7 +17,7 @@ export const validateConfig = <
>(
hass: HomeAssistant,
config: T
): Promise<Record<keyof T, ValidationResult>> =>
): Promise<Record<keyof T, ValidConfig | InvalidConfig>> =>
hass.callWS({
type: "validate_config",
...config,

View File

@@ -28,7 +28,7 @@ export interface DataEntryFlowStepForm {
step_id: string;
data_schema: HaFormSchema[];
errors: Record<string, string>;
description_placeholders: Record<string, string>;
description_placeholders?: Record<string, string>;
last_step: boolean | null;
}
@@ -49,7 +49,7 @@ export interface DataEntryFlowStepCreateEntry {
title: string;
result?: ConfigEntry;
description: string;
description_placeholders: Record<string, string>;
description_placeholders?: Record<string, string>;
}
export interface DataEntryFlowStepAbort {
@@ -57,7 +57,7 @@ export interface DataEntryFlowStepAbort {
flow_id: string;
handler: string;
reason: string;
description_placeholders: Record<string, string>;
description_placeholders?: Record<string, string>;
}
export interface DataEntryFlowStepProgress {
@@ -66,7 +66,17 @@ export interface DataEntryFlowStepProgress {
handler: string;
step_id: string;
progress_action: string;
description_placeholders: Record<string, string>;
description_placeholders?: Record<string, string>;
}
export interface DataEntryFlowStepMenu {
type: "menu";
flow_id: string;
handler: string;
step_id: string;
/** If array, use value to lookup translations in strings.json */
menu_options: string[] | Record<string, string>;
description_placeholders?: Record<string, string>;
}
export type DataEntryFlowStep =
@@ -74,7 +84,8 @@ export type DataEntryFlowStep =
| DataEntryFlowStepExternal
| DataEntryFlowStepCreateEntry
| DataEntryFlowStepAbort
| DataEntryFlowStepProgress;
| DataEntryFlowStepProgress
| DataEntryFlowStepMenu;
export const subscribeDataEntryFlowProgressed = (
conn: Connection,

View File

@@ -14,6 +14,7 @@ export interface EntityRegistryEntry {
device_id: string | null;
area_id: string | null;
disabled_by: string | null;
hidden_by: string | null;
entity_category: "config" | "diagnostic" | null;
}
@@ -38,6 +39,7 @@ export interface EntityRegistryEntryUpdateParams {
device_class?: string | null;
area_id?: string | null;
disabled_by?: string | null;
hidden_by: string | null;
new_entity_id?: string;
}

View File

@@ -29,7 +29,7 @@ export const createImage = async (
body: fd,
});
if (resp.status === 413) {
throw new Error("Uploaded image is too large");
throw new Error(`Uploaded image is too large (${file.name})`);
} else if (resp.status !== 200) {
throw new Error("Unknown error");
}

View File

@@ -1,3 +1,4 @@
import { HassEntity } from "home-assistant-js-websocket";
import { HomeAssistant } from "../types";
export interface InputDateTime {
@@ -17,6 +18,19 @@ export interface InputDateTimeMutableParams {
has_date: boolean;
}
export const stateToIsoDateString = (entityState: HassEntity) =>
`${entityState.attributes.year || "1970"}-${String(
entityState.attributes.month || "01"
).padStart(2, "0")}-${String(entityState.attributes.day || "01").padStart(
2,
"0"
)}T${String(entityState.attributes.hour || "00").padStart(2, "0")}:${String(
entityState.attributes.minute || "00"
).padStart(2, "0")}:${String(entityState.attributes.second || "00").padStart(
2,
"0"
)}`;
export const setInputDateTimeValue = (
hass: HomeAssistant,
entityId: string,

View File

@@ -43,7 +43,7 @@ export const uploadLocalMedia = async (
}
);
if (resp.status === 413) {
throw new Error("Uploaded image is too large");
throw new Error(`Uploaded file is too large (${file.name})`);
} else if (resp.status !== 200) {
throw new Error("Unknown error");
}

View File

@@ -1,213 +0,0 @@
import { HomeAssistant } from "../types";
import { DeviceRegistryEntry } from "./device_registry";
export interface OZWNodeIdentifiers {
ozw_instance: number;
node_id: number;
}
export interface OZWDevice {
node_id: number;
node_query_stage: string;
is_awake: boolean;
is_failed: boolean;
is_zwave_plus: boolean;
ozw_instance: number;
event: string;
node_manufacturer_name: string;
node_product_name: string;
}
export interface OZWDeviceMetaDataResponse {
node_id: number;
ozw_instance: number;
metadata: OZWDeviceMetaData;
}
export interface OZWDeviceMetaData {
OZWInfoURL: string;
ZWAProductURL: string;
ProductPic: string;
Description: string;
ProductManualURL: string;
ProductPageURL: string;
InclusionHelp: string;
ExclusionHelp: string;
ResetHelp: string;
WakeupHelp: string;
ProductSupportURL: string;
Frequency: string;
Name: string;
ProductPicBase64: string;
}
export interface OZWInstance {
ozw_instance: number;
OZWDaemon_Version: string;
OpenZWave_Version: string;
QTOpenZWave_Version: string;
Status: string;
getControllerPath: string;
homeID: string;
}
export interface OZWNetworkStatistics {
ozw_instance: number;
node_count: number;
readCnt: number;
writeCnt: number;
ACKCnt: number;
CANCnt: number;
NAKCnt: number;
dropped: number;
retries: number;
}
export interface OZWDeviceConfig {
label: string;
type: string;
value: string | number;
parameter: number;
min: number;
max: number;
help: string;
}
export const nodeQueryStages = [
"ProtocolInfo",
"Probe",
"WakeUp",
"ManufacturerSpecific1",
"NodeInfo",
"NodePlusInfo",
"ManufacturerSpecific2",
"Versions",
"Instances",
"Static",
"CacheLoad",
"Associations",
"Neighbors",
"Session",
"Dynamic",
"Configuration",
"Complete",
];
export const networkOnlineStatuses = [
"driverAllNodesQueried",
"driverAllNodesQueriedSomeDead",
"driverAwakeNodesQueried",
];
export const networkStartingStatuses = [
"starting",
"started",
"Ready",
"driverReady",
];
export const networkOfflineStatuses = [
"Offline",
"stopped",
"driverFailed",
"driverReset",
"driverRemoved",
"driverAllNodesOnFire",
];
export const getIdentifiersFromDevice = function (
device: DeviceRegistryEntry
): OZWNodeIdentifiers | undefined {
if (!device) {
return undefined;
}
const ozwIdentifier = device.identifiers.find(
(identifier) => identifier[0] === "ozw"
);
if (!ozwIdentifier) {
return undefined;
}
const identifiers = ozwIdentifier[1].split(".");
return {
node_id: parseInt(identifiers[1]),
ozw_instance: parseInt(identifiers[0]),
};
};
export const fetchOZWInstances = (
hass: HomeAssistant
): Promise<OZWInstance[]> =>
hass.callWS({
type: "ozw/get_instances",
});
export const fetchOZWNetworkStatus = (
hass: HomeAssistant,
ozw_instance: number
): Promise<OZWInstance> =>
hass.callWS({
type: "ozw/network_status",
ozw_instance,
});
export const fetchOZWNetworkStatistics = (
hass: HomeAssistant,
ozw_instance: number
): Promise<OZWNetworkStatistics> =>
hass.callWS({
type: "ozw/network_statistics",
ozw_instance,
});
export const fetchOZWNodes = (
hass: HomeAssistant,
ozw_instance: number
): Promise<OZWDevice[]> =>
hass.callWS({
type: "ozw/get_nodes",
ozw_instance,
});
export const fetchOZWNodeStatus = (
hass: HomeAssistant,
ozw_instance: number,
node_id: number
): Promise<OZWDevice> =>
hass.callWS({
type: "ozw/node_status",
ozw_instance,
node_id,
});
export const fetchOZWNodeMetadata = (
hass: HomeAssistant,
ozw_instance: number,
node_id: number
): Promise<OZWDeviceMetaDataResponse> =>
hass.callWS({
type: "ozw/node_metadata",
ozw_instance,
node_id,
});
export const fetchOZWNodeConfig = (
hass: HomeAssistant,
ozw_instance: number,
node_id: number
): Promise<OZWDeviceConfig[]> =>
hass.callWS({
type: "ozw/get_config_parameters",
ozw_instance,
node_id,
});
export const refreshNodeInfo = (
hass: HomeAssistant,
ozw_instance: number,
node_id: number
): Promise<OZWDevice> =>
hass.callWS({
type: "ozw/refresh_node_info",
ozw_instance,
node_id,
});

View File

@@ -2,6 +2,8 @@ export type Selector =
| AddonSelector
| AttributeSelector
| EntitySelector
| DateSelector
| DateTimeSelector
| DeviceSelector
| DurationSelector
| AreaSelector
@@ -15,22 +17,41 @@ export type Selector =
| SelectSelector
| IconSelector
| MediaSelector
| ThemeSelector;
| ThemeSelector
| LocationSelector
| ColorTempSelector
| ColorRGBSelector;
export interface EntitySelector {
entity: {
integration?: string;
domain?: string | string[];
device_class?: string;
multiple?: boolean;
};
}
export interface AttributeSelector {
attribute: {
entity_id: string;
entity_id?: string;
};
}
export interface ColorRGBSelector {
// eslint-disable-next-line @typescript-eslint/ban-types
color_rgb: {};
}
export interface DateSelector {
// eslint-disable-next-line @typescript-eslint/ban-types
date: {};
}
export interface DateTimeSelector {
// eslint-disable-next-line @typescript-eslint/ban-types
datetime: {};
}
export interface DeviceSelector {
device: {
integration?: string;
@@ -40,6 +61,7 @@ export interface DeviceSelector {
domain?: EntitySelector["entity"]["domain"];
device_class?: EntitySelector["entity"]["device_class"];
};
multiple?: boolean;
};
}
@@ -95,6 +117,13 @@ export interface NumberSelector {
};
}
export interface ColorTempSelector {
color_temp: {
min_mireds?: number;
max_mireds?: number;
};
}
export interface BooleanSelector {
// eslint-disable-next-line @typescript-eslint/ban-types
boolean: {};
@@ -164,6 +193,16 @@ export interface MediaSelector {
media: {};
}
export interface LocationSelector {
location: { radius?: boolean; icon?: string };
}
export interface LocationSelectorValue {
latitude: number;
longitude: number;
radius?: number;
}
export interface MediaSelectorValue {
entity_id?: string;
media_content_id?: string;

View File

@@ -1,7 +1,10 @@
import { HomeAssistant } from "../types";
import { Action } from "./script";
export const callExecuteScript = (hass: HomeAssistant, sequence: Action[]) =>
export const callExecuteScript = (
hass: HomeAssistant,
sequence: Action | Action[]
) =>
hass.callWS({
type: "execute_script",
sequence,

View File

@@ -12,12 +12,12 @@ export interface Zone {
}
export interface ZoneMutableParams {
name: string;
icon?: string;
latitude: number;
longitude: number;
name: string;
passive: boolean;
radius: number;
passive?: boolean;
radius?: number;
}
export const fetchZones = (hass: HomeAssistant) =>

View File

@@ -1,81 +0,0 @@
import { HomeAssistant } from "../types";
export interface ZWaveNetworkStatus {
state: number;
}
export interface ZWaveValue {
key: number;
value: {
index: number;
instance: number;
label: string;
poll_intensity: number;
};
}
export interface ZWaveConfigItem {
key: number;
value: {
data: any;
data_items: any[];
help: string;
label: string;
max: number;
min: number;
type: string;
};
}
export interface ZWaveConfigServiceData {
node_id: number;
parameter: number;
value: number | string;
}
export interface ZWaveNode {
attributes: ZWaveAttributes;
}
export interface ZWaveAttributes {
node_id: number;
wake_up_interval?: number;
}
export interface ZWaveMigrationConfig {
usb_path: string;
network_key: string;
}
export const ZWAVE_NETWORK_STATE_STOPPED = 0;
export const ZWAVE_NETWORK_STATE_FAILED = 1;
export const ZWAVE_NETWORK_STATE_STARTED = 5;
export const ZWAVE_NETWORK_STATE_AWAKED = 7;
export const ZWAVE_NETWORK_STATE_READY = 10;
export const fetchNetworkStatus = (
hass: HomeAssistant
): Promise<ZWaveNetworkStatus> =>
hass.callWS({
type: "zwave/network_status",
});
export const startZwaveJsConfigFlow = (
hass: HomeAssistant
): Promise<{ flow_id: string }> =>
hass.callWS({
type: "zwave/start_zwave_js_config_flow",
});
export const fetchMigrationConfig = (
hass: HomeAssistant
): Promise<ZWaveMigrationConfig> =>
hass.callWS({
type: "zwave/get_migration_config",
});
export const fetchValues = (hass: HomeAssistant, nodeId: number) =>
hass.callApi<ZWaveValue[]>("GET", `zwave/values/${nodeId}`);
export const fetchNodeConfig = (hass: HomeAssistant, nodeId: number) =>
hass.callApi<ZWaveConfigItem[]>("GET", `zwave/config/${nodeId}`);

View File

@@ -46,6 +46,7 @@ import "./step-flow-loading";
import "./step-flow-pick-flow";
import "./step-flow-pick-handler";
import "./step-flow-progress";
import "./step-flow-menu";
let instance = 0;
@@ -117,13 +118,17 @@ class DataEntryFlowDialog extends LitElement {
);
} catch (err: any) {
this.closeDialog();
let message = err.message || err.body || "Unknown error";
if (typeof message !== "string") {
message = JSON.stringify(message);
}
showAlertDialog(this, {
title: this.hass.localize(
"ui.panel.config.integrations.config_flow.error"
),
text: `${this.hass.localize(
"ui.panel.config.integrations.config_flow.could_not_load"
)}: ${err.message || err.body}`,
)}: ${message}`,
});
return;
}
@@ -288,6 +293,14 @@ class DataEntryFlowDialog extends LitElement {
.hass=${this.hass}
></step-flow-progress>
`
: this._step.type === "menu"
? html`
<step-flow-menu
.flowConfig=${this._params.flowConfig}
.step=${this._step}
.hass=${this.hass}
></step-flow-menu>
`
: this._devices === undefined || this._areas === undefined
? // When it's a create entry result, we will fetch device & area registry
html`
@@ -373,13 +386,20 @@ class DataEntryFlowDialog extends LitElement {
step = await this._params!.flowConfig.createFlow(this.hass, handler);
} catch (err: any) {
this.closeDialog();
const message =
err?.status_code === 404
? this.hass.localize(
"ui.panel.config.integrations.config_flow.no_config_flow"
)
: `${this.hass.localize(
"ui.panel.config.integrations.config_flow.could_not_load"
)}: ${err?.body?.message || err?.message}`;
showAlertDialog(this, {
title: this.hass.localize(
"ui.panel.config.integrations.config_flow.error"
),
text: `${this.hass.localize(
"ui.panel.config.integrations.config_flow.could_not_load"
)}: ${err.message || err.body}`,
text: message,
});
return;
} finally {
@@ -410,7 +430,7 @@ class DataEntryFlowDialog extends LitElement {
title: this.hass.localize(
"ui.panel.config.integrations.config_flow.error"
),
text: err.message || err.body,
text: err?.body?.message,
});
return;
} finally {

View File

@@ -181,6 +181,21 @@ export const showConfigFlowDialog = (
: "";
},
renderMenuHeader(hass, step) {
return (
hass.localize(
`component.${step.handler}.config.step.${step.step_id}.title`
) || hass.localize(`component.${step.handler}.title`)
);
},
renderMenuOption(hass, step, option) {
return hass.localize(
`component.${step.handler}.config.step.${step.step_id}.menu_options.${option}`,
step.description_placeholders
);
},
renderLoadingDescription(hass, reason, handler, step) {
if (!["loading_flow", "loading_step"].includes(reason)) {
return "";

View File

@@ -7,6 +7,7 @@ import {
DataEntryFlowStepCreateEntry,
DataEntryFlowStepExternal,
DataEntryFlowStepForm,
DataEntryFlowStepMenu,
DataEntryFlowStepProgress,
} from "../../data/data_entry_flow";
import { HomeAssistant } from "../../types";
@@ -80,6 +81,14 @@ export interface FlowConfig {
step: DataEntryFlowStepProgress
): TemplateResult | "";
renderMenuHeader(hass: HomeAssistant, step: DataEntryFlowStepMenu): string;
renderMenuOption(
hass: HomeAssistant,
step: DataEntryFlowStepMenu,
option: string
): string;
renderLoadingDescription(
hass: HomeAssistant,
loadingReason: LoadingReason,

View File

@@ -134,6 +134,21 @@ export const showOptionsFlowDialog = (
: "";
},
renderMenuHeader(hass, step) {
return (
hass.localize(
`component.${step.handler}.option.step.${step.step_id}.title`
) || hass.localize(`component.${step.handler}.title`)
);
},
renderMenuOption(hass, step, option) {
return hass.localize(
`component.${step.handler}.options.step.${step.step_id}.menu_options.${option}`,
step.description_placeholders
);
},
renderLoadingDescription(hass, reason) {
return (
hass.localize(`component.${configEntry.domain}.options.loading`) ||

View File

@@ -0,0 +1,80 @@
import "@material/mwc-list/mwc-list-item";
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import type { DataEntryFlowStepMenu } from "../../data/data_entry_flow";
import type { HomeAssistant } from "../../types";
import type { FlowConfig } from "./show-dialog-data-entry-flow";
import "../../components/ha-icon-next";
import { configFlowContentStyles } from "./styles";
import { fireEvent } from "../../common/dom/fire_event";
@customElement("step-flow-menu")
class StepFlowMenu extends LitElement {
@property({ attribute: false }) public flowConfig!: FlowConfig;
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public step!: DataEntryFlowStepMenu;
protected render(): TemplateResult {
let options: string[];
let translations: Record<string, string>;
if (Array.isArray(this.step.menu_options)) {
options = this.step.menu_options;
translations = {};
for (const option of options) {
translations[option] = this.flowConfig.renderMenuOption(
this.hass,
this.step,
option
);
}
} else {
options = Object.keys(this.step.menu_options);
translations = this.step.menu_options;
}
return html`
<h2>${this.flowConfig.renderMenuHeader(this.hass, this.step)}</h2>
<div class="options">
${options.map(
(option) => html`
<mwc-list-item hasMeta .step=${option} @click=${this._handleStep}>
<span>${translations[option]}</span>
<ha-icon-next slot="meta"></ha-icon-next>
</mwc-list-item>
`
)}
</div>
`;
}
private _handleStep(ev) {
fireEvent(this, "flow-update", {
stepPromise: this.flowConfig.handleFlowStep(
this.hass,
this.step.flow_id,
{
next_step_id: ev.currentTarget.step,
}
),
});
}
static styles = [
configFlowContentStyles,
css`
.options {
margin-top: 20px;
margin-bottom: 8px;
}
`,
];
}
declare global {
interface HTMLElementTagNameMap {
"step-flow-menu": StepFlowMenu;
}
}

View File

@@ -15,7 +15,7 @@ import memoizeOne from "memoize-one";
import { isComponentLoaded } from "../../common/config/is_component_loaded";
import { fireEvent } from "../../common/dom/fire_event";
import { navigate } from "../../common/navigate";
import "../../common/search/search-input";
import "../../components/search-input";
import { caseInsensitiveStringCompare } from "../../common/string/compare";
import { LocalizeFunc } from "../../common/translations/localize";
import "../../components/ha-icon-next";

View File

@@ -12,7 +12,7 @@ import {
import type { HomeAssistant } from "../../../types";
const BUTTONS = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "", "0", "clear"];
const ARM_ACTIONS = ["arm_away", "arm_home"];
const ARM_ACTIONS = ["arm_home", "arm_away"];
const DISARM_ACTIONS = ["disarm"];
@customElement("more-info-alarm_control_panel")

View File

@@ -1,3 +1,4 @@
import "@material/mwc-list/mwc-list-item";
import {
css,
CSSResultGroup,
@@ -12,6 +13,7 @@ import { fireEvent } from "../../../common/dom/fire_event";
import { stopPropagation } from "../../../common/dom/stop_propagation";
import { supportsFeature } from "../../../common/entity/supports-feature";
import { computeRTLDirection } from "../../../common/util/compute_rtl";
import "../../../components/ha-select";
import "../../../components/ha-slider";
import "../../../components/ha-switch";
import {
@@ -19,8 +21,6 @@ import {
HUMIDIFIER_SUPPORT_MODES,
} from "../../../data/humidifier";
import { HomeAssistant } from "../../../types";
import "@material/mwc-list/mwc-list";
import "@material/mwc-list/mwc-list-item";
class MoreInfoHumidifier extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -67,26 +67,24 @@ class MoreInfoHumidifier extends LitElement {
${supportModes
? html`
<div class="container-modes">
<mwc-list
.label=${hass.localize("ui.card.humidifier.mode")}
.value=${stateObj.attributes.mode}
fixedMenuPosition
naturalMenuWidth
@selected=${this._handleModeChanged}
@closed=${stopPropagation}
>
${stateObj.attributes.available_modes!.map(
(mode) => html`
<mwc-list-item .value=${mode}>
${hass.localize(
`state_attributes.humidifier.mode.${mode}`
) || mode}
</mwc-list-item>
`
)}
</mwc-list>
</div>
<ha-select
.label=${hass.localize("ui.card.humidifier.mode")}
.value=${stateObj.attributes.mode}
fixedMenuPosition
naturalMenuWidth
@selected=${this._handleModeChanged}
@closed=${stopPropagation}
>
${stateObj.attributes.available_modes!.map(
(mode) => html`
<mwc-list-item .value=${mode}>
${hass.localize(
`state_attributes.humidifier.mode.${mode}`
) || mode}
</mwc-list-item>
`
)}
</ha-select>
`
: ""}
</div>

View File

@@ -4,7 +4,10 @@ import { customElement, property } from "lit/decorators";
import "../../../components/ha-date-input";
import "../../../components/ha-time-input";
import { UNAVAILABLE_STATES, UNKNOWN } from "../../../data/entity";
import { setInputDateTimeValue } from "../../../data/input_datetime";
import {
setInputDateTimeValue,
stateToIsoDateString,
} from "../../../data/input_datetime";
import type { HomeAssistant } from "../../../types";
@customElement("more-info-input_datetime")
@@ -24,7 +27,7 @@ class MoreInfoInputDatetime extends LitElement {
? html`
<ha-date-input
.locale=${this.hass.locale}
.value=${`${this.stateObj.attributes.year}-${this.stateObj.attributes.month}-${this.stateObj.attributes.day}`}
.value=${stateToIsoDateString(this.stateObj)}
.disabled=${UNAVAILABLE_STATES.includes(this.stateObj.state)}
@value-changed=${this._dateChanged}
>

View File

@@ -50,42 +50,40 @@ class MoreInfoMediaPlayer extends LitElement {
const controls = computeMediaControls(stateObj);
return html`
${!controls
? ""
: html`
<div class="controls">
<div class="basic-controls">
${controls!.map(
(control) => html`
<ha-icon-button
action=${control.action}
@click=${this._handleClick}
.path=${control.icon}
.label=${this.hass.localize(
`ui.card.media_player.${control.action}`
)}
>
</ha-icon-button>
`
<div class="controls">
<div class="basic-controls">
${!controls
? ""
: controls.map(
(control) => html`
<ha-icon-button
action=${control.action}
@click=${this._handleClick}
.path=${control.icon}
.label=${this.hass.localize(
`ui.card.media_player.${control.action}`
)}
>
</ha-icon-button>
`
)}
</div>
${supportsFeature(stateObj, SUPPORT_BROWSE_MEDIA)
? html`
<mwc-button
.label=${this.hass.localize(
"ui.card.media_player.browse_media"
)}
</div>
${supportsFeature(stateObj, SUPPORT_BROWSE_MEDIA)
? html`
<mwc-button
.label=${this.hass.localize(
"ui.card.media_player.browse_media"
)}
@click=${this._showBrowseMedia}
>
<ha-svg-icon
.path=${mdiPlayBoxMultiple}
slot="icon"
></ha-svg-icon>
</mwc-button>
`
: ""}
</div>
`}
@click=${this._showBrowseMedia}
>
<ha-svg-icon
.path=${mdiPlayBoxMultiple}
slot="icon"
></ha-svg-icon>
</mwc-button>
`
: ""}
</div>
${(supportsFeature(stateObj, SUPPORT_VOLUME_SET) ||
supportsFeature(stateObj, SUPPORT_VOLUME_BUTTONS)) &&
![UNAVAILABLE, UNKNOWN, "off"].includes(stateObj.state)

View File

@@ -86,11 +86,11 @@ export class QuickBar extends LitElement {
@state() private _search = "";
@state() private _opened = false;
@state() private _open = false;
@state() private _commandMode = false;
@state() private _done = false;
@state() private _opened = false;
@state() private _narrow = false;
@@ -109,12 +109,12 @@ export class QuickBar extends LitElement {
"all and (max-width: 450px), all and (max-height: 500px)"
).matches;
this._initializeItemsIfNeeded();
this._opened = true;
this._open = true;
}
public closeDialog() {
this._open = false;
this._opened = false;
this._done = false;
this._focusSet = false;
this._filter = "";
this._search = "";
@@ -133,7 +133,7 @@ export class QuickBar extends LitElement {
);
protected render() {
if (!this._opened) {
if (!this._open) {
return html``;
}
@@ -162,7 +162,7 @@ export class QuickBar extends LitElement {
"ui.dialogs.quick-bar.filter_placeholder"
)}
.value=${this._commandMode ? `>${this._search}` : this._search}
.icon=${true}
icon
.iconTrailing=${this._search !== undefined || this._narrow}
@input=${this._handleSearchChange}
@keydown=${this._handleInputKeyDown}
@@ -218,24 +218,26 @@ export class QuickBar extends LitElement {
`
: html`
<mwc-list>
<lit-virtualizer
scroller
@keydown=${this._handleListItemKeyDown}
@rangechange=${this._handleRangeChanged}
@click=${this._handleItemClick}
class="ha-scrollbar"
style=${styleMap({
height: this._narrow
? "calc(100vh - 56px)"
: `${Math.min(
items.length * (this._commandMode ? 56 : 72) + 26,
this._done ? 500 : 0
)}px`,
})}
.items=${items}
.renderItem=${this._renderItem}
>
</lit-virtualizer>
${this._opened
? html`<lit-virtualizer
scroller
@keydown=${this._handleListItemKeyDown}
@rangechange=${this._handleRangeChanged}
@click=${this._handleItemClick}
class="ha-scrollbar"
style=${styleMap({
height: this._narrow
? "calc(100vh - 56px)"
: `${Math.min(
items.length * (this._commandMode ? 56 : 72) + 26,
500
)}px`,
})}
.items=${items}
.renderItem=${this._renderItem}
>
</lit-virtualizer>`
: ""}
</mwc-list>
`}
${this._hint ? html`<div class="hint">${this._hint}</div>` : ""}
@@ -252,9 +254,7 @@ export class QuickBar extends LitElement {
}
private _handleOpened() {
this.updateComplete.then(() => {
this._done = true;
});
this._opened = true;
}
private async _handleRangeChanged(e) {
@@ -454,9 +454,10 @@ export class QuickBar extends LitElement {
}
private _handleItemClick(ev) {
const listItem = ev.target.closest("mwc-list-item");
this.processItemAndCloseDialog(
(ev.target as any).item,
Number((ev.target as HTMLElement).getAttribute("index"))
listItem.item,
Number(listItem.getAttribute("index"))
);
}

View File

@@ -1,7 +1,6 @@
/* eslint-disable lit/prefer-static-styles */
import "@material/mwc-button/mwc-button";
import { mdiMicrophone } from "@mdi/js";
import "@polymer/paper-input/paper-input";
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
import {
css,
CSSResultGroup,
@@ -10,12 +9,16 @@ import {
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property, state, query } from "lit/decorators";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { fireEvent } from "../../common/dom/fire_event";
import { SpeechRecognition } from "../../common/dom/speech-recognition";
import { uid } from "../../common/util/uid";
import "../../components/ha-dialog";
import type { HaDialog } from "../../components/ha-dialog";
import "../../components/ha-icon-button";
import "../../components/ha-textfield";
import type { HaTextField } from "../../components/ha-textfield";
import {
AgentInfo,
getAgentInfo,
@@ -24,9 +27,6 @@ import {
} from "../../data/conversation";
import { haStyleDialog } from "../../resources/styles";
import type { HomeAssistant } from "../../types";
import "../../components/ha-dialog";
import type { HaDialog } from "../../components/ha-dialog";
import "@material/mwc-button/mwc-button";
interface Message {
who: string;
@@ -127,18 +127,19 @@ export class HaVoiceCommandDialog extends LitElement {
: ""}
</div>
<div class="input" slot="primaryAction">
<paper-input
<ha-textfield
@keyup=${this._handleKeyUp}
.label=${this.hass.localize(
`ui.dialogs.voice_command.${
SpeechRecognition ? "label_voice" : "label"
}`
)}
autofocus
dialogInitialFocus
iconTrailing
>
${SpeechRecognition
? html`
<span suffix="" slot="suffix">
<span slot="trailingIcon">
${this.results
? html`
<div class="bouncer">
@@ -155,7 +156,7 @@ export class HaVoiceCommandDialog extends LitElement {
</span>
`
: ""}
</paper-input>
</ha-textfield>
${this._agentInfo && this._agentInfo.attribution
? html`
<a
@@ -195,7 +196,7 @@ export class HaVoiceCommandDialog extends LitElement {
}
private _handleKeyUp(ev: KeyboardEvent) {
const input = ev.target as PaperInputElement;
const input = ev.target as HaTextField;
if (ev.keyCode === 13 && input.value) {
this._processText(input.value);
input.value = "";
@@ -327,6 +328,7 @@ export class HaVoiceCommandDialog extends LitElement {
css`
ha-icon-button {
color: var(--secondary-text-color);
margin-right: -24px;
}
ha-icon-button[active] {
@@ -338,7 +340,9 @@ export class HaVoiceCommandDialog extends LitElement {
--secondary-action-button-flex: 0;
--mdc-dialog-max-width: 450px;
}
ha-textfield {
display: block;
}
a.button {
text-decoration: none;
}
@@ -406,7 +410,6 @@ export class HaVoiceCommandDialog extends LitElement {
width: 48px;
height: 48px;
position: absolute;
top: 0;
}
.double-bounce1,
.double-bounce2 {

View File

@@ -300,7 +300,9 @@ export const provideHass = (
applyThemesOnElement(
document.documentElement,
themes,
selectedTheme!.theme
selectedTheme!.theme,
undefined,
true
);
},

View File

@@ -101,7 +101,8 @@ class SupervisorErrorScreen extends LitElement {
this.parentElement,
this.hass.themes,
themeName,
themeSettings
themeSettings,
true
);
}

View File

@@ -13,6 +13,9 @@ export const SubscribeMixin = <T extends Constructor<ReactiveElement>>(
class SubscribeClass extends superClass {
@property({ attribute: false }) public hass?: HomeAssistant;
// we wait with subscribing till these properties are set on the host element
protected hassSubscribeRequiredHostProps?: string[];
private __unsubs?: Array<UnsubscribeFunc | Promise<UnsubscribeFunc>>;
public connectedCallback() {
@@ -39,6 +42,16 @@ export const SubscribeMixin = <T extends Constructor<ReactiveElement>>(
super.updated(changedProps);
if (changedProps.has("hass")) {
this.__checkSubscribed();
return;
}
if (!this.hassSubscribeRequiredHostProps) {
return;
}
for (const key of changedProps.keys()) {
if (this.hassSubscribeRequiredHostProps.includes(key as string)) {
this.__checkSubscribed();
return;
}
}
}
@@ -52,7 +65,10 @@ export const SubscribeMixin = <T extends Constructor<ReactiveElement>>(
if (
this.__unsubs !== undefined ||
!(this as unknown as Element).isConnected ||
this.hass === undefined
this.hass === undefined ||
this.hassSubscribeRequiredHostProps?.some(
(prop) => this[prop] === undefined
)
) {
return;
}

View File

@@ -133,13 +133,19 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
import("./particles");
}
if (matchMedia("(prefers-color-scheme: dark)").matches) {
applyThemesOnElement(document.documentElement, {
default_theme: "default",
default_dark_theme: null,
themes: {},
darkMode: true,
theme: "default",
});
applyThemesOnElement(
document.documentElement,
{
default_theme: "default",
default_dark_theme: null,
themes: {},
darkMode: true,
theme: "default",
},
undefined,
undefined,
true
);
}
}

View File

@@ -16,10 +16,16 @@ import "../../../../components/ha-icon-button";
import "../../../../components/ha-select";
import type { HaSelect } from "../../../../components/ha-select";
import type { HaYamlEditor } from "../../../../components/ha-yaml-editor";
import { validateConfig } from "../../../../data/config";
import { Action, getActionType } from "../../../../data/script";
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
import { callExecuteScript } from "../../../../data/service";
import {
showAlertDialog,
showConfirmationDialog,
} from "../../../../dialogs/generic/show-dialog-box";
import { haStyle } from "../../../../resources/styles";
import type { HomeAssistant } from "../../../../types";
import { showToast } from "../../../../util/toast";
import "./types/ha-automation-action-activate_scene";
import "./types/ha-automation-action-choose";
import "./types/ha-automation-action-condition";
@@ -180,6 +186,11 @@ export default class HaAutomationActionRow extends LitElement {
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<mwc-list-item>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.run_action"
)}
</mwc-list-item>
<mwc-list-item .disabled=${!this._uiModeAvailable}>
${yamlMode
? this.hass.localize(
@@ -290,17 +301,54 @@ export default class HaAutomationActionRow extends LitElement {
private _handleAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:
this._switchYamlMode();
this._runAction();
break;
case 1:
fireEvent(this, "duplicate");
this._switchYamlMode();
break;
case 2:
fireEvent(this, "duplicate");
break;
case 3:
this._onDelete();
break;
}
}
private async _runAction() {
const validated = await validateConfig(this.hass, {
action: this.action,
});
if (!validated.action.valid) {
showAlertDialog(this, {
title: this.hass.localize(
"ui.panel.config.automation.editor.actions.invalid_action"
),
text: validated.action.error,
});
return;
}
try {
await callExecuteScript(this.hass, this.action);
} catch (err: any) {
showAlertDialog(this, {
title: this.hass.localize(
"ui.panel.config.automation.editor.actions.run_action_error"
),
text: err.message || err,
});
return;
}
showToast(this, {
message: this.hass.localize(
"ui.panel.config.automation.editor.actions.run_action_success"
),
});
}
private _onDelete() {
showConfirmationDialog(this, {
text: this.hass.localize(

View File

@@ -145,7 +145,7 @@ export class HaDeviceAction extends LitElement {
static styles = css`
ha-device-picker {
display: block;
margin-bottom: 24px;
margin-bottom: 16px;
}
ha-device-action-picker {
display: block;

View File

@@ -50,7 +50,7 @@ export class HaEventAction extends LitElement implements ActionElement {
<ha-yaml-editor
.hass=${this.hass}
.label=${this.hass.localize(
"ui.panel.config.automation.editor.actions.type.event.service_data"
"ui.panel.config.automation.editor.actions.type.event.event_data"
)}
.name=${"event_data"}
.defaultValue=${event_data}

View File

@@ -162,8 +162,8 @@ export class HaRepeatAction extends LitElement implements ActionElement {
return [
haStyle,
css`
ha-select {
margin-top: 8px;
ha-textfield {
margin-top: 16px;
}
`,
];

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