@@ -13,8 +13,6 @@ interface IElementMeasurements {
1313}
1414
1515const IDLE_CALLBACK_TIMEOUT = 100
16- // Increased delay for Safari, as Safari doesn't have scroll time when using 'smooth' scroll
17- const SAFARI_VISIBILITY_DELAY = / ^ ( (? ! c h r o m e | a n d r o i d ) .) * s a f a r i / i. test ( navigator . userAgent ) ? 100 : 0
1816
1917/**
2018 * This is a component that allows optimizing the amount of elements present in the DOM through replacing them
@@ -71,15 +69,14 @@ export function VirtualElement({
7169 const [ measurements , setMeasurements ] = useState < IElementMeasurements | null > ( null )
7270 const [ ref , setRef ] = useState < HTMLDivElement | null > ( null )
7371
74- let inViewChangetimer : NodeJS . Timeout | undefined
72+ // Timers for visibility changes:
73+ const inViewChangeTimerRef = useRef < ReturnType < typeof setTimeout > | undefined > ( undefined )
74+ const skipInitialrunRef = useRef < boolean > ( true )
75+ const isTransitioning = useRef ( false )
7576
7677 const showPlaceholder = ! isShowingChildren && ! initialShow
7778
7879 const isCurrentlyObserving = useRef ( false )
79- const isTransitioning = useRef ( false )
80-
81- // Track the last visibility change to debounce
82- const lastVisibilityChangeRef = useRef < number > ( 0 )
8380
8481 const styleObj = useMemo < React . CSSProperties > (
8582 ( ) => ( {
@@ -119,50 +116,64 @@ export function VirtualElement({
119116 }
120117 } , [ ref , inView , placeholderHeight ] )
121118
122- const onVisibleChanged = useCallback (
123- ( visible : boolean ) => {
124- const now = Date . now ( )
125-
126- // Debounce visibility changes in Safari to prevent unnecessary recaconditions
127- if ( SAFARI_VISIBILITY_DELAY > 0 && now - lastVisibilityChangeRef . current < SAFARI_VISIBILITY_DELAY ) {
128- return
129- }
119+ useEffect ( ( ) => {
120+ if ( inView ) {
121+ setIsShowingChildren ( true )
122+ }
130123
131- lastVisibilityChangeRef . current = now
132- if ( inView === visible ) {
133- return
134- }
124+ // Startup skip:
125+ if ( skipInitialrunRef . current ) {
126+ skipInitialrunRef . current = false
127+ return
128+ }
135129
136- setInView ( visible )
137- if ( visible ) {
138- setIsShowingChildren ( true )
139- }
130+ if ( isTransitioning . current ) {
131+ return
132+ }
140133
141- // Don't do updates while transitioning:
142- if ( isTransitioning . current ) {
143- return
144- }
145- isTransitioning . current = true
134+ isTransitioning . current = true
146135
147- // Clear any existing timers
148- if ( inViewChangetimer ) {
149- clearTimeout ( inViewChangetimer )
150- }
136+ // Clear any existing timers
137+ if ( inViewChangeTimerRef . current ) {
138+ clearTimeout ( inViewChangeTimerRef . current )
139+ inViewChangeTimerRef . current = undefined
140+ }
151141
152- // Delay the observer connection to prevent flickering
153- inViewChangetimer = setTimeout ( ( ) => {
154- if ( visible ) {
142+ // Delay the visibility change to avoid flickering
143+ // But low enough for scrolling to be responsive
144+ inViewChangeTimerRef . current = setTimeout ( ( ) => {
145+ try {
146+ if ( inView ) {
155147 if ( ref ) {
156148 if ( ! isCurrentlyObserving . current ) {
157149 resizeObserverManager . observe ( ref , handleResize )
158150 isCurrentlyObserving . current = true
159151 }
160152 }
153+ } else {
154+ if ( ref && isCurrentlyObserving . current ) {
155+ resizeObserverManager . unobserve ( ref )
156+ isCurrentlyObserving . current = false
157+ }
158+ setIsShowingChildren ( false )
161159 }
160+ } catch ( error ) {
161+ console . error ( 'Error in visibility change handler:' , error )
162+ } finally {
162163 isTransitioning . current = false
163- } , 100 )
164+ inViewChangeTimerRef . current = undefined
165+ }
166+ } , 50 )
167+ } , [ inView , ref , handleResize , resizeObserverManager ] )
168+
169+ const onVisibleChanged = useCallback (
170+ ( visible : boolean ) => {
171+ // Only update state if there's a change
172+ if ( inView !== visible ) {
173+ setInView ( visible )
174+ }
164175 } ,
165- [ ref ]
176+ [ inView ]
166177 )
167178
168179 const isScrolling = ( ) : boolean => {
@@ -193,11 +204,11 @@ export function VirtualElement({
193204 isCurrentlyObserving . current = false
194205 }
195206
196- if ( inViewChangetimer ) {
197- clearTimeout ( inViewChangetimer )
207+ if ( inViewChangeTimerRef . current ) {
208+ clearTimeout ( inViewChangeTimerRef . current )
198209 }
199210 }
200- } , [ ref , inView ] )
211+ } , [ ref , inView , handleResize ] )
201212
202213 useEffect ( ( ) => {
203214 if ( inView === true ) {
@@ -263,7 +274,7 @@ export function VirtualElement({
263274 window . clearTimeout ( optimizeTimeout )
264275 }
265276 }
266- } , [ ref , inView ] )
277+ } , [ ref , inView , placeholderHeight ] )
267278
268279 return (
269280 < InView
0 commit comments