Support for Virtualization
Thank you for this amazing library. It's hands down the best free open-source selection tool we've come across. We really appreciate the efforts put into it!
Is your feature request related to a problem? Please describe.
We're running into issues when using viselect in a virtualized grid. The core problem is that the library internally manages its own selection store, which doesn't persist when elements are unmounted due to virtualization. As a result, items are immediately unselected once they scroll out of view, even if we attempt to restore selection state on our end when they re-render.
Describe the solution you'd like
We'd like viselect to better support virtualization by decoupling selection state store. Ideally, the library could expose an external selection store (or allow plugging one in), so we can manage selection across unmounts ourselves. Alternatively, a hook or event system that allows us to intercept selection changes and preserve them externally would help too.
Describe alternatives you've considered
We've tried manually saving selection state and restoring it when virtualized items re-render, but viselect unselects those items as soon as they leave the visible DOM, making this workaround ineffective. There doesn't appear to be a reliable way to persist selection state across unmounts with the current API.
Additional context
This feature is essential for integrating viselect with libraries like react-window, react-virtualized or react-virtuoso, which unmount off-screen DOM nodes for performance. Having virtualization support would greatly increase the use cases viselect can serve.
Thanks again for maintaining such a high-quality library, we're excited to keep using it and would love to see it support even more advanced use cases like this!
Hey! Thank you for your elaborate feature request, I'll have a look into this as this definitely falls into the core-functionality of this library, I'd say. But I'm not quite sure yet how feasible this is as of right now :)
Hey @simonwep! Thanks a lot for the response 🙌🏻
Totally understand that feasibility might be tricky at this point. We are comfortable digging into the code, and if you’re open to it, we’d love to collaborate on this and work toward a PR. We’d appreciate any pointers on where to start as we definitely don’t want to go against the design philosophy or introduce something that’s hard to maintain, so any guidance from you would be super helpful.
Looking forward to hearing your thoughts!
I took a quick look at how it behaves (or how they work) based on react-virtualized. I did some testing and it seems like it works as expected. The only thing that doesn't work is auto-scrolling due to no element having overflow: auto or being actually scrollable. Can you share some code that didn't work?
The elements don't actually need to be attached to the DOM for it to work, you only need an external store like below where you store your id's and ignore what viselect currently thinks is selected ;)
Demo
Source for demo
import React, {useState} from 'react';
import {Grid} from 'react-virtualized';
import {createRoot} from 'react-dom/client';
import {SelectionArea, SelectionEvent} from '../src';
import './index.css';
const SelectableArea = ({boxes, offset, className}: {
boxes: number;
offset: number;
className: string;
}) => {
const [selected, setSelected] = useState<Set<number>>(() => new Set());
const extractIds = (els: Element[]): number[] =>
els.map(v => v.getAttribute('data-key'))
.filter(Boolean)
.map(Number);
const onStart = ({event, selection}: SelectionEvent) => {
if (!event?.ctrlKey && !event?.metaKey) {
selection.clearSelection();
setSelected(() => new Set());
}
};
const onMove = ({store: {changed: {added, removed}}}: SelectionEvent) => {
setSelected(prev => {
const next = new Set(prev);
extractIds(added).forEach(id => next.add(id));
extractIds(removed).forEach(id => next.delete(id));
return next;
});
};
return (
<SelectionArea className={`container ${className}`}
onStart={onStart}
onMove={onMove}
selectables=".selectable">
<Grid
cellRenderer={({rowIndex, columnIndex, style}) => {
const key = rowIndex * 10 + columnIndex + offset;
return (
<div key={key}
className={selected.has(key) ? 'selected selectable' : 'selectable'}
style={style}
data-key={key}/>
);
}}
columnCount={10}
columnWidth={50}
height={300}
rowCount={boxes}
rowHeight={30}
width={700}
/>
</SelectionArea>
);
};
const root = createRoot(document.getElementById('root') as HTMLElement);
root.render(
<React.StrictMode>
<h1>React</h1>
<SelectableArea boxes={500} offset={0} className="green"/>
<SelectableArea boxes={1000} offset={500} className="blue"/>
<SelectableArea boxes={3000} offset={3000} className="red"/>
</React.StrictMode>,
);
hey, I have managed to get this work, its a bit tricky but I wanted to thank you.. I'll use it for vuefinder :)
https://github.com/user-attachments/assets/ff84e49a-4c65-45d4-b418-c27c96d06cf4
Hey @n1crack! Glad you figured it out - is there anything viselect could do to make it easier or did it work as I assumed as per my last comment? :)
We definitely need an external store. There are a few issues related to this that I’ll post about them soon, but nothing urgent for now.
When scrolling down, new items appear and need to be refreshed in the selection state. Also, if you start a selection from the right edge, scroll to the bottom, and select all, the component isn’t aware of the DOM elements that were removed — so we need to recalculate those items.
@simonwep
its better if I give you a demo :)
https://vuefinder.ozdemir.be/examples/large-dataset.html
the performance is nice with 50k items.. but the selection should be more accurate..
Dear @simonwep
Thank you for your example (and this library of course)!
Do you think it's possible to support selecting and scrolling (explicitely with the mouse wheel)?
I've adapted your example to use the "Alt" key only as binding (since CTRL+Alt+Scroll triggers my browser's zoom), but it fails to select items when I "continously" scroll and select. It works as in your video if I stop scrolling, perform a selection, continue to scroll, perform another selection.
https://github.com/user-attachments/assets/7674592c-9e2d-4155-baf6-aa6dc32a7a50
The first 3 selections were individual selections (e.g. I pressed Alt-key, made a selection, let go Alt-key and repeated). For the 4th selection I kept the Alt-key pressed while starting to scroll.
In a different example (not using react-virtualized) I called resolveSelectables once new items became visible, but for it to take effect I had to stop scrolling (I could keep the Alt-key pressed, but the scroll action had to stop)
Edit: Apologies, I wrote this comment without seeing the messages from the past 24h since my screen was stale
Edit 2: @n1crack Could you share how you managed to scroll and continiously select items? Seems to work in vuefinder, but it does not work (at least for me) in Simon's example above
Can you change the boundaries and try again?
Hi @n1crack I've set boundaries and also call event.selection.resolveSelectables in onMove.
This helped, but viselect reports selected elements as removed in onMove once they leave the visible screen (and get unmounted by react-virtualized) . Is there something else you've done to mitigate this?
I'm still working on this,
https://github.com/n1crack/vuefinder/blob/master/src/composables/useSelection.ts
I resolved the issues pretty much. You can check the links again.