{"version":3,"sources":["../src/pan-event.ts"],"sourcesContent":["import {\n  addPointerEvent,\n  getEventPoint,\n  isMultiTouchEvent,\n} from \"@chakra-ui/event-utils\"\nimport sync, { cancelSync, getFrameData } from \"framesync\"\nimport {\n  AnyPointerEvent,\n  PanEventHandlers,\n  PanEventHistory,\n  Point,\n  PointerEventInfo,\n  TimestampedPoint,\n} from \"./types\"\n\n/**\n * A Pan Session is recognized when the pointer is down\n * and moved in the allowed direction.\n */\nexport class PanEvent {\n  /**\n   * We use this to keep track of the `x` and `y` pan session history\n   * as the pan event happens. It helps to calculate the `offset` and `delta`\n   */\n  private history: PanEventHistory = []\n\n  // The pointer event that started the pan session\n  private startEvent: AnyPointerEvent | null = null\n\n  // The current pointer event for the pan session\n  private lastEvent: AnyPointerEvent | null = null\n\n  // The current pointer event info for the pan session\n  private lastEventInfo: PointerEventInfo | null = null\n\n  private handlers: Partial<PanEventHandlers> = {}\n\n  private removeListeners: Function = () => {}\n\n  /**\n   * Minimal pan distance required before recognizing the pan.\n   * @default \"3px\"\n   */\n  private threshold = 3\n\n  private win: typeof globalThis\n\n  constructor(\n    event: AnyPointerEvent,\n    handlers: Partial<PanEventHandlers>,\n    threshold?: number,\n  ) {\n    this.win = (event.view ?? window) as typeof globalThis.window\n\n    // If we have more than one touch, don't start detecting this gesture\n    if (isMultiTouchEvent(event)) return\n\n    this.handlers = handlers\n\n    if (threshold) {\n      this.threshold = threshold\n    }\n\n    // stop default browser behavior\n    event.stopPropagation()\n    event.preventDefault()\n\n    // get and save the `pointerdown` event info in history\n    // we'll use it to compute the `offset`\n    const info = { point: getEventPoint(event) }\n    const { timestamp } = getFrameData()\n    this.history = [{ ...info.point, timestamp }]\n\n    // notify pan session start\n    const { onSessionStart } = handlers\n    onSessionStart?.(event, getPanInfo(info, this.history))\n\n    // attach event listeners and return a single function to remove them all\n    this.removeListeners = pipe(\n      addPointerEvent(this.win, \"pointermove\", this.onPointerMove),\n      addPointerEvent(this.win, \"pointerup\", this.onPointerUp),\n      addPointerEvent(this.win, \"pointercancel\", this.onPointerUp),\n    )\n  }\n\n  private updatePoint = () => {\n    if (!(this.lastEvent && this.lastEventInfo)) return\n\n    const info = getPanInfo(this.lastEventInfo, this.history)\n\n    const isPanStarted = this.startEvent !== null\n\n    const isDistancePastThreshold =\n      distance(info.offset, { x: 0, y: 0 }) >= this.threshold\n\n    if (!isPanStarted && !isDistancePastThreshold) return\n\n    const { timestamp } = getFrameData()\n    this.history.push({ ...info.point, timestamp })\n\n    const { onStart, onMove } = this.handlers\n\n    if (!isPanStarted) {\n      onStart?.(this.lastEvent, info)\n      this.startEvent = this.lastEvent\n    }\n\n    onMove?.(this.lastEvent, info)\n  }\n\n  private onPointerMove = (event: AnyPointerEvent, info: PointerEventInfo) => {\n    this.lastEvent = event\n    this.lastEventInfo = info\n\n    // Throttle mouse move event to once per frame\n    sync.update(this.updatePoint, true)\n  }\n\n  private onPointerUp = (event: AnyPointerEvent, info: PointerEventInfo) => {\n    // notify pan session ended\n    const panInfo = getPanInfo(info, this.history)\n    const { onEnd, onSessionEnd } = this.handlers\n\n    onSessionEnd?.(event, panInfo)\n    this.end()\n\n    // if panning never started, no need to call `onEnd`\n    // panning requires a pointermove of at least 3px\n    if (!onEnd || !this.startEvent) return\n\n    onEnd?.(event, panInfo)\n  }\n\n  updateHandlers(handlers: Partial<PanEventHandlers>) {\n    this.handlers = handlers\n  }\n\n  end() {\n    this.removeListeners?.()\n    cancelSync.update(this.updatePoint)\n  }\n}\n\n/* -----------------------------------------------------------------------------\n * Utilities\n * -----------------------------------------------------------------------------*/\n\nfunction subtract(a: Point, b: Point) {\n  return { x: a.x - b.x, y: a.y - b.y }\n}\n\nfunction getPanInfo(info: PointerEventInfo, history: PanEventHistory) {\n  return {\n    point: info.point,\n    delta: subtract(info.point, history[history.length - 1]),\n    offset: subtract(info.point, history[0]),\n    velocity: getVelocity(history, 0.1),\n  }\n}\n\nconst toMilliseconds = (v: number) => v * 1000\n\nfunction getVelocity(history: TimestampedPoint[], timeDelta: number): Point {\n  if (history.length < 2) {\n    return { x: 0, y: 0 }\n  }\n\n  let i = history.length - 1\n  let timestampedPoint: TimestampedPoint | null = null\n  const lastPoint = history[history.length - 1]\n  while (i >= 0) {\n    timestampedPoint = history[i]\n    if (\n      lastPoint.timestamp - timestampedPoint.timestamp >\n      toMilliseconds(timeDelta)\n    ) {\n      break\n    }\n    i--\n  }\n\n  if (!timestampedPoint) {\n    return { x: 0, y: 0 }\n  }\n\n  const time = (lastPoint.timestamp - timestampedPoint.timestamp) / 1000\n  if (time === 0) {\n    return { x: 0, y: 0 }\n  }\n\n  const currentVelocity = {\n    x: (lastPoint.x - timestampedPoint.x) / time,\n    y: (lastPoint.y - timestampedPoint.y) / time,\n  }\n\n  if (currentVelocity.x === Infinity) {\n    currentVelocity.x = 0\n  }\n  if (currentVelocity.y === Infinity) {\n    currentVelocity.y = 0\n  }\n\n  return currentVelocity\n}\n\nfunction pipe<R>(...fns: Array<(a: R) => R>) {\n  return (v: R) => fns.reduce((a, b) => b(a), v)\n}\n\nfunction distance1D(a: number, b: number) {\n  return Math.abs(a - b)\n}\n\nfunction isPoint(point: any): point is { x: number; y: number } {\n  return \"x\" in point && \"y\" in point\n}\n\nexport function distance<P extends Point | number>(a: P, b: P) {\n  if (typeof a === \"number\" && typeof b === \"number\") {\n    return distance1D(a, b)\n  }\n\n  if (isPoint(a) && isPoint(b)) {\n    const xDelta = distance1D(a.x, b.x)\n    const yDelta = distance1D(a.y, b.y)\n    return Math.sqrt(xDelta ** 2 + yDelta ** 2)\n  }\n\n  return 0\n}\n"],"mappings":"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","names":[]}