Gestures

Description

Fluent gesture recognition extensions for any IComponent

GestureExtensions adds tap, double-tap, long-press, pan, pinch, and rotate recognition to any IComponent through fluent extension methods. A single GestureRecognizer is attached per element and shared across all gesture handlers registered on that component, so the DOM listener overhead is fixed regardless of how many gesture types you register.

Recognition is built on the unified Pointer Events API (pointerdown / pointermove / pointerup with setPointerCapture), which covers mouse, touch, and stylus input without separate touch-event handling.

Built-in thresholds:

  • Tap move tolerance — 10 px. Movement within this radius does not disqualify a tap.
  • Double-tap window — 300 ms between two taps.
  • Long-press threshold — 500 ms of stationary contact.

When a double-tap handler is registered alongside a single-tap handler, single taps are delayed by the double-tap window to allow a follow-up tap to upgrade them.

Pan, pinch, and rotate handlers set touch-action: none on the element to prevent the browser from claiming touch events for native scrolling or zooming.

All handlers receive a GestureState snapshot. The same instance is reused per recognizer and refreshed before each callback — do not hold a reference to it across events.

GestureState fields

Field Type Description
Component IComponent The component the gesture was recognised on.
Phase GesturePhase Start, Move, or End. Always End for tap and long-press.
X / Y double Current pointer (or two-pointer centroid) position in client coordinates.
DeltaX / DeltaY double Movement since the previous event. Pan only; zero for pinch/rotate.
OffsetX / OffsetY double Cumulative movement since the gesture started.
Scale double Cumulative scale factor since pinch started (1 = no change).
ScaleDelta double Scale factor change since the previous pinch event.
Rotation double Cumulative rotation in degrees since rotate started.
RotationDelta double Rotation change in degrees since the previous rotate event.
PointerCount int Number of pointers currently down on the element.
Event Event The underlying DOM pointer event, if any. null for long-press.

Usage

// Tap
someComponent
    .OnTapped(state => Console.WriteLine($"tapped at {state.X}, {state.Y}"))
    .OnDoubleTapped(() => Console.WriteLine("double-tap"));

// Long press
someComponent.OnLongPress(state => ShowContextMenu(state.X, state.Y));

// Pan — move a draggable element
var offsetX = 0.0;
var offsetY = 0.0;

draggable.OnPan(state =>
{
    if (state.Phase == GesturePhase.Start)
    {
        offsetX = 0;
        offsetY = 0;
    }
    offsetX = state.OffsetX;
    offsetY = state.OffsetY;
    UpdatePosition(offsetX, offsetY);
});

// Pinch — zoom a canvas
canvas.OnPinch(state =>
{
    ApplyScale(state.Scale);
});

// Rotate — spin an element
rotateable.OnRotate(state =>
{
    ApplyRotation(state.Rotation);
});

API reference

enum

GesturePhase

public enum GesturePhase

Identifies the phase of a continuous gesture (pan / pinch / rotate).

Namespace
Tesserae

Values

NameDescription
StartThe gesture has just started (movement crossed the threshold, or a second finger landed).
MoveThe gesture is in progress.
EndThe gesture has ended (the last relevant pointer was lifted or cancelled).
Value
GesturePhase.Start
Start

The gesture has just started (movement crossed the threshold, or a second finger landed).

Value
GesturePhase.Move
Move

The gesture is in progress.

Value
GesturePhase.End
End

The gesture has ended (the last relevant pointer was lifted or cancelled).

class

GestureState

public sealed class GestureState

Immutable-ish snapshot of a recognised gesture, handed to gesture callbacks. A single instance is reused per recogniser and its fields are refreshed before every callback, so do not hold a reference across events.

Namespace
Tesserae

Properties

NameDescription
ComponentThe component the gesture was recognised on.
PhaseThe current phase of a continuous gesture (always End for taps / long-press).
XCurrent pointer (or two-pointer centroid) X position in client coordinates.
YCurrent pointer (or two-pointer centroid) Y position in client coordinates.
DeltaXHorizontal movement since the previous event of this gesture.
DeltaYVertical movement since the previous event of this gesture.
OffsetXCumulative horizontal movement since the gesture started.
OffsetYCumulative vertical movement since the gesture started.
ScaleCumulative scale factor since the pinch started (1 = no change).
ScaleDeltaScale factor change since the previous pinch event (1 = no change).
RotationCumulative rotation in degrees since the rotate gesture started.
RotationDeltaRotation change in degrees since the previous rotate event.
PointerCountNumber of pointers currently down on the element.
EventThe underlying DOM pointer event that produced this state, if any.
Property
GestureState.Component
public IComponent Component { get; internal set; }

