Compare commits

...
22 changed files with 567 additions and 90 deletions

No files matched your search

@@ -0,0 +1,4 @@
<svg width="36" height="16" viewBox="0 0 36 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="16" height="16" rx="3" fill="black" fill-opacity="0.32"/>
<rect x="20" width="16" height="16" rx="3" fill="black" fill-opacity="0.12"/>
</svg>

After

Width:  |  Height:  |  Size: 252 B

@@ -0,0 +1,4 @@
<svg width="36" height="16" viewBox="0 0 36 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="16" height="16" rx="3" fill="white" fill-opacity="0.48"/>
<rect x="20" width="16" height="16" rx="3" fill="white" fill-opacity="0.24"/>
</svg>

After

Width:  |  Height:  |  Size: 252 B

@@ -0,0 +1,5 @@
<svg width="56" height="16" viewBox="0 0 56 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="16" height="16" rx="3" fill="black" fill-opacity="0.32"/>
<rect x="20" width="16" height="16" rx="3" fill="black" fill-opacity="0.12"/>
<rect x="40" width="16" height="16" rx="3" fill="black" fill-opacity="0.12"/>
</svg>

After

Width:  |  Height:  |  Size: 330 B

@@ -0,0 +1,5 @@
<svg width="56" height="16" viewBox="0 0 56 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="16" height="16" rx="3" fill="white" fill-opacity="0.48"/>
<rect x="20" width="16" height="16" rx="3" fill="white" fill-opacity="0.24"/>
<rect x="40" width="16" height="16" rx="3" fill="white" fill-opacity="0.24"/>
</svg>

After

Width:  |  Height:  |  Size: 330 B

@@ -0,0 +1,6 @@
<svg width="76" height="16" viewBox="0 0 76 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="16" height="16" rx="3" fill="black" fill-opacity="0.32"/>
<rect x="20" width="16" height="16" rx="3" fill="black" fill-opacity="0.12"/>
<rect x="40" width="16" height="16" rx="3" fill="black" fill-opacity="0.12"/>
<rect x="60" width="16" height="16" rx="3" fill="black" fill-opacity="0.12"/>
</svg>

After

Width:  |  Height:  |  Size: 408 B

@@ -0,0 +1,6 @@
<svg width="76" height="16" viewBox="0 0 76 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="16" height="16" rx="3" fill="white" fill-opacity="0.48"/>
<rect x="20" width="16" height="16" rx="3" fill="white" fill-opacity="0.24"/>
<rect x="40" width="16" height="16" rx="3" fill="white" fill-opacity="0.24"/>
<rect x="60" width="16" height="16" rx="3" fill="white" fill-opacity="0.24"/>
</svg>

After

Width:  |  Height:  |  Size: 408 B

@@ -0,0 +1,3 @@
<svg width="36" height="16" viewBox="0 0 36 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="36" height="16" rx="3" fill="black" fill-opacity="0.32"/>
</svg>

After

Width:  |  Height:  |  Size: 174 B

@@ -0,0 +1,3 @@
<svg width="36" height="16" viewBox="0 0 36 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="36" height="16" rx="3" fill="white" fill-opacity="0.48"/>
</svg>

After

Width:  |  Height:  |  Size: 174 B

@@ -0,0 +1,4 @@
<svg width="56" height="16" viewBox="0 0 56 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="36" height="16" rx="3" fill="black" fill-opacity="0.32"/>
<rect x="40" width="16" height="16" rx="3" fill="black" fill-opacity="0.12"/>
</svg>

After

Width:  |  Height:  |  Size: 252 B

@@ -0,0 +1,4 @@
<svg width="56" height="16" viewBox="0 0 56 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="36" height="16" rx="3" fill="white" fill-opacity="0.48"/>
<rect x="40" width="16" height="16" rx="3" fill="white" fill-opacity="0.24"/>
</svg>

After

Width:  |  Height:  |  Size: 252 B

