diff --git a/projects/angular-grid-layout/src/lib/grid-item/grid-item.component.scss b/projects/angular-grid-layout/src/lib/grid-item/grid-item.component.scss index 6cc1fd4..6685614 100644 --- a/projects/angular-grid-layout/src/lib/grid-item/grid-item.component.scss +++ b/projects/angular-grid-layout/src/lib/grid-item/grid-item.component.scss @@ -4,6 +4,7 @@ position: absolute; z-index: 1; overflow: hidden; + touch-action: none; div { position: absolute; diff --git a/projects/angular-grid-layout/src/lib/grid-item/grid-item.component.ts b/projects/angular-grid-layout/src/lib/grid-item/grid-item.component.ts index 5a9e5ed..0df45bd 100644 --- a/projects/angular-grid-layout/src/lib/grid-item/grid-item.component.ts +++ b/projects/angular-grid-layout/src/lib/grid-item/grid-item.component.ts @@ -4,7 +4,7 @@ import { } from '@angular/core'; import { BehaviorSubject, iif, merge, NEVER, Observable, Subject, Subscription } from 'rxjs'; import { exhaustMap, filter, map, startWith, switchMap, take, takeUntil } from 'rxjs/operators'; -import { ktdMouseOrTouchDown, ktdMouseOrTouchEnd, ktdPointerClient } from '../utils/pointer.utils'; +import { ktdPointerDown, ktdPointerUp, ktdPointerClient } from '../utils/pointer.utils'; import { GRID_ITEM_GET_RENDER_DATA_TOKEN, KtdGridItemRenderDataTokenType } from '../grid.definitions'; import { KTD_GRID_DRAG_HANDLE, KtdGridDragHandle } from '../directives/drag-handle'; import { KTD_GRID_RESIZE_HANDLE, KtdGridResizeHandle } from '../directives/resize-handle'; @@ -158,8 +158,8 @@ export class KtdGridItemComponent implements OnInit, OnDestroy, AfterContentInit switchMap((dragHandles: QueryList) => { return iif( () => dragHandles.length > 0, - merge(...dragHandles.toArray().map(dragHandle => ktdMouseOrTouchDown(dragHandle.element.nativeElement, 1))), - ktdMouseOrTouchDown(this.elementRef.nativeElement, 1) + merge(...dragHandles.toArray().map(dragHandle => ktdPointerDown(dragHandle.element.nativeElement))), + ktdPointerDown(this.elementRef.nativeElement) ) }) ); @@ -179,7 +179,7 @@ export class KtdGridItemComponent implements OnInit, OnDestroy, AfterContentInit const startPointer = ktdPointerClient(startEvent); return this.gridService.mouseOrTouchMove$(document).pipe( - takeUntil(ktdMouseOrTouchEnd(document, 1)), + takeUntil(ktdPointerUp(document)), ktdOutsideZone(this.ngZone), filter((moveEvent) => { moveEvent.preventDefault(); @@ -211,10 +211,10 @@ export class KtdGridItemComponent implements OnInit, OnDestroy, AfterContentInit if (resizeHandles.length > 0) { // Side effect to hide the resizeElem if there are resize handles. this.renderer.setStyle(this.resizeElem.nativeElement, 'display', 'none'); - return merge(...resizeHandles.toArray().map(resizeHandle => ktdMouseOrTouchDown(resizeHandle.element.nativeElement, 1))); + return merge(...resizeHandles.toArray().map(resizeHandle => ktdPointerDown(resizeHandle.element.nativeElement))); } else { this.renderer.setStyle(this.resizeElem.nativeElement, 'display', 'block'); - return ktdMouseOrTouchDown(this.resizeElem.nativeElement, 1); + return ktdPointerDown(this.resizeElem.nativeElement); } }) ); diff --git a/projects/angular-grid-layout/src/lib/grid.component.ts b/projects/angular-grid-layout/src/lib/grid.component.ts index a12361e..73f9330 100644 --- a/projects/angular-grid-layout/src/lib/grid.component.ts +++ b/projects/angular-grid-layout/src/lib/grid.component.ts @@ -12,7 +12,7 @@ import { compact } from './utils/react-grid-layout.utils'; import { GRID_ITEM_GET_RENDER_DATA_TOKEN, KtdGridBackgroundCfg, KtdGridCfg, KtdGridCompactType, KtdGridItemRenderData, KtdGridLayout, KtdGridLayoutItem } from './grid.definitions'; -import { ktdMouseOrTouchEnd, ktdPointerClientX, ktdPointerClientY } from './utils/pointer.utils'; +import { ktdPointerUp, ktdPointerClientX, ktdPointerClientY } from './utils/pointer.utils'; import { KtdDictionary } from '../types'; import { KtdGridService } from './grid.service'; import { getMutableClientRect, KtdClientRect } from './utils/client-rect'; @@ -516,7 +516,7 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte })), ktdScrollIfNearElementClientRect$(scrollableParent, {scrollStep: this.scrollSpeed}) )).pipe( - takeUntil(ktdMouseOrTouchEnd(document)) + takeUntil(ktdPointerUp(document)) ).subscribe()); /** @@ -533,8 +533,8 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte ]) ]) ).pipe( - takeUntil(ktdMouseOrTouchEnd(document)), - ).subscribe(([pointerDragEvent, scrollDifference]: [MouseEvent | TouchEvent, { top: number, left: number }]) => { + takeUntil(ktdPointerUp(document)), + ).subscribe(([pointerDragEvent, scrollDifference]: [MouseEvent | TouchEvent | PointerEvent, { top: number, left: number }]) => { pointerDragEvent.preventDefault(); /** diff --git a/projects/angular-grid-layout/src/lib/grid.service.ts b/projects/angular-grid-layout/src/lib/grid.service.ts index 372ddab..0132630 100644 --- a/projects/angular-grid-layout/src/lib/grid.service.ts +++ b/projects/angular-grid-layout/src/lib/grid.service.ts @@ -2,7 +2,7 @@ import { Injectable, NgZone, OnDestroy } from '@angular/core'; import { ktdNormalizePassiveListenerOptions } from './utils/passive-listeners'; import { fromEvent, iif, Observable, Subject, Subscription } from 'rxjs'; import { filter } from 'rxjs/operators'; -import { ktdIsMobileOrTablet } from './utils/pointer.utils'; +import { ktdIsMobileOrTablet, ktdSupportsPointerEvents } from './utils/pointer.utils'; /** Event options that can be used to bind an active, capturing event. */ const activeCapturingEventOptions = ktdNormalizePassiveListenerOptions({ @@ -27,11 +27,15 @@ export class KtdGridService implements OnDestroy { } mouseOrTouchMove$(element): Observable { - return iif( - () => ktdIsMobileOrTablet(), - this.touchMove$, - fromEvent(element, 'mousemove', activeCapturingEventOptions as AddEventListenerOptions) // TODO: Fix rxjs typings, boolean should be a good param too. - ); + if (!ktdSupportsPointerEvents()) { + return iif( + () => ktdIsMobileOrTablet(), + this.touchMove$, + fromEvent(element, 'mousemove', activeCapturingEventOptions as AddEventListenerOptions) // TODO: Fix rxjs typings, boolean should be a good param too. + ); + } + + return fromEvent(element, 'pointermove', activeCapturingEventOptions as AddEventListenerOptions); } private registerTouchMoveSubscription() { diff --git a/projects/angular-grid-layout/src/lib/utils/pointer.utils.ts b/projects/angular-grid-layout/src/lib/utils/pointer.utils.ts index ada274b..cccc6e8 100644 --- a/projects/angular-grid-layout/src/lib/utils/pointer.utils.ts +++ b/projects/angular-grid-layout/src/lib/utils/pointer.utils.ts @@ -43,19 +43,24 @@ export function ktdPointerClientY(event: MouseEvent | TouchEvent): number { return ktdIsMouseEvent(event) ? event.clientY : event.touches[0].clientY; } -export function ktdPointerClient(event: MouseEvent | TouchEvent): {clientX: number, clientY: number} { - return { +export function ktdPointerClient(event: MouseEvent | TouchEvent): { clientX: number, clientY: number } { + return { clientX: ktdIsMouseEvent(event) ? event.clientX : event.touches[0].clientX, clientY: ktdIsMouseEvent(event) ? event.clientY : event.touches[0].clientY }; } +/** Returns true if browser supports pointer events */ +export function ktdSupportsPointerEvents(): boolean { + return !!window.PointerEvent; +} + /** * Emits when a mousedown or touchstart emits. Avoids conflicts between both events. * @param element, html element where to listen the events. * @param touchNumber number of the touch to track the event, default to the first one. */ -export function ktdMouseOrTouchDown(element, touchNumber = 1): Observable { +function ktdMouseOrTouchDown(element, touchNumber = 1): Observable { return iif( () => ktdIsMobileOrTablet(), fromEvent(element, 'touchstart', passiveEventListenerOptions as AddEventListenerOptions).pipe( @@ -79,7 +84,7 @@ export function ktdMouseOrTouchDown(element, touchNumber = 1): Observable { +function ktdMouserOrTouchMove(element: HTMLElement, touchNumber = 1): Observable { return iif( () => ktdIsMobileOrTablet(), fromEvent(element, 'touchmove', activeEventListenerOptions as AddEventListenerOptions).pipe( @@ -105,10 +110,49 @@ export function ktdTouchEnd(element, touchNumber = 1): Observable { * @param element, html element where to listen the events. * @param touchNumber number of the touch to track the event, default to the first one. */ -export function ktdMouseOrTouchEnd(element, touchNumber = 1): Observable { +function ktdMouserOrTouchEnd(element: HTMLElement, touchNumber = 1): Observable { return iif( () => ktdIsMobileOrTablet(), ktdTouchEnd(element, touchNumber), fromEvent(element, 'mouseup'), ); } + + +/** + * Emits when a 'pointerdown' event occurs (only for the primary pointer). Fallbacks to 'mousemove' or a 'touchmove' if pointer events are not supported. + * @param element, html element where to listen the events. + */ +export function ktdPointerDown(element): Observable { + if (!ktdSupportsPointerEvents()) { + return ktdMouseOrTouchDown(element); + } + + return fromEvent(element, 'pointerdown', passiveEventListenerOptions as AddEventListenerOptions).pipe( + filter((pointerEvent) => pointerEvent.isPrimary) + ) +} + +/** + * Emits when a 'pointermove' event occurs (only for the primary pointer). Fallbacks to 'mousemove' or a 'touchmove' if pointer events are not supported. + * @param element, html element where to listen the events. + */ +export function ktdPointerMove(element): Observable { + if (!ktdSupportsPointerEvents()) { + return ktdMouserOrTouchMove(element); + } + return fromEvent(element, 'pointermove', activeEventListenerOptions as AddEventListenerOptions).pipe( + filter((pointerEvent) => pointerEvent.isPrimary), + ); +} + +/** + * Emits when a 'pointerup' event occurs (only for the primary pointer). Fallbacks to 'mousemove' or a 'touchmove' if pointer events are not supported. + * @param element, html element where to listen the events. + */ +export function ktdPointerUp(element): Observable { + if (!ktdSupportsPointerEvents()) { + return ktdMouserOrTouchEnd(element); + } + return fromEvent(element, 'pointerup').pipe(filter(pointerEvent => pointerEvent.isPrimary)); +}