Skip to content

feat: support dragging range slider track to move entire range#8698

Merged
mscolnick merged 9 commits intomarimo-team:mainfrom
Sushit-prog:feature/range-slider-fixed-range
Mar 20, 2026
Merged

feat: support dragging range slider track to move entire range#8698
mscolnick merged 9 commits intomarimo-team:mainfrom
Sushit-prog:feature/range-slider-fixed-range

Conversation

@Sushit-prog
Copy link
Copy Markdown
Contributor

@Sushit-prog Sushit-prog commented Mar 16, 2026

Closes #5240

What changed

Dragging the filled track between the two handles now moves
the entire range together, preserving the selected width.
Individual handles still work independently. No new parameters
— this is default behavior for all range sliders.

Frontend-only change in range-slider.tsx.

How it works

On pointerdown, the drag start position and value are captured
once. On pointermove, the delta is computed from that fixed
anchor and applied to both handles simultaneously. Pointer
capture ensures smooth dragging even when the cursor leaves
the element.

Testing

  • ✅ Track drag preserves range width
  • ✅ Individual handle drag works independently
  • ✅ Step snapping during track drag
  • ✅ Vertical orientation
  • ✅ Discrete steps
  • ✅ Boundary clamping at min/max

@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 16, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
marimo-docs Ready Ready Preview, Comment Mar 20, 2026 5:07am

Request Review

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 16, 2026

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@Sushit-prog
Copy link
Copy Markdown
Contributor Author

I have read the CLA Document and I hereby sign the CLA

@Light2Dark
Copy link
Copy Markdown
Collaborator

I'm confused, I thought we didn't want to add a new parameter

@Sushit-prog
Copy link
Copy Markdown
Contributor Author

I'm confused, I thought we didn't want to add a new parameter

Hi @Light2Dark! Thanks for the review. I understand the confusion, based on the discussion, I can update this to make dragging the middle range bar (between the two handles) always move both handles together as default behaviour, without needing the fixed_range parameter at all. Would that be the preferred approach? Happy to update the PR!

Copy link
Copy Markdown
Contributor

@akshayka akshayka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not add a new argument to range_slider

Copy link
Copy Markdown
Collaborator

