Skip to content

Commit cb44b5a

Browse files
hannah-hyjchunhtai
andauthored
[A11y] TextField prefix icon and suffix icon create a sibling node' (flutter#173312)
prefix/suffix icon should have their own merge group. Noted a textfield can have suffix and suffix icon the same time so they should be different merge group. Co-authored-by: chunhtai <47866232+chunhtai@users.noreply.github.com>
1 parent 052b903 commit cb44b5a

File tree

2 files changed

+74
-16
lines changed

2 files changed

+74
-16
lines changed

packages/flutter/lib/src/material/input_decorator.dart

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ library;
1313
import 'dart:math' as math;
1414
import 'dart:ui' show lerpDouble;
1515

16+
import 'package:collection/collection.dart';
1617
import 'package:flutter/foundation.dart';
1718
import 'package:flutter/rendering.dart';
1819
import 'package:flutter/widgets.dart';
@@ -1685,25 +1686,28 @@ class _RenderDecoration extends RenderBox
16851686
) {
16861687
final ChildSemanticsConfigurationsResultBuilder builder =
16871688
ChildSemanticsConfigurationsResultBuilder();
1688-
List<SemanticsConfiguration>? prefixMergeGroup;
1689-
List<SemanticsConfiguration>? suffixMergeGroup;
1689+
1690+
final Map<SemanticsTag, List<SemanticsConfiguration>> mergeGroups =
1691+
<SemanticsTag, List<SemanticsConfiguration>>{};
1692+
final Set<SemanticsTag> tags = <SemanticsTag>{
1693+
_InputDecoratorState._kPrefixSemanticsTag,
1694+
_InputDecoratorState._kPrefixIconSemanticsTag,
1695+
_InputDecoratorState._kSuffixSemanticsTag,
1696+
_InputDecoratorState._kSuffixIconSemanticsTag,
1697+
};
1698+
16901699
for (final SemanticsConfiguration childConfig in childConfigs) {
1691-
if (childConfig.tagsChildrenWith(_InputDecoratorState._kPrefixSemanticsTag)) {
1692-
prefixMergeGroup ??= <SemanticsConfiguration>[];
1693-
prefixMergeGroup.add(childConfig);
1694-
} else if (childConfig.tagsChildrenWith(_InputDecoratorState._kSuffixSemanticsTag)) {
1695-
suffixMergeGroup ??= <SemanticsConfiguration>[];
1696-
suffixMergeGroup.add(childConfig);
1700+
final SemanticsTag? tag = tags.firstWhereOrNull(
1701+
(SemanticsTag tag) => childConfig.tagsChildrenWith(tag),
1702+
);
1703+
if (tag != null) {
1704+
mergeGroups.putIfAbsent(tag, () => <SemanticsConfiguration>[]).add(childConfig);
16971705
} else {
16981706
builder.markAsMergeUp(childConfig);
16991707
}
17001708
}
1701-
if (prefixMergeGroup != null) {
1702-
builder.markAsSiblingMergeGroup(prefixMergeGroup);
1703-
}
1704-
if (suffixMergeGroup != null) {
1705-
builder.markAsSiblingMergeGroup(suffixMergeGroup);
1706-
}
1709+
1710+
mergeGroups.values.forEach(builder.markAsSiblingMergeGroup);
17071711
return builder.build();
17081712
}
17091713

@@ -1985,7 +1989,13 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
19851989
name: hashCode.toString(),
19861990
);
19871991
static const SemanticsTag _kPrefixSemanticsTag = SemanticsTag('_InputDecoratorState.prefix');
1992+
static const SemanticsTag _kPrefixIconSemanticsTag = SemanticsTag(
1993+
'_InputDecoratorState.prefixIcon',
1994+
);
19881995
static const SemanticsTag _kSuffixSemanticsTag = SemanticsTag('_InputDecoratorState.suffix');
1996+
static const SemanticsTag _kSuffixIconSemanticsTag = SemanticsTag(
1997+
'_InputDecoratorState.suffixIcon',
1998+
);
19891999

19902000
@override
19912001
void initState() {
@@ -2466,7 +2476,10 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
24662476
iconSize: WidgetStatePropertyAll<double>(iconSize),
24672477
).merge(iconButtonTheme.style),
24682478
),
2469-
child: Semantics(child: decoration.prefixIcon),
2479+
child: Semantics(
2480+
tagForChildren: _kPrefixIconSemanticsTag,
2481+
child: decoration.prefixIcon,
2482+
),
24702483
),
24712484
),
24722485
),
@@ -2503,7 +2516,10 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
25032516
iconSize: WidgetStatePropertyAll<double>(iconSize),
25042517
).merge(iconButtonTheme.style),
25052518
),
2506-
child: Semantics(child: decoration.suffixIcon),
2519+
child: Semantics(
2520+
tagForChildren: _kSuffixIconSemanticsTag,
2521+
child: decoration.suffixIcon,
2522+
),
25072523
),
25082524
),
25092525
),

packages/flutter/test/material/text_field_test.dart

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5675,6 +5675,48 @@ void main() {
56755675
semantics.dispose();
56765676
});
56775677

5678+
testWidgets('TextField prefix icon and suffix icon create a sibling node', (
5679+
WidgetTester tester,
5680+
) async {
5681+
final SemanticsTester semantics = SemanticsTester(tester);
5682+
await tester.pumpWidget(
5683+
overlay(
5684+
child: TextField(
5685+
controller: _textEditingController(text: 'some text'),
5686+
decoration: const InputDecoration(prefixIcon: Text('Prefix'), suffixIcon: Text('Suffix')),
5687+
),
5688+
),
5689+
);
5690+
5691+
expect(
5692+
semantics,
5693+
hasSemantics(
5694+
TestSemantics.root(
5695+
children: <TestSemantics>[
5696+
TestSemantics.rootChild(
5697+
id: 1,
5698+
textDirection: TextDirection.ltr,
5699+
value: 'some text',
5700+
actions: <SemanticsAction>[SemanticsAction.tap, SemanticsAction.focus],
5701+
inputType: ui.SemanticsInputType.text,
5702+
currentValueLength: 9,
5703+
flags: <SemanticsFlag>[
5704+
SemanticsFlag.isTextField,
5705+
SemanticsFlag.hasEnabledState,
5706+
SemanticsFlag.isEnabled,
5707+
],
5708+
),
5709+
TestSemantics.rootChild(id: 2, textDirection: TextDirection.ltr, label: 'Prefix'),
5710+
TestSemantics.rootChild(id: 3, textDirection: TextDirection.ltr, label: 'Suffix'),
5711+
],
5712+
),
5713+
ignoreTransform: true,
5714+
ignoreRect: true,
5715+
),
5716+
);
5717+
semantics.dispose();
5718+
});
5719+
56785720
testWidgets('TextField with specified suffixStyle', (WidgetTester tester) async {
56795721
final TextStyle suffixStyle = TextStyle(color: Colors.pink[500], fontSize: 10.0);
56805722

0 commit comments

Comments
 (0)