mirror of
				https://github.com/home-assistant/frontend.git
				synced 2025-10-25 03:29:41 +00:00 
			
		
		
		
	Compare commits
	
		
			61 Commits
		
	
	
		
			cursor/ena
			...
			20251001.3
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 46cd1d5156 | ||
|   | 07a5c41fd4 | ||
|   | 4ad3c553d5 | ||
|   | d40cc448a5 | ||
|   | e2f3f9d348 | ||
|   | 98d44950f8 | ||
|   | 8ae9edb1ef | ||
|   | 84c4396c13 | ||
|   | 2b937a30e3 | ||
|   | b7815bfd86 | ||
|   | d94fa03411 | ||
|   | 0a7007ef9e | ||
|   | dd12136dee | ||
|   | 6e2f89fe3d | ||
|   | 092085b9af | ||
|   | 1c06eb8661 | ||
|   | c7e87b06b5 | ||
|   | 38c738c199 | ||
|   | e899587307 | ||
|   | c9feb0b75f | ||
|   | 10718c35d1 | ||
|   | 4dc6a37bad | ||
|   | ac49fc7aba | ||
|   | e4f008800b | ||
|   | 0b0ffd7bab | ||
|   | dfa77526a2 | ||
|   | 9a3bd6c613 | ||
|   | 1161de5746 | ||
|   | 9df8e20391 | ||
|   | 11047a9c95 | ||
|   | 18fa66f61c | ||
|   | 758a048f34 | ||
|   | ee0fc360b0 | ||
|   | 4012f95ec1 | ||
|   | 0336ce4606 | ||
|   | 9ba36ab7e2 | ||
|   | fe7a08a1b0 | ||
|   | 87a8f9cedc | ||
|   | 01df7e20ca | ||
|   | d181219522 | ||
|   | 6ae24b8135 | ||
|   | 8e009f24f9 | ||
|   | 53031f44ac | ||
|   | af5a988457 | ||
|   | bab0391a19 | ||
|   | 444123c47e | ||
|   | f123d34046 | ||
|   | 1b40f99f68 | ||
|   | b314b3ed2b | ||
|   | 59b8932969 | ||
|   | 107af753ec | ||
|   | 1f0acb3046 | ||
|   | 431e533929 | ||
|   | 02c845cbc6 | ||
|   | 628111ed20 | ||
|   | e825a9c02f | ||
|   | 7a35bddf36 | ||
|   | ad69270af8 | ||
|   | 404edf9483 | ||
|   | a166b4e9b6 | ||
|   | 7a285f11db | 
							
								
								
									
										2
									
								
								.github/workflows/ci.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -37,7 +37,7 @@ jobs: | ||||
|       - name: Build resources | ||||
|         run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data gather-gallery-pages | ||||
|       - name: Setup lint cache | ||||
|         uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 | ||||
|         uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 | ||||
|         with: | ||||
|           path: | | ||||
|             node_modules/.cache/prettier | ||||
|   | ||||
							
								
								
									
										6
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
								
							| @@ -36,14 +36,14 @@ jobs: | ||||
|  | ||||
|       # Initializes the CodeQL tools for scanning. | ||||
|       - name: Initialize CodeQL | ||||
|         uses: github/codeql-action/init@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6 | ||||
|         uses: github/codeql-action/init@192325c86100d080feab897ff886c34abd4c83a3 # v3.30.3 | ||||
|         with: | ||||
|           languages: ${{ matrix.language }} | ||||
|  | ||||
|       # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java). | ||||
|       # If this step fails, then you should remove it and run the build manually (see below) | ||||
|       - name: Autobuild | ||||
|         uses: github/codeql-action/autobuild@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6 | ||||
|         uses: github/codeql-action/autobuild@192325c86100d080feab897ff886c34abd4c83a3 # v3.30.3 | ||||
|  | ||||
|       # ℹ️ Command-line programs to run using the OS shell. | ||||
|       # 📚 https://git.io/JvXDl | ||||
| @@ -57,4 +57,4 @@ jobs: | ||||
|       #   make release | ||||
|  | ||||
|       - name: Perform CodeQL Analysis | ||||
|         uses: github/codeql-action/analyze@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6 | ||||
|         uses: github/codeql-action/analyze@192325c86100d080feab897ff886c34abd4c83a3 # v3.30.3 | ||||
|   | ||||
							
								
								
									
										8
									
								
								.github/workflows/release.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/workflows/release.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -55,7 +55,7 @@ jobs: | ||||
