Skip to content

fix: reset gesture state on touchcancel - #834

Open
NemeZZiZZ wants to merge 1 commit into
klinecharts:mainfrom
NemeZZiZZ:fix/touchcancel-gesture-reset
Open

fix: reset gesture state on touchcancel#834
NemeZZiZZ wants to merge 1 commit into
klinecharts:mainfrom
NemeZZiZZ:fix/touchcancel-gesture-reset

Conversation

@NemeZZiZZ

Copy link
Copy Markdown
Contributor

Problem

When the system cancels an in-progress touch gesture (incoming call overlay, notification
shade, palm rejection, browser gesture takeover), a touchcancel event is fired instead of
touchend. Currently it is almost unhandled:

  • src/common/EventHandler.ts:692 — a touchcancel listener on the target only clears the
    long-tap timeout.
  • The root-level touchmove/touchend listeners (subscribed on touch start, unsubscribed
    on touch end) are not removed on cancel.
  • Gesture state (_activeTouchId, _touchMoveStartCoordinate) is not reset, and the chart
    level (Event) never learns the gesture is over.

After a cancelled gesture the handler stays in the "finger down" state: the next touch has
to untangle stale listeners/state, and dragging/scrolling state started on the chart
(_startScrollCoordinate, axis scale state, crosshair) is left dangling — the chart can
keep behaving as if the finger is still down.

Fix

Handle touchcancel symmetrically to touchend:

src/common/EventHandler.ts

  • Add touchCancelEvent to the EventHandler interface.
  • Add _touchCancelHandler: resolves the active touch, resets _activeTouchId,
    _lastTouchEventTimeStamp, _touchMoveStartCoordinate, unsubscribes the root touch
    listeners, marks the gesture so it cannot resolve into a tap, and dispatches
    touchCancelEvent.
  • Subscribe/unsubscribe touchcancel together with touchmove/touchend on the root
    element ({ passive: false }).

src/Event.ts

  • Add touchCancelEvent(e): sends mouseUpEvent to the widget under the finger (so
    widgets that started a pressed interaction release it), then unconditionally resets the
    scroll/scale gesture state (same set as touchEndEvent) plus the touch-specific state
    (_touchCoordinate, _touchCancelCrosshair, _touchZoomed) and clears the crosshair.

Unlike touchEndEvent, the state reset is unconditional: on cancel there is no
meaningful "widget under the finger" semantics to gate on, and the point of the handler is
to guarantee a clean slate.

Verification

  • pnpm code-lint — pass (154 files, no fixes)
  • pnpm type-check — pass
  • pnpm build-esm — pass
  • Manual: start a drag on a touch device, trigger a system cancel (e.g. notification shade
    pull) mid-gesture — afterwards the chart responds to the next touch immediately and no
    crosshair/scroll state is stuck; on main the same sequence leaves the gesture state
    dangling.

Notes

  • Fling inertia is not started on cancel (it only starts in touchEndEvent), so no change
    is needed there.
  • The existing target-level touchcancel listener for the long-tap timeout is kept.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant