{"version":3,"sources":["../../src/use-virtual-list/index.ts"],"sourcesContent":["import { useMemo, useRef } from 'react'\nimport { useCreation } from '../use-creation'\nimport { useElementSize } from '../use-element-size'\nimport { useEventListener } from '../use-event-listener'\nimport { useLatest } from '../use-latest'\nimport { useSafeState } from '../use-safe-state'\nimport { useStableFn } from '../use-stable-fn'\nimport { useTargetElement } from '../use-target-element'\nimport { useUpdateEffect } from '../use-update-effect'\nimport { isNumber } from '../utils/basic'\n\nimport type { CSSProperties } from 'react'\nimport type { ElementTarget } from '../use-target-element'\n\ninterface UseVirtualListBaseOptions {\n  /**\n   * The ElementTarget that should be used as the container. Usually, it should be scrollable (overflow scroll).\n   */\n  containerTarget: NonNullable<ElementTarget<HTMLElement>>\n  /**\n   * The ElementTarget that should be used as the wrapper, which contains the list of items directly.\n   *\n   * The margin and height (width for horizontal) of the wrapper element will be set to the total height (width) of all items.\n   */\n  wrapperTarget: NonNullable<ElementTarget<HTMLElement>>\n  /**\n   * The number of items that should be rendered in the viewport.\n   *\n   * @defaultValue 5\n   */\n  overscan?: number\n}\n\ninterface UseVerticalVirtualListOptions<D> extends UseVirtualListBaseOptions {\n  /**\n   * The height of each item, or a function that returns the height of each item.\n   *\n   * When `itemHeight` is set, the list will be rendered vertically.\n   */\n  itemHeight: number | ((index: number, item: D) => number)\n}\n\ninterface UseHorizontalVirtualListOptions<D> extends UseVirtualListBaseOptions {\n  /**\n   * The width of each item, or a function that returns the width of each item.\n   *\n   * When `itemWidth` is set and `itemHeight` is not set, the list will be rendered horizontally.\n   */\n  itemWidth: number | ((index: number, item: D) => number)\n}\n\nexport type UseVirtualListOptions<D> = UseVerticalVirtualListOptions<D> | UseHorizontalVirtualListOptions<D>\n\nexport interface UseVirtualListReturnsActions {\n  /**\n   * Scroll to the item at the specified index, automatically use vertical or horizontal scrolling based on the options.\n   *\n   * @param {Number} index The index of the item to scroll to.\n   */\n  scrollTo: (index: number) => void\n  /**\n   * Scroll to the start of the list, automatically use vertical or horizontal scrolling based on the options.\n   */\n  scrollToStart: () => void\n  /**\n   * Scroll to the end of the list, automatically use vertical or horizontal scrolling based on the options.\n   */\n  scrollToEnd: () => void\n}\n\nexport interface UseVirtualListReturnsListItem<D> {\n  /**\n   * The data of the item.\n   */\n  data: D\n  /**\n   * The index of the item.\n   */\n  index: number\n}\n\nexport type UseVirtualListReturns<D> = readonly [\n  /**\n   * The list of items that should be rendered.\n   */\n  list: UseVirtualListReturnsListItem<D>[],\n  /**\n   * The actions that can be used to control the list.\n   */\n  UseVirtualListReturnsActions,\n]\n\n/**\n * A React Hook that helps to render large lists (known as **Virtual List**) more efficiently by rendering only the items that are visible to the user, supporting dynamic heights, horizontal and vertical scrolling, and more.\n *\n * @since 1.6.0\n */\nexport function useVirtualList<D = any>(list: D[], options: UseVirtualListOptions<D>): UseVirtualListReturns<D> {\n  const overscanCount = options.overscan ?? 5\n  const isVerticalLayout = 'itemHeight' in options\n  const itemSize = isVerticalLayout ? options.itemHeight : options.itemWidth\n\n  const containerRef = useTargetElement<HTMLElement>(options.containerTarget)\n  const wrapperRef = useTargetElement<HTMLElement>(options.wrapperTarget)\n  const containerSize = useElementSize(containerRef)\n\n  const [wrapperStyle, setWrapperStyle] = useSafeState<CSSProperties>({})\n  const [renderList, setRenderList] = useSafeState<{ data: D; index: number }[]>([])\n\n  const isTriggeredInternallyRef = useRef(false)\n\n  function findMatchedIdx(\n    condition: (sum: number) => boolean,\n    itemSize: (index: number, item: D) => number,\n    fromIdx: number = 0,\n  ) {\n    let sum = 0\n    let idx = fromIdx\n    for (let i = fromIdx; i < list.length; i++) {\n      const height = itemSize(i, list[i])\n      sum += height\n      if (condition(sum)) {\n        idx = i\n        break\n      }\n    }\n    return idx\n  }\n\n  function getOffsetIdx(scrollPosition: number) {\n    if (isNumber(itemSize)) return Math.floor(scrollPosition / itemSize)\n    const offsetIdx = findMatchedIdx((sum) => sum >= scrollPosition, itemSize)\n    return offsetIdx + 1\n  }\n\n  function getVisibleItemCount(containerSize: number, startIdx: number) {\n    if (isNumber(itemSize)) return Math.ceil(containerSize / itemSize)\n    const endIdx = findMatchedIdx((sum) => sum >= containerSize, itemSize, startIdx)\n    return endIdx - startIdx\n  }\n\n  function getOffsetSize(idx: number) {\n    if (isNumber(itemSize)) return idx * itemSize\n    return list.slice(0, idx).reduce((sum, _, i) => sum + itemSize(i, list[i]), 0)\n  }\n\n  const latestGetOffsetSize = useLatest(getOffsetSize)\n  const totalSize = useMemo(() => latestGetOffsetSize.current(list.length), [list])\n\n  function calculateVirtualOffset() {\n    const containerEl = containerRef.current\n    const wrapperEl = wrapperRef.current\n\n    if (!containerEl || !wrapperEl || !list.length) return\n\n    const { scrollTop, scrollLeft, clientHeight, clientWidth } = containerEl\n\n    const offsetIdx = getOffsetIdx(isVerticalLayout ? scrollTop : scrollLeft)\n    const visibleCount = getVisibleItemCount(isVerticalLayout ? clientHeight : clientWidth, offsetIdx)\n\n    const renderStartIdx = Math.max(0, offsetIdx - overscanCount)\n    const renderEndIdx = Math.min(list.length, offsetIdx + visibleCount + overscanCount)\n\n    const offsetSize = getOffsetSize(renderStartIdx)\n\n    const renderList = list.slice(renderStartIdx, renderEndIdx).map((item, index) => ({\n      data: item,\n      index: index + renderStartIdx,\n    }))\n\n    setWrapperStyle({\n      // [isVerticalLayout ? 'width' : 'height']: '100%',\n      [isVerticalLayout ? 'height' : 'width']: `${totalSize - offsetSize}px`,\n      [isVerticalLayout ? 'marginTop' : 'marginLeft']: `${offsetSize}px`,\n    })\n\n    setRenderList(renderList)\n  }\n\n  const latest = useLatest({\n    list,\n    itemSize,\n    getOffsetSize,\n    calculateVirtualOffset,\n  })\n\n  useEventListener(containerRef, 'scroll', () => {\n    if (isTriggeredInternallyRef.current) {\n      isTriggeredInternallyRef.current = false\n      return\n    }\n\n    latest.current.calculateVirtualOffset()\n  })\n\n  // biome-ignore lint/correctness/useExhaustiveDependencies: The effect should only run when the container size changes.\n  useUpdateEffect(() => void calculateVirtualOffset(), [containerSize.height, containerSize.width, list])\n\n  // biome-ignore lint/correctness/useExhaustiveDependencies: The effect should only run when wrapper styles change.\n  useUpdateEffect(() => {\n    if (!wrapperRef.current) return\n    for (const key of Object.keys(wrapperStyle)) {\n      wrapperRef.current.style[key as never] = wrapperStyle[key as never]\n    }\n  }, [wrapperStyle])\n\n  const scrollTo = useStableFn((index: number) => {\n    const containerEl = containerRef.current\n    if (!containerEl) return\n\n    isTriggeredInternallyRef.current = true\n\n    const offsetSize = isNumber(latest.current.itemSize)\n      ? index * latest.current.itemSize\n      : latest.current.getOffsetSize(index)\n\n    containerEl[isVerticalLayout ? 'scrollTop' : 'scrollLeft'] = offsetSize\n\n    // Manually trigger the `calculateVirtualOffset` instantly, use ref to mark the manual trigger,\n    // because subscription of `useEventListener` will not be triggered in the same frame.\n    // This is useful to prevent layout jittering when scrolling.\n    latest.current.calculateVirtualOffset()\n  })\n\n  const scrollToStart = useStableFn(() => scrollTo(0))\n  const scrollToEnd = useStableFn(() => scrollTo(latest.current.list.length - 1))\n\n  const actions = useCreation(() => ({ scrollTo, scrollToStart, scrollToEnd }))\n\n  return [renderList, actions] as const\n}\n"],"names":["useVirtualList","list","options","overscanCount","overscan","isVerticalLayout","itemSize","itemHeight","itemWidth","containerRef","useTargetElement","containerTarget","wrapperRef","wrapperTarget","containerSize","useElementSize","useSafeState","wrapperStyle","setWrapperStyle","renderList","setRenderList","isTriggeredInternallyRef","useRef","findMatchedIdx","condition","fromIdx","sum","idx","i","length","height","getOffsetIdx","scrollPosition","isNumber","Math","floor","offsetIdx","getVisibleItemCount","startIdx","ceil","endIdx","getOffsetSize","slice","reduce","_","latestGetOffsetSize","useLatest","totalSize","useMemo","current","calculateVirtualOffset","containerEl","wrapperEl","scrollTop","scrollLeft","clientHeight","clientWidth","visibleCount","renderStartIdx","max","renderEndIdx","min","offsetSize","map","item","index","data","latest","useEventListener","useUpdateEffect","width","Object","keys","key","style","scrollTo","useStableFn","scrollToStart","scrollToEnd","actions","useCreation"],"mappings":"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"}