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.
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 generationfrom cjm_fasthtml_card_stack.core.html_ids import CardStackHtmlIdsfrom cjm_fasthtml_card_stack.core.button_ids import CardStackButtonIdsids = CardStackHtmlIds(prefix="cs0")btn = CardStackButtonIds(prefix="cs0")js = generate_touch_nav_js(ids, btn)# Element IDs and button IDs presentassert ids.card_stack in jsassert btn.nav_up in jsassert btn.nav_down in js# No mode check when no modes specifiedassert"isTouchDisabled"notin js# Setup function exposed on namespaceassert"ns._setupTouchNav"in js# Pointer Events API (not Touch Events)assert"pointerdown"in jsassert"pointermove"in jsassert"pointerup"in jsassert"pointercancel"in jsassert"pointerType"in jsassert"setPointerCapture"in js# Pointer tracking map for multi-touchassert"pointers"in jsassert"primaryId"in js# Pinch-to-zoom maps to scale controlsassert"ns.increaseScale"in jsassert"ns.decreaseScale"in js# Momentum with requestAnimationFrame and frictionassert"_TOUCH_MOMENTUM_FRICTION"in jsassert"requestAnimationFrame"in jsassert"momentumTick"in js# Direction locking (horizontal vs vertical)assert"deltaX"in js# Step distance from focused slotassert"_getTouchStepDistance"in jsassert"viewport-slot"in js# Constants inlinedassert"_TOUCH_SWIPE_THRESHOLD"in jsassert"_TOUCH_PINCH_THRESHOLD"in jsassert"_TOUCH_VEL_SAMPLES"in js# Velocity history bufferassert"history"in js# Uses AbortController for clean listener teardown/re-setupassert"AbortController"in jsassert"_touchNavAbort"in jsassert"signal"in js# No zone activation when zone_id not providedassert"setActiveZone"notin js# Zone activation when zone_id providedjs_zone = generate_touch_nav_js(ids, btn, zone_id="my-card-stack")assert"setActiveZone('my-card-stack')"in js_zoneprint("Touch nav JS basic tests passed!")
Touch nav JS basic tests passed!
# Test with disabled modesjs_modes = generate_touch_nav_js(ids, btn, disable_in_modes=("split", "edit"))assert"isTouchDisabled"in js_modesassert"'split'"in js_modesassert"'edit'"in js_modes# Momentum loop also checks modeassert"isTouchDisabled"in js_modesprint("Touch nav JS mode disabling tests passed!")