@@ -53,11 +53,16 @@ const getSelectedFileIdsFromSelectoEvent = (e, getFileFromElement) => {
5353const accumulateSelectedItemsDuringDrag = (
5454 selectedDuringDragRef ,
5555 selectedFileIds ,
56- visibleFileIds
56+ visibleFileIds ,
57+ preserveAll
5758) => {
5859 const newAccumulated = new Set ( )
5960 for ( const fileId of selectedDuringDragRef . current ) {
60- if ( ! visibleFileIds . has ( fileId ) || selectedFileIds . has ( fileId ) ) {
61+ if (
62+ preserveAll ||
63+ ! visibleFileIds . has ( fileId ) ||
64+ selectedFileIds . has ( fileId )
65+ ) {
6166 newAccumulated . add ( fileId )
6267 }
6368 }
@@ -91,8 +96,10 @@ const RectangularSelection = ({
9196 const [ isContainerReady , setIsContainerReady ] = useState ( false )
9297 const { setSelectedItems, selectedItems, setIsSelectAll } =
9398 useSelectionContext ( )
99+ const [ resolvedScrollContainer , setResolvedScrollContainer ] = useState ( null )
94100 const isDraggingRef = useRef ( false )
95101 const dragStartPosRef = useRef ( null )
102+ const wheelScrolledDuringDragRef = useRef ( false )
96103 const mutationObserverRef = useRef ( null )
97104 const selectedDuringDragRef = useRef ( new Set ( ) )
98105
@@ -108,6 +115,12 @@ const RectangularSelection = ({
108115 }
109116 } , [ ] )
110117
118+ useEffect ( ( ) => {
119+ setResolvedScrollContainer (
120+ scrollElement || scrollContainerRef ?. current || null
121+ )
122+ } , [ scrollElement , scrollContainerRef , isContainerReady ] )
123+
111124 /**
112125 * Extracts file data from a DOM element using the data-file-id attribute.
113126 * Uses a Map for O(1) lookups instead of O(n) array.find().
@@ -149,10 +162,14 @@ const RectangularSelection = ({
149162 e ,
150163 getFileFromElement
151164 )
165+ // After a wheel scroll, items may still be in the DOM but outside
166+ // the selection rectangle (content shifted, not rectangle shrunk).
167+ // In that case, preserve all accumulated items to avoid losing them.
152168 const newAccumulated = accumulateSelectedItemsDuringDrag (
153169 selectedDuringDragRef ,
154170 selectedFileIds ,
155- visibleFileIds
171+ visibleFileIds ,
172+ wheelScrolledDuringDragRef . current
156173 )
157174 selectedDuringDragRef . current = newAccumulated
158175
@@ -248,6 +265,7 @@ const RectangularSelection = ({
248265 */
249266 const handleDragEnd = useCallback ( ( ) => {
250267 dragStartPosRef . current = null
268+ wheelScrolledDuringDragRef . current = false
251269 selectedDuringDragRef . current . clear ( )
252270 } , [ ] )
253271
@@ -258,8 +276,7 @@ const RectangularSelection = ({
258276 * re-discover selectable targets so newly rendered elements can be selected.
259277 */
260278 useEffect ( ( ) => {
261- const container = scrollElement || scrollContainerRef ?. current
262- if ( ! container ) return
279+ if ( ! resolvedScrollContainer ) return
263280
264281 if ( mutationObserverRef . current ) {
265282 mutationObserverRef . current . disconnect ( )
@@ -271,11 +288,36 @@ const RectangularSelection = ({
271288 }
272289 } )
273290
274- observer . observe ( container , { childList : true , subtree : true } )
291+ observer . observe ( resolvedScrollContainer , {
292+ childList : true ,
293+ subtree : true
294+ } )
275295 mutationObserverRef . current = observer
276296
277297 return ( ) => observer . disconnect ( )
278- } , [ scrollElement , scrollContainerRef ] )
298+ } , [ resolvedScrollContainer ] )
299+
300+ /**
301+ * Listens for mouse wheel scroll during a drag selection.
302+ * Marks that a wheel scroll occurred so the accumulator preserves
303+ * all previously selected items instead of dropping those that
304+ * are still in the DOM but scrolled out of the selection rectangle.
305+ */
306+ useEffect ( ( ) => {
307+ if ( ! resolvedScrollContainer ) return
308+
309+ const handleWheel = ( ) => {
310+ if ( ! isDraggingRef . current ) return
311+ wheelScrolledDuringDragRef . current = true
312+ }
313+
314+ resolvedScrollContainer . addEventListener ( 'wheel' , handleWheel , {
315+ passive : true
316+ } )
317+
318+ return ( ) =>
319+ resolvedScrollContainer . removeEventListener ( 'wheel' , handleWheel )
320+ } , [ resolvedScrollContainer ] )
279321
280322 /**
281323 * Handles scroll events from react-selecto during drag selection.
@@ -290,15 +332,14 @@ const RectangularSelection = ({
290332 */
291333 const handleScroll = useCallback (
292334 e => {
293- const container = scrollElement || scrollContainerRef ?. current
294- if ( ! container ) return
335+ if ( ! resolvedScrollContainer ) return
295336
296- container . scrollBy (
337+ resolvedScrollContainer . scrollBy (
297338 e . direction [ 0 ] * SCROLL_STEP_IN_PIXELS ,
298339 e . direction [ 1 ] * SCROLL_STEP_IN_PIXELS
299340 )
300341 } ,
301- [ scrollElement , scrollContainerRef ]
342+ [ resolvedScrollContainer ]
302343 )
303344
304345 /**
@@ -335,11 +376,6 @@ const RectangularSelection = ({
335376 [ setSelectedItems , setIsSelectAll ]
336377 )
337378
338- // Use the directly provided scrollElement (from virtuoso's scrollerRef),
339- // or fall back to scrollContainerRef.current for non-virtualized containers
340- // eslint-disable-next-line react-hooks/refs
341- const scrollContainer = scrollElement || scrollContainerRef ?. current
342-
343379 return (
344380 < div
345381 ref = { containerRef }
@@ -368,9 +404,9 @@ const RectangularSelection = ({
368404 onSelect = { handleSelect }
369405 onScroll = { handleScroll }
370406 scrollOptions = {
371- scrollContainer
407+ resolvedScrollContainer
372408 ? {
373- container : scrollContainer ,
409+ container : resolvedScrollContainer ,
374410 throttleTime : 30 ,
375411 threshold : 30
376412 }
0 commit comments