mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 09:46:36 +00:00
Allow a card to span the full width of a section (#21758)
* Limit card size with the grid size * Set full option in YAML * Export card grid size * Add editor * Set full column for map card and iframe by default * Do not set string variable
This commit is contained in:
parent
5a229e3c88
commit
2f68ee0efc
@ -1,24 +1,24 @@
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "./ha-icon-button";
|
||||
import "../panels/lovelace/editor/card-editor/ha-grid-layout-slider";
|
||||
import "./ha-icon-button";
|
||||
|
||||
import { mdiRestore } from "@mdi/js";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { conditionalClamp } from "../common/number/clamp";
|
||||
|
||||
type GridSizeValue = {
|
||||
rows?: number | "auto";
|
||||
columns?: number;
|
||||
};
|
||||
import {
|
||||
CardGridSize,
|
||||
DEFAULT_GRID_SIZE,
|
||||
} from "../panels/lovelace/common/compute-card-grid-size";
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
@customElement("ha-grid-size-picker")
|
||||
export class HaGridSizeEditor extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public value?: GridSizeValue;
|
||||
@property({ attribute: false }) public value?: CardGridSize;
|
||||
|
||||
@property({ attribute: false }) public rows = 8;
|
||||
|
||||
@ -34,7 +34,7 @@ export class HaGridSizeEditor extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public isDefault?: boolean;
|
||||
|
||||
@state() public _localValue?: GridSizeValue = undefined;
|
||||
@state() public _localValue?: CardGridSize = { rows: 1, columns: 1 };
|
||||
|
||||
protected willUpdate(changedProperties) {
|
||||
if (changedProperties.has("value")) {
|
||||
@ -49,6 +49,7 @@ export class HaGridSizeEditor extends LitElement {
|
||||
this.rowMin !== undefined && this.rowMin === this.rowMax;
|
||||
|
||||
const autoHeight = this._localValue?.rows === "auto";
|
||||
const fullWidth = this._localValue?.columns === "full";
|
||||
|
||||
const rowMin = this.rowMin ?? 1;
|
||||
const rowMax = this.rowMax ?? this.rows;
|
||||
@ -67,7 +68,7 @@ export class HaGridSizeEditor extends LitElement {
|
||||
.min=${columnMin}
|
||||
.max=${columnMax}
|
||||
.range=${this.columns}
|
||||
.value=${columnValue}
|
||||
.value=${fullWidth ? this.columns : columnValue}
|
||||
@value-changed=${this._valueChanged}
|
||||
@slider-moved=${this._sliderMoved}
|
||||
.disabled=${disabledColumns}
|
||||
@ -104,12 +105,12 @@ export class HaGridSizeEditor extends LitElement {
|
||||
`
|
||||
: nothing}
|
||||
<div
|
||||
class="preview"
|
||||
class="preview ${classMap({ "full-width": fullWidth })}"
|
||||
style=${styleMap({
|
||||
"--total-rows": this.rows,
|
||||
"--total-columns": this.columns,
|
||||
"--rows": rowValue,
|
||||
"--columns": columnValue,
|
||||
"--columns": fullWidth ? this.columns : columnValue,
|
||||
})}
|
||||
>
|
||||
<div>
|
||||
@ -140,12 +141,21 @@ export class HaGridSizeEditor extends LitElement {
|
||||
const cell = ev.currentTarget as HTMLElement;
|
||||
const rows = Number(cell.getAttribute("data-row"));
|
||||
const columns = Number(cell.getAttribute("data-column"));
|
||||
const clampedRow = conditionalClamp(rows, this.rowMin, this.rowMax);
|
||||
const clampedColumn = conditionalClamp(
|
||||
const clampedRow: CardGridSize["rows"] = conditionalClamp(
|
||||
rows,
|
||||
this.rowMin,
|
||||
this.rowMax
|
||||
);
|
||||
let clampedColumn: CardGridSize["columns"] = conditionalClamp(
|
||||
columns,
|
||||
this.columnMin,
|
||||
this.columnMax
|
||||
);
|
||||
|
||||
const currentSize = this.value ?? DEFAULT_GRID_SIZE;
|
||||
if (currentSize.columns === "full" && clampedColumn === this.columns) {
|
||||
clampedColumn = "full";
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { rows: clampedRow, columns: clampedColumn },
|
||||
});
|
||||
@ -153,12 +163,23 @@ export class HaGridSizeEditor extends LitElement {
|
||||
|
||||
private _valueChanged(ev) {
|
||||
ev.stopPropagation();
|
||||
const key = ev.currentTarget.id;
|
||||
const newValue = {
|
||||
...this.value,
|
||||
[key]: ev.detail.value,
|
||||
const key = ev.currentTarget.id as "rows" | "columns";
|
||||
const currentSize = this.value ?? DEFAULT_GRID_SIZE;
|
||||
let value = ev.detail.value as CardGridSize[typeof key];
|
||||
|
||||
if (
|
||||
key === "columns" &&
|
||||
currentSize.columns === "full" &&
|
||||
value === this.columns
|
||||
) {
|
||||
value = "full";
|
||||
}
|
||||
|
||||
const newSize = {
|
||||
...currentSize,
|
||||
[key]: value,
|
||||
};
|
||||
fireEvent(this, "value-changed", { value: newValue });
|
||||
fireEvent(this, "value-changed", { value: newSize });
|
||||
}
|
||||
|
||||
private _reset(ev) {
|
||||
@ -173,11 +194,14 @@ export class HaGridSizeEditor extends LitElement {
|
||||
|
||||
private _sliderMoved(ev) {
|
||||
ev.stopPropagation();
|
||||
const key = ev.currentTarget.id;
|
||||
const value = ev.detail.value;
|
||||
const key = ev.currentTarget.id as "rows" | "columns";
|
||||
const currentSize = this.value ?? DEFAULT_GRID_SIZE;
|
||||
const value = ev.detail.value as CardGridSize[typeof key] | undefined;
|
||||
|
||||
if (value === undefined) return;
|
||||
|
||||
this._localValue = {
|
||||
...this.value,
|
||||
...currentSize,
|
||||
[key]: ev.detail.value,
|
||||
};
|
||||
}
|
||||
@ -231,10 +255,13 @@ export class HaGridSizeEditor extends LitElement {
|
||||
}
|
||||
.selected .cell {
|
||||
background-color: var(--primary-color);
|
||||
grid-column: 1 / span var(--columns, 0);
|
||||
grid-row: 1 / span var(--rows, 0);
|
||||
grid-column: 1 / span min(var(--columns, 0), var(--total-columns));
|
||||
grid-row: 1 / span min(var(--rows, 0), var(--total-rows));
|
||||
opacity: 0.5;
|
||||
}
|
||||
.preview.full-width .selected .cell {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@ -114,7 +114,7 @@ export class HuiIframeCard extends LitElement implements LovelaceCard {
|
||||
|
||||
public getLayoutOptions(): LovelaceLayoutOptions {
|
||||
return {
|
||||
grid_columns: 4,
|
||||
grid_columns: "full",
|
||||
grid_rows: 4,
|
||||
grid_min_rows: 2,
|
||||
};
|
||||
|
@ -426,7 +426,7 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
||||
|
||||
public getLayoutOptions(): LovelaceLayoutOptions {
|
||||
return {
|
||||
grid_columns: 4,
|
||||
grid_columns: "full",
|
||||
grid_rows: 4,
|
||||
grid_min_columns: 2,
|
||||
grid_min_rows: 2,
|
||||
|
36
src/panels/lovelace/common/compute-card-grid-size.ts
Normal file
36
src/panels/lovelace/common/compute-card-grid-size.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { conditionalClamp } from "../../../common/number/clamp";
|
||||
import { LovelaceLayoutOptions } from "../types";
|
||||
|
||||
export const DEFAULT_GRID_SIZE = {
|
||||
columns: 4,
|
||||
rows: "auto",
|
||||
} as CardGridSize;
|
||||
|
||||
export type CardGridSize = {
|
||||
rows: number | "auto";
|
||||
columns: number | "full";
|
||||
};
|
||||
|
||||
export const computeCardGridSize = (
|
||||
options: LovelaceLayoutOptions
|
||||
): CardGridSize => {
|
||||
const rows = options.grid_rows ?? DEFAULT_GRID_SIZE.rows;
|
||||
const columns = options.grid_columns ?? DEFAULT_GRID_SIZE.columns;
|
||||
const minRows = options.grid_min_rows;
|
||||
const maxRows = options.grid_max_rows;
|
||||
const minColumns = options.grid_min_columns;
|
||||
const maxColumns = options.grid_max_columns;
|
||||
|
||||
const clampedRows =
|
||||
typeof rows === "string" ? rows : conditionalClamp(rows, minRows, maxRows);
|
||||
|
||||
const clampedColumns =
|
||||
typeof columns === "string"
|
||||
? columns
|
||||
: conditionalClamp(columns, minColumns, maxColumns);
|
||||
|
||||
return {
|
||||
rows: clampedRows,
|
||||
columns: clampedColumns,
|
||||
};
|
||||
};
|
@ -1,6 +1,6 @@
|
||||
import type { ActionDetail } from "@material/mwc-list";
|
||||
import { mdiCheck, mdiDotsVertical } from "@mdi/js";
|
||||
import { LitElement, PropertyValues, css, html, nothing } from "lit";
|
||||
import { css, html, LitElement, nothing, PropertyValues } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
@ -8,6 +8,7 @@ import { preventDefault } from "../../../../common/dom/prevent_default";
|
||||
import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-button-menu";
|
||||
import "../../../../components/ha-formfield";
|
||||
import "../../../../components/ha-grid-size-picker";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-list-item";
|
||||
@ -21,7 +22,10 @@ import { LovelaceCardConfig } from "../../../../data/lovelace/config/card";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { HuiCard } from "../../cards/hui-card";
|
||||
import { computeSizeOnGrid } from "../../sections/hui-grid-section";
|
||||
import {
|
||||
CardGridSize,
|
||||
computeCardGridSize,
|
||||
} from "../../common/compute-card-grid-size";
|
||||
import { LovelaceLayoutOptions } from "../../types";
|
||||
|
||||
@customElement("hui-card-layout-editor")
|
||||
@ -50,7 +54,7 @@ export class HuiCardLayoutEditor extends LitElement {
|
||||
})
|
||||
);
|
||||
|
||||
private _gridSizeValue = memoizeOne(computeSizeOnGrid);
|
||||
private _computeCardGridSize = memoizeOne(computeCardGridSize);
|
||||
|
||||
private _isDefault = memoizeOne(
|
||||
(options?: LovelaceLayoutOptions) =>
|
||||
@ -63,7 +67,7 @@ export class HuiCardLayoutEditor extends LitElement {
|
||||
this._defaultLayoutOptions
|
||||
);
|
||||
|
||||
const sizeValue = this._gridSizeValue(options);
|
||||
const value = this._computeCardGridSize(options);
|
||||
|
||||
return html`
|
||||
<div class="header">
|
||||
@ -128,7 +132,7 @@ export class HuiCardLayoutEditor extends LitElement {
|
||||
: html`
|
||||
<ha-grid-size-picker
|
||||
.hass=${this.hass}
|
||||
.value=${sizeValue}
|
||||
.value=${value}
|
||||
.isDefault=${this._isDefault(this.config.layout_options)}
|
||||
@value-changed=${this._gridSizeChanged}
|
||||
.rowMin=${options.grid_min_rows}
|
||||
@ -136,6 +140,24 @@ export class HuiCardLayoutEditor extends LitElement {
|
||||
.columnMin=${options.grid_min_columns}
|
||||
.columnMax=${options.grid_max_columns}
|
||||
></ha-grid-size-picker>
|
||||
<ha-settings-row>
|
||||
<span slot="heading" data-for="full-width">
|
||||
${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.edit_card.layout.full_width"
|
||||
)}
|
||||
</span>
|
||||
<span slot="description" data-for="full-width">
|
||||
${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.edit_card.layout.full_width_helper"
|
||||
)}
|
||||
</span>
|
||||
<ha-switch
|
||||
@change=${this._fullWidthChanged}
|
||||
.checked=${value.columns === "full"}
|
||||
name="full-width"
|
||||
>
|
||||
</ha-switch>
|
||||
</ha-settings-row>
|
||||
`}
|
||||
`;
|
||||
}
|
||||
@ -195,7 +217,7 @@ export class HuiCardLayoutEditor extends LitElement {
|
||||
|
||||
private _gridSizeChanged(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
const value = ev.detail.value;
|
||||
const value = ev.detail.value as CardGridSize;
|
||||
|
||||
const newConfig: LovelaceCardConfig = {
|
||||
...this.config,
|
||||
@ -229,6 +251,21 @@ export class HuiCardLayoutEditor extends LitElement {
|
||||
fireEvent(this, "value-changed", { value: newConfig });
|
||||
}
|
||||
|
||||
private _fullWidthChanged(ev): void {
|
||||
ev.stopPropagation();
|
||||
const value = ev.target.checked;
|
||||
const newConfig: LovelaceCardConfig = {
|
||||
...this.config,
|
||||
layout_options: {
|
||||
...this.config.layout_options,
|
||||
grid_columns: value
|
||||
? "full"
|
||||
: (this._defaultLayoutOptions?.grid_min_columns ?? 1),
|
||||
},
|
||||
};
|
||||
fireEvent(this, "value-changed", { value: newConfig });
|
||||
}
|
||||
|
||||
static styles = [
|
||||
haStyle,
|
||||
css`
|
||||
@ -262,6 +299,13 @@ export class HuiCardLayoutEditor extends LitElement {
|
||||
display: block;
|
||||
margin: 16px 0;
|
||||
}
|
||||
ha-formfield {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
--mdc-typography-body2-font-size: 1em;
|
||||
max-width: 250px;
|
||||
margin: 16px auto;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@ -14,8 +14,8 @@ import type { HomeAssistant } from "../../../types";
|
||||
import { HuiCard } from "../cards/hui-card";
|
||||
import "../components/hui-card-edit-mode";
|
||||
import { moveCard } from "../editor/config-util";
|
||||
import type { Lovelace, LovelaceLayoutOptions } from "../types";
|
||||
import { conditionalClamp } from "../../../common/number/clamp";
|
||||
import type { Lovelace } from "../types";
|
||||
import { computeCardGridSize } from "../common/compute-card-grid-size";
|
||||
|
||||
const CARD_SORTABLE_OPTIONS: HaSortableOptions = {
|
||||
delay: 100,
|
||||
@ -24,43 +24,6 @@ const CARD_SORTABLE_OPTIONS: HaSortableOptions = {
|
||||
invertedSwapThreshold: 0.7,
|
||||
} as HaSortableOptions;
|
||||
|
||||
export const DEFAULT_GRID_OPTIONS = {
|
||||
grid_columns: 4,
|
||||
grid_rows: "auto",
|
||||
} as const satisfies LovelaceLayoutOptions;
|
||||
|
||||
type GridSizeValue = {
|
||||
rows?: number | "auto";
|
||||
columns?: number;
|
||||
};
|
||||
|
||||
export const computeSizeOnGrid = (
|
||||
options: LovelaceLayoutOptions
|
||||
): GridSizeValue => {
|
||||
const rows =
|
||||
typeof options.grid_rows === "number"
|
||||
? conditionalClamp(
|
||||
options.grid_rows,
|
||||
options.grid_min_rows,
|
||||
options.grid_max_rows
|
||||
)
|
||||
: DEFAULT_GRID_OPTIONS.grid_rows;
|
||||
|
||||
const columns =
|
||||
typeof options.grid_columns === "number"
|
||||
? conditionalClamp(
|
||||
options.grid_columns,
|
||||
options.grid_min_columns,
|
||||
options.grid_max_columns
|
||||
)
|
||||
: DEFAULT_GRID_OPTIONS.grid_columns;
|
||||
|
||||
return {
|
||||
rows,
|
||||
columns,
|
||||
};
|
||||
};
|
||||
|
||||
export class GridSection extends LitElement implements LovelaceSectionElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@ -134,16 +97,18 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
|
||||
card.layout = "grid";
|
||||
const layoutOptions = card.getLayoutOptions();
|
||||
|
||||
const { rows, columns } = computeSizeOnGrid(layoutOptions);
|
||||
const { rows, columns } = computeCardGridSize(layoutOptions);
|
||||
|
||||
return html`
|
||||
<div
|
||||
style=${styleMap({
|
||||
"--column-size": columns,
|
||||
"--row-size": rows,
|
||||
"--column-size":
|
||||
typeof columns === "number" ? columns : undefined,
|
||||
"--row-size": typeof rows === "number" ? rows : undefined,
|
||||
})}
|
||||
class="card ${classMap({
|
||||
"fit-rows": typeof layoutOptions?.grid_rows === "number",
|
||||
"full-width": columns === "full",
|
||||
})}"
|
||||
>
|
||||
${editMode
|
||||
@ -268,8 +233,8 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
|
||||
.card {
|
||||
border-radius: var(--ha-card-border-radius, 12px);
|
||||
position: relative;
|
||||
grid-row: span var(--row-size);
|
||||
grid-column: span var(--column-size);
|
||||
grid-row: span var(--row-size, 1);
|
||||
grid-column: span min(var(--column-size, 1), var(--column-count));
|
||||
}
|
||||
|
||||
.card.fit-rows {
|
||||
@ -280,6 +245,10 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
|
||||
);
|
||||
}
|
||||
|
||||
.card.full-width {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.card:has(> *) {
|
||||
display: block;
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ export interface LovelaceBadge extends HTMLElement {
|
||||
}
|
||||
|
||||
export type LovelaceLayoutOptions = {
|
||||
grid_columns?: number;
|
||||
grid_columns?: number | "full";
|
||||
grid_rows?: number | "auto";
|
||||
grid_max_columns?: number;
|
||||
grid_min_columns?: number;
|
||||
|
@ -5588,6 +5588,10 @@
|
||||
"tab_layout": "Layout",
|
||||
"visibility": {
|
||||
"explanation": "The card will be shown when ALL conditions below are fulfilled. If no conditions are set, the card will always be shown."
|
||||
},
|
||||
"layout": {
|
||||
"full_width": "Full width card",
|
||||
"full_width_helper": "Take up the full width of the section whatever its size"
|
||||
}
|
||||
},
|
||||
"edit_badge": {
|
||||
|
Loading…
x
Reference in New Issue
Block a user