diff --git a/src/common/decorators/restore-scroll.ts b/src/common/decorators/restore-scroll.ts new file mode 100644 index 0000000000..29535d07e0 --- /dev/null +++ b/src/common/decorators/restore-scroll.ts @@ -0,0 +1,33 @@ +import type { LitElement } from "lit-element"; +import type { ClassElement } from "../../types"; + +export const restoreScroll = (selector: string): any => { + return (element: ClassElement) => ({ + kind: "method", + placement: "prototype", + key: element.key, + descriptor: { + set(this: LitElement, value: number) { + this[`__${String(element.key)}`] = value; + }, + get(this: LitElement) { + return this[`__${String(element.key)}`]; + }, + enumerable: true, + configurable: true, + }, + finisher(cls: typeof LitElement) { + const connectedCallback = cls.prototype.connectedCallback; + cls.prototype.connectedCallback = function () { + connectedCallback.call(this); + if (this[element.key]) { + const target = this.renderRoot.querySelector(selector); + if (!target) { + return; + } + target.scrollTop = this[element.key]; + } + }; + }, + }); +}; diff --git a/src/components/data-table/ha-data-table.ts b/src/components/data-table/ha-data-table.ts index c1150236ca..a3194ad23d 100644 --- a/src/components/data-table/ha-data-table.ts +++ b/src/components/data-table/ha-data-table.ts @@ -9,6 +9,8 @@ import { PropertyValues, query, TemplateResult, + eventOptions, + internalProperty, } from "lit-element"; import { classMap } from "lit-html/directives/class-map"; import { ifDefined } from "lit-html/directives/if-defined"; @@ -23,6 +25,7 @@ import type { HaCheckbox } from "../ha-checkbox"; import "../ha-icon"; import { filterData, sortData } from "./sort-filter"; import memoizeOne from "memoize-one"; +import { restoreScroll } from "../../common/decorators/restore-scroll"; declare global { // for fire event @@ -96,15 +99,15 @@ export class HaDataTable extends LitElement { @property({ type: String }) public filter = ""; - @property({ type: Boolean }) private _filterable = false; + @internalProperty() private _filterable = false; - @property({ type: String }) private _filter = ""; + @internalProperty() private _filter = ""; - @property({ type: String }) private _sortColumn?: string; + @internalProperty() private _sortColumn?: string; - @property({ type: String }) private _sortDirection: SortingDirection = null; + @internalProperty() private _sortDirection: SortingDirection = null; - @property({ type: Array }) private _filteredData: DataTableRowData[] = []; + @internalProperty() private _filteredData: DataTableRowData[] = []; @query("slot[name='header']") private _header!: HTMLSlotElement; @@ -118,6 +121,9 @@ export class HaDataTable extends LitElement { private curRequest = 0; + // @ts-ignore + @restoreScroll(".scroller") private _savedScrollPos?: number; + private _debounceSearch = debounce( (value: string) => { this._filter = value; @@ -286,7 +292,10 @@ export class HaDataTable extends LitElement { ` : html` -
+
${scroll({ items: !this.hasFab ? this._filteredData @@ -499,6 +508,11 @@ export class HaDataTable extends LitElement { this._table.style.height = `calc(100% - ${this._header.clientHeight}px)`; } + @eventOptions({ passive: true }) + private _saveScrollPos(e: Event) { + this._savedScrollPos = (e.target as HTMLDivElement).scrollTop; + } + static get styles(): CSSResult { return css` /* default mdc styles, colors changed, without checkbox styles */ diff --git a/src/layouts/hass-subpage.ts b/src/layouts/hass-subpage.ts index cafb96bfd1..4eb7a3153e 100644 --- a/src/layouts/hass-subpage.ts +++ b/src/layouts/hass-subpage.ts @@ -11,6 +11,7 @@ import { import { classMap } from "lit-html/directives/class-map"; import "../components/ha-menu-button"; import "../components/ha-icon-button-arrow-prev"; +import { restoreScroll } from "../common/decorators/restore-scroll"; @customElement("hass-subpage") class HassSubpage extends LitElement { @@ -23,16 +24,8 @@ class HassSubpage extends LitElement { @property({ type: Boolean }) public hassio = false; - @property() private _savedScrollPos?: number; - - public connectedCallback() { - super.connectedCallback(); - if (this._savedScrollPos) { - (this.shadowRoot!.querySelector( - ".content" - ) as HTMLDivElement).scrollTop = this._savedScrollPos; - } - } + // @ts-ignore + @restoreScroll(".content") private _savedScrollPos?: number; protected render(): TemplateResult { return html` diff --git a/src/layouts/hass-tabs-subpage.ts b/src/layouts/hass-tabs-subpage.ts index 3522fe27ad..d089792a7c 100644 --- a/src/layouts/hass-tabs-subpage.ts +++ b/src/layouts/hass-tabs-subpage.ts @@ -20,6 +20,7 @@ import { HomeAssistant, Route } from "../types"; import "../components/ha-svg-icon"; import "../components/ha-icon"; import "../components/ha-tab"; +import { restoreScroll } from "../common/decorators/restore-scroll"; export interface PageNavigation { path: string; @@ -53,16 +54,8 @@ class HassTabsSubpage extends LitElement { @property() private _activeTab?: PageNavigation; - @property() private _savedScrollPos?: number; - - public connectedCallback() { - super.connectedCallback(); - if (this._savedScrollPos) { - (this.shadowRoot!.querySelector( - ".content" - ) as HTMLDivElement).scrollTop = this._savedScrollPos; - } - } + // @ts-ignore + @restoreScroll(".content") private _savedScrollPos?: number; private _getTabs = memoizeOne( ( diff --git a/src/types.ts b/src/types.ts index 67b908761b..aff205c13b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -47,6 +47,16 @@ declare global { export type Constructor = new (...args: any[]) => T; +export interface ClassElement { + kind: "field" | "method"; + key: PropertyKey; + placement: "static" | "prototype" | "own"; + initializer?: Function; + extras?: ClassElement[]; + finisher?: (cls: Constructor) => undefined | Constructor; + descriptor?: PropertyDescriptor; +} + export interface WebhookError { code: number; message: string;