From 810e39379dec53dc759407313536f4032700c8b9 Mon Sep 17 00:00:00 2001 From: 4gray Date: Thu, 1 Oct 2026 06:54:09 +0200 Subject: [PATCH] fix(epg): keep N from scrolling back to an off-screen focused programme The guide revealed the keyboard focus after every handled key, so N's smooth jump to now was cancelled by a reveal scroll whenever a programme cell was still focused outside the visible lane. Only keys that move the focus reveal it now. Co-Authored-By: Claude Opus 5.5 --- .../lib/epg-guide/epg-guide.component.spec.ts | 22 +++++++++++++++++++ .../src/lib/epg-guide/epg-guide.component.ts | 7 +++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/libs/ui/epg/src/lib/epg-guide/epg-guide.component.spec.ts b/libs/ui/epg/src/lib/epg-guide/epg-guide.component.spec.ts index 3eab4e372..e00e6e91c 100644 --- a/libs/ui/epg/src/lib/epg-guide/epg-guide.component.spec.ts +++ b/libs/ui/epg/src/lib/epg-guide/epg-guide.component.spec.ts @@ -447,6 +447,28 @@ describe('EpgGuideComponent', () => { ]); }); + it('jumps to now on N without scrolling back to a focus left off-screen', async () => { + await settle(fixture); + const viewportEl: HTMLElement = fixture.debugElement.query( + By.css('cdk-virtual-scroll-viewport') + ).nativeElement; + const scrollTo = jest.fn(); + viewportEl.scrollTo = scrollTo as unknown as HTMLElement['scrollTo']; + // jsdom reports a zero-sized lane, so this programme counts as hidden. + component.focusCell(0, 0); + await settle(fixture); + scrollTo.mockClear(); + + component.onKeydown(keydown('n')); + + // One combined smooth scroll; a reveal after it would cancel it. + expect(scrollTo).toHaveBeenCalledTimes(1); + expect(scrollTo).toHaveBeenCalledWith( + expect.objectContaining({ top: 0, behavior: 'smooth' }) + ); + expect(component.focus()).toEqual({ row: 0, block: 0 }); + }); + it('moves the roving focus to a clicked programme card', async () => { await settle(fixture); const card = fixture.debugElement.query( diff --git a/libs/ui/epg/src/lib/epg-guide/epg-guide.component.ts b/libs/ui/epg/src/lib/epg-guide/epg-guide.component.ts index d1913221b..bf69caa5f 100644 --- a/libs/ui/epg/src/lib/epg-guide/epg-guide.component.ts +++ b/libs/ui/epg/src/lib/epg-guide/epg-guide.component.ts @@ -257,11 +257,16 @@ export class EpgGuideComponent implements OnDestroy { */ @HostListener('document:keydown', ['$event']) onKeydown(event: KeyboardEvent): void { + const focusBefore = this.focus(); if (!this.keyboard.handle(event)) { return; } event.preventDefault(); - this.viewportController.revealFocus(this.focus()); + // Only a key that moved the focus scrolls to it: N scrolls to now on + // its own, and a reveal of a focus left off-screen would cancel it. + if (this.focus() !== focusBefore) { + this.viewportController.revealFocus(this.focus()); + } afterNextRender(() => this.viewportController.focusRovingTarget(), { injector: this.injector, });