{"version":3,"sources":["../../../../src/sensors/useScroll/core.ts"],"sourcesContent":["import { batch, observable, type Observable } from \"@legendapp/state\";\nimport {\n  type DeepMaybeObservable,\n  get,\n  createObserve,\n  onMount,\n  onUnmount,\n  peek,\n} from \"@usels/core\";\nimport { isWindow } from \"@usels/core/shared/index\";\nimport { throttle } from \"es-toolkit/function\";\nimport type { MaybeEventTarget } from \"../../types\";\nimport { resolveWindowSource, type ConfigurableWindow } from \"@shared/configurable\";\nimport { createEventListener } from \"../../browser/useEventListener/core\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface UseScrollOptions extends ConfigurableWindow {\n  throttle?: number;\n  idle?: number;\n  onScroll?: (e: Event) => void;\n  onStop?: () => void;\n  onError?: (error: unknown) => void;\n  offset?: {\n    left?: number;\n    right?: number;\n    top?: number;\n    bottom?: number;\n  };\n  behavior?: ScrollBehavior;\n  eventListenerOptions?: AddEventListenerOptions;\n}\n\nexport interface ArrivedState {\n  left: boolean;\n  right: boolean;\n  top: boolean;\n  bottom: boolean;\n}\n\nexport interface ScrollDirections {\n  left: boolean;\n  right: boolean;\n  top: boolean;\n  bottom: boolean;\n}\n\nexport interface UseScrollReturn {\n  x$: Observable<number>;\n  y$: Observable<number>;\n  isScrolling$: Observable<boolean>;\n  arrivedState$: Observable<ArrivedState>;\n  directions$: Observable<ScrollDirections>;\n  measure: () => void;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction getScrollValues(el: Element | Document | Window | null): {\n  x: number;\n  y: number;\n} {\n  if (!el) return { x: 0, y: 0 };\n  if (isWindow(el)) return { x: el.scrollX, y: el.scrollY };\n  if (el instanceof Document)\n    return {\n      x: el.documentElement.scrollLeft,\n      y: el.documentElement.scrollTop,\n    };\n  return { x: el.scrollLeft, y: el.scrollTop };\n}\n\nfunction getScrollDimensions(\n  el: Element | Document | Window | null,\n  win: Window | null | undefined\n): {\n  scrollW: number;\n  scrollH: number;\n  clientW: number;\n  clientH: number;\n} {\n  if (!el || isWindow(el)) {\n    const doc = win?.document;\n    return {\n      scrollW: doc?.documentElement.scrollWidth ?? 0,\n      scrollH: doc?.documentElement.scrollHeight ?? 0,\n      clientW: win?.innerWidth ?? 0,\n      clientH: win?.innerHeight ?? 0,\n    };\n  }\n  if (el instanceof Document) {\n    const root = el.documentElement;\n    return {\n      scrollW: root.scrollWidth,\n      scrollH: root.scrollHeight,\n      clientW: root.clientWidth,\n      clientH: root.clientHeight,\n    };\n  }\n  return {\n    scrollW: el.scrollWidth,\n    scrollH: el.scrollHeight,\n    clientW: el.clientWidth,\n    clientH: el.clientHeight,\n  };\n}\n\n// ---------------------------------------------------------------------------\n// Core\n// ---------------------------------------------------------------------------\n\n/**\n * Framework-agnostic reactive scroll tracker. Monitors `scroll` events on the\n * target (Element, Document, Window, Ref$, or Observable<Element>) and\n * exposes reactive position, direction, arrived-state, and scrolling status.\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport function createScroll(\n  element: MaybeEventTarget<Element | Document | Window>,\n  options?: DeepMaybeObservable<UseScrollOptions>\n): UseScrollReturn {\n  const opts$ = observable(options);\n\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- window field type varies by hint map\n  const window$ = resolveWindowSource(opts$.window as any);\n\n  const initial = getScrollValues(peek(element) ?? null);\n\n  const x$ = observable<number>(initial.x);\n  const y$ = observable<number>(initial.y);\n  const isScrolling$ = observable<boolean>(false);\n  const arrivedState$ = observable<ArrivedState>({\n    left: true,\n    right: false,\n    top: true,\n    bottom: false,\n  });\n  const directions$ = observable<ScrollDirections>({\n    left: false,\n    right: false,\n    top: false,\n    bottom: false,\n  });\n\n  let idleTimer: ReturnType<typeof setTimeout> | null = null;\n\n  const resetIdle = () => {\n    if (idleTimer) clearTimeout(idleTimer);\n    isScrolling$.set(true);\n    const idle = (opts$.peek() as UseScrollOptions | undefined)?.idle ?? 200;\n    idleTimer = setTimeout(() => {\n      isScrolling$.set(false);\n      (opts$.peek() as UseScrollOptions | undefined)?.onStop?.();\n    }, idle);\n  };\n\n  const measure = () => {\n    const el = peek(element);\n    if (!el) return;\n\n    const prevX = x$.peek();\n    const prevY = y$.peek();\n    const { x: newX, y: newY } = getScrollValues(el);\n\n    directions$.assign({\n      left: newX < prevX,\n      right: newX > prevX,\n      top: newY < prevY,\n      bottom: newY > prevY,\n    });\n\n    x$.set(newX);\n    y$.set(newY);\n\n    const { scrollW, scrollH, clientW, clientH } = getScrollDimensions(el, window$.peek());\n    const maxX = scrollW - clientW;\n    const maxY = scrollH - clientH;\n    const offset = opts$.peek()?.offset ?? {};\n\n    arrivedState$.assign({\n      left: newX <= (offset.left ?? 0),\n      right: newX >= maxX - (offset.right ?? 0),\n      top: newY <= (offset.top ?? 0),\n      bottom: newY >= maxY - (offset.bottom ?? 0),\n    });\n\n    resetIdle();\n  };\n\n  // Stable handler — throttle state lives in the closure. `opts$.peek()` reads\n  // the latest throttle/onScroll each call.\n  const rawHandler = (e: Event) => {\n    measure();\n    opts$.peek()?.onScroll?.(e);\n  };\n  const rawOpts = opts$.peek();\n  const ms = rawOpts?.throttle ?? 0;\n  const handler = ms > 0 ? throttle(rawHandler, ms) : rawHandler;\n\n  const listenerOpts = rawOpts?.eventListenerOptions ?? { capture: false, passive: true };\n  createEventListener(\n    element as MaybeEventTarget,\n    \"scroll\",\n    handler,\n    listenerOpts as AddEventListenerOptions\n  );\n\n  // Reactive: when element transitions to null, reset state and clear timers.\n  createObserve(() => {\n    const el = get(element);\n    if (!el) {\n      if (idleTimer) {\n        clearTimeout(idleTimer);\n        idleTimer = null;\n      }\n      batch(() => {\n        isScrolling$.set(false);\n        x$.set(0);\n        y$.set(0);\n        arrivedState$.assign({ left: true, right: false, top: true, bottom: false });\n        directions$.assign({ left: false, right: false, top: false, bottom: false });\n      });\n    }\n  });\n\n  onMount(() => {\n    measure();\n  });\n\n  onUnmount(() => {\n    if (idleTimer) clearTimeout(idleTimer);\n  });\n\n  return {\n    x$,\n    y$,\n    isScrolling$,\n    arrivedState$,\n    directions$,\n    measure,\n  };\n}\n"],"mappings":"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","names":[]}