mirror of
				https://github.com/home-assistant/frontend.git
				synced 2025-11-04 08:29:52 +00:00 
			
		
		
		
	Compare commits
	
		
			336 Commits
		
	
	
		
			20220203.0
			...
			Button-Edi
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					14de223ffc | ||
| 
						 | 
					1290336cc5 | ||
| 
						 | 
					abaf5dd0f2 | ||
| 
						 | 
					9c1d1cb6f6 | ||
| 
						 | 
					470225abde | ||
| 
						 | 
					ee230b86c1 | ||
| 
						 | 
					f927fc64a9 | ||
| 
						 | 
					03677c33f7 | ||
| 
						 | 
					bc36a206da | ||
| 
						 | 
					af06ab1e2d | ||
| 
						 | 
					3e2135a485 | ||
| 
						 | 
					2e7f8fb46f | ||
| 
						 | 
					102568c4bd | ||
| 
						 | 
					4fcdae842e | ||
| 
						 | 
					ea19740f5a | ||
| 
						 | 
					3e0942b631 | ||
| 
						 | 
					0261cea796 | ||
| 
						 | 
					5247b2813f | ||
| 
						 | 
					8a5090684e | ||
| 
						 | 
					1784ba5e68 | ||
| 
						 | 
					4fbe9a7b10 | ||
| 
						 | 
					1ca9c7838a | ||
| 
						 | 
					4fc2c3ef05 | ||
| 
						 | 
					73ff8e28a8 | ||
| 
						 | 
					dde1c5e03c | ||
| 
						 | 
					01eed22592 | ||
| 
						 | 
					94ebb63589 | ||
| 
						 | 
					29119db5ce | ||
| 
						 | 
					9908162ac2 | ||
| 
						 | 
					1e929ae78a | ||
| 
						 | 
					ab5df0fe6e | ||
| 
						 | 
					d5010dda9e | ||
| 
						 | 
					4ac097f32b | ||
| 
						 | 
					5d3d15072f | ||
| 
						 | 
					5c53bc4225 | ||
| 
						 | 
					d5a307f8f4 | ||
| 
						 | 
					a27dd1e7f1 | ||
| 
						 | 
					c86ed1fb3e | ||
| 
						 | 
					7fa7a48072 | ||
| 
						 | 
					4e0fc8ee08 | ||
| 
						 | 
					5f6490e54e | ||
| 
						 | 
					db78b046a2 | ||
| 
						 | 
					c37fe1e7ff | ||
| 
						 | 
					f1ec479d41 | ||
| 
						 | 
					e01cb3ca82 | ||
| 
						 | 
					b8d3c68a7a | ||
| 
						 | 
					641003bb2a | ||
| 
						 | 
					3358fc2b18 | ||
| 
						 | 
					dcf50e055b | ||
| 
						 | 
					1fa04baa16 | ||
| 
						 | 
					84ffa2369a | ||
| 
						 | 
					cc27ddb362 | ||
| 
						 | 
					c4dc6bfb0d | ||
| 
						 | 
					4fbcc30a37 | ||
| 
						 | 
					4916527e5f | ||
| 
						 | 
					fad8a27232 | ||
| 
						 | 
					a993d3a753 | ||
| 
						 | 
					5dfe17a43a | ||
| 
						 | 
					9b6c935ffb | ||
| 
						 | 
					f4e28da0a3 | ||
| 
						 | 
					294a69d7e4 | ||
| 
						 | 
					f89b8cffcf | ||
| 
						 | 
					99fd3a1b6f | ||
| 
						 | 
					246e426182 | ||
| 
						 | 
					9f1e9b43fe | ||
| 
						 | 
					8301ae262c | ||
| 
						 | 
					d968fe41ee | ||
| 
						 | 
					db830e9014 | ||
| 
						 | 
					fc6b594a27 | ||
| 
						 | 
					86dbf99ebe | ||
| 
						 | 
					68e7ce1883 | ||
| 
						 | 
					e9003ac35e | ||
| 
						 | 
					1dd5214b42 | ||
| 
						 | 
					96738350bb | ||
| 
						 | 
					5bdecf57cf | ||
| 
						 | 
					ec12282f8c | ||
| 
						 | 
					552dbca201 | ||
| 
						 | 
					0bbc0ebb3c | ||
| 
						 | 
					ac7acc5802 | ||
| 
						 | 
					64e1d160d1 | ||
| 
						 | 
					8e51878b6d | ||
| 
						 | 
					7c94ced303 | ||
| 
						 | 
					a040e1d5e0 | ||
| 
						 | 
					87c7407857 | ||
| 
						 | 
					d0d0c44ec7 | ||
| 
						 | 
					4cdff3faea | ||
| 
						 | 
					0dac10aa23 | ||
| 
						 | 
					4b8b14a69d | ||
| 
						 | 
					9d28df31bd | ||
| 
						 | 
					8258641443 | ||
| 
						 | 
					dfcb0f6ba0 | ||
| 
						 | 
					2e10eb04b6 | ||
| 
						 | 
					b4b52d3872 | ||
| 
						 | 
					3873203721 | ||
| 
						 | 
					ccb91e0b49 | ||
| 
						 | 
					bd20c15a55 | ||
| 
						 | 
					0936fd9ae4 | ||
| 
						 | 
					adefc7a4e2 | ||
| 
						 | 
					8f8017ecff | ||
| 
						 | 
					604b79696e | ||
| 
						 | 
					8c445f6409 | ||
| 
						 | 
					797c871137 | ||
| 
						 | 
					24829bd903 | ||
| 
						 | 
					add92a559d | ||
| 
						 | 
					7f086c0900 | ||
| 
						 | 
					17018c0f26 | ||
| 
						 | 
					cd6a478130 | ||
| 
						 | 
					4f6d7ca5c9 | ||
| 
						 | 
					c2994343b4 | ||
| 
						 | 
					e5f77c35d4 | ||
| 
						 | 
					a9e5a5dd44 | ||
| 
						 | 
					1159798b8d | ||
| 
						 | 
					437de42c55 | ||
| 
						 | 
					89e0bb3f16 | ||
| 
						 | 
					28c9631b6c | ||
| 
						 | 
					8882624618 | ||
| 
						 | 
					a769f84755 | ||
| 
						 | 
					7abf9c2473 | ||
| 
						 | 
					298296a81f | ||
| 
						 | 
					6907fa5c8e | ||
| 
						 | 
					546461b70f | ||
| 
						 | 
					4031009c26 | ||
| 
						 | 
					91e4557625 | ||
| 
						 | 
					f0c4b92dbb | ||
| 
						 | 
					ffac3d055e | ||
| 
						 | 
					04ae8c9d14 | ||
| 
						 | 
					0158610d42 | ||
| 
						 | 
					5ab6121581 | ||
| 
						 | 
					3d9c31aef9 | ||
| 
						 | 
					acfeea5c92 | ||
| 
						 | 
					75e8e17073 | ||
| 
						 | 
					976fd4b32d | ||
| 
						 | 
					49beafbe5f | ||
| 
						 | 
					151f8d5524 | ||
| 
						 | 
					48355aa98e | ||
| 
						 | 
					fc31929f41 | ||
| 
						 | 
					b7c149fcc1 | ||
| 
						 | 
					02d058561b | ||
| 
						 | 
					4e57fb1ec1 | ||
| 
						 | 
					30f79c5a46 | ||
| 
						 | 
					30f7252d84 | ||
| 
						 | 
					8af795a7ce | ||
| 
						 | 
					8576eeae41 | ||
| 
						 | 
					cd740ed135 | ||
| 
						 | 
					892f774792 | ||
| 
						 | 
					aa504fe1f8 | ||
| 
						 | 
					be491451d5 | ||
| 
						 | 
					bad184210d | ||
| 
						 | 
					a43b3b64b3 | ||
| 
						 | 
					aa831a9adf | ||
| 
						 | 
					43d4f55392 | ||
| 
						 | 
					130c66fb24 | ||
| 
						 | 
					684c232c8c | ||
| 
						 | 
					09f8f816d1 | ||
| 
						 | 
					1719d062b3 | ||
| 
						 | 
					87290c4330 | ||
| 
						 | 
					fec0dc0032 | ||
| 
						 | 
					70ca27c8c9 | ||
| 
						 | 
					9ae1f01ad6 | ||
| 
						 | 
					0113cc3cf6 | ||
| 
						 | 
					2a98ace0b3 | ||
| 
						 | 
					5f69a4c165 | ||
| 
						 | 
					8db22d4f88 | ||
| 
						 | 
					3204dbfc4d | ||
| 
						 | 
					430b47fc4a | ||
| 
						 | 
					5d8b3227f3 | ||
| 
						 | 
					b341ee9d38 | ||
| 
						 | 
					e6dbbc31a8 | ||
| 
						 | 
					0010bf5a8f | ||
| 
						 | 
					6e2e80a297 | ||
| 
						 | 
					aa9ff01030 | ||
| 
						 | 
					7f8ecf57d7 | ||
| 
						 | 
					6be6755f6f | ||
| 
						 | 
					64459a06c6 | ||
| 
						 | 
					df35496c6e | ||
| 
						 | 
					aa988c758d | ||
| 
						 | 
					1dd1095d19 | ||
| 
						 | 
					7e68393c84 | ||
| 
						 | 
					540c06c9f7 | ||
| 
						 | 
					f633cc2b0d | ||
| 
						 | 
					1baaf76471 | ||
| 
						 | 
					8263e299a8 | ||
| 
						 | 
					ebd6a26554 | ||
| 
						 | 
					5335772a7a | ||
| 
						 | 
					f5b5414461 | ||
| 
						 | 
					1e6f402d0f | ||
| 
						 | 
					ed9d886009 | ||
| 
						 | 
					940f5c0002 | ||
| 
						 | 
					15d1b8b2ac | ||
| 
						 | 
					73855e6f99 | ||
| 
						 | 
					d230541256 | ||
| 
						 | 
					b1f369a355 | ||
| 
						 | 
					e6d1e86c64 | ||
| 
						 | 
					eb1f94c370 | ||
| 
						 | 
					27750b8b5d | ||
| 
						 | 
					564a725284 | ||
| 
						 | 
					a5ee610af5 | ||
| 
						 | 
					eaf97ee7f5 | ||
| 
						 | 
					a14d75deec | ||
| 
						 | 
					72b5721c88 | ||
| 
						 | 
					94b4b818aa | ||
| 
						 | 
					98699b640a | ||
| 
						 | 
					decc0d3e0d | ||
| 
						 | 
					2281f5bafa | ||
| 
						 | 
					6cac7eeff0 | ||
| 
						 | 
					794bc161c8 | ||
| 
						 | 
					28cd9b6408 | ||
| 
						 | 
					9b4c6eea63 | ||
| 
						 | 
					afe044d152 | ||
| 
						 | 
					dc2038916b | ||
| 
						 | 
					cf8e2a6d02 | ||
| 
						 | 
					3269b2878b | ||
| 
						 | 
					29e1b7b452 | ||
| 
						 | 
					3d6d07e5bd | ||
| 
						 | 
					7bac41fe41 | ||
| 
						 | 
					6e4b027575 | ||
| 
						 | 
					728c391b5d | ||
| 
						 | 
					8999ca2ea0 | ||
| 
						 | 
					4fc0617289 | ||
| 
						 | 
					494cc3a569 | ||
| 
						 | 
					cc177ef911 | ||
| 
						 | 
					41ec65ef3d | ||
| 
						 | 
					79e1e195a0 | ||
| 
						 | 
					dfbf7fb436 | ||
| 
						 | 
					f37a5fa021 | ||
| 
						 | 
					5e2fcf928c | ||
| 
						 | 
					bc6ef7780c | ||
| 
						 | 
					b29563a254 | ||
| 
						 | 
					fe8a1152c4 | ||
| 
						 | 
					26689a0a85 | ||
| 
						 | 
					4f6a241817 | ||
| 
						 | 
					eae7e82127 | ||
| 
						 | 
					9500ac498c | ||
| 
						 | 
					5c5459bcaf | ||
| 
						 | 
					246724c59e | ||
| 
						 | 
					8f5c9295d3 | ||
| 
						 | 
					0abafff4c9 | ||
| 
						 | 
					f88ce269a7 | ||
| 
						 | 
					0dc56d7983 | ||
| 
						 | 
					cbd0ef6b65 | ||
| 
						 | 
					f923228078 | ||
| 
						 | 
					b55c7edd70 | ||
| 
						 | 
					bfb90632ac | ||
| 
						 | 
					3a664d45a9 | ||
| 
						 | 
					53607fe8c6 | ||
| 
						 | 
					9dec0f8ccd | ||
| 
						 | 
					89f4fe9d20 | ||
| 
						 | 
					f43655eea5 | ||
| 
						 | 
					6563984fdd | ||
| 
						 | 
					16d8eb0be3 | ||
| 
						 | 
					965fc9bc4e | ||
| 
						 | 
					56cb958a47 | ||
| 
						 | 
					f5feb1d8aa | ||
| 
						 | 
					e95065ed08 | ||
| 
						 | 
					68a411838d | ||
| 
						 | 
					ba63ab8b7a | ||
| 
						 | 
					26d4599ef4 | ||
| 
						 | 
					d049990f04 | ||
| 
						 | 
					9c8d683a19 | ||
| 
						 | 
					901677bbdf | ||
| 
						 | 
					8bb2374b1b | ||
| 
						 | 
					520896a3c2 | ||
| 
						 | 
					92db272759 | ||
| 
						 | 
					fc654d86c6 | ||
| 
						 | 
					523afe2f6f | ||
| 
						 | 
					460b9003fc | ||
| 
						 | 
					2ac0ad1d98 | ||
| 
						 | 
					a321432175 | ||
| 
						 | 
					63c9b3f830 | ||
| 
						 | 
					806b1296b0 | ||
| 
						 | 
					7f90ffa82f | ||
| 
						 | 
					db33c38e21 | ||
| 
						 | 
					a8c1fdd21e | ||
| 
						 | 
					d86a18b80b | ||
| 
						 | 
					bef6591548 | ||
| 
						 | 
					e1c07f109c | ||
| 
						 | 
					fb66d224ae | ||
| 
						 | 
					ee1fd3e865 | ||
| 
						 | 
					a9bfea233c | ||
| 
						 | 
					35cc291118 | ||
| 
						 | 
					db7cac5782 | ||
| 
						 | 
					099fa706a0 | ||
| 
						 | 
					ed84ce9692 | ||
| 
						 | 
					9912d427f2 | ||
| 
						 | 
					76f574f875 | ||
| 
						 | 
					ac90bb7088 | ||
| 
						 | 
					ce9f83e9a2 | ||
| 
						 | 
					fca7d2c5b0 | ||
| 
						 | 
					d7a5921e7b | ||
| 
						 | 
					cefa2ee183 | ||
| 
						 | 
					0eeed85193 | ||
| 
						 | 
					fd80408de2 | ||
| 
						 | 
					467a5169c0 | ||
| 
						 | 
					b0b3222b33 | ||
| 
						 | 
					b053881cef | ||
| 
						 | 
					92a9ed7080 | ||
| 
						 | 
					830b449006 | ||
| 
						 | 
					d38a8a317e | ||
| 
						 | 
					a0aed9112c | ||
| 
						 | 
					ce3b8544b9 | ||
| 
						 | 
					134ed7d303 | ||
| 
						 | 
					dc27871189 | ||
| 
						 | 
					9c9bfa2b77 | ||
| 
						 | 
					f02dd39619 | ||
| 
						 | 
					d37d99223d | ||
| 
						 | 
					4db943c5ff | ||
| 
						 | 
					ed001fb10b | ||
| 
						 | 
					5f43715dd8 | ||
| 
						 | 
					5435218187 | ||
| 
						 | 
					4ef5f3af89 | ||
| 
						 | 
					9eea17b793 | ||
| 
						 | 
					6a51e2aaad | ||
| 
						 | 
					2d33327d88 | ||
| 
						 | 
					e9ec2da917 | ||
| 
						 | 
					09d46dac61 | ||
| 
						 | 
					236fa14ec3 | ||
| 
						 | 
					2cb37820df | ||
| 
						 | 
					869fa91ae5 | ||
| 
						 | 
					22df03427f | ||
| 
						 | 
					e72a4e4a20 | ||
| 
						 | 
					ca8d31c6bb | ||
| 
						 | 
					354ea88984 | ||
| 
						 | 
					76af6e48cd | ||
| 
						 | 
					d05f807b9d | ||
| 
						 | 
					4092f7f75d | ||
| 
						 | 
					04668ad809 | ||
| 
						 | 
					9be5a15c77 | ||
| 
						 | 
					21d86f4797 | ||
| 
						 | 
					45e6ec1ee2 | ||
| 
						 | 
					9b97faa5e3 | ||
| 
						 | 
					8730c122fd | ||
| 
						 | 
					0046252e32 | ||
| 
						 | 
					f47440083e | ||
| 
						 | 
					bfaf44f9d1 | ||
| 
						 | 
					deba6a0db4 | ||
| 
						 | 
					8d2c716fbe | 
@@ -16,6 +16,9 @@
 | 
				
			|||||||
    "runem.lit-plugin",
 | 
					    "runem.lit-plugin",
 | 
				
			||||||
    "ms-python.vscode-pylance"
 | 
					    "ms-python.vscode-pylance"
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
 | 
					  "containerEnv": {
 | 
				
			||||||
 | 
					    "WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
  "settings": {
 | 
					  "settings": {
 | 
				
			||||||
    "terminal.integrated.shell.linux": "/bin/bash",
 | 
					    "terminal.integrated.shell.linux": "/bin/bash",
 | 
				
			||||||
    "files.eol": "\n",
 | 
					    "files.eol": "\n",
 | 
				
			||||||
 
 | 
				
			|||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -1,11 +1,10 @@
 | 
				
			|||||||
diff --git a/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js b/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js
 | 
					diff --git a/polyfillLoaders/EventTarget.js b/polyfillLoaders/EventTarget.js
 | 
				
			||||||
index d92179f7fd5315203f870a6963e871dc8ddf6c0c..362e284121b97e0fba0925225777aebc32e26b8d 100644
 | 
					index 4e18ade7ba485849f17f28c94c42f0e0e01ac387..8f34f4f646c7f7becc208fb5a546c96034fc74dc 100644
 | 
				
			||||||
--- a/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js
 | 
					--- a/polyfillLoaders/EventTarget.js
 | 
				
			||||||
+++ b/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js
 | 
					+++ b/polyfillLoaders/EventTarget.js
 | 
				
			||||||
@@ -1,14 +1,15 @@
 | 
					@@ -6,16 +6,15 @@
 | 
				
			||||||
-let _ET, ET;
 | 
					 let _ET;
 | 
				
			||||||
+let _ET;
 | 
					 let ET;
 | 
				
			||||||
+let ET;
 | 
					 | 
				
			||||||
 export default async function EventTarget() {
 | 
					 export default async function EventTarget() {
 | 
				
			||||||
-    return ET || init();
 | 
					-    return ET || init();
 | 
				
			||||||
+  return ET || init();
 | 
					+  return ET || init();
 | 
				
			||||||
@@ -27,3 +26,4 @@ index d92179f7fd5315203f870a6963e871dc8ddf6c0c..362e284121b97e0fba0925225777aebc
 | 
				
			|||||||
+  }
 | 
					+  }
 | 
				
			||||||
+  return (ET = _ET);
 | 
					+  return (ET = _ET);
 | 
				
			||||||
 }
 | 
					 }
 | 
				
			||||||
 | 
					 //# sourceMappingURL=EventTarget.js.map
 | 
				
			||||||
							
								
								
									
										631
									
								
								.yarn/releases/yarn-3.0.2.cjs
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										631
									
								
								.yarn/releases/yarn-3.0.2.cjs
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										785
									
								
								.yarn/releases/yarn-3.2.0.cjs
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										785
									
								
								.yarn/releases/yarn-3.2.0.cjs
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -6,4 +6,4 @@ plugins:
 | 
				
			|||||||
  - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
 | 
					  - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
 | 
				
			||||||
    spec: "@yarnpkg/plugin-interactive-tools"
 | 
					    spec: "@yarnpkg/plugin-interactive-tools"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
yarnPath: .yarn/releases/yarn-3.0.2.cjs
 | 
					yarnPath: .yarn/releases/yarn-3.2.0.cjs
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
This is the repository for the official [Home Assistant](https://home-assistant.io) frontend.
 | 
					This is the repository for the official [Home Assistant](https://home-assistant.io) frontend.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[](https://demo.home-assistant.io/)
 | 
					[](https://demo.home-assistant.io/)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- [View demo of Home Assistant](https://demo.home-assistant.io/)
 | 
					- [View demo of Home Assistant](https://demo.home-assistant.io/)
 | 
				
			||||||
- [More information about Home Assistant](https://home-assistant.io)
 | 
					- [More information about Home Assistant](https://home-assistant.io)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,7 +10,7 @@ module.exports.ignorePackages = ({ latestBuild }) => [
 | 
				
			|||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Files from NPM packages that we should replace with empty file
 | 
					// Files from NPM packages that we should replace with empty file
 | 
				
			||||||
module.exports.emptyPackages = ({ latestBuild }) =>
 | 
					module.exports.emptyPackages = ({ latestBuild, isHassioBuild }) =>
 | 
				
			||||||
  [
 | 
					  [
 | 
				
			||||||
    // Contains all color definitions for all material color sets.
 | 
					    // Contains all color definitions for all material color sets.
 | 
				
			||||||
    // We don't use it
 | 
					    // We don't use it
 | 
				
			||||||
@@ -28,6 +28,15 @@ module.exports.emptyPackages = ({ latestBuild }) =>
 | 
				
			|||||||
      ),
 | 
					      ),
 | 
				
			||||||
    // This polyfill is loaded in workers to support ES5, filter it out.
 | 
					    // This polyfill is loaded in workers to support ES5, filter it out.
 | 
				
			||||||
    latestBuild && require.resolve("proxy-polyfill/src/index.js"),
 | 
					    latestBuild && require.resolve("proxy-polyfill/src/index.js"),
 | 
				
			||||||
 | 
					    // Icons in supervisor conflict with icons in HA so we don't load.
 | 
				
			||||||
 | 
					    isHassioBuild &&
 | 
				
			||||||
 | 
					      require.resolve(
 | 
				
			||||||
 | 
					        path.resolve(paths.polymer_dir, "src/components/ha-icon.ts")
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    isHassioBuild &&
 | 
				
			||||||
 | 
					      require.resolve(
 | 
				
			||||||
 | 
					        path.resolve(paths.polymer_dir, "src/components/ha-icon-picker.ts")
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
  ].filter(Boolean);
 | 
					  ].filter(Boolean);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
 | 
					module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
 | 
				
			||||||
@@ -196,6 +205,7 @@ module.exports.config = {
 | 
				
			|||||||
      publicPath: publicPath(latestBuild, paths.hassio_publicPath),
 | 
					      publicPath: publicPath(latestBuild, paths.hassio_publicPath),
 | 
				
			||||||
      isProdBuild,
 | 
					      isProdBuild,
 | 
				
			||||||
      latestBuild,
 | 
					      latestBuild,
 | 
				
			||||||
 | 
					      isHassioBuild: true,
 | 
				
			||||||
      defineOverlay: {
 | 
					      defineOverlay: {
 | 
				
			||||||
        __SUPERVISOR__: true,
 | 
					        __SUPERVISOR__: true,
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,7 @@
 | 
				
			|||||||
const gulp = require("gulp");
 | 
					const gulp = require("gulp");
 | 
				
			||||||
const fs = require("fs");
 | 
					const fs = require("fs");
 | 
				
			||||||
const path = require("path");
 | 
					const path = require("path");
 | 
				
			||||||
const marked = require("marked");
 | 
					const { marked } = require("marked");
 | 
				
			||||||
const glob = require("glob");
 | 
					const glob = require("glob");
 | 
				
			||||||
const yaml = require("js-yaml");
 | 
					const yaml = require("js-yaml");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,7 @@ const source = require("vinyl-source-stream");
 | 
				
			|||||||
const vinylBuffer = require("vinyl-buffer");
 | 
					const vinylBuffer = require("vinyl-buffer");
 | 
				
			||||||
const gulp = require("gulp");
 | 
					const gulp = require("gulp");
 | 
				
			||||||
const fs = require("fs");
 | 
					const fs = require("fs");
 | 
				
			||||||
const foreach = require("gulp-foreach");
 | 
					const flatmap = require("gulp-flatmap");
 | 
				
			||||||
const merge = require("gulp-merge-json");
 | 
					const merge = require("gulp-merge-json");
 | 
				
			||||||
const rename = require("gulp-rename");
 | 
					const rename = require("gulp-rename");
 | 
				
			||||||
const transform = require("gulp-json-transform");
 | 
					const transform = require("gulp-json-transform");
 | 
				
			||||||
@@ -183,7 +183,7 @@ gulp.task("build-merged-translations", () =>
 | 
				
			|||||||
    })
 | 
					    })
 | 
				
			||||||
    .pipe(transform((data, file) => lokaliseTransform(data, data, file)))
 | 
					    .pipe(transform((data, file) => lokaliseTransform(data, data, file)))
 | 
				
			||||||
    .pipe(
 | 
					    .pipe(
 | 
				
			||||||
      foreach((stream, file) => {
 | 
					      flatmap((stream, file) => {
 | 
				
			||||||
        // For each language generate a merged json file. It begins with the master
 | 
					        // For each language generate a merged json file. It begins with the master
 | 
				
			||||||
        // translation as a failsafe for untranslated strings, and merges all parent
 | 
					        // translation as a failsafe for untranslated strings, and merges all parent
 | 
				
			||||||
        // tags into one file for each specific subtag
 | 
					        // tags into one file for each specific subtag
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,6 +30,7 @@ const createWebpackConfig = ({
 | 
				
			|||||||
  isProdBuild,
 | 
					  isProdBuild,
 | 
				
			||||||
  latestBuild,
 | 
					  latestBuild,
 | 
				
			||||||
  isStatsBuild,
 | 
					  isStatsBuild,
 | 
				
			||||||
 | 
					  isHassioBuild,
 | 
				
			||||||
  dontHash,
 | 
					  dontHash,
 | 
				
			||||||
}) => {
 | 
					}) => {
 | 
				
			||||||
  if (!dontHash) {
 | 
					  if (!dontHash) {
 | 
				
			||||||
@@ -117,7 +118,9 @@ const createWebpackConfig = ({
 | 
				
			|||||||
        },
 | 
					        },
 | 
				
			||||||
      }),
 | 
					      }),
 | 
				
			||||||
      new webpack.NormalModuleReplacementPlugin(
 | 
					      new webpack.NormalModuleReplacementPlugin(
 | 
				
			||||||
        new RegExp(bundle.emptyPackages({ latestBuild }).join("|")),
 | 
					        new RegExp(
 | 
				
			||||||
 | 
					          bundle.emptyPackages({ latestBuild, isHassioBuild }).join("|")
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
        path.resolve(paths.polymer_dir, "src/util/empty.js")
 | 
					        path.resolve(paths.polymer_dir, "src/util/empty.js")
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
      !isProdBuild && new LogStartCompilePlugin(),
 | 
					      !isProdBuild && new LogStartCompilePlugin(),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
 | 
					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 { fireEvent } from "../../../../src/common/dom/fire_event";
 | 
				
			||||||
import { LovelaceConfig } from "../../../../src/data/lovelace";
 | 
					import { LovelaceConfig } from "../../../../src/data/lovelace";
 | 
				
			||||||
import { Lovelace } from "../../../../src/panels/lovelace/types";
 | 
					import { Lovelace } from "../../../../src/panels/lovelace/types";
 | 
				
			||||||
@@ -7,6 +7,9 @@ import "../../../../src/panels/lovelace/views/hui-view";
 | 
				
			|||||||
import { HomeAssistant } from "../../../../src/types";
 | 
					import { HomeAssistant } from "../../../../src/types";
 | 
				
			||||||
import "./hc-launch-screen";
 | 
					import "./hc-launch-screen";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(window as any).loadCardHelpers = () =>
 | 
				
			||||||
 | 
					  import("../../../../src/panels/lovelace/custom-card-helpers");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@customElement("hc-lovelace")
 | 
					@customElement("hc-lovelace")
 | 
				
			||||||
class HcLovelace extends LitElement {
 | 
					class HcLovelace extends LitElement {
 | 
				
			||||||
  @property({ attribute: false }) public hass!: HomeAssistant;
 | 
					  @property({ attribute: false }) public hass!: HomeAssistant;
 | 
				
			||||||
@@ -17,6 +20,8 @@ class HcLovelace extends LitElement {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @property() public urlPath: string | null = null;
 | 
					  @property() public urlPath: string | null = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @query("hui-view") private _huiView?: HTMLElement;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  protected render(): TemplateResult {
 | 
					  protected render(): TemplateResult {
 | 
				
			||||||
    const index = this._viewIndex;
 | 
					    const index = this._viewIndex;
 | 
				
			||||||
    if (index === undefined) {
 | 
					    if (index === undefined) {
 | 
				
			||||||
@@ -75,12 +80,12 @@ class HcLovelace extends LitElement {
 | 
				
			|||||||
          this.lovelaceConfig.background;
 | 
					          this.lovelaceConfig.background;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (configBackground) {
 | 
					        if (configBackground) {
 | 
				
			||||||
          (this.shadowRoot!.querySelector(
 | 
					          this._huiView!.style.setProperty(
 | 
				
			||||||
            "hui-view"
 | 
					 | 
				
			||||||
          ) as HTMLElement)!.style.setProperty(
 | 
					 | 
				
			||||||
            "--lovelace-background",
 | 
					            "--lovelace-background",
 | 
				
			||||||
            configBackground
 | 
					            configBackground
 | 
				
			||||||
          );
 | 
					          );
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          this._huiView!.style.removeProperty("--lovelace-background");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -113,6 +118,9 @@ class HcLovelace extends LitElement {
 | 
				
			|||||||
      :host > * {
 | 
					      :host > * {
 | 
				
			||||||
        flex: 1;
 | 
					        flex: 1;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					      hui-view {
 | 
				
			||||||
 | 
					        background: var(--lovelace-background, var(--primary-background-color));
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    `;
 | 
					    `;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,3 @@
 | 
				
			|||||||
import "web-animations-js/web-animations-next-lite.min";
 | 
					 | 
				
			||||||
import "../../../src/resources/ha-style";
 | 
					import "../../../src/resources/ha-style";
 | 
				
			||||||
import "../../../src/resources/roboto";
 | 
					import "../../../src/resources/roboto";
 | 
				
			||||||
import "./layout/hc-lovelace";
 | 
					import "./layout/hc-lovelace";
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,8 +2,3 @@ import "../../src/resources/ha-style";
 | 
				
			|||||||
import "../../src/resources/roboto";
 | 
					import "../../src/resources/roboto";
 | 
				
			||||||
import "../../src/resources/safari-14-attachshadow-patch";
 | 
					import "../../src/resources/safari-14-attachshadow-patch";
 | 
				
			||||||
import "./ha-demo";
 | 
					import "./ha-demo";
 | 
				
			||||||
 | 
					 | 
				
			||||||
/* polyfill for paper-dropdown */
 | 
					 | 
				
			||||||
setTimeout(() => {
 | 
					 | 
				
			||||||
  import("web-animations-js/web-animations-next-lite.min");
 | 
					 | 
				
			||||||
}, 1000);
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										
											BIN
										
									
								
								gallery/public/images/clearspace.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								gallery/public/images/clearspace.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 44 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								gallery/public/images/logo-variants.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								gallery/public/images/logo-variants.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 35 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								gallery/public/images/logo-with-text.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								gallery/public/images/logo-with-text.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 67 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								gallery/public/images/logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								gallery/public/images/logo.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 27 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								gallery/public/images/using-our-logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								gallery/public/images/using-our-logo.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 32 KiB  | 
@@ -20,7 +20,6 @@ module.exports = [
 | 
				
			|||||||
      "editor-trigger",
 | 
					      "editor-trigger",
 | 
				
			||||||
      "editor-condition",
 | 
					      "editor-condition",
 | 
				
			||||||
      "editor-action",
 | 
					      "editor-action",
 | 
				
			||||||
      "selectors",
 | 
					 | 
				
			||||||
      "trace",
 | 
					      "trace",
 | 
				
			||||||
      "trace-timeline",
 | 
					      "trace-timeline",
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
@@ -37,6 +36,10 @@ module.exports = [
 | 
				
			|||||||
    category: "misc",
 | 
					    category: "misc",
 | 
				
			||||||
    header: "Miscelaneous",
 | 
					    header: "Miscelaneous",
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    category: "brand",
 | 
				
			||||||
 | 
					    header: "Brand",
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    category: "user-test",
 | 
					    category: "user-test",
 | 
				
			||||||
    header: "User Tests",
 | 
					    header: "User Tests",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,7 @@ import { html, LitElement, css, TemplateResult } from "lit";
 | 
				
			|||||||
import { customElement, property } from "lit/decorators";
 | 
					import { customElement, property } from "lit/decorators";
 | 
				
			||||||
import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element";
 | 
					import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element";
 | 
				
			||||||
import { fireEvent } from "../../../src/common/dom/fire_event";
 | 
					import { fireEvent } from "../../../src/common/dom/fire_event";
 | 
				
			||||||
 | 
					import "../../../src/components/ha-card";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@customElement("demo-black-white-row")
 | 
					@customElement("demo-black-white-row")
 | 
				
			||||||
class DemoBlackWhiteRow extends LitElement {
 | 
					class DemoBlackWhiteRow extends LitElement {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -78,6 +78,9 @@ class DemoCards extends LitElement {
 | 
				
			|||||||
    ha-formfield {
 | 
					    ha-formfield {
 | 
				
			||||||
      margin-right: 16px;
 | 
					      margin-right: 16px;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    #container {
 | 
				
			||||||
 | 
					      background-color: var(--primary-background-color);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  `;
 | 
					  `;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,7 +12,14 @@ class PageDescription extends HaMarkdown {
 | 
				
			|||||||
    if (!PAGES[this.page].description) {
 | 
					    if (!PAGES[this.page].description) {
 | 
				
			||||||
      return html``;
 | 
					      return html``;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    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(
 | 
					      ${until(
 | 
				
			||||||
        PAGES[this.page]
 | 
					        PAGES[this.page]
 | 
				
			||||||
          .description()
 | 
					          .description()
 | 
				
			||||||
@@ -25,9 +32,22 @@ class PageDescription extends HaMarkdown {
 | 
				
			|||||||
  static styles = [
 | 
					  static styles = [
 | 
				
			||||||
    HaMarkdown.styles,
 | 
					    HaMarkdown.styles,
 | 
				
			||||||
    css`
 | 
					    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 {
 | 
					      .root {
 | 
				
			||||||
        max-width: 800px;
 | 
					        max-width: 800px;
 | 
				
			||||||
        margin: 0 auto;
 | 
					        margin: 16px auto;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      .root > *:first-child {
 | 
					      .root > *:first-child {
 | 
				
			||||||
        margin-top: 0;
 | 
					        margin-top: 0;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,6 +5,7 @@ import { html, css, LitElement, PropertyValues } from "lit";
 | 
				
			|||||||
import { customElement, property, query } from "lit/decorators";
 | 
					import { customElement, property, query } from "lit/decorators";
 | 
				
			||||||
import "../../src/components/ha-icon-button";
 | 
					import "../../src/components/ha-icon-button";
 | 
				
			||||||
import "../../src/managers/notification-manager";
 | 
					import "../../src/managers/notification-manager";
 | 
				
			||||||
 | 
					import "../../src/components/ha-expansion-panel";
 | 
				
			||||||
import { haStyle } from "../../src/resources/styles";
 | 
					import { haStyle } from "../../src/resources/styles";
 | 
				
			||||||
import { PAGES, SIDEBAR } from "../build/import-pages";
 | 
					import { PAGES, SIDEBAR } from "../build/import-pages";
 | 
				
			||||||
import { dynamicElement } from "../../src/common/dom/dynamic-element-directive";
 | 
					import { dynamicElement } from "../../src/common/dom/dynamic-element-directive";
 | 
				
			||||||
@@ -53,10 +54,9 @@ class HaGallery extends LitElement {
 | 
				
			|||||||
      sidebar.push(
 | 
					      sidebar.push(
 | 
				
			||||||
        group.header
 | 
					        group.header
 | 
				
			||||||
          ? html`
 | 
					          ? html`
 | 
				
			||||||
              <details>
 | 
					              <ha-expansion-panel .header=${group.header}>
 | 
				
			||||||
                <summary class="section">${group.header}</summary>
 | 
					 | 
				
			||||||
                ${links}
 | 
					                ${links}
 | 
				
			||||||
              </details>
 | 
					              </ha-expansion-panel>
 | 
				
			||||||
            `
 | 
					            `
 | 
				
			||||||
          : links
 | 
					          : links
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
@@ -92,6 +92,12 @@ class HaGallery extends LitElement {
 | 
				
			|||||||
            ${dynamicElement(`demo-${this._page.replace("/", "-")}`)}
 | 
					            ${dynamicElement(`demo-${this._page.replace("/", "-")}`)}
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
          <div class="page-footer">
 | 
					          <div class="page-footer">
 | 
				
			||||||
 | 
					            <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 ||
 | 
					              ${PAGES[this._page].description ||
 | 
				
			||||||
              Object.keys(PAGES[this._page].metadata).length > 0
 | 
					              Object.keys(PAGES[this._page].metadata).length > 0
 | 
				
			||||||
                ? html`
 | 
					                ? html`
 | 
				
			||||||
@@ -115,6 +121,7 @@ class HaGallery extends LitElement {
 | 
				
			|||||||
                : ""}
 | 
					                : ""}
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
      </mwc-drawer>
 | 
					      </mwc-drawer>
 | 
				
			||||||
      <notification-manager
 | 
					      <notification-manager
 | 
				
			||||||
        .hass=${FAKE_HASS}
 | 
					        .hass=${FAKE_HASS}
 | 
				
			||||||
@@ -186,27 +193,16 @@ class HaGallery extends LitElement {
 | 
				
			|||||||
        padding: 4px;
 | 
					        padding: 4px;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      .sidebar details {
 | 
					 | 
				
			||||||
        margin-top: 1em;
 | 
					 | 
				
			||||||
        margin-left: 1em;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .sidebar summary {
 | 
					 | 
				
			||||||
        cursor: pointer;
 | 
					 | 
				
			||||||
        font-weight: bold;
 | 
					 | 
				
			||||||
        margin-bottom: 8px;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .sidebar a {
 | 
					      .sidebar a {
 | 
				
			||||||
        color: var(--primary-text-color);
 | 
					        color: var(--primary-text-color);
 | 
				
			||||||
        display: block;
 | 
					        display: block;
 | 
				
			||||||
        padding: 4px 12px;
 | 
					        padding: 12px;
 | 
				
			||||||
        text-decoration: none;
 | 
					        text-decoration: none;
 | 
				
			||||||
        position: relative;
 | 
					        position: relative;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      .sidebar a[active]::before {
 | 
					      .sidebar a[active]::before {
 | 
				
			||||||
        border-radius: 4px;
 | 
					        border-radius: 12px;
 | 
				
			||||||
        position: absolute;
 | 
					        position: absolute;
 | 
				
			||||||
        top: 0;
 | 
					        top: 0;
 | 
				
			||||||
        right: 2px;
 | 
					        right: 2px;
 | 
				
			||||||
@@ -237,14 +233,32 @@ class HaGallery extends LitElement {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      .page-footer {
 | 
					      .page-footer {
 | 
				
			||||||
        text-align: center;
 | 
					        text-align: center;
 | 
				
			||||||
        margin: 16px 0;
 | 
					        margin: 16px;
 | 
				
			||||||
        padding-top: 16px;
 | 
					        padding: 16px;
 | 
				
			||||||
        border-top: 1px solid rgba(0, 0, 0, 0.12);
 | 
					        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 {
 | 
					      .page-footer a {
 | 
				
			||||||
        display: inline-block;
 | 
					        display: inline-block;
 | 
				
			||||||
        margin: 0 8px;
 | 
					        margin: 0 8px;
 | 
				
			||||||
 | 
					        text-decoration: none;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    `,
 | 
					    `,
 | 
				
			||||||
  ];
 | 
					  ];
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,10 +3,20 @@ import { html, css, LitElement, TemplateResult } from "lit";
 | 
				
			|||||||
import { customElement, property } from "lit/decorators";
 | 
					import { customElement, property } from "lit/decorators";
 | 
				
			||||||
import "../../../../src/components/ha-card";
 | 
					import "../../../../src/components/ha-card";
 | 
				
			||||||
import { describeAction } from "../../../../src/data/script_i18n";
 | 
					import { describeAction } from "../../../../src/data/script_i18n";
 | 
				
			||||||
 | 
					import { getEntity } from "../../../../src/fake_data/entity";
 | 
				
			||||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
 | 
					import { provideHass } from "../../../../src/fake_data/provide_hass";
 | 
				
			||||||
import { HomeAssistant } from "../../../../src/types";
 | 
					import { HomeAssistant } from "../../../../src/types";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const actions = [
 | 
					const ENTITIES = [
 | 
				
			||||||
 | 
					  getEntity("scene", "kitchen_morning", "scening", {
 | 
				
			||||||
 | 
					    friendly_name: "Kitchen Morning",
 | 
				
			||||||
 | 
					  }),
 | 
				
			||||||
 | 
					  getEntity("media_player", "kitchen", "playing", {
 | 
				
			||||||
 | 
					    friendly_name: "Sonos Kitchen",
 | 
				
			||||||
 | 
					  }),
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ACTIONS = [
 | 
				
			||||||
  { wait_template: "{{ true }}", alias: "Something with an alias" },
 | 
					  { wait_template: "{{ true }}", alias: "Something with an alias" },
 | 
				
			||||||
  { delay: "0:05" },
 | 
					  { delay: "0:05" },
 | 
				
			||||||
  { wait_template: "{{ true }}" },
 | 
					  { wait_template: "{{ true }}" },
 | 
				
			||||||
@@ -19,8 +29,20 @@ const actions = [
 | 
				
			|||||||
    device_id: "abcdefgh",
 | 
					    device_id: "abcdefgh",
 | 
				
			||||||
    domain: "plex",
 | 
					    domain: "plex",
 | 
				
			||||||
    entity_id: "media_player.kitchen",
 | 
					    entity_id: "media_player.kitchen",
 | 
				
			||||||
 | 
					    type: "turn_on",
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  { scene: "scene.kitchen_morning" },
 | 
					  { scene: "scene.kitchen_morning" },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    service: "scene.turn_on",
 | 
				
			||||||
 | 
					    target: { entity_id: "scene.kitchen_morning" },
 | 
				
			||||||
 | 
					    metadata: {},
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    service: "media_player.play_media",
 | 
				
			||||||
 | 
					    target: { entity_id: "media_player.kitchen" },
 | 
				
			||||||
 | 
					    data: { media_content_id: "", media_content_type: "" },
 | 
				
			||||||
 | 
					    metadata: { title: "Happy Song" },
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    wait_for_trigger: [
 | 
					    wait_for_trigger: [
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
@@ -52,7 +74,7 @@ export class DemoAutomationDescribeAction extends LitElement {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    return html`
 | 
					    return html`
 | 
				
			||||||
      <ha-card header="Actions">
 | 
					      <ha-card header="Actions">
 | 
				
			||||||
        ${actions.map(
 | 
					        ${ACTIONS.map(
 | 
				
			||||||
          (conf) => html`
 | 
					          (conf) => html`
 | 
				
			||||||
            <div class="action">
 | 
					            <div class="action">
 | 
				
			||||||
              <span>${describeAction(this.hass, conf as any)}</span>
 | 
					              <span>${describeAction(this.hass, conf as any)}</span>
 | 
				
			||||||
@@ -68,6 +90,7 @@ export class DemoAutomationDescribeAction extends LitElement {
 | 
				
			|||||||
    super.firstUpdated(changedProps);
 | 
					    super.firstUpdated(changedProps);
 | 
				
			||||||
    const hass = provideHass(this);
 | 
					    const hass = provideHass(this);
 | 
				
			||||||
    hass.updateTranslations(null, "en");
 | 
					    hass.updateTranslations(null, "en");
 | 
				
			||||||
 | 
					    hass.addEntities(ENTITIES);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static get styles() {
 | 
					  static get styles() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,7 +14,7 @@ import { HaDelayAction } from "../../../../src/panels/config/automation/action/t
 | 
				
			|||||||
import { HaDeviceAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-device_id";
 | 
					import { HaDeviceAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-device_id";
 | 
				
			||||||
import { HaEventAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-event";
 | 
					import { HaEventAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-event";
 | 
				
			||||||
import { HaRepeatAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-repeat";
 | 
					import { HaRepeatAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-repeat";
 | 
				
			||||||
import { HaSceneAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-scene";
 | 
					import { HaSceneAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-activate_scene";
 | 
				
			||||||
import { HaServiceAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-service";
 | 
					import { HaServiceAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-service";
 | 
				
			||||||
import { HaWaitForTriggerAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger";
 | 
					import { HaWaitForTriggerAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger";
 | 
				
			||||||
import { HaWaitAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-wait_template";
 | 
					import { HaWaitAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-wait_template";
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +0,0 @@
 | 
				
			|||||||
---
 | 
					 | 
				
			||||||
title: Selectors
 | 
					 | 
				
			||||||
---
 | 
					 | 
				
			||||||
@@ -1,102 +0,0 @@
 | 
				
			|||||||
/* eslint-disable lit/no-template-arrow */
 | 
					 | 
				
			||||||
import { LitElement, TemplateResult, html } from "lit";
 | 
					 | 
				
			||||||
import { customElement, state } from "lit/decorators";
 | 
					 | 
				
			||||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
 | 
					 | 
				
			||||||
import type { HomeAssistant } from "../../../../src/types";
 | 
					 | 
				
			||||||
import "../../components/demo-black-white-row";
 | 
					 | 
				
			||||||
import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
 | 
					 | 
				
			||||||
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
 | 
					 | 
				
			||||||
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
 | 
					 | 
				
			||||||
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
 | 
					 | 
				
			||||||
import "../../../../src/panels/config/automation/trigger/ha-automation-trigger";
 | 
					 | 
				
			||||||
import { Selector } from "../../../../src/data/selector";
 | 
					 | 
				
			||||||
import "../../../../src/components/ha-selector/ha-selector";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const SCHEMAS: { name: string; selector: Selector }[] = [
 | 
					 | 
				
			||||||
  { name: "Addon", selector: { addon: {} } },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  { name: "Entity", selector: { entity: {} } },
 | 
					 | 
				
			||||||
  { name: "Device", selector: { device: {} } },
 | 
					 | 
				
			||||||
  { name: "Area", selector: { area: {} } },
 | 
					 | 
				
			||||||
  { name: "Target", selector: { target: {} } },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    name: "Number",
 | 
					 | 
				
			||||||
    selector: {
 | 
					 | 
				
			||||||
      number: {
 | 
					 | 
				
			||||||
        min: 0,
 | 
					 | 
				
			||||||
        max: 10,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  { name: "Boolean", selector: { boolean: {} } },
 | 
					 | 
				
			||||||
  { name: "Time", selector: { time: {} } },
 | 
					 | 
				
			||||||
  { name: "Action", selector: { action: {} } },
 | 
					 | 
				
			||||||
  { name: "Text", selector: { text: { multiline: false } } },
 | 
					 | 
				
			||||||
  { name: "Text Multiline", selector: { text: { multiline: true } } },
 | 
					 | 
				
			||||||
  { name: "Object", selector: { object: {} } },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    name: "Select",
 | 
					 | 
				
			||||||
    selector: {
 | 
					 | 
				
			||||||
      select: {
 | 
					 | 
				
			||||||
        options: ["Everyone Home", "Some Home", "All gone"],
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@customElement("demo-automation-selectors")
 | 
					 | 
				
			||||||
class DemoHaSelector extends LitElement {
 | 
					 | 
				
			||||||
  @state() private hass!: HomeAssistant;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private data: any = SCHEMAS.map(() => undefined);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  constructor() {
 | 
					 | 
				
			||||||
    super();
 | 
					 | 
				
			||||||
    const hass = provideHass(this);
 | 
					 | 
				
			||||||
    hass.updateTranslations(null, "en");
 | 
					 | 
				
			||||||
    hass.updateTranslations("config", "en");
 | 
					 | 
				
			||||||
    mockEntityRegistry(hass);
 | 
					 | 
				
			||||||
    mockDeviceRegistry(hass);
 | 
					 | 
				
			||||||
    mockAreaRegistry(hass);
 | 
					 | 
				
			||||||
    mockHassioSupervisor(hass);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  protected render(): TemplateResult {
 | 
					 | 
				
			||||||
    const valueChanged = (ev) => {
 | 
					 | 
				
			||||||
      const sampleIdx = ev.target.sampleIdx;
 | 
					 | 
				
			||||||
      this.data[sampleIdx] = ev.detail.value;
 | 
					 | 
				
			||||||
      this.requestUpdate();
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    return html`
 | 
					 | 
				
			||||||
      ${SCHEMAS.map(
 | 
					 | 
				
			||||||
        (info, sampleIdx) => html`
 | 
					 | 
				
			||||||
          <demo-black-white-row
 | 
					 | 
				
			||||||
            .title=${info.name}
 | 
					 | 
				
			||||||
            .value=${{ selector: info.selector, data: this.data[sampleIdx] }}
 | 
					 | 
				
			||||||
          >
 | 
					 | 
				
			||||||
            ${["light", "dark"].map(
 | 
					 | 
				
			||||||
              (slot) =>
 | 
					 | 
				
			||||||
                html`
 | 
					 | 
				
			||||||
                  <ha-selector
 | 
					 | 
				
			||||||
                    slot=${slot}
 | 
					 | 
				
			||||||
                    .hass=${this.hass}
 | 
					 | 
				
			||||||
                    .selector=${info.selector}
 | 
					 | 
				
			||||||
                    .label=${info.name}
 | 
					 | 
				
			||||||
                    .value=${this.data[sampleIdx]}
 | 
					 | 
				
			||||||
                    .sampleIdx=${sampleIdx}
 | 
					 | 
				
			||||||
                    @value-changed=${valueChanged}
 | 
					 | 
				
			||||||
                  ></ha-selector>
 | 
					 | 
				
			||||||
                `
 | 
					 | 
				
			||||||
            )}
 | 
					 | 
				
			||||||
          </demo-black-white-row>
 | 
					 | 
				
			||||||
        `
 | 
					 | 
				
			||||||
      )}
 | 
					 | 
				
			||||||
    `;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
declare global {
 | 
					 | 
				
			||||||
  interface HTMLElementTagNameMap {
 | 
					 | 
				
			||||||
    "demo-automation-selectors": DemoHaSelector;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										34
									
								
								gallery/src/pages/brand/logo.markdown
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								gallery/src/pages/brand/logo.markdown
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					title: "Logo"
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										41
									
								
								gallery/src/pages/brand/our-story.markdown
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								gallery/src/pages/brand/our-story.markdown
									
									
									
									
									
										Normal 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. It’s 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 don’t 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 shouldn’t 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 shouldn’t 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.
 | 
				
			||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
---
 | 
					---
 | 
				
			||||||
title: Alerts
 | 
					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>`
 | 
					# Alert `<ha-alert>`
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,109 @@
 | 
				
			|||||||
/* eslint-disable lit/no-template-arrow */
 | 
					/* eslint-disable lit/no-template-arrow */
 | 
				
			||||||
import "@material/mwc-button";
 | 
					import "@material/mwc-button";
 | 
				
			||||||
import { LitElement, TemplateResult, html } from "lit";
 | 
					import { LitElement, TemplateResult, html } from "lit";
 | 
				
			||||||
import { customElement } from "lit/decorators";
 | 
					import { customElement, state } from "lit/decorators";
 | 
				
			||||||
import { computeInitialHaFormData } from "../../../../src/components/ha-form/compute-initial-ha-form-data";
 | 
					import { computeInitialHaFormData } from "../../../../src/components/ha-form/compute-initial-ha-form-data";
 | 
				
			||||||
import type { HaFormSchema } from "../../../../src/components/ha-form/types";
 | 
					import type { HaFormSchema } from "../../../../src/components/ha-form/types";
 | 
				
			||||||
import "../../../../src/components/ha-form/ha-form";
 | 
					import "../../../../src/components/ha-form/ha-form";
 | 
				
			||||||
import "../../components/demo-black-white-row";
 | 
					import "../../components/demo-black-white-row";
 | 
				
			||||||
 | 
					import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
 | 
				
			||||||
 | 
					import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
 | 
				
			||||||
 | 
					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: {
 | 
					const SCHEMAS: {
 | 
				
			||||||
  title: string;
 | 
					  title: string;
 | 
				
			||||||
@@ -14,6 +112,74 @@ const SCHEMAS: {
 | 
				
			|||||||
  schema: HaFormSchema[];
 | 
					  schema: HaFormSchema[];
 | 
				
			||||||
  data?: Record<string, any>;
 | 
					  data?: Record<string, any>;
 | 
				
			||||||
}[] = [
 | 
					}[] = [
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    title: "Selectors",
 | 
				
			||||||
 | 
					    translations: {
 | 
				
			||||||
 | 
					      addon: "Addon",
 | 
				
			||||||
 | 
					      entity: "Entity",
 | 
				
			||||||
 | 
					      device: "Device",
 | 
				
			||||||
 | 
					      area: "Area",
 | 
				
			||||||
 | 
					      target: "Target",
 | 
				
			||||||
 | 
					      number: "Number",
 | 
				
			||||||
 | 
					      boolean: "Boolean",
 | 
				
			||||||
 | 
					      time: "Time",
 | 
				
			||||||
 | 
					      action: "Action",
 | 
				
			||||||
 | 
					      text: "Text",
 | 
				
			||||||
 | 
					      text_multiline: "Text Multiline",
 | 
				
			||||||
 | 
					      object: "Object",
 | 
				
			||||||
 | 
					      select: "Select",
 | 
				
			||||||
 | 
					      icon: "Icon",
 | 
				
			||||||
 | 
					      media: "Media",
 | 
				
			||||||
 | 
					      location: "Location",
 | 
				
			||||||
 | 
					      entities: "Entities",
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    schema: [
 | 
				
			||||||
 | 
					      { name: "addon", selector: { addon: {} } },
 | 
				
			||||||
 | 
					      { name: "entity", selector: { entity: {} } },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        name: "Attribute",
 | 
				
			||||||
 | 
					        selector: { attribute: { entity_id: "" } },
 | 
				
			||||||
 | 
					        context: { filter_entity: "entity" },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      { name: "Device", selector: { device: {} } },
 | 
				
			||||||
 | 
					      { name: "Duration", selector: { duration: {} } },
 | 
				
			||||||
 | 
					      { name: "area", selector: { area: {} } },
 | 
				
			||||||
 | 
					      { name: "target", selector: { target: {} } },
 | 
				
			||||||
 | 
					      { name: "number", selector: { number: { min: 0, max: 10 } } },
 | 
				
			||||||
 | 
					      { name: "boolean", selector: { boolean: {} } },
 | 
				
			||||||
 | 
					      { name: "time", selector: { time: {} } },
 | 
				
			||||||
 | 
					      { name: "action", selector: { action: {} } },
 | 
				
			||||||
 | 
					      { name: "text", selector: { text: { multiline: false } } },
 | 
				
			||||||
 | 
					      { name: "text_multiline", selector: { text: { multiline: true } } },
 | 
				
			||||||
 | 
					      { name: "object", selector: { object: {} } },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        name: "select",
 | 
				
			||||||
 | 
					        selector: {
 | 
				
			||||||
 | 
					          select: { options: ["Everyone Home", "Some Home", "All gone"] },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        name: "icon",
 | 
				
			||||||
 | 
					        selector: {
 | 
				
			||||||
 | 
					          icon: {},
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        name: "media",
 | 
				
			||||||
 | 
					        selector: {
 | 
				
			||||||
 | 
					          media: {},
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        name: "location",
 | 
				
			||||||
 | 
					        selector: { location: { radius: true, icon: "mdi:home" } },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        name: "entities",
 | 
				
			||||||
 | 
					        selector: { entity: { multiple: true } },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    title: "Authentication",
 | 
					    title: "Authentication",
 | 
				
			||||||
    translations: {
 | 
					    translations: {
 | 
				
			||||||
@@ -50,13 +216,11 @@ const SCHEMAS: {
 | 
				
			|||||||
      {
 | 
					      {
 | 
				
			||||||
        type: "boolean",
 | 
					        type: "boolean",
 | 
				
			||||||
        name: "bool",
 | 
					        name: "bool",
 | 
				
			||||||
        optional: true,
 | 
					 | 
				
			||||||
        default: false,
 | 
					        default: false,
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        type: "integer",
 | 
					        type: "integer",
 | 
				
			||||||
        name: "int",
 | 
					        name: "int",
 | 
				
			||||||
        optional: true,
 | 
					 | 
				
			||||||
        default: 10,
 | 
					        default: 10,
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
@@ -67,7 +231,6 @@ const SCHEMAS: {
 | 
				
			|||||||
      {
 | 
					      {
 | 
				
			||||||
        type: "string",
 | 
					        type: "string",
 | 
				
			||||||
        name: "string",
 | 
					        name: "string",
 | 
				
			||||||
        optional: true,
 | 
					 | 
				
			||||||
        default: "Default",
 | 
					        default: "Default",
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
@@ -77,7 +240,6 @@ const SCHEMAS: {
 | 
				
			|||||||
          ["other", "other"],
 | 
					          ["other", "other"],
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
        name: "select",
 | 
					        name: "select",
 | 
				
			||||||
        optional: true,
 | 
					 | 
				
			||||||
        default: "default",
 | 
					        default: "default",
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
@@ -87,7 +249,6 @@ const SCHEMAS: {
 | 
				
			|||||||
          other: "Other",
 | 
					          other: "Other",
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        name: "multi",
 | 
					        name: "multi",
 | 
				
			||||||
        optional: true,
 | 
					 | 
				
			||||||
        default: ["default"],
 | 
					        default: ["default"],
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
@@ -108,7 +269,6 @@ const SCHEMAS: {
 | 
				
			|||||||
      {
 | 
					      {
 | 
				
			||||||
        type: "integer",
 | 
					        type: "integer",
 | 
				
			||||||
        name: "int with default",
 | 
					        name: "int with default",
 | 
				
			||||||
        optional: true,
 | 
					 | 
				
			||||||
        default: 10,
 | 
					        default: 10,
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
@@ -122,7 +282,6 @@ const SCHEMAS: {
 | 
				
			|||||||
      {
 | 
					      {
 | 
				
			||||||
        type: "integer",
 | 
					        type: "integer",
 | 
				
			||||||
        name: "int range optional",
 | 
					        name: "int range optional",
 | 
				
			||||||
        optional: true,
 | 
					 | 
				
			||||||
        valueMin: 0,
 | 
					        valueMin: 0,
 | 
				
			||||||
        valueMax: 10,
 | 
					        valueMax: 10,
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
@@ -148,7 +307,6 @@ const SCHEMAS: {
 | 
				
			|||||||
          ["other", "Other"],
 | 
					          ["other", "Other"],
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
        name: "select optional",
 | 
					        name: "select optional",
 | 
				
			||||||
        optional: true,
 | 
					 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        type: "select",
 | 
					        type: "select",
 | 
				
			||||||
@@ -161,7 +319,6 @@ const SCHEMAS: {
 | 
				
			|||||||
          ["option", "1000"],
 | 
					          ["option", "1000"],
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
        name: "select many otions",
 | 
					        name: "select many otions",
 | 
				
			||||||
        optional: true,
 | 
					 | 
				
			||||||
        default: "default",
 | 
					        default: "default",
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
@@ -190,7 +347,6 @@ const SCHEMAS: {
 | 
				
			|||||||
          option: "1000",
 | 
					          option: "1000",
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        name: "multi many otions",
 | 
					        name: "multi many otions",
 | 
				
			||||||
        optional: true,
 | 
					 | 
				
			||||||
        default: ["default"],
 | 
					        default: ["default"],
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
@@ -239,23 +395,36 @@ const SCHEMAS: {
 | 
				
			|||||||
        valueMin: 1,
 | 
					        valueMin: 1,
 | 
				
			||||||
        valueMax: 65535,
 | 
					        valueMax: 65535,
 | 
				
			||||||
        name: "port",
 | 
					        name: "port",
 | 
				
			||||||
        optional: true,
 | 
					 | 
				
			||||||
        default: 80,
 | 
					        default: 80,
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      { type: "string", name: "path", optional: true, default: "/" },
 | 
					      { type: "string", name: "path", default: "/" },
 | 
				
			||||||
      { type: "boolean", name: "ssl", optional: true, default: false },
 | 
					      { type: "boolean", name: "ssl", default: false },
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@customElement("demo-components-ha-form")
 | 
					@customElement("demo-components-ha-form")
 | 
				
			||||||
class DemoHaForm extends LitElement {
 | 
					class DemoHaForm extends LitElement {
 | 
				
			||||||
 | 
					  @state() private hass!: HomeAssistant;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private data = SCHEMAS.map(
 | 
					  private data = SCHEMAS.map(
 | 
				
			||||||
    ({ schema, data }) => data || computeInitialHaFormData(schema)
 | 
					    ({ schema, data }) => data || computeInitialHaFormData(schema)
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private disabled = SCHEMAS.map(() => false);
 | 
					  private disabled = SCHEMAS.map(() => false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor() {
 | 
				
			||||||
 | 
					    super();
 | 
				
			||||||
 | 
					    const hass = provideHass(this);
 | 
				
			||||||
 | 
					    hass.updateTranslations(null, "en");
 | 
				
			||||||
 | 
					    hass.updateTranslations("config", "en");
 | 
				
			||||||
 | 
					    hass.addEntities(ENTITIES);
 | 
				
			||||||
 | 
					    mockEntityRegistry(hass);
 | 
				
			||||||
 | 
					    mockDeviceRegistry(hass, DEVICES);
 | 
				
			||||||
 | 
					    mockAreaRegistry(hass, AREAS);
 | 
				
			||||||
 | 
					    mockHassioSupervisor(hass);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  protected render(): TemplateResult {
 | 
					  protected render(): TemplateResult {
 | 
				
			||||||
    return html`
 | 
					    return html`
 | 
				
			||||||
      ${SCHEMAS.map((info, idx) => {
 | 
					      ${SCHEMAS.map((info, idx) => {
 | 
				
			||||||
@@ -278,6 +447,7 @@ class DemoHaForm extends LitElement {
 | 
				
			|||||||
              (slot) => html`
 | 
					              (slot) => html`
 | 
				
			||||||
                <ha-form
 | 
					                <ha-form
 | 
				
			||||||
                  slot=${slot}
 | 
					                  slot=${slot}
 | 
				
			||||||
 | 
					                  .hass=${this.hass}
 | 
				
			||||||
                  .data=${this.data[idx]}
 | 
					                  .data=${this.data[idx]}
 | 
				
			||||||
                  .schema=${info.schema}
 | 
					                  .schema=${info.schema}
 | 
				
			||||||
                  .error=${info.error}
 | 
					                  .error=${info.error}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,5 @@
 | 
				
			|||||||
---
 | 
					---
 | 
				
			||||||
title: Target Selectors
 | 
					title: Selectors
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					See the website for [list of available selectors](https://www.home-assistant.io/docs/blueprint/selectors/).
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,6 +12,100 @@ import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
 | 
				
			|||||||
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
 | 
					import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
 | 
				
			||||||
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
 | 
					import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
 | 
				
			||||||
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
 | 
					import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
 | 
				
			||||||
 | 
					import { getEntity } from "../../../../src/fake_data/entity";
 | 
				
			||||||
 | 
					import { ProvideHassElement } from "../../../../src/mixins/provide-hass-lit-mixin";
 | 
				
			||||||
 | 
					import { showDialog } from "../../../../src/dialogs/make-dialog-manager";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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: {
 | 
					const SCHEMAS: {
 | 
				
			||||||
  name: string;
 | 
					  name: string;
 | 
				
			||||||
@@ -21,7 +115,12 @@ const SCHEMAS: {
 | 
				
			|||||||
    name: "One of each",
 | 
					    name: "One of each",
 | 
				
			||||||
    input: {
 | 
					    input: {
 | 
				
			||||||
      entity: { name: "Entity", selector: { entity: {} } },
 | 
					      entity: { name: "Entity", selector: { entity: {} } },
 | 
				
			||||||
 | 
					      attribute: {
 | 
				
			||||||
 | 
					        name: "Attribute",
 | 
				
			||||||
 | 
					        selector: { attribute: { entity_id: "" } },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
      device: { name: "Device", selector: { device: {} } },
 | 
					      device: { name: "Device", selector: { device: {} } },
 | 
				
			||||||
 | 
					      duration: { name: "Duration", selector: { duration: {} } },
 | 
				
			||||||
      addon: { name: "Addon", selector: { addon: {} } },
 | 
					      addon: { name: "Addon", selector: { addon: {} } },
 | 
				
			||||||
      area: { name: "Area", selector: { area: {} } },
 | 
					      area: { name: "Area", selector: { area: {} } },
 | 
				
			||||||
      target: { name: "Target", selector: { target: {} } },
 | 
					      target: { name: "Target", selector: { target: {} } },
 | 
				
			||||||
@@ -47,24 +146,69 @@ const SCHEMAS: {
 | 
				
			|||||||
      },
 | 
					      },
 | 
				
			||||||
      boolean: { name: "Boolean", selector: { boolean: {} } },
 | 
					      boolean: { name: "Boolean", selector: { boolean: {} } },
 | 
				
			||||||
      time: { name: "Time", selector: { time: {} } },
 | 
					      time: { name: "Time", selector: { time: {} } },
 | 
				
			||||||
 | 
					      date: { name: "Date", selector: { date: {} } },
 | 
				
			||||||
 | 
					      datetime: { name: "Date Time", selector: { datetime: {} } },
 | 
				
			||||||
      action: { name: "Action", selector: { action: {} } },
 | 
					      action: { name: "Action", selector: { action: {} } },
 | 
				
			||||||
      text: { name: "Text", selector: { text: { multiline: false } } },
 | 
					      text: {
 | 
				
			||||||
 | 
					        name: "Text",
 | 
				
			||||||
 | 
					        selector: { text: {} },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      password: {
 | 
				
			||||||
 | 
					        name: "Password",
 | 
				
			||||||
 | 
					        selector: { text: { type: "password" } },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
      text_multiline: {
 | 
					      text_multiline: {
 | 
				
			||||||
        name: "Text multiline",
 | 
					        name: "Text multiline",
 | 
				
			||||||
        selector: { text: { multiline: true } },
 | 
					        selector: {
 | 
				
			||||||
 | 
					          text: { multiline: true },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      object: { name: "Object", selector: { object: {} } },
 | 
					      object: { name: "Object", selector: { object: {} } },
 | 
				
			||||||
      select: {
 | 
					      select_radio: {
 | 
				
			||||||
        name: "Select",
 | 
					        name: "Select (Radio)",
 | 
				
			||||||
        selector: { select: { options: ["Option 1", "Option 2"] } },
 | 
					        selector: { select: { options: ["Option 1", "Option 2"] } },
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
 | 
					      select: {
 | 
				
			||||||
 | 
					        name: "Select",
 | 
				
			||||||
 | 
					        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 } } },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@customElement("demo-components-ha-selector")
 | 
					@customElement("demo-components-ha-selector")
 | 
				
			||||||
class DemoHaSelector extends LitElement {
 | 
					class DemoHaSelector extends LitElement implements ProvideHassElement {
 | 
				
			||||||
  @state() private hass!: HomeAssistant;
 | 
					  @state() public hass!: HomeAssistant;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private data = SCHEMAS.map(() => ({}));
 | 
					  private data = SCHEMAS.map(() => ({}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -73,12 +217,130 @@ class DemoHaSelector extends LitElement {
 | 
				
			|||||||
    const hass = provideHass(this);
 | 
					    const hass = provideHass(this);
 | 
				
			||||||
    hass.updateTranslations(null, "en");
 | 
					    hass.updateTranslations(null, "en");
 | 
				
			||||||
    hass.updateTranslations("config", "en");
 | 
					    hass.updateTranslations("config", "en");
 | 
				
			||||||
 | 
					    hass.addEntities(ENTITIES);
 | 
				
			||||||
    mockEntityRegistry(hass);
 | 
					    mockEntityRegistry(hass);
 | 
				
			||||||
    mockDeviceRegistry(hass);
 | 
					    mockDeviceRegistry(hass, DEVICES);
 | 
				
			||||||
    mockAreaRegistry(hass);
 | 
					    mockAreaRegistry(hass, AREAS);
 | 
				
			||||||
    mockHassioSupervisor(hass);
 | 
					    mockHassioSupervisor(hass);
 | 
				
			||||||
 | 
					    hass.mockWS("auth/sign_path", (params) => params);
 | 
				
			||||||
 | 
					    hass.mockWS("media_player/browse_media", this._browseMedia);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public provideHass(el) {
 | 
				
			||||||
 | 
					    el.hass = this.hass;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public connectedCallback() {
 | 
				
			||||||
 | 
					    super.connectedCallback();
 | 
				
			||||||
 | 
					    this.addEventListener("show-dialog", this._dialogManager);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public disconnectedCallback() {
 | 
				
			||||||
 | 
					    super.disconnectedCallback();
 | 
				
			||||||
 | 
					    this.removeEventListener("show-dialog", this._dialogManager);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private _browseMedia = ({ media_content_id }) => {
 | 
				
			||||||
 | 
					    if (media_content_id === undefined) {
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        title: "Media",
 | 
				
			||||||
 | 
					        media_class: "directory",
 | 
				
			||||||
 | 
					        media_content_type: "",
 | 
				
			||||||
 | 
					        media_content_id: "media-source://media_source/local/.",
 | 
				
			||||||
 | 
					        can_play: false,
 | 
				
			||||||
 | 
					        can_expand: true,
 | 
				
			||||||
 | 
					        children_media_class: "directory",
 | 
				
			||||||
 | 
					        thumbnail: null,
 | 
				
			||||||
 | 
					        children: [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            title: "Misc",
 | 
				
			||||||
 | 
					            media_class: "directory",
 | 
				
			||||||
 | 
					            media_content_type: "",
 | 
				
			||||||
 | 
					            media_content_id: "media-source://media_source/local/misc",
 | 
				
			||||||
 | 
					            can_play: false,
 | 
				
			||||||
 | 
					            can_expand: true,
 | 
				
			||||||
 | 
					            children_media_class: null,
 | 
				
			||||||
 | 
					            thumbnail: null,
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            title: "Movies",
 | 
				
			||||||
 | 
					            media_class: "directory",
 | 
				
			||||||
 | 
					            media_content_type: "",
 | 
				
			||||||
 | 
					            media_content_id: "media-source://media_source/local/movies",
 | 
				
			||||||
 | 
					            can_play: true,
 | 
				
			||||||
 | 
					            can_expand: true,
 | 
				
			||||||
 | 
					            children_media_class: "movie",
 | 
				
			||||||
 | 
					            thumbnail: null,
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            title: "Music",
 | 
				
			||||||
 | 
					            media_class: "album",
 | 
				
			||||||
 | 
					            media_content_type: "",
 | 
				
			||||||
 | 
					            media_content_id: "media-source://media_source/local/music",
 | 
				
			||||||
 | 
					            can_play: false,
 | 
				
			||||||
 | 
					            can_expand: true,
 | 
				
			||||||
 | 
					            children_media_class: "music",
 | 
				
			||||||
 | 
					            thumbnail: "/images/album_cover_2.jpg",
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      title: "Subfolder",
 | 
				
			||||||
 | 
					      media_class: "directory",
 | 
				
			||||||
 | 
					      media_content_type: "",
 | 
				
			||||||
 | 
					      media_content_id: "media-source://media_source/local/sub",
 | 
				
			||||||
 | 
					      can_play: false,
 | 
				
			||||||
 | 
					      can_expand: true,
 | 
				
			||||||
 | 
					      children_media_class: "directory",
 | 
				
			||||||
 | 
					      thumbnail: null,
 | 
				
			||||||
 | 
					      children: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          title: "audio.mp3",
 | 
				
			||||||
 | 
					          media_class: "music",
 | 
				
			||||||
 | 
					          media_content_type: "audio/mpeg",
 | 
				
			||||||
 | 
					          media_content_id: "media-source://media_source/local/audio.mp3",
 | 
				
			||||||
 | 
					          can_play: true,
 | 
				
			||||||
 | 
					          can_expand: false,
 | 
				
			||||||
 | 
					          children_media_class: null,
 | 
				
			||||||
 | 
					          thumbnail: "/images/album_cover.jpg",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          title: "image.jpg",
 | 
				
			||||||
 | 
					          media_class: "image",
 | 
				
			||||||
 | 
					          media_content_type: "image/jpeg",
 | 
				
			||||||
 | 
					          media_content_id: "media-source://media_source/local/image.jpg",
 | 
				
			||||||
 | 
					          can_play: true,
 | 
				
			||||||
 | 
					          can_expand: false,
 | 
				
			||||||
 | 
					          children_media_class: null,
 | 
				
			||||||
 | 
					          thumbnail: "https://brands.home-assistant.io/_/image/logo.png",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          title: "movie.mp4",
 | 
				
			||||||
 | 
					          media_class: "movie",
 | 
				
			||||||
 | 
					          media_content_type: "image/jpeg",
 | 
				
			||||||
 | 
					          media_content_id: "media-source://media_source/local/movie.mp4",
 | 
				
			||||||
 | 
					          can_play: true,
 | 
				
			||||||
 | 
					          can_expand: false,
 | 
				
			||||||
 | 
					          children_media_class: null,
 | 
				
			||||||
 | 
					          thumbnail: null,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private _dialogManager = (e) => {
 | 
				
			||||||
 | 
					    const { dialogTag, dialogImport, dialogParams, addHistory } = e.detail;
 | 
				
			||||||
 | 
					    showDialog(
 | 
				
			||||||
 | 
					      this,
 | 
				
			||||||
 | 
					      this.shadowRoot!,
 | 
				
			||||||
 | 
					      dialogTag,
 | 
				
			||||||
 | 
					      dialogParams,
 | 
				
			||||||
 | 
					      dialogImport,
 | 
				
			||||||
 | 
					      addHistory
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  protected render(): TemplateResult {
 | 
					  protected render(): TemplateResult {
 | 
				
			||||||
    return html`
 | 
					    return html`
 | 
				
			||||||
      ${SCHEMAS.map((info, idx) => {
 | 
					      ${SCHEMAS.map((info, idx) => {
 | 
				
			||||||
@@ -117,7 +379,6 @@ class DemoHaSelector extends LitElement {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static styles = css`
 | 
					  static styles = css`
 | 
				
			||||||
    paper-input,
 | 
					 | 
				
			||||||
    ha-selector {
 | 
					    ha-selector {
 | 
				
			||||||
      width: 60;
 | 
					      width: 60;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,8 @@
 | 
				
			|||||||
title: Editing design.home-assistant.io
 | 
					title: Editing design.home-assistant.io
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# How to edit design.home-assistant.io
 | 
					# 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.
 | 
					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 { customElement } from "lit/decorators";
 | 
				
			||||||
import "../../../../src/components/ha-card";
 | 
					import "../../../../src/components/ha-card";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
@customElement("demo-user-experience-usability")
 | 
					@customElement("demo-user-experience-usability")
 | 
				
			||||||
export class DemoUserExperienceUsability extends LitElement {
 | 
					export class DemoUserExperienceUsability extends LitElement {
 | 
				
			||||||
  protected render() {
 | 
					  protected render() {
 | 
				
			||||||
    return html`
 | 
					    return html`
 | 
				
			||||||
      <ha-card>
 | 
					      <ha-card>
 | 
				
			||||||
        <div class="card-content">
 | 
					        <div class="card-content">Hello world!</div>
 | 
				
			||||||
          Hello world!
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
      </ha-card>
 | 
					      </ha-card>
 | 
				
			||||||
    `;
 | 
					    `;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -29,6 +29,7 @@ const createConfigEntry = (
 | 
				
			|||||||
  source: "zeroconf",
 | 
					  source: "zeroconf",
 | 
				
			||||||
  state: "loaded",
 | 
					  state: "loaded",
 | 
				
			||||||
  supports_options: false,
 | 
					  supports_options: false,
 | 
				
			||||||
 | 
					  supports_remove_device: false,
 | 
				
			||||||
  supports_unload: true,
 | 
					  supports_unload: true,
 | 
				
			||||||
  disabled_by: null,
 | 
					  disabled_by: null,
 | 
				
			||||||
  pref_disable_new_entities: false,
 | 
					  pref_disable_new_entities: false,
 | 
				
			||||||
@@ -187,6 +188,7 @@ const createEntityRegistryEntries = (
 | 
				
			|||||||
    device_id: "mock-device-id",
 | 
					    device_id: "mock-device-id",
 | 
				
			||||||
    area_id: null,
 | 
					    area_id: null,
 | 
				
			||||||
    disabled_by: null,
 | 
					    disabled_by: null,
 | 
				
			||||||
 | 
					    hidden_by: null,
 | 
				
			||||||
    entity_category: null,
 | 
					    entity_category: null,
 | 
				
			||||||
    entity_id: "binary_sensor.updater",
 | 
					    entity_id: "binary_sensor.updater",
 | 
				
			||||||
    name: null,
 | 
					    name: null,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -42,7 +42,9 @@ class HassioAddonRepositoryEl extends LitElement {
 | 
				
			|||||||
    const repo = this.repo;
 | 
					    const repo = this.repo;
 | 
				
			||||||
    let _addons = this.addons;
 | 
					    let _addons = this.addons;
 | 
				
			||||||
    if (!this.hass.userData?.showAdvanced) {
 | 
					    if (!this.hass.userData?.showAdvanced) {
 | 
				
			||||||
      _addons = _addons.filter((addon) => !addon.advanced);
 | 
					      _addons = _addons.filter(
 | 
				
			||||||
 | 
					        (addon) => !addon.advanced && addon.stage === "stable"
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const addons = this._getAddons(_addons, this.filter);
 | 
					    const addons = this._getAddons(_addons, this.filter);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,7 +14,7 @@ import memoizeOne from "memoize-one";
 | 
				
			|||||||
import { atLeastVersion } from "../../../src/common/config/version";
 | 
					import { atLeastVersion } from "../../../src/common/config/version";
 | 
				
			||||||
import { fireEvent } from "../../../src/common/dom/fire_event";
 | 
					import { fireEvent } from "../../../src/common/dom/fire_event";
 | 
				
			||||||
import { navigate } from "../../../src/common/navigate";
 | 
					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 { extractSearchParam } from "../../../src/common/url/search-params";
 | 
				
			||||||
import "../../../src/components/ha-button-menu";
 | 
					import "../../../src/components/ha-button-menu";
 | 
				
			||||||
import "../../../src/components/ha-icon-button";
 | 
					import "../../../src/components/ha-icon-button";
 | 
				
			||||||
@@ -110,8 +110,6 @@ class HassioAddonStore extends LitElement {
 | 
				
			|||||||
              <div class="search">
 | 
					              <div class="search">
 | 
				
			||||||
                <search-input
 | 
					                <search-input
 | 
				
			||||||
                  .hass=${this.hass}
 | 
					                  .hass=${this.hass}
 | 
				
			||||||
                  no-label-float
 | 
					 | 
				
			||||||
                  no-underline
 | 
					 | 
				
			||||||
                  .filter=${this._filter}
 | 
					                  .filter=${this._filter}
 | 
				
			||||||
                  @value-changed=${this._filterChanged}
 | 
					                  @value-changed=${this._filterChanged}
 | 
				
			||||||
                ></search-input>
 | 
					                ></search-input>
 | 
				
			||||||
@@ -221,13 +219,14 @@ class HassioAddonStore extends LitElement {
 | 
				
			|||||||
        margin-top: 24px;
 | 
					        margin-top: 24px;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      .search {
 | 
					      .search {
 | 
				
			||||||
        padding: 0 16px;
 | 
					        position: sticky;
 | 
				
			||||||
        background: var(--sidebar-background-color);
 | 
					        top: 0;
 | 
				
			||||||
        border-bottom: 1px solid var(--divider-color);
 | 
					        z-index: 2;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      .search search-input {
 | 
					      search-input {
 | 
				
			||||||
        position: relative;
 | 
					        display: block;
 | 
				
			||||||
        top: 2px;
 | 
					        --mdc-text-field-fill-color: var(--sidebar-background-color);
 | 
				
			||||||
 | 
					        --mdc-text-field-idle-line-color: var(--divider-color);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      .advanced {
 | 
					      .advanced {
 | 
				
			||||||
        padding: 12px;
 | 
					        padding: 12px;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,5 @@
 | 
				
			|||||||
import "@material/mwc-button";
 | 
					import "@material/mwc-button";
 | 
				
			||||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
 | 
					import "@material/mwc-list/mwc-list-item";
 | 
				
			||||||
import "@polymer/paper-item/paper-item";
 | 
					 | 
				
			||||||
import "@polymer/paper-listbox/paper-listbox";
 | 
					 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  css,
 | 
					  css,
 | 
				
			||||||
  CSSResultGroup,
 | 
					  CSSResultGroup,
 | 
				
			||||||
@@ -11,10 +9,11 @@ import {
 | 
				
			|||||||
  TemplateResult,
 | 
					  TemplateResult,
 | 
				
			||||||
} from "lit";
 | 
					} from "lit";
 | 
				
			||||||
import { customElement, property, state } from "lit/decorators";
 | 
					import { customElement, property, state } from "lit/decorators";
 | 
				
			||||||
import "web-animations-js/web-animations-next-lite.min";
 | 
					import { stopPropagation } from "../../../../src/common/dom/stop_propagation";
 | 
				
			||||||
import "../../../../src/components/buttons/ha-progress-button";
 | 
					import "../../../../src/components/buttons/ha-progress-button";
 | 
				
			||||||
import "../../../../src/components/ha-alert";
 | 
					import "../../../../src/components/ha-alert";
 | 
				
			||||||
import "../../../../src/components/ha-card";
 | 
					import "../../../../src/components/ha-card";
 | 
				
			||||||
 | 
					import "../../../../src/components/ha-select";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  HassioAddonDetails,
 | 
					  HassioAddonDetails,
 | 
				
			||||||
  HassioAddonSetOptionParams,
 | 
					  HassioAddonSetOptionParams,
 | 
				
			||||||
@@ -57,49 +56,44 @@ class HassioAddonAudio extends LitElement {
 | 
				
			|||||||
          ${this._error
 | 
					          ${this._error
 | 
				
			||||||
            ? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
 | 
					            ? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
 | 
				
			||||||
            : ""}
 | 
					            : ""}
 | 
				
			||||||
 | 
					          ${this._inputDevices &&
 | 
				
			||||||
          <paper-dropdown-menu
 | 
					          html`<ha-select
 | 
				
			||||||
            .label=${this.supervisor.localize(
 | 
					            .label=${this.supervisor.localize(
 | 
				
			||||||
              "addon.configuration.audio.input"
 | 
					              "addon.configuration.audio.input"
 | 
				
			||||||
            )}
 | 
					            )}
 | 
				
			||||||
            @iron-select=${this._setInputDevice}
 | 
					            @selected=${this._setInputDevice}
 | 
				
			||||||
 | 
					            @closed=${stopPropagation}
 | 
				
			||||||
 | 
					            fixedMenuPosition
 | 
				
			||||||
 | 
					            naturalMenuWidth
 | 
				
			||||||
 | 
					            .value=${this._selectedInput!}
 | 
				
			||||||
          >
 | 
					          >
 | 
				
			||||||
            <paper-listbox
 | 
					            ${this._inputDevices.map(
 | 
				
			||||||
              slot="dropdown-content"
 | 
					 | 
				
			||||||
              attr-for-selected="device"
 | 
					 | 
				
			||||||
              .selected=${this._selectedInput!}
 | 
					 | 
				
			||||||
            >
 | 
					 | 
				
			||||||
              ${this._inputDevices &&
 | 
					 | 
				
			||||||
              this._inputDevices.map(
 | 
					 | 
				
			||||||
              (item) => html`
 | 
					              (item) => html`
 | 
				
			||||||
                  <paper-item device=${item.device || ""}>
 | 
					                <mwc-list-item .value=${item.device || ""}>
 | 
				
			||||||
                  ${item.name}
 | 
					                  ${item.name}
 | 
				
			||||||
                  </paper-item>
 | 
					                </mwc-list-item>
 | 
				
			||||||
              `
 | 
					              `
 | 
				
			||||||
            )}
 | 
					            )}
 | 
				
			||||||
            </paper-listbox>
 | 
					          </ha-select>`}
 | 
				
			||||||
          </paper-dropdown-menu>
 | 
					          ${this._outputDevices &&
 | 
				
			||||||
          <paper-dropdown-menu
 | 
					          html`<ha-select
 | 
				
			||||||
            .label=${this.supervisor.localize(
 | 
					            .label=${this.supervisor.localize(
 | 
				
			||||||
              "addon.configuration.audio.output"
 | 
					              "addon.configuration.audio.output"
 | 
				
			||||||
            )}
 | 
					            )}
 | 
				
			||||||
            @iron-select=${this._setOutputDevice}
 | 
					            @selected=${this._setOutputDevice}
 | 
				
			||||||
 | 
					            @closed=${stopPropagation}
 | 
				
			||||||
 | 
					            fixedMenuPosition
 | 
				
			||||||
 | 
					            naturalMenuWidth
 | 
				
			||||||
 | 
					            .value=${this._selectedOutput!}
 | 
				
			||||||
          >
 | 
					          >
 | 
				
			||||||
            <paper-listbox
 | 
					            ${this._outputDevices.map(
 | 
				
			||||||
              slot="dropdown-content"
 | 
					 | 
				
			||||||
              attr-for-selected="device"
 | 
					 | 
				
			||||||
              .selected=${this._selectedOutput!}
 | 
					 | 
				
			||||||
            >
 | 
					 | 
				
			||||||
              ${this._outputDevices &&
 | 
					 | 
				
			||||||
              this._outputDevices.map(
 | 
					 | 
				
			||||||
              (item) => html`
 | 
					              (item) => html`
 | 
				
			||||||
                  <paper-item device=${item.device || ""}
 | 
					                <mwc-list-item .value=${item.device || ""}
 | 
				
			||||||
                    >${item.name}</paper-item
 | 
					                  >${item.name}</mwc-list-item
 | 
				
			||||||
                >
 | 
					                >
 | 
				
			||||||
              `
 | 
					              `
 | 
				
			||||||
            )}
 | 
					            )}
 | 
				
			||||||
            </paper-listbox>
 | 
					          </ha-select>`}
 | 
				
			||||||
          </paper-dropdown-menu>
 | 
					 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div class="card-actions">
 | 
					        <div class="card-actions">
 | 
				
			||||||
          <ha-progress-button @click=${this._saveSettings}>
 | 
					          <ha-progress-button @click=${this._saveSettings}>
 | 
				
			||||||
@@ -116,8 +110,7 @@ class HassioAddonAudio extends LitElement {
 | 
				
			|||||||
      hassioStyle,
 | 
					      hassioStyle,
 | 
				
			||||||
      css`
 | 
					      css`
 | 
				
			||||||
        :host,
 | 
					        :host,
 | 
				
			||||||
        ha-card,
 | 
					        ha-card {
 | 
				
			||||||
        paper-dropdown-menu {
 | 
					 | 
				
			||||||
          display: block;
 | 
					          display: block;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        paper-item {
 | 
					        paper-item {
 | 
				
			||||||
@@ -126,24 +119,30 @@ class HassioAddonAudio extends LitElement {
 | 
				
			|||||||
        .card-actions {
 | 
					        .card-actions {
 | 
				
			||||||
          text-align: right;
 | 
					          text-align: right;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        ha-select {
 | 
				
			||||||
 | 
					          width: 100%;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        ha-select:last-child {
 | 
				
			||||||
 | 
					          margin-top: 8px;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      `,
 | 
					      `,
 | 
				
			||||||
    ];
 | 
					    ];
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  protected update(changedProperties: PropertyValues): void {
 | 
					  protected willUpdate(changedProperties: PropertyValues): void {
 | 
				
			||||||
    super.update(changedProperties);
 | 
					    super.willUpdate(changedProperties);
 | 
				
			||||||
    if (changedProperties.has("addon")) {
 | 
					    if (changedProperties.has("addon")) {
 | 
				
			||||||
      this._addonChanged();
 | 
					      this._addonChanged();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private _setInputDevice(ev): void {
 | 
					  private _setInputDevice(ev): void {
 | 
				
			||||||
    const device = ev.detail.item.getAttribute("device");
 | 
					    const device = ev.target.value;
 | 
				
			||||||
    this._selectedInput = device;
 | 
					    this._selectedInput = device;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private _setOutputDevice(ev): void {
 | 
					  private _setOutputDevice(ev): void {
 | 
				
			||||||
    const device = ev.detail.item.getAttribute("device");
 | 
					    const device = ev.target.value;
 | 
				
			||||||
    this._selectedOutput = device;
 | 
					    this._selectedOutput = device;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,7 @@ import {
 | 
				
			|||||||
  mdiFlask,
 | 
					  mdiFlask,
 | 
				
			||||||
  mdiHomeAssistant,
 | 
					  mdiHomeAssistant,
 | 
				
			||||||
  mdiKey,
 | 
					  mdiKey,
 | 
				
			||||||
 | 
					  mdiLinkLock,
 | 
				
			||||||
  mdiNetwork,
 | 
					  mdiNetwork,
 | 
				
			||||||
  mdiNumeric1,
 | 
					  mdiNumeric1,
 | 
				
			||||||
  mdiNumeric2,
 | 
					  mdiNumeric2,
 | 
				
			||||||
@@ -16,6 +17,8 @@ import {
 | 
				
			|||||||
  mdiNumeric4,
 | 
					  mdiNumeric4,
 | 
				
			||||||
  mdiNumeric5,
 | 
					  mdiNumeric5,
 | 
				
			||||||
  mdiNumeric6,
 | 
					  mdiNumeric6,
 | 
				
			||||||
 | 
					  mdiNumeric7,
 | 
				
			||||||
 | 
					  mdiNumeric8,
 | 
				
			||||||
  mdiPound,
 | 
					  mdiPound,
 | 
				
			||||||
  mdiShield,
 | 
					  mdiShield,
 | 
				
			||||||
} from "@mdi/js";
 | 
					} from "@mdi/js";
 | 
				
			||||||
@@ -31,6 +34,7 @@ import "../../../../src/components/buttons/ha-progress-button";
 | 
				
			|||||||
import "../../../../src/components/ha-alert";
 | 
					import "../../../../src/components/ha-alert";
 | 
				
			||||||
import "../../../../src/components/ha-card";
 | 
					import "../../../../src/components/ha-card";
 | 
				
			||||||
import "../../../../src/components/ha-chip";
 | 
					import "../../../../src/components/ha-chip";
 | 
				
			||||||
 | 
					import "../../../../src/components/ha-chip-set";
 | 
				
			||||||
import "../../../../src/components/ha-markdown";
 | 
					import "../../../../src/components/ha-markdown";
 | 
				
			||||||
import "../../../../src/components/ha-settings-row";
 | 
					import "../../../../src/components/ha-settings-row";
 | 
				
			||||||
import "../../../../src/components/ha-svg-icon";
 | 
					import "../../../../src/components/ha-svg-icon";
 | 
				
			||||||
@@ -84,6 +88,8 @@ const RATING_ICON = {
 | 
				
			|||||||
  4: mdiNumeric4,
 | 
					  4: mdiNumeric4,
 | 
				
			||||||
  5: mdiNumeric5,
 | 
					  5: mdiNumeric5,
 | 
				
			||||||
  6: mdiNumeric6,
 | 
					  6: mdiNumeric6,
 | 
				
			||||||
 | 
					  7: mdiNumeric7,
 | 
				
			||||||
 | 
					  8: mdiNumeric8,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@customElement("hassio-addon-info")
 | 
					@customElement("hassio-addon-info")
 | 
				
			||||||
@@ -209,7 +215,7 @@ class HassioAddonInfo extends LitElement {
 | 
				
			|||||||
                >`}
 | 
					                >`}
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <div class="capabilities">
 | 
					          <ha-chip-set class="capabilities">
 | 
				
			||||||
            ${this.addon.stage !== "stable"
 | 
					            ${this.addon.stage !== "stable"
 | 
				
			||||||
              ? html` <ha-chip
 | 
					              ? html` <ha-chip
 | 
				
			||||||
                  hasIcon
 | 
					                  hasIcon
 | 
				
			||||||
@@ -234,9 +240,9 @@ class HassioAddonInfo extends LitElement {
 | 
				
			|||||||
            <ha-chip
 | 
					            <ha-chip
 | 
				
			||||||
              hasIcon
 | 
					              hasIcon
 | 
				
			||||||
              class=${classMap({
 | 
					              class=${classMap({
 | 
				
			||||||
                green: [5, 6].includes(Number(this.addon.rating)),
 | 
					                green: Number(this.addon.rating) >= 6,
 | 
				
			||||||
                yellow: [3, 4].includes(Number(this.addon.rating)),
 | 
					                yellow: [3, 4, 5].includes(Number(this.addon.rating)),
 | 
				
			||||||
                red: [1, 2].includes(Number(this.addon.rating)),
 | 
					                red: Number(this.addon.rating) >= 2,
 | 
				
			||||||
              })}
 | 
					              })}
 | 
				
			||||||
              @click=${this._showMoreInfo}
 | 
					              @click=${this._showMoreInfo}
 | 
				
			||||||
              id="rating"
 | 
					              id="rating"
 | 
				
			||||||
@@ -364,7 +370,17 @@ class HassioAddonInfo extends LitElement {
 | 
				
			|||||||
                  </ha-chip>
 | 
					                  </ha-chip>
 | 
				
			||||||
                `
 | 
					                `
 | 
				
			||||||
              : ""}
 | 
					              : ""}
 | 
				
			||||||
          </div>
 | 
					            ${this.addon.signed
 | 
				
			||||||
 | 
					              ? html`
 | 
				
			||||||
 | 
					                  <ha-chip hasIcon @click=${this._showMoreInfo} id="signed">
 | 
				
			||||||
 | 
					                    <ha-svg-icon slot="icon" .path=${mdiLinkLock}></ha-svg-icon>
 | 
				
			||||||
 | 
					                    ${this.supervisor.localize(
 | 
				
			||||||
 | 
					                      "addon.dashboard.capability.label.signed"
 | 
				
			||||||
 | 
					                    )}
 | 
				
			||||||
 | 
					                  </ha-chip>
 | 
				
			||||||
 | 
					                `
 | 
				
			||||||
 | 
					              : ""}
 | 
				
			||||||
 | 
					          </ha-chip-set>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <div class="description light-color">
 | 
					          <div class="description light-color">
 | 
				
			||||||
            ${this.addon.description}.<br />
 | 
					            ${this.addon.description}.<br />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,4 @@
 | 
				
			|||||||
import { mdiFolderUpload } from "@mdi/js";
 | 
					import { mdiFolderUpload } from "@mdi/js";
 | 
				
			||||||
import "@polymer/paper-input/paper-input-container";
 | 
					 | 
				
			||||||
import { html, LitElement, TemplateResult } from "lit";
 | 
					import { html, LitElement, TemplateResult } from "lit";
 | 
				
			||||||
import { customElement, state } from "lit/decorators";
 | 
					import { customElement, state } from "lit/decorators";
 | 
				
			||||||
import { fireEvent } from "../../../src/common/dom/fire_event";
 | 
					import { fireEvent } from "../../../src/common/dom/fire_event";
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
import { mdiFolder, mdiHomeAssistant, mdiPuzzle } from "@mdi/js";
 | 
					import { mdiFolder, mdiHomeAssistant, mdiPuzzle } from "@mdi/js";
 | 
				
			||||||
import { PaperInputElement } from "@polymer/paper-input/paper-input";
 | 
					import { PaperInputElement } from "@polymer/paper-input/paper-input";
 | 
				
			||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
 | 
					import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
 | 
				
			||||||
import { customElement, property } from "lit/decorators";
 | 
					import { customElement, property, query } from "lit/decorators";
 | 
				
			||||||
import { atLeastVersion } from "../../../src/common/config/version";
 | 
					import { atLeastVersion } from "../../../src/common/config/version";
 | 
				
			||||||
import { formatDate } from "../../../src/common/datetime/format_date";
 | 
					import { formatDate } from "../../../src/common/datetime/format_date";
 | 
				
			||||||
import { formatDateTime } from "../../../src/common/datetime/format_date_time";
 | 
					import { formatDateTime } from "../../../src/common/datetime/format_date_time";
 | 
				
			||||||
@@ -92,6 +92,8 @@ export class SupervisorBackupContent extends LitElement {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @property() public confirmBackupPassword = "";
 | 
					  @property() public confirmBackupPassword = "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @query("paper-input, ha-radio, ha-checkbox", true) private _focusTarget;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public willUpdate(changedProps) {
 | 
					  public willUpdate(changedProps) {
 | 
				
			||||||
    super.willUpdate(changedProps);
 | 
					    super.willUpdate(changedProps);
 | 
				
			||||||
    if (!this.hasUpdated) {
 | 
					    if (!this.hasUpdated) {
 | 
				
			||||||
@@ -109,6 +111,10 @@ export class SupervisorBackupContent extends LitElement {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public override focus() {
 | 
				
			||||||
 | 
					    this._focusTarget?.focus();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private _localize = (string: string) =>
 | 
					  private _localize = (string: string) =>
 | 
				
			||||||
    this.supervisor?.localize(`backup.${string}`) ||
 | 
					    this.supervisor?.localize(`backup.${string}`) ||
 | 
				
			||||||
    this.localize!(`ui.panel.page-onboarding.restore.${string}`);
 | 
					    this.localize!(`ui.panel.page-onboarding.restore.${string}`);
 | 
				
			||||||
@@ -169,13 +175,13 @@ export class SupervisorBackupContent extends LitElement {
 | 
				
			|||||||
        : ""}
 | 
					        : ""}
 | 
				
			||||||
      ${this.backupType === "partial"
 | 
					      ${this.backupType === "partial"
 | 
				
			||||||
        ? html`<div class="partial-picker">
 | 
					        ? html`<div class="partial-picker">
 | 
				
			||||||
            ${this.backup && this.backup.homeassistant
 | 
					 | 
				
			||||||
              ? html`
 | 
					 | 
				
			||||||
            <ha-formfield
 | 
					            <ha-formfield
 | 
				
			||||||
              .label=${html`<supervisor-formfield-label
 | 
					              .label=${html`<supervisor-formfield-label
 | 
				
			||||||
                label="Home Assistant"
 | 
					                label="Home Assistant"
 | 
				
			||||||
                .iconPath=${mdiHomeAssistant}
 | 
					                .iconPath=${mdiHomeAssistant}
 | 
				
			||||||
                      .version=${this.backup.homeassistant}
 | 
					                .version=${this.backup
 | 
				
			||||||
 | 
					                  ? this.backup.homeassistant
 | 
				
			||||||
 | 
					                  : this.hass.config.version}
 | 
				
			||||||
              >
 | 
					              >
 | 
				
			||||||
              </supervisor-formfield-label>`}
 | 
					              </supervisor-formfield-label>`}
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
@@ -185,8 +191,7 @@ export class SupervisorBackupContent extends LitElement {
 | 
				
			|||||||
              >
 | 
					              >
 | 
				
			||||||
              </ha-checkbox>
 | 
					              </ha-checkbox>
 | 
				
			||||||
            </ha-formfield>
 | 
					            </ha-formfield>
 | 
				
			||||||
                `
 | 
					
 | 
				
			||||||
              : ""}
 | 
					 | 
				
			||||||
            ${foldersSection?.templates.length
 | 
					            ${foldersSection?.templates.length
 | 
				
			||||||
              ? html`
 | 
					              ? html`
 | 
				
			||||||
                  <ha-formfield
 | 
					                  <ha-formfield
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -148,7 +148,6 @@ export class HassioUpdate extends LitElement {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        ha-settings-row {
 | 
					        ha-settings-row {
 | 
				
			||||||
          padding: 0;
 | 
					          padding: 0;
 | 
				
			||||||
          --paper-item-body-two-line-min-height: 32px;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      `,
 | 
					      `,
 | 
				
			||||||
    ];
 | 
					    ];
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -64,6 +64,7 @@ export class DialogHassioBackupUpload
 | 
				
			|||||||
              .path=${mdiClose}
 | 
					              .path=${mdiClose}
 | 
				
			||||||
              slot="actionItems"
 | 
					              slot="actionItems"
 | 
				
			||||||
              dialogAction="cancel"
 | 
					              dialogAction="cancel"
 | 
				
			||||||
 | 
					              dialogInitialFocus
 | 
				
			||||||
            ></ha-icon-button>
 | 
					            ></ha-icon-button>
 | 
				
			||||||
          </ha-header-bar>
 | 
					          </ha-header-bar>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -92,6 +92,7 @@ class HassioBackupDialog
 | 
				
			|||||||
              .backup=${this._backup}
 | 
					              .backup=${this._backup}
 | 
				
			||||||
              .onboarding=${this._dialogParams.onboarding || false}
 | 
					              .onboarding=${this._dialogParams.onboarding || false}
 | 
				
			||||||
              .localize=${this._dialogParams.localize}
 | 
					              .localize=${this._dialogParams.localize}
 | 
				
			||||||
 | 
					              dialogInitialFocus
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
            </supervisor-backup-content>`}
 | 
					            </supervisor-backup-content>`}
 | 
				
			||||||
        ${this._error
 | 
					        ${this._error
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -61,6 +61,7 @@ class HassioCreateBackupDialog extends LitElement {
 | 
				
			|||||||
          : html`<supervisor-backup-content
 | 
					          : html`<supervisor-backup-content
 | 
				
			||||||
              .hass=${this.hass}
 | 
					              .hass=${this.hass}
 | 
				
			||||||
              .supervisor=${this._dialogParams.supervisor}
 | 
					              .supervisor=${this._dialogParams.supervisor}
 | 
				
			||||||
 | 
					              dialogInitialFocus
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
            </supervisor-backup-content>`}
 | 
					            </supervisor-backup-content>`}
 | 
				
			||||||
        ${this._error
 | 
					        ${this._error
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +1,11 @@
 | 
				
			|||||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
 | 
					import "@material/mwc-list/mwc-list-item";
 | 
				
			||||||
import "@polymer/paper-item/paper-item";
 | 
					 | 
				
			||||||
import "@polymer/paper-listbox/paper-listbox";
 | 
					 | 
				
			||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
 | 
					import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
 | 
				
			||||||
import { customElement, property, state } from "lit/decorators";
 | 
					import { customElement, property, state } from "lit/decorators";
 | 
				
			||||||
import memoizeOne from "memoize-one";
 | 
					import memoizeOne from "memoize-one";
 | 
				
			||||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
 | 
					import { fireEvent } from "../../../../src/common/dom/fire_event";
 | 
				
			||||||
import "../../../../src/components/ha-circular-progress";
 | 
					import "../../../../src/components/ha-circular-progress";
 | 
				
			||||||
import "../../../../src/components/ha-markdown";
 | 
					import "../../../../src/components/ha-markdown";
 | 
				
			||||||
 | 
					import "../../../../src/components/ha-select";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  extractApiErrorMessage,
 | 
					  extractApiErrorMessage,
 | 
				
			||||||
  ignoreSupervisorError,
 | 
					  ignoreSupervisorError,
 | 
				
			||||||
@@ -90,18 +89,20 @@ class HassioDatadiskDialog extends LitElement {
 | 
				
			|||||||
                    )}
 | 
					                    )}
 | 
				
			||||||
                    <br /><br />
 | 
					                    <br /><br />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <paper-dropdown-menu
 | 
					                    <ha-select
 | 
				
			||||||
                      .label=${this.dialogParams.supervisor.localize(
 | 
					                      .label=${this.dialogParams.supervisor.localize(
 | 
				
			||||||
                        "dialog.datadisk_move.select_device"
 | 
					                        "dialog.datadisk_move.select_device"
 | 
				
			||||||
                      )}
 | 
					                      )}
 | 
				
			||||||
                      @value-changed=${this._select_device}
 | 
					                      @selected=${this._select_device}
 | 
				
			||||||
 | 
					                      dialogInitialFocus
 | 
				
			||||||
                    >
 | 
					                    >
 | 
				
			||||||
                      <paper-listbox slot="dropdown-content">
 | 
					 | 
				
			||||||
                      ${this.devices.map(
 | 
					                      ${this.devices.map(
 | 
				
			||||||
                          (device) => html`<paper-item>${device}</paper-item>`
 | 
					                        (device) =>
 | 
				
			||||||
 | 
					                          html`<mwc-list-item .value=${device}
 | 
				
			||||||
 | 
					                            >${device}</mwc-list-item
 | 
				
			||||||
 | 
					                          >`
 | 
				
			||||||
                      )}
 | 
					                      )}
 | 
				
			||||||
                      </paper-listbox>
 | 
					                    </ha-select>
 | 
				
			||||||
                    </paper-dropdown-menu>
 | 
					 | 
				
			||||||
                  `
 | 
					                  `
 | 
				
			||||||
                : this.devices === undefined
 | 
					                : this.devices === undefined
 | 
				
			||||||
                ? this.dialogParams.supervisor.localize(
 | 
					                ? this.dialogParams.supervisor.localize(
 | 
				
			||||||
@@ -111,7 +112,11 @@ class HassioDatadiskDialog extends LitElement {
 | 
				
			|||||||
                    "dialog.datadisk_move.no_devices"
 | 
					                    "dialog.datadisk_move.no_devices"
 | 
				
			||||||
                  )}
 | 
					                  )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              <mwc-button slot="secondaryAction" @click=${this.closeDialog}>
 | 
					              <mwc-button
 | 
				
			||||||
 | 
					                slot="secondaryAction"
 | 
				
			||||||
 | 
					                @click=${this.closeDialog}
 | 
				
			||||||
 | 
					                dialogInitialFocus
 | 
				
			||||||
 | 
					              >
 | 
				
			||||||
                ${this.dialogParams.supervisor.localize(
 | 
					                ${this.dialogParams.supervisor.localize(
 | 
				
			||||||
                  "dialog.datadisk_move.cancel"
 | 
					                  "dialog.datadisk_move.cancel"
 | 
				
			||||||
                )}
 | 
					                )}
 | 
				
			||||||
@@ -130,8 +135,8 @@ class HassioDatadiskDialog extends LitElement {
 | 
				
			|||||||
    `;
 | 
					    `;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private _select_device(event) {
 | 
					  private _select_device(ev) {
 | 
				
			||||||
    this.selectedDevice = event.detail.value;
 | 
					    this.selectedDevice = ev.target.value;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private async _moveDatadisk() {
 | 
					  private async _moveDatadisk() {
 | 
				
			||||||
@@ -156,7 +161,7 @@ class HassioDatadiskDialog extends LitElement {
 | 
				
			|||||||
      haStyle,
 | 
					      haStyle,
 | 
				
			||||||
      haStyleDialog,
 | 
					      haStyleDialog,
 | 
				
			||||||
      css`
 | 
					      css`
 | 
				
			||||||
        paper-dropdown-menu {
 | 
					        ha-select {
 | 
				
			||||||
          width: 100%;
 | 
					          width: 100%;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        ha-circular-progress {
 | 
					        ha-circular-progress {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
 | 
				
			|||||||
import { customElement, property, state } from "lit/decorators";
 | 
					import { customElement, property, state } from "lit/decorators";
 | 
				
			||||||
import memoizeOne from "memoize-one";
 | 
					import memoizeOne from "memoize-one";
 | 
				
			||||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
 | 
					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 { stringCompare } from "../../../../src/common/string/compare";
 | 
				
			||||||
import "../../../../src/components/ha-dialog";
 | 
					import "../../../../src/components/ha-dialog";
 | 
				
			||||||
import "../../../../src/components/ha-expansion-panel";
 | 
					import "../../../../src/components/ha-expansion-panel";
 | 
				
			||||||
@@ -80,8 +80,6 @@ class HassioHardwareDialog extends LitElement {
 | 
				
			|||||||
          ></ha-icon-button>
 | 
					          ></ha-icon-button>
 | 
				
			||||||
          <search-input
 | 
					          <search-input
 | 
				
			||||||
            .hass=${this.hass}
 | 
					            .hass=${this.hass}
 | 
				
			||||||
            autofocus
 | 
					 | 
				
			||||||
            no-label-float
 | 
					 | 
				
			||||||
            .filter=${this._filter}
 | 
					            .filter=${this._filter}
 | 
				
			||||||
            @value-changed=${this._handleSearchChange}
 | 
					            @value-changed=${this._handleSearchChange}
 | 
				
			||||||
            .label=${this._dialogParams.supervisor.localize(
 | 
					            .label=${this._dialogParams.supervisor.localize(
 | 
				
			||||||
@@ -178,7 +176,7 @@ class HassioHardwareDialog extends LitElement {
 | 
				
			|||||||
          padding: 0.2em 0.4em;
 | 
					          padding: 0.2em 0.4em;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        search-input {
 | 
					        search-input {
 | 
				
			||||||
          margin: 0 16px;
 | 
					          margin: 8px 16px 0;
 | 
				
			||||||
          display: block;
 | 
					          display: block;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        .device-property {
 | 
					        .device-property {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -37,7 +37,10 @@ class HassioMarkdownDialog extends LitElement {
 | 
				
			|||||||
        @closed=${this.closeDialog}
 | 
					        @closed=${this.closeDialog}
 | 
				
			||||||
        .heading=${createCloseHeading(this.hass, this.title)}
 | 
					        .heading=${createCloseHeading(this.hass, this.title)}
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        <ha-markdown .content=${this.content || ""}></ha-markdown>
 | 
					        <ha-markdown
 | 
				
			||||||
 | 
					          .content=${this.content || ""}
 | 
				
			||||||
 | 
					          dialogInitialFocus
 | 
				
			||||||
 | 
					        ></ha-markdown>
 | 
				
			||||||
      </ha-dialog>
 | 
					      </ha-dialog>
 | 
				
			||||||
    `;
 | 
					    `;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -119,6 +119,7 @@ export class DialogHassioNetwork
 | 
				
			|||||||
                    html`<mwc-tab
 | 
					                    html`<mwc-tab
 | 
				
			||||||
                      .id=${device.interface}
 | 
					                      .id=${device.interface}
 | 
				
			||||||
                      .label=${device.interface}
 | 
					                      .label=${device.interface}
 | 
				
			||||||
 | 
					                      dialogInitialFocus
 | 
				
			||||||
                    >
 | 
					                    >
 | 
				
			||||||
                    </mwc-tab>`
 | 
					                    </mwc-tab>`
 | 
				
			||||||
                )}
 | 
					                )}
 | 
				
			||||||
@@ -315,6 +316,7 @@ export class DialogHassioNetwork
 | 
				
			|||||||
              value="auto"
 | 
					              value="auto"
 | 
				
			||||||
              name="${version}method"
 | 
					              name="${version}method"
 | 
				
			||||||
              .checked=${this._interface![version]?.method === "auto"}
 | 
					              .checked=${this._interface![version]?.method === "auto"}
 | 
				
			||||||
 | 
					              dialogInitialFocus
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
            </ha-radio>
 | 
					            </ha-radio>
 | 
				
			||||||
          </ha-formfield>
 | 
					          </ha-formfield>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,22 +19,21 @@ import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
 | 
				
			|||||||
import type { HomeAssistant } from "../../../../src/types";
 | 
					import type { HomeAssistant } from "../../../../src/types";
 | 
				
			||||||
import { RegistriesDialogParams } from "./show-dialog-registries";
 | 
					import { RegistriesDialogParams } from "./show-dialog-registries";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const SCHEMA = [
 | 
					const SCHEMA: HaFormSchema[] = [
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    type: "string",
 | 
					 | 
				
			||||||
    name: "registry",
 | 
					    name: "registry",
 | 
				
			||||||
    required: true,
 | 
					    required: true,
 | 
				
			||||||
 | 
					    selector: { text: {} },
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    type: "string",
 | 
					 | 
				
			||||||
    name: "username",
 | 
					    name: "username",
 | 
				
			||||||
    required: true,
 | 
					    required: true,
 | 
				
			||||||
 | 
					    selector: { text: {} },
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    type: "string",
 | 
					 | 
				
			||||||
    name: "password",
 | 
					    name: "password",
 | 
				
			||||||
    required: true,
 | 
					    required: true,
 | 
				
			||||||
    format: "password",
 | 
					    selector: { text: { type: "password" } },
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -81,6 +80,7 @@ class HassioRegistriesDialog extends LitElement {
 | 
				
			|||||||
                .schema=${SCHEMA}
 | 
					                .schema=${SCHEMA}
 | 
				
			||||||
                @value-changed=${this._valueChanged}
 | 
					                @value-changed=${this._valueChanged}
 | 
				
			||||||
                .computeLabel=${this._computeLabel}
 | 
					                .computeLabel=${this._computeLabel}
 | 
				
			||||||
 | 
					                dialogInitialFocus
 | 
				
			||||||
              ></ha-form>
 | 
					              ></ha-form>
 | 
				
			||||||
              <div class="action">
 | 
					              <div class="action">
 | 
				
			||||||
                <mwc-button
 | 
					                <mwc-button
 | 
				
			||||||
@@ -125,7 +125,7 @@ class HassioRegistriesDialog extends LitElement {
 | 
				
			|||||||
                    </ha-alert>
 | 
					                    </ha-alert>
 | 
				
			||||||
                  `}
 | 
					                  `}
 | 
				
			||||||
              <div class="action">
 | 
					              <div class="action">
 | 
				
			||||||
                <mwc-button @click=${this._addRegistry}>
 | 
					                <mwc-button @click=${this._addRegistry} dialogInitialFocus>
 | 
				
			||||||
                  ${this.supervisor.localize(
 | 
					                  ${this.supervisor.localize(
 | 
				
			||||||
                    "dialog.registries.add_new_registry"
 | 
					                    "dialog.registries.add_new_registry"
 | 
				
			||||||
                  )}
 | 
					                  )}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -106,6 +106,9 @@ class HassioRepositoriesDialog extends LitElement {
 | 
				
			|||||||
                    </paper-item-body>
 | 
					                    </paper-item-body>
 | 
				
			||||||
                    <div class="delete">
 | 
					                    <div class="delete">
 | 
				
			||||||
                      <ha-icon-button
 | 
					                      <ha-icon-button
 | 
				
			||||||
 | 
					                        .label=${this._dialogParams!.supervisor.localize(
 | 
				
			||||||
 | 
					                          "dialog.repositories.remove"
 | 
				
			||||||
 | 
					                        )}
 | 
				
			||||||
                        .disabled=${usedRepositories.includes(repo.slug)}
 | 
					                        .disabled=${usedRepositories.includes(repo.slug)}
 | 
				
			||||||
                        .slug=${repo.slug}
 | 
					                        .slug=${repo.slug}
 | 
				
			||||||
                        .path=${usedRepositories.includes(repo.slug)
 | 
					                        .path=${usedRepositories.includes(repo.slug)
 | 
				
			||||||
@@ -139,6 +142,7 @@ class HassioRepositoriesDialog extends LitElement {
 | 
				
			|||||||
                "dialog.repositories.add"
 | 
					                "dialog.repositories.add"
 | 
				
			||||||
              )}
 | 
					              )}
 | 
				
			||||||
              @keydown=${this._handleKeyAdd}
 | 
					              @keydown=${this._handleKeyAdd}
 | 
				
			||||||
 | 
					              dialogInitialFocus
 | 
				
			||||||
            ></paper-input>
 | 
					            ></paper-input>
 | 
				
			||||||
            <mwc-button @click=${this._addRepository}>
 | 
					            <mwc-button @click=${this._addRepository}>
 | 
				
			||||||
              ${this._processing
 | 
					              ${this._processing
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,12 @@
 | 
				
			|||||||
// Compat needs to be first import
 | 
					// Compat needs to be first import
 | 
				
			||||||
import "../../src/resources/compatibility";
 | 
					import "../../src/resources/compatibility";
 | 
				
			||||||
 | 
					import { setCancelSyntheticClickEvents } from "@polymer/polymer/lib/utils/settings";
 | 
				
			||||||
import "../../src/resources/roboto";
 | 
					import "../../src/resources/roboto";
 | 
				
			||||||
import "../../src/resources/safari-14-attachshadow-patch";
 | 
					import "../../src/resources/safari-14-attachshadow-patch";
 | 
				
			||||||
import "./hassio-main";
 | 
					import "./hassio-main";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					setCancelSyntheticClickEvents(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const styleEl = document.createElement("style");
 | 
					const styleEl = document.createElement("style");
 | 
				
			||||||
styleEl.innerHTML = `
 | 
					styleEl.innerHTML = `
 | 
				
			||||||
body {
 | 
					body {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -121,7 +121,8 @@ export class HassioMain extends SupervisorBaseElement {
 | 
				
			|||||||
      this.parentElement,
 | 
					      this.parentElement,
 | 
				
			||||||
      this.hass.themes,
 | 
					      this.hass.themes,
 | 
				
			||||||
      themeName,
 | 
					      themeName,
 | 
				
			||||||
      themeSettings
 | 
					      themeSettings,
 | 
				
			||||||
 | 
					      true
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -205,16 +205,6 @@ class HassioCoreInfo extends LitElement {
 | 
				
			|||||||
          color: var(--secondary-text-color);
 | 
					          color: var(--secondary-text-color);
 | 
				
			||||||
          --mdc-menu-min-width: 200px;
 | 
					          --mdc-menu-min-width: 200px;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        @media (min-width: 563px) {
 | 
					 | 
				
			||||||
          paper-listbox {
 | 
					 | 
				
			||||||
            max-height: 150px;
 | 
					 | 
				
			||||||
            overflow: auto;
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        paper-item {
 | 
					 | 
				
			||||||
          cursor: pointer;
 | 
					 | 
				
			||||||
          min-height: 35px;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        mwc-list-item ha-svg-icon {
 | 
					        mwc-list-item ha-svg-icon {
 | 
				
			||||||
          color: var(--secondary-text-color);
 | 
					          color: var(--secondary-text-color);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -440,16 +440,6 @@ class HassioHostInfo extends LitElement {
 | 
				
			|||||||
          color: var(--secondary-text-color);
 | 
					          color: var(--secondary-text-color);
 | 
				
			||||||
          --mdc-menu-min-width: 200px;
 | 
					          --mdc-menu-min-width: 200px;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        @media (min-width: 563px) {
 | 
					 | 
				
			||||||
          paper-listbox {
 | 
					 | 
				
			||||||
            max-height: 150px;
 | 
					 | 
				
			||||||
            overflow: auto;
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        paper-item {
 | 
					 | 
				
			||||||
          cursor: pointer;
 | 
					 | 
				
			||||||
          min-height: 35px;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        mwc-list-item ha-svg-icon {
 | 
					        mwc-list-item ha-svg-icon {
 | 
				
			||||||
          color: var(--secondary-text-color);
 | 
					          color: var(--secondary-text-color);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +1,10 @@
 | 
				
			|||||||
import "@material/mwc-button";
 | 
					import "@material/mwc-button";
 | 
				
			||||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
 | 
					 | 
				
			||||||
import "@polymer/paper-item/paper-item";
 | 
					 | 
				
			||||||
import "@polymer/paper-listbox/paper-listbox";
 | 
					 | 
				
			||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
 | 
					import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
 | 
				
			||||||
import { customElement, property, state } from "lit/decorators";
 | 
					import { customElement, property, state } from "lit/decorators";
 | 
				
			||||||
import "../../../src/components/buttons/ha-progress-button";
 | 
					import "../../../src/components/buttons/ha-progress-button";
 | 
				
			||||||
import "../../../src/components/ha-alert";
 | 
					import "../../../src/components/ha-alert";
 | 
				
			||||||
import "../../../src/components/ha-card";
 | 
					import "../../../src/components/ha-card";
 | 
				
			||||||
 | 
					import "../../../src/components/ha-select";
 | 
				
			||||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
 | 
					import { extractApiErrorMessage } from "../../../src/data/hassio/common";
 | 
				
			||||||
import { fetchHassioLogs } from "../../../src/data/hassio/supervisor";
 | 
					import { fetchHassioLogs } from "../../../src/data/hassio/supervisor";
 | 
				
			||||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
 | 
					import { Supervisor } from "../../../src/data/supervisor/supervisor";
 | 
				
			||||||
@@ -73,24 +71,19 @@ class HassioSupervisorLog extends LitElement {
 | 
				
			|||||||
          : ""}
 | 
					          : ""}
 | 
				
			||||||
        ${this.hass.userData?.showAdvanced
 | 
					        ${this.hass.userData?.showAdvanced
 | 
				
			||||||
          ? html`
 | 
					          ? html`
 | 
				
			||||||
              <paper-dropdown-menu
 | 
					              <ha-select
 | 
				
			||||||
                .label=${this.supervisor.localize("system.log.log_provider")}
 | 
					                .label=${this.supervisor.localize("system.log.log_provider")}
 | 
				
			||||||
                @iron-select=${this._setLogProvider}
 | 
					                @selected=${this._setLogProvider}
 | 
				
			||||||
              >
 | 
					                .value=${this._selectedLogProvider}
 | 
				
			||||||
                <paper-listbox
 | 
					 | 
				
			||||||
                  slot="dropdown-content"
 | 
					 | 
				
			||||||
                  attr-for-selected="provider"
 | 
					 | 
				
			||||||
                  .selected=${this._selectedLogProvider}
 | 
					 | 
				
			||||||
              >
 | 
					              >
 | 
				
			||||||
                ${logProviders.map(
 | 
					                ${logProviders.map(
 | 
				
			||||||
                  (provider) => html`
 | 
					                  (provider) => html`
 | 
				
			||||||
                      <paper-item provider=${provider.key}>
 | 
					                    <mwc-list-item .value=${provider.key}>
 | 
				
			||||||
                      ${provider.name}
 | 
					                      ${provider.name}
 | 
				
			||||||
                      </paper-item>
 | 
					                    </mwc-list-item>
 | 
				
			||||||
                  `
 | 
					                  `
 | 
				
			||||||
                )}
 | 
					                )}
 | 
				
			||||||
                </paper-listbox>
 | 
					              </ha-select>
 | 
				
			||||||
              </paper-dropdown-menu>
 | 
					 | 
				
			||||||
            `
 | 
					            `
 | 
				
			||||||
          : ""}
 | 
					          : ""}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -110,7 +103,7 @@ class HassioSupervisorLog extends LitElement {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private async _setLogProvider(ev): Promise<void> {
 | 
					  private async _setLogProvider(ev): Promise<void> {
 | 
				
			||||||
    const provider = ev.detail.item.getAttribute("provider");
 | 
					    const provider = ev.target.value;
 | 
				
			||||||
    this._selectedLogProvider = provider;
 | 
					    this._selectedLogProvider = provider;
 | 
				
			||||||
    this._loadData();
 | 
					    this._loadData();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -153,9 +146,9 @@ class HassioSupervisorLog extends LitElement {
 | 
				
			|||||||
        pre {
 | 
					        pre {
 | 
				
			||||||
          white-space: pre-wrap;
 | 
					          white-space: pre-wrap;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        paper-dropdown-menu {
 | 
					        ha-select {
 | 
				
			||||||
          padding: 0 2%;
 | 
					          width: 100%;
 | 
				
			||||||
          width: 96%;
 | 
					          margin-bottom: 4px;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      `,
 | 
					      `,
 | 
				
			||||||
    ];
 | 
					    ];
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,7 +10,6 @@ import {
 | 
				
			|||||||
import { customElement, property, state } from "lit/decorators";
 | 
					import { customElement, property, state } from "lit/decorators";
 | 
				
			||||||
import memoizeOne from "memoize-one";
 | 
					import memoizeOne from "memoize-one";
 | 
				
			||||||
import { fireEvent } from "../../../src/common/dom/fire_event";
 | 
					import { fireEvent } from "../../../src/common/dom/fire_event";
 | 
				
			||||||
import "../../../src/common/search/search-input";
 | 
					 | 
				
			||||||
import "../../../src/components/buttons/ha-progress-button";
 | 
					import "../../../src/components/buttons/ha-progress-button";
 | 
				
			||||||
import "../../../src/components/ha-alert";
 | 
					import "../../../src/components/ha-alert";
 | 
				
			||||||
import "../../../src/components/ha-button-menu";
 | 
					import "../../../src/components/ha-button-menu";
 | 
				
			||||||
@@ -192,13 +191,7 @@ class UpdateAvailableCard extends LitElement {
 | 
				
			|||||||
                    </a>`
 | 
					                    </a>`
 | 
				
			||||||
                  : ""}
 | 
					                  : ""}
 | 
				
			||||||
                <span></span>
 | 
					                <span></span>
 | 
				
			||||||
                <ha-progress-button
 | 
					                <ha-progress-button @click=${this._update} raised>
 | 
				
			||||||
                  .disabled=${!this._version ||
 | 
					 | 
				
			||||||
                  (this._shouldCreateBackup &&
 | 
					 | 
				
			||||||
                    this.supervisor.info?.state !== "running")}
 | 
					 | 
				
			||||||
                  @click=${this._update}
 | 
					 | 
				
			||||||
                  raised
 | 
					 | 
				
			||||||
                >
 | 
					 | 
				
			||||||
                  ${this.supervisor.localize("common.update")}
 | 
					                  ${this.supervisor.localize("common.update")}
 | 
				
			||||||
                </ha-progress-button>
 | 
					                </ha-progress-button>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
@@ -360,8 +353,14 @@ class UpdateAvailableCard extends LitElement {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private async _update() {
 | 
					  private async _update() {
 | 
				
			||||||
 | 
					    if (this._shouldCreateBackup && this.supervisor.info.state === "freeze") {
 | 
				
			||||||
 | 
					      this._error = this.supervisor.localize("backup.backup_already_running");
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this._error = undefined;
 | 
					    this._error = undefined;
 | 
				
			||||||
    this._updating = true;
 | 
					    this._updating = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      if (this._updateType === "addon") {
 | 
					      if (this._updateType === "addon") {
 | 
				
			||||||
        await updateHassioAddon(
 | 
					        await updateHassioAddon(
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										66
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										66
									
								
								package.json
									
									
									
									
									
								
							@@ -1,8 +1,8 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "description": "A frontend for Home Assistant using the Polymer framework",
 | 
					  "description": "A frontend for Home Assistant",
 | 
				
			||||||
  "repository": {
 | 
					  "repository": {
 | 
				
			||||||
    "type": "git",
 | 
					    "type": "git",
 | 
				
			||||||
    "url": "https://github.com/home-assistant/home-assistant-polymer"
 | 
					    "url": "https://github.com/home-assistant/frontend"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "name": "home-assistant-frontend",
 | 
					  "name": "home-assistant-frontend",
 | 
				
			||||||
  "version": "1.0.0",
 | 
					  "version": "1.0.0",
 | 
				
			||||||
@@ -22,17 +22,18 @@
 | 
				
			|||||||
  "license": "Apache-2.0",
 | 
					  "license": "Apache-2.0",
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "@braintree/sanitize-url": "^5.0.2",
 | 
					    "@braintree/sanitize-url": "^5.0.2",
 | 
				
			||||||
    "@codemirror/commands": "^0.19.5",
 | 
					    "@codemirror/autocomplete": "^0.19.12",
 | 
				
			||||||
    "@codemirror/gutter": "^0.19.4",
 | 
					    "@codemirror/commands": "^0.19.8",
 | 
				
			||||||
    "@codemirror/highlight": "^0.19.6",
 | 
					    "@codemirror/gutter": "^0.19.9",
 | 
				
			||||||
    "@codemirror/history": "^0.19.0",
 | 
					    "@codemirror/highlight": "^0.19.7",
 | 
				
			||||||
 | 
					    "@codemirror/history": "^0.19.2",
 | 
				
			||||||
    "@codemirror/legacy-modes": "^0.19.0",
 | 
					    "@codemirror/legacy-modes": "^0.19.0",
 | 
				
			||||||
    "@codemirror/rectangular-selection": "^0.19.1",
 | 
					    "@codemirror/rectangular-selection": "^0.19.1",
 | 
				
			||||||
    "@codemirror/search": "^0.19.2",
 | 
					    "@codemirror/search": "^0.19.6",
 | 
				
			||||||
    "@codemirror/state": "^0.19.4",
 | 
					    "@codemirror/state": "^0.19.6",
 | 
				
			||||||
    "@codemirror/stream-parser": "^0.19.2",
 | 
					    "@codemirror/stream-parser": "^0.19.5",
 | 
				
			||||||
    "@codemirror/text": "^0.19.5",
 | 
					    "@codemirror/text": "^0.19.6",
 | 
				
			||||||
    "@codemirror/view": "^0.19.15",
 | 
					    "@codemirror/view": "^0.19.40",
 | 
				
			||||||
    "@formatjs/intl-datetimeformat": "^4.2.5",
 | 
					    "@formatjs/intl-datetimeformat": "^4.2.5",
 | 
				
			||||||
    "@formatjs/intl-getcanonicallocales": "^1.8.0",
 | 
					    "@formatjs/intl-getcanonicallocales": "^1.8.0",
 | 
				
			||||||
    "@formatjs/intl-locale": "^2.4.40",
 | 
					    "@formatjs/intl-locale": "^2.4.40",
 | 
				
			||||||
@@ -45,7 +46,8 @@
 | 
				
			|||||||
    "@fullcalendar/daygrid": "5.9.0",
 | 
					    "@fullcalendar/daygrid": "5.9.0",
 | 
				
			||||||
    "@fullcalendar/interaction": "5.9.0",
 | 
					    "@fullcalendar/interaction": "5.9.0",
 | 
				
			||||||
    "@fullcalendar/list": "5.9.0",
 | 
					    "@fullcalendar/list": "5.9.0",
 | 
				
			||||||
    "@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.6.0#./.yarn/patches/@lit-labs/virtualizer/0.7.0.patch",
 | 
					    "@lit-labs/motion": "^1.0.2",
 | 
				
			||||||
 | 
					    "@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.7.0-pre.2#./.yarn/patches/@lit-labs/virtualizer/event-target-shim.patch",
 | 
				
			||||||
    "@material/chips": "14.0.0-canary.261f2db59.0",
 | 
					    "@material/chips": "14.0.0-canary.261f2db59.0",
 | 
				
			||||||
    "@material/data-table": "14.0.0-canary.261f2db59.0",
 | 
					    "@material/data-table": "14.0.0-canary.261f2db59.0",
 | 
				
			||||||
    "@material/mwc-button": "0.25.3",
 | 
					    "@material/mwc-button": "0.25.3",
 | 
				
			||||||
@@ -57,7 +59,7 @@
 | 
				
			|||||||
    "@material/mwc-formfield": "0.25.3",
 | 
					    "@material/mwc-formfield": "0.25.3",
 | 
				
			||||||
    "@material/mwc-icon-button": "patch:@material/mwc-icon-button@0.25.3#./.yarn/patches/@material/mwc-icon-button/remove-icon.patch",
 | 
					    "@material/mwc-icon-button": "patch:@material/mwc-icon-button@0.25.3#./.yarn/patches/@material/mwc-icon-button/remove-icon.patch",
 | 
				
			||||||
    "@material/mwc-linear-progress": "0.25.3",
 | 
					    "@material/mwc-linear-progress": "0.25.3",
 | 
				
			||||||
    "@material/mwc-list": "0.25.3",
 | 
					    "@material/mwc-list": "^0.25.3",
 | 
				
			||||||
    "@material/mwc-menu": "0.25.3",
 | 
					    "@material/mwc-menu": "0.25.3",
 | 
				
			||||||
    "@material/mwc-radio": "0.25.3",
 | 
					    "@material/mwc-radio": "0.25.3",
 | 
				
			||||||
    "@material/mwc-ripple": "0.25.3",
 | 
					    "@material/mwc-ripple": "0.25.3",
 | 
				
			||||||
@@ -66,6 +68,7 @@
 | 
				
			|||||||
    "@material/mwc-switch": "0.25.3",
 | 
					    "@material/mwc-switch": "0.25.3",
 | 
				
			||||||
    "@material/mwc-tab": "0.25.3",
 | 
					    "@material/mwc-tab": "0.25.3",
 | 
				
			||||||
    "@material/mwc-tab-bar": "0.25.3",
 | 
					    "@material/mwc-tab-bar": "0.25.3",
 | 
				
			||||||
 | 
					    "@material/mwc-textarea": "^0.25.3",
 | 
				
			||||||
    "@material/mwc-textfield": "0.25.3",
 | 
					    "@material/mwc-textfield": "0.25.3",
 | 
				
			||||||
    "@material/mwc-top-app-bar-fixed": "^0.25.3",
 | 
					    "@material/mwc-top-app-bar-fixed": "^0.25.3",
 | 
				
			||||||
    "@material/top-app-bar": "14.0.0-canary.261f2db59.0",
 | 
					    "@material/top-app-bar": "14.0.0-canary.261f2db59.0",
 | 
				
			||||||
@@ -76,7 +79,6 @@
 | 
				
			|||||||
    "@polymer/iron-icon": "^3.0.1",
 | 
					    "@polymer/iron-icon": "^3.0.1",
 | 
				
			||||||
    "@polymer/iron-input": "^3.0.1",
 | 
					    "@polymer/iron-input": "^3.0.1",
 | 
				
			||||||
    "@polymer/iron-resizable-behavior": "^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-input": "^3.2.1",
 | 
				
			||||||
    "@polymer/paper-item": "^3.0.1",
 | 
					    "@polymer/paper-item": "^3.0.1",
 | 
				
			||||||
    "@polymer/paper-listbox": "^3.0.1",
 | 
					    "@polymer/paper-listbox": "^3.0.1",
 | 
				
			||||||
@@ -87,13 +89,15 @@
 | 
				
			|||||||
    "@polymer/paper-tooltip": "^3.0.1",
 | 
					    "@polymer/paper-tooltip": "^3.0.1",
 | 
				
			||||||
    "@polymer/polymer": "3.4.1",
 | 
					    "@polymer/polymer": "3.4.1",
 | 
				
			||||||
    "@thomasloven/round-slider": "0.5.4",
 | 
					    "@thomasloven/round-slider": "0.5.4",
 | 
				
			||||||
    "@vaadin/vaadin-combo-box": "^21.0.2",
 | 
					    "@vaadin/combo-box": "^22.0.4",
 | 
				
			||||||
    "@vaadin/vaadin-date-picker": "^21.0.2",
 | 
					    "@vaadin/vaadin-themable-mixin": "^22.0.4",
 | 
				
			||||||
    "@vibrant/color": "^3.2.1-alpha.1",
 | 
					    "@vibrant/color": "^3.2.1-alpha.1",
 | 
				
			||||||
    "@vibrant/core": "^3.2.1-alpha.1",
 | 
					    "@vibrant/core": "^3.2.1-alpha.1",
 | 
				
			||||||
    "@vibrant/quantizer-mmcq": "^3.2.1-alpha.1",
 | 
					    "@vibrant/quantizer-mmcq": "^3.2.1-alpha.1",
 | 
				
			||||||
    "@vue/web-component-wrapper": "^1.2.0",
 | 
					    "@vue/web-component-wrapper": "^1.2.0",
 | 
				
			||||||
 | 
					    "@webcomponents/scoped-custom-element-registry": "^0.0.5",
 | 
				
			||||||
    "@webcomponents/webcomponentsjs": "^2.2.10",
 | 
					    "@webcomponents/webcomponentsjs": "^2.2.10",
 | 
				
			||||||
 | 
					    "app-datepicker": "^5.0.1",
 | 
				
			||||||
    "chart.js": "^3.3.2",
 | 
					    "chart.js": "^3.3.2",
 | 
				
			||||||
    "comlink": "^4.3.1",
 | 
					    "comlink": "^4.3.1",
 | 
				
			||||||
    "core-js": "^3.15.2",
 | 
					    "core-js": "^3.15.2",
 | 
				
			||||||
@@ -103,16 +107,16 @@
 | 
				
			|||||||
    "deep-freeze": "^0.0.1",
 | 
					    "deep-freeze": "^0.0.1",
 | 
				
			||||||
    "fuse.js": "^6.0.0",
 | 
					    "fuse.js": "^6.0.0",
 | 
				
			||||||
    "google-timezones-json": "^1.0.2",
 | 
					    "google-timezones-json": "^1.0.2",
 | 
				
			||||||
    "hls.js": "^1.0.11",
 | 
					    "hls.js": "^1.1.5",
 | 
				
			||||||
    "home-assistant-js-websocket": "^6.0.1",
 | 
					    "home-assistant-js-websocket": "^7.0.1",
 | 
				
			||||||
    "idb-keyval": "^5.1.3",
 | 
					    "idb-keyval": "^5.1.3",
 | 
				
			||||||
    "intl-messageformat": "^9.9.1",
 | 
					    "intl-messageformat": "^9.9.1",
 | 
				
			||||||
    "js-yaml": "^4.1.0",
 | 
					    "js-yaml": "^4.1.0",
 | 
				
			||||||
    "leaflet": "^1.7.1",
 | 
					    "leaflet": "^1.7.1",
 | 
				
			||||||
    "leaflet-draw": "^1.0.4",
 | 
					    "leaflet-draw": "^1.0.4",
 | 
				
			||||||
    "lit": "^2.1.2",
 | 
					    "lit": "^2.1.2",
 | 
				
			||||||
    "lit-vaadin-helpers": "^0.2.1",
 | 
					    "lit-vaadin-helpers": "^0.3.0",
 | 
				
			||||||
    "marked": "^3.0.2",
 | 
					    "marked": "^4.0.12",
 | 
				
			||||||
    "memoize-one": "^5.2.1",
 | 
					    "memoize-one": "^5.2.1",
 | 
				
			||||||
    "node-vibrant": "3.2.1-alpha.1",
 | 
					    "node-vibrant": "3.2.1-alpha.1",
 | 
				
			||||||
    "proxy-polyfill": "^0.3.2",
 | 
					    "proxy-polyfill": "^0.3.2",
 | 
				
			||||||
@@ -131,13 +135,12 @@
 | 
				
			|||||||
    "vis-network": "^8.5.4",
 | 
					    "vis-network": "^8.5.4",
 | 
				
			||||||
    "vue": "^2.6.12",
 | 
					    "vue": "^2.6.12",
 | 
				
			||||||
    "vue2-daterange-picker": "^0.5.1",
 | 
					    "vue2-daterange-picker": "^0.5.1",
 | 
				
			||||||
    "web-animations-js": "^2.3.2",
 | 
					    "workbox-cacheable-response": "^6.4.2",
 | 
				
			||||||
    "workbox-cacheable-response": "^6.1.5",
 | 
					    "workbox-core": "^6.4.2",
 | 
				
			||||||
    "workbox-core": "^6.1.5",
 | 
					    "workbox-expiration": "^6.4.2",
 | 
				
			||||||
    "workbox-expiration": "^6.1.5",
 | 
					    "workbox-precaching": "^6.4.2",
 | 
				
			||||||
    "workbox-precaching": "^6.1.5",
 | 
					    "workbox-routing": "^6.4.2",
 | 
				
			||||||
    "workbox-routing": "^6.1.5",
 | 
					    "workbox-strategies": "^6.4.2",
 | 
				
			||||||
    "workbox-strategies": "^6.1.5",
 | 
					 | 
				
			||||||
    "xss": "^1.0.9"
 | 
					    "xss": "^1.0.9"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
@@ -166,7 +169,7 @@
 | 
				
			|||||||
    "@types/js-yaml": "^4",
 | 
					    "@types/js-yaml": "^4",
 | 
				
			||||||
    "@types/leaflet": "^1",
 | 
					    "@types/leaflet": "^1",
 | 
				
			||||||
    "@types/leaflet-draw": "^1",
 | 
					    "@types/leaflet-draw": "^1",
 | 
				
			||||||
    "@types/marked": "^2",
 | 
					    "@types/marked": "^4",
 | 
				
			||||||
    "@types/mocha": "^8",
 | 
					    "@types/mocha": "^8",
 | 
				
			||||||
    "@types/qrcode": "^1.4.2",
 | 
					    "@types/qrcode": "^1.4.2",
 | 
				
			||||||
    "@types/sortablejs": "^1",
 | 
					    "@types/sortablejs": "^1",
 | 
				
			||||||
@@ -193,7 +196,7 @@
 | 
				
			|||||||
    "fs-extra": "^7.0.1",
 | 
					    "fs-extra": "^7.0.1",
 | 
				
			||||||
    "glob": "^7.2.0",
 | 
					    "glob": "^7.2.0",
 | 
				
			||||||
    "gulp": "^4.0.2",
 | 
					    "gulp": "^4.0.2",
 | 
				
			||||||
    "gulp-foreach": "^0.1.0",
 | 
					    "gulp-flatmap": "^1.0.2",
 | 
				
			||||||
    "gulp-json-transform": "^0.4.6",
 | 
					    "gulp-json-transform": "^0.4.6",
 | 
				
			||||||
    "gulp-merge-json": "^1.3.1",
 | 
					    "gulp-merge-json": "^1.3.1",
 | 
				
			||||||
    "gulp-rename": "^2.0.0",
 | 
					    "gulp-rename": "^2.0.0",
 | 
				
			||||||
@@ -230,7 +233,7 @@
 | 
				
			|||||||
    "webpack-dev-server": "^4.3.0",
 | 
					    "webpack-dev-server": "^4.3.0",
 | 
				
			||||||
    "webpack-manifest-plugin": "^4.0.2",
 | 
					    "webpack-manifest-plugin": "^4.0.2",
 | 
				
			||||||
    "webpackbar": "^5.0.0-3",
 | 
					    "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",
 | 
					  "_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch",
 | 
				
			||||||
  "resolutions": {
 | 
					  "resolutions": {
 | 
				
			||||||
@@ -250,5 +253,6 @@
 | 
				
			|||||||
  "prettier": {
 | 
					  "prettier": {
 | 
				
			||||||
    "trailingComma": "es5",
 | 
					    "trailingComma": "es5",
 | 
				
			||||||
    "arrowParens": "always"
 | 
					    "arrowParens": "always"
 | 
				
			||||||
  }
 | 
					  },
 | 
				
			||||||
 | 
					  "packageManager": "yarn@3.2.0"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,6 @@
 | 
				
			|||||||
from pathlib import Path
 | 
					from pathlib import Path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def where():
 | 
					def where() -> Path:
 | 
				
			||||||
    """Return path to the frontend."""
 | 
					    """Return path to the frontend."""
 | 
				
			||||||
    return Path(__file__).parent
 | 
					    return Path(__file__).parent
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										0
									
								
								public/py.typed
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								public/py.typed
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										18
									
								
								script/core
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								script/core
									
									
									
									
									
								
							@@ -4,6 +4,8 @@
 | 
				
			|||||||
# Stop on errors
 | 
					# Stop on errors
 | 
				
			||||||
set -e
 | 
					set -e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					WD="${WORKSPACE_DIRECTORY:=/workspaces/frontend}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if [ -z "${DEVCONTAINER}" ]; then
 | 
					if [ -z "${DEVCONTAINER}" ]; then
 | 
				
			||||||
  echo "This task should only run inside a devcontainer, for local install HA Core in a venv."
 | 
					  echo "This task should only run inside a devcontainer, for local install HA Core in a venv."
 | 
				
			||||||
  exit 1
 | 
					  exit 1
 | 
				
			||||||
@@ -16,9 +18,9 @@ if [ -z $(which hass) ]; then
 | 
				
			|||||||
    git+git://github.com/home-assistant/home-assistant.git@dev
 | 
					    git+git://github.com/home-assistant/home-assistant.git@dev
 | 
				
			||||||
fi
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if [ ! -d "/workspaces/frontend/config" ]; then
 | 
					if [ ! -d "${WD}/config" ]; then
 | 
				
			||||||
  echo "Creating default configuration."
 | 
					  echo "Creating default configuration."
 | 
				
			||||||
  mkdir -p "/workspaces/frontend/config";
 | 
					  mkdir -p "${WD}/config";
 | 
				
			||||||
  hass --script ensure_config -c config
 | 
					  hass --script ensure_config -c config
 | 
				
			||||||
  echo "demo:
 | 
					  echo "demo:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -26,24 +28,24 @@ logger:
 | 
				
			|||||||
  default: info
 | 
					  default: info
 | 
				
			||||||
  logs:
 | 
					  logs:
 | 
				
			||||||
    homeassistant.components.frontend: debug
 | 
					    homeassistant.components.frontend: debug
 | 
				
			||||||
" >> /workspaces/frontend/config/configuration.yaml
 | 
					" >> "${WD}/config/configuration.yaml"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if [ ! -z "${HASSIO}" ]; then
 | 
					  if [ ! -z "${HASSIO}" ]; then
 | 
				
			||||||
  echo "
 | 
					  echo "
 | 
				
			||||||
# frontend:
 | 
					# frontend:
 | 
				
			||||||
#   development_repo: /workspaces/frontend
 | 
					#   development_repo: ${WD}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
hassio:
 | 
					hassio:
 | 
				
			||||||
  development_repo: /workspaces/frontend" >> /workspaces/frontend/config/configuration.yaml
 | 
					  development_repo: ${WD}" >> "${WD}/config/configuration.yaml"
 | 
				
			||||||
  else
 | 
					  else
 | 
				
			||||||
  echo "
 | 
					  echo "
 | 
				
			||||||
frontend:
 | 
					frontend:
 | 
				
			||||||
  development_repo: /workspaces/frontend
 | 
					  development_repo: ${WD}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# hassio:
 | 
					# hassio:
 | 
				
			||||||
#   development_repo: /workspaces/frontend" >> /workspaces/frontend/config/configuration.yaml
 | 
					#   development_repo: ${WD}" >> "${WD}/config/configuration.yaml"
 | 
				
			||||||
  fi
 | 
					  fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fi
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
hass -c /workspaces/frontend/config
 | 
					hass -c "${WD}/config"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
[metadata]
 | 
					[metadata]
 | 
				
			||||||
name         = home-assistant-frontend
 | 
					name         = home-assistant-frontend
 | 
				
			||||||
version      = 20220203.0
 | 
					version      = 20220317.0
 | 
				
			||||||
author       = The Home Assistant Authors
 | 
					author       = The Home Assistant Authors
 | 
				
			||||||
author_email = hello@home-assistant.io
 | 
					author_email = hello@home-assistant.io
 | 
				
			||||||
license      = Apache-2.0
 | 
					license      = Apache-2.0
 | 
				
			||||||
@@ -19,3 +19,8 @@ python_requires = >= 3.4.0
 | 
				
			|||||||
[options.packages.find]
 | 
					[options.packages.find]
 | 
				
			||||||
include =
 | 
					include =
 | 
				
			||||||
    hass_frontend*
 | 
					    hass_frontend*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[mypy]
 | 
				
			||||||
 | 
					python_version = 3.4
 | 
				
			||||||
 | 
					show_error_codes = True
 | 
				
			||||||
 | 
					strict = True
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -101,13 +101,19 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
 | 
				
			|||||||
    this._fetchAuthProviders();
 | 
					    this._fetchAuthProviders();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (matchMedia("(prefers-color-scheme: dark)").matches) {
 | 
					    if (matchMedia("(prefers-color-scheme: dark)").matches) {
 | 
				
			||||||
      applyThemesOnElement(document.documentElement, {
 | 
					      applyThemesOnElement(
 | 
				
			||||||
 | 
					        document.documentElement,
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
          default_theme: "default",
 | 
					          default_theme: "default",
 | 
				
			||||||
          default_dark_theme: null,
 | 
					          default_dark_theme: null,
 | 
				
			||||||
          themes: {},
 | 
					          themes: {},
 | 
				
			||||||
          darkMode: true,
 | 
					          darkMode: true,
 | 
				
			||||||
          theme: "default",
 | 
					          theme: "default",
 | 
				
			||||||
      });
 | 
					        },
 | 
				
			||||||
 | 
					        undefined,
 | 
				
			||||||
 | 
					        undefined,
 | 
				
			||||||
 | 
					        true
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!this.redirectUri) {
 | 
					    if (!this.redirectUri) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,11 @@
 | 
				
			|||||||
import { HaDurationData } from "../../components/ha-duration-input";
 | 
					import type { HaDurationData } from "../../components/ha-duration-input";
 | 
				
			||||||
import { ForDict } from "../../data/automation";
 | 
					import type { ForDict } from "../../data/automation";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const createDurationData = (
 | 
					export const createDurationData = (
 | 
				
			||||||
  duration: string | number | ForDict | undefined
 | 
					  duration: string | number | ForDict | undefined
 | 
				
			||||||
): HaDurationData => {
 | 
					): HaDurationData | undefined => {
 | 
				
			||||||
  if (duration === undefined) {
 | 
					  if (duration === undefined) {
 | 
				
			||||||
    return {};
 | 
					    return undefined;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (typeof duration !== "object") {
 | 
					  if (typeof duration !== "object") {
 | 
				
			||||||
    if (typeof duration === "string" || isNaN(duration)) {
 | 
					    if (typeof duration === "string" || isNaN(duration)) {
 | 
				
			||||||
@@ -19,6 +19,9 @@ export const createDurationData = (
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    return { seconds: duration };
 | 
					    return { seconds: duration };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  if (!("days" in duration)) {
 | 
				
			||||||
 | 
					    return duration;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  const { days, minutes, seconds, milliseconds } = duration;
 | 
					  const { days, minutes, seconds, milliseconds } = duration;
 | 
				
			||||||
  let hours = duration.hours || 0;
 | 
					  let hours = duration.hours || 0;
 | 
				
			||||||
  hours = (hours || 0) + (days || 0) * 24;
 | 
					  hours = (hours || 0) + (days || 0) * 24;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,11 +31,12 @@ export const applyThemesOnElement = (
 | 
				
			|||||||
  element,
 | 
					  element,
 | 
				
			||||||
  themes: HomeAssistant["themes"],
 | 
					  themes: HomeAssistant["themes"],
 | 
				
			||||||
  selectedTheme?: string,
 | 
					  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`.
 | 
					  // 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
 | 
					  // If there is no explicitly desired dark mode provided, we automatically
 | 
				
			||||||
  // use the active one from `themes`.
 | 
					  // use the active one from `themes`.
 | 
				
			||||||
@@ -47,7 +48,7 @@ export const applyThemesOnElement = (
 | 
				
			|||||||
  let cacheKey = themeToApply;
 | 
					  let cacheKey = themeToApply;
 | 
				
			||||||
  let themeRules: Partial<ThemeVars> = {};
 | 
					  let themeRules: Partial<ThemeVars> = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (darkMode) {
 | 
					  if (themeToApply && darkMode) {
 | 
				
			||||||
    cacheKey = `${cacheKey}__dark`;
 | 
					    cacheKey = `${cacheKey}__dark`;
 | 
				
			||||||
    themeRules = { ...darkStyles };
 | 
					    themeRules = { ...darkStyles };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
import { HomeAssistant } from "../../types";
 | 
					import type { HomeAssistant } from "../../types";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const canToggleDomain = (hass: HomeAssistant, domain: string) => {
 | 
					export const canToggleDomain = (hass: HomeAssistant, domain: string) => {
 | 
				
			||||||
  const services = hass.services[domain];
 | 
					  const services = hass.services[domain];
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,14 +1,30 @@
 | 
				
			|||||||
import { HassEntity } from "home-assistant-js-websocket";
 | 
					import type { HassEntity } from "home-assistant-js-websocket";
 | 
				
			||||||
import { HomeAssistant } from "../../types";
 | 
					import type { HomeAssistant } from "../../types";
 | 
				
			||||||
import { canToggleDomain } from "./can_toggle_domain";
 | 
					import { canToggleDomain } from "./can_toggle_domain";
 | 
				
			||||||
import { computeStateDomain } from "./compute_state_domain";
 | 
					import { computeStateDomain } from "./compute_state_domain";
 | 
				
			||||||
import { supportsFeature } from "./supports-feature";
 | 
					import { supportsFeature } from "./supports-feature";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const canToggleState = (hass: HomeAssistant, stateObj: HassEntity) => {
 | 
					export const canToggleState = (hass: HomeAssistant, stateObj: HassEntity) => {
 | 
				
			||||||
  const domain = computeStateDomain(stateObj);
 | 
					  const domain = computeStateDomain(stateObj);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (domain === "group") {
 | 
					  if (domain === "group") {
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					      stateObj.attributes?.entity_id?.some((entity) => {
 | 
				
			||||||
 | 
					        const entityStateObj = hass.states[entity];
 | 
				
			||||||
 | 
					        if (!entityStateObj) {
 | 
				
			||||||
 | 
					          return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const entityDomain = computeStateDomain(entityStateObj);
 | 
				
			||||||
 | 
					        return canToggleDomain(hass, entityDomain);
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
      return stateObj.state === "on" || stateObj.state === "off";
 | 
					      return stateObj.state === "on" || stateObj.state === "off";
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (domain === "climate") {
 | 
					  if (domain === "climate") {
 | 
				
			||||||
    return supportsFeature(stateObj, 4096);
 | 
					    return supportsFeature(stateObj, 4096);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -123,7 +123,11 @@ export const computeStateDisplay = (
 | 
				
			|||||||
    domain === "scene" ||
 | 
					    domain === "scene" ||
 | 
				
			||||||
    (domain === "sensor" && stateObj.attributes.device_class === "timestamp")
 | 
					    (domain === "sensor" && stateObj.attributes.device_class === "timestamp")
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
      return formatDateTime(new Date(compareState), locale);
 | 
					      return formatDateTime(new Date(compareState), locale);
 | 
				
			||||||
 | 
					    } catch (_err) {
 | 
				
			||||||
 | 
					      return compareState;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -120,6 +120,7 @@ export const computeOpenIcon = (stateObj: HassEntity): string => {
 | 
				
			|||||||
    case "awning":
 | 
					    case "awning":
 | 
				
			||||||
    case "door":
 | 
					    case "door":
 | 
				
			||||||
    case "gate":
 | 
					    case "gate":
 | 
				
			||||||
 | 
					    case "curtain":
 | 
				
			||||||
      return mdiArrowExpandHorizontal;
 | 
					      return mdiArrowExpandHorizontal;
 | 
				
			||||||
    default:
 | 
					    default:
 | 
				
			||||||
      return mdiArrowUp;
 | 
					      return mdiArrowUp;
 | 
				
			||||||
@@ -131,6 +132,7 @@ export const computeCloseIcon = (stateObj: HassEntity): string => {
 | 
				
			|||||||
    case "awning":
 | 
					    case "awning":
 | 
				
			||||||
    case "door":
 | 
					    case "door":
 | 
				
			||||||
    case "gate":
 | 
					    case "gate":
 | 
				
			||||||
 | 
					    case "curtain":
 | 
				
			||||||
      return mdiArrowCollapseHorizontal;
 | 
					      return mdiArrowCollapseHorizontal;
 | 
				
			||||||
    default:
 | 
					    default:
 | 
				
			||||||
      return mdiArrowDown;
 | 
					      return mdiArrowDown;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,11 +9,10 @@ import {
 | 
				
			|||||||
  mdiCast,
 | 
					  mdiCast,
 | 
				
			||||||
  mdiCastConnected,
 | 
					  mdiCastConnected,
 | 
				
			||||||
  mdiClock,
 | 
					  mdiClock,
 | 
				
			||||||
  mdiEmoticonDead,
 | 
					 | 
				
			||||||
  mdiFlash,
 | 
					 | 
				
			||||||
  mdiGestureTapButton,
 | 
					  mdiGestureTapButton,
 | 
				
			||||||
  mdiLanConnect,
 | 
					  mdiLanConnect,
 | 
				
			||||||
  mdiLanDisconnect,
 | 
					  mdiLanDisconnect,
 | 
				
			||||||
 | 
					  mdiLightSwitch,
 | 
				
			||||||
  mdiLock,
 | 
					  mdiLock,
 | 
				
			||||||
  mdiLockAlert,
 | 
					  mdiLockAlert,
 | 
				
			||||||
  mdiLockClock,
 | 
					  mdiLockClock,
 | 
				
			||||||
@@ -22,14 +21,11 @@ import {
 | 
				
			|||||||
  mdiPowerPlug,
 | 
					  mdiPowerPlug,
 | 
				
			||||||
  mdiPowerPlugOff,
 | 
					  mdiPowerPlugOff,
 | 
				
			||||||
  mdiRestart,
 | 
					  mdiRestart,
 | 
				
			||||||
  mdiSleep,
 | 
					 | 
				
			||||||
  mdiTimerSand,
 | 
					 | 
				
			||||||
  mdiToggleSwitch,
 | 
					  mdiToggleSwitch,
 | 
				
			||||||
  mdiToggleSwitchOff,
 | 
					  mdiToggleSwitchOff,
 | 
				
			||||||
  mdiCheckCircleOutline,
 | 
					  mdiCheckCircleOutline,
 | 
				
			||||||
  mdiCloseCircleOutline,
 | 
					  mdiCloseCircleOutline,
 | 
				
			||||||
  mdiWeatherNight,
 | 
					  mdiWeatherNight,
 | 
				
			||||||
  mdiZWave,
 | 
					 | 
				
			||||||
} from "@mdi/js";
 | 
					} from "@mdi/js";
 | 
				
			||||||
import { HassEntity } from "home-assistant-js-websocket";
 | 
					import { HassEntity } from "home-assistant-js-websocket";
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@@ -112,19 +108,7 @@ export const domainIcon = (
 | 
				
			|||||||
        case "switch":
 | 
					        case "switch":
 | 
				
			||||||
          return compareState === "on" ? mdiToggleSwitch : mdiToggleSwitchOff;
 | 
					          return compareState === "on" ? mdiToggleSwitch : mdiToggleSwitchOff;
 | 
				
			||||||
        default:
 | 
					        default:
 | 
				
			||||||
          return mdiFlash;
 | 
					          return mdiLightSwitch;
 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    case "zwave":
 | 
					 | 
				
			||||||
      switch (compareState) {
 | 
					 | 
				
			||||||
        case "dead":
 | 
					 | 
				
			||||||
          return mdiEmoticonDead;
 | 
					 | 
				
			||||||
        case "sleeping":
 | 
					 | 
				
			||||||
          return mdiSleep;
 | 
					 | 
				
			||||||
        case "initializing":
 | 
					 | 
				
			||||||
          return mdiTimerSand;
 | 
					 | 
				
			||||||
        default:
 | 
					 | 
				
			||||||
          return mdiZWave;
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    case "sensor": {
 | 
					    case "sensor": {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,24 +1,32 @@
 | 
				
			|||||||
 | 
					const SUFFIXES = [" ", ": "];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Strips a device name from an entity name.
 | 
					 * Strips a device name from an entity name.
 | 
				
			||||||
 * @param entityName the entity name
 | 
					 * @param entityName the entity name
 | 
				
			||||||
 * @param lowerCasedPrefixWithSpaceSuffix the prefix to strip, lower cased with a space suffix
 | 
					 * @param lowerCasedPrefix the prefix to strip, lower cased
 | 
				
			||||||
 * @returns
 | 
					 * @returns
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export const stripPrefixFromEntityName = (
 | 
					export const stripPrefixFromEntityName = (
 | 
				
			||||||
  entityName: string,
 | 
					  entityName: string,
 | 
				
			||||||
  lowerCasedPrefixWithSpaceSuffix: string
 | 
					  lowerCasedPrefix: string
 | 
				
			||||||
) => {
 | 
					) => {
 | 
				
			||||||
  if (!entityName.toLowerCase().startsWith(lowerCasedPrefixWithSpaceSuffix)) {
 | 
					  const lowerCasedEntityName = entityName.toLowerCase();
 | 
				
			||||||
    return undefined;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const newName = entityName.substring(lowerCasedPrefixWithSpaceSuffix.length);
 | 
					  for (const suffix of SUFFIXES) {
 | 
				
			||||||
 | 
					    const lowerCasedPrefixWithSuffix = `${lowerCasedPrefix}${suffix}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (lowerCasedEntityName.startsWith(lowerCasedPrefixWithSuffix)) {
 | 
				
			||||||
 | 
					      const newName = entityName.substring(lowerCasedPrefixWithSuffix.length);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // If first word already has an upper case letter (e.g. from brand name)
 | 
					      // If first word already has an upper case letter (e.g. from brand name)
 | 
				
			||||||
      // leave as-is, otherwise capitalize the first word.
 | 
					      // leave as-is, otherwise capitalize the first word.
 | 
				
			||||||
      return hasUpperCase(newName.substr(0, newName.indexOf(" ")))
 | 
					      return hasUpperCase(newName.substr(0, newName.indexOf(" ")))
 | 
				
			||||||
        ? newName
 | 
					        ? newName
 | 
				
			||||||
        : newName[0].toUpperCase() + newName.slice(1);
 | 
					        : newName[0].toUpperCase() + newName.slice(1);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return undefined;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const hasUpperCase = (str: string): boolean => str.toLowerCase() !== str;
 | 
					const hasUpperCase = (str: string): boolean => str.toLowerCase() !== str;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,112 +0,0 @@
 | 
				
			|||||||
import { mdiClose, mdiMagnify } from "@mdi/js";
 | 
					 | 
				
			||||||
import "@polymer/paper-input/paper-input";
 | 
					 | 
				
			||||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
 | 
					 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  css,
 | 
					 | 
				
			||||||
  CSSResultGroup,
 | 
					 | 
				
			||||||
  html,
 | 
					 | 
				
			||||||
  LitElement,
 | 
					 | 
				
			||||||
  PropertyValues,
 | 
					 | 
				
			||||||
  TemplateResult,
 | 
					 | 
				
			||||||
} from "lit";
 | 
					 | 
				
			||||||
import { customElement, property, query } from "lit/decorators";
 | 
					 | 
				
			||||||
import "../../components/ha-icon-button";
 | 
					 | 
				
			||||||
import "../../components/ha-svg-icon";
 | 
					 | 
				
			||||||
import { HomeAssistant } from "../../types";
 | 
					 | 
				
			||||||
import { fireEvent } from "../dom/fire_event";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@customElement("search-input")
 | 
					 | 
				
			||||||
class SearchInput extends LitElement {
 | 
					 | 
				
			||||||
  @property({ attribute: false }) public hass!: HomeAssistant;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @property() public filter?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @property({ type: Boolean, attribute: "no-label-float" })
 | 
					 | 
				
			||||||
  public noLabelFloat? = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @property({ type: Boolean, attribute: "no-underline" })
 | 
					 | 
				
			||||||
  public noUnderline = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @property({ type: Boolean })
 | 
					 | 
				
			||||||
  public autofocus = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @property({ type: String })
 | 
					 | 
				
			||||||
  public label?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  public focus() {
 | 
					 | 
				
			||||||
    this.shadowRoot!.querySelector("paper-input")!.focus();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @query("paper-input", true) private _input!: PaperInputElement;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  protected render(): TemplateResult {
 | 
					 | 
				
			||||||
    return html`
 | 
					 | 
				
			||||||
      <paper-input
 | 
					 | 
				
			||||||
        .autofocus=${this.autofocus}
 | 
					 | 
				
			||||||
        .label=${this.label || "Search"}
 | 
					 | 
				
			||||||
        .value=${this.filter}
 | 
					 | 
				
			||||||
        @value-changed=${this._filterInputChanged}
 | 
					 | 
				
			||||||
        .noLabelFloat=${this.noLabelFloat}
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <slot name="prefix" slot="prefix">
 | 
					 | 
				
			||||||
          <ha-svg-icon class="prefix" .path=${mdiMagnify}></ha-svg-icon>
 | 
					 | 
				
			||||||
        </slot>
 | 
					 | 
				
			||||||
        ${this.filter &&
 | 
					 | 
				
			||||||
        html`
 | 
					 | 
				
			||||||
          <ha-icon-button
 | 
					 | 
				
			||||||
            slot="suffix"
 | 
					 | 
				
			||||||
            @click=${this._clearSearch}
 | 
					 | 
				
			||||||
            .label=${this.hass.localize("ui.common.clear")}
 | 
					 | 
				
			||||||
            .path=${mdiClose}
 | 
					 | 
				
			||||||
          ></ha-icon-button>
 | 
					 | 
				
			||||||
        `}
 | 
					 | 
				
			||||||
      </paper-input>
 | 
					 | 
				
			||||||
    `;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  protected updated(changedProps: PropertyValues) {
 | 
					 | 
				
			||||||
    if (
 | 
					 | 
				
			||||||
      changedProps.has("noUnderline") &&
 | 
					 | 
				
			||||||
      (this.noUnderline || changedProps.get("noUnderline") !== undefined)
 | 
					 | 
				
			||||||
    ) {
 | 
					 | 
				
			||||||
      (
 | 
					 | 
				
			||||||
        this._input.inputElement!.parentElement!.shadowRoot!.querySelector(
 | 
					 | 
				
			||||||
          "div.unfocused-line"
 | 
					 | 
				
			||||||
        ) as HTMLElement
 | 
					 | 
				
			||||||
      ).style.display = this.noUnderline ? "none" : "block";
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private async _filterChanged(value: string) {
 | 
					 | 
				
			||||||
    fireEvent(this, "value-changed", { value: String(value) });
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private async _filterInputChanged(e) {
 | 
					 | 
				
			||||||
    this._filterChanged(e.target.value);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private async _clearSearch() {
 | 
					 | 
				
			||||||
    this._filterChanged("");
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  static get styles(): CSSResultGroup {
 | 
					 | 
				
			||||||
    return css`
 | 
					 | 
				
			||||||
      ha-svg-icon,
 | 
					 | 
				
			||||||
      ha-icon-button {
 | 
					 | 
				
			||||||
        color: var(--primary-text-color);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      ha-icon-button {
 | 
					 | 
				
			||||||
        --mdc-icon-button-size: 24px;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      ha-svg-icon.prefix {
 | 
					 | 
				
			||||||
        margin: 8px;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    `;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
declare global {
 | 
					 | 
				
			||||||
  interface HTMLElementTagNameMap {
 | 
					 | 
				
			||||||
    "search-input": SearchInput;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										4
									
								
								src/common/string/is_ip_address.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/common/string/is_ip_address.ts
									
									
									
									
									
										Normal 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);
 | 
				
			||||||
@@ -15,6 +15,7 @@ export const iconColorCSS = css`
 | 
				
			|||||||
  ha-state-icon[data-domain="media_player"][data-state="on"],
 | 
					  ha-state-icon[data-domain="media_player"][data-state="on"],
 | 
				
			||||||
  ha-state-icon[data-domain="media_player"][data-state="paused"],
 | 
					  ha-state-icon[data-domain="media_player"][data-state="paused"],
 | 
				
			||||||
  ha-state-icon[data-domain="media_player"][data-state="playing"],
 | 
					  ha-state-icon[data-domain="media_player"][data-state="playing"],
 | 
				
			||||||
 | 
					  ha-state-icon[data-domain="remote"][data-state="on"],
 | 
				
			||||||
  ha-state-icon[data-domain="script"][data-state="on"],
 | 
					  ha-state-icon[data-domain="script"][data-state="on"],
 | 
				
			||||||
  ha-state-icon[data-domain="sun"][data-state="above_horizon"],
 | 
					  ha-state-icon[data-domain="sun"][data-state="above_horizon"],
 | 
				
			||||||
  ha-state-icon[data-domain="switch"][data-state="on"],
 | 
					  ha-state-icon[data-domain="switch"][data-state="on"],
 | 
				
			||||||
@@ -69,9 +70,6 @@ export const iconColorCSS = css`
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ha-state-icon[data-domain="plant"][data-state="problem"],
 | 
					  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 */
 | 
					  /* Color the icon if unavailable */
 | 
				
			||||||
  ha-state-icon[data-state="unavailable"] {
 | 
					  ha-state-icon[data-state="unavailable"] {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,7 +11,7 @@ export const debounce = <T extends any[]>(
 | 
				
			|||||||
  immediate = false
 | 
					  immediate = false
 | 
				
			||||||
) => {
 | 
					) => {
 | 
				
			||||||
  let timeout: number | undefined;
 | 
					  let timeout: number | undefined;
 | 
				
			||||||
  return (...args: T): void => {
 | 
					  const debouncedFunc = (...args: T): void => {
 | 
				
			||||||
    const later = () => {
 | 
					    const later = () => {
 | 
				
			||||||
      timeout = undefined;
 | 
					      timeout = undefined;
 | 
				
			||||||
      if (!immediate) {
 | 
					      if (!immediate) {
 | 
				
			||||||
@@ -25,4 +25,8 @@ export const debounce = <T extends any[]>(
 | 
				
			|||||||
      func(...args);
 | 
					      func(...args);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					  debouncedFunc.cancel = () => {
 | 
				
			||||||
 | 
					    clearTimeout(timeout);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  return debouncedFunc;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,9 @@
 | 
				
			|||||||
import "@material/mwc-button";
 | 
					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 { 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-circular-progress";
 | 
				
			||||||
 | 
					import "../ha-svg-icon";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@customElement("ha-progress-button")
 | 
					@customElement("ha-progress-button")
 | 
				
			||||||
export class HaProgressButton extends LitElement {
 | 
					export class HaProgressButton extends LitElement {
 | 
				
			||||||
@@ -12,38 +13,53 @@ export class HaProgressButton extends LitElement {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @property({ type: Boolean }) public raised = false;
 | 
					  @property({ type: Boolean }) public raised = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @query("mwc-button", true) private _button?: Button;
 | 
					  @state() private _result?: "success" | "error";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public render(): TemplateResult {
 | 
					  public render(): TemplateResult {
 | 
				
			||||||
 | 
					    const overlay = this._result || this.progress;
 | 
				
			||||||
    return html`
 | 
					    return html`
 | 
				
			||||||
      <mwc-button
 | 
					      <mwc-button
 | 
				
			||||||
        ?raised=${this.raised}
 | 
					        ?raised=${this.raised}
 | 
				
			||||||
        .disabled=${this.disabled || this.progress}
 | 
					        .disabled=${this.disabled || this.progress}
 | 
				
			||||||
        @click=${this._buttonTapped}
 | 
					        @click=${this._buttonTapped}
 | 
				
			||||||
 | 
					        class=${this._result || ""}
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        <slot></slot>
 | 
					        <slot></slot>
 | 
				
			||||||
      </mwc-button>
 | 
					      </mwc-button>
 | 
				
			||||||
      ${this.progress
 | 
					      ${!overlay
 | 
				
			||||||
        ? html`<div class="progress">
 | 
					        ? ""
 | 
				
			||||||
            <ha-circular-progress size="small" active></ha-circular-progress>
 | 
					        : html`
 | 
				
			||||||
          </div>`
 | 
					            <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 {
 | 
					  public actionSuccess(): void {
 | 
				
			||||||
    this._tempClass("success");
 | 
					    this._setResult("success");
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public actionError(): void {
 | 
					  public actionError(): void {
 | 
				
			||||||
    this._tempClass("error");
 | 
					    this._setResult("error");
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private _tempClass(className: string): void {
 | 
					  private _setResult(result: "success" | "error"): void {
 | 
				
			||||||
    this._button!.classList.add(className);
 | 
					    this._result = result;
 | 
				
			||||||
    setTimeout(() => {
 | 
					    setTimeout(() => {
 | 
				
			||||||
      this._button!.classList.remove(className);
 | 
					      this._result = undefined;
 | 
				
			||||||
    }, 1000);
 | 
					    }, 2000);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private _buttonTapped(ev: Event): void {
 | 
					  private _buttonTapped(ev: Event): void {
 | 
				
			||||||
@@ -69,6 +85,7 @@ export class HaProgressButton extends LitElement {
 | 
				
			|||||||
        background-color: var(--success-color);
 | 
					        background-color: var(--success-color);
 | 
				
			||||||
        transition: none;
 | 
					        transition: none;
 | 
				
			||||||
        border-radius: 4px;
 | 
					        border-radius: 4px;
 | 
				
			||||||
 | 
					        pointer-events: none;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      mwc-button[raised].success {
 | 
					      mwc-button[raised].success {
 | 
				
			||||||
@@ -81,6 +98,7 @@ export class HaProgressButton extends LitElement {
 | 
				
			|||||||
        background-color: var(--error-color);
 | 
					        background-color: var(--error-color);
 | 
				
			||||||
        transition: none;
 | 
					        transition: none;
 | 
				
			||||||
        border-radius: 4px;
 | 
					        border-radius: 4px;
 | 
				
			||||||
 | 
					        pointer-events: none;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      mwc-button[raised].error {
 | 
					      mwc-button[raised].error {
 | 
				
			||||||
@@ -89,13 +107,21 @@ export class HaProgressButton extends LitElement {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      .progress {
 | 
					      .progress {
 | 
				
			||||||
        bottom: 0;
 | 
					        bottom: 4px;
 | 
				
			||||||
        margin-top: 4px;
 | 
					 | 
				
			||||||
        position: absolute;
 | 
					        position: absolute;
 | 
				
			||||||
        text-align: center;
 | 
					        text-align: center;
 | 
				
			||||||
        top: 0;
 | 
					        top: 4px;
 | 
				
			||||||
        width: 100%;
 | 
					        width: 100%;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      ha-svg-icon {
 | 
				
			||||||
 | 
					        color: white;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      mwc-button.success slot,
 | 
				
			||||||
 | 
					      mwc-button.error slot {
 | 
				
			||||||
 | 
					        visibility: hidden;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    `;
 | 
					    `;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,3 @@
 | 
				
			|||||||
import { Layout1d, scroll } from "@lit-labs/virtualizer";
 | 
					 | 
				
			||||||
import { mdiArrowDown, mdiArrowUp } from "@mdi/js";
 | 
					import { mdiArrowDown, mdiArrowUp } from "@mdi/js";
 | 
				
			||||||
import deepClone from "deep-clone-simple";
 | 
					import deepClone from "deep-clone-simple";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
@@ -22,7 +21,7 @@ import { styleMap } from "lit/directives/style-map";
 | 
				
			|||||||
import memoizeOne from "memoize-one";
 | 
					import memoizeOne from "memoize-one";
 | 
				
			||||||
import { restoreScroll } from "../../common/decorators/restore-scroll";
 | 
					import { restoreScroll } from "../../common/decorators/restore-scroll";
 | 
				
			||||||
import { fireEvent } from "../../common/dom/fire_event";
 | 
					import { fireEvent } from "../../common/dom/fire_event";
 | 
				
			||||||
import "../../common/search/search-input";
 | 
					import "../search-input";
 | 
				
			||||||
import { debounce } from "../../common/util/debounce";
 | 
					import { debounce } from "../../common/util/debounce";
 | 
				
			||||||
import { nextRender } from "../../common/util/render-status";
 | 
					import { nextRender } from "../../common/util/render-status";
 | 
				
			||||||
import { haStyleScrollbar } from "../../resources/styles";
 | 
					import { haStyleScrollbar } from "../../resources/styles";
 | 
				
			||||||
@@ -31,6 +30,7 @@ import type { HaCheckbox } from "../ha-checkbox";
 | 
				
			|||||||
import "../ha-svg-icon";
 | 
					import "../ha-svg-icon";
 | 
				
			||||||
import { filterData, sortData } from "./sort-filter";
 | 
					import { filterData, sortData } from "./sort-filter";
 | 
				
			||||||
import { HomeAssistant } from "../../types";
 | 
					import { HomeAssistant } from "../../types";
 | 
				
			||||||
 | 
					import "@lit-labs/virtualizer";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
declare global {
 | 
					declare global {
 | 
				
			||||||
  // for fire event
 | 
					  // for fire event
 | 
				
			||||||
@@ -70,6 +70,7 @@ export interface DataTableSortColumnData {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export interface DataTableColumnData<T = any> extends DataTableSortColumnData {
 | 
					export interface DataTableColumnData<T = any> extends DataTableSortColumnData {
 | 
				
			||||||
  title: TemplateResult | string;
 | 
					  title: TemplateResult | string;
 | 
				
			||||||
 | 
					  label?: TemplateResult | string;
 | 
				
			||||||
  type?: "numeric" | "icon" | "icon-button" | "overflow-menu";
 | 
					  type?: "numeric" | "icon" | "icon-button" | "overflow-menu";
 | 
				
			||||||
  template?: (data: any, row: T) => TemplateResult | string;
 | 
					  template?: (data: any, row: T) => TemplateResult | string;
 | 
				
			||||||
  width?: string;
 | 
					  width?: string;
 | 
				
			||||||
@@ -294,6 +295,7 @@ export class HaDataTable extends LitElement {
 | 
				
			|||||||
              };
 | 
					              };
 | 
				
			||||||
              return html`
 | 
					              return html`
 | 
				
			||||||
                <div
 | 
					                <div
 | 
				
			||||||
 | 
					                  aria-label=${column.label}
 | 
				
			||||||
                  class="mdc-data-table__header-cell ${classMap(classes)}"
 | 
					                  class="mdc-data-table__header-cell ${classMap(classes)}"
 | 
				
			||||||
                  style=${column.width
 | 
					                  style=${column.width
 | 
				
			||||||
                    ? styleMap({
 | 
					                    ? styleMap({
 | 
				
			||||||
@@ -337,41 +339,47 @@ export class HaDataTable extends LitElement {
 | 
				
			|||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
              `
 | 
					              `
 | 
				
			||||||
            : html`
 | 
					            : html`
 | 
				
			||||||
                <div
 | 
					                <lit-virtualizer
 | 
				
			||||||
 | 
					                  scroller
 | 
				
			||||||
                  class="mdc-data-table__content scroller ha-scrollbar"
 | 
					                  class="mdc-data-table__content scroller ha-scrollbar"
 | 
				
			||||||
                  @scroll=${this._saveScrollPos}
 | 
					                  @scroll=${this._saveScrollPos}
 | 
				
			||||||
                >
 | 
					                  .items=${this._items}
 | 
				
			||||||
                  ${scroll({
 | 
					                  .renderItem=${this._renderRow}
 | 
				
			||||||
                    items: this._items,
 | 
					                ></lit-virtualizer>
 | 
				
			||||||
                    layout: Layout1d,
 | 
					              `}
 | 
				
			||||||
                    renderItem: (row: DataTableRowData, index) => {
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    `;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private _renderRow = (
 | 
				
			||||||
 | 
					    row: DataTableRowData,
 | 
				
			||||||
 | 
					    index: number
 | 
				
			||||||
 | 
					  ): TemplateResult => {
 | 
				
			||||||
    // not sure how this happens...
 | 
					    // not sure how this happens...
 | 
				
			||||||
    if (!row) {
 | 
					    if (!row) {
 | 
				
			||||||
      return html``;
 | 
					      return html``;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (row.append) {
 | 
					    if (row.append) {
 | 
				
			||||||
                        return html`
 | 
					      return html` <div class="mdc-data-table__row">${row.content}</div> `;
 | 
				
			||||||
                          <div class="mdc-data-table__row">${row.content}</div>
 | 
					 | 
				
			||||||
                        `;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (row.empty) {
 | 
					    if (row.empty) {
 | 
				
			||||||
      return html` <div class="mdc-data-table__row"></div> `;
 | 
					      return html` <div class="mdc-data-table__row"></div> `;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return html`
 | 
					    return html`
 | 
				
			||||||
      <div
 | 
					      <div
 | 
				
			||||||
                          aria-rowindex=${index! + 2}
 | 
					        aria-rowindex=${index + 2}
 | 
				
			||||||
        role="row"
 | 
					        role="row"
 | 
				
			||||||
        .rowId=${row[this.id]}
 | 
					        .rowId=${row[this.id]}
 | 
				
			||||||
        @click=${this._handleRowClick}
 | 
					        @click=${this._handleRowClick}
 | 
				
			||||||
        class="mdc-data-table__row ${classMap({
 | 
					        class="mdc-data-table__row ${classMap({
 | 
				
			||||||
                            "mdc-data-table__row--selected":
 | 
					          "mdc-data-table__row--selected": this._checkedRows.includes(
 | 
				
			||||||
                              this._checkedRows.includes(String(row[this.id])),
 | 
					            String(row[this.id])
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
          clickable: this.clickable,
 | 
					          clickable: this.clickable,
 | 
				
			||||||
        })}"
 | 
					        })}"
 | 
				
			||||||
        aria-selected=${ifDefined(
 | 
					        aria-selected=${ifDefined(
 | 
				
			||||||
                            this._checkedRows.includes(String(row[this.id]))
 | 
					          this._checkedRows.includes(String(row[this.id])) ? true : undefined
 | 
				
			||||||
                              ? true
 | 
					 | 
				
			||||||
                              : undefined
 | 
					 | 
				
			||||||
        )}
 | 
					        )}
 | 
				
			||||||
        .selectable=${row.selectable !== false}
 | 
					        .selectable=${row.selectable !== false}
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
@@ -386,16 +394,13 @@ export class HaDataTable extends LitElement {
 | 
				
			|||||||
                  @change=${this._handleRowCheckboxClick}
 | 
					                  @change=${this._handleRowCheckboxClick}
 | 
				
			||||||
                  .rowId=${row[this.id]}
 | 
					                  .rowId=${row[this.id]}
 | 
				
			||||||
                  .disabled=${row.selectable === false}
 | 
					                  .disabled=${row.selectable === false}
 | 
				
			||||||
                                    .checked=${this._checkedRows.includes(
 | 
					                  .checked=${this._checkedRows.includes(String(row[this.id]))}
 | 
				
			||||||
                                      String(row[this.id])
 | 
					 | 
				
			||||||
                                    )}
 | 
					 | 
				
			||||||
                >
 | 
					                >
 | 
				
			||||||
                </ha-checkbox>
 | 
					                </ha-checkbox>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            `
 | 
					            `
 | 
				
			||||||
          : ""}
 | 
					          : ""}
 | 
				
			||||||
                          ${Object.entries(this.columns).map(
 | 
					        ${Object.entries(this.columns).map(([key, column]) => {
 | 
				
			||||||
                            ([key, column]) => {
 | 
					 | 
				
			||||||
          if (column.hidden) {
 | 
					          if (column.hidden) {
 | 
				
			||||||
            return "";
 | 
					            return "";
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
@@ -403,10 +408,8 @@ export class HaDataTable extends LitElement {
 | 
				
			|||||||
            <div
 | 
					            <div
 | 
				
			||||||
              role="cell"
 | 
					              role="cell"
 | 
				
			||||||
              class="mdc-data-table__cell ${classMap({
 | 
					              class="mdc-data-table__cell ${classMap({
 | 
				
			||||||
                                    "mdc-data-table__cell--numeric":
 | 
					                "mdc-data-table__cell--numeric": column.type === "numeric",
 | 
				
			||||||
                                      column.type === "numeric",
 | 
					                "mdc-data-table__cell--icon": column.type === "icon",
 | 
				
			||||||
                                    "mdc-data-table__cell--icon":
 | 
					 | 
				
			||||||
                                      column.type === "icon",
 | 
					 | 
				
			||||||
                "mdc-data-table__cell--icon-button":
 | 
					                "mdc-data-table__cell--icon-button":
 | 
				
			||||||
                  column.type === "icon-button",
 | 
					                  column.type === "icon-button",
 | 
				
			||||||
                "mdc-data-table__cell--overflow-menu":
 | 
					                "mdc-data-table__cell--overflow-menu":
 | 
				
			||||||
@@ -416,31 +419,18 @@ export class HaDataTable extends LitElement {
 | 
				
			|||||||
              })}"
 | 
					              })}"
 | 
				
			||||||
              style=${column.width
 | 
					              style=${column.width
 | 
				
			||||||
                ? styleMap({
 | 
					                ? styleMap({
 | 
				
			||||||
                                        [column.grows ? "minWidth" : "width"]:
 | 
					                    [column.grows ? "minWidth" : "width"]: column.width,
 | 
				
			||||||
                                          column.width,
 | 
					                    maxWidth: column.maxWidth ? column.maxWidth : "",
 | 
				
			||||||
                                        maxWidth: column.maxWidth
 | 
					 | 
				
			||||||
                                          ? column.maxWidth
 | 
					 | 
				
			||||||
                                          : "",
 | 
					 | 
				
			||||||
                  })
 | 
					                  })
 | 
				
			||||||
                : ""}
 | 
					                : ""}
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
                                  ${column.template
 | 
					              ${column.template ? column.template(row[key], row) : row[key]}
 | 
				
			||||||
                                    ? column.template(row[key], row)
 | 
					 | 
				
			||||||
                                    : row[key]}
 | 
					 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          `;
 | 
					          `;
 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                          )}
 | 
					 | 
				
			||||||
                        </div>
 | 
					 | 
				
			||||||
                      `;
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
        })}
 | 
					        })}
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
              `}
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
    `;
 | 
					    `;
 | 
				
			||||||
  }
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private async _sortFilterData() {
 | 
					  private async _sortFilterData() {
 | 
				
			||||||
    const startTime = new Date().getTime();
 | 
					    const startTime = new Date().getTime();
 | 
				
			||||||
@@ -536,7 +526,7 @@ export class HaDataTable extends LitElement {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private _handleRowCheckboxClick(ev: Event) {
 | 
					  private _handleRowCheckboxClick = (ev: Event) => {
 | 
				
			||||||
    const checkbox = ev.currentTarget as HaCheckbox;
 | 
					    const checkbox = ev.currentTarget as HaCheckbox;
 | 
				
			||||||
    const rowId = (checkbox as any).rowId;
 | 
					    const rowId = (checkbox as any).rowId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -549,16 +539,16 @@ export class HaDataTable extends LitElement {
 | 
				
			|||||||
      this._checkedRows = this._checkedRows.filter((row) => row !== rowId);
 | 
					      this._checkedRows = this._checkedRows.filter((row) => row !== rowId);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this._checkedRowsChanged();
 | 
					    this._checkedRowsChanged();
 | 
				
			||||||
  }
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private _handleRowClick(ev: Event) {
 | 
					  private _handleRowClick = (ev: Event) => {
 | 
				
			||||||
    const target = ev.target as HTMLElement;
 | 
					    const target = ev.target as HTMLElement;
 | 
				
			||||||
    if (["HA-CHECKBOX", "MWC-BUTTON"].includes(target.tagName)) {
 | 
					    if (["HA-CHECKBOX", "MWC-BUTTON"].includes(target.tagName)) {
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const rowId = (ev.currentTarget as any).rowId;
 | 
					    const rowId = (ev.currentTarget as any).rowId;
 | 
				
			||||||
    fireEvent(this, "row-click", { id: rowId }, { bubbles: false });
 | 
					    fireEvent(this, "row-click", { id: rowId }, { bubbles: false });
 | 
				
			||||||
  }
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private _checkedRowsChanged() {
 | 
					  private _checkedRowsChanged() {
 | 
				
			||||||
    // force scroller to update, change it's items
 | 
					    // force scroller to update, change it's items
 | 
				
			||||||
@@ -571,6 +561,9 @@ export class HaDataTable extends LitElement {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private _handleSearchChange(ev: CustomEvent): void {
 | 
					  private _handleSearchChange(ev: CustomEvent): void {
 | 
				
			||||||
 | 
					    if (this.filter) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    this._debounceSearch(ev.detail.value);
 | 
					    this._debounceSearch(ev.detail.value);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -935,11 +928,10 @@ export class HaDataTable extends LitElement {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        .table-header {
 | 
					        .table-header {
 | 
				
			||||||
          border-bottom: 1px solid var(--divider-color);
 | 
					          border-bottom: 1px solid var(--divider-color);
 | 
				
			||||||
          padding: 0 16px;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        search-input {
 | 
					        search-input {
 | 
				
			||||||
          position: relative;
 | 
					          display: block;
 | 
				
			||||||
          top: 2px;
 | 
					          flex: 1;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        slot[name="header"] {
 | 
					        slot[name="header"] {
 | 
				
			||||||
          display: block;
 | 
					          display: block;
 | 
				
			||||||
@@ -952,6 +944,7 @@ export class HaDataTable extends LitElement {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        .scroller {
 | 
					        .scroller {
 | 
				
			||||||
          height: calc(100% - 57px);
 | 
					          height: calc(100% - 57px);
 | 
				
			||||||
 | 
					          overflow: overlay !important;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        .mdc-data-table__table.auto-height .scroller {
 | 
					        .mdc-data-table__table.auto-height .scroller {
 | 
				
			||||||
@@ -967,6 +960,9 @@ export class HaDataTable extends LitElement {
 | 
				
			|||||||
        .clickable {
 | 
					        .clickable {
 | 
				
			||||||
          cursor: pointer;
 | 
					          cursor: pointer;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        lit-virtualizer {
 | 
				
			||||||
 | 
					          contain: size layout !important;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      `,
 | 
					      `,
 | 
				
			||||||
    ];
 | 
					    ];
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -115,6 +115,9 @@ class DateRangePickerElement extends WrappedElement {
 | 
				
			|||||||
            color: var(--primary-text-color);
 | 
					            color: var(--primary-text-color);
 | 
				
			||||||
            min-width: initial !important;
 | 
					            min-width: initial !important;
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					          .daterangepicker:before {
 | 
				
			||||||
 | 
					            display: none;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
          .daterangepicker:after {
 | 
					          .daterangepicker:after {
 | 
				
			||||||
            border-bottom: 6px solid var(--card-background-color);
 | 
					            border-bottom: 6px solid var(--card-background-color);
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,20 +1,7 @@
 | 
				
			|||||||
import "@material/mwc-button/mwc-button";
 | 
					import "@material/mwc-button/mwc-button";
 | 
				
			||||||
import { mdiCheck, mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
 | 
					 | 
				
			||||||
import "@polymer/paper-input/paper-input";
 | 
					 | 
				
			||||||
import "@polymer/paper-item/paper-item";
 | 
					 | 
				
			||||||
import "@polymer/paper-item/paper-item-body";
 | 
					 | 
				
			||||||
import "@polymer/paper-listbox/paper-listbox";
 | 
					 | 
				
			||||||
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
 | 
					 | 
				
			||||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
 | 
					import { UnsubscribeFunc } from "home-assistant-js-websocket";
 | 
				
			||||||
import {
 | 
					import { html, LitElement, PropertyValues, TemplateResult } from "lit";
 | 
				
			||||||
  css,
 | 
					import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
 | 
				
			||||||
  CSSResultGroup,
 | 
					 | 
				
			||||||
  html,
 | 
					 | 
				
			||||||
  LitElement,
 | 
					 | 
				
			||||||
  PropertyValues,
 | 
					 | 
				
			||||||
  TemplateResult,
 | 
					 | 
				
			||||||
} from "lit";
 | 
					 | 
				
			||||||
import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers";
 | 
					 | 
				
			||||||
import { customElement, property, state } from "lit/decorators";
 | 
					import { customElement, property, state } from "lit/decorators";
 | 
				
			||||||
import memoizeOne from "memoize-one";
 | 
					import memoizeOne from "memoize-one";
 | 
				
			||||||
import { fireEvent } from "../../common/dom/fire_event";
 | 
					import { fireEvent } from "../../common/dom/fire_event";
 | 
				
			||||||
@@ -50,36 +37,12 @@ interface AreaDevices {
 | 
				
			|||||||
  devices: string[];
 | 
					  devices: string[];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// eslint-disable-next-line lit/prefer-static-styles
 | 
					const rowRenderer: ComboBoxLitRenderer<AreaDevices> = (
 | 
				
			||||||
const rowRenderer: ComboBoxLitRenderer<AreaDevices> = (item) => html`<style>
 | 
					  item
 | 
				
			||||||
    paper-item {
 | 
					) => html`<mwc-list-item twoline>
 | 
				
			||||||
      padding: 0;
 | 
					  <span>${item.name}</span>
 | 
				
			||||||
      margin: -10px;
 | 
					  <span slot="secondary">${item.devices.length} devices</span>
 | 
				
			||||||
      margin-left: 0;
 | 
					</mwc-list-item>`;
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    #content {
 | 
					 | 
				
			||||||
      display: flex;
 | 
					 | 
				
			||||||
      align-items: center;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    ha-svg-icon {
 | 
					 | 
				
			||||||
      padding-left: 2px;
 | 
					 | 
				
			||||||
      margin-right: -2px;
 | 
					 | 
				
			||||||
      color: var(--secondary-text-color);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    :host(:not([selected])) ha-svg-icon {
 | 
					 | 
				
			||||||
      display: none;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    :host([selected]) paper-item {
 | 
					 | 
				
			||||||
      margin-left: 10px;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  </style>
 | 
					 | 
				
			||||||
  <ha-svg-icon .path=${mdiCheck}></ha-svg-icon>
 | 
					 | 
				
			||||||
  <paper-item>
 | 
					 | 
				
			||||||
    <paper-item-body two-line="">
 | 
					 | 
				
			||||||
      <div class="name">${item.name}</div>
 | 
					 | 
				
			||||||
      <div secondary>${item.devices.length} devices</div>
 | 
					 | 
				
			||||||
    </paper-item-body>
 | 
					 | 
				
			||||||
  </paper-item>`;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
@customElement("ha-area-devices-picker")
 | 
					@customElement("ha-area-devices-picker")
 | 
				
			||||||
export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
 | 
					export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
 | 
				
			||||||
@@ -117,9 +80,6 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
 | 
				
			|||||||
  @property({ type: Array, attribute: "include-device-classes" })
 | 
					  @property({ type: Array, attribute: "include-device-classes" })
 | 
				
			||||||
  public includeDeviceClasses?: string[];
 | 
					  public includeDeviceClasses?: string[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @property({ type: Boolean })
 | 
					 | 
				
			||||||
  private _opened?: boolean;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @state() private _areaPicker = true;
 | 
					  @state() private _areaPicker = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @state() private _devices?: DeviceRegistryEntry[];
 | 
					  @state() private _devices?: DeviceRegistryEntry[];
 | 
				
			||||||
@@ -302,71 +262,30 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
 | 
				
			|||||||
      `;
 | 
					      `;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return html`
 | 
					    return html`
 | 
				
			||||||
      <vaadin-combo-box-light
 | 
					      <ha-combo-box
 | 
				
			||||||
 | 
					        .hass=${this.hass}
 | 
				
			||||||
        item-value-path="id"
 | 
					        item-value-path="id"
 | 
				
			||||||
        item-id-path="id"
 | 
					        item-id-path="id"
 | 
				
			||||||
        item-label-path="name"
 | 
					        item-label-path="name"
 | 
				
			||||||
        .items=${areas}
 | 
					        .items=${areas}
 | 
				
			||||||
        .value=${this._value}
 | 
					        .value=${this._value}
 | 
				
			||||||
        ${comboBoxRenderer(rowRenderer)}
 | 
					        .renderer=${rowRenderer}
 | 
				
			||||||
        @opened-changed=${this._openedChanged}
 | 
					 | 
				
			||||||
        @value-changed=${this._areaPicked}
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <paper-input
 | 
					 | 
				
			||||||
        .label=${this.label === undefined && this.hass
 | 
					        .label=${this.label === undefined && this.hass
 | 
				
			||||||
          ? this.hass.localize("ui.components.device-picker.device")
 | 
					          ? this.hass.localize("ui.components.device-picker.device")
 | 
				
			||||||
          : `${this.label} in area`}
 | 
					          : `${this.label} in area`}
 | 
				
			||||||
          class="input"
 | 
					        @value-changed=${this._areaPicked}
 | 
				
			||||||
          autocapitalize="none"
 | 
					 | 
				
			||||||
          autocomplete="off"
 | 
					 | 
				
			||||||
          autocorrect="off"
 | 
					 | 
				
			||||||
          spellcheck="false"
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          <div class="suffix" slot="suffix">
 | 
					 | 
				
			||||||
            ${this.value
 | 
					 | 
				
			||||||
              ? html`<ha-icon-button
 | 
					 | 
				
			||||||
                  class="clear-button"
 | 
					 | 
				
			||||||
                  .label=${this.hass.localize(
 | 
					 | 
				
			||||||
                    "ui.components.device-picker.clear"
 | 
					 | 
				
			||||||
                  )}
 | 
					 | 
				
			||||||
                  .path=${mdiClose}
 | 
					 | 
				
			||||||
                  @click=${this._clearValue}
 | 
					 | 
				
			||||||
                  no-ripple
 | 
					 | 
				
			||||||
                ></ha-icon-button> `
 | 
					 | 
				
			||||||
              : ""}
 | 
					 | 
				
			||||||
            ${areas.length > 0
 | 
					 | 
				
			||||||
              ? html`
 | 
					 | 
				
			||||||
                  <ha-icon-button
 | 
					 | 
				
			||||||
                    .label=${this.hass.localize(
 | 
					 | 
				
			||||||
                      "ui.components.device-picker.show_devices"
 | 
					 | 
				
			||||||
                    )}
 | 
					 | 
				
			||||||
                    .path=${this._opened ? mdiMenuUp : mdiMenuDown}
 | 
					 | 
				
			||||||
                    class="toggle-button"
 | 
					 | 
				
			||||||
                  ></ha-icon-button>
 | 
					 | 
				
			||||||
                `
 | 
					 | 
				
			||||||
              : ""}
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
        </paper-input>
 | 
					 | 
				
			||||||
      </vaadin-combo-box-light>
 | 
					 | 
				
			||||||
      <mwc-button @click=${this._switchPicker}
 | 
					 | 
				
			||||||
        >Choose individual devices</mwc-button
 | 
					 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
 | 
					      </ha-combo-box>
 | 
				
			||||||
 | 
					      <mwc-button @click=${this._switchPicker}>
 | 
				
			||||||
 | 
					        Choose individual devices
 | 
				
			||||||
 | 
					      </mwc-button>
 | 
				
			||||||
    `;
 | 
					    `;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private _clearValue(ev: Event) {
 | 
					 | 
				
			||||||
    ev.stopPropagation();
 | 
					 | 
				
			||||||
    this._setValue([]);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private get _value() {
 | 
					  private get _value() {
 | 
				
			||||||
    return this.value || [];
 | 
					    return this.value || [];
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private _openedChanged(ev: PolymerChangedEvent<boolean>) {
 | 
					 | 
				
			||||||
    this._opened = ev.detail.value;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private async _switchPicker() {
 | 
					  private async _switchPicker() {
 | 
				
			||||||
    this._areaPicker = !this._areaPicker;
 | 
					    this._areaPicker = !this._areaPicker;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -398,22 +317,6 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
 | 
				
			|||||||
      fireEvent(this, "change");
 | 
					      fireEvent(this, "change");
 | 
				
			||||||
    }, 0);
 | 
					    }, 0);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					 | 
				
			||||||
  static get styles(): CSSResultGroup {
 | 
					 | 
				
			||||||
    return css`
 | 
					 | 
				
			||||||
      .suffix {
 | 
					 | 
				
			||||||
        display: flex;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      ha-icon-button {
 | 
					 | 
				
			||||||
        --mdc-icon-button-size: 24px;
 | 
					 | 
				
			||||||
        padding: 0px 2px;
 | 
					 | 
				
			||||||
        color: var(--secondary-text-color);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      [hidden] {
 | 
					 | 
				
			||||||
        display: none;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    `;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
declare global {
 | 
					declare global {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,4 @@
 | 
				
			|||||||
import "@polymer/paper-input/paper-input";
 | 
					import "@material/mwc-list/mwc-list-item";
 | 
				
			||||||
import "@polymer/paper-item/paper-item";
 | 
					 | 
				
			||||||
import "@polymer/paper-item/paper-item-body";
 | 
					 | 
				
			||||||
import "@polymer/paper-listbox/paper-listbox";
 | 
					 | 
				
			||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
 | 
					import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
 | 
				
			||||||
import { property, state } from "lit/decorators";
 | 
					import { property, state } from "lit/decorators";
 | 
				
			||||||
import { fireEvent } from "../../common/dom/fire_event";
 | 
					import { fireEvent } from "../../common/dom/fire_event";
 | 
				
			||||||
@@ -10,7 +7,7 @@ import {
 | 
				
			|||||||
  deviceAutomationsEqual,
 | 
					  deviceAutomationsEqual,
 | 
				
			||||||
} from "../../data/device_automation";
 | 
					} from "../../data/device_automation";
 | 
				
			||||||
import { HomeAssistant } from "../../types";
 | 
					import { HomeAssistant } from "../../types";
 | 
				
			||||||
import "../ha-paper-dropdown-menu";
 | 
					import "../ha-select";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const NO_AUTOMATION_KEY = "NO_AUTOMATION";
 | 
					const NO_AUTOMATION_KEY = "NO_AUTOMATION";
 | 
				
			||||||
const UNKNOWN_AUTOMATION_KEY = "UNKNOWN_AUTOMATION";
 | 
					const UNKNOWN_AUTOMATION_KEY = "UNKNOWN_AUTOMATION";
 | 
				
			||||||
@@ -67,14 +64,12 @@ export abstract class HaDeviceAutomationPicker<
 | 
				
			|||||||
    this._createNoAutomation = createNoAutomation;
 | 
					    this._createNoAutomation = createNoAutomation;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private get _key() {
 | 
					  private get _value() {
 | 
				
			||||||
    if (
 | 
					    if (!this.value) {
 | 
				
			||||||
      !this.value ||
 | 
					      return "";
 | 
				
			||||||
      deviceAutomationsEqual(
 | 
					    }
 | 
				
			||||||
        this._createNoAutomation(this.deviceId),
 | 
					
 | 
				
			||||||
        this.value
 | 
					    if (!this._automations.length) {
 | 
				
			||||||
      )
 | 
					 | 
				
			||||||
    ) {
 | 
					 | 
				
			||||||
      return NO_AUTOMATION_KEY;
 | 
					      return NO_AUTOMATION_KEY;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -93,42 +88,32 @@ export abstract class HaDeviceAutomationPicker<
 | 
				
			|||||||
    if (this._renderEmpty) {
 | 
					    if (this._renderEmpty) {
 | 
				
			||||||
      return html``;
 | 
					      return html``;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    const value = this._value;
 | 
				
			||||||
    return html`
 | 
					    return html`
 | 
				
			||||||
      <ha-paper-dropdown-menu
 | 
					      <ha-select
 | 
				
			||||||
        .label=${this.label}
 | 
					        .label=${this.label}
 | 
				
			||||||
        .value=${this.value
 | 
					        .value=${value}
 | 
				
			||||||
          ? this._localizeDeviceAutomation(this.hass, this.value)
 | 
					        @selected=${this._automationChanged}
 | 
				
			||||||
          : ""}
 | 
					        .disabled=${this._automations.length === 0}
 | 
				
			||||||
        ?disabled=${this._automations.length === 0}
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <paper-listbox
 | 
					 | 
				
			||||||
          slot="dropdown-content"
 | 
					 | 
				
			||||||
          .selected=${this._key}
 | 
					 | 
				
			||||||
          attr-for-selected="key"
 | 
					 | 
				
			||||||
          @iron-select=${this._automationChanged}
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          <paper-item
 | 
					 | 
				
			||||||
            key=${NO_AUTOMATION_KEY}
 | 
					 | 
				
			||||||
            .automation=${this._createNoAutomation(this.deviceId)}
 | 
					 | 
				
			||||||
            hidden
 | 
					 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
 | 
					        ${value === NO_AUTOMATION_KEY
 | 
				
			||||||
 | 
					          ? html`<mwc-list-item .value=${NO_AUTOMATION_KEY}>
 | 
				
			||||||
              ${this.NO_AUTOMATION_TEXT}
 | 
					              ${this.NO_AUTOMATION_TEXT}
 | 
				
			||||||
          </paper-item>
 | 
					            </mwc-list-item>`
 | 
				
			||||||
          <paper-item key=${UNKNOWN_AUTOMATION_KEY} hidden>
 | 
					          : ""}
 | 
				
			||||||
 | 
					        ${value === UNKNOWN_AUTOMATION_KEY
 | 
				
			||||||
 | 
					          ? html`<mwc-list-item .value=${UNKNOWN_AUTOMATION_KEY}>
 | 
				
			||||||
              ${this.UNKNOWN_AUTOMATION_TEXT}
 | 
					              ${this.UNKNOWN_AUTOMATION_TEXT}
 | 
				
			||||||
          </paper-item>
 | 
					            </mwc-list-item>`
 | 
				
			||||||
 | 
					          : ""}
 | 
				
			||||||
        ${this._automations.map(
 | 
					        ${this._automations.map(
 | 
				
			||||||
          (automation, idx) => html`
 | 
					          (automation, idx) => html`
 | 
				
			||||||
              <paper-item
 | 
					            <mwc-list-item .value=${`${automation.device_id}_${idx}`}>
 | 
				
			||||||
                key=${`${this.deviceId}_${idx}`}
 | 
					 | 
				
			||||||
                .automation=${automation}
 | 
					 | 
				
			||||||
              >
 | 
					 | 
				
			||||||
              ${this._localizeDeviceAutomation(this.hass, automation)}
 | 
					              ${this._localizeDeviceAutomation(this.hass, automation)}
 | 
				
			||||||
              </paper-item>
 | 
					            </mwc-list-item>
 | 
				
			||||||
          `
 | 
					          `
 | 
				
			||||||
        )}
 | 
					        )}
 | 
				
			||||||
        </paper-listbox>
 | 
					      </ha-select>
 | 
				
			||||||
      </ha-paper-dropdown-menu>
 | 
					 | 
				
			||||||
    `;
 | 
					    `;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -138,14 +123,6 @@ export abstract class HaDeviceAutomationPicker<
 | 
				
			|||||||
    if (changedProps.has("deviceId")) {
 | 
					    if (changedProps.has("deviceId")) {
 | 
				
			||||||
      this._updateDeviceInfo();
 | 
					      this._updateDeviceInfo();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    // The value has changed, force the listbox to update
 | 
					 | 
				
			||||||
    if (changedProps.has("value") || changedProps.has("_renderEmpty")) {
 | 
					 | 
				
			||||||
      const listbox = this.shadowRoot!.querySelector("paper-listbox")!;
 | 
					 | 
				
			||||||
      if (listbox) {
 | 
					 | 
				
			||||||
        listbox._selectSelected(this._key);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private async _updateDeviceInfo() {
 | 
					  private async _updateDeviceInfo() {
 | 
				
			||||||
@@ -168,9 +145,16 @@ export abstract class HaDeviceAutomationPicker<
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private _automationChanged(ev) {
 | 
					  private _automationChanged(ev) {
 | 
				
			||||||
    if (ev.detail.item.automation) {
 | 
					    const value = ev.target.value;
 | 
				
			||||||
      this._setValue(ev.detail.item.automation);
 | 
					    if (!value || [UNKNOWN_AUTOMATION_KEY, NO_AUTOMATION_KEY].includes(value)) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    const [deviceId, idx] = value.split("_");
 | 
				
			||||||
 | 
					    const automation = this._automations[idx];
 | 
				
			||||||
 | 
					    if (automation.device_id !== deviceId) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    this._setValue(automation);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private _setValue(automation: T) {
 | 
					  private _setValue(automation: T) {
 | 
				
			||||||
@@ -183,14 +167,9 @@ export abstract class HaDeviceAutomationPicker<
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  static get styles(): CSSResultGroup {
 | 
					  static get styles(): CSSResultGroup {
 | 
				
			||||||
    return css`
 | 
					    return css`
 | 
				
			||||||
      ha-paper-dropdown-menu {
 | 
					      ha-select {
 | 
				
			||||||
        width: 100%;
 | 
					        width: 100%;
 | 
				
			||||||
      }
 | 
					        margin-top: 4px;
 | 
				
			||||||
      paper-listbox {
 | 
					 | 
				
			||||||
        min-width: 200px;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      paper-item {
 | 
					 | 
				
			||||||
        cursor: pointer;
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    `;
 | 
					    `;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,18 +1,9 @@
 | 
				
			|||||||
import "@polymer/paper-item/paper-item";
 | 
					import "@material/mwc-list/mwc-list-item";
 | 
				
			||||||
import "@polymer/paper-item/paper-item-body";
 | 
					 | 
				
			||||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
 | 
					import { UnsubscribeFunc } from "home-assistant-js-websocket";
 | 
				
			||||||
import {
 | 
					import { html, LitElement, PropertyValues, TemplateResult } from "lit";
 | 
				
			||||||
  css,
 | 
					 | 
				
			||||||
  CSSResultGroup,
 | 
					 | 
				
			||||||
  html,
 | 
					 | 
				
			||||||
  LitElement,
 | 
					 | 
				
			||||||
  PropertyValues,
 | 
					 | 
				
			||||||
  TemplateResult,
 | 
					 | 
				
			||||||
} from "lit";
 | 
					 | 
				
			||||||
import { customElement, property, state, query } from "lit/decorators";
 | 
					 | 
				
			||||||
import memoizeOne from "memoize-one";
 | 
					 | 
				
			||||||
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
 | 
					import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
 | 
				
			||||||
import { mdiCheck } from "@mdi/js";
 | 
					import { customElement, property, query, state } from "lit/decorators";
 | 
				
			||||||
 | 
					import memoizeOne from "memoize-one";
 | 
				
			||||||
import { fireEvent } from "../../common/dom/fire_event";
 | 
					import { fireEvent } from "../../common/dom/fire_event";
 | 
				
			||||||
import { computeDomain } from "../../common/entity/compute_domain";
 | 
					import { computeDomain } from "../../common/entity/compute_domain";
 | 
				
			||||||
import { stringCompare } from "../../common/string/compare";
 | 
					import { stringCompare } from "../../common/string/compare";
 | 
				
			||||||
@@ -46,36 +37,12 @@ export type HaDevicePickerDeviceFilterFunc = (
 | 
				
			|||||||
  device: DeviceRegistryEntry
 | 
					  device: DeviceRegistryEntry
 | 
				
			||||||
) => boolean;
 | 
					) => boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// eslint-disable-next-line lit/prefer-static-styles
 | 
					const rowRenderer: ComboBoxLitRenderer<Device> = (item) => html`<mwc-list-item
 | 
				
			||||||
const rowRenderer: ComboBoxLitRenderer<Device> = (item) => html`<style>
 | 
					  .twoline=${!!item.area}
 | 
				
			||||||
    paper-item {
 | 
					>
 | 
				
			||||||
      padding: 0;
 | 
					  <span>${item.name}</span>
 | 
				
			||||||
      margin: -10px;
 | 
					  <span slot="secondary">${item.area}</span>
 | 
				
			||||||
      margin-left: 0;
 | 
					</mwc-list-item>`;
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    #content {
 | 
					 | 
				
			||||||
      display: flex;
 | 
					 | 
				
			||||||
      align-items: center;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    ha-svg-icon {
 | 
					 | 
				
			||||||
      padding-left: 2px;
 | 
					 | 
				
			||||||
      margin-right: -2px;
 | 
					 | 
				
			||||||
      color: var(--secondary-text-color);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    :host(:not([selected])) ha-svg-icon {
 | 
					 | 
				
			||||||
      display: none;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    :host([selected]) paper-item {
 | 
					 | 
				
			||||||
      margin-left: 10px;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  </style>
 | 
					 | 
				
			||||||
  <ha-svg-icon .path=${mdiCheck}></ha-svg-icon>
 | 
					 | 
				
			||||||
  <paper-item>
 | 
					 | 
				
			||||||
    <paper-item-body two-line>
 | 
					 | 
				
			||||||
      ${item.name}
 | 
					 | 
				
			||||||
      <span secondary>${item.area}</span>
 | 
					 | 
				
			||||||
    </paper-item-body>
 | 
					 | 
				
			||||||
  </paper-item>`;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
@customElement("ha-device-picker")
 | 
					@customElement("ha-device-picker")
 | 
				
			||||||
export class HaDevicePicker extends SubscribeMixin(LitElement) {
 | 
					export class HaDevicePicker extends SubscribeMixin(LitElement) {
 | 
				
			||||||
@@ -138,7 +105,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
 | 
				
			|||||||
      if (!devices.length) {
 | 
					      if (!devices.length) {
 | 
				
			||||||
        return [
 | 
					        return [
 | 
				
			||||||
          {
 | 
					          {
 | 
				
			||||||
            id: "",
 | 
					            id: "no_devices",
 | 
				
			||||||
            area: "",
 | 
					            area: "",
 | 
				
			||||||
            name: this.hass.localize("ui.components.device-picker.no_devices"),
 | 
					            name: this.hass.localize("ui.components.device-picker.no_devices"),
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
@@ -234,7 +201,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
 | 
				
			|||||||
      if (!outputDevices.length) {
 | 
					      if (!outputDevices.length) {
 | 
				
			||||||
        return [
 | 
					        return [
 | 
				
			||||||
          {
 | 
					          {
 | 
				
			||||||
            id: "",
 | 
					            id: "no_devices",
 | 
				
			||||||
            area: "",
 | 
					            area: "",
 | 
				
			||||||
            name: this.hass.localize("ui.components.device-picker.no_match"),
 | 
					            name: this.hass.localize("ui.components.device-picker.no_match"),
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
@@ -303,7 +270,6 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
 | 
				
			|||||||
        .renderer=${rowRenderer}
 | 
					        .renderer=${rowRenderer}
 | 
				
			||||||
        .disabled=${this.disabled}
 | 
					        .disabled=${this.disabled}
 | 
				
			||||||
        item-value-path="id"
 | 
					        item-value-path="id"
 | 
				
			||||||
        item-id-path="id"
 | 
					 | 
				
			||||||
        item-label-path="name"
 | 
					        item-label-path="name"
 | 
				
			||||||
        @opened-changed=${this._openedChanged}
 | 
					        @opened-changed=${this._openedChanged}
 | 
				
			||||||
        @value-changed=${this._deviceChanged}
 | 
					        @value-changed=${this._deviceChanged}
 | 
				
			||||||
@@ -317,7 +283,11 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  private _deviceChanged(ev: PolymerChangedEvent<string>) {
 | 
					  private _deviceChanged(ev: PolymerChangedEvent<string>) {
 | 
				
			||||||
    ev.stopPropagation();
 | 
					    ev.stopPropagation();
 | 
				
			||||||
    const newValue = ev.detail.value;
 | 
					    let newValue = ev.detail.value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (newValue === "no_devices") {
 | 
				
			||||||
 | 
					      newValue = "";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (newValue !== this._value) {
 | 
					    if (newValue !== this._value) {
 | 
				
			||||||
      this._setValue(newValue);
 | 
					      this._setValue(newValue);
 | 
				
			||||||
@@ -335,19 +305,6 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
 | 
				
			|||||||
      fireEvent(this, "change");
 | 
					      fireEvent(this, "change");
 | 
				
			||||||
    }, 0);
 | 
					    }, 0);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					 | 
				
			||||||
  static get styles(): CSSResultGroup {
 | 
					 | 
				
			||||||
    return css`
 | 
					 | 
				
			||||||
      paper-input > ha-icon-button {
 | 
					 | 
				
			||||||
        --mdc-icon-button-size: 24px;
 | 
					 | 
				
			||||||
        padding: 2px;
 | 
					 | 
				
			||||||
        color: var(--secondary-text-color);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      [hidden] {
 | 
					 | 
				
			||||||
        display: none;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    `;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
declare global {
 | 
					declare global {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
import { html, LitElement, TemplateResult } from "lit";
 | 
					import { css, html, LitElement, TemplateResult } from "lit";
 | 
				
			||||||
import { customElement, property } from "lit/decorators";
 | 
					import { customElement, property } from "lit/decorators";
 | 
				
			||||||
import { fireEvent } from "../../common/dom/fire_event";
 | 
					import { fireEvent } from "../../common/dom/fire_event";
 | 
				
			||||||
import { PolymerChangedEvent } from "../../polymer-types";
 | 
					import { PolymerChangedEvent } from "../../polymer-types";
 | 
				
			||||||
@@ -116,6 +116,12 @@ class HaDevicesPicker extends LitElement {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    this._updateDevices([...currentDevices, toAdd]);
 | 
					    this._updateDevices([...currentDevices, toAdd]);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static override styles = css`
 | 
				
			||||||
 | 
					    div {
 | 
				
			||||||
 | 
					      margin-top: 8px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  `;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
declare global {
 | 
					declare global {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import type { HassEntity } from "home-assistant-js-websocket";
 | 
					import type { HassEntity } from "home-assistant-js-websocket";
 | 
				
			||||||
import { html, LitElement, TemplateResult } from "lit";
 | 
					import { css, html, LitElement, TemplateResult } from "lit";
 | 
				
			||||||
import { customElement, property } from "lit/decorators";
 | 
					import { customElement, property } from "lit/decorators";
 | 
				
			||||||
import { fireEvent } from "../../common/dom/fire_event";
 | 
					import { fireEvent } from "../../common/dom/fire_event";
 | 
				
			||||||
import { isValidEntityId } from "../../common/entity/valid_entity_id";
 | 
					import { isValidEntityId } from "../../common/entity/valid_entity_id";
 | 
				
			||||||
@@ -51,6 +51,8 @@ class HaEntitiesPickerLight extends LitElement {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @property({ attribute: "pick-entity-label" }) public pickEntityLabel?: string;
 | 
					  @property({ attribute: "pick-entity-label" }) public pickEntityLabel?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @property() public entityFilter?: HaEntityPickerEntityFilterFunc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  protected render(): TemplateResult {
 | 
					  protected render(): TemplateResult {
 | 
				
			||||||
    if (!this.hass) {
 | 
					    if (!this.hass) {
 | 
				
			||||||
      return html``;
 | 
					      return html``;
 | 
				
			||||||
@@ -94,7 +96,9 @@ class HaEntitiesPickerLight extends LitElement {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  private _entityFilter: HaEntityPickerEntityFilterFunc = (
 | 
					  private _entityFilter: HaEntityPickerEntityFilterFunc = (
 | 
				
			||||||
    stateObj: HassEntity
 | 
					    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() {
 | 
					  private get _currentEntities() {
 | 
				
			||||||
    return this.value || [];
 | 
					    return this.value || [];
 | 
				
			||||||
@@ -114,7 +118,7 @@ class HaEntitiesPickerLight extends LitElement {
 | 
				
			|||||||
    const newValue = event.detail.value;
 | 
					    const newValue = event.detail.value;
 | 
				
			||||||
    if (
 | 
					    if (
 | 
				
			||||||
      newValue === curValue ||
 | 
					      newValue === curValue ||
 | 
				
			||||||
      (newValue !== "" && !isValidEntityId(newValue))
 | 
					      (newValue !== undefined && !isValidEntityId(newValue))
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -145,6 +149,12 @@ class HaEntitiesPickerLight extends LitElement {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    this._updateEntities([...currentEntities, toAdd]);
 | 
					    this._updateEntities([...currentEntities, toAdd]);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static override styles = css`
 | 
				
			||||||
 | 
					    div {
 | 
				
			||||||
 | 
					      margin-top: 8px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  `;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
declare global {
 | 
					declare global {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,54 +1,14 @@
 | 
				
			|||||||
import { mdiCheck, mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
 | 
					 | 
				
			||||||
import "@polymer/paper-input/paper-input";
 | 
					 | 
				
			||||||
import "@polymer/paper-item/paper-item";
 | 
					 | 
				
			||||||
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
 | 
					 | 
				
			||||||
import { HassEntity } from "home-assistant-js-websocket";
 | 
					import { HassEntity } from "home-assistant-js-websocket";
 | 
				
			||||||
import {
 | 
					import { html, LitElement, PropertyValues, TemplateResult } from "lit";
 | 
				
			||||||
  css,
 | 
					 | 
				
			||||||
  CSSResultGroup,
 | 
					 | 
				
			||||||
  html,
 | 
					 | 
				
			||||||
  LitElement,
 | 
					 | 
				
			||||||
  PropertyValues,
 | 
					 | 
				
			||||||
  TemplateResult,
 | 
					 | 
				
			||||||
} from "lit";
 | 
					 | 
				
			||||||
import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers";
 | 
					 | 
				
			||||||
import { customElement, property, query } from "lit/decorators";
 | 
					import { customElement, property, query } from "lit/decorators";
 | 
				
			||||||
import { fireEvent } from "../../common/dom/fire_event";
 | 
					 | 
				
			||||||
import { formatAttributeName } from "../../data/entity_attributes";
 | 
					import { formatAttributeName } from "../../data/entity_attributes";
 | 
				
			||||||
import { PolymerChangedEvent } from "../../polymer-types";
 | 
					import { PolymerChangedEvent } from "../../polymer-types";
 | 
				
			||||||
import { HomeAssistant } from "../../types";
 | 
					import { HomeAssistant } from "../../types";
 | 
				
			||||||
import "../ha-icon-button";
 | 
					import "../ha-combo-box";
 | 
				
			||||||
import "../ha-svg-icon";
 | 
					import type { HaComboBox } from "../ha-combo-box";
 | 
				
			||||||
import "./state-badge";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
 | 
					export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// eslint-disable-next-line lit/prefer-static-styles
 | 
					 | 
				
			||||||
const rowRenderer: ComboBoxLitRenderer<string> = (item) => html`<style>
 | 
					 | 
				
			||||||
    paper-item {
 | 
					 | 
				
			||||||
      padding: 0;
 | 
					 | 
				
			||||||
      margin: -10px;
 | 
					 | 
				
			||||||
      margin-left: 0;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    #content {
 | 
					 | 
				
			||||||
      display: flex;
 | 
					 | 
				
			||||||
      align-items: center;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    ha-svg-icon {
 | 
					 | 
				
			||||||
      padding-left: 2px;
 | 
					 | 
				
			||||||
      margin-right: -2px;
 | 
					 | 
				
			||||||
      color: var(--secondary-text-color);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    :host(:not([selected])) ha-svg-icon {
 | 
					 | 
				
			||||||
      display: none;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    :host([selected]) paper-item {
 | 
					 | 
				
			||||||
      margin-left: 10px;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  </style>
 | 
					 | 
				
			||||||
  <ha-svg-icon .path=${mdiCheck}></ha-svg-icon>
 | 
					 | 
				
			||||||
  <paper-item>${formatAttributeName(item)}</paper-item>`;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@customElement("ha-entity-attribute-picker")
 | 
					@customElement("ha-entity-attribute-picker")
 | 
				
			||||||
class HaEntityAttributePicker extends LitElement {
 | 
					class HaEntityAttributePicker extends LitElement {
 | 
				
			||||||
  @property({ attribute: false }) public hass!: HomeAssistant;
 | 
					  @property({ attribute: false }) public hass!: HomeAssistant;
 | 
				
			||||||
@@ -68,7 +28,7 @@ class HaEntityAttributePicker extends LitElement {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @property({ type: Boolean }) private _opened = false;
 | 
					  @property({ type: Boolean }) private _opened = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @query("vaadin-combo-box-light", true) private _comboBox!: HTMLElement;
 | 
					  @query("ha-combo-box", true) private _comboBox!: HaComboBox;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  protected shouldUpdate(changedProps: PropertyValues) {
 | 
					  protected shouldUpdate(changedProps: PropertyValues) {
 | 
				
			||||||
    return !(!changedProps.has("_opened") && this._opened);
 | 
					    return !(!changedProps.has("_opened") && this._opened);
 | 
				
			||||||
@@ -78,7 +38,10 @@ class HaEntityAttributePicker extends LitElement {
 | 
				
			|||||||
    if (changedProps.has("_opened") && this._opened) {
 | 
					    if (changedProps.has("_opened") && this._opened) {
 | 
				
			||||||
      const state = this.entityId ? this.hass.states[this.entityId] : undefined;
 | 
					      const state = this.entityId ? this.hass.states[this.entityId] : undefined;
 | 
				
			||||||
      (this._comboBox as any).items = state
 | 
					      (this._comboBox as any).items = state
 | 
				
			||||||
        ? Object.keys(state.attributes)
 | 
					        ? Object.keys(state.attributes).map((key) => ({
 | 
				
			||||||
 | 
					            value: key,
 | 
				
			||||||
 | 
					            label: formatAttributeName(key),
 | 
				
			||||||
 | 
					          }))
 | 
				
			||||||
        : [];
 | 
					        : [];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -89,100 +52,31 @@ class HaEntityAttributePicker extends LitElement {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return html`
 | 
					    return html`
 | 
				
			||||||
      <vaadin-combo-box-light
 | 
					      <ha-combo-box
 | 
				
			||||||
        .value=${this._value}
 | 
					        .hass=${this.hass}
 | 
				
			||||||
        .allowCustomValue=${this.allowCustomValue}
 | 
					        .value=${this.value || ""}
 | 
				
			||||||
        attr-for-value="bind-value"
 | 
					 | 
				
			||||||
        ${comboBoxRenderer(rowRenderer)}
 | 
					 | 
				
			||||||
        @opened-changed=${this._openedChanged}
 | 
					 | 
				
			||||||
        @value-changed=${this._valueChanged}
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <paper-input
 | 
					 | 
				
			||||||
        .autofocus=${this.autofocus}
 | 
					        .autofocus=${this.autofocus}
 | 
				
			||||||
        .label=${this.label ??
 | 
					        .label=${this.label ??
 | 
				
			||||||
        this.hass.localize(
 | 
					        this.hass.localize(
 | 
				
			||||||
          "ui.components.entity.entity-attribute-picker.attribute"
 | 
					          "ui.components.entity.entity-attribute-picker.attribute"
 | 
				
			||||||
        )}
 | 
					        )}
 | 
				
			||||||
          .value=${this._value ? formatAttributeName(this._value) : ""}
 | 
					 | 
				
			||||||
        .disabled=${this.disabled || !this.entityId}
 | 
					        .disabled=${this.disabled || !this.entityId}
 | 
				
			||||||
          class="input"
 | 
					        .allowCustomValue=${this.allowCustomValue}
 | 
				
			||||||
          autocapitalize="none"
 | 
					        item-value-path="value"
 | 
				
			||||||
          autocomplete="off"
 | 
					        item-label-path="label"
 | 
				
			||||||
          autocorrect="off"
 | 
					        @opened-changed=${this._openedChanged}
 | 
				
			||||||
          spellcheck="false"
 | 
					        @value-changed=${this._valueChanged}
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
          <div class="suffix" slot="suffix">
 | 
					      </ha-combo-box>
 | 
				
			||||||
            ${this.value
 | 
					 | 
				
			||||||
              ? html`
 | 
					 | 
				
			||||||
                  <ha-icon-button
 | 
					 | 
				
			||||||
                    .label=${this.hass.localize(
 | 
					 | 
				
			||||||
                      "ui.components.entity.entity-picker.clear"
 | 
					 | 
				
			||||||
                    )}
 | 
					 | 
				
			||||||
                    .path=${mdiClose}
 | 
					 | 
				
			||||||
                    class="clear-button"
 | 
					 | 
				
			||||||
                    tabindex="-1"
 | 
					 | 
				
			||||||
                    @click=${this._clearValue}
 | 
					 | 
				
			||||||
                    no-ripple
 | 
					 | 
				
			||||||
                  ></ha-icon-button>
 | 
					 | 
				
			||||||
                `
 | 
					 | 
				
			||||||
              : ""}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <ha-icon-button
 | 
					 | 
				
			||||||
              .label=${this.hass.localize(
 | 
					 | 
				
			||||||
                "ui.components.entity.entity-attribute-picker.show_attributes"
 | 
					 | 
				
			||||||
              )}
 | 
					 | 
				
			||||||
              .path=${this._opened ? mdiMenuUp : mdiMenuDown}
 | 
					 | 
				
			||||||
              class="toggle-button"
 | 
					 | 
				
			||||||
              tabindex="-1"
 | 
					 | 
				
			||||||
            ></ha-icon-button>
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
        </paper-input>
 | 
					 | 
				
			||||||
      </vaadin-combo-box-light>
 | 
					 | 
				
			||||||
    `;
 | 
					    `;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private _clearValue(ev: Event) {
 | 
					 | 
				
			||||||
    ev.stopPropagation();
 | 
					 | 
				
			||||||
    this._setValue("");
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private get _value() {
 | 
					 | 
				
			||||||
    return this.value;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private _openedChanged(ev: PolymerChangedEvent<boolean>) {
 | 
					  private _openedChanged(ev: PolymerChangedEvent<boolean>) {
 | 
				
			||||||
    this._opened = ev.detail.value;
 | 
					    this._opened = ev.detail.value;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private _valueChanged(ev: PolymerChangedEvent<string>) {
 | 
					  private _valueChanged(ev: PolymerChangedEvent<string>) {
 | 
				
			||||||
    const newValue = ev.detail.value;
 | 
					    this.value = ev.detail.value;
 | 
				
			||||||
    if (newValue !== this._value) {
 | 
					 | 
				
			||||||
      this._setValue(newValue);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private _setValue(value: string) {
 | 
					 | 
				
			||||||
    this.value = value;
 | 
					 | 
				
			||||||
    setTimeout(() => {
 | 
					 | 
				
			||||||
      fireEvent(this, "value-changed", { value });
 | 
					 | 
				
			||||||
      fireEvent(this, "change");
 | 
					 | 
				
			||||||
    }, 0);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  static get styles(): CSSResultGroup {
 | 
					 | 
				
			||||||
    return css`
 | 
					 | 
				
			||||||
      .suffix {
 | 
					 | 
				
			||||||
        display: flex;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      ha-icon-button {
 | 
					 | 
				
			||||||
        --mdc-icon-button-size: 24px;
 | 
					 | 
				
			||||||
        padding: 0px 2px;
 | 
					 | 
				
			||||||
        color: var(--secondary-text-color);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      [hidden] {
 | 
					 | 
				
			||||||
        display: none;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    `;
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,61 +1,35 @@
 | 
				
			|||||||
import { mdiCheck, mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
 | 
					import "@material/mwc-list/mwc-list-item";
 | 
				
			||||||
import "@polymer/paper-input/paper-input";
 | 
					 | 
				
			||||||
import "@polymer/paper-item/paper-icon-item";
 | 
					 | 
				
			||||||
import "@polymer/paper-item/paper-item-body";
 | 
					 | 
				
			||||||
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
 | 
					 | 
				
			||||||
import { HassEntity } from "home-assistant-js-websocket";
 | 
					import { HassEntity } from "home-assistant-js-websocket";
 | 
				
			||||||
import {
 | 
					import { html, LitElement, PropertyValues, TemplateResult } from "lit";
 | 
				
			||||||
  css,
 | 
					import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
 | 
				
			||||||
  CSSResultGroup,
 | 
					import { customElement, property, query, state } from "lit/decorators";
 | 
				
			||||||
  html,
 | 
					 | 
				
			||||||
  LitElement,
 | 
					 | 
				
			||||||
  PropertyValues,
 | 
					 | 
				
			||||||
  TemplateResult,
 | 
					 | 
				
			||||||
} from "lit";
 | 
					 | 
				
			||||||
import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers";
 | 
					 | 
				
			||||||
import { customElement, property, query } from "lit/decorators";
 | 
					 | 
				
			||||||
import memoizeOne from "memoize-one";
 | 
					import memoizeOne from "memoize-one";
 | 
				
			||||||
import { fireEvent } from "../../common/dom/fire_event";
 | 
					import { fireEvent } from "../../common/dom/fire_event";
 | 
				
			||||||
import { computeDomain } from "../../common/entity/compute_domain";
 | 
					import { computeDomain } from "../../common/entity/compute_domain";
 | 
				
			||||||
import { computeStateName } from "../../common/entity/compute_state_name";
 | 
					import { computeStateName } from "../../common/entity/compute_state_name";
 | 
				
			||||||
import { PolymerChangedEvent } from "../../polymer-types";
 | 
					import { PolymerChangedEvent } from "../../polymer-types";
 | 
				
			||||||
import { HomeAssistant } from "../../types";
 | 
					import { HomeAssistant } from "../../types";
 | 
				
			||||||
 | 
					import "../ha-combo-box";
 | 
				
			||||||
 | 
					import type { HaComboBox } from "../ha-combo-box";
 | 
				
			||||||
import "../ha-icon-button";
 | 
					import "../ha-icon-button";
 | 
				
			||||||
import "../ha-svg-icon";
 | 
					import "../ha-svg-icon";
 | 
				
			||||||
import "./state-badge";
 | 
					import "./state-badge";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface HassEntityWithCachedName extends HassEntity {
 | 
				
			||||||
 | 
					  friendly_name: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
 | 
					export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// eslint-disable-next-line lit/prefer-static-styles
 | 
					// eslint-disable-next-line lit/prefer-static-styles
 | 
				
			||||||
const rowRenderer: ComboBoxLitRenderer<HassEntity> = (item) => html`<style>
 | 
					const rowRenderer: ComboBoxLitRenderer<HassEntityWithCachedName> = (item) =>
 | 
				
			||||||
    paper-icon-item {
 | 
					  html`<mwc-list-item graphic="avatar" .twoline=${!!item.entity_id}>
 | 
				
			||||||
      padding: 0;
 | 
					    ${item.state
 | 
				
			||||||
      margin: -8px;
 | 
					      ? html`<state-badge slot="graphic" .stateObj=${item}></state-badge>`
 | 
				
			||||||
    }
 | 
					      : ""}
 | 
				
			||||||
    #content {
 | 
					    <span>${item.friendly_name}</span>
 | 
				
			||||||
      display: flex;
 | 
					    <span slot="secondary">${item.entity_id}</span>
 | 
				
			||||||
      align-items: center;
 | 
					  </mwc-list-item>`;
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    ha-svg-icon {
 | 
					 | 
				
			||||||
      padding-left: 2px;
 | 
					 | 
				
			||||||
      color: var(--secondary-text-color);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    :host(:not([selected])) ha-svg-icon {
 | 
					 | 
				
			||||||
      display: none;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    :host([selected]) paper-icon-item {
 | 
					 | 
				
			||||||
      margin-left: 0;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  </style>
 | 
					 | 
				
			||||||
  <ha-svg-icon .path=${mdiCheck}></ha-svg-icon>
 | 
					 | 
				
			||||||
  <paper-icon-item>
 | 
					 | 
				
			||||||
    <state-badge slot="item-icon" .stateObj=${item}></state-badge>
 | 
					 | 
				
			||||||
    <paper-item-body two-line="">
 | 
					 | 
				
			||||||
      ${computeStateName(item)}
 | 
					 | 
				
			||||||
      <span secondary>${item.entity_id}</span>
 | 
					 | 
				
			||||||
    </paper-item-body>
 | 
					 | 
				
			||||||
  </paper-icon-item>`;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@customElement("ha-entity-picker")
 | 
					@customElement("ha-entity-picker")
 | 
				
			||||||
export class HaEntityPicker extends LitElement {
 | 
					export class HaEntityPicker extends LitElement {
 | 
				
			||||||
  @property({ attribute: false }) public hass!: HomeAssistant;
 | 
					  @property({ attribute: false }) public hass!: HomeAssistant;
 | 
				
			||||||
@@ -107,25 +81,25 @@ export class HaEntityPicker extends LitElement {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @property({ type: Boolean }) public hideClearIcon = false;
 | 
					  @property({ type: Boolean }) public hideClearIcon = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @property({ type: Boolean }) private _opened = false;
 | 
					  @state() private _opened = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @query("vaadin-combo-box-light", true) private comboBox!: HTMLElement;
 | 
					  @query("ha-combo-box", true) public comboBox!: HaComboBox;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public open() {
 | 
					  public open() {
 | 
				
			||||||
    this.updateComplete.then(() => {
 | 
					    this.updateComplete.then(() => {
 | 
				
			||||||
      (this.shadowRoot?.querySelector("vaadin-combo-box-light") as any)?.open();
 | 
					      this.comboBox?.open();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public focus() {
 | 
					  public focus() {
 | 
				
			||||||
    this.updateComplete.then(() => {
 | 
					    this.updateComplete.then(() => {
 | 
				
			||||||
      this.shadowRoot?.querySelector("paper-input")?.focus();
 | 
					      this.comboBox?.focus();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private _initedStates = false;
 | 
					  private _initedStates = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private _states: HassEntity[] = [];
 | 
					  private _states: HassEntityWithCachedName[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private _getStates = memoizeOne(
 | 
					  private _getStates = memoizeOne(
 | 
				
			||||||
    (
 | 
					    (
 | 
				
			||||||
@@ -136,14 +110,35 @@ export class HaEntityPicker extends LitElement {
 | 
				
			|||||||
      entityFilter: this["entityFilter"],
 | 
					      entityFilter: this["entityFilter"],
 | 
				
			||||||
      includeDeviceClasses: this["includeDeviceClasses"],
 | 
					      includeDeviceClasses: this["includeDeviceClasses"],
 | 
				
			||||||
      includeUnitOfMeasurement: this["includeUnitOfMeasurement"]
 | 
					      includeUnitOfMeasurement: this["includeUnitOfMeasurement"]
 | 
				
			||||||
    ) => {
 | 
					    ): HassEntityWithCachedName[] => {
 | 
				
			||||||
      let states: HassEntity[] = [];
 | 
					      let states: HassEntityWithCachedName[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (!hass) {
 | 
					      if (!hass) {
 | 
				
			||||||
        return [];
 | 
					        return [];
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      let entityIds = Object.keys(hass.states);
 | 
					      let entityIds = Object.keys(hass.states);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (!entityIds.length) {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            entity_id: "",
 | 
				
			||||||
 | 
					            state: "",
 | 
				
			||||||
 | 
					            last_changed: "",
 | 
				
			||||||
 | 
					            last_updated: "",
 | 
				
			||||||
 | 
					            context: { id: "", user_id: null, parent_id: null },
 | 
				
			||||||
 | 
					            friendly_name: this.hass!.localize(
 | 
				
			||||||
 | 
					              "ui.components.entity.entity-picker.no_entities"
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            attributes: {
 | 
				
			||||||
 | 
					              friendly_name: this.hass!.localize(
 | 
				
			||||||
 | 
					                "ui.components.entity.entity-picker.no_entities"
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					              icon: "mdi:magnify",
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (includeDomains) {
 | 
					      if (includeDomains) {
 | 
				
			||||||
        entityIds = entityIds.filter((eid) =>
 | 
					        entityIds = entityIds.filter((eid) =>
 | 
				
			||||||
          includeDomains.includes(computeDomain(eid))
 | 
					          includeDomains.includes(computeDomain(eid))
 | 
				
			||||||
@@ -156,7 +151,10 @@ export class HaEntityPicker extends LitElement {
 | 
				
			|||||||
        );
 | 
					        );
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      states = entityIds.sort().map((key) => hass!.states[key]);
 | 
					      states = entityIds.sort().map((key) => ({
 | 
				
			||||||
 | 
					        ...hass!.states[key],
 | 
				
			||||||
 | 
					        friendly_name: computeStateName(hass!.states[key]) || key,
 | 
				
			||||||
 | 
					      }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (includeDeviceClasses) {
 | 
					      if (includeDeviceClasses) {
 | 
				
			||||||
        states = states.filter(
 | 
					        states = states.filter(
 | 
				
			||||||
@@ -195,7 +193,10 @@ export class HaEntityPicker extends LitElement {
 | 
				
			|||||||
            state: "",
 | 
					            state: "",
 | 
				
			||||||
            last_changed: "",
 | 
					            last_changed: "",
 | 
				
			||||||
            last_updated: "",
 | 
					            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"
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
            attributes: {
 | 
					            attributes: {
 | 
				
			||||||
              friendly_name: this.hass!.localize(
 | 
					              friendly_name: this.hass!.localize(
 | 
				
			||||||
                "ui.components.entity.entity-picker.no_match"
 | 
					                "ui.components.entity.entity-picker.no_match"
 | 
				
			||||||
@@ -241,64 +242,25 @@ export class HaEntityPicker extends LitElement {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  protected render(): TemplateResult {
 | 
					  protected render(): TemplateResult {
 | 
				
			||||||
    return html`
 | 
					    return html`
 | 
				
			||||||
      <vaadin-combo-box-light
 | 
					      <ha-combo-box
 | 
				
			||||||
        item-value-path="entity_id"
 | 
					        item-value-path="entity_id"
 | 
				
			||||||
        item-label-path="entity_id"
 | 
					        item-label-path="friendly_name"
 | 
				
			||||||
 | 
					        .hass=${this.hass}
 | 
				
			||||||
        .value=${this._value}
 | 
					        .value=${this._value}
 | 
				
			||||||
 | 
					        .label=${this.label === undefined
 | 
				
			||||||
 | 
					          ? this.hass.localize("ui.components.entity.entity-picker.entity")
 | 
				
			||||||
 | 
					          : this.label}
 | 
				
			||||||
        .allowCustomValue=${this.allowCustomEntity}
 | 
					        .allowCustomValue=${this.allowCustomEntity}
 | 
				
			||||||
        .filteredItems=${this._states}
 | 
					        .filteredItems=${this._states}
 | 
				
			||||||
        ${comboBoxRenderer(rowRenderer)}
 | 
					        .renderer=${rowRenderer}
 | 
				
			||||||
        @opened-changed=${this._openedChanged}
 | 
					        @opened-changed=${this._openedChanged}
 | 
				
			||||||
        @value-changed=${this._valueChanged}
 | 
					        @value-changed=${this._valueChanged}
 | 
				
			||||||
        @filter-changed=${this._filterChanged}
 | 
					        @filter-changed=${this._filterChanged}
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        <paper-input
 | 
					      </ha-combo-box>
 | 
				
			||||||
          .autofocus=${this.autofocus}
 | 
					 | 
				
			||||||
          .label=${this.label === undefined
 | 
					 | 
				
			||||||
            ? this.hass.localize("ui.components.entity.entity-picker.entity")
 | 
					 | 
				
			||||||
            : this.label}
 | 
					 | 
				
			||||||
          .disabled=${this.disabled}
 | 
					 | 
				
			||||||
          class="input"
 | 
					 | 
				
			||||||
          autocapitalize="none"
 | 
					 | 
				
			||||||
          autocomplete="off"
 | 
					 | 
				
			||||||
          autocorrect="off"
 | 
					 | 
				
			||||||
          spellcheck="false"
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          <div class="suffix" slot="suffix">
 | 
					 | 
				
			||||||
            ${this.value && !this.hideClearIcon
 | 
					 | 
				
			||||||
              ? html`
 | 
					 | 
				
			||||||
                  <ha-icon-button
 | 
					 | 
				
			||||||
                    .label=${this.hass.localize(
 | 
					 | 
				
			||||||
                      "ui.components.entity.entity-picker.clear"
 | 
					 | 
				
			||||||
                    )}
 | 
					 | 
				
			||||||
                    .path=${mdiClose}
 | 
					 | 
				
			||||||
                    class="clear-button"
 | 
					 | 
				
			||||||
                    tabindex="-1"
 | 
					 | 
				
			||||||
                    @click=${this._clearValue}
 | 
					 | 
				
			||||||
                    no-ripple
 | 
					 | 
				
			||||||
                  ></ha-icon-button>
 | 
					 | 
				
			||||||
                `
 | 
					 | 
				
			||||||
              : ""}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <ha-icon-button
 | 
					 | 
				
			||||||
              .label=${this.hass.localize(
 | 
					 | 
				
			||||||
                "ui.components.entity.entity-picker.show_entities"
 | 
					 | 
				
			||||||
              )}
 | 
					 | 
				
			||||||
              .path=${this._opened ? mdiMenuUp : mdiMenuDown}
 | 
					 | 
				
			||||||
              class="toggle-button"
 | 
					 | 
				
			||||||
              tabindex="-1"
 | 
					 | 
				
			||||||
            ></ha-icon-button>
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
        </paper-input>
 | 
					 | 
				
			||||||
      </vaadin-combo-box-light>
 | 
					 | 
				
			||||||
    `;
 | 
					    `;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private _clearValue(ev: Event) {
 | 
					 | 
				
			||||||
    ev.stopPropagation();
 | 
					 | 
				
			||||||
    this._setValue("");
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private get _value() {
 | 
					  private get _value() {
 | 
				
			||||||
    return this.value || "";
 | 
					    return this.value || "";
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -308,6 +270,7 @@ export class HaEntityPicker extends LitElement {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private _valueChanged(ev: PolymerChangedEvent<string>) {
 | 
					  private _valueChanged(ev: PolymerChangedEvent<string>) {
 | 
				
			||||||
 | 
					    ev.stopPropagation();
 | 
				
			||||||
    const newValue = ev.detail.value;
 | 
					    const newValue = ev.detail.value;
 | 
				
			||||||
    if (newValue !== this._value) {
 | 
					    if (newValue !== this._value) {
 | 
				
			||||||
      this._setValue(newValue);
 | 
					      this._setValue(newValue);
 | 
				
			||||||
@@ -317,9 +280,9 @@ export class HaEntityPicker extends LitElement {
 | 
				
			|||||||
  private _filterChanged(ev: CustomEvent): void {
 | 
					  private _filterChanged(ev: CustomEvent): void {
 | 
				
			||||||
    const filterString = ev.detail.value.toLowerCase();
 | 
					    const filterString = ev.detail.value.toLowerCase();
 | 
				
			||||||
    (this.comboBox as any).filteredItems = this._states.filter(
 | 
					    (this.comboBox as any).filteredItems = this._states.filter(
 | 
				
			||||||
      (state) =>
 | 
					      (entityState) =>
 | 
				
			||||||
        state.entity_id.toLowerCase().includes(filterString) ||
 | 
					        entityState.entity_id.toLowerCase().includes(filterString) ||
 | 
				
			||||||
        computeStateName(state).toLowerCase().includes(filterString)
 | 
					        computeStateName(entityState).toLowerCase().includes(filterString)
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -330,22 +293,6 @@ export class HaEntityPicker extends LitElement {
 | 
				
			|||||||
      fireEvent(this, "change");
 | 
					      fireEvent(this, "change");
 | 
				
			||||||
    }, 0);
 | 
					    }, 0);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					 | 
				
			||||||
  static get styles(): CSSResultGroup {
 | 
					 | 
				
			||||||
    return css`
 | 
					 | 
				
			||||||
      .suffix {
 | 
					 | 
				
			||||||
        display: flex;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      ha-icon-button {
 | 
					 | 
				
			||||||
        --mdc-icon-button-size: 24px;
 | 
					 | 
				
			||||||
        padding: 0px 2px;
 | 
					 | 
				
			||||||
        color: var(--secondary-text-color);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      [hidden] {
 | 
					 | 
				
			||||||
        display: none;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    `;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
declare global {
 | 
					declare global {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,17 +1,5 @@
 | 
				
			|||||||
import { mdiCheck } from "@mdi/js";
 | 
					 | 
				
			||||||
import "@polymer/paper-input/paper-input";
 | 
					 | 
				
			||||||
import "@polymer/paper-item/paper-icon-item";
 | 
					 | 
				
			||||||
import "@polymer/paper-item/paper-item-body";
 | 
					 | 
				
			||||||
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
 | 
					 | 
				
			||||||
import { HassEntity } from "home-assistant-js-websocket";
 | 
					import { HassEntity } from "home-assistant-js-websocket";
 | 
				
			||||||
import {
 | 
					import { html, LitElement, PropertyValues, TemplateResult } from "lit";
 | 
				
			||||||
  css,
 | 
					 | 
				
			||||||
  CSSResultGroup,
 | 
					 | 
				
			||||||
  html,
 | 
					 | 
				
			||||||
  LitElement,
 | 
					 | 
				
			||||||
  PropertyValues,
 | 
					 | 
				
			||||||
  TemplateResult,
 | 
					 | 
				
			||||||
} from "lit";
 | 
					 | 
				
			||||||
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
 | 
					import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
 | 
				
			||||||
import { customElement, property, query, state } from "lit/decorators";
 | 
					import { customElement, property, query, state } from "lit/decorators";
 | 
				
			||||||
import memoizeOne from "memoize-one";
 | 
					import memoizeOne from "memoize-one";
 | 
				
			||||||
@@ -76,41 +64,12 @@ export class HaStatisticPicker extends LitElement {
 | 
				
			|||||||
    id: string;
 | 
					    id: string;
 | 
				
			||||||
    name: string;
 | 
					    name: string;
 | 
				
			||||||
    state?: HassEntity;
 | 
					    state?: HassEntity;
 | 
				
			||||||
    // eslint-disable-next-line lit/prefer-static-styles
 | 
					  }> = (item) => html`<mwc-list-item graphic="avatar" twoline>
 | 
				
			||||||
  }> = (item) => html`<style>
 | 
					 | 
				
			||||||
      paper-icon-item {
 | 
					 | 
				
			||||||
        padding: 0;
 | 
					 | 
				
			||||||
        margin: -8px;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      #content {
 | 
					 | 
				
			||||||
        display: flex;
 | 
					 | 
				
			||||||
        align-items: center;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      ha-svg-icon {
 | 
					 | 
				
			||||||
        padding-left: 2px;
 | 
					 | 
				
			||||||
        color: var(--secondary-text-color);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      :host(:not([selected])) ha-svg-icon {
 | 
					 | 
				
			||||||
        display: none;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      :host([selected]) paper-icon-item {
 | 
					 | 
				
			||||||
        margin-left: 0;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      a {
 | 
					 | 
				
			||||||
        color: var(--primary-color);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    </style>
 | 
					 | 
				
			||||||
    <ha-svg-icon .path=${mdiCheck}></ha-svg-icon>
 | 
					 | 
				
			||||||
    <paper-icon-item>
 | 
					 | 
				
			||||||
    ${item.state
 | 
					    ${item.state
 | 
				
			||||||
        ? html`<state-badge
 | 
					      ? html`<state-badge slot="graphic" .stateObj=${item.state}></state-badge>`
 | 
				
			||||||
            slot="item-icon"
 | 
					 | 
				
			||||||
            .stateObj=${item.state}
 | 
					 | 
				
			||||||
          ></state-badge>`
 | 
					 | 
				
			||||||
      : ""}
 | 
					      : ""}
 | 
				
			||||||
      <paper-item-body two-line="">
 | 
					    <span>${item.name}</span>
 | 
				
			||||||
        ${item.name}
 | 
					    <span slot="secondary"
 | 
				
			||||||
        <span secondary
 | 
					 | 
				
			||||||
      >${item.id === "" || item.id === "__missing"
 | 
					      >${item.id === "" || item.id === "__missing"
 | 
				
			||||||
        ? html`<a
 | 
					        ? html`<a
 | 
				
			||||||
            target="_blank"
 | 
					            target="_blank"
 | 
				
			||||||
@@ -122,8 +81,7 @@ export class HaStatisticPicker extends LitElement {
 | 
				
			|||||||
          >`
 | 
					          >`
 | 
				
			||||||
        : item.id}</span
 | 
					        : item.id}</span
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      </paper-item-body>
 | 
					  </mwc-list-item>`;
 | 
				
			||||||
    </paper-icon-item>`;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private _getStatistics = memoizeOne(
 | 
					  private _getStatistics = memoizeOne(
 | 
				
			||||||
    (
 | 
					    (
 | 
				
			||||||
@@ -293,19 +251,6 @@ export class HaStatisticPicker extends LitElement {
 | 
				
			|||||||
      fireEvent(this, "change");
 | 
					      fireEvent(this, "change");
 | 
				
			||||||
    }, 0);
 | 
					    }, 0);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					 | 
				
			||||||
  static get styles(): CSSResultGroup {
 | 
					 | 
				
			||||||
    return css`
 | 
					 | 
				
			||||||
      paper-input > ha-icon-button {
 | 
					 | 
				
			||||||
        --mdc-icon-button-size: 24px;
 | 
					 | 
				
			||||||
        padding: 2px;
 | 
					 | 
				
			||||||
        color: var(--secondary-text-color);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      [hidden] {
 | 
					 | 
				
			||||||
        display: none;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    `;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
declare global {
 | 
					declare global {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
import { html, LitElement, TemplateResult } from "lit";
 | 
					import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
 | 
				
			||||||
import { customElement, property } from "lit/decorators";
 | 
					import { customElement, property } from "lit/decorators";
 | 
				
			||||||
import { fireEvent } from "../../common/dom/fire_event";
 | 
					import { fireEvent } from "../../common/dom/fire_event";
 | 
				
			||||||
import type { PolymerChangedEvent } from "../../polymer-types";
 | 
					import type { PolymerChangedEvent } from "../../polymer-types";
 | 
				
			||||||
@@ -103,6 +103,20 @@ class HaStatisticsPicker extends LitElement {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    this._updateStatistics([...currentEntities, toAdd]);
 | 
					    this._updateStatistics([...currentEntities, toAdd]);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static get styles(): CSSResultGroup {
 | 
				
			||||||
 | 
					    return css`
 | 
				
			||||||
 | 
					      :host {
 | 
				
			||||||
 | 
					        width: 200px;
 | 
				
			||||||
 | 
					        display: block;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      ha-statistic-picker {
 | 
				
			||||||
 | 
					        display: block;
 | 
				
			||||||
 | 
					        width: 100%;
 | 
				
			||||||
 | 
					        margin-top: 8px;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    `;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
declare global {
 | 
					declare global {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,3 @@
 | 
				
			|||||||
import { mdiCheck } from "@mdi/js";
 | 
					 | 
				
			||||||
import { html, LitElement, TemplateResult } from "lit";
 | 
					import { html, LitElement, TemplateResult } from "lit";
 | 
				
			||||||
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
 | 
					import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
 | 
				
			||||||
import { customElement, property, query, state } from "lit/decorators";
 | 
					import { customElement, property, query, state } from "lit/decorators";
 | 
				
			||||||
@@ -12,39 +11,12 @@ import { PolymerChangedEvent } from "../polymer-types";
 | 
				
			|||||||
import { HomeAssistant } from "../types";
 | 
					import { HomeAssistant } from "../types";
 | 
				
			||||||
import { HaComboBox } from "./ha-combo-box";
 | 
					import { HaComboBox } from "./ha-combo-box";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// eslint-disable-next-line lit/prefer-static-styles
 | 
					const rowRenderer: ComboBoxLitRenderer<HassioAddonInfo> = (
 | 
				
			||||||
const rowRenderer: ComboBoxLitRenderer<HassioAddonInfo> = (item) => html`<style>
 | 
					  item
 | 
				
			||||||
    paper-item {
 | 
					) => html`<mwc-list-item twoline>
 | 
				
			||||||
      padding: 0;
 | 
					  <span>${item.name}</span>
 | 
				
			||||||
      margin: -10px;
 | 
					  <span slot="secondary">${item.slug}</span>
 | 
				
			||||||
      margin-left: 0px;
 | 
					</mwc-list-item>`;
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    #content {
 | 
					 | 
				
			||||||
      display: flex;
 | 
					 | 
				
			||||||
      align-items: center;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    :host([selected]) paper-item {
 | 
					 | 
				
			||||||
      margin-left: 0;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    ha-svg-icon {
 | 
					 | 
				
			||||||
      padding-left: 2px;
 | 
					 | 
				
			||||||
      margin-right: -2px;
 | 
					 | 
				
			||||||
      color: var(--secondary-text-color);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    :host(:not([selected])) ha-svg-icon {
 | 
					 | 
				
			||||||
      display: none;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    :host([selected]) paper-icon-item {
 | 
					 | 
				
			||||||
      margin-left: 0;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  </style>
 | 
					 | 
				
			||||||
  <ha-svg-icon .path=${mdiCheck}></ha-svg-icon>
 | 
					 | 
				
			||||||
  <paper-item>
 | 
					 | 
				
			||||||
    <paper-item-body two-line>
 | 
					 | 
				
			||||||
      ${item.name}
 | 
					 | 
				
			||||||
      <span secondary>${item.slug}</span>
 | 
					 | 
				
			||||||
    </paper-item-body>
 | 
					 | 
				
			||||||
  </paper-item>`;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
@customElement("ha-addon-picker")
 | 
					@customElement("ha-addon-picker")
 | 
				
			||||||
class HaAddonPicker extends LitElement {
 | 
					class HaAddonPicker extends LitElement {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,19 +1,6 @@
 | 
				
			|||||||
import { mdiCheck, mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
 | 
					 | 
				
			||||||
import "@polymer/paper-input/paper-input";
 | 
					 | 
				
			||||||
import "@polymer/paper-item/paper-item";
 | 
					 | 
				
			||||||
import "@polymer/paper-item/paper-item-body";
 | 
					 | 
				
			||||||
import "@polymer/paper-listbox/paper-listbox";
 | 
					 | 
				
			||||||
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
 | 
					 | 
				
			||||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
 | 
					import { UnsubscribeFunc } from "home-assistant-js-websocket";
 | 
				
			||||||
import {
 | 
					import { html, LitElement, PropertyValues, TemplateResult } from "lit";
 | 
				
			||||||
  css,
 | 
					import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
 | 
				
			||||||
  CSSResultGroup,
 | 
					 | 
				
			||||||
  html,
 | 
					 | 
				
			||||||
  LitElement,
 | 
					 | 
				
			||||||
  PropertyValues,
 | 
					 | 
				
			||||||
  TemplateResult,
 | 
					 | 
				
			||||||
} from "lit";
 | 
					 | 
				
			||||||
import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers";
 | 
					 | 
				
			||||||
import { customElement, property, query, state } from "lit/decorators";
 | 
					import { customElement, property, query, state } from "lit/decorators";
 | 
				
			||||||
import { classMap } from "lit/directives/class-map";
 | 
					import { classMap } from "lit/directives/class-map";
 | 
				
			||||||
import memoizeOne from "memoize-one";
 | 
					import memoizeOne from "memoize-one";
 | 
				
			||||||
@@ -41,38 +28,18 @@ import { SubscribeMixin } from "../mixins/subscribe-mixin";
 | 
				
			|||||||
import { PolymerChangedEvent } from "../polymer-types";
 | 
					import { PolymerChangedEvent } from "../polymer-types";
 | 
				
			||||||
import { HomeAssistant } from "../types";
 | 
					import { HomeAssistant } from "../types";
 | 
				
			||||||
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
 | 
					import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
 | 
				
			||||||
 | 
					import type { HaComboBox } from "./ha-combo-box";
 | 
				
			||||||
 | 
					import "./ha-combo-box";
 | 
				
			||||||
import "./ha-icon-button";
 | 
					import "./ha-icon-button";
 | 
				
			||||||
import "./ha-svg-icon";
 | 
					import "./ha-svg-icon";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const rowRenderer: ComboBoxLitRenderer<AreaRegistryEntry> = (
 | 
					const rowRenderer: ComboBoxLitRenderer<AreaRegistryEntry> = (
 | 
				
			||||||
  item
 | 
					  item
 | 
				
			||||||
  // eslint-disable-next-line lit/prefer-static-styles
 | 
					) => html`<mwc-list-item
 | 
				
			||||||
) => html`<style>
 | 
					  class=${classMap({ "add-new": item.area_id === "add_new" })}
 | 
				
			||||||
    paper-item {
 | 
					>
 | 
				
			||||||
      padding: 0;
 | 
					  ${item.name}
 | 
				
			||||||
      margin: -10px;
 | 
					</mwc-list-item>`;
 | 
				
			||||||
      margin-left: 0;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    #content {
 | 
					 | 
				
			||||||
      display: flex;
 | 
					 | 
				
			||||||
      align-items: center;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    ha-svg-icon {
 | 
					 | 
				
			||||||
      padding-left: 2px;
 | 
					 | 
				
			||||||
      margin-right: -2px;
 | 
					 | 
				
			||||||
      color: var(--secondary-text-color);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    :host(:not([selected])) ha-svg-icon {
 | 
					 | 
				
			||||||
      display: none;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    :host([selected]) paper-item {
 | 
					 | 
				
			||||||
      margin-left: 10px;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  </style>
 | 
					 | 
				
			||||||
  <ha-svg-icon .path=${mdiCheck}></ha-svg-icon>
 | 
					 | 
				
			||||||
  <paper-item class=${classMap({ "add-new": item.area_id === "add_new" })}>
 | 
					 | 
				
			||||||
    <paper-item-body two-line>${item.name}</paper-item-body>
 | 
					 | 
				
			||||||
  </paper-item>`;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
@customElement("ha-area-picker")
 | 
					@customElement("ha-area-picker")
 | 
				
			||||||
export class HaAreaPicker extends SubscribeMixin(LitElement) {
 | 
					export class HaAreaPicker extends SubscribeMixin(LitElement) {
 | 
				
			||||||
@@ -125,7 +92,9 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @state() private _opened?: boolean;
 | 
					  @state() private _opened?: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @query("vaadin-combo-box-light", true) public comboBox!: HTMLElement;
 | 
					  @query("ha-combo-box", true) public comboBox!: HaComboBox;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private _filter?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private _init = false;
 | 
					  private _init = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -145,13 +114,13 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  public open() {
 | 
					  public open() {
 | 
				
			||||||
    this.updateComplete.then(() => {
 | 
					    this.updateComplete.then(() => {
 | 
				
			||||||
      (this.shadowRoot?.querySelector("vaadin-combo-box-light") as any)?.open();
 | 
					      this.comboBox?.open();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public focus() {
 | 
					  public focus() {
 | 
				
			||||||
    this.updateComplete.then(() => {
 | 
					    this.updateComplete.then(() => {
 | 
				
			||||||
      this.shadowRoot?.querySelector("paper-input")?.focus();
 | 
					      this.comboBox?.focus();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -170,7 +139,7 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
 | 
				
			|||||||
      if (!areas.length) {
 | 
					      if (!areas.length) {
 | 
				
			||||||
        return [
 | 
					        return [
 | 
				
			||||||
          {
 | 
					          {
 | 
				
			||||||
            area_id: "",
 | 
					            area_id: "no_areas",
 | 
				
			||||||
            name: this.hass.localize("ui.components.area-picker.no_areas"),
 | 
					            name: this.hass.localize("ui.components.area-picker.no_areas"),
 | 
				
			||||||
            picture: null,
 | 
					            picture: null,
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
@@ -294,7 +263,7 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
 | 
				
			|||||||
      if (!outputAreas.length) {
 | 
					      if (!outputAreas.length) {
 | 
				
			||||||
        outputAreas = [
 | 
					        outputAreas = [
 | 
				
			||||||
          {
 | 
					          {
 | 
				
			||||||
            area_id: "",
 | 
					            area_id: "no_areas",
 | 
				
			||||||
            name: this.hass.localize("ui.components.area-picker.no_match"),
 | 
					            name: this.hass.localize("ui.components.area-picker.no_match"),
 | 
				
			||||||
            picture: null,
 | 
					            picture: null,
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
@@ -339,52 +308,25 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
 | 
				
			|||||||
      return html``;
 | 
					      return html``;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return html`
 | 
					    return html`
 | 
				
			||||||
      <vaadin-combo-box-light
 | 
					      <ha-combo-box
 | 
				
			||||||
 | 
					        .hass=${this.hass}
 | 
				
			||||||
        item-value-path="area_id"
 | 
					        item-value-path="area_id"
 | 
				
			||||||
        item-id-path="area_id"
 | 
					        item-id-path="area_id"
 | 
				
			||||||
        item-label-path="name"
 | 
					        item-label-path="name"
 | 
				
			||||||
        .value=${this.value}
 | 
					        .value=${this.value}
 | 
				
			||||||
        .disabled=${this.disabled}
 | 
					        .disabled=${this.disabled}
 | 
				
			||||||
        ${comboBoxRenderer(rowRenderer)}
 | 
					 | 
				
			||||||
        @opened-changed=${this._openedChanged}
 | 
					 | 
				
			||||||
        @value-changed=${this._areaChanged}
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <paper-input
 | 
					 | 
				
			||||||
        .label=${this.label === undefined && this.hass
 | 
					        .label=${this.label === undefined && this.hass
 | 
				
			||||||
          ? this.hass.localize("ui.components.area-picker.area")
 | 
					          ? this.hass.localize("ui.components.area-picker.area")
 | 
				
			||||||
          : this.label}
 | 
					          : this.label}
 | 
				
			||||||
        .placeholder=${this.placeholder
 | 
					        .placeholder=${this.placeholder
 | 
				
			||||||
          ? this._area(this.placeholder)?.name
 | 
					          ? this._area(this.placeholder)?.name
 | 
				
			||||||
          : undefined}
 | 
					          : undefined}
 | 
				
			||||||
          .disabled=${this.disabled}
 | 
					        .renderer=${rowRenderer}
 | 
				
			||||||
          class="input"
 | 
					        @filter-changed=${this._filterChanged}
 | 
				
			||||||
          autocapitalize="none"
 | 
					        @opened-changed=${this._openedChanged}
 | 
				
			||||||
          autocomplete="off"
 | 
					        @value-changed=${this._areaChanged}
 | 
				
			||||||
          autocorrect="off"
 | 
					 | 
				
			||||||
          spellcheck="false"
 | 
					 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
          ${this.value
 | 
					      </ha-combo-box>
 | 
				
			||||||
            ? html`
 | 
					 | 
				
			||||||
                <ha-icon-button
 | 
					 | 
				
			||||||
                  .label=${this.hass.localize(
 | 
					 | 
				
			||||||
                    "ui.components.area-picker.clear"
 | 
					 | 
				
			||||||
                  )}
 | 
					 | 
				
			||||||
                  .path=${mdiClose}
 | 
					 | 
				
			||||||
                  slot="suffix"
 | 
					 | 
				
			||||||
                  class="clear-button"
 | 
					 | 
				
			||||||
                  @click=${this._clearValue}
 | 
					 | 
				
			||||||
                ></ha-icon-button>
 | 
					 | 
				
			||||||
              `
 | 
					 | 
				
			||||||
            : ""}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          <ha-icon-button
 | 
					 | 
				
			||||||
            .label=${this.hass.localize("ui.components.area-picker.toggle")}
 | 
					 | 
				
			||||||
            .path=${this._opened ? mdiMenuUp : mdiMenuDown}
 | 
					 | 
				
			||||||
            slot="suffix"
 | 
					 | 
				
			||||||
            class="toggle-button"
 | 
					 | 
				
			||||||
          ></ha-icon-button>
 | 
					 | 
				
			||||||
        </paper-input>
 | 
					 | 
				
			||||||
      </vaadin-combo-box-light>
 | 
					 | 
				
			||||||
    `;
 | 
					    `;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -392,9 +334,29 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
 | 
				
			|||||||
    this._areas?.find((area) => area.area_id === areaId)
 | 
					    this._areas?.find((area) => area.area_id === areaId)
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private _clearValue(ev: Event) {
 | 
					  private _filterChanged(ev: CustomEvent): void {
 | 
				
			||||||
    ev.stopPropagation();
 | 
					    this._filter = ev.detail.value;
 | 
				
			||||||
    this._setValue("");
 | 
					    if (!this._filter) {
 | 
				
			||||||
 | 
					      this.comboBox.filteredItems = this.comboBox.items;
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    // @ts-ignore
 | 
				
			||||||
 | 
					    if (!this.noAdd && this.comboBox._comboBox.filteredItems?.length === 0) {
 | 
				
			||||||
 | 
					      this.comboBox.filteredItems = [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          area_id: "add_new_suggestion",
 | 
				
			||||||
 | 
					          name: this.hass.localize(
 | 
				
			||||||
 | 
					            "ui.components.area-picker.add_new_sugestion",
 | 
				
			||||||
 | 
					            { name: this._filter }
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					          picture: null,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      ];
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      this.comboBox.filteredItems = this.comboBox.items?.filter((item) =>
 | 
				
			||||||
 | 
					        item.name.toLowerCase().includes(this._filter!.toLowerCase())
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private get _value() {
 | 
					  private get _value() {
 | 
				
			||||||
@@ -406,9 +368,14 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private _areaChanged(ev: PolymerChangedEvent<string>) {
 | 
					  private _areaChanged(ev: PolymerChangedEvent<string>) {
 | 
				
			||||||
    const newValue = ev.detail.value;
 | 
					    ev.stopPropagation();
 | 
				
			||||||
 | 
					    let newValue = ev.detail.value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (newValue !== "add_new") {
 | 
					    if (newValue === "no_areas") {
 | 
				
			||||||
 | 
					      newValue = "";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!["add_new_suggestion", "add_new"].includes(newValue)) {
 | 
				
			||||||
      if (newValue !== this._value) {
 | 
					      if (newValue !== this._value) {
 | 
				
			||||||
        this._setValue(newValue);
 | 
					        this._setValue(newValue);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@@ -425,6 +392,8 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
 | 
				
			|||||||
      inputLabel: this.hass.localize(
 | 
					      inputLabel: this.hass.localize(
 | 
				
			||||||
        "ui.components.area-picker.add_dialog.name"
 | 
					        "ui.components.area-picker.add_dialog.name"
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
 | 
					      defaultValue:
 | 
				
			||||||
 | 
					        newValue === "add_new_suggestion" ? this._filter : undefined,
 | 
				
			||||||
      confirm: async (name) => {
 | 
					      confirm: async (name) => {
 | 
				
			||||||
        if (!name) {
 | 
					        if (!name) {
 | 
				
			||||||
          return;
 | 
					          return;
 | 
				
			||||||
@@ -445,6 +414,8 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
 | 
				
			|||||||
            this.entityFilter,
 | 
					            this.entityFilter,
 | 
				
			||||||
            this.noAdd
 | 
					            this.noAdd
 | 
				
			||||||
          );
 | 
					          );
 | 
				
			||||||
 | 
					          await this.updateComplete;
 | 
				
			||||||
 | 
					          await this.comboBox.updateComplete;
 | 
				
			||||||
          this._setValue(area.area_id);
 | 
					          this._setValue(area.area_id);
 | 
				
			||||||
        } catch (err: any) {
 | 
					        } catch (err: any) {
 | 
				
			||||||
          showAlertDialog(this, {
 | 
					          showAlertDialog(this, {
 | 
				
			||||||
@@ -465,19 +436,6 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
 | 
				
			|||||||
      fireEvent(this, "change");
 | 
					      fireEvent(this, "change");
 | 
				
			||||||
    }, 0);
 | 
					    }, 0);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					 | 
				
			||||||
  static get styles(): CSSResultGroup {
 | 
					 | 
				
			||||||
    return css`
 | 
					 | 
				
			||||||
      paper-input > ha-icon-button {
 | 
					 | 
				
			||||||
        --mdc-icon-button-size: 24px;
 | 
					 | 
				
			||||||
        padding: 2px;
 | 
					 | 
				
			||||||
        color: var(--secondary-text-color);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      [hidden] {
 | 
					 | 
				
			||||||
        display: none;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    `;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
declare global {
 | 
					declare global {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										313
									
								
								src/components/ha-base-time-input.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										313
									
								
								src/components/ha-base-time-input.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,313 @@
 | 
				
			|||||||
 | 
					import { LitElement, html, TemplateResult, css } from "lit";
 | 
				
			||||||
 | 
					import { customElement, property } from "lit/decorators";
 | 
				
			||||||
 | 
					import "./ha-select";
 | 
				
			||||||
 | 
					import "@material/mwc-list/mwc-list-item";
 | 
				
			||||||
 | 
					import "./ha-textfield";
 | 
				
			||||||
 | 
					import { fireEvent } from "../common/dom/fire_event";
 | 
				
			||||||
 | 
					import { stopPropagation } from "../common/dom/stop_propagation";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface TimeChangedEvent {
 | 
				
			||||||
 | 
					  hours: number;
 | 
				
			||||||
 | 
					  minutes: number;
 | 
				
			||||||
 | 
					  seconds: number;
 | 
				
			||||||
 | 
					  milliseconds: number;
 | 
				
			||||||
 | 
					  amPm?: "AM" | "PM";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@customElement("ha-base-time-input")
 | 
				
			||||||
 | 
					export class HaBaseTimeInput extends LitElement {
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Label for the input
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  @property() label?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * auto validate time inputs
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  @property({ type: Boolean }) autoValidate = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * determines if inputs are required
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  @property({ type: Boolean }) public required?: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * 12 or 24 hr format
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  @property({ type: Number }) format: 12 | 24 = 12;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * disables the inputs
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  @property({ type: Boolean }) disabled = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * hour
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  @property({ type: Number }) hours = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * minute
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  @property({ type: Number }) minutes = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * second
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  @property({ type: Number }) seconds = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * milli second
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  @property({ type: Number }) milliseconds = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Label for the hour input
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  @property() hourLabel = "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Label for the min input
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  @property() minLabel = "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Label for the sec input
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  @property() secLabel = "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Label for the milli sec input
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  @property() millisecLabel = "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * show the sec field
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  @property({ type: Boolean }) enableSecond = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * show the milli sec field
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  @property({ type: Boolean }) enableMillisecond = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * limit hours input
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  @property({ type: Boolean }) noHoursLimit = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * AM or PM
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  @property() amPm: "AM" | "PM" = "AM";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Formatted time string
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  @property() value?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  protected render(): TemplateResult {
 | 
				
			||||||
 | 
					    return html`
 | 
				
			||||||
 | 
					      ${this.label ? html`<label>${this.label}</label>` : ""}
 | 
				
			||||||
 | 
					      <div class="time-input-wrap">
 | 
				
			||||||
 | 
					        <ha-textfield
 | 
				
			||||||
 | 
					          id="hour"
 | 
				
			||||||
 | 
					          type="number"
 | 
				
			||||||
 | 
					          inputmode="numeric"
 | 
				
			||||||
 | 
					          .value=${this.hours}
 | 
				
			||||||
 | 
					          .label=${this.hourLabel}
 | 
				
			||||||
 | 
					          name="hours"
 | 
				
			||||||
 | 
					          @input=${this._valueChanged}
 | 
				
			||||||
 | 
					          @focus=${this._onFocus}
 | 
				
			||||||
 | 
					          no-spinner
 | 
				
			||||||
 | 
					          .required=${this.required}
 | 
				
			||||||
 | 
					          .autoValidate=${this.autoValidate}
 | 
				
			||||||
 | 
					          maxlength="2"
 | 
				
			||||||
 | 
					          .max=${this._hourMax}
 | 
				
			||||||
 | 
					          min="0"
 | 
				
			||||||
 | 
					          .disabled=${this.disabled}
 | 
				
			||||||
 | 
					          suffix=":"
 | 
				
			||||||
 | 
					          class="hasSuffix"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					        </ha-textfield>
 | 
				
			||||||
 | 
					        <ha-textfield
 | 
				
			||||||
 | 
					          id="min"
 | 
				
			||||||
 | 
					          type="number"
 | 
				
			||||||
 | 
					          inputmode="numeric"
 | 
				
			||||||
 | 
					          .value=${this._formatValue(this.minutes)}
 | 
				
			||||||
 | 
					          .label=${this.minLabel}
 | 
				
			||||||
 | 
					          @input=${this._valueChanged}
 | 
				
			||||||
 | 
					          @focus=${this._onFocus}
 | 
				
			||||||
 | 
					          name="minutes"
 | 
				
			||||||
 | 
					          no-spinner
 | 
				
			||||||
 | 
					          .required=${this.required}
 | 
				
			||||||
 | 
					          .autoValidate=${this.autoValidate}
 | 
				
			||||||
 | 
					          maxlength="2"
 | 
				
			||||||
 | 
					          max="59"
 | 
				
			||||||
 | 
					          min="0"
 | 
				
			||||||
 | 
					          .disabled=${this.disabled}
 | 
				
			||||||
 | 
					          .suffix=${this.enableSecond ? ":" : ""}
 | 
				
			||||||
 | 
					          class=${this.enableSecond ? "has-suffix" : ""}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					        </ha-textfield>
 | 
				
			||||||
 | 
					        ${this.enableSecond
 | 
				
			||||||
 | 
					          ? html`<ha-textfield
 | 
				
			||||||
 | 
					              id="sec"
 | 
				
			||||||
 | 
					              type="number"
 | 
				
			||||||
 | 
					              inputmode="numeric"
 | 
				
			||||||
 | 
					              .value=${this._formatValue(this.seconds)}
 | 
				
			||||||
 | 
					              .label=${this.secLabel}
 | 
				
			||||||
 | 
					              @input=${this._valueChanged}
 | 
				
			||||||
 | 
					              @focus=${this._onFocus}
 | 
				
			||||||
 | 
					              name="seconds"
 | 
				
			||||||
 | 
					              no-spinner
 | 
				
			||||||
 | 
					              .required=${this.required}
 | 
				
			||||||
 | 
					              .autoValidate=${this.autoValidate}
 | 
				
			||||||
 | 
					              maxlength="2"
 | 
				
			||||||
 | 
					              max="59"
 | 
				
			||||||
 | 
					              min="0"
 | 
				
			||||||
 | 
					              .disabled=${this.disabled}
 | 
				
			||||||
 | 
					              .suffix=${this.enableMillisecond ? ":" : ""}
 | 
				
			||||||
 | 
					              class=${this.enableMillisecond ? "has-suffix" : ""}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					            </ha-textfield>`
 | 
				
			||||||
 | 
					          : ""}
 | 
				
			||||||
 | 
					        ${this.enableMillisecond
 | 
				
			||||||
 | 
					          ? html`<ha-textfield
 | 
				
			||||||
 | 
					              id="millisec"
 | 
				
			||||||
 | 
					              type="number"
 | 
				
			||||||
 | 
					              .value=${this._formatValue(this.milliseconds, 3)}
 | 
				
			||||||
 | 
					              .label=${this.millisecLabel}
 | 
				
			||||||
 | 
					              @input=${this._valueChanged}
 | 
				
			||||||
 | 
					              @focus=${this._onFocus}
 | 
				
			||||||
 | 
					              name="milliseconds"
 | 
				
			||||||
 | 
					              no-spinner
 | 
				
			||||||
 | 
					              .required=${this.required}
 | 
				
			||||||
 | 
					              .autoValidate=${this.autoValidate}
 | 
				
			||||||
 | 
					              maxlength="3"
 | 
				
			||||||
 | 
					              max="999"
 | 
				
			||||||
 | 
					              min="0"
 | 
				
			||||||
 | 
					              .disabled=${this.disabled}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					            </ha-textfield>`
 | 
				
			||||||
 | 
					          : ""}
 | 
				
			||||||
 | 
					        ${this.format === 24
 | 
				
			||||||
 | 
					          ? ""
 | 
				
			||||||
 | 
					          : html`<ha-select
 | 
				
			||||||
 | 
					              .required=${this.required}
 | 
				
			||||||
 | 
					              .value=${this.amPm}
 | 
				
			||||||
 | 
					              .disabled=${this.disabled}
 | 
				
			||||||
 | 
					              name="amPm"
 | 
				
			||||||
 | 
					              naturalMenuWidth
 | 
				
			||||||
 | 
					              fixedMenuPosition
 | 
				
			||||||
 | 
					              @selected=${this._valueChanged}
 | 
				
			||||||
 | 
					              @closed=${stopPropagation}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              <mwc-list-item value="AM">AM</mwc-list-item>
 | 
				
			||||||
 | 
					              <mwc-list-item value="PM">PM</mwc-list-item>
 | 
				
			||||||
 | 
					            </ha-select>`}
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    `;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private _valueChanged(ev) {
 | 
				
			||||||
 | 
					    this[ev.target.name] =
 | 
				
			||||||
 | 
					      ev.target.name === "amPm" ? ev.target.value : Number(ev.target.value);
 | 
				
			||||||
 | 
					    const value: TimeChangedEvent = {
 | 
				
			||||||
 | 
					      hours: this.hours,
 | 
				
			||||||
 | 
					      minutes: this.minutes,
 | 
				
			||||||
 | 
					      seconds: this.seconds,
 | 
				
			||||||
 | 
					      milliseconds: this.milliseconds,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    if (this.format === 12) {
 | 
				
			||||||
 | 
					      value.amPm = this.amPm;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    fireEvent(this, "value-changed", {
 | 
				
			||||||
 | 
					      value,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private _onFocus(ev) {
 | 
				
			||||||
 | 
					    ev.target.select();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Format time fragments
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  private _formatValue(value: number, padding = 2) {
 | 
				
			||||||
 | 
					    return value.toString().padStart(padding, "0");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * 24 hour format has a max hr of 23
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  private get _hourMax() {
 | 
				
			||||||
 | 
					    if (this.noHoursLimit) {
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (this.format === 12) {
 | 
				
			||||||
 | 
					      return 12;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return 23;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static styles = css`
 | 
				
			||||||
 | 
					    :host {
 | 
				
			||||||
 | 
					      display: block;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    .time-input-wrap {
 | 
				
			||||||
 | 
					      display: flex;
 | 
				
			||||||
 | 
					      border-radius: var(--mdc-shape-small, 4px) var(--mdc-shape-small, 4px) 0 0;
 | 
				
			||||||
 | 
					      overflow: hidden;
 | 
				
			||||||
 | 
					      position: relative;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    ha-textfield {
 | 
				
			||||||
 | 
					      width: 40px;
 | 
				
			||||||
 | 
					      text-align: center;
 | 
				
			||||||
 | 
					      --mdc-shape-small: 0;
 | 
				
			||||||
 | 
					      --text-field-appearance: none;
 | 
				
			||||||
 | 
					      --text-field-padding: 0 4px;
 | 
				
			||||||
 | 
					      --text-field-suffix-padding-left: 2px;
 | 
				
			||||||
 | 
					      --text-field-suffix-padding-right: 0;
 | 
				
			||||||
 | 
					      --text-field-text-align: center;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    ha-textfield.hasSuffix {
 | 
				
			||||||
 | 
					      --text-field-padding: 0 0 0 4px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    ha-textfield:first-child {
 | 
				
			||||||
 | 
					      --text-field-border-top-left-radius: var(--mdc-shape-medium);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    ha-textfield:last-child {
 | 
				
			||||||
 | 
					      --text-field-border-top-right-radius: var(--mdc-shape-medium);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    ha-select {
 | 
				
			||||||
 | 
					      --mdc-shape-small: 0;
 | 
				
			||||||
 | 
					      width: 85px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    label {
 | 
				
			||||||
 | 
					      -moz-osx-font-smoothing: grayscale;
 | 
				
			||||||
 | 
					      -webkit-font-smoothing: antialiased;
 | 
				
			||||||
 | 
					      font-family: var(
 | 
				
			||||||
 | 
					        --mdc-typography-body2-font-family,
 | 
				
			||||||
 | 
					        var(--mdc-typography-font-family, Roboto, sans-serif)
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      font-size: var(--mdc-typography-body2-font-size, 0.875rem);
 | 
				
			||||||
 | 
					      line-height: var(--mdc-typography-body2-line-height, 1.25rem);
 | 
				
			||||||
 | 
					      font-weight: var(--mdc-typography-body2-font-weight, 400);
 | 
				
			||||||
 | 
					      letter-spacing: var(
 | 
				
			||||||
 | 
					        --mdc-typography-body2-letter-spacing,
 | 
				
			||||||
 | 
					        0.0178571429em
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      text-decoration: var(--mdc-typography-body2-text-decoration, inherit);
 | 
				
			||||||
 | 
					      text-transform: var(--mdc-typography-body2-text-transform, inherit);
 | 
				
			||||||
 | 
					      color: var(--mdc-theme-text-primary-on-background, rgba(0, 0, 0, 0.87));
 | 
				
			||||||
 | 
					      padding-left: 4px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  `;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					declare global {
 | 
				
			||||||
 | 
					  interface HTMLElementTagNameMap {
 | 
				
			||||||
 | 
					    "ha-base-time-input": HaBaseTimeInput;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,10 +1,10 @@
 | 
				
			|||||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
 | 
					import "@material/mwc-list/mwc-list-item";
 | 
				
			||||||
import "@polymer/paper-item/paper-item";
 | 
					import "./ha-select";
 | 
				
			||||||
import "@polymer/paper-listbox/paper-listbox";
 | 
					 | 
				
			||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
 | 
					import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
 | 
				
			||||||
import { customElement, property } from "lit/decorators";
 | 
					import { customElement, property } from "lit/decorators";
 | 
				
			||||||
import memoizeOne from "memoize-one";
 | 
					import memoizeOne from "memoize-one";
 | 
				
			||||||
import { fireEvent } from "../common/dom/fire_event";
 | 
					import { fireEvent } from "../common/dom/fire_event";
 | 
				
			||||||
 | 
					import { stopPropagation } from "../common/dom/stop_propagation";
 | 
				
			||||||
import { stringCompare } from "../common/string/compare";
 | 
					import { stringCompare } from "../common/string/compare";
 | 
				
			||||||
import { Blueprint, Blueprints, fetchBlueprints } from "../data/blueprint";
 | 
					import { Blueprint, Blueprints, fetchBlueprints } from "../data/blueprint";
 | 
				
			||||||
import { HomeAssistant } from "../types";
 | 
					import { HomeAssistant } from "../types";
 | 
				
			||||||
@@ -24,7 +24,11 @@ class HaBluePrintPicker extends LitElement {
 | 
				
			|||||||
  @property({ type: Boolean }) public disabled = false;
 | 
					  @property({ type: Boolean }) public disabled = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public open() {
 | 
					  public open() {
 | 
				
			||||||
    this.shadowRoot!.querySelector("paper-dropdown-menu-light")!.open();
 | 
					    const select = this.shadowRoot?.querySelector("ha-select");
 | 
				
			||||||
 | 
					    if (select) {
 | 
				
			||||||
 | 
					      // @ts-expect-error
 | 
				
			||||||
 | 
					      select.menuOpen = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private _processedBlueprints = memoizeOne((blueprints?: Blueprints) => {
 | 
					  private _processedBlueprints = memoizeOne((blueprints?: Blueprints) => {
 | 
				
			||||||
@@ -45,32 +49,29 @@ class HaBluePrintPicker extends LitElement {
 | 
				
			|||||||
      return html``;
 | 
					      return html``;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return html`
 | 
					    return html`
 | 
				
			||||||
      <paper-dropdown-menu-light
 | 
					      <ha-select
 | 
				
			||||||
        .label=${this.label ||
 | 
					        .label=${this.label ||
 | 
				
			||||||
        this.hass.localize("ui.components.blueprint-picker.label")}
 | 
					        this.hass.localize("ui.components.blueprint-picker.label")}
 | 
				
			||||||
 | 
					        fixedMenuPosition
 | 
				
			||||||
 | 
					        naturalMenuWidth
 | 
				
			||||||
 | 
					        .value=${this.value}
 | 
				
			||||||
        .disabled=${this.disabled}
 | 
					        .disabled=${this.disabled}
 | 
				
			||||||
        horizontal-align="left"
 | 
					        @selected=${this._blueprintChanged}
 | 
				
			||||||
 | 
					        @closed=${stopPropagation}
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        <paper-listbox
 | 
					        <mwc-list-item value="">
 | 
				
			||||||
          slot="dropdown-content"
 | 
					 | 
				
			||||||
          .selected=${this.value}
 | 
					 | 
				
			||||||
          attr-for-selected="data-blueprint-path"
 | 
					 | 
				
			||||||
          @iron-select=${this._blueprintChanged}
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          <paper-item data-blueprint-path="">
 | 
					 | 
				
			||||||
          ${this.hass.localize(
 | 
					          ${this.hass.localize(
 | 
				
			||||||
            "ui.components.blueprint-picker.select_blueprint"
 | 
					            "ui.components.blueprint-picker.select_blueprint"
 | 
				
			||||||
          )}
 | 
					          )}
 | 
				
			||||||
          </paper-item>
 | 
					        </mwc-list-item>
 | 
				
			||||||
        ${this._processedBlueprints(this.blueprints).map(
 | 
					        ${this._processedBlueprints(this.blueprints).map(
 | 
				
			||||||
          (blueprint) => html`
 | 
					          (blueprint) => html`
 | 
				
			||||||
              <paper-item data-blueprint-path=${blueprint.path}>
 | 
					            <mwc-list-item .value=${blueprint.path}>
 | 
				
			||||||
              ${blueprint.name}
 | 
					              ${blueprint.name}
 | 
				
			||||||
              </paper-item>
 | 
					            </mwc-list-item>
 | 
				
			||||||
          `
 | 
					          `
 | 
				
			||||||
        )}
 | 
					        )}
 | 
				
			||||||
        </paper-listbox>
 | 
					      </ha-select>
 | 
				
			||||||
      </paper-dropdown-menu-light>
 | 
					 | 
				
			||||||
    `;
 | 
					    `;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -84,10 +85,10 @@ class HaBluePrintPicker extends LitElement {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private _blueprintChanged(ev) {
 | 
					  private _blueprintChanged(ev) {
 | 
				
			||||||
    const newValue = ev.detail.item.dataset.blueprintPath;
 | 
					    const newValue = ev.target.value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (newValue !== this.value) {
 | 
					    if (newValue !== this.value) {
 | 
				
			||||||
      this.value = ev.detail.value;
 | 
					      this.value = newValue;
 | 
				
			||||||
      setTimeout(() => {
 | 
					      setTimeout(() => {
 | 
				
			||||||
        fireEvent(this, "value-changed", { value: newValue });
 | 
					        fireEvent(this, "value-changed", { value: newValue });
 | 
				
			||||||
        fireEvent(this, "change");
 | 
					        fireEvent(this, "change");
 | 
				
			||||||
@@ -100,15 +101,11 @@ class HaBluePrintPicker extends LitElement {
 | 
				
			|||||||
      :host {
 | 
					      :host {
 | 
				
			||||||
        display: inline-block;
 | 
					        display: inline-block;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      paper-dropdown-menu-light {
 | 
					      ha-select {
 | 
				
			||||||
        width: 100%;
 | 
					        width: 100%;
 | 
				
			||||||
        min-width: 200px;
 | 
					        min-width: 200px;
 | 
				
			||||||
        display: block;
 | 
					        display: block;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      paper-item {
 | 
					 | 
				
			||||||
        cursor: pointer;
 | 
					 | 
				
			||||||
        min-width: 200px;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    `;
 | 
					    `;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import "@material/mwc-menu";
 | 
					import "@material/mwc-menu";
 | 
				
			||||||
import type { Corner, Menu } from "@material/mwc-menu";
 | 
					import type { Corner, Menu, MenuCorner } from "@material/mwc-menu";
 | 
				
			||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
 | 
					import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
 | 
				
			||||||
import { customElement, property, query } from "lit/decorators";
 | 
					import { customElement, property, query } from "lit/decorators";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -7,6 +7,12 @@ import { customElement, property, query } from "lit/decorators";
 | 
				
			|||||||
export class HaButtonMenu extends LitElement {
 | 
					export class HaButtonMenu extends LitElement {
 | 
				
			||||||
  @property() public corner: Corner = "TOP_START";
 | 
					  @property() public corner: Corner = "TOP_START";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @property() public menuCorner: MenuCorner = "START";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @property({ type: Number }) public x?: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @property({ type: Number }) public y?: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @property({ type: Boolean }) public multi = false;
 | 
					  @property({ type: Boolean }) public multi = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @property({ type: Boolean }) public activatable = false;
 | 
					  @property({ type: Boolean }) public activatable = false;
 | 
				
			||||||
@@ -32,9 +38,12 @@ export class HaButtonMenu extends LitElement {
 | 
				
			|||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <mwc-menu
 | 
					      <mwc-menu
 | 
				
			||||||
        .corner=${this.corner}
 | 
					        .corner=${this.corner}
 | 
				
			||||||
 | 
					        .menuCorner=${this.menuCorner}
 | 
				
			||||||
        .fixed=${this.fixed}
 | 
					        .fixed=${this.fixed}
 | 
				
			||||||
        .multi=${this.multi}
 | 
					        .multi=${this.multi}
 | 
				
			||||||
        .activatable=${this.activatable}
 | 
					        .activatable=${this.activatable}
 | 
				
			||||||
 | 
					        .y=${this.y}
 | 
				
			||||||
 | 
					        .x=${this.x}
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        <slot></slot>
 | 
					        <slot></slot>
 | 
				
			||||||
      </mwc-menu>
 | 
					      </mwc-menu>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,6 +4,7 @@ import { mdiFilterVariant } from "@mdi/js";
 | 
				
			|||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
 | 
					import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
 | 
				
			||||||
import { customElement, property, state } from "lit/decorators";
 | 
					import { customElement, property, state } from "lit/decorators";
 | 
				
			||||||
import { fireEvent } from "../common/dom/fire_event";
 | 
					import { fireEvent } from "../common/dom/fire_event";
 | 
				
			||||||
 | 
					import { stopPropagation } from "../common/dom/stop_propagation";
 | 
				
			||||||
import { computeStateName } from "../common/entity/compute_state_name";
 | 
					import { computeStateName } from "../common/entity/compute_state_name";
 | 
				
			||||||
import { computeDeviceName } from "../data/device_registry";
 | 
					import { computeDeviceName } from "../data/device_registry";
 | 
				
			||||||
import { findRelated, RelatedResult } from "../data/search";
 | 
					import { findRelated, RelatedResult } from "../data/search";
 | 
				
			||||||
@@ -65,6 +66,7 @@ export class HaRelatedFilterButtonMenu extends LitElement {
 | 
				
			|||||||
        .fullwidth=${this.narrow}
 | 
					        .fullwidth=${this.narrow}
 | 
				
			||||||
        .corner=${this.corner}
 | 
					        .corner=${this.corner}
 | 
				
			||||||
        @closed=${this._onClosed}
 | 
					        @closed=${this._onClosed}
 | 
				
			||||||
 | 
					        @input=${stopPropagation}
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        <ha-area-picker
 | 
					        <ha-area-picker
 | 
				
			||||||
          .label=${this.hass.localize(
 | 
					          .label=${this.hass.localize(
 | 
				
			||||||
@@ -74,6 +76,7 @@ export class HaRelatedFilterButtonMenu extends LitElement {
 | 
				
			|||||||
          .value=${this.value?.area}
 | 
					          .value=${this.value?.area}
 | 
				
			||||||
          no-add
 | 
					          no-add
 | 
				
			||||||
          @value-changed=${this._areaPicked}
 | 
					          @value-changed=${this._areaPicked}
 | 
				
			||||||
 | 
					          @click=${this._preventDefault}
 | 
				
			||||||
        ></ha-area-picker>
 | 
					        ></ha-area-picker>
 | 
				
			||||||
        <ha-device-picker
 | 
					        <ha-device-picker
 | 
				
			||||||
          .label=${this.hass.localize(
 | 
					          .label=${this.hass.localize(
 | 
				
			||||||
@@ -82,6 +85,7 @@ export class HaRelatedFilterButtonMenu extends LitElement {
 | 
				
			|||||||
          .hass=${this.hass}
 | 
					          .hass=${this.hass}
 | 
				
			||||||
          .value=${this.value?.device}
 | 
					          .value=${this.value?.device}
 | 
				
			||||||
          @value-changed=${this._devicePicked}
 | 
					          @value-changed=${this._devicePicked}
 | 
				
			||||||
 | 
					          @click=${this._preventDefault}
 | 
				
			||||||
        ></ha-device-picker>
 | 
					        ></ha-device-picker>
 | 
				
			||||||
        <ha-entity-picker
 | 
					        <ha-entity-picker
 | 
				
			||||||
          .label=${this.hass.localize(
 | 
					          .label=${this.hass.localize(
 | 
				
			||||||
@@ -91,6 +95,7 @@ export class HaRelatedFilterButtonMenu extends LitElement {
 | 
				
			|||||||
          .value=${this.value?.entity}
 | 
					          .value=${this.value?.entity}
 | 
				
			||||||
          .excludeDomains=${this.excludeDomains}
 | 
					          .excludeDomains=${this.excludeDomains}
 | 
				
			||||||
          @value-changed=${this._entityPicked}
 | 
					          @value-changed=${this._entityPicked}
 | 
				
			||||||
 | 
					          @click=${this._preventDefault}
 | 
				
			||||||
        ></ha-entity-picker>
 | 
					        ></ha-entity-picker>
 | 
				
			||||||
      </mwc-menu-surface>
 | 
					      </mwc-menu-surface>
 | 
				
			||||||
    `;
 | 
					    `;
 | 
				
			||||||
@@ -103,11 +108,17 @@ export class HaRelatedFilterButtonMenu extends LitElement {
 | 
				
			|||||||
    this._open = true;
 | 
					    this._open = true;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private _onClosed(): void {
 | 
					  private _onClosed(ev): void {
 | 
				
			||||||
 | 
					    ev.stopPropagation();
 | 
				
			||||||
    this._open = false;
 | 
					    this._open = false;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private _preventDefault(ev) {
 | 
				
			||||||
 | 
					    ev.preventDefault();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private async _entityPicked(ev: CustomEvent) {
 | 
					  private async _entityPicked(ev: CustomEvent) {
 | 
				
			||||||
 | 
					    ev.stopPropagation();
 | 
				
			||||||
    const entityId = ev.detail.value;
 | 
					    const entityId = ev.detail.value;
 | 
				
			||||||
    if (!entityId) {
 | 
					    if (!entityId) {
 | 
				
			||||||
      fireEvent(this, "related-changed", { value: undefined });
 | 
					      fireEvent(this, "related-changed", { value: undefined });
 | 
				
			||||||
@@ -127,6 +138,7 @@ export class HaRelatedFilterButtonMenu extends LitElement {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private async _devicePicked(ev: CustomEvent) {
 | 
					  private async _devicePicked(ev: CustomEvent) {
 | 
				
			||||||
 | 
					    ev.stopPropagation();
 | 
				
			||||||
    const deviceId = ev.detail.value;
 | 
					    const deviceId = ev.detail.value;
 | 
				
			||||||
    if (!deviceId) {
 | 
					    if (!deviceId) {
 | 
				
			||||||
      fireEvent(this, "related-changed", { value: undefined });
 | 
					      fireEvent(this, "related-changed", { value: undefined });
 | 
				
			||||||
@@ -150,6 +162,7 @@ export class HaRelatedFilterButtonMenu extends LitElement {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private async _areaPicked(ev: CustomEvent) {
 | 
					  private async _areaPicked(ev: CustomEvent) {
 | 
				
			||||||
 | 
					    ev.stopPropagation();
 | 
				
			||||||
    const areaId = ev.detail.value;
 | 
					    const areaId = ev.detail.value;
 | 
				
			||||||
    if (!areaId) {
 | 
					    if (!areaId) {
 | 
				
			||||||
      fireEvent(this, "related-changed", { value: undefined });
 | 
					      fireEvent(this, "related-changed", { value: undefined });
 | 
				
			||||||
@@ -173,9 +186,7 @@ export class HaRelatedFilterButtonMenu extends LitElement {
 | 
				
			|||||||
      :host {
 | 
					      :host {
 | 
				
			||||||
        display: inline-block;
 | 
					        display: inline-block;
 | 
				
			||||||
        position: relative;
 | 
					        position: relative;
 | 
				
			||||||
      }
 | 
					        --mdc-menu-min-width: 250px;
 | 
				
			||||||
      :host([narrow]) {
 | 
					 | 
				
			||||||
        position: static;
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      ha-area-picker,
 | 
					      ha-area-picker,
 | 
				
			||||||
      ha-device-picker,
 | 
					      ha-device-picker,
 | 
				
			||||||
@@ -185,8 +196,15 @@ export class HaRelatedFilterButtonMenu extends LitElement {
 | 
				
			|||||||
        padding: 4px 16px;
 | 
					        padding: 4px 16px;
 | 
				
			||||||
        box-sizing: border-box;
 | 
					        box-sizing: border-box;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					      ha-area-picker {
 | 
				
			||||||
 | 
					        padding-top: 16px;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      ha-entity-picker {
 | 
				
			||||||
 | 
					        padding-bottom: 16px;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
      :host([narrow]) ha-area-picker,
 | 
					      :host([narrow]) ha-area-picker,
 | 
				
			||||||
      :host([narrow]) ha-device-picker {
 | 
					      :host([narrow]) ha-device-picker,
 | 
				
			||||||
 | 
					      :host([narrow]) ha-entity-picker {
 | 
				
			||||||
        width: 100%;
 | 
					        width: 100%;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    `;
 | 
					    `;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										24
									
								
								src/components/ha-check-list-item.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/components/ha-check-list-item.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					import { css } from "lit";
 | 
				
			||||||
 | 
					import { CheckListItemBase } from "@material/mwc-list/mwc-check-list-item-base";
 | 
				
			||||||
 | 
					import { styles as controlStyles } from "@material/mwc-list/mwc-control-list-item.css";
 | 
				
			||||||
 | 
					import { styles } from "@material/mwc-list/mwc-list-item.css";
 | 
				
			||||||
 | 
					import { customElement } from "lit/decorators";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@customElement("ha-check-list-item")
 | 
				
			||||||
 | 
					export class HaCheckListItem extends CheckListItemBase {
 | 
				
			||||||
 | 
					  static override styles = [
 | 
				
			||||||
 | 
					    styles,
 | 
				
			||||||
 | 
					    controlStyles,
 | 
				
			||||||
 | 
					    css`
 | 
				
			||||||
 | 
					      :host {
 | 
				
			||||||
 | 
					        --mdc-theme-secondary: var(--primary-color);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    `,
 | 
				
			||||||
 | 
					  ];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					declare global {
 | 
				
			||||||
 | 
					  interface HTMLElementTagNameMap {
 | 
				
			||||||
 | 
					    "ha-check-list-item": HaCheckListItem;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,12 +1,18 @@
 | 
				
			|||||||
import { Checkbox } from "@material/mwc-checkbox";
 | 
					import { CheckboxBase } from "@material/mwc-checkbox/mwc-checkbox-base";
 | 
				
			||||||
 | 
					import { styles } from "@material/mwc-checkbox/mwc-checkbox.css";
 | 
				
			||||||
 | 
					import { css } from "lit";
 | 
				
			||||||
import { customElement } from "lit/decorators";
 | 
					import { customElement } from "lit/decorators";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@customElement("ha-checkbox")
 | 
					@customElement("ha-checkbox")
 | 
				
			||||||
export class HaCheckbox extends Checkbox {
 | 
					export class HaCheckbox extends CheckboxBase {
 | 
				
			||||||
  public firstUpdated() {
 | 
					  static override styles = [
 | 
				
			||||||
    super.firstUpdated();
 | 
					    styles,
 | 
				
			||||||
    this.style.setProperty("--mdc-theme-secondary", "var(--primary-color)");
 | 
					    css`
 | 
				
			||||||
 | 
					      :host {
 | 
				
			||||||
 | 
					        --mdc-theme-secondary: var(--primary-color);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					    `,
 | 
				
			||||||
 | 
					  ];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
declare global {
 | 
					declare global {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,16 @@
 | 
				
			|||||||
 | 
					import type {
 | 
				
			||||||
 | 
					  Completion,
 | 
				
			||||||
 | 
					  CompletionContext,
 | 
				
			||||||
 | 
					  CompletionResult,
 | 
				
			||||||
 | 
					} from "@codemirror/autocomplete";
 | 
				
			||||||
import type { EditorView, KeyBinding, ViewUpdate } from "@codemirror/view";
 | 
					import type { EditorView, KeyBinding, ViewUpdate } from "@codemirror/view";
 | 
				
			||||||
 | 
					import { HassEntities } from "home-assistant-js-websocket";
 | 
				
			||||||
import { css, CSSResultGroup, PropertyValues, ReactiveElement } from "lit";
 | 
					import { css, CSSResultGroup, PropertyValues, ReactiveElement } from "lit";
 | 
				
			||||||
import { customElement, property, state } from "lit/decorators";
 | 
					import { customElement, property, state } from "lit/decorators";
 | 
				
			||||||
 | 
					import memoizeOne from "memoize-one";
 | 
				
			||||||
import { fireEvent } from "../common/dom/fire_event";
 | 
					import { fireEvent } from "../common/dom/fire_event";
 | 
				
			||||||
import { loadCodeMirror } from "../resources/codemirror.ondemand";
 | 
					import { loadCodeMirror } from "../resources/codemirror.ondemand";
 | 
				
			||||||
 | 
					import { HomeAssistant } from "../types";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
declare global {
 | 
					declare global {
 | 
				
			||||||
  interface HASSDomEvents {
 | 
					  interface HASSDomEvents {
 | 
				
			||||||
@@ -24,10 +32,15 @@ export class HaCodeEditor extends ReactiveElement {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @property() public mode = "yaml";
 | 
					  @property() public mode = "yaml";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public hass?: HomeAssistant;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @property({ type: Boolean }) public autofocus = false;
 | 
					  @property({ type: Boolean }) public autofocus = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @property({ type: Boolean }) public readOnly = false;
 | 
					  @property({ type: Boolean }) public readOnly = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @property({ type: Boolean, attribute: "autocomplete-entities" })
 | 
				
			||||||
 | 
					  public autocompleteEntities = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @property() public error = false;
 | 
					  @property() public error = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @state() private _value = "";
 | 
					  @state() private _value = "";
 | 
				
			||||||
@@ -110,11 +123,7 @@ export class HaCodeEditor extends ReactiveElement {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  private async _load(): Promise<void> {
 | 
					  private async _load(): Promise<void> {
 | 
				
			||||||
    this._loadedCodeMirror = await loadCodeMirror();
 | 
					    this._loadedCodeMirror = await loadCodeMirror();
 | 
				
			||||||
 | 
					    const extensions = [
 | 
				
			||||||
    this.codemirror = new this._loadedCodeMirror.EditorView({
 | 
					 | 
				
			||||||
      state: this._loadedCodeMirror.EditorState.create({
 | 
					 | 
				
			||||||
        doc: this._value,
 | 
					 | 
				
			||||||
        extensions: [
 | 
					 | 
				
			||||||
      this._loadedCodeMirror.lineNumbers(),
 | 
					      this._loadedCodeMirror.lineNumbers(),
 | 
				
			||||||
      this._loadedCodeMirror.EditorState.allowMultipleSelections.of(true),
 | 
					      this._loadedCodeMirror.EditorState.allowMultipleSelections.of(true),
 | 
				
			||||||
      this._loadedCodeMirror.history(),
 | 
					      this._loadedCodeMirror.history(),
 | 
				
			||||||
@@ -140,13 +149,66 @@ export class HaCodeEditor extends ReactiveElement {
 | 
				
			|||||||
      this._loadedCodeMirror.EditorView.updateListener.of((update) =>
 | 
					      this._loadedCodeMirror.EditorView.updateListener.of((update) =>
 | 
				
			||||||
        this._onUpdate(update)
 | 
					        this._onUpdate(update)
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
        ],
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!this.readOnly && this.autocompleteEntities && this.hass) {
 | 
				
			||||||
 | 
					      extensions.push(
 | 
				
			||||||
 | 
					        this._loadedCodeMirror.autocompletion({
 | 
				
			||||||
 | 
					          override: [this._entityCompletions.bind(this)],
 | 
				
			||||||
 | 
					          maxRenderedOptions: 10,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.codemirror = new this._loadedCodeMirror.EditorView({
 | 
				
			||||||
 | 
					      state: this._loadedCodeMirror.EditorState.create({
 | 
				
			||||||
 | 
					        doc: this._value,
 | 
				
			||||||
 | 
					        extensions,
 | 
				
			||||||
      }),
 | 
					      }),
 | 
				
			||||||
      root: this.shadowRoot!,
 | 
					      root: this.shadowRoot!,
 | 
				
			||||||
      parent: this.shadowRoot!,
 | 
					      parent: this.shadowRoot!,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private _getStates = memoizeOne((states: HassEntities): Completion[] => {
 | 
				
			||||||
 | 
					    if (!states) {
 | 
				
			||||||
 | 
					      return [];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const options = Object.keys(states).map((key) => ({
 | 
				
			||||||
 | 
					      type: "variable",
 | 
				
			||||||
 | 
					      label: key,
 | 
				
			||||||
 | 
					      detail: states[key].attributes.friendly_name,
 | 
				
			||||||
 | 
					      info: `State: ${states[key].state}`,
 | 
				
			||||||
 | 
					    }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return options;
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private _entityCompletions(
 | 
				
			||||||
 | 
					    context: CompletionContext
 | 
				
			||||||
 | 
					  ): CompletionResult | null | Promise<CompletionResult | null> {
 | 
				
			||||||
 | 
					    const entityWord = context.matchBefore(/[a-z_]{3,}\./);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					      !entityWord ||
 | 
				
			||||||
 | 
					      (entityWord.from === entityWord.to && !context.explicit)
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const states = this._getStates(this.hass!.states);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!states || !states.length) {
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      from: Number(entityWord.from),
 | 
				
			||||||
 | 
					      options: states,
 | 
				
			||||||
 | 
					      span: /^\w*.\w*$/,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private _blockKeyboardShortcuts() {
 | 
					  private _blockKeyboardShortcuts() {
 | 
				
			||||||
    this.addEventListener("keydown", (ev) => ev.stopPropagation());
 | 
					    this.addEventListener("keydown", (ev) => ev.stopPropagation());
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -163,10 +225,9 @@ export class HaCodeEditor extends ReactiveElement {
 | 
				
			|||||||
    fireEvent(this, "value-changed", { value: this._value });
 | 
					    fireEvent(this, "value-changed", { value: this._value });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Only Lit 2.0 will use this
 | 
					 | 
				
			||||||
  static get styles(): CSSResultGroup {
 | 
					  static get styles(): CSSResultGroup {
 | 
				
			||||||
    return css`
 | 
					    return css`
 | 
				
			||||||
      :host(.error-state) div.cm-wrap .cm-gutters {
 | 
					      :host(.error-state) .cm-gutters {
 | 
				
			||||||
        border-color: var(--error-state-color, red);
 | 
					        border-color: var(--error-state-color, red);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    `;
 | 
					    `;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,37 +1,78 @@
 | 
				
			|||||||
 | 
					import "@material/mwc-list/mwc-list-item";
 | 
				
			||||||
import { mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
 | 
					import { mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
 | 
				
			||||||
import "@polymer/paper-input/paper-input";
 | 
					import "@vaadin/combo-box/theme/material/vaadin-combo-box-light";
 | 
				
			||||||
import "@polymer/paper-item/paper-item";
 | 
					import type { ComboBoxLight } from "@vaadin/combo-box/vaadin-combo-box-light";
 | 
				
			||||||
import "@polymer/paper-item/paper-item-body";
 | 
					import { registerStyles } from "@vaadin/vaadin-themable-mixin/register-styles";
 | 
				
			||||||
import "@polymer/paper-listbox/paper-listbox";
 | 
					 | 
				
			||||||
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
 | 
					 | 
				
			||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
 | 
					import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
 | 
				
			||||||
import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers";
 | 
					import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers";
 | 
				
			||||||
import { customElement, property, query, state } from "lit/decorators";
 | 
					import { customElement, property, query } from "lit/decorators";
 | 
				
			||||||
import { fireEvent } from "../common/dom/fire_event";
 | 
					import { fireEvent } from "../common/dom/fire_event";
 | 
				
			||||||
import { PolymerChangedEvent } from "../polymer-types";
 | 
					import { PolymerChangedEvent } from "../polymer-types";
 | 
				
			||||||
import { HomeAssistant } from "../types";
 | 
					import { HomeAssistant } from "../types";
 | 
				
			||||||
import "./ha-icon-button";
 | 
					import "./ha-icon-button";
 | 
				
			||||||
 | 
					import "./ha-textfield";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// eslint-disable-next-line lit/prefer-static-styles
 | 
					registerStyles(
 | 
				
			||||||
const defaultRowRenderer: ComboBoxLitRenderer<string> = (item) => html`<style>
 | 
					  "vaadin-combo-box-item",
 | 
				
			||||||
    paper-item {
 | 
					  css`
 | 
				
			||||||
      margin: -5px -10px;
 | 
					    :host {
 | 
				
			||||||
      padding: 0;
 | 
					      padding: 0;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  </style>
 | 
					    :host([focused]:not([disabled])) {
 | 
				
			||||||
  <paper-item>${item}</paper-item>`;
 | 
					      background-color: rgba(var(--rgb-primary-text-color, 0, 0, 0), 0.12);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    :host([selected]:not([disabled])) {
 | 
				
			||||||
 | 
					      background-color: transparent;
 | 
				
			||||||
 | 
					      color: var(--mdc-theme-primary);
 | 
				
			||||||
 | 
					      --mdc-ripple-color: var(--mdc-theme-primary);
 | 
				
			||||||
 | 
					      --mdc-theme-text-primary-on-background: var(--mdc-theme-primary);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    :host([selected]:not([disabled])):before {
 | 
				
			||||||
 | 
					      background-color: var(--mdc-theme-primary);
 | 
				
			||||||
 | 
					      opacity: 0.12;
 | 
				
			||||||
 | 
					      content: "";
 | 
				
			||||||
 | 
					      position: absolute;
 | 
				
			||||||
 | 
					      top: 0;
 | 
				
			||||||
 | 
					      left: 0;
 | 
				
			||||||
 | 
					      width: 100%;
 | 
				
			||||||
 | 
					      height: 100%;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    :host([selected][focused]:not([disabled])):before {
 | 
				
			||||||
 | 
					      opacity: 0.24;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    :host(:hover:not([disabled])) {
 | 
				
			||||||
 | 
					      background-color: transparent;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    [part="content"] {
 | 
				
			||||||
 | 
					      width: 100%;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    [part="checkmark"] {
 | 
				
			||||||
 | 
					      display: none;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  `
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@customElement("ha-combo-box")
 | 
					@customElement("ha-combo-box")
 | 
				
			||||||
export class HaComboBox extends LitElement {
 | 
					export class HaComboBox extends LitElement {
 | 
				
			||||||
  @property({ attribute: false }) public hass!: HomeAssistant;
 | 
					  @property({ attribute: false }) public hass?: HomeAssistant;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @property() public label?: string;
 | 
					  @property() public label?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @property() public value?: string;
 | 
					  @property() public value?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @property() public items?: [];
 | 
					  @property() public placeholder?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @property() public filteredItems?: [];
 | 
					  @property() public validationMessage?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @property({ attribute: "error-message" }) public errorMessage?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @property({ type: Boolean }) public invalid?: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @property({ type: Boolean }) public icon?: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @property() public items?: any[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @property() public filteredItems?: any[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @property({ attribute: "allow-custom-value", type: Boolean })
 | 
					  @property({ attribute: "allow-custom-value", type: Boolean })
 | 
				
			||||||
  public allowCustomValue?: boolean;
 | 
					  public allowCustomValue?: boolean;
 | 
				
			||||||
@@ -46,24 +87,25 @@ export class HaComboBox extends LitElement {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @property({ type: Boolean }) public disabled?: boolean;
 | 
					  @property({ type: Boolean }) public disabled?: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @state() private _opened?: boolean;
 | 
					  @property({ type: Boolean, reflect: true, attribute: "opened" })
 | 
				
			||||||
 | 
					  private _opened?: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @query("vaadin-combo-box-light", true) private _comboBox!: HTMLElement;
 | 
					  @query("vaadin-combo-box-light", true) private _comboBox!: ComboBoxLight;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public open() {
 | 
					  public open() {
 | 
				
			||||||
    this.updateComplete.then(() => {
 | 
					    this.updateComplete.then(() => {
 | 
				
			||||||
      (this._comboBox as any)?.open();
 | 
					      this._comboBox?.open();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public focus() {
 | 
					  public focus() {
 | 
				
			||||||
    this.updateComplete.then(() => {
 | 
					    this.updateComplete.then(() => {
 | 
				
			||||||
      this.shadowRoot?.querySelector("paper-input")?.focus();
 | 
					      this._comboBox?.inputElement?.focus();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public get selectedItem() {
 | 
					  public get selectedItem() {
 | 
				
			||||||
    return (this._comboBox as any).selectedItem;
 | 
					    return this._comboBox.selectedItem;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  protected render(): TemplateResult {
 | 
					  protected render(): TemplateResult {
 | 
				
			||||||
@@ -72,55 +114,78 @@ export class HaComboBox extends LitElement {
 | 
				
			|||||||
        .itemValuePath=${this.itemValuePath}
 | 
					        .itemValuePath=${this.itemValuePath}
 | 
				
			||||||
        .itemIdPath=${this.itemIdPath}
 | 
					        .itemIdPath=${this.itemIdPath}
 | 
				
			||||||
        .itemLabelPath=${this.itemLabelPath}
 | 
					        .itemLabelPath=${this.itemLabelPath}
 | 
				
			||||||
        .value=${this.value}
 | 
					        .value=${this.value || ""}
 | 
				
			||||||
        .items=${this.items}
 | 
					        .items=${this.items}
 | 
				
			||||||
        .filteredItems=${this.filteredItems}
 | 
					        .filteredItems=${this.filteredItems}
 | 
				
			||||||
        .allowCustomValue=${this.allowCustomValue}
 | 
					        .allowCustomValue=${this.allowCustomValue}
 | 
				
			||||||
        .disabled=${this.disabled}
 | 
					        .disabled=${this.disabled}
 | 
				
			||||||
        ${comboBoxRenderer(this.renderer || defaultRowRenderer)}
 | 
					        ${comboBoxRenderer(this.renderer || this._defaultRowRenderer)}
 | 
				
			||||||
        @opened-changed=${this._openedChanged}
 | 
					        @opened-changed=${this._openedChanged}
 | 
				
			||||||
        @filter-changed=${this._filterChanged}
 | 
					        @filter-changed=${this._filterChanged}
 | 
				
			||||||
        @value-changed=${this._valueChanged}
 | 
					        @value-changed=${this._valueChanged}
 | 
				
			||||||
 | 
					        attr-for-value="value"
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        <paper-input
 | 
					        <ha-textfield
 | 
				
			||||||
          .label=${this.label}
 | 
					          .label=${this.label}
 | 
				
			||||||
 | 
					          .placeholder=${this.placeholder}
 | 
				
			||||||
          .disabled=${this.disabled}
 | 
					          .disabled=${this.disabled}
 | 
				
			||||||
 | 
					          .validationMessage=${this.validationMessage}
 | 
				
			||||||
 | 
					          .errorMessage=${this.errorMessage}
 | 
				
			||||||
          class="input"
 | 
					          class="input"
 | 
				
			||||||
          autocapitalize="none"
 | 
					          autocapitalize="none"
 | 
				
			||||||
          autocomplete="off"
 | 
					          autocomplete="off"
 | 
				
			||||||
          autocorrect="off"
 | 
					          autocorrect="off"
 | 
				
			||||||
          spellcheck="false"
 | 
					          spellcheck="false"
 | 
				
			||||||
 | 
					          .suffix=${html`<div style="width: 28px;"></div>`}
 | 
				
			||||||
 | 
					          .icon=${this.icon}
 | 
				
			||||||
 | 
					          .invalid=${this.invalid}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
 | 
					          <slot name="icon" slot="leadingIcon"></slot>
 | 
				
			||||||
 | 
					        </ha-textfield>
 | 
				
			||||||
        ${this.value
 | 
					        ${this.value
 | 
				
			||||||
            ? html`
 | 
					          ? html`<ha-svg-icon
 | 
				
			||||||
                <ha-icon-button
 | 
					              aria-label=${this.hass?.localize("ui.components.combo-box.clear")}
 | 
				
			||||||
                  .label=${this.hass.localize("ui.components.combo-box.clear")}
 | 
					 | 
				
			||||||
                  .path=${mdiClose}
 | 
					 | 
				
			||||||
                  slot="suffix"
 | 
					 | 
				
			||||||
              class="clear-button"
 | 
					              class="clear-button"
 | 
				
			||||||
 | 
					              .path=${mdiClose}
 | 
				
			||||||
              @click=${this._clearValue}
 | 
					              @click=${this._clearValue}
 | 
				
			||||||
                ></ha-icon-button>
 | 
					            ></ha-svg-icon>`
 | 
				
			||||||
              `
 | 
					 | 
				
			||||||
          : ""}
 | 
					          : ""}
 | 
				
			||||||
 | 
					        <ha-svg-icon
 | 
				
			||||||
          <ha-icon-button
 | 
					          aria-label=${this.hass?.localize("ui.components.combo-box.show")}
 | 
				
			||||||
            .label=${this.hass.localize("ui.components.combo-box.show")}
 | 
					 | 
				
			||||||
            .path=${this._opened ? mdiMenuUp : mdiMenuDown}
 | 
					 | 
				
			||||||
            slot="suffix"
 | 
					 | 
				
			||||||
          class="toggle-button"
 | 
					          class="toggle-button"
 | 
				
			||||||
          ></ha-icon-button>
 | 
					          .path=${this._opened ? mdiMenuUp : mdiMenuDown}
 | 
				
			||||||
        </paper-input>
 | 
					          @click=${this._toggleOpen}
 | 
				
			||||||
 | 
					        ></ha-svg-icon>
 | 
				
			||||||
      </vaadin-combo-box-light>
 | 
					      </vaadin-combo-box-light>
 | 
				
			||||||
    `;
 | 
					    `;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private _defaultRowRenderer: ComboBoxLitRenderer<
 | 
				
			||||||
 | 
					    string | Record<string, any>
 | 
				
			||||||
 | 
					  > = (item) =>
 | 
				
			||||||
 | 
					    html`<mwc-list-item>
 | 
				
			||||||
 | 
					      ${this.itemLabelPath ? item[this.itemLabelPath] : item}
 | 
				
			||||||
 | 
					    </mwc-list-item>`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private _clearValue(ev: Event) {
 | 
					  private _clearValue(ev: Event) {
 | 
				
			||||||
    ev.stopPropagation();
 | 
					    ev.stopPropagation();
 | 
				
			||||||
    fireEvent(this, "value-changed", { value: undefined });
 | 
					    fireEvent(this, "value-changed", { value: undefined });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private _toggleOpen(ev: Event) {
 | 
				
			||||||
 | 
					    if (this._opened) {
 | 
				
			||||||
 | 
					      this._comboBox?.close();
 | 
				
			||||||
 | 
					      ev.stopPropagation();
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      this._comboBox?.inputElement.focus();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private _openedChanged(ev: PolymerChangedEvent<boolean>) {
 | 
					  private _openedChanged(ev: PolymerChangedEvent<boolean>) {
 | 
				
			||||||
 | 
					    // delay this so we can handle click event before setting _opened
 | 
				
			||||||
 | 
					    setTimeout(() => {
 | 
				
			||||||
      this._opened = ev.detail.value;
 | 
					      this._opened = ev.detail.value;
 | 
				
			||||||
 | 
					    }, 0);
 | 
				
			||||||
    // @ts-ignore
 | 
					    // @ts-ignore
 | 
				
			||||||
    fireEvent(this, ev.type, ev.detail);
 | 
					    fireEvent(this, ev.type, ev.detail);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -141,11 +206,38 @@ export class HaComboBox extends LitElement {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  static get styles(): CSSResultGroup {
 | 
					  static get styles(): CSSResultGroup {
 | 
				
			||||||
    return css`
 | 
					    return css`
 | 
				
			||||||
      paper-input > ha-icon-button {
 | 
					      :host {
 | 
				
			||||||
 | 
					        display: block;
 | 
				
			||||||
 | 
					        width: 100%;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      vaadin-combo-box-light {
 | 
				
			||||||
 | 
					        position: relative;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      ha-textfield {
 | 
				
			||||||
 | 
					        width: 100%;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      ha-textfield > ha-icon-button {
 | 
				
			||||||
        --mdc-icon-button-size: 24px;
 | 
					        --mdc-icon-button-size: 24px;
 | 
				
			||||||
        padding: 2px;
 | 
					        padding: 2px;
 | 
				
			||||||
        color: var(--secondary-text-color);
 | 
					        color: var(--secondary-text-color);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					      ha-svg-icon {
 | 
				
			||||||
 | 
					        color: var(--input-dropdown-icon-color);
 | 
				
			||||||
 | 
					        position: absolute;
 | 
				
			||||||
 | 
					        cursor: pointer;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      .toggle-button {
 | 
				
			||||||
 | 
					        right: 12px;
 | 
				
			||||||
 | 
					        top: -10px;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      :host([opened]) .toggle-button {
 | 
				
			||||||
 | 
					        color: var(--primary-color);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      .clear-button {
 | 
				
			||||||
 | 
					        --mdc-icon-size: 20px;
 | 
				
			||||||
 | 
					        top: -7px;
 | 
				
			||||||
 | 
					        right: 36px;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    `;
 | 
					    `;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,140 +1,78 @@
 | 
				
			|||||||
import { mdiCalendar } from "@mdi/js";
 | 
					import { mdiCalendar } from "@mdi/js";
 | 
				
			||||||
import "@polymer/paper-input/paper-input";
 | 
					import { css, CSSResultGroup, html, LitElement } from "lit";
 | 
				
			||||||
import "@vaadin/vaadin-date-picker/theme/material/vaadin-date-picker-light";
 | 
					import { customElement, property } from "lit/decorators";
 | 
				
			||||||
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
 | 
					import { formatDateNumeric } from "../common/datetime/format_date";
 | 
				
			||||||
import { customElement, property, query } from "lit/decorators";
 | 
					 | 
				
			||||||
import { fireEvent } from "../common/dom/fire_event";
 | 
					import { fireEvent } from "../common/dom/fire_event";
 | 
				
			||||||
 | 
					import { HomeAssistant } from "../types";
 | 
				
			||||||
import "./ha-svg-icon";
 | 
					import "./ha-svg-icon";
 | 
				
			||||||
 | 
					import "./ha-textfield";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const i18n = {
 | 
					const loadDatePickerDialog = () => import("./ha-dialog-date-picker");
 | 
				
			||||||
  monthNames: [
 | 
					
 | 
				
			||||||
    "January",
 | 
					export interface datePickerDialogParams {
 | 
				
			||||||
    "February",
 | 
					  value?: string;
 | 
				
			||||||
    "March",
 | 
					  min?: string;
 | 
				
			||||||
    "April",
 | 
					  max?: string;
 | 
				
			||||||
    "May",
 | 
					  locale?: string;
 | 
				
			||||||
    "June",
 | 
					  onChange: (value: string) => void;
 | 
				
			||||||
    "July",
 | 
					 | 
				
			||||||
    "August",
 | 
					 | 
				
			||||||
    "September",
 | 
					 | 
				
			||||||
    "October",
 | 
					 | 
				
			||||||
    "November",
 | 
					 | 
				
			||||||
    "December",
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
  weekdays: [
 | 
					 | 
				
			||||||
    "Sunday",
 | 
					 | 
				
			||||||
    "Monday",
 | 
					 | 
				
			||||||
    "Tuesday",
 | 
					 | 
				
			||||||
    "Wednesday",
 | 
					 | 
				
			||||||
    "Thursday",
 | 
					 | 
				
			||||||
    "Friday",
 | 
					 | 
				
			||||||
    "Saturday",
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
  weekdaysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
 | 
					 | 
				
			||||||
  firstDayOfWeek: 0,
 | 
					 | 
				
			||||||
  week: "Week",
 | 
					 | 
				
			||||||
  calendar: "Calendar",
 | 
					 | 
				
			||||||
  clear: "Clear",
 | 
					 | 
				
			||||||
  today: "Today",
 | 
					 | 
				
			||||||
  cancel: "Cancel",
 | 
					 | 
				
			||||||
  formatTitle: (monthName, fullYear) => monthName + " " + fullYear,
 | 
					 | 
				
			||||||
  formatDate: (d: { day: number; month: number; year: number }) =>
 | 
					 | 
				
			||||||
    [
 | 
					 | 
				
			||||||
      ("0000" + String(d.year)).slice(-4),
 | 
					 | 
				
			||||||
      ("0" + String(d.month + 1)).slice(-2),
 | 
					 | 
				
			||||||
      ("0" + String(d.day)).slice(-2),
 | 
					 | 
				
			||||||
    ].join("-"),
 | 
					 | 
				
			||||||
  parseDate: (text: string) => {
 | 
					 | 
				
			||||||
    const parts = text.split("-");
 | 
					 | 
				
			||||||
    const today = new Date();
 | 
					 | 
				
			||||||
    let date;
 | 
					 | 
				
			||||||
    let month = today.getMonth();
 | 
					 | 
				
			||||||
    let year = today.getFullYear();
 | 
					 | 
				
			||||||
    if (parts.length === 3) {
 | 
					 | 
				
			||||||
      year = parseInt(parts[0]);
 | 
					 | 
				
			||||||
      if (parts[0].length < 3 && year >= 0) {
 | 
					 | 
				
			||||||
        year += year < 50 ? 2000 : 1900;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      month = parseInt(parts[1]) - 1;
 | 
					 | 
				
			||||||
      date = parseInt(parts[2]);
 | 
					 | 
				
			||||||
    } else if (parts.length === 2) {
 | 
					 | 
				
			||||||
      month = parseInt(parts[0]) - 1;
 | 
					 | 
				
			||||||
      date = parseInt(parts[1]);
 | 
					 | 
				
			||||||
    } else if (parts.length === 1) {
 | 
					 | 
				
			||||||
      date = parseInt(parts[0]);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (date !== undefined) {
 | 
					const showDatePickerDialog = (
 | 
				
			||||||
      return { day: date, month, year };
 | 
					  element: HTMLElement,
 | 
				
			||||||
    }
 | 
					  dialogParams: datePickerDialogParams
 | 
				
			||||||
    return undefined;
 | 
					): void => {
 | 
				
			||||||
  },
 | 
					  fireEvent(element, "show-dialog", {
 | 
				
			||||||
 | 
					    dialogTag: "ha-dialog-date-picker",
 | 
				
			||||||
 | 
					    dialogImport: loadDatePickerDialog,
 | 
				
			||||||
 | 
					    dialogParams,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@customElement("ha-date-input")
 | 
					@customElement("ha-date-input")
 | 
				
			||||||
export class HaDateInput extends LitElement {
 | 
					export class HaDateInput extends LitElement {
 | 
				
			||||||
 | 
					  @property({ attribute: false }) public locale!: HomeAssistant["locale"];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @property() public value?: string;
 | 
					  @property() public value?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @property({ type: Boolean }) public disabled = false;
 | 
					  @property({ type: Boolean }) public disabled = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @property() public label?: string;
 | 
					  @property() public label?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @query("vaadin-date-picker-light", true) private _datePicker;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private _inited = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  updated(changedProps: PropertyValues) {
 | 
					 | 
				
			||||||
    if (changedProps.has("value")) {
 | 
					 | 
				
			||||||
      this._datePicker.value = this.value;
 | 
					 | 
				
			||||||
      this._inited = true;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  render() {
 | 
					  render() {
 | 
				
			||||||
    return html`<vaadin-date-picker-light
 | 
					    return html`<ha-textfield
 | 
				
			||||||
      .disabled=${this.disabled}
 | 
					 | 
				
			||||||
      @value-changed=${this._valueChanged}
 | 
					 | 
				
			||||||
      attr-for-value="value"
 | 
					 | 
				
			||||||
      .i18n=${i18n}
 | 
					 | 
				
			||||||
    >
 | 
					 | 
				
			||||||
      <paper-input
 | 
					 | 
				
			||||||
      .label=${this.label}
 | 
					      .label=${this.label}
 | 
				
			||||||
      .disabled=${this.disabled}
 | 
					      .disabled=${this.disabled}
 | 
				
			||||||
        no-label-float
 | 
					      iconTrailing
 | 
				
			||||||
 | 
					      @click=${this._openDialog}
 | 
				
			||||||
 | 
					      .value=${this.value
 | 
				
			||||||
 | 
					        ? formatDateNumeric(new Date(this.value), this.locale)
 | 
				
			||||||
 | 
					        : ""}
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
        <ha-svg-icon slot="suffix" .path=${mdiCalendar}></ha-svg-icon>
 | 
					      <ha-svg-icon slot="trailingIcon" .path=${mdiCalendar}></ha-svg-icon>
 | 
				
			||||||
      </paper-input>
 | 
					    </ha-textfield>`;
 | 
				
			||||||
    </vaadin-date-picker-light>`;
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private _valueChanged(ev: CustomEvent) {
 | 
					  private _openDialog() {
 | 
				
			||||||
    if (
 | 
					    if (this.disabled) {
 | 
				
			||||||
      !this.value ||
 | 
					      return;
 | 
				
			||||||
      (this._inited && !this._compareStringDates(ev.detail.value, this.value))
 | 
					    }
 | 
				
			||||||
    ) {
 | 
					    showDatePickerDialog(this, {
 | 
				
			||||||
      this.value = ev.detail.value;
 | 
					      min: "1970-01-01",
 | 
				
			||||||
 | 
					      value: this.value,
 | 
				
			||||||
 | 
					      onChange: (value) => this._valueChanged(value),
 | 
				
			||||||
 | 
					      locale: this.locale.language,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private _valueChanged(value: string) {
 | 
				
			||||||
 | 
					    if (this.value !== value) {
 | 
				
			||||||
 | 
					      this.value = value;
 | 
				
			||||||
      fireEvent(this, "change");
 | 
					      fireEvent(this, "change");
 | 
				
			||||||
      fireEvent(this, "value-changed", { value: ev.detail.value });
 | 
					      fireEvent(this, "value-changed", { value });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private _compareStringDates(a: string, b: string): boolean {
 | 
					 | 
				
			||||||
    const aParts = a.split("-");
 | 
					 | 
				
			||||||
    const bParts = b.split("-");
 | 
					 | 
				
			||||||
    let i = 0;
 | 
					 | 
				
			||||||
    for (const aPart of aParts) {
 | 
					 | 
				
			||||||
      if (Number(aPart) !== Number(bParts[i])) {
 | 
					 | 
				
			||||||
        return false;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      i++;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return true;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  static get styles(): CSSResultGroup {
 | 
					  static get styles(): CSSResultGroup {
 | 
				
			||||||
    return css`
 | 
					    return css`
 | 
				
			||||||
      paper-input {
 | 
					 | 
				
			||||||
        width: 110px;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      ha-svg-icon {
 | 
					      ha-svg-icon {
 | 
				
			||||||
        color: var(--secondary-text-color);
 | 
					        color: var(--secondary-text-color);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,6 @@ import "@material/mwc-list/mwc-list";
 | 
				
			|||||||
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
 | 
					import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
 | 
				
			||||||
import "@material/mwc-list/mwc-list-item";
 | 
					import "@material/mwc-list/mwc-list-item";
 | 
				
			||||||
import { mdiCalendar } from "@mdi/js";
 | 
					import { mdiCalendar } from "@mdi/js";
 | 
				
			||||||
import "@polymer/paper-input/paper-input";
 | 
					 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  css,
 | 
					  css,
 | 
				
			||||||
  CSSResultGroup,
 | 
					  CSSResultGroup,
 | 
				
			||||||
@@ -19,6 +18,7 @@ import { computeRTLDirection } from "../common/util/compute_rtl";
 | 
				
			|||||||
import { HomeAssistant } from "../types";
 | 
					import { HomeAssistant } from "../types";
 | 
				
			||||||
import "./date-range-picker";
 | 
					import "./date-range-picker";
 | 
				
			||||||
import "./ha-svg-icon";
 | 
					import "./ha-svg-icon";
 | 
				
			||||||
 | 
					import "./ha-textfield";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface DateRangePickerRanges {
 | 
					export interface DateRangePickerRanges {
 | 
				
			||||||
  [key: string]: [Date, Date];
 | 
					  [key: string]: [Date, Date];
 | 
				
			||||||
@@ -61,7 +61,7 @@ export class HaDateRangePicker extends LitElement {
 | 
				
			|||||||
      >
 | 
					      >
 | 
				
			||||||
        <div slot="input" class="date-range-inputs">
 | 
					        <div slot="input" class="date-range-inputs">
 | 
				
			||||||
          <ha-svg-icon .path=${mdiCalendar}></ha-svg-icon>
 | 
					          <ha-svg-icon .path=${mdiCalendar}></ha-svg-icon>
 | 
				
			||||||
          <paper-input
 | 
					          <ha-textfield
 | 
				
			||||||
            .value=${formatDateTime(this.startDate, this.hass.locale)}
 | 
					            .value=${formatDateTime(this.startDate, this.hass.locale)}
 | 
				
			||||||
            .label=${this.hass.localize(
 | 
					            .label=${this.hass.localize(
 | 
				
			||||||
              "ui.components.date-range-picker.start_date"
 | 
					              "ui.components.date-range-picker.start_date"
 | 
				
			||||||
@@ -69,16 +69,16 @@ export class HaDateRangePicker extends LitElement {
 | 
				
			|||||||
            .disabled=${this.disabled}
 | 
					            .disabled=${this.disabled}
 | 
				
			||||||
            @click=${this._handleInputClick}
 | 
					            @click=${this._handleInputClick}
 | 
				
			||||||
            readonly
 | 
					            readonly
 | 
				
			||||||
          ></paper-input>
 | 
					          ></ha-textfield>
 | 
				
			||||||
          <paper-input
 | 
					          <ha-textfield
 | 
				
			||||||
            .value=${formatDateTime(this.endDate, this.hass.locale)}
 | 
					            .value=${formatDateTime(this.endDate, this.hass.locale)}
 | 
				
			||||||
            label=${this.hass.localize(
 | 
					            .label=${this.hass.localize(
 | 
				
			||||||
              "ui.components.date-range-picker.end_date"
 | 
					              "ui.components.date-range-picker.end_date"
 | 
				
			||||||
            )}
 | 
					            )}
 | 
				
			||||||
            .disabled=${this.disabled}
 | 
					            .disabled=${this.disabled}
 | 
				
			||||||
            @click=${this._handleInputClick}
 | 
					            @click=${this._handleInputClick}
 | 
				
			||||||
            readonly
 | 
					            readonly
 | 
				
			||||||
          ></paper-input>
 | 
					          ></ha-textfield>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        ${this.ranges
 | 
					        ${this.ranges
 | 
				
			||||||
          ? html`<div
 | 
					          ? html`<div
 | 
				
			||||||
@@ -158,13 +158,13 @@ export class HaDateRangePicker extends LitElement {
 | 
				
			|||||||
        border-top: 1px solid var(--divider-color);
 | 
					        border-top: 1px solid var(--divider-color);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      paper-input {
 | 
					      ha-textfield {
 | 
				
			||||||
        display: inline-block;
 | 
					        display: inline-block;
 | 
				
			||||||
        max-width: 250px;
 | 
					        max-width: 250px;
 | 
				
			||||||
        min-width: 200px;
 | 
					        min-width: 200px;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      paper-input:last-child {
 | 
					      ha-textfield:last-child {
 | 
				
			||||||
        margin-left: 8px;
 | 
					        margin-left: 8px;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -176,7 +176,7 @@ export class HaDateRangePicker extends LitElement {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      @media only screen and (max-width: 500px) {
 | 
					      @media only screen and (max-width: 500px) {
 | 
				
			||||||
        paper-input {
 | 
					        ha-textfield {
 | 
				
			||||||
          min-width: inherit;
 | 
					          min-width: inherit;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user