mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-25 18:26:35 +00:00
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
This commit is contained in:
parent
7c5f947865
commit
5a229e3c88
@ -5,6 +5,7 @@ import type { LovelaceStrategyConfig } from "./strategy";
|
|||||||
export interface LovelaceBaseSectionConfig {
|
export interface LovelaceBaseSectionConfig {
|
||||||
title?: string;
|
title?: string;
|
||||||
visibility?: Condition[];
|
visibility?: Condition[];
|
||||||
|
column_span?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LovelaceSectionConfig extends LovelaceBaseSectionConfig {
|
export interface LovelaceSectionConfig extends LovelaceBaseSectionConfig {
|
||||||
|
@ -13,10 +13,15 @@ const SCHEMA = [
|
|||||||
name: "title",
|
name: "title",
|
||||||
selector: { text: {} },
|
selector: { text: {} },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "column_span",
|
||||||
|
selector: { number: { min: 1, max: 3 } },
|
||||||
|
},
|
||||||
] as const satisfies HaFormSchema[];
|
] as const satisfies HaFormSchema[];
|
||||||
|
|
||||||
type SettingsData = {
|
type SettingsData = {
|
||||||
title: string;
|
title: string;
|
||||||
|
column_span?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
@customElement("hui-section-settings-editor")
|
@customElement("hui-section-settings-editor")
|
||||||
@ -28,6 +33,7 @@ export class HuiDialogEditSection extends LitElement {
|
|||||||
render() {
|
render() {
|
||||||
const data: SettingsData = {
|
const data: SettingsData = {
|
||||||
title: this.config.title || "",
|
title: this.config.title || "",
|
||||||
|
column_span: this.config.column_span || 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
@ -59,6 +65,7 @@ export class HuiDialogEditSection extends LitElement {
|
|||||||
const newConfig: LovelaceSectionRawConfig = {
|
const newConfig: LovelaceSectionRawConfig = {
|
||||||
...this.config,
|
...this.config,
|
||||||
title: newData.title,
|
title: newData.title,
|
||||||
|
column_span: newData.column_span,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!newConfig.title) {
|
if (!newConfig.title) {
|
||||||
|
@ -220,8 +220,14 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
|
|||||||
gap: var(--row-gap);
|
gap: var(--row-gap);
|
||||||
}
|
}
|
||||||
.container {
|
.container {
|
||||||
|
--grid-column-count: calc(
|
||||||
|
var(--column-count) * var(--column-span, 1)
|
||||||
|
);
|
||||||
display: grid;
|
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);
|
grid-auto-rows: minmax(var(--row-height), auto);
|
||||||
row-gap: var(--row-gap);
|
row-gap: var(--row-gap);
|
||||||
column-gap: var(--column-gap);
|
column-gap: var(--column-gap);
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { ResizeController } from "@lit-labs/observers/resize-controller";
|
||||||
import { mdiArrowAll, mdiDelete, mdiPencil, mdiViewGridPlus } from "@mdi/js";
|
import { mdiArrowAll, mdiDelete, mdiPencil, mdiViewGridPlus } from "@mdi/js";
|
||||||
import {
|
import {
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
@ -26,6 +27,10 @@ import { showEditSectionDialog } from "../editor/section-editor/show-edit-sectio
|
|||||||
import { HuiSection } from "../sections/hui-section";
|
import { HuiSection } from "../sections/hui-section";
|
||||||
import type { Lovelace } from "../types";
|
import type { Lovelace } from "../types";
|
||||||
|
|
||||||
|
export const DEFAULT_MAX_COLUMNS = 4;
|
||||||
|
|
||||||
|
const parsePx = (value: string) => parseInt(value.replace("px", ""));
|
||||||
|
|
||||||
@customElement("hui-sections-view")
|
@customElement("hui-sections-view")
|
||||||
export class SectionsView extends LitElement implements LovelaceViewElement {
|
export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@ -46,6 +51,30 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
|||||||
|
|
||||||
@state() _dragging = false;
|
@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 {
|
public setConfig(config: LovelaceViewConfig): void {
|
||||||
this._config = config;
|
this._config = config;
|
||||||
}
|
}
|
||||||
@ -95,10 +124,11 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
|||||||
if (!this.lovelace) return nothing;
|
if (!this.lovelace) return nothing;
|
||||||
|
|
||||||
const sections = this.sections;
|
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 editMode = this.lovelace.editMode;
|
||||||
|
|
||||||
const maxColumnsCount = this._config?.max_columns;
|
const maxColumnCount = this._columnsController.value ?? 1;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<hui-view-badges
|
<hui-view-badges
|
||||||
@ -118,17 +148,29 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
|||||||
<div
|
<div
|
||||||
class="container"
|
class="container"
|
||||||
style=${styleMap({
|
style=${styleMap({
|
||||||
"--max-columns-count": maxColumnsCount,
|
"--total-section-count": totalSectionCount,
|
||||||
"--total-count": totalCount,
|
"--max-column-count": maxColumnCount,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
${repeat(
|
${repeat(
|
||||||
sections,
|
sections,
|
||||||
(section) => this._getSectionKey(section),
|
(section) => this._getSectionKey(section),
|
||||||
(section, idx) => {
|
(section, idx) => {
|
||||||
|
const sectionConfig = this._config?.sections?.[idx];
|
||||||
|
const columnSpan = Math.min(
|
||||||
|
sectionConfig?.column_span || 1,
|
||||||
|
maxColumnCount
|
||||||
|
);
|
||||||
|
|
||||||
(section as any).itemPath = [idx];
|
(section as any).itemPath = [idx];
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="section">
|
<div
|
||||||
|
class="section"
|
||||||
|
style=${styleMap({
|
||||||
|
"--column-span": columnSpan,
|
||||||
|
})}
|
||||||
|
>
|
||||||
${editMode
|
${editMode
|
||||||
? html`
|
? html`
|
||||||
<div class="section-overlay">
|
<div class="section-overlay">
|
||||||
@ -252,19 +294,19 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
|||||||
--row-height: var(--ha-view-sections-row-height, 56px);
|
--row-height: var(--ha-view-sections-row-height, 56px);
|
||||||
--row-gap: var(--ha-view-sections-row-gap, 8px);
|
--row-gap: var(--ha-view-sections-row-gap, 8px);
|
||||||
--column-gap: var(--ha-view-sections-column-gap, 32px);
|
--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-max-width: var(--ha-view-sections-column-max-width, 500px);
|
||||||
|
--column-min-width: var(--ha-view-sections-column-min-width, 320px);
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container > * {
|
.container > * {
|
||||||
position: relative;
|
position: relative;
|
||||||
max-width: var(--column-max-width);
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section {
|
.section {
|
||||||
border-radius: var(--ha-card-border-radius, 12px);
|
border-radius: var(--ha-card-border-radius, 12px);
|
||||||
|
grid-column: span var(--column-span);
|
||||||
}
|
}
|
||||||
|
|
||||||
.section:not(:has(> *:not([hidden]))) {
|
.section:not(:has(> *:not([hidden]))) {
|
||||||
@ -272,29 +314,22 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
--max-count: min(var(--total-count), var(--max-columns-count, 4));
|
--column-count: min(
|
||||||
--max-width: min(
|
var(--max-column-count),
|
||||||
calc(
|
var(--total-section-count)
|
||||||
(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)
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
display: grid;
|
display: grid;
|
||||||
align-items: start;
|
align-items: start;
|
||||||
justify-items: center;
|
justify-content: center;
|
||||||
grid-template-columns: repeat(
|
grid-template-columns: repeat(var(--column-count), 1fr);
|
||||||
auto-fit,
|
|
||||||
minmax(min(var(--column-min-width), 100%), 1fr)
|
|
||||||
);
|
|
||||||
gap: var(--row-gap) var(--column-gap);
|
gap: var(--row-gap) var(--column-gap);
|
||||||
padding: var(--row-gap) var(--column-gap);
|
padding: var(--row-gap) var(--column-gap);
|
||||||
box-sizing: border-box;
|
box-sizing: content-box;
|
||||||
max-width: var(--max-width);
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
max-width: calc(
|
||||||
|
var(--column-count) * var(--column-max-width) +
|
||||||
|
(var(--column-count) - 1) * var(--column-gap)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user