From 5a229e3c8818d1298660d67b9982474e391d989a Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Wed, 28 Aug 2024 09:54:03 +0200 Subject: [PATCH] Allow resizing section to span multiple columns (#21742) * WIP: Allow to resize section * Use listeners * Rename variables * Rename variables * Remove column min width * Make column breakpoints optional * Use old logic to calculate the number of columns * Remove breakpoints * Simplify column span --- src/data/lovelace/config/section.ts | 1 + .../hui-section-settings-editor.ts | 7 ++ .../lovelace/sections/hui-grid-section.ts | 8 +- .../lovelace/views/hui-sections-view.ts | 83 +++++++++++++------ 4 files changed, 74 insertions(+), 25 deletions(-) diff --git a/src/data/lovelace/config/section.ts b/src/data/lovelace/config/section.ts index bfd54042a5..4c57cc63e4 100644 --- a/src/data/lovelace/config/section.ts +++ b/src/data/lovelace/config/section.ts @@ -5,6 +5,7 @@ import type { LovelaceStrategyConfig } from "./strategy"; export interface LovelaceBaseSectionConfig { title?: string; visibility?: Condition[]; + column_span?: number; } export interface LovelaceSectionConfig extends LovelaceBaseSectionConfig { diff --git a/src/panels/lovelace/editor/section-editor/hui-section-settings-editor.ts b/src/panels/lovelace/editor/section-editor/hui-section-settings-editor.ts index 4e06726100..70a74fdc2c 100644 --- a/src/panels/lovelace/editor/section-editor/hui-section-settings-editor.ts +++ b/src/panels/lovelace/editor/section-editor/hui-section-settings-editor.ts @@ -13,10 +13,15 @@ const SCHEMA = [ name: "title", selector: { text: {} }, }, + { + name: "column_span", + selector: { number: { min: 1, max: 3 } }, + }, ] as const satisfies HaFormSchema[]; type SettingsData = { title: string; + column_span?: number; }; @customElement("hui-section-settings-editor") @@ -28,6 +33,7 @@ export class HuiDialogEditSection extends LitElement { render() { const data: SettingsData = { title: this.config.title || "", + column_span: this.config.column_span || 1, }; return html` @@ -59,6 +65,7 @@ export class HuiDialogEditSection extends LitElement { const newConfig: LovelaceSectionRawConfig = { ...this.config, title: newData.title, + column_span: newData.column_span, }; if (!newConfig.title) { diff --git a/src/panels/lovelace/sections/hui-grid-section.ts b/src/panels/lovelace/sections/hui-grid-section.ts index ea011df222..bd67d01429 100644 --- a/src/panels/lovelace/sections/hui-grid-section.ts +++ b/src/panels/lovelace/sections/hui-grid-section.ts @@ -220,8 +220,14 @@ export class GridSection extends LitElement implements LovelaceSectionElement { gap: var(--row-gap); } .container { + --grid-column-count: calc( + var(--column-count) * var(--column-span, 1) + ); display: grid; - grid-template-columns: repeat(var(--column-count), minmax(0, 1fr)); + grid-template-columns: repeat( + var(--grid-column-count), + minmax(0, 1fr) + ); grid-auto-rows: minmax(var(--row-height), auto); row-gap: var(--row-gap); column-gap: var(--column-gap); diff --git a/src/panels/lovelace/views/hui-sections-view.ts b/src/panels/lovelace/views/hui-sections-view.ts index 4dd2a89ac8..7f2781f74b 100644 --- a/src/panels/lovelace/views/hui-sections-view.ts +++ b/src/panels/lovelace/views/hui-sections-view.ts @@ -1,3 +1,4 @@ +import { ResizeController } from "@lit-labs/observers/resize-controller"; import { mdiArrowAll, mdiDelete, mdiPencil, mdiViewGridPlus } from "@mdi/js"; import { CSSResultGroup, @@ -26,6 +27,10 @@ import { showEditSectionDialog } from "../editor/section-editor/show-edit-sectio import { HuiSection } from "../sections/hui-section"; import type { Lovelace } from "../types"; +export const DEFAULT_MAX_COLUMNS = 4; + +const parsePx = (value: string) => parseInt(value.replace("px", "")); + @customElement("hui-sections-view") export class SectionsView extends LitElement implements LovelaceViewElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -46,6 +51,30 @@ export class SectionsView extends LitElement implements LovelaceViewElement { @state() _dragging = false; + private _columnsController = new ResizeController(this, { + callback: (entries) => { + const totalWidth = entries[0]?.contentRect.width; + + const style = getComputedStyle(this); + const container = this.shadowRoot!.querySelector(".container")!; + const containerStyle = getComputedStyle(container); + + const paddingLeft = parsePx(containerStyle.paddingLeft); + const paddingRight = parsePx(containerStyle.paddingRight); + const padding = paddingLeft + paddingRight; + const minColumnWidth = parsePx( + style.getPropertyValue("--column-min-width") + ); + const columnGap = parsePx(containerStyle.columnGap); + + const columns = Math.floor( + (totalWidth - padding + columnGap) / (minColumnWidth + columnGap) + ); + const maxColumns = this._config?.max_columns ?? DEFAULT_MAX_COLUMNS; + return Math.max(Math.min(maxColumns, columns), 1); + }, + }); + public setConfig(config: LovelaceViewConfig): void { this._config = config; } @@ -95,10 +124,11 @@ export class SectionsView extends LitElement implements LovelaceViewElement { if (!this.lovelace) return nothing; const sections = this.sections; - const totalCount = this._sectionCount + (this.lovelace?.editMode ? 1 : 0); + const totalSectionCount = + this._sectionCount + (this.lovelace?.editMode ? 1 : 0); const editMode = this.lovelace.editMode; - const maxColumnsCount = this._config?.max_columns; + const maxColumnCount = this._columnsController.value ?? 1; return html` ${repeat( sections, (section) => this._getSectionKey(section), (section, idx) => { + const sectionConfig = this._config?.sections?.[idx]; + const columnSpan = Math.min( + sectionConfig?.column_span || 1, + maxColumnCount + ); + (section as any).itemPath = [idx]; + return html` -
+
${editMode ? html`
@@ -252,19 +294,19 @@ export class SectionsView extends LitElement implements LovelaceViewElement { --row-height: var(--ha-view-sections-row-height, 56px); --row-gap: var(--ha-view-sections-row-gap, 8px); --column-gap: var(--ha-view-sections-column-gap, 32px); - --column-min-width: var(--ha-view-sections-column-min-width, 320px); --column-max-width: var(--ha-view-sections-column-max-width, 500px); + --column-min-width: var(--ha-view-sections-column-min-width, 320px); display: block; } .container > * { position: relative; - max-width: var(--column-max-width); width: 100%; } .section { border-radius: var(--ha-card-border-radius, 12px); + grid-column: span var(--column-span); } .section:not(:has(> *:not([hidden]))) { @@ -272,29 +314,22 @@ export class SectionsView extends LitElement implements LovelaceViewElement { } .container { - --max-count: min(var(--total-count), var(--max-columns-count, 4)); - --max-width: min( - calc( - (var(--max-count) + 1) * var(--column-min-width) + - (var(--max-count) + 2) * var(--column-gap) - 1px - ), - calc( - var(--max-count) * var(--column-max-width) + (var(--max-count) + 1) * - var(--column-gap) - ) + --column-count: min( + var(--max-column-count), + var(--total-section-count) ); display: grid; align-items: start; - justify-items: center; - grid-template-columns: repeat( - auto-fit, - minmax(min(var(--column-min-width), 100%), 1fr) - ); + justify-content: center; + grid-template-columns: repeat(var(--column-count), 1fr); gap: var(--row-gap) var(--column-gap); padding: var(--row-gap) var(--column-gap); - box-sizing: border-box; - max-width: var(--max-width); + box-sizing: content-box; margin: 0 auto; + max-width: calc( + var(--column-count) * var(--column-max-width) + + (var(--column-count) - 1) * var(--column-gap) + ); } @media (max-width: 600px) {