{"version":3,"sources":["../../../../src/sensors/useInfiniteScroll/core.ts"],"sourcesContent":["import { observable, type Observable } from \"@legendapp/state\";\nimport {\n  createObserve,\n  peek,\n  type DeepMaybeObservable,\n  type ReadonlyObservable,\n  type Awaitable,\n} from \"@usels/core\";\nimport { isWindow } from \"@usels/core/shared/index\";\nimport { resolveWindowSource, type ConfigurableWindow } from \"@shared/configurable\";\nimport type { MaybeEventTarget } from \"../../types\";\nimport { createScroll, type UseScrollOptions } from \"../useScroll/core\";\nimport { createElementVisibility } from \"../../elements/useElementVisibility/core\";\n\nexport type UseInfiniteScrollDirection = \"top\" | \"bottom\" | \"left\" | \"right\";\n\nexport interface UseInfiniteScrollOptions extends ConfigurableWindow {\n  /** Load trigger direction. Default: \"bottom\" */\n  direction?: UseInfiniteScrollDirection;\n  /** arrivedState offset in px. Default: 0 */\n  distance?: number;\n  /** Whether to auto-load on mount if content is already at boundary. Default: true */\n  immediate?: boolean;\n  /** Minimum ms between consecutive load triggers. Default: 100 */\n  interval?: number;\n  /** Gate function — return false to block loading. Evaluated before each load. */\n  canLoadMore?: (el: HTMLElement) => boolean;\n  /** Additional options passed to useScroll */\n  scrollOptions?: UseScrollOptions;\n}\n\nexport interface UseInfiniteScrollReturn {\n  /** Currently loading */\n  isLoading$: ReadonlyObservable<boolean>;\n  /** Manually trigger a load */\n  load: () => Promise<void>;\n  /** Re-check scroll position and trigger load if needed */\n  reset: () => void;\n}\n\n/**\n * Framework-agnostic infinite scroll core.\n *\n * Triggers `onLoadMore` whenever the scroll position reaches the configured\n * boundary of a scrollable element. Composes `createScroll` and\n * `createElementVisibility`. Must be called inside a `useScope` factory.\n */\nexport function createInfiniteScroll(\n  element: MaybeEventTarget<Element | Document | Window>,\n  onLoadMore: (direction: UseInfiniteScrollDirection) => Awaitable<void>,\n  options?: DeepMaybeObservable<UseInfiniteScrollOptions>\n): UseInfiniteScrollReturn {\n  const opts$ = observable(options);\n  const win$ = resolveWindowSource(opts$.window as unknown as Observable<unknown>);\n\n  // Mount-time-only: direction and immediate cannot change meaningfully after mount\n  const direction: UseInfiniteScrollDirection = opts$.peek()?.direction ?? \"bottom\";\n  const immediate: boolean = opts$.peek()?.immediate ?? true;\n\n  // Reactive scroll options — derived from opts$\n  const scrollOpts$ = observable<UseScrollOptions>(() => {\n    const distance = opts$.get()?.distance ?? 0;\n    const extra = opts$.get()?.scrollOptions;\n    return {\n      ...extra,\n      offset: {\n        ...extra?.offset,\n        [direction]: distance + 1, // +1px tolerance for sub-pixel scroll boundaries\n      },\n    };\n  });\n\n  const isLoading$ = observable(false);\n\n  const { arrivedState$, measure } = createScroll(element, scrollOpts$ as never);\n  const isVisible$ = createElementVisibility(element);\n\n  function isNarrower(): boolean {\n    const el = peek(element);\n    if (!el) return false;\n    const isVertical = direction === \"top\" || direction === \"bottom\";\n    if (isWindow(el)) {\n      const win = win$.peek();\n      const docEl = win?.document.documentElement;\n      if (!docEl || !win) return false;\n      return isVertical\n        ? docEl.scrollHeight <= win.innerHeight\n        : docEl.scrollWidth <= win.innerWidth;\n    }\n    const domEl = el as HTMLElement;\n    return isVertical\n      ? domEl.scrollHeight <= domEl.clientHeight\n      : domEl.scrollWidth <= domEl.clientWidth;\n  }\n\n  const load = async (): Promise<void> => {\n    if (isLoading$.peek()) return;\n    if (!isVisible$.peek()) return;\n\n    const canLoadMore = opts$.peek()?.canLoadMore;\n    if (canLoadMore) {\n      const el = peek(element);\n      const domEl = isWindow(el)\n        ? (win$.peek()?.document.documentElement ?? null)\n        : (el as HTMLElement | null);\n      if (domEl && !canLoadMore(domEl as HTMLElement)) return;\n    }\n\n    isLoading$.set(true);\n    try {\n      const interval = opts$.peek()?.interval ?? 100;\n      await Promise.all([onLoadMore(direction), new Promise((r) => setTimeout(r, interval))]);\n    } finally {\n      isLoading$.set(false);\n      setTimeout(() => {\n        measure();\n      }, 0);\n    }\n  };\n\n  // Auto-load when arrivedState matches direction or content doesn't overflow\n  createObserve(() => {\n    if (!immediate && !arrivedState$.get()) return; // skip auto-fill on mount when immediate=false\n    const isVisible = isVisible$.get();\n    const arrived = arrivedState$.get()[direction];\n    if (!isVisible || isLoading$.peek()) return;\n\n    const canLoadMore = opts$.peek()?.canLoadMore;\n    if (canLoadMore) {\n      const el = peek(element);\n      const domEl = isWindow(el)\n        ? (win$.peek()?.document.documentElement ?? null)\n        : (el as HTMLElement | null);\n      if (domEl && !canLoadMore(domEl as HTMLElement)) return;\n    }\n\n    if (arrived || isNarrower()) {\n      void load();\n    }\n  });\n\n  const reset = (): void => {\n    setTimeout(() => {\n      measure();\n    }, 0);\n  };\n\n  return {\n    isLoading$: isLoading$ as ReadonlyObservable<boolean>,\n    load,\n    reset,\n  };\n}\n"],"mappings":"AAAA,SAAS,kBAAmC;AAC5C;AAAA,EACE;AAAA,EACA;AAAA,OAIK;AACP,SAAS,gBAAgB;AACzB,SAAS,2BAAoD;AAE7D,SAAS,oBAA2C;AACpD,SAAS,+BAA+B;AAmCjC,SAAS,qBACd,SACA,YACA,SACyB;AACzB,QAAM,QAAQ,WAAW,OAAO;AAChC,QAAM,OAAO,oBAAoB,MAAM,MAAwC;AAG/E,QAAM,YAAwC,MAAM,KAAK,GAAG,aAAa;AACzE,QAAM,YAAqB,MAAM,KAAK,GAAG,aAAa;AAGtD,QAAM,cAAc,WAA6B,MAAM;AACrD,UAAM,WAAW,MAAM,IAAI,GAAG,YAAY;AAC1C,UAAM,QAAQ,MAAM,IAAI,GAAG;AAC3B,WAAO;AAAA,MACL,GAAG;AAAA,MACH,QAAQ;AAAA,QACN,GAAG,OAAO;AAAA,QACV,CAAC,SAAS,GAAG,WAAW;AAAA;AAAA,MAC1B;AAAA,IACF;AAAA,EACF,CAAC;AAED,QAAM,aAAa,WAAW,KAAK;AAEnC,QAAM,EAAE,eAAe,QAAQ,IAAI,aAAa,SAAS,WAAoB;AAC7E,QAAM,aAAa,wBAAwB,OAAO;AAElD,WAAS,aAAsB;AAC7B,UAAM,KAAK,KAAK,OAAO;AACvB,QAAI,CAAC,GAAI,QAAO;AAChB,UAAM,aAAa,cAAc,SAAS,cAAc;AACxD,QAAI,SAAS,EAAE,GAAG;AAChB,YAAM,MAAM,KAAK,KAAK;AACtB,YAAM,QAAQ,KAAK,SAAS;AAC5B,UAAI,CAAC,SAAS,CAAC,IAAK,QAAO;AAC3B,aAAO,aACH,MAAM,gBAAgB,IAAI,cAC1B,MAAM,eAAe,IAAI;AAAA,IAC/B;AACA,UAAM,QAAQ;AACd,WAAO,aACH,MAAM,gBAAgB,MAAM,eAC5B,MAAM,eAAe,MAAM;AAAA,EACjC;AAEA,QAAM,OAAO,YAA2B;AACtC,QAAI,WAAW,KAAK,EAAG;AACvB,QAAI,CAAC,WAAW,KAAK,EAAG;AAExB,UAAM,cAAc,MAAM,KAAK,GAAG;AAClC,QAAI,aAAa;AACf,YAAM,KAAK,KAAK,OAAO;AACvB,YAAM,QAAQ,SAAS,EAAE,IACpB,KAAK,KAAK,GAAG,SAAS,mBAAmB,OACzC;AACL,UAAI,SAAS,CAAC,YAAY,KAAoB,EAAG;AAAA,IACnD;AAEA,eAAW,IAAI,IAAI;AACnB,QAAI;AACF,YAAM,WAAW,MAAM,KAAK,GAAG,YAAY;AAC3C,YAAM,QAAQ,IAAI,CAAC,WAAW,SAAS,GAAG,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,QAAQ,CAAC,CAAC,CAAC;AAAA,IACxF,UAAE;AACA,iBAAW,IAAI,KAAK;AACpB,iBAAW,MAAM;AACf,gBAAQ;AAAA,MACV,GAAG,CAAC;AAAA,IACN;AAAA,EACF;AAGA,gBAAc,MAAM;AAClB,QAAI,CAAC,aAAa,CAAC,cAAc,IAAI,EAAG;AACxC,UAAM,YAAY,WAAW,IAAI;AACjC,UAAM,UAAU,cAAc,IAAI,EAAE,SAAS;AAC7C,QAAI,CAAC,aAAa,WAAW,KAAK,EAAG;AAErC,UAAM,cAAc,MAAM,KAAK,GAAG;AAClC,QAAI,aAAa;AACf,YAAM,KAAK,KAAK,OAAO;AACvB,YAAM,QAAQ,SAAS,EAAE,IACpB,KAAK,KAAK,GAAG,SAAS,mBAAmB,OACzC;AACL,UAAI,SAAS,CAAC,YAAY,KAAoB,EAAG;AAAA,IACnD;AAEA,QAAI,WAAW,WAAW,GAAG;AAC3B,WAAK,KAAK;AAAA,IACZ;AAAA,EACF,CAAC;AAED,QAAM,QAAQ,MAAY;AACxB,eAAW,MAAM;AACf,cAAQ;AAAA,IACV,GAAG,CAAC;AAAA,EACN;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":[]}