TanStack / ranger

🤖 Headless utilities for building range and multi-range sliders in React, Preact, Solid, Vue, Svelte and Angular
MIT License
757 stars 65 forks source link

Support moving the segment. #55

Open mrybinski opened 1 year ago

mrybinski commented 1 year ago

Excellent library! Really easy to use. I starred it and will watch closely. I can see you're working on the new version, I didn't check it out yet, I just used the latest from npm. So if you already want to add that feature, great! It would be awesome if you can support segment moving. I forked your lib locally and prototyped the moving (but only assuming there are 2 handles). As you will see, I forked the master, not the beta, that's why I didn't create PR.

My prototype assumes there are 2 handles, but it can be easily extended, to support moving segments between any values (and only moving the ones that have handles on each end). Let me know if that is something you would like to support. If so, I can spend some free time working on the beta version PR, unless you want to work on this yourself. For now, I will use my fork of your lib, and include your license message. Here's the video, and below code that I used.

https://user-images.githubusercontent.com/6354034/211786432-4e973f13-a67b-4efc-b750-a85a6038c923.mov

In short, I added this event on segment press:

const handleSegmentPress = React.useCallback(
        (e) => {
            const clientX = e.type === 'touchmove' ? e.changedTouches[0].clientX : e.clientX;

            const onDrag = e => handleSegmentDrag(e, clientX);
            const handleRelease = e => {
                const {
                    tempValues,
                    values,
                    onChange = () => {},
                } = getLatest();

                document.removeEventListener('mousemove', onDrag);
                document.removeEventListener('touchmove', onDrag);
                document.removeEventListener('mouseup', handleRelease);
                document.removeEventListener('touchend', handleRelease);
                const sortedValues = sortNumList(tempValues || values);
                onChange(sortedValues);
                onDrag(sortedValues);
                setTempValues();
            };

            document.addEventListener('mousemove', onDrag);
            document.addEventListener('touchmove', onDrag);
            document.addEventListener('mouseup', handleRelease);
            document.addEventListener('touchend', handleRelease);
        },
        [getLatest, handleSegmentDrag]
    );

and this is handleSegmentDrag:

const handleSegmentDrag = React.useCallback(
        (e, initialX) => {
            const clientX = e.type === 'touchmove' ? e.changedTouches[0].clientX : e.clientX;
            const initial = getValueForClientX(initialX);
            const newValue = getValueForClientX(clientX);
            const diff = newValue - initial;
            if (diff) {
                let actualDiff = 0;
                if (diff > 0) {
                    const last = values[values.length - 1];
                    const newRoundedLastValue = roundToStep(last + diff);

                    actualDiff = newRoundedLastValue - last;

                } else {
                    const first = values[0];
                    const newRoundedFirstValue = roundToStep(first + diff);
                    actualDiff = newRoundedFirstValue - first;
                }

                if (actualDiff) {
                    setTempValues(values.map(v => v += actualDiff));
                }
            }

        },
        [getValueForClientX, roundToStep, values]
    );
rkulinski commented 1 year ago

Thanks. Once migration is done for sure we can consider it!

alpinisme commented 2 months ago

Just chiming in to say that this would also be a great feature for our use case at my workplace.