Skip to content

Cards trigger on system navigation gestures (swipe-up to close app) — touchcancel/pointercancel fires actions like a normal release #2580

Description

@Gschirr

Describe the bug

Bubble Card buttons (button_type: switch / state) and sliders placed near the bottom of a dashboard are triggered accidentally when closing the Home Assistant companion app via Android gesture navigation (swipe up from the bottom edge). Depending on which card sits at the bottom, this toggles a fan/switch, changes a slider value, or opens a more-info dialog — reproducibly, on every few app-close swipes.

To reproduce

  1. Android phone with gesture navigation, HA companion app
  2. Place any interactive Bubble Card (switch button, state button, or slider) as the last card of a view
  3. Swipe up from the bottom edge to close the app
  4. Reopen the app: the entity has been toggled / the slider moved / a more-info dialog is open

Expected behavior

When the OS takes over a touch as a system gesture, the browser/WebView cancels the touch (touchcancel / pointercancel). A cancelled touch should never fire the card's action.

For reference, Home Assistant's own frontend fixed exactly this class of bug for built-in cards in home-assistant/frontend#22852 (shipped late 2024): actions are suppressed when the touch is cancelled. Bubble Card's custom touch handling does not do this, which is why dashboards built from Bubble Cards are still affected on current HA versions.

Root cause (from reading v3.2.0 source)

In src/tools/tap-actions.js, handlePointerDown() registers the same endHandler for pointerup, touchend, pointercancel and touchcancel (lines ~202–208). ActionHandler.handleEnd(e) never checks whether the event is a cancel — a cancelled touch takes the exact same code path as a normal release and fires the tap action (line ~486). When Android/iOS recognizes a system navigation gesture it cancels the touch, so the tap fires anyway. The hasMoved/isScrolling guards don't help because the OS takes over before enough touchmove events reach the page.

The slider (src/components/slider/create.js) does handle cancels in onPointerCancel(), but deliberately ignores cancels within the first 150 ms of a drag (cancelGracePeriod, iOS workaround) — a quick system swipe is cancelled exactly inside that window, which is why sliders leak too (matches #2287).

Suggested fix

Treat cancel events as an abort in handleEnd, mirroring HA's action-handler behavior since home-assistant/frontend#22852:

handleEnd(e) {
  if (e.type === 'touchend' || e.type === 'touchcancel') {
    this.justEndedTouchEventTime = Date.now();
  }
  if (!this.interactionStarted) return;

  // A cancelled interaction means the browser/OS took over the gesture
  // (system navigation swipe, scroll take-over, app switch). Never fire
  // tap/double-tap/hold from a cancelled touch.
  if (e.type === 'touchcancel' || e.type === 'pointercancel') {
    clearTimeout(this.holdTimeout);
    clearTimeout(this.tapTimeout);
    this.holdTimeout = null;
    this.tapTimeout = null;
    document.removeEventListener('pointermove', this.pointerMoveListener);
    document.removeEventListener('touchmove', this.touchMoveListener);
    this.interactionStarted = false;
    this.holdFired = false;
    stopHoldIndicator();
    return;
  }
  // ... existing logic unchanged

Note on iOS: Safari can fire a spurious pointercancel when it initially treats a touch as a potential scroll — aborting the tap in that case is the correct/expected behavior (HA's own cards behave the same), so no grace period is needed for taps. For the slider, the fix likely means committing no value change when the cancel arrives during the grace period rather than ignoring the cancel entirely — happy to discuss.

I'm happy to open a PR for the tap-actions.js change if this direction is acceptable.

Previous reports

This was reported before as #2287 (iOS, swipe-up-to-Home triggering sliders) and #1923 (Android), but both were closed by the stale bot without a fix. The issue still reproduces on v3.2.0.

Environment

  • Bubble Card v3.2.0 (HACS)
  • Home Assistant 2026.8.1
  • Companion App for Android (current release), Android with gesture navigation

Workaround

Setting tap_action: {action: none} (both root-level and in button_action) on the bottom-most cards makes that zone inert; #1923 also describes a pointer-events overlay hack. Both are layout workarounds, not fixes.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions