import type { MaybeOriginAction, OriginAction } from "../actions/types"; import type { OriginContext } from "../scene/types"; import type { OriginGestureCompletionPredicate, OriginGestureDefinition, OriginGestureDirection, OriginGestureOptions, OriginGestureStart, } from "./types"; /** Directed movement in CSS pixels required before a gesture captures. */ export const DEFAULT_THRESHOLD = 8; /** Ratio by which directed movement must exceed cross-axis movement. */ export const DEFAULT_AXIS_DOMINANCE = 1.15; /** * The single shape the recognizer executes. Builder definitions and legacy * options objects are both normalized into it. */ export interface RecognizerPolicy { readonly direction: OriginGestureDirection; readonly start: OriginGestureStart; readonly threshold: number; readonly axisDominance: number; readonly completion?: OriginGestureCompletionPredicate; readonly action: (context: OriginContext) => MaybeOriginAction; } function isDefinition( value: OriginGestureOptions | OriginGestureDefinition, ): value is OriginGestureDefinition { return "kind" in value && value.kind === "origin-gesture-definition"; } /** Legacy `edge` is measured from the side opposite the movement direction. */ const LEGACY_START_EDGE = { left: "right", right: "left", up: "bottom", down: "top", } as const; function fromLegacyOptions(options: OriginGestureOptions): RecognizerPolicy { return { direction: options.direction, start: options.edge === undefined ? { kind: "anywhere" } : Object.freeze({ kind: "edge", edge: LEGACY_START_EDGE[options.direction], distance: options.edge, }), threshold: options.threshold ?? DEFAULT_THRESHOLD, axisDominance: DEFAULT_AXIS_DOMINANCE, action: options.action, }; } function fromDefinition(definition: OriginGestureDefinition): RecognizerPolicy { return { direction: definition.direction, start: definition.start, threshold: definition.recognition.threshold ?? DEFAULT_THRESHOLD, axisDominance: definition.recognition.axisDominance ?? DEFAULT_AXIS_DOMINANCE, completion: definition.completion, // Pair the resolved navigation intent with the definition's animation. action: async (context) => { const navigation = await definition.navigation(context); if (!navigation) return navigation; const action: OriginAction = { ...navigation, choreography: definition.choreography, }; return action; }, }; } export function recognizerPolicy( definition: OriginGestureDefinition | OriginGestureOptions, ): RecognizerPolicy { return isDefinition(definition) ? fromDefinition(definition) : fromLegacyOptions(definition); }