From 8b5d173648669ced8929bcebcae01e86be0106b9 Mon Sep 17 00:00:00 2001 From: arturovt Date: Fri, 29 Jan 2021 21:51:36 +0200 Subject: [PATCH] feat: listen to touch events only on touch devices --- src/is-touch-device.ts | 15 ++++ src/resizable.directive.ts | 119 +++++++++++++++------------- src/resize-handle.directive.ts | 137 +++++++++++++++++++-------------- 3 files changed, 162 insertions(+), 109 deletions(-) create mode 100644 src/is-touch-device.ts diff --git a/src/is-touch-device.ts b/src/is-touch-device.ts new file mode 100644 index 0000000..6b34188 --- /dev/null +++ b/src/is-touch-device.ts @@ -0,0 +1,15 @@ +/** + * @hidden + */ +export const IS_TOUCH_DEVICE: boolean = (() => { + // In case we're in Node.js environment. + if (typeof window === 'undefined') { + return false; + } else { + return ( + 'ontouchstart' in window || + navigator.maxTouchPoints > 0 || + navigator.msMaxTouchPoints > 0 + ); + } +})(); diff --git a/src/resizable.directive.ts b/src/resizable.directive.ts index c02e8d8..338fa03 100644 --- a/src/resizable.directive.ts +++ b/src/resizable.directive.ts @@ -31,6 +31,7 @@ import { import { Edges } from './interfaces/edges.interface'; import { BoundingRectangle } from './interfaces/bounding-rectangle.interface'; import { ResizeEvent } from './interfaces/resize-event.interface'; +import { IS_TOUCH_DEVICE } from './is-touch-device'; interface PointerEventCoordinate { clientX: number; @@ -855,7 +856,7 @@ class PointerEventListeners { this.pointerDown = new Observable( (observer: Observer) => { let unsubscribeMouseDown: () => void; - let unsubscribeTouchStart: () => void; + let unsubscribeTouchStart: (() => void) | undefined; zone.runOutsideAngular(() => { unsubscribeMouseDown = renderer.listen( @@ -870,22 +871,26 @@ class PointerEventListeners { } ); - unsubscribeTouchStart = renderer.listen( - 'document', - 'touchstart', - (event: TouchEvent) => { - observer.next({ - clientX: event.touches[0].clientX, - clientY: event.touches[0].clientY, - event - }); - } - ); + if (IS_TOUCH_DEVICE) { + unsubscribeTouchStart = renderer.listen( + 'document', + 'touchstart', + (event: TouchEvent) => { + observer.next({ + clientX: event.touches[0].clientX, + clientY: event.touches[0].clientY, + event + }); + } + ); + } }); return () => { unsubscribeMouseDown(); - unsubscribeTouchStart(); + if (IS_TOUCH_DEVICE) { + unsubscribeTouchStart!(); + } }; } ).pipe(share()); @@ -893,7 +898,7 @@ class PointerEventListeners { this.pointerMove = new Observable( (observer: Observer) => { let unsubscribeMouseMove: () => void; - let unsubscribeTouchMove: () => void; + let unsubscribeTouchMove: (() => void) | undefined; zone.runOutsideAngular(() => { unsubscribeMouseMove = renderer.listen( @@ -908,22 +913,26 @@ class PointerEventListeners { } ); - unsubscribeTouchMove = renderer.listen( - 'document', - 'touchmove', - (event: TouchEvent) => { - observer.next({ - clientX: event.targetTouches[0].clientX, - clientY: event.targetTouches[0].clientY, - event - }); - } - ); + if (IS_TOUCH_DEVICE) { + unsubscribeTouchMove = renderer.listen( + 'document', + 'touchmove', + (event: TouchEvent) => { + observer.next({ + clientX: event.targetTouches[0].clientX, + clientY: event.targetTouches[0].clientY, + event + }); + } + ); + } }); return () => { unsubscribeMouseMove(); - unsubscribeTouchMove(); + if (IS_TOUCH_DEVICE) { + unsubscribeTouchMove!(); + } }; } ).pipe(share()); @@ -931,8 +940,8 @@ class PointerEventListeners { this.pointerUp = new Observable( (observer: Observer) => { let unsubscribeMouseUp: () => void; - let unsubscribeTouchEnd: () => void; - let unsubscribeTouchCancel: () => void; + let unsubscribeTouchEnd: (() => void) | undefined; + let unsubscribeTouchCancel: (() => void) | undefined; zone.runOutsideAngular(() => { unsubscribeMouseUp = renderer.listen( @@ -947,35 +956,39 @@ class PointerEventListeners { } ); - unsubscribeTouchEnd = renderer.listen( - 'document', - 'touchend', - (event: TouchEvent) => { - observer.next({ - clientX: event.changedTouches[0].clientX, - clientY: event.changedTouches[0].clientY, - event - }); - } - ); - - unsubscribeTouchCancel = renderer.listen( - 'document', - 'touchcancel', - (event: TouchEvent) => { - observer.next({ - clientX: event.changedTouches[0].clientX, - clientY: event.changedTouches[0].clientY, - event - }); - } - ); + if (IS_TOUCH_DEVICE) { + unsubscribeTouchEnd = renderer.listen( + 'document', + 'touchend', + (event: TouchEvent) => { + observer.next({ + clientX: event.changedTouches[0].clientX, + clientY: event.changedTouches[0].clientY, + event + }); + } + ); + + unsubscribeTouchCancel = renderer.listen( + 'document', + 'touchcancel', + (event: TouchEvent) => { + observer.next({ + clientX: event.changedTouches[0].clientX, + clientY: event.changedTouches[0].clientY, + event + }); + } + ); + } }); return () => { unsubscribeMouseUp(); - unsubscribeTouchEnd(); - unsubscribeTouchCancel(); + if (IS_TOUCH_DEVICE) { + unsubscribeTouchEnd!(); + unsubscribeTouchCancel!(); + } }; } ).pipe(share()); diff --git a/src/resize-handle.directive.ts b/src/resize-handle.directive.ts index 6ccbbd8..544b49a 100644 --- a/src/resize-handle.directive.ts +++ b/src/resize-handle.directive.ts @@ -1,14 +1,17 @@ import { Directive, Input, - HostListener, Renderer2, ElementRef, + OnInit, OnDestroy, NgZone } from '@angular/core'; +import { fromEvent, merge, Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; import { ResizableDirective } from './resizable.directive'; import { Edges } from './interfaces/edges.interface'; +import { IS_TOUCH_DEVICE } from './is-touch-device'; /** * An element placed inside a `mwlResizable` directive to be used as a drag and resize handle @@ -24,7 +27,7 @@ import { Edges } from './interfaces/edges.interface'; @Directive({ selector: '[mwlResizeHandle]' }) -export class ResizeHandleDirective implements OnDestroy { +export class ResizeHandleDirective implements OnInit, OnDestroy { /** * The `Edges` object that contains the edges of the parent element that dragging the handle will trigger a resize on */ @@ -36,6 +39,8 @@ export class ResizeHandleDirective implements OnDestroy { [key: string]: (() => void) | undefined; } = {}; + private destroy$ = new Subject(); + constructor( private renderer: Renderer2, private element: ElementRef, @@ -43,80 +48,94 @@ export class ResizeHandleDirective implements OnDestroy { private resizable: ResizableDirective ) {} + ngOnInit(): void { + this.zone.runOutsideAngular(() => { + this.listenOnTheHost('mousedown').subscribe(event => { + this.onMousedown(event, event.clientX, event.clientY); + }); + + this.listenOnTheHost('mouseup').subscribe(event => { + this.onMouseup(event.clientX, event.clientY); + }); + + if (IS_TOUCH_DEVICE) { + this.listenOnTheHost('touchstart').subscribe(event => { + this.onMousedown( + event, + event.touches[0].clientX, + event.touches[0].clientY + ); + }); + + merge( + this.listenOnTheHost('touchend'), + this.listenOnTheHost('touchcancel') + ).subscribe(event => { + this.onMouseup( + event.changedTouches[0].clientX, + event.changedTouches[0].clientY + ); + }); + } + }); + } + ngOnDestroy(): void { + this.destroy$.next(); this.unsubscribeEventListeners(); } /** * @hidden */ - @HostListener('touchstart', [ - '$event', - '$event.touches[0].clientX', - '$event.touches[0].clientY' - ]) - @HostListener('mousedown', ['$event', '$event.clientX', '$event.clientY']) onMousedown( event: MouseEvent | TouchEvent, clientX: number, clientY: number ): void { event.preventDefault(); - this.zone.runOutsideAngular(() => { - if (!this.eventListeners.touchmove) { - this.eventListeners.touchmove = this.renderer.listen( - this.element.nativeElement, - 'touchmove', - (touchMoveEvent: TouchEvent) => { - this.onMousemove( - touchMoveEvent, - touchMoveEvent.targetTouches[0].clientX, - touchMoveEvent.targetTouches[0].clientY - ); - } - ); - } - if (!this.eventListeners.mousemove) { - this.eventListeners.mousemove = this.renderer.listen( - this.element.nativeElement, - 'mousemove', - (mouseMoveEvent: MouseEvent) => { - this.onMousemove( - mouseMoveEvent, - mouseMoveEvent.clientX, - mouseMoveEvent.clientY - ); - } - ); - } - this.resizable.mousedown.next({ - clientX, - clientY, - edges: this.resizeEdges - }); + if (!this.eventListeners.touchmove) { + this.eventListeners.touchmove = this.renderer.listen( + this.element.nativeElement, + 'touchmove', + (touchMoveEvent: TouchEvent) => { + this.onMousemove( + touchMoveEvent, + touchMoveEvent.targetTouches[0].clientX, + touchMoveEvent.targetTouches[0].clientY + ); + } + ); + } + if (!this.eventListeners.mousemove) { + this.eventListeners.mousemove = this.renderer.listen( + this.element.nativeElement, + 'mousemove', + (mouseMoveEvent: MouseEvent) => { + this.onMousemove( + mouseMoveEvent, + mouseMoveEvent.clientX, + mouseMoveEvent.clientY + ); + } + ); + } + this.resizable.mousedown.next({ + clientX, + clientY, + edges: this.resizeEdges }); } /** * @hidden */ - @HostListener('touchend', [ - '$event.changedTouches[0].clientX', - '$event.changedTouches[0].clientY' - ]) - @HostListener('touchcancel', [ - '$event.changedTouches[0].clientX', - '$event.changedTouches[0].clientY' - ]) - @HostListener('mouseup', ['$event.clientX', '$event.clientY']) onMouseup(clientX: number, clientY: number): void { - this.zone.runOutsideAngular(() => { - this.unsubscribeEventListeners(); - this.resizable.mouseup.next({ - clientX, - clientY, - edges: this.resizeEdges - }); + this.unsubscribeEventListeners(); + this.resizable.mouseup.next({ + clientX, + clientY, + edges: this.resizeEdges }); } @@ -139,4 +158,10 @@ export class ResizeHandleDirective implements OnDestroy { delete this.eventListeners[type]; }); } + + private listenOnTheHost(eventName: string) { + return fromEvent(this.element.nativeElement, eventName).pipe( + takeUntil(this.destroy$) + ); + } }