@manzt manzt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the discussion (#5240 (comment)), we just want to update the default behavior to support the range and avoid the new parameter.

@Sushit-prog
Copy link
Copy Markdown
Contributor Author

Updated the PR based on feedback, removed the fixed_range parameter entirely from both Python and frontend. Middle-drag on the range bar now works as default behaviour for all range sliders, implemented as a frontend-only change in range-slider.tsx. No new Python parameters added. Dragging the blue bar between the two thumbs moves both handles together while keeping the range width constant.

@Sushit-prog
Copy link
Copy Markdown
Contributor Author

The only failing backend test is test_ibis_table.py::TestTemporalColSummaries::test_time_column which appears to be a pre-existing issue with sqlglot/ibis unrelated to this PR. All other 6623 tests pass. The frontend changes are complete middle-drag on the range bar now works as default behavior with no new parameters added.

@Light2Dark Light2Dark added enhancement New feature or request and removed api-change labels Mar 17, 2026
@Light2Dark Light2Dark requested a review from Copilot March 17, 2026 13:48
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the range slider UX so users can drag the filled track to move both handles together while keeping the selected range width constant (implemented in the frontend), and documents the behavior in the Python UI docstring.

Changes:

  • Implement middle-drag (filled track drag) behavior for range sliders in the frontend RangeSlider component.
  • Update Python range_slider docstring to document the new default interaction.
  • Minor cleanup in the frontend plugin component (comment/formatting).

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 8 comments.

File Description
marimo/_plugins/ui/_impl/input.py Adds a Notes: section documenting filled-track dragging behavior for range_slider.
frontend/src/plugins/impl/RangeSliderPlugin.tsx Minor formatting/comment cleanup around props/onValueChange.
frontend/src/components/ui/range-slider.tsx Adds pointer-driven filled-track dragging logic (range-preserving drag).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread frontend/src/components/ui/range-slider.tsx
Comment on lines +80 to +83
const trackLength = rect.height;
delta = -((e.clientY - dragStartY.current) / trackLength) * totalRange;
} else {
const trackLength = rect.width;
const rangeWidth = origRight - origLeft;

// Snap delta to step
const step = props.step ?? 1;
Comment on lines +110 to +118
const handleRangePointerUp = (e: React.PointerEvent<HTMLSpanElement>) => {
if (!isDraggingRange.current) {
return;
}
isDraggingRange.current = false;
if (props.value && props.value.length === 2) {
props.onValueCommit?.(props.value);
}
};
Comment on lines +114 to +117
isDraggingRange.current = false;
if (props.value && props.value.length === 2) {
props.onValueCommit?.(props.value);
}
Comment on lines +59 to +107
const handleRangePointerMove = (e: React.PointerEvent<HTMLSpanElement>) => {
if (!isDraggingRange.current || !props.value || props.value.length !== 2) {
return;
}

const rootEl = rootRef.current;
if (!rootEl) {
return;
}

const rect = rootEl.getBoundingClientRect();
const isVertical = props.orientation === "vertical";

const min = props.min ?? 0;
const max = props.max ?? 100;
const totalRange = max - min;

// Delta is always from the ORIGINAL drag start position
// so the movement stays proportional and doesn't drift
let delta: number;
if (isVertical) {
const trackLength = rect.height;
delta = -((e.clientY - dragStartY.current) / trackLength) * totalRange;
} else {
const trackLength = rect.width;
delta = ((e.clientX - dragStartX.current) / trackLength) * totalRange;
}

// Always use the ORIGINAL values from when drag started — never current props.value
const [origLeft, origRight] = dragStartValue.current;
const rangeWidth = origRight - origLeft;

// Snap delta to step
const step = props.step ?? 1;
const snappedDelta = Math.round(delta / step) * step;

// Clamp so neither thumb exceeds min/max
const clampedDelta = Math.max(
min - origLeft,
Math.min(max - origRight, snappedDelta),
);

const newLeft = origLeft + clampedDelta;
const newRight = newLeft + rangeWidth;

// Only fire if value actually changed
if (newLeft !== props.value[0] || newRight !== props.value[1]) {
props.onValueChange?.([newLeft, newRight]);
}
Comment on lines +34 to +41
const mergedRef = (node: React.ElementRef<typeof SliderPrimitive.Root>) => {
rootRef.current = node;
if (typeof ref === "function") {
ref(node);
} else if (ref) {
ref.current = node;
}
};
Comment thread marimo/_plugins/ui/_impl/input.py Outdated
Comment on lines +422 to +425
Notes:
Dragging the filled track (the colored bar between the two handles)
moves both handles together while preserving the selected range width.
Individual handles can still be dragged independently to adjust the range.
@dmadisetti
Copy link
Copy Markdown
Collaborator

Thanks for iterating! A few of the copilot issues look valid. Additionally I think you can undo some of the python changes. The implementation also seems a little broken right now:

Screen.Recording.2026-03-17.at.11.51.20.AM.mov

Ping back when you have a screencast of it working! Thanks again!

@dmadisetti dmadisetti marked this pull request as draft March 17, 2026 18:55
- use internal currentDragValue ref independent of controlled props.value
- capture trackRect once on pointerdown to avoid drift from re-renders
- release pointer capture on pointerup
- add stopPropagation on pointermove to prevent Radix interference
- fix stale value in onValueCommit
- revert unnecessary Python docstring changes

# Conflicts:
#	frontend/src/components/ui/range-slider.tsx
#	marimo/_plugins/ui/_impl/input.py
@Sushit-prog Sushit-prog changed the title feat: add fixed_range parameter to range_slider to keep constant range width feat: support dragging range slider track to move entire range Mar 19, 2026
@Sushit-prog
Copy link
Copy Markdown
Contributor Author

Hi @dmadisetti, fixed all the issues. Here's a summary of what was addressed:

  • Fixed the broken drag behaviour — track drag now moves both handles
    smoothly while preserving the range width consistently
  • Released pointer capture on pointerup
  • Fixed stale props.value in onValueCommit using currentDragValue ref
  • Captured track bounding rect once on pointerdown to prevent drift
    during re-renders
  • Added stopPropagation on pointermove to prevent Radix interference
  • Converted mergedRef to useCallback
  • Handled props.steps array for correct step snapping
  • Reverted Python docstring Notes: section as suggested

Also updated the PR title to reflect the actual implementation.

fixed.mp4

@Sushit-prog Sushit-prog marked this pull request as ready for review March 19, 2026 18:21
@dmadisetti
Copy link
Copy Markdown
Collaborator

Super smooth! I was still able to drag when disabled though

@Sushit-prog
Copy link
Copy Markdown
Contributor Author

@dmadisetti Fixed track drag is now prevented when the
slider is disabled.

dmadisetti
dmadisetti previously approved these changes Mar 19, 2026
Copy link
Copy Markdown
Collaborator

@dmadisetti dmadisetti left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM and functionality feels right.

It's be great if you could remove the changes you have in marimo/_plugins/ui/_impl/input.py, frontend/src/plugins/impl/RangeSliderPlugin.tsx (just whitespace but still)

Thanks! I'll let someone a little more familiar with typescript merge in case there's something I'm missing

@Sushit-prog
Copy link
Copy Markdown
Contributor Author

Done! Reverted all changes in input.py and RangeSliderPlugin.tsx
back to main. Only range-slider.tsx is changed now.

@Sushit-prog Sushit-prog requested a review from akshayka March 20, 2026 18:50
@mscolnick mscolnick merged commit d87972f into marimo-team:main Mar 20, 2026
24 checks passed
@github-actions
Copy link
Copy Markdown

🚀 Development release published. You may be able to view the changes at https://marimo.app?v=0.21.2-dev21

@Sushit-prog Sushit-prog deleted the feature/range-slider-fixed-range branch March 31, 2026 12:39
VishakBaddur pushed a commit to VishakBaddur/marimo that referenced this pull request Apr 4, 2026
…o-team#8698)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

drag range_slider to keep constant range

7 participants