Skip to content

Commit dd700e6

Browse files
authored
Request focus if SemanticsAction.focus is sent to a focusable widget (#142942)
## Description This causes the `Focus` widget to request focus on its focus node if the accessibility system (screen reader) focuses a widget via the `SemanticsAction.focus` action. ## Related Issues - flutter/flutter#83809 ## Tests - Added a test to make sure that focus is requested when `SemanticsAction.focus` is sent by the engine.
1 parent 4d21d2d commit dd700e6

File tree

58 files changed

+281
-70
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+281
-70
lines changed

dev/integration_tests/flutter_gallery/test/demo/material/chip_demo_test.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ void main() {
2020
isEnabled: true,
2121
isFocusable: true,
2222
hasTapAction: true,
23+
hasFocusAction: true,
2324
label: 'Update border shape',
2425
));
2526

@@ -29,6 +30,7 @@ void main() {
2930
isEnabled: true,
3031
isFocusable: true,
3132
hasTapAction: true,
33+
hasFocusAction: true,
3234
label: 'Reset chips',
3335
));
3436

packages/flutter/lib/src/widgets/focus_scope.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,14 @@ class _FocusState extends State<Focus> {
671671
Widget child = widget.child;
672672
if (widget.includeSemantics) {
673673
child = Semantics(
674+
// Automatically request the focus for a focusable widget when it
675+
// receives an input focus action from the semantics. Nothing is needed
676+
// for losing the focus because if focus is lost, that means another
677+
// node will gain focus and take focus from this widget.
678+
onFocus:
679+
_couldRequestFocus
680+
? focusNode.requestFocus
681+
: null,
674682
focusable: _couldRequestFocus,
675683
focused: _hadPrimaryFocus,
676684
child: widget.child,

packages/flutter/test/cupertino/checkbox_test.dart

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ void main() {
3434
hasEnabledState: true,
3535
isEnabled: true,
3636
hasTapAction: true,
37+
hasFocusAction: true,
3738
isFocusable: true,
3839
));
3940

@@ -54,6 +55,7 @@ void main() {
5455
isChecked: true,
5556
isEnabled: true,
5657
hasTapAction: true,
58+
hasFocusAction: true,
5759
isFocusable: true,
5860
));
5961

@@ -73,6 +75,7 @@ void main() {
7375
hasEnabledState: true,
7476
// isFocusable is delayed by 1 frame.
7577
isFocusable: true,
78+
hasFocusAction: true,
7679
));
7780

7881
await tester.pump();
@@ -178,6 +181,7 @@ void main() {
178181
hasEnabledState: true,
179182
isEnabled: true,
180183
hasTapAction: true,
184+
hasFocusAction: true,
181185
isFocusable: true,
182186
));
183187
handle.dispose();
@@ -247,7 +251,7 @@ void main() {
247251
SemanticsFlag.isFocusable,
248252
SemanticsFlag.isCheckStateMixed,
249253
],
250-
actions: <SemanticsAction>[SemanticsAction.tap],
254+
actions: <SemanticsAction>[SemanticsAction.tap, SemanticsAction.focus],
251255
), hasLength(1));
252256

253257
await tester.pumpWidget(
@@ -268,7 +272,7 @@ void main() {
268272
SemanticsFlag.isChecked,
269273
SemanticsFlag.isFocusable,
270274
],
271-
actions: <SemanticsAction>[SemanticsAction.tap],
275+
actions: <SemanticsAction>[SemanticsAction.tap, SemanticsAction.focus],
272276
), hasLength(1));
273277

274278
await tester.pumpWidget(
@@ -288,7 +292,7 @@ void main() {
288292
SemanticsFlag.isEnabled,
289293
SemanticsFlag.isFocusable,
290294
],
291-
actions: <SemanticsAction>[SemanticsAction.tap],
295+
actions: <SemanticsAction>[SemanticsAction.tap, SemanticsAction.focus],
292296
), hasLength(1));
293297

294298
semantics.dispose();

packages/flutter/test/cupertino/radio_test.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ void main() {
147147
],
148148
actions: <SemanticsAction>[
149149
SemanticsAction.tap,
150+
SemanticsAction.focus,
150151
],
151152
),
152153
);
@@ -171,6 +172,7 @@ void main() {
171172
hasEnabledState: true,
172173
isEnabled: true,
173174
hasTapAction: true,
175+
hasFocusAction: true,
174176
isFocusable: true,
175177
isInMutuallyExclusiveGroup: true,
176178
));
@@ -190,6 +192,7 @@ void main() {
190192
hasEnabledState: true,
191193
isEnabled: true,
192194
hasTapAction: true,
195+
hasFocusAction: true,
193196
isFocusable: true,
194197
isInMutuallyExclusiveGroup: true,
195198
isChecked: true,
@@ -210,6 +213,7 @@ void main() {
210213
hasEnabledState: true,
211214
isFocusable: true,
212215
isInMutuallyExclusiveGroup: true,
216+
hasFocusAction: true,
213217
));
214218

