Compare commits

...
Author SHA1 Message Date
Petar Petrov 04f744e90e Allow a few pixels of slack when counting sections view columns.
Subtracting wrapper padding made 1080px kiosks fall 8px short of the
3-column threshold, so span-2/3 sections stacked as a single column.
2026-08-13 13:38:20 +03:00
3 changed files with 101 additions and 9 deletions
@@ -0,0 +1,29 @@
/**
* Allow a column a few pixels under --column-min-width rather than dropping
* it. Subtracting wrapper padding (#53515) made 1080px viewports 8px short of
* the 3-column threshold, so span-2/3 sections stacked as a single column.
* 16px also covers a typical scrollbar without undoing 1-column sidebar
* stacking below ~720px.
*/
export const SECTION_COLUMN_FIT_TOLERANCE_PX = 16;
export const parseCssPx = (value: string): number => {
const parsed = parseFloat(value);
return Number.isFinite(parsed) ? parsed : 0;
};
export const computeSectionsColumnCount = (
totalWidth: number,
padding: number,
minColumnWidth: number,
columnGap: number
): number => {
if (totalWidth <= 0) {
return 1;
}
const columns = Math.floor(
(totalWidth - padding + columnGap + SECTION_COLUMN_FIT_TOLERANCE_PX) /
(minColumnWidth + columnGap)
);
return Math.max(1, columns);
};
+13 -9
View File
@@ -33,6 +33,10 @@ import {
import type { HuiSection } from "../sections/hui-section";
import "../sections/hui-section-background";
import type { Lovelace } from "../types";
import {
computeSectionsColumnCount,
parseCssPx,
} from "./compute-sections-column-count";
import { generateDefaultSection } from "./default-section";
import "./hui-view-footer";
import "./hui-view-header";
@@ -41,8 +45,6 @@ import { computeSectionsBackgroundAlignment } from "./sections-background-alignm
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;
@@ -93,18 +95,20 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
const container = this.shadowRoot!.querySelector(".container")!;
const containerStyle = getComputedStyle(container);
const paddingLeft = parsePx(wrapperStyle.paddingLeft);
const paddingRight = parsePx(wrapperStyle.paddingRight);
const paddingLeft = parseCssPx(wrapperStyle.paddingLeft);
const paddingRight = parseCssPx(wrapperStyle.paddingRight);
const padding = paddingLeft + paddingRight;
const minColumnWidth = parsePx(
const minColumnWidth = parseCssPx(
style.getPropertyValue("--column-min-width")
);
const columnGap = parsePx(containerStyle.columnGap);
const columnGap = parseCssPx(containerStyle.columnGap);
const columns = Math.floor(
(totalWidth - padding + columnGap) / (minColumnWidth + columnGap)
return computeSectionsColumnCount(
totalWidth,
padding,
minColumnWidth,
columnGap
);
return Math.max(columns, 1);
},
});
@@ -0,0 +1,59 @@
import { describe, expect, it } from "vitest";
import {
computeSectionsColumnCount,
parseCssPx,
} from "../../../../src/panels/lovelace/views/compute-sections-column-count";
// Defaults from hui-sections-view: --column-min-width 320px, --column-gap 32px,
// wrapper padding 0 var(--column-gap) → 64px.
const MIN_COLUMN_WIDTH = 320;
const COLUMN_GAP = 32;
const PADDING = 64;
const columnsFor = (totalWidth: number) =>
computeSectionsColumnCount(totalWidth, PADDING, MIN_COLUMN_WIDTH, COLUMN_GAP);
describe("parseCssPx", () => {
it("parses integer pixel values", () => {
expect(parseCssPx("32px")).toBe(32);
});
it("parses fractional pixel values from zoom", () => {
expect(parseCssPx("31.68px")).toBe(31.68);
});
it("returns 0 for empty or non-numeric values", () => {
expect(parseCssPx("")).toBe(0);
expect(parseCssPx("auto")).toBe(0);
});
});
describe("computeSectionsColumnCount", () => {
it("returns 3 columns at 1080px (kiosk width just under the exact fit)", () => {
expect(columnsFor(1080)).toBe(3);
});
it("returns 3 columns at the exact 3-column fit of 1088px", () => {
expect(columnsFor(1088)).toBe(3);
});
it("returns 1 column at 670px so the sidebar still stacks", () => {
expect(columnsFor(670)).toBe(1);
});
it("returns 1 column when width is missing or zero", () => {
expect(columnsFor(0)).toBe(1);
expect(
computeSectionsColumnCount(-10, PADDING, MIN_COLUMN_WIDTH, COLUMN_GAP)
).toBe(1);
});
it("still returns 3 columns with zoom-scaled fractional CSS pixels", () => {
const minColumnWidth = parseCssPx("316.8px");
const columnGap = parseCssPx("31.68px");
const padding = parseCssPx("31.68px") + parseCssPx("31.68px");
expect(
computeSectionsColumnCount(1080, padding, minColumnWidth, columnGap)
).toBe(3);
});
});