Unverified Commit b7a425f1 authored by Justin McCandless's avatar Justin McCandless Committed by GitHub

Right click on selection when unfocused should re-focus (#104666)

Bug fix for focusing a field with right click on existing selection (Mac and iOS)
parent d09e4548
...@@ -1622,7 +1622,7 @@ class TextSelectionGestureDetectorBuilder { ...@@ -1622,7 +1622,7 @@ class TextSelectionGestureDetectorBuilder {
switch (defaultTargetPlatform) { switch (defaultTargetPlatform) {
case TargetPlatform.iOS: case TargetPlatform.iOS:
case TargetPlatform.macOS: case TargetPlatform.macOS:
if (!_lastSecondaryTapWasOnSelection) { if (!_lastSecondaryTapWasOnSelection || !renderEditable.hasFocus) {
renderEditable.selectWord(cause: SelectionChangedCause.tap); renderEditable.selectWord(cause: SelectionChangedCause.tap);
} }
if (shouldShowSelectionToolbar) { if (shouldShowSelectionToolbar) {
......
...@@ -5653,6 +5653,7 @@ void main() { ...@@ -5653,6 +5653,7 @@ void main() {
skip: isContextMenuProvidedByPlatform, // [intended] only applies to platforms where we supply the context menu. skip: isContextMenuProvidedByPlatform, // [intended] only applies to platforms where we supply the context menu.
); );
group('Right click focus', () {
testWidgets('Can right click to focus multiple times', (WidgetTester tester) async { testWidgets('Can right click to focus multiple times', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/pull/103228 // Regression test for https://github.com/flutter/flutter/pull/103228
final FocusNode focusNode1 = FocusNode(); final FocusNode focusNode1 = FocusNode();
...@@ -5704,4 +5705,103 @@ void main() { ...@@ -5704,4 +5705,103 @@ void main() {
expect(focusNode1.hasFocus, isTrue); expect(focusNode1.hasFocus, isTrue);
expect(focusNode2.hasFocus, isFalse); expect(focusNode2.hasFocus, isFalse);
}); });
testWidgets('Can right click to focus on previously selected word on Apple platforms', (WidgetTester tester) async {
final FocusNode focusNode1 = FocusNode();
final FocusNode focusNode2 = FocusNode();
final TextEditingController controller = TextEditingController(
text: 'first second',
);
final UniqueKey key1 = UniqueKey();
await tester.pumpWidget(
CupertinoApp(
home: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CupertinoTextField(
key: key1,
controller: controller,
focusNode: focusNode1,
),
Focus(
focusNode: focusNode2,
child: const Text('focusable'),
),
],
),
),
);
// Interact with the field to establish the input connection.
await tester.tapAt(
tester.getCenter(find.byKey(key1)),
buttons: kSecondaryMouseButton,
);
await tester.pump();
expect(focusNode1.hasFocus, isTrue);
expect(focusNode2.hasFocus, isFalse);
// Select the second word.
controller.selection = const TextSelection(
baseOffset: 6,
extentOffset: 12,
);
await tester.pump();
expect(focusNode1.hasFocus, isTrue);
expect(focusNode2.hasFocus, isFalse);
expect(controller.selection.isCollapsed, isFalse);
expect(controller.selection.baseOffset, 6);
expect(controller.selection.extentOffset, 12);
// Unfocus the first field.
focusNode2.requestFocus();
await tester.pumpAndSettle();
expect(focusNode1.hasFocus, isFalse);
expect(focusNode2.hasFocus, isTrue);
// Right click the second word in the first field, which is still selected
// even though the selection is not visible.
await tester.tapAt(
textOffsetToPosition(tester, 8),
buttons: kSecondaryMouseButton,
);
await tester.pump();
expect(focusNode1.hasFocus, isTrue);
expect(focusNode2.hasFocus, isFalse);
expect(controller.selection.baseOffset, 6);
expect(controller.selection.extentOffset, 12);
// Select everything.
controller.selection = const TextSelection(
baseOffset: 0,
extentOffset: 12,
);
await tester.pump();
expect(focusNode1.hasFocus, isTrue);
expect(focusNode2.hasFocus, isFalse);
expect(controller.selection.baseOffset, 0);
expect(controller.selection.extentOffset, 12);
// Unfocus the first field.
focusNode2.requestFocus();
await tester.pumpAndSettle();
// Right click the first word in the first field.
await tester.tapAt(
textOffsetToPosition(tester, 2),
buttons: kSecondaryMouseButton,
);
await tester.pump();
expect(focusNode1.hasFocus, isTrue);
expect(focusNode2.hasFocus, isFalse);
expect(controller.selection.baseOffset, 0);
expect(controller.selection.extentOffset, 5);
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
});
} }
...@@ -11378,6 +11378,7 @@ void main() { ...@@ -11378,6 +11378,7 @@ void main() {
skip: isContextMenuProvidedByPlatform, // [intended] only applies to platforms where we supply the context menu. skip: isContextMenuProvidedByPlatform, // [intended] only applies to platforms where we supply the context menu.
); );
group('Right click focus', () {
testWidgets('Can right click to focus multiple times', (WidgetTester tester) async { testWidgets('Can right click to focus multiple times', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/pull/103228 // Regression test for https://github.com/flutter/flutter/pull/103228
final FocusNode focusNode1 = FocusNode(); final FocusNode focusNode1 = FocusNode();
...@@ -11431,4 +11432,104 @@ void main() { ...@@ -11431,4 +11432,104 @@ void main() {
expect(focusNode1.hasFocus, isTrue); expect(focusNode1.hasFocus, isTrue);
expect(focusNode2.hasFocus, isFalse); expect(focusNode2.hasFocus, isFalse);
}); });
testWidgets('Can right click to focus on previously selected word on Apple platforms', (WidgetTester tester) async {
final FocusNode focusNode1 = FocusNode();
final FocusNode focusNode2 = FocusNode();
final TextEditingController controller = TextEditingController(
text: 'first second',
);
final UniqueKey key1 = UniqueKey();
await tester.pumpWidget(
MaterialApp(
home: Material(
child: Column(
children: <Widget>[
TextField(
key: key1,
controller: controller,
focusNode: focusNode1,
),
Focus(
focusNode: focusNode2,
child: const Text('focusable'),
),
],
),
),
),
);
// Interact with the field to establish the input connection.
await tester.tapAt(
tester.getCenter(find.byKey(key1)),
buttons: kSecondaryButton,
);
await tester.pump();
expect(focusNode1.hasFocus, isTrue);
expect(focusNode2.hasFocus, isFalse);
// Select the second word.
controller.selection = const TextSelection(
baseOffset: 6,
extentOffset: 12,
);
await tester.pump();
expect(focusNode1.hasFocus, isTrue);
expect(focusNode2.hasFocus, isFalse);
expect(controller.selection.isCollapsed, isFalse);
expect(controller.selection.baseOffset, 6);
expect(controller.selection.extentOffset, 12);
// Unfocus the first field.
focusNode2.requestFocus();
await tester.pumpAndSettle();
expect(focusNode1.hasFocus, isFalse);
expect(focusNode2.hasFocus, isTrue);
// Right click the second word in the first field, which is still selected
// even though the selection is not visible.
await tester.tapAt(
textOffsetToPosition(tester, 8),
buttons: kSecondaryButton,
);
await tester.pump();
expect(focusNode1.hasFocus, isTrue);
expect(focusNode2.hasFocus, isFalse);
expect(controller.selection.baseOffset, 6);
expect(controller.selection.extentOffset, 12);
// Select everything.
controller.selection = const TextSelection(
baseOffset: 0,
extentOffset: 12,
);
await tester.pump();
expect(focusNode1.hasFocus, isTrue);
expect(focusNode2.hasFocus, isFalse);
expect(controller.selection.baseOffset, 0);
expect(controller.selection.extentOffset, 12);
// Unfocus the first field.
focusNode2.requestFocus();
await tester.pumpAndSettle();
// Right click the first word in the first field.
await tester.tapAt(
textOffsetToPosition(tester, 2),
buttons: kSecondaryButton,
);
await tester.pump();
expect(focusNode1.hasFocus, isTrue);
expect(focusNode2.hasFocus, isFalse);
expect(controller.selection.baseOffset, 0);
expect(controller.selection.extentOffset, 5);
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
});
} }
...@@ -1391,12 +1391,14 @@ class FakeRenderEditable extends RenderEditable { ...@@ -1391,12 +1391,14 @@ class FakeRenderEditable extends RenderEditable {
@override @override
void selectWordsInRange({ required Offset from, Offset? to, required SelectionChangedCause cause }) { void selectWordsInRange({ required Offset from, Offset? to, required SelectionChangedCause cause }) {
selectWordsInRangeCalled = true; selectWordsInRangeCalled = true;
hasFocus = true;
} }
bool selectWordEdgeCalled = false; bool selectWordEdgeCalled = false;
@override @override
void selectWordEdge({ required SelectionChangedCause cause }) { void selectWordEdge({ required SelectionChangedCause cause }) {
selectWordEdgeCalled = true; selectWordEdgeCalled = true;
hasFocus = true;
} }
bool selectPositionAtCalled = false; bool selectPositionAtCalled = false;
...@@ -1407,6 +1409,7 @@ class FakeRenderEditable extends RenderEditable { ...@@ -1407,6 +1409,7 @@ class FakeRenderEditable extends RenderEditable {
selectPositionAtCalled = true; selectPositionAtCalled = true;
selectPositionAtFrom = from; selectPositionAtFrom = from;
selectPositionAtTo = to; selectPositionAtTo = to;
hasFocus = true;
} }
bool selectPositionCalled = false; bool selectPositionCalled = false;
...@@ -1420,6 +1423,7 @@ class FakeRenderEditable extends RenderEditable { ...@@ -1420,6 +1423,7 @@ class FakeRenderEditable extends RenderEditable {
@override @override
void selectWord({ required SelectionChangedCause cause }) { void selectWord({ required SelectionChangedCause cause }) {
selectWordCalled = true; selectWordCalled = true;
hasFocus = true;
} }
@override @override
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment