{"version":3,"sources":["../../../../src/browser/useAnimate/core.ts"],"sourcesContent":["import { batch, observable } from \"@legendapp/state\";\nimport type { Observable } from \"@legendapp/state\";\nimport {\n  createRafFn,\n  createSupported,\n  get,\n  createObserve,\n  onMount,\n  onUnmount,\n  peek,\n  type DeepMaybeObservable,\n  type Fn,\n  type MaybeObservable,\n  type ReadonlyObservable,\n  type Supportable,\n} from \"@usels/core\";\nimport { createEventListener } from \"../useEventListener/core\";\nimport { resolveWindowSource, type ConfigurableWindow } from \"@shared/configurable\";\nimport type { MaybeEventTarget } from \"../../types\";\nimport { createOpaque } from \"@usels/core/reactivity/useOpaque/core\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface UseAnimateOptions extends KeyframeAnimationOptions, ConfigurableWindow {\n  /** Start playing immediately on mount. @default true */\n  immediate?: boolean;\n  /** Commit final styles to the element on finish. @default false */\n  commitStyles?: boolean;\n  /** Persist the animation. @default false */\n  persist?: boolean;\n  /** Initial playback rate. @default 1 */\n  playbackRate?: number;\n  /** Called once the Animation instance is ready. */\n  onReady?: (animate: Animation) => void;\n  /** Called when an animation error occurs. */\n  onError?: (e: unknown) => void;\n}\n\nexport type UseAnimateKeyframes = MaybeObservable<Keyframe[] | PropertyIndexedKeyframes | null>;\n\nexport interface UseAnimateReturn extends Supportable {\n  /** Current Animation instance. */\n  animate$: ReadonlyObservable<Animation | null>;\n\n  // ── Actions ──\n  play: Fn;\n  pause: Fn;\n  reverse: Fn;\n  finish: Fn;\n  cancel: Fn;\n\n  // ── Read-only state (synced every rAF frame) ──\n  pending$: ReadonlyObservable<boolean>;\n  playState$: ReadonlyObservable<AnimationPlayState>;\n  replaceState$: ReadonlyObservable<AnimationReplaceState>;\n\n  // ── Writable state (bidirectional sync) ──\n  startTime$: Observable<number | null>;\n  currentTime$: Observable<number | null>;\n  timeline$: Observable<AnimationTimeline | null>;\n  playbackRate$: Observable<number>;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction extractNativeOpts(\n  raw: UseAnimateOptions | undefined\n): KeyframeAnimationOptions | undefined {\n  if (!raw) return undefined;\n  const {\n    window: _w,\n    immediate: _i,\n    commitStyles: _c,\n    persist: _p,\n    playbackRate: _r,\n    onReady: _or,\n    onError: _oe,\n    ...rest\n  } = raw;\n  return rest;\n}\n\n// ---------------------------------------------------------------------------\n// Core — framework-agnostic Web Animations API wrapper\n// ---------------------------------------------------------------------------\n\n/**\n * Framework-agnostic reactive wrapper around the\n * [Web Animations API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API).\n *\n * Must be called inside a `useScope` factory — resource setup (`update`) is\n * registered via `onMount` and cleanup via `onUnmount`. rAF sync, element\n * tracking, keyframe reactivity, and animation-event listening are all driven\n * by scope-aware `createObserve()` so the whole pipeline tears down on scope\n * disposal.\n */\nexport function createAnimate(\n  target: MaybeEventTarget,\n  keyframes: UseAnimateKeyframes,\n  options?: number | DeepMaybeObservable<UseAnimateOptions>\n): UseAnimateReturn {\n  // ── Options handling ──\n  const isNumberOpts = typeof options === \"number\";\n  const opts$ = observable<UseAnimateOptions | undefined>(\n    (isNumberOpts ? undefined : options) as UseAnimateOptions | undefined\n  );\n\n  // Mount-time-only fields\n  const peeked = opts$.peek() ?? {};\n  const immediate = peeked.immediate ?? true;\n  const commitStyles = peeked.commitStyles ?? false;\n  const persist = peeked.persist ?? false;\n  const initialPlaybackRate = peeked.playbackRate ?? 1;\n\n  const win$ = resolveWindowSource(opts$.window as unknown as Observable<unknown>);\n\n  const fireError = (e: unknown) => (opts$.peek()?.onError ?? console.error)(e);\n\n  // ── Core state ──\n  const isSupported$ = createSupported(\n    () =>\n      !!win$.peek() &&\n      typeof Element !== \"undefined\" &&\n      typeof Element.prototype.animate === \"function\"\n  );\n\n  let anim: Animation | undefined;\n  const _animate$ = createOpaque<Animation>(null);\n\n  // Read-only state (rAF synced)\n  const pending$ = observable(false);\n  const playState$ = observable<AnimationPlayState>(\"idle\");\n  const replaceState$ = observable<AnimationReplaceState>(\"active\");\n\n  // Writable state (bidirectional sync)\n  const startTime$ = observable<number | null>(null);\n  const currentTime$ = observable<number | null>(null);\n  const timeline$ = observable<AnimationTimeline | null>(null);\n  const playbackRate$ = observable<number>(initialPlaybackRate);\n\n  // ── Bidirectional sync guards ──\n  let isSyncing = false;\n  let reverseLocked = false;\n\n  // Synchronously read animation state into observables (one-shot)\n  function syncState() {\n    if (!anim) return;\n    isSyncing = true;\n    batch(() => {\n      pending$.set(anim!.pending);\n      playState$.set(anim!.playState);\n      replaceState$.set(anim!.replaceState);\n      startTime$.set(anim!.startTime as number | null);\n      currentTime$.set(anim!.currentTime as number | null);\n      timeline$.set(anim!.timeline);\n      playbackRate$.set(anim!.playbackRate);\n    });\n    isSyncing = false;\n  }\n\n  // rAF loop: Animation → Observables (every frame)\n  const { resume: syncResume, pause: syncPause } = createRafFn(() => syncState(), {\n    immediate: false,\n  });\n\n  // Observable → Animation (user .set() detection)\n  createObserve(() => {\n    const time = currentTime$.get();\n    if (!isSyncing && anim) {\n      anim.currentTime = time;\n      syncResume();\n    }\n  });\n\n  createObserve(() => {\n    const rate = playbackRate$.get();\n    if (!isSyncing && anim) {\n      anim.playbackRate = rate;\n    }\n  });\n\n  createObserve(() => {\n    const time = startTime$.get();\n    if (!isSyncing && anim) {\n      anim.startTime = time;\n    }\n  });\n\n  createObserve(() => {\n    const tl = timeline$.get();\n    if (!isSyncing && anim) {\n      anim.timeline = tl;\n    }\n  });\n\n  // ── update() — create / recreate animation ──\n  function update(init?: boolean) {\n    const el = peek(target);\n    if (!isSupported$.peek() || !el || !(el instanceof Element)) return;\n\n    if (!anim) {\n      const nativeOpts = isNumberOpts\n        ? (options as number)\n        : extractNativeOpts(opts$.peek() as UseAnimateOptions | undefined);\n      const newAnim = el.animate(get(keyframes), nativeOpts);\n      anim = newAnim;\n      _animate$.set(newAnim);\n    }\n\n    if (persist) {\n      try {\n        anim.persist();\n      } catch {\n        /* noop */\n      }\n    }\n    if (initialPlaybackRate !== 1) {\n      anim.playbackRate = initialPlaybackRate;\n    }\n\n    if (init && !immediate) {\n      anim.pause();\n      syncState();\n    } else {\n      syncResume();\n    }\n\n    opts$.peek()?.onReady?.(anim);\n  }\n\n  // ── Action functions ──\n  const play: Fn = () => {\n    if (anim) {\n      try {\n        anim.play();\n        syncResume();\n      } catch (e) {\n        syncPause();\n        fireError(e);\n      }\n    } else {\n      update();\n    }\n  };\n\n  const pause: Fn = () => {\n    try {\n      anim?.pause();\n      syncState();\n      syncPause();\n    } catch (e) {\n      fireError(e);\n    }\n  };\n\n  const reverse: Fn = () => {\n    if (!anim) update();\n    if (!anim) {\n      syncPause();\n      return;\n    }\n    // Guard against reverse bursts in the same frame.\n    if (reverseLocked) return;\n    reverseLocked = true;\n    const raf = win$.peek()?.requestAnimationFrame ?? requestAnimationFrame;\n    raf(() => {\n      reverseLocked = false;\n    });\n    try {\n      // Avoid native Animation.reverse() path due to renderer instability observed in Chromium.\n      // Emulate reverse by flipping playbackRate and explicitly starting playback.\n      if (anim.currentTime == null) {\n        // If the timeline has no currentTime yet, start from the effect end before reversing.\n        const timing = (anim.effect as KeyframeEffect | null)?.getComputedTiming();\n        const end = typeof timing?.endTime === \"number\" ? timing.endTime : 0;\n        anim.currentTime = end;\n      }\n      const nextRate = anim.playbackRate === 0 ? -1 : -anim.playbackRate;\n      anim.playbackRate = nextRate;\n      anim.play();\n      syncState();\n      syncResume();\n    } catch (e) {\n      syncPause();\n      fireError(e);\n    }\n  };\n\n  const finish: Fn = () => {\n    try {\n      anim?.finish();\n      if (commitStyles) {\n        try {\n          anim?.commitStyles();\n        } catch {\n          /* noop */\n        }\n      }\n      syncState();\n      syncPause();\n    } catch (e) {\n      fireError(e);\n    }\n  };\n\n  const cancel: Fn = () => {\n    try {\n      anim?.cancel();\n      syncState();\n      syncPause();\n    } catch (e) {\n      fireError(e);\n    }\n  };\n\n  // ── Element change → recreate animation ──\n  createObserve(() => {\n    const el = get(target);\n    if (el) {\n      update(true);\n    } else {\n      try {\n        anim?.cancel();\n      } catch {\n        /* noop */\n      }\n      anim = undefined;\n      _animate$.set(null);\n      syncPause();\n    }\n  });\n\n  // ── Keyframes & options reactivity ──\n  createObserve(() => {\n    const kf = get(keyframes);\n    const nativeOpts = isNumberOpts\n      ? (options as number)\n      : extractNativeOpts(opts$.get() as UseAnimateOptions | undefined);\n    if (anim) {\n      const el = peek(target);\n      if (el && el instanceof Element) {\n        anim.effect = new KeyframeEffect(el, kf ?? null, nativeOpts);\n      }\n    }\n  });\n\n  // ── Animation events → sync pause ──\n  createEventListener(_animate$, [\"cancel\", \"finish\", \"remove\"], () => {\n    syncState();\n    syncPause();\n  });\n\n  // commitStyles on finish\n  createEventListener(_animate$, \"finish\", () => {\n    if (commitStyles) {\n      try {\n        anim?.commitStyles();\n      } catch {\n        /* noop */\n      }\n    }\n  });\n\n  // ── Lifecycle ──\n  onMount(() => update(true));\n\n  onUnmount(() => {\n    try {\n      anim?.cancel();\n    } catch {\n      /* noop */\n    }\n    syncPause();\n  });\n\n  // ── Return ──\n  return {\n    isSupported$: isSupported$ as ReadonlyObservable<boolean>,\n    animate$: _animate$ as unknown as ReadonlyObservable<Animation | null>,\n\n    play,\n    pause,\n    reverse,\n    finish,\n    cancel,\n\n    pending$: pending$ as ReadonlyObservable<boolean>,\n    playState$: playState$ as ReadonlyObservable<AnimationPlayState>,\n    replaceState$: replaceState$ as ReadonlyObservable<AnimationReplaceState>,\n\n    startTime$,\n    currentTime$,\n    timeline$,\n    playbackRate$,\n  };\n}\n"],"mappings":"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","names":[]}