11import { assert } from "../utils/assert" ;
2- import type { Bounds , CachedBounds , SizeFunction } from "./types" ;
2+ import type { CachedBounds , SizeFunction } from "./types" ;
33
44export function createCachedBounds < Props extends object > ( {
55 itemCount,
@@ -8,66 +8,89 @@ export function createCachedBounds<Props extends object>({
88} : {
99 itemCount : number ;
1010 itemProps : Props ;
11- itemSize : number | SizeFunction < Props > ;
11+ itemSize : number | SizeFunction < Props > | undefined ;
1212} ) : CachedBounds {
13- const cache = new Map < number , Bounds > ( ) ;
13+ const cache = new Map < number , number > ( ) ;
14+
15+ let lastContiguousMeasuredIndex = - 1 ;
16+ let totalSize = 0 ;
1417
1518 const api = {
1619 getEstimatedSize ( ) {
1720 if ( itemCount === 0 ) {
1821 return 0 ;
1922 } else {
20- const bounds = api . getItemBounds ( cache . size === 0 ? 0 : cache . size - 1 ) ;
21- assert ( bounds , "Unexpected bounds cache miss" ) ;
23+ if ( cache . size === 0 && itemSize !== undefined ) {
24+ // If possible, lazily measure the first row so we can estimate a size
25+ api . getItemBounds ( 0 ) ;
26+ }
2227
23- return ( bounds . scrollOffset + bounds . size ) / cache . size ;
28+ return cache . size === 0 ? undefined : totalSize / cache . size ;
2429 }
2530 } ,
2631 getItemBounds ( index : number ) {
2732 assert ( index < itemCount , `Invalid index ${ index } ` ) ;
2833
29- while ( cache . size - 1 < index ) {
30- const currentIndex = cache . size ;
34+ let size : number | undefined = undefined ;
3135
32- let size : number = 0 ;
33- switch ( typeof itemSize ) {
34- case "function" : {
35- size = itemSize ( currentIndex , itemProps ) ;
36- break ;
37- }
38- case "number" : {
39- size = itemSize ;
40- break ;
36+ if ( itemSize ) {
37+ while ( cache . size <= index ) {
38+ const currentIndex = cache . size ;
39+
40+ switch ( typeof itemSize ) {
41+ case "function" : {
42+ size = itemSize ( currentIndex , itemProps ) ;
43+ break ;
44+ }
45+ case "number" : {
46+ size = itemSize ;
47+ break ;
48+ }
4149 }
50+
51+ lastContiguousMeasuredIndex = currentIndex ;
52+ totalSize += size ;
53+
54+ cache . set ( currentIndex , size ) ;
4255 }
4356
44- if ( currentIndex === 0 ) {
45- cache . set ( currentIndex , {
46- scrollOffset : 0 ,
47- size
48- } ) ;
49- } else {
50- const previousRowBounds = cache . get ( currentIndex - 1 ) ;
51- assert (
52- previousRowBounds !== undefined ,
53- `Unexpected bounds cache miss for index ${ index } `
54- ) ;
55-
56- cache . set ( currentIndex , {
57- scrollOffset :
58- previousRowBounds . scrollOffset + previousRowBounds . size ,
59- size
60- } ) ;
57+ size = cache . get ( index ) ;
58+ } else {
59+ size = cache . get ( index ) ;
60+ }
61+
62+ if ( size !== undefined ) {
63+ const averageSize = api . getEstimatedSize ( ) ?? 0 ;
64+
65+ let scrollOffset = 0 ;
66+
67+ while ( index > 0 ) {
68+ index -- ;
69+ scrollOffset += cache . get ( index ) ?? averageSize ;
6170 }
71+
72+ return {
73+ scrollOffset,
74+ size
75+ } ;
76+ }
77+ } ,
78+ hasItemBounds ( index : number ) {
79+ return cache . has ( index ) ;
80+ } ,
81+ setItemSize ( index : number , size : number ) {
82+ const prevSize = cache . get ( index ) ;
83+ if ( prevSize !== undefined ) {
84+ totalSize -= prevSize ;
6285 }
6386
64- const bounds = cache . get ( index ) ;
65- assert (
66- bounds !== undefined ,
67- `Unexpected bounds cache miss for index ${ index } `
68- ) ;
87+ totalSize += size ;
88+
89+ if ( index === lastContiguousMeasuredIndex + 1 ) {
90+ lastContiguousMeasuredIndex = index ;
91+ }
6992
70- return bounds ;
93+ cache . set ( index , size ) ;
7194 } ,
7295 get size ( ) {
7396 return cache . size ;
0 commit comments