From a794a802280e3dfd27c5372d105a63b912a3b21d Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Wed, 18 Mar 2026 16:11:29 +0100 Subject: [PATCH] Add scrollbar support for cards with fixed grid row height (#30209) * Add scrollbar support for cards with fixed grid row height * Fix default sizing * Add warning for card that doesn't support resizing well * Remove not used explanation --- .../lovelace/cards/hui-entities-card.ts | 124 +++++++++--------- src/panels/lovelace/cards/hui-grid-card.ts | 7 + .../cards/hui-horizontal-stack-card.ts | 8 +- src/panels/lovelace/cards/hui-stack-card.ts | 50 ++++--- .../lovelace/cards/hui-vertical-stack-card.ts | 8 +- .../card-editor/hui-card-layout-editor.ts | 42 +++--- src/translations/en.json | 3 +- 7 files changed, 144 insertions(+), 98 deletions(-) diff --git a/src/panels/lovelace/cards/hui-entities-card.ts b/src/panels/lovelace/cards/hui-entities-card.ts index eaa2cff89a..c48e680f5b 100644 --- a/src/panels/lovelace/cards/hui-entities-card.ts +++ b/src/panels/lovelace/cards/hui-entities-card.ts @@ -24,6 +24,7 @@ import type { LovelaceHeaderFooter, } from "../types"; import type { EntitiesCardConfig } from "./types"; +import { haStyleScrollbar } from "../../../resources/styles"; export const computeShowHeaderToggle = < T extends EntityConfig | LovelaceRowConfig, @@ -242,7 +243,7 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard { `} `} -
+
${this._configEntities!.map((entityConf) => this._renderEntity(entityConf) )} @@ -255,71 +256,76 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard { `; } - static styles = css` - ha-card { - height: 100%; - display: flex; - flex-direction: column; - justify-content: space-between; - } - .card-header { - display: flex; - justify-content: space-between; - } + static styles = [ + haStyleScrollbar, + css` + ha-card { + height: 100%; + display: flex; + flex-direction: column; + justify-content: space-between; + } + .card-header { + display: flex; + justify-content: space-between; + } - .card-header .name { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } + .card-header .name { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } - #states { - flex: 1; - display: flex; - flex-direction: column; - gap: var(--entities-card-row-gap, var(--card-row-gap, 8px)); - } + #states { + flex: 1; + min-height: 0; + overflow-x: hidden; + display: flex; + flex-direction: column; + gap: var(--entities-card-row-gap, var(--card-row-gap, 8px)); + } - #states > div > * { - overflow: clip visible; - } + #states > div > * { + overflow: clip visible; + } - #states > div { - position: relative; - } + #states > div { + position: relative; + } - .icon { - padding: 0px 18px 0px 8px; - } + .icon { + padding: 0px 18px 0px 8px; + } - .header { - border-top-left-radius: var( - --ha-card-border-radius, - var(--ha-border-radius-lg) - ); - border-top-right-radius: var( - --ha-card-border-radius, - var(--ha-border-radius-lg) - ); - overflow: hidden; - } - .header:not(:has(> hui-buttons-header-footer)) { - margin-bottom: var(--ha-space-4); - } + .header { + border-top-left-radius: var( + --ha-card-border-radius, + var(--ha-border-radius-lg) + ); + border-top-right-radius: var( + --ha-card-border-radius, + var(--ha-border-radius-lg) + ); + overflow: hidden; + } + .header:not(:has(> hui-buttons-header-footer)) { + margin-bottom: var(--ha-space-4); + } - .footer { - border-bottom-left-radius: var( - --ha-card-border-radius, - var(--ha-border-radius-lg) - ); - border-bottom-right-radius: var( - --ha-card-border-radius, - var(--ha-border-radius-lg) - ); - margin-top: -16px; - overflow: hidden; - } - `; + .footer { + border-bottom-left-radius: var( + --ha-card-border-radius, + var(--ha-border-radius-lg) + ); + border-bottom-right-radius: var( + --ha-card-border-radius, + var(--ha-border-radius-lg) + ); + margin-top: -16px; + overflow: hidden; + } + `, + ]; private _renderEntity(entityConf: LovelaceRowConfig): TemplateResult { const element = createRowElement( diff --git a/src/panels/lovelace/cards/hui-grid-card.ts b/src/panels/lovelace/cards/hui-grid-card.ts index f92c71031c..fc11b6b89a 100644 --- a/src/panels/lovelace/cards/hui-grid-card.ts +++ b/src/panels/lovelace/cards/hui-grid-card.ts @@ -75,7 +75,14 @@ class HuiGridCard extends HuiStackCard { return [ super.sharedStyles, css` + :host { + display: flex; + flex-direction: column; + height: 100%; + } #root { + flex: 1; + min-height: 0; display: grid; grid-template-columns: repeat( var(--grid-card-column-count, ${DEFAULT_COLUMNS}), diff --git a/src/panels/lovelace/cards/hui-horizontal-stack-card.ts b/src/panels/lovelace/cards/hui-horizontal-stack-card.ts index 9448c65c82..2419049380 100644 --- a/src/panels/lovelace/cards/hui-horizontal-stack-card.ts +++ b/src/panels/lovelace/cards/hui-horizontal-stack-card.ts @@ -26,9 +26,15 @@ export class HuiHorizontalStackCard extends HuiStackCard { return [ super.sharedStyles, css` + :host { + display: flex; + flex-direction: column; + height: 100%; + } #root { display: flex; - height: 100%; + flex: 1; + min-height: 0; gap: var(--horizontal-stack-card-gap, var(--stack-card-gap, 8px)); } #root > hui-card { diff --git a/src/panels/lovelace/cards/hui-stack-card.ts b/src/panels/lovelace/cards/hui-stack-card.ts index 9ecbeb9a0a..3e2024c0c4 100644 --- a/src/panels/lovelace/cards/hui-stack-card.ts +++ b/src/panels/lovelace/cards/hui-stack-card.ts @@ -2,6 +2,7 @@ import { LitElement, css, html, nothing } from "lit"; import { property, state } from "lit/decorators"; import { computeRTLDirection } from "../../../common/util/compute_rtl"; import type { LovelaceCardConfig } from "../../../data/lovelace/config/card"; +import { haStyleScrollbar } from "../../../resources/styles"; import type { HomeAssistant } from "../../../types"; import type { LovelaceCard, @@ -116,31 +117,38 @@ export abstract class HuiStackCard ${this._config.title ? html`

${this._config.title}

` : ""} -
+
${this._cards} ${this.preview && this._errorCard ? this._errorCard : nothing}
`; } - static sharedStyles = css` - .card-header { - color: var(--ha-card-header-color, var(--primary-text-color)); - text-align: var(--ha-stack-title-text-align, start); - font-family: var(--ha-card-header-font-family, inherit); - font-size: var(--ha-card-header-font-size, var(--ha-font-size-2xl)); - font-weight: var(--ha-font-weight-normal); - margin-block-start: 0px; - margin-block-end: 0px; - letter-spacing: -0.012em; - line-height: var(--ha-line-height-condensed); - display: block; - padding: 24px 16px 16px; - } - :host([ispanel]) #root { - --ha-card-border-radius: var(--restore-card-border-radius); - --ha-card-border-width: var(--restore-card-border-width); - --ha-card-box-shadow: var(--restore-card-box-shadow); - } - `; + static sharedStyles = [ + haStyleScrollbar, + css` + .card-header { + color: var(--ha-card-header-color, var(--primary-text-color)); + text-align: var(--ha-stack-title-text-align, start); + font-family: var(--ha-card-header-font-family, inherit); + font-size: var(--ha-card-header-font-size, var(--ha-font-size-2xl)); + font-weight: var(--ha-font-weight-normal); + margin-block-start: 0px; + margin-block-end: 0px; + letter-spacing: -0.012em; + line-height: var(--ha-line-height-condensed); + display: block; + padding: 24px 16px 16px; + } + :host([ispanel]) #root { + --ha-card-border-radius: var(--restore-card-border-radius); + --ha-card-border-width: var(--restore-card-border-width); + --ha-card-box-shadow: var(--restore-card-box-shadow); + } + `, + ]; } diff --git a/src/panels/lovelace/cards/hui-vertical-stack-card.ts b/src/panels/lovelace/cards/hui-vertical-stack-card.ts index 8500cdec2b..f6c812e903 100644 --- a/src/panels/lovelace/cards/hui-vertical-stack-card.ts +++ b/src/panels/lovelace/cards/hui-vertical-stack-card.ts @@ -26,10 +26,16 @@ class HuiVerticalStackCard extends HuiStackCard { return [ super.sharedStyles, css` - #root { + :host { display: flex; flex-direction: column; height: 100%; + } + #root { + display: flex; + flex-direction: column; + flex: 1; + min-height: 0; gap: var(--vertical-stack-card-gap, var(--stack-card-gap, 8px)); } `, diff --git a/src/panels/lovelace/editor/card-editor/hui-card-layout-editor.ts b/src/panels/lovelace/editor/card-editor/hui-card-layout-editor.ts index ed8e1231c7..88e6d5a7f6 100644 --- a/src/panels/lovelace/editor/card-editor/hui-card-layout-editor.ts +++ b/src/panels/lovelace/editor/card-editor/hui-card-layout-editor.ts @@ -1,10 +1,11 @@ import { mdiDotsVertical, mdiPlaylistEdit } from "@mdi/js"; import type { PropertyValues } from "lit"; -import { css, html, LitElement } from "lit"; +import { css, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { styleMap } from "lit/directives/style-map"; import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../common/dom/fire_event"; +import "../../../../components/ha-alert"; import "../../../../components/ha-button"; import "../../../../components/ha-dropdown"; import type { HaDropdownSelectEvent } from "../../../../components/ha-dropdown"; @@ -24,6 +25,7 @@ import type { HuiCard } from "../../cards/hui-card"; import type { CardGridSize } from "../../common/compute-card-grid-size"; import { computeCardGridSize, + DEFAULT_GRID_SIZE, GRID_COLUMN_MULTIPLIER, isPreciseMode, migrateLayoutToGridOptions, @@ -50,6 +52,7 @@ export class HuiCardLayoutEditor extends LitElement { private _mergedOptions = memoizeOne( (options?: LovelaceGridOptions, defaultOptions?: LovelaceGridOptions) => ({ + ...DEFAULT_GRID_SIZE, ...defaultOptions, ...options, }) @@ -86,12 +89,17 @@ export class HuiCardLayoutEditor extends LitElement { const gridTotalColumns = 12 * columnSpan; return html` + ${this._defaultGridOptions && + Object.keys(this._defaultGridOptions).length === 0 + ? html` + + ${this.hass.localize( + "ui.panel.lovelace.editor.edit_card.layout.no_grid_support" + )} + + ` + : nothing}
-

- ${this.hass.localize( - "ui.panel.lovelace.editor.edit_card.layout.explanation" - )} -