{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "use-list-virtualizer",
  "title": "useListVirtualizer",
  "description": "A custom React hook for list virtualizer.",
  "dependencies": [
    "@tanstack/react-virtual"
  ],
  "files": [
    {
      "path": "registry/default/hooks/use-list-virtualizer.ts",
      "content": "import * as React from \"react\";\nimport { useVirtualizer, type VirtualItem } from \"@tanstack/react-virtual\";\n\n/**\n * The virtualizer instance type. Use to type a ref that receives\n * the virtualizer via the `virtualizerRef` option.\n */\nexport type ListVirtualizerInstance = ReturnType<\n  typeof useVirtualizer<HTMLDivElement, Element>\n>;\n\n/**\n * Returns a stable `onItemHighlighted` handler that scrolls the virtualizer\n * to the highlighted item on keyboard navigation.\n *\n * Use this on the Root component when the virtualizer lives in a child\n * component (e.g. when using `useFilteredItems`).\n */\nexport function useHighlightHandler(\n  virtualizerRef: React.RefObject<ListVirtualizerInstance | null>,\n) {\n  return React.useCallback(\n    (\n      item: unknown,\n      {\n        reason,\n        index,\n      }: { reason: \"none\" | \"keyboard\" | \"pointer\"; index: number },\n    ) => {\n      const virtualizer = virtualizerRef.current;\n      if (!item || !virtualizer) return;\n\n      const isStart = index === 0;\n      const isEnd = index === virtualizer.options.count - 1;\n\n      const shouldScroll =\n        reason === \"none\" || (reason === \"keyboard\" && (isStart || isEnd));\n\n      if (shouldScroll) {\n        queueMicrotask(() => {\n          virtualizer.scrollToIndex(index, {\n            align: isEnd ? \"start\" : \"end\",\n          });\n        });\n      }\n    },\n    // virtualizerRef is a stable ref object\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n    [],\n  );\n}\n\nexport interface UseListVirtualizerOptions<T> {\n  /**\n   * Whether virtualization is enabled.\n   * Typically tied to whether the list container is open/visible.\n   * @default true\n   */\n  enabled?: boolean;\n\n  /**\n   * All items in the list (unfiltered).\n   * Used by Base UI Autocomplete for proper aria-setsize.\n   */\n  items: T[];\n\n  /**\n   * The filtered items to display.\n   * Can be the same as `items` if no filtering is applied (e.g., server-side filtering).\n   */\n  filteredItems: T[];\n\n  /**\n   * Estimated height of each item in pixels, or a function that returns\n   * the estimate for each item. Used for initial layout before measurement.\n   * @default 40\n   */\n  estimateSize?: number | ((index: number, item: T) => number);\n\n  /**\n   * Number of items to render outside the visible area.\n   * Higher values = smoother scrolling, more memory.\n   * @default 20\n   */\n  overscan?: number;\n\n  /**\n   * Padding at the start of the list in pixels.\n   * Should match the list container's padding.\n   * @default 8\n   */\n  paddingStart?: number;\n\n  /**\n   * Padding at the end of the list in pixels.\n   * @default 8\n   */\n  paddingEnd?: number;\n\n  /**\n   * Ref to expose the virtualizer instance to a parent component.\n   * When provided, the virtualizer is exposed via `useImperativeHandle`.\n   * Use with `createHighlightHandler` for the `useFilteredItems` pattern.\n   */\n  virtualizerRef?: React.RefObject<ListVirtualizerInstance | null>;\n}\n\nexport interface UseListVirtualizerReturn<T> {\n  /**\n   * Props to spread on the root component (Autocomplete.Root).\n   * Includes: virtualized, items, filteredItems, onItemHighlighted\n   */\n  rootProps: {\n    virtualized: true;\n    items: T[];\n    filteredItems: T[];\n    onItemHighlighted: (\n      item: T | null | undefined,\n      details: { reason: \"none\" | \"keyboard\" | \"pointer\"; index: number }\n    ) => void;\n  };\n\n  /**\n   * Ref callback for the scroll container element.\n   * Attach to a div wrapping the virtual items.\n   */\n  scrollRef: (element: HTMLDivElement | null) => void;\n\n  /**\n   * Ref callback for measuring individual items.\n   * Attach to each list item for dynamic height support.\n   */\n  measureRef: (element: HTMLDivElement | null) => void;\n\n  /**\n   * Total height of all virtual items in pixels.\n   * Use for the height of the placeholder container.\n   */\n  totalSize: number;\n\n  /**\n   * Array of virtual items to render.\n   * Each contains: key, index, start, size\n   */\n  virtualItems: VirtualItem[];\n\n  /**\n   * Get the item at a virtual index.\n   * Convenience method: filteredItems[virtualItem.index]\n   */\n  getItem: (virtualItem: { index: number }) => T | undefined;\n\n  /**\n   * Get inline styles for a virtual item.\n   * Returns: { position, top, left, right, transform }\n   */\n  getItemStyle: (virtualItem: { start: number }) => React.CSSProperties;\n\n  /**\n   * Get aria attributes and props for a virtual item.\n   * Returns: { 'aria-setsize', 'aria-posinset', 'data-index', index }\n   */\n  getItemProps: (virtualItem: { index: number }) => {\n    \"aria-setsize\": number;\n    \"aria-posinset\": number;\n    \"data-index\": number;\n    index: number;\n  };\n}\n\nexport function useListVirtualizer<T>(\n  options: UseListVirtualizerOptions<T>\n): UseListVirtualizerReturn<T> {\n  const {\n    enabled: _enabled = true,\n    items,\n    filteredItems,\n    estimateSize = 40,\n    overscan = 20,\n    paddingStart = 8,\n    paddingEnd = 8,\n    virtualizerRef,\n  } = options;\n\n  const scrollElementRef = React.useRef<HTMLDivElement | null>(null);\n\n  const virtualizer = useVirtualizer({\n    // Don't pass `enabled` to virtualizer - let it always calculate sizes\n    // to prevent height jumping during close animation\n    count: filteredItems.length,\n    getScrollElement: () => scrollElementRef.current,\n    estimateSize:\n      typeof estimateSize === \"function\"\n        ? (index) => estimateSize(index, filteredItems[index])\n        : () => estimateSize,\n    overscan,\n    paddingStart,\n    paddingEnd,\n    scrollPaddingStart: paddingStart,\n    scrollPaddingEnd: paddingEnd,\n  });\n\n  // Expose virtualizer to parent when ref is provided\n  React.useImperativeHandle(virtualizerRef, () => virtualizer, [virtualizer]);\n\n  // Ref callback that stores the element and triggers measurement\n  const scrollRef = React.useCallback(\n    (element: HTMLDivElement | null) => {\n      scrollElementRef.current = element;\n      if (element) {\n        virtualizer.measure();\n      }\n    },\n    [virtualizer]\n  );\n\n  // Handler for keyboard/pointer navigation scroll-to-index\n  const onItemHighlighted = React.useCallback(\n    (\n      item: T | null | undefined,\n      {\n        reason,\n        index,\n      }: { reason: \"none\" | \"keyboard\" | \"pointer\"; index: number }\n    ) => {\n      if (item == null) return;\n\n      const isStart = index === 0;\n      const isEnd = index === filteredItems.length - 1;\n\n      // Scroll on initial highlight or keyboard navigation at edges\n      const shouldScroll =\n        reason === \"none\" || (reason === \"keyboard\" && (isStart || isEnd));\n\n      if (shouldScroll) {\n        queueMicrotask(() => {\n          virtualizer.scrollToIndex(index, {\n            align: isEnd ? \"start\" : \"end\",\n          });\n        });\n      }\n    },\n    [filteredItems.length, virtualizer]\n  );\n\n  const getItem = React.useCallback(\n    (virtualItem: { index: number }) => filteredItems[virtualItem.index],\n    [filteredItems]\n  );\n\n  const getItemStyle = React.useCallback(\n    (virtualItem: { start: number }): React.CSSProperties => ({\n      position: \"absolute\",\n      top: 0,\n      left: 0,\n      right: 0, // Use right: 0 instead of width: 100% so margins work correctly\n      // No explicit height - let content determine size for dynamic measurement\n      transform: `translateY(${virtualItem.start}px)`,\n    }),\n    []\n  );\n\n  const getItemProps = React.useCallback(\n    (virtualItem: { index: number }) => ({\n      \"aria-setsize\": filteredItems.length,\n      \"aria-posinset\": virtualItem.index + 1,\n      \"data-index\": virtualItem.index,\n      index: virtualItem.index,\n    }),\n    [filteredItems.length]\n  );\n\n  return {\n    rootProps: {\n      virtualized: true,\n      items,\n      filteredItems,\n      onItemHighlighted,\n    },\n    scrollRef,\n    measureRef: virtualizer.measureElement as (\n      element: HTMLDivElement | null\n    ) => void,\n    totalSize: filteredItems.length > 0 ? virtualizer.getTotalSize() : 0,\n    virtualItems: virtualizer.getVirtualItems(),\n    getItem,\n    getItemStyle,\n    getItemProps,\n  };\n}\n",
      "type": "registry:hook",
      "target": "hooks/cubby-ui/use-list-virtualizer.ts"
    }
  ],
  "type": "registry:hook"
}