fix: the summary panel settings dialog scrolls in Home Assistant

In HA hp-dialog renders ha-dialog, whose own `.body` is the scroller and
is not a flex container; `.summary-editor` (overflow:auto,
overscroll-behavior:contain, min-height:0) therefore grew to its content
and became a scroll container that never scrolls — Chromium stops wheel
and touch scroll chaining at such a child, so nothing moved (reproduced
on ha.jbstudio.pro, HA 2026.9.1, and in an isolated Playwright page).
hp-dialog gains an opt-in `flex-content` attribute forwarded as
ha-dialog's `flexcontent`, which lays the body out as a flex column: the
editor is height-bound again and scrolls itself, header and footer stay,
exactly as the native branch already did. Only the summary dialog opts in.

Smoke demo/smoke_summary_dialog_scroll.mjs stubs ha-dialog after the HA
2026.9 contract: wheel on desktop, touch swipe on a phone, dialog within
the viewport, and a witness that the stub reproduces the bug without
flexcontent. Docs screenshots: 11/11 pixel-identical (docs:accept
--identical, #512), fingerprint refreshed.

Mutants: summary-dialog-drops-flex-content, hp-dialog-ignores-flex-content.

Issue: #508
User-Visible: yes
This commit is contained in:
Codex
2026-09-09 19:49:18 +00:00
committed by claude[bot]
parent 6b592c13c0
commit e38beed796
53 changed files with 659 additions and 427 deletions
+9
View File
@@ -38,6 +38,12 @@ export class HpDialog extends LitElement {
alert: { type: Boolean, reflect: true },
describedBy: { type: String, attribute: 'described-by' },
dismissOnScrim: { type: Boolean, attribute: 'dismiss-on-scrim' },
// #508: ha-dialog scrolls its own `.body`; a consumer whose content is
// itself a scroll container (overflow:auto + overscroll-behavior:contain)
// must be height-bound by a flex column, or Chromium stops scroll chaining
// at the never-scrolling child and neither wheel nor touch reaches HA's
// scroller. `flex-content` forwards ha-dialog's public `flexcontent`.
flexContent: { type: Boolean, reflect: true, attribute: 'flex-content' },
hass: { attribute: false },
};
@@ -226,6 +232,7 @@ export class HpDialog extends LitElement {
alert = false;
describedBy = '';
dismissOnScrim = false;
flexContent = false;
hass: any = null;
private _opener: HTMLElement | null = null;
@@ -505,6 +512,7 @@ export class HpDialog extends LitElement {
.hass=${this.hass}
.open=${true}
width=${this.wide ? 'medium' : 'small'}
?flexcontent=${this.flexContent}
.preventScrimClose=${!this.dismissOnScrim}
.ariaLabelledBy=${this._titleId}
.ariaDescribedBy=${this.describedBy}
@@ -520,6 +528,7 @@ export class HpDialog extends LitElement {
.hass=${this.hass}
.open=${true}
width=${this.wide ? 'medium' : 'small'}
?flexcontent=${this.flexContent}
.preventScrimClose=${!this.dismissOnScrim}
.ariaLabelledBy=${this._titleId}
@opened=${this._focusInitial}
+1 -1
View File
@@ -61,7 +61,7 @@ export const renderSummaryPanelEditor: SummaryPanelEditorRenderer = (context) =>
...dialog, localShow: (event.target as HTMLInputElement).checked,
})} /></label>`;
return html`<hp-dialog .hass=${host.hass} data-kind="summary"
.title=${t('summary.settings')} wide
.title=${t('summary.settings')} wide flex-content
dismiss-on-scrim aria-busy=${String(dialog.busy)}
@hp-close=${close}>
<div class="body summary-editor" @click=${() => context.closeSource()}>