Skip to content

fix: scroll down when a mouse drag-select passes the bottom of the editor - #2780

Open
mtallenca wants to merge 3 commits into
singerdmx:masterfrom
mtallenca:fix/mouse-drag-scroll-down
Open

mtallenca wants to merge 3 commits into
singerdmx:masterfrom
mtallenca:fix/mouse-drag-scroll-down

Conversation

@mtallenca

@mtallenca mtallenca commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Description

Selecting text by dragging with a mouse or trackpad scrolls the editor when the pointer passes the top of the viewport, but not when it passes the bottom. You can extend a selection upwards through a long document, but a selection dragged downwards stops at the last visible line.

RenderEditor.extendSelection keeps base and extent in document order (base ≤ extent) whichever way the drag goes. While dragging, _showCaretOnScreen asks getOffsetToRevealCursor which endpoint to reveal. For a non-collapsed selection that isn't a DragTextSelection, it reveals endpoints.first, the top of the selection. On an upward drag that's the end under the pointer, so the editor scrolls. On a downward drag it's where the drag started, which is already on screen, so the editor never scrolls.

Selection-handle drags don't have this problem because they produce a DragTextSelection, whose first flag says which end is moving.

Fix: extendSelection now produces a DragTextSelection too, with first: true when the selection grows upwards (the base moved) and first: false when it grows downwards (the extent moved). getOffsetToRevealCursor then reveals the moving end. Upward drags behave as before.

We hit this on an iPad with a Magic Keyboard trackpad: in a long note, a user could select upwards through the whole note but not downwards.

Follow-up: scrolling while the pointer is held at an edge

Testing that fix on the iPad turned up more problems with the same drag:

  • It only scrolled while the pointer moved. Each move revealed one more line, and holding the pointer still at an edge did nothing. Where the pointer can't go past the edge (the bottom of the screen), the selection couldn't get further down. The drag now uses EdgeDraggingAutoScroller, as SelectableRegion does: while the pointer is within 24 px of the top or bottom edge, the editor keeps scrolling and extending the selection until the button is released.
  • A covered bottom. When something is drawn over the bottom of the editor, the pointer can't get near the scroll view's real edge, so auto-scroll barely started. The edge is now measured from the top of scrollBottomInset, the setting that already keeps the caret above such an overlay.
  • showCursor: false (e.g. a read-only editor). _showCaretOnScreen returns early when the cursor is hidden, so the drag never revealed its moving end. A drag now reveals it whatever showCursor is.
  • No selection handles without a keyboard. The selection overlay was refreshed only on a focus change or when a keyboard was showing. A read-only editor doesn't listen for focus changes, so a mouse drag there never got handles. _didChangeTextEditingValue now refreshes the overlay after the build when no keyboard is showing, too.
  • A cancelled drag (e.g. by a system gesture) now ends like a released one, so auto-scroll can't run on after it.

Tests

Added regression tests to test/bug_fix_test.dart:

  • A mouse drag from near the top of a 300 px editor holding 60 lines to its bottom edge must scroll. Without the fix the offset stays 0.
  • A drag held still at the bottom edge must keep scrolling. Without the auto-scroll it stops when the pointer stops.
  • A drag held 40 px above the bottom edge of an editor with scrollBottomInset: 60 must keep scrolling. Without the inset handling it never starts.

The full suite passes.

Type of Change

  • ✨ Feature: New functionality without breaking existing features.
  • 🛠️ Bug fix: Resolves an issue without altering current behavior.
  • 🧹 Refactor: Code reorganization, no behavior change.
  • ❌ Breaking: Alters existing functionality and requires updates.
  • 🧪 Tests: New or modified tests
  • 📝 Documentation: Updates or additions to documentation.
  • 🗑️ Chore: Routine tasks, or maintenance.
  • ✅ Build configuration change: Build/configuration changes.

Scroll with EdgeDraggingAutoScroller while the pointer is held near the top
or bottom, measuring the bottom edge from scrollBottomInset when something
covers it. Reveal the drag's moving end even with showCursor: false, refresh
the selection overlay when no keyboard is showing, and end a drag the system
cancels.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant