Browse Source

Merge pull request #26013 from abpframework/maliming/fix-extensible-table-header-offset

Fix `abp-extensible-table` header misalignment after a list request
rel-10.6
sumeyye 2 days ago
committed by GitHub
parent
commit
7750001cc3
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 70
      npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.ts

70
npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.ts

@ -1,8 +1,10 @@
import { import {
afterNextRender,
AfterViewInit, AfterViewInit,
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,
computed, computed,
DestroyRef,
inject, inject,
Injector, Injector,
LOCALE_ID, LOCALE_ID,
@ -17,9 +19,10 @@ import {
contentChild, contentChild,
viewChild, viewChild,
} from '@angular/core'; } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { AsyncPipe, isPlatformBrowser, NgComponentOutlet, NgTemplateOutlet } from '@angular/common'; import { AsyncPipe, isPlatformBrowser, NgComponentOutlet, NgTemplateOutlet } from '@angular/common';
import { Observable, filter, map, Subject, debounceTime, distinctUntilChanged } from 'rxjs'; import { Observable, map, Subject, debounceTime, distinctUntilChanged } from 'rxjs';
import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap';
import { import {
@ -92,6 +95,7 @@ const DEFAULT_ACTIONS_COLUMN_WIDTH = 150;
}) })
export class ExtensibleTableComponent<R = any> implements AfterViewInit, OnDestroy { export class ExtensibleTableComponent<R = any> implements AfterViewInit, OnDestroy {
readonly #injector = inject(Injector); readonly #injector = inject(Injector);
readonly #destroyRef = inject(DestroyRef);
readonly getInjected = this.#injector.get.bind(this.#injector); readonly getInjected = this.#injector.get.bind(this.#injector);
protected readonly locale = inject(LOCALE_ID); protected readonly locale = inject(LOCALE_ID);
protected readonly config = inject(ConfigStateService); protected readonly config = inject(ConfigStateService);
@ -186,6 +190,10 @@ export class ExtensibleTableComponent<R = any> implements AfterViewInit, OnDestr
}, },
); );
private horizontalScrollOffset = 0;
private hasPendingHorizontalScrollOffset = false;
hasAtLeastOnePermittedAction: boolean; hasAtLeastOnePermittedAction: boolean;
readonly propList: EntityPropList<R>; readonly propList: EntityPropList<R>;
@ -232,6 +240,7 @@ export class ExtensibleTableComponent<R = any> implements AfterViewInit, OnDestr
} }
this._data.set(dataValue.map((record, index) => this.prepareRecord(record, index))); this._data.set(dataValue.map((record, index) => this.prepareRecord(record, index)));
this.restoreHorizontalScrollOffset();
}); });
} }
@ -363,13 +372,66 @@ export class ExtensibleTableComponent<R = any> implements AfterViewInit, OnDestr
ngAfterViewInit(): void { ngAfterViewInit(): void {
if (!this.infiniteScroll()) { if (!this.infiniteScroll()) {
this.list() this.list()
?.requestStatus$?.pipe(filter(status => status === 'loading')) ?.requestStatus$?.pipe(takeUntilDestroyed(this.#destroyRef))
.subscribe(() => { .subscribe(status => {
this._data.set([]); if (status === 'loading') {
this.rememberHorizontalScrollOffset();
this._data.set([]);
return;
}
// A failed request never reaches the data input, restore from here instead
if (status === 'error') {
this.restoreHorizontalScrollOffset();
}
}); });
} }
} }
private getBodyElement(): HTMLElement | null {
if (!this.isBrowser) {
return null;
}
return this.table()?.element?.querySelector('datatable-body') ?? null;
}
// The element carrying the column width is gone while loading without rows, so the browser
// resets the horizontal scroll position and the header keeps the offset it had before
private rememberHorizontalScrollOffset(): void {
const body = this.getBodyElement();
this.hasPendingHorizontalScrollOffset = true;
// Not scrollable while a previous request is still in flight, keep the offset taken back then
if (body && body.scrollWidth > body.clientWidth) {
this.horizontalScrollOffset = body.scrollLeft;
}
}
private restoreHorizontalScrollOffset(): void {
if (!this.hasPendingHorizontalScrollOffset) {
return;
}
this.hasPendingHorizontalScrollOffset = false;
afterNextRender(
() => {
const body = this.getBodyElement();
if (!body) {
return;
}
body.scrollLeft = this.horizontalScrollOffset;
// The header only follows a scroll event, scrolling to the same position does not raise one
body.dispatchEvent(new Event('scroll'));
},
{ injector: this.#injector },
);
}
ngOnDestroy(): void { ngOnDestroy(): void {
this.loadMoreSubscription.unsubscribe(); this.loadMoreSubscription.unsubscribe();
} }

Loading…
Cancel
Save