@@ -7,6 +7,7 @@ export interface VisualWindowProps {
7
7
itemData : Array < unknown >
8
8
className ?: string
9
9
detectHeight ?: boolean
10
+ overhang ?: number
10
11
}
11
12
12
13
export interface VisualWindowChildProps {
@@ -15,19 +16,25 @@ export interface VisualWindowChildProps {
15
16
style : React . CSSProperties
16
17
}
17
18
18
- export default function VisualWindow ( { children, defaultItemHeight, className, itemData, detectHeight = true } : VisualWindowProps ) {
19
+ export default function VisualWindow ( { children, defaultItemHeight, className, itemData, detectHeight = true , overhang = 0 } : VisualWindowProps ) {
19
20
const itemCount = useMemo ( ( ) => itemData . length , [ itemData ] )
20
21
21
- const [ measurements , setMeasurement ] = useState < { [ k : number ] : DOMRect } > ( { } )
22
+ const [ measurements , setMeasurement ] = useState < {
23
+ [ k : number ] : {
24
+ width : number
25
+ height : number
26
+ }
27
+ } > ( { } )
22
28
23
- const ref = useRef < HTMLDivElement > ( null )
29
+ const mainRef = useRef < HTMLDivElement > ( null )
24
30
const childRef = useRef ( new Map < number , HTMLElement > ( ) ) . current
25
31
const { scrollY : scrollPosition } = useScrollPosition ( )
26
32
27
33
const checkMeasurements = useCallback ( ( ) => {
28
34
for ( const [ i , c ] of childRef . entries ( ) ) {
29
35
const bounding = c . getBoundingClientRect ( )
30
- if ( bounding . height !== defaultItemHeight && ( measurements [ i ] === undefined || measurements [ i ] . height !== bounding . height ) ) setMeasurement ( x => ( { ...x , [ i ] : bounding } ) )
36
+
37
+ if ( bounding . height !== defaultItemHeight && measurements ?. [ i ] ?. height !== bounding ?. height ) setMeasurement ( x => ( { ...x , [ i ] : { width : bounding . width , height : bounding . height } } ) )
31
38
if ( bounding . height === defaultItemHeight && measurements [ i ] !== undefined ) {
32
39
setMeasurement ( x => {
33
40
const tmp = { ...x }
@@ -47,30 +54,45 @@ export default function VisualWindow({children, defaultItemHeight, className, it
47
54
48
55
const height = useMemo ( ( ) => defaultItemHeight * itemCount + Object . values ( measurements ) . reduce ( ( sum , val ) => ( sum += val . height - defaultItemHeight ) , 0 ) , [ defaultItemHeight , itemCount , measurements ] )
49
56
50
- const itemRenderCount = useMemo ( ( ) => ( defaultItemHeight === 0 ? 0 : Math . min ( Math . ceil ( Math . max ( document . documentElement . clientHeight || 0 , window . innerHeight || 0 ) / defaultItemHeight ) , itemCount ) ) , [ defaultItemHeight , itemCount ] )
57
+ const maxViewWindow = useMemo ( ( ) => Math . max ( document ? .documentElement ? .clientHeight ?? 0 , window ? .innerHeight ?? 0 ) , [ document ?. documentElement ?. clientHeight , window ?. innerHeight ] )
51
58
52
59
const startItem = useMemo ( ( ) => {
53
- if ( ref . current === null || itemCount === 0 ) return 0
54
-
55
- const windowOffset = ref . current . getBoundingClientRect ( ) . top
56
- if ( windowOffset > 0 ) return 0
60
+ if ( itemCount === 0 ) return 0
57
61
58
- let tmpOffset = windowOffset
59
62
let start = 0
60
- if ( Object . keys ( measurements ) . length > 0 ) {
61
- for ( ; start < itemCount && tmpOffset < 0 ; start ++ ) tmpOffset += measurements [ start ] !== undefined ? measurements [ start ] . height : defaultItemHeight
62
- start --
63
- } else start = Math . floor ( Math . abs ( windowOffset ) / defaultItemHeight )
63
+ const windowOffset = mainRef ?. current ?. getBoundingClientRect ( ) ?. top ?? 0
64
+ if ( windowOffset < 0 ) {
65
+ let tmpOffset = windowOffset
66
+
67
+ if ( Object . keys ( measurements ) . length > 0 ) {
68
+ for ( ; start < itemCount && tmpOffset < 0 ; start ++ ) tmpOffset += measurements ?. [ start ] ?. height ?? defaultItemHeight
69
+ start --
70
+ } else start = Math . floor ( Math . abs ( windowOffset ) / defaultItemHeight )
64
71
65
- const max = Math . max ( itemCount - itemRenderCount , 0 )
72
+ start = Math . max ( start - overhang , 0 )
73
+ }
74
+ return start
75
+ } , [ mainRef , scrollPosition , defaultItemHeight , itemCount , measurements , overhang ] )
76
+
77
+ const endItem = useMemo ( ( ) => {
78
+ if ( itemCount === 0 ) return 0
79
+
80
+ let end = startItem
81
+ let height = 0
82
+
83
+ if ( Object . keys ( measurements ) . length > 0 ) {
84
+ while ( height <= maxViewWindow + ( measurements ?. [ startItem ] ?. height ?? defaultItemHeight ) ) {
85
+ height += measurements ?. [ end ] ?. height ?? defaultItemHeight
86
+ end ++
87
+ }
88
+ } else end = Math . ceil ( maxViewWindow / defaultItemHeight ) + startItem
66
89
67
- return Math . min ( Math . max ( start , 0 ) , max )
68
- // eslint-disable-next-line react-hooks/exhaustive-deps
69
- } , [ ref , scrollPosition , defaultItemHeight , itemCount , itemRenderCount , measurements ] )
90
+ return Math . max ( Math . min ( end + overhang , itemCount - 1 ) , 0 )
91
+ } , [ startItem , defaultItemHeight , itemCount , measurements , maxViewWindow , overhang ] )
70
92
71
93
const calcChildren = useMemo ( ( ) => {
72
94
const output = [ ]
73
- for ( let i = startItem ; i < itemRenderCount + startItem ; i ++ ) {
95
+ for ( let i = startItem ; i <= endItem ; i ++ ) {
74
96
let addRef = { }
75
97
76
98
if ( typeof children === "object" ) {
@@ -108,14 +130,13 @@ export default function VisualWindow({children, defaultItemHeight, className, it
108
130
} ,
109
131
output ,
110
132
)
111
- // eslint-disable-next-line react-hooks/exhaustive-deps
112
- } , [ startItem , defaultItemHeight , itemRenderCount , children , itemData , detectHeight ] )
133
+ } , [ startItem , endItem , children , itemData , detectHeight ] )
113
134
114
135
useEffect ( ( ) => {
115
- if ( ! detectHeight || ! ref . current ) return ( ) => { }
136
+ if ( ! detectHeight || ! mainRef . current ) return ( ) => { }
116
137
117
138
const observer = new MutationObserver ( ( ) => checkMeasurements ( ) )
118
- observer . observe ( ref . current , {
139
+ observer . observe ( mainRef . current , {
119
140
attributes : false ,
120
141
childList : true ,
121
142
subtree : true ,
@@ -126,10 +147,10 @@ export default function VisualWindow({children, defaultItemHeight, className, it
126
147
return createElement (
127
148
"div" ,
128
149
{
129
- ref,
150
+ ref : mainRef ,
130
151
style : { position : "relative" , height} ,
131
152
className,
132
153
} ,
133
- itemCount > 0 && itemRenderCount > 0 && calcChildren ,
154
+ itemCount > 0 && calcChildren ,
134
155
)
135
156
}
0 commit comments