{"version":3,"file":"usePager.mjs","names":[],"sources":["../../../../package/common/calendar/usePager.ts"],"sourcesContent":["import { useCallback, useEffect, useLayoutEffect, useRef } from 'react';\nimport { flushSync } from 'react-dom';\n\nimport { useMediaQuery } from '../hooks/useMediaQuery';\n\nconst SETTLE_MS = 240;\n\nexport interface Pager {\n  /** Ref for each element that slides. The time grid has two, its head and its body. */\n  trackRef: (element: HTMLElement | null) => void;\n  /** Width of one pane. */\n  width: () => number;\n  /** Offset every track by `dx` px, following the finger. */\n  move: (dx: number) => void;\n  /** Animate to the next (`1`) or previous (`-1`) pane and page, or back (`0`). */\n  settle: (step: -1 | 0 | 1) => void;\n  /** Jump to the end of a running settle: a new press arrived before it finished. */\n  finish: () => void;\n}\n\n/**\n * Drives the three panes a pageable view renders: previous, current and next, keyed by period, so\n * after paging the pane that slid in is the same DOM node as the one now current. The track is\n * reset in the same commit that swaps them, which is why nothing flashes.\n *\n * `focusFallback` receives focus when it would otherwise be left inside a pane that just went inert.\n */\nexport function usePager(\n  periodKey: string,\n  onPage: (step: 1 | -1) => void,\n  focusFallback: () => HTMLElement | null,\n): Pager {\n  const tracks = useRef(new Set<HTMLElement>());\n  const offset = useRef(0);\n  const animations = useRef<Animation[]>([]);\n  const pendingStep = useRef<-1 | 0 | 1>(0);\n  const keyRef = useRef(periodKey);\n  const reducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)');\n\n  const latest = useRef({ onPage, focusFallback, reducedMotion });\n  useLayoutEffect(() => {\n    latest.current = { onPage, focusFallback, reducedMotion };\n  });\n\n  const write = useCallback((dx: number) => {\n    offset.current = dx;\n    for (const track of tracks.current) track.style.transform = dx === 0 ? '' : `translateX(${dx}px)`;\n  }, []);\n\n  useLayoutEffect(() => {\n    keyRef.current = periodKey;\n    write(0);\n    const active = document.activeElement;\n    if (active instanceof HTMLElement && active.closest('[inert]')) {\n      const inPager = [...tracks.current].some((track) => track.contains(active));\n      if (inPager) latest.current.focusFallback()?.focus();\n    }\n  }, [periodKey, write]);\n\n  const trackRef = useCallback((element: HTMLElement | null) => {\n    if (!element) return;\n    tracks.current.add(element);\n    return () => {\n      tracks.current.delete(element);\n    };\n  }, []);\n\n  const width = useCallback(() => {\n    const [first] = tracks.current;\n    return first?.parentElement?.clientWidth ?? 0;\n  }, []);\n\n  const complete = useCallback(\n    (step: -1 | 0 | 1) => {\n      animations.current = [];\n      if (step === 0) {\n        write(0);\n        return;\n      }\n      const before = keyRef.current;\n      flushSync(() => latest.current.onPage(step));\n      // A controlled parent that did not take the new date leaves the old panes in place.\n      if (keyRef.current === before) write(0);\n    },\n    [write],\n  );\n\n  const settle = useCallback(\n    (step: -1 | 0 | 1) => {\n      const paneWidth = width();\n      const from = offset.current;\n      const to = step === 0 ? 0 : -step * paneWidth;\n      const elements = [...tracks.current];\n      write(to);\n      const canAnimate = typeof elements[0]?.animate === 'function';\n      if (latest.current.reducedMotion || from === to || !canAnimate) {\n        complete(step);\n        return;\n      }\n      // Shorter the less of the way is left, so a flick that has almost arrived does not crawl.\n      const duration = Math.max(120, SETTLE_MS * Math.min(1, Math.abs(to - from) / Math.max(paneWidth, 1)));\n      const keyframes = [{ transform: `translateX(${from}px)` }, { transform: `translateX(${to}px)` }];\n      pendingStep.current = step;\n      animations.current = elements.map((element) =>\n        element.animate(keyframes, { duration, easing: 'cubic-bezier(0.25, 1, 0.5, 1)' }),\n      );\n      animations.current[0].onfinish = () => complete(step);\n    },\n    [complete, width, write],\n  );\n\n  const finish = useCallback(() => {\n    if (animations.current.length === 0) return;\n    // Not `Animation#finish()`: it queues the finish event for a later frame, and the new press\n    // has to start from the paged state now.\n    for (const animation of animations.current) {\n      animation.onfinish = null;\n      animation.cancel();\n    }\n    complete(pendingStep.current);\n  }, [complete]);\n\n  useEffect(() => {\n    const running = animations;\n    return () => {\n      for (const animation of running.current) {\n        animation.onfinish = null;\n        animation.cancel();\n      }\n    };\n  }, []);\n\n  const move = useCallback((dx: number) => write(dx), [write]);\n\n  return { trackRef, width, move, settle, finish };\n}\n"],"mappings":"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"}