mirror of
				https://github.com/home-assistant/frontend.git
				synced 2025-10-29 21:49:45 +00:00 
			
		
		
		
	Compare commits
	
		
			37 Commits
		
	
	
		
			ha-wa-dial
			...
			20251001.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 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 | 
							
								
								
									
										4
									
								
								.github/workflows/cast_deployment.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/cast_deployment.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -42,7 +42,7 @@ jobs: | ||||
|       - name: Deploy to Netlify | ||||
|         id: deploy | ||||
|         run: | | ||||
|           npx -y netlify-cli deploy --dir=cast/dist --alias dev | ||||
|           npx -y netlify-cli@23.7.3 deploy --dir=cast/dist --alias dev | ||||
|         env: | ||||
|           NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} | ||||
|           NETLIFY_SITE_ID: ${{ secrets.NETLIFY_CAST_SITE_ID }} | ||||
| @@ -77,7 +77,7 @@ jobs: | ||||
|       - name: Deploy to Netlify | ||||
|         id: deploy | ||||
|         run: | | ||||
|           npx -y netlify-cli deploy --dir=cast/dist --prod | ||||
|           npx -y netlify-cli@23.7.3 deploy --dir=cast/dist --prod | ||||
|         env: | ||||
|           NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} | ||||
|           NETLIFY_SITE_ID: ${{ secrets.NETLIFY_CAST_SITE_ID }} | ||||
|   | ||||
							
								
								
									
										4
									
								
								.github/workflows/demo_deployment.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/demo_deployment.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -43,7 +43,7 @@ jobs: | ||||
