{"version":3,"sources":["../../src/gsap/useLoupeGsap.ts"],"names":["useRef","useState","useTimeline","useEffect","useMotionValueEvent"],"mappings":";;;;;;;AAwGO,SAAS,aAAa,IAAA,EAA+C;AAC1E,EAAA,MAAM,KAAA,GAAQA,aAA4B,IAAI,CAAA;AAC9C,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAIC,eAA8B,IAAI,CAAA;AAClE,EAAA,MAAM,EAAE,IAAA,EAAK,GAAIC,iBAAA,EAAY;AAI7B,EAAA,MAAM,QAAA,GAAWF,YAAA,CAAO,IAAA,CAAK,KAAK,CAAA;AAClC,EAAA,QAAA,CAAS,UAAU,IAAA,CAAK,KAAA;AACxB,EAAA,MAAM,OAAA,GAAUA,YAAA,CAAO,IAAA,CAAK,IAAA,KAAS,KAAK,CAAA;AAC1C,EAAA,OAAA,CAAQ,OAAA,GAAU,KAAK,IAAA,KAAS,KAAA;AAChC,EAAA,MAAM,QAAQ,IAAA,CAAK,KAAA;AAInB,EAAAG,eAAA,CAAU,MAAM;AACd,IAAA,IAAI,SAAA,GAAY,KAAA;AAChB,IAAA,IAAI,GAAA,GAA2B,IAAA;AAC/B,IAAA,IAAI,KAAA,GAA6B,IAAA;AAEjC,IAAA,CAAC,YAAY;AACX,MAAA,MAAM,GAAA,GAAM,MAAM,OAAO,MAAM,CAAA;AAC/B,MAAA,IAAI,SAAA,EAAW;AACf,MAAA,MAAM,CAAA,GAAK,GAAA,CAA+B,IAAA,IAAS,GAAA,CAAI,OAAA;AAEvD,MAAA,MAAM,OAAO,MAAM;AACjB,QAAA,KAAA,GAAQ,QAAA,CAAS,QAAQ,CAAC,CAAA;AAG1B,QAAA,KAAA,CAAM,MAAM,CAAC,CAAA;AACb,QAAA,KAAA,CAAM,OAAA,GAAU,KAAA;AAChB,QAAA,WAAA,CAAY,KAAK,CAAA;AAGjB,QAAA,MAAA,CAAO,KAAA,EAAO,IAAA,CAAK,GAAA,EAAI,EAAG,QAAQ,OAAO,CAAA;AAAA,MAC3C,CAAA;AAEA,MAAA,IAAI,KAAA,EAAO;AACT,QAAA,GAAA,GAAM,CAAA,CAAE,OAAA,CAAQ,IAAA,EAAM,KAAK,CAAA;AAAA,MAC7B,CAAA,MAAO;AACL,QAAA,IAAA,EAAK;AAAA,MACP;AAAA,IACF,CAAA,GAAG;AAEH,IAAA,OAAO,MAAM;AACX,MAAA,SAAA,GAAY,IAAA;AAIZ,MAAA,IAAI,GAAA,MAAS,MAAA,EAAO;AAAA,WAAA,IACX,KAAA,QAAa,IAAA,EAAK;AAC3B,MAAA,KAAA,CAAM,OAAA,GAAU,IAAA;AAChB,MAAA,WAAA,CAAY,IAAI,CAAA;AAAA,IAClB,CAAA;AAAA,EAEF,CAAA,EAAG,IAAA,CAAK,IAAA,IAAQ,EAAE,CAAA;AAGlB,EAAAC,gCAAA,CAAoB,IAAA,EAAM,QAAA,EAAU,CAAC,EAAA,KAAO;AAC1C,IAAA,MAAM,KAAK,KAAA,CAAM,OAAA;AACjB,IAAA,IAAI,CAAC,EAAA,EAAI;AACT,IAAA,MAAA,CAAO,EAAA,EAAI,EAAA,EAAI,OAAA,CAAQ,OAAO,CAAA;AAAA,EAChC,CAAC,CAAA;AAED,EAAA,OAAO,EAAE,QAAA,EAAS;AACpB;AAQA,SAAS,MAAA,CAAO,EAAA,EAAkB,EAAA,EAAY,IAAA,EAAqB;AACjE,EAAA,MAAM,SAAA,GAAY,GAAG,QAAA,EAAS;AAC9B,EAAA,IAAI,CAAC,SAAA,EAAW;AAChB,EAAA,MAAM,UAAU,EAAA,GAAK,GAAA;AACrB,EAAA,IAAI,GAAA;AACJ,EAAA,IAAI,IAAA,EAAM;AACR,IAAA,GAAA,GAAA,CAAQ,OAAA,GAAU,YAAa,SAAA,IAAa,SAAA;AAAA,EAC9C,CAAA,MAAO;AACL,IAAA,GAAA,GAAM,KAAK,GAAA,CAAI,CAAA,EAAG,KAAK,GAAA,CAAI,OAAA,EAAS,SAAS,CAAC,CAAA;AAAA,EAChD;AACA,EAAA,EAAA,CAAG,IAAA,CAAK,KAAK,IAAI,CAAA;AACnB","file":"index.cjs","sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport { useMotionValueEvent } from 'framer-motion';\nimport type { gsap } from 'gsap';\n// Self-import the main entry so this subpath shares the same\n// TimelineContext instance as `<TimelineProvider>`. If we import\n// from a relative path here, tsup bundles a SECOND copy of the\n// context into the gsap chunk and `useTimeline` can't see the\n// provider — which produces a white screen (\"useTimeline must be\n// used inside <TimelineProvider>\"). The main package is\n// externalized in tsup.config so the host resolves this import to\n// the already-loaded main bundle at runtime.\nimport { useTimeline } from '@arinze-clinton/loupe';\n\n/** A seekable GSAP timeline or tween. Both expose `.time()`,\n *  `.duration()`, `.pause()`, and `.kill()`. */\ntype GsapPlayhead = gsap.core.Timeline | gsap.core.Tween;\n\n/**\n * Options for {@link useLoupeGsap}.\n */\nexport type UseLoupeGsapOptions = {\n  /**\n   * Build the GSAP timeline (or tween). Receives the loaded `gsap`\n   * instance so you don't have to import it yourself, and must\n   * return the timeline/tween you want Loupe to drive.\n   *\n   * The timeline is created with its own clock disabled — Loupe's\n   * `time` becomes the single source of truth. Build it the way you\n   * always would (`gsap.timeline().to(...).from(...)`); the hook\n   * pauses it for you.\n   *\n   * @example\n   * ```ts\n   * build: (gsap) =>\n   *   gsap.timeline()\n   *     .from('.title', { y: 40, opacity: 0, duration: 0.5 })\n   *     .from('.subtitle', { opacity: 0, duration: 0.4 }, '-=0.2')\n   * ```\n   */\n  build: (g: typeof gsap) => GsapPlayhead;\n  /**\n   * Optional element to scope GSAP selector text (`'.title'`) and\n   * cleanup to. When set, the timeline is built inside a\n   * `gsap.context(fn, scope)` so selectors only match descendants\n   * and every tween is reverted on unmount/rebuild. Strongly\n   * recommended in React. Pass the ref's `.current`.\n   */\n  scope?: Element | null;\n  /**\n   * Rebuild the timeline when any of these change (same contract as\n   * a `useEffect` dep array). Defaults to `[]` — built once.\n   */\n  deps?: ReadonlyArray<unknown>;\n  /**\n   * Wrap the playhead at the timeline's duration boundary so it\n   * loops with Loupe's own phase loop. Default `true`. Set `false`\n   * to clamp at the end instead (the timeline holds its last frame\n   * past its duration).\n   */\n  loop?: boolean;\n};\n\nexport type UseLoupeGsapResult = {\n  /** The built GSAP timeline/tween, once ready. Null on the first\n   *  render before the dynamic import resolves. */\n  timeline: GsapPlayhead | null;\n};\n\n/**\n * Drive a GSAP timeline's playhead from the nearest\n * `TimelineProvider`'s `time` MotionValue.\n *\n * GSAP's own ticker is never used — the timeline is paused on\n * creation and every position comes from Loupe's deterministic\n * clock. When Loupe plays, the timeline scrubs forward; when Loupe\n * pauses, it freezes on the current position; scrubbing the panel\n * scrubs the timeline. That makes the same animation reproducible\n * for thumbnails, still renders, and review.\n *\n * `gsap` is an optional peer dependency — install it in your app:\n * `npm i gsap`. It's dynamic-imported so consumers who don't touch\n * the `/gsap` subpath never pay for it.\n *\n * @example\n * ```tsx\n * function Hero() {\n *   const scopeRef = useRef<HTMLDivElement | null>(null);\n *   useLoupeGsap({\n *     scope: scopeRef.current,\n *     deps: [scopeRef.current],\n *     build: (gsap) =>\n *       gsap.timeline()\n *         .from('.title', { y: 40, opacity: 0, duration: 0.5 })\n *         .from('.cta', { opacity: 0, duration: 0.4 }, '-=0.2'),\n *   });\n *   return (\n *     <div ref={scopeRef}>\n *       <h1 className=\"title\">Loupe</h1>\n *       <button className=\"cta\">Try it</button>\n *     </div>\n *   );\n * }\n * ```\n */\nexport function useLoupeGsap(opts: UseLoupeGsapOptions): UseLoupeGsapResult {\n  const tlRef = useRef<GsapPlayhead | null>(null);\n  const [timeline, setTimeline] = useState<GsapPlayhead | null>(null);\n  const { time } = useTimeline();\n\n  // Keep the latest options on a ref so the change handler and the\n  // build effect always read fresh values without re-subscribing.\n  const buildRef = useRef(opts.build);\n  buildRef.current = opts.build;\n  const loopRef = useRef(opts.loop !== false);\n  loopRef.current = opts.loop !== false;\n  const scope = opts.scope;\n\n  // Build (and rebuild) the timeline. gsap is dynamic-imported so\n  // the runtime package stays gsap-free at import time.\n  useEffect(() => {\n    let cancelled = false;\n    let ctx: gsap.Context | null = null;\n    let built: GsapPlayhead | null = null;\n\n    (async () => {\n      const mod = await import('gsap');\n      if (cancelled) return;\n      const g = (mod as { gsap?: typeof gsap }).gsap ?? (mod.default as typeof gsap);\n\n      const make = () => {\n        built = buildRef.current(g);\n        // Loupe owns the clock — kill GSAP's own playback so the\n        // two don't fight over the playhead.\n        built.pause(0);\n        tlRef.current = built;\n        setTimeline(built);\n        // Snap to wherever time already is so we don't flash the\n        // timeline's start before the first subscriber tick fires.\n        seekTo(built, time.get(), loopRef.current);\n      };\n\n      if (scope) {\n        ctx = g.context(make, scope);\n      } else {\n        make();\n      }\n    })();\n\n    return () => {\n      cancelled = true;\n      // `ctx.revert()` undoes every tween created inside the scope\n      // and reverts inline styles to their pre-animation values.\n      // Without a scope, kill the timeline directly.\n      if (ctx) ctx.revert();\n      else if (built) built.kill();\n      tlRef.current = null;\n      setTimeline(null);\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, opts.deps ?? []);\n\n  // Drive the playhead from Loupe's time MotionValue.\n  useMotionValueEvent(time, 'change', (ms) => {\n    const tl = tlRef.current;\n    if (!tl) return;\n    seekTo(tl, ms, loopRef.current);\n  });\n\n  return { timeline };\n}\n\n/**\n * Move a GSAP timeline/tween to the position implied by Loupe's\n * `time` (ms), respecting wrap vs. clamp. GSAP works in seconds, so\n * we convert. `suppressEvents = true` keeps callbacks from firing on\n * scrub (matching how `goToAndStop` behaves for Lottie).\n */\nfunction seekTo(tl: GsapPlayhead, ms: number, loop: boolean): void {\n  const durationS = tl.duration();\n  if (!durationS) return;\n  const seconds = ms / 1000;\n  let pos: number;\n  if (loop) {\n    pos = ((seconds % durationS) + durationS) % durationS;\n  } else {\n    pos = Math.max(0, Math.min(seconds, durationS));\n  }\n  tl.time(pos, true);\n}\n"]}