A long press is a secondary action: show a context menu, select an element, open details. The mobile equivalent of right-click. Start a timer on pointer down, cancel it on pointer up if it's early.
The problem it solves
Imperative setTimeouts in pointer handlers quickly become a race between: an ordinary click, a long press, a context menu, and destroy. The state is hard to track, and bugs like "one tap did both actions" are very common.
Operators and why they matter
switchMap(() => timer(N)) — on each pointer down, create a timer. A new down cancels the previous one.
timer(N) — the hold duration.
takeUntil(pointerUp$) — release before the timer fires and the long press does NOT trigger.
mapTo('long-press') — success = the command.
Gotchas
delay without cancellation — the long press fires after an ordinary short tap.
exhaustMap — a second press is ignored until the first timer finishes. The user thinks the UI is unresponsive.
Coordinate the long press with the ordinary click. If both are registered, you need to decide whose event to apply.
What you get
The secondary action fires only on a real hold. Short taps stay ordinary clicks.