Skip to content

Commit

Permalink
refactor(grid): use pointer events instead of mouse and touch events …
Browse files Browse the repository at this point in the history
…if possible
  • Loading branch information
llorenspujol committed Jun 13, 2024
1 parent f74824f commit a1a40a5
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
position: absolute;
z-index: 1;
overflow: hidden;
touch-action: none;

div {
position: absolute;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -158,8 +158,8 @@ export class KtdGridItemComponent implements OnInit, OnDestroy, AfterContentInit
switchMap((dragHandles: QueryList<KtdGridDragHandle>) => {
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)
)
})
);
Expand All @@ -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();
Expand Down Expand Up @@ -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);
}
})
);
Expand Down
8 changes: 4 additions & 4 deletions projects/angular-grid-layout/src/lib/grid.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -516,7 +516,7 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
})),
ktdScrollIfNearElementClientRect$(scrollableParent, {scrollStep: this.scrollSpeed})
)).pipe(
takeUntil(ktdMouseOrTouchEnd(document))
takeUntil(ktdPointerUp(document))
).subscribe());

/**
Expand All @@ -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();

/**
Expand Down
16 changes: 10 additions & 6 deletions projects/angular-grid-layout/src/lib/grid.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -27,11 +27,15 @@ export class KtdGridService implements OnDestroy {
}

mouseOrTouchMove$(element): Observable<MouseEvent | TouchEvent> {
return iif(
() => ktdIsMobileOrTablet(),
this.touchMove$,
fromEvent<MouseEvent>(element, 'mousemove', activeCapturingEventOptions as AddEventListenerOptions) // TODO: Fix rxjs typings, boolean should be a good param too.
);
if (!ktdSupportsPointerEvents()) {
return iif(
() => ktdIsMobileOrTablet(),
this.touchMove$,
fromEvent<MouseEvent>(element, 'mousemove', activeCapturingEventOptions as AddEventListenerOptions) // TODO: Fix rxjs typings, boolean should be a good param too.
);
}

return fromEvent<MouseEvent>(element, 'pointermove', activeCapturingEventOptions as AddEventListenerOptions);
}

private registerTouchMoveSubscription() {
Expand Down
54 changes: 49 additions & 5 deletions projects/angular-grid-layout/src/lib/utils/pointer.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<MouseEvent | TouchEvent> {
function ktdMouseOrTouchDown(element, touchNumber = 1): Observable<MouseEvent | TouchEvent> {
return iif(
() => ktdIsMobileOrTablet(),
fromEvent<TouchEvent>(element, 'touchstart', passiveEventListenerOptions as AddEventListenerOptions).pipe(
Expand All @@ -79,7 +84,7 @@ export function ktdMouseOrTouchDown(element, touchNumber = 1): Observable<MouseE
* @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 ktdMouseOrTouchMove(element, touchNumber = 1): Observable<MouseEvent | TouchEvent> {
function ktdMouserOrTouchMove(element: HTMLElement, touchNumber = 1): Observable<MouseEvent | TouchEvent> {
return iif(
() => ktdIsMobileOrTablet(),
fromEvent<TouchEvent>(element, 'touchmove', activeEventListenerOptions as AddEventListenerOptions).pipe(
Expand All @@ -105,10 +110,49 @@ export function ktdTouchEnd(element, touchNumber = 1): Observable<TouchEvent> {
* @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<MouseEvent | TouchEvent> {
function ktdMouserOrTouchEnd(element: HTMLElement, touchNumber = 1): Observable<MouseEvent | TouchEvent> {
return iif(
() => ktdIsMobileOrTablet(),
ktdTouchEnd(element, touchNumber),
fromEvent<MouseEvent>(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<MouseEvent | TouchEvent | PointerEvent> {
if (!ktdSupportsPointerEvents()) {
return ktdMouseOrTouchDown(element);
}

return fromEvent<PointerEvent>(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<MouseEvent | TouchEvent | PointerEvent> {
if (!ktdSupportsPointerEvents()) {
return ktdMouserOrTouchMove(element);
}
return fromEvent<PointerEvent>(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<MouseEvent | TouchEvent | PointerEvent> {
if (!ktdSupportsPointerEvents()) {
return ktdMouserOrTouchEnd(element);
}
return fromEvent<PointerEvent>(element, 'pointerup').pipe(filter(pointerEvent => pointerEvent.isPrimary));
}

0 comments on commit a1a40a5

Please sign in to comment.