From b02e8bf270b15ce855e9b2b7758722ebb70f8a4f Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Fri, 12 Sep 2025 09:53:05 +0100 Subject: [PATCH 001/125] Autofocus element with dialogInitialFocus --- src/components/ha-dialog.ts | 240 ++++++++++++++++++++++-------------- 1 file changed, 145 insertions(+), 95 deletions(-) diff --git a/src/components/ha-dialog.ts b/src/components/ha-dialog.ts index 60cd39c7e57b..5a6c2540b02f 100644 --- a/src/components/ha-dialog.ts +++ b/src/components/ha-dialog.ts @@ -5,6 +5,7 @@ import type { TemplateResult } from "lit"; import { css, html } from "lit"; import { customElement } from "lit/decorators"; import { FOCUS_TARGET } from "../dialogs/make-dialog-manager"; +import { nextRender } from "../common/util/render-status"; import type { HomeAssistant } from "../types"; import "./ha-icon-button"; @@ -33,110 +34,159 @@ export class HaDialog extends DialogBase { this.contentElement?.scrollTo(x, y); } - protected renderHeading() { - return html` ${super.renderHeading()} `; + private _handleWaShow = () => { + this._internalOpen = true; + this.dispatchEvent( + new CustomEvent("opened", { bubbles: true, composed: true }) + ); + this._handleDialogInitialFocus(); + }; + + private _updateScrolledAttribute() { + if (!this.contentElement) return; + this.toggleAttribute("scrolled", this.contentElement.scrollTop !== 0); } - protected firstUpdated(): void { - super.firstUpdated(); - this.suppressDefaultPressSelector = [ - this.suppressDefaultPressSelector, - SUPPRESS_DEFAULT_PRESS_SELECTOR, - ].join(", "); - this._updateScrolledAttribute(); - this.contentElement?.addEventListener("scroll", this._onScroll, { - passive: true, + private _handleDialogInitialFocus() { + const candidates = this.querySelectorAll("[dialogInitialFocus]"); + if (!candidates.length) return; + + const computeFocusTarget = (el: Element): HTMLElement | null => { + if (!(el instanceof HTMLElement)) return null; + // If element itself is focusable or implements focus(), use it + if (typeof el.focus === "function") { + return el; + } + const focusableSelector = [ + "button:not([disabled])", + "input:not([disabled])", + "select:not([disabled])", + "textarea:not([disabled])", + "[tabindex]:not([tabindex='-1'])", + ].join(","); + return ( + (el.querySelector(focusableSelector) as HTMLElement | null) || null + ); + }; + + const el = candidates[0]; + const focusTarget = computeFocusTarget(el); + if (!focusTarget) return; + + nextRender().then(() => { + try { + (focusTarget as HTMLElement).focus({ preventScroll: true }); + } catch (_e) { + (focusTarget as HTMLElement).focus(); + } }); } - disconnectedCallback(): void { - super.disconnectedCallback(); - this.contentElement.removeEventListener("scroll", this._onScroll); + protected render() { + return html` + + ${this.heading ? html`
${this.heading}
` : ""} + + ${this.hideActions + ? nothing + : html` + + + `} +
+ `; } - private _onScroll = () => { - this._updateScrolledAttribute(); - }; + static override styles = css` + :host { + --dialog-z-index: 8; + --dialog-backdrop-filter: none; + --dialog-box-shadow: none; + --ha-font-weight-normal: 400; + --justify-action-buttons: flex-end; + --vertical-align-dialog: center; + --dialog-content-position: relative; + --dialog-content-padding: 24px; + --dialog-surface-position: relative; + --dialog-surface-top: auto; + --dialog-surface-margin-top: auto; + --ha-dialog-border-radius: 24px; + --ha-dialog-surface-backdrop-filter: none; + --ha-dialog-surface-background: var(--mdc-theme-surface, #fff); + --secondary-action-button-flex: unset; + --primary-action-button-flex: unset; + } - private _updateScrolledAttribute() { - if (!this.contentElement) return; - this.toggleAttribute("scrolled", this.contentElement.scrollTop !== 0); - } + wa-dialog { + --spacing: var(--dialog-content-padding, 24px); + --width: calc( + var(--mdc-dialog-min-width, 100vw) - var( + --safe-area-inset-left, + 0 + ) - var(--safe-area-inset-right, 0) + ); + --show-duration: 200ms; + --hide-duration: 200ms; + z-index: var(--dialog-z-index, 8); + /* Override Web Awesome's surface color with Home Assistant theme */ + --wa-color-surface-raised: var( + --ha-dialog-surface-background, + var(--mdc-theme-surface, #fff) + ); + /* Set border radius */ + --wa-panel-border-radius: var(--ha-dialog-border-radius, 24px); + } - static override styles = [ - styles, - css` - :host([scrolled]) ::slotted(ha-dialog-header) { - border-bottom: 1px solid - var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12)); - } - .mdc-dialog { - --mdc-dialog-scroll-divider-color: var( - --dialog-scroll-divider-color, - var(--divider-color) - ); - z-index: var(--dialog-z-index, 8); - -webkit-backdrop-filter: var( - --ha-dialog-scrim-backdrop-filter, - var(--dialog-backdrop-filter, none) - ); - backdrop-filter: var( - --ha-dialog-scrim-backdrop-filter, - var(--dialog-backdrop-filter, none) - ); - --mdc-dialog-box-shadow: var(--dialog-box-shadow, none); - --mdc-typography-headline6-font-weight: var(--ha-font-weight-normal); - --mdc-typography-headline6-font-size: 1.574rem; - } - .mdc-dialog__actions { - justify-content: var(--justify-action-buttons, flex-end); - padding: 12px 16px 16px 16px; - } - .mdc-dialog__actions span:nth-child(1) { - flex: var(--secondary-action-button-flex, unset); - } - .mdc-dialog__actions span:nth-child(2) { - flex: var(--primary-action-button-flex, unset); - } - .mdc-dialog__container { - align-items: var(--vertical-align-dialog, center); - } - .mdc-dialog__title { - padding: 16px 16px 0 16px; - } - .mdc-dialog__title:has(span) { - padding: 12px 12px 0; - } - .mdc-dialog__title::before { - content: unset; - } - .mdc-dialog .mdc-dialog__content { - position: var(--dialog-content-position, relative); - padding: var(--dialog-content-padding, 24px); - } - :host([hideactions]) .mdc-dialog .mdc-dialog__content { - padding-bottom: var(--dialog-content-padding, 24px); - } - .mdc-dialog .mdc-dialog__surface { - position: var(--dialog-surface-position, relative); - top: var(--dialog-surface-top); - margin-top: var(--dialog-surface-margin-top); - min-width: var(--mdc-dialog-min-width, 100vw); - min-height: var(--mdc-dialog-min-height, auto); - border-radius: var( - --ha-dialog-border-radius, - var(--ha-border-radius-3xl) - ); - -webkit-backdrop-filter: var(--ha-dialog-surface-backdrop-filter, none); - backdrop-filter: var(--ha-dialog-surface-backdrop-filter, none); - background: var( - --ha-dialog-surface-background, - var(--mdc-theme-surface, #fff) - ); - } - :host([flexContent]) .mdc-dialog .mdc-dialog__content { - display: flex; - flex-direction: column; + wa-dialog::part(header) { + border-bottom: 1px solid var(--divider-color, rgba(0, 0, 0, 0.12)); + padding: 24px 24px 0 24px; + } + + wa-dialog::part(title) { + margin: 0; + margin-bottom: 8px; + color: var(--mdc-dialog-heading-ink-color, rgba(0, 0, 0, 0.87)); + font-size: var(--mdc-typography-headline6-font-size, 1.574rem); + line-height: var(--mdc-typography-headline6-line-height, 2rem); + font-weight: var( + --mdc-typography-headline6-font-weight, + var(--ha-font-weight-normal) + ); + letter-spacing: var(--mdc-typography-headline6-letter-spacing, 0.0125em); + text-decoration: var(--mdc-typography-headline6-text-decoration, inherit); + text-transform: var(--mdc-typography-headline6-text-transform, inherit); + } + + wa-dialog::part(body) { + position: var(--dialog-content-position, relative); + padding: var(--dialog-content-padding, 24px); + } + + wa-dialog::part(footer) { + justify-content: flex-end; + padding: 12px 16px 16px 16px; + } + + :host([flexContent]) wa-dialog::part(body) { + display: flex; + flex-direction: column; + } + + :host([hideActions]) wa-dialog::part(body) { + padding-bottom: var(--dialog-content-padding, 24px); + } + + @media all and (max-width: 450px), all and (max-height: 500px) { + wa-dialog { + --width: 100vw; } @media all and (max-width: 450px), all and (max-height: 500px) { From b7fd7002f26c1843c4cf3ff4e1e04cc84d8ff042 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Fri, 12 Sep 2025 11:27:18 +0100 Subject: [PATCH 002/125] Ensure dialog follows safe areas --- src/components/ha-dialog.ts | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/components/ha-dialog.ts b/src/components/ha-dialog.ts index 5a6c2540b02f..06af7a95fa47 100644 --- a/src/components/ha-dialog.ts +++ b/src/components/ha-dialog.ts @@ -175,6 +175,15 @@ export class HaDialog extends DialogBase { padding: 12px 16px 16px 16px; } + wa-dialog::part(dialog) { + max-width: calc( + 100vw - var(--safe-area-inset-left, 0) - var(--safe-area-inset-right, 0) + ); + max-height: calc( + 100vh - var(--safe-area-inset-top, 0) - var(--safe-area-inset-bottom, 0) + ); + } + :host([flexContent]) wa-dialog::part(body) { display: flex; flex-direction: column; @@ -186,7 +195,20 @@ export class HaDialog extends DialogBase { @media all and (max-width: 450px), all and (max-height: 500px) { wa-dialog { - --width: 100vw; + --width: calc( + 100vw - var(--safe-area-inset-left, 0px) - var( + --safe-area-inset-right, + 0px + ) + ); + } + wa-dialog::part(dialog) { + min-height: calc( + 100vh - var(--safe-area-inset-top, 0px) - var( + --safe-area-inset-bottom, + 0px + ) + ); } @media all and (max-width: 450px), all and (max-height: 500px) { From 5a76ac269e634db4622bbd57a80f904237ca0eeb Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Fri, 12 Sep 2025 11:40:25 +0100 Subject: [PATCH 003/125] Restore --- src/components/ha-dialog.ts | 259 +++++++++++++----------------------- 1 file changed, 95 insertions(+), 164 deletions(-) diff --git a/src/components/ha-dialog.ts b/src/components/ha-dialog.ts index 06af7a95fa47..d1b9fb56a171 100644 --- a/src/components/ha-dialog.ts +++ b/src/components/ha-dialog.ts @@ -5,7 +5,6 @@ import type { TemplateResult } from "lit"; import { css, html } from "lit"; import { customElement } from "lit/decorators"; import { FOCUS_TARGET } from "../dialogs/make-dialog-manager"; -import { nextRender } from "../common/util/render-status"; import type { HomeAssistant } from "../types"; import "./ha-icon-button"; @@ -34,182 +33,114 @@ export class HaDialog extends DialogBase { this.contentElement?.scrollTo(x, y); } - private _handleWaShow = () => { - this._internalOpen = true; - this.dispatchEvent( - new CustomEvent("opened", { bubbles: true, composed: true }) - ); - this._handleDialogInitialFocus(); - }; - - private _updateScrolledAttribute() { - if (!this.contentElement) return; - this.toggleAttribute("scrolled", this.contentElement.scrollTop !== 0); + protected renderHeading() { + return html` ${super.renderHeading()} `; } - private _handleDialogInitialFocus() { - const candidates = this.querySelectorAll("[dialogInitialFocus]"); - if (!candidates.length) return; - - const computeFocusTarget = (el: Element): HTMLElement | null => { - if (!(el instanceof HTMLElement)) return null; - // If element itself is focusable or implements focus(), use it - if (typeof el.focus === "function") { - return el; - } - const focusableSelector = [ - "button:not([disabled])", - "input:not([disabled])", - "select:not([disabled])", - "textarea:not([disabled])", - "[tabindex]:not([tabindex='-1'])", - ].join(","); - return ( - (el.querySelector(focusableSelector) as HTMLElement | null) || null - ); - }; - - const el = candidates[0]; - const focusTarget = computeFocusTarget(el); - if (!focusTarget) return; - - nextRender().then(() => { - try { - (focusTarget as HTMLElement).focus({ preventScroll: true }); - } catch (_e) { - (focusTarget as HTMLElement).focus(); - } + protected firstUpdated(): void { + super.firstUpdated(); + this.suppressDefaultPressSelector = [ + this.suppressDefaultPressSelector, + SUPPRESS_DEFAULT_PRESS_SELECTOR, + ].join(", "); + this._updateScrolledAttribute(); + this.contentElement?.addEventListener("scroll", this._onScroll, { + passive: true, }); } - protected render() { - return html` - - ${this.heading ? html`
${this.heading}
` : ""} - - ${this.hideActions - ? nothing - : html` - - - `} -
- `; + disconnectedCallback(): void { + super.disconnectedCallback(); + this.contentElement.removeEventListener("scroll", this._onScroll); } - static override styles = css` - :host { - --dialog-z-index: 8; - --dialog-backdrop-filter: none; - --dialog-box-shadow: none; - --ha-font-weight-normal: 400; - --justify-action-buttons: flex-end; - --vertical-align-dialog: center; - --dialog-content-position: relative; - --dialog-content-padding: 24px; - --dialog-surface-position: relative; - --dialog-surface-top: auto; - --dialog-surface-margin-top: auto; - --ha-dialog-border-radius: 24px; - --ha-dialog-surface-backdrop-filter: none; - --ha-dialog-surface-background: var(--mdc-theme-surface, #fff); - --secondary-action-button-flex: unset; - --primary-action-button-flex: unset; - } - - wa-dialog { - --spacing: var(--dialog-content-padding, 24px); - --width: calc( - var(--mdc-dialog-min-width, 100vw) - var( - --safe-area-inset-left, - 0 - ) - var(--safe-area-inset-right, 0) - ); - --show-duration: 200ms; - --hide-duration: 200ms; - z-index: var(--dialog-z-index, 8); - /* Override Web Awesome's surface color with Home Assistant theme */ - --wa-color-surface-raised: var( - --ha-dialog-surface-background, - var(--mdc-theme-surface, #fff) - ); - /* Set border radius */ - --wa-panel-border-radius: var(--ha-dialog-border-radius, 24px); - } - - wa-dialog::part(header) { - border-bottom: 1px solid var(--divider-color, rgba(0, 0, 0, 0.12)); - padding: 24px 24px 0 24px; - } - - wa-dialog::part(title) { - margin: 0; - margin-bottom: 8px; - color: var(--mdc-dialog-heading-ink-color, rgba(0, 0, 0, 0.87)); - font-size: var(--mdc-typography-headline6-font-size, 1.574rem); - line-height: var(--mdc-typography-headline6-line-height, 2rem); - font-weight: var( - --mdc-typography-headline6-font-weight, - var(--ha-font-weight-normal) - ); - letter-spacing: var(--mdc-typography-headline6-letter-spacing, 0.0125em); - text-decoration: var(--mdc-typography-headline6-text-decoration, inherit); - text-transform: var(--mdc-typography-headline6-text-transform, inherit); - } - - wa-dialog::part(body) { - position: var(--dialog-content-position, relative); - padding: var(--dialog-content-padding, 24px); - } - - wa-dialog::part(footer) { - justify-content: flex-end; - padding: 12px 16px 16px 16px; - } - - wa-dialog::part(dialog) { - max-width: calc( - 100vw - var(--safe-area-inset-left, 0) - var(--safe-area-inset-right, 0) - ); - max-height: calc( - 100vh - var(--safe-area-inset-top, 0) - var(--safe-area-inset-bottom, 0) - ); - } - - :host([flexContent]) wa-dialog::part(body) { - display: flex; - flex-direction: column; - } + private _onScroll = () => { + this._updateScrolledAttribute(); + }; - :host([hideActions]) wa-dialog::part(body) { - padding-bottom: var(--dialog-content-padding, 24px); - } + private _updateScrolledAttribute() { + if (!this.contentElement) return; + this.toggleAttribute("scrolled", this.contentElement.scrollTop !== 0); + } - @media all and (max-width: 450px), all and (max-height: 500px) { - wa-dialog { - --width: calc( - 100vw - var(--safe-area-inset-left, 0px) - var( - --safe-area-inset-right, - 0px - ) + static override styles = [ + styles, + css` + :host([scrolled]) ::slotted(ha-dialog-header) { + border-bottom: 1px solid + var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12)); + } + .mdc-dialog { + --mdc-dialog-scroll-divider-color: var( + --dialog-scroll-divider-color, + var(--divider-color) + ); + z-index: var(--dialog-z-index, 8); + -webkit-backdrop-filter: var( + --ha-dialog-scrim-backdrop-filter, + var(--dialog-backdrop-filter, none) ); + backdrop-filter: var( + --ha-dialog-scrim-backdrop-filter, + var(--dialog-backdrop-filter, none) + ); + --mdc-dialog-box-shadow: var(--dialog-box-shadow, none); + --mdc-typography-headline6-font-weight: var(--ha-font-weight-normal); + --mdc-typography-headline6-font-size: 1.574rem; + } + .mdc-dialog__actions { + justify-content: var(--justify-action-buttons, flex-end); + padding: 12px 16px 16px 16px; + } + .mdc-dialog__actions span:nth-child(1) { + flex: var(--secondary-action-button-flex, unset); + } + .mdc-dialog__actions span:nth-child(2) { + flex: var(--primary-action-button-flex, unset); } - wa-dialog::part(dialog) { - min-height: calc( - 100vh - var(--safe-area-inset-top, 0px) - var( - --safe-area-inset-bottom, - 0px - ) + .mdc-dialog__container { + align-items: var(--vertical-align-dialog, center); + padding-top: var(--safe-area-inset-top); + padding-bottom: var(--safe-area-inset-bottom); + } + .mdc-dialog__title { + padding: 16px 16px 0 16px; + } + .mdc-dialog__title:has(span) { + padding: 12px 12px 0; + } + .mdc-dialog__title::before { + content: unset; + } + .mdc-dialog .mdc-dialog__content { + position: var(--dialog-content-position, relative); + padding: var(--dialog-content-padding, 24px); + } + :host([hideactions]) .mdc-dialog .mdc-dialog__content { + padding-bottom: var(--dialog-content-padding, 24px); + } + .mdc-dialog .mdc-dialog__surface { + position: var(--dialog-surface-position, relative); + top: var(--dialog-surface-top); + margin-top: var(--dialog-surface-margin-top); + min-width: calc( + var(--mdc-dialog-min-width, 100vw) - var( + --safe-area-inset-left + ) - var(--safe-area-inset-right) + ); + min-height: var(--mdc-dialog-min-height, auto); + 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( + --ha-dialog-surface-background, + var(--mdc-theme-surface, #fff) ); } + :host([flexContent]) .mdc-dialog .mdc-dialog__content { + display: flex; + flex-direction: column; + } @media all and (max-width: 450px), all and (max-height: 500px) { .mdc-dialog .mdc-dialog__surface { From 542e568ba8fba57945d3e9bc1ec28adb70984b73 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Fri, 12 Sep 2025 11:41:03 +0100 Subject: [PATCH 004/125] Move to new component --- src/components/ha-wa-dialog.ts | 301 +++++++++++++++++++++++++++++++++ 1 file changed, 301 insertions(+) create mode 100644 src/components/ha-wa-dialog.ts diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts new file mode 100644 index 000000000000..3f0e27ad3527 --- /dev/null +++ b/src/components/ha-wa-dialog.ts @@ -0,0 +1,301 @@ +import type { TemplateResult } from "lit"; +import { css, html, LitElement, nothing } from "lit"; +import { customElement, property, query, state } from "lit/decorators"; +import "@home-assistant/webawesome/dist/components/dialog/dialog"; +import { FOCUS_TARGET } from "../dialogs/make-dialog-manager"; +import { nextRender } from "../common/util/render-status"; +import type { HomeAssistant } from "../types"; + +export const createCloseHeading = ( + _hass: HomeAssistant | undefined, + title: string | TemplateResult +) => html` ${title} `; + +@customElement("ha-wa-dialog") +export class HaWaDialog extends LitElement { + protected readonly [FOCUS_TARGET]; + + @property({ type: Boolean, reflect: true }) + public open = false; + + @property({ type: Boolean, reflect: true, attribute: "scrim-click-action" }) + public scrimClickAction = false; + + @property({ type: Boolean, reflect: true, attribute: "hide-actions" }) + public hideActions = false; + + @property({ type: Boolean, reflect: true, attribute: "flex-content" }) + public flexContent = false; + + @property() + public heading?: string | TemplateResult; + + @state() + private _internalOpen = false; + + @query("wa-dialog") + private _waDialog?: any; + + public scrollToPos(x: number, y: number) { + this._waDialog?.scrollTo(x, y); + } + + private _handleWaShow = () => { + this._internalOpen = true; + this.dispatchEvent( + new CustomEvent("opened", { bubbles: true, composed: true }) + ); + this._handleDialogInitialFocus(); + }; + + private _handleWaHide = (event: any) => { + // If scrimClickAction is false, prevent closing on overlay click + if ( + !this.scrimClickAction && + event.detail?.source === this._waDialog?.dialog + ) { + event.preventDefault(); + return; + } + + this._internalOpen = false; + this.open = false; + this.dispatchEvent( + new CustomEvent("closed", { bubbles: true, composed: true }) + ); + }; + + private _handleWaAfterHide = () => { + this.dispatchEvent( + new CustomEvent("closed", { bubbles: true, composed: true }) + ); + }; + + protected updated( + changedProperties: Map + ): void { + super.updated(changedProperties); + + if (changedProperties.has("open")) { + this._internalOpen = this.open; + // Handle dialogInitialFocus translation + if (this.open) { + this._handleDialogInitialFocus(); + } + } + + if (changedProperties.has("scrimClickAction")) { + if (this._waDialog) { + this._waDialog.lightDismiss = this.scrimClickAction; + } + } + } + + private _handleDialogInitialFocus() { + const candidates = this.querySelectorAll("[dialogInitialFocus]"); + if (!candidates.length) return; + + const computeFocusTarget = (el: Element): HTMLElement | null => { + if (!(el instanceof HTMLElement)) return null; + // If element itself is focusable or implements focus(), use it + if (typeof el.focus === "function") { + return el; + } + const focusableSelector = [ + "button:not([disabled])", + "input:not([disabled])", + "select:not([disabled])", + "textarea:not([disabled])", + "[tabindex]:not([tabindex='-1'])", + ].join(","); + return ( + (el.querySelector(focusableSelector) as HTMLElement | null) || null + ); + }; + + const el = candidates[0]; + const focusTarget = computeFocusTarget(el); + if (!focusTarget) return; + + nextRender().then(() => { + try { + (focusTarget as HTMLElement).focus({ preventScroll: true }); + } catch (_e) { + (focusTarget as HTMLElement).focus(); + } + }); + } + + protected render() { + return html` + + ${this.heading ? html`
${this.heading}
` : ""} + + ${this.hideActions + ? nothing + : html` + + + `} +
+ `; + } + + static override styles = css` + :host { + --dialog-z-index: 8; + --dialog-backdrop-filter: none; + --dialog-box-shadow: none; + --ha-font-weight-normal: 400; + --justify-action-buttons: flex-end; + --vertical-align-dialog: center; + --dialog-content-position: relative; + --dialog-content-padding: 24px; + --dialog-surface-position: relative; + --dialog-surface-top: auto; + --dialog-surface-margin-top: auto; + --ha-dialog-border-radius: 24px; + --ha-dialog-surface-backdrop-filter: none; + --ha-dialog-surface-background: var(--mdc-theme-surface, #fff); + --secondary-action-button-flex: unset; + --primary-action-button-flex: unset; + } + + wa-dialog { + --spacing: var(--dialog-content-padding, 24px); + --width: calc( + 100vw - var(--safe-area-inset-left, 0px) - var( + --safe-area-inset-right, + 0px + ) + ); + --show-duration: 200ms; + --hide-duration: 200ms; + z-index: var(--dialog-z-index, 8); + /* Override Web Awesome's surface color with Home Assistant theme */ + --wa-color-surface-raised: var( + --ha-dialog-surface-background, + var(--mdc-theme-surface, #fff) + ); + /* Set border radius */ + --wa-panel-border-radius: var(--ha-dialog-border-radius, 24px); + } + + wa-dialog::part(header) { + border-bottom: 1px solid var(--divider-color, rgba(0, 0, 0, 0.12)); + padding: 24px 24px 0 24px; + } + + wa-dialog::part(title) { + margin: 0; + margin-bottom: 8px; + color: var(--mdc-dialog-heading-ink-color, rgba(0, 0, 0, 0.87)); + font-size: var(--mdc-typography-headline6-font-size, 1.574rem); + line-height: var(--mdc-typography-headline6-line-height, 2rem); + font-weight: var( + --mdc-typography-headline6-font-weight, + var(--ha-font-weight-normal) + ); + letter-spacing: var(--mdc-typography-headline6-letter-spacing, 0.0125em); + text-decoration: var(--mdc-typography-headline6-text-decoration, inherit); + text-transform: var(--mdc-typography-headline6-text-transform, inherit); + } + + wa-dialog::part(body) { + position: var(--dialog-content-position, relative); + padding: var(--dialog-content-padding, 24px); + } + + wa-dialog::part(footer) { + justify-content: flex-end; + padding: 12px 16px 16px 16px; + } + + wa-dialog::part(dialog) { + max-width: calc( + 100vw - var(--safe-area-inset-left, 0px) - var( + --safe-area-inset-right, + 0px + ) + ); + max-height: calc( + 100vh - var(--safe-area-inset-top, 0px) - var( + --safe-area-inset-bottom, + 0px + ) + ); + } + + :host([flexContent]) wa-dialog::part(body) { + display: flex; + flex-direction: column; + } + + :host([hideActions]) wa-dialog::part(body) { + padding-bottom: var(--dialog-content-padding, 24px); + } + + @media all and (max-width: 450px), all and (max-height: 500px) { + wa-dialog { + --width: calc( + 100vw - var(--safe-area-inset-left, 0px) - var( + --safe-area-inset-right, + 0px + ) + ); + } + wa-dialog::part(dialog) { + min-height: calc( + 100vh - var(--safe-area-inset-top, 0px) - var( + --safe-area-inset-bottom, + 0px + ) + ); + } + } + + .header_title { + display: flex; + align-items: center; + direction: var(--direction); + } + + .header_title span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + display: block; + padding-left: 4px; + padding-right: 4px; + margin-right: 12px; + margin-inline-end: 12px; + margin-inline-start: initial; + } + + .header_button { + text-decoration: none; + color: inherit; + inset-inline-start: initial; + inset-inline-end: -12px; + direction: var(--direction); + } + + .hidden { + display: none !important; + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + "ha-wa-dialog": HaWaDialog; + } +} From 6c14bade7e428a4ce347ed533dac96731bbd4260 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Fri, 12 Sep 2025 11:49:51 +0100 Subject: [PATCH 005/125] Use builtin width --- src/components/ha-wa-dialog.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 3f0e27ad3527..61a8f1aacbe3 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -171,12 +171,6 @@ export class HaWaDialog extends LitElement { wa-dialog { --spacing: var(--dialog-content-padding, 24px); - --width: calc( - 100vw - var(--safe-area-inset-left, 0px) - var( - --safe-area-inset-right, - 0px - ) - ); --show-duration: 200ms; --hide-duration: 200ms; z-index: var(--dialog-z-index, 8); @@ -220,6 +214,12 @@ export class HaWaDialog extends LitElement { } wa-dialog::part(dialog) { + min-width: calc( + var(--width, 100vw) - var(--safe-area-inset-left, 0px) - var( + --safe-area-inset-right, + 0px + ) + ); max-width: calc( 100vw - var(--safe-area-inset-left, 0px) - var( --safe-area-inset-right, From 6ceebe17f72f3b20efc7f7892d54a64b0416e552 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Fri, 12 Sep 2025 11:57:54 +0100 Subject: [PATCH 006/125] Square when mobile --- src/components/ha-wa-dialog.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 61a8f1aacbe3..4b559668e11f 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -244,6 +244,9 @@ export class HaWaDialog extends LitElement { } @media all and (max-width: 450px), all and (max-height: 500px) { + :host { + --ha-dialog-border-radius: 0px; + } wa-dialog { --width: calc( 100vw - var(--safe-area-inset-left, 0px) - var( From 4ba7d678a991c2c5d7a751392c6c61c371e053bc Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Fri, 12 Sep 2025 12:20:11 +0100 Subject: [PATCH 007/125] Use custom close button and closure actions --- src/components/ha-wa-dialog.ts | 67 +++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 4b559668e11f..00f4b4b362c6 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -2,14 +2,10 @@ import type { TemplateResult } from "lit"; import { css, html, LitElement, nothing } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import "@home-assistant/webawesome/dist/components/dialog/dialog"; -import { FOCUS_TARGET } from "../dialogs/make-dialog-manager"; +import { mdiClose } from "@mdi/js"; import { nextRender } from "../common/util/render-status"; -import type { HomeAssistant } from "../types"; - -export const createCloseHeading = ( - _hass: HomeAssistant | undefined, - title: string | TemplateResult -) => html` ${title} `; +import { FOCUS_TARGET } from "../dialogs/make-dialog-manager"; +import "./ha-icon-button"; @customElement("ha-wa-dialog") export class HaWaDialog extends LitElement { @@ -48,27 +44,24 @@ export class HaWaDialog extends LitElement { this._handleDialogInitialFocus(); }; - private _handleWaHide = (event: any) => { - // If scrimClickAction is false, prevent closing on overlay click - if ( - !this.scrimClickAction && - event.detail?.source === this._waDialog?.dialog - ) { - event.preventDefault(); - return; - } - + private _handleWaHide = () => { this._internalOpen = false; - this.open = false; this.dispatchEvent( - new CustomEvent("closed", { bubbles: true, composed: true }) + new CustomEvent("closed", { + bubbles: true, + composed: true, + detail: { action: "close" }, + }) ); }; - private _handleWaAfterHide = () => { - this.dispatchEvent( - new CustomEvent("closed", { bubbles: true, composed: true }) - ); + private _onCloseClick = () => { + if (this._waDialog?.hide) { + this._waDialog.hide(); + } else { + this._internalOpen = false; + this._handleWaHide(); + } }; protected updated( @@ -78,7 +71,6 @@ export class HaWaDialog extends LitElement { if (changedProperties.has("open")) { this._internalOpen = this.open; - // Handle dialogInitialFocus translation if (this.open) { this._handleDialogInitialFocus(); } @@ -134,10 +126,22 @@ export class HaWaDialog extends LitElement { .withoutHeader=${!this.heading} @wa-show=${this._handleWaShow} @wa-hide=${this._handleWaHide} - @wa-after-hide=${this._handleWaAfterHide} class=${this.open ? "mdc-dialog--open" : ""} > - ${this.heading ? html`
${this.heading}
` : ""} + ${this.heading + ? html` +
+ + ${this.heading} +
+ ` + : ""} ${this.hideActions ? nothing @@ -174,12 +178,10 @@ export class HaWaDialog extends LitElement { --show-duration: 200ms; --hide-duration: 200ms; z-index: var(--dialog-z-index, 8); - /* Override Web Awesome's surface color with Home Assistant theme */ --wa-color-surface-raised: var( --ha-dialog-surface-background, var(--mdc-theme-surface, #fff) ); - /* Set border radius */ --wa-panel-border-radius: var(--ha-dialog-border-radius, 24px); } @@ -188,6 +190,11 @@ export class HaWaDialog extends LitElement { padding: 24px 24px 0 24px; } + wa-dialog::part(close-button), + wa-dialog::part(close-button__base) { + display: none; + } + wa-dialog::part(title) { margin: 0; margin-bottom: 8px; @@ -286,8 +293,8 @@ export class HaWaDialog extends LitElement { .header_button { text-decoration: none; color: inherit; - inset-inline-start: initial; - inset-inline-end: -12px; + inset-inline-start: -12px; + inset-inline-end: initial; direction: var(--direction); } From e4f3b35ef8093d27fdc16afae9bee610266d5caf Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Fri, 12 Sep 2025 12:20:57 +0100 Subject: [PATCH 008/125] Remove border --- src/components/ha-wa-dialog.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 00f4b4b362c6..a5b8c06af70e 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -186,7 +186,6 @@ export class HaWaDialog extends LitElement { } wa-dialog::part(header) { - border-bottom: 1px solid var(--divider-color, rgba(0, 0, 0, 0.12)); padding: 24px 24px 0 24px; } From d35ee494676b931ef7b7cecc99625d5985709bd4 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Fri, 12 Sep 2025 13:12:21 +0100 Subject: [PATCH 009/125] More closely follow MD3 guidelines --- src/components/ha-wa-dialog.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index a5b8c06af70e..dd017dd89d82 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -186,7 +186,7 @@ export class HaWaDialog extends LitElement { } wa-dialog::part(header) { - padding: 24px 24px 0 24px; + padding: 24px 24px 16px 24px; } wa-dialog::part(close-button), @@ -196,7 +196,7 @@ export class HaWaDialog extends LitElement { wa-dialog::part(title) { margin: 0; - margin-bottom: 8px; + margin-bottom: 0; color: var(--mdc-dialog-heading-ink-color, rgba(0, 0, 0, 0.87)); font-size: var(--mdc-typography-headline6-font-size, 1.574rem); line-height: var(--mdc-typography-headline6-line-height, 2rem); @@ -211,7 +211,8 @@ export class HaWaDialog extends LitElement { wa-dialog::part(body) { position: var(--dialog-content-position, relative); - padding: var(--dialog-content-padding, 24px); + padding: 0 var(--dialog-content-padding, 24px) + var(--dialog-content-padding, 24px) var(--dialog-content-padding, 24px); } wa-dialog::part(footer) { From d8e713cd5981a9e4bc7cf57a639324e306304fcf Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Fri, 12 Sep 2025 13:23:22 +0100 Subject: [PATCH 010/125] Handle enter key --- src/components/ha-wa-dialog.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index dd017dd89d82..1959d0f4a612 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -42,6 +42,7 @@ export class HaWaDialog extends LitElement { new CustomEvent("opened", { bubbles: true, composed: true }) ); this._handleDialogInitialFocus(); + this._setupDialogKeydown(); }; private _handleWaHide = () => { @@ -83,6 +84,21 @@ export class HaWaDialog extends LitElement { } } + private _setupDialogKeydown() { + this._waDialog?.addEventListener("keydown", this._handleDialogKeydown); + } + + private _handleDialogKeydown = (event: KeyboardEvent) => { + if (event.key === "Enter") { + const primaryButton = this.querySelector( + '[slot="primaryAction"]' + ) as HTMLElement; + if (primaryButton) { + primaryButton.click(); + } + } + }; + private _handleDialogInitialFocus() { const candidates = this.querySelectorAll("[dialogInitialFocus]"); if (!candidates.length) return; From a567cb070ac3e2b251c623b8255ae5bfa2fd18a9 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Fri, 12 Sep 2025 13:42:50 +0100 Subject: [PATCH 011/125] Test with basic form --- .../device-registry-detail/dialog-device-registry-detail.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/panels/config/devices/device-registry-detail/dialog-device-registry-detail.ts b/src/panels/config/devices/device-registry-detail/dialog-device-registry-detail.ts index 673f9cf9f389..e02b0300e68c 100644 --- a/src/panels/config/devices/device-registry-detail/dialog-device-registry-detail.ts +++ b/src/panels/config/devices/device-registry-detail/dialog-device-registry-detail.ts @@ -5,7 +5,7 @@ import { fireEvent } from "../../../../common/dom/fire_event"; import { computeDeviceNameDisplay } from "../../../../common/entity/compute_device_name"; import "../../../../components/ha-alert"; import "../../../../components/ha-area-picker"; -import "../../../../components/ha-dialog"; +import "../../../../components/ha-wa-dialog"; import "../../../../components/ha-button"; import "../../../../components/ha-labels-picker"; import type { HaSwitch } from "../../../../components/ha-switch"; @@ -57,7 +57,7 @@ class DialogDeviceRegistryDetail extends LitElement { } const device = this._params.device; return html` - ${this.hass.localize("ui.dialogs.device-registry-detail.update")} - + `; } From df89f97296cf134ac0a904d709494b8064ca5b6b Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Fri, 12 Sep 2025 13:53:22 +0100 Subject: [PATCH 012/125] Setup for use with header (more info etc) --- src/components/ha-wa-dialog.ts | 52 ++++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 6 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 1959d0f4a612..68a19fbc9bc4 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -17,18 +17,24 @@ export class HaWaDialog extends LitElement { @property({ type: Boolean, reflect: true, attribute: "scrim-click-action" }) public scrimClickAction = false; - @property({ type: Boolean, reflect: true, attribute: "hide-actions" }) + @property({ type: Boolean, reflect: true, attribute: "hideactions" }) public hideActions = false; - @property({ type: Boolean, reflect: true, attribute: "flex-content" }) + @property({ type: Boolean, reflect: true, attribute: "flexcontent" }) public flexContent = false; @property() public heading?: string | TemplateResult; + @property({ reflect: true, attribute: "escape-key-action" }) + public escapeKeyAction?: string; + @state() private _internalOpen = false; + @state() + private _hasCustomHeadingSlot = false; + @query("wa-dialog") private _waDialog?: any; @@ -45,7 +51,12 @@ export class HaWaDialog extends LitElement { this._setupDialogKeydown(); }; - private _handleWaHide = () => { + private _handleWaHide = (ev?: CustomEvent) => { + // Prevent closing via Escape when escapeKeyAction is empty string + if (this.escapeKeyAction === "" && ev?.detail?.source === this._waDialog) { + ev?.preventDefault?.(); + return; + } this._internalOpen = false; this.dispatchEvent( new CustomEvent("closed", { @@ -82,6 +93,10 @@ export class HaWaDialog extends LitElement { this._waDialog.lightDismiss = this.scrimClickAction; } } + + if (changedProperties.has("_hasCustomHeadingSlot")) { + this.toggleAttribute("has-custom-heading", this._hasCustomHeadingSlot); + } } private _setupDialogKeydown() { @@ -89,6 +104,12 @@ export class HaWaDialog extends LitElement { } private _handleDialogKeydown = (event: KeyboardEvent) => { + // Suppress Escape key when escapeKeyAction is an empty string + if (event.key === "Escape" && this.escapeKeyAction === "") { + event.stopImmediatePropagation(); + event.preventDefault(); + return; + } if (event.key === "Enter") { const primaryButton = this.querySelector( '[slot="primaryAction"]' @@ -144,7 +165,12 @@ export class HaWaDialog extends LitElement { @wa-hide=${this._handleWaHide} class=${this.open ? "mdc-dialog--open" : ""} > - ${this.heading + + ${this.heading && !this._hasCustomHeadingSlot ? html`
{ + const slot = ev.target as HTMLSlotElement; + const hasContent = slot.assignedNodes({ flatten: true }).length > 0; + if (hasContent !== this._hasCustomHeadingSlot) { + this._hasCustomHeadingSlot = hasContent; + } + }; + static override styles = css` :host { --dialog-z-index: 8; @@ -205,6 +239,10 @@ export class HaWaDialog extends LitElement { padding: 24px 24px 16px 24px; } + :host([has-custom-heading]) wa-dialog::part(header) { + padding: 0; + } + wa-dialog::part(close-button), wa-dialog::part(close-button__base) { display: none; @@ -255,14 +293,16 @@ export class HaWaDialog extends LitElement { 0px ) ); + position: var(--dialog-surface-position, relative); + margin-top: var(--dialog-surface-margin-top, auto); } - :host([flexContent]) wa-dialog::part(body) { + :host([flexcontent]) wa-dialog::part(body) { display: flex; flex-direction: column; } - :host([hideActions]) wa-dialog::part(body) { + :host([hideactions]) wa-dialog::part(body) { padding-bottom: var(--dialog-content-padding, 24px); } From 7e3a5be46d7560dd0e3879b3d2b435288f264b84 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Fri, 12 Sep 2025 13:53:31 +0100 Subject: [PATCH 013/125] Test with more info dialog --- src/dialogs/more-info/ha-more-info-dialog.ts | 21 +++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/dialogs/more-info/ha-more-info-dialog.ts b/src/dialogs/more-info/ha-more-info-dialog.ts index 80b446ef16cc..03ebc022e120 100644 --- a/src/dialogs/more-info/ha-more-info-dialog.ts +++ b/src/dialogs/more-info/ha-more-info-dialog.ts @@ -34,7 +34,7 @@ import { import { shouldHandleRequestSelectedEvent } from "../../common/mwc/handle-request-selected-event"; import { navigate } from "../../common/navigate"; import "../../components/ha-button-menu"; -import "../../components/ha-dialog"; +import "../../components/ha-wa-dialog"; import "../../components/ha-dialog-header"; import "../../components/ha-icon-button"; import "../../components/ha-icon-button-prev"; @@ -362,7 +362,7 @@ export class MoreInfoDialog extends LitElement { const title = this._childView?.viewTitle || breadcrumb.pop() || entityId; return html` - ` )} - + `; } @@ -676,7 +676,7 @@ export class MoreInfoDialog extends LitElement { return [ haStyleDialog, css` - ha-dialog { + ha-wa-dialog { /* Set the top top of the dialog to a fixed position, so it doesnt jump when the content changes size */ --vertical-align-dialog: flex-start; --dialog-surface-margin-top: max( @@ -706,25 +706,22 @@ export class MoreInfoDialog extends LitElement { @media all and (max-width: 450px), all and (max-height: 500px) { /* When in fullscreen dialog should be attached to top */ - ha-dialog { + ha-wa-dialog { --dialog-surface-margin-top: 0px; } } @media all and (min-width: 600px) and (min-height: 501px) { - ha-dialog { - --mdc-dialog-min-width: 580px; - --mdc-dialog-max-width: 580px; - --mdc-dialog-max-height: calc(100% - 72px); + ha-wa-dialog { + --width: 580px; } .main-title { cursor: default; } - :host([large]) ha-dialog { - --mdc-dialog-min-width: 90vw; - --mdc-dialog-max-width: 90vw; + :host([large]) ha-wa-dialog { + --width: 90vw; } } From cefd4eae197106c4dcc8114dac8c68803c7ecde9 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Fri, 12 Sep 2025 14:05:36 +0100 Subject: [PATCH 014/125] Allow for custom close action --- src/components/ha-wa-dialog.ts | 35 ++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 68a19fbc9bc4..b01b51511c66 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -49,6 +49,7 @@ export class HaWaDialog extends LitElement { ); this._handleDialogInitialFocus(); this._setupDialogKeydown(); + this._addDialogActionListener(); }; private _handleWaHide = (ev?: CustomEvent) => { @@ -67,6 +68,39 @@ export class HaWaDialog extends LitElement { ); }; + private _onDialogActionClick = (ev: Event) => { + const path = (ev.composedPath?.() ?? []) as EventTarget[]; + const actionEl = path.find( + (n) => + n instanceof HTMLElement && + (n as HTMLElement).hasAttribute("dialogAction") + ) as HTMLElement | undefined; + if (!actionEl) return; + // Read attribute for parity with legacy API; value not required here + actionEl.getAttribute("dialogAction"); + // For compatibility, any dialogAction should trigger a close of the dialog + // (e.g. "close", "cancel"). + if (this._waDialog?.hide) { + this._waDialog.hide(); + } else { + this._internalOpen = false; + this._handleWaHide(); + } + // Prevent duplicate handling upstream + ev.stopPropagation(); + }; + + private _addDialogActionListener() { + // Listen for any clicks on elements with the legacy `dialogAction` attribute + // from slotted heading and content. + this.addEventListener("click", this._onDialogActionClick); + } + + disconnectedCallback(): void { + super.disconnectedCallback(); + this.removeEventListener("click", this._onDialogActionClick); + } + private _onCloseClick = () => { if (this._waDialog?.hide) { this._waDialog.hide(); @@ -224,6 +258,7 @@ export class HaWaDialog extends LitElement { } wa-dialog { + --width: min(580px, 95vw); --spacing: var(--dialog-content-padding, 24px); --show-duration: 200ms; --hide-duration: 200ms; From 395af3dc66df9093d32a4dc1d2fa3fd77c2ac302 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Fri, 12 Sep 2025 14:56:44 +0100 Subject: [PATCH 015/125] Rename --- src/components/ha-wa-dialog.ts | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index b01b51511c66..4dca9aeebb76 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -14,8 +14,11 @@ export class HaWaDialog extends LitElement { @property({ type: Boolean, reflect: true }) public open = false; - @property({ type: Boolean, reflect: true, attribute: "scrim-click-action" }) - public scrimClickAction = false; + @property({ reflect: true, attribute: "escape-key-action" }) + public escapeKeyAction?: string; + + @property({ type: Boolean, reflect: true, attribute: "overlay-click-action" }) + public overlayClickAction = false; @property({ type: Boolean, reflect: true, attribute: "hideactions" }) public hideActions = false; @@ -26,9 +29,6 @@ export class HaWaDialog extends LitElement { @property() public heading?: string | TemplateResult; - @property({ reflect: true, attribute: "escape-key-action" }) - public escapeKeyAction?: string; - @state() private _internalOpen = false; @@ -122,12 +122,6 @@ export class HaWaDialog extends LitElement { } } - if (changedProperties.has("scrimClickAction")) { - if (this._waDialog) { - this._waDialog.lightDismiss = this.scrimClickAction; - } - } - if (changedProperties.has("_hasCustomHeadingSlot")) { this.toggleAttribute("has-custom-heading", this._hasCustomHeadingSlot); } @@ -193,7 +187,7 @@ export class HaWaDialog extends LitElement { return html` Date: Fri, 12 Sep 2025 14:56:47 +0100 Subject: [PATCH 016/125] Set --- src/dialogs/more-info/ha-more-info-dialog.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dialogs/more-info/ha-more-info-dialog.ts b/src/dialogs/more-info/ha-more-info-dialog.ts index 03ebc022e120..8ad398281154 100644 --- a/src/dialogs/more-info/ha-more-info-dialog.ts +++ b/src/dialogs/more-info/ha-more-info-dialog.ts @@ -367,6 +367,7 @@ export class MoreInfoDialog extends LitElement { @closed=${this.closeDialog} @opened=${this._handleOpened} .escapeKeyAction=${this._isEscapeEnabled ? undefined : ""} + .overlayClickAction=${this._isEscapeEnabled} .heading=${title} hideActions flexContent From 5ccfcb610f0d54257907cdf58d6e0ba5a71299cb Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Fri, 12 Sep 2025 15:17:35 +0100 Subject: [PATCH 017/125] Add gap to footer actions --- src/components/ha-wa-dialog.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 4dca9aeebb76..9c05222115b1 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -301,6 +301,7 @@ export class HaWaDialog extends LitElement { wa-dialog::part(footer) { justify-content: flex-end; padding: 12px 16px 16px 16px; + gap: 12px; } wa-dialog::part(dialog) { From 9e5eddba155fd4360b16c9880fa5d6111e48e60b Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Fri, 12 Sep 2025 15:31:00 +0100 Subject: [PATCH 018/125] Handle scrolling when open --- src/components/ha-wa-dialog.ts | 111 +++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 9c05222115b1..dcbcee7ce2ee 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -7,6 +7,63 @@ import { nextRender } from "../common/util/render-status"; import { FOCUS_TARGET } from "../dialogs/make-dialog-manager"; import "./ha-icon-button"; +// Global scroll lock management to prevent background scrolling while dialogs are open +let waDialogScrollLockCount = 0; +let previousBodyOverflow: string | null = null; +let previousHtmlOverflow: string | null = null; +let previousBodyPaddingRight: string | null = null; + +const lockDocumentScroll = () => { + if (waDialogScrollLockCount === 0) { + const htmlEl = document.documentElement as HTMLElement; + const bodyEl = document.body as HTMLElement; + previousHtmlOverflow = htmlEl.style.overflow || null; + previousBodyOverflow = bodyEl.style.overflow || null; + previousBodyPaddingRight = bodyEl.style.paddingRight || null; + + // Compensate for scrollbar to avoid layout shift on desktop + const scrollbarWidth = window.innerWidth - htmlEl.clientWidth; + if (scrollbarWidth > 0) { + const computedPaddingRight = parseFloat( + window.getComputedStyle(bodyEl).paddingRight || "0" + ); + bodyEl.style.paddingRight = `${computedPaddingRight + scrollbarWidth}px`; + } + + htmlEl.style.overflow = "hidden"; + bodyEl.style.overflow = "hidden"; + } + waDialogScrollLockCount += 1; +}; + +const unlockDocumentScroll = () => { + if (waDialogScrollLockCount > 0) { + waDialogScrollLockCount -= 1; + } + if (waDialogScrollLockCount === 0) { + const htmlEl = document.documentElement as HTMLElement; + const bodyEl = document.body as HTMLElement; + if (previousHtmlOverflow !== null) { + htmlEl.style.overflow = previousHtmlOverflow; + } else { + htmlEl.style.removeProperty("overflow"); + } + if (previousBodyOverflow !== null) { + bodyEl.style.overflow = previousBodyOverflow; + } else { + bodyEl.style.removeProperty("overflow"); + } + if (previousBodyPaddingRight !== null) { + bodyEl.style.paddingRight = previousBodyPaddingRight; + } else { + bodyEl.style.removeProperty("padding-right"); + } + previousHtmlOverflow = null; + previousBodyOverflow = null; + previousBodyPaddingRight = null; + } +}; + @customElement("ha-wa-dialog") export class HaWaDialog extends LitElement { protected readonly [FOCUS_TARGET]; @@ -38,6 +95,38 @@ export class HaWaDialog extends LitElement { @query("wa-dialog") private _waDialog?: any; + // Cache reference to the internal scrollable body of wa-dialog + private _waBodyEl?: HTMLElement | null; + + private _scrollLocked = false; + + private _observeBodyScroll(attach: boolean) { + // Get the internal body part from wa-dialog shadow DOM + if (!this._waDialog) return; + if (!this._waBodyEl) { + this._waBodyEl = this._waDialog.shadowRoot?.querySelector( + '[part="body"]' + ) as HTMLElement | null; + } + const bodyEl = this._waBodyEl; + if (!bodyEl) return; + if (attach) { + bodyEl.addEventListener("scroll", this._onScroll, { passive: true }); + this._updateScrolledAttribute(); + } else { + bodyEl.removeEventListener("scroll", this._onScroll as EventListener); + } + } + + private _onScroll = () => { + this._updateScrolledAttribute(); + }; + + private _updateScrolledAttribute() { + const scrollTop = (this._waBodyEl?.scrollTop ?? 0) as number; + this.toggleAttribute("scrolled", scrollTop !== 0); + } + public scrollToPos(x: number, y: number) { this._waDialog?.scrollTo(x, y); } @@ -50,6 +139,11 @@ export class HaWaDialog extends LitElement { this._handleDialogInitialFocus(); this._setupDialogKeydown(); this._addDialogActionListener(); + this._observeBodyScroll(true); + if (!this._scrollLocked) { + lockDocumentScroll(); + this._scrollLocked = true; + } }; private _handleWaHide = (ev?: CustomEvent) => { @@ -66,6 +160,11 @@ export class HaWaDialog extends LitElement { detail: { action: "close" }, }) ); + this._observeBodyScroll(false); + if (this._scrollLocked) { + unlockDocumentScroll(); + this._scrollLocked = false; + } }; private _onDialogActionClick = (ev: Event) => { @@ -99,6 +198,11 @@ export class HaWaDialog extends LitElement { disconnectedCallback(): void { super.disconnectedCallback(); this.removeEventListener("click", this._onDialogActionClick); + this._observeBodyScroll(false); + if (this._scrollLocked) { + unlockDocumentScroll(); + this._scrollLocked = false; + } } private _onCloseClick = () => { @@ -119,6 +223,8 @@ export class HaWaDialog extends LitElement { this._internalOpen = this.open; if (this.open) { this._handleDialogInitialFocus(); + // When opened via property update, ensure scroll listener is attached + this._observeBodyScroll(true); } } @@ -251,6 +357,11 @@ export class HaWaDialog extends LitElement { --primary-action-button-flex: unset; } + :host([scrolled]) wa-dialog::part(header) { + border-bottom: 1px solid + var(--dialog-scroll-divider-color, var(--divider-color)); + } + wa-dialog { --width: min(580px, 95vw); --spacing: var(--dialog-content-padding, 24px); From e31367f902b941f0a302562176a31c8c610432c2 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Fri, 12 Sep 2025 15:34:04 +0100 Subject: [PATCH 019/125] Safe area fix for top --- src/dialogs/more-info/ha-more-info-dialog.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/dialogs/more-info/ha-more-info-dialog.ts b/src/dialogs/more-info/ha-more-info-dialog.ts index 8ad398281154..d7ef14ede914 100644 --- a/src/dialogs/more-info/ha-more-info-dialog.ts +++ b/src/dialogs/more-info/ha-more-info-dialog.ts @@ -706,9 +706,8 @@ export class MoreInfoDialog extends LitElement { } @media all and (max-width: 450px), all and (max-height: 500px) { - /* When in fullscreen dialog should be attached to top */ ha-wa-dialog { - --dialog-surface-margin-top: 0px; + --dialog-surface-margin-top: var(--safe-area-inset-top, 0px); } } From cdf8ee1c4cc1d7d2c4ae364b4d9b6fd145096c63 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 15 Sep 2025 11:29:25 +0100 Subject: [PATCH 020/125] Match original naming for scrim click action --- src/components/ha-wa-dialog.ts | 17 +++++++++++++---- src/dialogs/more-info/ha-more-info-dialog.ts | 2 +- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index dcbcee7ce2ee..d28589089632 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -74,8 +74,8 @@ export class HaWaDialog extends LitElement { @property({ reflect: true, attribute: "escape-key-action" }) public escapeKeyAction?: string; - @property({ type: Boolean, reflect: true, attribute: "overlay-click-action" }) - public overlayClickAction = false; + @property({ type: Boolean, reflect: true, attribute: "scrim-click-action" }) + public scrimClickAction = false; @property({ type: Boolean, reflect: true, attribute: "hideactions" }) public hideActions = false; @@ -293,7 +293,7 @@ export class HaWaDialog extends LitElement { return html` Date: Mon, 15 Sep 2025 11:44:35 +0100 Subject: [PATCH 021/125] Cleanup --- src/components/ha-wa-dialog.ts | 36 +--------------------------------- 1 file changed, 1 insertion(+), 35 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index d28589089632..3f1ef9d6a7ea 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -95,7 +95,6 @@ export class HaWaDialog extends LitElement { @query("wa-dialog") private _waDialog?: any; - // Cache reference to the internal scrollable body of wa-dialog private _waBodyEl?: HTMLElement | null; private _scrollLocked = false; @@ -138,7 +137,6 @@ export class HaWaDialog extends LitElement { ); this._handleDialogInitialFocus(); this._setupDialogKeydown(); - this._addDialogActionListener(); this._observeBodyScroll(true); if (!this._scrollLocked) { lockDocumentScroll(); @@ -147,7 +145,6 @@ export class HaWaDialog extends LitElement { }; private _handleWaHide = (ev?: CustomEvent) => { - // Prevent closing via Escape when escapeKeyAction is empty string if (this.escapeKeyAction === "" && ev?.detail?.source === this._waDialog) { ev?.preventDefault?.(); return; @@ -167,37 +164,8 @@ export class HaWaDialog extends LitElement { } }; - private _onDialogActionClick = (ev: Event) => { - const path = (ev.composedPath?.() ?? []) as EventTarget[]; - const actionEl = path.find( - (n) => - n instanceof HTMLElement && - (n as HTMLElement).hasAttribute("dialogAction") - ) as HTMLElement | undefined; - if (!actionEl) return; - // Read attribute for parity with legacy API; value not required here - actionEl.getAttribute("dialogAction"); - // For compatibility, any dialogAction should trigger a close of the dialog - // (e.g. "close", "cancel"). - if (this._waDialog?.hide) { - this._waDialog.hide(); - } else { - this._internalOpen = false; - this._handleWaHide(); - } - // Prevent duplicate handling upstream - ev.stopPropagation(); - }; - - private _addDialogActionListener() { - // Listen for any clicks on elements with the legacy `dialogAction` attribute - // from slotted heading and content. - this.addEventListener("click", this._onDialogActionClick); - } - disconnectedCallback(): void { super.disconnectedCallback(); - this.removeEventListener("click", this._onDialogActionClick); this._observeBodyScroll(false); if (this._scrollLocked) { unlockDocumentScroll(); @@ -223,7 +191,6 @@ export class HaWaDialog extends LitElement { this._internalOpen = this.open; if (this.open) { this._handleDialogInitialFocus(); - // When opened via property update, ensure scroll listener is attached this._observeBodyScroll(true); } } @@ -238,7 +205,6 @@ export class HaWaDialog extends LitElement { } private _handleDialogKeydown = (event: KeyboardEvent) => { - // Suppress Escape key when escapeKeyAction is an empty string if (event.key === "Escape" && this.escapeKeyAction === "") { event.stopImmediatePropagation(); event.preventDefault(); @@ -260,7 +226,7 @@ export class HaWaDialog extends LitElement { const computeFocusTarget = (el: Element): HTMLElement | null => { if (!(el instanceof HTMLElement)) return null; - // If element itself is focusable or implements focus(), use it + // If element itself is focusable or implements focus(), use it instead of the focusable selector if (typeof el.focus === "function") { return el; } From 36f6bf7268c30f52e5a2a7fdbbc47a264c123696 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 15 Sep 2025 11:45:03 +0100 Subject: [PATCH 022/125] Cleanup --- src/components/ha-wa-dialog.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 3f1ef9d6a7ea..b61906077113 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -100,7 +100,6 @@ export class HaWaDialog extends LitElement { private _scrollLocked = false; private _observeBodyScroll(attach: boolean) { - // Get the internal body part from wa-dialog shadow DOM if (!this._waDialog) return; if (!this._waBodyEl) { this._waBodyEl = this._waDialog.shadowRoot?.querySelector( From be799d9287993ac001fcd29b1a5c6cd967c94863 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 15 Sep 2025 11:45:27 +0100 Subject: [PATCH 023/125] Rename --- src/components/ha-wa-dialog.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index b61906077113..c29af2f15ff5 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -129,7 +129,7 @@ export class HaWaDialog extends LitElement { this._waDialog?.scrollTo(x, y); } - private _handleWaShow = () => { + private _handleShow = () => { this._internalOpen = true; this.dispatchEvent( new CustomEvent("opened", { bubbles: true, composed: true }) @@ -143,7 +143,7 @@ export class HaWaDialog extends LitElement { } }; - private _handleWaHide = (ev?: CustomEvent) => { + private _handleHide = (ev?: CustomEvent) => { if (this.escapeKeyAction === "" && ev?.detail?.source === this._waDialog) { ev?.preventDefault?.(); return; @@ -177,7 +177,7 @@ export class HaWaDialog extends LitElement { this._waDialog.hide(); } else { this._internalOpen = false; - this._handleWaHide(); + this._handleHide(); } }; @@ -260,8 +260,8 @@ export class HaWaDialog extends LitElement { .open=${this._internalOpen} .lightDismiss=${this.scrimClickAction} .withoutHeader=${!this.heading} - @wa-show=${this._handleWaShow} - @wa-hide=${this._handleWaHide} + @wa-show=${this._handleShow} + @wa-hide=${this._handleHide} class=${this.open ? "mdc-dialog--open" : ""} > Date: Mon, 15 Sep 2025 12:08:33 +0100 Subject: [PATCH 024/125] Restore --- src/components/ha-wa-dialog.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index c29af2f15ff5..fa346e20083a 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -136,6 +136,7 @@ export class HaWaDialog extends LitElement { ); this._handleDialogInitialFocus(); this._setupDialogKeydown(); + this.addEventListener("click", this._onDialogActionClick); this._observeBodyScroll(true); if (!this._scrollLocked) { lockDocumentScroll(); @@ -163,8 +164,29 @@ export class HaWaDialog extends LitElement { } }; + // Listen for any clicks on elements with the + // dialogAction attribute from slotted heading and content. + private _onDialogActionClick = (ev: Event) => { + ev.stopPropagation(); + const path = (ev.composedPath?.() ?? []) as EventTarget[]; + const actionEl = path.find( + (n) => + n instanceof HTMLElement && + (n as HTMLElement).hasAttribute("dialogAction") + ) as HTMLElement | undefined; + if (!actionEl) return; + actionEl.getAttribute("dialogAction"); + if (this._waDialog?.hide) { + this._waDialog.hide(); + } else { + this._internalOpen = false; + this._handleHide(); + } + }; + disconnectedCallback(): void { super.disconnectedCallback(); + this.removeEventListener("click", this._onDialogActionClick); this._observeBodyScroll(false); if (this._scrollLocked) { unlockDocumentScroll(); From a2cd8b66ff83e55a6bc4d61c46c5b1e4c1eed2df Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Thu, 18 Sep 2025 10:03:47 +0100 Subject: [PATCH 025/125] Simplify and use more native features --- src/components/ha-wa-dialog.ts | 508 ++++-------------- src/dialogs/more-info/ha-more-info-dialog.ts | 10 +- .../dialog-device-registry-detail.ts | 29 +- 3 files changed, 131 insertions(+), 416 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index fa346e20083a..313c922fad6d 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -1,207 +1,33 @@ -import type { TemplateResult } from "lit"; -import { css, html, LitElement, nothing } from "lit"; -import { customElement, property, query, state } from "lit/decorators"; +import { css, html, LitElement } from "lit"; +import { customElement, property, state } from "lit/decorators"; import "@home-assistant/webawesome/dist/components/dialog/dialog"; -import { mdiClose } from "@mdi/js"; -import { nextRender } from "../common/util/render-status"; -import { FOCUS_TARGET } from "../dialogs/make-dialog-manager"; +import { mdiChartBoxOutline, mdiClose, mdiCogOutline } from "@mdi/js"; +import type { HomeAssistant } from "../types"; +import "./ha-dialog-header"; import "./ha-icon-button"; -// Global scroll lock management to prevent background scrolling while dialogs are open -let waDialogScrollLockCount = 0; -let previousBodyOverflow: string | null = null; -let previousHtmlOverflow: string | null = null; -let previousBodyPaddingRight: string | null = null; - -const lockDocumentScroll = () => { - if (waDialogScrollLockCount === 0) { - const htmlEl = document.documentElement as HTMLElement; - const bodyEl = document.body as HTMLElement; - previousHtmlOverflow = htmlEl.style.overflow || null; - previousBodyOverflow = bodyEl.style.overflow || null; - previousBodyPaddingRight = bodyEl.style.paddingRight || null; - - // Compensate for scrollbar to avoid layout shift on desktop - const scrollbarWidth = window.innerWidth - htmlEl.clientWidth; - if (scrollbarWidth > 0) { - const computedPaddingRight = parseFloat( - window.getComputedStyle(bodyEl).paddingRight || "0" - ); - bodyEl.style.paddingRight = `${computedPaddingRight + scrollbarWidth}px`; - } - - htmlEl.style.overflow = "hidden"; - bodyEl.style.overflow = "hidden"; - } - waDialogScrollLockCount += 1; -}; - -const unlockDocumentScroll = () => { - if (waDialogScrollLockCount > 0) { - waDialogScrollLockCount -= 1; - } - if (waDialogScrollLockCount === 0) { - const htmlEl = document.documentElement as HTMLElement; - const bodyEl = document.body as HTMLElement; - if (previousHtmlOverflow !== null) { - htmlEl.style.overflow = previousHtmlOverflow; - } else { - htmlEl.style.removeProperty("overflow"); - } - if (previousBodyOverflow !== null) { - bodyEl.style.overflow = previousBodyOverflow; - } else { - bodyEl.style.removeProperty("overflow"); - } - if (previousBodyPaddingRight !== null) { - bodyEl.style.paddingRight = previousBodyPaddingRight; - } else { - bodyEl.style.removeProperty("padding-right"); - } - previousHtmlOverflow = null; - previousBodyOverflow = null; - previousBodyPaddingRight = null; - } -}; - @customElement("ha-wa-dialog") export class HaWaDialog extends LitElement { - protected readonly [FOCUS_TARGET]; + @property({ attribute: false }) public hass!: HomeAssistant; @property({ type: Boolean, reflect: true }) public open = false; - @property({ reflect: true, attribute: "escape-key-action" }) - public escapeKeyAction?: string; + // TODO: Should this be scrim, overlay, or match WA with lightDismiss? + @property({ type: Boolean, reflect: true, attribute: "scrim-dismissable" }) + public scrimDismissable = false; - @property({ type: Boolean, reflect: true, attribute: "scrim-click-action" }) - public scrimClickAction = false; + @property({ type: String, attribute: "header-title" }) + public headerTitle = ""; - @property({ type: Boolean, reflect: true, attribute: "hideactions" }) - public hideActions = false; + @property({ type: String, attribute: "header-subtitle" }) + public headerSubtitle = ""; @property({ type: Boolean, reflect: true, attribute: "flexcontent" }) public flexContent = false; - @property() - public heading?: string | TemplateResult; - @state() - private _internalOpen = false; - - @state() - private _hasCustomHeadingSlot = false; - - @query("wa-dialog") - private _waDialog?: any; - - private _waBodyEl?: HTMLElement | null; - - private _scrollLocked = false; - - private _observeBodyScroll(attach: boolean) { - if (!this._waDialog) return; - if (!this._waBodyEl) { - this._waBodyEl = this._waDialog.shadowRoot?.querySelector( - '[part="body"]' - ) as HTMLElement | null; - } - const bodyEl = this._waBodyEl; - if (!bodyEl) return; - if (attach) { - bodyEl.addEventListener("scroll", this._onScroll, { passive: true }); - this._updateScrolledAttribute(); - } else { - bodyEl.removeEventListener("scroll", this._onScroll as EventListener); - } - } - - private _onScroll = () => { - this._updateScrolledAttribute(); - }; - - private _updateScrolledAttribute() { - const scrollTop = (this._waBodyEl?.scrollTop ?? 0) as number; - this.toggleAttribute("scrolled", scrollTop !== 0); - } - - public scrollToPos(x: number, y: number) { - this._waDialog?.scrollTo(x, y); - } - - private _handleShow = () => { - this._internalOpen = true; - this.dispatchEvent( - new CustomEvent("opened", { bubbles: true, composed: true }) - ); - this._handleDialogInitialFocus(); - this._setupDialogKeydown(); - this.addEventListener("click", this._onDialogActionClick); - this._observeBodyScroll(true); - if (!this._scrollLocked) { - lockDocumentScroll(); - this._scrollLocked = true; - } - }; - - private _handleHide = (ev?: CustomEvent) => { - if (this.escapeKeyAction === "" && ev?.detail?.source === this._waDialog) { - ev?.preventDefault?.(); - return; - } - this._internalOpen = false; - this.dispatchEvent( - new CustomEvent("closed", { - bubbles: true, - composed: true, - detail: { action: "close" }, - }) - ); - this._observeBodyScroll(false); - if (this._scrollLocked) { - unlockDocumentScroll(); - this._scrollLocked = false; - } - }; - - // Listen for any clicks on elements with the - // dialogAction attribute from slotted heading and content. - private _onDialogActionClick = (ev: Event) => { - ev.stopPropagation(); - const path = (ev.composedPath?.() ?? []) as EventTarget[]; - const actionEl = path.find( - (n) => - n instanceof HTMLElement && - (n as HTMLElement).hasAttribute("dialogAction") - ) as HTMLElement | undefined; - if (!actionEl) return; - actionEl.getAttribute("dialogAction"); - if (this._waDialog?.hide) { - this._waDialog.hide(); - } else { - this._internalOpen = false; - this._handleHide(); - } - }; - - disconnectedCallback(): void { - super.disconnectedCallback(); - this.removeEventListener("click", this._onDialogActionClick); - this._observeBodyScroll(false); - if (this._scrollLocked) { - unlockDocumentScroll(); - this._scrollLocked = false; - } - } - - private _onCloseClick = () => { - if (this._waDialog?.hide) { - this._waDialog.hide(); - } else { - this._internalOpen = false; - this._handleHide(); - } - }; + private _open = false; protected updated( changedProperties: Map @@ -209,141 +35,77 @@ export class HaWaDialog extends LitElement { super.updated(changedProperties); if (changedProperties.has("open")) { - this._internalOpen = this.open; - if (this.open) { - this._handleDialogInitialFocus(); - this._observeBodyScroll(true); - } - } - - if (changedProperties.has("_hasCustomHeadingSlot")) { - this.toggleAttribute("has-custom-heading", this._hasCustomHeadingSlot); + this._open = this.open; } } - private _setupDialogKeydown() { - this._waDialog?.addEventListener("keydown", this._handleDialogKeydown); - } - - private _handleDialogKeydown = (event: KeyboardEvent) => { - if (event.key === "Escape" && this.escapeKeyAction === "") { - event.stopImmediatePropagation(); - event.preventDefault(); - return; - } - if (event.key === "Enter") { - const primaryButton = this.querySelector( - '[slot="primaryAction"]' - ) as HTMLElement; - if (primaryButton) { - primaryButton.click(); - } - } - }; - - private _handleDialogInitialFocus() { - const candidates = this.querySelectorAll("[dialogInitialFocus]"); - if (!candidates.length) return; - - const computeFocusTarget = (el: Element): HTMLElement | null => { - if (!(el instanceof HTMLElement)) return null; - // If element itself is focusable or implements focus(), use it instead of the focusable selector - if (typeof el.focus === "function") { - return el; - } - const focusableSelector = [ - "button:not([disabled])", - "input:not([disabled])", - "select:not([disabled])", - "textarea:not([disabled])", - "[tabindex]:not([tabindex='-1'])", - ].join(","); - return ( - (el.querySelector(focusableSelector) as HTMLElement | null) || null - ); - }; - - const el = candidates[0]; - const focusTarget = computeFocusTarget(el); - if (!focusTarget) return; - - nextRender().then(() => { - try { - (focusTarget as HTMLElement).focus({ preventScroll: true }); - } catch (_e) { - (focusTarget as HTMLElement).focus(); - } - }); - } - protected render() { return html` - - ${this.heading && !this._hasCustomHeadingSlot - ? html` -
- - ${this.heading} -
- ` - : ""} - - ${this.hideActions - ? nothing - : html` - - - `} + + + + + + + + ${this.headerTitle} + + + + ${this.headerSubtitle} + + + + + + + + + +
+ +
+
`; } - private _onHeadingSlotChange = (ev: Event) => { - const slot = ev.target as HTMLSlotElement; - const hasContent = slot.assignedNodes({ flatten: true }).length > 0; - if (hasContent !== this._hasCustomHeadingSlot) { - this._hasCustomHeadingSlot = hasContent; - } + private _handleShow = () => { + this._open = true; + this.dispatchEvent(new CustomEvent("opened")); }; - static override styles = css` - :host { - --dialog-z-index: 8; - --dialog-backdrop-filter: none; - --dialog-box-shadow: none; - --ha-font-weight-normal: 400; - --justify-action-buttons: flex-end; - --vertical-align-dialog: center; - --dialog-content-position: relative; - --dialog-content-padding: 24px; - --dialog-surface-position: relative; - --dialog-surface-top: auto; - --dialog-surface-margin-top: auto; - --ha-dialog-border-radius: 24px; - --ha-dialog-surface-backdrop-filter: none; - --ha-dialog-surface-background: var(--mdc-theme-surface, #fff); - --secondary-action-button-flex: unset; - --primary-action-button-flex: unset; - } + private _handleHide = () => { + this._open = false; + this.dispatchEvent(new CustomEvent("closed")); + }; + private _toggleSize = () => { + // TODO: Implement + }; + + static override styles = css` :host([scrolled]) wa-dialog::part(header) { max-width: 100%; border-bottom: 1px solid @@ -364,11 +126,36 @@ export class HaWaDialog extends LitElement { max-width: 100%; } + wa-dialog::part(dialog) { + min-width: var(--width, 100vw); + max-width: var(--width, 100vw); + max-height: 100vh; + position: var(--dialog-surface-position, relative); + margin-top: var(--dialog-surface-margin-top, auto); + } + + @media all and (max-width: 450px), all and (max-height: 500px) { + :host { + --ha-dialog-border-radius: 0px; + } + + wa-dialog { + --width: 100vw; + } + + wa-dialog::part(dialog) { + min-height: 100vh; + } + } + wa-dialog::part(header) { max-width: 100%; + overflow: hidden; + display: flex; + align-items: center; padding: 24px 24px 16px 24px; + gap: 4px; } - :host([has-custom-heading]) wa-dialog::part(header) { max-width: 100%; padding: 0; @@ -379,7 +166,12 @@ export class HaWaDialog extends LitElement { display: none; } - wa-dialog::part(title) { + .header-title-container { + display: flex; + align-items: center; + } + + .header-title { margin: 0; margin-bottom: 0; color: var(--mdc-dialog-heading-ink-color, rgba(0, 0, 0, 0.87)); @@ -392,107 +184,35 @@ export class HaWaDialog extends LitElement { letter-spacing: var(--mdc-typography-headline6-letter-spacing, 0.0125em); text-decoration: var(--mdc-typography-headline6-text-decoration, inherit); text-transform: var(--mdc-typography-headline6-text-transform, inherit); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + margin-right: 12px; } wa-dialog::part(body) { + padding: 0; + } + + .body { position: var(--dialog-content-position, relative); padding: 0 var(--dialog-content-padding, 24px) var(--dialog-content-padding, 24px) var(--dialog-content-padding, 24px); } - - wa-dialog::part(footer) { - justify-content: flex-end; - padding: 12px 16px 16px 16px; - gap: 12px; - } - - wa-dialog::part(dialog) { - min-width: calc( - var(--width, 100vw) - var(--safe-area-inset-left, 0px) - var( - --safe-area-inset-right, - 0px - ) - ); - max-width: calc( - 100vw - var(--safe-area-inset-left, 0px) - var( - --safe-area-inset-right, - 0px - ) - ); - max-height: calc( - 100vh - var(--safe-area-inset-top, 0px) - var( - --safe-area-inset-bottom, - 0px - ) - ); - position: var(--dialog-surface-position, relative); - margin-top: var(--dialog-surface-margin-top, auto); - } - :host([flexcontent]) wa-dialog::part(body) { max-width: 100%; + overflow: auto; display: flex; flex-direction: column; } - :host([hideactions]) wa-dialog::part(body) { padding-bottom: var(--dialog-content-padding, 24px); } - @media all and (max-width: 450px), all and (max-height: 500px) { - :host { - --ha-dialog-border-radius: 0px; - } - wa-dialog { - --width: calc( - 100vw - var(--safe-area-inset-left, 0px) - var( - --safe-area-inset-right, - 0px - ) - ); - max-width: 100%; - } - wa-dialog::part(dialog) { - min-height: calc( - 100vh - var(--safe-area-inset-top, 0px) - var( - --safe-area-inset-bottom, - 0px - ) - ); - max-width: 100%; - } - } - - .header_title { - display: flex; - align-items: center; - direction: var(--direction); - max-width: 100%; - } - - .header_title span { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - display: block; - padding-left: 4px; - padding-right: 4px; - margin-right: 12px; - margin-inline-end: 12px; - margin-inline-start: initial; - max-width: 0; - } - - .header_button { - text-decoration: none; - color: inherit; - inset-inline-start: -12px; - inset-inline-end: initial; - direction: var(--direction); - } - - .hidden { - display: none !important; + wa-dialog::part(footer) { + justify-content: flex-end; + padding: 12px 16px 16px 16px; + gap: 12px; } `; } diff --git a/src/dialogs/more-info/ha-more-info-dialog.ts b/src/dialogs/more-info/ha-more-info-dialog.ts index b501e28b4170..5747fa17a559 100644 --- a/src/dialogs/more-info/ha-more-info-dialog.ts +++ b/src/dialogs/more-info/ha-more-info-dialog.ts @@ -366,10 +366,8 @@ export class MoreInfoDialog extends LitElement { open @closed=${this.closeDialog} @opened=${this._handleOpened} - .escapeKeyAction=${this._isEscapeEnabled ? undefined : ""} - .scrimClickAction=${this._isEscapeEnabled} - .heading=${title} - hideActions + .headerTitle=${title} + scrimDismissable flexContent > @@ -428,7 +426,7 @@ export class MoreInfoDialog extends LitElement { ${!__DEMO__ && isAdmin ? html` diff --git a/src/panels/config/devices/device-registry-detail/dialog-device-registry-detail.ts b/src/panels/config/devices/device-registry-detail/dialog-device-registry-detail.ts index e02b0300e68c..029fc396b6ce 100644 --- a/src/panels/config/devices/device-registry-detail/dialog-device-registry-detail.ts +++ b/src/panels/config/devices/device-registry-detail/dialog-device-registry-detail.ts @@ -60,7 +60,7 @@ class DialogDeviceRegistryDetail extends LitElement {
${this._error @@ -131,21 +131,18 @@ class DialogDeviceRegistryDetail extends LitElement {
- - ${this.hass.localize("ui.common.cancel")} - - - ${this.hass.localize("ui.dialogs.device-registry-detail.update")} - +
+ + ${this.hass.localize("ui.common.cancel")} + + + ${this.hass.localize("ui.dialogs.device-registry-detail.update")} + +
`; } From 03e21db39543bf0632ca107943c6c5869a330d57 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Thu, 18 Sep 2025 10:04:57 +0100 Subject: [PATCH 026/125] Center title if no subtitle is used --- src/components/ha-dialog-header.ts | 44 ++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/src/components/ha-dialog-header.ts b/src/components/ha-dialog-header.ts index ea6541f5952a..329a9db1b64b 100644 --- a/src/components/ha-dialog-header.ts +++ b/src/components/ha-dialog-header.ts @@ -1,12 +1,44 @@ import { css, html, LitElement } from "lit"; -import { customElement } from "lit/decorators"; +import { customElement, state } from "lit/decorators"; +import { classMap } from "lit/directives/class-map"; @customElement("ha-dialog-header") export class HaDialogHeader extends LitElement { + @state() + private _hasSubtitle = false; + + protected firstUpdated() { + this._checkSubtitleContent(); + } + + private _checkSubtitleContent() { + const subtitleSlot = this.shadowRoot?.querySelector( + 'slot[name="subtitle"]' + ) as HTMLSlotElement; + if (subtitleSlot) { + const assignedNodes = subtitleSlot.assignedNodes({ flatten: true }); + const hasContent = assignedNodes.some((node) => { + if (node.nodeType === Node.TEXT_NODE) { + return node.textContent?.trim(); + } + if (node.nodeType === Node.ELEMENT_NODE) { + return (node as Element).textContent?.trim(); + } + return false; + }); + this._hasSubtitle = hasContent; + } + } + protected render() { return html`
-
+
@@ -15,7 +47,10 @@ export class HaDialogHeader extends LitElement {
- +
@@ -44,6 +79,9 @@ export class HaDialogHeader extends LitElement { padding: 4px; box-sizing: border-box; } + .header-bar.no-subtitle { + align-items: center; + } .header-content { flex: 1; padding: 10px 4px; From 4c5efa64468409b63403449e242f630b28bb353e Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Thu, 18 Sep 2025 10:09:38 +0100 Subject: [PATCH 027/125] Clean --- src/components/ha-wa-dialog.ts | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 313c922fad6d..6d4a86c5320a 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -1,7 +1,7 @@ import { css, html, LitElement } from "lit"; import { customElement, property, state } from "lit/decorators"; import "@home-assistant/webawesome/dist/components/dialog/dialog"; -import { mdiChartBoxOutline, mdiClose, mdiCogOutline } from "@mdi/js"; +import { mdiClose } from "@mdi/js"; import type { HomeAssistant } from "../types"; import "./ha-dialog-header"; import "./ha-icon-button"; @@ -65,22 +65,7 @@ export class HaWaDialog extends LitElement { ${this.headerSubtitle} - - - - - - +
From bb3ae3a252a4c717e082a1fc67dec3bdefec8d8f Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Thu, 18 Sep 2025 10:26:56 +0100 Subject: [PATCH 028/125] Fix footer --- src/components/ha-wa-dialog.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 6d4a86c5320a..d456325e75bf 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -68,7 +68,7 @@ export class HaWaDialog extends LitElement { -
+
@@ -195,9 +195,15 @@ export class HaWaDialog extends LitElement { } wa-dialog::part(footer) { - justify-content: flex-end; padding: 12px 16px 16px 16px; + } + + ::slotted([slot="footer"]) { + display: flex; gap: 12px; + justify-content: flex-end; + align-items: center; + width: 100%; } `; } From 6cb6d55cd61d3ae480b68b841137aee1fc088144 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Thu, 18 Sep 2025 10:46:14 +0100 Subject: [PATCH 029/125] Back action --- src/components/ha-wa-dialog.ts | 23 +- src/dialogs/more-info/ha-more-info-dialog.ts | 344 +++++++++---------- 2 files changed, 181 insertions(+), 186 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index d456325e75bf..773de42c4590 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -23,6 +23,12 @@ export class HaWaDialog extends LitElement { @property({ type: String, attribute: "header-subtitle" }) public headerSubtitle = ""; + @property({ type: Function, attribute: "header-action" }) + public backAction?: () => void; + + @property({ type: String, attribute: "back-label" }) + public backLabel?: string; + @property({ type: Boolean, reflect: true, attribute: "flexcontent" }) public flexContent = false; @@ -51,11 +57,18 @@ export class HaWaDialog extends LitElement { - + ${this.backAction + ? html`` + : html``} diff --git a/src/dialogs/more-info/ha-more-info-dialog.ts b/src/dialogs/more-info/ha-more-info-dialog.ts index 5747fa17a559..19cb5d52e6a5 100644 --- a/src/dialogs/more-info/ha-more-info-dialog.ts +++ b/src/dialogs/more-info/ha-more-info-dialog.ts @@ -1,6 +1,5 @@ import { mdiChartBoxOutline, - mdiClose, mdiCogOutline, mdiDevices, mdiDotsVertical, @@ -35,7 +34,6 @@ import { shouldHandleRequestSelectedEvent } from "../../common/mwc/handle-reques import { navigate } from "../../common/navigate"; import "../../components/ha-button-menu"; import "../../components/ha-wa-dialog"; -import "../../components/ha-dialog-header"; import "../../components/ha-icon-button"; import "../../components/ha-icon-button-prev"; import "../../components/ha-list-item"; @@ -366,195 +364,179 @@ export class MoreInfoDialog extends LitElement { open @closed=${this.closeDialog} @opened=${this._handleOpened} + .backLabel=${this.hass.localize( + "ui.dialogs.more_info_control.back_to_info" + )} + .backAction=${showCloseIcon ? undefined : this._goBack} .headerTitle=${title} scrimDismissable flexContent > - - ${showCloseIcon - ? html` - - ` - : html` - - `} - - ${breadcrumb.length > 0 - ? !__DEMO__ && isAdmin + + ${breadcrumb.length > 0 + ? !__DEMO__ && isAdmin + ? html` + + ` + : html` + + ` + : nothing} +

${title}

+
+ ${isDefaultView + ? html` + ${this._shouldShowHistory(domain) ? html` - - ` - : html` - + ` - : nothing} -

${title}

-
- ${isDefaultView - ? html` - ${this._shouldShowHistory(domain) - ? html` - - ` - : nothing} - ${!__DEMO__ && isAdmin - ? html` + : nothing} + ${!__DEMO__ && isAdmin + ? html` + + - - - ${deviceId - ? html` - - ${this.hass.localize( - "ui.dialogs.more_info_control.device_or_service_info", - { - type: this.hass.localize( - `ui.dialogs.more_info_control.device_type.${deviceType}` - ), - } - )} - - - ` - : nothing} - ${this._shouldShowEditIcon(domain, stateObj) - ? html` - - ${this.hass.localize( - "ui.dialogs.more_info_control.edit" - )} - - - ` - : nothing} - ${this._entry && - stateObj && - domain === "light" && - lightSupportsFavoriteColors(stateObj) - ? html` - - ${this._infoEditMode - ? this.hass.localize( - `ui.dialogs.more_info_control.exit_edit_mode` - ) - : this.hass.localize( - `ui.dialogs.more_info_control.${domain}.edit_mode` - )} - - - ` - : nothing} - - ${this.hass.localize( - "ui.dialogs.more_info_control.related" - )} - - - - ` - : nothing} - ` - : isSpecificInitialView - ? html` - + ${this.hass.localize( + "ui.dialogs.more_info_control.device_or_service_info", + { + type: this.hass.localize( + `ui.dialogs.more_info_control.device_type.${deviceType}` + ), + } + )} + + + ` + : nothing} + ${this._shouldShowEditIcon(domain, stateObj) + ? html` + + ${this.hass.localize( + "ui.dialogs.more_info_control.edit" + )} + + + ` + : nothing} + ${this._entry && + stateObj && + domain === "light" && + lightSupportsFavoriteColors(stateObj) + ? html` + + ${this._infoEditMode + ? this.hass.localize( + `ui.dialogs.more_info_control.exit_edit_mode` + ) + : this.hass.localize( + `ui.dialogs.more_info_control.${domain}.edit_mode` + )} + + + ` + : nothing} + + ${this.hass.localize( + "ui.dialogs.more_info_control.related" + )} + + + + ` + : nothing} + ` + : isSpecificInitialView + ? html` + + + + - - - - ${this.hass.localize("ui.dialogs.more_info_control.info")} - - - - ` - : nothing} -
+ ${this.hass.localize("ui.dialogs.more_info_control.info")} + + + + ` + : nothing} ${keyed( this._entityId, html` From 71907f20443d869553d3c995b0c224398c76f72e Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Thu, 18 Sep 2025 10:52:06 +0100 Subject: [PATCH 030/125] Improve --- src/components/ha-dialog-header.ts | 10 +++++----- src/dialogs/more-info/ha-more-info-dialog.ts | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/ha-dialog-header.ts b/src/components/ha-dialog-header.ts index 329a9db1b64b..bfa9dee72185 100644 --- a/src/components/ha-dialog-header.ts +++ b/src/components/ha-dialog-header.ts @@ -17,16 +17,16 @@ export class HaDialogHeader extends LitElement { ) as HTMLSlotElement; if (subtitleSlot) { const assignedNodes = subtitleSlot.assignedNodes({ flatten: true }); - const hasContent = assignedNodes.some((node) => { + this._hasSubtitle = assignedNodes.some((node) => { + let text: string | any[] | undefined; if (node.nodeType === Node.TEXT_NODE) { - return node.textContent?.trim(); + text = node.textContent?.trim(); } if (node.nodeType === Node.ELEMENT_NODE) { - return (node as Element).textContent?.trim(); + text = (node as Element).textContent?.trim(); } - return false; + return text && text.length > 0; }); - this._hasSubtitle = hasContent; } } diff --git a/src/dialogs/more-info/ha-more-info-dialog.ts b/src/dialogs/more-info/ha-more-info-dialog.ts index 19cb5d52e6a5..d7e6bf8bafc9 100644 --- a/src/dialogs/more-info/ha-more-info-dialog.ts +++ b/src/dialogs/more-info/ha-more-info-dialog.ts @@ -369,7 +369,7 @@ export class MoreInfoDialog extends LitElement { )} .backAction=${showCloseIcon ? undefined : this._goBack} .headerTitle=${title} - scrimDismissable + .scrimDismissable=${this._isEscapeEnabled} flexContent > From 29d3c9299a474d5b34534a7fdfa2114ec1449630 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Thu, 18 Sep 2025 11:58:38 +0100 Subject: [PATCH 031/125] Fix --- src/components/ha-wa-dialog.ts | 23 +++++--------------- src/dialogs/more-info/ha-more-info-dialog.ts | 23 ++++++++++++++++++-- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 773de42c4590..678e4982053d 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -23,12 +23,6 @@ export class HaWaDialog extends LitElement { @property({ type: String, attribute: "header-subtitle" }) public headerSubtitle = ""; - @property({ type: Function, attribute: "header-action" }) - public backAction?: () => void; - - @property({ type: String, attribute: "back-label" }) - public backLabel?: string; - @property({ type: Boolean, reflect: true, attribute: "flexcontent" }) public flexContent = false; @@ -57,18 +51,11 @@ export class HaWaDialog extends LitElement { - ${this.backAction - ? html`` - : html``} + diff --git a/src/dialogs/more-info/ha-more-info-dialog.ts b/src/dialogs/more-info/ha-more-info-dialog.ts index d7e6bf8bafc9..5aa00ff4f08e 100644 --- a/src/dialogs/more-info/ha-more-info-dialog.ts +++ b/src/dialogs/more-info/ha-more-info-dialog.ts @@ -1,5 +1,6 @@ import { mdiChartBoxOutline, + mdiClose, mdiCogOutline, mdiDevices, mdiDotsVertical, @@ -372,6 +373,24 @@ export class MoreInfoDialog extends LitElement { .scrimDismissable=${this._isEscapeEnabled} flexContent > + ${showCloseIcon + ? html` + + ` + : html` + + `} ${breadcrumb.length > 0 ? !__DEMO__ && isAdmin @@ -409,7 +428,7 @@ export class MoreInfoDialog extends LitElement { ${!__DEMO__ && isAdmin ? html` From e18b284119338eec92d9a36079204fc2e1be5379 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Thu, 18 Sep 2025 12:12:23 +0100 Subject: [PATCH 032/125] Fix --- src/components/ha-wa-dialog.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 678e4982053d..3ac1d7ec8b36 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -194,12 +194,9 @@ export class HaWaDialog extends LitElement { padding-bottom: var(--dialog-content-padding, 24px); } - wa-dialog::part(footer) { - padding: 12px 16px 16px 16px; - } - ::slotted([slot="footer"]) { display: flex; + padding: 12px 16px 16px 16px; gap: 12px; justify-content: flex-end; align-items: center; From eacf265e61ba36f60ce17e164687ffbf7266de6b Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Thu, 18 Sep 2025 12:14:32 +0100 Subject: [PATCH 033/125] Fix --- src/dialogs/more-info/ha-more-info-dialog.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/dialogs/more-info/ha-more-info-dialog.ts b/src/dialogs/more-info/ha-more-info-dialog.ts index 5aa00ff4f08e..2a8c2bd2d986 100644 --- a/src/dialogs/more-info/ha-more-info-dialog.ts +++ b/src/dialogs/more-info/ha-more-info-dialog.ts @@ -728,7 +728,6 @@ export class MoreInfoDialog extends LitElement { display: flex; flex-direction: column; align-items: flex-start; - margin: 0 0 -10px 0; } .title p { From 13782bbe07ca9ecdda57a987cb33705e6e3b1f2e Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Thu, 18 Sep 2025 13:51:21 +0100 Subject: [PATCH 034/125] Sizing --- src/components/ha-wa-dialog.ts | 46 ++++++++++++++++++-- src/dialogs/more-info/ha-more-info-dialog.ts | 18 ++------ 2 files changed, 47 insertions(+), 17 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 3ac1d7ec8b36..d10a407a42b1 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -6,6 +6,9 @@ import type { HomeAssistant } from "../types"; import "./ha-dialog-header"; import "./ha-icon-button"; +export type DialogSize = "small" | "medium" | "large" | "full"; +export type DialogSizeOnTitleClick = DialogSize | "none"; + @customElement("ha-wa-dialog") export class HaWaDialog extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -13,6 +16,19 @@ export class HaWaDialog extends LitElement { @property({ type: Boolean, reflect: true }) public open = false; + @property({ type: String, reflect: true, attribute: "dialog-size" }) + public dialogSize: DialogSize = "medium"; + + @property({ type: String, reflect: true, attribute: "current-dialog-size" }) + public currentDialogSize: DialogSize = this.dialogSize; + + @property({ + type: String, + reflect: true, + attribute: "dialog-size-on-title-click", + }) + public dialogSizeOnTitleClick: DialogSizeOnTitleClick = "none"; + // TODO: Should this be scrim, overlay, or match WA with lightDismiss? @property({ type: Boolean, reflect: true, attribute: "scrim-dismissable" }) public scrimDismissable = false; @@ -37,6 +53,10 @@ export class HaWaDialog extends LitElement { if (changedProperties.has("open")) { this._open = this.open; } + + if (changedProperties.has("dialogSize")) { + this.currentDialogSize = this.dialogSize; + } } protected render() { @@ -58,7 +78,7 @@ export class HaWaDialog extends LitElement { > - + ${this.headerTitle} @@ -86,8 +106,15 @@ export class HaWaDialog extends LitElement { this.dispatchEvent(new CustomEvent("closed")); }; - private _toggleSize = () => { - // TODO: Implement + public toggleSize = () => { + if (this.dialogSizeOnTitleClick === "none") { + return; + } + + this.currentDialogSize = + this.currentDialogSize === this.dialogSizeOnTitleClick + ? this.dialogSize + : this.dialogSizeOnTitleClick; }; static override styles = css` @@ -109,6 +136,19 @@ export class HaWaDialog extends LitElement { --wa-panel-border-radius: var(--ha-dialog-border-radius, 24px); z-index: var(--dialog-z-index, 8); max-width: 100%; + transition: width 200ms ease-in-out; + } + + :host([current-dialog-size="small"]) wa-dialog { + --width: min(320px, 95vw); + } + + :host([current-dialog-size="large"]) wa-dialog { + --width: min(720px, 95vw); + } + + :host([current-dialog-size="full"]) wa-dialog { + --width: 95vw; } wa-dialog::part(dialog) { diff --git a/src/dialogs/more-info/ha-more-info-dialog.ts b/src/dialogs/more-info/ha-more-info-dialog.ts index 2a8c2bd2d986..3890423ba308 100644 --- a/src/dialogs/more-info/ha-more-info-dialog.ts +++ b/src/dialogs/more-info/ha-more-info-dialog.ts @@ -96,8 +96,6 @@ const DEFAULT_VIEW: View = "info"; export class MoreInfoDialog extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ type: Boolean, reflect: true }) public large = false; - @state() private _parentEntityIds: string[] = []; @state() private _entityId?: string | null; @@ -129,7 +127,6 @@ export class MoreInfoDialog extends LitElement { this._currView = params.view || DEFAULT_VIEW; this._initialView = params.view || DEFAULT_VIEW; this._childView = undefined; - this.large = false; this._loadEntityRegistryEntry(); } @@ -371,6 +368,7 @@ export class MoreInfoDialog extends LitElement { .backAction=${showCloseIcon ? undefined : this._goBack} .headerTitle=${title} .scrimDismissable=${this._isEscapeEnabled} + .dialogSizeOnTitleClick=${"full"} flexContent > ${showCloseIcon @@ -391,7 +389,7 @@ export class MoreInfoDialog extends LitElement { )} > `} - + ${breadcrumb.length > 0 ? !__DEMO__ && isAdmin ? html` @@ -642,8 +640,8 @@ export class MoreInfoDialog extends LitElement { this._entry = ev.detail; } - private _enlarge() { - this.large = !this.large; + private _toggleSize() { + this.shadowRoot?.querySelector("ha-wa-dialog")?.toggleSize(); } private _handleOpened() { @@ -711,17 +709,9 @@ export class MoreInfoDialog extends LitElement { } @media all and (min-width: 600px) and (min-height: 501px) { - ha-wa-dialog { - --width: 580px; - } - .main-title { cursor: default; } - - :host([large]) ha-wa-dialog { - --width: 90vw; - } } .title { From 85fa487de95955d071efee418863529b8f771d65 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Thu, 18 Sep 2025 13:52:01 +0100 Subject: [PATCH 035/125] Animate transition --- src/components/ha-wa-dialog.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index d10a407a42b1..8339d03a8f7b 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -136,7 +136,6 @@ export class HaWaDialog extends LitElement { --wa-panel-border-radius: var(--ha-dialog-border-radius, 24px); z-index: var(--dialog-z-index, 8); max-width: 100%; - transition: width 200ms ease-in-out; } :host([current-dialog-size="small"]) wa-dialog { @@ -157,6 +156,9 @@ export class HaWaDialog extends LitElement { max-height: 100vh; position: var(--dialog-surface-position, relative); margin-top: var(--dialog-surface-margin-top, auto); + transition: + min-width 200ms ease-in-out, + max-width 200ms ease-in-out; } @media all and (max-width: 450px), all and (max-height: 500px) { From dc04508b0689a26f2a433002f7e45110746404c9 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Thu, 18 Sep 2025 14:15:41 +0100 Subject: [PATCH 036/125] Transisitons and width --- src/components/ha-wa-dialog.ts | 35 ++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 8339d03a8f7b..9bd6822e56d7 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -51,7 +51,9 @@ export class HaWaDialog extends LitElement { super.updated(changedProperties); if (changedProperties.has("open")) { - this._open = this.open; + if (this.open) { + this._open = this.open; + } } if (changedProperties.has("dialogSize")) { @@ -66,7 +68,7 @@ export class HaWaDialog extends LitElement { .lightDismiss=${this.scrimDismissable} .withoutHeader=${true} @wa-show=${this._handleShow} - @wa-hide=${this._handleHide} + @wa-after-hide=${this._handleAfterHide} > @@ -101,7 +103,7 @@ export class HaWaDialog extends LitElement { this.dispatchEvent(new CustomEvent("opened")); }; - private _handleHide = () => { + private _handleAfterHide = () => { this._open = false; this.dispatchEvent(new CustomEvent("closed")); }; @@ -125,10 +127,19 @@ export class HaWaDialog extends LitElement { } wa-dialog { + --full-width: min( + calc( + 100vw - var(--safe-area-inset-left, 0px) - var( + --safe-area-inset-right, + 0px + ) + ), + 95vw + ); --width: min(580px, 95vw); --spacing: var(--dialog-content-padding, 24px); - --show-duration: 200ms; - --hide-duration: 200ms; + --show-duration: var(--ha-dialog-show-duration, 200ms); + --hide-duration: var(--ha-dialog-hide-duration, 200ms); --wa-color-surface-raised: var( --ha-dialog-surface-background, var(--mdc-theme-surface, #fff) @@ -139,20 +150,20 @@ export class HaWaDialog extends LitElement { } :host([current-dialog-size="small"]) wa-dialog { - --width: min(320px, 95vw); + --width: min(320px, var(--full-width)); } :host([current-dialog-size="large"]) wa-dialog { - --width: min(720px, 95vw); + --width: min(720px, var(--full-width)); } :host([current-dialog-size="full"]) wa-dialog { - --width: 95vw; + --width: var(--full-width); } wa-dialog::part(dialog) { - min-width: var(--width, 100vw); - max-width: var(--width, 100vw); + min-width: var(--width, var(--full-width)); + max-width: var(--width, var(--full-width)); max-height: 100vh; position: var(--dialog-surface-position, relative); margin-top: var(--dialog-surface-margin-top, auto); @@ -166,10 +177,6 @@ export class HaWaDialog extends LitElement { --ha-dialog-border-radius: 0px; } - wa-dialog { - --width: 100vw; - } - wa-dialog::part(dialog) { min-height: 100vh; } From 6fb2feef46b6f82143ce605dca265b1d79788fc1 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Thu, 18 Sep 2025 14:26:53 +0100 Subject: [PATCH 037/125] Use var --- src/components/ha-wa-dialog.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 9bd6822e56d7..fa67fedbf3d9 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -168,8 +168,8 @@ export class HaWaDialog extends LitElement { position: var(--dialog-surface-position, relative); margin-top: var(--dialog-surface-margin-top, auto); transition: - min-width 200ms ease-in-out, - max-width 200ms ease-in-out; + min-width var(--ha-dialog-expand-duration, 200ms) ease-in-out, + max-width var(--ha-dialog-expand-duration, 200ms) ease-in-out; } @media all and (max-width: 450px), all and (max-height: 500px) { From 96f1795b0831b7dd73c59f0b70dc13afbcbf4155 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Thu, 18 Sep 2025 14:28:59 +0100 Subject: [PATCH 038/125] Remove extra padding --- src/components/ha-wa-dialog.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index fa67fedbf3d9..77353111bc3e 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -243,6 +243,10 @@ export class HaWaDialog extends LitElement { padding-bottom: var(--dialog-content-padding, 24px); } + wa-dialog::part(footer) { + padding: 0; + } + ::slotted([slot="footer"]) { display: flex; padding: 12px 16px 16px 16px; From 0ca16aaec8a7293ae0f5e27374da509c5fb75c39 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Thu, 18 Sep 2025 16:13:33 +0100 Subject: [PATCH 039/125] Query dialogIntiialFocus and focus the element if found --- src/components/ha-wa-dialog.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 77353111bc3e..3e9d7328b72e 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -101,6 +101,13 @@ export class HaWaDialog extends LitElement { private _handleShow = () => { this._open = true; this.dispatchEvent(new CustomEvent("opened")); + + this.updateComplete.then(() => { + const focusElement = this.querySelector( + "[dialogInitialFocus]" + ) as HTMLElement; + focusElement?.focus(); + }); }; private _handleAfterHide = () => { From f6530d6d860d4299798cc64764c063546695d05e Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 22 Sep 2025 11:31:15 +0100 Subject: [PATCH 040/125] Use scroll lock styles from webawesome --- src/resources/theme/wa.globals.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/resources/theme/wa.globals.ts b/src/resources/theme/wa.globals.ts index dd05b4e5547d..5b8c536119e9 100644 --- a/src/resources/theme/wa.globals.ts +++ b/src/resources/theme/wa.globals.ts @@ -1,5 +1,6 @@ import scrollLockStyles from "@home-assistant/webawesome/dist/styles/utilities/scroll-lock.css.js"; import { css } from "lit"; +import scrollLockStyles from "@home-assistant/webawesome/dist/styles/utilities/scroll-lock.css.js"; import { extractDerivedVars } from "../../common/style/derived-css-vars"; export const waMainStyles = css` From e504714b4f24d39220c8a228ab4c71e651f3c239 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Tue, 23 Sep 2025 11:57:16 +0100 Subject: [PATCH 041/125] Constrain overflow --- src/components/ha-wa-dialog.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 3e9d7328b72e..bbb27baa1d8b 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -177,6 +177,9 @@ export class HaWaDialog extends LitElement { transition: min-width var(--ha-dialog-expand-duration, 200ms) ease-in-out, max-width var(--ha-dialog-expand-duration, 200ms) ease-in-out; + display: flex; + flex-direction: column; + overflow: hidden; } @media all and (max-width: 450px), all and (max-height: 500px) { @@ -233,16 +236,22 @@ export class HaWaDialog extends LitElement { wa-dialog::part(body) { padding: 0; + display: flex; + flex-direction: column; + max-width: 100%; + overflow: hidden; } .body { position: var(--dialog-content-position, relative); padding: 0 var(--dialog-content-padding, 24px) var(--dialog-content-padding, 24px) var(--dialog-content-padding, 24px); + overflow: auto; + flex-grow: 1; } - :host([flexcontent]) wa-dialog::part(body) { + :host([flexcontent]) .body { max-width: 100%; - overflow: auto; + overflow: hidden; display: flex; flex-direction: column; } From 653cc0d05986f92c90c622d25ad96d3c181ae183 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Tue, 23 Sep 2025 12:32:24 +0100 Subject: [PATCH 042/125] Add dialog footer --- src/components/ha-dialog-footer.ts | 34 +++++++++++++++++++ .../dialog-device-registry-detail.ts | 16 ++++++--- 2 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 src/components/ha-dialog-footer.ts diff --git a/src/components/ha-dialog-footer.ts b/src/components/ha-dialog-footer.ts new file mode 100644 index 000000000000..58eb42cbd7be --- /dev/null +++ b/src/components/ha-dialog-footer.ts @@ -0,0 +1,34 @@ +import { css, html, LitElement } from "lit"; +import { customElement } from "lit/decorators"; + +@customElement("ha-dialog-footer") +export class HaDialogFooter extends LitElement { + protected render() { + return html` +
+ + +
+ `; + } + + static get styles() { + return [ + css` + .footer { + display: flex; + gap: 12px; + justify-content: flex-end; + align-items: center; + width: 100%; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-dialog-footer": HaDialogFooter; + } +} diff --git a/src/panels/config/devices/device-registry-detail/dialog-device-registry-detail.ts b/src/panels/config/devices/device-registry-detail/dialog-device-registry-detail.ts index 029fc396b6ce..e1516b67fb56 100644 --- a/src/panels/config/devices/device-registry-detail/dialog-device-registry-detail.ts +++ b/src/panels/config/devices/device-registry-detail/dialog-device-registry-detail.ts @@ -5,8 +5,9 @@ import { fireEvent } from "../../../../common/dom/fire_event"; import { computeDeviceNameDisplay } from "../../../../common/entity/compute_device_name"; import "../../../../components/ha-alert"; import "../../../../components/ha-area-picker"; -import "../../../../components/ha-wa-dialog"; import "../../../../components/ha-button"; +import "../../../../components/ha-dialog-footer"; +import "../../../../components/ha-wa-dialog"; import "../../../../components/ha-labels-picker"; import type { HaSwitch } from "../../../../components/ha-switch"; import "../../../../components/ha-textfield"; @@ -131,18 +132,23 @@ class DialogDeviceRegistryDetail extends LitElement {
-
+ ${this.hass.localize("ui.common.cancel")} - + ${this.hass.localize("ui.dialogs.device-registry-detail.update")} -
+ `; } From 48470ba64c443e5a71469c1c2beab437ef8c59d1 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Tue, 23 Sep 2025 12:32:53 +0100 Subject: [PATCH 043/125] Trigger primary action on enter pressed when ha-dialog-footer is used with a primaryAction slot --- src/components/ha-wa-dialog.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index bbb27baa1d8b..a1800b3ecf1c 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -108,11 +108,13 @@ export class HaWaDialog extends LitElement { ) as HTMLElement; focusElement?.focus(); }); + window.addEventListener("keydown", this._onKeyDown, true); }; private _handleAfterHide = () => { this._open = false; this.dispatchEvent(new CustomEvent("closed")); + window.removeEventListener("keydown", this._onKeyDown, true); }; public toggleSize = () => { @@ -126,6 +128,28 @@ export class HaWaDialog extends LitElement { : this.dialogSizeOnTitleClick; }; + private _onKeyDown = (ev: KeyboardEvent) => { + if (!this._open) return; + if (ev.defaultPrevented) return; + if (ev.key !== "Enter") return; + + const footer = this.querySelector("ha-dialog-footer") as HTMLElement | null; + if (!footer) return; + + const primaryAction = footer.querySelector( + '[slot="primaryAction"]' + ) as HTMLElement | null; + if (!primaryAction) return; + + const isDisabled = + (primaryAction as any).disabled ?? primaryAction.hasAttribute("disabled"); + if (isDisabled) return; + + primaryAction.click(); + ev.preventDefault(); + ev.stopPropagation(); + }; + static override styles = css` :host([scrolled]) wa-dialog::part(header) { max-width: 100%; From 2ebbf1d7db0733a5e6f0ac900f354082ed10f991 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Wed, 24 Sep 2025 11:59:41 +0100 Subject: [PATCH 044/125] Remove todo --- src/components/ha-wa-dialog.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index a1800b3ecf1c..0e8f598a45ba 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -29,7 +29,6 @@ export class HaWaDialog extends LitElement { }) public dialogSizeOnTitleClick: DialogSizeOnTitleClick = "none"; - // TODO: Should this be scrim, overlay, or match WA with lightDismiss? @property({ type: Boolean, reflect: true, attribute: "scrim-dismissable" }) public scrimDismissable = false; From a5f76c29da78bb2c16bf4823814ebb1c7de76d26 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Thu, 25 Sep 2025 10:04:51 +0100 Subject: [PATCH 045/125] Remove dupe from dev rebase --- src/resources/theme/wa.globals.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/resources/theme/wa.globals.ts b/src/resources/theme/wa.globals.ts index 5b8c536119e9..dd05b4e5547d 100644 --- a/src/resources/theme/wa.globals.ts +++ b/src/resources/theme/wa.globals.ts @@ -1,6 +1,5 @@ import scrollLockStyles from "@home-assistant/webawesome/dist/styles/utilities/scroll-lock.css.js"; import { css } from "lit"; -import scrollLockStyles from "@home-assistant/webawesome/dist/styles/utilities/scroll-lock.css.js"; import { extractDerivedVars } from "../../common/style/derived-css-vars"; export const waMainStyles = css` From 74153fb0933633840abdc68eaa25335eebf430ac Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Fri, 26 Sep 2025 12:16:44 +0100 Subject: [PATCH 046/125] Update src/components/ha-wa-dialog.ts Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com> --- src/components/ha-wa-dialog.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 0e8f598a45ba..c6bf704e7792 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -65,7 +65,7 @@ export class HaWaDialog extends LitElement { From c2901dbdcd036ab2c424721b5a829f4f1841aa03 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Fri, 26 Sep 2025 12:16:52 +0100 Subject: [PATCH 047/125] Update src/components/ha-wa-dialog.ts Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com> --- src/components/ha-wa-dialog.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index c6bf704e7792..94c055911f8d 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -86,7 +86,7 @@ export class HaWaDialog extends LitElement { ${this.headerSubtitle} - +
From 056a3ddc8ec43f56b0c426a7fd573107f050e23b Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Fri, 26 Sep 2025 12:17:47 +0100 Subject: [PATCH 048/125] Apply suggestion Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com> --- src/components/ha-wa-dialog.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 94c055911f8d..c1029b6a63f0 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -128,9 +128,9 @@ export class HaWaDialog extends LitElement { }; private _onKeyDown = (ev: KeyboardEvent) => { - if (!this._open) return; - if (ev.defaultPrevented) return; - if (ev.key !== "Enter") return; + if (!this._open || ev.defaultPrevented || ev.key !== "Enter") { + return; + } const footer = this.querySelector("ha-dialog-footer") as HTMLElement | null; if (!footer) return; From 45319b1cc583c47a63c55df4bed1ef786fe5a370 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Fri, 26 Sep 2025 12:18:10 +0100 Subject: [PATCH 049/125] No override Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com> --- src/components/ha-wa-dialog.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index c1029b6a63f0..cfbd4791d6bd 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -149,7 +149,7 @@ export class HaWaDialog extends LitElement { ev.stopPropagation(); }; - static override styles = css` + static styles = css` :host([scrolled]) wa-dialog::part(header) { max-width: 100%; border-bottom: 1px solid From 3c3edeb2eff6f1a11fdb885ff0c186518f606453 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 29 Sep 2025 11:16:46 +0100 Subject: [PATCH 050/125] Docstring --- src/components/ha-wa-dialog.ts | 45 ++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index cfbd4791d6bd..e9e381461d04 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -9,6 +9,51 @@ import "./ha-icon-button"; export type DialogSize = "small" | "medium" | "large" | "full"; export type DialogSizeOnTitleClick = DialogSize | "none"; +/** + * Home Assistant dialog component + * + * @element ha-wa-dialog + * @extends {LitElement} + * + * @summary + * A stylable dialog built using the `wa-dialog` component, providing the standard header (ha-dialog-header), + * body, and footer (when used in conjunction with `ha-dialog-footer`) with slots, sizing, and keyboard handling. + * + * @slot heading - Replace the entire header area. + * @slot navigationIcon - Leading header action (e.g. close/back button). + * @slot title - Header title. Click can toggle size depending on `dialog-size-on-title-click`. + * @slot subtitle - Header subtitle, shown under the title. + * @slot actionItems - Trailing header actions (e.g. buttons, menus). + * @slot - Dialog content body. + * @slot footer - Dialog footer content; typically action buttons. + * + * @csspart dialog - The dialog surface. + * @csspart header - The header container. + * @csspart body - The scrollable body container. + * @csspart footer - The footer container. + * + * @cssprop --dialog-content-padding - Padding for the dialog content sections. Defaults to 24px. + * @cssprop --ha-dialog-show-duration - Show animation duration. Defaults to 200ms. + * @cssprop --ha-dialog-hide-duration - Hide animation duration. Defaults to 200ms. + * @cssprop --ha-dialog-surface-background - Dialog background color. Defaults to surface. + * @cssprop --ha-dialog-border-radius - Border radius of the dialog surface. Defaults to 24px. + * @cssprop --dialog-z-index - Z-index for the dialog. Defaults to 8. + * @cssprop --dialog-surface-position - CSS position of the dialog surface. Defaults to relative. + * @cssprop --dialog-surface-margin-top - Top margin for the dialog surface. Defaults to auto. + * @cssprop --ha-dialog-expand-duration - Duration for width transitions when resizing. Defaults to 200ms. + * + * @attr {boolean} open - Controls the dialog open state. + * @attr {("small"|"medium"|"large"|"full")} dialog-size - Preferred dialog width preset. Defaults to "medium". + * @attr {("small"|"medium"|"large"|"full")} current-dialog-size - The active dialog size; toggles with title click when enabled. + * @attr {("none"|"small"|"medium"|"large"|"full")} dialog-size-on-title-click - Target size when clicking the title. "none" disables. + * @attr {boolean} scrim-dismissable - Allows closing the dialog by clicking the scrim/overlay. + * @attr {string} header-title - Header title text when no custom title slot is provided. + * @attr {string} header-subtitle - Header subtitle text when no custom subtitle slot is provided. + * @attr {boolean} flexcontent - Makes the dialog body a flex container for flexible layouts. + * + * @event opened - Fired when the dialog is shown. + * @event closed - Fired after the dialog is hidden. + */ @customElement("ha-wa-dialog") export class HaWaDialog extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; From 695c9a3c9a46fe53426e4ac25e9c1571898f5a0a Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 29 Sep 2025 11:18:40 +0100 Subject: [PATCH 051/125] Rename to width --- src/components/ha-wa-dialog.ts | 50 ++++++++++---------- src/dialogs/more-info/ha-more-info-dialog.ts | 2 +- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index e9e381461d04..b67d0bf99de8 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -6,8 +6,8 @@ import type { HomeAssistant } from "../types"; import "./ha-dialog-header"; import "./ha-icon-button"; -export type DialogSize = "small" | "medium" | "large" | "full"; -export type DialogSizeOnTitleClick = DialogSize | "none"; +export type DialogWidth = "small" | "medium" | "large" | "full"; +export type DialogWidthOnTitleClick = DialogWidth | "none"; /** * Home Assistant dialog component @@ -21,7 +21,7 @@ export type DialogSizeOnTitleClick = DialogSize | "none"; * * @slot heading - Replace the entire header area. * @slot navigationIcon - Leading header action (e.g. close/back button). - * @slot title - Header title. Click can toggle size depending on `dialog-size-on-title-click`. + * @slot title - Header title. Click can toggle width depending on `width-on-title-click`. * @slot subtitle - Header subtitle, shown under the title. * @slot actionItems - Trailing header actions (e.g. buttons, menus). * @slot - Dialog content body. @@ -40,12 +40,12 @@ export type DialogSizeOnTitleClick = DialogSize | "none"; * @cssprop --dialog-z-index - Z-index for the dialog. Defaults to 8. * @cssprop --dialog-surface-position - CSS position of the dialog surface. Defaults to relative. * @cssprop --dialog-surface-margin-top - Top margin for the dialog surface. Defaults to auto. - * @cssprop --ha-dialog-expand-duration - Duration for width transitions when resizing. Defaults to 200ms. + * @cssprop --ha-dialog-expand-duration - Duration for width transitions when changing width. Defaults to 200ms. * * @attr {boolean} open - Controls the dialog open state. - * @attr {("small"|"medium"|"large"|"full")} dialog-size - Preferred dialog width preset. Defaults to "medium". - * @attr {("small"|"medium"|"large"|"full")} current-dialog-size - The active dialog size; toggles with title click when enabled. - * @attr {("none"|"small"|"medium"|"large"|"full")} dialog-size-on-title-click - Target size when clicking the title. "none" disables. + * @attr {("small"|"medium"|"large"|"full")} width - Preferred dialog width preset. Defaults to "medium". + * @attr {("small"|"medium"|"large"|"full")} current-width - The active dialog width; toggles with title click when enabled. + * @attr {("none"|"small"|"medium"|"large"|"full")} width-on-title-click - Target width when clicking the title. "none" disables. * @attr {boolean} scrim-dismissable - Allows closing the dialog by clicking the scrim/overlay. * @attr {string} header-title - Header title text when no custom title slot is provided. * @attr {string} header-subtitle - Header subtitle text when no custom subtitle slot is provided. @@ -61,18 +61,18 @@ export class HaWaDialog extends LitElement { @property({ type: Boolean, reflect: true }) public open = false; - @property({ type: String, reflect: true, attribute: "dialog-size" }) - public dialogSize: DialogSize = "medium"; + @property({ type: String, reflect: true, attribute: "width" }) + public width: DialogWidth = "medium"; - @property({ type: String, reflect: true, attribute: "current-dialog-size" }) - public currentDialogSize: DialogSize = this.dialogSize; + @property({ type: String, reflect: true, attribute: "current-width" }) + public currentWidth: DialogWidth = this.width; @property({ type: String, reflect: true, - attribute: "dialog-size-on-title-click", + attribute: "width-on-title-click", }) - public dialogSizeOnTitleClick: DialogSizeOnTitleClick = "none"; + public widthOnTitleClick: DialogWidthOnTitleClick = "none"; @property({ type: Boolean, reflect: true, attribute: "scrim-dismissable" }) public scrimDismissable = false; @@ -100,8 +100,8 @@ export class HaWaDialog extends LitElement { } } - if (changedProperties.has("dialogSize")) { - this.currentDialogSize = this.dialogSize; + if (changedProperties.has("width")) { + this.currentWidth = this.width; } } @@ -124,7 +124,7 @@ export class HaWaDialog extends LitElement { > - + ${this.headerTitle} @@ -161,15 +161,15 @@ export class HaWaDialog extends LitElement { window.removeEventListener("keydown", this._onKeyDown, true); }; - public toggleSize = () => { - if (this.dialogSizeOnTitleClick === "none") { + public toggleWidth = () => { + if (this.widthOnTitleClick === "none") { return; } - this.currentDialogSize = - this.currentDialogSize === this.dialogSizeOnTitleClick - ? this.dialogSize - : this.dialogSizeOnTitleClick; + this.currentWidth = + this.currentWidth === this.widthOnTitleClick + ? this.width + : this.widthOnTitleClick; }; private _onKeyDown = (ev: KeyboardEvent) => { @@ -224,15 +224,15 @@ export class HaWaDialog extends LitElement { max-width: 100%; } - :host([current-dialog-size="small"]) wa-dialog { + :host([current-width="small"]) wa-dialog { --width: min(320px, var(--full-width)); } - :host([current-dialog-size="large"]) wa-dialog { + :host([current-width="large"]) wa-dialog { --width: min(720px, var(--full-width)); } - :host([current-dialog-size="full"]) wa-dialog { + :host([current-width="full"]) wa-dialog { --width: var(--full-width); } diff --git a/src/dialogs/more-info/ha-more-info-dialog.ts b/src/dialogs/more-info/ha-more-info-dialog.ts index 3890423ba308..dde0772d7ea9 100644 --- a/src/dialogs/more-info/ha-more-info-dialog.ts +++ b/src/dialogs/more-info/ha-more-info-dialog.ts @@ -641,7 +641,7 @@ export class MoreInfoDialog extends LitElement { } private _toggleSize() { - this.shadowRoot?.querySelector("ha-wa-dialog")?.toggleSize(); + this.shadowRoot?.querySelector("ha-wa-dialog")?.toggleWidth(); } private _handleOpened() { From 06b97afdf615481de68a6960d8751c8a094304d3 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 29 Sep 2025 11:19:12 +0100 Subject: [PATCH 052/125] Upd --- src/components/ha-wa-dialog.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index b67d0bf99de8..2a3b11395379 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -21,7 +21,7 @@ export type DialogWidthOnTitleClick = DialogWidth | "none"; * * @slot heading - Replace the entire header area. * @slot navigationIcon - Leading header action (e.g. close/back button). - * @slot title - Header title. Click can toggle width depending on `width-on-title-click`. + * @slot title - Header title. Click can toggle width if `width-on-title-click` is not "none". * @slot subtitle - Header subtitle, shown under the title. * @slot actionItems - Trailing header actions (e.g. buttons, menus). * @slot - Dialog content body. From b277eccdb01a66800f5cc23e6b42c90c37f6aa03 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 29 Sep 2025 11:38:09 +0100 Subject: [PATCH 053/125] Dismissable by default --- src/components/ha-wa-dialog.ts | 4 ++-- .../device-registry-detail/dialog-device-registry-detail.ts | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 2a3b11395379..361759d6c9e4 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -46,7 +46,7 @@ export type DialogWidthOnTitleClick = DialogWidth | "none"; * @attr {("small"|"medium"|"large"|"full")} width - Preferred dialog width preset. Defaults to "medium". * @attr {("small"|"medium"|"large"|"full")} current-width - The active dialog width; toggles with title click when enabled. * @attr {("none"|"small"|"medium"|"large"|"full")} width-on-title-click - Target width when clicking the title. "none" disables. - * @attr {boolean} scrim-dismissable - Allows closing the dialog by clicking the scrim/overlay. + * @attr {boolean} scrim-dismissable - Allows closing the dialog by clicking the scrim/overlay. Defaults to true. * @attr {string} header-title - Header title text when no custom title slot is provided. * @attr {string} header-subtitle - Header subtitle text when no custom subtitle slot is provided. * @attr {boolean} flexcontent - Makes the dialog body a flex container for flexible layouts. @@ -75,7 +75,7 @@ export class HaWaDialog extends LitElement { public widthOnTitleClick: DialogWidthOnTitleClick = "none"; @property({ type: Boolean, reflect: true, attribute: "scrim-dismissable" }) - public scrimDismissable = false; + public scrimDismissable = true; @property({ type: String, attribute: "header-title" }) public headerTitle = ""; diff --git a/src/panels/config/devices/device-registry-detail/dialog-device-registry-detail.ts b/src/panels/config/devices/device-registry-detail/dialog-device-registry-detail.ts index e1516b67fb56..6543f9b9743e 100644 --- a/src/panels/config/devices/device-registry-detail/dialog-device-registry-detail.ts +++ b/src/panels/config/devices/device-registry-detail/dialog-device-registry-detail.ts @@ -62,6 +62,7 @@ class DialogDeviceRegistryDetail extends LitElement { open @closed=${this.closeDialog} .headerTitle=${computeDeviceNameDisplay(device, this.hass)} + .scrimDismissable=${false} >
${this._error From 099161d55dbf645431f0ec841011d16f3e47b038 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 29 Sep 2025 11:43:16 +0100 Subject: [PATCH 054/125] Use state for dialog width control --- src/components/ha-wa-dialog.ts | 25 ++++++++++---------- src/dialogs/more-info/ha-more-info-dialog.ts | 2 +- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 361759d6c9e4..3db7ff8843fb 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -44,7 +44,6 @@ export type DialogWidthOnTitleClick = DialogWidth | "none"; * * @attr {boolean} open - Controls the dialog open state. * @attr {("small"|"medium"|"large"|"full")} width - Preferred dialog width preset. Defaults to "medium". - * @attr {("small"|"medium"|"large"|"full")} current-width - The active dialog width; toggles with title click when enabled. * @attr {("none"|"small"|"medium"|"large"|"full")} width-on-title-click - Target width when clicking the title. "none" disables. * @attr {boolean} scrim-dismissable - Allows closing the dialog by clicking the scrim/overlay. Defaults to true. * @attr {string} header-title - Header title text when no custom title slot is provided. @@ -64,9 +63,6 @@ export class HaWaDialog extends LitElement { @property({ type: String, reflect: true, attribute: "width" }) public width: DialogWidth = "medium"; - @property({ type: String, reflect: true, attribute: "current-width" }) - public currentWidth: DialogWidth = this.width; - @property({ type: String, reflect: true, @@ -89,6 +85,9 @@ export class HaWaDialog extends LitElement { @state() private _open = false; + @state() + private _sizeChanged = false; + protected updated( changedProperties: Map ): void { @@ -101,8 +100,10 @@ export class HaWaDialog extends LitElement { } if (changedProperties.has("width")) { - this.currentWidth = this.width; + this._sizeChanged = false; } + + this.classList.toggle("size-changed", this._sizeChanged); } protected render() { @@ -166,10 +167,7 @@ export class HaWaDialog extends LitElement { return; } - this.currentWidth = - this.currentWidth === this.widthOnTitleClick - ? this.width - : this.widthOnTitleClick; + this._sizeChanged = !this._sizeChanged; }; private _onKeyDown = (ev: KeyboardEvent) => { @@ -224,15 +222,18 @@ export class HaWaDialog extends LitElement { max-width: 100%; } - :host([current-width="small"]) wa-dialog { + :host([width="small"]), + :host(.size-changed[width-on-title-click="small"]) wa-dialog { --width: min(320px, var(--full-width)); } - :host([current-width="large"]) wa-dialog { + :host([width="large"]), + :host(.size-changed[width-on-title-click="large"]) wa-dialog { --width: min(720px, var(--full-width)); } - :host([current-width="full"]) wa-dialog { + :host([width="full"]), + :host(.size-changed[width-on-title-click="full"]) wa-dialog { --width: var(--full-width); } diff --git a/src/dialogs/more-info/ha-more-info-dialog.ts b/src/dialogs/more-info/ha-more-info-dialog.ts index dde0772d7ea9..eb3f5c0b7694 100644 --- a/src/dialogs/more-info/ha-more-info-dialog.ts +++ b/src/dialogs/more-info/ha-more-info-dialog.ts @@ -368,7 +368,7 @@ export class MoreInfoDialog extends LitElement { .backAction=${showCloseIcon ? undefined : this._goBack} .headerTitle=${title} .scrimDismissable=${this._isEscapeEnabled} - .dialogSizeOnTitleClick=${"full"} + width-on-title-click="full" flexContent > ${showCloseIcon From e57ac1ef83832a0bfbaf9f93168f26b83c2f04a5 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 29 Sep 2025 11:45:35 +0100 Subject: [PATCH 055/125] Update src/components/ha-wa-dialog.ts Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com> --- src/components/ha-wa-dialog.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 3db7ff8843fb..d73da66baf2b 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -217,7 +217,7 @@ export class HaWaDialog extends LitElement { --ha-dialog-surface-background, var(--mdc-theme-surface, #fff) ); - --wa-panel-border-radius: var(--ha-dialog-border-radius, 24px); + --wa-panel-border-radius: var(--ha-dialog-border-radius, var(--ha-border-radius-3xl)); z-index: var(--dialog-z-index, 8); max-width: 100%; } From ef35eae84dcbcb1350a0c1e61518e15338ae5080 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 29 Sep 2025 12:05:11 +0100 Subject: [PATCH 056/125] Use fireEvent --- src/components/ha-wa-dialog.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index d73da66baf2b..d073a1026fa0 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -5,6 +5,7 @@ import { mdiClose } from "@mdi/js"; import type { HomeAssistant } from "../types"; import "./ha-dialog-header"; import "./ha-icon-button"; +import { fireEvent } from "../common/dom/fire_event"; export type DialogWidth = "small" | "medium" | "large" | "full"; export type DialogWidthOnTitleClick = DialogWidth | "none"; @@ -145,7 +146,7 @@ export class HaWaDialog extends LitElement { private _handleShow = () => { this._open = true; - this.dispatchEvent(new CustomEvent("opened")); + fireEvent(this, "opened"); this.updateComplete.then(() => { const focusElement = this.querySelector( @@ -158,7 +159,7 @@ export class HaWaDialog extends LitElement { private _handleAfterHide = () => { this._open = false; - this.dispatchEvent(new CustomEvent("closed")); + fireEvent(this, "closed"); window.removeEventListener("keydown", this._onKeyDown, true); }; @@ -217,7 +218,10 @@ export class HaWaDialog extends LitElement { --ha-dialog-surface-background, var(--mdc-theme-surface, #fff) ); - --wa-panel-border-radius: var(--ha-dialog-border-radius, var(--ha-border-radius-3xl)); + --wa-panel-border-radius: var( + --ha-dialog-border-radius, + var(--ha-border-radius-3xl) + ); z-index: var(--dialog-z-index, 8); max-width: 100%; } @@ -347,4 +351,9 @@ declare global { interface HTMLElementTagNameMap { "ha-wa-dialog": HaWaDialog; } + + interface HASSDomEvents { + opened: undefined; + closed: undefined; + } } From 11c0fcbf6295d289b3905b9d18bed145df56e4ce Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 29 Sep 2025 12:11:40 +0100 Subject: [PATCH 057/125] Use query, simplify --- src/components/ha-wa-dialog.ts | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index d073a1026fa0..3638544df94e 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -1,5 +1,5 @@ import { css, html, LitElement } from "lit"; -import { customElement, property, state } from "lit/decorators"; +import { customElement, property, state, query } from "lit/decorators"; import "@home-assistant/webawesome/dist/components/dialog/dialog"; import { mdiClose } from "@mdi/js"; import type { HomeAssistant } from "../types"; @@ -89,6 +89,9 @@ export class HaWaDialog extends LitElement { @state() private _sizeChanged = false; + @query('ha-dialog-footer [slot="primaryAction"]') + private _primaryAction!: HTMLElement; + protected updated( changedProperties: Map ): void { @@ -176,19 +179,14 @@ export class HaWaDialog extends LitElement { return; } - const footer = this.querySelector("ha-dialog-footer") as HTMLElement | null; - if (!footer) return; - - const primaryAction = footer.querySelector( - '[slot="primaryAction"]' - ) as HTMLElement | null; - if (!primaryAction) return; - - const isDisabled = - (primaryAction as any).disabled ?? primaryAction.hasAttribute("disabled"); - if (isDisabled) return; + if ( + !this._primaryAction || + !(this._primaryAction instanceof HTMLButtonElement) || + this._primaryAction.disabled + ) + return; - primaryAction.click(); + this._primaryAction.click(); ev.preventDefault(); ev.stopPropagation(); }; From ed3c00f820e78f968856f17fb8855044fa82b234 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 29 Sep 2025 12:23:07 +0100 Subject: [PATCH 058/125] Set on disconnected --- src/components/ha-wa-dialog.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 3638544df94e..3b1381724142 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -166,6 +166,12 @@ export class HaWaDialog extends LitElement { window.removeEventListener("keydown", this._onKeyDown, true); }; + public disconnectedCallback(): void { + super.disconnectedCallback(); + this._open = false; + window.removeEventListener("keydown", this._onKeyDown, true); + } + public toggleWidth = () => { if (this.widthOnTitleClick === "none") { return; From 9fe24123b35bab93f4fb954166f22b766f32dc37 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 29 Sep 2025 12:25:49 +0100 Subject: [PATCH 059/125] Remove enter key handling for initial PR --- src/components/ha-wa-dialog.ts | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 3b1381724142..44e04bdbf290 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -1,5 +1,5 @@ import { css, html, LitElement } from "lit"; -import { customElement, property, state, query } from "lit/decorators"; +import { customElement, property, state } from "lit/decorators"; import "@home-assistant/webawesome/dist/components/dialog/dialog"; import { mdiClose } from "@mdi/js"; import type { HomeAssistant } from "../types"; @@ -89,9 +89,6 @@ export class HaWaDialog extends LitElement { @state() private _sizeChanged = false; - @query('ha-dialog-footer [slot="primaryAction"]') - private _primaryAction!: HTMLElement; - protected updated( changedProperties: Map ): void { @@ -157,19 +154,16 @@ export class HaWaDialog extends LitElement { ) as HTMLElement; focusElement?.focus(); }); - window.addEventListener("keydown", this._onKeyDown, true); }; private _handleAfterHide = () => { this._open = false; fireEvent(this, "closed"); - window.removeEventListener("keydown", this._onKeyDown, true); }; public disconnectedCallback(): void { super.disconnectedCallback(); this._open = false; - window.removeEventListener("keydown", this._onKeyDown, true); } public toggleWidth = () => { @@ -180,23 +174,6 @@ export class HaWaDialog extends LitElement { this._sizeChanged = !this._sizeChanged; }; - private _onKeyDown = (ev: KeyboardEvent) => { - if (!this._open || ev.defaultPrevented || ev.key !== "Enter") { - return; - } - - if ( - !this._primaryAction || - !(this._primaryAction instanceof HTMLButtonElement) || - this._primaryAction.disabled - ) - return; - - this._primaryAction.click(); - ev.preventDefault(); - ev.stopPropagation(); - }; - static styles = css` :host([scrolled]) wa-dialog::part(header) { max-width: 100%; From c0028e2dd016da40c5d00ff9ee9096f9d0bfb62c Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 29 Sep 2025 12:33:27 +0100 Subject: [PATCH 060/125] Use themeable variables --- src/components/ha-wa-dialog.ts | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 44e04bdbf290..f2a88be9261a 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -182,16 +182,13 @@ export class HaWaDialog extends LitElement { } wa-dialog { - --full-width: min( - calc( - 100vw - var(--safe-area-inset-left, 0px) - var( - --safe-area-inset-right, - 0px - ) - ), - 95vw + --full-width: calc( + 100vw - var(--safe-area-inset-left, 0px) - var( + --safe-area-inset-right, + 0px + ) ); - --width: min(580px, 95vw); + --width: min(var(--ha-dialog-width-md, 580px), var(--full-width)); --spacing: var(--dialog-content-padding, 24px); --show-duration: var(--ha-dialog-show-duration, 200ms); --hide-duration: var(--ha-dialog-hide-duration, 200ms); @@ -209,12 +206,12 @@ export class HaWaDialog extends LitElement { :host([width="small"]), :host(.size-changed[width-on-title-click="small"]) wa-dialog { - --width: min(320px, var(--full-width)); + --width: min(var(--ha-dialog-width-sm, 320px), var(--full-width)); } :host([width="large"]), :host(.size-changed[width-on-title-click="large"]) wa-dialog { - --width: min(720px, var(--full-width)); + --width: min(var(--ha-dialog-width-lg, 720px), var(--full-width)); } :host([width="full"]), From 23e998b1d8a5b454906529529aad0c7fcb958731 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 29 Sep 2025 12:41:39 +0100 Subject: [PATCH 061/125] Add note --- src/components/ha-wa-dialog.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index f2a88be9261a..a48d04da797f 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -20,6 +20,10 @@ export type DialogWidthOnTitleClick = DialogWidth | "none"; * A stylable dialog built using the `wa-dialog` component, providing the standard header (ha-dialog-header), * body, and footer (when used in conjunction with `ha-dialog-footer`) with slots, sizing, and keyboard handling. * + * You can open and close the dialog declaratively by using the `data-dialog="close"` attribute. + * @see https://webawesome.com/docs/components/dialog/#opening-and-closing-dialogs-declaratively + * @see src/dialogs/more-info/ha-more-info-dialog.ts#L366 + * * @slot heading - Replace the entire header area. * @slot navigationIcon - Leading header action (e.g. close/back button). * @slot title - Header title. Click can toggle width if `width-on-title-click` is not "none". From a83794be15b66c0d3be48235ae9084630d3dd77c Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 29 Sep 2025 13:21:51 +0100 Subject: [PATCH 062/125] Update --- src/components/ha-wa-dialog.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index a48d04da797f..8798b6f05481 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -198,7 +198,10 @@ export class HaWaDialog extends LitElement { --hide-duration: var(--ha-dialog-hide-duration, 200ms); --wa-color-surface-raised: var( --ha-dialog-surface-background, - var(--mdc-theme-surface, #fff) + var( + --card-background-color, + var(--ha-color-surface-default, var(--mdc-theme-surface, #fff)) + ) ); --wa-panel-border-radius: var( --ha-dialog-border-radius, From f0c1752d676f4c26ac3aae5a36f54415422af315 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 29 Sep 2025 13:25:51 +0100 Subject: [PATCH 063/125] Update --- src/components/ha-wa-dialog.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 8798b6f05481..e96f1fe7e071 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -196,12 +196,13 @@ export class HaWaDialog extends LitElement { --spacing: var(--dialog-content-padding, 24px); --show-duration: var(--ha-dialog-show-duration, 200ms); --hide-duration: var(--ha-dialog-hide-duration, 200ms); + --ha-dialog-surface-background: var( + --card-background-color, + var(--ha-color-surface-default) + ); --wa-color-surface-raised: var( --ha-dialog-surface-background, - var( - --card-background-color, - var(--ha-color-surface-default, var(--mdc-theme-surface, #fff)) - ) + var(--card-background-color, var(--ha-color-surface-default, #fff)) ); --wa-panel-border-radius: var( --ha-dialog-border-radius, From df4b751ca5c9e7fe04ee9d3ab8ca18e7885b7d69 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 29 Sep 2025 13:30:29 +0100 Subject: [PATCH 064/125] Update --- src/components/ha-wa-dialog.ts | 2 +- src/resources/theme/color/semantic.globals.ts | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index e96f1fe7e071..3652a41cb15b 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -202,7 +202,7 @@ export class HaWaDialog extends LitElement { ); --wa-color-surface-raised: var( --ha-dialog-surface-background, - var(--card-background-color, var(--ha-color-surface-default, #fff)) + var(--card-background-color, var(--ha-color-surface-default)) ); --wa-panel-border-radius: var( --ha-dialog-border-radius, diff --git a/src/resources/theme/color/semantic.globals.ts b/src/resources/theme/color/semantic.globals.ts index d99291d83314..f34b3ca9db25 100644 --- a/src/resources/theme/color/semantic.globals.ts +++ b/src/resources/theme/color/semantic.globals.ts @@ -152,6 +152,10 @@ export const semanticColorStyles = css` --ha-color-on-success-quiet: var(--ha-color-green-50); --ha-color-on-success-normal: var(--ha-color-green-40); --ha-color-on-success-loud: var(--white-color); + + /* Surfaces */ + --ha-color-surface-default: var(--card-background-color); + --ha-color-on-surface-default: var(--primary-text-color); } `; @@ -280,5 +284,10 @@ export const darkSemanticColorStyles = css` --ha-color-on-success-quiet: var(--ha-color-green-70); --ha-color-on-success-normal: var(--ha-color-green-60); --ha-color-on-success-loud: var(--white-color); + + /* surfaces */ + /* Bridge to legacy while we introduce semantic surfaces */ + --ha-color-surface-default: var(--card-background-color); + --ha-color-on-surface-default: var(--primary-text-color); } `; From 5843d6e30e17130db7850824f9b2c73898debb76 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 29 Sep 2025 13:32:15 +0100 Subject: [PATCH 065/125] Remove comment --- src/resources/theme/color/semantic.globals.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/resources/theme/color/semantic.globals.ts b/src/resources/theme/color/semantic.globals.ts index f34b3ca9db25..20a893a7d8eb 100644 --- a/src/resources/theme/color/semantic.globals.ts +++ b/src/resources/theme/color/semantic.globals.ts @@ -285,8 +285,7 @@ export const darkSemanticColorStyles = css` --ha-color-on-success-normal: var(--ha-color-green-60); --ha-color-on-success-loud: var(--white-color); - /* surfaces */ - /* Bridge to legacy while we introduce semantic surfaces */ + /* Surfaces */ --ha-color-surface-default: var(--card-background-color); --ha-color-on-surface-default: var(--primary-text-color); } From 7f4afd2cbebe63cc02f1cb55e99354e5a5715167 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 29 Sep 2025 14:28:47 +0100 Subject: [PATCH 066/125] Use ha variables --- src/components/ha-wa-dialog.ts | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 3652a41cb15b..6a2c945a5a6d 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -277,16 +277,22 @@ export class HaWaDialog extends LitElement { .header-title { margin: 0; margin-bottom: 0; - color: var(--mdc-dialog-heading-ink-color, rgba(0, 0, 0, 0.87)); - font-size: var(--mdc-typography-headline6-font-size, 1.574rem); - line-height: var(--mdc-typography-headline6-line-height, 2rem); + color: var( + --ha-dialog-header-title-color, + var(--ha-color-on-surface-default, var(--primary-text-color)) + ); + font-size: var( + --ha-dialog-header-title-font-size, + var(--ha-font-size-2xl) + ); + line-height: var( + --ha-dialog-header-title-line-height, + var(--ha-line-height-condensed) + ); font-weight: var( - --mdc-typography-headline6-font-weight, + --ha-dialog-header-title-font-weight, var(--ha-font-weight-normal) ); - letter-spacing: var(--mdc-typography-headline6-letter-spacing, 0.0125em); - text-decoration: var(--mdc-typography-headline6-text-decoration, inherit); - text-transform: var(--mdc-typography-headline6-text-transform, inherit); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; From 9f242658962b3bba730d05fa29d99c5ad5dc77aa Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 29 Sep 2025 14:30:53 +0100 Subject: [PATCH 067/125] Docstring --- src/components/ha-dialog-footer.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/components/ha-dialog-footer.ts b/src/components/ha-dialog-footer.ts index 58eb42cbd7be..33f82ea09152 100644 --- a/src/components/ha-dialog-footer.ts +++ b/src/components/ha-dialog-footer.ts @@ -1,6 +1,19 @@ 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. + */ @customElement("ha-dialog-footer") export class HaDialogFooter extends LitElement { protected render() { From 812fe506a7bffaea62c1979f01bc0f448866cb06 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 29 Sep 2025 14:31:50 +0100 Subject: [PATCH 068/125] Cleanup --- src/components/ha-wa-dialog.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 6a2c945a5a6d..1487a2d4eccf 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -320,9 +320,6 @@ export class HaWaDialog extends LitElement { display: flex; flex-direction: column; } - :host([hideactions]) wa-dialog::part(body) { - padding-bottom: var(--dialog-content-padding, 24px); - } wa-dialog::part(footer) { padding: 0; From 65dfc88becbc8d0c9b748aa5640a0074a41f7337 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 29 Sep 2025 16:10:22 +0100 Subject: [PATCH 069/125] Remove --- src/dialogs/more-info/ha-more-info-dialog.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/dialogs/more-info/ha-more-info-dialog.ts b/src/dialogs/more-info/ha-more-info-dialog.ts index eb3f5c0b7694..f38c2805f714 100644 --- a/src/dialogs/more-info/ha-more-info-dialog.ts +++ b/src/dialogs/more-info/ha-more-info-dialog.ts @@ -365,7 +365,6 @@ export class MoreInfoDialog extends LitElement { .backLabel=${this.hass.localize( "ui.dialogs.more_info_control.back_to_info" )} - .backAction=${showCloseIcon ? undefined : this._goBack} .headerTitle=${title} .scrimDismissable=${this._isEscapeEnabled} width-on-title-click="full" From f2ebc435693bfa27bf77900ffb41adf92476bcc0 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 29 Sep 2025 16:33:13 +0100 Subject: [PATCH 070/125] Fix z-index application --- src/components/ha-wa-dialog.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 1487a2d4eccf..284251f1103d 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -208,6 +208,7 @@ export class HaWaDialog extends LitElement { --ha-dialog-border-radius, var(--ha-border-radius-3xl) ); + position: var(--dialog-surface-position, relative); z-index: var(--dialog-z-index, 8); max-width: 100%; } From cfb03abfdf31c33f8eddd2ad0f8e9377b312486f Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Tue, 30 Sep 2025 10:17:29 +0100 Subject: [PATCH 071/125] Use core color variables --- src/resources/theme/color/semantic.globals.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/resources/theme/color/semantic.globals.ts b/src/resources/theme/color/semantic.globals.ts index 20a893a7d8eb..e0fa043be759 100644 --- a/src/resources/theme/color/semantic.globals.ts +++ b/src/resources/theme/color/semantic.globals.ts @@ -154,8 +154,8 @@ export const semanticColorStyles = css` --ha-color-on-success-loud: var(--white-color); /* Surfaces */ - --ha-color-surface-default: var(--card-background-color); - --ha-color-on-surface-default: var(--primary-text-color); + --ha-color-surface-default: var(--ha-color-neutral-95); + --ha-color-on-surface-default: var(--ha-color-neutral-05); } `; @@ -286,7 +286,7 @@ export const darkSemanticColorStyles = css` --ha-color-on-success-loud: var(--white-color); /* Surfaces */ - --ha-color-surface-default: var(--card-background-color); - --ha-color-on-surface-default: var(--primary-text-color); + --ha-color-surface-default: var(--ha-color-neutral-10); + --ha-color-on-surface-default: var(--ha-color-neutral-95); } `; From c782615f50981f18f59271b7216d6f85f37d2180 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Tue, 30 Sep 2025 10:25:11 +0100 Subject: [PATCH 072/125] Revert instead of resetting --- src/dialogs/more-info/ha-more-info-dialog.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dialogs/more-info/ha-more-info-dialog.ts b/src/dialogs/more-info/ha-more-info-dialog.ts index f38c2805f714..93f4e17ff17f 100644 --- a/src/dialogs/more-info/ha-more-info-dialog.ts +++ b/src/dialogs/more-info/ha-more-info-dialog.ts @@ -703,7 +703,7 @@ export class MoreInfoDialog extends LitElement { @media all and (max-width: 450px), all and (max-height: 500px) { ha-wa-dialog { - --dialog-surface-margin-top: var(--safe-area-inset-top, 0px); + --dialog-surface-margin-top: initial; } } From 6a06a5ece3ba0002d67c265dea78a5814a5a848c Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Tue, 30 Sep 2025 10:30:29 +0100 Subject: [PATCH 073/125] 95vw for desktop, 100vw - safe areas for mobile --- src/components/ha-wa-dialog.ts | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 284251f1103d..f60b7b6acb03 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -186,11 +186,14 @@ export class HaWaDialog extends LitElement { } wa-dialog { - --full-width: calc( - 100vw - var(--safe-area-inset-left, 0px) - var( - --safe-area-inset-right, - 0px - ) + --full-width: min( + 95vw, + calc( + 100vw - var(--safe-area-inset-left, 0px) - var( + --safe-area-inset-right, + 0px + ) + ) ); --width: min(var(--ha-dialog-width-md, 580px), var(--full-width)); --spacing: var(--dialog-content-padding, 24px); @@ -247,6 +250,15 @@ export class HaWaDialog extends LitElement { --ha-dialog-border-radius: 0px; } + wa-dialog { + --full-width: calc( + 100vw - var(--safe-area-inset-left, 0px) - var( + --safe-area-inset-right, + 0px + ) + ); + } + wa-dialog::part(dialog) { min-height: 100vh; } From badb6f935184ee0b37be872cb6e9545f504b9bea Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Tue, 30 Sep 2025 10:46:31 +0100 Subject: [PATCH 074/125] Use padding for safe areas --- src/components/ha-wa-dialog.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index f60b7b6acb03..bb614b94f312 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -251,16 +251,15 @@ export class HaWaDialog extends LitElement { } wa-dialog { - --full-width: calc( - 100vw - var(--safe-area-inset-left, 0px) - var( - --safe-area-inset-right, - 0px - ) - ); + --full-width: 100vw; } wa-dialog::part(dialog) { min-height: 100vh; + padding-top: var(--safe-area-inset-top, 0px); + padding-bottom: var(--safe-area-inset-bottom, 0px); + padding-left: var(--safe-area-inset-left, 0px); + padding-right: var(--safe-area-inset-right, 0px); } } From a27b16bdcd34b283c93a483263c8f3c68f343f78 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Tue, 30 Sep 2025 13:37:59 +0100 Subject: [PATCH 075/125] % not vh --- src/components/ha-wa-dialog.ts | 2 +- temp/android | 1 + temp/iOS | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) create mode 160000 temp/android create mode 160000 temp/iOS diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index bb614b94f312..33cd9995ee63 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -255,7 +255,7 @@ export class HaWaDialog extends LitElement { } wa-dialog::part(dialog) { - min-height: 100vh; + min-height: 100%; padding-top: var(--safe-area-inset-top, 0px); padding-bottom: var(--safe-area-inset-bottom, 0px); padding-left: var(--safe-area-inset-left, 0px); diff --git a/temp/android b/temp/android new file mode 160000 index 000000000000..45838ad30679 --- /dev/null +++ b/temp/android @@ -0,0 +1 @@ +Subproject commit 45838ad3067970fd1f52ac320abcc7f5ee6caffb diff --git a/temp/iOS b/temp/iOS new file mode 160000 index 000000000000..d967794caf7f --- /dev/null +++ b/temp/iOS @@ -0,0 +1 @@ +Subproject commit d967794caf7faf074060aaed5e6b18b2419e5670 From 5765256e296b62f1de0df842946379bb5d87c1a1 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Tue, 30 Sep 2025 13:43:47 +0100 Subject: [PATCH 076/125] minmax --- src/components/ha-wa-dialog.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 33cd9995ee63..7b1fade7ff4c 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -211,8 +211,6 @@ export class HaWaDialog extends LitElement { --ha-dialog-border-radius, var(--ha-border-radius-3xl) ); - position: var(--dialog-surface-position, relative); - z-index: var(--dialog-z-index, 8); max-width: 100%; } @@ -256,6 +254,7 @@ export class HaWaDialog extends LitElement { wa-dialog::part(dialog) { min-height: 100%; + max-height: 100%; padding-top: var(--safe-area-inset-top, 0px); padding-bottom: var(--safe-area-inset-bottom, 0px); padding-left: var(--safe-area-inset-left, 0px); From 3563ceadcd39e7ad9dbe31f129f37985392b328a Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Tue, 30 Sep 2025 13:53:53 +0100 Subject: [PATCH 077/125] Invert --- src/components/ha-wa-dialog.ts | 8 ++++---- src/dialogs/more-info/ha-more-info-dialog.ts | 2 +- .../dialog-device-registry-detail.ts | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 7b1fade7ff4c..11aa093d83db 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -50,7 +50,7 @@ export type DialogWidthOnTitleClick = DialogWidth | "none"; * @attr {boolean} open - Controls the dialog open state. * @attr {("small"|"medium"|"large"|"full")} width - Preferred dialog width preset. Defaults to "medium". * @attr {("none"|"small"|"medium"|"large"|"full")} width-on-title-click - Target width when clicking the title. "none" disables. - * @attr {boolean} scrim-dismissable - Allows closing the dialog by clicking the scrim/overlay. Defaults to true. + * @attr {boolean} prevent-scrim-close - Prevents closing the dialog by clicking the scrim/overlay. Defaults to false. * @attr {string} header-title - Header title text when no custom title slot is provided. * @attr {string} header-subtitle - Header subtitle text when no custom subtitle slot is provided. * @attr {boolean} flexcontent - Makes the dialog body a flex container for flexible layouts. @@ -75,8 +75,8 @@ export class HaWaDialog extends LitElement { }) public widthOnTitleClick: DialogWidthOnTitleClick = "none"; - @property({ type: Boolean, reflect: true, attribute: "scrim-dismissable" }) - public scrimDismissable = true; + @property({ type: Boolean, reflect: true, attribute: "prevent-scrim-close" }) + public preventScrimClose = false; @property({ type: String, attribute: "header-title" }) public headerTitle = ""; @@ -115,7 +115,7 @@ export class HaWaDialog extends LitElement { return html` diff --git a/src/panels/config/devices/device-registry-detail/dialog-device-registry-detail.ts b/src/panels/config/devices/device-registry-detail/dialog-device-registry-detail.ts index 6543f9b9743e..72a0b0795014 100644 --- a/src/panels/config/devices/device-registry-detail/dialog-device-registry-detail.ts +++ b/src/panels/config/devices/device-registry-detail/dialog-device-registry-detail.ts @@ -62,7 +62,7 @@ class DialogDeviceRegistryDetail extends LitElement { open @closed=${this.closeDialog} .headerTitle=${computeDeviceNameDisplay(device, this.hass)} - .scrimDismissable=${false} + .preventScrimClose=${true} >
${this._error From 29dbbeb64cb1b5922bbbcf91bc1b719f298f54bf Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Tue, 30 Sep 2025 14:47:03 +0100 Subject: [PATCH 078/125] % --- src/components/ha-wa-dialog.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 11aa093d83db..6278031c42a2 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -232,7 +232,7 @@ export class HaWaDialog extends LitElement { wa-dialog::part(dialog) { min-width: var(--width, var(--full-width)); max-width: var(--width, var(--full-width)); - max-height: 100vh; + max-height: 100%; position: var(--dialog-surface-position, relative); margin-top: var(--dialog-surface-margin-top, auto); transition: @@ -254,7 +254,6 @@ export class HaWaDialog extends LitElement { wa-dialog::part(dialog) { min-height: 100%; - max-height: 100%; padding-top: var(--safe-area-inset-top, 0px); padding-bottom: var(--safe-area-inset-bottom, 0px); padding-left: var(--safe-area-inset-left, 0px); From 1cfd1ce0d18cf3d92c0566b7b9af9af861ad388e Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Tue, 30 Sep 2025 15:39:57 +0100 Subject: [PATCH 079/125] Fix scrollbar style and dialog max height --- src/components/ha-wa-dialog.ts | 304 +++++++++++++++++---------------- 1 file changed, 154 insertions(+), 150 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 6278031c42a2..2d124163c3a0 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -2,10 +2,11 @@ import { css, html, LitElement } from "lit"; import { customElement, property, state } from "lit/decorators"; import "@home-assistant/webawesome/dist/components/dialog/dialog"; import { mdiClose } from "@mdi/js"; -import type { HomeAssistant } from "../types"; import "./ha-dialog-header"; import "./ha-icon-button"; +import type { HomeAssistant } from "../types"; import { fireEvent } from "../common/dom/fire_event"; +import { haStyleScrollbar } from "../resources/styles"; export type DialogWidth = "small" | "medium" | "large" | "full"; export type DialogWidthOnTitleClick = DialogWidth | "none"; @@ -178,172 +179,175 @@ export class HaWaDialog extends LitElement { this._sizeChanged = !this._sizeChanged; }; - static styles = css` - :host([scrolled]) wa-dialog::part(header) { - max-width: 100%; - border-bottom: 1px solid - var(--dialog-scroll-divider-color, var(--divider-color)); - } - - wa-dialog { - --full-width: min( - 95vw, - calc( - 100vw - var(--safe-area-inset-left, 0px) - var( - --safe-area-inset-right, - 0px - ) - ) - ); - --width: min(var(--ha-dialog-width-md, 580px), var(--full-width)); - --spacing: var(--dialog-content-padding, 24px); - --show-duration: var(--ha-dialog-show-duration, 200ms); - --hide-duration: var(--ha-dialog-hide-duration, 200ms); - --ha-dialog-surface-background: var( - --card-background-color, - var(--ha-color-surface-default) - ); - --wa-color-surface-raised: var( - --ha-dialog-surface-background, - var(--card-background-color, var(--ha-color-surface-default)) - ); - --wa-panel-border-radius: var( - --ha-dialog-border-radius, - var(--ha-border-radius-3xl) - ); - max-width: 100%; - } - - :host([width="small"]), - :host(.size-changed[width-on-title-click="small"]) wa-dialog { - --width: min(var(--ha-dialog-width-sm, 320px), var(--full-width)); - } - - :host([width="large"]), - :host(.size-changed[width-on-title-click="large"]) wa-dialog { - --width: min(var(--ha-dialog-width-lg, 720px), var(--full-width)); - } + static styles = [ + haStyleScrollbar, + css` + :host([scrolled]) wa-dialog::part(header) { + max-width: 100%; + border-bottom: 1px solid + var(--dialog-scroll-divider-color, var(--divider-color)); + } - :host([width="full"]), - :host(.size-changed[width-on-title-click="full"]) wa-dialog { - --width: var(--full-width); - } + wa-dialog { + --full-width: min( + 95vw, + calc( + 100vw - var(--safe-area-inset-left, 0px) - var( + --safe-area-inset-right, + 0px + ) + ) + ); + --width: min(var(--ha-dialog-width-md, 580px), var(--full-width)); + --spacing: var(--dialog-content-padding, 24px); + --show-duration: var(--ha-dialog-show-duration, 200ms); + --hide-duration: var(--ha-dialog-hide-duration, 200ms); + --ha-dialog-surface-background: var( + --card-background-color, + var(--ha-color-surface-default) + ); + --wa-color-surface-raised: var( + --ha-dialog-surface-background, + var(--card-background-color, var(--ha-color-surface-default)) + ); + --wa-panel-border-radius: var( + --ha-dialog-border-radius, + var(--ha-border-radius-3xl) + ); + max-width: 100%; + } - wa-dialog::part(dialog) { - min-width: var(--width, var(--full-width)); - max-width: var(--width, var(--full-width)); - max-height: 100%; - position: var(--dialog-surface-position, relative); - margin-top: var(--dialog-surface-margin-top, auto); - transition: - min-width var(--ha-dialog-expand-duration, 200ms) ease-in-out, - max-width var(--ha-dialog-expand-duration, 200ms) ease-in-out; - display: flex; - flex-direction: column; - overflow: hidden; - } + :host([width="small"]), + :host(.size-changed[width-on-title-click="small"]) wa-dialog { + --width: min(var(--ha-dialog-width-sm, 320px), var(--full-width)); + } - @media all and (max-width: 450px), all and (max-height: 500px) { - :host { - --ha-dialog-border-radius: 0px; + :host([width="large"]), + :host(.size-changed[width-on-title-click="large"]) wa-dialog { + --width: min(var(--ha-dialog-width-lg, 720px), var(--full-width)); } - wa-dialog { - --full-width: 100vw; + :host([width="full"]), + :host(.size-changed[width-on-title-click="full"]) wa-dialog { + --width: var(--full-width); } wa-dialog::part(dialog) { - min-height: 100%; - padding-top: var(--safe-area-inset-top, 0px); - padding-bottom: var(--safe-area-inset-bottom, 0px); - padding-left: var(--safe-area-inset-left, 0px); - padding-right: var(--safe-area-inset-right, 0px); + min-width: var(--width, var(--full-width)); + max-width: var(--width, var(--full-width)); + max-height: calc(100% - 80px); + position: var(--dialog-surface-position, relative); + margin-top: var(--dialog-surface-margin-top, auto); + transition: + min-width var(--ha-dialog-expand-duration, 200ms) ease-in-out, + max-width var(--ha-dialog-expand-duration, 200ms) ease-in-out; + display: flex; + flex-direction: column; + overflow: hidden; } - } - wa-dialog::part(header) { - max-width: 100%; - overflow: hidden; - display: flex; - align-items: center; - padding: 24px 24px 16px 24px; - gap: 4px; - } - :host([has-custom-heading]) wa-dialog::part(header) { - max-width: 100%; - padding: 0; - } + @media all and (max-width: 450px), all and (max-height: 500px) { + :host { + --ha-dialog-border-radius: 0px; + } + + wa-dialog { + --full-width: 100vw; + } + + wa-dialog::part(dialog) { + min-height: 100%; + padding-top: var(--safe-area-inset-top, 0px); + padding-bottom: var(--safe-area-inset-bottom, 0px); + padding-left: var(--safe-area-inset-left, 0px); + padding-right: var(--safe-area-inset-right, 0px); + } + } - wa-dialog::part(close-button), - wa-dialog::part(close-button__base) { - display: none; - } + wa-dialog::part(header) { + max-width: 100%; + overflow: hidden; + display: flex; + align-items: center; + padding: 24px 24px 16px 24px; + gap: 4px; + } + :host([has-custom-heading]) wa-dialog::part(header) { + max-width: 100%; + padding: 0; + } - .header-title-container { - display: flex; - align-items: center; - } + wa-dialog::part(close-button), + wa-dialog::part(close-button__base) { + display: none; + } - .header-title { - margin: 0; - margin-bottom: 0; - color: var( - --ha-dialog-header-title-color, - var(--ha-color-on-surface-default, var(--primary-text-color)) - ); - font-size: var( - --ha-dialog-header-title-font-size, - var(--ha-font-size-2xl) - ); - line-height: var( - --ha-dialog-header-title-line-height, - var(--ha-line-height-condensed) - ); - font-weight: var( - --ha-dialog-header-title-font-weight, - var(--ha-font-weight-normal) - ); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - margin-right: 12px; - } + .header-title-container { + display: flex; + align-items: center; + } - wa-dialog::part(body) { - padding: 0; - display: flex; - flex-direction: column; - max-width: 100%; - overflow: hidden; - } + .header-title { + margin: 0; + margin-bottom: 0; + color: var( + --ha-dialog-header-title-color, + var(--ha-color-on-surface-default, var(--primary-text-color)) + ); + font-size: var( + --ha-dialog-header-title-font-size, + var(--ha-font-size-2xl) + ); + line-height: var( + --ha-dialog-header-title-line-height, + var(--ha-line-height-condensed) + ); + font-weight: var( + --ha-dialog-header-title-font-weight, + var(--ha-font-weight-normal) + ); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + margin-right: 12px; + } - .body { - position: var(--dialog-content-position, relative); - padding: 0 var(--dialog-content-padding, 24px) - var(--dialog-content-padding, 24px) var(--dialog-content-padding, 24px); - overflow: auto; - flex-grow: 1; - } - :host([flexcontent]) .body { - max-width: 100%; - overflow: hidden; - display: flex; - flex-direction: column; - } + wa-dialog::part(body) { + padding: 0; + display: flex; + flex-direction: column; + max-width: 100%; + overflow: hidden; + } - wa-dialog::part(footer) { - padding: 0; - } + .body { + position: var(--dialog-content-position, relative); + padding: 0 var(--dialog-content-padding, 24px) + var(--dialog-content-padding, 24px) + var(--dialog-content-padding, 24px); + overflow: auto; + flex-grow: 1; + } + :host([flexcontent]) .body { + max-width: 100%; + display: flex; + flex-direction: column; + } - ::slotted([slot="footer"]) { - display: flex; - padding: 12px 16px 16px 16px; - gap: 12px; - justify-content: flex-end; - align-items: center; - width: 100%; - } - `; + wa-dialog::part(footer) { + padding: 0; + } + + ::slotted([slot="footer"]) { + display: flex; + padding: 12px 16px 16px 16px; + gap: 12px; + justify-content: flex-end; + align-items: center; + width: 100%; + } + `, + ]; } declare global { From b822aadf9afa0ecb911586e22d1d5ebcdb7364e4 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Tue, 30 Sep 2025 15:41:57 +0100 Subject: [PATCH 080/125] Clean --- temp/android | 1 - temp/iOS | 1 - 2 files changed, 2 deletions(-) delete mode 160000 temp/android delete mode 160000 temp/iOS diff --git a/temp/android b/temp/android deleted file mode 160000 index 45838ad30679..000000000000 --- a/temp/android +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 45838ad3067970fd1f52ac320abcc7f5ee6caffb diff --git a/temp/iOS b/temp/iOS deleted file mode 160000 index d967794caf7f..000000000000 --- a/temp/iOS +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d967794caf7faf074060aaed5e6b18b2419e5670 From 33da0077885fb9d649f177a6cbdb75e541d62f76 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Tue, 30 Sep 2025 15:44:01 +0100 Subject: [PATCH 081/125] Update comment --- src/components/ha-wa-dialog.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 2d124163c3a0..408b11c7f614 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -18,8 +18,8 @@ export type DialogWidthOnTitleClick = DialogWidth | "none"; * @extends {LitElement} * * @summary - * A stylable dialog built using the `wa-dialog` component, providing the standard header (ha-dialog-header), - * body, and footer (when used in conjunction with `ha-dialog-footer`) with slots, sizing, and keyboard handling. + * A stylable dialog built using the `wa-dialog` component, providing a standardized header (ha-dialog-header), + * body, and footer (preferably using `ha-dialog-footer`) with slots * * You can open and close the dialog declaratively by using the `data-dialog="close"` attribute. * @see https://webawesome.com/docs/components/dialog/#opening-and-closing-dialogs-declaratively @@ -31,7 +31,7 @@ export type DialogWidthOnTitleClick = DialogWidth | "none"; * @slot subtitle - Header subtitle, shown under the title. * @slot actionItems - Trailing header actions (e.g. buttons, menus). * @slot - Dialog content body. - * @slot footer - Dialog footer content; typically action buttons. + * @slot footer - Dialog footer content. * * @csspart dialog - The dialog surface. * @csspart header - The header container. From 0f4531bb2758bb5dd0af88eb1e8ac2f643dc1d48 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Tue, 30 Sep 2025 15:51:34 +0100 Subject: [PATCH 082/125] Simplify --- src/components/ha-dialog-header.ts | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/src/components/ha-dialog-header.ts b/src/components/ha-dialog-header.ts index bfa9dee72185..b97604c1f645 100644 --- a/src/components/ha-dialog-header.ts +++ b/src/components/ha-dialog-header.ts @@ -7,27 +7,12 @@ export class HaDialogHeader extends LitElement { @state() private _hasSubtitle = false; - protected firstUpdated() { - this._checkSubtitleContent(); - } - private _checkSubtitleContent() { const subtitleSlot = this.shadowRoot?.querySelector( 'slot[name="subtitle"]' ) as HTMLSlotElement; - if (subtitleSlot) { - const assignedNodes = subtitleSlot.assignedNodes({ flatten: true }); - this._hasSubtitle = assignedNodes.some((node) => { - let text: string | any[] | undefined; - if (node.nodeType === Node.TEXT_NODE) { - text = node.textContent?.trim(); - } - if (node.nodeType === Node.ELEMENT_NODE) { - text = (node as Element).textContent?.trim(); - } - return text && text.length > 0; - }); - } + this._hasSubtitle = + subtitleSlot?.assignedNodes({ flatten: true }).length > 0; } protected render() { From c97d460001fb1b858195af4b287f95cf50167315 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Tue, 30 Sep 2025 15:53:28 +0100 Subject: [PATCH 083/125] Make reusable --- src/common/dom/has-slot-content.ts | 7 +++++++ src/components/ha-dialog-header.ts | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 src/common/dom/has-slot-content.ts diff --git a/src/common/dom/has-slot-content.ts b/src/common/dom/has-slot-content.ts new file mode 100644 index 000000000000..b619a554411f --- /dev/null +++ b/src/common/dom/has-slot-content.ts @@ -0,0 +1,7 @@ +/** + * Check if a slot has any assigned content + * @param slot - The HTMLSlotElement to check + * @returns true if the slot has any assigned nodes + */ +export const hasSlotContent = (slot: HTMLSlotElement | null): boolean => + slot ? slot.assignedNodes({ flatten: true }).length > 0 : false; diff --git a/src/components/ha-dialog-header.ts b/src/components/ha-dialog-header.ts index b97604c1f645..8049ce312176 100644 --- a/src/components/ha-dialog-header.ts +++ b/src/components/ha-dialog-header.ts @@ -1,6 +1,7 @@ import { css, html, LitElement } from "lit"; import { customElement, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; +import { hasSlotContent } from "../common/dom/has-slot-content"; @customElement("ha-dialog-header") export class HaDialogHeader extends LitElement { @@ -11,8 +12,7 @@ export class HaDialogHeader extends LitElement { const subtitleSlot = this.shadowRoot?.querySelector( 'slot[name="subtitle"]' ) as HTMLSlotElement; - this._hasSubtitle = - subtitleSlot?.assignedNodes({ flatten: true }).length > 0; + this._hasSubtitle = hasSlotContent(subtitleSlot); } protected render() { From 144c4d3b40d78b76cc09fc746d0e1092dc810d79 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Tue, 30 Sep 2025 15:54:07 +0100 Subject: [PATCH 084/125] Use query --- src/components/ha-dialog-header.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/ha-dialog-header.ts b/src/components/ha-dialog-header.ts index 8049ce312176..0286c7b8e977 100644 --- a/src/components/ha-dialog-header.ts +++ b/src/components/ha-dialog-header.ts @@ -1,5 +1,5 @@ import { css, html, LitElement } from "lit"; -import { customElement, state } from "lit/decorators"; +import { customElement, query, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { hasSlotContent } from "../common/dom/has-slot-content"; @@ -8,11 +8,11 @@ export class HaDialogHeader extends LitElement { @state() private _hasSubtitle = false; + @query('slot[name="subtitle"]') + private _subtitleSlot!: HTMLSlotElement; + private _checkSubtitleContent() { - const subtitleSlot = this.shadowRoot?.querySelector( - 'slot[name="subtitle"]' - ) as HTMLSlotElement; - this._hasSubtitle = hasSlotContent(subtitleSlot); + this._hasSubtitle = hasSlotContent(this._subtitleSlot); } protected render() { From 233cd24cf0668c9b4703895ba678c0bb154788e4 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Tue, 30 Sep 2025 15:59:15 +0100 Subject: [PATCH 085/125] Make subtitle position above or below based on prop --- src/components/ha-dialog-header.ts | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/components/ha-dialog-header.ts b/src/components/ha-dialog-header.ts index 0286c7b8e977..efae6d616bc2 100644 --- a/src/components/ha-dialog-header.ts +++ b/src/components/ha-dialog-header.ts @@ -1,10 +1,13 @@ import { css, html, LitElement } from "lit"; -import { customElement, query, state } from "lit/decorators"; +import { customElement, property, query, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { hasSlotContent } from "../common/dom/has-slot-content"; @customElement("ha-dialog-header") export class HaDialogHeader extends LitElement { + @property({ type: String, attribute: "subtitle-position" }) + public subtitlePosition: "above" | "below" = "below"; + @state() private _hasSubtitle = false; @@ -16,6 +19,14 @@ export class HaDialogHeader extends LitElement { } protected render() { + const titleSlot = html`
+ +
`; + + const subtitleSlot = html`
+ +
`; + return html`
-
- -
-
- -
+ ${this.subtitlePosition === "above" + ? html`${subtitleSlot}${titleSlot}` + : html`${titleSlot}${subtitleSlot}`}
From c69d7313072947fa1ef329ec45b495f37ccaf617 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Tue, 30 Sep 2025 16:03:27 +0100 Subject: [PATCH 086/125] Passthrough header subtitle position --- src/components/ha-wa-dialog.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 408b11c7f614..97c558d09440 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -54,6 +54,7 @@ export type DialogWidthOnTitleClick = DialogWidth | "none"; * @attr {boolean} prevent-scrim-close - Prevents closing the dialog by clicking the scrim/overlay. Defaults to false. * @attr {string} header-title - Header title text when no custom title slot is provided. * @attr {string} header-subtitle - Header subtitle text when no custom subtitle slot is provided. + * @attr {("above"|"below")} header-subtitle-position - Position of the subtitle relative to the title. Defaults to "below". * @attr {boolean} flexcontent - Makes the dialog body a flex container for flexible layouts. * * @event opened - Fired when the dialog is shown. @@ -85,6 +86,9 @@ export class HaWaDialog extends LitElement { @property({ type: String, attribute: "header-subtitle" }) public headerSubtitle = ""; + @property({ type: String, attribute: "header-subtitle-position" }) + public headerSubtitlePosition: "above" | "below" = "below"; + @property({ type: Boolean, reflect: true, attribute: "flexcontent" }) public flexContent = false; @@ -122,7 +126,7 @@ export class HaWaDialog extends LitElement { @wa-after-hide=${this._handleAfterHide} > - + Date: Tue, 30 Sep 2025 16:25:00 +0100 Subject: [PATCH 087/125] Use medium weight --- src/components/ha-dialog-header.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ha-dialog-header.ts b/src/components/ha-dialog-header.ts index efae6d616bc2..4093121ef6a6 100644 --- a/src/components/ha-dialog-header.ts +++ b/src/components/ha-dialog-header.ts @@ -83,7 +83,7 @@ export class HaDialogHeader extends LitElement { .header-title { font-size: var(--ha-font-size-xl); line-height: var(--ha-line-height-condensed); - font-weight: var(--ha-font-weight-normal); + font-weight: var(--ha-font-weight-medium); } .header-subtitle { font-size: var(--ha-font-size-m); From 02e5297b3644b278970720fcc1546561238f19e0 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Tue, 30 Sep 2025 16:33:37 +0100 Subject: [PATCH 088/125] Add design notes --- src/components/ha-dialog-footer.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/components/ha-dialog-footer.ts b/src/components/ha-dialog-footer.ts index 33f82ea09152..93fee8427a74 100644 --- a/src/components/ha-dialog-footer.ts +++ b/src/components/ha-dialog-footer.ts @@ -13,6 +13,11 @@ import { customElement } from "lit/decorators"; * * @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 { From c4fcc8118ac75b3784bf3d33dd840c8d0907000e Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Wed, 1 Oct 2025 09:14:43 +0100 Subject: [PATCH 089/125] Align header to design --- src/components/ha-dialog-header.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/ha-dialog-header.ts b/src/components/ha-dialog-header.ts index 4093121ef6a6..b86e809063c3 100644 --- a/src/components/ha-dialog-header.ts +++ b/src/components/ha-dialog-header.ts @@ -57,6 +57,7 @@ export class HaDialogHeader extends LitElement { css` :host { display: block; + min-height: 48px; } :host([show-border]) { border-bottom: 1px solid @@ -87,7 +88,7 @@ export class HaDialogHeader extends LitElement { } .header-subtitle { font-size: var(--ha-font-size-m); - line-height: 20px; + line-height: var(--ha-line-height-normal); color: var(--secondary-text-color); } @media all and (min-width: 450px) and (min-height: 500px) { From c7a356fe6093d3b364f3141367e91d686e25667a Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Wed, 1 Oct 2025 13:26:23 +0100 Subject: [PATCH 090/125] Tooltips should render within the component --- src/components/chart/ha-chart-base.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/components/chart/ha-chart-base.ts b/src/components/chart/ha-chart-base.ts index f5d2f606a0cb..184aeb032a4e 100644 --- a/src/components/chart/ha-chart-base.ts +++ b/src/components/chart/ha-chart-base.ts @@ -586,15 +586,19 @@ export class HaChartBase extends LitElement { const isMobile = window.matchMedia( "all and (max-width: 450px), all and (max-height: 500px)" ).matches; - if (isMobile && options.tooltip) { - // mobile charts are full width so we need to confine the tooltip to the chart + if (options.tooltip) { const tooltips = Array.isArray(options.tooltip) ? options.tooltip : [options.tooltip]; tooltips.forEach((tooltip) => { - tooltip.confine = true; - tooltip.appendTo = undefined; - tooltip.triggerOn = "click"; + // Tooltips should render within the component, not in the body of the html + tooltip.appendTo = () => + this.renderRoot.querySelector(".chart-container") as HTMLElement; + // Mobile charts are full width so we need to confine the tooltip to the chart + if (isMobile) { + tooltip.confine = true; + tooltip.triggerOn = "click"; + } }); options.tooltip = tooltips; } From a1a929fbdff2a90e9dd7e4b37fb0368820533d9f Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Thu, 2 Oct 2025 14:49:40 +0100 Subject: [PATCH 091/125] Revert changes for ha-more-info-dialog for now --- src/dialogs/more-info/ha-more-info-dialog.ts | 398 ++++++++++--------- 1 file changed, 207 insertions(+), 191 deletions(-) diff --git a/src/dialogs/more-info/ha-more-info-dialog.ts b/src/dialogs/more-info/ha-more-info-dialog.ts index eda464d8500c..80b446ef16cc 100644 --- a/src/dialogs/more-info/ha-more-info-dialog.ts +++ b/src/dialogs/more-info/ha-more-info-dialog.ts @@ -34,7 +34,8 @@ import { import { shouldHandleRequestSelectedEvent } from "../../common/mwc/handle-request-selected-event"; import { navigate } from "../../common/navigate"; import "../../components/ha-button-menu"; -import "../../components/ha-wa-dialog"; +import "../../components/ha-dialog"; +import "../../components/ha-dialog-header"; import "../../components/ha-icon-button"; import "../../components/ha-icon-button-prev"; import "../../components/ha-list-item"; @@ -96,6 +97,8 @@ const DEFAULT_VIEW: View = "info"; export class MoreInfoDialog extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; + @property({ type: Boolean, reflect: true }) public large = false; + @state() private _parentEntityIds: string[] = []; @state() private _entityId?: string | null; @@ -127,6 +130,7 @@ export class MoreInfoDialog extends LitElement { this._currView = params.view || DEFAULT_VIEW; this._initialView = params.view || DEFAULT_VIEW; this._childView = undefined; + this.large = false; this._loadEntityRegistryEntry(); } @@ -358,201 +362,200 @@ export class MoreInfoDialog extends LitElement { const title = this._childView?.viewTitle || breadcrumb.pop() || entityId; return html` - - ${showCloseIcon - ? html` - - ` - : html` - - `} - - ${breadcrumb.length > 0 - ? !__DEMO__ && isAdmin - ? html` - - ` - : html` - - ` - : nothing} -

${title}

-
- ${isDefaultView - ? html` - ${this._shouldShowHistory(domain) - ? html` - - ` - : nothing} - ${!__DEMO__ && isAdmin + + ${showCloseIcon + ? html` + + ` + : html` + + `} + + ${breadcrumb.length > 0 + ? !__DEMO__ && isAdmin ? html` - - ")} > + ${join(breadcrumb, html``)} + + ` + : html` + + ` + : nothing} +

${title}

+
+ ${isDefaultView + ? html` + ${this._shouldShowHistory(domain) + ? html` - - ${deviceId - ? html` - - ${this.hass.localize( - "ui.dialogs.more_info_control.device_or_service_info", - { - type: this.hass.localize( - `ui.dialogs.more_info_control.device_type.${deviceType}` - ), - } - )} - - - ` - : nothing} - ${this._shouldShowEditIcon(domain, stateObj) - ? html` - - ${this.hass.localize( - "ui.dialogs.more_info_control.edit" - )} - - - ` - : nothing} - ${this._entry && - stateObj && - domain === "light" && - lightSupportsFavoriteColors(stateObj) - ? html` - - ${this._infoEditMode - ? this.hass.localize( - `ui.dialogs.more_info_control.exit_edit_mode` - ) - : this.hass.localize( - `ui.dialogs.more_info_control.${domain}.edit_mode` - )} - - - ` - : nothing} - - ${this.hass.localize( - "ui.dialogs.more_info_control.related" + ` + : nothing} + ${!__DEMO__ && isAdmin + ? html` + - - - ` - : nothing} - ` - : isSpecificInitialView - ? html` - - - - - ${this.hass.localize("ui.dialogs.more_info_control.info")} - - - + .path=${mdiCogOutline} + @click=${this._goToSettings} + >
+ + + + ${deviceId + ? html` + + ${this.hass.localize( + "ui.dialogs.more_info_control.device_or_service_info", + { + type: this.hass.localize( + `ui.dialogs.more_info_control.device_type.${deviceType}` + ), + } + )} + + + ` + : nothing} + ${this._shouldShowEditIcon(domain, stateObj) + ? html` + + ${this.hass.localize( + "ui.dialogs.more_info_control.edit" + )} + + + ` + : nothing} + ${this._entry && + stateObj && + domain === "light" && + lightSupportsFavoriteColors(stateObj) + ? html` + + ${this._infoEditMode + ? this.hass.localize( + `ui.dialogs.more_info_control.exit_edit_mode` + ) + : this.hass.localize( + `ui.dialogs.more_info_control.${domain}.edit_mode` + )} + + + ` + : nothing} + + ${this.hass.localize( + "ui.dialogs.more_info_control.related" + )} + + + + ` + : nothing} ` - : nothing} + : isSpecificInitialView + ? html` + + + + + ${this.hass.localize("ui.dialogs.more_info_control.info")} + + + + ` + : nothing} +
${keyed( this._entityId, html` @@ -617,7 +620,7 @@ export class MoreInfoDialog extends LitElement {
` )} - + `; } @@ -639,8 +642,8 @@ export class MoreInfoDialog extends LitElement { this._entry = ev.detail; } - private _toggleSize() { - this.shadowRoot?.querySelector("ha-wa-dialog")?.toggleWidth(); + private _enlarge() { + this.large = !this.large; } private _handleOpened() { @@ -673,7 +676,7 @@ export class MoreInfoDialog extends LitElement { return [ haStyleDialog, css` - ha-wa-dialog { + ha-dialog { /* Set the top top of the dialog to a fixed position, so it doesnt jump when the content changes size */ --vertical-align-dialog: flex-start; --dialog-surface-margin-top: max( @@ -702,21 +705,34 @@ export class MoreInfoDialog extends LitElement { } @media all and (max-width: 450px), all and (max-height: 500px) { - ha-wa-dialog { - --dialog-surface-margin-top: initial; + /* When in fullscreen dialog should be attached to top */ + ha-dialog { + --dialog-surface-margin-top: 0px; } } @media all and (min-width: 600px) and (min-height: 501px) { + ha-dialog { + --mdc-dialog-min-width: 580px; + --mdc-dialog-max-width: 580px; + --mdc-dialog-max-height: calc(100% - 72px); + } + .main-title { cursor: default; } + + :host([large]) ha-dialog { + --mdc-dialog-min-width: 90vw; + --mdc-dialog-max-width: 90vw; + } } .title { display: flex; flex-direction: column; align-items: flex-start; + margin: 0 0 -10px 0; } .title p { From 63d183e022a79bba805e353981f8c3493d93fcd8 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Thu, 2 Oct 2025 15:30:37 +0100 Subject: [PATCH 092/125] Fix open change --- src/components/ha-wa-dialog.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 97c558d09440..427652daefaf 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -104,9 +104,7 @@ export class HaWaDialog extends LitElement { super.updated(changedProperties); if (changedProperties.has("open")) { - if (this.open) { - this._open = this.open; - } + this._open = this.open; } if (changedProperties.has("width")) { From f3793107135ab660ed8c2ab7a4905028cc0c38ee Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Thu, 2 Oct 2025 15:31:41 +0100 Subject: [PATCH 093/125] Migrate restart dialog to ha-wa-dialog --- src/dialogs/restart/dialog-restart.ts | 48 ++++++++++----------------- 1 file changed, 17 insertions(+), 31 deletions(-) diff --git a/src/dialogs/restart/dialog-restart.ts b/src/dialogs/restart/dialog-restart.ts index 06830489f736..26815442c4ca 100644 --- a/src/dialogs/restart/dialog-restart.ts +++ b/src/dialogs/restart/dialog-restart.ts @@ -1,7 +1,6 @@ import "@material/mwc-linear-progress/mwc-linear-progress"; import { mdiAutoFix, - mdiClose, mdiLifebuoy, mdiPower, mdiPowerCycle, @@ -9,16 +8,14 @@ import { } from "@mdi/js"; import type { CSSResultGroup } from "lit"; import { LitElement, css, html, nothing } from "lit"; -import { customElement, property, query, state } from "lit/decorators"; +import { customElement, property, state } from "lit/decorators"; import { isComponentLoaded } from "../../common/config/is_component_loaded"; import { fireEvent } from "../../common/dom/fire_event"; import "../../components/ha-alert"; import "../../components/ha-expansion-panel"; import "../../components/ha-fade-in"; -import "../../components/ha-icon-button"; import "../../components/ha-icon-next"; -import "../../components/ha-md-dialog"; -import type { HaMdDialog } from "../../components/ha-md-dialog"; +import "../../components/ha-wa-dialog"; import "../../components/ha-md-list"; import "../../components/ha-md-list-item"; import "../../components/ha-spinner"; @@ -58,12 +55,14 @@ class DialogRestart extends LitElement { @state() private _hostInfo?: HassioHostInfo; - @query("ha-md-dialog") private _dialog?: HaMdDialog; + @state() + private _dialogOpen = false; public async showDialog(): Promise { const isHassioLoaded = isComponentLoaded(this.hass, "hassio"); this._open = true; + this._dialogOpen = true; if (isHassioLoaded && !this._hostInfo) { this._loadHostInfo(); @@ -92,16 +91,13 @@ class DialogRestart extends LitElement { } private _dialogClosed(): void { + this._dialogOpen = false; this._open = false; this._loadingHostInfo = false; this._loadingBackupInfo = false; fireEvent(this, "dialog-closed", { dialog: this.localName }); } - public closeDialog(): void { - this._dialog?.close(); - } - protected render() { if (!this._open) { return nothing; @@ -113,17 +109,13 @@ class DialogRestart extends LitElement { const dialogTitle = this.hass.localize("ui.dialogs.restart.heading"); return html` - - - - ${dialogTitle} - -
+ +
${this._loadingBackupInfo ? html` @@ -265,12 +257,12 @@ class DialogRestart extends LitElement { `}
- + `; } private async _reload() { - this.closeDialog(); + this._dialogOpen = false; showToast(this, { message: this.hass.localize("ui.dialogs.restart.reload.reloading"), @@ -374,7 +366,7 @@ class DialogRestart extends LitElement { return; } - this.closeDialog(); + this._dialogOpen = false; let actionFunc; @@ -413,15 +405,9 @@ class DialogRestart extends LitElement { haStyle, haStyleDialog, css` - ha-md-dialog { + ha-wa-dialog { --dialog-content-padding: 0; } - @media all and (min-width: 550px) { - ha-md-dialog { - min-width: 500px; - max-width: 500px; - } - } ha-expansion-panel { border-top: 1px solid var(--divider-color); From 29e1411c265cd95ff2e4fa2e5e7ef9ac17a66ab0 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Thu, 2 Oct 2025 15:59:57 +0100 Subject: [PATCH 094/125] Flatten and fix slot check --- src/components/ha-wa-dialog.ts | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 427652daefaf..836db728c19c 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -1,4 +1,4 @@ -import { css, html, LitElement } from "lit"; +import { css, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import "@home-assistant/webawesome/dist/components/dialog/dialog"; import { mdiClose } from "@mdi/js"; @@ -132,14 +132,18 @@ export class HaWaDialog extends LitElement { .path=${mdiClose} > - - - ${this.headerTitle} - - - - ${this.headerSubtitle} - + ${this.headerTitle + ? html` + ${this.headerTitle} + ` + : nothing} + ${this.headerSubtitle + ? html`${this.headerSubtitle}` + : nothing} From 76461a442f34b5ba7b60ce7bb900645b4cb744e3 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Thu, 2 Oct 2025 16:26:08 +0100 Subject: [PATCH 095/125] Slot check was never needed --- src/components/ha-dialog-header.ts | 28 ++++------------------------ 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/src/components/ha-dialog-header.ts b/src/components/ha-dialog-header.ts index b86e809063c3..60bdb711ac0c 100644 --- a/src/components/ha-dialog-header.ts +++ b/src/components/ha-dialog-header.ts @@ -1,40 +1,23 @@ import { css, html, LitElement } from "lit"; -import { customElement, property, query, state } from "lit/decorators"; -import { classMap } from "lit/directives/class-map"; -import { hasSlotContent } from "../common/dom/has-slot-content"; +import { customElement, property } from "lit/decorators"; @customElement("ha-dialog-header") export class HaDialogHeader extends LitElement { @property({ type: String, attribute: "subtitle-position" }) public subtitlePosition: "above" | "below" = "below"; - @state() - private _hasSubtitle = false; - - @query('slot[name="subtitle"]') - private _subtitleSlot!: HTMLSlotElement; - - private _checkSubtitleContent() { - this._hasSubtitle = hasSlotContent(this._subtitleSlot); - } - protected render() { const titleSlot = html`
`; const subtitleSlot = html`
- +
`; return html`
-
+
@@ -66,13 +49,10 @@ export class HaDialogHeader extends LitElement { .header-bar { display: flex; flex-direction: row; - align-items: flex-start; + align-items: center; padding: 4px; box-sizing: border-box; } - .header-bar.no-subtitle { - align-items: center; - } .header-content { flex: 1; padding: 10px 4px; From 9ae4912bad40610d25d490bb70047db6a03df7de Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Thu, 2 Oct 2025 16:29:17 +0100 Subject: [PATCH 096/125] Revert changes to chart --- src/components/chart/ha-chart-base.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/components/chart/ha-chart-base.ts b/src/components/chart/ha-chart-base.ts index 184aeb032a4e..f5d2f606a0cb 100644 --- a/src/components/chart/ha-chart-base.ts +++ b/src/components/chart/ha-chart-base.ts @@ -586,19 +586,15 @@ export class HaChartBase extends LitElement { const isMobile = window.matchMedia( "all and (max-width: 450px), all and (max-height: 500px)" ).matches; - if (options.tooltip) { + if (isMobile && options.tooltip) { + // mobile charts are full width so we need to confine the tooltip to the chart const tooltips = Array.isArray(options.tooltip) ? options.tooltip : [options.tooltip]; tooltips.forEach((tooltip) => { - // Tooltips should render within the component, not in the body of the html - tooltip.appendTo = () => - this.renderRoot.querySelector(".chart-container") as HTMLElement; - // Mobile charts are full width so we need to confine the tooltip to the chart - if (isMobile) { - tooltip.confine = true; - tooltip.triggerOn = "click"; - } + tooltip.confine = true; + tooltip.appendTo = undefined; + tooltip.triggerOn = "click"; }); options.tooltip = tooltips; } From aceabe4b945a1d9579486f6d979aea3960cf4129 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 6 Oct 2025 08:20:06 +0100 Subject: [PATCH 097/125] Add variables for customisation --- src/components/ha-wa-dialog.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 836db728c19c..5c188b956cc9 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -195,7 +195,7 @@ export class HaWaDialog extends LitElement { } wa-dialog { - --full-width: min( + --full-width: var(--ha-dialog-width-full, min( 95vw, calc( 100vw - var(--safe-area-inset-left, 0px) - var( @@ -203,8 +203,8 @@ export class HaWaDialog extends LitElement { 0px ) ) - ); - --width: min(var(--ha-dialog-width-md, 580px), var(--full-width)); + )); + --width: var(--ha-dialog-width-md, min(580px, var(--full-width))); --spacing: var(--dialog-content-padding, 24px); --show-duration: var(--ha-dialog-show-duration, 200ms); --hide-duration: var(--ha-dialog-hide-duration, 200ms); @@ -225,12 +225,12 @@ export class HaWaDialog extends LitElement { :host([width="small"]), :host(.size-changed[width-on-title-click="small"]) wa-dialog { - --width: min(var(--ha-dialog-width-sm, 320px), var(--full-width)); + --width: var(--ha-dialog-width-sm, min(320px, var(--full-width))); } :host([width="large"]), :host(.size-changed[width-on-title-click="large"]) wa-dialog { - --width: min(var(--ha-dialog-width-lg, 720px), var(--full-width)); + --width: var(--ha-dialog-width-lg, min(720px, var(--full-width))); } :host([width="full"]), @@ -241,7 +241,7 @@ export class HaWaDialog extends LitElement { wa-dialog::part(dialog) { min-width: var(--width, var(--full-width)); max-width: var(--width, var(--full-width)); - max-height: calc(100% - 80px); + max-height: var(--ha-dialog-max-height, calc(100% - 80px)); position: var(--dialog-surface-position, relative); margin-top: var(--dialog-surface-margin-top, auto); transition: @@ -258,11 +258,11 @@ export class HaWaDialog extends LitElement { } wa-dialog { - --full-width: 100vw; + --full-width: var(--ha-dialog-width-full, 100vw); } wa-dialog::part(dialog) { - min-height: 100%; + min-height: var(--ha-dialog-min-height, 100%); padding-top: var(--safe-area-inset-top, 0px); padding-bottom: var(--safe-area-inset-bottom, 0px); padding-left: var(--safe-area-inset-left, 0px); From 9c289026e44571d0653551a2e3eca7f9022bcffc Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 6 Oct 2025 08:41:02 +0100 Subject: [PATCH 098/125] Match other dialog height change --- src/components/ha-wa-dialog.ts | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 5c188b956cc9..9f6427e00aa0 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -195,15 +195,18 @@ export class HaWaDialog extends LitElement { } wa-dialog { - --full-width: var(--ha-dialog-width-full, min( - 95vw, - calc( - 100vw - var(--safe-area-inset-left, 0px) - var( - --safe-area-inset-right, - 0px - ) + --full-width: var( + --ha-dialog-width-full, + min( + 95vw, + calc( + 100vw - var(--safe-area-inset-left, 0px) - var( + --safe-area-inset-right, + 0px + ) + ) ) - )); + ); --width: var(--ha-dialog-width-md, min(580px, var(--full-width))); --spacing: var(--dialog-content-padding, 24px); --show-duration: var(--ha-dialog-show-duration, 200ms); @@ -262,7 +265,10 @@ export class HaWaDialog extends LitElement { } wa-dialog::part(dialog) { - min-height: var(--ha-dialog-min-height, 100%); + min-height: var(--ha-dialog-min-height, 100vh); + min-height: var(--ha-dialog-min-height, 100svh); + max-height: var(--ha-dialog-max-height, 100vh); + max-height: var(--ha-dialog-max-height, 100svh); padding-top: var(--safe-area-inset-top, 0px); padding-bottom: var(--safe-area-inset-bottom, 0px); padding-left: var(--safe-area-inset-left, 0px); From 10db43a90be6f8bbbc884ca6cba9013ab627a6b2 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 6 Oct 2025 08:51:58 +0100 Subject: [PATCH 099/125] vw/svw --- src/components/ha-wa-dialog.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 9f6427e00aa0..489be3788123 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -223,7 +223,8 @@ export class HaWaDialog extends LitElement { --ha-dialog-border-radius, var(--ha-border-radius-3xl) ); - max-width: 100%; + max-width: var(--ha-dialog-max-width, 100vw); + max-width: var(--ha-dialog-max-width, 100svw); } :host([width="small"]), From a9ea3be3783d937c246695dea57c63b7330e59cd Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 6 Oct 2025 09:38:38 +0100 Subject: [PATCH 100/125] Reset --- src/components/ha-dialog.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/components/ha-dialog.ts b/src/components/ha-dialog.ts index d1b9fb56a171..60cd39c7e57b 100644 --- a/src/components/ha-dialog.ts +++ b/src/components/ha-dialog.ts @@ -100,8 +100,6 @@ export class HaDialog extends DialogBase { } .mdc-dialog__container { align-items: var(--vertical-align-dialog, center); - padding-top: var(--safe-area-inset-top); - padding-bottom: var(--safe-area-inset-bottom); } .mdc-dialog__title { padding: 16px 16px 0 16px; @@ -123,13 +121,12 @@ export class HaDialog extends DialogBase { position: var(--dialog-surface-position, relative); top: var(--dialog-surface-top); margin-top: var(--dialog-surface-margin-top); - min-width: calc( - var(--mdc-dialog-min-width, 100vw) - var( - --safe-area-inset-left - ) - var(--safe-area-inset-right) - ); + min-width: var(--mdc-dialog-min-width, 100vw); min-height: var(--mdc-dialog-min-height, auto); - border-radius: var(--ha-dialog-border-radius, 24px); + border-radius: var( + --ha-dialog-border-radius, + var(--ha-border-radius-3xl) + ); -webkit-backdrop-filter: var(--ha-dialog-surface-backdrop-filter, none); backdrop-filter: var(--ha-dialog-surface-backdrop-filter, none); background: var( From dffbadb87ee34d08ff610b2cca65599085b3c54a Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 6 Oct 2025 09:53:39 +0100 Subject: [PATCH 101/125] Add height, fix title render --- src/components/ha-dialog-header.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/ha-dialog-header.ts b/src/components/ha-dialog-header.ts index 60bdb711ac0c..8e43631b1bb2 100644 --- a/src/components/ha-dialog-header.ts +++ b/src/components/ha-dialog-header.ts @@ -62,6 +62,7 @@ export class HaDialogHeader extends LitElement { white-space: nowrap; } .header-title { + height: var(--ha-font-size-xl); font-size: var(--ha-font-size-xl); line-height: var(--ha-line-height-condensed); font-weight: var(--ha-font-weight-medium); From e8449653b342b38d20be837f74abb6378bf70b40 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 6 Oct 2025 09:56:26 +0100 Subject: [PATCH 102/125] Fix title rendering --- src/components/ha-dialog-header.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/ha-dialog-header.ts b/src/components/ha-dialog-header.ts index 8e43631b1bb2..f6ede8ed46e2 100644 --- a/src/components/ha-dialog-header.ts +++ b/src/components/ha-dialog-header.ts @@ -62,7 +62,10 @@ export class HaDialogHeader extends LitElement { white-space: nowrap; } .header-title { - height: var(--ha-font-size-xl); + 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); From 7f1c7b57ac4f18977287d2113d310cf001dae566 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 6 Oct 2025 11:23:32 +0100 Subject: [PATCH 103/125] Use spacing vars --- src/components/ha-dialog-footer.ts | 2 +- src/components/ha-wa-dialog.ts | 61 ++++++++++++++++-------------- 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/src/components/ha-dialog-footer.ts b/src/components/ha-dialog-footer.ts index 93fee8427a74..d0948b6426e9 100644 --- a/src/components/ha-dialog-footer.ts +++ b/src/components/ha-dialog-footer.ts @@ -35,7 +35,7 @@ export class HaDialogFooter extends LitElement { css` .footer { display: flex; - gap: 12px; + gap: var(--ha-space-3); justify-content: flex-end; align-items: center; width: 100%; diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 489be3788123..8e85773bb362 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -38,15 +38,15 @@ export type DialogWidthOnTitleClick = DialogWidth | "none"; * @csspart body - The scrollable body container. * @csspart footer - The footer container. * - * @cssprop --dialog-content-padding - Padding for the dialog content sections. Defaults to 24px. - * @cssprop --ha-dialog-show-duration - Show animation duration. Defaults to 200ms. - * @cssprop --ha-dialog-hide-duration - Hide animation duration. Defaults to 200ms. - * @cssprop --ha-dialog-surface-background - Dialog background color. Defaults to surface. - * @cssprop --ha-dialog-border-radius - Border radius of the dialog surface. Defaults to 24px. - * @cssprop --dialog-z-index - Z-index for the dialog. Defaults to 8. - * @cssprop --dialog-surface-position - CSS position of the dialog surface. Defaults to relative. - * @cssprop --dialog-surface-margin-top - Top margin for the dialog surface. Defaults to auto. - * @cssprop --ha-dialog-expand-duration - Duration for width transitions when changing width. Defaults to 200ms. + * @cssprop --dialog-content-padding - Padding for the dialog content sections. + * @cssprop --ha-dialog-show-duration - Show animation duration. + * @cssprop --ha-dialog-hide-duration - Hide animation duration. + * @cssprop --ha-dialog-surface-background - Dialog background color. + * @cssprop --ha-dialog-border-radius - Border radius of the dialog surface. + * @cssprop --dialog-z-index - Z-index for the dialog. + * @cssprop --dialog-surface-position - CSS position of the dialog surface. + * @cssprop --dialog-surface-margin-top - Top margin for the dialog surface. + * @cssprop --ha-dialog-expand-duration - Duration for width transitions when changing width. * * @attr {boolean} open - Controls the dialog open state. * @attr {("small"|"medium"|"large"|"full")} width - Preferred dialog width preset. Defaults to "medium". @@ -200,15 +200,15 @@ export class HaWaDialog extends LitElement { min( 95vw, calc( - 100vw - var(--safe-area-inset-left, 0px) - var( + 100vw - var(--safe-area-inset-left, var(--ha-space-0)) - var( --safe-area-inset-right, - 0px + var(--ha-space-0) ) ) ) ); --width: var(--ha-dialog-width-md, min(580px, var(--full-width))); - --spacing: var(--dialog-content-padding, 24px); + --spacing: var(--dialog-content-padding, var(--ha-space-6)); --show-duration: var(--ha-dialog-show-duration, 200ms); --hide-duration: var(--ha-dialog-hide-duration, 200ms); --ha-dialog-surface-background: var( @@ -245,7 +245,10 @@ export class HaWaDialog extends LitElement { wa-dialog::part(dialog) { min-width: var(--width, var(--full-width)); max-width: var(--width, var(--full-width)); - max-height: var(--ha-dialog-max-height, calc(100% - 80px)); + max-height: var( + --ha-dialog-max-height, + calc(100% - var(--ha-space-20)) + ); position: var(--dialog-surface-position, relative); margin-top: var(--dialog-surface-margin-top, auto); transition: @@ -258,7 +261,7 @@ export class HaWaDialog extends LitElement { @media all and (max-width: 450px), all and (max-height: 500px) { :host { - --ha-dialog-border-radius: 0px; + --ha-dialog-border-radius: var(--ha-space-0); } wa-dialog { @@ -270,10 +273,10 @@ export class HaWaDialog extends LitElement { min-height: var(--ha-dialog-min-height, 100svh); max-height: var(--ha-dialog-max-height, 100vh); max-height: var(--ha-dialog-max-height, 100svh); - padding-top: var(--safe-area-inset-top, 0px); - padding-bottom: var(--safe-area-inset-bottom, 0px); - padding-left: var(--safe-area-inset-left, 0px); - padding-right: var(--safe-area-inset-right, 0px); + padding-top: var(--safe-area-inset-top, var(--ha-space-0)); + padding-bottom: var(--safe-area-inset-bottom, var(--ha-space-0)); + padding-left: var(--safe-area-inset-left, var(--ha-space-0)); + padding-right: var(--safe-area-inset-right, var(--ha-space-0)); } } @@ -282,12 +285,13 @@ export class HaWaDialog extends LitElement { overflow: hidden; display: flex; align-items: center; - padding: 24px 24px 16px 24px; - gap: 4px; + padding: var(--ha-space-6) var(--ha-space-6) var(--ha-space-4) + var(--ha-space-6); + gap: var(--ha-space-1); } :host([has-custom-heading]) wa-dialog::part(header) { max-width: 100%; - padding: 0; + padding: var(--ha-space-0); } wa-dialog::part(close-button), @@ -322,7 +326,7 @@ export class HaWaDialog extends LitElement { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - margin-right: 12px; + margin-right: var(--ha-space-3); } wa-dialog::part(body) { @@ -335,9 +339,9 @@ export class HaWaDialog extends LitElement { .body { position: var(--dialog-content-position, relative); - padding: 0 var(--dialog-content-padding, 24px) - var(--dialog-content-padding, 24px) - var(--dialog-content-padding, 24px); + padding: 0 var(--dialog-content-padding, var(--ha-space-6)) + var(--dialog-content-padding, var(--ha-space-6)) + var(--dialog-content-padding, var(--ha-space-6)); overflow: auto; flex-grow: 1; } @@ -348,13 +352,14 @@ export class HaWaDialog extends LitElement { } wa-dialog::part(footer) { - padding: 0; + padding: var(--ha-space-0); } ::slotted([slot="footer"]) { display: flex; - padding: 12px 16px 16px 16px; - gap: 12px; + padding: var(--ha-space-3) var(--ha-space-4) var(--ha-space-4) + var(--ha-space-4); + gap: var(--ha-space-3); justify-content: flex-end; align-items: center; width: 100%; From 4702be7c8e8375f8e35b9d5320361684fab0d354 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Tue, 7 Oct 2025 08:59:53 +0100 Subject: [PATCH 104/125] Remove from device editor --- .../dialog-device-registry-detail.ts | 42 +++++++++---------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/src/panels/config/devices/device-registry-detail/dialog-device-registry-detail.ts b/src/panels/config/devices/device-registry-detail/dialog-device-registry-detail.ts index 72a0b0795014..673f9cf9f389 100644 --- a/src/panels/config/devices/device-registry-detail/dialog-device-registry-detail.ts +++ b/src/panels/config/devices/device-registry-detail/dialog-device-registry-detail.ts @@ -5,9 +5,8 @@ import { fireEvent } from "../../../../common/dom/fire_event"; import { computeDeviceNameDisplay } from "../../../../common/entity/compute_device_name"; import "../../../../components/ha-alert"; import "../../../../components/ha-area-picker"; +import "../../../../components/ha-dialog"; import "../../../../components/ha-button"; -import "../../../../components/ha-dialog-footer"; -import "../../../../components/ha-wa-dialog"; import "../../../../components/ha-labels-picker"; import type { HaSwitch } from "../../../../components/ha-switch"; import "../../../../components/ha-textfield"; @@ -58,11 +57,10 @@ class DialogDeviceRegistryDetail extends LitElement { } const device = this._params.device; return html` -
${this._error @@ -133,24 +131,22 @@ class DialogDeviceRegistryDetail extends LitElement {
- - - ${this.hass.localize("ui.common.cancel")} - - - ${this.hass.localize("ui.dialogs.device-registry-detail.update")} - - - + + ${this.hass.localize("ui.common.cancel")} + + + ${this.hass.localize("ui.dialogs.device-registry-detail.update")} + + `; } From 5cd4f3b90341c4385ba91a40fa6c27da54de149f Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Tue, 7 Oct 2025 09:47:41 +0100 Subject: [PATCH 105/125] Update src/components/ha-wa-dialog.ts Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com> --- src/components/ha-wa-dialog.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/components/ha-wa-dialog.ts b/src/components/ha-wa-dialog.ts index 8e85773bb362..f18b45572d49 100644 --- a/src/components/ha-wa-dialog.ts +++ b/src/components/ha-wa-dialog.ts @@ -155,16 +155,15 @@ export class HaWaDialog extends LitElement { `; } - private _handleShow = () => { + private async _handleShow = () => { this._open = true; fireEvent(this, "opened"); - this.updateComplete.then(() => { - const focusElement = this.querySelector( - "[dialogInitialFocus]" - ) as HTMLElement; - focusElement?.focus(); - }); + await this.updateComplete + const focusElement = this.querySelector( + "[dialogInitialFocus]" + ) as HTMLElement; + focusElement?.focus(); }; private _handleAfterHide = () => { From fb80d5e6bbd1fe606340a8aa7b04cf57a984ec31 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Tue, 7 Oct 2025 09:47:59 +0100 Subject: [PATCH 106/125] Update src/components/ha-dialog-footer.ts Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com> --- src/components/ha-dialog-footer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ha-dialog-footer.ts b/src/components/ha-dialog-footer.ts index d0948b6426e9..56259087acdf 100644 --- a/src/components/ha-dialog-footer.ts +++ b/src/components/ha-dialog-footer.ts @@ -23,7 +23,7 @@ import { customElement } from "lit/decorators"; export class HaDialogFooter extends LitElement { protected render() { return html` -