From 565af341dd412845d74b1eb1a21842042ff0c5a6 Mon Sep 17 00:00:00 2001 From: Joshua Cain Date: Fri, 24 Jul 2026 12:46:28 -0400 Subject: [PATCH] fix: allow other events to call preventDefault (#12089) --- .../idle-activity-tracker.spec.ts | 16 ++++++++++++++++ .../session-timeout/idle-activity-tracker.ts | 6 ++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/lib/core/src/lib/auth/session-timeout/idle-activity-tracker.spec.ts b/lib/core/src/lib/auth/session-timeout/idle-activity-tracker.spec.ts index 305777b022..dc5de0a7bb 100644 --- a/lib/core/src/lib/auth/session-timeout/idle-activity-tracker.spec.ts +++ b/lib/core/src/lib/auth/session-timeout/idle-activity-tracker.spec.ts @@ -83,6 +83,22 @@ describe('IdleActivityTracker', () => { expect(spy).toHaveBeenCalledTimes(1); }); + it('should allow a later listener to prevent default when an activity event fires', () => { + const preventDefault = jasmine.createSpy('preventDefault').and.callFake((event: Event) => event.preventDefault()); + tracker.start(); + doc.addEventListener('mousemove', preventDefault); + + try { + const event = new MouseEvent('mousemove', { cancelable: true }); + doc.dispatchEvent(event); + + expect(preventDefault).toHaveBeenCalledTimes(1); + expect(event.defaultPrevented).toBeTrue(); + } finally { + doc.removeEventListener('mousemove', preventDefault); + } + }); + it('is a no-op when stop() is called without a prior start()', () => { const spy = jasmine.createSpy('activity'); tracker.activity$.subscribe(spy); diff --git a/lib/core/src/lib/auth/session-timeout/idle-activity-tracker.ts b/lib/core/src/lib/auth/session-timeout/idle-activity-tracker.ts index d8ef34e265..77d1149f61 100644 --- a/lib/core/src/lib/auth/session-timeout/idle-activity-tracker.ts +++ b/lib/core/src/lib/auth/session-timeout/idle-activity-tracker.ts @@ -25,6 +25,8 @@ export const ACTIVITY_EVENTS = ['click', 'keydown', 'mousedown', 'mousemove', 'p /** High-frequency activity events (e.g. mousemove, scroll) are throttled to avoid rescheduling the idle timer on every DOM event. */ export const ACTIVITY_THROTTLE_MS = 1000; +const ACTIVITY_LISTENER_OPTIONS: AddEventListenerOptions = { capture: true, passive: true }; + @Injectable() export class IdleActivityTracker implements OnDestroy { private readonly document = inject(DOCUMENT); @@ -44,7 +46,7 @@ export class IdleActivityTracker implements OnDestroy { } this.ngZone.runOutsideAngular(() => { - ACTIVITY_EVENTS.forEach((eventName) => this.document.addEventListener(eventName, this.handleActivity, { passive: true })); + ACTIVITY_EVENTS.forEach((eventName) => this.document.addEventListener(eventName, this.handleActivity, ACTIVITY_LISTENER_OPTIONS)); this.document.addEventListener('visibilitychange', this.handleVisibilityChange); }); this.isRegistered = true; @@ -55,7 +57,7 @@ export class IdleActivityTracker implements OnDestroy { return; } - ACTIVITY_EVENTS.forEach((eventName) => this.document.removeEventListener(eventName, this.handleActivity)); + ACTIVITY_EVENTS.forEach((eventName) => this.document.removeEventListener(eventName, this.handleActivity, ACTIVITY_LISTENER_OPTIONS)); this.document.removeEventListener('visibilitychange', this.handleVisibilityChange); this.isRegistered = false; }