215219
await tester.pump();

packages/flutter/test/cupertino/route_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1945,7 +1945,7 @@ void main() {
19451945
await tester.pumpAndSettle();
19461946

19471947
expect(semantics, isNot(includesNodeWith(
1948-
actions: <SemanticsAction>[SemanticsAction.tap],
1948+
actions: <SemanticsAction>[SemanticsAction.tap, SemanticsAction.focus],
19491949
label: 'Dismiss',
19501950
)));
19511951
debugDefaultTargetPlatformOverride = null;

packages/flutter/test/cupertino/text_field_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2621,7 +2621,7 @@ void main() {
26212621
),
26222622
);
26232623

2624-
expect(semantics, isNot(includesNodeWith(actions: <SemanticsAction>[SemanticsAction.tap])));
2624+
expect(semantics, isNot(includesNodeWith(actions: <SemanticsAction>[SemanticsAction.tap, SemanticsAction.focus])));
26252625

26262626
semantics.dispose();
26272627
});

packages/flutter/test/material/back_button_test.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ void main() {
215215
hasEnabledState: true,
216216
isEnabled: true,
217217
hasTapAction: true,
218+
hasFocusAction: true,
218219
isFocusable: true,
219220
));
220221
handle.dispose();
@@ -258,6 +259,7 @@ void main() {
258259
hasEnabledState: true,
259260
isEnabled: true,
260261
hasTapAction: true,
262+
hasFocusAction: true,
261263
isFocusable: true,
262264
));
263265
handle.dispose();

packages/flutter/test/material/bottom_navigation_bar_test.dart

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2111,6 +2111,7 @@ void main() {
21112111
isFocusable: true,
21122112
isSelected: true,
21132113
hasTapAction: true,
2114+
hasFocusAction: true,
21142115
),
21152116
);
21162117
expect(
@@ -2120,6 +2121,7 @@ void main() {
21202121
textDirection: TextDirection.ltr,
21212122
isFocusable: true,
21222123
hasTapAction: true,
2124+
hasFocusAction: true,
21232125
),
21242126
);
21252127
expect(
@@ -2129,6 +2131,7 @@ void main() {
21292131
textDirection: TextDirection.ltr,
21302132
isFocusable: true,
21312133
hasTapAction: true,
2134+
hasFocusAction: true,
21322135
),
21332136
);
21342137
});
@@ -2165,6 +2168,7 @@ void main() {
21652168
isFocusable: true,
21662169
isSelected: true,
21672170
hasTapAction: true,
2171+
hasFocusAction: true,
21682172
),
21692173
);
21702174
expect(
@@ -2174,6 +2178,7 @@ void main() {
21742178
textDirection: TextDirection.ltr,
21752179
isFocusable: true,
21762180
hasTapAction: true,
2181+
hasFocusAction: true,
21772182
),
21782183
);
21792184
expect(
@@ -2183,6 +2188,7 @@ void main() {
21832188
textDirection: TextDirection.ltr,
21842189
isFocusable: true,
21852190
hasTapAction: true,
2191+
hasFocusAction: true,
21862192
),
21872193
);
21882194
});
@@ -2515,6 +2521,7 @@ void main() {
25152521
isFocusable: true,
25162522
isSelected: true,
25172523
hasTapAction: true,
2524+
hasFocusAction: true,
25182525
),
25192526
);
25202527
expect(
@@ -2524,6 +2531,7 @@ void main() {
25242531
textDirection: TextDirection.ltr,
25252532
isFocusable: true,
25262533
hasTapAction: true,
2534+
hasFocusAction: true,
25272535
),
25282536
);
25292537
});
@@ -2558,6 +2566,7 @@ void main() {
25582566
isFocusable: true,
25592567
isSelected: true,
25602568
hasTapAction: true,
2569+
hasFocusAction: true,
25612570
),
25622571
);
25632572
expect(
@@ -2567,6 +2576,7 @@ void main() {
25672576
textDirection: TextDirection.ltr,
25682577
isFocusable: true,
25692578
hasTapAction: true,
2579+
hasFocusAction: true,
25702580
),
25712581
);
25722582
});
@@ -2747,13 +2757,13 @@ void main() {
27472757
SemanticsFlag.isSelected,
27482758
SemanticsFlag.isFocusable,
27492759
],
2750-
actions: <SemanticsAction>[SemanticsAction.tap],
2760+
actions: <SemanticsAction>[SemanticsAction.tap, SemanticsAction.focus],
27512761
label: 'A\nTab 1 of 2',
27522762
textDirection: TextDirection.ltr,
27532763
),
27542764
TestSemantics(
27552765
flags: <SemanticsFlag>[SemanticsFlag.isFocusable],
2756-
actions: <SemanticsAction>[SemanticsAction.tap],
2766+
actions: <SemanticsAction>[SemanticsAction.tap, SemanticsAction.focus],
27572767
label: 'B\nTab 2 of 2',
27582768
textDirection: TextDirection.ltr,
27592769
),

0 commit comments

Comments
 (0)