Skip to content

SelectionArea

Enables app-wide text selection in its subtree.

Learn more: Input & gestures

Import: package:fleury/fleury.dart, or package:fleury/fleury_core.dart in browser code.

SelectionArea(
onSelectionChanged: (content) =>
setState(() => selected = content?.plainText ?? ''),
child: const Column(
children: [
Text('Planning notes'),
Text('Meet on Tuesday.'),
],
),
)

Wraps a subtree in:

  1. A SelectionScope that publishes a SelectionContainerDelegate, so descendant render objects (e.g. RenderText) can register themselves as Selectable.
  2. A GestureDetector that turns mouse drag into SelectionEdgeUpdateEvents.
  3. A KeyBindings block providing Ctrl+A (select all), Ctrl+C (copy via the ambient ClipboardScope), and Esc (clear). Bindings bubble when there’s nothing to act on, so ancestors still see the event.

Mouse drag. Press, drag, release. Every Text under the area highlights its share of the selection automatically; the full selected text crosses widget boundaries with newlines inserted at row transitions.

Keyboard. Ctrl+A selects everything below the area; Ctrl+C pushes the selection through the ambient clipboard’s write (which tries platform tools, OSC 52, and an in-process register in that order); Esc clears. Selecting text gives the region keyboard focus. It does not autofocus or add a Tab stop by default; nested controls keep their own focus behavior. Supply focusNode to control focus or opt the region into traversal.

Auto-copy. Set copyOnRelease: true to fire a clipboard write on every mouse-release that completes a non-empty drag — matches the “select to copy” idiom of native terminals.

Multi-click. Double-click selects the word at the click position (alphanumerics + Unicode letters/digits/underscore, with the punctuation-or-whitespace at the click position falling back to a single character). Triple-click selects the entire laid-out line at the click point.

Shift+Arrow. Extends the moving edge of an existing selection by one grapheme (left/right) or one row (up/down). Wide characters (CJK, emoji, ZWJ sequences) are crossed in one step, and vertical motion hops between Selectables so Shift+Down can cross from one Text into another. When no selection is active the Shift+Arrow event bubbles so a different consumer (focus traversal, scroll) can claim it.

Shift+Home / Shift+End. Extends the moving edge to the start or end of the cursor’s current row (the Selectable it sits in), keeping the anchor — so Shift+End selects through the last character of the line. Like Shift+Arrow, this bubbles when no selection is active.

Shift+Click. When a selection is active, holding Shift while clicking moves the cursor to the click point without disturbing the anchor — extends the selection to the click. Shift+Click also breaks the double/triple-click streak so a Shift+Click after a double-click starts a fresh single selection from the anchor. With no active selection, Shift+Click falls through to the normal “fresh anchor” path.

Opt-out. For a subtree that should NOT participate in any ancestor SelectionArea’s selection, wrap it in SelectionArea.disabled. For an individual widget, set allowSelect: false — supported on Text and RichText.

Selectable widgets. Text and RichText are selectable today. A mixed subtree (plain Text next to styled RichText next to more plain Text) selects across all of them — the clipboard copy is the plain text with \n between rows, never ANSI escape codes.

Shift-bypass for native selection. On terminals that support it (xterm, GNOME Terminal/VTE, Konsole, Kitty, WezTerm, Ghostty, Windows Terminal), holding Shift while clicking bypasses the app’s mouse reporting and uses the terminal’s own native selection. This requires no code on our side — the terminal intercepts the event before it ever reaches the app. iTerm2 uses Option instead. Alacritty historically didn’t support this; check per-terminal docs.

macOS Terminal.app limitation. Apple’s Terminal.app does not implement OSC 52, so payloads from the OSC 52 fallback path are silently dropped there. The platform-tool path (pbcopy) is used when not over SSH, but cross-machine clipboard from SSH on Terminal.app does not work. Recommend iTerm2, Ghostty, or Kitty for that platform.

ParameterTypeDefaultDescription
focusNode:FocusNode?—Optional caller-owned focus node for this region. Without one, the area manages a node that receives pointer focus but is skipped by Tab. More
onSelectionChanged:SelectionChangedCallback?—Called whenever the active selection changes. Receives a SelectedContent (whose plainText is the concatenated selection across all Selectables in reading order), or null when nothing is selected.
copyOnRelease:boolfalseWhen true, the active selection is pushed to the ambient ClipboardScope on every mouse-release that completes a non-empty drag. Off by default — most apps want explicit Ctrl+C wiring or a different trigger.
copyClearsSelection:boolfalseWhen true, a Ctrl+C copy clears the selection after writing it. Off by default (an explicit SelectionArea keeps the selection visible so you can copy again). The app-wide default wrap (DefaultRootSelection) turns it ON: there, Ctrl+C also means quit (the runApp exit guard), so a held selection must not trap Ctrl+C on “copy” — clearing after the copy lets the next Ctrl+C bubble through to quit.
selectAllShortcut:booltrueWhether to bind Ctrl+A to select-all. On by default. The app-wide default wrap (DefaultRootSelection) turns it OFF so the always-on selection never shadows an app’s own Ctrl+A binding (a deeper binding out-ranks an outer one, so a bound-here Ctrl+A would eat the chord first). Drag-select still works; an app that wants keyboard select-all uses an explicit SelectionArea.
scrollController:ScrollController?—Optional scroll controller for the scrollable below us. When set, dragging near the top/bottom edge of the selectable region auto-scrolls the viewport and extends the selection into the newly-visible content — the standard “drag-to-extend past the edge” interaction of every native text editor. More
autoScrollEdgeRows:int1How many rows from the top/bottom edge of the selectable region trigger auto-scroll. Defaults to 1 — auto-scroll engages the instant the cursor enters the first or last visible row.
autoScrollInterval:Durationconst Duration(milliseconds: 50)Interval between auto-scroll ticks while the cursor is in the edge zone. Defaults to 50ms (~20 rows per second). Used only when scrollController is set.
child:WidgetrequiredThe subtree in which selection is enabled.
  • focusNode: The supplied node’s focusability and traversal settings are preserved. Mounting the area never requests focus; selecting its text does.
  • scrollController: When null, auto-scroll is disabled and dragging past the visible edge stops at the edge.

SelectionArea is defined in packages/fleury/lib/src/widgets/selection/selection_area.dart.

Category: Input handling & focus · All widgets