Last active
February 28, 2020 10:59
-
-
Save Rkokie/8bf117683dcd42c8ef4f7675a7e1e971 to your computer and use it in GitHub Desktop.
Angular long press events
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
... | |
providers: { | |
{provide: EVENT_MANAGER_PLUGINS, useClass: LongPressEventPlugin, multi: true} | |
} | |
... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {Injectable} from '@angular/core'; | |
import {EventManager} from '@angular/platform-browser'; | |
import {LongPressEventHandler} from './long-press-event-handler'; | |
@Injectable({ | |
providedIn: 'root' | |
}) | |
export class LongPressEventPlugin { | |
manager: EventManager; | |
supports(eventName: string): boolean { | |
return 'longpress' === eventName; | |
} | |
addEventListener( | |
element: HTMLElement, | |
eventName: string, | |
originalHandler?: (event?: any) => void | |
): () => void { | |
let handler = new LongPressEventHandler(element); | |
handler.longPress.subscribe((pointerEvent: PointerEvent) => { | |
originalHandler(pointerEvent); | |
}); | |
this.manager.getZone().runOutsideAngular(() => { | |
element.addEventListener(name, originalHandler); | |
}); | |
return () => { | |
handler.destroy(); | |
element.removeEventListener(name, originalHandler); | |
handler = null; | |
}; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
Directive, | |
Input, | |
Output, | |
EventEmitter,OnInit, OnDestroy, ElementRef | |
} from '@angular/core'; | |
import {LongPressEventHandler} from '../events/long-press-event-handler'; | |
@Directive({ | |
selector: '[(longPress)],[longPress],longPress,[(longPressEnd)],[longPressEnd],longPressEnd' | |
}) | |
export class LongPressDirective implements OnInit, OnDestroy { | |
private eventHandler: LongPressEventHandler; | |
@Input() duration: number = 250; | |
@Output() longPress: EventEmitter<any>; | |
@Output() longPressing: EventEmitter<any>; | |
@Output() longPressEnd: EventEmitter<any>; | |
constructor( | |
private _elementRef: ElementRef | |
) { | |
this.eventHandler = new LongPressEventHandler(this._elementRef.nativeElement); | |
this.duration = this.eventHandler.duration; | |
this.longPress = this.eventHandler.longPress; | |
this.longPressing = this.eventHandler.longPressing; | |
this.longPressEnd = this.eventHandler.longPressEnd; | |
} | |
ngOnInit(): void | |
{ | |
} | |
ngOnDestroy(): void | |
{ | |
this.eventHandler.destroy(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {EventEmitter} from '@angular/core'; | |
var DOWN_EVENT = 'pointerdown'; | |
var MOVE_EVENT = 'pointermove'; | |
var UP_EVENT = 'pointerup'; | |
var CANCEL_EVENT = 'pointercancel'; | |
if (!('PointerEvent' in window)) { | |
DOWN_EVENT = 'touchstart'; | |
MOVE_EVENT = 'touchmove'; | |
UP_EVENT = 'touchend'; | |
CANCEL_EVENT = 'touchcancel'; | |
} | |
export class LongPressEventHandler { | |
public duration: number = 250; | |
public longPress: EventEmitter<any> = new EventEmitter(); | |
public longPressing: EventEmitter<any> = new EventEmitter(); | |
public longPressEnd: EventEmitter<any> = new EventEmitter(); | |
private _destroyed = false; | |
private _pressing: boolean; | |
private _longPressingState: boolean; | |
private longPressClickProtectionOn = false; | |
private timeout: any; | |
private mouseX: number = 0; | |
private mouseY: number = 0; | |
private handlers: {[eventName: string]: (event: any) => void} = {}; | |
private document: Document | HTMLElement; | |
constructor( | |
private element: HTMLElement | |
) { | |
element.classList.add('no-select'); | |
if (document) { | |
this.document = document; | |
} else { | |
this.document = element; | |
} | |
this.bindEventHandlers(); | |
} | |
private get pressing(): boolean { | |
return this._pressing; | |
} | |
private set pressing(flag: boolean) | |
{ | |
this._pressing = flag; | |
if (flag) { | |
this.element.classList.add('press'); | |
} else { | |
this.element.classList.remove('press'); | |
} | |
} | |
private get longPressingState(): boolean { | |
return this._longPressingState; | |
} | |
private set longPressingState(flag: boolean) { | |
this._longPressingState = flag; | |
if (flag) { | |
this.element.classList.add('longpress'); | |
} else { | |
this.element.classList.remove('longpress'); | |
} | |
} | |
private bindEventHandlers(): void | |
{ | |
this.handlers = { | |
down: (event) => this.onMouseDown(event), | |
move: (event) => this.onMouseMove(event), | |
up: (event) => this.onMouseUp(event) | |
}; | |
this.element.addEventListener(CANCEL_EVENT, this.handlers.up, false); | |
this.element.addEventListener(DOWN_EVENT, this.handlers.down, false); | |
this.element.addEventListener(MOVE_EVENT, this.handlers.move, false); | |
} | |
private getEventXY(event: PointerEvent | TouchEvent): {x: number, y: number} | |
{ | |
if (event instanceof TouchEvent) { | |
return { x: event.touches[0].clientX, y: event.touches[0].clientY }; | |
} else { | |
return { x: event.clientX, y: event.clientY }; | |
} | |
} | |
private onMouseDown(event: PointerEvent | TouchEvent) { | |
// don't do right/middle clicks | |
if (!(event instanceof TouchEvent) && event.which !== 1) { | |
return; | |
} | |
const mousePos = this.getEventXY(event); | |
this.mouseX = mousePos.x; | |
this.mouseY = mousePos.y; | |
this.pressing = true; | |
this.longPressingState = false; | |
this.document.addEventListener(UP_EVENT, this.handlers.up, false); | |
this.timeout = setTimeout(() => { | |
this.longPressingState = true; | |
this.longPress.emit(event); | |
this.loop(event); | |
this.document.addEventListener('click', this.onBodyClick.bind(this), { capture: true, once: true }); | |
this.longPressClickProtectionOn = true; | |
}, this.duration); | |
this.loop(event); | |
} | |
private onMouseMove(event: PointerEvent | TouchEvent) { | |
if (this.pressing && !this.longPressingState) { | |
const mousePos = this.getEventXY(event); | |
const xThres = Math.abs(mousePos.x - this.mouseX) > 5; | |
const yThres = Math.abs(mousePos.y - this.mouseY) > 5; | |
if (xThres || yThres) { | |
this.endPress(); | |
} | |
} | |
} | |
private onMouseUp($event: PointerEvent | TouchEvent) { | |
if (this.longPressingState) { | |
$event.stopImmediatePropagation(); | |
} | |
this.document.removeEventListener(UP_EVENT, this.handlers.up, false); | |
this.endPress(); | |
} | |
private onBodyClick($event: MouseEvent): void | |
{ | |
if (this._destroyed) { | |
return; | |
} | |
if (this.longPressClickProtectionOn) { | |
$event.preventDefault(); | |
$event.stopImmediatePropagation(); | |
} | |
} | |
loop(event) { | |
if (this.longPressingState) { | |
this.timeout = setTimeout(() => { | |
this.longPressing.emit(event); | |
this.loop(event); | |
}, 50); | |
} | |
} | |
endPress() { | |
clearTimeout(this.timeout); | |
this.longPressingState = false; | |
this.pressing = false; | |
this.longPressEnd.emit(true); | |
setTimeout(_ => { | |
this.longPressClickProtectionOn = false; | |
}, 100); | |
} | |
destroy(): void | |
{ | |
this._destroyed = true; | |
this.element.removeEventListener(CANCEL_EVENT, this.handlers.up, false); | |
this.element.removeEventListener(DOWN_EVENT, this.handlers.down, false); | |
this.element.removeEventListener(MOVE_EVENT, this.handlers.move, false); | |
this.document.removeEventListener(UP_EVENT, this.handlers.up, false); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Implementing all the above will give you the ability to use:
@HostListener('longpress')
in you're custom components.(longPress) (longPressing) (longPressEnd)
attributes within your html[duration]
attribute in your html to set longpress duration, defaults to 250ms