Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion packages/material_ui/lib/src/search_anchor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,12 @@ class _SearchAnchorState extends State<SearchAnchor> {
duration: _kAnchorFadeDuration,
child: IgnorePointer(
ignoring: !widget.enabled,
child: GestureDetector(onTap: _openView, child: widget.builder(context, _searchController)),
child: GestureDetector(
// Avoid providing duplicate semantics actions.
excludeFromSemantics: true,
onTap: _openView,
child: widget.builder(context, _searchController),
),
Comment on lines +609 to +614

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Hardcoding excludeFromSemantics: true on the SearchAnchor's GestureDetector will cause an accessibility regression for custom builders. If a developer provides a custom non-tappable widget (like a simple Text or Icon) in widget.builder, it relies on this GestureDetector to provide the semantic tap action. With excludeFromSemantics: true, the widget will become completely non-interactive for screen readers.

To fix this while still resolving the duplicate semantics issue for SearchBar, we should introduce an excludeFromSemantics property to SearchAnchor (defaulting to false). Then, we can set it to true in the SearchAnchor.bar factory constructor.

        child: GestureDetector(
          excludeFromSemantics: widget.excludeFromSemantics,
          onTap: _openView,
          child: widget.builder(context, _searchController),
        ),

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right, this is something I worry, but is this a common case? @QuncCccccc , if people indeed tend to provide passive component in the builder, we will have turn this into a widget parameter that something like SearchAnchor(automaticallyTapHandler:)

which will base on this parameter to build or not build a gesture recognizer. and user will need to be smart about when to set it to true or false

),
);
}
Expand Down Expand Up @@ -1825,6 +1830,10 @@ class _SearchBarState extends State<SearchBar> {
child: IgnorePointer(
ignoring: !widget.enabled,
child: InkWell(
canRequestFocus: false,
// Avoid providing duplicate semantics actions that the TextField
// already provides.
excludeFromSemantics: true,
onTap: () {
widget.onTap?.call();
if (!_focusNode.hasFocus) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
changelog: |
- Fixes unlabeled tap target semantics in `SearchBar`.
version: patch
43 changes: 43 additions & 0 deletions packages/material_ui/test/search_anchor_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3407,6 +3407,49 @@ void main() {
semantics.dispose();
});

testWidgets('SearchBar meets labeledTapTargetGuideline', (WidgetTester tester) async {
final semantics = SemanticsTester(tester);
await tester.pumpWidget(
MaterialApp(
home: Material(
child: Center(
child: SearchBar(
hintText: 'Search...',
trailing: <Widget>[
IconButton(tooltip: 'Clear', icon: const Icon(Icons.clear), onPressed: () {}),
],
),
),
),
),
);

await expectLater(tester, meetsGuideline(labeledTapTargetGuideline));
semantics.dispose();
});

testWidgets('SearchAnchor.bar meets labeledTapTargetGuideline', (WidgetTester tester) async {
final semantics = SemanticsTester(tester);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: SearchAnchor.bar(
barHintText: 'Search...',
barTrailing: <Widget>[
IconButton(tooltip: 'Clear', icon: const Icon(Icons.clear), onPressed: () {}),
],
suggestionsBuilder: (BuildContext context, SearchController controller) {
return <Widget>[];
},
),
),
),
);

await expectLater(tester, meetsGuideline(labeledTapTargetGuideline));
semantics.dispose();
});

testWidgets('Check SearchBar opacity when disabled', (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
Expand Down
Loading