The component the gesture was recognised on.

Property
GestureState.Phase
public GesturePhase Phase { get; internal set; }

The current phase of a continuous gesture (always End for taps / long-press).

Property
GestureState.X
public double X { get; internal set; }

Current pointer (or two-pointer centroid) X position in client coordinates.

Property
GestureState.Y
public double Y { get; internal set; }

Current pointer (or two-pointer centroid) Y position in client coordinates.

Property
GestureState.DeltaX
public double DeltaX { get; internal set; }

Horizontal movement since the previous event of this gesture.

Property
GestureState.DeltaY
public double DeltaY { get; internal set; }

Vertical movement since the previous event of this gesture.

Property
GestureState.OffsetX
public double OffsetX { get; internal set; }

Cumulative horizontal movement since the gesture started.

Property
GestureState.OffsetY
public double OffsetY { get; internal set; }

Cumulative vertical movement since the gesture started.

Property
GestureState.Scale
public double Scale { get; internal set; }

Cumulative scale factor since the pinch started (1 = no change).

Property
GestureState.ScaleDelta
public double ScaleDelta { get; internal set; }

Scale factor change since the previous pinch event (1 = no change).

Property
GestureState.Rotation
public double Rotation { get; internal set; }

Cumulative rotation in degrees since the rotate gesture started.

Property
GestureState.RotationDelta
public double RotationDelta { get; internal set; }

Rotation change in degrees since the previous rotate event.

Property
GestureState.PointerCount
public int PointerCount { get; internal set; }

Number of pointers currently down on the element.

Property
GestureState.Event
public Event Event { get; internal set; }

The underlying DOM pointer event that produced this state, if any.

class

GestureRecognizer

public sealed class GestureRecognizer

Higher-level pointer/touch gesture recognition layered on top of the unified Pointer Events API (pointerdown/pointermove/pointerup + setPointerCapture). A single recogniser is attached per element and shared by every gesture handler registered through GestureExtensions.

Namespace
Tesserae

Methods

NameDescription
ForReturns the recogniser attached to the component's element, creating and caching one on the element on first use so multiple gesture handlers share a single set of DOM listeners.
OnTappedRegisters a tap (quick press-and-release without movement) handler.
OnDoubleTappedRegisters a double-tap handler. When present, single taps are delayed to disambiguate.
OnLongPressRegisters a long-press handler (stationary press held past the long-press threshold).
OnPanRegisters a pan handler. Enables touch-action: none so touch panning isn't hijacked by scrolling.
OnPinchRegisters a pinch (two-finger scale) handler. Enables touch-action: none.
OnRotateRegisters a rotate (two-finger angle) handler. Enables touch-action: none.
Method
GestureRecognizer.For
public static GestureRecognizer For(IComponent component)

Returns the recogniser attached to the component's element, creating and caching one on the element on first use so multiple gesture handlers share a single set of DOM listeners.

Method
GestureRecognizer.OnTapped
public GestureRecognizer OnTapped(Action<GestureState> handler)

Registers a tap (quick press-and-release without movement) handler.

Method
GestureRecognizer.OnDoubleTapped
public GestureRecognizer OnDoubleTapped(Action<GestureState> handler)

Registers a double-tap handler. When present, single taps are delayed to disambiguate.

Method
GestureRecognizer.OnLongPress
public GestureRecognizer OnLongPress(Action<GestureState> handler)

Registers a long-press handler (stationary press held past the long-press threshold).

Method
GestureRecognizer.OnPan
public GestureRecognizer OnPan(Action<GestureState> handler)

Registers a pan handler. Enables touch-action: none so touch panning isn't hijacked by scrolling.

Method
GestureRecognizer.OnPinch
public GestureRecognizer OnPinch(Action<GestureState> handler)

Registers a pinch (two-finger scale) handler. Enables touch-action: none.