|           script/release | ||||
|  | ||||
|       - name: Upload release assets | ||||
|         uses: softprops/action-gh-release@62c96d0c4e8a889135c1f3a25910db8dbe0e85f7 # v2.3.4 | ||||
|         uses: softprops/action-gh-release@6cbd405e2c4e67a21c47fa9e383d020e4e28b836 # v2.3.3 | ||||
|         with: | ||||
|           files: | | ||||
|             dist/*.whl | ||||
| @@ -75,7 +75,7 @@ jobs: | ||||
|  | ||||
|       # home-assistant/wheels doesn't support SHA pinning | ||||
|       - name: Build wheels | ||||
|         uses: home-assistant/wheels@2025.09.1 | ||||
|         uses: home-assistant/wheels@2025.07.0 | ||||
|         with: | ||||
|           abi: cp313 | ||||
|           tag: musllinux_1_2 | ||||
| @@ -108,7 +108,7 @@ jobs: | ||||
|       - name: Tar folder | ||||
|         run: tar -czf landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz -C landing-page/dist . | ||||
|       - name: Upload release asset | ||||
|         uses: softprops/action-gh-release@62c96d0c4e8a889135c1f3a25910db8dbe0e85f7 # v2.3.4 | ||||
|         uses: softprops/action-gh-release@6cbd405e2c4e67a21c47fa9e383d020e4e28b836 # v2.3.3 | ||||
|         with: | ||||
|           files: landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz | ||||
|  | ||||
| @@ -137,6 +137,6 @@ jobs: | ||||
|       - name: Tar folder | ||||
|         run: tar -czf hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz -C hassio/build . | ||||
|       - name: Upload release asset | ||||
|         uses: softprops/action-gh-release@62c96d0c4e8a889135c1f3a25910db8dbe0e85f7 # v2.3.4 | ||||
|         uses: softprops/action-gh-release@6cbd405e2c4e67a21c47fa9e383d020e4e28b836 # v2.3.3 | ||||
|         with: | ||||
|           files: hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							| @@ -10,7 +10,7 @@ jobs: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: 90 days stale policy | ||||
|         uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0 | ||||
|         uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10.0.0 | ||||
|         with: | ||||
|           repo-token: ${{ secrets.GITHUB_TOKEN }} | ||||
|           days-before-stale: 90 | ||||
|   | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -6,4 +6,4 @@ enableGlobalCache: false | ||||
|  | ||||
| nodeLinker: node-modules | ||||
|  | ||||
| yarnPath: .yarn/releases/yarn-4.10.3.cjs | ||||
| yarnPath: .yarn/releases/yarn-4.10.2.cjs | ||||
|   | ||||
| @@ -183,6 +183,7 @@ module.exports.babelOptions = ({ | ||||
|       include: /\/node_modules\//, | ||||
|       exclude: [ | ||||
|         "element-internals-polyfill", | ||||
|         "@shoelace-style", | ||||
|         "@?lit(?:-labs|-element|-html)?", | ||||
|       ].map((p) => new RegExp(`/node_modules/${p}/`)), | ||||
|     }, | ||||
|   | ||||
| @@ -242,7 +242,7 @@ class HcCast extends LitElement { | ||||
|     } | ||||
|  | ||||
|     .question:before { | ||||
|       border-radius: var(--ha-border-radius-sm); | ||||
|       border-radius: 4px; | ||||
|       position: absolute; | ||||
|       top: 0; | ||||
|       right: 0; | ||||
|   | ||||
| @@ -95,8 +95,7 @@ class HcLayout extends LitElement { | ||||
|     } | ||||
|  | ||||
|     .hero { | ||||
|       border-radius: var(--ha-border-radius-sm) var(--ha-border-radius-sm) | ||||
|         var(--ha-border-radius-square) var(--ha-border-radius-square); | ||||
|       border-radius: 4px 4px 0 0; | ||||
|     } | ||||
|     .subtitle { | ||||
|       font-size: var(--ha-font-size-m); | ||||
|   | ||||
| @@ -75,7 +75,7 @@ export const castDemoEntities: () => Entity[] = () => | ||||
|         longitude: 4.8903147, | ||||
|         radius: 100, | ||||
|         friendly_name: "Home", | ||||
|         icon: "mdi:home", | ||||
|         icon: "hass:home", | ||||
|       }, | ||||
|     }, | ||||
|     "input_number.harmonyvolume": { | ||||
| @@ -88,7 +88,7 @@ export const castDemoEntities: () => Entity[] = () => | ||||
|         step: 1, | ||||
|         mode: "slider", | ||||
|         friendly_name: "Volume", | ||||
|         icon: "mdi:volume-high", | ||||
|         icon: "hass:volume-high", | ||||
|       }, | ||||
|     }, | ||||
|     "climate.upstairs": { | ||||
|   | ||||
| @@ -56,7 +56,7 @@ export const castDemoLovelace: () => LovelaceConfig = () => { | ||||
|                 type: "weblink", | ||||
|                 url: "/lovelace/climate", | ||||
|                 name: "Climate controls", | ||||
|                 icon: "mdi:arrow-right", | ||||
|                 icon: "hass:arrow-right", | ||||
|               }, | ||||
|             ], | ||||
|           }, | ||||
| @@ -76,7 +76,7 @@ export const castDemoLovelace: () => LovelaceConfig = () => { | ||||
|                 type: "weblink", | ||||
|                 url: "/lovelace/overview", | ||||
|                 name: "Back", | ||||
|                 icon: "mdi:arrow-left", | ||||
|                 icon: "hass:arrow-left", | ||||
|               }, | ||||
|             ], | ||||
|           }, | ||||
|   | ||||
| @@ -143,7 +143,7 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) => | ||||
|       state: "on", | ||||
|       attributes: { | ||||
|         friendly_name: "Home Automation", | ||||
|         icon: "mdi:home-automation", | ||||
|         icon: "hass:home-automation", | ||||
|       }, | ||||
|     }, | ||||
|     "input_boolean.tvtime": { | ||||
|   | ||||
| @@ -4,7 +4,7 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({ | ||||
|   title: "Home Assistant", | ||||
|   views: [ | ||||
|     { | ||||
|       icon: "mdi:home-assistant", | ||||
|       icon: "hass:home-assistant", | ||||
|       id: "home", | ||||
|       title: "Home", | ||||
|       cards: [ | ||||
|   | ||||
| @@ -1236,7 +1236,7 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({ | ||||
|         }, | ||||
|       ], | ||||
|       path: "security", | ||||
|       icon: "mdi:shield-home", | ||||
|       icon: "hass:shield-home", | ||||
|       name: "Security", | ||||
|       background: | ||||
|         'center / cover no-repeat url("/assets/jimpower/background-15.jpg") fixed', | ||||
|   | ||||
| @@ -208,7 +208,7 @@ class HaGallery extends LitElement { | ||||
|       } | ||||
|  | ||||
|       .sidebar a[active]::before { | ||||
|         border-radius: var(--ha-border-radius-lg); | ||||
|         border-radius: 12px; | ||||
|         position: absolute; | ||||
|         top: 0; | ||||
|         right: 2px; | ||||
| @@ -241,7 +241,7 @@ class HaGallery extends LitElement { | ||||
|         text-align: center; | ||||
|         margin: 16px; | ||||
|         padding: 16px; | ||||
|         border-radius: var(--ha-border-radius-lg); | ||||
|         border-radius: 12px; | ||||
|         background-color: var(--primary-background-color); | ||||
|       } | ||||
|  | ||||
|   | ||||
| @@ -117,7 +117,7 @@ export class DemoHaBadge extends LitElement { | ||||
|     } | ||||
|     .card-content { | ||||
|       display: flex; | ||||
|       gap: var(--ha-space-6); | ||||
|       gap: 24px; | ||||
|     } | ||||
|   `; | ||||
| } | ||||
|   | ||||
| @@ -155,11 +155,11 @@ export class DemoHaButton extends LitElement { | ||||
|     .card-content { | ||||
|       display: flex; | ||||
|       flex-direction: column; | ||||
|       gap: var(--ha-space-6); | ||||
|       gap: 24px; | ||||
|     } | ||||
|     .card-content div { | ||||
|       display: flex; | ||||
|       gap: var(--ha-space-2); | ||||
|       gap: 8px; | ||||
|     } | ||||
|   `; | ||||
| } | ||||
|   | ||||
| @@ -9,10 +9,10 @@ import { css, html, LitElement } from "lit"; | ||||
| import { customElement } from "lit/decorators"; | ||||
| import { ifDefined } from "lit/directives/if-defined"; | ||||
| import { repeat } from "lit/directives/repeat"; | ||||
| import "../../../../src/components/ha-card"; | ||||
| import "../../../../src/components/ha-control-button"; | ||||
| import "../../../../src/components/ha-control-button-group"; | ||||
| import "../../../../src/components/ha-card"; | ||||
| import "../../../../src/components/ha-svg-icon"; | ||||
| import "../../../../src/components/ha-control-button-group"; | ||||
|  | ||||
| interface Button { | ||||
|   label: string; | ||||
| @@ -156,17 +156,17 @@ export class DemoHaBarButton extends LitElement { | ||||
|       --control-button-icon-color: var(--primary-color); | ||||
|       --control-button-background-color: var(--primary-color); | ||||
|       --control-button-background-opacity: 0.2; | ||||
|       --control-button-border-radius: var(--ha-border-radius-xl); | ||||
|       --control-button-border-radius: 18px; | ||||
|       height: 100px; | ||||
|       width: 100px; | ||||
|     } | ||||
|     .custom-group { | ||||
|       --control-button-group-thickness: 100px; | ||||
|       --control-button-group-border-radius: var(--ha-border-radius-6xl); | ||||
|       --control-button-group-border-radius: 36px; | ||||
|       --control-button-group-spacing: 20px; | ||||
|     } | ||||
|     .custom-group ha-control-button { | ||||
|       --control-button-border-radius: var(--ha-border-radius-xl); | ||||
|       --control-button-border-radius: 18px; | ||||
|       --mdc-icon-size: 32px; | ||||
|     } | ||||
|     .vertical-buttons { | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| import type { TemplateResult } from "lit"; | ||||
| import { LitElement, css, html } from "lit"; | ||||
| import { customElement, state } from "lit/decorators"; | ||||
| import { ifDefined } from "lit/directives/if-defined"; | ||||
| import { repeat } from "lit/directives/repeat"; | ||||
| import "../../../../src/components/ha-card"; | ||||
| import "../../../../src/components/ha-control-number-buttons"; | ||||
| import { repeat } from "lit/directives/repeat"; | ||||
| import { ifDefined } from "lit/directives/if-defined"; | ||||
|  | ||||
| const buttons: { | ||||
|   id: string; | ||||
| @@ -94,7 +94,7 @@ export class DemoHarControlNumberButtons extends LitElement { | ||||
|       --control-number-buttons-background-color: #2196f3; | ||||
|       --control-number-buttons-background-opacity: 0.1; | ||||
|       --control-number-buttons-thickness: 100px; | ||||
|       --control-number-buttons-border-radius: var(--ha-border-radius-6xl); | ||||
|       --control-number-buttons-border-radius: 36px; | ||||
|     } | ||||
|   `; | ||||
| } | ||||
|   | ||||
| @@ -131,7 +131,7 @@ export class DemoHaControlSelectMenu extends LitElement { | ||||
|       --control-button-icon-color: var(--primary-color); | ||||
|       --control-button-background-color: var(--primary-color); | ||||
|       --control-button-background-opacity: 0.2; | ||||
|       --control-button-border-radius: var(--ha-border-radius-xl); | ||||
|       --control-button-border-radius: 18px; | ||||
|       height: 100px; | ||||
|       width: 100px; | ||||
|     } | ||||
|   | ||||
| @@ -187,7 +187,7 @@ export class DemoHaControlSelect extends LitElement { | ||||
|       --mdc-icon-size: 24px; | ||||
|       --control-select-color: var(--state-fan-active-color); | ||||
|       --control-select-thickness: 130px; | ||||
|       --control-select-border-radius: var(--ha-border-radius-6xl); | ||||
|       --control-select-border-radius: 36px; | ||||
|     } | ||||
|     .vertical-selects { | ||||
|       height: 300px; | ||||
|   | ||||
| @@ -3,8 +3,8 @@ import { css, html, LitElement } from "lit"; | ||||
| import { customElement, state } from "lit/decorators"; | ||||
| import { ifDefined } from "lit/directives/if-defined"; | ||||
| import { repeat } from "lit/directives/repeat"; | ||||
| import "../../../../src/components/ha-card"; | ||||
| import "../../../../src/components/ha-control-slider"; | ||||
| import "../../../../src/components/ha-card"; | ||||
|  | ||||
| const sliders: { | ||||
|   id: string; | ||||
| @@ -151,7 +151,7 @@ export class DemoHaBarSlider extends LitElement { | ||||
|       --control-slider-background: #ffcf4c; | ||||
|       --control-slider-background-opacity: 0.2; | ||||
|       --control-slider-thickness: 130px; | ||||
|       --control-slider-border-radius: var(--ha-border-radius-6xl); | ||||
|       --control-slider-border-radius: 36px; | ||||
|     } | ||||
|     .vertical-sliders { | ||||
|       height: 300px; | ||||
|   | ||||
| @@ -9,8 +9,8 @@ import { css, html, LitElement } from "lit"; | ||||
| import { customElement, state } from "lit/decorators"; | ||||
| import { ifDefined } from "lit/directives/if-defined"; | ||||
| import { repeat } from "lit/directives/repeat"; | ||||
| import "../../../../src/components/ha-card"; | ||||
| import "../../../../src/components/ha-control-switch"; | ||||
| import "../../../../src/components/ha-card"; | ||||
|  | ||||
| const switches: { | ||||
|   id: string; | ||||
| @@ -118,7 +118,7 @@ export class DemoHaControlSwitch extends LitElement { | ||||
|       --control-switch-on-color: var(--green-color); | ||||
|       --control-switch-off-color: var(--red-color); | ||||
|       --control-switch-thickness: 130px; | ||||
|       --control-switch-border-radius: var(--ha-border-radius-6xl); | ||||
|       --control-switch-border-radius: 36px; | ||||
|       --control-switch-padding: 6px; | ||||
|       --mdc-icon-size: 24px; | ||||
|     } | ||||
|   | ||||
| @@ -123,11 +123,11 @@ export class DemoHaProgressButton extends LitElement { | ||||
|     .card-content { | ||||
|       display: flex; | ||||
|       flex-direction: column; | ||||
|       gap: var(--ha-space-6); | ||||
|       gap: 24px; | ||||
|     } | ||||
|     .card-content div { | ||||
|       display: flex; | ||||
|       gap: var(--ha-space-2); | ||||
|       gap: 8px; | ||||
|     } | ||||
|   `; | ||||
| } | ||||
|   | ||||
| @@ -131,7 +131,7 @@ export class DemoHaSelectBox extends LitElement { | ||||
|       --mdc-icon-size: 24px; | ||||
|       --control-select-color: var(--state-fan-active-color); | ||||
|       --control-select-thickness: 130px; | ||||
|       --control-select-border-radius: var(--ha-border-radius-6xl); | ||||
|       --control-select-border-radius: 36px; | ||||
|     } | ||||
|  | ||||
|     p.title { | ||||
|   | ||||
| @@ -79,7 +79,7 @@ export class DemoHaSlider extends LitElement { | ||||
|       background-color: var(--primary-background-color); | ||||
|       padding: 0 50px; | ||||
|       margin: 16px; | ||||
|       border-radius: var(--ha-border-radius-md); | ||||
|       border-radius: 8px; | ||||
|     } | ||||
|     ha-card { | ||||
|       margin: 24px auto; | ||||
| @@ -88,7 +88,7 @@ export class DemoHaSlider extends LitElement { | ||||
|       display: flex; | ||||
|       flex-direction: column; | ||||
|       align-items: center; | ||||
|       gap: var(--ha-space-6); | ||||
|       gap: 24px; | ||||
|     } | ||||
|   `; | ||||
| } | ||||
|   | ||||
| @@ -61,7 +61,7 @@ export class DemoHaSpinner extends LitElement { | ||||
|       background-color: var(--primary-background-color); | ||||
|       padding: 0 50px; | ||||
|       margin: 16px; | ||||
|       border-radius: var(--ha-border-radius-md); | ||||
|       border-radius: 8px; | ||||
|     } | ||||
|     ha-card { | ||||
|       margin: 24px auto; | ||||
| @@ -70,7 +70,7 @@ export class DemoHaSpinner extends LitElement { | ||||
|       display: flex; | ||||
|       flex-direction: column; | ||||
|       align-items: center; | ||||
|       gap: var(--ha-space-6); | ||||
|       gap: 24px; | ||||
|     } | ||||
|   `; | ||||
| } | ||||
|   | ||||
| @@ -1,3 +0,0 @@ | ||||
| --- | ||||
| title: Dialog (ha-wa-dialog) | ||||
| --- | ||||
| @@ -1,523 +0,0 @@ | ||||
| import { css, html, LitElement } from "lit"; | ||||
| import { customElement, state } from "lit/decorators"; | ||||
| import { mdiCog, mdiHelp } from "@mdi/js"; | ||||
| import "../../../../src/components/ha-button"; | ||||
| import "../../../../src/components/ha-card"; | ||||
| import "../../../../src/components/ha-dialog-footer"; | ||||
| import "../../../../src/components/ha-form/ha-form"; | ||||
| import "../../../../src/components/ha-icon-button"; | ||||
| import "../../../../src/components/ha-wa-dialog"; | ||||
| import type { HaFormSchema } from "../../../../src/components/ha-form/types"; | ||||
|  | ||||
| const SCHEMA: HaFormSchema[] = [ | ||||
|   { type: "string", name: "Name", default: "", autofocus: true }, | ||||
|   { type: "string", name: "Email", default: "" }, | ||||
| ]; | ||||
|  | ||||
| type DialogType = | ||||
|   | false | ||||
|   | "basic" | ||||
|   | "basic-subtitle-below" | ||||
|   | "basic-subtitle-above" | ||||
|   | "form" | ||||
|   | "actions"; | ||||
|  | ||||
| @customElement("demo-components-ha-wa-dialog") | ||||
| export class DemoHaWaDialog extends LitElement { | ||||
|   @state() private _openDialog: DialogType = false; | ||||
|  | ||||
|   protected render() { | ||||
|     return html` | ||||
|       <div class="content"> | ||||
|         <h1>Dialog <code><ha-wa-dialog></code></h1> | ||||
|  | ||||
|         <p class="subtitle">Dialog component built with WebAwesome.</p> | ||||
|  | ||||
|         <h2>Demos</h2> | ||||
|  | ||||
|         <div class="buttons"> | ||||
|           <ha-button @click=${this._handleOpenDialog("basic")} | ||||
|             >Basic dialog</ha-button | ||||
|           > | ||||
|           <ha-button @click=${this._handleOpenDialog("basic-subtitle-below")} | ||||
|             >Basic dialog with subtitle below</ha-button | ||||
|           > | ||||
|           <ha-button @click=${this._handleOpenDialog("basic-subtitle-above")} | ||||
|             >Basic dialog with subtitle above</ha-button | ||||
|           > | ||||
|           <ha-button @click=${this._handleOpenDialog("form")} | ||||
|             >Dialog with form</ha-button | ||||
|           > | ||||
|           <ha-button @click=${this._handleOpenDialog("actions")} | ||||
|             >Dialog with actions</ha-button | ||||
|           > | ||||
|         </div> | ||||
|  | ||||
|         <ha-wa-dialog | ||||
|           .open=${this._openDialog === "basic"} | ||||
|           header-title="Basic dialog" | ||||
|           @closed=${this._handleClosed} | ||||
|         > | ||||
|           <div>Dialog content</div> | ||||
|         </ha-wa-dialog> | ||||
|  | ||||
|         <ha-wa-dialog | ||||
|           .open=${this._openDialog === "basic-subtitle-below"} | ||||
|           header-title="Basic dialog with subtitle" | ||||
|           header-subtitle="This is a basic dialog with a subtitle below" | ||||
|           @closed=${this._handleClosed} | ||||
|         > | ||||
|           <div>Dialog content</div> | ||||
|         </ha-wa-dialog> | ||||
|  | ||||
|         <ha-wa-dialog | ||||
|           .open=${this._openDialog === "basic-subtitle-above"} | ||||
|           header-title="Dialog with subtitle above" | ||||
|           header-subtitle="This is a basic dialog with a subtitle above" | ||||
|           header-subtitle-position="above" | ||||
|           @closed=${this._handleClosed} | ||||
|         > | ||||
|           <div>Dialog content</div> | ||||
|         </ha-wa-dialog> | ||||
|  | ||||
|         <ha-wa-dialog | ||||
|           .open=${this._openDialog === "form"} | ||||
|           header-title="Dialog with form" | ||||
|           header-subtitle="This is a dialog with a form and a footer" | ||||
|           prevent-scrim-close | ||||
|           @closed=${this._handleClosed} | ||||
|         > | ||||
|           <ha-form autofocus .schema=${SCHEMA}></ha-form> | ||||
|           <ha-dialog-footer slot="footer"> | ||||
|             <ha-button | ||||
|               data-dialog="close" | ||||
|               slot="secondaryAction" | ||||
|               variant="plain" | ||||
|               >Cancel</ha-button | ||||
|             > | ||||
|             <ha-button data-dialog="close" slot="primaryAction" variant="accent" | ||||
|               >Submit</ha-button | ||||
|             > | ||||
|           </ha-dialog-footer> | ||||
|         </ha-wa-dialog> | ||||
|  | ||||
|         <ha-wa-dialog | ||||
|           .open=${this._openDialog === "actions"} | ||||
|           header-title="Dialog with actions" | ||||
|           header-subtitle="This is a dialog with header actions" | ||||
|           @closed=${this._handleClosed} | ||||
|         > | ||||
|           <div slot="headerActionItems"> | ||||
|             <ha-icon-button label="Settings" path=${mdiCog}></ha-icon-button> | ||||
|             <ha-icon-button label="Help" path=${mdiHelp}></ha-icon-button> | ||||
|           </div> | ||||
|  | ||||
|           <div>Dialog content</div> | ||||
|         </ha-wa-dialog> | ||||
|  | ||||
|         <h2>Design</h2> | ||||
|  | ||||
|         <h3>Width</h3> | ||||
|  | ||||
|         <p>There are multiple widths available for the dialog.</p> | ||||
|  | ||||
|         <table> | ||||
|           <thead> | ||||
|             <tr> | ||||
|               <th>Name</th> | ||||
|               <th>Value</th> | ||||
|             </tr> | ||||
|           </thead> | ||||
|           <tbody> | ||||
|             <tr> | ||||
|               <td><code>small</code></td> | ||||
|               <td><code>min(320px, var(--full-width))</code></td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>medium</code></td> | ||||
|               <td><code>min(580px, var(--full-width))</code></td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>large</code></td> | ||||
|               <td><code>min(720px, var(--full-width))</code></td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>full</code></td> | ||||
|               <td><code>var(--full-width)</code></td> | ||||
|             </tr> | ||||
|           </tbody> | ||||
|         </table> | ||||
|  | ||||
|         <p> | ||||
|           <code>--full-width</code> is calculated based on the available width | ||||
|           of the screen. 95vw is the maximum width of the dialog on a large | ||||
|           screen, while on a small screen it is 100vw minus the safe area | ||||
|           insets. | ||||
|         </p> | ||||
|  | ||||
|         <p>Dialogs have a default width of <code>medium</code>.</p> | ||||
|  | ||||
|         <h3>Prevent scrim close</h3> | ||||
|  | ||||
|         <p> | ||||
|           You can prevent the dialog from being closed by clicking the | ||||
|           scrim/overlay. This is allowed by default. | ||||
|         </p> | ||||
|  | ||||
|         <h3>Header</h3> | ||||
|  | ||||
|         <p>The header contains a title, a subtitle and action items.</p> | ||||
|  | ||||
|         <table> | ||||
|           <thead> | ||||
|             <tr> | ||||
|               <th>Slot</th> | ||||
|               <th>Description</th> | ||||
|             </tr> | ||||
|           </thead> | ||||
|           <tbody> | ||||
|             <tr> | ||||
|               <td><code>header</code></td> | ||||
|               <td>The entire header area.</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>headerTitle</code></td> | ||||
|               <td>The header title text.</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>headerSubtitle</code></td> | ||||
|               <td>The header subtitle text.</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>headerActionItems</code></td> | ||||
|               <td>The header action items.</td> | ||||
|             </tr> | ||||
|           </tbody> | ||||
|         </table> | ||||
|  | ||||
|         <h4>Header title</h4> | ||||
|  | ||||
|         <p>The header title is a text string.</p> | ||||
|  | ||||
|         <h4>Header subtitle</h4> | ||||
|  | ||||
|         <p>The header subtitle is a text string.</p> | ||||
|  | ||||
|         <h4>Header action items</h4> | ||||
|  | ||||
|         <p> | ||||
|           The header action items usually containing icon buttons and/or menu | ||||
|           buttons. | ||||
|         </p> | ||||
|  | ||||
|         <h3>Body</h3> | ||||
|  | ||||
|         <p>The body is the content of the dialog.</p> | ||||
|  | ||||
|         <h3>Footer</h3> | ||||
|  | ||||
|         <p>The footer is the footer of the dialog.</p> | ||||
|  | ||||
|         <p> | ||||
|           It is recommended to use the <code>ha-dialog-footer</code> component | ||||
|           for the footer and to style the buttons inside the footer as so: | ||||
|         </p> | ||||
|  | ||||
|         <table> | ||||
|           <thead> | ||||
|             <tr> | ||||
|               <th>Slot</th> | ||||
|               <th>Description</th> | ||||
|               <th>Variant to use</th> | ||||
|             </tr> | ||||
|           </thead> | ||||
|           <tbody> | ||||
|             <tr> | ||||
|               <td><code>secondaryAction</code></td> | ||||
|               <td>The secondary action button(s).</td> | ||||
|               <td><code>plain</code></td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>primaryAction</code></td> | ||||
|               <td>The primary action button(s).</td> | ||||
|               <td><code>accent</code></td> | ||||
|             </tr> | ||||
|           </tbody> | ||||
|         </table> | ||||
|  | ||||
|         <h2>Implementation</h2> | ||||
|  | ||||
|         <h3>Example Usage</h3> | ||||
|  | ||||
|         <pre><code><ha-wa-dialog | ||||
|   open | ||||
|   header-title="Dialog title" | ||||
|   header-subtitle="Dialog subtitle" | ||||
|   prevent-scrim-close | ||||
| > | ||||
|   <div slot="headerActionItems"> | ||||
|     <ha-icon-button label="Settings" path="mdiCog"></ha-icon-button> | ||||
|     <ha-icon-button label="Help" path="mdiHelp"></ha-icon-button> | ||||
|   </div> | ||||
|   <div>Dialog content</div> | ||||
|   <ha-dialog-footer slot="footer"> | ||||
|     <ha-button data-dialog="close" slot="secondaryAction" variant="plain" | ||||
|       >Cancel</ha-button | ||||
|     > | ||||
|     <ha-button slot="primaryAction" variant="accent">Submit</ha-button> | ||||
|   </ha-dialog-footer> | ||||
| </ha-wa-dialog></code></pre> | ||||
|  | ||||
|         <h3>API</h3> | ||||
|  | ||||
|         <p> | ||||
|           This component is based on the webawesome dialog component. Check the | ||||
|           <a | ||||
|             href="https://webawesome.com/docs/components/dialog/" | ||||
|             target="_blank" | ||||
|             rel="noopener noreferrer" | ||||
|             >webawesome documentation</a | ||||
|           > | ||||
|           for more details. | ||||
|         </p> | ||||
|  | ||||
|         <h4>Attributes</h4> | ||||
|  | ||||
|         <table> | ||||
|           <thead> | ||||
|             <tr> | ||||
|               <th>Attribute</th> | ||||
|               <th>Description</th> | ||||
|               <th>Default</th> | ||||
|               <th>Options</th> | ||||
|             </tr> | ||||
|           </thead> | ||||
|           <tbody> | ||||
|             <tr> | ||||
|               <td><code>open</code></td> | ||||
|               <td>Controls the dialog open state.</td> | ||||
|               <td><code>false</code></td> | ||||
|               <td><code>false</code>, <code>true</code></td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>width</code></td> | ||||
|               <td>Preferred dialog width preset.</td> | ||||
|               <td><code>medium</code></td> | ||||
|               <td> | ||||
|                 <code>small</code>, <code>medium</code>, <code>large</code>, | ||||
|                 <code>full</code> | ||||
|               </td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>prevent-scrim-close</code></td> | ||||
|               <td> | ||||
|                 Prevents closing the dialog by clicking the scrim/overlay. | ||||
|               </td> | ||||
|               <td><code>false</code></td> | ||||
|               <td><code>true</code></td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>header-title</code></td> | ||||
|               <td>Header title text when no custom title slot is provided.</td> | ||||
|               <td></td> | ||||
|               <td></td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>header-subtitle</code></td> | ||||
|               <td> | ||||
|                 Header subtitle text when no custom subtitle slot is provided. | ||||
|               </td> | ||||
|               <td></td> | ||||
|               <td></td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>header-subtitle-position</code></td> | ||||
|               <td>Position of the subtitle relative to the title.</td> | ||||
|               <td><code>below</code></td> | ||||
|               <td><code>above</code>, <code>below</code></td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>flexcontent</code></td> | ||||
|               <td> | ||||
|                 Makes the dialog body a flex container for flexible layouts. | ||||
|               </td> | ||||
|               <td><code>false</code></td> | ||||
|               <td><code>false</code>, <code>true</code></td> | ||||
|             </tr> | ||||
|           </tbody> | ||||
|         </table> | ||||
|  | ||||
|         <h4>CSS Custom Properties</h4> | ||||
|  | ||||
|         <table> | ||||
|           <thead> | ||||
|             <tr> | ||||
|               <th>CSS Property</th> | ||||
|               <th>Description</th> | ||||
|             </tr> | ||||
|           </thead> | ||||
|           <tbody> | ||||
|             <tr> | ||||
|               <td><code>--dialog-content-padding</code></td> | ||||
|               <td>Padding for dialog content sections.</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>--ha-dialog-show-duration</code></td> | ||||
|               <td>Show animation duration.</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>--ha-dialog-hide-duration</code></td> | ||||
|               <td>Hide animation duration.</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>--ha-dialog-surface-background</code></td> | ||||
|               <td>Dialog background color.</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>--ha-dialog-border-radius</code></td> | ||||
|               <td>Border radius of the dialog surface.</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>--dialog-z-index</code></td> | ||||
|               <td>Z-index for the dialog.</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>--dialog-surface-position</code></td> | ||||
|               <td>CSS position of the dialog surface.</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>--dialog-surface-margin-top</code></td> | ||||
|               <td>Top margin for the dialog surface.</td> | ||||
|             </tr> | ||||
|           </tbody> | ||||
|         </table> | ||||
|  | ||||
|         <h4>Events</h4> | ||||
|  | ||||
|         <table> | ||||
|           <thead> | ||||
|             <tr> | ||||
|               <th>Event</th> | ||||
|               <th>Description</th> | ||||
|             </tr> | ||||
|           </thead> | ||||
|           <tbody> | ||||
|             <tr> | ||||
|               <td><code>opened</code></td> | ||||
|               <td>Fired when the dialog is shown.</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>closed</code></td> | ||||
|               <td>Fired after the dialog is hidden.</td> | ||||
|             </tr> | ||||
|           </tbody> | ||||
|         </table> | ||||
|       </div> | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   private _handleOpenDialog = (dialog: DialogType) => () => { | ||||
|     this._openDialog = dialog; | ||||
|   }; | ||||
|  | ||||
|   private _handleClosed = () => { | ||||
|     this._openDialog = false; | ||||
|   }; | ||||
|  | ||||
|   static styles = [ | ||||
|     css` | ||||
|       :host { | ||||
|         display: block; | ||||
|         padding: var(--ha-space-4); | ||||
|       } | ||||
|  | ||||
|       .content { | ||||
|         max-width: 1000px; | ||||
|         margin: 0 auto; | ||||
|       } | ||||
|  | ||||
|       h1 { | ||||
|         margin-top: 0; | ||||
|         margin-bottom: var(--ha-space-2); | ||||
|       } | ||||
|  | ||||
|       h2 { | ||||
|         margin-top: var(--ha-space-6); | ||||
|         margin-bottom: var(--ha-space-3); | ||||
|       } | ||||
|  | ||||
|       h3, | ||||
|       h4 { | ||||
|         margin-top: var(--ha-space-4); | ||||
|         margin-bottom: var(--ha-space-2); | ||||
|       } | ||||
|  | ||||
|       p { | ||||
|         margin: var(--ha-space-2) 0; | ||||
|         line-height: 1.6; | ||||
|       } | ||||
|  | ||||
|       .subtitle { | ||||
|         color: var(--secondary-text-color); | ||||
|         font-size: 1.1em; | ||||
|         margin-bottom: var(--ha-space-4); | ||||
|       } | ||||
|  | ||||
|       table { | ||||
|         width: 100%; | ||||
|         border-collapse: collapse; | ||||
|         margin: var(--ha-space-3) 0; | ||||
|       } | ||||
|  | ||||
|       th, | ||||
|       td { | ||||
|         text-align: left; | ||||
|         padding: var(--ha-space-2); | ||||
|         border-bottom: 1px solid var(--divider-color); | ||||
|       } | ||||
|  | ||||
|       th { | ||||
|         font-weight: 500; | ||||
|       } | ||||
|  | ||||
|       code { | ||||
|         background-color: var(--secondary-background-color); | ||||
|         padding: 2px 6px; | ||||
|         border-radius: 4px; | ||||
|         font-family: monospace; | ||||
|         font-size: 0.9em; | ||||
|       } | ||||
|  | ||||
|       pre { | ||||
|         background-color: var(--secondary-background-color); | ||||
|         padding: var(--ha-space-3); | ||||
|         border-radius: 8px; | ||||
|         overflow-x: auto; | ||||
|         margin: var(--ha-space-3) 0; | ||||
|       } | ||||
|  | ||||
|       pre code { | ||||
|         background-color: transparent; | ||||
|         padding: 0; | ||||
|       } | ||||
|  | ||||
|       .buttons { | ||||
|         display: flex; | ||||
|         flex-direction: row; | ||||
|         flex-wrap: wrap; | ||||
|         gap: var(--ha-space-2); | ||||
|         margin: var(--ha-space-4) 0; | ||||
|       } | ||||
|  | ||||
|       a { | ||||
|         color: var(--primary-color); | ||||
|       } | ||||
|     `, | ||||
|   ]; | ||||
| } | ||||
|  | ||||
| declare global { | ||||
|   interface HTMLElementTagNameMap { | ||||
|     "demo-components-ha-wa-dialog": DemoHaWaDialog; | ||||
|   } | ||||
| } | ||||
| @@ -5,13 +5,13 @@ import type { | ||||
| import { css, html, LitElement, nothing } from "lit"; | ||||
| import { customElement, property } from "lit/decorators"; | ||||
| import memoizeOne from "memoize-one"; | ||||
| import { mockIcons } from "../../../../demo/src/stubs/icons"; | ||||
| import { computeDomain } from "../../../../src/common/entity/compute_domain"; | ||||
| import { computeStateDisplay } from "../../../../src/common/entity/compute_state_display"; | ||||
| import "../../../../src/components/data-table/ha-data-table"; | ||||
| import type { DataTableColumnContainer } from "../../../../src/components/data-table/ha-data-table"; | ||||
| import "../../../../src/components/entity/state-badge"; | ||||
| import { provideHass } from "../../../../src/fake_data/provide_hass"; | ||||
| import { mockIcons } from "../../../../demo/src/stubs/icons"; | ||||
| import type { HomeAssistant } from "../../../../src/types"; | ||||
|  | ||||
| const SENSOR_DEVICE_CLASSES = [ | ||||
| @@ -434,7 +434,7 @@ export class DemoEntityState extends LitElement { | ||||
|       display: block; | ||||
|       height: 20px; | ||||
|       width: 20px; | ||||
|       border-radius: var(--ha-border-radius-md); | ||||
|       border-radius: 10px; | ||||
|       background-color: rgb(--color); | ||||
|     } | ||||
|   `; | ||||
|   | ||||
| @@ -121,7 +121,7 @@ class HassioCardContent extends LitElement { | ||||
|       height: 12px; | ||||
|       top: 8px; | ||||
|       right: 8px; | ||||
|       border-radius: var(--ha-border-radius-circle); | ||||
|       border-radius: 50%; | ||||
|     } | ||||
|     .topbar { | ||||
|       position: absolute; | ||||
|   | ||||
| @@ -164,7 +164,7 @@ class HassioHardwareDialog extends LitElement { | ||||
|         pre, | ||||
|         code { | ||||
|           background-color: var(--markdown-code-background-color, none); | ||||
|           border-radius: var(--ha-border-radius-sm); | ||||
|           border-radius: 3px; | ||||
|         } | ||||
|         pre { | ||||
|           padding: 16px; | ||||
|   | ||||
| @@ -228,7 +228,7 @@ class HassioRegistriesDialog extends LitElement { | ||||
|       css` | ||||
|         .registry { | ||||
|           border: 1px solid var(--divider-color); | ||||
|           border-radius: var(--ha-border-radius-sm); | ||||
|           border-radius: 4px; | ||||
|           margin-top: 4px; | ||||
|         } | ||||
|         .action { | ||||
|   | ||||
| @@ -193,7 +193,7 @@ class HassioRepositoriesDialog extends LitElement { | ||||
|         } | ||||
|         .option { | ||||
|           border: 1px solid var(--divider-color); | ||||
|           border-radius: var(--ha-border-radius-sm); | ||||
|           border-radius: 4px; | ||||
|           margin-top: 4px; | ||||
|         } | ||||
|         ha-button { | ||||
|   | ||||
| @@ -159,7 +159,7 @@ class HassioSystemManagedDialog extends LitElement { | ||||
|           display: flex; | ||||
|           justify-content: center; | ||||
|           align-items: center; | ||||
|           gap: var(--ha-space-4); | ||||
|           gap: 16px; | ||||
|           --mdc-icon-size: 48px; | ||||
|           margin-bottom: 32px; | ||||
|         } | ||||
|   | ||||
| @@ -31,7 +31,7 @@ export const hassioStyle = css` | ||||
|   .card-group { | ||||
|     display: grid; | ||||
|     grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); | ||||
|     grid-gap: var(--ha-space-2); | ||||
|     grid-gap: 8px; | ||||
|   } | ||||
|   @media screen and (min-width: 640px) { | ||||
|     .card-group { | ||||
|   | ||||
| @@ -302,7 +302,7 @@ class LandingPageLogs extends LitElement { | ||||
|         max-height: 300px; | ||||
|         overflow: auto; | ||||
|         border: 1px solid var(--divider-color); | ||||
|         border-radius: var(--ha-border-radius-sm); | ||||
|         border-radius: 4px; | ||||
|         padding: 4px; | ||||
|       } | ||||
|  | ||||
|   | ||||
| @@ -213,7 +213,7 @@ class HaLandingPage extends LandingPageBaseElement { | ||||
|       ha-card .card-content { | ||||
|         display: flex; | ||||
|         flex-direction: column; | ||||
|         gap: var(--ha-space-4); | ||||
|         gap: 16px; | ||||
|       } | ||||
|       ha-alert p { | ||||
|         text-align: unset; | ||||
| @@ -221,7 +221,7 @@ class HaLandingPage extends LandingPageBaseElement { | ||||
|       ha-language-picker { | ||||
|         display: block; | ||||
|         width: 200px; | ||||
|         border-radius: var(--ha-border-radius-sm); | ||||
|         border-radius: 4px; | ||||
|         overflow: hidden; | ||||
|         --ha-select-height: 40px; | ||||
|         --mdc-select-fill-color: none; | ||||
|   | ||||
							
								
								
									
										64
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										64
									
								
								package.json
									
									
									
									
									
								
							| @@ -28,24 +28,24 @@ | ||||
|   "dependencies": { | ||||
|     "@babel/runtime": "7.28.4", | ||||
|     "@braintree/sanitize-url": "7.1.1", | ||||
|     "@codemirror/autocomplete": "6.19.0", | ||||
|     "@codemirror/commands": "6.9.0", | ||||
|     "@codemirror/autocomplete": "6.18.7", | ||||
|     "@codemirror/commands": "6.8.1", | ||||
|     "@codemirror/language": "6.11.3", | ||||
|     "@codemirror/legacy-modes": "6.5.2", | ||||
|     "@codemirror/legacy-modes": "6.5.1", | ||||
|     "@codemirror/search": "6.5.11", | ||||
|     "@codemirror/state": "6.5.2", | ||||
|     "@codemirror/view": "6.38.5", | ||||
|     "@codemirror/view": "6.38.2", | ||||
|     "@date-fns/tz": "1.4.1", | ||||
|     "@egjs/hammerjs": "2.0.17", | ||||
|     "@formatjs/intl-datetimeformat": "6.18.1", | ||||
|     "@formatjs/intl-displaynames": "6.8.12", | ||||
|     "@formatjs/intl-durationformat": "0.7.5", | ||||
|     "@formatjs/intl-getcanonicallocales": "2.5.6", | ||||
|     "@formatjs/intl-listformat": "7.7.12", | ||||
|     "@formatjs/intl-locale": "4.2.12", | ||||
|     "@formatjs/intl-numberformat": "8.15.5", | ||||
|     "@formatjs/intl-pluralrules": "5.4.5", | ||||
|     "@formatjs/intl-relativetimeformat": "11.4.12", | ||||
|     "@formatjs/intl-datetimeformat": "6.18.0", | ||||
|     "@formatjs/intl-displaynames": "6.8.11", | ||||
|     "@formatjs/intl-durationformat": "0.7.4", | ||||
|     "@formatjs/intl-getcanonicallocales": "2.5.5", | ||||
|     "@formatjs/intl-listformat": "7.7.11", | ||||
|     "@formatjs/intl-locale": "4.2.11", | ||||
|     "@formatjs/intl-numberformat": "8.15.4", | ||||
|     "@formatjs/intl-pluralrules": "5.4.4", | ||||
|     "@formatjs/intl-relativetimeformat": "11.4.11", | ||||
|     "@fullcalendar/core": "6.1.19", | ||||
|     "@fullcalendar/daygrid": "6.1.19", | ||||
|     "@fullcalendar/interaction": "6.1.19", | ||||
| @@ -89,14 +89,14 @@ | ||||
|     "@thomasloven/round-slider": "0.6.0", | ||||
|     "@tsparticles/engine": "3.9.1", | ||||
|     "@tsparticles/preset-links": "3.2.0", | ||||
|     "@vaadin/combo-box": "24.9.2", | ||||
|     "@vaadin/vaadin-themable-mixin": "24.9.2", | ||||
|     "@vaadin/combo-box": "24.9.0", | ||||
|     "@vaadin/vaadin-themable-mixin": "24.9.0", | ||||
|     "@vibrant/color": "4.0.0", | ||||
|     "@vue/web-component-wrapper": "1.3.0", | ||||
|     "@webcomponents/scoped-custom-element-registry": "0.0.10", | ||||
|     "@webcomponents/webcomponentsjs": "2.8.0", | ||||
|     "app-datepicker": "5.1.1", | ||||
|     "barcode-detector": "3.0.6", | ||||
|     "barcode-detector": "3.0.5", | ||||
|     "color-name": "2.0.2", | ||||
|     "comlink": "4.4.2", | ||||
|     "core-js": "3.45.1", | ||||
| @@ -111,10 +111,10 @@ | ||||
|     "fuse.js": "7.1.0", | ||||
|     "google-timezones-json": "1.2.0", | ||||
|     "gulp-zopfli-green": "6.0.2", | ||||
|     "hls.js": "1.6.13", | ||||
|     "hls.js": "1.6.12", | ||||
|     "home-assistant-js-websocket": "9.5.0", | ||||
|     "idb-keyval": "6.2.2", | ||||
|     "intl-messageformat": "10.7.17", | ||||
|     "intl-messageformat": "10.7.16", | ||||
|     "js-yaml": "4.1.0", | ||||
|     "leaflet": "1.9.4", | ||||
|     "leaflet-draw": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch", | ||||
| @@ -122,7 +122,7 @@ | ||||
|     "lit": "3.3.1", | ||||
|     "lit-html": "3.3.1", | ||||
|     "luxon": "3.7.2", | ||||
|     "marked": "16.4.0", | ||||
|     "marked": "16.3.0", | ||||
|     "memoize-one": "6.0.0", | ||||
|     "node-vibrant": "4.0.3", | ||||
|     "object-hash": "3.0.0", | ||||
| @@ -152,13 +152,13 @@ | ||||
|     "@babel/helper-define-polyfill-provider": "0.6.5", | ||||
|     "@babel/plugin-transform-runtime": "7.28.3", | ||||
|     "@babel/preset-env": "7.28.3", | ||||
|     "@bundle-stats/plugin-webpack-filter": "4.21.5", | ||||
|     "@lokalise/node-api": "15.3.0", | ||||
|     "@octokit/auth-oauth-device": "8.0.2", | ||||
|     "@octokit/plugin-retry": "8.0.2", | ||||
|     "@bundle-stats/plugin-webpack-filter": "4.21.3", | ||||
|     "@lokalise/node-api": "15.2.1", | ||||
|     "@octokit/auth-oauth-device": "8.0.1", | ||||
|     "@octokit/plugin-retry": "8.0.1", | ||||
|     "@octokit/rest": "22.0.0", | ||||
|     "@rsdoctor/rspack-plugin": "1.3.1", | ||||
|     "@rspack/core": "1.5.8", | ||||
|     "@rsdoctor/rspack-plugin": "1.2.3", | ||||
|     "@rspack/core": "1.5.5", | ||||
|     "@rspack/dev-server": "1.1.4", | ||||
|     "@types/babel__plugin-transform-runtime": "7.9.5", | ||||
|     "@types/chromecast-caf-receiver": "6.0.22", | ||||
| @@ -183,7 +183,7 @@ | ||||
|     "babel-plugin-template-html-minifier": "4.1.0", | ||||
|     "browserslist-useragent-regexp": "4.1.3", | ||||
|     "del": "8.0.1", | ||||
|     "eslint": "9.37.0", | ||||
|     "eslint": "9.36.0", | ||||
|     "eslint-config-airbnb-base": "15.0.0", | ||||
|     "eslint-config-prettier": "10.1.8", | ||||
|     "eslint-import-resolver-webpack": "0.13.10", | ||||
| @@ -191,7 +191,7 @@ | ||||
|     "eslint-plugin-lit": "2.1.1", | ||||
|     "eslint-plugin-lit-a11y": "5.1.1", | ||||
|     "eslint-plugin-unused-imports": "4.2.0", | ||||
|     "eslint-plugin-wc": "3.0.2", | ||||
|     "eslint-plugin-wc": "3.0.1", | ||||
|     "fancy-log": "2.0.0", | ||||
|     "fs-extra": "11.3.2", | ||||
|     "glob": "11.0.3", | ||||
| @@ -203,7 +203,7 @@ | ||||
|     "husky": "9.1.7", | ||||
|     "jsdom": "27.0.0", | ||||
|     "jszip": "3.10.1", | ||||
|     "lint-staged": "16.2.3", | ||||
|     "lint-staged": "16.1.6", | ||||
|     "lit-analyzer": "2.0.3", | ||||
|     "lodash.merge": "4.6.2", | ||||
|     "lodash.template": "4.5.0", | ||||
| @@ -213,11 +213,11 @@ | ||||
|     "rspack-manifest-plugin": "5.1.0", | ||||
|     "serve": "14.2.5", | ||||
|     "sinon": "21.0.0", | ||||
|     "tar": "7.5.1", | ||||
|     "tar": "7.4.3", | ||||
|     "terser-webpack-plugin": "5.3.14", | ||||
|     "ts-lit-plugin": "2.0.2", | ||||
|     "typescript": "5.9.3", | ||||
|     "typescript-eslint": "8.46.0", | ||||
|     "typescript": "5.9.2", | ||||
|     "typescript-eslint": "8.44.0", | ||||
|     "vite-tsconfig-paths": "5.1.4", | ||||
|     "vitest": "3.2.4", | ||||
|     "webpack-stats-plugin": "1.1.3", | ||||
| @@ -235,5 +235,5 @@ | ||||
|     "tslib": "2.8.1", | ||||
|     "@material/mwc-list@^0.27.0": "patch:@material/mwc-list@npm%3A0.27.0#~/.yarn/patches/@material-mwc-list-npm-0.27.0-5344fc9de4.patch" | ||||
|   }, | ||||
|   "packageManager": "yarn@4.10.3" | ||||
|   "packageManager": "yarn@4.10.2" | ||||
| } | ||||
|   | ||||
| @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" | ||||
|  | ||||
| [project] | ||||
| name         = "home-assistant-frontend" | ||||
| version      = "20250924.0" | ||||
| version      = "20251001.3" | ||||
| license      = "Apache-2.0" | ||||
| license-files = ["LICENSE*"] | ||||
| description  = "The Home Assistant frontend" | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
|     ":semanticCommitsDisabled", | ||||
|     "group:monorepos", | ||||
|     "group:recommended", | ||||
|     "security:minimumReleaseAgeNpm" | ||||
|     "npm:unpublishSafe" | ||||
|   ], | ||||
|   "enabledManagers": ["npm", "nvm"], | ||||
|   "postUpdateOptions": ["yarnDedupeHighest"], | ||||
|   | ||||
| @@ -103,10 +103,7 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) { | ||||
|           ); | ||||
|           box-shadow: var(--ha-card-box-shadow, none); | ||||
|           box-sizing: border-box; | ||||
|           border-radius: var( | ||||
|             --ha-card-border-radius, | ||||
|             var(--ha-border-radius-lg) | ||||
|           ); | ||||
|           border-radius: var(--ha-card-border-radius, 12px); | ||||
|           border-width: var(--ha-card-border-width, 1px); | ||||
|           border-style: solid; | ||||
|           border-color: var( | ||||
| @@ -135,7 +132,7 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) { | ||||
|         } | ||||
|         ha-language-picker { | ||||
|           width: 200px; | ||||
|           border-radius: var(--ha-border-radius-sm); | ||||
|           border-radius: 4px; | ||||
|           overflow: hidden; | ||||
|           --ha-select-height: 40px; | ||||
|           --mdc-select-fill-color: none; | ||||
|   | ||||
| @@ -1,40 +1,23 @@ | ||||
| import { formatHex, parse } from "culori"; | ||||
|  | ||||
| /** | ||||
|  * Expands a 3-digit hex color to a 6-digit hex color. | ||||
|  * @param hex - The hex color to expand. | ||||
|  * @returns The expanded hex color. | ||||
|  * @throws If the hex color is invalid. | ||||
|  */ | ||||
| export const expandHex = (hex: string): string => { | ||||
|   const color = parse(hex); | ||||
|   if (!color) { | ||||
|     throw new Error(`Invalid hex color: ${hex}`); | ||||
|   hex = hex.replace("#", ""); | ||||
|   if (hex.length === 6) return hex; | ||||
|   let result = ""; | ||||
|   for (const val of hex) { | ||||
|     result += val + val; | ||||
|   } | ||||
|   const formattedColor = formatHex(color); | ||||
|   if (!formattedColor) { | ||||
|     throw new Error(`Could not format hex color: ${hex}`); | ||||
|   } | ||||
|   return formattedColor.replace("#", ""); | ||||
|   return result; | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Blends two hex colors. c1 is placed over c2, blend is c1's opacity. | ||||
|  * @param c1 - The first hex color. | ||||
|  * @param c2 - The second hex color. | ||||
|  * @param blend - The blend percentage (0-100). | ||||
|  * @returns The blended hex color. | ||||
|  */ | ||||
| // Blend 2 hex colors: c1 is placed over c2, blend is c1's opacity. | ||||
| export const hexBlend = (c1: string, c2: string, blend = 50): string => { | ||||
|   let color = ""; | ||||
|   c1 = expandHex(c1); | ||||
|   c2 = expandHex(c2); | ||||
|   let color = ""; | ||||
|   for (let i = 0; i <= 5; i += 2) { | ||||
|     const h1 = parseInt(c1.substring(i, i + 2), 16); | ||||
|     const h2 = parseInt(c2.substring(i, i + 2), 16); | ||||
|     const hex = Math.floor(h2 + (h1 - h2) * (blend / 100)) | ||||
|       .toString(16) | ||||
|       .padStart(2, "0"); | ||||
|     let hex = Math.floor(h2 + (h1 - h2) * (blend / 100)).toString(16); | ||||
|     while (hex.length < 2) hex = "0" + hex; | ||||
|     color += hex; | ||||
|   } | ||||
|   return `#${color}`; | ||||
|   | ||||
| @@ -1,49 +1,28 @@ | ||||
| import { wcagLuminance, wcagContrast } from "culori"; | ||||
| export const luminosity = (rgb: [number, number, number]): number => { | ||||
|   // http://www.w3.org/TR/WCAG20/#relativeluminancedef | ||||
|   const lum: [number, number, number] = [0, 0, 0]; | ||||
|   for (let i = 0; i < rgb.length; i++) { | ||||
|     const chan = rgb[i] / 255; | ||||
|     lum[i] = chan <= 0.03928 ? chan / 12.92 : ((chan + 0.055) / 1.055) ** 2.4; | ||||
|   } | ||||
|  | ||||
| /** | ||||
|  * Calculates the luminosity of an RGB color. | ||||
|  * @param rgb - The RGB color to calculate the luminosity of. | ||||
|  * @returns The luminosity of the color. | ||||
|  */ | ||||
| export const luminosity = (rgb: [number, number, number]): number => | ||||
|   wcagLuminance({ | ||||
|     mode: "rgb", | ||||
|     r: rgb[0] / 255, | ||||
|     g: rgb[1] / 255, | ||||
|     b: rgb[2] / 255, | ||||
|   }); | ||||
|   return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2]; | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Calculates the contrast ratio between two RGB colors. | ||||
|  * @param color1 - The first color to calculate the contrast ratio of. | ||||
|  * @param color2 - The second color to calculate the contrast ratio of. | ||||
|  * @returns The contrast ratio between the two colors. | ||||
|  */ | ||||
| export const rgbContrast = ( | ||||
|   color1: [number, number, number], | ||||
|   color2: [number, number, number] | ||||
| ) => | ||||
|   wcagContrast( | ||||
|     { | ||||
|       mode: "rgb", | ||||
|       r: color1[0] / 255, | ||||
|       g: color1[1] / 255, | ||||
|       b: color1[2] / 255, | ||||
|     }, | ||||
|     { | ||||
|       mode: "rgb", | ||||
|       r: color2[0] / 255, | ||||
|       g: color2[1] / 255, | ||||
|       b: color2[2] / 255, | ||||
|     } | ||||
|   ); | ||||
| ) => { | ||||
|   const lum1 = luminosity(color1); | ||||
|   const lum2 = luminosity(color2); | ||||
|  | ||||
|   if (lum1 > lum2) { | ||||
|     return (lum1 + 0.05) / (lum2 + 0.05); | ||||
|   } | ||||
|  | ||||
|   return (lum2 + 0.05) / (lum1 + 0.05); | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Calculates the contrast ratio between two RGB colors. | ||||
|  * @param rgb1 - The first color to calculate the contrast ratio of. | ||||
|  * @param rgb2 - The second color to calculate the contrast ratio of. | ||||
|  * @returns The contrast ratio between the two colors. | ||||
|  */ | ||||
| export const getRGBContrastRatio = ( | ||||
|   rgb1: [number, number, number], | ||||
|   rgb2: [number, number, number] | ||||
|   | ||||
| @@ -1,141 +0,0 @@ | ||||
| import type { | ||||
|   ReactiveController, | ||||
|   ReactiveControllerHost, | ||||
| } from "@lit/reactive-element/reactive-controller"; | ||||
|  | ||||
| const UNDO_REDO_STACK_LIMIT = 75; | ||||
|  | ||||
| /** | ||||
|  * Configuration options for the UndoRedoController. | ||||
|  * | ||||
|  * @template ConfigType The type of configuration to manage. | ||||
|  */ | ||||
| export interface UndoRedoControllerConfig<ConfigType> { | ||||
|   stackLimit?: number; | ||||
|   currentConfig: () => ConfigType; | ||||
|   apply: (config: ConfigType) => void; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * A controller to manage undo and redo operations for a given configuration type. | ||||
|  * | ||||
|  * @template ConfigType The type of configuration to manage. | ||||
|  */ | ||||
| export class UndoRedoController<ConfigType> implements ReactiveController { | ||||
|   private _host: ReactiveControllerHost; | ||||
|  | ||||
|   private _undoStack: ConfigType[] = []; | ||||
|  | ||||
|   private _redoStack: ConfigType[] = []; | ||||
|  | ||||
|   private readonly _stackLimit: number = UNDO_REDO_STACK_LIMIT; | ||||
|  | ||||
|   private readonly _apply: (config: ConfigType) => void = () => { | ||||
|     throw new Error("No apply function provided"); | ||||
|   }; | ||||
|  | ||||
|   private readonly _currentConfig: () => ConfigType = () => { | ||||
|     throw new Error("No currentConfig function provided"); | ||||
|   }; | ||||
|  | ||||
|   constructor( | ||||
|     host: ReactiveControllerHost, | ||||
|     options: UndoRedoControllerConfig<ConfigType> | ||||
|   ) { | ||||
|     if (options.stackLimit !== undefined) { | ||||
|       this._stackLimit = options.stackLimit; | ||||
|     } | ||||
|  | ||||
|     this._apply = options.apply; | ||||
|     this._currentConfig = options.currentConfig; | ||||
|     this._host = host; | ||||
|     host.addController(this); | ||||
|   } | ||||
|  | ||||
|   hostConnected() { | ||||
|     window.addEventListener("undo-change", this._onUndoChange); | ||||
|   } | ||||
|  | ||||
|   hostDisconnected() { | ||||
|     window.removeEventListener("undo-change", this._onUndoChange); | ||||
|   } | ||||
|  | ||||
|   private _onUndoChange = (ev: Event) => { | ||||
|     ev.stopPropagation(); | ||||
|     this.undo(); | ||||
|     this._host.requestUpdate(); | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
|    * Indicates whether there are actions available to undo. | ||||
|    * | ||||
|    * @returns `true` if there are actions to undo, `false` otherwise. | ||||
|    */ | ||||
|   public get canUndo(): boolean { | ||||
|     return this._undoStack.length > 0; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Indicates whether there are actions available to redo. | ||||
|    * | ||||
|    * @returns `true` if there are actions to redo, `false` otherwise. | ||||
|    */ | ||||
|   public get canRedo(): boolean { | ||||
|     return this._redoStack.length > 0; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Commits the current configuration to the undo stack and clears the redo stack. | ||||
|    * | ||||
|    * @param config The current configuration to commit. | ||||
|    */ | ||||
|   public commit(config: ConfigType) { | ||||
|     if (this._undoStack.length >= this._stackLimit) { | ||||
|       this._undoStack.shift(); | ||||
|     } | ||||
|     this._undoStack.push({ ...config }); | ||||
|     this._redoStack = []; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Undoes the last action and applies the previous configuration | ||||
|    * while saving the current configuration to the redo stack. | ||||
|    */ | ||||
|   public undo() { | ||||
|     if (this._undoStack.length === 0) { | ||||
|       return; | ||||
|     } | ||||
|     this._redoStack.push({ ...this._currentConfig() }); | ||||
|     const config = this._undoStack.pop()!; | ||||
|     this._apply(config); | ||||
|     this._host.requestUpdate(); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Redoes the last undone action and reapplies the configuration | ||||
|    * while saving the current configuration to the undo stack. | ||||
|    */ | ||||
|   public redo() { | ||||
|     if (this._redoStack.length === 0) { | ||||
|       return; | ||||
|     } | ||||
|     this._undoStack.push({ ...this._currentConfig() }); | ||||
|     const config = this._redoStack.pop()!; | ||||
|     this._apply(config); | ||||
|     this._host.requestUpdate(); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Resets the undo and redo stacks, clearing all history. | ||||
|    */ | ||||
|   public reset() { | ||||
|     this._undoStack = []; | ||||
|     this._redoStack = []; | ||||
|   } | ||||
| } | ||||
|  | ||||
| declare global { | ||||
|   interface HASSDomEvents { | ||||
|     "undo-change": undefined; | ||||
|   } | ||||
| } | ||||
| @@ -31,10 +31,10 @@ export const isNavigationClick = (e: MouseEvent, preventDefault = true) => { | ||||
|  | ||||
|   const location = window.location; | ||||
|   const origin = location.origin || location.protocol + "//" + location.host; | ||||
|   if (!href.startsWith(origin)) { | ||||
|   if (href.indexOf(origin) !== 0) { | ||||
|     return undefined; | ||||
|   } | ||||
|   href = href.slice(origin.length); | ||||
|   href = href.substr(origin.length); | ||||
|  | ||||
|   if (href === "#") { | ||||
|     return undefined; | ||||
|   | ||||
| @@ -61,9 +61,3 @@ export const computeEntityEntryName = ( | ||||
|  | ||||
|   return name; | ||||
| }; | ||||
|  | ||||
| export const entityUseDeviceName = ( | ||||
|   stateObj: HassEntity, | ||||
|   entities: HomeAssistant["entities"], | ||||
|   devices: HomeAssistant["devices"] | ||||
| ): boolean => !computeEntityName(stateObj, entities, devices); | ||||
|   | ||||
| @@ -1,104 +0,0 @@ | ||||
| import type { HassEntity } from "home-assistant-js-websocket"; | ||||
| import type { HomeAssistant } from "../../types"; | ||||
| import { ensureArray } from "../array/ensure-array"; | ||||
| import { computeAreaName } from "./compute_area_name"; | ||||
| import { computeDeviceName } from "./compute_device_name"; | ||||
| import { computeEntityName, entityUseDeviceName } from "./compute_entity_name"; | ||||
| import { computeFloorName } from "./compute_floor_name"; | ||||
| import { getEntityContext } from "./context/get_entity_context"; | ||||
|  | ||||
| const DEFAULT_SEPARATOR = " "; | ||||
|  | ||||
| export type EntityNameItem = | ||||
|   | { | ||||
|       type: "entity" | "device" | "area" | "floor"; | ||||
|     } | ||||
|   | { | ||||
|       type: "text"; | ||||
|       text: string; | ||||
|     }; | ||||
|  | ||||
| export interface EntityNameOptions { | ||||
|   separator?: string; | ||||
| } | ||||
|  | ||||
| export const computeEntityNameDisplay = ( | ||||
|   stateObj: HassEntity, | ||||
|   name: EntityNameItem | EntityNameItem[], | ||||
|   entities: HomeAssistant["entities"], | ||||
|   devices: HomeAssistant["devices"], | ||||
|   areas: HomeAssistant["areas"], | ||||
|   floors: HomeAssistant["floors"], | ||||
|   options?: EntityNameOptions | ||||
| ) => { | ||||
|   let items = ensureArray(name); | ||||
|  | ||||
|   const separator = options?.separator ?? DEFAULT_SEPARATOR; | ||||
|  | ||||
|   // If all items are text, just join them | ||||
|   if (items.every((n) => n.type === "text")) { | ||||
|     return items.map((item) => item.text).join(separator); | ||||
|   } | ||||
|  | ||||
|   const useDeviceName = entityUseDeviceName(stateObj, entities, devices); | ||||
|  | ||||
|   // If entity uses device name, and device is not already included, replace it with device name | ||||
|   if (useDeviceName) { | ||||
|     const hasDevice = items.some((n) => n.type === "device"); | ||||
|     if (!hasDevice) { | ||||
|       items = items.map((n) => (n.type === "entity" ? { type: "device" } : n)); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   const names = computeEntityNameList( | ||||
|     stateObj, | ||||
|     items, | ||||
|     entities, | ||||
|     devices, | ||||
|     areas, | ||||
|     floors | ||||
|   ); | ||||
|  | ||||
|   // If after processing there is only one name, return that | ||||
|   if (names.length === 1) { | ||||
|     return names[0] || ""; | ||||
|   } | ||||
|  | ||||
|   return names.filter((n) => n).join(separator); | ||||
| }; | ||||
|  | ||||
| export const computeEntityNameList = ( | ||||
|   stateObj: HassEntity, | ||||
|   name: EntityNameItem[], | ||||
|   entities: HomeAssistant["entities"], | ||||
|   devices: HomeAssistant["devices"], | ||||
|   areas: HomeAssistant["areas"], | ||||
|   floors: HomeAssistant["floors"] | ||||
| ): (string | undefined)[] => { | ||||
|   const { device, area, floor } = getEntityContext( | ||||
|     stateObj, | ||||
|     entities, | ||||
|     devices, | ||||
|     areas, | ||||
|     floors | ||||
|   ); | ||||
|  | ||||
|   const names = name.map((item) => { | ||||
|     switch (item.type) { | ||||
|       case "entity": | ||||
|         return computeEntityName(stateObj, entities, devices); | ||||
|       case "device": | ||||
|         return device ? computeDeviceName(device) : undefined; | ||||
|       case "area": | ||||
|         return area ? computeAreaName(area) : undefined; | ||||
|       case "floor": | ||||
|         return floor ? computeFloorName(floor) : undefined; | ||||
|       case "text": | ||||
|         return item.text; | ||||
|       default: | ||||
|         return ""; | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   return names; | ||||
| }; | ||||
| @@ -1,3 +1,3 @@ | ||||
| /** Compute the object ID of a state. */ | ||||
| export const computeObjectId = (entityId: string): string => | ||||
|   entityId.slice(entityId.indexOf(".") + 1); | ||||
|   entityId.substr(entityId.indexOf(".") + 1); | ||||
|   | ||||
| @@ -122,22 +122,3 @@ export const generateEntityFilter = ( | ||||
|     return true; | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| export const findEntities = ( | ||||
|   entities: string[], | ||||
|   filters: EntityFilterFunc[] | ||||
| ): string[] => { | ||||
|   const seen = new Set<string>(); | ||||
|   const results: string[] = []; | ||||
|  | ||||
|   for (const filter of filters) { | ||||
|     for (const entity of entities) { | ||||
|       if (filter(entity) && !seen.has(entity)) { | ||||
|         seen.add(entity); | ||||
|         results.push(entity); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return results; | ||||
| }; | ||||
|   | ||||
| @@ -18,7 +18,6 @@ export const FIXED_DOMAIN_STATES = { | ||||
|     "pending", | ||||
|     "triggered", | ||||
|   ], | ||||
|   alert: ["on", "off", "idle"], | ||||
|   assist_satellite: ["idle", "listening", "responding", "processing"], | ||||
|   automation: ["on", "off"], | ||||
|   binary_sensor: ["on", "off"], | ||||
|   | ||||
| @@ -40,7 +40,6 @@ const STATE_COLORED_DOMAIN = new Set([ | ||||
|   "vacuum", | ||||
|   "valve", | ||||
|   "water_heater", | ||||
|   "weather", | ||||
| ]); | ||||
|  | ||||
| export const stateColorCss = (stateObj: HassEntity, state?: string) => { | ||||
|   | ||||
| @@ -32,8 +32,6 @@ export const numberFormatToLocale = ( | ||||
|       return ["de", "es", "it"]; // Use German with fallback to Spanish then Italian formatting 1.234.567,89 | ||||
|     case NumberFormat.space_comma: | ||||
|       return ["fr", "sv", "cs"]; // Use French with fallback to Swedish and Czech formatting 1 234 567,89 | ||||
|     case NumberFormat.quote_decimal: | ||||
|       return ["de-CH"]; // Use German (Switzerland) formatting 1'234'567.89 | ||||
|     case NumberFormat.system: | ||||
|       return undefined; | ||||
|     default: | ||||
|   | ||||
| @@ -67,7 +67,10 @@ function isSeparatorAtPos(value: string, index: number): boolean { | ||||
|     case undefined: | ||||
|       return false; | ||||
|     default: | ||||
|       return isEmojiImprecise(code); | ||||
|       if (isEmojiImprecise(code)) { | ||||
|         return true; | ||||
|       } | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,12 +1,13 @@ | ||||
| import type { HassConfig, HassEntity } from "home-assistant-js-websocket"; | ||||
| import type { FrontendLocaleData } from "../../data/translation"; | ||||
| import type { HomeAssistant } from "../../types"; | ||||
| import { | ||||
|   computeEntityNameDisplay, | ||||
|   type EntityNameItem, | ||||
|   type EntityNameOptions, | ||||
| } from "../entity/compute_entity_name_display"; | ||||
| import type { LocalizeFunc } from "./localize"; | ||||
| import { computeEntityName } from "../entity/compute_entity_name"; | ||||
| import { computeDeviceName } from "../entity/compute_device_name"; | ||||
| import { getEntityContext } from "../entity/context/get_entity_context"; | ||||
| import { computeAreaName } from "../entity/compute_area_name"; | ||||
| import { computeFloorName } from "../entity/compute_floor_name"; | ||||
| import { ensureArray } from "../array/ensure-array"; | ||||
|  | ||||
| export type FormatEntityStateFunc = ( | ||||
|   stateObj: HassEntity, | ||||
| @@ -26,8 +27,8 @@ export type EntityNameType = "entity" | "device" | "area" | "floor"; | ||||
|  | ||||
| export type FormatEntityNameFunc = ( | ||||
|   stateObj: HassEntity, | ||||
|   name: EntityNameItem | EntityNameItem[], | ||||
|   options?: EntityNameOptions | ||||
|   type: EntityNameType | EntityNameType[], | ||||
|   separator?: string | ||||
| ) => string; | ||||
|  | ||||
| export const computeFormatFunctions = async ( | ||||
| @@ -74,15 +75,45 @@ export const computeFormatFunctions = async ( | ||||
|       ), | ||||
|     formatEntityAttributeName: (stateObj, attribute) => | ||||
|       computeAttributeNameDisplay(localize, stateObj, entities, attribute), | ||||
|     formatEntityName: (stateObj, name, options) => | ||||
|       computeEntityNameDisplay( | ||||
|     formatEntityName: (stateObj, type, separator = " ") => { | ||||
|       const types = ensureArray(type); | ||||
|       const namesList: (string | undefined)[] = []; | ||||
|  | ||||
|       const { device, area, floor } = getEntityContext( | ||||
|         stateObj, | ||||
|         name, | ||||
|         entities, | ||||
|         devices, | ||||
|         areas, | ||||
|         floors, | ||||
|         options | ||||
|       ), | ||||
|         floors | ||||
|       ); | ||||
|  | ||||
|       for (const t of types) { | ||||
|         switch (t) { | ||||
|           case "entity": { | ||||
|             namesList.push(computeEntityName(stateObj, entities, devices)); | ||||
|             break; | ||||
|           } | ||||
|           case "device": { | ||||
|             if (device) { | ||||
|               namesList.push(computeDeviceName(device)); | ||||
|             } | ||||
|             break; | ||||
|           } | ||||
|           case "area": { | ||||
|             if (area) { | ||||
|               namesList.push(computeAreaName(area)); | ||||
|             } | ||||
|             break; | ||||
|           } | ||||
|           case "floor": { | ||||
|             if (floor) { | ||||
|               namesList.push(computeFloorName(floor)); | ||||
|             } | ||||
|             break; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|       return namesList.filter((name) => name !== undefined).join(separator); | ||||
|     }, | ||||
|   }; | ||||
| }; | ||||
|   | ||||
| @@ -14,7 +14,7 @@ export default function parseAspectRatio(input: string) { | ||||
|   } | ||||
|   try { | ||||
|     if (input.endsWith("%")) { | ||||
|       return { w: 100, h: parseOrThrow(input.slice(0, -1)) }; | ||||
|       return { w: 100, h: parseOrThrow(input.substr(0, input.length - 1)) }; | ||||
|     } | ||||
|  | ||||
|     const arr = input.replace(":", "x").split("x"); | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import { ResizeController } from "@lit-labs/observers/resize-controller"; | ||||
| import { consume } from "@lit/context"; | ||||
| import { ResizeController } from "@lit-labs/observers/resize-controller"; | ||||
| import { mdiChevronDown, mdiChevronUp, mdiRestart } from "@mdi/js"; | ||||
| import { differenceInMinutes } from "date-fns"; | ||||
| import type { DataZoomComponentOption } from "echarts/components"; | ||||
| @@ -7,16 +7,15 @@ import type { EChartsType } from "echarts/core"; | ||||
| import type { | ||||
|   ECElementEvent, | ||||
|   LegendComponentOption, | ||||
|   LineSeriesOption, | ||||
|   XAXisOption, | ||||
|   YAXisOption, | ||||
|   LineSeriesOption, | ||||
| } from "echarts/types/dist/shared"; | ||||
| import type { PropertyValues } from "lit"; | ||||
| import { css, html, LitElement, nothing } from "lit"; | ||||
| import { customElement, property, state } from "lit/decorators"; | ||||
| import { classMap } from "lit/directives/class-map"; | ||||
| import { styleMap } from "lit/directives/style-map"; | ||||
| import { ensureArray } from "../../common/array/ensure-array"; | ||||
| import { getAllGraphColors } from "../../common/color/colors"; | ||||
| import { fireEvent } from "../../common/dom/fire_event"; | ||||
| import { listenMediaQuery } from "../../common/dom/media_query"; | ||||
| @@ -25,10 +24,11 @@ import type { Themes } from "../../data/ws-themes"; | ||||
| import type { ECOption } from "../../resources/echarts"; | ||||
| import type { HomeAssistant } from "../../types"; | ||||
| import { isMac } from "../../util/is_mac"; | ||||
| import "../chips/ha-assist-chip"; | ||||
| import "../ha-icon-button"; | ||||
| import { filterXSS } from "../../common/util/xss"; | ||||
| import { formatTimeLabel } from "./axis-label"; | ||||
| import { ensureArray } from "../../common/array/ensure-array"; | ||||
| import "../chips/ha-assist-chip"; | ||||
| import { downSampleLineData } from "./down-sample"; | ||||
|  | ||||
| export const MIN_TIME_BETWEEN_UPDATES = 60 * 5 * 1000; | ||||
| @@ -976,7 +976,7 @@ export class HaChartBase extends LitElement { | ||||
|       right: 4px; | ||||
|       display: flex; | ||||
|       flex-direction: column; | ||||
|       gap: var(--ha-space-1); | ||||
|       gap: 4px; | ||||
|     } | ||||
|     .chart-controls.small { | ||||
|       top: 0; | ||||
| @@ -985,7 +985,7 @@ export class HaChartBase extends LitElement { | ||||
|     .chart-controls ha-icon-button, | ||||
|     .chart-controls ::slotted(ha-icon-button) { | ||||
|       background: var(--card-background-color); | ||||
|       border-radius: var(--ha-border-radius-sm); | ||||
|       border-radius: 4px; | ||||
|       --mdc-icon-button-size: 32px; | ||||
|       color: var(--primary-color); | ||||
|       border: 1px solid var(--divider-color); | ||||
| @@ -1013,7 +1013,7 @@ export class HaChartBase extends LitElement { | ||||
|       flex-wrap: wrap; | ||||
|       justify-content: center; | ||||
|       align-items: center; | ||||
|       gap: var(--ha-space-2); | ||||
|       gap: 8px; | ||||
|     } | ||||
|     .chart-legend li { | ||||
|       height: 24px; | ||||
| @@ -1038,7 +1038,7 @@ export class HaChartBase extends LitElement { | ||||
|     .chart-legend .bullet { | ||||
|       border-width: 1px; | ||||
|       border-style: solid; | ||||
|       border-radius: var(--ha-border-radius-circle); | ||||
|       border-radius: 50%; | ||||
|       display: block; | ||||
|       height: 16px; | ||||
|       width: 16px; | ||||
|   | ||||
| @@ -6,8 +6,6 @@ import { computeDomain } from "../../common/entity/compute_domain"; | ||||
| import { stateColorProperties } from "../../common/entity/state_color"; | ||||
| import { UNAVAILABLE, UNKNOWN } from "../../data/entity"; | ||||
| import { computeCssValue } from "../../resources/css-variables"; | ||||
| import { computeStateDomain } from "../../common/entity/compute_state_domain"; | ||||
| import { FIXED_DOMAIN_STATES } from "../../common/entity/get_states"; | ||||
|  | ||||
| const DOMAIN_STATE_SHADES: Record<string, Record<string, number>> = { | ||||
|   media_player: { | ||||
| @@ -53,28 +51,6 @@ function computeTimelineStateColor( | ||||
| let colorIndex = 0; | ||||
| const stateColorMap = new Map<string, string>(); | ||||
|  | ||||
| function computeTimelineEnumColor( | ||||
|   state: string, | ||||
|   computedStyles: CSSStyleDeclaration, | ||||
|   stateObj?: HassEntity | ||||
| ): string | undefined { | ||||
|   if (!stateObj) { | ||||
|     return undefined; | ||||
|   } | ||||
|   const domain = computeStateDomain(stateObj); | ||||
|   const states = | ||||
|     FIXED_DOMAIN_STATES[domain] || | ||||
|     (domain === "sensor" && | ||||
|       stateObj.attributes.device_class === "enum" && | ||||
|       stateObj.attributes.options) || | ||||
|     []; | ||||
|   const idx = states.indexOf(state); | ||||
|   if (idx === -1) { | ||||
|     return undefined; | ||||
|   } | ||||
|   return getGraphColorByIndex(idx, computedStyles); | ||||
| } | ||||
|  | ||||
| function computeTimeLineGenericColor( | ||||
|   state: string, | ||||
|   computedStyles: CSSStyleDeclaration | ||||
| @@ -95,7 +71,6 @@ export function computeTimelineColor( | ||||
| ): string { | ||||
|   return ( | ||||
|     computeTimelineStateColor(state, computedStyles, stateObj) || | ||||
|     computeTimelineEnumColor(state, computedStyles, stateObj) || | ||||
|     computeTimeLineGenericColor(state, computedStyles) | ||||
|   ); | ||||
| } | ||||
|   | ||||
| @@ -290,9 +290,7 @@ export class DialogDataTableSettings extends LitElement { | ||||
|           ha-dialog { | ||||
|             --vertical-align-dialog: flex-start; | ||||
|             --dialog-surface-margin-top: 250px; | ||||
|             --ha-dialog-border-radius: var(--ha-border-radius-4xl) | ||||
|               var(--ha-border-radius-4xl) var(--ha-border-radius-square) | ||||
|               var(--ha-border-radius-square); | ||||
|             --ha-dialog-border-radius: 28px 28px 0 0; | ||||
|             --mdc-dialog-min-height: calc(100% - 250px); | ||||
|             --mdc-dialog-max-height: calc(100% - 250px); | ||||
|           } | ||||
|   | ||||
| @@ -1053,7 +1053,7 @@ export class HaDataTable extends LitElement { | ||||
|  | ||||
|         .mdc-data-table { | ||||
|           background-color: var(--data-table-background-color); | ||||
|           border-radius: var(--ha-border-radius-sm); | ||||
|           border-radius: 4px; | ||||
|           border-width: 1px; | ||||
|           border-style: solid; | ||||
|           border-color: var(--divider-color); | ||||
|   | ||||
| @@ -1,9 +1,6 @@ | ||||
| import { expose } from "comlink"; | ||||
| import Fuse from "fuse.js"; | ||||
| import memoizeOne from "memoize-one"; | ||||
| import { ipCompare, stringCompare } from "../../common/string/compare"; | ||||
| import { stringCompare, ipCompare } from "../../common/string/compare"; | ||||
| import { stripDiacritics } from "../../common/string/strip-diacritics"; | ||||
| import { HaFuse } from "../../resources/fuse"; | ||||
| import type { | ||||
|   ClonedDataTableColumnData, | ||||
|   DataTableRowData, | ||||
| @@ -11,48 +8,29 @@ import type { | ||||
|   SortingDirection, | ||||
| } from "./ha-data-table"; | ||||
|  | ||||
| const fuseIndex = memoizeOne( | ||||
|   (data: DataTableRowData[], columns: SortableColumnContainer) => { | ||||
|     const searchKeys = new Set<string>(); | ||||
|     Object.entries(columns).forEach(([key, column]) => { | ||||
|       if (column.filterable) { | ||||
|         searchKeys.add( | ||||
|           column.filterKey | ||||
|             ? `${column.valueColumn || key}.${column.filterKey}` | ||||
|             : key | ||||
|         ); | ||||
|       } | ||||
|     }); | ||||
|     return Fuse.createIndex([...searchKeys], data); | ||||
|   } | ||||
| ); | ||||
|  | ||||
| const filterData = ( | ||||
|   data: DataTableRowData[], | ||||
|   columns: SortableColumnContainer, | ||||
|   filter: string | ||||
| ) => { | ||||
|   filter = stripDiacritics(filter.toLowerCase()); | ||||
|  | ||||
|   if (filter === "") { | ||||
|     return data; | ||||
|   } | ||||
|  | ||||
|   const index = fuseIndex(data, columns); | ||||
|  | ||||
|   const fuse = new HaFuse( | ||||
|     data, | ||||
|     { shouldSort: false, minMatchCharLength: 1 }, | ||||
|     index | ||||
|   return data.filter((row) => | ||||
|     Object.entries(columns).some((columnEntry) => { | ||||
|       const [key, column] = columnEntry; | ||||
|       if (column.filterable) { | ||||
|         const value = String( | ||||
|           column.filterKey | ||||
|             ? row[column.valueColumn || key][column.filterKey] | ||||
|             : row[column.valueColumn || key] | ||||
|         ); | ||||
|  | ||||
|   const searchResults = fuse.multiTermsSearch(filter); | ||||
|  | ||||
|   if (searchResults) { | ||||
|     return searchResults.map((result) => result.item); | ||||
|         if (stripDiacritics(value).toLowerCase().includes(filter)) { | ||||
|           return true; | ||||
|         } | ||||
|  | ||||
|   return data; | ||||
|       } | ||||
|       return false; | ||||
|     }) | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| const sortData = ( | ||||
|   | ||||
| @@ -4,11 +4,11 @@ import Vue from "vue"; | ||||
| import DateRangePicker from "vue2-daterange-picker"; | ||||
| // @ts-ignore | ||||
| import dateRangePickerStyles from "vue2-daterange-picker/dist/vue2-daterange-picker.css"; | ||||
| import { | ||||
|   localizeMonths, | ||||
|   localizeWeekdays, | ||||
| } from "../common/datetime/localize_date"; | ||||
| import { fireEvent } from "../common/dom/fire_event"; | ||||
| import { | ||||
|   localizeWeekdays, | ||||
|   localizeMonths, | ||||
| } from "../common/datetime/localize_date"; | ||||
| import { mainWindow } from "../common/dom/get_main_window"; | ||||
|  | ||||
| // eslint-disable-next-line @typescript-eslint/naming-convention | ||||
| @@ -177,7 +177,7 @@ class DateRangePickerElement extends WrappedElement { | ||||
|             top: auto; | ||||
|             box-shadow: var(--ha-card-box-shadow, none); | ||||
|             background-color: var(--card-background-color); | ||||
|             border-radius: var(--ha-card-border-radius, var(--ha-border-radius-lg)); | ||||
|             border-radius: var(--ha-card-border-radius, 12px); | ||||
|             border-width: var(--ha-card-border-width, 1px); | ||||
|             border-style: solid; | ||||
|             border-color: var( | ||||
| @@ -203,7 +203,7 @@ class DateRangePickerElement extends WrappedElement { | ||||
|           .daterangepicker .calendar-table th { | ||||
|             background-color: transparent; | ||||
|             color: var(--secondary-text-color); | ||||
|             border-radius: var(--ha-border-radius-square); | ||||
|             border-radius: 0; | ||||
|             outline: none; | ||||
|             min-width: 32px; | ||||
|             height: 32px; | ||||
| @@ -225,13 +225,13 @@ class DateRangePickerElement extends WrappedElement { | ||||
|             color: var(--text-primary-color); | ||||
|           } | ||||
|           .daterangepicker td.start-date.end-date { | ||||
|             border-radius: var(--ha-border-radius-circle); | ||||
|             border-radius: 50%; | ||||
|           } | ||||
|           .daterangepicker td.start-date { | ||||
|             border-radius: var(--ha-border-radius-circle) var(--ha-border-radius-square) var(--ha-border-radius-square) var(--ha-border-radius-circle); | ||||
|             border-radius: 50% 0 0 50%; | ||||
|           } | ||||
|           .daterangepicker td.end-date { | ||||
|             border-radius: var(--ha-border-radius-square) var(--ha-border-radius-circle) var(--ha-border-radius-circle) var(--ha-border-radius-square); | ||||
|             border-radius: 0 50% 50% 0; | ||||
|           } | ||||
|           .reportrange-text { | ||||
|             background: none !important; | ||||
| @@ -265,7 +265,7 @@ class DateRangePickerElement extends WrappedElement { | ||||
|             border: 1px solid var(--primary-color); | ||||
|             background-color: transparent; | ||||
|             color: var(--primary-color); | ||||
|             border-radius: var(--ha-border-radius-sm); | ||||
|             border-radius: 4px; | ||||
|             padding: 8px; | ||||
|             cursor: pointer; | ||||
|           } | ||||
| @@ -321,10 +321,10 @@ class DateRangePickerElement extends WrappedElement { | ||||
|               -webkit-transform: rotate(-45deg); | ||||
|             } | ||||
|             .daterangepicker td.start-date { | ||||
|               border-radius: var(--ha-border-radius-square) var(--ha-border-radius-circle) var(--ha-border-radius-circle) var(--ha-border-radius-square); | ||||
|               border-radius: 0 50% 50% 0; | ||||
|             } | ||||
|             .daterangepicker td.end-date { | ||||
|               border-radius: var(--ha-border-radius-circle) var(--ha-border-radius-square) var(--ha-border-radius-square) var(--ha-border-radius-circle); | ||||
|               border-radius: 50% 0 0 50%; | ||||
|             } | ||||
|             `; | ||||
|     } | ||||
|   | ||||
| @@ -1,493 +0,0 @@ | ||||
| import "@material/mwc-menu/mwc-menu-surface"; | ||||
| import { mdiDrag, mdiPlus } from "@mdi/js"; | ||||
| import type { ComboBoxLitRenderer } from "@vaadin/combo-box/lit"; | ||||
| import type { IFuseOptions } from "fuse.js"; | ||||
| import Fuse from "fuse.js"; | ||||
| import { css, html, LitElement, nothing } from "lit"; | ||||
| import { customElement, property, query, state } from "lit/decorators"; | ||||
| import { repeat } from "lit/directives/repeat"; | ||||
| import memoizeOne from "memoize-one"; | ||||
| import { ensureArray } from "../../common/array/ensure-array"; | ||||
| import { fireEvent } from "../../common/dom/fire_event"; | ||||
| import { stopPropagation } from "../../common/dom/stop_propagation"; | ||||
| import type { EntityNameItem } from "../../common/entity/compute_entity_name_display"; | ||||
| import { getEntityContext } from "../../common/entity/context/get_entity_context"; | ||||
| import type { EntityNameType } from "../../common/translations/entity-state"; | ||||
| import type { LocalizeKeys } from "../../common/translations/localize"; | ||||
| import type { HomeAssistant, ValueChangedEvent } from "../../types"; | ||||
| import "../chips/ha-assist-chip"; | ||||
| import "../chips/ha-chip-set"; | ||||
| import "../chips/ha-input-chip"; | ||||
| import "../ha-combo-box"; | ||||
| import type { HaComboBox } from "../ha-combo-box"; | ||||
| import "../ha-sortable"; | ||||
|  | ||||
| interface EntityNameOption { | ||||
|   primary: string; | ||||
|   secondary?: string; | ||||
|   value: string; | ||||
| } | ||||
|  | ||||
| const rowRenderer: ComboBoxLitRenderer<EntityNameOption> = (item) => html` | ||||
|   <ha-combo-box-item type="button"> | ||||
|     <span slot="headline">${item.primary}</span> | ||||
|     ${item.secondary | ||||
|       ? html`<span slot="supporting-text">${item.secondary}</span>` | ||||
|       : nothing} | ||||
|   </ha-combo-box-item> | ||||
| `; | ||||
|  | ||||
| const KNOWN_TYPES = new Set(["entity", "device", "area", "floor"]); | ||||
|  | ||||
| const UNIQUE_TYPES = new Set(["entity", "device", "area", "floor"]); | ||||
|  | ||||
| @customElement("ha-entity-name-picker") | ||||
| export class HaEntityNamePicker extends LitElement { | ||||
|   @property({ attribute: false }) public hass!: HomeAssistant; | ||||
|  | ||||
|   @property({ attribute: false }) public entityId?: string; | ||||
|  | ||||
|   @property({ attribute: false }) public value?: | ||||
|     | string | ||||
|     | EntityNameItem | ||||
|     | EntityNameItem[]; | ||||
|  | ||||
|   @property() public label?: string; | ||||
|  | ||||
|   @property() public helper?: string; | ||||
|  | ||||
|   @property({ type: Boolean }) public required = false; | ||||
|  | ||||
|   @property({ type: Boolean, reflect: true }) public disabled = false; | ||||
|  | ||||
|   @query(".container", true) private _container?: HTMLDivElement; | ||||
|  | ||||
|   @query("ha-combo-box", true) private _comboBox!: HaComboBox; | ||||
|  | ||||
|   @state() private _opened = false; | ||||
|  | ||||
|   private _editIndex?: number; | ||||
|  | ||||
|   private _validOptions = memoizeOne((entityId?: string) => { | ||||
|     const options = new Set<string>(); | ||||
|     if (!entityId) { | ||||
|       return options; | ||||
|     } | ||||
|  | ||||
|     const stateObj = this.hass.states[entityId]; | ||||
|  | ||||
|     if (!stateObj) { | ||||
|       return options; | ||||
|     } | ||||
|  | ||||
|     options.add("entity"); | ||||
|  | ||||
|     const context = getEntityContext( | ||||
|       stateObj, | ||||
|       this.hass.entities, | ||||
|       this.hass.devices, | ||||
|       this.hass.areas, | ||||
|       this.hass.floors | ||||
|     ); | ||||
|  | ||||
|     if (context.device) options.add("device"); | ||||
|     if (context.area) options.add("area"); | ||||
|     if (context.floor) options.add("floor"); | ||||
|     return options; | ||||
|   }); | ||||
|  | ||||
|   private _getOptions = memoizeOne((entityId?: string) => { | ||||
|     if (!entityId) { | ||||
|       return []; | ||||
|     } | ||||
|  | ||||
|     const options = this._validOptions(entityId); | ||||
|  | ||||
|     const items = ( | ||||
|       ["entity", "device", "area", "floor"] as const | ||||
|     ).map<EntityNameOption>((name) => { | ||||
|       const stateObj = this.hass.states[entityId]; | ||||
|       const isValid = options.has(name); | ||||
|       const primary = this.hass.localize( | ||||
|         `ui.components.entity.entity-name-picker.types.${name}` | ||||
|       ); | ||||
|       const secondary = | ||||
|         stateObj && isValid | ||||
|           ? this.hass.formatEntityName(stateObj, { type: name }) | ||||
|           : this.hass.localize( | ||||
|               `ui.components.entity.entity-name-picker.types.${name}_missing` as LocalizeKeys | ||||
|             ) || "-"; | ||||
|  | ||||
|       return { | ||||
|         primary, | ||||
|         secondary, | ||||
|         value: name, | ||||
|       }; | ||||
|     }); | ||||
|  | ||||
|     return items; | ||||
|   }); | ||||
|  | ||||
|   private _formatItem = (item: EntityNameItem) => { | ||||
|     if (item.type === "text") { | ||||
|       return `"${item.text}"`; | ||||
|     } | ||||
|     if (KNOWN_TYPES.has(item.type)) { | ||||
|       return this.hass.localize( | ||||
|         `ui.components.entity.entity-name-picker.types.${item.type as EntityNameType}` | ||||
|       ); | ||||
|     } | ||||
|     return item.type; | ||||
|   }; | ||||
|  | ||||
|   protected render() { | ||||
|     const value = this._value; | ||||
|     const options = this._getOptions(this.entityId); | ||||
|     const validOptions = this._validOptions(this.entityId); | ||||
|  | ||||
|     return html` | ||||
|       ${this.label ? html`<label>${this.label}</label>` : nothing} | ||||
|       <div class="container"> | ||||
|         <ha-sortable | ||||
|           no-style | ||||
|           @item-moved=${this._moveItem} | ||||
|           .disabled=${this.disabled} | ||||
|           handle-selector="button.primary.action" | ||||
|           filter=".add" | ||||
|         > | ||||
|           <ha-chip-set> | ||||
|             ${repeat( | ||||
|               this._value, | ||||
|               (item) => item, | ||||
|               (item: EntityNameItem, idx) => { | ||||
|                 const label = this._formatItem(item); | ||||
|                 const isValid = | ||||
|                   item.type === "text" || validOptions.has(item.type); | ||||
|                 return html` | ||||
|                   <ha-input-chip | ||||
|                     data-idx=${idx} | ||||
|                     @remove=${this._removeItem} | ||||
|                     @click=${this._editItem} | ||||
|                     .label=${label} | ||||
|                     .selected=${!this.disabled} | ||||
|                     .disabled=${this.disabled} | ||||
|                     class=${!isValid ? "invalid" : ""} | ||||
|                   > | ||||
|                     <ha-svg-icon slot="icon" .path=${mdiDrag}></ha-svg-icon> | ||||
|                     <span>${label}</span> | ||||
|                   </ha-input-chip> | ||||
|                 `; | ||||
|               } | ||||
|             )} | ||||
|             ${this.disabled | ||||
|               ? nothing | ||||
|               : html` | ||||
|                   <ha-assist-chip | ||||
|                     @click=${this._addItem} | ||||
|                     .disabled=${this.disabled} | ||||
|                     label=${this.hass.localize( | ||||
|                       "ui.components.entity.entity-name-picker.add" | ||||
|                     )} | ||||
|                     class="add" | ||||
|                   > | ||||
|                     <ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon> | ||||
|                   </ha-assist-chip> | ||||
|                 `} | ||||
|           </ha-chip-set> | ||||
|         </ha-sortable> | ||||
|  | ||||
|         <mwc-menu-surface | ||||
|           .open=${this._opened} | ||||
|           @closed=${this._onClosed} | ||||
|           @opened=${this._onOpened} | ||||
|           @input=${stopPropagation} | ||||
|           .anchor=${this._container} | ||||
|         > | ||||
|           <ha-combo-box | ||||
|             .hass=${this.hass} | ||||
|             .value=${""} | ||||
|             .autofocus=${this.autofocus} | ||||
|             .disabled=${this.disabled || !this.entityId} | ||||
|             .required=${this.required && !value.length} | ||||
|             .helper=${this.helper} | ||||
|             .items=${options} | ||||
|             allow-custom-value | ||||
|             item-id-path="value" | ||||
|             item-value-path="value" | ||||
|             item-label-path="primary" | ||||
|             .renderer=${rowRenderer} | ||||
|             @opened-changed=${this._openedChanged} | ||||
|             @value-changed=${this._comboBoxValueChanged} | ||||
|             @filter-changed=${this._filterChanged} | ||||
|           > | ||||
|           </ha-combo-box> | ||||
|         </mwc-menu-surface> | ||||
|       </div> | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   private _onClosed(ev) { | ||||
|     ev.stopPropagation(); | ||||
|     this._opened = false; | ||||
|     this._editIndex = undefined; | ||||
|   } | ||||
|  | ||||
|   private async _onOpened(ev) { | ||||
|     if (!this._opened) { | ||||
|       return; | ||||
|     } | ||||
|     ev.stopPropagation(); | ||||
|     this._opened = true; | ||||
|     await this._comboBox?.focus(); | ||||
|     await this._comboBox?.open(); | ||||
|   } | ||||
|  | ||||
|   private async _addItem(ev) { | ||||
|     ev.stopPropagation(); | ||||
|     this._opened = true; | ||||
|   } | ||||
|  | ||||
|   private async _editItem(ev) { | ||||
|     ev.stopPropagation(); | ||||
|     const idx = parseInt(ev.currentTarget.dataset.idx, 10); | ||||
|     this._editIndex = idx; | ||||
|     this._opened = true; | ||||
|   } | ||||
|  | ||||
|   private get _value(): EntityNameItem[] { | ||||
|     return this._toItems(this.value); | ||||
|   } | ||||
|  | ||||
|   private _toItems = memoizeOne((value?: typeof this.value) => { | ||||
|     if (typeof value === "string") { | ||||
|       return [{ type: "text", text: value } as const]; | ||||
|     } | ||||
|     return value ? ensureArray(value) : []; | ||||
|   }); | ||||
|  | ||||
|   private _toValue = memoizeOne( | ||||
|     (items: EntityNameItem[]): typeof this.value => { | ||||
|       if (items.length === 0) { | ||||
|         return []; | ||||
|       } | ||||
|       if (items.length === 1) { | ||||
|         const item = items[0]; | ||||
|         return item.type === "text" ? item.text : item; | ||||
|       } | ||||
|       return items; | ||||
|     } | ||||
|   ); | ||||
|  | ||||
|   private _openedChanged(ev: ValueChangedEvent<boolean>) { | ||||
|     const open = ev.detail.value; | ||||
|     if (open) { | ||||
|       const options = this._comboBox.items || []; | ||||
|  | ||||
|       const initialItem = | ||||
|         this._editIndex != null ? this._value[this._editIndex] : undefined; | ||||
|  | ||||
|       const initialValue = initialItem | ||||
|         ? initialItem.type === "text" | ||||
|           ? initialItem.text | ||||
|           : initialItem.type | ||||
|         : ""; | ||||
|  | ||||
|       const filteredItems = this._filterSelectedOptions(options, initialValue); | ||||
|  | ||||
|       this._comboBox.filteredItems = filteredItems; | ||||
|       this._comboBox.setInputValue(initialValue); | ||||
|     } else { | ||||
|       this._opened = false; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private _filterSelectedOptions = ( | ||||
|     options: EntityNameOption[], | ||||
|     current?: string | ||||
|   ) => { | ||||
|     const value = this._value; | ||||
|  | ||||
|     const types = value.map((item) => item.type) as string[]; | ||||
|  | ||||
|     const filteredOptions = options.filter( | ||||
|       (option) => | ||||
|         !UNIQUE_TYPES.has(option.value) || | ||||
|         !types.includes(option.value) || | ||||
|         option.value === current | ||||
|     ); | ||||
|     return filteredOptions; | ||||
|   }; | ||||
|  | ||||
|   private _filterChanged(ev: ValueChangedEvent<string>) { | ||||
|     const input = ev.detail.value; | ||||
|     const filter = input?.toLowerCase() || ""; | ||||
|     const options = this._comboBox.items || []; | ||||
|  | ||||
|     const currentItem = | ||||
|       this._editIndex != null ? this._value[this._editIndex] : undefined; | ||||
|  | ||||
|     const currentValue = currentItem | ||||
|       ? currentItem.type === "text" | ||||
|         ? currentItem.text | ||||
|         : currentItem.type | ||||
|       : ""; | ||||
|  | ||||
|     this._comboBox.filteredItems = this._filterSelectedOptions( | ||||
|       options, | ||||
|       currentValue | ||||
|     ); | ||||
|  | ||||
|     if (!filter) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     const fuseOptions: IFuseOptions<EntityNameOption> = { | ||||
|       keys: ["primary", "secondary", "value"], | ||||
|       isCaseSensitive: false, | ||||
|       minMatchCharLength: Math.min(filter.length, 2), | ||||
|       threshold: 0.2, | ||||
|       ignoreDiacritics: true, | ||||
|     }; | ||||
|  | ||||
|     const fuse = new Fuse(this._comboBox.filteredItems, fuseOptions); | ||||
|     const filteredItems = fuse.search(filter).map((result) => result.item); | ||||
|  | ||||
|     this._comboBox.filteredItems = filteredItems; | ||||
|   } | ||||
|  | ||||
|   private async _moveItem(ev: CustomEvent) { | ||||
|     ev.stopPropagation(); | ||||
|     const { oldIndex, newIndex } = ev.detail; | ||||
|     const value = this._value; | ||||
|     const newValue = value.concat(); | ||||
|     const element = newValue.splice(oldIndex, 1)[0]; | ||||
|     newValue.splice(newIndex, 0, element); | ||||
|     this._setValue(newValue); | ||||
|     await this.updateComplete; | ||||
|     this._filterChanged({ detail: { value: "" } } as ValueChangedEvent<string>); | ||||
|   } | ||||
|  | ||||
|   private async _removeItem(ev) { | ||||
|     ev.stopPropagation(); | ||||
|     const value = [...this._value]; | ||||
|     const idx = parseInt(ev.target.dataset.idx, 10); | ||||
|     value.splice(idx, 1); | ||||
|     this._setValue(value); | ||||
|     await this.updateComplete; | ||||
|     this._filterChanged({ detail: { value: "" } } as ValueChangedEvent<string>); | ||||
|   } | ||||
|  | ||||
|   private _comboBoxValueChanged(ev: ValueChangedEvent<string>): void { | ||||
|     ev.stopPropagation(); | ||||
|     const value = ev.detail.value; | ||||
|  | ||||
|     if (this.disabled || value === "") { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     const item: EntityNameItem = KNOWN_TYPES.has(value as any) | ||||
|       ? { type: value as EntityNameType } | ||||
|       : { type: "text", text: value }; | ||||
|  | ||||
|     const newValue = [...this._value]; | ||||
|  | ||||
|     if (this._editIndex != null) { | ||||
|       newValue[this._editIndex] = item; | ||||
|     } else { | ||||
|       newValue.push(item); | ||||
|     } | ||||
|  | ||||
|     this._setValue(newValue); | ||||
|   } | ||||
|  | ||||
|   private _setValue(value: EntityNameItem[]) { | ||||
|     const newValue = this._toValue(value); | ||||
|     this.value = newValue; | ||||
|     fireEvent(this, "value-changed", { | ||||
|       value: newValue, | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   static styles = css` | ||||
|     :host { | ||||
|       position: relative; | ||||
|       width: 100%; | ||||
|     } | ||||
|  | ||||
|     .container { | ||||
|       position: relative; | ||||
|       background-color: var(--mdc-text-field-fill-color, whitesmoke); | ||||
|       border-radius: var(--ha-border-radius-sm); | ||||
|       border-end-end-radius: var(--ha-border-radius-square); | ||||
|       border-end-start-radius: var(--ha-border-radius-square); | ||||
|     } | ||||
|     .container:after { | ||||
|       display: block; | ||||
|       content: ""; | ||||
|       position: absolute; | ||||
|       pointer-events: none; | ||||
|       bottom: 0; | ||||
|       left: 0; | ||||
|       right: 0; | ||||
|       height: 1px; | ||||
|       width: 100%; | ||||
|       background-color: var( | ||||
|         --mdc-text-field-idle-line-color, | ||||
|         rgba(0, 0, 0, 0.42) | ||||
|       ); | ||||
|       transform: | ||||
|         height 180ms ease-in-out, | ||||
|         background-color 180ms ease-in-out; | ||||
|     } | ||||
|     :host([disabled]) .container:after { | ||||
|       background-color: var( | ||||
|         --mdc-text-field-disabled-line-color, | ||||
|         rgba(0, 0, 0, 0.42) | ||||
|       ); | ||||
|     } | ||||
|     .container:focus-within:after { | ||||
|       height: 2px; | ||||
|       background-color: var(--mdc-theme-primary); | ||||
|     } | ||||
|  | ||||
|     label { | ||||
|       display: block; | ||||
|       margin: 0 0 var(--ha-space-2); | ||||
|     } | ||||
|  | ||||
|     .add { | ||||
|       order: 1; | ||||
|     } | ||||
|  | ||||
|     mwc-menu-surface { | ||||
|       --mdc-menu-min-width: 100%; | ||||
|     } | ||||
|  | ||||
|     ha-chip-set { | ||||
|       padding: var(--ha-space-2) var(--ha-space-2); | ||||
|     } | ||||
|  | ||||
|     .invalid { | ||||
|       text-decoration: line-through; | ||||
|     } | ||||
|  | ||||
|     .sortable-fallback { | ||||
|       display: none; | ||||
|       opacity: 0; | ||||
|     } | ||||
|  | ||||
|     .sortable-ghost { | ||||
|       opacity: 0.4; | ||||
|     } | ||||
|  | ||||
|     .sortable-drag { | ||||
|       cursor: grabbing; | ||||
|     } | ||||
|   `; | ||||
| } | ||||
|  | ||||
| declare global { | ||||
|   interface HTMLElementTagNameMap { | ||||
|     "ha-entity-name-picker": HaEntityNamePicker; | ||||
|   } | ||||
| } | ||||
| @@ -6,7 +6,6 @@ import { customElement, property, query } from "lit/decorators"; | ||||
| import memoizeOne from "memoize-one"; | ||||
| import { fireEvent } from "../../common/dom/fire_event"; | ||||
| import { computeDomain } from "../../common/entity/compute_domain"; | ||||
| import { computeEntityNameList } from "../../common/entity/compute_entity_name_display"; | ||||
| import { computeStateName } from "../../common/entity/compute_state_name"; | ||||
| import { isValidEntityId } from "../../common/entity/valid_entity_id"; | ||||
| import { computeRTL } from "../../common/util/compute_rtl"; | ||||
| @@ -145,14 +144,9 @@ export class HaEntityPicker extends LitElement { | ||||
|       `; | ||||
|     } | ||||
|  | ||||
|     const [entityName, deviceName, areaName] = computeEntityNameList( | ||||
|       stateObj, | ||||
|       [{ type: "entity" }, { type: "device" }, { type: "area" }], | ||||
|       this.hass.entities, | ||||
|       this.hass.devices, | ||||
|       this.hass.areas, | ||||
|       this.hass.floors | ||||
|     ); | ||||
|     const entityName = this.hass.formatEntityName(stateObj, "entity"); | ||||
|     const deviceName = this.hass.formatEntityName(stateObj, "device"); | ||||
|     const areaName = this.hass.formatEntityName(stateObj, "area"); | ||||
|  | ||||
|     const isRTL = computeRTL(this.hass); | ||||
|  | ||||
| @@ -306,24 +300,21 @@ export class HaEntityPicker extends LitElement { | ||||
|         ); | ||||
|       } | ||||
|  | ||||
|       const isRTL = computeRTL(hass); | ||||
|       const isRTL = computeRTL(this.hass); | ||||
|  | ||||
|       items = entityIds.map<EntityComboBoxItem>((entityId) => { | ||||
|         const stateObj = hass.states[entityId]; | ||||
|         const stateObj = hass!.states[entityId]; | ||||
|  | ||||
|         const friendlyName = computeStateName(stateObj); // Keep this for search | ||||
|         const entityName = this.hass.formatEntityName(stateObj, "entity"); | ||||
|         const deviceName = this.hass.formatEntityName(stateObj, "device"); | ||||
|         const areaName = this.hass.formatEntityName(stateObj, "area"); | ||||
|  | ||||
|         const [entityName, deviceName, areaName] = computeEntityNameList( | ||||
|           stateObj, | ||||
|           [{ type: "entity" }, { type: "device" }, { type: "area" }], | ||||
|           hass.entities, | ||||
|           hass.devices, | ||||
|           hass.areas, | ||||
|           hass.floors | ||||
|         const domainName = domainToName( | ||||
|           this.hass.localize, | ||||
|           computeDomain(entityId) | ||||
|         ); | ||||
|  | ||||
|         const domainName = domainToName(hass.localize, computeDomain(entityId)); | ||||
|  | ||||
|         const primary = entityName || deviceName || entityId; | ||||
|         const secondary = [areaName, entityName ? deviceName : undefined] | ||||
|           .filter(Boolean) | ||||
|   | ||||
| @@ -112,7 +112,7 @@ export class HaEntityToggle extends LitElement { | ||||
|     if (!this.hass || !this.stateObj) { | ||||
|       return; | ||||
|     } | ||||
|     forwardHaptic(this, "light"); | ||||
|     forwardHaptic("light"); | ||||
|     const stateDomain = computeStateDomain(this.stateObj); | ||||
|     let serviceDomain; | ||||
|     let service; | ||||
|   | ||||
| @@ -6,7 +6,6 @@ import { customElement, property, query } from "lit/decorators"; | ||||
| import memoizeOne from "memoize-one"; | ||||
| import { ensureArray } from "../../common/array/ensure-array"; | ||||
| import { fireEvent } from "../../common/dom/fire_event"; | ||||
| import { computeEntityNameList } from "../../common/entity/compute_entity_name_display"; | ||||
| import { computeStateName } from "../../common/entity/compute_state_name"; | ||||
| import { computeRTL } from "../../common/util/compute_rtl"; | ||||
| import { domainToName } from "../../data/integration"; | ||||
| @@ -200,7 +199,7 @@ export class HaStatisticPicker extends LitElement { | ||||
|         }); | ||||
|       } | ||||
|  | ||||
|       const isRTL = computeRTL(hass); | ||||
|       const isRTL = computeRTL(this.hass); | ||||
|  | ||||
|       const output: StatisticComboBoxItem[] = []; | ||||
|  | ||||
| @@ -257,15 +256,9 @@ export class HaStatisticPicker extends LitElement { | ||||
|         const id = meta.statistic_id; | ||||
|  | ||||
|         const friendlyName = computeStateName(stateObj); // Keep this for search | ||||
|  | ||||
|         const [entityName, deviceName, areaName] = computeEntityNameList( | ||||
|           stateObj, | ||||
|           [{ type: "entity" }, { type: "device" }, { type: "area" }], | ||||
|           hass.entities, | ||||
|           hass.devices, | ||||
|           hass.areas, | ||||
|           hass.floors | ||||
|         ); | ||||
|         const entityName = hass.formatEntityName(stateObj, "entity"); | ||||
|         const deviceName = hass.formatEntityName(stateObj, "device"); | ||||
|         const areaName = hass.formatEntityName(stateObj, "area"); | ||||
|  | ||||
|         const primary = entityName || deviceName || id; | ||||
|         const secondary = [areaName, entityName ? deviceName : undefined] | ||||
| @@ -338,14 +331,9 @@ export class HaStatisticPicker extends LitElement { | ||||
|     const stateObj = this.hass.states[statisticId]; | ||||
|  | ||||
|     if (stateObj) { | ||||
|       const [entityName, deviceName, areaName] = computeEntityNameList( | ||||
|         stateObj, | ||||
|         [{ type: "entity" }, { type: "device" }, { type: "area" }], | ||||
|         this.hass.entities, | ||||
|         this.hass.devices, | ||||
|         this.hass.areas, | ||||
|         this.hass.floors | ||||
|       ); | ||||
|       const entityName = this.hass.formatEntityName(stateObj, "entity"); | ||||
|       const deviceName = this.hass.formatEntityName(stateObj, "device"); | ||||
|       const areaName = this.hass.formatEntityName(stateObj, "area"); | ||||
|  | ||||
|       const isRTL = computeRTL(this.hass); | ||||
|  | ||||
|   | ||||
| @@ -236,7 +236,7 @@ export class StateBadge extends LitElement { | ||||
|           border-radius: var(--state-badge-with-media-image-border-radius, 8%); | ||||
|         } | ||||
|         :host(.has-no-radius) { | ||||
|           border-radius: var(--ha-border-radius-square); | ||||
|           border-radius: 0; | ||||
|         } | ||||
|         :host(:focus) { | ||||
|           outline: none; | ||||
|   | ||||
| @@ -99,7 +99,7 @@ class HaAlert extends LitElement { | ||||
|       opacity: 0.12; | ||||
|       pointer-events: none; | ||||
|       content: ""; | ||||
|       border-radius: var(--ha-border-radius-sm); | ||||
|       border-radius: 4px; | ||||
|     } | ||||
|     .icon.no-title { | ||||
|       align-self: center; | ||||
|   | ||||
| @@ -1,24 +1,24 @@ | ||||
| import { mdiAlertCircle, mdiMicrophone, mdiSend } from "@mdi/js"; | ||||
| import type { PropertyValues, TemplateResult } from "lit"; | ||||
| import { css, html, LitElement, nothing } from "lit"; | ||||
| import { css, LitElement, html, nothing } from "lit"; | ||||
| import { mdiAlertCircle, mdiMicrophone, mdiSend } from "@mdi/js"; | ||||
| import { customElement, property, query, state } from "lit/decorators"; | ||||
| import { classMap } from "lit/directives/class-map"; | ||||
| import { supportsFeature } from "../common/entity/supports-feature"; | ||||
| import type { HomeAssistant } from "../types"; | ||||
| import { | ||||
|   type PipelineRunEvent, | ||||
|   runAssistPipeline, | ||||
|   type AssistPipeline, | ||||
|   type ConversationChatLogAssistantDelta, | ||||
|   type ConversationChatLogToolResultDelta, | ||||
|   type PipelineRunEvent, | ||||
| } from "../data/assist_pipeline"; | ||||
| import { supportsFeature } from "../common/entity/supports-feature"; | ||||
| import { ConversationEntityFeature } from "../data/conversation"; | ||||
| import { showAlertDialog } from "../dialogs/generic/show-dialog-box"; | ||||
| import type { HomeAssistant } from "../types"; | ||||
| import { AudioRecorder } from "../util/audio-recorder"; | ||||
| import { documentationUrl } from "../util/documentation-url"; | ||||
| import "./ha-alert"; | ||||
| import "./ha-textfield"; | ||||
| import type { HaTextField } from "./ha-textfield"; | ||||
| import { documentationUrl } from "../util/documentation-url"; | ||||
| import { showAlertDialog } from "../dialogs/generic/show-dialog-box"; | ||||
|  | ||||
| interface AssistMessage { | ||||
|   who: string; | ||||
| @@ -591,7 +591,7 @@ export class HaAssistChat extends LitElement { | ||||
|       clear: both; | ||||
|       margin: 8px 0; | ||||
|       padding: 8px; | ||||
|       border-radius: var(--ha-border-radius-xl); | ||||
|       border-radius: 15px; | ||||
|     } | ||||
|     .message:last-child { | ||||
|       margin-bottom: 0; | ||||
| @@ -659,7 +659,7 @@ export class HaAssistChat extends LitElement { | ||||
|     .double-bounce2 { | ||||
|       width: 48px; | ||||
|       height: 48px; | ||||
|       border-radius: var(--ha-border-radius-circle); | ||||
|       border-radius: 50%; | ||||
|       background-color: var(--primary-color); | ||||
|       opacity: 0.2; | ||||
|       position: absolute; | ||||
|   | ||||
| @@ -54,7 +54,7 @@ export class HaBadge extends LitElement { | ||||
|       flex-direction: row; | ||||
|       align-items: center; | ||||
|       justify-content: center; | ||||
|       gap: var(--ha-space-2); | ||||
|       gap: 8px; | ||||
|       height: var(--ha-badge-size, 36px); | ||||
|       min-width: var(--ha-badge-size, 36px); | ||||
|       padding: 0px 12px; | ||||
| @@ -122,7 +122,7 @@ export class HaBadge extends LitElement { | ||||
|     ::slotted(img[slot="icon"]) { | ||||
|       width: 30px; | ||||
|       height: 30px; | ||||
|       border-radius: var(--ha-border-radius-circle); | ||||
|       border-radius: 50%; | ||||
|       object-fit: cover; | ||||
|       overflow: hidden; | ||||
|       margin-left: -10px; | ||||
|   | ||||
| @@ -46,7 +46,7 @@ export class HaBar extends LitElement { | ||||
|       fill: var(--ha-bar-primary-color, var(--primary-color)); | ||||
|     } | ||||
|     svg { | ||||
|       border-radius: var(--ha-bar-border-radius, var(--ha-border-radius-sm)); | ||||
|       border-radius: var(--ha-bar-border-radius, 4px); | ||||
|       height: 12px; | ||||
|       width: 100%; | ||||
|     } | ||||
|   | ||||
| @@ -337,9 +337,7 @@ export class HaBaseTimeInput extends LitElement { | ||||
|     .time-input-wrap { | ||||
|       display: flex; | ||||
|       flex: var(--time-input-flex, unset); | ||||
|       border-radius: var(--mdc-shape-small, var(--ha-border-radius-sm)) | ||||
|         var(--mdc-shape-small, var(--ha-border-radius-sm)) | ||||
|         var(--ha-border-radius-square) var(--ha-border-radius-square); | ||||
|       border-radius: var(--mdc-shape-small, 4px) var(--mdc-shape-small, 4px) 0 0; | ||||
|       overflow: hidden; | ||||
|       position: relative; | ||||
|       direction: ltr; | ||||
|   | ||||
| @@ -223,12 +223,6 @@ export class HaButton extends Button { | ||||
|         .button.has-end { | ||||
|           padding-inline-end: 8px; | ||||
|         } | ||||
|  | ||||
|         .label { | ||||
|           overflow: hidden; | ||||
|           text-overflow: ellipsis; | ||||
|           padding: var(--ha-space-1) 0; | ||||
|         } | ||||
|       `, | ||||
|     ]; | ||||
|   } | ||||
|   | ||||
| @@ -17,7 +17,7 @@ export class HaCard extends LitElement { | ||||
|       backdrop-filter: var(--ha-card-backdrop-filter, none); | ||||
|       box-shadow: var(--ha-card-box-shadow, none); | ||||
|       box-sizing: border-box; | ||||
|       border-radius: var(--ha-card-border-radius, var(--ha-border-radius-lg)); | ||||
|       border-radius: var(--ha-card-border-radius, 12px); | ||||
|       border-width: var(--ha-card-border-width, 1px); | ||||
|       border-style: solid; | ||||
|       border-color: var(--ha-card-border-color, var(--divider-color, #e0e0e0)); | ||||
|   | ||||
| @@ -45,7 +45,7 @@ export class HaCodeEditorCompletionItems extends LitElement { | ||||
|       margin: 0 3px; | ||||
|       padding: 3px; | ||||
|       background-color: var(--markdown-code-background-color, none); | ||||
|       border-radius: var(--ha-border-radius-sm); | ||||
|       border-radius: var(--ha-border-radius-sm, 4px); | ||||
|       line-height: var(--ha-line-height-condensed); | ||||
|     } | ||||
|   `; | ||||
|   | ||||
| @@ -5,20 +5,19 @@ import type { | ||||
|   CompletionResult, | ||||
|   CompletionSource, | ||||
| } from "@codemirror/autocomplete"; | ||||
| import { redo, redoDepth, undo, undoDepth } from "@codemirror/commands"; | ||||
| import { undo, undoDepth, redo, redoDepth } from "@codemirror/commands"; | ||||
| import type { Extension, TransactionSpec } from "@codemirror/state"; | ||||
| import type { EditorView, KeyBinding, ViewUpdate } from "@codemirror/view"; | ||||
| import { placeholder } from "@codemirror/view"; | ||||
| import { | ||||
|   mdiArrowCollapse, | ||||
|   mdiArrowExpand, | ||||
|   mdiArrowCollapse, | ||||
|   mdiContentCopy, | ||||
|   mdiRedo, | ||||
|   mdiUndo, | ||||
|   mdiRedo, | ||||
| } from "@mdi/js"; | ||||
| import type { HassEntities } from "home-assistant-js-websocket"; | ||||
| import type { PropertyValues } from "lit"; | ||||
| import { css, html, ReactiveElement, render } from "lit"; | ||||
| import { css, ReactiveElement, html, render } from "lit"; | ||||
| import { customElement, property, state } from "lit/decorators"; | ||||
| import memoizeOne from "memoize-one"; | ||||
| import { fireEvent } from "../common/dom/fire_event"; | ||||
| @@ -82,8 +81,6 @@ export class HaCodeEditor extends ReactiveElement { | ||||
|   @property({ type: Boolean, attribute: "has-toolbar" }) | ||||
|   public hasToolbar = true; | ||||
|  | ||||
|   @property({ type: String }) public placeholder?: string; | ||||
|  | ||||
|   @state() private _value = ""; | ||||
|  | ||||
|   @state() private _isFullscreen = false; | ||||
| @@ -267,7 +264,6 @@ export class HaCodeEditor extends ReactiveElement { | ||||
|       this._loadedCodeMirror.foldingCompartment.of( | ||||
|         this._getFoldingExtensions() | ||||
|       ), | ||||
|       ...(this.placeholder ? [placeholder(this.placeholder)] : []), | ||||
|     ]; | ||||
|  | ||||
|     if (!this.readOnly) { | ||||
| @@ -778,7 +774,7 @@ export class HaCodeEditor extends ReactiveElement { | ||||
|       right: 8px !important; | ||||
|       bottom: 8px !important; | ||||
|       z-index: 6; | ||||
|       border-radius: var(--ha-border-radius-lg) !important; | ||||
|       border-radius: 12px !important; | ||||
|       box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3) !important; | ||||
|       overflow: hidden !important; | ||||
|       background-color: var( | ||||
| @@ -801,7 +797,7 @@ export class HaCodeEditor extends ReactiveElement { | ||||
|     :host(.fullscreen) .cm-editor { | ||||
|       height: 100% !important; | ||||
|       max-height: 100% !important; | ||||
|       border-radius: var(--ha-border-radius-square) !important; | ||||
|       border-radius: 0 !important; | ||||
|     } | ||||
|  | ||||
|     :host(:not(.hasToolbar)) .code-editor-toolbar { | ||||
|   | ||||
| @@ -155,7 +155,7 @@ export class HaColorPicker extends LitElement { | ||||
|       display: block; | ||||
|       background-color: var(--circle-color, var(--divider-color)); | ||||
|       border: 1px solid var(--outline-color); | ||||
|       border-radius: var(--ha-border-radius-pill); | ||||
|       border-radius: 10px; | ||||
|       width: 20px; | ||||
|       height: 20px; | ||||
|       box-sizing: border-box; | ||||
|   | ||||
| @@ -32,7 +32,7 @@ export class HaControlButton extends LitElement { | ||||
|       --control-button-icon-color: var(--primary-text-color); | ||||
|       --control-button-background-color: var(--disabled-color); | ||||
|       --control-button-background-opacity: 0.2; | ||||
|       --control-button-border-radius: var(--ha-border-radius-md); | ||||
|       --control-button-border-radius: 10px; | ||||
|       --control-button-padding: 8px; | ||||
|       --mdc-icon-size: 20px; | ||||
|       --ha-ripple-color: var(--secondary-text-color); | ||||
|   | ||||
| @@ -187,7 +187,7 @@ export class HaControlNumberButton extends LitElement { | ||||
|       --control-number-buttons-focus-color: var(--secondary-text-color); | ||||
|       --control-number-buttons-background-color: var(--disabled-color); | ||||
|       --control-number-buttons-background-opacity: 0.2; | ||||
|       --control-number-buttons-border-radius: var(--ha-border-radius-md); | ||||
|       --control-number-buttons-border-radius: 10px; | ||||
|       --mdc-icon-size: 16px; | ||||
|       height: var(--feature-height); | ||||
|       width: 100%; | ||||
|   | ||||
| @@ -174,7 +174,7 @@ export class HaControlSelectMenu extends SelectBase { | ||||
|         --control-select-menu-text-color: var(--primary-text-color); | ||||
|         --control-select-menu-background-color: var(--disabled-color); | ||||
|         --control-select-menu-background-opacity: 0.2; | ||||
|         --control-select-menu-border-radius: var(--ha-border-radius-lg); | ||||
|         --control-select-menu-border-radius: 14px; | ||||
|         --control-select-menu-height: 48px; | ||||
|         --control-select-menu-padding: 6px 10px; | ||||
|         --mdc-icon-size: 20px; | ||||
|   | ||||
| @@ -380,7 +380,7 @@ export class HaControlSlider extends LitElement { | ||||
|       --control-slider-background: var(--disabled-color); | ||||
|       --control-slider-background-opacity: 0.2; | ||||
|       --control-slider-thickness: 40px; | ||||
|       --control-slider-border-radius: var(--ha-border-radius-md); | ||||
|       --control-slider-border-radius: 10px; | ||||
|       --control-slider-tooltip-font-size: var(--ha-font-size-m); | ||||
|       height: var(--control-slider-thickness); | ||||
|       width: 100%; | ||||
| @@ -403,7 +403,7 @@ export class HaControlSlider extends LitElement { | ||||
|       background-color: var(--clear-background-color); | ||||
|       color: var(--primary-text-color); | ||||
|       font-size: var(--control-slider-tooltip-font-size); | ||||
|       border-radius: var(--ha-border-radius-lg); | ||||
|       border-radius: 0.8em; | ||||
|       padding: 0.2em 0.4em; | ||||
|       opacity: 0; | ||||
|       white-space: nowrap; | ||||
|   | ||||
| @@ -157,7 +157,7 @@ export class HaControlSwitch extends LitElement { | ||||
|       --control-switch-off-color: var(--disabled-color); | ||||
|       --control-switch-background-opacity: 0.2; | ||||
|       --control-switch-thickness: 40px; | ||||
|       --control-switch-border-radius: var(--ha-border-radius-lg); | ||||
|       --control-switch-border-radius: 12px; | ||||
|       --control-switch-padding: 4px; | ||||
|       --mdc-icon-size: 20px; | ||||
|       height: var(--control-switch-thickness); | ||||
|   | ||||
| @@ -76,7 +76,7 @@ export class HaCopyTextfield extends LitElement { | ||||
|     .container { | ||||
|       display: flex; | ||||
|       align-items: center; | ||||
|       gap: var(--ha-space-2); | ||||
|       gap: 8px; | ||||
|       margin-top: 8px; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -100,7 +100,7 @@ const CURRENCIES = [ | ||||
|   "MMK", | ||||
|   "MNT", | ||||
|   "MOP", | ||||
|   "MRU", | ||||
|   "MRO", | ||||
|   "MUR", | ||||
|   "MVR", | ||||
|   "MWK", | ||||
|   | ||||
| @@ -343,7 +343,7 @@ export class HaDateRangePicker extends LitElement { | ||||
|     .date-range-inputs { | ||||
|       display: flex; | ||||
|       align-items: center; | ||||
|       gap: var(--ha-space-2); | ||||
|       gap: 8px; | ||||
|     } | ||||
|  | ||||
|     .date-range-ranges { | ||||
|   | ||||
| @@ -1,52 +0,0 @@ | ||||
| import { css, html, LitElement } from "lit"; | ||||
| import { customElement } from "lit/decorators"; | ||||
|  | ||||
| /** | ||||
|  * Home Assistant dialog footer component | ||||
|  * | ||||
|  * @element ha-dialog-footer | ||||
|  * @extends {LitElement} | ||||
|  * | ||||
|  * @summary | ||||
|  * A simple footer container for dialog actions, | ||||
|  * typically used as the `footer` slot in `ha-wa-dialog`. | ||||
|  * | ||||
|  * @slot primaryAction - Primary action button(s), aligned to the end. | ||||
|  * @slot secondaryAction - Secondary action button(s), placed before the primary action. | ||||
|  * | ||||
|  * @remarks | ||||
|  * **Button Styling Guidance:** | ||||
|  * - `primaryAction` slot: Use `variant="accent"` | ||||
|  * - `secondaryAction` slot: Use `variant="plain"` | ||||
|  */ | ||||
| @customElement("ha-dialog-footer") | ||||
| export class HaDialogFooter extends LitElement { | ||||
|   protected render() { | ||||
|     return html` | ||||
|       <footer> | ||||
|         <slot name="secondaryAction"></slot> | ||||
|         <slot name="primaryAction"></slot> | ||||
|       </footer> | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   static get styles() { | ||||
|     return [ | ||||
|       css` | ||||
|         footer { | ||||
|           display: flex; | ||||
|           gap: var(--ha-space-3); | ||||
|           justify-content: flex-end; | ||||
|           align-items: center; | ||||
|           width: 100%; | ||||
|         } | ||||
|       `, | ||||
|     ]; | ||||
|   } | ||||
| } | ||||
|  | ||||
| declare global { | ||||
|   interface HTMLElementTagNameMap { | ||||
|     "ha-dialog-footer": HaDialogFooter; | ||||
|   } | ||||
| } | ||||
| @@ -1,20 +1,9 @@ | ||||
| import { css, html, LitElement } from "lit"; | ||||
| import { customElement, property } from "lit/decorators"; | ||||
| import { customElement } from "lit/decorators"; | ||||
|  | ||||
| @customElement("ha-dialog-header") | ||||
| export class HaDialogHeader extends LitElement { | ||||
|   @property({ type: String, attribute: "subtitle-position" }) | ||||
|   public subtitlePosition: "above" | "below" = "below"; | ||||
|  | ||||
|   protected render() { | ||||
|     const titleSlot = html`<div class="header-title"> | ||||
|       <slot name="title"></slot> | ||||
|     </div>`; | ||||
|  | ||||
|     const subtitleSlot = html`<div class="header-subtitle"> | ||||
|       <slot name="subtitle"></slot> | ||||
|     </div>`; | ||||
|  | ||||
|     return html` | ||||
|       <header class="header"> | ||||
|         <div class="header-bar"> | ||||
| @@ -22,9 +11,12 @@ export class HaDialogHeader extends LitElement { | ||||
|             <slot name="navigationIcon"></slot> | ||||
|           </section> | ||||
|           <section class="header-content"> | ||||
|             ${this.subtitlePosition === "above" | ||||
|               ? html`${subtitleSlot}${titleSlot}` | ||||
|               : html`${titleSlot}${subtitleSlot}`} | ||||
|             <div class="header-title"> | ||||
|               <slot name="title"></slot> | ||||
|             </div> | ||||
|             <div class="header-subtitle"> | ||||
|               <slot name="subtitle"></slot> | ||||
|             </div> | ||||
|           </section> | ||||
|           <section class="header-action-items"> | ||||
|             <slot name="actionItems"></slot> | ||||
| @@ -48,7 +40,7 @@ export class HaDialogHeader extends LitElement { | ||||
|         .header-bar { | ||||
|           display: flex; | ||||
|           flex-direction: row; | ||||
|           align-items: center; | ||||
|           align-items: flex-start; | ||||
|           padding: 4px; | ||||
|           box-sizing: border-box; | ||||
|         } | ||||
| @@ -61,17 +53,13 @@ export class HaDialogHeader extends LitElement { | ||||
|           white-space: nowrap; | ||||
|         } | ||||
|         .header-title { | ||||
|           height: var( | ||||
|             --ha-dialog-header-title-height, | ||||
|             calc(var(--ha-font-size-xl) + 4px) | ||||
|           ); | ||||
|           font-size: var(--ha-font-size-xl); | ||||
|           line-height: var(--ha-line-height-condensed); | ||||
|           font-weight: var(--ha-font-weight-medium); | ||||
|           font-weight: var(--ha-font-weight-normal); | ||||
|         } | ||||
|         .header-subtitle { | ||||
|           font-size: var(--ha-font-size-m); | ||||
|           line-height: var(--ha-line-height-normal); | ||||
|           line-height: 20px; | ||||
|           color: var(--secondary-text-color); | ||||
|         } | ||||
|         @media all and (min-width: 450px) and (min-height: 500px) { | ||||
|   | ||||
| @@ -123,10 +123,7 @@ export class HaDialog extends DialogBase { | ||||
|         margin-top: var(--dialog-surface-margin-top); | ||||
|         min-width: var(--mdc-dialog-min-width, auto); | ||||
|         min-height: var(--mdc-dialog-min-height, auto); | ||||
|         border-radius: var( | ||||
|           --ha-dialog-border-radius, | ||||
|           var(--ha-border-radius-3xl) | ||||
|         ); | ||||
|         border-radius: var(--ha-dialog-border-radius, 24px); | ||||
|         -webkit-backdrop-filter: var(--ha-dialog-surface-backdrop-filter, none); | ||||
|         backdrop-filter: var(--ha-dialog-surface-backdrop-filter, none); | ||||
|         background: var( | ||||
|   | ||||
| @@ -144,7 +144,7 @@ export class HaExpansionPanel extends LitElement { | ||||
|     .top { | ||||
|       display: flex; | ||||
|       align-items: center; | ||||
|       border-radius: var(--ha-card-border-radius, var(--ha-border-radius-lg)); | ||||
|       border-radius: var(--ha-card-border-radius, 12px); | ||||
|     } | ||||
|  | ||||
|     .top.expanded { | ||||
| @@ -161,7 +161,7 @@ export class HaExpansionPanel extends LitElement { | ||||
|       border-width: 1px; | ||||
|       border-style: solid; | ||||
|       border-color: var(--outline-color); | ||||
|       border-radius: var(--ha-card-border-radius, var(--ha-border-radius-lg)); | ||||
|       border-radius: var(--ha-card-border-radius, 12px); | ||||
|     } | ||||
|  | ||||
|     .summary-icon { | ||||
|   | ||||
| @@ -242,7 +242,7 @@ export class HaFileUpload extends LitElement { | ||||
|       align-items: center; | ||||
|       border: solid 1px | ||||
|         var(--mdc-text-field-idle-line-color, rgba(0, 0, 0, 0.42)); | ||||
|       border-radius: var(--mdc-shape-small, var(--ha-border-radius-sm)); | ||||
|       border-radius: var(--mdc-shape-small, 4px); | ||||
|       height: 100%; | ||||
|     } | ||||
|     .row { | ||||
|   | ||||
| @@ -4,7 +4,6 @@ import type { CSSResultGroup, PropertyValues } from "lit"; | ||||
| import { css, html, LitElement, nothing } from "lit"; | ||||
| import { customElement, property, state } from "lit/decorators"; | ||||
| import { fireEvent } from "../common/dom/fire_event"; | ||||
| import { deepEqual } from "../common/util/deep-equal"; | ||||
| import type { Blueprints } from "../data/blueprint"; | ||||
| import { fetchBlueprints } from "../data/blueprint"; | ||||
| import type { RelatedResult } from "../data/search"; | ||||
| @@ -15,6 +14,7 @@ import "./ha-check-list-item"; | ||||
| import "./ha-expansion-panel"; | ||||
| import "./ha-icon-button"; | ||||
| import "./ha-list"; | ||||
| import { deepEqual } from "../common/util/deep-equal"; | ||||
|  | ||||
| @customElement("ha-filter-blueprints") | ||||
| export class HaFilterBlueprints extends LitElement { | ||||
| @@ -189,7 +189,7 @@ export class HaFilterBlueprints extends LitElement { | ||||
|           height: 0; | ||||
|         } | ||||
|         ha-expansion-panel { | ||||
|           --ha-card-border-radius: var(--ha-border-radius-square); | ||||
|           --ha-card-border-radius: 0; | ||||
|           --expansion-panel-content-padding: 0; | ||||
|         } | ||||
|         .header { | ||||
| @@ -207,7 +207,7 @@ export class HaFilterBlueprints extends LitElement { | ||||
|           margin-inline-end: 0; | ||||
|           min-width: 16px; | ||||
|           box-sizing: border-box; | ||||
|           border-radius: var(--ha-border-radius-circle); | ||||
|           border-radius: 50%; | ||||
|           font-size: var(--ha-font-size-xs); | ||||
|           font-weight: var(--ha-font-weight-normal); | ||||
|           background-color: var(--primary-color); | ||||
|   | ||||
| @@ -12,7 +12,6 @@ import type { CSSResultGroup } from "lit"; | ||||
| import { LitElement, css, html, nothing } from "lit"; | ||||
| import { customElement, property, state } from "lit/decorators"; | ||||
| import { fireEvent } from "../common/dom/fire_event"; | ||||
| import { stopPropagation } from "../common/dom/stop_propagation"; | ||||
| import type { CategoryRegistryEntry } from "../data/category_registry"; | ||||
| import { | ||||
|   createCategoryRegistryEntry, | ||||
| @@ -25,11 +24,12 @@ import { SubscribeMixin } from "../mixins/subscribe-mixin"; | ||||
| import { showCategoryRegistryDetailDialog } from "../panels/config/category/show-dialog-category-registry-detail"; | ||||
| import { haStyleScrollbar } from "../resources/styles"; | ||||
| import type { HomeAssistant } from "../types"; | ||||
| import "./ha-button-menu"; | ||||
| import "./ha-expansion-panel"; | ||||
| import "./ha-icon"; | ||||
| import "./ha-list"; | ||||
| import "./ha-button-menu"; | ||||
| import "./ha-list-item"; | ||||
| import "./ha-list"; | ||||
| import { stopPropagation } from "../common/dom/stop_propagation"; | ||||
|  | ||||
| @customElement("ha-filter-categories") | ||||
| export class HaFilterCategories extends SubscribeMixin(LitElement) { | ||||
| @@ -284,7 +284,7 @@ export class HaFilterCategories extends SubscribeMixin(LitElement) { | ||||
|           height: 0; | ||||
|         } | ||||
|         ha-expansion-panel { | ||||
|           --ha-card-border-radius: var(--ha-border-radius-square); | ||||
|           --ha-card-border-radius: 0; | ||||
|           --expansion-panel-content-padding: 0; | ||||
|         } | ||||
|         .header { | ||||
| @@ -302,7 +302,7 @@ export class HaFilterCategories extends SubscribeMixin(LitElement) { | ||||
|           margin-inline-end: 0; | ||||
|           min-width: 16px; | ||||
|           box-sizing: border-box; | ||||
|           border-radius: var(--ha-border-radius-circle); | ||||
|           border-radius: 50%; | ||||
|           font-size: var(--ha-font-size-xs); | ||||
|           font-weight: var(--ha-font-weight-normal); | ||||
|           background-color: var(--primary-color); | ||||
|   | ||||
| @@ -216,7 +216,7 @@ export class HaFilterDevices extends LitElement { | ||||
|         } | ||||
|  | ||||
|         ha-expansion-panel { | ||||
|           --ha-card-border-radius: var(--ha-border-radius-square); | ||||
|           --ha-card-border-radius: 0; | ||||
|           --expansion-panel-content-padding: 0; | ||||
|         } | ||||
|         .header { | ||||
| @@ -234,7 +234,7 @@ export class HaFilterDevices extends LitElement { | ||||
|           margin-inline-end: 0; | ||||
|           min-width: 16px; | ||||
|           box-sizing: border-box; | ||||
|           border-radius: var(--ha-border-radius-circle); | ||||
|           border-radius: 50%; | ||||
|           font-size: var(--ha-font-size-xs); | ||||
|           font-weight: var(--ha-font-weight-normal); | ||||
|           background-color: var(--primary-color); | ||||
|   | ||||
| @@ -170,7 +170,7 @@ export class HaFilterDomains extends LitElement { | ||||
|           height: 0; | ||||
|         } | ||||
|         ha-expansion-panel { | ||||
|           --ha-card-border-radius: var(--ha-border-radius-square); | ||||
|           --ha-card-border-radius: 0; | ||||
|           --expansion-panel-content-padding: 0; | ||||
|         } | ||||
|         .header { | ||||
| @@ -188,7 +188,7 @@ export class HaFilterDomains extends LitElement { | ||||
|           margin-inline-end: initial; | ||||
|           min-width: 16px; | ||||
|           box-sizing: border-box; | ||||
|           border-radius: var(--ha-border-radius-circle); | ||||
|           border-radius: 50%; | ||||
|           font-size: var(--ha-font-size-xs); | ||||
|           font-weight: var(--ha-font-weight-normal); | ||||
|           background-color: var(--primary-color); | ||||
|   | ||||
| @@ -7,7 +7,6 @@ import { fireEvent } from "../common/dom/fire_event"; | ||||
| import { computeStateDomain } from "../common/entity/compute_state_domain"; | ||||
| import { computeStateName } from "../common/entity/compute_state_name"; | ||||
| import { stringCompare } from "../common/string/compare"; | ||||
| import { deepEqual } from "../common/util/deep-equal"; | ||||
| import type { RelatedResult } from "../data/search"; | ||||
| import { findRelated } from "../data/search"; | ||||
| import { haStyleScrollbar } from "../resources/styles"; | ||||
| @@ -18,6 +17,7 @@ import "./ha-expansion-panel"; | ||||
| import "./ha-list"; | ||||
| import "./ha-state-icon"; | ||||
| import "./search-input-outlined"; | ||||
| import { deepEqual } from "../common/util/deep-equal"; | ||||
|  | ||||
| @customElement("ha-filter-entities") | ||||
| export class HaFilterEntities extends LitElement { | ||||
| @@ -231,7 +231,7 @@ export class HaFilterEntities extends LitElement { | ||||
|           height: 0; | ||||
|         } | ||||
|         ha-expansion-panel { | ||||
|           --ha-card-border-radius: var(--ha-border-radius-square); | ||||
|           --ha-card-border-radius: 0; | ||||
|           --expansion-panel-content-padding: 0; | ||||
|         } | ||||
|         .header { | ||||
| @@ -249,7 +249,7 @@ export class HaFilterEntities extends LitElement { | ||||
|           margin-inline-end: 0; | ||||
|           min-width: 16px; | ||||
|           box-sizing: border-box; | ||||
|           border-radius: var(--ha-border-radius-circle); | ||||
|           border-radius: 50%; | ||||
|           font-size: var(--ha-font-size-xs); | ||||
|           font-weight: var(--ha-font-weight-normal); | ||||
|           background-color: var(--primary-color); | ||||
|   | ||||
| @@ -7,7 +7,6 @@ import { repeat } from "lit/directives/repeat"; | ||||
| import memoizeOne from "memoize-one"; | ||||
| import { fireEvent } from "../common/dom/fire_event"; | ||||
| import { computeRTL } from "../common/util/compute_rtl"; | ||||
| import { deepEqual } from "../common/util/deep-equal"; | ||||
| import { getFloorAreaLookup } from "../data/floor_registry"; | ||||
| import type { RelatedResult } from "../data/search"; | ||||
| import { findRelated } from "../data/search"; | ||||
| @@ -21,6 +20,7 @@ import "./ha-icon-button"; | ||||
| import "./ha-list"; | ||||
| import "./ha-svg-icon"; | ||||
| import "./ha-tree-indicator"; | ||||
| import { deepEqual } from "../common/util/deep-equal"; | ||||
|  | ||||
| @customElement("ha-filter-floor-areas") | ||||
| export class HaFilterFloorAreas extends LitElement { | ||||
| @@ -281,7 +281,7 @@ export class HaFilterFloorAreas extends LitElement { | ||||
|           height: 0; | ||||
|         } | ||||
|         ha-expansion-panel { | ||||
|           --ha-card-border-radius: var(--ha-border-radius-square); | ||||
|           --ha-card-border-radius: 0; | ||||
|           --expansion-panel-content-padding: 0; | ||||
|         } | ||||
|         .header { | ||||
| @@ -299,7 +299,7 @@ export class HaFilterFloorAreas extends LitElement { | ||||
|           margin-inline-end: 0; | ||||
|           min-width: 16px; | ||||
|           box-sizing: border-box; | ||||
|           border-radius: var(--ha-border-radius-circle); | ||||
|           border-radius: 50%; | ||||
|           font-size: var(--ha-font-size-xs); | ||||
|           font-weight: var(--ha-font-weight-normal); | ||||
|           background-color: var(--primary-color); | ||||
|   | ||||
| @@ -8,7 +8,7 @@ import { fireEvent } from "../common/dom/fire_event"; | ||||
| import { stringCompare } from "../common/string/compare"; | ||||
| import type { LocalizeFunc } from "../common/translations/localize"; | ||||
| import type { IntegrationManifest } from "../data/integration"; | ||||
| import { domainToName, fetchIntegrationManifests } from "../data/integration"; | ||||
| import { fetchIntegrationManifests, domainToName } from "../data/integration"; | ||||
| import { haStyleScrollbar } from "../resources/styles"; | ||||
| import type { HomeAssistant } from "../types"; | ||||
| import "./ha-check-list-item"; | ||||
| @@ -188,7 +188,7 @@ export class HaFilterIntegrations extends LitElement { | ||||
|           height: 0; | ||||
|         } | ||||
|         ha-expansion-panel { | ||||
|           --ha-card-border-radius: var(--ha-border-radius-square); | ||||
|           --ha-card-border-radius: 0; | ||||
|           --expansion-panel-content-padding: 0; | ||||
|         } | ||||
|         .header { | ||||
| @@ -206,7 +206,7 @@ export class HaFilterIntegrations extends LitElement { | ||||
|           margin-inline-end: 0; | ||||
|           min-width: 16px; | ||||
|           box-sizing: border-box; | ||||
|           border-radius: var(--ha-border-radius-circle); | ||||
|           border-radius: 50%; | ||||
|           font-size: var(--ha-font-size-xs); | ||||
|           font-weight: var(--ha-font-weight-normal); | ||||
|           background-color: var(--primary-color); | ||||
|   | ||||
| @@ -214,7 +214,7 @@ export class HaFilterLabels extends SubscribeMixin(LitElement) { | ||||
|           height: 0; | ||||
|         } | ||||
|         ha-expansion-panel { | ||||
|           --ha-card-border-radius: var(--ha-border-radius-square); | ||||
|           --ha-card-border-radius: 0; | ||||
|           --expansion-panel-content-padding: 0; | ||||
|         } | ||||
|         .header { | ||||
| @@ -232,7 +232,7 @@ export class HaFilterLabels extends SubscribeMixin(LitElement) { | ||||
|           margin-inline-end: 0; | ||||
|           min-width: 16px; | ||||
|           box-sizing: border-box; | ||||
|           border-radius: var(--ha-border-radius-circle); | ||||
|           border-radius: 50%; | ||||
|           font-size: var(--ha-font-size-xs); | ||||
|           font-weight: var(--ha-font-weight-normal); | ||||
|           background-color: var(--primary-color); | ||||
|   | ||||
| @@ -158,7 +158,7 @@ export class HaFilterStates extends LitElement { | ||||
|           height: 0; | ||||
|         } | ||||
|         ha-expansion-panel { | ||||
|           --ha-card-border-radius: var(--ha-border-radius-square); | ||||
|           --ha-card-border-radius: 0; | ||||
|           --expansion-panel-content-padding: 0; | ||||
|         } | ||||
|         .header { | ||||
| @@ -176,7 +176,7 @@ export class HaFilterStates extends LitElement { | ||||
|           margin-inline-end: 0; | ||||
|           min-width: 16px; | ||||
|           box-sizing: border-box; | ||||
|           border-radius: var(--ha-border-radius-circle); | ||||
|           border-radius: 50%; | ||||
|           font-size: var(--ha-font-size-xs); | ||||
|           font-weight: var(--ha-font-weight-normal); | ||||
|           background-color: var(--primary-color); | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user