I'm trying to get a single Observable that can distinguish between a regular click (0-100ms) and a long press (exactly at 1000ms).
pseudocode
mouseup
between 0 - 100ms -> emit clickmouseup
sometime after the long press eventVisual representation
time diagram
reproduction
https://codesandbox.io/s/long-press-p4el0?file=/src/index.ts
So far I was able to get close using:
interface UIEventResponse {
type: UIEventType,
event: UIEvent
}
type UIEventType = 'click' | 'longPress'
import { fromEvent, merge, Observable, race, timer } from "rxjs";
import { map, mergeMap, switchMap, take, takeUntil } from "rxjs/operators";
const clickTimeout = 100
const longPressTimeout = 1000
const mouseDown$ = fromEvent<MouseEvent>(window, "mousedown");
const mouseUp$ = fromEvent<MouseEvent>(window, "mouseup");
const click1$ = merge(mouseDown$).pipe(
switchMap((event) => {
return race(
timer(longPressTimeout).pipe(mapTo(true)),
mouseUp$.pipe(mapTo(false))
);
})
);
However, if the user keeps the button pressed until before the longPress event can be emitted, it is still emitting a click event.
So I want to restrict the click event to 0-100ms after the mousedown
. If the user holds for 1 second it should immediately emit a long press. My current code only works for the regular click but the long press afterwards is ignored:
const click2$: Observable<UIEventResponse> = mouseDown$.pipe(
switchMap((event) => {
return race<UIEventResponse>(
timer(longPressTimeout).pipe(
mapTo({
type: "longPress",
event
})
),
mouseUp$.pipe(
takeUntil(timer(clickTimeout)),
mapTo({
type: "click",
event
})
)
);
})
);
I figure this is because the takeUntil
in the second stream of the race
unsubscribes the race
. How can I prevent the mouseup
event from ignoring the first stream in the race
and thus still have the long press event emitted?
Any help is greatly appreciated.
Thanks to @Giovanni Londero for pointing me in the right direction and helping me find a solution that works for me!
const click$: Observable<UIEventResponse> = mouseDown$.pipe(
switchMap((event) => {
return race<UIEventResponse>(
timer(longPressTimeout).pipe(
mapTo({
type: "longPress",
event
})
),
mouseUp$.pipe(
mapTo({
type: "click",
event
}),
timeoutWith(clickTimeout, mouseUp$.pipe(mapTo(undefined)))
)
);
}),
filter((val) => !!val)
);
I'm happy to get some recommendations on how to improve this code.