|       - name: Deploy to Netlify | ||||
|         id: deploy | ||||
|         run: | | ||||
|           npx -y netlify-cli deploy --dir=demo/dist --prod | ||||
|           npx -y netlify-cli@23.7.3 deploy --dir=demo/dist --prod | ||||
|         env: | ||||
|           NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} | ||||
|           NETLIFY_SITE_ID: ${{ secrets.NETLIFY_DEMO_DEV_SITE_ID }} | ||||
| @@ -78,7 +78,7 @@ jobs: | ||||
|       - name: Deploy to Netlify | ||||
|         id: deploy | ||||
|         run: | | ||||
|           npx -y netlify-cli deploy --dir=demo/dist --prod | ||||
|           npx -y netlify-cli@23.7.3 deploy --dir=demo/dist --prod | ||||
|         env: | ||||
|           NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} | ||||
|           NETLIFY_SITE_ID: ${{ secrets.NETLIFY_DEMO_SITE_ID }} | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/design_deployment.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/design_deployment.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -35,7 +35,7 @@ jobs: | ||||
|       - name: Deploy to Netlify | ||||
|         id: deploy | ||||
|         run: | | ||||
|           npx -y netlify-cli deploy --dir=gallery/dist --prod | ||||
|           npx -y netlify-cli@23.7.3 deploy --dir=gallery/dist --prod | ||||
|         env: | ||||
|           NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} | ||||
|           NETLIFY_SITE_ID: ${{ secrets.NETLIFY_GALLERY_SITE_ID }} | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/design_preview.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/design_preview.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -40,7 +40,7 @@ jobs: | ||||
|       - name: Deploy preview to Netlify | ||||
|         id: deploy | ||||
|         run: | | ||||
|           npx -y netlify-cli deploy --dir=gallery/dist --alias "deploy-preview-${{ github.event.number }}" \ | ||||
|           npx -y netlify-cli@23.7.3 deploy --dir=gallery/dist --alias "deploy-preview-${{ github.event.number }}" \ | ||||
|             --json > deploy_output.json | ||||
|         env: | ||||
|           NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} | ||||
|   | ||||
| @@ -5,17 +5,17 @@ const castContext = framework.CastReceiverContext.getInstance(); | ||||
| const playerManager = castContext.getPlayerManager(); | ||||
|  | ||||
| playerManager.setMessageInterceptor( | ||||
|   "LOAD" as framework.messages.MessageType.LOAD, | ||||
|   framework.messages.MessageType.LOAD, | ||||
|   (loadRequestData) => { | ||||
|     const media = loadRequestData.media; | ||||
|     // Special handling if it came from Google Assistant | ||||
|     if (media.entity) { | ||||
|       media.contentId = media.entity; | ||||
|       media.streamType = "LIVE" as framework.messages.StreamType.LIVE; | ||||
|       media.streamType = framework.messages.StreamType.LIVE; | ||||
|       media.contentType = "application/vnd.apple.mpegurl"; | ||||
|       // @ts-ignore | ||||
|       media.hlsVideoSegmentFormat = | ||||
|         "fmp4" as framework.messages.HlsVideoSegmentFormat.FMP4; | ||||
|         framework.messages.HlsVideoSegmentFormat.FMP4; | ||||
|     } | ||||
|     return loadRequestData; | ||||
|   } | ||||
|   | ||||
| @@ -40,8 +40,7 @@ const playDummyMedia = (viewTitle?: string) => { | ||||
|   loadRequestData.media.contentId = | ||||
|     "https://cast.home-assistant.io/images/google-nest-hub.png"; | ||||
|   loadRequestData.media.contentType = "image/jpeg"; | ||||
|   loadRequestData.media.streamType = | ||||
|     "NONE" as framework.messages.StreamType.NONE; | ||||
|   loadRequestData.media.streamType = framework.messages.StreamType.NONE; | ||||
|   const metadata = new framework.messages.GenericMediaMetadata(); | ||||
|   metadata.title = viewTitle; | ||||
|   loadRequestData.media.metadata = metadata; | ||||
| @@ -90,7 +89,7 @@ const showMediaPlayer = () => { | ||||
| const options = new framework.CastReceiverOptions(); | ||||
| options.disableIdleTimeout = true; | ||||
| options.customNamespaces = { | ||||
|   [CAST_NS]: "json" as framework.system.MessageType.JSON, | ||||
|   [CAST_NS]: framework.system.MessageType.JSON, | ||||
| }; | ||||
|  | ||||
| castContext.addCustomMessageListener( | ||||
| @@ -98,7 +97,9 @@ castContext.addCustomMessageListener( | ||||
|   // @ts-ignore | ||||
|   (ev: ReceivedMessage<HassMessage>) => { | ||||
|     // We received a show Lovelace command, stop media from playing, hide media player and show Lovelace controller | ||||
|     if (playerManager.getPlayerState() !== "IDLE") { | ||||
|     if ( | ||||
|       playerManager.getPlayerState() !== framework.messages.PlayerState.IDLE | ||||
|     ) { | ||||
|       playerManager.stop(); | ||||
|     } else { | ||||
|       showLovelaceController(); | ||||
| @@ -112,7 +113,7 @@ castContext.addCustomMessageListener( | ||||
| const playerManager = castContext.getPlayerManager(); | ||||
|  | ||||
| playerManager.setMessageInterceptor( | ||||
|   "LOAD" as framework.messages.MessageType.LOAD, | ||||
|   framework.messages.MessageType.LOAD, | ||||
|   (loadRequestData) => { | ||||
|     if ( | ||||
|       loadRequestData.media.contentId === | ||||
| @@ -126,23 +127,24 @@ playerManager.setMessageInterceptor( | ||||
|     // Special handling if it came from Google Assistant | ||||
|     if (media.entity) { | ||||
|       media.contentId = media.entity; | ||||
|       media.streamType = "LIVE" as framework.messages.StreamType.LIVE; | ||||
|       media.streamType = framework.messages.StreamType.LIVE; | ||||
|       media.contentType = "application/vnd.apple.mpegurl"; | ||||
|       // @ts-ignore | ||||
|       media.hlsVideoSegmentFormat = | ||||
|         "fmp4" as framework.messages.HlsVideoSegmentFormat.FMP4; | ||||
|         framework.messages.HlsVideoSegmentFormat.FMP4; | ||||
|     } | ||||
|     return loadRequestData; | ||||
|   } | ||||
| ); | ||||
|  | ||||
| playerManager.addEventListener( | ||||
|   "MEDIA_STATUS" as framework.events.EventType.MEDIA_STATUS, | ||||
|   framework.events.EventType.MEDIA_STATUS, | ||||
|   (event) => { | ||||
|     if ( | ||||
|       event.mediaStatus?.playerState === "IDLE" && | ||||
|       event.mediaStatus?.playerState === framework.messages.PlayerState.IDLE && | ||||
|       event.mediaStatus?.idleReason && | ||||
|       event.mediaStatus?.idleReason !== "INTERRUPTED" | ||||
|       event.mediaStatus?.idleReason !== | ||||
|         framework.messages.IdleReason.INTERRUPTED | ||||
|     ) { | ||||
|       // media finished or stopped, return to default Lovelace | ||||
|       showLovelaceController(); | ||||
|   | ||||
| @@ -52,7 +52,7 @@ | ||||
|     "@fullcalendar/list": "6.1.19", | ||||
|     "@fullcalendar/luxon3": "6.1.19", | ||||
|     "@fullcalendar/timegrid": "6.1.19", | ||||
|     "@home-assistant/webawesome": "3.0.0-beta.4.ha.3", | ||||
|     "@home-assistant/webawesome": "3.0.0-beta.6.ha.0", | ||||
|     "@lezer/highlight": "1.2.1", | ||||
|     "@lit-labs/motion": "1.0.9", | ||||
|     "@lit-labs/observers": "2.0.6", | ||||
| @@ -161,7 +161,7 @@ | ||||
|     "@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.24", | ||||
|     "@types/chromecast-caf-receiver": "6.0.22", | ||||
|     "@types/chromecast-caf-sender": "1.0.11", | ||||
|     "@types/color-name": "2.0.0", | ||||
|     "@types/culori": "4.0.1", | ||||
|   | ||||
| @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" | ||||
|  | ||||
| [project] | ||||
| name         = "home-assistant-frontend" | ||||
| version      = "20250924.0" | ||||
| version      = "20251001.0" | ||||
| license      = "Apache-2.0" | ||||
| license-files = ["LICENSE*"] | ||||
| description  = "The Home Assistant frontend" | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import type { CSSResultGroup, TemplateResult } from "lit"; | ||||
| import { css, html, LitElement } from "lit"; | ||||
| import { css, html, LitElement, nothing } from "lit"; | ||||
| import { customElement, property } from "lit/decorators"; | ||||
| import { fireEvent } from "../common/dom/fire_event"; | ||||
| import type { LocalizeFunc } from "../common/translations/localize"; | ||||
| @@ -73,14 +73,18 @@ export class HaAnalytics extends LitElement { | ||||
|                 .checked=${this.analytics?.preferences[preference]} | ||||
|                 .preference=${preference} | ||||
|                 name=${preference} | ||||
|                 ?disabled=${baseEnabled} | ||||
|               > | ||||
|               </ha-switch> | ||||
|               <ha-tooltip .for="switch-${preference}" placement="right"> | ||||
|                 ${this.localize( | ||||
|                   `ui.panel.${this.translationKeyPanel}.analytics.need_base_enabled` | ||||
|                 )} | ||||
|               </ha-tooltip> | ||||
|               ${baseEnabled | ||||
|                 ? nothing | ||||
|                 : html`<ha-tooltip | ||||
|                     .for="switch-${preference}" | ||||
|                     placement="right" | ||||
|                   > | ||||
|                     ${this.localize( | ||||
|                       `ui.panel.${this.translationKeyPanel}.analytics.need_base_enabled` | ||||
|                     )} | ||||
|                   </ha-tooltip>`} | ||||
|             </span> | ||||
|           </ha-settings-row> | ||||
|         ` | ||||
|   | ||||
| @@ -54,9 +54,9 @@ export class HaBottomSheet extends LitElement { | ||||
|       border-top-left-radius: var(--ha-border-radius-lg); | ||||
|       border-top-right-radius: var(--ha-border-radius-lg); | ||||
|       max-height: 90vh; | ||||
|       margin-bottom: var(--safe-area-inset-bottom); | ||||
|       margin-left: var(--safe-area-inset-left); | ||||
|       margin-right: var(--safe-area-inset-right); | ||||
|       padding-bottom: var(--safe-area-inset-bottom); | ||||
|       padding-left: var(--safe-area-inset-left); | ||||
|       padding-right: var(--safe-area-inset-right); | ||||
|     } | ||||
|   `; | ||||
| } | ||||
|   | ||||
| @@ -39,22 +39,24 @@ class HaSegmentedBar extends LitElement { | ||||
|           <slot name="extra"></slot> | ||||
|         </div> | ||||
|         <div class="bar"> | ||||
|           ${this.segments.map((segment) => { | ||||
|             const bar = html`<div | ||||
|               style=${styleMap({ | ||||
|                 width: `${(segment.value / totalValue) * 100}%`, | ||||
|                 backgroundColor: segment.color, | ||||
|               })} | ||||
|             ></div>`; | ||||
|             return this.hideTooltip && !segment.label | ||||
|               ? bar | ||||
|               : html` | ||||
|                   <ha-tooltip> | ||||
|                     <span slot="content">${segment.label}</span> | ||||
|                     ${bar} | ||||
|                   </ha-tooltip> | ||||
|                 `; | ||||
|           })} | ||||
|           ${this.segments.map( | ||||
|             (segment, index) => html` | ||||
|               ${this.hideTooltip || !segment.label | ||||
|                 ? nothing | ||||
|                 : html` | ||||
|                     <ha-tooltip for="segment-${index}" placement="top"> | ||||
|                       ${segment.label} | ||||
|                     </ha-tooltip> | ||||
|                   `} | ||||
|               <div | ||||
|                 id="segment-${index}" | ||||
|                 style=${styleMap({ | ||||
|                   width: `${(segment.value / totalValue) * 100}%`, | ||||
|                   backgroundColor: segment.color, | ||||
|                 })} | ||||
|               ></div> | ||||
|             ` | ||||
|           )} | ||||
|         </div> | ||||
|         ${this.hideLegend | ||||
|           ? nothing | ||||
|   | ||||
| @@ -82,12 +82,12 @@ export class HaNumberSelector extends LitElement { | ||||
|                 labeled | ||||
|                 .min=${this.selector.number!.min} | ||||
|                 .max=${this.selector.number!.max} | ||||
|                 .value=${this.value ?? ""} | ||||
|                 .value=${this.value} | ||||
|                 .step=${sliderStep} | ||||
|                 .disabled=${this.disabled} | ||||
|                 .required=${this.required} | ||||
|                 @change=${this._handleSliderChange} | ||||
|                 .ticks=${this.selector.number?.slider_ticks} | ||||
|                 .withMarkers=${this.selector.number?.slider_ticks || false} | ||||
|               > | ||||
|               </ha-slider> | ||||
|             ` | ||||
|   | ||||
| @@ -19,7 +19,7 @@ export class HaSlider extends Slider { | ||||
|       Slider.styles, | ||||
|       css` | ||||
|         :host { | ||||
|           --wa-form-control-activated-color: var(--primary-color); | ||||
|           --wa-form-control-activated-color: var(--ha-control-color); | ||||
|           --track-size: var(--ha-slider-track-size, 4px); | ||||
|           --marker-height: calc(var(--ha-slider-track-size, 4px) / 2); | ||||
|           --marker-width: calc(var(--ha-slider-track-size, 4px) / 2); | ||||
|   | ||||
| @@ -18,6 +18,8 @@ export class HaTabGroupTab extends Tab { | ||||
|           opacity: 0.8; | ||||
|  | ||||
|           color: inherit; | ||||
|  | ||||
|           --wa-space-l: 16px; | ||||
|         } | ||||
|  | ||||
|         :host([active]:not([disabled])) { | ||||
|   | ||||
| @@ -4,6 +4,7 @@ export interface LovelaceBadgeConfig { | ||||
|   type: string; | ||||
|   [key: string]: any; | ||||
|   visibility?: Condition[]; | ||||
|   disabled?: boolean; | ||||
| } | ||||
|  | ||||
| export const ensureBadgeConfig = ( | ||||
|   | ||||
| @@ -4,7 +4,7 @@ import type { Action } from "./script"; | ||||
| export const callExecuteScript = ( | ||||
|   hass: HomeAssistant, | ||||
|   sequence: Action | Action[] | ||||
| ): Promise<{ context: Context; response: Record<string, any> }> => | ||||
| ): Promise<{ context: Context; response: Record<string, any> | null }> => | ||||
|   hass.callWS({ | ||||
|     type: "execute_script", | ||||
|     sequence, | ||||
|   | ||||
| @@ -26,20 +26,21 @@ class StepFlowLoading extends LitElement { | ||||
|       this.step | ||||
|     ); | ||||
|     return html` | ||||
|       <div class="init-spinner"> | ||||
|       <div class="content"> | ||||
|         <ha-spinner size="large"></ha-spinner> | ||||
|         ${description ? html`<div>${description}</div>` : ""} | ||||
|         <ha-spinner></ha-spinner> | ||||
|       </div> | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   static styles = css` | ||||
|     .init-spinner { | ||||
|     .content { | ||||
|       margin-top: 0; | ||||
|       padding: 50px 100px; | ||||
|       text-align: center; | ||||
|     } | ||||
|     ha-spinner { | ||||
|       margin-top: 16px; | ||||
|       margin-bottom: 16px; | ||||
|     } | ||||
|   `; | ||||
| } | ||||
|   | ||||
| @@ -34,7 +34,7 @@ class StepFlowProgress extends LitElement { | ||||
|                 )}%</ha-progress-ring | ||||
|               > | ||||
|             ` | ||||
|           : html` <ha-spinner size="large"></ha-spinner> `} | ||||
|           : html`<ha-spinner size="large"></ha-spinner>`} | ||||
|         ${this.flowConfig.renderShowFormProgressDescription( | ||||
|           this.hass, | ||||
|           this.step | ||||
| @@ -48,6 +48,7 @@ class StepFlowProgress extends LitElement { | ||||
|       configFlowContentStyles, | ||||
|       css` | ||||
|         .content { | ||||
|           margin-top: 0; | ||||
|           padding: 50px 100px; | ||||
|           text-align: center; | ||||
|         } | ||||
|   | ||||
| @@ -48,10 +48,10 @@ class MoreInfoMediaPlayer extends LitElement { | ||||
|  | ||||
|   @property({ attribute: false }) public stateObj?: MediaPlayerEntity; | ||||
|  | ||||
|   private _formateDuration(duration: number) { | ||||
|   private _formatDuration(duration: number) { | ||||
|     const hours = Math.floor(duration / 3600); | ||||
|     const minutes = Math.floor((duration % 3600) / 60); | ||||
|     const seconds = duration % 60; | ||||
|     const seconds = Math.floor(duration % 60); | ||||
|     return formatDurationDigital(this.hass.locale, { | ||||
|       hours, | ||||
|       minutes, | ||||
| @@ -258,14 +258,17 @@ class MoreInfoMediaPlayer extends LitElement { | ||||
|  | ||||
|     const stateObj = this.stateObj; | ||||
|     const controls = computeMediaControls(stateObj, true); | ||||
|     const coverUrl = stateObj.attributes.entity_picture || ""; | ||||
|     const coverUrl = | ||||
|       stateObj.attributes.entity_picture_local || | ||||
|       stateObj.attributes.entity_picture || | ||||
|       ""; | ||||
|     const playerObj = new HassMediaPlayerEntity(this.hass, this.stateObj); | ||||
|     const position = Math.floor(playerObj.currentProgress) || 0; | ||||
|     const duration = stateObj.attributes.media_duration || 0; | ||||
|     const remaining = duration - position; | ||||
|     const durationFormated = | ||||
|       remaining > 0 ? this._formateDuration(remaining) : 0; | ||||
|     const postionFormated = this._formateDuration(position); | ||||
|  | ||||
|     const position = Math.max(Math.floor(playerObj.currentProgress || 0), 0); | ||||
|     const duration = Math.max(stateObj.attributes.media_duration || 0, 0); | ||||
|     const remaining = Math.max(duration - position, 0); | ||||
|     const remainingFormatted = this._formatDuration(remaining); | ||||
|     const positionFormatted = this._formatDuration(position); | ||||
|     const primaryTitle = playerObj.primaryTitle; | ||||
|     const secondaryTitle = playerObj.secondaryTitle; | ||||
|     const turnOn = controls?.find((c) => c.action === "turn_on"); | ||||
| @@ -323,11 +326,10 @@ class MoreInfoMediaPlayer extends LitElement { | ||||
|                 @change=${this._handleMediaSeekChanged} | ||||
|                 ?disabled=${!stateActive(stateObj) || | ||||
|                 !supportsFeature(stateObj, MediaPlayerEntityFeature.SEEK)} | ||||
|               ></ha-slider> | ||||
|               <div class="position-info-row"> | ||||
|                 <span class="position-time">${postionFormated}</span> | ||||
|                 <span class="duration-time">${durationFormated}</span> | ||||
|               </div> | ||||
|               > | ||||
|                 <span slot="reference">${positionFormatted}</span> | ||||
|                 <span slot="reference">${remainingFormatted}</span> | ||||
|               </ha-slider> | ||||
|             </div> | ||||
|           ` | ||||
|         : nothing} | ||||
| @@ -477,6 +479,22 @@ class MoreInfoMediaPlayer extends LitElement { | ||||
|       height: 320px; | ||||
|     } | ||||
|  | ||||
|     @media (max-height: 750px) { | ||||
|       .cover-container { | ||||
|         height: 120px; | ||||
|       } | ||||
|  | ||||
|       .cover-image { | ||||
|         width: 100px; | ||||
|         height: 100px; | ||||
|       } | ||||
|  | ||||
|       .cover-image--playing { | ||||
|         width: 120px; | ||||
|         height: 120px; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     .empty-cover { | ||||
|       background-color: var(--secondary-background-color); | ||||
|       font-size: 1.5em; | ||||
| @@ -548,13 +566,8 @@ class MoreInfoMediaPlayer extends LitElement { | ||||
|       flex-direction: column; | ||||
|     } | ||||
|  | ||||
|     .position-info-row { | ||||
|       display: flex; | ||||
|       flex-direction: row; | ||||
|       justify-content: space-between; | ||||
|     .position-bar ha-slider::part(references) { | ||||
|       color: var(--secondary-text-color); | ||||
|       padding: 0 8px; | ||||
|       font-size: var(--ha-font-size-s); | ||||
|     } | ||||
|  | ||||
|     .media-info-row { | ||||
|   | ||||
| @@ -8,6 +8,7 @@ import { createCloseHeading } from "../../components/ha-dialog"; | ||||
| import "../../components/ha-textarea"; | ||||
| import type { HaTextArea } from "../../components/ha-textarea"; | ||||
| import { convertTextToSpeech } from "../../data/tts"; | ||||
| import { haStyleDialog } from "../../resources/styles"; | ||||
| import type { HomeAssistant } from "../../types"; | ||||
| import { showAlertDialog } from "../generic/show-dialog-box"; | ||||
| import type { TTSTryDialogParams } from "./show-dialog-tts-try"; | ||||
| @@ -149,21 +150,24 @@ export class TTSTryDialog extends LitElement { | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   static styles = css` | ||||
|     ha-dialog { | ||||
|       --mdc-dialog-max-width: 500px; | ||||
|     } | ||||
|     ha-textarea, | ||||
|     ha-select { | ||||
|       width: 100%; | ||||
|     } | ||||
|     ha-select { | ||||
|       margin-top: 8px; | ||||
|     } | ||||
|     .loading { | ||||
|       height: 36px; | ||||
|     } | ||||
|   `; | ||||
|   static styles = [ | ||||
|     haStyleDialog, | ||||
|     css` | ||||
|       ha-dialog { | ||||
|         --mdc-dialog-max-width: 500px; | ||||
|       } | ||||
|       ha-textarea, | ||||
|       ha-select { | ||||
|         width: 100%; | ||||
|       } | ||||
|       ha-select { | ||||
|         margin-top: 8px; | ||||
|       } | ||||
|       .loading { | ||||
|         height: 36px; | ||||
|       } | ||||
|     `, | ||||
|   ]; | ||||
| } | ||||
|  | ||||
| declare global { | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| import { consume } from "@lit/context"; | ||||
| import { | ||||
|   mdiAppleKeyboardCommand, | ||||
|   mdiCog, | ||||
|   mdiContentSave, | ||||
|   mdiDebugStepOver, | ||||
| @@ -87,6 +88,7 @@ import "./blueprint-automation-editor"; | ||||
| import "./manual-automation-editor"; | ||||
| import type { HaManualAutomationEditor } from "./manual-automation-editor"; | ||||
| import { UndoRedoMixin } from "../../../mixins/undo-redo-mixin"; | ||||
| import { isMac } from "../../../util/is_mac"; | ||||
|  | ||||
| declare global { | ||||
|   interface HTMLElementTagNameMap { | ||||
| @@ -215,6 +217,10 @@ export class HaAutomationEditor extends UndoRedoMixin< | ||||
|       : undefined; | ||||
|  | ||||
|     const useBlueprint = "use_blueprint" in this._config; | ||||
|     const shortcutIcon = isMac | ||||
|       ? html`<ha-svg-icon .path=${mdiAppleKeyboardCommand}></ha-svg-icon>` | ||||
|       : this.hass.localize("ui.panel.config.automation.editor.ctrl"); | ||||
|  | ||||
|     return html` | ||||
|       <hass-subpage | ||||
|         .hass=${this.hass} | ||||
| @@ -231,16 +237,35 @@ export class HaAutomationEditor extends UndoRedoMixin< | ||||
|                 .path=${mdiUndo} | ||||
|                 @click=${this.undo} | ||||
|                 .disabled=${!this.canUndo} | ||||
|                 id="button-undo" | ||||
|               > | ||||
|               </ha-icon-button> | ||||
|               <ha-tooltip placement="bottom" for="button-undo"> | ||||
|                 ${this.hass.localize("ui.common.undo")} | ||||
|                 <span class="shortcut" | ||||
|                   >( | ||||
|                   <span>${shortcutIcon}</span> | ||||
|                   <span>+</span> | ||||
|                   <span>Z</span>) | ||||
|                 </span> | ||||
|               </ha-tooltip> | ||||
|               <ha-icon-button | ||||
|                 slot="toolbar-icon" | ||||
|                 .label=${this.hass.localize("ui.common.redo")} | ||||
|                 .path=${mdiRedo} | ||||
|                 @click=${this.redo} | ||||
|                 .disabled=${!this.canRedo} | ||||
|                 id="button-redo" | ||||
|               > | ||||
|               </ha-icon-button>` | ||||
|               </ha-icon-button> | ||||
|               <ha-tooltip placement="bottom" for="button-redo"> | ||||
|                 ${this.hass.localize("ui.common.redo")} | ||||
|                 <span class="shortcut"> | ||||
|                   (<span>${shortcutIcon}</span> | ||||
|                   <span>+</span> | ||||
|                   <span>Y</span>) | ||||
|                 </span> | ||||
|               </ha-tooltip>` | ||||
|           : nothing} | ||||
|         ${this._config?.id && !this.narrow | ||||
|           ? html` | ||||
| @@ -1292,6 +1317,15 @@ export class HaAutomationEditor extends UndoRedoMixin< | ||||
|         ha-fab.dirty { | ||||
|           bottom: calc(16px + var(--safe-area-inset-bottom, 0px)); | ||||
|         } | ||||
|         ha-tooltip ha-svg-icon { | ||||
|           width: 12px; | ||||
|         } | ||||
|         ha-tooltip .shortcut { | ||||
|           display: inline-flex; | ||||
|           flex-direction: row; | ||||
|           align-items: center; | ||||
|           gap: 2px; | ||||
|         } | ||||
|       `, | ||||
|     ]; | ||||
|   } | ||||
|   | ||||
| @@ -171,7 +171,7 @@ export default class HaAutomationSidebar extends LitElement { | ||||
|         @mousedown=${this._handleMouseDown} | ||||
|         @touchstart=${this._handleMouseDown} | ||||
|       > | ||||
|         ${this._resizing ? html`<div class="indicator"></div>` : nothing} | ||||
|         <div class="indicator ${this._resizing ? "" : "hidden"}"></div> | ||||
|       </div> | ||||
|       ${this._renderContent()} | ||||
|     `; | ||||
| @@ -333,6 +333,15 @@ export default class HaAutomationSidebar extends LitElement { | ||||
|       height: 100%; | ||||
|       width: 4px; | ||||
|       border-radius: var(--ha-border-radius-pill); | ||||
|       transform: scale3d(1, 1, 1); | ||||
|       opacity: 1; | ||||
|       transition: | ||||
|         transform 180ms ease-in-out, | ||||
|         opacity 180ms ease-in-out; | ||||
|     } | ||||
|     .handle .indicator.hidden { | ||||
|       transform: scale3d(0, 1, 1); | ||||
|       opacity: 0; | ||||
|     } | ||||
|   `; | ||||
| } | ||||
|   | ||||
| @@ -213,6 +213,7 @@ class HaConfigEnergy extends LitElement { | ||||
|           this.hass.states[key], | ||||
|         ]) | ||||
|       ), | ||||
|       issues: this._validationResult, | ||||
|     }; | ||||
|     const json = JSON.stringify(data, null, 2); | ||||
|     const blob = new Blob([json], { type: "application/json" }); | ||||
|   | ||||
| @@ -260,7 +260,7 @@ class DialogPersonDetail extends LitElement implements HassDialog { | ||||
|         > | ||||
|           ${this._params.entry | ||||
|             ? this.hass!.localize("ui.common.save") | ||||
|             : this.hass!.localize("ui.panel.config.person.detail.create")} | ||||
|             : this.hass!.localize("ui.common.add")} | ||||
|         </ha-button> | ||||
|       </ha-dialog> | ||||
|     `; | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| import { consume } from "@lit/context"; | ||||
| import { | ||||
|   mdiAppleKeyboardCommand, | ||||
|   mdiCog, | ||||
|   mdiContentSave, | ||||
|   mdiDebugStepOver, | ||||
| @@ -75,6 +76,7 @@ import "./blueprint-script-editor"; | ||||
| import "./manual-script-editor"; | ||||
| import type { HaManualScriptEditor } from "./manual-script-editor"; | ||||
| import { UndoRedoMixin } from "../../../mixins/undo-redo-mixin"; | ||||
| import { isMac } from "../../../util/is_mac"; | ||||
|  | ||||
| const baseEditorMixins = SubscribeMixin( | ||||
|   PreventUnsavedMixin(KeyboardShortcutMixin(LitElement)) | ||||
| @@ -168,6 +170,10 @@ export class HaScriptEditor extends UndoRedoMixin< | ||||
|       : undefined; | ||||
|  | ||||
|     const useBlueprint = "use_blueprint" in this._config; | ||||
|     const shortcutIcon = isMac | ||||
|       ? html`<ha-svg-icon .path=${mdiAppleKeyboardCommand}></ha-svg-icon>` | ||||
|       : this.hass.localize("ui.panel.config.automation.editor.ctrl"); | ||||
|  | ||||
|     return html` | ||||
|       <hass-subpage | ||||
|         .hass=${this.hass} | ||||
| @@ -184,16 +190,34 @@ export class HaScriptEditor extends UndoRedoMixin< | ||||
|                 .path=${mdiUndo} | ||||
|                 @click=${this.undo} | ||||
|                 .disabled=${!this.canUndo} | ||||
|                 id="button-undo" | ||||
|               > | ||||
|               </ha-icon-button> | ||||
|               <ha-tooltip placement="bottom" for="button-undo"> | ||||
|                 ${this.hass.localize("ui.common.undo")} | ||||
|                 <span class="shortcut"> | ||||
|                   (<span>${shortcutIcon}</span> | ||||
|                   <span>+</span> | ||||
|                   <span>Z</span>) | ||||
|                 </span> | ||||
|               </ha-tooltip> | ||||
|               <ha-icon-button | ||||
|                 slot="toolbar-icon" | ||||
|                 .label=${this.hass.localize("ui.common.redo")} | ||||
|                 .path=${mdiRedo} | ||||
|                 @click=${this.redo} | ||||
|                 .disabled=${!this.canRedo} | ||||
|                 id="button-redo" | ||||
|               > | ||||
|               </ha-icon-button>` | ||||
|               </ha-icon-button> | ||||
|               <ha-tooltip placement="bottom" for="button-redo"> | ||||
|                 ${this.hass.localize("ui.common.redo")} | ||||
|                 <span class="shortcut"> | ||||
|                   (<span>${shortcutIcon}</span> | ||||
|                   <span>+</span> | ||||
|                   <span>Y</span>) | ||||
|                 </span> | ||||
|               </ha-tooltip>` | ||||
|           : nothing} | ||||
|         ${this.scriptId && !this.narrow | ||||
|           ? html` | ||||
| @@ -1233,6 +1257,15 @@ export class HaScriptEditor extends UndoRedoMixin< | ||||
|           text-decoration: none; | ||||
|           color: var(--primary-color); | ||||
|         } | ||||
|         ha-tooltip ha-svg-icon { | ||||
|           width: 12px; | ||||
|         } | ||||
|         ha-tooltip .shortcut { | ||||
|           display: inline-flex; | ||||
|           flex-direction: row; | ||||
|           align-items: center; | ||||
|           gap: 2px; | ||||
|         } | ||||
|       `, | ||||
|     ]; | ||||
|   } | ||||
|   | ||||
| @@ -51,7 +51,7 @@ class HaPanelDevAction extends LitElement { | ||||
|   @state() private _response?: { | ||||
|     domain: string; | ||||
|     service: string; | ||||
|     result: Record<string, any>; | ||||
|     result: Record<string, any> | null; | ||||
|     media?: Promise<TemplateResult | typeof nothing>; | ||||
|   }; | ||||
|  | ||||
| @@ -205,7 +205,7 @@ class HaPanelDevAction extends LitElement { | ||||
|           </ha-progress-button> | ||||
|         </div> | ||||
|       </div> | ||||
|       ${this._response | ||||
|       ${this._response?.result | ||||
|         ? html`<div class="content response"> | ||||
|             <ha-card | ||||
|               .header=${this.hass.localize( | ||||
| @@ -491,7 +491,7 @@ class HaPanelDevAction extends LitElement { | ||||
|         service, | ||||
|         result, | ||||
|         media: | ||||
|           "media_source_id" in result | ||||
|           result && "media_source_id" in result | ||||
|             ? resolveMediaSource(this.hass, result.media_source_id).then( | ||||
|                 (resolved) => | ||||
|                   resolved.mime_type.startsWith("image/") | ||||
|   | ||||
| @@ -161,7 +161,7 @@ export class HuiBadge extends ReactiveElement { | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   private _updateVisibility(forceVisible?: boolean) { | ||||
|   private _updateVisibility(ignoreConditions?: boolean) { | ||||
|     if (!this._element || !this.hass) { | ||||
|       return; | ||||
|     } | ||||
| @@ -171,9 +171,18 @@ export class HuiBadge extends ReactiveElement { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     if (this.preview) { | ||||
|       this._setElementVisibility(true); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     if (this.config?.disabled) { | ||||
|       this._setElementVisibility(false); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     const visible = | ||||
|       forceVisible || | ||||
|       this.preview || | ||||
|       ignoreConditions || | ||||
|       !this.config?.visibility || | ||||
|       checkConditionsMet(this.config.visibility, this.hass); | ||||
|     this._setElementVisibility(visible); | ||||
|   | ||||
| @@ -1,30 +1,32 @@ | ||||
| import { mdiWaterBoiler } from "@mdi/js"; | ||||
| import type { PropertyValues, TemplateResult } from "lit"; | ||||
| import { html, LitElement } from "lit"; | ||||
| import { customElement, property, state } from "lit/decorators"; | ||||
| import { customElement, property, query, state } from "lit/decorators"; | ||||
| import { styleMap } from "lit/directives/style-map"; | ||||
| import { stopPropagation } from "../../../common/dom/stop_propagation"; | ||||
| import { computeDomain } from "../../../common/entity/compute_domain"; | ||||
| import { stateColorCss } from "../../../common/entity/state_color"; | ||||
| import "../../../components/ha-control-button"; | ||||
| import "../../../components/ha-control-button-group"; | ||||
| import "../../../components/ha-control-select"; | ||||
| import type { ControlSelectOption } from "../../../components/ha-control-select"; | ||||
| import "../../../components/ha-control-slider"; | ||||
| import { UNAVAILABLE } from "../../../data/entity"; | ||||
| import "../../../components/ha-control-select-menu"; | ||||
| import type { HaControlSelectMenu } from "../../../components/ha-control-select-menu"; | ||||
| import "../../../components/ha-list-item"; | ||||
| import type { | ||||
|   OperationMode, | ||||
|   WaterHeaterEntity, | ||||
| } from "../../../data/water_heater"; | ||||
| import { | ||||
|   compareWaterHeaterOperationMode, | ||||
|   computeOperationModeIcon, | ||||
|   compareWaterHeaterOperationMode, | ||||
| } from "../../../data/water_heater"; | ||||
| import { UNAVAILABLE } from "../../../data/entity"; | ||||
| import type { HomeAssistant } from "../../../types"; | ||||
| import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types"; | ||||
| import { cardFeatureStyles } from "./common/card-feature-styles"; | ||||
| import { filterModes } from "./common/filter-modes"; | ||||
| import type { | ||||
|   LovelaceCardFeatureContext, | ||||
|   WaterHeaterOperationModesCardFeatureConfig, | ||||
|   LovelaceCardFeatureContext, | ||||
| } from "./types"; | ||||
|  | ||||
| export const supportsWaterHeaterOperationModesCardFeature = ( | ||||
| @@ -52,6 +54,9 @@ class HuiWaterHeaterOperationModeCardFeature | ||||
|  | ||||
|   @state() _currentOperationMode?: OperationMode; | ||||
|  | ||||
|   @query("ha-control-select-menu", true) | ||||
|   private _haSelect?: HaControlSelectMenu; | ||||
|  | ||||
|   private get _stateObj() { | ||||
|     if (!this.hass || !this.context || !this.context.entity_id) { | ||||
|       return undefined; | ||||
| @@ -97,8 +102,23 @@ class HuiWaterHeaterOperationModeCardFeature | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   protected updated(changedProps: PropertyValues) { | ||||
|     super.updated(changedProps); | ||||
|     if (this._haSelect && changedProps.has("hass")) { | ||||
|       const oldHass = changedProps.get("hass") as HomeAssistant | undefined; | ||||
|       if ( | ||||
|         this.hass && | ||||
|         this.hass.formatEntityAttributeValue !== | ||||
|           oldHass?.formatEntityAttributeValue | ||||
|       ) { | ||||
|         this._haSelect.layoutOptions(); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private async _valueChanged(ev: CustomEvent) { | ||||
|     const mode = (ev.detail as any).value as OperationMode; | ||||
|     const mode = | ||||
|       (ev.detail as any).value ?? ((ev.target as any).value as OperationMode); | ||||
|  | ||||
|     if (mode === this._stateObj!.state) return; | ||||
|  | ||||
| @@ -143,9 +163,48 @@ class HuiWaterHeaterOperationModeCardFeature | ||||
|     ).map<ControlSelectOption>((mode) => ({ | ||||
|       value: mode, | ||||
|       label: this.hass!.formatEntityState(this._stateObj!, mode), | ||||
|       path: computeOperationModeIcon(mode as OperationMode), | ||||
|       icon: html` | ||||
|         <ha-svg-icon | ||||
|           slot="graphic" | ||||
|           .path=${computeOperationModeIcon(mode as OperationMode)} | ||||
|         ></ha-svg-icon> | ||||
|       `, | ||||
|     })); | ||||
|  | ||||
|     if (this._config.style === "dropdown") { | ||||
|       return html` | ||||
|         <ha-control-select-menu | ||||
|           show-arrow | ||||
|           hide-label | ||||
|           .label=${this.hass.localize("ui.card.water_heater.mode")} | ||||
|           .value=${this._currentOperationMode} | ||||
|           .disabled=${this._stateObj.state === UNAVAILABLE} | ||||
|           fixedMenuPosition | ||||
|           naturalMenuWidth | ||||
|           @selected=${this._valueChanged} | ||||
|           @closed=${stopPropagation} | ||||
|         > | ||||
|           ${this._currentOperationMode | ||||
|             ? html` | ||||
|                 <ha-svg-icon | ||||
|                   slot="icon" | ||||
|                   .path=${computeOperationModeIcon(this._currentOperationMode)} | ||||
|                 ></ha-svg-icon> | ||||
|               ` | ||||
|             : html` | ||||
|                 <ha-svg-icon slot="icon" .path=${mdiWaterBoiler}></ha-svg-icon> | ||||
|               `} | ||||
|           ${options.map( | ||||
|             (option) => html` | ||||
|               <ha-list-item .value=${option.value} graphic="icon"> | ||||
|                 ${option.icon}${option.label} | ||||
|               </ha-list-item> | ||||
|             ` | ||||
|           )} | ||||
|         </ha-control-select-menu> | ||||
|       `; | ||||
|     } | ||||
|  | ||||
|     return html` | ||||
|       <ha-control-select | ||||
|         .options=${options} | ||||
|   | ||||
| @@ -140,6 +140,7 @@ export interface ToggleCardFeatureConfig { | ||||
|  | ||||
| export interface WaterHeaterOperationModesCardFeatureConfig { | ||||
|   type: "water-heater-operation-modes"; | ||||
|   style?: "dropdown" | "icons"; | ||||
|   operation_modes?: OperationMode[]; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -16,6 +16,7 @@ import type { | ||||
| } from "../../card-features/types"; | ||||
| import type { LovelaceCardFeatureEditor } from "../../types"; | ||||
| import { compareWaterHeaterOperationMode } from "../../../../data/water_heater"; | ||||
| import type { LocalizeFunc } from "../../../../common/translations/localize"; | ||||
|  | ||||
| type WaterHeaterOperationModesCardFeatureData = | ||||
|   WaterHeaterOperationModesCardFeatureConfig & { | ||||
| @@ -39,11 +40,27 @@ export class HuiWaterHeaterOperationModesCardFeatureEditor | ||||
|  | ||||
|   private _schema = memoizeOne( | ||||
|     ( | ||||
|       localize: LocalizeFunc, | ||||
|       formatEntityState: FormatEntityStateFunc, | ||||
|       stateObj: HassEntity | undefined, | ||||
|       customizeModes: boolean | ||||
|     ) => | ||||
|       [ | ||||
|         { | ||||
|           name: "style", | ||||
|           selector: { | ||||
|             select: { | ||||
|               multiple: false, | ||||
|               mode: "list", | ||||
|               options: ["dropdown", "icons"].map((mode) => ({ | ||||
|                 value: mode, | ||||
|                 label: localize( | ||||
|                   `ui.panel.lovelace.editor.features.types.water-heater-operation-modes.style_list.${mode}` | ||||
|                 ), | ||||
|               })), | ||||
|             }, | ||||
|           }, | ||||
|         }, | ||||
|         { | ||||
|           name: "customize_modes", | ||||
|           selector: { | ||||
| @@ -85,11 +102,13 @@ export class HuiWaterHeaterOperationModesCardFeatureEditor | ||||
|       : undefined; | ||||
|  | ||||
|     const data: WaterHeaterOperationModesCardFeatureData = { | ||||
|       style: "icons", | ||||
|       ...this._config, | ||||
|       customize_modes: this._config.operation_modes !== undefined, | ||||
|     }; | ||||
|  | ||||
|     const schema = this._schema( | ||||
|       this.hass.localize, | ||||
|       this.hass.formatEntityState, | ||||
|       stateObj, | ||||
|       data.customize_modes | ||||
| @@ -131,6 +150,7 @@ export class HuiWaterHeaterOperationModesCardFeatureEditor | ||||
|   ) => { | ||||
|     switch (schema.name) { | ||||
|       case "operation_modes": | ||||
|       case "style": | ||||
|       case "customize_modes": | ||||
|         return this.hass!.localize( | ||||
|           `ui.panel.lovelace.editor.features.types.water-heater-operation-modes.${schema.name}` | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import { object, string, any } from "superstruct"; | ||||
| import { object, string, any, optional, boolean } from "superstruct"; | ||||
|  | ||||
| export const baseLovelaceBadgeConfig = object({ | ||||
|   type: string(), | ||||
|   visibility: any(), | ||||
|   disabled: optional(boolean()), | ||||
| }); | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import { object, string, any } from "superstruct"; | ||||
| import { object, string, any, optional, boolean } from "superstruct"; | ||||
|  | ||||
| export const baseLovelaceCardConfig = object({ | ||||
|   type: string(), | ||||
| @@ -6,4 +6,5 @@ export const baseLovelaceCardConfig = object({ | ||||
|   layout_options: any(), | ||||
|   grid_options: any(), | ||||
|   visibility: any(), | ||||
|   disabled: optional(boolean()), | ||||
| }); | ||||
|   | ||||
| @@ -4,13 +4,14 @@ import { isComponentLoaded } from "../../../../common/config/is_component_loaded | ||||
| import type { LovelaceSectionConfig } from "../../../../data/lovelace/config/section"; | ||||
| import { getCommonControlUsagePrediction } from "../../../../data/usage_prediction"; | ||||
| import type { HomeAssistant } from "../../../../types"; | ||||
| import type { TileCardConfig } from "../../cards/types"; | ||||
| import type { HeadingCardConfig, TileCardConfig } from "../../cards/types"; | ||||
|  | ||||
| const DEFAULT_LIMIT = 8; | ||||
|  | ||||
| export interface CommonControlSectionStrategyConfig { | ||||
|   type: "common-controls"; | ||||
|   title?: string; | ||||
|   icon?: string; | ||||
|   limit?: number; | ||||
|   exclude_entities?: string[]; | ||||
|   hide_empty?: boolean; | ||||
| @@ -31,7 +32,8 @@ export class CommonControlsSectionStrategy extends ReactiveElement { | ||||
|       section.cards?.push({ | ||||
|         type: "heading", | ||||
|         heading: config.title, | ||||
|       }); | ||||
|         icon: config.icon, | ||||
|       } satisfies HeadingCardConfig); | ||||
|     } | ||||
|  | ||||
|     if (!isComponentLoaded(hass, "usage_prediction")) { | ||||
| @@ -46,7 +48,9 @@ export class CommonControlsSectionStrategy extends ReactiveElement { | ||||
|     } | ||||
|  | ||||
|     const predictedCommonControl = await getCommonControlUsagePrediction(hass); | ||||
|     let predictedEntities = predictedCommonControl.entities; | ||||
|     let predictedEntities = predictedCommonControl.entities.filter( | ||||
|       (entity) => entity in hass.states | ||||
|     ); | ||||
|  | ||||
|     if (config.exclude_entities) { | ||||
|       predictedEntities = predictedEntities.filter( | ||||
|   | ||||
| @@ -14,7 +14,15 @@ import { | ||||
|   polyfillTimeZoneData, | ||||
| } from "./locale-data-polyfill"; | ||||
|  | ||||
| let polyfilled = false; | ||||
|  | ||||
| const _polyfillTimeZoneData = polyfillTimeZoneData; | ||||
|  | ||||
| const polyfillIntl = async () => { | ||||
|   if (polyfilled) { | ||||
|     return; | ||||
|   } | ||||
|   polyfilled = true; | ||||
|   const locale = getLocalLanguage(); | ||||
|   const polyfills: Promise<unknown>[] = []; | ||||
|   if (shouldPolyfillGetCanonicalLocales()) { | ||||
| @@ -26,7 +34,7 @@ const polyfillIntl = async () => { | ||||
|   if (shouldPolyfillDateTimeFormat(locale)) { | ||||
|     polyfills.push( | ||||
|       import("@formatjs/intl-datetimeformat/polyfill-force").then(() => | ||||
|         polyfillTimeZoneData() | ||||
|         _polyfillTimeZoneData() | ||||
|       ) | ||||
|     ); | ||||
|   } | ||||
| @@ -58,7 +66,7 @@ const polyfillIntl = async () => { | ||||
|   if (polyfills.length === 0) { | ||||
|     return; | ||||
|   } | ||||
|   await Promise.all(polyfills).then(() => | ||||
|   await Promise.allSettled(polyfills).then(() => | ||||
|     // Load the default language | ||||
|     polyfillLocaleData(locale) | ||||
|   ); | ||||
|   | ||||
| @@ -273,6 +273,9 @@ export const colorStyles = css` | ||||
|     --material-background-color: var(--card-background-color); | ||||
|     --material-secondary-background-color: var(--secondary-background-color); | ||||
|     --material-secondary-text-color: var(--secondary-text-color); | ||||
|  | ||||
|     /* Color for Home Assistant input controls e.g. slider, radio buttons */ | ||||
|     --ha-control-color: var(--primary-color); | ||||
|   } | ||||
| `; | ||||
|  | ||||
|   | ||||
| @@ -19,6 +19,32 @@ export const coreStyles = css` | ||||
|     --ha-border-radius-pill: 9999px; | ||||
|     --ha-border-radius-circle: 50%; | ||||
|     --ha-border-radius-square: 0; | ||||
| <<<<<<< HEAD | ||||
| ======= | ||||
|  | ||||
|     /* Spacing */ | ||||
|     --ha-space-0: 0px; | ||||
|     --ha-space-1: 4px; | ||||
|     --ha-space-2: 8px; | ||||
|     --ha-space-3: 12px; | ||||
|     --ha-space-4: 16px; | ||||
|     --ha-space-5: 20px; | ||||
|     --ha-space-6: 24px; | ||||
|     --ha-space-7: 28px; | ||||
|     --ha-space-8: 32px; | ||||
|     --ha-space-9: 36px; | ||||
|     --ha-space-10: 40px; | ||||
|     --ha-space-11: 44px; | ||||
|     --ha-space-12: 48px; | ||||
|     --ha-space-13: 52px; | ||||
|     --ha-space-14: 56px; | ||||
|     --ha-space-15: 60px; | ||||
|     --ha-space-16: 64px; | ||||
|     --ha-space-17: 68px; | ||||
|     --ha-space-18: 72px; | ||||
|     --ha-space-19: 76px; | ||||
|     --ha-space-20: 80px; | ||||
| >>>>>>> 994c1b575 (Fix intl polyfill loading (#27261)) | ||||
|   } | ||||
| `; | ||||
|  | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| import scrollLockStyles from "@home-assistant/webawesome/dist/styles/utilities/scroll-lock.css.js"; | ||||
| import { css } from "lit"; | ||||
| import { extractDerivedVars } from "../../common/style/derived-css-vars"; | ||||
|  | ||||
| @@ -18,6 +19,8 @@ export const waMainStyles = css` | ||||
|     --wa-border-width-l: var(--ha-border-radius-l); | ||||
|     --wa-space-xl: 32px; | ||||
|   } | ||||
|  | ||||
|   ${scrollLockStyles} | ||||
| `; | ||||
|  | ||||
| export const waMainDerivedVariables = extractDerivedVars(waMainStyles); | ||||
|   | ||||
| @@ -5365,7 +5365,7 @@ | ||||
|           "person_not_found_title": "Person not found", | ||||
|           "person_not_found": "We couldn't find the person you were trying to edit.", | ||||
|           "detail": { | ||||
|             "new_person": "New person", | ||||
|             "new_person": "Add person", | ||||
|             "name": "Name", | ||||
|             "name_error_msg": "Name is required", | ||||
|             "linked_user": "Linked user", | ||||
| @@ -5376,7 +5376,6 @@ | ||||
|             "device_tracker_picked": "Track device", | ||||
|             "device_tracker_pick": "Pick device to track", | ||||
|             "delete": "Delete", | ||||
|             "create": "Create", | ||||
|             "update": "Update", | ||||
|             "confirm_delete_user_title": "Delete user account", | ||||
|             "confirm_delete_user_text": "The user account for ''{name}'' will be permanently deleted. You can still track the user, but the person will no longer be able to log in.", | ||||
| @@ -8218,7 +8217,12 @@ | ||||
|               "water-heater-operation-modes": { | ||||
|                 "label": "Water heater operation modes", | ||||
|                 "operation_modes": "Operation modes", | ||||
|                 "customize_modes": "Customize operation modes" | ||||
|                 "customize_modes": "Customize operation modes", | ||||
|                 "style": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style%]", | ||||
|                 "style_list": { | ||||
|                   "dropdown": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style_list::dropdown%]", | ||||
|                   "icons": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style_list::icons%]" | ||||
|                 } | ||||
|               }, | ||||
|               "lawn-mower-commands": { | ||||
|                 "label": "Lawn mower commands", | ||||
|   | ||||
							
								
								
									
										38
									
								
								yarn.lock
									
									
									
									
									
								
							
							
						
						
									
										38
									
								
								yarn.lock
									
									
									
									
									
								
							| @@ -1351,10 +1351,10 @@ __metadata: | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "@ctrl/tinycolor@npm:^4.1.0": | ||||
|   version: 4.2.0 | ||||
|   resolution: "@ctrl/tinycolor@npm:4.2.0" | ||||
|   checksum: 10/1be14de7d7e8184c0bc5c8d7e3486cc8186e6702e8ca899c7239f328bb1df9a15d1575e2af7b4c6ba020727fa78f5a9f887555971f30a2890cece9e4253a9d3a | ||||
| "@ctrl/tinycolor@npm:4.1.0": | ||||
|   version: 4.1.0 | ||||
|   resolution: "@ctrl/tinycolor@npm:4.1.0" | ||||
|   checksum: 10/e64569399139ef0abd2eb0ec9fb7267dfd7820f7ad7d4567a63e5fc35e5cfdcb8ecdb3bad65cb9244b47ba6c77bc51085826c00e981acf263a3221dc89343adc | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| @@ -1940,11 +1940,11 @@ __metadata: | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "@home-assistant/webawesome@npm:3.0.0-beta.4.ha.3": | ||||
|   version: 3.0.0-beta.4.ha.3 | ||||
|   resolution: "@home-assistant/webawesome@npm:3.0.0-beta.4.ha.3" | ||||
| "@home-assistant/webawesome@npm:3.0.0-beta.6.ha.0": | ||||
|   version: 3.0.0-beta.6.ha.0 | ||||
|   resolution: "@home-assistant/webawesome@npm:3.0.0-beta.6.ha.0" | ||||
|   dependencies: | ||||
|     "@ctrl/tinycolor": "npm:^4.1.0" | ||||
|     "@ctrl/tinycolor": "npm:4.1.0" | ||||
|     "@floating-ui/dom": "npm:^1.6.13" | ||||
|     "@lit/react": "npm:^1.0.8" | ||||
|     "@shoelace-style/animations": "npm:^1.2.0" | ||||
| @@ -1953,8 +1953,7 @@ __metadata: | ||||
|     lit: "npm:^3.2.1" | ||||
|     nanoid: "npm:^5.1.5" | ||||
|     qr-creator: "npm:^1.0.0" | ||||
|     style-observer: "npm:^0.0.7" | ||||
|   checksum: 10/b9241821ed471ccbad86b0ea4697a2d41395f05fdc26f46e5edbc7f6b5eeab5d248251ef702326312ded00d5bf850ce0dcdcf7cd5e2e542b9d9cb9a84f3726da | ||||
|   checksum: 10/ec9d74585b544e5755f7b2644a0d7f9318b5776bedf51430c8f8729918fddb6e54cce46acace674960383385362846cc4c0f2da5245fa622bce8c54733a31865 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| @@ -4491,10 +4490,10 @@ __metadata: | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "@types/chromecast-caf-receiver@npm:6.0.24": | ||||
|   version: 6.0.24 | ||||
|   resolution: "@types/chromecast-caf-receiver@npm:6.0.24" | ||||
|   checksum: 10/1f2b95e8a15dbb36d5328895229d4a5cb255b33e62d46335bd6ed75e16aa9ea6a7d765a64ae120d19b3134fb3e51e9547d2544c7277f7bffe0bf0b3999f026da | ||||
| "@types/chromecast-caf-receiver@npm:6.0.22": | ||||
|   version: 6.0.22 | ||||
|   resolution: "@types/chromecast-caf-receiver@npm:6.0.22" | ||||
|   checksum: 10/6c51cb52527776ddfa187a261b88184c98bdd61c129dd8719cba213894d565cf69073734d6473696ffd60a768f6fb5a3fe9932693f43174fbc5e7af201db8a90 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| @@ -9397,7 +9396,7 @@ __metadata: | ||||
|     "@fullcalendar/list": "npm:6.1.19" | ||||
|     "@fullcalendar/luxon3": "npm:6.1.19" | ||||
|     "@fullcalendar/timegrid": "npm:6.1.19" | ||||
|     "@home-assistant/webawesome": "npm:3.0.0-beta.4.ha.3" | ||||
|     "@home-assistant/webawesome": "npm:3.0.0-beta.6.ha.0" | ||||
|     "@lezer/highlight": "npm:1.2.1" | ||||
|     "@lit-labs/motion": "npm:1.0.9" | ||||
|     "@lit-labs/observers": "npm:2.0.6" | ||||
| @@ -9442,7 +9441,7 @@ __metadata: | ||||
|     "@tsparticles/engine": "npm:3.9.1" | ||||
|     "@tsparticles/preset-links": "npm:3.2.0" | ||||
|     "@types/babel__plugin-transform-runtime": "npm:7.9.5" | ||||
|     "@types/chromecast-caf-receiver": "npm:6.0.24" | ||||
|     "@types/chromecast-caf-receiver": "npm:6.0.22" | ||||
|     "@types/chromecast-caf-sender": "npm:1.0.11" | ||||
|     "@types/color-name": "npm:2.0.0" | ||||
|     "@types/culori": "npm:4.0.1" | ||||
| @@ -14003,13 +14002,6 @@ __metadata: | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "style-observer@npm:^0.0.7": | ||||
|   version: 0.0.7 | ||||
|   resolution: "style-observer@npm:0.0.7" | ||||
|   checksum: 10/bb57f98bae4463c1e1b57234f8ffe72ec0de27fb08b032c1919910129c210aacd1ddd615432b9453d491e10d3b719cf6c2a68a97165ca55d6fc9b86c0fca37fb | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "style-observer@npm:^0.0.8": | ||||
|   version: 0.0.8 | ||||
|   resolution: "style-observer@npm:0.0.8" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user