Support clearing selection programmatically through SelectableRegionState (#152882)
This change exposes: * `SelectableRegionState.clearSelection()` to allow a user to programmatically clear the selection. * `SelectionAreaState`/`SelectionAreaState.selectableRegion` to allow a user to access public API in `SelectableRegion` from `SelectionArea`. Fixes #126980
This commit is contained in:
parent
0847228315
commit
0a7f8af6d1
@ -104,12 +104,16 @@ class SelectionArea extends StatefulWidget {
|
||||
}
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _SelectionAreaState();
|
||||
State<StatefulWidget> createState() => SelectionAreaState();
|
||||
}
|
||||
|
||||
class _SelectionAreaState extends State<SelectionArea> {
|
||||
/// State for a [SelectionArea].
|
||||
class SelectionAreaState extends State<SelectionArea> {
|
||||
FocusNode get _effectiveFocusNode => widget.focusNode ?? (_internalNode ??= FocusNode());
|
||||
FocusNode? _internalNode;
|
||||
final GlobalKey<SelectableRegionState> _selectableRegionKey = GlobalKey<SelectableRegionState>();
|
||||
/// The [State] of the [SelectableRegion] for which this [SelectionArea] wraps.
|
||||
SelectableRegionState get selectableRegion => _selectableRegionKey.currentState!;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
@ -127,6 +131,7 @@ class _SelectionAreaState extends State<SelectionArea> {
|
||||
TargetPlatform.macOS => cupertinoDesktopTextSelectionHandleControls,
|
||||
};
|
||||
return SelectableRegion(
|
||||
key: _selectableRegionKey,
|
||||
selectionControls: controls,
|
||||
focusNode: _effectiveFocusNode,
|
||||
contextMenuBuilder: widget.contextMenuBuilder,
|
||||
|
@ -450,7 +450,7 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
|
||||
// the new window causing the Flutter application to go inactive. In this
|
||||
// case we want to retain the selection so it remains when we return to
|
||||
// the Flutter application.
|
||||
_clearSelection();
|
||||
clearSelection();
|
||||
}
|
||||
}
|
||||
if (kIsWeb) {
|
||||
@ -559,7 +559,7 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
|
||||
..onDragStart = _handleMouseDragStart
|
||||
..onDragUpdate = _handleMouseDragUpdate
|
||||
..onDragEnd = _handleMouseDragEnd
|
||||
..onCancel = _clearSelection
|
||||
..onCancel = clearSelection
|
||||
..dragStartBehavior = DragStartBehavior.down;
|
||||
},
|
||||
);
|
||||
@ -607,7 +607,7 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
|
||||
..onDragStart = _handleMouseDragStart
|
||||
..onDragUpdate = _handleMouseDragUpdate
|
||||
..onDragEnd = _handleMouseDragEnd
|
||||
..onCancel = _clearSelection
|
||||
..onCancel = clearSelection
|
||||
..dragStartBehavior = DragStartBehavior.down;
|
||||
},
|
||||
);
|
||||
@ -1228,7 +1228,7 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
|
||||
/// See also:
|
||||
/// * [_selectStartTo], which sets or updates selection start edge.
|
||||
/// * [_finalizeSelection], which stops the `continuous` updates.
|
||||
/// * [_clearSelection], which clears the ongoing selection.
|
||||
/// * [clearSelection], which clears the ongoing selection.
|
||||
/// * [_selectWordAt], which selects a whole word at the location.
|
||||
/// * [_selectParagraphAt], which selects an entire paragraph at the location.
|
||||
/// * [_collapseSelectionAt], which collapses the selection at the location.
|
||||
@ -1269,7 +1269,7 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
|
||||
/// See also:
|
||||
/// * [_selectEndTo], which sets or updates selection end edge.
|
||||
/// * [_finalizeSelection], which stops the `continuous` updates.
|
||||
/// * [_clearSelection], which clears the ongoing selection.
|
||||
/// * [clearSelection], which clears the ongoing selection.
|
||||
/// * [_selectWordAt], which selects a whole word at the location.
|
||||
/// * [_selectParagraphAt], which selects an entire paragraph at the location.
|
||||
/// * [_collapseSelectionAt], which collapses the selection at the location.
|
||||
@ -1293,7 +1293,7 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
|
||||
/// * [_selectStartTo], which sets or updates selection start edge.
|
||||
/// * [_selectEndTo], which sets or updates selection end edge.
|
||||
/// * [_finalizeSelection], which stops the `continuous` updates.
|
||||
/// * [_clearSelection], which clears the ongoing selection.
|
||||
/// * [clearSelection], which clears the ongoing selection.
|
||||
/// * [_selectWordAt], which selects a whole word at the location.
|
||||
/// * [_selectParagraphAt], which selects an entire paragraph at the location.
|
||||
/// * [selectAll], which selects the entire content.
|
||||
@ -1307,7 +1307,7 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
|
||||
/// The `offset` is in global coordinates.
|
||||
///
|
||||
/// If the whole word is already in the current selection, selection won't
|
||||
/// change. One call [_clearSelection] first if the selection needs to be
|
||||
/// change. One call [clearSelection] first if the selection needs to be
|
||||
/// updated even if the word is already covered by the current selection.
|
||||
///
|
||||
/// One can also use [_selectEndTo] or [_selectStartTo] to adjust the selection
|
||||
@ -1317,7 +1317,7 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
|
||||
/// * [_selectStartTo], which sets or updates selection start edge.
|
||||
/// * [_selectEndTo], which sets or updates selection end edge.
|
||||
/// * [_finalizeSelection], which stops the `continuous` updates.
|
||||
/// * [_clearSelection], which clears the ongoing selection.
|
||||
/// * [clearSelection], which clears the ongoing selection.
|
||||
/// * [_collapseSelectionAt], which collapses the selection at the location.
|
||||
/// * [_selectParagraphAt], which selects an entire paragraph at the location.
|
||||
/// * [selectAll], which selects the entire content.
|
||||
@ -1332,7 +1332,7 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
|
||||
/// The `offset` is in global coordinates.
|
||||
///
|
||||
/// If the paragraph is already in the current selection, selection won't
|
||||
/// change. One call [_clearSelection] first if the selection needs to be
|
||||
/// change. One call [clearSelection] first if the selection needs to be
|
||||
/// updated even if the paragraph is already covered by the current selection.
|
||||
///
|
||||
/// One can also use [_selectEndTo] or [_selectStartTo] to adjust the selection
|
||||
@ -1342,7 +1342,7 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
|
||||
/// * [_selectStartTo], which sets or updates selection start edge.
|
||||
/// * [_selectEndTo], which sets or updates selection end edge.
|
||||
/// * [_finalizeSelection], which stops the `continuous` updates.
|
||||
/// * [_clearSelection], which clear the ongoing selection.
|
||||
/// * [clearSelection], which clear the ongoing selection.
|
||||
/// * [_selectWordAt], which selects a whole word at the location.
|
||||
/// * [selectAll], which selects the entire content.
|
||||
void _selectParagraphAt({required Offset offset}) {
|
||||
@ -1353,7 +1353,7 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
|
||||
|
||||
/// Stops any ongoing selection updates.
|
||||
///
|
||||
/// This method is different from [_clearSelection] that it does not remove
|
||||
/// This method is different from [clearSelection] that it does not remove
|
||||
/// the current selection. It only stops the continuous updates.
|
||||
///
|
||||
/// A continuous update can happen as result of calling [_selectStartTo] or
|
||||
@ -1365,8 +1365,8 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
|
||||
_stopSelectionStartEdgeUpdate();
|
||||
}
|
||||
|
||||
/// Removes the ongoing selection.
|
||||
void _clearSelection() {
|
||||
/// Removes the ongoing selection for this [SelectableRegion].
|
||||
void clearSelection() {
|
||||
_finalizeSelection();
|
||||
_directionalHorizontalBaseline = null;
|
||||
_adjustingSelectionEnd = null;
|
||||
@ -1496,7 +1496,7 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.android:
|
||||
case TargetPlatform.fuchsia:
|
||||
_clearSelection();
|
||||
clearSelection();
|
||||
case TargetPlatform.iOS:
|
||||
hideToolbar(false);
|
||||
case TargetPlatform.linux:
|
||||
@ -1525,7 +1525,7 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.android:
|
||||
case TargetPlatform.fuchsia:
|
||||
_clearSelection();
|
||||
clearSelection();
|
||||
case TargetPlatform.iOS:
|
||||
hideToolbar(false);
|
||||
case TargetPlatform.linux:
|
||||
@ -1619,7 +1619,7 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
|
||||
|
||||
@override
|
||||
void selectAll([SelectionChangedCause? cause]) {
|
||||
_clearSelection();
|
||||
clearSelection();
|
||||
_selectable?.dispatchSelectionEvent(const SelectAllSelectionEvent());
|
||||
if (cause == SelectionChangedCause.toolbar) {
|
||||
_showToolbar();
|
||||
@ -1635,7 +1635,7 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
|
||||
@override
|
||||
void copySelection(SelectionChangedCause cause) {
|
||||
_copy();
|
||||
_clearSelection();
|
||||
clearSelection();
|
||||
}
|
||||
|
||||
@Deprecated(
|
||||
|
@ -4697,6 +4697,64 @@ void main() {
|
||||
skip: kIsWeb, // [intended] Web uses its native context menu.
|
||||
);
|
||||
|
||||
testWidgets('can clear selection through SelectableRegionState', (WidgetTester tester) async {
|
||||
final FocusNode focusNode = FocusNode();
|
||||
addTearDown(focusNode.dispose);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: SelectableRegion(
|
||||
focusNode: focusNode,
|
||||
selectionControls: materialTextSelectionControls,
|
||||
child: const Column(
|
||||
children: <Widget>[
|
||||
Text('How are you?'),
|
||||
Text('Good, and you?'),
|
||||
Text('Fine, thank you.'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final SelectableRegionState state =
|
||||
tester.state<SelectableRegionState>(find.byType(SelectableRegion));
|
||||
final RenderParagraph paragraph1 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you?'), matching: find.byType(RichText)));
|
||||
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph1, 2), kind: PointerDeviceKind.mouse);
|
||||
addTearDown(gesture.removePointer);
|
||||
await tester.pump();
|
||||
await gesture.up();
|
||||
await tester.pump();
|
||||
|
||||
await gesture.down(textOffsetToPosition(paragraph1, 2));
|
||||
await tester.pumpAndSettle();
|
||||
expect(paragraph1.selections[0], const TextSelection(baseOffset: 0, extentOffset: 3));
|
||||
|
||||
await gesture.moveTo(textOffsetToPosition(paragraph1, 4));
|
||||
await tester.pump();
|
||||
expect(paragraph1.selections[0], const TextSelection(baseOffset: 0, extentOffset: 7));
|
||||
|
||||
final RenderParagraph paragraph2 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Good, and you?'), matching: find.byType(RichText)));
|
||||
await gesture.moveTo(textOffsetToPosition(paragraph2, 5));
|
||||
// Should select the rest of paragraph 1.
|
||||
expect(paragraph1.selections[0], const TextSelection(baseOffset: 0, extentOffset: 12));
|
||||
expect(paragraph2.selections[0], const TextSelection(baseOffset: 0, extentOffset: 6));
|
||||
|
||||
final RenderParagraph paragraph3 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Fine, thank you.'), matching: find.byType(RichText)));
|
||||
await gesture.moveTo(textOffsetToPosition(paragraph3, 6));
|
||||
expect(paragraph1.selections[0], const TextSelection(baseOffset: 0, extentOffset: 12));
|
||||
expect(paragraph2.selections[0], const TextSelection(baseOffset: 0, extentOffset: 14));
|
||||
expect(paragraph3.selections[0], const TextSelection(baseOffset: 0, extentOffset: 11));
|
||||
await gesture.up();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Clear selection programatically.
|
||||
state.clearSelection();
|
||||
expect(paragraph1.selections, isEmpty);
|
||||
expect(paragraph2.selections, isEmpty);
|
||||
expect(paragraph3.selections, isEmpty);
|
||||
}, skip: kIsWeb); // https://github.com/flutter/flutter/issues/125582.
|
||||
|
||||
testWidgets('Text processing actions are added to the toolbar', (WidgetTester tester) async {
|
||||
final MockProcessTextHandler mockProcessTextHandler = MockProcessTextHandler();
|
||||
TestWidgetsFlutterBinding.ensureInitialized().defaultBinaryMessenger
|
||||
|
Loading…
x
Reference in New Issue
Block a user