@@ -0,0 +1,5 @@
<svg width="76" height="16" viewBox="0 0 76 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="36" height="16" rx="3" fill="black" fill-opacity="0.32"/>
<rect x="40" width="16" height="16" rx="3" fill="black" fill-opacity="0.12"/>
<rect x="60" width="16" height="16" rx="3" fill="black" fill-opacity="0.12"/>
</svg>

After

Width:  |  Height:  |  Size: 330 B

@@ -0,0 +1,5 @@
<svg width="76" height="16" viewBox="0 0 76 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="36" height="16" rx="3" fill="white" fill-opacity="0.48"/>
<rect x="40" width="16" height="16" rx="3" fill="white" fill-opacity="0.24"/>
<rect x="60" width="16" height="16" rx="3" fill="white" fill-opacity="0.24"/>
</svg>

After

Width:  |  Height:  |  Size: 330 B

@@ -0,0 +1,3 @@
<svg width="56" height="16" viewBox="0 0 56 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="56" height="16" rx="3" fill="black" fill-opacity="0.32"/>
</svg>

After

Width:  |  Height:  |  Size: 174 B

@@ -0,0 +1,3 @@
<svg width="56" height="16" viewBox="0 0 56 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="56" height="16" rx="3" fill="white" fill-opacity="0.48"/>
</svg>

After

Width:  |  Height:  |  Size: 174 B

@@ -0,0 +1,4 @@
<svg width="76" height="16" viewBox="0 0 76 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="56" height="16" rx="3" fill="black" fill-opacity="0.32"/>
<rect x="60" width="16" height="16" rx="3" fill="black" fill-opacity="0.12"/>
</svg>

After

Width:  |  Height:  |  Size: 252 B

@@ -0,0 +1,4 @@
<svg width="76" height="16" viewBox="0 0 76 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="56" height="16" rx="3" fill="white" fill-opacity="0.48"/>
<rect x="60" width="16" height="16" rx="3" fill="white" fill-opacity="0.24"/>
</svg>

After

Width:  |  Height:  |  Size: 252 B

@@ -0,0 +1,3 @@
<svg width="76" height="16" viewBox="0 0 76 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="76" height="16" rx="3" fill="black" fill-opacity="0.32"/>
</svg>

After

Width:  |  Height:  |  Size: 174 B

@@ -0,0 +1,3 @@
<svg width="76" height="16" viewBox="0 0 76 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="76" height="16" rx="3" fill="white" fill-opacity="0.48"/>
</svg>

After

Width:  |  Height:  |  Size: 174 B

