On `pointerdown`, record the start time and use `requestAnimationFrame` to track elapsed time while filling a visual progress indicator (a ring or a fill). Once progress hits 100% (typically 500–1000ms), open the menu.
The cancellation logic is the important part: `pointerup`, `pointercancel`, or moving past a small distance threshold while held (which usually means the user is scrolling, not pressing) must immediately stop the timer and reset progress to zero — otherwise a user trying to scroll gets an unwanted menu.
On touch, this collides with the browser's own long-press (text selection, context menu), so suppress the default with `touch-action: none` or `-webkit-touch-callout: none`.
When to use
Good for hiding secondary actions — a reaction picker in a chat app, entering icon-edit mode. Don't make it the only way in; discoverability is low, so pair it with another path.