{"version":3,"file":"index.cjs","sources":["../../src/index.tsx"],"sourcesContent":["import * as React from 'react'\nimport { flushSync } from 'react-dom'\nimport {\n  Virtualizer,\n  elementScroll,\n  observeElementOffset,\n  observeElementRect,\n  observeWindowOffset,\n  observeWindowRect,\n  windowScroll,\n} from '@tanstack/virtual-core'\nimport type { PartialKeys, VirtualizerOptions } from '@tanstack/virtual-core'\n\nexport * from '@tanstack/virtual-core'\n\nconst useIsomorphicLayoutEffect =\n  typeof document !== 'undefined' ? React.useLayoutEffect : React.useEffect\n\nexport type ReactVirtualizer<\n  TScrollElement extends Element | Window,\n  TItemElement extends Element,\n> = Virtualizer<TScrollElement, TItemElement> & {\n  /**\n   * Ref callback for the inner size container element. Only meaningful when\n   * `directDomUpdates: true` — the virtualizer writes the container's\n   * main-axis size (`height` or `width`) directly to skip React re-renders.\n   */\n  containerRef: (node: HTMLElement | null) => void\n}\n\nexport type ReactVirtualizerOptions<\n  TScrollElement extends Element | Window,\n  TItemElement extends Element,\n> = VirtualizerOptions<TScrollElement, TItemElement> & {\n  useFlushSync?: boolean\n  /**\n   * Skip React re-renders for scroll-only updates. The virtualizer writes\n   * item positions (`top`/`left`) and the container size (`height`/`width`)\n   * directly to the DOM, and only re-renders when the visible index range\n   * or `isScrolling` changes.\n   *\n   * Requirements when enabled:\n   * - Item elements must be `position: absolute`; in `'transform'` mode they\n   *   must also be anchored with `top: 0` / `left: 0`.\n   * - Item elements must NOT set the main-axis position in their style — the\n   *   virtualizer owns `top` / `left` in `'position'` mode and `transform` in\n   *   `'transform'` mode.\n   * - The inner size container must receive `virtualizer.containerRef` and\n   *   must NOT set `height` / `width` in its style.\n   * - For multi-lane layouts (grids / masonry), the cross-axis position\n   *   (e.g. `left: ${(item.lane * 100) / lanes}%`) is stable per item and\n   *   must still be set in your JSX — only the main axis is automated.\n   *\n   * This flag is intended to be set once at mount. Toggling it (or\n   *  `directDomUpdatesMode`) at runtime can leave stale inline styles on\n   *  items and the container.\n   */\n  directDomUpdates?: boolean\n  /**\n   * How `directDomUpdates` positions item elements.\n   * - `'transform'` (default): writes `transform: translate3d(...)`.\n   *   Promotes items to their own compositor layer — usually smoother on long\n   *   lists, but creates a stacking context and can interfere with\n   *   `position: fixed` descendants. Item elements must still be anchored with\n   *   `position: absolute`, `top: 0`, and `left: 0`.\n   * - `'position'`: writes `top` / `left`. Item elements must be\n   *   `position: absolute`.\n   */\n  directDomUpdatesMode?: 'position' | 'transform'\n}\n\nfunction useVirtualizerBase<\n  TScrollElement extends Element | Window,\n  TItemElement extends Element,\n>({\n  useFlushSync = true,\n  directDomUpdates = false,\n  directDomUpdatesMode = 'transform',\n  ...options\n}: ReactVirtualizerOptions<TScrollElement, TItemElement>): ReactVirtualizer<\n  TScrollElement,\n  TItemElement\n> {\n  const rerender = React.useReducer((x: number) => x + 1, 0)[1]\n\n  // Mutable across renders so the onChange closure captured by setOptions\n  // always reads the latest values without us having to re-create it.\n  const directRef = React.useRef({\n    enabled: directDomUpdates,\n    mode: directDomUpdatesMode,\n    container: null as HTMLElement | null,\n    lastSize: null as number | null,\n    // Keyed by the element itself so a remounted node (same key, new DOM\n    // node — e.g. when `enabled` is toggled off then on) is treated as fresh\n    // and gets its style written.\n    lastPositions: new WeakMap<HTMLElement, number>(),\n    prevRange: null as {\n      startIndex: number\n      endIndex: number\n      isScrolling: boolean\n    } | null,\n  })\n  directRef.current.enabled = directDomUpdates\n  directRef.current.mode = directDomUpdatesMode\n\n  // Writes container size + item positions to the DOM. Idempotent — guarded\n  // by lastSize / lastPositions. Called from onChange (covers scroll-driven\n  // updates) and from a layout effect (covers post-render commits when refs\n  // have just registered new items in elementsCache).\n  const applyDirectStyles = (\n    instance: Virtualizer<TScrollElement, TItemElement>,\n  ) => {\n    const state = directRef.current\n    if (!state.enabled) return\n\n    const totalSize = instance.getTotalSize()\n    if (state.container && totalSize !== state.lastSize) {\n      state.lastSize = totalSize\n      const sizeAxis = instance.options.horizontal ? 'width' : 'height'\n      state.container.style[sizeAxis] = `${totalSize}px`\n    }\n\n    const horizontal = !!instance.options.horizontal\n    const useTransform = state.mode === 'transform'\n    const posAxis = horizontal ? 'left' : 'top'\n    const scrollMargin = instance.options.scrollMargin\n    const items = instance.getVirtualItems()\n    for (const item of items) {\n      const next = item.start - scrollMargin\n      const el = instance.elementsCache.get(item.key) as HTMLElement | undefined\n      if (!el) continue\n      if (state.lastPositions.get(el) === next) continue\n      state.lastPositions.set(el, next)\n      if (useTransform) {\n        el.style.transform = horizontal\n          ? `translate3d(${next}px, 0, 0)`\n          : `translate3d(0, ${next}px, 0)`\n      } else {\n        el.style[posAxis] = `${next}px`\n      }\n    }\n  }\n\n  const resolvedOptions: VirtualizerOptions<TScrollElement, TItemElement> = {\n    ...options,\n    onChange: (instance, sync) => {\n      const state = directRef.current\n      let shouldRerender = true\n\n      if (state.enabled) {\n        applyDirectStyles(instance)\n\n        // Only re-render on range / isScrolling changes\n        const range = instance.range\n        const prev = state.prevRange\n        shouldRerender =\n          !prev ||\n          prev.isScrolling !== instance.isScrolling ||\n          prev.startIndex !== range?.startIndex ||\n          prev.endIndex !== range?.endIndex\n        if (shouldRerender) {\n          state.prevRange = range\n            ? {\n                startIndex: range.startIndex,\n                endIndex: range.endIndex,\n                isScrolling: instance.isScrolling,\n              }\n            : null\n        }\n      }\n\n      if (shouldRerender) {\n        if (useFlushSync && sync) {\n          flushSync(rerender)\n        } else {\n          rerender()\n        }\n      }\n\n      options.onChange?.(instance, sync)\n    },\n  }\n\n  const [instance] = React.useState(() => {\n    const v = new Virtualizer<TScrollElement, TItemElement>(resolvedOptions)\n    return Object.assign(v, {\n      containerRef: (node: HTMLElement | null) => {\n        const state = directRef.current\n        state.container = node\n        state.lastSize = null\n        if (node && state.enabled) {\n          const total = v.getTotalSize()\n          state.lastSize = total\n          const axis = v.options.horizontal ? 'width' : 'height'\n          node.style[axis] = `${total}px`\n        }\n      },\n    })\n  })\n\n  instance.setOptions(resolvedOptions)\n\n  useIsomorphicLayoutEffect(() => {\n    return instance._didMount()\n  }, [])\n\n  useIsomorphicLayoutEffect(() => {\n    return instance._willUpdate()\n  })\n\n  // After every render commit, newly mounted item refs have registered in\n  // elementsCache; write their positions to the DOM so the user doesn't see\n  // them at (0, 0) until the next onChange.\n  useIsomorphicLayoutEffect(() => {\n    applyDirectStyles(instance)\n  })\n\n  return instance\n}\n\nexport function useVirtualizer<\n  TScrollElement extends Element,\n  TItemElement extends Element,\n>(\n  options: PartialKeys<\n    ReactVirtualizerOptions<TScrollElement, TItemElement>,\n    'observeElementRect' | 'observeElementOffset' | 'scrollToFn'\n  >,\n): ReactVirtualizer<TScrollElement, TItemElement> {\n  return useVirtualizerBase<TScrollElement, TItemElement>({\n    observeElementRect: observeElementRect,\n    observeElementOffset: observeElementOffset,\n    scrollToFn: elementScroll,\n    ...options,\n  })\n}\n\nexport function useWindowVirtualizer<TItemElement extends Element>(\n  options: PartialKeys<\n    ReactVirtualizerOptions<Window, TItemElement>,\n    | 'getScrollElement'\n    | 'observeElementRect'\n    | 'observeElementOffset'\n    | 'scrollToFn'\n  >,\n): ReactVirtualizer<Window, TItemElement> {\n  return useVirtualizerBase<Window, TItemElement>({\n    getScrollElement: () => (typeof document !== 'undefined' ? window : null),\n    observeElementRect: observeWindowRect,\n    observeElementOffset: observeWindowOffset,\n    scrollToFn: windowScroll,\n    initialOffset: () => (typeof document !== 'undefined' ? window.scrollY : 0),\n    ...options,\n  })\n}\n"],"names":["React","instance","flushSync","Virtualizer","observeElementRect","observeElementOffset","elementScroll","observeWindowRect","observeWindowOffset","windowScroll"],"mappings":"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"}