Skip to content

Commit 6193615

Browse files
authored
Wire up SemanticsAction.focus to the framework (#149374)
Wire up `SemanticsAction.focus` to `Semantics` and `CustomPaint`. Reenable respective tests, and add `focus` tests to them. Contributes to a fix for flutter/flutter#83809
1 parent 8c38366 commit 6193615

File tree

6 files changed

+53
-10
lines changed

6 files changed

+53
-10
lines changed

packages/flutter/lib/src/rendering/custom_paint.dart

+3
Original file line numberDiff line numberDiff line change
@@ -1036,6 +1036,9 @@ class RenderCustomPaint extends RenderProxyBox {
10361036
if (properties.onDidLoseAccessibilityFocus != null) {
10371037
config.onDidLoseAccessibilityFocus = properties.onDidLoseAccessibilityFocus;
10381038
}
1039+
if (properties.onFocus != null) {
1040+
config.onFocus = properties.onFocus;
1041+
}
10391042
if (properties.onDismiss != null) {
10401043
config.onDismiss = properties.onDismiss;
10411044
}

packages/flutter/lib/src/rendering/proxy_box.dart

+7
Original file line numberDiff line numberDiff line change
@@ -4467,6 +4467,9 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
44674467
if (_properties.onDidLoseAccessibilityFocus != null) {
44684468
config.onDidLoseAccessibilityFocus = _performDidLoseAccessibilityFocus;
44694469
}
4470+
if (_properties.onFocus != null) {
4471+
config.onFocus = _performFocus;
4472+
}
44704473
if (_properties.customSemanticsActions != null) {
44714474
config.customSemanticsActions = _properties.customSemanticsActions!;
44724475
}
@@ -4551,6 +4554,10 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
45514554
void _performDidLoseAccessibilityFocus() {
45524555
_properties.onDidLoseAccessibilityFocus?.call();
45534556
}
4557+
4558+
void _performFocus() {
4559+
_properties.onFocus?.call();
4560+
}
45544561
}
45554562

45564563
/// Causes the semantics of all earlier render objects below the same semantic

packages/flutter/lib/src/semantics/semantics.dart

+35
Original file line numberDiff line numberDiff line change
@@ -943,6 +943,7 @@ class SemanticsProperties extends DiagnosticableTree {
943943
this.onSetText,
944944
this.onDidGainAccessibilityFocus,
945945
this.onDidLoseAccessibilityFocus,
946+
this.onFocus,
946947
this.onDismiss,
947948
this.customSemanticsActions,
948949
}) : assert(label == null || attributedLabel == null, 'Only one of label or attributedLabel should be provided'),
@@ -1605,6 +1606,8 @@ class SemanticsProperties extends DiagnosticableTree {
16051606
///
16061607
/// * [onDidLoseAccessibilityFocus], which is invoked when the accessibility
16071608
/// focus is removed from the node.
1609+
/// * [onFocus], which is invoked when the assistive technology requests that
1610+
/// the input focus is gained by a widget.
16081611
/// * [FocusNode], [FocusScope], [FocusManager], which manage the input focus.
16091612
final VoidCallback? onDidGainAccessibilityFocus;
16101613

@@ -1627,6 +1630,30 @@ class SemanticsProperties extends DiagnosticableTree {
16271630
/// * [FocusNode], [FocusScope], [FocusManager], which manage the input focus.
16281631
final VoidCallback? onDidLoseAccessibilityFocus;
16291632

1633+
/// {@template flutter.semantics.SemanticsProperties.onFocus}
1634+
/// The handler for [SemanticsAction.focus].
1635+
///
1636+
/// This handler is invoked when the assistive technology requests that the
1637+
/// focusable widget corresponding to this semantics node gain input focus.
1638+
/// The [FocusNode] that manages the focus of the widget must gain focus. The
1639+
/// widget must begin responding to relevant key events. For example:
1640+
///
1641+
/// * Buttons must respond to tap/click events produced via keyboard shortcuts.
1642+
/// * Text fields must become focused and editable, showing an on-screen
1643+
/// keyboard, if necessary.
1644+
/// * Checkboxes, switches, and radio buttons must become togglable using
1645+
/// keyboard shortcuts.
1646+
///
1647+
/// Focus behavior is specific to the platform and to the assistive technology
1648+
/// used. See the documentation of [SemanticsAction.focus] for more detail.
1649+
///
1650+
/// See also:
1651+
///
1652+
/// * [onDidGainAccessibilityFocus], which is invoked when the node gains
1653+
/// accessibility focus.
1654+
/// {@endtemplate}
1655+
final VoidCallback? onFocus;
1656+
16301657
/// The handler for [SemanticsAction.dismiss].
16311658
///
16321659
/// This is a request to dismiss the currently focused node.
@@ -4064,6 +4091,14 @@ class SemanticsConfiguration {
40644091
_onDidLoseAccessibilityFocus = value;
40654092
}
40664093

4094+
/// {@macro flutter.semantics.SemanticsProperties.onFocus}
4095+
VoidCallback? get onFocus => _onFocus;
4096+
VoidCallback? _onFocus;
4097+
set onFocus(VoidCallback? value) {
4098+
_addArgumentlessAction(SemanticsAction.focus, value!);
4099+
_onFocus = value;
4100+
}
4101+
40674102
/// A delegate that decides how to handle [SemanticsConfiguration]s produced
40684103
/// in the widget subtree.
40694104
///

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

+2
Original file line numberDiff line numberDiff line change
@@ -7151,6 +7151,7 @@ class Semantics extends SingleChildRenderObjectWidget {
71517151
SetTextHandler? onSetText,
71527152
VoidCallback? onDidGainAccessibilityFocus,
71537153
VoidCallback? onDidLoseAccessibilityFocus,
7154+
VoidCallback? onFocus,
71547155
Map<CustomSemanticsAction, VoidCallback>? customSemanticsActions,
71557156
}) : this.fromProperties(
71567157
key: key,
@@ -7215,6 +7216,7 @@ class Semantics extends SingleChildRenderObjectWidget {
72157216
onMoveCursorBackwardByCharacter: onMoveCursorBackwardByCharacter,
72167217
onDidGainAccessibilityFocus: onDidGainAccessibilityFocus,
72177218
onDidLoseAccessibilityFocus: onDidLoseAccessibilityFocus,
7219+
onFocus: onFocus,
72187220
onDismiss: onDismiss,
72197221
onSetSelection: onSetSelection,
72207222
onSetText: onSetText,

packages/flutter/test/widgets/custom_painter_test.dart

+3-5
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@ void _defineTests() {
342342
onSetText: (String text) => performedActions.add(SemanticsAction.setText),
343343
onDidGainAccessibilityFocus: () => performedActions.add(SemanticsAction.didGainAccessibilityFocus),
344344
onDidLoseAccessibilityFocus: () => performedActions.add(SemanticsAction.didLoseAccessibilityFocus),
345+
onFocus: () => performedActions.add(SemanticsAction.focus),
345346
),
346347
),
347348
),
@@ -400,19 +401,16 @@ void _defineTests() {
400401
case SemanticsAction.scrollUp:
401402
case SemanticsAction.showOnScreen:
402403
case SemanticsAction.tap:
404+
case SemanticsAction.focus:
403405
semanticsOwner.performAction(expectedId, action);
404-
// TODO(yjbanov): temporary adding default case until https://github.com/flutter/engine/pull/53094 rolls in (see https://github.com/flutter/flutter/issues/83809)
405-
// ignore: no_default_cases
406-
default:
407-
throw UnimplementedError();
408406
}
409407
expect(performedActions.length, expectedLength);
410408
expect(performedActions.last, action);
411409
expectedLength += 1;
412410
}
413411

