JS: Touch Navigation

JavaScript generator for touch-to-nav conversion: swipe, drag,

generate_touch_nav_js

Converts touch gestures on the card stack container into navigation button clicks and scale adjustments. Uses the Pointer Events API with setPointerCapture so that events survive HTMX OOB DOM swaps mid-drag.

Single-finger gestures:

  • Simple swipe — a quick flick that doesn’t cover a full card height triggers one navigation step.
  • Touch-and-drag — holding the finger down and dragging triggers one step each time the finger travels one focused-slot-height of distance. After direction lock, setPointerCapture pins events to the card stack element so DOM mutations underneath don’t break tracking.
  • Momentum — a fast swipe continues navigating after the finger lifts, decelerating via an exponential friction model.

Two-finger gestures:

  • Pinch-to-zoom — maps to the card stack’s increaseScale / decreaseScale functions.

Supports mode-based disabling via disable_in_modes.


source

generate_touch_nav_js


def generate_touch_nav_js(
    ids:CardStackHtmlIds, # HTML IDs for this card stack instance
    button_ids:CardStackButtonIds, # Button IDs for navigation triggers
    disable_in_modes:Tuple=(), # Mode names where touch nav is suppressed
    zone_id:str='', # Keyboard zone ID to activate on touch interaction
)->str: # JavaScript code fragment for touch navigation

Generate JS for touch gesture to navigation conversion.

# Test touch nav JS generation
from cjm_fasthtml_card_stack.core.html_ids import CardStackHtmlIds
from cjm_fasthtml_card_stack.core.button_ids import CardStackButtonIds

ids = CardStackHtmlIds(prefix="cs0")
btn = CardStackButtonIds(prefix="cs0")
js = generate_touch_nav_js(ids, btn)

# Element IDs and button IDs present
assert ids.card_stack in js
assert btn.nav_up in js
assert btn.nav_down in js

# No mode check when no modes specified
assert "isTouchDisabled" not in js

# Setup function exposed on namespace
assert "ns._setupTouchNav" in js

# Pointer Events API (not Touch Events)
assert "pointerdown" in js
assert "pointermove" in js
assert "pointerup" in js
assert "pointercancel" in js
assert "pointerType" in js
assert "setPointerCapture" in js

# Pointer tracking map for multi-touch
assert "pointers" in js
assert "primaryId" in js

# Pinch-to-zoom maps to scale controls
assert "ns.increaseScale" in js
assert "ns.decreaseScale" in js

# Momentum with requestAnimationFrame and friction
assert "_TOUCH_MOMENTUM_FRICTION" in js
assert "requestAnimationFrame" in js
assert "momentumTick" in js

# Direction locking (horizontal vs vertical)
assert "deltaX" in js

# Step distance from focused slot
assert "_getTouchStepDistance" in js
assert "viewport-slot" in js

# Constants inlined
assert "_TOUCH_SWIPE_THRESHOLD" in js
assert "_TOUCH_PINCH_THRESHOLD" in js
assert "_TOUCH_VEL_SAMPLES" in js

# Velocity history buffer
assert "history" in js

# Uses AbortController for clean listener teardown/re-setup
assert "AbortController" in js
assert "_touchNavAbort" in js
assert "signal" in js

# No zone activation when zone_id not provided
assert "setActiveZone" not in js

# Zone activation when zone_id provided
js_zone = generate_touch_nav_js(ids, btn, zone_id="my-card-stack")
assert "setActiveZone('my-card-stack')" in js_zone

print("Touch nav JS basic tests passed!")
Touch nav JS basic tests passed!
# Test with disabled modes
js_modes = generate_touch_nav_js(ids, btn, disable_in_modes=("split", "edit"))
assert "isTouchDisabled" in js_modes
assert "'split'" in js_modes
assert "'edit'" in js_modes
# Momentum loop also checks mode
assert "isTouchDisabled" in js_modes
print("Touch nav JS mode disabling tests passed!")
Touch nav JS mode disabling tests passed!