Skip to content

Instantly share code, notes, and snippets.

@Rkokie
Last active February 28, 2020 10:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Rkokie/8bf117683dcd42c8ef4f7675a7e1e971 to your computer and use it in GitHub Desktop.
Save Rkokie/8bf117683dcd42c8ef4f7675a7e1e971 to your computer and use it in GitHub Desktop.
Angular long press events
...
providers: {
{provide: EVENT_MANAGER_PLUGINS, useClass: LongPressEventPlugin, multi: true}
}
...
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;
};
}
}
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();
}
}
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);
}
}
@Rkokie
Copy link
Author

Rkokie commented Feb 10, 2020

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment