client: fall back to touch events for long-press (Pointer Events not on old WebKit)

Fixes half of #70 - the long-tap/count-picker interaction.

Pointer Events (onPointerDown/Up/Cancel) only shipped in Safari 13/iOS
13. The same class of old-enough-WebKit kiosk device #66/#68 are
already about doesn't fire them at all, so the long-press timer never
starts there even though plain clicks (universally supported) keep
working - which matches the report exactly: add-one-by-tapping works
on the tablet, the long-press count picker doesn't, and both work fine
on a laptop's modern browser.

Added onTouchStart/onTouchEnd/onTouchCancel alongside the existing
pointer handlers, wired to the same start/cancel functions - safe on
browsers that fire both pointer and touch events for one touch, since
starting/cancelling the timer is already idempotent.

Not touching the second half of the report (negative amounts for
cart-only drinks) - genuinely ambiguous relayed text with several
plausible readings that imply different UI, asked mara for
clarification on the issue instead of guessing.

pnpm --filter client typecheck and build both clean.
This commit is contained in:
iris 2026-08-03 16:35:26 +02:00
commit 0ae25ecab7

View file

@ -86,6 +86,16 @@ export function Sale({ config, onChangeBar }: Props) {
cartUuidRef.current = null;
}
// Pointer Events (onPointerDown/Up/Cancel below) only shipped in Safari
// 13/iOS 13 — the exact kind of old-enough-WebKit kiosk device #66/#68
// are already about never fires them at all, so the long-press timer
// this drives never starts on that class of device even though plain
// clicks (universally supported) keep working, which is why "add one"
// works on the tablet but the long-press count picker doesn't. The
// touch handlers alongside the pointer ones are the broad-support
// fallback; both driving the same start/cancel functions is safe
// because a device that fires both (most modern browsers do, for a
// single touch) just restarts/re-cancels the same idempotent timer.
function startLongPress(d: Drink) {
longPressFired.current = false;
cancelLongPress();
@ -160,6 +170,9 @@ export function Sale({ config, onChangeBar }: Props) {
onPointerUp={cancelLongPress}
onPointerLeave={cancelLongPress}
onPointerCancel={cancelLongPress}
onTouchStart={() => startLongPress(d)}
onTouchEnd={cancelLongPress}
onTouchCancel={cancelLongPress}
onContextMenu={e => e.preventDefault()}
onClick={() => handleDrinkClick(d)}
>