From 894fbd3e5feb3826338bb86a2269b4d275553e65 Mon Sep 17 00:00:00 2001 From: Jack Crymble Date: Wed, 11 Mar 2026 11:33:06 +0000 Subject: [PATCH 1/3] fix: pass passive false for touch events in listenOnTheHost --- .../src/lib/resize-handle.directive.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/projects/angular-resizable-element/src/lib/resize-handle.directive.ts b/projects/angular-resizable-element/src/lib/resize-handle.directive.ts index 3367a5b..17e7f19 100644 --- a/projects/angular-resizable-element/src/lib/resize-handle.directive.ts +++ b/projects/angular-resizable-element/src/lib/resize-handle.directive.ts @@ -173,8 +173,13 @@ export class ResizeHandleDirective implements OnInit, OnDestroy { } private listenOnTheHost(eventName: string) { - return fromEvent(this.element.nativeElement, eventName).pipe( - takeUntil(this.destroy$), - ); + const isTouchEvent = + eventName === 'touchstart' || + eventName === 'touchend' || + eventName === 'touchcancel'; + const source$ = isTouchEvent + ? fromEvent(this.element.nativeElement, eventName, { passive: false }) + : fromEvent(this.element.nativeElement, eventName); + return source$.pipe(takeUntil(this.destroy$)); } } From 087018a3cf8f9699ab2eb6c2dedcc7fafb5d3d29 Mon Sep 17 00:00:00 2001 From: Jack Crymble Date: Wed, 11 Mar 2026 12:13:34 +0000 Subject: [PATCH 2/3] test: add tests --- .../src/test/resizable.spec.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/projects/angular-resizable-element/src/test/resizable.spec.ts b/projects/angular-resizable-element/src/test/resizable.spec.ts index 0abee81..2e233be 100644 --- a/projects/angular-resizable-element/src/test/resizable.spec.ts +++ b/projects/angular-resizable-element/src/test/resizable.spec.ts @@ -506,6 +506,29 @@ describe('resizable directive', () => { }); }); + describe('touch event listeners', () => { + it('should use passive: false for touch events so preventDefault can be called', () => { + const fixture = createComponent(); + const handle = fixture.nativeElement.querySelector('.resize-handle-top'); + const touch = new Touch({ + identifier: 1, + target: handle, + clientX: 150, + clientY: 200, + radiusX: 2, + radiusY: 2, + rotationAngle: 0, + force: 1, + }); + const touchEvent = new TouchEvent('touchstart', { + cancelable: true, + bubbles: true, + touches: [touch], + }); + expect(() => handle.dispatchEvent(touchEvent)).not.to.throw(); + }); + }); + describe('handle outside of element', () => { let domEvents: Array<{ name: string; From 7bac69cdcf415fc7777348b6d3853fab3f70815b Mon Sep 17 00:00:00 2001 From: Jack Crymble Date: Wed, 11 Mar 2026 13:32:31 +0000 Subject: [PATCH 3/3] fix: add tests and create separate util function for listenOptions --- .../src/lib/resize-handle.directive.ts | 14 +++++----- .../src/lib/util/get-listen-options.ts | 14 ++++++++++ .../src/test/resizable.spec.ts | 26 +++++++------------ 3 files changed, 29 insertions(+), 25 deletions(-) create mode 100644 projects/angular-resizable-element/src/lib/util/get-listen-options.ts diff --git a/projects/angular-resizable-element/src/lib/resize-handle.directive.ts b/projects/angular-resizable-element/src/lib/resize-handle.directive.ts index 17e7f19..20451a4 100644 --- a/projects/angular-resizable-element/src/lib/resize-handle.directive.ts +++ b/projects/angular-resizable-element/src/lib/resize-handle.directive.ts @@ -12,6 +12,7 @@ import { fromEvent, merge, Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { ResizableDirective } from './resizable.directive'; import { Edges } from './interfaces/edges.interface'; +import { getListenOptions } from './util/get-listen-options'; import { IS_TOUCH_DEVICE } from './util/is-touch-device'; /** @@ -173,13 +174,10 @@ export class ResizeHandleDirective implements OnInit, OnDestroy { } private listenOnTheHost(eventName: string) { - const isTouchEvent = - eventName === 'touchstart' || - eventName === 'touchend' || - eventName === 'touchcancel'; - const source$ = isTouchEvent - ? fromEvent(this.element.nativeElement, eventName, { passive: false }) - : fromEvent(this.element.nativeElement, eventName); - return source$.pipe(takeUntil(this.destroy$)); + return fromEvent( + this.element.nativeElement, + eventName, + getListenOptions(eventName), + ).pipe(takeUntil(this.destroy$)); } } diff --git a/projects/angular-resizable-element/src/lib/util/get-listen-options.ts b/projects/angular-resizable-element/src/lib/util/get-listen-options.ts new file mode 100644 index 0000000..1f34a3c --- /dev/null +++ b/projects/angular-resizable-element/src/lib/util/get-listen-options.ts @@ -0,0 +1,14 @@ +/** + * Options for fromEvent when listening on the host. + * Touch events need passive: false so preventDefault can be called. + * @hidden + */ +export function getListenOptions( + eventName: string, +): { passive: false } | Record { + const isTouchEvent = + eventName === 'touchstart' || + eventName === 'touchend' || + eventName === 'touchcancel'; + return isTouchEvent ? { passive: false } : {}; +} diff --git a/projects/angular-resizable-element/src/test/resizable.spec.ts b/projects/angular-resizable-element/src/test/resizable.spec.ts index 2e233be..6231ff1 100644 --- a/projects/angular-resizable-element/src/test/resizable.spec.ts +++ b/projects/angular-resizable-element/src/test/resizable.spec.ts @@ -5,6 +5,7 @@ import { ResizeEvent, ResizeHandleDirective, } from 'angular-resizable-element'; +import { getListenOptions } from '../lib/util/get-listen-options'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { expect } from 'chai'; import * as sinon from 'sinon'; @@ -507,25 +508,16 @@ describe('resizable directive', () => { }); describe('touch event listeners', () => { - it('should use passive: false for touch events so preventDefault can be called', () => { - const fixture = createComponent(); - const handle = fixture.nativeElement.querySelector('.resize-handle-top'); - const touch = new Touch({ - identifier: 1, - target: handle, - clientX: 150, - clientY: 200, - radiusX: 2, - radiusY: 2, - rotationAngle: 0, - force: 1, + ['touchstart', 'touchend', 'touchcancel'].forEach((eventName) => { + it(`when eventName is ${eventName}, getListenOptions returns passive: false so fromEvent is called with passive: false`, () => { + expect(getListenOptions(eventName)).to.deep.equal({ passive: false }); }); - const touchEvent = new TouchEvent('touchstart', { - cancelable: true, - bubbles: true, - touches: [touch], + }); + + ['mousedown', 'mouseup'].forEach((eventName) => { + it(`when eventName is ${eventName}, getListenOptions returns empty options`, () => { + expect(getListenOptions(eventName)).to.deep.equal({}); }); - expect(() => handle.dispatchEvent(touchEvent)).not.to.throw(); }); });