Home > Mobile >  Recreate contextmenu event in RxJS
Recreate contextmenu event in RxJS

Time:05-20

I want to recreate the contextmenu event from touch events in RxJS because iOS does not support the contextmenu event on touch.

In plain language, this observable should emit when:

  • the touchstart event occurred
  • for 2000ms (basically long-press)
  • followed by touchend

It should not emit when:

  • the touchend occurs in less than 2000ms
  • the touchstart event is followed by touchmove event

I can't figure out how to skip if the touchend occurs sooner or if the touchstart is followed by touchmove. This is what I've so far:

const touchStart$ = fromEvent<TouchEvent>(this.el.nativeElement, "touchstart");
const touchEnd$ = fromEvent<TouchEvent>(this.el.nativeElement, "touchend");
const touchMove$ = fromEvent<TouchEvent>(this.el.nativeElement, "touchmove");
const contextmenu$ = touchStart$.pipe(
      switchMap(event => touchEnd$.pipe(mapTo(event))),
      switchMap(event => timer(2000).pipe(mapTo(event), takeUntil(merge(touchEnd$, touchMove$))))
);

contextmenu$.subscribe($event => {
      console.log("CONTEXTMENU EVENT HAPPENED");
});

CodePudding user response:

Your solution can be simplified a little bit. The inner mapTo isn't necessary, if you don't reuse the name event, you can just use a single mapTo at the end of your pipe. Also, you may want to use take(1) instead of takeUntil(touchMove$) on the inner switchMap since you want to end the stream after the first emission:

  const longPress$ = this.touchStart$.pipe(
    switchMap(event => timer(2000).pipe(
      takeUntil(merge(touchMove$, touchEnd$)),
      switchMap(() => touchEnd$.pipe(take(1))),
      mapTo(event)
    ))
  );

I'm assuming you got the behavior you wanted, but I think normally a longpress will fire when duration passes; i.e. we needn't wait for the touchend event. If that were the case, it gets even simpler:

  const longPress$ = this.touchStart$.pipe(
    switchMap(event => timer(2000).pipe(
      takeUntil(merge(touchMove$, touchEnd$)),
      mapTo(event)
    ))
  );

Here are a couple StackBlitzes with colorful logging: Original | Simplified

CodePudding user response:

I came up with a possible solution:

const touchStart$ = fromEvent<TouchEvent>(this.el.nativeElement, "touchstart");
const touchEnd$ = fromEvent<TouchEvent>(this.el.nativeElement, "touchend");
const touchMove$ = fromEvent<TouchEvent>(this.el.nativeElement, "touchmove");
const contextmenu$ = touchStart$.pipe(
    switchMap(event =>
        timer(2000).pipe(
           mapTo(event),
           takeUntil(merge(touchMove$, touchEnd$)),
           switchMap(event => touchEnd$.pipe(mapTo(event), takeUntil(touchMove$)))
        )
    )
);

contextmenu$.subscribe($event => {
    console.log("CONTEXT MENU EVENT HAPPENED");
});
  • Related