react-simple-virtualize 2.0.1 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -35,25 +35,25 @@ const MyLargeList = () => {
35
35
  // 1. Setup your data
36
36
  const items = Array.from({ length: 10000 }, (_, i) => `Item ${i + 1}`);
37
37
 
38
- // 2. Define dimensions
39
- const containerHeight = 500;
40
- const itemHeight = 50;
41
-
42
- // 3. Initialize the hook
43
- const { virtualItems, totalHeight, onScroll } = useVirtualize({
38
+ // 2. Initialize the hook
39
+ const {
40
+ virtualItems,
41
+ totalHeight,
42
+ scrollRef,
43
+ onScroll
44
+ } = useVirtualize({
44
45
  itemCount: items.length,
45
- itemHeight,
46
- containerHeight,
47
- overscan: 5, // Optional: Number of items to render outside viewport
46
+ itemHeight: 50,
47
+ overscan: 5,
48
48
  });
49
49
 
50
50
  return (
51
- // Outer Container: Needs fixed height & overflow-y: auto
51
+ // Outer Container: Needs a size (fixed or flex) & overflow-y: auto
52
52
  <div
53
- onScroll={onScroll}
54
53
  ref={scrollRef}
54
+ onScroll={onScroll}
55
55
  style={{
56
- height: containerHeight,
56
+ height: '500px', // Or '100%', 'flex: 1'
57
57
  overflowY: 'auto',
58
58
  position: 'relative',
59
59
  border: '1px solid #ccc'
@@ -63,20 +63,20 @@ const MyLargeList = () => {
63
63
  <div style={{ height: totalHeight, position: 'relative' }}>
64
64
 
65
65
  {/* Render only visible items */}
66
- {virtualItems.map(({ index, offsetTop }) => (
66
+ {virtualItems.map(({ index, offsetTop, measureRef }) => (
67
67
  <div
68
68
  key={index}
69
- ref={virtualItem.measureRef}
69
+ ref={measureRef} // <--- Crucial for Dynamic Height!
70
70
  style={{
71
71
  position: 'absolute',
72
72
  top: 0,
73
73
  left: 0,
74
74
  width: '100%',
75
- height: itemHeight,
76
- transform: `translateY(${offsetTop}px)`, // Crucial for positioning
75
+ transform: `translateY(${offsetTop}px)`,
77
76
  display: 'flex',
78
77
  alignItems: 'center',
79
78
  paddingLeft: 10,
79
+ minHeight: 50, // Optional: just for visual
80
80
  background: index % 2 === 0 ? '#fff' : '#f5f5f5'
81
81
  }}
82
82
  >
package/dist/index.d.ts CHANGED
@@ -20,7 +20,7 @@ export interface VirtualItem {
20
20
  export declare function useVirtualize({ itemCount, itemHeight: estimatedItemHeight, overscan, containerHeight: propsContainerHeight, }: UseVirtualizeProps): {
21
21
  virtualItems: VirtualItem[];
22
22
  totalHeight: number;
23
- scrollRef: import("react").RefObject<HTMLDivElement | undefined>;
23
+ scrollRef: import("react").RefObject<HTMLDivElement | null>;
24
24
  measurementVersion: number;
25
25
  onScroll: (e: UIEvent<HTMLElement>) => void;
26
26
  };
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- var e=require("react");exports.useVirtualize=function(r){var t=r.itemCount,n=r.itemHeight,u=r.overscan,c=void 0===u?3:u,i=r.containerHeight,a=e.useState(0),o=a[0],f=a[1],s=e.useState(0),l=s[0],v=s[1],h=null!=i?i:l,R=e.useRef({}),m=e.useRef({}),g=e.useRef(-1),d=e.useRef(void 0),b=e.useRef(0),C=e.useRef(0),M=e.useState(0),k=M[0],p=M[1],x=e.useCallback(function(){return C.current>0?b.current/C.current:n},[n]);e.useLayoutEffect(function(){if(void 0===i){var e=d.current;if(e){var r=new ResizeObserver(function(e){var r=e[0];r.contentRect.height!==l&&v(r.contentRect.height)});return r.observe(e),function(){return r.disconnect()}}}},[i,l]);var S=e.useCallback(function(e){if(e<=g.current)return m.current[e]||0;var r=g.current;return(r>=0?m.current[r]:0)+(r>=0?R.current[r]:0)+(e-r-1)*x()},[n,x]),H=e.useCallback(function(e){for(var r=0,n=t-1;r<=n;){var u=Math.floor((r+n)/2),c=S(u),i=R.current[u]||x();if(c<=e&&c+i>e)return u;c<e?r=u+1:n=u-1}return Math.max(0,r-1)},[S,t,n]),T=e.useCallback(function(e){return function(r){if(r){var t=r.getBoundingClientRect().height,n=R.current[e];if(n!==t){if(void 0===n?(b.current+=t,C.current+=1):b.current+=t-n,R.current[e]=t,e>g.current){var u=S(e);m.current[e]=u,g.current=e}else g.current=Math.min(g.current,e-1);p(function(e){return e+1})}}}},[S]),z=H(o),V=Math.min(t-1,H(o+h)+c);return{virtualItems:e.useMemo(function(){for(var e=[],r=Math.max(0,z-c);r<=V;r++)e.push({index:r,offsetTop:S(r),measureRef:T(r)});return e},[z,V,S,T,c]),totalHeight:S(t),scrollRef:d,measurementVersion:k,onScroll:function(e){return f(e.currentTarget.scrollTop)}}};
1
+ var e=require("react");exports.useVirtualize=function(r){var t=r.itemCount,n=r.itemHeight,u=r.overscan,c=void 0===u?3:u,i=r.containerHeight,a=e.useState(0),o=a[0],f=a[1],s=e.useState(0),l=s[0],v=s[1],h=null!=i?i:l,R=e.useRef({}),m=e.useRef({}),g=e.useRef(-1),b=e.useRef(null),d=e.useRef(0),C=e.useRef(0),M=e.useState(0),k=M[0],p=M[1],x=e.useCallback(function(){return C.current>0?d.current/C.current:n},[n]);e.useLayoutEffect(function(){if(void 0===i){var e=b.current;if(e){var r=new ResizeObserver(function(e){var r=e[0];r.contentRect.height!==l&&v(r.contentRect.height)});return r.observe(e),function(){return r.disconnect()}}}},[i,l]);var S=e.useCallback(function(e){if(e<=g.current)return m.current[e]||0;var r=g.current;return(r>=0?m.current[r]:0)+(r>=0?R.current[r]:0)+(e-r-1)*x()},[n,x]),H=e.useCallback(function(e){for(var r=0,n=t-1;r<=n;){var u=Math.floor((r+n)/2),c=S(u),i=R.current[u]||x();if(c<=e&&c+i>e)return u;c<e?r=u+1:n=u-1}return Math.max(0,r-1)},[S,t,n]),T=e.useCallback(function(e){return function(r){if(r){var t=r.getBoundingClientRect().height,n=R.current[e];if(n!==t){if(void 0===n?(d.current+=t,C.current+=1):d.current+=t-n,R.current[e]=t,e>g.current){var u=S(e);m.current[e]=u,g.current=e}else g.current=Math.min(g.current,e-1);p(function(e){return e+1})}}}},[S]),z=H(o),V=Math.min(t-1,H(o+h)+c);return{virtualItems:e.useMemo(function(){for(var e=[],r=Math.max(0,z-c);r<=V;r++)e.push({index:r,offsetTop:S(r),measureRef:T(r)});return e},[z,V,S,T,c]),totalHeight:S(t),scrollRef:b,measurementVersion:k,onScroll:function(e){return f(e.currentTarget.scrollTop)}}};
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import {\n useState,\n useRef,\n useMemo,\n useCallback,\n useLayoutEffect,\n UIEvent,\n} from \"react\";\n\nexport interface UseVirtualizeProps {\n /** Total number of items in the list */\n itemCount: number;\n /** Estimated height of a single item (used for unmeasured items) */\n itemHeight: number;\n /** Number of extra items to render outside the visible view (default: 3) */\n overscan?: number;\n /** Fixed container height. If not provided, it will be measured automatically via ResizeObserver */\n containerHeight?: number;\n}\n\nexport interface VirtualItem {\n /** The index of the item in the original data array */\n index: number;\n /** The calculated top position (px) for absolute positioning */\n offsetTop: number;\n /** Callback ref to measure the actual DOM height of the item */\n measureRef: (el: HTMLElement | null) => void;\n}\n\nexport function useVirtualize({\n itemCount,\n itemHeight: estimatedItemHeight,\n overscan = 3,\n containerHeight: propsContainerHeight,\n}: UseVirtualizeProps) {\n const [scrollTop, setScrollTop] = useState(0);\n const [observedHeight, setObservedHeight] = useState(0);\n\n // Determine the effective container height:\n // Use the prop if provided; otherwise, fall back to the measured height.\n const height = propsContainerHeight ?? observedHeight;\n\n // --- REFS (State that doesn't trigger re-renders) ---\n\n // Stores the exact height of each measured item: { index: height }\n const measurementCache = useRef({} as Record<number, number>);\n\n // Stores the calculated total offset for each index: { index: offset }\n // This acts as a Prefix Sum array to avoid recalculating from 0 every time.\n const offsetCache = useRef({} as Record<number, number>);\n\n // Tracks the last index that has a valid calculated offset in the cache.\n const lastMeasuredIndex = useRef(-1);\n\n // Reference to the scrollable container (used for ResizeObserver)\n const scrollRef = useRef<HTMLDivElement>(undefined);\n\n // Total measured height (Pixel)\n const totalMeasuredSize = useRef(0);\n // Total measured element?\n const totalMeasuredCount = useRef(0);\n\n const [measurementVersion, setMeasurementVersion] = useState(0);\n\n // --- HELPER: GET ESTIMATED HEIGHT ---\n // Zeka burada: Eğer hiç ölçüm yoksa senin verdiğin 'itemHeight'ı kullan.\n // Ama ölçüm yaptıysak, gerçek ortalamayı kullan.\n const getEstimatedItemHeight = useCallback(() => {\n return totalMeasuredCount.current > 0\n ? totalMeasuredSize.current / totalMeasuredCount.current\n : estimatedItemHeight;\n }, [estimatedItemHeight]);\n\n // --- 1. AUTO-SIZER LOGIC ---\n useLayoutEffect(() => {\n // If the user provided a fixed height, skip observation to save resources.\n if (propsContainerHeight !== undefined) return;\n\n const scrollElement = scrollRef.current;\n if (!scrollElement) return;\n\n const observer = new ResizeObserver(([entry]) => {\n // Update state only if dimensions actually changed\n if (entry.contentRect.height !== observedHeight) {\n setObservedHeight(entry.contentRect.height);\n }\n });\n\n observer.observe(scrollElement);\n\n return () => observer.disconnect();\n }, [propsContainerHeight, observedHeight]);\n\n // --- 2. OFFSET CALCULATOR (Core Logic) ---\n // Returns the vertical position (px) of an item.\n const getItemOffset = useCallback(\n (index: number) => {\n // If the offset for this index is already cached, return it directly.\n if (index <= lastMeasuredIndex.current) {\n return offsetCache.current[index] || 0;\n }\n\n // 1. Retrieve reference data from the last measured item.\n const lastIndex = lastMeasuredIndex.current;\n\n // If no items have been measured yet (-1), the starting offset is 0.\n // Otherwise, retrieve the offset of the last measured item.\n const lastOffset = lastIndex >= 0 ? offsetCache.current[lastIndex] : 0;\n\n // Retrieve the height of the last measured item (default to 0 if none measured).\n const lastHeight =\n lastIndex >= 0 ? measurementCache.current[lastIndex] : 0;\n\n // 2. Calculate the bottom position of the last measured item.\n // This serves as the anchor point for estimating subsequent offsets.\n const lastBottom = lastOffset + lastHeight;\n\n // 3. Calculate how many unmeasured items exist between the last measured index and the target index.\n const unmeasuredCount = index - lastIndex - 1;\n\n // 4. Result: Anchor Point + (Number of Unmeasured Items * Estimated Height)\n return lastBottom + unmeasuredCount * getEstimatedItemHeight();\n },\n [estimatedItemHeight, getEstimatedItemHeight]\n );\n\n // --- 3. BINARY SEARCH (Find Visible Index) ---\n // Finds the index corresponding to the current scroll position.\n // Performance: O(log N) - Crucial for large lists.\n const getStartIndexForOffset = useCallback(\n (offset: number) => {\n let low = 0;\n let high = itemCount - 1;\n\n while (low <= high) {\n const mid = Math.floor((low + high) / 2);\n const currentOffset = getItemOffset(mid);\n const currentHeight =\n measurementCache.current[mid] || getEstimatedItemHeight();\n\n if (currentOffset <= offset && currentOffset + currentHeight > offset) {\n return mid;\n } else if (currentOffset < offset) {\n low = mid + 1;\n } else {\n high = mid - 1;\n }\n }\n\n // Fallback to 0 if not found\n return Math.max(0, low - 1);\n },\n [getItemOffset, itemCount, estimatedItemHeight]\n );\n\n // --- 4. MEASUREMENT CALLBACK (The \"Ref\" logic) ---\n // This function is passed to the consumer to attach to their DOM elements.\n const measureElement = useCallback(\n (index: number) => (el: HTMLElement | null) => {\n if (!el) return;\n\n const measuredHeight = el.getBoundingClientRect().height;\n const prevHeight = measurementCache.current[index];\n\n if (prevHeight !== measuredHeight) {\n if (prevHeight === undefined) {\n totalMeasuredSize.current += measuredHeight;\n totalMeasuredCount.current += 1;\n } else {\n totalMeasuredSize.current += measuredHeight - prevHeight;\n }\n\n measurementCache.current[index] = measuredHeight;\n\n if (index > lastMeasuredIndex.current) {\n const prevOffset = getItemOffset(index);\n offsetCache.current[index] = prevOffset;\n lastMeasuredIndex.current = index;\n } else {\n lastMeasuredIndex.current = Math.min(\n lastMeasuredIndex.current,\n index - 1\n );\n }\n\n setMeasurementVersion(v => v + 1);\n }\n },\n [getItemOffset]\n );\n\n // --- 5. RENDER RANGE CALCULATION ---\n const startIndex = getStartIndexForOffset(scrollTop);\n const endIndex = Math.min(\n itemCount - 1,\n getStartIndexForOffset(scrollTop + height) + overscan\n );\n\n // Generate the virtual items array to be rendered\n const virtualItems = useMemo(() => {\n const items: VirtualItem[] = [];\n for (let i = Math.max(0, startIndex - overscan); i <= endIndex; i++) {\n items.push({\n index: i,\n offsetTop: getItemOffset(i),\n measureRef: measureElement(i),\n });\n }\n return items;\n }, [startIndex, endIndex, getItemOffset, measureElement, overscan]);\n\n // Calculate total list height for the scrollbar\n const totalHeight = getItemOffset(itemCount);\n\n return {\n virtualItems,\n totalHeight,\n scrollRef,\n measurementVersion,\n onScroll: (e: UIEvent<HTMLElement>) =>\n setScrollTop(e.currentTarget.scrollTop),\n };\n}\n"],"names":["_ref","itemCount","estimatedItemHeight","itemHeight","_ref$overscan","overscan","propsContainerHeight","containerHeight","_useState","useState","scrollTop","setScrollTop","_useState2","observedHeight","setObservedHeight","height","measurementCache","useRef","offsetCache","lastMeasuredIndex","scrollRef","undefined","totalMeasuredSize","totalMeasuredCount","_useState3","measurementVersion","setMeasurementVersion","getEstimatedItemHeight","useCallback","current","useLayoutEffect","scrollElement","observer","ResizeObserver","_ref2","entry","contentRect","observe","disconnect","getItemOffset","index","lastIndex","getStartIndexForOffset","offset","low","high","mid","Math","floor","currentOffset","currentHeight","max","measureElement","el","measuredHeight","getBoundingClientRect","prevHeight","prevOffset","min","v","startIndex","endIndex","virtualItems","useMemo","items","i","push","offsetTop","measureRef","totalHeight","onScroll","e","currentTarget"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import {\n useState,\n useRef,\n useMemo,\n useCallback,\n useLayoutEffect,\n UIEvent,\n} from \"react\";\n\nexport interface UseVirtualizeProps {\n /** Total number of items in the list */\n itemCount: number;\n /** Estimated height of a single item (used for unmeasured items) */\n itemHeight: number;\n /** Number of extra items to render outside the visible view (default: 3) */\n overscan?: number;\n /** Fixed container height. If not provided, it will be measured automatically via ResizeObserver */\n containerHeight?: number;\n}\n\nexport interface VirtualItem {\n /** The index of the item in the original data array */\n index: number;\n /** The calculated top position (px) for absolute positioning */\n offsetTop: number;\n /** Callback ref to measure the actual DOM height of the item */\n measureRef: (el: HTMLElement | null) => void;\n}\n\nexport function useVirtualize({\n itemCount,\n itemHeight: estimatedItemHeight,\n overscan = 3,\n containerHeight: propsContainerHeight,\n}: UseVirtualizeProps) {\n const [scrollTop, setScrollTop] = useState(0);\n const [observedHeight, setObservedHeight] = useState(0);\n\n // Determine the effective container height:\n // Use the prop if provided; otherwise, fall back to the measured height.\n const height = propsContainerHeight ?? observedHeight;\n\n // --- REFS (State that doesn't trigger re-renders) ---\n\n // Stores the exact height of each measured item: { index: height }\n const measurementCache = useRef({} as Record<number, number>);\n\n // Stores the calculated total offset for each index: { index: offset }\n // This acts as a Prefix Sum array to avoid recalculating from 0 every time.\n const offsetCache = useRef({} as Record<number, number>);\n\n // Tracks the last index that has a valid calculated offset in the cache.\n const lastMeasuredIndex = useRef(-1);\n\n // Reference to the scrollable container (used for ResizeObserver)\n const scrollRef = useRef<HTMLDivElement>(null);\n\n // Total measured height (Pixel)\n const totalMeasuredSize = useRef(0);\n // Total measured element?\n const totalMeasuredCount = useRef(0);\n\n const [measurementVersion, setMeasurementVersion] = useState(0);\n\n // --- HELPER: GET ESTIMATED HEIGHT ---\n // Zeka burada: Eğer hiç ölçüm yoksa senin verdiğin 'itemHeight'ı kullan.\n // Ama ölçüm yaptıysak, gerçek ortalamayı kullan.\n const getEstimatedItemHeight = useCallback(() => {\n return totalMeasuredCount.current > 0\n ? totalMeasuredSize.current / totalMeasuredCount.current\n : estimatedItemHeight;\n }, [estimatedItemHeight]);\n\n // --- 1. AUTO-SIZER LOGIC ---\n useLayoutEffect(() => {\n // If the user provided a fixed height, skip observation to save resources.\n if (propsContainerHeight !== undefined) return;\n\n const scrollElement = scrollRef.current;\n if (!scrollElement) return;\n\n const observer = new ResizeObserver(([entry]) => {\n // Update state only if dimensions actually changed\n if (entry.contentRect.height !== observedHeight) {\n setObservedHeight(entry.contentRect.height);\n }\n });\n\n observer.observe(scrollElement);\n\n return () => observer.disconnect();\n }, [propsContainerHeight, observedHeight]);\n\n // --- 2. OFFSET CALCULATOR (Core Logic) ---\n // Returns the vertical position (px) of an item.\n const getItemOffset = useCallback(\n (index: number) => {\n // If the offset for this index is already cached, return it directly.\n if (index <= lastMeasuredIndex.current) {\n return offsetCache.current[index] || 0;\n }\n\n // 1. Retrieve reference data from the last measured item.\n const lastIndex = lastMeasuredIndex.current;\n\n // If no items have been measured yet (-1), the starting offset is 0.\n // Otherwise, retrieve the offset of the last measured item.\n const lastOffset = lastIndex >= 0 ? offsetCache.current[lastIndex] : 0;\n\n // Retrieve the height of the last measured item (default to 0 if none measured).\n const lastHeight =\n lastIndex >= 0 ? measurementCache.current[lastIndex] : 0;\n\n // 2. Calculate the bottom position of the last measured item.\n // This serves as the anchor point for estimating subsequent offsets.\n const lastBottom = lastOffset + lastHeight;\n\n // 3. Calculate how many unmeasured items exist between the last measured index and the target index.\n const unmeasuredCount = index - lastIndex - 1;\n\n // 4. Result: Anchor Point + (Number of Unmeasured Items * Estimated Height)\n return lastBottom + unmeasuredCount * getEstimatedItemHeight();\n },\n [estimatedItemHeight, getEstimatedItemHeight]\n );\n\n // --- 3. BINARY SEARCH (Find Visible Index) ---\n // Finds the index corresponding to the current scroll position.\n // Performance: O(log N) - Crucial for large lists.\n const getStartIndexForOffset = useCallback(\n (offset: number) => {\n let low = 0;\n let high = itemCount - 1;\n\n while (low <= high) {\n const mid = Math.floor((low + high) / 2);\n const currentOffset = getItemOffset(mid);\n const currentHeight =\n measurementCache.current[mid] || getEstimatedItemHeight();\n\n if (currentOffset <= offset && currentOffset + currentHeight > offset) {\n return mid;\n } else if (currentOffset < offset) {\n low = mid + 1;\n } else {\n high = mid - 1;\n }\n }\n\n // Fallback to 0 if not found\n return Math.max(0, low - 1);\n },\n [getItemOffset, itemCount, estimatedItemHeight]\n );\n\n // --- 4. MEASUREMENT CALLBACK (The \"Ref\" logic) ---\n // This function is passed to the consumer to attach to their DOM elements.\n const measureElement = useCallback(\n (index: number) => (el: HTMLElement | null) => {\n if (!el) return;\n\n const measuredHeight = el.getBoundingClientRect().height;\n const prevHeight = measurementCache.current[index];\n\n if (prevHeight !== measuredHeight) {\n if (prevHeight === undefined) {\n totalMeasuredSize.current += measuredHeight;\n totalMeasuredCount.current += 1;\n } else {\n totalMeasuredSize.current += measuredHeight - prevHeight;\n }\n\n measurementCache.current[index] = measuredHeight;\n\n if (index > lastMeasuredIndex.current) {\n const prevOffset = getItemOffset(index);\n offsetCache.current[index] = prevOffset;\n lastMeasuredIndex.current = index;\n } else {\n lastMeasuredIndex.current = Math.min(\n lastMeasuredIndex.current,\n index - 1\n );\n }\n\n setMeasurementVersion(v => v + 1);\n }\n },\n [getItemOffset]\n );\n\n // --- 5. RENDER RANGE CALCULATION ---\n const startIndex = getStartIndexForOffset(scrollTop);\n const endIndex = Math.min(\n itemCount - 1,\n getStartIndexForOffset(scrollTop + height) + overscan\n );\n\n // Generate the virtual items array to be rendered\n const virtualItems = useMemo(() => {\n const items: VirtualItem[] = [];\n for (let i = Math.max(0, startIndex - overscan); i <= endIndex; i++) {\n items.push({\n index: i,\n offsetTop: getItemOffset(i),\n measureRef: measureElement(i),\n });\n }\n return items;\n }, [startIndex, endIndex, getItemOffset, measureElement, overscan]);\n\n // Calculate total list height for the scrollbar\n const totalHeight = getItemOffset(itemCount);\n\n return {\n virtualItems,\n totalHeight,\n scrollRef,\n measurementVersion,\n onScroll: (e: UIEvent<HTMLElement>) =>\n setScrollTop(e.currentTarget.scrollTop),\n };\n}\n"],"names":["_ref","itemCount","estimatedItemHeight","itemHeight","_ref$overscan","overscan","propsContainerHeight","containerHeight","_useState","useState","scrollTop","setScrollTop","_useState2","observedHeight","setObservedHeight","height","measurementCache","useRef","offsetCache","lastMeasuredIndex","scrollRef","totalMeasuredSize","totalMeasuredCount","_useState3","measurementVersion","setMeasurementVersion","getEstimatedItemHeight","useCallback","current","useLayoutEffect","undefined","scrollElement","observer","ResizeObserver","_ref2","entry","contentRect","observe","disconnect","getItemOffset","index","lastIndex","getStartIndexForOffset","offset","low","high","mid","Math","floor","currentOffset","currentHeight","max","measureElement","el","measuredHeight","getBoundingClientRect","prevHeight","prevOffset","min","v","startIndex","endIndex","virtualItems","useMemo","items","i","push","offsetTop","measureRef","totalHeight","onScroll","e","currentTarget"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{useState as r,useRef as t,useCallback as e,useLayoutEffect as n,useMemo as c}from"react";function u({itemCount:u,itemHeight:o,overscan:i=3,containerHeight:s}){const[f,a]=r(0),[h,l]=r(0),m=null!=s?s:h,g=t({}),v=t({}),d=t(-1),R=t(void 0),p=t(0),M=t(0),[x,H]=r(0),T=e(()=>M.current>0?p.current/M.current:o,[o]);n(()=>{if(void 0!==s)return;const r=R.current;if(!r)return;const t=new ResizeObserver(([r])=>{r.contentRect.height!==h&&l(r.contentRect.height)});return t.observe(r),()=>t.disconnect()},[s,h]);const b=e(r=>{if(r<=d.current)return v.current[r]||0;const t=d.current;return(t>=0?v.current[t]:0)+(t>=0?g.current[t]:0)+(r-t-1)*T()},[o,T]),C=e(r=>{let t=0,e=u-1;for(;t<=e;){const n=Math.floor((t+e)/2),c=b(n),u=g.current[n]||T();if(c<=r&&c+u>r)return n;c<r?t=n+1:e=n-1}return Math.max(0,t-1)},[b,u,o]),w=e(r=>t=>{if(!t)return;const e=t.getBoundingClientRect().height,n=g.current[r];if(n!==e){if(void 0===n?(p.current+=e,M.current+=1):p.current+=e-n,g.current[r]=e,r>d.current){const t=b(r);v.current[r]=t,d.current=r}else d.current=Math.min(d.current,r-1);H(r=>r+1)}},[b]),z=C(f),B=Math.min(u-1,C(f+m)+i);return{virtualItems:c(()=>{const r=[];for(let t=Math.max(0,z-i);t<=B;t++)r.push({index:t,offsetTop:b(t),measureRef:w(t)});return r},[z,B,b,w,i]),totalHeight:b(u),scrollRef:R,measurementVersion:x,onScroll:r=>a(r.currentTarget.scrollTop)}}export{u as useVirtualize};
1
+ import{useState as r,useRef as t,useCallback as e,useLayoutEffect as n,useMemo as c}from"react";function u({itemCount:u,itemHeight:o,overscan:i=3,containerHeight:s}){const[l,f]=r(0),[a,h]=r(0),m=null!=s?s:a,g=t({}),v=t({}),R=t(-1),d=t(null),p=t(0),M=t(0),[x,H]=r(0),T=e(()=>M.current>0?p.current/M.current:o,[o]);n(()=>{if(void 0!==s)return;const r=d.current;if(!r)return;const t=new ResizeObserver(([r])=>{r.contentRect.height!==a&&h(r.contentRect.height)});return t.observe(r),()=>t.disconnect()},[s,a]);const b=e(r=>{if(r<=R.current)return v.current[r]||0;const t=R.current;return(t>=0?v.current[t]:0)+(t>=0?g.current[t]:0)+(r-t-1)*T()},[o,T]),C=e(r=>{let t=0,e=u-1;for(;t<=e;){const n=Math.floor((t+e)/2),c=b(n),u=g.current[n]||T();if(c<=r&&c+u>r)return n;c<r?t=n+1:e=n-1}return Math.max(0,t-1)},[b,u,o]),w=e(r=>t=>{if(!t)return;const e=t.getBoundingClientRect().height,n=g.current[r];if(n!==e){if(void 0===n?(p.current+=e,M.current+=1):p.current+=e-n,g.current[r]=e,r>R.current){const t=b(r);v.current[r]=t,R.current=r}else R.current=Math.min(R.current,r-1);H(r=>r+1)}},[b]),z=C(l),B=Math.min(u-1,C(l+m)+i);return{virtualItems:c(()=>{const r=[];for(let t=Math.max(0,z-i);t<=B;t++)r.push({index:t,offsetTop:b(t),measureRef:w(t)});return r},[z,B,b,w,i]),totalHeight:b(u),scrollRef:d,measurementVersion:x,onScroll:r=>f(r.currentTarget.scrollTop)}}export{u as useVirtualize};
2
2
  //# sourceMappingURL=index.modern.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.modern.mjs","sources":["../src/index.ts"],"sourcesContent":["import {\n useState,\n useRef,\n useMemo,\n useCallback,\n useLayoutEffect,\n UIEvent,\n} from \"react\";\n\nexport interface UseVirtualizeProps {\n /** Total number of items in the list */\n itemCount: number;\n /** Estimated height of a single item (used for unmeasured items) */\n itemHeight: number;\n /** Number of extra items to render outside the visible view (default: 3) */\n overscan?: number;\n /** Fixed container height. If not provided, it will be measured automatically via ResizeObserver */\n containerHeight?: number;\n}\n\nexport interface VirtualItem {\n /** The index of the item in the original data array */\n index: number;\n /** The calculated top position (px) for absolute positioning */\n offsetTop: number;\n /** Callback ref to measure the actual DOM height of the item */\n measureRef: (el: HTMLElement | null) => void;\n}\n\nexport function useVirtualize({\n itemCount,\n itemHeight: estimatedItemHeight,\n overscan = 3,\n containerHeight: propsContainerHeight,\n}: UseVirtualizeProps) {\n const [scrollTop, setScrollTop] = useState(0);\n const [observedHeight, setObservedHeight] = useState(0);\n\n // Determine the effective container height:\n // Use the prop if provided; otherwise, fall back to the measured height.\n const height = propsContainerHeight ?? observedHeight;\n\n // --- REFS (State that doesn't trigger re-renders) ---\n\n // Stores the exact height of each measured item: { index: height }\n const measurementCache = useRef({} as Record<number, number>);\n\n // Stores the calculated total offset for each index: { index: offset }\n // This acts as a Prefix Sum array to avoid recalculating from 0 every time.\n const offsetCache = useRef({} as Record<number, number>);\n\n // Tracks the last index that has a valid calculated offset in the cache.\n const lastMeasuredIndex = useRef(-1);\n\n // Reference to the scrollable container (used for ResizeObserver)\n const scrollRef = useRef<HTMLDivElement>(undefined);\n\n // Total measured height (Pixel)\n const totalMeasuredSize = useRef(0);\n // Total measured element?\n const totalMeasuredCount = useRef(0);\n\n const [measurementVersion, setMeasurementVersion] = useState(0);\n\n // --- HELPER: GET ESTIMATED HEIGHT ---\n // Zeka burada: Eğer hiç ölçüm yoksa senin verdiğin 'itemHeight'ı kullan.\n // Ama ölçüm yaptıysak, gerçek ortalamayı kullan.\n const getEstimatedItemHeight = useCallback(() => {\n return totalMeasuredCount.current > 0\n ? totalMeasuredSize.current / totalMeasuredCount.current\n : estimatedItemHeight;\n }, [estimatedItemHeight]);\n\n // --- 1. AUTO-SIZER LOGIC ---\n useLayoutEffect(() => {\n // If the user provided a fixed height, skip observation to save resources.\n if (propsContainerHeight !== undefined) return;\n\n const scrollElement = scrollRef.current;\n if (!scrollElement) return;\n\n const observer = new ResizeObserver(([entry]) => {\n // Update state only if dimensions actually changed\n if (entry.contentRect.height !== observedHeight) {\n setObservedHeight(entry.contentRect.height);\n }\n });\n\n observer.observe(scrollElement);\n\n return () => observer.disconnect();\n }, [propsContainerHeight, observedHeight]);\n\n // --- 2. OFFSET CALCULATOR (Core Logic) ---\n // Returns the vertical position (px) of an item.\n const getItemOffset = useCallback(\n (index: number) => {\n // If the offset for this index is already cached, return it directly.\n if (index <= lastMeasuredIndex.current) {\n return offsetCache.current[index] || 0;\n }\n\n // 1. Retrieve reference data from the last measured item.\n const lastIndex = lastMeasuredIndex.current;\n\n // If no items have been measured yet (-1), the starting offset is 0.\n // Otherwise, retrieve the offset of the last measured item.\n const lastOffset = lastIndex >= 0 ? offsetCache.current[lastIndex] : 0;\n\n // Retrieve the height of the last measured item (default to 0 if none measured).\n const lastHeight =\n lastIndex >= 0 ? measurementCache.current[lastIndex] : 0;\n\n // 2. Calculate the bottom position of the last measured item.\n // This serves as the anchor point for estimating subsequent offsets.\n const lastBottom = lastOffset + lastHeight;\n\n // 3. Calculate how many unmeasured items exist between the last measured index and the target index.\n const unmeasuredCount = index - lastIndex - 1;\n\n // 4. Result: Anchor Point + (Number of Unmeasured Items * Estimated Height)\n return lastBottom + unmeasuredCount * getEstimatedItemHeight();\n },\n [estimatedItemHeight, getEstimatedItemHeight]\n );\n\n // --- 3. BINARY SEARCH (Find Visible Index) ---\n // Finds the index corresponding to the current scroll position.\n // Performance: O(log N) - Crucial for large lists.\n const getStartIndexForOffset = useCallback(\n (offset: number) => {\n let low = 0;\n let high = itemCount - 1;\n\n while (low <= high) {\n const mid = Math.floor((low + high) / 2);\n const currentOffset = getItemOffset(mid);\n const currentHeight =\n measurementCache.current[mid] || getEstimatedItemHeight();\n\n if (currentOffset <= offset && currentOffset + currentHeight > offset) {\n return mid;\n } else if (currentOffset < offset) {\n low = mid + 1;\n } else {\n high = mid - 1;\n }\n }\n\n // Fallback to 0 if not found\n return Math.max(0, low - 1);\n },\n [getItemOffset, itemCount, estimatedItemHeight]\n );\n\n // --- 4. MEASUREMENT CALLBACK (The \"Ref\" logic) ---\n // This function is passed to the consumer to attach to their DOM elements.\n const measureElement = useCallback(\n (index: number) => (el: HTMLElement | null) => {\n if (!el) return;\n\n const measuredHeight = el.getBoundingClientRect().height;\n const prevHeight = measurementCache.current[index];\n\n if (prevHeight !== measuredHeight) {\n if (prevHeight === undefined) {\n totalMeasuredSize.current += measuredHeight;\n totalMeasuredCount.current += 1;\n } else {\n totalMeasuredSize.current += measuredHeight - prevHeight;\n }\n\n measurementCache.current[index] = measuredHeight;\n\n if (index > lastMeasuredIndex.current) {\n const prevOffset = getItemOffset(index);\n offsetCache.current[index] = prevOffset;\n lastMeasuredIndex.current = index;\n } else {\n lastMeasuredIndex.current = Math.min(\n lastMeasuredIndex.current,\n index - 1\n );\n }\n\n setMeasurementVersion(v => v + 1);\n }\n },\n [getItemOffset]\n );\n\n // --- 5. RENDER RANGE CALCULATION ---\n const startIndex = getStartIndexForOffset(scrollTop);\n const endIndex = Math.min(\n itemCount - 1,\n getStartIndexForOffset(scrollTop + height) + overscan\n );\n\n // Generate the virtual items array to be rendered\n const virtualItems = useMemo(() => {\n const items: VirtualItem[] = [];\n for (let i = Math.max(0, startIndex - overscan); i <= endIndex; i++) {\n items.push({\n index: i,\n offsetTop: getItemOffset(i),\n measureRef: measureElement(i),\n });\n }\n return items;\n }, [startIndex, endIndex, getItemOffset, measureElement, overscan]);\n\n // Calculate total list height for the scrollbar\n const totalHeight = getItemOffset(itemCount);\n\n return {\n virtualItems,\n totalHeight,\n scrollRef,\n measurementVersion,\n onScroll: (e: UIEvent<HTMLElement>) =>\n setScrollTop(e.currentTarget.scrollTop),\n };\n}\n"],"names":["useVirtualize","itemCount","itemHeight","estimatedItemHeight","overscan","containerHeight","propsContainerHeight","scrollTop","setScrollTop","useState","observedHeight","setObservedHeight","height","measurementCache","useRef","offsetCache","lastMeasuredIndex","scrollRef","undefined","totalMeasuredSize","totalMeasuredCount","measurementVersion","setMeasurementVersion","getEstimatedItemHeight","useCallback","current","useLayoutEffect","scrollElement","observer","ResizeObserver","entry","contentRect","observe","disconnect","getItemOffset","index","lastIndex","getStartIndexForOffset","offset","low","high","mid","Math","floor","currentOffset","currentHeight","max","measureElement","el","measuredHeight","getBoundingClientRect","prevHeight","prevOffset","min","v","startIndex","endIndex","virtualItems","useMemo","items","i","push","offsetTop","measureRef","totalHeight","onScroll","e","currentTarget"],"mappings":"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"}
1
+ {"version":3,"file":"index.modern.mjs","sources":["../src/index.ts"],"sourcesContent":["import {\n useState,\n useRef,\n useMemo,\n useCallback,\n useLayoutEffect,\n UIEvent,\n} from \"react\";\n\nexport interface UseVirtualizeProps {\n /** Total number of items in the list */\n itemCount: number;\n /** Estimated height of a single item (used for unmeasured items) */\n itemHeight: number;\n /** Number of extra items to render outside the visible view (default: 3) */\n overscan?: number;\n /** Fixed container height. If not provided, it will be measured automatically via ResizeObserver */\n containerHeight?: number;\n}\n\nexport interface VirtualItem {\n /** The index of the item in the original data array */\n index: number;\n /** The calculated top position (px) for absolute positioning */\n offsetTop: number;\n /** Callback ref to measure the actual DOM height of the item */\n measureRef: (el: HTMLElement | null) => void;\n}\n\nexport function useVirtualize({\n itemCount,\n itemHeight: estimatedItemHeight,\n overscan = 3,\n containerHeight: propsContainerHeight,\n}: UseVirtualizeProps) {\n const [scrollTop, setScrollTop] = useState(0);\n const [observedHeight, setObservedHeight] = useState(0);\n\n // Determine the effective container height:\n // Use the prop if provided; otherwise, fall back to the measured height.\n const height = propsContainerHeight ?? observedHeight;\n\n // --- REFS (State that doesn't trigger re-renders) ---\n\n // Stores the exact height of each measured item: { index: height }\n const measurementCache = useRef({} as Record<number, number>);\n\n // Stores the calculated total offset for each index: { index: offset }\n // This acts as a Prefix Sum array to avoid recalculating from 0 every time.\n const offsetCache = useRef({} as Record<number, number>);\n\n // Tracks the last index that has a valid calculated offset in the cache.\n const lastMeasuredIndex = useRef(-1);\n\n // Reference to the scrollable container (used for ResizeObserver)\n const scrollRef = useRef<HTMLDivElement>(null);\n\n // Total measured height (Pixel)\n const totalMeasuredSize = useRef(0);\n // Total measured element?\n const totalMeasuredCount = useRef(0);\n\n const [measurementVersion, setMeasurementVersion] = useState(0);\n\n // --- HELPER: GET ESTIMATED HEIGHT ---\n // Zeka burada: Eğer hiç ölçüm yoksa senin verdiğin 'itemHeight'ı kullan.\n // Ama ölçüm yaptıysak, gerçek ortalamayı kullan.\n const getEstimatedItemHeight = useCallback(() => {\n return totalMeasuredCount.current > 0\n ? totalMeasuredSize.current / totalMeasuredCount.current\n : estimatedItemHeight;\n }, [estimatedItemHeight]);\n\n // --- 1. AUTO-SIZER LOGIC ---\n useLayoutEffect(() => {\n // If the user provided a fixed height, skip observation to save resources.\n if (propsContainerHeight !== undefined) return;\n\n const scrollElement = scrollRef.current;\n if (!scrollElement) return;\n\n const observer = new ResizeObserver(([entry]) => {\n // Update state only if dimensions actually changed\n if (entry.contentRect.height !== observedHeight) {\n setObservedHeight(entry.contentRect.height);\n }\n });\n\n observer.observe(scrollElement);\n\n return () => observer.disconnect();\n }, [propsContainerHeight, observedHeight]);\n\n // --- 2. OFFSET CALCULATOR (Core Logic) ---\n // Returns the vertical position (px) of an item.\n const getItemOffset = useCallback(\n (index: number) => {\n // If the offset for this index is already cached, return it directly.\n if (index <= lastMeasuredIndex.current) {\n return offsetCache.current[index] || 0;\n }\n\n // 1. Retrieve reference data from the last measured item.\n const lastIndex = lastMeasuredIndex.current;\n\n // If no items have been measured yet (-1), the starting offset is 0.\n // Otherwise, retrieve the offset of the last measured item.\n const lastOffset = lastIndex >= 0 ? offsetCache.current[lastIndex] : 0;\n\n // Retrieve the height of the last measured item (default to 0 if none measured).\n const lastHeight =\n lastIndex >= 0 ? measurementCache.current[lastIndex] : 0;\n\n // 2. Calculate the bottom position of the last measured item.\n // This serves as the anchor point for estimating subsequent offsets.\n const lastBottom = lastOffset + lastHeight;\n\n // 3. Calculate how many unmeasured items exist between the last measured index and the target index.\n const unmeasuredCount = index - lastIndex - 1;\n\n // 4. Result: Anchor Point + (Number of Unmeasured Items * Estimated Height)\n return lastBottom + unmeasuredCount * getEstimatedItemHeight();\n },\n [estimatedItemHeight, getEstimatedItemHeight]\n );\n\n // --- 3. BINARY SEARCH (Find Visible Index) ---\n // Finds the index corresponding to the current scroll position.\n // Performance: O(log N) - Crucial for large lists.\n const getStartIndexForOffset = useCallback(\n (offset: number) => {\n let low = 0;\n let high = itemCount - 1;\n\n while (low <= high) {\n const mid = Math.floor((low + high) / 2);\n const currentOffset = getItemOffset(mid);\n const currentHeight =\n measurementCache.current[mid] || getEstimatedItemHeight();\n\n if (currentOffset <= offset && currentOffset + currentHeight > offset) {\n return mid;\n } else if (currentOffset < offset) {\n low = mid + 1;\n } else {\n high = mid - 1;\n }\n }\n\n // Fallback to 0 if not found\n return Math.max(0, low - 1);\n },\n [getItemOffset, itemCount, estimatedItemHeight]\n );\n\n // --- 4. MEASUREMENT CALLBACK (The \"Ref\" logic) ---\n // This function is passed to the consumer to attach to their DOM elements.\n const measureElement = useCallback(\n (index: number) => (el: HTMLElement | null) => {\n if (!el) return;\n\n const measuredHeight = el.getBoundingClientRect().height;\n const prevHeight = measurementCache.current[index];\n\n if (prevHeight !== measuredHeight) {\n if (prevHeight === undefined) {\n totalMeasuredSize.current += measuredHeight;\n totalMeasuredCount.current += 1;\n } else {\n totalMeasuredSize.current += measuredHeight - prevHeight;\n }\n\n measurementCache.current[index] = measuredHeight;\n\n if (index > lastMeasuredIndex.current) {\n const prevOffset = getItemOffset(index);\n offsetCache.current[index] = prevOffset;\n lastMeasuredIndex.current = index;\n } else {\n lastMeasuredIndex.current = Math.min(\n lastMeasuredIndex.current,\n index - 1\n );\n }\n\n setMeasurementVersion(v => v + 1);\n }\n },\n [getItemOffset]\n );\n\n // --- 5. RENDER RANGE CALCULATION ---\n const startIndex = getStartIndexForOffset(scrollTop);\n const endIndex = Math.min(\n itemCount - 1,\n getStartIndexForOffset(scrollTop + height) + overscan\n );\n\n // Generate the virtual items array to be rendered\n const virtualItems = useMemo(() => {\n const items: VirtualItem[] = [];\n for (let i = Math.max(0, startIndex - overscan); i <= endIndex; i++) {\n items.push({\n index: i,\n offsetTop: getItemOffset(i),\n measureRef: measureElement(i),\n });\n }\n return items;\n }, [startIndex, endIndex, getItemOffset, measureElement, overscan]);\n\n // Calculate total list height for the scrollbar\n const totalHeight = getItemOffset(itemCount);\n\n return {\n virtualItems,\n totalHeight,\n scrollRef,\n measurementVersion,\n onScroll: (e: UIEvent<HTMLElement>) =>\n setScrollTop(e.currentTarget.scrollTop),\n };\n}\n"],"names":["useVirtualize","itemCount","itemHeight","estimatedItemHeight","overscan","containerHeight","propsContainerHeight","scrollTop","setScrollTop","useState","observedHeight","setObservedHeight","height","measurementCache","useRef","offsetCache","lastMeasuredIndex","scrollRef","totalMeasuredSize","totalMeasuredCount","measurementVersion","setMeasurementVersion","getEstimatedItemHeight","useCallback","current","useLayoutEffect","undefined","scrollElement","observer","ResizeObserver","entry","contentRect","observe","disconnect","getItemOffset","index","lastIndex","getStartIndexForOffset","offset","low","high","mid","Math","floor","currentOffset","currentHeight","max","measureElement","el","measuredHeight","getBoundingClientRect","prevHeight","prevOffset","min","v","startIndex","endIndex","virtualItems","useMemo","items","i","push","offsetTop","measureRef","totalHeight","onScroll","e","currentTarget"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{useState as r,useRef as t,useCallback as n,useLayoutEffect as e,useMemo as u}from"react";function c(c){var i=c.itemCount,o=c.itemHeight,f=c.overscan,a=void 0===f?3:f,v=c.containerHeight,h=r(0),s=h[0],l=h[1],m=r(0),g=m[0],d=m[1],R=null!=v?v:g,p=t({}),M=t({}),x=t(-1),H=t(void 0),T=t(0),b=t(0),C=r(0),w=C[0],z=C[1],B=n(function(){return b.current>0?T.current/b.current:o},[o]);e(function(){if(void 0===v){var r=H.current;if(r){var t=new ResizeObserver(function(r){var t=r[0];t.contentRect.height!==g&&d(t.contentRect.height)});return t.observe(r),function(){return t.disconnect()}}}},[v,g]);var I=n(function(r){if(r<=x.current)return M.current[r]||0;var t=x.current;return(t>=0?M.current[t]:0)+(t>=0?p.current[t]:0)+(r-t-1)*B()},[o,B]),O=n(function(r){for(var t=0,n=i-1;t<=n;){var e=Math.floor((t+n)/2),u=I(e),c=p.current[e]||B();if(u<=r&&u+c>r)return e;u<r?t=e+1:n=e-1}return Math.max(0,t-1)},[I,i,o]),S=n(function(r){return function(t){if(t){var n=t.getBoundingClientRect().height,e=p.current[r];if(e!==n){if(void 0===e?(T.current+=n,b.current+=1):T.current+=n-e,p.current[r]=n,r>x.current){var u=I(r);M.current[r]=u,x.current=r}else x.current=Math.min(x.current,r-1);z(function(r){return r+1})}}}},[I]),V=O(s),j=Math.min(i-1,O(s+R)+a);return{virtualItems:u(function(){for(var r=[],t=Math.max(0,V-a);t<=j;t++)r.push({index:t,offsetTop:I(t),measureRef:S(t)});return r},[V,j,I,S,a]),totalHeight:I(i),scrollRef:H,measurementVersion:w,onScroll:function(r){return l(r.currentTarget.scrollTop)}}}export{c as useVirtualize};
1
+ import{useState as r,useRef as t,useCallback as n,useLayoutEffect as e,useMemo as u}from"react";function c(c){var i=c.itemCount,o=c.itemHeight,f=c.overscan,a=void 0===f?3:f,v=c.containerHeight,h=r(0),l=h[0],s=h[1],m=r(0),g=m[0],d=m[1],R=null!=v?v:g,p=t({}),M=t({}),x=t(-1),H=t(null),T=t(0),b=t(0),C=r(0),w=C[0],z=C[1],B=n(function(){return b.current>0?T.current/b.current:o},[o]);e(function(){if(void 0===v){var r=H.current;if(r){var t=new ResizeObserver(function(r){var t=r[0];t.contentRect.height!==g&&d(t.contentRect.height)});return t.observe(r),function(){return t.disconnect()}}}},[v,g]);var I=n(function(r){if(r<=x.current)return M.current[r]||0;var t=x.current;return(t>=0?M.current[t]:0)+(t>=0?p.current[t]:0)+(r-t-1)*B()},[o,B]),O=n(function(r){for(var t=0,n=i-1;t<=n;){var e=Math.floor((t+n)/2),u=I(e),c=p.current[e]||B();if(u<=r&&u+c>r)return e;u<r?t=e+1:n=e-1}return Math.max(0,t-1)},[I,i,o]),S=n(function(r){return function(t){if(t){var n=t.getBoundingClientRect().height,e=p.current[r];if(e!==n){if(void 0===e?(T.current+=n,b.current+=1):T.current+=n-e,p.current[r]=n,r>x.current){var u=I(r);M.current[r]=u,x.current=r}else x.current=Math.min(x.current,r-1);z(function(r){return r+1})}}}},[I]),V=O(l),j=Math.min(i-1,O(l+R)+a);return{virtualItems:u(function(){for(var r=[],t=Math.max(0,V-a);t<=j;t++)r.push({index:t,offsetTop:I(t),measureRef:S(t)});return r},[V,j,I,S,a]),totalHeight:I(i),scrollRef:H,measurementVersion:w,onScroll:function(r){return s(r.currentTarget.scrollTop)}}}export{c as useVirtualize};
2
2
  //# sourceMappingURL=index.module.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.module.js","sources":["../src/index.ts"],"sourcesContent":["import {\n useState,\n useRef,\n useMemo,\n useCallback,\n useLayoutEffect,\n UIEvent,\n} from \"react\";\n\nexport interface UseVirtualizeProps {\n /** Total number of items in the list */\n itemCount: number;\n /** Estimated height of a single item (used for unmeasured items) */\n itemHeight: number;\n /** Number of extra items to render outside the visible view (default: 3) */\n overscan?: number;\n /** Fixed container height. If not provided, it will be measured automatically via ResizeObserver */\n containerHeight?: number;\n}\n\nexport interface VirtualItem {\n /** The index of the item in the original data array */\n index: number;\n /** The calculated top position (px) for absolute positioning */\n offsetTop: number;\n /** Callback ref to measure the actual DOM height of the item */\n measureRef: (el: HTMLElement | null) => void;\n}\n\nexport function useVirtualize({\n itemCount,\n itemHeight: estimatedItemHeight,\n overscan = 3,\n containerHeight: propsContainerHeight,\n}: UseVirtualizeProps) {\n const [scrollTop, setScrollTop] = useState(0);\n const [observedHeight, setObservedHeight] = useState(0);\n\n // Determine the effective container height:\n // Use the prop if provided; otherwise, fall back to the measured height.\n const height = propsContainerHeight ?? observedHeight;\n\n // --- REFS (State that doesn't trigger re-renders) ---\n\n // Stores the exact height of each measured item: { index: height }\n const measurementCache = useRef({} as Record<number, number>);\n\n // Stores the calculated total offset for each index: { index: offset }\n // This acts as a Prefix Sum array to avoid recalculating from 0 every time.\n const offsetCache = useRef({} as Record<number, number>);\n\n // Tracks the last index that has a valid calculated offset in the cache.\n const lastMeasuredIndex = useRef(-1);\n\n // Reference to the scrollable container (used for ResizeObserver)\n const scrollRef = useRef<HTMLDivElement>(undefined);\n\n // Total measured height (Pixel)\n const totalMeasuredSize = useRef(0);\n // Total measured element?\n const totalMeasuredCount = useRef(0);\n\n const [measurementVersion, setMeasurementVersion] = useState(0);\n\n // --- HELPER: GET ESTIMATED HEIGHT ---\n // Zeka burada: Eğer hiç ölçüm yoksa senin verdiğin 'itemHeight'ı kullan.\n // Ama ölçüm yaptıysak, gerçek ortalamayı kullan.\n const getEstimatedItemHeight = useCallback(() => {\n return totalMeasuredCount.current > 0\n ? totalMeasuredSize.current / totalMeasuredCount.current\n : estimatedItemHeight;\n }, [estimatedItemHeight]);\n\n // --- 1. AUTO-SIZER LOGIC ---\n useLayoutEffect(() => {\n // If the user provided a fixed height, skip observation to save resources.\n if (propsContainerHeight !== undefined) return;\n\n const scrollElement = scrollRef.current;\n if (!scrollElement) return;\n\n const observer = new ResizeObserver(([entry]) => {\n // Update state only if dimensions actually changed\n if (entry.contentRect.height !== observedHeight) {\n setObservedHeight(entry.contentRect.height);\n }\n });\n\n observer.observe(scrollElement);\n\n return () => observer.disconnect();\n }, [propsContainerHeight, observedHeight]);\n\n // --- 2. OFFSET CALCULATOR (Core Logic) ---\n // Returns the vertical position (px) of an item.\n const getItemOffset = useCallback(\n (index: number) => {\n // If the offset for this index is already cached, return it directly.\n if (index <= lastMeasuredIndex.current) {\n return offsetCache.current[index] || 0;\n }\n\n // 1. Retrieve reference data from the last measured item.\n const lastIndex = lastMeasuredIndex.current;\n\n // If no items have been measured yet (-1), the starting offset is 0.\n // Otherwise, retrieve the offset of the last measured item.\n const lastOffset = lastIndex >= 0 ? offsetCache.current[lastIndex] : 0;\n\n // Retrieve the height of the last measured item (default to 0 if none measured).\n const lastHeight =\n lastIndex >= 0 ? measurementCache.current[lastIndex] : 0;\n\n // 2. Calculate the bottom position of the last measured item.\n // This serves as the anchor point for estimating subsequent offsets.\n const lastBottom = lastOffset + lastHeight;\n\n // 3. Calculate how many unmeasured items exist between the last measured index and the target index.\n const unmeasuredCount = index - lastIndex - 1;\n\n // 4. Result: Anchor Point + (Number of Unmeasured Items * Estimated Height)\n return lastBottom + unmeasuredCount * getEstimatedItemHeight();\n },\n [estimatedItemHeight, getEstimatedItemHeight]\n );\n\n // --- 3. BINARY SEARCH (Find Visible Index) ---\n // Finds the index corresponding to the current scroll position.\n // Performance: O(log N) - Crucial for large lists.\n const getStartIndexForOffset = useCallback(\n (offset: number) => {\n let low = 0;\n let high = itemCount - 1;\n\n while (low <= high) {\n const mid = Math.floor((low + high) / 2);\n const currentOffset = getItemOffset(mid);\n const currentHeight =\n measurementCache.current[mid] || getEstimatedItemHeight();\n\n if (currentOffset <= offset && currentOffset + currentHeight > offset) {\n return mid;\n } else if (currentOffset < offset) {\n low = mid + 1;\n } else {\n high = mid - 1;\n }\n }\n\n // Fallback to 0 if not found\n return Math.max(0, low - 1);\n },\n [getItemOffset, itemCount, estimatedItemHeight]\n );\n\n // --- 4. MEASUREMENT CALLBACK (The \"Ref\" logic) ---\n // This function is passed to the consumer to attach to their DOM elements.\n const measureElement = useCallback(\n (index: number) => (el: HTMLElement | null) => {\n if (!el) return;\n\n const measuredHeight = el.getBoundingClientRect().height;\n const prevHeight = measurementCache.current[index];\n\n if (prevHeight !== measuredHeight) {\n if (prevHeight === undefined) {\n totalMeasuredSize.current += measuredHeight;\n totalMeasuredCount.current += 1;\n } else {\n totalMeasuredSize.current += measuredHeight - prevHeight;\n }\n\n measurementCache.current[index] = measuredHeight;\n\n if (index > lastMeasuredIndex.current) {\n const prevOffset = getItemOffset(index);\n offsetCache.current[index] = prevOffset;\n lastMeasuredIndex.current = index;\n } else {\n lastMeasuredIndex.current = Math.min(\n lastMeasuredIndex.current,\n index - 1\n );\n }\n\n setMeasurementVersion(v => v + 1);\n }\n },\n [getItemOffset]\n );\n\n // --- 5. RENDER RANGE CALCULATION ---\n const startIndex = getStartIndexForOffset(scrollTop);\n const endIndex = Math.min(\n itemCount - 1,\n getStartIndexForOffset(scrollTop + height) + overscan\n );\n\n // Generate the virtual items array to be rendered\n const virtualItems = useMemo(() => {\n const items: VirtualItem[] = [];\n for (let i = Math.max(0, startIndex - overscan); i <= endIndex; i++) {\n items.push({\n index: i,\n offsetTop: getItemOffset(i),\n measureRef: measureElement(i),\n });\n }\n return items;\n }, [startIndex, endIndex, getItemOffset, measureElement, overscan]);\n\n // Calculate total list height for the scrollbar\n const totalHeight = getItemOffset(itemCount);\n\n return {\n virtualItems,\n totalHeight,\n scrollRef,\n measurementVersion,\n onScroll: (e: UIEvent<HTMLElement>) =>\n setScrollTop(e.currentTarget.scrollTop),\n };\n}\n"],"names":["useVirtualize","_ref","itemCount","estimatedItemHeight","itemHeight","_ref$overscan","overscan","propsContainerHeight","containerHeight","_useState","useState","scrollTop","setScrollTop","_useState2","observedHeight","setObservedHeight","height","measurementCache","useRef","offsetCache","lastMeasuredIndex","scrollRef","undefined","totalMeasuredSize","totalMeasuredCount","_useState3","measurementVersion","setMeasurementVersion","getEstimatedItemHeight","useCallback","current","useLayoutEffect","scrollElement","observer","ResizeObserver","_ref2","entry","contentRect","observe","disconnect","getItemOffset","index","lastIndex","getStartIndexForOffset","offset","low","high","mid","Math","floor","currentOffset","currentHeight","max","measureElement","el","measuredHeight","getBoundingClientRect","prevHeight","prevOffset","min","v","startIndex","endIndex","virtualItems","useMemo","items","i","push","offsetTop","measureRef","totalHeight","onScroll","e","currentTarget"],"mappings":"yGA6BgBA,EAAaC,GAKR,IAJnBC,EAASD,EAATC,UACYC,EAAmBF,EAA/BG,WAAUC,EAAAJ,EACVK,SAAAA,OAAQ,IAAAD,EAAG,EAACA,EACKE,EAAoBN,EAArCO,gBAEAC,EAAkCC,EAAS,GAApCC,EAASF,EAAEG,GAAAA,EAAYH,EAAA,GAC9BI,EAA4CH,EAAS,GAA9CI,EAAcD,EAAEE,GAAAA,EAAiBF,EAAA,GAIlCG,EAAST,MAAAA,EAAAA,EAAwBO,EAKjCG,EAAmBC,EAAO,CAAA,GAI1BC,EAAcD,EAAO,CAA4B,GAGjDE,EAAoBF,GAAQ,GAG5BG,EAAYH,OAAuBI,GAGnCC,EAAoBL,EAAO,GAE3BM,EAAqBN,EAAO,GAElCO,EAAoDf,EAAS,GAAtDgB,EAAkBD,EAAEE,GAAAA,EAAqBF,KAK1CG,EAAyBC,EAAY,WACzC,OAAOL,EAAmBM,QAAU,EAChCP,EAAkBO,QAAUN,EAAmBM,QAC/C3B,CACN,EAAG,CAACA,IAGJ4B,EAAgB,WAEd,QAA6BT,IAAzBf,EAAJ,CAEA,IAAMyB,EAAgBX,EAAUS,QAChC,GAAKE,EAAL,CAEA,IAAMC,EAAW,IAAIC,eAAe,SAAAC,GAAE,IAAAC,EAAKD,KAErCC,EAAMC,YAAYrB,SAAWF,GAC/BC,EAAkBqB,EAAMC,YAAYrB,OAExC,GAIA,OAFAiB,EAASK,QAAQN,GAEJ,WAAA,OAAAC,EAASM,YAAY,GACpC,EAAG,CAAChC,EAAsBO,IAI1B,IAAM0B,EAAgBX,EACpB,SAACY,GAEC,GAAIA,GAASrB,EAAkBU,QAC7B,OAAOX,EAAYW,QAAQW,IAAU,EAIvC,IAAMC,EAAYtB,EAAkBU,QAkBpC,OAdmBY,GAAa,EAAIvB,EAAYW,QAAQY,GAAa,IAInEA,GAAa,EAAIzB,EAAiBa,QAAQY,GAAa,IAOjCD,EAAQC,EAAY,GAGNd,GACxC,EACA,CAACzB,EAAqByB,IAMlBe,EAAyBd,EAC7B,SAACe,GAIC,IAHA,IAAIC,EAAM,EACNC,EAAO5C,EAAY,EAEhB2C,GAAOC,GAAM,CAClB,IAAMC,EAAMC,KAAKC,OAAOJ,EAAMC,GAAQ,GAChCI,EAAgBV,EAAcO,GAC9BI,EACJlC,EAAiBa,QAAQiB,IAAQnB,IAEnC,GAAIsB,GAAiBN,GAAUM,EAAgBC,EAAgBP,EAC7D,OAAOG,EACEG,EAAgBN,EACzBC,EAAME,EAAM,EAEZD,EAAOC,EAAM,CAEhB,CAGD,OAAOC,KAAKI,IAAI,EAAGP,EAAM,EAC3B,EACA,CAACL,EAAetC,EAAWC,IAKvBkD,EAAiBxB,EACrB,SAACY,GAAkB,OAAA,SAACa,GAClB,GAAKA,EAAL,CAEA,IAAMC,EAAiBD,EAAGE,wBAAwBxC,OAC5CyC,EAAaxC,EAAiBa,QAAQW,GAE5C,GAAIgB,IAAeF,EAAgB,CAUjC,QATmBjC,IAAfmC,GACFlC,EAAkBO,SAAWyB,EAC7B/B,EAAmBM,SAAW,GAE9BP,EAAkBO,SAAWyB,EAAiBE,EAGhDxC,EAAiBa,QAAQW,GAASc,EAE9Bd,EAAQrB,EAAkBU,QAAS,CACrC,IAAM4B,EAAalB,EAAcC,GACjCtB,EAAYW,QAAQW,GAASiB,EAC7BtC,EAAkBU,QAAUW,CAC7B,MACCrB,EAAkBU,QAAUkB,KAAKW,IAC/BvC,EAAkBU,QAClBW,EAAQ,GAIZd,EAAsB,SAAAiC,GAAK,OAAAA,EAAI,CAAC,EACjC,EACH,CAAC,EACD,CAACpB,IAIGqB,EAAalB,EAAuBhC,GACpCmD,EAAWd,KAAKW,IACpBzD,EAAY,EACZyC,EAAuBhC,EAAYK,GAAUV,GAmB/C,MAAO,CACLyD,aAhBmBC,EAAQ,WAE3B,IADA,IAAMC,EAAuB,GACpBC,EAAIlB,KAAKI,IAAI,EAAGS,EAAavD,GAAW4D,GAAKJ,EAAUI,IAC9DD,EAAME,KAAK,CACT1B,MAAOyB,EACPE,UAAW5B,EAAc0B,GACzBG,WAAYhB,EAAea,KAG/B,OAAOD,CACT,EAAG,CAACJ,EAAYC,EAAUtB,EAAea,EAAgB/C,IAOvDgE,YAJkB9B,EAActC,GAKhCmB,UAAAA,EACAK,mBAAAA,EACA6C,SAAU,SAACC,GACT,OAAA5D,EAAa4D,EAAEC,cAAc9D,UAAU,EAE7C"}
1
+ {"version":3,"file":"index.module.js","sources":["../src/index.ts"],"sourcesContent":["import {\n useState,\n useRef,\n useMemo,\n useCallback,\n useLayoutEffect,\n UIEvent,\n} from \"react\";\n\nexport interface UseVirtualizeProps {\n /** Total number of items in the list */\n itemCount: number;\n /** Estimated height of a single item (used for unmeasured items) */\n itemHeight: number;\n /** Number of extra items to render outside the visible view (default: 3) */\n overscan?: number;\n /** Fixed container height. If not provided, it will be measured automatically via ResizeObserver */\n containerHeight?: number;\n}\n\nexport interface VirtualItem {\n /** The index of the item in the original data array */\n index: number;\n /** The calculated top position (px) for absolute positioning */\n offsetTop: number;\n /** Callback ref to measure the actual DOM height of the item */\n measureRef: (el: HTMLElement | null) => void;\n}\n\nexport function useVirtualize({\n itemCount,\n itemHeight: estimatedItemHeight,\n overscan = 3,\n containerHeight: propsContainerHeight,\n}: UseVirtualizeProps) {\n const [scrollTop, setScrollTop] = useState(0);\n const [observedHeight, setObservedHeight] = useState(0);\n\n // Determine the effective container height:\n // Use the prop if provided; otherwise, fall back to the measured height.\n const height = propsContainerHeight ?? observedHeight;\n\n // --- REFS (State that doesn't trigger re-renders) ---\n\n // Stores the exact height of each measured item: { index: height }\n const measurementCache = useRef({} as Record<number, number>);\n\n // Stores the calculated total offset for each index: { index: offset }\n // This acts as a Prefix Sum array to avoid recalculating from 0 every time.\n const offsetCache = useRef({} as Record<number, number>);\n\n // Tracks the last index that has a valid calculated offset in the cache.\n const lastMeasuredIndex = useRef(-1);\n\n // Reference to the scrollable container (used for ResizeObserver)\n const scrollRef = useRef<HTMLDivElement>(null);\n\n // Total measured height (Pixel)\n const totalMeasuredSize = useRef(0);\n // Total measured element?\n const totalMeasuredCount = useRef(0);\n\n const [measurementVersion, setMeasurementVersion] = useState(0);\n\n // --- HELPER: GET ESTIMATED HEIGHT ---\n // Zeka burada: Eğer hiç ölçüm yoksa senin verdiğin 'itemHeight'ı kullan.\n // Ama ölçüm yaptıysak, gerçek ortalamayı kullan.\n const getEstimatedItemHeight = useCallback(() => {\n return totalMeasuredCount.current > 0\n ? totalMeasuredSize.current / totalMeasuredCount.current\n : estimatedItemHeight;\n }, [estimatedItemHeight]);\n\n // --- 1. AUTO-SIZER LOGIC ---\n useLayoutEffect(() => {\n // If the user provided a fixed height, skip observation to save resources.\n if (propsContainerHeight !== undefined) return;\n\n const scrollElement = scrollRef.current;\n if (!scrollElement) return;\n\n const observer = new ResizeObserver(([entry]) => {\n // Update state only if dimensions actually changed\n if (entry.contentRect.height !== observedHeight) {\n setObservedHeight(entry.contentRect.height);\n }\n });\n\n observer.observe(scrollElement);\n\n return () => observer.disconnect();\n }, [propsContainerHeight, observedHeight]);\n\n // --- 2. OFFSET CALCULATOR (Core Logic) ---\n // Returns the vertical position (px) of an item.\n const getItemOffset = useCallback(\n (index: number) => {\n // If the offset for this index is already cached, return it directly.\n if (index <= lastMeasuredIndex.current) {\n return offsetCache.current[index] || 0;\n }\n\n // 1. Retrieve reference data from the last measured item.\n const lastIndex = lastMeasuredIndex.current;\n\n // If no items have been measured yet (-1), the starting offset is 0.\n // Otherwise, retrieve the offset of the last measured item.\n const lastOffset = lastIndex >= 0 ? offsetCache.current[lastIndex] : 0;\n\n // Retrieve the height of the last measured item (default to 0 if none measured).\n const lastHeight =\n lastIndex >= 0 ? measurementCache.current[lastIndex] : 0;\n\n // 2. Calculate the bottom position of the last measured item.\n // This serves as the anchor point for estimating subsequent offsets.\n const lastBottom = lastOffset + lastHeight;\n\n // 3. Calculate how many unmeasured items exist between the last measured index and the target index.\n const unmeasuredCount = index - lastIndex - 1;\n\n // 4. Result: Anchor Point + (Number of Unmeasured Items * Estimated Height)\n return lastBottom + unmeasuredCount * getEstimatedItemHeight();\n },\n [estimatedItemHeight, getEstimatedItemHeight]\n );\n\n // --- 3. BINARY SEARCH (Find Visible Index) ---\n // Finds the index corresponding to the current scroll position.\n // Performance: O(log N) - Crucial for large lists.\n const getStartIndexForOffset = useCallback(\n (offset: number) => {\n let low = 0;\n let high = itemCount - 1;\n\n while (low <= high) {\n const mid = Math.floor((low + high) / 2);\n const currentOffset = getItemOffset(mid);\n const currentHeight =\n measurementCache.current[mid] || getEstimatedItemHeight();\n\n if (currentOffset <= offset && currentOffset + currentHeight > offset) {\n return mid;\n } else if (currentOffset < offset) {\n low = mid + 1;\n } else {\n high = mid - 1;\n }\n }\n\n // Fallback to 0 if not found\n return Math.max(0, low - 1);\n },\n [getItemOffset, itemCount, estimatedItemHeight]\n );\n\n // --- 4. MEASUREMENT CALLBACK (The \"Ref\" logic) ---\n // This function is passed to the consumer to attach to their DOM elements.\n const measureElement = useCallback(\n (index: number) => (el: HTMLElement | null) => {\n if (!el) return;\n\n const measuredHeight = el.getBoundingClientRect().height;\n const prevHeight = measurementCache.current[index];\n\n if (prevHeight !== measuredHeight) {\n if (prevHeight === undefined) {\n totalMeasuredSize.current += measuredHeight;\n totalMeasuredCount.current += 1;\n } else {\n totalMeasuredSize.current += measuredHeight - prevHeight;\n }\n\n measurementCache.current[index] = measuredHeight;\n\n if (index > lastMeasuredIndex.current) {\n const prevOffset = getItemOffset(index);\n offsetCache.current[index] = prevOffset;\n lastMeasuredIndex.current = index;\n } else {\n lastMeasuredIndex.current = Math.min(\n lastMeasuredIndex.current,\n index - 1\n );\n }\n\n setMeasurementVersion(v => v + 1);\n }\n },\n [getItemOffset]\n );\n\n // --- 5. RENDER RANGE CALCULATION ---\n const startIndex = getStartIndexForOffset(scrollTop);\n const endIndex = Math.min(\n itemCount - 1,\n getStartIndexForOffset(scrollTop + height) + overscan\n );\n\n // Generate the virtual items array to be rendered\n const virtualItems = useMemo(() => {\n const items: VirtualItem[] = [];\n for (let i = Math.max(0, startIndex - overscan); i <= endIndex; i++) {\n items.push({\n index: i,\n offsetTop: getItemOffset(i),\n measureRef: measureElement(i),\n });\n }\n return items;\n }, [startIndex, endIndex, getItemOffset, measureElement, overscan]);\n\n // Calculate total list height for the scrollbar\n const totalHeight = getItemOffset(itemCount);\n\n return {\n virtualItems,\n totalHeight,\n scrollRef,\n measurementVersion,\n onScroll: (e: UIEvent<HTMLElement>) =>\n setScrollTop(e.currentTarget.scrollTop),\n };\n}\n"],"names":["useVirtualize","_ref","itemCount","estimatedItemHeight","itemHeight","_ref$overscan","overscan","propsContainerHeight","containerHeight","_useState","useState","scrollTop","setScrollTop","_useState2","observedHeight","setObservedHeight","height","measurementCache","useRef","offsetCache","lastMeasuredIndex","scrollRef","totalMeasuredSize","totalMeasuredCount","_useState3","measurementVersion","setMeasurementVersion","getEstimatedItemHeight","useCallback","current","useLayoutEffect","undefined","scrollElement","observer","ResizeObserver","_ref2","entry","contentRect","observe","disconnect","getItemOffset","index","lastIndex","getStartIndexForOffset","offset","low","high","mid","Math","floor","currentOffset","currentHeight","max","measureElement","el","measuredHeight","getBoundingClientRect","prevHeight","prevOffset","min","v","startIndex","endIndex","virtualItems","useMemo","items","i","push","offsetTop","measureRef","totalHeight","onScroll","e","currentTarget"],"mappings":"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"}
package/dist/index.umd.js CHANGED
@@ -1,2 +1,2 @@
1
- !function(e,r){"object"==typeof exports&&"undefined"!=typeof module?r(exports,require("react")):"function"==typeof define&&define.amd?define(["exports","react"],r):r((e||self).reactSimpleVirtualize={},e.react)}(this,function(e,r){e.useVirtualize=function(e){var t=e.itemCount,n=e.itemHeight,u=e.overscan,c=void 0===u?3:u,i=e.containerHeight,o=r.useState(0),f=o[0],a=o[1],s=r.useState(0),l=s[0],v=s[1],h=null!=i?i:l,d=r.useRef({}),m=r.useRef({}),R=r.useRef(-1),g=r.useRef(void 0),p=r.useRef(0),b=r.useRef(0),x=r.useState(0),C=x[0],M=x[1],y=r.useCallback(function(){return b.current>0?p.current/b.current:n},[n]);r.useLayoutEffect(function(){if(void 0===i){var e=g.current;if(e){var r=new ResizeObserver(function(e){var r=e[0];r.contentRect.height!==l&&v(r.contentRect.height)});return r.observe(e),function(){return r.disconnect()}}}},[i,l]);var S=r.useCallback(function(e){if(e<=R.current)return m.current[e]||0;var r=R.current;return(r>=0?m.current[r]:0)+(r>=0?d.current[r]:0)+(e-r-1)*y()},[n,y]),T=r.useCallback(function(e){for(var r=0,n=t-1;r<=n;){var u=Math.floor((r+n)/2),c=S(u),i=d.current[u]||y();if(c<=e&&c+i>e)return u;c<e?r=u+1:n=u-1}return Math.max(0,r-1)},[S,t,n]),k=r.useCallback(function(e){return function(r){if(r){var t=r.getBoundingClientRect().height,n=d.current[e];if(n!==t){if(void 0===n?(p.current+=t,b.current+=1):p.current+=t-n,d.current[e]=t,e>R.current){var u=S(e);m.current[e]=u,R.current=e}else R.current=Math.min(R.current,e-1);M(function(e){return e+1})}}}},[S]),z=T(f),H=Math.min(t-1,T(f+h)+c);return{virtualItems:r.useMemo(function(){for(var e=[],r=Math.max(0,z-c);r<=H;r++)e.push({index:r,offsetTop:S(r),measureRef:k(r)});return e},[z,H,S,k,c]),totalHeight:S(t),scrollRef:g,measurementVersion:C,onScroll:function(e){return a(e.currentTarget.scrollTop)}}}});
1
+ !function(e,r){"object"==typeof exports&&"undefined"!=typeof module?r(exports,require("react")):"function"==typeof define&&define.amd?define(["exports","react"],r):r((e||self).reactSimpleVirtualize={},e.react)}(this,function(e,r){e.useVirtualize=function(e){var t=e.itemCount,n=e.itemHeight,u=e.overscan,c=void 0===u?3:u,i=e.containerHeight,o=r.useState(0),f=o[0],a=o[1],s=r.useState(0),l=s[0],h=s[1],v=null!=i?i:l,d=r.useRef({}),m=r.useRef({}),R=r.useRef(-1),g=r.useRef(null),p=r.useRef(0),b=r.useRef(0),x=r.useState(0),C=x[0],M=x[1],y=r.useCallback(function(){return b.current>0?p.current/b.current:n},[n]);r.useLayoutEffect(function(){if(void 0===i){var e=g.current;if(e){var r=new ResizeObserver(function(e){var r=e[0];r.contentRect.height!==l&&h(r.contentRect.height)});return r.observe(e),function(){return r.disconnect()}}}},[i,l]);var S=r.useCallback(function(e){if(e<=R.current)return m.current[e]||0;var r=R.current;return(r>=0?m.current[r]:0)+(r>=0?d.current[r]:0)+(e-r-1)*y()},[n,y]),T=r.useCallback(function(e){for(var r=0,n=t-1;r<=n;){var u=Math.floor((r+n)/2),c=S(u),i=d.current[u]||y();if(c<=e&&c+i>e)return u;c<e?r=u+1:n=u-1}return Math.max(0,r-1)},[S,t,n]),k=r.useCallback(function(e){return function(r){if(r){var t=r.getBoundingClientRect().height,n=d.current[e];if(n!==t){if(void 0===n?(p.current+=t,b.current+=1):p.current+=t-n,d.current[e]=t,e>R.current){var u=S(e);m.current[e]=u,R.current=e}else R.current=Math.min(R.current,e-1);M(function(e){return e+1})}}}},[S]),z=T(f),H=Math.min(t-1,T(f+v)+c);return{virtualItems:r.useMemo(function(){for(var e=[],r=Math.max(0,z-c);r<=H;r++)e.push({index:r,offsetTop:S(r),measureRef:k(r)});return e},[z,H,S,k,c]),totalHeight:S(t),scrollRef:g,measurementVersion:C,onScroll:function(e){return a(e.currentTarget.scrollTop)}}}});
2
2
  //# sourceMappingURL=index.umd.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.umd.js","sources":["../src/index.ts"],"sourcesContent":["import {\n useState,\n useRef,\n useMemo,\n useCallback,\n useLayoutEffect,\n UIEvent,\n} from \"react\";\n\nexport interface UseVirtualizeProps {\n /** Total number of items in the list */\n itemCount: number;\n /** Estimated height of a single item (used for unmeasured items) */\n itemHeight: number;\n /** Number of extra items to render outside the visible view (default: 3) */\n overscan?: number;\n /** Fixed container height. If not provided, it will be measured automatically via ResizeObserver */\n containerHeight?: number;\n}\n\nexport interface VirtualItem {\n /** The index of the item in the original data array */\n index: number;\n /** The calculated top position (px) for absolute positioning */\n offsetTop: number;\n /** Callback ref to measure the actual DOM height of the item */\n measureRef: (el: HTMLElement | null) => void;\n}\n\nexport function useVirtualize({\n itemCount,\n itemHeight: estimatedItemHeight,\n overscan = 3,\n containerHeight: propsContainerHeight,\n}: UseVirtualizeProps) {\n const [scrollTop, setScrollTop] = useState(0);\n const [observedHeight, setObservedHeight] = useState(0);\n\n // Determine the effective container height:\n // Use the prop if provided; otherwise, fall back to the measured height.\n const height = propsContainerHeight ?? observedHeight;\n\n // --- REFS (State that doesn't trigger re-renders) ---\n\n // Stores the exact height of each measured item: { index: height }\n const measurementCache = useRef({} as Record<number, number>);\n\n // Stores the calculated total offset for each index: { index: offset }\n // This acts as a Prefix Sum array to avoid recalculating from 0 every time.\n const offsetCache = useRef({} as Record<number, number>);\n\n // Tracks the last index that has a valid calculated offset in the cache.\n const lastMeasuredIndex = useRef(-1);\n\n // Reference to the scrollable container (used for ResizeObserver)\n const scrollRef = useRef<HTMLDivElement>(undefined);\n\n // Total measured height (Pixel)\n const totalMeasuredSize = useRef(0);\n // Total measured element?\n const totalMeasuredCount = useRef(0);\n\n const [measurementVersion, setMeasurementVersion] = useState(0);\n\n // --- HELPER: GET ESTIMATED HEIGHT ---\n // Zeka burada: Eğer hiç ölçüm yoksa senin verdiğin 'itemHeight'ı kullan.\n // Ama ölçüm yaptıysak, gerçek ortalamayı kullan.\n const getEstimatedItemHeight = useCallback(() => {\n return totalMeasuredCount.current > 0\n ? totalMeasuredSize.current / totalMeasuredCount.current\n : estimatedItemHeight;\n }, [estimatedItemHeight]);\n\n // --- 1. AUTO-SIZER LOGIC ---\n useLayoutEffect(() => {\n // If the user provided a fixed height, skip observation to save resources.\n if (propsContainerHeight !== undefined) return;\n\n const scrollElement = scrollRef.current;\n if (!scrollElement) return;\n\n const observer = new ResizeObserver(([entry]) => {\n // Update state only if dimensions actually changed\n if (entry.contentRect.height !== observedHeight) {\n setObservedHeight(entry.contentRect.height);\n }\n });\n\n observer.observe(scrollElement);\n\n return () => observer.disconnect();\n }, [propsContainerHeight, observedHeight]);\n\n // --- 2. OFFSET CALCULATOR (Core Logic) ---\n // Returns the vertical position (px) of an item.\n const getItemOffset = useCallback(\n (index: number) => {\n // If the offset for this index is already cached, return it directly.\n if (index <= lastMeasuredIndex.current) {\n return offsetCache.current[index] || 0;\n }\n\n // 1. Retrieve reference data from the last measured item.\n const lastIndex = lastMeasuredIndex.current;\n\n // If no items have been measured yet (-1), the starting offset is 0.\n // Otherwise, retrieve the offset of the last measured item.\n const lastOffset = lastIndex >= 0 ? offsetCache.current[lastIndex] : 0;\n\n // Retrieve the height of the last measured item (default to 0 if none measured).\n const lastHeight =\n lastIndex >= 0 ? measurementCache.current[lastIndex] : 0;\n\n // 2. Calculate the bottom position of the last measured item.\n // This serves as the anchor point for estimating subsequent offsets.\n const lastBottom = lastOffset + lastHeight;\n\n // 3. Calculate how many unmeasured items exist between the last measured index and the target index.\n const unmeasuredCount = index - lastIndex - 1;\n\n // 4. Result: Anchor Point + (Number of Unmeasured Items * Estimated Height)\n return lastBottom + unmeasuredCount * getEstimatedItemHeight();\n },\n [estimatedItemHeight, getEstimatedItemHeight]\n );\n\n // --- 3. BINARY SEARCH (Find Visible Index) ---\n // Finds the index corresponding to the current scroll position.\n // Performance: O(log N) - Crucial for large lists.\n const getStartIndexForOffset = useCallback(\n (offset: number) => {\n let low = 0;\n let high = itemCount - 1;\n\n while (low <= high) {\n const mid = Math.floor((low + high) / 2);\n const currentOffset = getItemOffset(mid);\n const currentHeight =\n measurementCache.current[mid] || getEstimatedItemHeight();\n\n if (currentOffset <= offset && currentOffset + currentHeight > offset) {\n return mid;\n } else if (currentOffset < offset) {\n low = mid + 1;\n } else {\n high = mid - 1;\n }\n }\n\n // Fallback to 0 if not found\n return Math.max(0, low - 1);\n },\n [getItemOffset, itemCount, estimatedItemHeight]\n );\n\n // --- 4. MEASUREMENT CALLBACK (The \"Ref\" logic) ---\n // This function is passed to the consumer to attach to their DOM elements.\n const measureElement = useCallback(\n (index: number) => (el: HTMLElement | null) => {\n if (!el) return;\n\n const measuredHeight = el.getBoundingClientRect().height;\n const prevHeight = measurementCache.current[index];\n\n if (prevHeight !== measuredHeight) {\n if (prevHeight === undefined) {\n totalMeasuredSize.current += measuredHeight;\n totalMeasuredCount.current += 1;\n } else {\n totalMeasuredSize.current += measuredHeight - prevHeight;\n }\n\n measurementCache.current[index] = measuredHeight;\n\n if (index > lastMeasuredIndex.current) {\n const prevOffset = getItemOffset(index);\n offsetCache.current[index] = prevOffset;\n lastMeasuredIndex.current = index;\n } else {\n lastMeasuredIndex.current = Math.min(\n lastMeasuredIndex.current,\n index - 1\n );\n }\n\n setMeasurementVersion(v => v + 1);\n }\n },\n [getItemOffset]\n );\n\n // --- 5. RENDER RANGE CALCULATION ---\n const startIndex = getStartIndexForOffset(scrollTop);\n const endIndex = Math.min(\n itemCount - 1,\n getStartIndexForOffset(scrollTop + height) + overscan\n );\n\n // Generate the virtual items array to be rendered\n const virtualItems = useMemo(() => {\n const items: VirtualItem[] = [];\n for (let i = Math.max(0, startIndex - overscan); i <= endIndex; i++) {\n items.push({\n index: i,\n offsetTop: getItemOffset(i),\n measureRef: measureElement(i),\n });\n }\n return items;\n }, [startIndex, endIndex, getItemOffset, measureElement, overscan]);\n\n // Calculate total list height for the scrollbar\n const totalHeight = getItemOffset(itemCount);\n\n return {\n virtualItems,\n totalHeight,\n scrollRef,\n measurementVersion,\n onScroll: (e: UIEvent<HTMLElement>) =>\n setScrollTop(e.currentTarget.scrollTop),\n };\n}\n"],"names":["_ref","itemCount","estimatedItemHeight","itemHeight","_ref$overscan","overscan","propsContainerHeight","containerHeight","_useState","useState","scrollTop","setScrollTop","_useState2","observedHeight","setObservedHeight","height","measurementCache","useRef","offsetCache","lastMeasuredIndex","scrollRef","undefined","totalMeasuredSize","totalMeasuredCount","_useState3","measurementVersion","setMeasurementVersion","getEstimatedItemHeight","useCallback","current","useLayoutEffect","scrollElement","observer","ResizeObserver","_ref2","entry","contentRect","observe","disconnect","getItemOffset","index","lastIndex","getStartIndexForOffset","offset","low","high","mid","Math","floor","currentOffset","currentHeight","max","measureElement","el","measuredHeight","getBoundingClientRect","prevHeight","prevOffset","min","v","startIndex","endIndex","virtualItems","useMemo","items","i","push","offsetTop","measureRef","totalHeight","onScroll","e","currentTarget"],"mappings":"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"}
1
+ {"version":3,"file":"index.umd.js","sources":["../src/index.ts"],"sourcesContent":["import {\n useState,\n useRef,\n useMemo,\n useCallback,\n useLayoutEffect,\n UIEvent,\n} from \"react\";\n\nexport interface UseVirtualizeProps {\n /** Total number of items in the list */\n itemCount: number;\n /** Estimated height of a single item (used for unmeasured items) */\n itemHeight: number;\n /** Number of extra items to render outside the visible view (default: 3) */\n overscan?: number;\n /** Fixed container height. If not provided, it will be measured automatically via ResizeObserver */\n containerHeight?: number;\n}\n\nexport interface VirtualItem {\n /** The index of the item in the original data array */\n index: number;\n /** The calculated top position (px) for absolute positioning */\n offsetTop: number;\n /** Callback ref to measure the actual DOM height of the item */\n measureRef: (el: HTMLElement | null) => void;\n}\n\nexport function useVirtualize({\n itemCount,\n itemHeight: estimatedItemHeight,\n overscan = 3,\n containerHeight: propsContainerHeight,\n}: UseVirtualizeProps) {\n const [scrollTop, setScrollTop] = useState(0);\n const [observedHeight, setObservedHeight] = useState(0);\n\n // Determine the effective container height:\n // Use the prop if provided; otherwise, fall back to the measured height.\n const height = propsContainerHeight ?? observedHeight;\n\n // --- REFS (State that doesn't trigger re-renders) ---\n\n // Stores the exact height of each measured item: { index: height }\n const measurementCache = useRef({} as Record<number, number>);\n\n // Stores the calculated total offset for each index: { index: offset }\n // This acts as a Prefix Sum array to avoid recalculating from 0 every time.\n const offsetCache = useRef({} as Record<number, number>);\n\n // Tracks the last index that has a valid calculated offset in the cache.\n const lastMeasuredIndex = useRef(-1);\n\n // Reference to the scrollable container (used for ResizeObserver)\n const scrollRef = useRef<HTMLDivElement>(null);\n\n // Total measured height (Pixel)\n const totalMeasuredSize = useRef(0);\n // Total measured element?\n const totalMeasuredCount = useRef(0);\n\n const [measurementVersion, setMeasurementVersion] = useState(0);\n\n // --- HELPER: GET ESTIMATED HEIGHT ---\n // Zeka burada: Eğer hiç ölçüm yoksa senin verdiğin 'itemHeight'ı kullan.\n // Ama ölçüm yaptıysak, gerçek ortalamayı kullan.\n const getEstimatedItemHeight = useCallback(() => {\n return totalMeasuredCount.current > 0\n ? totalMeasuredSize.current / totalMeasuredCount.current\n : estimatedItemHeight;\n }, [estimatedItemHeight]);\n\n // --- 1. AUTO-SIZER LOGIC ---\n useLayoutEffect(() => {\n // If the user provided a fixed height, skip observation to save resources.\n if (propsContainerHeight !== undefined) return;\n\n const scrollElement = scrollRef.current;\n if (!scrollElement) return;\n\n const observer = new ResizeObserver(([entry]) => {\n // Update state only if dimensions actually changed\n if (entry.contentRect.height !== observedHeight) {\n setObservedHeight(entry.contentRect.height);\n }\n });\n\n observer.observe(scrollElement);\n\n return () => observer.disconnect();\n }, [propsContainerHeight, observedHeight]);\n\n // --- 2. OFFSET CALCULATOR (Core Logic) ---\n // Returns the vertical position (px) of an item.\n const getItemOffset = useCallback(\n (index: number) => {\n // If the offset for this index is already cached, return it directly.\n if (index <= lastMeasuredIndex.current) {\n return offsetCache.current[index] || 0;\n }\n\n // 1. Retrieve reference data from the last measured item.\n const lastIndex = lastMeasuredIndex.current;\n\n // If no items have been measured yet (-1), the starting offset is 0.\n // Otherwise, retrieve the offset of the last measured item.\n const lastOffset = lastIndex >= 0 ? offsetCache.current[lastIndex] : 0;\n\n // Retrieve the height of the last measured item (default to 0 if none measured).\n const lastHeight =\n lastIndex >= 0 ? measurementCache.current[lastIndex] : 0;\n\n // 2. Calculate the bottom position of the last measured item.\n // This serves as the anchor point for estimating subsequent offsets.\n const lastBottom = lastOffset + lastHeight;\n\n // 3. Calculate how many unmeasured items exist between the last measured index and the target index.\n const unmeasuredCount = index - lastIndex - 1;\n\n // 4. Result: Anchor Point + (Number of Unmeasured Items * Estimated Height)\n return lastBottom + unmeasuredCount * getEstimatedItemHeight();\n },\n [estimatedItemHeight, getEstimatedItemHeight]\n );\n\n // --- 3. BINARY SEARCH (Find Visible Index) ---\n // Finds the index corresponding to the current scroll position.\n // Performance: O(log N) - Crucial for large lists.\n const getStartIndexForOffset = useCallback(\n (offset: number) => {\n let low = 0;\n let high = itemCount - 1;\n\n while (low <= high) {\n const mid = Math.floor((low + high) / 2);\n const currentOffset = getItemOffset(mid);\n const currentHeight =\n measurementCache.current[mid] || getEstimatedItemHeight();\n\n if (currentOffset <= offset && currentOffset + currentHeight > offset) {\n return mid;\n } else if (currentOffset < offset) {\n low = mid + 1;\n } else {\n high = mid - 1;\n }\n }\n\n // Fallback to 0 if not found\n return Math.max(0, low - 1);\n },\n [getItemOffset, itemCount, estimatedItemHeight]\n );\n\n // --- 4. MEASUREMENT CALLBACK (The \"Ref\" logic) ---\n // This function is passed to the consumer to attach to their DOM elements.\n const measureElement = useCallback(\n (index: number) => (el: HTMLElement | null) => {\n if (!el) return;\n\n const measuredHeight = el.getBoundingClientRect().height;\n const prevHeight = measurementCache.current[index];\n\n if (prevHeight !== measuredHeight) {\n if (prevHeight === undefined) {\n totalMeasuredSize.current += measuredHeight;\n totalMeasuredCount.current += 1;\n } else {\n totalMeasuredSize.current += measuredHeight - prevHeight;\n }\n\n measurementCache.current[index] = measuredHeight;\n\n if (index > lastMeasuredIndex.current) {\n const prevOffset = getItemOffset(index);\n offsetCache.current[index] = prevOffset;\n lastMeasuredIndex.current = index;\n } else {\n lastMeasuredIndex.current = Math.min(\n lastMeasuredIndex.current,\n index - 1\n );\n }\n\n setMeasurementVersion(v => v + 1);\n }\n },\n [getItemOffset]\n );\n\n // --- 5. RENDER RANGE CALCULATION ---\n const startIndex = getStartIndexForOffset(scrollTop);\n const endIndex = Math.min(\n itemCount - 1,\n getStartIndexForOffset(scrollTop + height) + overscan\n );\n\n // Generate the virtual items array to be rendered\n const virtualItems = useMemo(() => {\n const items: VirtualItem[] = [];\n for (let i = Math.max(0, startIndex - overscan); i <= endIndex; i++) {\n items.push({\n index: i,\n offsetTop: getItemOffset(i),\n measureRef: measureElement(i),\n });\n }\n return items;\n }, [startIndex, endIndex, getItemOffset, measureElement, overscan]);\n\n // Calculate total list height for the scrollbar\n const totalHeight = getItemOffset(itemCount);\n\n return {\n virtualItems,\n totalHeight,\n scrollRef,\n measurementVersion,\n onScroll: (e: UIEvent<HTMLElement>) =>\n setScrollTop(e.currentTarget.scrollTop),\n };\n}\n"],"names":["_ref","itemCount","estimatedItemHeight","itemHeight","_ref$overscan","overscan","propsContainerHeight","containerHeight","_useState","useState","scrollTop","setScrollTop","_useState2","observedHeight","setObservedHeight","height","measurementCache","useRef","offsetCache","lastMeasuredIndex","scrollRef","totalMeasuredSize","totalMeasuredCount","_useState3","measurementVersion","setMeasurementVersion","getEstimatedItemHeight","useCallback","current","useLayoutEffect","undefined","scrollElement","observer","ResizeObserver","_ref2","entry","contentRect","observe","disconnect","getItemOffset","index","lastIndex","getStartIndexForOffset","offset","low","high","mid","Math","floor","currentOffset","currentHeight","max","measureElement","el","measuredHeight","getBoundingClientRect","prevHeight","prevOffset","min","v","startIndex","endIndex","virtualItems","useMemo","items","i","push","offsetTop","measureRef","totalHeight","onScroll","e","currentTarget"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-simple-virtualize",
3
- "version": "2.0.1",
3
+ "version": "2.0.3",
4
4
  "description": "A lightweight, dependency-free virtualization hook for React.",
5
5
  "source": "src/index.ts",
6
6
  "main": "dist/index.js",
package/src/index.ts CHANGED
@@ -53,7 +53,7 @@ export function useVirtualize({
53
53
  const lastMeasuredIndex = useRef(-1);
54
54
 
55
55
  // Reference to the scrollable container (used for ResizeObserver)
56
- const scrollRef = useRef<HTMLDivElement>(undefined);
56
+ const scrollRef = useRef<HTMLDivElement>(null);
57
57
 
58
58
  // Total measured height (Pixel)
59
59
  const totalMeasuredSize = useRef(0);
package/src/index.d.ts DELETED
@@ -1,10 +0,0 @@
1
- export function useVirtualize({ itemCount, itemHeight, containerHeight, overscan }: {
2
- itemCount: any;
3
- itemHeight: any;
4
- containerHeight: any;
5
- overscan?: number;
6
- }): {
7
- virtualItems: any;
8
- totalHeight: any;
9
- onScroll: (e: any) => void;
10
- };