From 37ae47c66f742f291ea28bc7a00383ffb7d2d258 Mon Sep 17 00:00:00 2001 From: manolumjm Date: Tue, 18 Jun 2024 08:59:01 +0200 Subject: [PATCH 1/3] fix: prevent browser drag event when dragging and resizing custom handles --- .../src/lib/grid-item/grid-item.component.ts | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) 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 0df45bd..b2c8b8e 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 @@ -2,17 +2,17 @@ import { AfterContentInit, ChangeDetectionStrategy, Component, ContentChild, ContentChildren, ElementRef, Inject, Input, NgZone, OnDestroy, OnInit, QueryList, Renderer2, ViewChild } from '@angular/core'; -import { BehaviorSubject, iif, merge, NEVER, Observable, Subject, Subscription } from 'rxjs'; +import { BehaviorSubject, NEVER, Observable, Subject, Subscription, fromEvent, merge } from 'rxjs'; import { exhaustMap, filter, map, startWith, switchMap, take, takeUntil } from 'rxjs/operators'; -import { ktdPointerDown, ktdPointerUp, ktdPointerClient } from '../utils/pointer.utils'; -import { GRID_ITEM_GET_RENDER_DATA_TOKEN, KtdGridItemRenderDataTokenType } from '../grid.definitions'; +import { BooleanInput, coerceBooleanProperty } from '../coercion/boolean-property'; +import { NumberInput, coerceNumberProperty } from '../coercion/number-property'; import { KTD_GRID_DRAG_HANDLE, KtdGridDragHandle } from '../directives/drag-handle'; +import { KTD_GRID_ITEM_PLACEHOLDER, KtdGridItemPlaceholder } from '../directives/placeholder'; import { KTD_GRID_RESIZE_HANDLE, KtdGridResizeHandle } from '../directives/resize-handle'; +import { GRID_ITEM_GET_RENDER_DATA_TOKEN, KtdGridItemRenderDataTokenType } from '../grid.definitions'; import { KtdGridService } from '../grid.service'; import { ktdOutsideZone } from '../utils/operators'; -import { BooleanInput, coerceBooleanProperty } from '../coercion/boolean-property'; -import { coerceNumberProperty, NumberInput } from '../coercion/number-property'; -import { KTD_GRID_ITEM_PLACEHOLDER, KtdGridItemPlaceholder } from '../directives/placeholder'; +import { ktdPointerClient, ktdPointerDown, ktdPointerUp } from '../utils/pointer.utils'; @Component({ standalone: true, @@ -145,6 +145,10 @@ export class KtdGridItemComponent implements OnInit, OnDestroy, AfterContentInit if (height != null) {this.renderer.setStyle(this.elementRef.nativeElement, 'height', height); } } + private preventDrag(event: Event) { + event.preventDefault(); + } + private _dragStart$(): Observable { return merge( this._manualDragEvents$, @@ -156,11 +160,15 @@ export class KtdGridItemComponent implements OnInit, OnDestroy, AfterContentInit return this._dragHandles.changes.pipe( startWith(this._dragHandles), switchMap((dragHandles: QueryList) => { - return iif( - () => dragHandles.length > 0, - merge(...dragHandles.toArray().map(dragHandle => ktdPointerDown(dragHandle.element.nativeElement))), - ktdPointerDown(this.elementRef.nativeElement) - ) + if(dragHandles.length > 0) { + // prevent parent dragStart event to avoid fail if user drags it away quickly + const preventDragStartEvent$ = fromEvent(this.elementRef.nativeElement, 'dragstart').subscribe(this.preventDrag); + this.subscriptions.push(preventDragStartEvent$); + return merge(...dragHandles.toArray().map(dragHandle => ktdPointerDown(dragHandle.element.nativeElement))); + } + else { + return ktdPointerDown(this.elementRef.nativeElement) + } }) ); }) From cebf6279ffbde611df0f7b68d8c450ed2e7a7ed6 Mon Sep 17 00:00:00 2001 From: manolumjm Date: Wed, 19 Jun 2024 10:06:46 +0200 Subject: [PATCH 2/3] fix: prevent dragStart event when start drag if is pointerEvent by mouse --- .../src/lib/grid-item/grid-item.component.ts | 25 ++++++++----------- .../src/lib/utils/pointer.utils.ts | 8 +++--- 2 files changed, 15 insertions(+), 18 deletions(-) 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 b2c8b8e..63af356 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 @@ -2,7 +2,7 @@ import { AfterContentInit, ChangeDetectionStrategy, Component, ContentChild, ContentChildren, ElementRef, Inject, Input, NgZone, OnDestroy, OnInit, QueryList, Renderer2, ViewChild } from '@angular/core'; -import { BehaviorSubject, NEVER, Observable, Subject, Subscription, fromEvent, merge } from 'rxjs'; +import { BehaviorSubject, NEVER, Observable, Subject, Subscription, iif, merge } from 'rxjs'; import { exhaustMap, filter, map, startWith, switchMap, take, takeUntil } from 'rxjs/operators'; import { BooleanInput, coerceBooleanProperty } from '../coercion/boolean-property'; import { NumberInput, coerceNumberProperty } from '../coercion/number-property'; @@ -145,10 +145,6 @@ export class KtdGridItemComponent implements OnInit, OnDestroy, AfterContentInit if (height != null) {this.renderer.setStyle(this.elementRef.nativeElement, 'height', height); } } - private preventDrag(event: Event) { - event.preventDefault(); - } - private _dragStart$(): Observable { return merge( this._manualDragEvents$, @@ -160,15 +156,11 @@ export class KtdGridItemComponent implements OnInit, OnDestroy, AfterContentInit return this._dragHandles.changes.pipe( startWith(this._dragHandles), switchMap((dragHandles: QueryList) => { - if(dragHandles.length > 0) { - // prevent parent dragStart event to avoid fail if user drags it away quickly - const preventDragStartEvent$ = fromEvent(this.elementRef.nativeElement, 'dragstart').subscribe(this.preventDrag); - this.subscriptions.push(preventDragStartEvent$); - return merge(...dragHandles.toArray().map(dragHandle => ktdPointerDown(dragHandle.element.nativeElement))); - } - else { - return ktdPointerDown(this.elementRef.nativeElement) - } + return iif( + () => dragHandles.length > 0, + merge(...dragHandles.toArray().map(dragHandle => ktdPointerDown(dragHandle.element.nativeElement))), + ktdPointerDown(this.elementRef.nativeElement) + ) }) ); }) @@ -185,6 +177,11 @@ export class KtdGridItemComponent implements OnInit, OnDestroy, AfterContentInit startEvent.preventDefault(); } + if(startEvent.target && startEvent instanceof PointerEvent && startEvent.pointerType === 'mouse') + { + startEvent.preventDefault(); + } + const startPointer = ktdPointerClient(startEvent); return this.gridService.mouseOrTouchMove$(document).pipe( takeUntil(ktdPointerUp(document)), 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 cccc6e8..cb674ac 100644 --- a/projects/angular-grid-layout/src/lib/utils/pointer.utils.ts +++ b/projects/angular-grid-layout/src/lib/utils/pointer.utils.ts @@ -84,7 +84,7 @@ function ktdMouseOrTouchDown(element, touchNumber = 1): Observable { +function ktdMouseOrTouchMove(element: HTMLElement, touchNumber = 1): Observable { return iif( () => ktdIsMobileOrTablet(), fromEvent(element, 'touchmove', activeEventListenerOptions as AddEventListenerOptions).pipe( @@ -127,8 +127,8 @@ export function ktdPointerDown(element): Observable(element, 'pointerdown', passiveEventListenerOptions as AddEventListenerOptions).pipe( + + return fromEvent(element, 'pointerdown', activeEventListenerOptions as AddEventListenerOptions).pipe( filter((pointerEvent) => pointerEvent.isPrimary) ) } @@ -139,7 +139,7 @@ export function ktdPointerDown(element): Observable { if (!ktdSupportsPointerEvents()) { - return ktdMouserOrTouchMove(element); + return ktdMouseOrTouchMove(element); } return fromEvent(element, 'pointermove', activeEventListenerOptions as AddEventListenerOptions).pipe( filter((pointerEvent) => pointerEvent.isPrimary), From 91bddf2275ae6b944a8eb91f20a92d520b573a63 Mon Sep 17 00:00:00 2001 From: llorenspujol Date: Wed, 19 Jun 2024 19:02:18 +0800 Subject: [PATCH 3/3] fix(grid-item): apply preventDefault on all start drag events starting from a mouse --- .../src/lib/grid-item/grid-item.component.ts | 20 +++++++++---------- .../src/lib/utils/pointer.utils.ts | 7 ++++++- .../table-sorting.component.scss | 1 + 3 files changed, 17 insertions(+), 11 deletions(-) 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 63af356..0a4e3be 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 @@ -3,7 +3,7 @@ import { QueryList, Renderer2, ViewChild } from '@angular/core'; import { BehaviorSubject, NEVER, Observable, Subject, Subscription, iif, merge } from 'rxjs'; -import { exhaustMap, filter, map, startWith, switchMap, take, takeUntil } from 'rxjs/operators'; +import { exhaustMap, filter, map, startWith, switchMap, take, takeUntil, tap } from 'rxjs/operators'; import { BooleanInput, coerceBooleanProperty } from '../coercion/boolean-property'; import { NumberInput, coerceNumberProperty } from '../coercion/number-property'; import { KTD_GRID_DRAG_HANDLE, KtdGridDragHandle } from '../directives/drag-handle'; @@ -12,7 +12,7 @@ import { KTD_GRID_RESIZE_HANDLE, KtdGridResizeHandle } from '../directives/resiz import { GRID_ITEM_GET_RENDER_DATA_TOKEN, KtdGridItemRenderDataTokenType } from '../grid.definitions'; import { KtdGridService } from '../grid.service'; import { ktdOutsideZone } from '../utils/operators'; -import { ktdPointerClient, ktdPointerDown, ktdPointerUp } from '../utils/pointer.utils'; +import { ktdIsMouseEventOrMousePointerEvent, ktdPointerClient, ktdPointerDown, ktdPointerUp } from '../utils/pointer.utils'; @Component({ standalone: true, @@ -171,14 +171,9 @@ export class KtdGridItemComponent implements OnInit, OnDestroy, AfterContentInit // with our own dragging (e.g. `img` tags do it by default). Prevent the default action // to stop it from happening. Note that preventing on `dragstart` also seems to work, but // it's flaky and it fails if the user drags it away quickly. Also note that we only want - // to do this for `mousedown` since doing the same for `touchstart` will stop any `click` - // events from firing on touch devices. - if (startEvent.target && (startEvent.target as HTMLElement).draggable && startEvent.type === 'mousedown') { - startEvent.preventDefault(); - } - - if(startEvent.target && startEvent instanceof PointerEvent && startEvent.pointerType === 'mouse') - { + // to do this for `mousedown` and `pointerdown` since doing the same for `touchstart` will + // stop any `click` events from firing on touch devices. + if (ktdIsMouseEventOrMousePointerEvent(startEvent)) { startEvent.preventDefault(); } @@ -221,6 +216,11 @@ export class KtdGridItemComponent implements OnInit, OnDestroy, AfterContentInit this.renderer.setStyle(this.resizeElem.nativeElement, 'display', 'block'); return ktdPointerDown(this.resizeElem.nativeElement); } + }), + tap((startEvent) => { + if (ktdIsMouseEventOrMousePointerEvent(startEvent)) { + startEvent.preventDefault(); + } }) ); } 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 cb674ac..45bd146 100644 --- a/projects/angular-grid-layout/src/lib/utils/pointer.utils.ts +++ b/projects/angular-grid-layout/src/lib/utils/pointer.utils.ts @@ -50,6 +50,11 @@ export function ktdPointerClient(event: MouseEvent | TouchEvent): { clientX: num }; } +export function ktdIsMouseEventOrMousePointerEvent(event: MouseEvent | TouchEvent | PointerEvent): boolean { + return event.type === 'mousedown' + || (event.type === 'pointerdown' && (event as PointerEvent).pointerType === 'mouse'); +} + /** Returns true if browser supports pointer events */ export function ktdSupportsPointerEvents(): boolean { return !!window.PointerEvent; @@ -127,7 +132,7 @@ export function ktdPointerDown(element): Observable(element, 'pointerdown', activeEventListenerOptions as AddEventListenerOptions).pipe( filter((pointerEvent) => pointerEvent.isPrimary) ) diff --git a/projects/demo-app/src/app/real-life-example/table-sorting/table-sorting.component.scss b/projects/demo-app/src/app/real-life-example/table-sorting/table-sorting.component.scss index bac579c..383e1e3 100644 --- a/projects/demo-app/src/app/real-life-example/table-sorting/table-sorting.component.scss +++ b/projects/demo-app/src/app/real-life-example/table-sorting/table-sorting.component.scss @@ -3,6 +3,7 @@ width: 100%; height: 100%; overflow-y: auto; + touch-action: none; // This is needed to make drag and drop on touch devices possible without interfering with the inner scroll. } table {