Unverified Commit 9d70c85f authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

a11y improvements for textfield (#40390)

parent 2bd7f9ff
...@@ -827,14 +827,18 @@ class _RenderDecoration extends RenderBox { ...@@ -827,14 +827,18 @@ class _RenderDecoration extends RenderBox {
visitor(prefix); visitor(prefix);
if (prefixIcon != null) if (prefixIcon != null)
visitor(prefixIcon); visitor(prefixIcon);
if (isFocused && hint != null) {
// Bypass opacity to always read hint when focused. This prevents the if (label != null) {
// label from changing when text is entered.
final RenderProxyBox typedHint = hint;
visitor(typedHint.child);
} else if (!isFocused && label != null) {
visitor(label); visitor(label);
} }
if (hint != null) {
if (isFocused) {
visitor(hint);
} else if (label == null) {
visitor(hint);
}
}
if (input != null) if (input != null)
visitor(input); visitor(input);
if (suffixIcon != null) if (suffixIcon != null)
...@@ -2060,6 +2064,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -2060,6 +2064,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
opacity: (isEmpty && !_hasInlineLabel) ? 1.0 : 0.0, opacity: (isEmpty && !_hasInlineLabel) ? 1.0 : 0.0,
duration: _kTransitionDuration, duration: _kTransitionDuration,
curve: _kTransitionCurve, curve: _kTransitionCurve,
alwaysIncludeSemantics: true,
child: Text( child: Text(
decoration.hintText, decoration.hintText,
style: hintStyle, style: hintStyle,
......
...@@ -1274,6 +1274,7 @@ class AnimatedOpacity extends ImplicitlyAnimatedWidget { ...@@ -1274,6 +1274,7 @@ class AnimatedOpacity extends ImplicitlyAnimatedWidget {
@required this.opacity, @required this.opacity,
Curve curve = Curves.linear, Curve curve = Curves.linear,
@required Duration duration, @required Duration duration,
this.alwaysIncludeSemantics = false,
}) : assert(opacity != null && opacity >= 0.0 && opacity <= 1.0), }) : assert(opacity != null && opacity >= 0.0 && opacity <= 1.0),
super(key: key, curve: curve, duration: duration); super(key: key, curve: curve, duration: duration);
...@@ -1290,6 +1291,16 @@ class AnimatedOpacity extends ImplicitlyAnimatedWidget { ...@@ -1290,6 +1291,16 @@ class AnimatedOpacity extends ImplicitlyAnimatedWidget {
/// The opacity must not be null. /// The opacity must not be null.
final double opacity; final double opacity;
/// Whether the semantic information of the children is always included.
///
/// Defaults to false.
///
/// When true, regardless of the opacity settings the child semantic
/// information is exposed as if the widget were fully visible. This is
/// useful in cases where labels may be hidden during animations that
/// would otherwise contribute relevant semantics.
final bool alwaysIncludeSemantics;
@override @override
_AnimatedOpacityState createState() => _AnimatedOpacityState(); _AnimatedOpacityState createState() => _AnimatedOpacityState();
...@@ -1319,6 +1330,7 @@ class _AnimatedOpacityState extends ImplicitlyAnimatedWidgetState<AnimatedOpacit ...@@ -1319,6 +1330,7 @@ class _AnimatedOpacityState extends ImplicitlyAnimatedWidgetState<AnimatedOpacit
return FadeTransition( return FadeTransition(
opacity: _opacityAnimation, opacity: _opacityAnimation,
child: widget.child, child: widget.child,
alwaysIncludeSemantics: widget.alwaysIncludeSemantics,
); );
} }
} }
......
...@@ -4212,6 +4212,100 @@ void main() { ...@@ -4212,6 +4212,100 @@ void main() {
expect(tester.getBottomLeft(find.byKey(keyB)).dy, rowBottomY); expect(tester.getBottomLeft(find.byKey(keyB)).dy, rowBottomY);
}); });
testWidgets('TextField semantics include label when unfocused and label/hint when focused', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
final TextEditingController controller = TextEditingController(text: 'value');
final Key key = UniqueKey();
await tester.pumpWidget(
overlay(
child: TextField(
key: key,
controller: controller,
decoration: const InputDecoration(
hintText: 'hint',
labelText: 'label',
),
),
),
);
final SemanticsNode node = tester.getSemantics(find.byKey(key));
expect(node.label, 'label');
expect(node.value, 'value');
// Focus text field.
await tester.tap(find.byKey(key));
await tester.pump();
expect(node.label, 'label\nhint');
expect(node.value, 'value');
semantics.dispose();
});
testWidgets('TextField semantics always include label when no hint is given', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
final TextEditingController controller = TextEditingController(text: 'value');
final Key key = UniqueKey();
await tester.pumpWidget(
overlay(
child: TextField(
key: key,
controller: controller,
decoration: const InputDecoration(
labelText: 'label',
),
),
),
);
final SemanticsNode node = tester.getSemantics(find.byKey(key));
expect(node.label, 'label');
expect(node.value, 'value');
// Focus text field.
await tester.tap(find.byKey(key));
await tester.pump();
expect(node.label, 'label');
expect(node.value, 'value');
semantics.dispose();
});
testWidgets('TextField semantics always include hint when no label is given', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
final TextEditingController controller = TextEditingController(text: 'value');
final Key key = UniqueKey();
await tester.pumpWidget(
overlay(
child: TextField(
key: key,
controller: controller,
decoration: const InputDecoration(
hintText: 'hint',
),
),
),
);
final SemanticsNode node = tester.getSemantics(find.byKey(key));
expect(node.label, 'hint');
expect(node.value, 'value');
// Focus text field.
await tester.tap(find.byKey(key));
await tester.pump();
expect(node.label, 'hint');
expect(node.value, 'value');
semantics.dispose();
});
testWidgets('TextField semantics', (WidgetTester tester) async { testWidgets('TextField semantics', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester); final SemanticsTester semantics = SemanticsTester(tester);
final TextEditingController controller = TextEditingController(); final TextEditingController controller = TextEditingController();
...@@ -4769,7 +4863,7 @@ void main() { ...@@ -4769,7 +4863,7 @@ void main() {
expect(semantics, hasSemantics(TestSemantics.root( expect(semantics, hasSemantics(TestSemantics.root(
children: <TestSemantics>[ children: <TestSemantics>[
TestSemantics.rootChild( TestSemantics.rootChild(
label: 'hint', label: 'label\nhint',
id: 1, id: 1,
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
textSelection: const TextSelection(baseOffset: 0, extentOffset: 0), textSelection: const TextSelection(baseOffset: 0, extentOffset: 0),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment