diff --git a/packages/flutter/lib/src/widgets/selectable_region.dart b/packages/flutter/lib/src/widgets/selectable_region.dart index 4b3d6e3730..16711e25d6 100644 --- a/packages/flutter/lib/src/widgets/selectable_region.dart +++ b/packages/flutter/lib/src/widgets/selectable_region.dart @@ -341,7 +341,20 @@ class SelectableRegionState extends State with TextSelectionDe _gestureRecognizers[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers( () => TapGestureRecognizer(debugOwner: this), (TapGestureRecognizer instance) { - instance.onTap = _clearSelection; + instance.onTapUp = (TapUpDetails details) { + if (defaultTargetPlatform == TargetPlatform.iOS && _positionIsOnActiveSelection(globalPosition: details.globalPosition)) { + // On iOS when the tap occurs on the previous selection, instead of + // moving the selection, the context menu will be toggled. + final bool toolbarIsVisible = _selectionOverlay?.toolbarIsVisible ?? false; + if (toolbarIsVisible) { + hideToolbar(false); + } else { + _showToolbar(location: details.globalPosition); + } + } else { + _clearSelection(); + } + }; instance.onSecondaryTapDown = _handleRightClickDown; }, ); diff --git a/packages/flutter/test/widgets/selectable_region_test.dart b/packages/flutter/test/widgets/selectable_region_test.dart index 2f70f266f9..a2e8bcb67f 100644 --- a/packages/flutter/test/widgets/selectable_region_test.dart +++ b/packages/flutter/test/widgets/selectable_region_test.dart @@ -886,6 +886,78 @@ void main() { await gesture.up(); }); + testWidgets( + 'single tap on the previous selection toggles the toolbar on iOS', + (WidgetTester tester) async { + Set buttonTypes = {}; + final UniqueKey toolbarKey = UniqueKey(); + await tester.pumpWidget( + MaterialApp( + home: SelectableRegion( + focusNode: FocusNode(), + selectionControls: materialTextSelectionHandleControls, + contextMenuBuilder: ( + BuildContext context, + SelectableRegionState selectableRegionState, + ) { + buttonTypes = selectableRegionState.contextMenuButtonItems + .map((ContextMenuButtonItem buttonItem) => buttonItem.type) + .toSet(); + return SizedBox.shrink(key: toolbarKey); + }, + child: const Column( + children: [ + Text('How are you?'), + Text('Good, and you?'), + Text('Fine, thank you.'), + ], + ), + ), + ), + ); + + expect(buttonTypes.isEmpty, true); + expect(find.byKey(toolbarKey), findsNothing); + + final RenderParagraph paragraph = tester.renderObject(find.descendant(of: find.text('How are you?'), matching: find.byType(RichText))); + final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph, 2)); + addTearDown(gesture.removePointer); + await tester.pump(const Duration(milliseconds: 500)); + await gesture.up(); + await tester.pumpAndSettle(); + expect(paragraph.selections[0], const TextSelection(baseOffset: 0, extentOffset: 3)); + expect(buttonTypes, contains(ContextMenuButtonType.copy)); + expect(buttonTypes, contains(ContextMenuButtonType.selectAll)); + expect(find.byKey(toolbarKey), findsOneWidget); + + await gesture.down(textOffsetToPosition(paragraph, 2)); + await tester.pump(); + await gesture.up(); + await tester.pumpAndSettle(); + expect(paragraph.selections[0], const TextSelection(baseOffset: 0, extentOffset: 3)); + expect(buttonTypes, contains(ContextMenuButtonType.copy)); + expect(buttonTypes, contains(ContextMenuButtonType.selectAll)); + expect(find.byKey(toolbarKey), findsNothing); + + await gesture.down(textOffsetToPosition(paragraph, 2)); + await tester.pump(); + await gesture.up(); + await tester.pumpAndSettle(); + expect(paragraph.selections[0], const TextSelection(baseOffset: 0, extentOffset: 3)); + expect(buttonTypes, contains(ContextMenuButtonType.copy)); + expect(buttonTypes, contains(ContextMenuButtonType.selectAll)); + expect(find.byKey(toolbarKey), findsOneWidget); + + // Clear selection. + await tester.tapAt(textOffsetToPosition(paragraph, 9)); + await tester.pump(); + expect(paragraph.selections.isEmpty, true); + expect(find.byKey(toolbarKey), findsNothing); + }, + variant: TargetPlatformVariant.only(TargetPlatform.iOS), + skip: kIsWeb, // [intended] Web uses its native context menu. + ); + testWidgets( 'right-click mouse can select word at position on Apple platforms', (WidgetTester tester) async {