From 8bcccb5792b5b665cd7133cfdab9d9148f96f625 Mon Sep 17 00:00:00 2001 From: Johan Henkens Date: Wed, 11 Mar 2026 06:08:10 -0700 Subject: [PATCH] Allow trace graph to scroll independently of the step-details tab (#29906) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Allow trace graph to scroll independently * Apply independent trace graph scrolling to script trace Port the independent column scrolling and sticky nav overlay from ha-automation-trace to ha-script-trace, and add scroll-to-active-node behavior to hat-script-graph. Co-Authored-By: Claude Sonnet 4.6 * Re-encapsulate nav buttons inside hat-script-graph Move the up/down navigation buttons back into hat-script-graph where they belong, restoring _previousTrackedNode/_nextTrackedNode as private. Wrap the graph content in a div.graph-scroll so it scrolls independently while the button column stays fixed — the same flex-row layout the parent was using, now self-contained inside the shadow DOM. This removes the duplicated nav overlay markup and disabled-state logic from ha-automation-trace and ha-script-trace, and removes the unnecessary public surface on HatScriptGraph. Co-Authored-By: Claude Sonnet 4.6 * Make not-scrollable more specific. Co-authored-by: Petar Petrov --------- Co-authored-by: Claude Sonnet 4.6 Co-authored-by: Petar Petrov --- src/components/trace/hat-script-graph.ts | 67 +++++++++++++------ src/layouts/hass-subpage.ts | 17 ++++- .../config/automation/ha-automation-trace.ts | 36 ++++++++-- src/panels/config/script/ha-script-trace.ts | 33 ++++++++- 4 files changed, 121 insertions(+), 32 deletions(-) diff --git a/src/components/trace/hat-script-graph.ts b/src/components/trace/hat-script-graph.ts index 41d0ae51e9..0247bbc8f0 100644 --- a/src/components/trace/hat-script-graph.ts +++ b/src/components/trace/hat-script-graph.ts @@ -597,28 +597,30 @@ export class HatScriptGraph extends LitElement { : undefined; try { return html` -
- ${triggerNodes - ? html` - ${triggerNodes} - ` - : ""} - ${conditionKey in this.trace.config - ? html`${ensureArray(this.trace.config[conditionKey])?.map( - (condition, i) => this._renderCondition(condition, i) - )}` - : ""} - ${actionKey in this.trace.config - ? html`${ensureArray(this.trace.config[actionKey]).map( - (action, i) => this._renderActionNode(action, `action/${i}`) - )}` - : ""} - ${"sequence" in this.trace.config - ? html`${ensureArray(this.trace.config.sequence).map( - (action, i) => - this._renderActionNode(action, `sequence/${i}`, i === 0) - )}` - : ""} +
+
+ ${triggerNodes + ? html` + ${triggerNodes} + ` + : ""} + ${conditionKey in this.trace.config + ? html`${ensureArray(this.trace.config[conditionKey])?.map( + (condition, i) => this._renderCondition(condition, i) + )}` + : ""} + ${actionKey in this.trace.config + ? html`${ensureArray(this.trace.config[actionKey]).map( + (action, i) => this._renderActionNode(action, `action/${i}`) + )}` + : ""} + ${"sequence" in this.trace.config + ? html`${ensureArray(this.trace.config.sequence).map( + (action, i) => + this._renderActionNode(action, `sequence/${i}`, i === 0) + )}` + : ""} +
) { super.updated(changedProps); + if (!changedProps.has("trace") && !changedProps.has("selected")) { + return; + } + + // Scroll to active node when selection changes + if (changedProps.has("selected")) { + const activeNode = this.renderRoot.querySelector( + "hat-graph-node[active], hat-graph-branch[active]" + ) as HTMLElement; + if (activeNode) { + activeNode.scrollIntoView({ behavior: "smooth", block: "nearest" }); + } + } + if (!changedProps.has("trace")) { return; } @@ -717,6 +733,8 @@ export class HatScriptGraph extends LitElement { return css` :host { display: flex; + flex-direction: row; + overflow: hidden; --stroke-clr: var(--stroke-color, var(--secondary-text-color)); --active-clr: var(--active-color, var(--primary-color)); --track-clr: var(--track-color, var(--accent-color)); @@ -734,6 +752,11 @@ export class HatScriptGraph extends LitElement { --hat-graph-node-size: ${NODE_SIZE}px; --hat-graph-branch-height: ${BRANCH_HEIGHT}px; } + .graph-scroll { + flex: 1; + overflow: auto; + min-width: 0; + } .graph-container { display: flex; flex-direction: column; diff --git a/src/layouts/hass-subpage.ts b/src/layouts/hass-subpage.ts index 94ef7fd24f..1b8fd76dcd 100644 --- a/src/layouts/hass-subpage.ts +++ b/src/layouts/hass-subpage.ts @@ -23,6 +23,8 @@ class HassSubpage extends LitElement { @property({ type: Boolean, reflect: true }) public narrow = false; + @property({ type: Boolean }) public scrollable = true; + // @ts-ignore @restoreScroll(".content") private _savedScrollPos?: number; @@ -57,7 +59,14 @@ class HassSubpage extends LitElement {
-
+
@@ -163,6 +172,12 @@ class HassSubpage extends LitElement { overflow: auto; -webkit-overflow-scrolling: touch; } + .content.not-scrollable { + overflow: hidden; + display: flex; + flex-direction: column; + } + :host([narrow]) .content { width: calc( 100% - var(--safe-area-inset-left, 0px) - var( diff --git a/src/panels/config/automation/ha-automation-trace.ts b/src/panels/config/automation/ha-automation-trace.ts index 697272dbfd..665def9d6d 100644 --- a/src/panels/config/automation/ha-automation-trace.ts +++ b/src/panels/config/automation/ha-automation-trace.ts @@ -101,7 +101,12 @@ export class HaAutomationTrace extends LitElement { return html` ${devButtons} - + ${!this.narrow && stateObj?.attributes.id ? html` + ${!this.narrow && this.scriptId ? html`