414412
semantics.dispose();
415-
}, skip: true); // TODO(yjbanov): temporary skip until https://github.com/flutter/engine/pull/53094 rolls in (see https://github.com/flutter/flutter/issues/83809)
413+
});
416414

417415
testWidgets('Supports all flags', (WidgetTester tester) async {
418416
final SemanticsTester semantics = SemanticsTester(tester);

packages/flutter/test/widgets/semantics_test.dart

+3-5
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,7 @@ void main() {
516516
onSetText: (String _) => performedActions.add(SemanticsAction.setText),
517517
onDidGainAccessibilityFocus: () => performedActions.add(SemanticsAction.didGainAccessibilityFocus),
518518
onDidLoseAccessibilityFocus: () => performedActions.add(SemanticsAction.didLoseAccessibilityFocus),
519+
onFocus: () => performedActions.add(SemanticsAction.focus),
519520
),
520521
);
521522

@@ -570,19 +571,16 @@ void main() {
570571
case SemanticsAction.scrollUp:
571572
case SemanticsAction.showOnScreen:
572573
case SemanticsAction.tap:
574+
case SemanticsAction.focus:
573575
semanticsOwner.performAction(expectedId, action);
574-
// TODO(yjbanov): temporary adding default case until https://github.com/flutter/engine/pull/53094 rolls in (see https://github.com/flutter/flutter/issues/83809)
575-
// ignore: no_default_cases
576-
default:
577-
throw UnimplementedError();
578576
}
579577
expect(performedActions.length, expectedLength);
580578
expect(performedActions.last, action);
581579
expectedLength += 1;
582580
}
583581

584582
semantics.dispose();
585-
}, skip: true); // TODO(yjbanov): temporary skip until https://github.com/flutter/engine/pull/53094 rolls in (see https://github.com/flutter/flutter/issues/83809)
583+
});
586584

587585
testWidgets('Semantics widget supports all flags', (WidgetTester tester) async {
588586
final SemanticsTester semantics = SemanticsTester(tester);

0 commit comments

Comments
 (0)