Skip to content

Commit bf7c270

Browse files
author
Jonah Williams
authored
Make helper and error text separate widgets, make error and counter live region (#21752)
1 parent f8c50ea commit bf7c270

File tree

2 files changed

+95
-24
lines changed

2 files changed

+95
-24
lines changed

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

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -320,32 +320,39 @@ class _HelperErrorState extends State<_HelperError> with SingleTickerProviderSta
320320

321321
Widget _buildHelper() {
322322
assert(widget.helperText != null);
323-
return Opacity(
324-
opacity: 1.0 - _controller.value,
325-
child: Text(
326-
widget.helperText,
327-
style: widget.helperStyle,
328-
textAlign: widget.textAlign,
329-
overflow: TextOverflow.ellipsis,
323+
return Semantics(
324+
container: true,
325+
child: Opacity(
326+
opacity: 1.0 - _controller.value,
327+
child: Text(
328+
widget.helperText,
329+
style: widget.helperStyle,
330+
textAlign: widget.textAlign,
331+
overflow: TextOverflow.ellipsis,
332+
),
330333
),
331334
);
332335
}
333336

334337
Widget _buildError() {
335338
assert(widget.errorText != null);
336-
return Opacity(
337-
opacity: _controller.value,
338-
child: FractionalTranslation(
339-
translation: Tween<Offset>(
340-
begin: const Offset(0.0, -0.25),
341-
end: const Offset(0.0, 0.0),
342-
).evaluate(_controller.view),
343-
child: Text(
344-
widget.errorText,
345-
style: widget.errorStyle,
346-
textAlign: widget.textAlign,
347-
overflow: TextOverflow.ellipsis,
348-
maxLines: widget.errorMaxLines,
339+
return Semantics(
340+
container: true,
341+
liveRegion: true,
342+
child: Opacity(
343+
opacity: _controller.value,
344+
child: FractionalTranslation(
345+
translation: Tween<Offset>(
346+
begin: const Offset(0.0, -0.25),
347+
end: const Offset(0.0, 0.0),
348+
).evaluate(_controller.view),
349+
child: Text(
350+
widget.errorText,
351+
style: widget.errorStyle,
352+
textAlign: widget.textAlign,
353+
overflow: TextOverflow.ellipsis,
354+
maxLines: widget.errorMaxLines,
355+
),
349356
),
350357
),
351358
);
@@ -1815,6 +1822,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
18151822
final Widget counter = decoration.counterText == null ? null :
18161823
Semantics(
18171824
container: true,
1825+
liveRegion: isFocused,
18181826
child: Text(
18191827
decoration.counterText,
18201828
style: _getHelperStyle(themeData).merge(decoration.counterStyle),

packages/flutter/test/material/text_field_test.dart

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2843,7 +2843,7 @@ void main() {
28432843
expect(semantics, hasSemantics(TestSemantics.root(
28442844
children: <TestSemantics>[
28452845
TestSemantics.rootChild(
2846-
label: 'label\nhelper',
2846+
label: 'label',
28472847
id: 1,
28482848
textDirection: TextDirection.ltr,
28492849
actions: <SemanticsAction>[
@@ -2855,6 +2855,11 @@ void main() {
28552855
children: <TestSemantics>[
28562856
TestSemantics(
28572857
id: 2,
2858+
label: 'helper',
2859+
textDirection: TextDirection.ltr,
2860+
),
2861+
TestSemantics(
2862+
id: 3,
28582863
label: '10 characters remaining',
28592864
textDirection: TextDirection.ltr,
28602865
),
@@ -2869,7 +2874,7 @@ void main() {
28692874
expect(semantics, hasSemantics(TestSemantics.root(
28702875
children: <TestSemantics>[
28712876
TestSemantics.rootChild(
2872-
label: 'hint\nhelper',
2877+
label: 'hint',
28732878
id: 1,
28742879
textDirection: TextDirection.ltr,
28752880
textSelection: const TextSelection(baseOffset: 0, extentOffset: 0),
@@ -2885,7 +2890,15 @@ void main() {
28852890
children: <TestSemantics>[
28862891
TestSemantics(
28872892
id: 2,
2893+
label: 'helper',
2894+
textDirection: TextDirection.ltr,
2895+
),
2896+
TestSemantics(
2897+
id: 3,
28882898
label: '10 characters remaining',
2899+
flags: <SemanticsFlag>[
2900+
SemanticsFlag.isLiveRegion,
2901+
],
28892902
textDirection: TextDirection.ltr,
28902903
),
28912904
],
@@ -2922,8 +2935,7 @@ void main() {
29222935
expect(semantics, hasSemantics(TestSemantics.root(
29232936
children: <TestSemantics>[
29242937
TestSemantics.rootChild(
2925-
label: 'label\nhelper',
2926-
id: 1,
2938+
label: 'label',
29272939
textDirection: TextDirection.ltr,
29282940
actions: <SemanticsAction>[
29292941
SemanticsAction.tap,
@@ -2932,6 +2944,10 @@ void main() {
29322944
SemanticsFlag.isTextField,
29332945
],
29342946
children: <TestSemantics>[
2947+
TestSemantics(
2948+
label: 'helper',
2949+
textDirection: TextDirection.ltr,
2950+
),
29352951
TestSemantics(
29362952
label: '0 out of 10',
29372953
textDirection: TextDirection.ltr,
@@ -2944,6 +2960,52 @@ void main() {
29442960
semantics.dispose();
29452961
});
29462962

2963+
testWidgets('InputDecoration errorText semantics', (WidgetTester tester) async {
2964+
final SemanticsTester semantics = SemanticsTester(tester);
2965+
final TextEditingController controller = TextEditingController();
2966+
final Key key = UniqueKey();
2967+
2968+
await tester.pumpWidget(
2969+
overlay(
2970+
child: TextField(
2971+
key: key,
2972+
controller: controller,
2973+
decoration: const InputDecoration(
2974+
labelText: 'label',
2975+
hintText: 'hint',
2976+
errorText: 'oh no!',
2977+
),
2978+
),
2979+
),
2980+
);
2981+
2982+
expect(semantics, hasSemantics(TestSemantics.root(
2983+
children: <TestSemantics>[
2984+
TestSemantics.rootChild(
2985+
label: 'label',
2986+
textDirection: TextDirection.ltr,
2987+
actions: <SemanticsAction>[
2988+
SemanticsAction.tap,
2989+
],
2990+
flags: <SemanticsFlag>[
2991+
SemanticsFlag.isTextField,
2992+
],
2993+
children: <TestSemantics>[
2994+
TestSemantics(
2995+
label: 'oh no!',
2996+
flags: <SemanticsFlag>[
2997+
SemanticsFlag.isLiveRegion,
2998+
],
2999+
textDirection: TextDirection.ltr,
3000+
),
3001+
],
3002+
),
3003+
],
3004+
), ignoreTransform: true, ignoreRect: true, ignoreId: true));
3005+
3006+
semantics.dispose();
3007+
});
3008+
29473009
testWidgets('floating label does not overlap with value at large textScaleFactors', (WidgetTester tester) async {
29483010
final TextEditingController controller = TextEditingController(text: 'Just some text');
29493011
await tester.pumpWidget(
@@ -2964,6 +3026,7 @@ void main() {
29643026
),
29653027
),
29663028
);
3029+
29673030
await tester.tap(find.byType(TextField));
29683031
final Rect labelRect = tester.getRect(find.text('Label'));
29693032
final Rect fieldRect = tester.getRect(find.text('Just some text'));

0 commit comments

Comments
 (0)