diff --git a/src/data/lovelace.ts b/src/data/lovelace.ts index 1b4f47aa38..d43747f06a 100644 --- a/src/data/lovelace.ts +++ b/src/data/lovelace.ts @@ -33,6 +33,7 @@ export interface LovelaceSectionElement extends HTMLElement { lovelace?: Lovelace; preview?: boolean; viewIndex?: number; + columnSpan?: number; index?: number; cards?: HuiCard[]; isStrategy: boolean; diff --git a/src/panels/lovelace/sections/hui-grid-section.ts b/src/panels/lovelace/sections/hui-grid-section.ts index 8378b3e173..dc5012724c 100644 --- a/src/panels/lovelace/sections/hui-grid-section.ts +++ b/src/panels/lovelace/sections/hui-grid-section.ts @@ -35,6 +35,8 @@ export class GridSection extends LitElement implements LovelaceSectionElement { @property({ type: Boolean }) public isStrategy = false; + @property({ type: Number, attribute: "column_span" }) public columnSpan = 1; + @property({ attribute: false }) public cards: HuiCard[] = []; @state() _config?: LovelaceSectionConfig; @@ -77,7 +79,12 @@ export class GridSection extends LitElement implements LovelaceSectionElement { .options=${CARD_SORTABLE_OPTIONS} invert-swap > -
+
${repeat( cardsConfig, (cardConfig) => this._getKey(cardConfig), diff --git a/src/panels/lovelace/sections/hui-section.ts b/src/panels/lovelace/sections/hui-section.ts index 07dc7a78f8..c6cc4a4638 100644 --- a/src/panels/lovelace/sections/hui-section.ts +++ b/src/panels/lovelace/sections/hui-section.ts @@ -47,6 +47,8 @@ export class HuiSection extends ReactiveElement { @property({ type: Number }) public viewIndex!: number; + @property({ type: Number }) public columnSpan!: number; + @state() private _cards: HuiCard[] = []; private _layoutElementType?: string; @@ -131,6 +133,9 @@ export class HuiSection extends ReactiveElement { if (changedProperties.has("_cards")) { this._layoutElement.cards = this._cards; } + if (changedProperties.has("columnSpan")) { + this._layoutElement.columnSpan = this.columnSpan; + } if (changedProperties.has("hass") || changedProperties.has("preview")) { this._updateElement(); } @@ -195,6 +200,7 @@ export class HuiSection extends ReactiveElement { this._layoutElement!.lovelace = this.lovelace; this._layoutElement!.index = this.index; this._layoutElement!.viewIndex = this.viewIndex; + this._layoutElement!.columnSpan = this.columnSpan; this._layoutElement!.cards = this._cards; if (addLayoutElement) { diff --git a/src/panels/lovelace/views/hui-sections-view.ts b/src/panels/lovelace/views/hui-sections-view.ts index 6fd5920428..243f99a25d 100644 --- a/src/panels/lovelace/views/hui-sections-view.ts +++ b/src/panels/lovelace/views/hui-sections-view.ts @@ -27,12 +27,15 @@ import { findLovelaceContainer } from "../editor/lovelace-path"; import { showEditSectionDialog } from "../editor/section-editor/show-edit-section-dialog"; import { HuiSection } from "../sections/hui-section"; import type { Lovelace } from "../types"; +import { listenMediaQuery } from "../../../common/dom/media_query"; export const DEFAULT_MAX_COLUMNS = 4; const parsePx = (value: string) => parseInt(value.replace("px", "")); -export const BREAKPOINTS: Record = { +type Breakpoints = Record; + +export const DEFAULT_BREAKPOINTS: Breakpoints = { "0": 1, "768": 2, "1280": 3, @@ -41,6 +44,16 @@ export const BREAKPOINTS: Record = { "2560": 6, }; +const buildMediaQueries = (breakpoints: Breakpoints) => + Object.keys(breakpoints).map((breakpoint, index, array) => { + const nextBreakpoint = array[index + 1] as string | undefined; + let mediaQuery = `(min-width: ${breakpoint}px)`; + if (nextBreakpoint) { + mediaQuery += ` and (max-width: ${parseInt(nextBreakpoint) - 1}px)`; + } + return mediaQuery; + }); + @customElement("hui-sections-view") export class SectionsView extends LitElement implements LovelaceViewElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -85,8 +98,13 @@ export class SectionsView extends LitElement implements LovelaceViewElement { }, }); + private _listeners: Array<() => void> = []; + + @state() private _columns: number = 1; + public setConfig(config: LovelaceViewConfig): void { this._config = config; + this._attachMediaQueriesListeners(); } private _sectionConfigKeys = new WeakMap(); @@ -109,12 +127,32 @@ export class SectionsView extends LitElement implements LovelaceViewElement { this._computeSectionsCount(); }; + private _attachMediaQueriesListeners() { + this._detachMediaQueriesListeners(); + const breakpoints = this._config?.column_breakpoints || DEFAULT_BREAKPOINTS; + const mediaQueries = buildMediaQueries(breakpoints); + this._listeners = mediaQueries.map((mediaQuery, index) => + listenMediaQuery(mediaQuery, (matches) => { + if (matches) { + this._columns = Object.values(breakpoints)[index]; + } + }) + ); + } + + private _detachMediaQueriesListeners() { + while (this._listeners.length) { + this._listeners.pop()!(); + } + } + connectedCallback(): void { super.connectedCallback(); this.addEventListener( "section-visibility-changed", this._sectionVisibilityChanged ); + this._attachMediaQueriesListeners(); } disconnectedCallback(): void { @@ -123,6 +161,7 @@ export class SectionsView extends LitElement implements LovelaceViewElement { "section-visibility-changed", this._sectionVisibilityChanged ); + this._detachMediaQueriesListeners(); } willUpdate(changedProperties: PropertyValues): void { @@ -170,14 +209,12 @@ export class SectionsView extends LitElement implements LovelaceViewElement { (section) => this._getSectionKey(section), (section, idx) => { const sectionConfig = this._config?.sections?.[idx]; - const columnSpan = Math.min( - sectionConfig?.column_span || 1, - maxColumnCount - ); + const columnSpan = Math.min(sectionConfig?.column_span || 1); const rowSpan = sectionConfig?.row_span || 1; (section as any).itemPath = [idx]; + (section as any).columnSpan = columnSpan; return html`
* { position: relative; width: 100%; } .section { - --section-column-span: min(var(--column-span, 1), var(--column-count)); border-radius: var(--ha-card-border-radius, 12px); - grid-column: span var(--section-column-span); + grid-column: span var(--column-span); grid-row: span var(--row-span); }