Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3697,7 +3697,12 @@ export abstract class IgxGridBaseDirective implements GridType,
this.throttleTime$.pipe(
take(1),
switchMap(time => timer(time, this.throttleScheduler))
)
),
// `trailing: true` ensures the final settle position of a fast momentum
// scroll is processed; otherwise the last scroll events are dropped and the
// rows stay frozen at an intermediate startIndex while the scrollbar is at top.
// `leading: true` keeps the immediate response on scroll start.
{ leading: true, trailing: true }
Comment thread
Zneeky marked this conversation as resolved.
),
destructor
)
Expand Down
52 changes: 52 additions & 0 deletions projects/igniteui-angular/grids/grid/src/grid.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3310,6 +3310,49 @@ describe('IgxGrid Component Tests #grid', () => {
expect(() => fix.detectChanges()).not.toThrow();
});
});

describe('IgxGrid - scroll throttle trailing edge', () => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [NoopAnimationsModule, IgxGridScrollThrottleComponent],
providers: [{ provide: SCROLL_THROTTLE_TIME_MULTIPLIER, useValue: 0 }]
}).compileComponents();
}));

it('should settle at the top row when a fast momentum scroll ends at scrollTop 0 (throttle trailing edge)', async () => {
const fix = TestBed.createComponent(IgxGridScrollThrottleComponent);
fix.detectChanges();
await wait(50);
fix.detectChanges();
const grid = fix.componentInstance.grid;
const virtDir = grid.verticalScrollContainer;
const scrollEl = virtDir.getScroll();
const maxScroll = scrollEl.scrollHeight - scrollEl.clientHeight;

// Move away from the top so the first rows are virtualized out of view.
grid.scrollNotify.next({ target: { scrollTop: maxScroll } });
await wait(50);
fix.detectChanges();
expect(virtDir.state.startIndex).toBeGreaterThan(0);

// Simulate a fast momentum/inertia scroll back to the top: an intermediate
// position lands on the throttle's leading edge and the scrollTop = 0 settle
// arrives within the same throttle window - timer(0, animationFrameScheduler)
// still defers the window close to the next frame - so it can only be delivered
// on the trailing edge. Feeding scrollNotify directly (instead of setting
// scrollTop) avoids the browser's async native scroll events, which all read the
// final scrollTop of 0, from masking a dropped-trailing regression.
grid.scrollNotify.next({ target: { scrollTop: Math.round(maxScroll / 2) } });
grid.scrollNotify.next({ target: { scrollTop: 0 } });
await wait(50);
fix.detectChanges();

// Without the trailing edge the settle event is dropped and the grid stays
// frozen at an intermediate startIndex while the scrollbar sits at the top.
expect(virtDir.state.startIndex).toBe(0);
expect(grid.gridAPI.get_row_by_index(0)).toBeDefined();
});
});
});

@Component({
Expand Down Expand Up @@ -4051,3 +4094,12 @@ export class IgxGridPerformanceComponent implements AfterViewInit, OnInit {
export class IgxGridNoDataComponent {
@ViewChild(IgxGridComponent, { static: true }) public grid: IgxGridComponent;
}

@Component({
template: `<igx-grid #grid [data]="data" height="300px" width="600px" [autoGenerate]="true"></igx-grid>`,
imports: [IgxGridComponent]
})
class IgxGridScrollThrottleComponent {
@ViewChild(IgxGridComponent, { static: true }) public grid: IgxGridComponent;
public data = Array.from({ length: 200 }, (_row, index) => ({ ID: index, Name: `Record ${index}`, Value: index * 10 }));
}
Loading