Method
GestureRecognizer.OnRotate
public GestureRecognizer OnRotate(Action<GestureState> handler)

Registers a rotate (two-finger angle) handler. Enables touch-action: none.

class

GestureExtensions

public static class GestureExtensions

Fluent gesture extensions on IComponent. These layer tap, double-tap, long-press, pan, pinch and rotate recognition on top of the native pointer events already exposed by ComponentBase{T, THTML}. All handlers receive a GestureState snapshot; thresholds (tap tolerance, double-tap timing, long-press duration) are sensible defaults shared by the per-element GestureRecognizer.

Namespace
Tesserae

Methods

NameDescription
OnTappedRecognises a tap (quick press-and-release without movement).
OnDoubleTappedRecognises a double-tap. Registering this delays single taps to disambiguate them.
OnLongPressRecognises a long-press (stationary press held past the long-press threshold).
OnPan<T>Recognises a pan / drag. The handler receives per-event deltas (DeltaX/ DeltaY) and the cumulative offset since the gesture started (OffsetX/OffsetY).
OnPinch<T>Recognises a two-finger pinch. The handler receives the cumulative Scale and the per-event ScaleDelta.
OnRotate<T>Recognises a two-finger rotation. The handler receives the cumulative Rotation and the per-event RotationDelta in degrees.
Method
GestureExtensions.OnTapped
Overload
OnTapped<T>(T, Action<GestureState>)Recognises a tap (quick press-and-release without movement).
OnTapped<T>(T, Action)Recognises a tap, invoking a parameterless handler.
OnTapped<T>(T, Action<GestureState>)
public static T OnTapped<T>(this T component, Action<GestureState> handler) where T : IComponent

Recognises a tap (quick press-and-release without movement).

Parameters

component T
handler Action<GestureState>
OnTapped<T>(T, Action)
public static T OnTapped<T>(this T component, Action handler) where T : IComponent

Recognises a tap, invoking a parameterless handler.

Parameters

component T
handler Action
Method
GestureExtensions.OnDoubleTapped
Overload
OnDoubleTapped<T>(T, Action<GestureState>)Recognises a double-tap. Registering this delays single taps to disambiguate them.
OnDoubleTapped<T>(T, Action)Recognises a double-tap, invoking a parameterless handler.
OnDoubleTapped<T>(T, Action<GestureState>)
public static T OnDoubleTapped<T>(this T component, Action<GestureState> handler) where T : IComponent

Recognises a double-tap. Registering this delays single taps to disambiguate them.

Parameters

component T
handler Action<GestureState>
OnDoubleTapped<T>(T, Action)
public static T OnDoubleTapped<T>(this T component, Action handler) where T : IComponent

Recognises a double-tap, invoking a parameterless handler.

Parameters

component T
handler Action
Method
GestureExtensions.OnLongPress
Overload
OnLongPress<T>(T, Action<GestureState>)Recognises a long-press (stationary press held past the long-press threshold).
OnLongPress<T>(T, Action)Recognises a long-press, invoking a parameterless handler.
OnLongPress<T>(T, Action<GestureState>)
public static T OnLongPress<T>(this T component, Action<GestureState> handler) where T : IComponent

Recognises a long-press (stationary press held past the long-press threshold).

Parameters

component T
handler Action<GestureState>
OnLongPress<T>(T, Action)
public static T OnLongPress<T>(this T component, Action handler) where T : IComponent

Recognises a long-press, invoking a parameterless handler.

Parameters

component T
handler Action
Method
GestureExtensions.OnPan<T>
public static T OnPan<T>(this T component, Action<GestureState> handler) where T : IComponent

Recognises a pan / drag. The handler receives per-event deltas (DeltaX/ DeltaY) and the cumulative offset since the gesture started (OffsetX/OffsetY).

Method
GestureExtensions.OnPinch<T>
public static T OnPinch<T>(this T component, Action<GestureState> handler) where T : IComponent

Recognises a two-finger pinch. The handler receives the cumulative Scale and the per-event ScaleDelta.

Method
GestureExtensions.OnRotate<T>
public static T OnRotate<T>(this T component, Action<GestureState> handler) where T : IComponent

Recognises a two-finger rotation. The handler receives the cumulative Rotation and the per-event RotationDelta in degrees.

See also

© 2026 Curiosity. All rights reserved.