{"version":3,"file":"useSwipeDismiss.mjs","names":[],"sources":["../../../../package/common/hooks/useSwipeDismiss.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\n\nimport { useCoarsePointer } from './useCoarsePointer';\n\nexport interface UseSwipeDismissOptions {\n  /** Called once the swipe commits (past the threshold) — after the exit transition finishes,\n   * not the instant the pointer is released, unless `animateExit` is `false`. */\n  onDismiss: () => void;\n  /** Which axis the drag tracks (default `'x'`, the natural direction for a toast/notification). */\n  axis?: 'x' | 'y';\n  /** Distance in px past which releasing counts as a commit, regardless of speed (default `80`). */\n  distanceThreshold?: number;\n  /** Speed in px/ms past which releasing counts as a commit, regardless of distance (default `0.5`) —\n   * lets a fast flick dismiss even if the pointer didn't travel very far. */\n  velocityThreshold?: number;\n  /** Disables the gesture entirely — no listeners are attached. */\n  disabled?: boolean;\n  /** The offset in px as it changes: on every move, then `0` as it springs back or the exit offset\n   * once it commits. For something else that follows the drag, such as a fading backdrop. */\n  onDrag?: (offset: number) => void;\n  /** Restricts the gesture to coarse (touch) pointers, auto-detected via `matchMedia`. On a\n   * mouse/trackpad device a drag is more likely to be a text selection than an intentional\n   * dismiss, and other affordances (a close button, auto-dismiss) typically already cover that\n   * case. Default `true`; set `false` to allow the gesture on any pointer type. */\n  mobileOnly?: boolean;\n  /** Fades the element out as it is dragged (default `true`). */\n  fade?: boolean;\n  /** Slides the element off before calling `onDismiss` (default `true`). `false` calls it the moment\n   * the swipe commits and leaves the element where it was let go, for a caller that animates the\n   * exit itself. */\n  animateExit?: boolean;\n}\n\nexport interface UseSwipeDismissResult<T extends HTMLElement> {\n  /** Attach to the element that should become draggable. The hook wires up its own pointer\n   * listeners on this node directly (no handlers to spread), so this is the only thing to attach.\n   *\n   * A callback ref rather than an object one, because the hook has to know when the node changes:\n   * an element that is unmounted and rendered again is a *different* node, and a ref object cannot\n   * say so. To keep a ref of your own on the element, call both from one callback. */\n  ref: React.RefCallback<T>;\n  /** True for the duration of an active drag — e.g. use it to pause an auto-dismiss timer. */\n  isDragging: boolean;\n}\n\nconst EXIT_TRANSITION_MS = 180;\n// Below this many px of movement, direction intent isn't decided yet — lets a vertical scroll\n// gesture that merely starts on the element pass through instead of being hijacked as a swipe.\nconst AXIS_LOCK_THRESHOLD = 10;\n\n/**\n * Drag-to-dismiss on any pointer type (touch, mouse, pen) via the Pointer Events API. Applies the\n * drag offset and a fading opacity directly to the DOM node (bypassing React state) so the visual\n * tracks the pointer at native speed, then either springs back or animates fully off-screen and\n * calls `onDismiss` once that exit transition completes.\n */\nexport function useSwipeDismiss<T extends HTMLElement>({\n  onDismiss,\n  axis = 'x',\n  distanceThreshold = 80,\n  velocityThreshold = 0.5,\n  disabled = false,\n  mobileOnly = true,\n  fade = true,\n  animateExit = true,\n  onDrag,\n}: UseSwipeDismissOptions): UseSwipeDismissResult<T> {\n  // The node lives in state, not in a ref: the listeners have to be re-attached when the element\n  // is replaced, and a ref object's `current` changing never re-runs the effect below. Rendering\n  // the element again after it was unmounted otherwise left the gesture wired to the old node.\n  const [node, setNode] = useState<T | null>(null);\n  const ref = useCallback((element: T | null) => setNode(element), []);\n  const [isDragging, setIsDragging] = useState(false);\n\n  const isCoarsePointer = useCoarsePointer();\n\n  const effectiveDisabled = disabled || (mobileOnly && !isCoarsePointer);\n\n  // Mutable across the life of one drag; doesn't need to trigger re-renders.\n  const dragRef = useRef<{\n    pointerId: number;\n    startX: number;\n    startY: number;\n    startTime: number;\n    offset: number;\n    axisLocked: boolean;\n    active: boolean;\n  } | null>(null);\n\n  const onDismissRef = useRef(onDismiss);\n  const onDragRef = useRef(onDrag);\n  useEffect(() => {\n    onDismissRef.current = onDismiss;\n    onDragRef.current = onDrag;\n  });\n\n  useEffect(() => {\n    const el = node;\n    if (!el || effectiveDisabled) return;\n    let settleTimer: ReturnType<typeof setTimeout> | undefined;\n\n    function applyTransform(offset: number, transition: string) {\n      if (!el) return;\n      if (transition !== 'none') {\n        // Switching straight from the live drag's `transition: none` to an animated one, in the\n        // same tick as the transform change, can get coalesced into a single frame with no\n        // animation in between — the element just jumps to its end state instead of sliding\n        // there. Forcing a layout read first commits the current (pre-transition) position as\n        // its own frame, so the transition below actually has something to animate from.\n        void el.offsetHeight;\n      }\n      const translate = axis === 'x' ? `translateX(${offset}px)` : `translateY(${offset}px)`;\n      el.style.transition = transition;\n      el.style.transform = translate;\n      if (fade) el.style.opacity = String(Math.max(0, 1 - Math.abs(offset) / (distanceThreshold * 2.5)));\n      onDragRef.current?.(offset);\n    }\n\n    // Left behind, the inline styles would override the element's stylesheet for good.\n    function clearInlineStyles() {\n      if (!el || dragRef.current) return;\n      el.style.removeProperty('transform');\n      el.style.removeProperty('opacity');\n      el.style.removeProperty('transition');\n    }\n\n    function onPointerMove(event: PointerEvent) {\n      const drag = dragRef.current;\n      if (!drag || event.pointerId !== drag.pointerId) return;\n      // No button held: its release never reached the page, as when a context menu kept it.\n      if (event.buttons === 0) {\n        finishDrag();\n        return;\n      }\n\n      const deltaX = event.clientX - drag.startX;\n      const deltaY = event.clientY - drag.startY;\n\n      if (!drag.axisLocked) {\n        const primary = axis === 'x' ? deltaX : deltaY;\n        const cross = axis === 'x' ? deltaY : deltaX;\n        if (Math.abs(primary) < AXIS_LOCK_THRESHOLD && Math.abs(cross) < AXIS_LOCK_THRESHOLD) return;\n        if (Math.abs(cross) > Math.abs(primary)) {\n          // The gesture is mostly along the other axis (e.g. a page scroll) — hand it back.\n          drag.active = false;\n          return;\n        }\n        drag.axisLocked = true;\n        setIsDragging(true);\n        // A finished `forwards` animation (a toast's entrance) holds its frame over inline styles.\n        // Cancelled, as `animation: none` would replay it once restored. Not on a mere tap.\n        for (const animation of el?.getAnimations?.() ?? []) animation.cancel();\n      }\n\n      if (!drag.active) return;\n      event.preventDefault();\n      drag.offset = axis === 'x' ? deltaX : deltaY;\n      applyTransform(drag.offset, 'none');\n    }\n\n    function finishDrag() {\n      const drag = dragRef.current;\n      dragRef.current = null;\n      window.removeEventListener('pointermove', onPointerMove);\n      window.removeEventListener('pointerup', finishDrag);\n      window.removeEventListener('pointercancel', finishDrag);\n      if (!drag || !drag.active || !drag.axisLocked) {\n        setIsDragging(false);\n        return;\n      }\n\n      const elapsed = Math.max(1, Date.now() - drag.startTime);\n      const velocity = drag.offset / elapsed;\n      const committed = Math.abs(drag.offset) >= distanceThreshold || Math.abs(velocity) >= velocityThreshold;\n\n      if (committed && !animateExit) {\n        onDismissRef.current();\n      } else if (committed) {\n        const rect = el?.getBoundingClientRect();\n        const size = (axis === 'x' ? rect?.width : rect?.height) ?? 0;\n        const exitOffset = Math.sign(drag.offset || 1) * (size + 40);\n        applyTransform(\n          exitOffset,\n          `transform ${EXIT_TRANSITION_MS}ms ease-out, opacity ${EXIT_TRANSITION_MS}ms ease-out`,\n        );\n        setTimeout(() => onDismissRef.current(), EXIT_TRANSITION_MS);\n      } else {\n        applyTransform(0, `transform ${EXIT_TRANSITION_MS}ms ease-out, opacity ${EXIT_TRANSITION_MS}ms ease-out`);\n        clearTimeout(settleTimer);\n        settleTimer = setTimeout(clearInlineStyles, EXIT_TRANSITION_MS);\n      }\n      setIsDragging(false);\n    }\n\n    function onPointerDown(event: PointerEvent) {\n      // Another mouse button opens a menu, which can keep its release from the page.\n      if (dragRef.current || !el || event.button !== 0) return;\n      dragRef.current = {\n        pointerId: event.pointerId,\n        startX: event.clientX,\n        startY: event.clientY,\n        startTime: Date.now(),\n        offset: 0,\n        axisLocked: false,\n        active: true,\n      };\n      window.addEventListener('pointermove', onPointerMove, { passive: false });\n      window.addEventListener('pointerup', finishDrag);\n      window.addEventListener('pointercancel', finishDrag);\n    }\n\n    el.addEventListener('pointerdown', onPointerDown);\n    return () => {\n      clearTimeout(settleTimer);\n      // A press under way would miss its release, and its drag would block every press after it.\n      dragRef.current = null;\n      setIsDragging(false);\n      el.removeEventListener('pointerdown', onPointerDown);\n      window.removeEventListener('pointermove', onPointerMove);\n      window.removeEventListener('pointerup', finishDrag);\n      window.removeEventListener('pointercancel', finishDrag);\n    };\n  }, [node, axis, distanceThreshold, velocityThreshold, effectiveDisabled, fade, animateExit]);\n\n  return { ref, isDragging };\n}\n"],"mappings":"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"}