Use fixed layout for automation sidebar to have scrollbar on the side (#26751)

Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com>
This commit is contained in:
Paul Bottein
2025-08-29 14:19:13 +02:00
committed by GitHub
parent 0269540ee9
commit 1fd7c84583
7 changed files with 92 additions and 162 deletions

View File

@@ -1,7 +1,7 @@
import { mdiDrag, mdiPlus } from "@mdi/js"; import { mdiDrag, mdiPlus } from "@mdi/js";
import deepClone from "deep-clone-simple"; import deepClone from "deep-clone-simple";
import type { PropertyValues } from "lit"; import type { PropertyValues } from "lit";
import { LitElement, css, html, nothing } from "lit"; import { LitElement, html, nothing } from "lit";
import { customElement, property, queryAll, state } from "lit/decorators"; import { customElement, property, queryAll, state } from "lit/decorators";
import { repeat } from "lit/directives/repeat"; import { repeat } from "lit/directives/repeat";
import { storage } from "../../../../common/decorators/storage"; import { storage } from "../../../../common/decorators/storage";
@@ -409,14 +409,7 @@ export default class HaAutomationAction extends LitElement {
this._rowSortSelected = undefined; this._rowSortSelected = undefined;
} }
static styles = [ static styles = automationRowsStyles;
automationRowsStyles,
css`
:host([root]) .rows {
padding-right: 8px;
}
`,
];
} }
declare global { declare global {

View File

@@ -1,7 +1,7 @@
import { mdiDrag, mdiPlus } from "@mdi/js"; import { mdiDrag, mdiPlus } from "@mdi/js";
import deepClone from "deep-clone-simple"; import deepClone from "deep-clone-simple";
import type { PropertyValues } from "lit"; import type { PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit"; import { html, LitElement, nothing } from "lit";
import { customElement, property, queryAll, state } from "lit/decorators"; import { customElement, property, queryAll, state } from "lit/decorators";
import { repeat } from "lit/directives/repeat"; import { repeat } from "lit/directives/repeat";
import { storage } from "../../../../common/decorators/storage"; import { storage } from "../../../../common/decorators/storage";
@@ -428,14 +428,7 @@ export default class HaAutomationCondition extends LitElement {
this._rowSortSelected = undefined; this._rowSortSelected = undefined;
} }
static styles = [ static styles = automationRowsStyles;
automationRowsStyles,
css`
:host([root]) .rows {
padding-right: 8px;
}
`,
];
} }
declare global { declare global {

View File

@@ -1181,27 +1181,6 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
display: block; display: block;
} }
:not(.yaml-mode) > .alert-wrapper {
position: sticky;
top: -24px;
margin-top: -24px;
z-index: 1;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
pointer-events: none;
}
:not(.yaml-mode) > .alert-wrapper ha-alert {
background-color: var(--card-background-color);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
border-radius: var(--ha-border-radius-sm);
margin-bottom: 0;
pointer-events: auto;
}
manual-automation-editor { manual-automation-editor {
max-width: 1540px; max-width: 1540px;
padding: 0 12px; padding: 0 12px;

View File

@@ -23,7 +23,6 @@ import {
extractSearchParam, extractSearchParam,
removeSearchParam, removeSearchParam,
} from "../../../common/url/search-params"; } from "../../../common/url/search-params";
import { computeRTL } from "../../../common/util/compute_rtl";
import "../../../components/ha-button"; import "../../../components/ha-button";
import "../../../components/ha-fab"; import "../../../components/ha-fab";
import "../../../components/ha-icon-button"; import "../../../components/ha-icon-button";
@@ -93,9 +92,6 @@ export class HaManualAutomationEditor extends LitElement {
@state() private _sidebarConfig?: SidebarConfig; @state() private _sidebarConfig?: SidebarConfig;
@query(".content")
private _contentElement?: HTMLDivElement;
@query("ha-automation-sidebar") private _sidebarElement?: HaAutomationSidebar; @query("ha-automation-sidebar") private _sidebarElement?: HaAutomationSidebar;
private _previousConfig?: ManualAutomationConfig; private _previousConfig?: ManualAutomationConfig;
@@ -260,8 +256,7 @@ export class HaManualAutomationEditor extends LitElement {
return html` return html`
<div <div
class=${classMap({ class=${classMap({
"split-view": true, "has-sidebar": this._sidebarConfig && !this.narrow,
"sidebar-hidden": !this._sidebarConfig,
})} })}
> >
<div class="content-wrapper"> <div class="content-wrapper">
@@ -269,31 +264,31 @@ export class HaManualAutomationEditor extends LitElement {
<slot name="alerts"></slot> <slot name="alerts"></slot>
${this._renderContent()} ${this._renderContent()}
</div> </div>
<ha-fab <div class="fab-positioner">
slot="fab" <ha-fab
class=${this.dirty ? "dirty" : ""} slot="fab"
.label=${this.hass.localize("ui.common.save")} class=${this.dirty ? "dirty" : ""}
.disabled=${this.saving} .label=${this.hass.localize("ui.common.save")}
extended .disabled=${this.saving}
@click=${this._saveAutomation} extended
> @click=${this._saveAutomation}
<ha-svg-icon slot="icon" .path=${mdiContentSave}></ha-svg-icon> >
</ha-fab> <ha-svg-icon slot="icon" .path=${mdiContentSave}></ha-svg-icon>
</ha-fab>
</div>
</div>
<div class="sidebar-positioner">
<ha-automation-sidebar
tabindex="-1"
class=${classMap({ hidden: !this._sidebarConfig })}
.isWide=${this.isWide}
.hass=${this.hass}
.narrow=${this.narrow}
.config=${this._sidebarConfig}
@value-changed=${this._sidebarConfigChanged}
.disabled=${this.disabled}
></ha-automation-sidebar>
</div> </div>
<ha-automation-sidebar
tabindex="-1"
class=${classMap({
sidebar: true,
overlay: !this.isWide && !this.narrow,
rtl: computeRTL(this.hass),
})}
.isWide=${this.isWide}
.hass=${this.hass}
.narrow=${this.narrow}
.config=${this._sidebarConfig}
@value-changed=${this._sidebarConfigChanged}
.disabled=${this.disabled}
></ha-automation-sidebar>
</div> </div>
`; `;
} }
@@ -344,8 +339,6 @@ export class HaManualAutomationEditor extends LitElement {
private _handleCloseSidebar() { private _handleCloseSidebar() {
this._sidebarConfig = undefined; this._sidebarConfig = undefined;
// fix content shift when bottom rows are scrolled into view
this._contentElement?.scrollIntoView();
} }
private _triggerChanged(ev: CustomEvent): void { private _triggerChanged(ev: CustomEvent): void {

View File

@@ -69,7 +69,7 @@ export const indentStyle = css`
.selector-row, .selector-row,
:host([indent]) ha-form { :host([indent]) ha-form {
margin-left: 12px; margin-left: 12px;
padding: 12px 20px 16px 16px; padding: 12px 0 16px 16px;
border-left: 2px solid var(--ha-color-border-neutral-quiet); border-left: 2px solid var(--ha-color-border-neutral-quiet);
border-bottom: 2px solid var(--ha-color-border-neutral-quiet); border-bottom: 2px solid var(--ha-color-border-neutral-quiet);
border-radius: 0; border-radius: 0;
@@ -108,77 +108,59 @@ export const saveFabStyles = css`
export const manualEditorStyles = css` export const manualEditorStyles = css`
:host { :host {
display: block; display: block;
--sidebar-width: 0;
--sidebar-gap: 0;
} }
.split-view { .has-sidebar {
--sidebar-width: min(35vw, 500px);
--sidebar-gap: 16px;
}
.fab-positioner {
display: flex; display: flex;
flex-direction: row; justify-content: flex-end;
height: 100%;
position: relative;
gap: 16px;
} }
.split-view.sidebar-hidden { .fab-positioner ha-fab {
gap: 0; position: fixed;
right: unset;
left: unset;
bottom: calc(-80px - var(--safe-area-inset-bottom));
transition: bottom 0.3s;
}
.fab-positioner ha-fab.dirty {
bottom: 16px;
} }
.content-wrapper { .content-wrapper {
position: relative; padding-right: calc(var(--sidebar-width) + var(--sidebar-gap));
flex: 6; padding-inline-end: calc(var(--sidebar-width) + var(--sidebar-gap));
padding-inline-start: 0;
} }
.content { .content {
padding: 32px 16px 64px 0; padding-top: 24px;
height: calc(100vh - 153px); padding-bottom: 72px;
height: calc(100dvh - 153px);
overflow-y: auto;
overflow-x: hidden;
} }
.sidebar { ha-automation-sidebar {
padding: 12px 0;
flex: 4;
height: calc(100vh - 81px);
height: calc(100dvh - 81px);
width: 40%;
}
.split-view.sidebar-hidden .sidebar {
border-color: transparent;
border-width: 0;
overflow: hidden;
flex: 0;
visibility: hidden;
}
.sidebar.overlay {
position: fixed; position: fixed;
bottom: 8px; top: calc(var(--header-height) + 16px);
right: 8px; height: calc(-81px + 100dvh);
height: calc(100% - 70px); width: var(--sidebar-width);
padding: 0; display: block;
z-index: 5;
box-shadow: -8px 0 16px rgba(0, 0, 0, 0.2);
} }
.sidebar.overlay.rtl { ha-automation-sidebar.hidden {
right: unset; display: none;
left: 8px;
} }
@media all and (max-width: 870px) { .sidebar-positioner {
.split-view { display: flex;
gap: 0; justify-content: flex-end;
}
.sidebar {
height: 0;
width: 0;
flex: 0;
}
} }
.split-view.sidebar-hidden .sidebar.overlay {
width: 0;
}
.description { .description {
margin: 0; margin: 0;
} }
@@ -189,9 +171,6 @@ export const manualEditorStyles = css`
export const automationRowsStyles = css` export const automationRowsStyles = css`
.rows { .rows {
padding: 16px 0 16px 16px;
margin: -16px;
margin-right: -20px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 16px; gap: 16px;

View File

@@ -1,7 +1,7 @@
import { mdiDrag, mdiPlus } from "@mdi/js"; import { mdiDrag, mdiPlus } from "@mdi/js";
import deepClone from "deep-clone-simple"; import deepClone from "deep-clone-simple";
import type { PropertyValues } from "lit"; import type { PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit"; import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { repeat } from "lit/directives/repeat"; import { repeat } from "lit/directives/repeat";
import { storage } from "../../../../common/decorators/storage"; import { storage } from "../../../../common/decorators/storage";
@@ -368,14 +368,7 @@ export default class HaAutomationTrigger extends LitElement {
this._rowSortSelected = undefined; this._rowSortSelected = undefined;
} }
static styles = [ static styles = automationRowsStyles;
automationRowsStyles,
css`
:host([root]) .rows {
padding-right: 8px;
}
`,
];
} }
declare global { declare global {

View File

@@ -22,7 +22,6 @@ import {
extractSearchParam, extractSearchParam,
removeSearchParam, removeSearchParam,
} from "../../../common/url/search-params"; } from "../../../common/url/search-params";
import { computeRTL } from "../../../common/util/compute_rtl";
import "../../../components/ha-icon-button"; import "../../../components/ha-icon-button";
import "../../../components/ha-markdown"; import "../../../components/ha-markdown";
import type { SidebarConfig } from "../../../data/automation"; import type { SidebarConfig } from "../../../data/automation";
@@ -199,8 +198,7 @@ export class HaManualScriptEditor extends LitElement {
return html` return html`
<div <div
class=${classMap({ class=${classMap({
"split-view": true, "has-sidebar": this._sidebarConfig && !this.narrow,
"sidebar-hidden": !this._sidebarConfig,
})} })}
> >
<div class="content-wrapper"> <div class="content-wrapper">
@@ -208,31 +206,33 @@ export class HaManualScriptEditor extends LitElement {
<slot name="alerts"></slot> <slot name="alerts"></slot>
${this._renderContent()} ${this._renderContent()}
</div> </div>
<ha-fab <div class="fab-positioner">
slot="fab" <div class="fab-positioner">
class=${this.dirty ? "dirty" : ""} <ha-fab
.label=${this.hass.localize("ui.common.save")} slot="fab"
.disabled=${this.saving} class=${this.dirty ? "dirty" : ""}
extended .label=${this.hass.localize("ui.common.save")}
@click=${this._saveScript} .disabled=${this.saving}
> extended
<ha-svg-icon slot="icon" .path=${mdiContentSave}></ha-svg-icon> @click=${this._saveScript}
</ha-fab> >
<ha-svg-icon slot="icon" .path=${mdiContentSave}></ha-svg-icon>
</ha-fab>
</div>
</div>
</div>
<div class="sidebar-positioner">
<ha-automation-sidebar
tabindex="-1"
class=${classMap({ hidden: !this._sidebarConfig })}
.narrow=${this.narrow}
.isWide=${this.isWide}
.hass=${this.hass}
.config=${this._sidebarConfig}
@value-changed=${this._sidebarConfigChanged}
.disabled=${this.disabled}
></ha-automation-sidebar>
</div> </div>
<ha-automation-sidebar
tabindex="-1"
class=${classMap({
sidebar: true,
overlay: !this.isWide,
rtl: computeRTL(this.hass),
})}
.narrow=${this.narrow}
.isWide=${this.isWide}
.hass=${this.hass}
.config=${this._sidebarConfig}
@value-changed=${this._sidebarConfigChanged}
.disabled=${this.disabled}
></ha-automation-sidebar>
</div> </div>
`; `;
} }