Skip to content

Commit

Permalink
Unifies text field focus management in desktops (flutter#129652)
Browse files Browse the repository at this point in the history
related flutter#128709

engine PR: flutter/engine#43279

The web engine requires a way to unfocus textfield, It comes to nature
to me that we should leverage didGain/didLose a11y focus action. I also
unifies the action handler of all desktop platforms

## Pre-launch Checklist

- [ ] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [ ] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [ ] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [ ] I signed the [CLA].
- [ ] I listed at least one issue that this PR fixes in the description
above.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [ ] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#overview
[Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene
[test-exempt]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes
[Discord]: https://github.com/flutter/flutter/wiki/Chat
  • Loading branch information
chunhtai authored and LouiseHsu committed Jul 13, 2023
1 parent b334efc commit 8057823
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 6 deletions.
17 changes: 17 additions & 0 deletions packages/flutter/lib/src/material/text_field.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1290,6 +1290,7 @@ class _TextFieldState extends State<TextField> with RestorationMixin implements
Color? autocorrectionTextRectColor;
Radius? cursorRadius = widget.cursorRadius;
VoidCallback? handleDidGainAccessibilityFocus;
VoidCallback? handleDidLoseAccessibilityFocus;

switch (theme.platform) {
case TargetPlatform.iOS:
Expand Down Expand Up @@ -1320,6 +1321,9 @@ class _TextFieldState extends State<TextField> with RestorationMixin implements
_effectiveFocusNode.requestFocus();
}
};
handleDidLoseAccessibilityFocus = () {
_effectiveFocusNode.unfocus();
};

case TargetPlatform.android:
case TargetPlatform.fuchsia:
Expand All @@ -1337,6 +1341,15 @@ class _TextFieldState extends State<TextField> with RestorationMixin implements
cursorOpacityAnimates ??= false;
cursorColor = _hasError ? _errorColor : widget.cursorColor ?? selectionStyle.cursorColor ?? theme.colorScheme.primary;
selectionColor = selectionStyle.selectionColor ?? theme.colorScheme.primary.withOpacity(0.40);
handleDidGainAccessibilityFocus = () {
// Automatically activate the TextField when it receives accessibility focus.
if (!_effectiveFocusNode.hasFocus && _effectiveFocusNode.canRequestFocus) {
_effectiveFocusNode.requestFocus();
}
};
handleDidLoseAccessibilityFocus = () {
_effectiveFocusNode.unfocus();
};

case TargetPlatform.windows:
forcePressEnabled = false;
Expand All @@ -1351,6 +1364,9 @@ class _TextFieldState extends State<TextField> with RestorationMixin implements
_effectiveFocusNode.requestFocus();
}
};
handleDidLoseAccessibilityFocus = () {
_effectiveFocusNode.unfocus();
};
}

Widget child = RepaintBoundary(
Expand Down Expand Up @@ -1478,6 +1494,7 @@ class _TextFieldState extends State<TextField> with RestorationMixin implements
_requestKeyboard();
},
onDidGainAccessibilityFocus: handleDidGainAccessibilityFocus,
onDidLoseAccessibilityFocus: handleDidLoseAccessibilityFocus,
child: child,
);
},
Expand Down
16 changes: 12 additions & 4 deletions packages/flutter/test/material/search_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,9 @@ void main() {
const Widget flexibleSpace = Text('FlexibleSpace');

TestSemantics buildExpected({ required String routeName }) {
final bool isDesktop = debugDefaultTargetPlatformOverride == TargetPlatform.macOS ||
debugDefaultTargetPlatformOverride == TargetPlatform.windows ||
debugDefaultTargetPlatformOverride == TargetPlatform.linux;
return TestSemantics.root(
children: <TestSemantics>[
TestSemantics(
Expand Down Expand Up @@ -651,9 +654,10 @@ void main() {
debugDefaultTargetPlatformOverride != TargetPlatform.macOS) SemanticsFlag.namesRoute,
],
actions: <SemanticsAction>[
if (debugDefaultTargetPlatformOverride == TargetPlatform.macOS ||
debugDefaultTargetPlatformOverride == TargetPlatform.windows)
if (isDesktop)
SemanticsAction.didGainAccessibilityFocus,
if (isDesktop)
SemanticsAction.didLoseAccessibilityFocus,
SemanticsAction.tap,
SemanticsAction.setSelection,
SemanticsAction.setText,
Expand Down Expand Up @@ -748,6 +752,9 @@ void main() {

group('contributes semantics', () {
TestSemantics buildExpected({ required String routeName }) {
final bool isDesktop = debugDefaultTargetPlatformOverride == TargetPlatform.macOS ||
debugDefaultTargetPlatformOverride == TargetPlatform.windows ||
debugDefaultTargetPlatformOverride == TargetPlatform.linux;
return TestSemantics.root(
children: <TestSemantics>[
TestSemantics(
Expand Down Expand Up @@ -791,9 +798,10 @@ void main() {
debugDefaultTargetPlatformOverride != TargetPlatform.macOS) SemanticsFlag.namesRoute,
],
actions: <SemanticsAction>[
if (debugDefaultTargetPlatformOverride == TargetPlatform.macOS ||
debugDefaultTargetPlatformOverride == TargetPlatform.windows)
if (isDesktop)
SemanticsAction.didGainAccessibilityFocus,
if (isDesktop)
SemanticsAction.didLoseAccessibilityFocus,
SemanticsAction.tap,
SemanticsAction.setSelection,
SemanticsAction.setText,
Expand Down
9 changes: 7 additions & 2 deletions packages/flutter/test/material/text_field_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -655,7 +655,7 @@ void main() {
expect(editableText.cursorOpacityAnimates, false);
});

testWidgets('Activates the text field when receives semantics focus on Mac, Windows', (WidgetTester tester) async {
testWidgets('Activates the text field when receives semantics focus on desktops', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
final SemanticsOwner semanticsOwner = tester.binding.pipelineOwner.semanticsOwner!;
final FocusNode focusNode = FocusNode();
Expand Down Expand Up @@ -686,6 +686,7 @@ void main() {
actions: <SemanticsAction>[
SemanticsAction.tap,
SemanticsAction.didGainAccessibilityFocus,
SemanticsAction.didLoseAccessibilityFocus,
],
textDirection: TextDirection.ltr,
),
Expand All @@ -705,8 +706,12 @@ void main() {
semanticsOwner.performAction(4, SemanticsAction.didGainAccessibilityFocus);
await tester.pumpAndSettle();
expect(focusNode.hasFocus, isTrue);

semanticsOwner.performAction(4, SemanticsAction.didLoseAccessibilityFocus);
await tester.pumpAndSettle();
expect(focusNode.hasFocus, isFalse);
semantics.dispose();
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.macOS, TargetPlatform.windows }));
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.macOS, TargetPlatform.windows, TargetPlatform.linux }));

testWidgets('TextField passes onEditingComplete to EditableText', (WidgetTester tester) async {
void onEditingComplete() { }
Expand Down

0 comments on commit 8057823

Please sign in to comment.