@@ -1,26 +1,38 @@
import { mdiClose, mdiDotsVertical, mdiFileMoveOutline } from "@mdi/js";
import {
mdiCellphone,
mdiClose,
mdiDotsVertical,
mdiFileMoveOutline,
mdiMonitor,
} from "@mdi/js";
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { styleMap } from "lit/directives/style-map";
import { cache } from "lit/directives/cache";
import type { HASSDomEvent } from "../../../../common/dom/fire_event";
import { storage } from "../../../../common/decorators/storage";
import { fireEvent } from "../../../../common/dom/fire_event";
import { stopPropagation } from "../../../../common/dom/stop_propagation";
import { withViewTransition } from "../../../../common/util/view-transition";
import "../../../../components/ha-button";
import "../../../../components/ha-dialog-header";
import "../../../../components/ha-dialog-footer";
import "../../../../components/ha-dialog";
import "../../../../components/ha-dropdown";
import "../../../../components/ha-dropdown-item";
import "../../../../components/ha-icon-button";
import "../../../../components/ha-svg-icon";
import "../../../../components/ha-tooltip";
import "../../../../components/ha-tab-group";
import "../../../../components/ha-tab-group-tab";
import "../../../../components/ha-yaml-editor";
import type { HaYamlEditor } from "../../../../components/ha-yaml-editor";
import type { LovelaceSectionRawConfig } from "../../../../data/lovelace/config/section";
import { isStrategySection } from "../../../../data/lovelace/config/section";
import {
DEFAULT_SECTION_COLUMN_SPAN,
isStrategySection,
} from "../../../../data/lovelace/config/section";
import type { LovelaceStrategyConfig } from "../../../../data/lovelace/config/strategy";
import type { LovelaceConfig } from "../../../../data/lovelace/config/types";
import { saveConfig } from "../../../../data/lovelace/config/types";
@@ -32,6 +44,7 @@ import { DirtyStateProviderMixin } from "../../../../mixins/dirty-state-provider
import {
haStyleDialog,
haStyleDialogFixedTop,
haStyleScrollbar,
} from "../../../../resources/styles";
import type { HomeAssistant, ValueChangedEvent } from "../../../../types";
import { cleanLegacyStrategyConfig } from "../../strategies/legacy-strategy";
@@ -45,6 +58,8 @@ import {
setAtPath,
} from "../lovelace-path";
import { showSelectViewDialog } from "../select-view/show-select-view-dialog";
import "../../sections/hui-section";
import "../../sections/hui-section-background";
import "./hui-section-settings-editor";
import "./hui-section-visibility-editor";
import "./hui-section-strategy-element-editor";
@@ -53,10 +68,19 @@ import type { ConfigChangedEvent } from "../hui-element-editor";
import type { EditSectionDialogParams } from "./show-edit-section-dialog";
import type { HaDropdownSelectEvent } from "../../../../components/ha-dropdown";
import { getViewType } from "../../views/get-view-type";
import { SECTIONS_VIEW_LAYOUT } from "../../views/const";
import { DEFAULT_MAX_COLUMNS, SECTIONS_VIEW_LAYOUT } from "../../views/const";
const TABS = ["tab-strategy", "tab-appearance", "tab-visibility"] as const;
type PreviewMode = "desktop" | "mobile";
const PREVIEW_MODES: { mode: PreviewMode; iconPath: string }[] = [
{ mode: "mobile", iconPath: mdiCellphone },
{ mode: "desktop", iconPath: mdiMonitor },
];
const TWO_COLUMNS_MIN_WIDTH = 2 * 320 + 3 * 32;
@customElement("hui-dialog-edit-section")
export class HuiDialogEditSection
extends DirtyStateProviderMixin<LovelaceSectionRawConfig>()(LitElement)
@@ -66,6 +90,8 @@ export class HuiDialogEditSection
@property({ attribute: false }) public lovelace?: Lovelace;
@property({ type: Boolean, reflect: true }) public large = false;
@state() private _params?: EditSectionDialogParams;
@state() private _config?: LovelaceSectionRawConfig;
@@ -82,6 +108,15 @@ export class HuiDialogEditSection
@state() private _yamlError = false;
@storage({
key: "dashboardSectionPreviewMode",
state: true,
subscribe: false,
})
private _storedPreviewMode?: PreviewMode;
@state() private _defaultPreviewMode: PreviewMode = "mobile";
@query("ha-yaml-editor") private _editor?: HaYamlEditor;
@query("hui-section-strategy-element-editor")
@@ -117,6 +152,12 @@ export class HuiDialogEditSection
viewPath
);
this._initDirtyTracking({ type: "deep" }, this._config);
this._defaultPreviewMode =
window.innerWidth >= TWO_COLUMNS_MIN_WIDTH ? "desktop" : "mobile";
}
private get _previewMode(): PreviewMode {
return this._storedPreviewMode ?? this._defaultPreviewMode;
}
public closeDialog() {
@@ -126,6 +167,7 @@ export class HuiDialogEditSection
private _dialogClosed(): void {
this._params = undefined;
this.large = false;
this._yamlMode = false;
this._config = undefined;
this._currTab = TABS[0];
@@ -139,19 +181,23 @@ export class HuiDialogEditSection
return nothing;
}
const heading = this.hass!.localize(
"ui.panel.lovelace.editor.edit_section.header"
);
const strategy = isStrategySection(this._config)
? cleanLegacyStrategyConfig(this._config.strategy)
: undefined;
const heading = this._computeHeading(strategy);
const tabs = strategy ? TABS : TABS.filter((tab) => tab !== "tab-strategy");
const currentTab =
!strategy && this._currTab === "tab-strategy"
? "tab-appearance"
: this._currTab;
const columnSpan = Math.min(
this._config.column_span ?? DEFAULT_SECTION_COLUMN_SPAN,
this._viewConfig?.max_columns || DEFAULT_MAX_COLUMNS
);
const singleColumn = columnSpan === 1;
const previewColumnSpan = this._previewMode === "desktop" ? columnSpan : 1;
let content: TemplateResult<1> | typeof nothing = nothing;
if (this._yamlMode) {
@@ -201,65 +247,151 @@ export class HuiDialogEditSection
return html`
<ha-dialog
.open=${this._open}
.width=${this.large ? "full" : "large"}
.preventScrimClose=${this.isDirtyState}
@keydown=${this._ignoreKeydown}
@closed=${this._dialogClosed}
class=${classMap({
"yaml-mode": this._yamlMode,
})}
>
<ha-dialog-header show-border slot="header">
<ha-icon-button
slot="headerNavigationIcon"
@click=${this._cancel}
.label=${this.hass.localize("ui.common.close")}
.path=${mdiClose}
></ha-icon-button>
<span
slot="headerTitle"
class="title-enlargeable"
@click=${this._enlarge}
>${heading}</span
>
<ha-dropdown
slot="headerActionItems"
placement="bottom-end"
@closed=${stopPropagation}
@wa-select=${this._handleAction}
>
<ha-icon-button
slot="navigationIcon"
@click=${this._cancel}
.label=${this.hass.localize("ui.common.close")}
.path=${mdiClose}
slot="trigger"
.label=${this.hass!.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<span slot="title">${heading}</span>
<ha-dropdown
slot="actionItems"
placement="bottom-end"
@closed=${stopPropagation}
@wa-select=${this._handleAction}
>
<ha-icon-button
slot="trigger"
.label=${this.hass!.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-dropdown-item value="move-to-view">
<ha-svg-icon
slot="icon"
.path=${mdiFileMoveOutline}
></ha-svg-icon>
${this.hass!.localize(
"ui.panel.lovelace.editor.edit_view.move_to_view"
)}
</ha-dropdown-item>
</ha-dropdown>
${
!this._yamlMode
? html`
<ha-tab-group @wa-tab-show=${this._handleTabChanged}>
${tabs.map(
(tab) => html`
<ha-tab-group-tab
slot="nav"
.panel=${tab}
.active=${currentTab === tab}
>
${this.hass!.localize(
`ui.panel.lovelace.editor.edit_section.${tab.replace("-", "_")}`
)}
</ha-tab-group-tab>
`
)}
</ha-tab-group>
`
: nothing
}
</ha-dialog-header>
${this._yamlMode ? content : cache(content)}
<ha-dropdown-item value="move-to-view">
<ha-svg-icon slot="icon" .path=${mdiFileMoveOutline}></ha-svg-icon>
${this.hass!.localize(
"ui.panel.lovelace.editor.edit_view.move_to_view"
)}
</ha-dropdown-item>
</ha-dropdown>
<div class="content">
<div class="element-editor ha-scrollbar">
${
!this._yamlMode
? html`
<ha-tab-group @wa-tab-show=${this._handleTabChanged}>
${tabs.map(
(tab) => html`
<ha-tab-group-tab
slot="nav"
.panel=${tab}
.active=${currentTab === tab}
>
${this.hass!.localize(
`ui.panel.lovelace.editor.edit_section.${tab.replace("-", "_")}`
)}
</ha-tab-group-tab>
`
)}
</ha-tab-group>
`
: nothing
}
${this._yamlMode ? content : cache(content)}
</div>
<div class="element-preview ha-scrollbar">
<div class="preview-header">
<span id="preview-label">
${this.hass.localize(
"ui.panel.lovelace.editor.edit_section.preview"
)}
</span>
${
singleColumn
? nothing
: html`
<div
class="preview-modes"
role="group"
aria-labelledby="preview-label"
>
${PREVIEW_MODES.map(
({ mode, iconPath }) => html`
<button
id="preview-mode-${mode}"
class=${classMap({
"preview-mode": true,
active: this._previewMode === mode,
})}
aria-pressed=${this._previewMode === mode}
aria-label=${this.hass.localize(
`ui.panel.lovelace.editor.edit_section.preview_${mode}`
)}
.mode=${mode}
@click=${this._previewModeClicked}
>
<ha-svg-icon .path=${iconPath}></ha-svg-icon>
</button>
<ha-tooltip
for="preview-mode-${mode}"
placement="bottom"
>
${this.hass.localize(
`ui.panel.lovelace.editor.edit_section.preview_${mode}`
)}
</ha-tooltip>
`
)}
</div>
`
}
</div>
<div class="preview-frame">
<div
class=${classMap({
"section-preview": true,
wide: previewColumnSpan > 1,
})}
style=${styleMap({
"--column-span": previewColumnSpan,
})}
>
<div
class=${classMap({
"section-container": true,
"has-background": this._config.background !== undefined,
blur: this._yamlError || this._strategyError,
})}
>
${
this._config.background !== undefined
? html`
<hui-section-background
.hass=${this.hass}
.background=${this._config.background}
.theme=${this._config.theme}
></hui-section-background>
`
: nothing
}
<hui-section
.hass=${this.hass}
.config=${this._config}
preview
></hui-section>
</div>
</div>
</div>
</div>
</div>
<ha-dialog-footer slot="footer">
<ha-button
slot="secondaryAction"
@@ -318,6 +450,37 @@ export class HuiDialogEditSection
this._updateDirtyState(this._config);
}
private _computeHeading(strategy?: LovelaceStrategyConfig) {
const header = this.hass.localize(
"ui.panel.lovelace.editor.edit_section.header"
);
if (!strategy) {
return header;
}
const strategyName = this.hass.localize(
`ui.panel.lovelace.editor.section_strategy.${strategy.type}.name`
);
if (!strategyName) {
return header;
}
return this.hass.localize(
"ui.panel.lovelace.editor.edit_section.typed_header",
{ type: strategyName }
);
}
private _previewModeClicked(ev: Event) {
this._storedPreviewMode = (
ev.currentTarget as HTMLElement & { mode: PreviewMode }
).mode;
}
private _enlarge() {
withViewTransition(() => {
this.large = !this.large;
});
}
private _handleTabChanged(ev: CustomEvent): void {
const newTab = ev.detail.name;
if (newTab === this._currTab) {
@@ -526,15 +689,153 @@ export class HuiDialogEditSection
return [
haStyleDialog,
haStyleDialogFixedTop,
haStyleScrollbar,
css`
:host {
--code-mirror-max-height: calc(100vh - 209px);
}
ha-dialog {
--dialog-content-padding: var(--ha-space-6);
--dialog-content-padding: var(--ha-space-2);
}
ha-dialog.yaml-mode {
--dialog-content-padding: 0;
.content {
display: flex;
flex-direction: column;
width: 100%;
max-width: 100%;
}
.gui-mode-button {
margin-inline-end: auto;
@media all and (max-width: 450px), all and (max-height: 500px) {
.content {
gap: var(--ha-space-3);
}
}
@media all and (min-width: 451px) and (min-height: 501px) {
:host([large]) .content {
max-width: none;
}
}
.content .preview-frame {
padding: 4px;
overflow-x: auto;
}
.content .section-preview {
--preview-column-width: 390px;
display: block;
margin: 0 auto;
max-width: var(--preview-column-width);
}
.content .section-preview.wide {
max-width: none;
width: calc(
var(--column-span) * var(--preview-column-width) +
(var(--column-span) - 1) *
var(--ha-view-sections-column-gap, 32px)
);
}
.preview-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--ha-space-2);
min-height: 32px;
padding: var(--ha-space-1) var(--ha-space-2);
color: var(--secondary-text-color);
font-size: var(--ha-font-size-s);
font-weight: var(--ha-font-weight-medium);
}
.preview-modes {
display: flex;
gap: 2px;
padding: 2px;
border-radius: var(--ha-border-radius-pill);
background: var(--ha-color-fill-neutral-normal-resting);
}
.preview-mode {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 28px;
padding: 0;
border: none;
border-radius: var(--ha-border-radius-pill);
background: none;
color: var(--secondary-text-color);
cursor: pointer;
--mdc-icon-size: 18px;
}
.preview-mode:hover {
color: var(--primary-text-color);
}
.preview-mode.active {
color: var(--primary-text-color);
background: var(--card-background-color);
box-shadow: 0 1px 2px rgb(0 0 0 / 0.2);
}
.preview-mode:focus-visible {
outline: 2px solid var(--primary-color);
outline-offset: 2px;
}
.content .element-editor {
padding-inline-start: var(--ha-space-1);
padding-inline-end: var(--ha-space-2);
margin-bottom: 0;
}
@media (min-width: 1000px) {
.content {
flex-direction: row;
max-height: var(--code-mirror-max-height);
}
.content > .element-editor {
padding-inline-end: var(--ha-space-4);
max-width: 500px;
}
.content > .element-editor,
.content > .element-preview {
flex-basis: 0;
flex-grow: 1;
flex-shrink: 1;
min-width: 0;
height: auto;
}
.content .preview-frame {
padding: 8px 10px;
}
}
.element-editor {
margin-bottom: 8px;
}
.blur {
filter: blur(2px) grayscale(100%);
}
.element-preview {
position: relative;
height: max-content;
background: var(--primary-background-color);
padding: 4px;
border-radius: var(--ha-border-radius-sm);
}
.section-container {
position: relative;
}
.section-container.has-background {
padding: var(--ha-space-2);
border-radius: var(
--ha-section-border-radius,
var(--ha-border-radius-xl)
);
}
.section-container hui-section {
display: block;
}
ha-tab-group {
margin-bottom: 16px;
}
ha-tab-group-tab {
flex: 1;
@@ -543,6 +844,14 @@ export class HuiDialogEditSection
width: 100%;
justify-content: center;
}
.gui-mode-button {
margin-right: auto;
margin-inline-end: auto;
margin-inline-start: initial;
}
.title-enlargeable {
display: block;
}
`,
];
}
@@ -19,8 +19,71 @@ import {
import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view";
import { DEFAULT_MAX_COLUMNS } from "../../views/const";
const COLUMN_SPAN_NAMES = ["single", "double", "triple"] as const;
const MAX_NAMED_COLUMN_SPAN = COLUMN_SPAN_NAMES.length + 1;
const computeColumnSpanLabel = (
span: number,
maxColumns: number,
localize: LocalizeFunc
) => {
if (span >= maxColumns) {
return localize(
"ui.panel.lovelace.editor.edit_section.settings.column_span_options.full"
);
}
return localize(
`ui.panel.lovelace.editor.edit_section.settings.column_span_options.${COLUMN_SPAN_NAMES[span - 1]}`
);
};
const computeColumnSpanSchemas = (
maxColumns: number,
localize: LocalizeFunc
) => {
if (maxColumns <= 1) {
return [];
}
if (maxColumns > MAX_NAMED_COLUMN_SPAN) {
return [
{
name: "column_span",
selector: {
number: { min: 1, max: maxColumns, slider_ticks: true },
},
},
] as const;
}
const options = Array.from({ length: maxColumns }, (_, i) => {
const span = i + 1;
return {
value: String(span),
label: computeColumnSpanLabel(span, maxColumns, localize),
image: {
src: `/static/images/form/section_column_span_${span}_of_${maxColumns}.svg`,
src_dark: `/static/images/form/section_column_span_${span}_of_${maxColumns}_dark.svg`,
flip_rtl: true,
},
};
});
return [
{
name: "column_span",
selector: {
select: {
mode: "box",
box_max_columns:
maxColumns === MAX_NAMED_COLUMN_SPAN ? 2 : maxColumns,
options,
},
},
},
] as const;
};
interface SettingsData {
column_span?: number;
column_span?: string | number;
background_enabled?: boolean;
background_color?: string;
background_opacity?: number;
@@ -36,20 +99,30 @@ export class HuiDialogEditSection extends LitElement {
@property({ attribute: false }) public viewConfig!: LovelaceViewConfig;
private get _maxColumns() {
return this.viewConfig.max_columns || DEFAULT_MAX_COLUMNS;
}
private get _displayedColumnSpan() {
return String(
Math.min(
this.config.column_span ?? DEFAULT_SECTION_COLUMN_SPAN,
this._maxColumns
)
);
}
private get _columnSpanFormValue() {
if (this._maxColumns > MAX_NAMED_COLUMN_SPAN) {
return Number(this._displayedColumnSpan);
}
return this._displayedColumnSpan;
}
private _schema = memoizeOne(
(maxColumns: number, localize: LocalizeFunc) =>
(localize: LocalizeFunc, maxColumns: number) =>
[
{
name: "column_span",
default: DEFAULT_SECTION_COLUMN_SPAN,
selector: {
number: {
min: 1,
max: maxColumns,
slider_ticks: true,
},
},
},
...computeColumnSpanSchemas(maxColumns, localize),
{
name: "background_enabled",
selector: { boolean: {} },
@@ -109,17 +182,14 @@ export class HuiDialogEditSection extends LitElement {
const background = resolveSectionBackground(this.config.background);
const data: SettingsData = {
column_span: this.config.column_span,
column_span: this._columnSpanFormValue,
background_enabled: backgroundEnabled,
background_color: background?.color,
background_opacity: background?.opacity,
theme: this.config.theme,
};
const schema = this._schema(
this.viewConfig.max_columns || DEFAULT_MAX_COLUMNS,
this._localize
);
const schema = this._schema(this._localize, this._maxColumns);
return html`
<ha-form
@@ -154,10 +224,15 @@ export class HuiDialogEditSection extends LitElement {
...this.config,
};
if (newData.column_span) {
newConfig.column_span = newData.column_span;
} else {
if (newData.column_span === undefined) {
delete newConfig.column_span;
} else if (String(newData.column_span) !== this._displayedColumnSpan) {
const columnSpan = Number(newData.column_span);
if (columnSpan === DEFAULT_SECTION_COLUMN_SPAN) {
delete newConfig.column_span;
} else {
newConfig.column_span = columnSpan;
}
}
if (newData.background_enabled) {
+5 -1
View File
@@ -279,7 +279,8 @@ export class HuiSection extends ConditionalListenerMixin<LovelaceSectionConfig>(
}
private async _initializeConfig() {
let sectionConfig = { ...this.config };
const config = this.config;
let sectionConfig = { ...config };
let isStrategy = false;
if (isStrategySection(sectionConfig)) {
@@ -288,6 +289,9 @@ export class HuiSection extends ConditionalListenerMixin<LovelaceSectionConfig>(
sectionConfig,
this.hass!
);
if (config !== this.config) {
return;
}
}
sectionConfig = {
+17 -2
View File
@@ -10164,8 +10164,17 @@
"error_section": {
"title": "Error"
},
"section_strategy": {
"common-controls": {
"name": "Common controls"
}
},
"edit_section": {
"header": "Edit section",
"header": "Section configuration",
"typed_header": "{type} section configuration",
"preview": "Preview",
"preview_desktop": "Desktop preview",
"preview_mobile": "Mobile preview",
"tab_strategy": "Configuration",
"tab_visibility": "[%key:ui::panel::lovelace::editor::edit_view::tab_visibility%]",
"tab_appearance": "Appearance",
@@ -10173,7 +10182,13 @@
"edit_yaml": "[%key:ui::panel::lovelace::editor::edit_view::edit_yaml%]",
"settings": {
"column_span": "Width",
"column_span_helper": "Larger sections will be made smaller to fit the display. (for example, on mobile devices)",
"column_span_helper": "Smaller screens may show it narrower, and phones stack sections one under the other.",
"column_span_options": {
"single": "Single",
"double": "Double",
"triple": "Triple",
"full": "Full width"
},
"background": "Background options",
"background_enabled": "Background",
"background_enabled_helper": "Display a colored background behind the section",