Skip to content

Commit

Permalink
TextField - allow to customize cursor color in error state (#136121)
Browse files Browse the repository at this point in the history
The color of the TextField's cursor in error state is the same as the error text color by default. However we should be allowed to customize it

Fixes #135580
  • Loading branch information
lirantzairi committed Oct 24, 2023
1 parent c15ff68 commit 7cd6fd4
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 19 deletions.
11 changes: 10 additions & 1 deletion packages/flutter/lib/src/material/text_field.dart
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ class TextField extends StatefulWidget {
this.cursorRadius,
this.cursorOpacityAnimates,
this.cursorColor,
this.cursorErrorColor,
this.selectionHeightStyle = ui.BoxHeightStyle.tight,
this.selectionWidthStyle = ui.BoxWidthStyle.tight,
this.keyboardAppearance,
Expand Down Expand Up @@ -595,6 +596,13 @@ class TextField extends StatefulWidget {
/// the value of [ColorScheme.primary] of [ThemeData.colorScheme].
final Color? cursorColor;

/// The color of the cursor when the [InputDecorator] is showing an error.
///
/// If this is null it will default to [TextStyle.color] of
/// [InputDecoration.errorStyle]. If that is null, it will use
/// [ColorScheme.error] of [ThemeData.colorScheme].
final Color? cursorErrorColor;

/// Controls how tall the selection highlight boxes are computed to be.
///
/// See [ui.BoxHeightStyle] for details on available styles.
Expand Down Expand Up @@ -893,6 +901,7 @@ class TextField extends StatefulWidget {
properties.add(DiagnosticsProperty<Radius>('cursorRadius', cursorRadius, defaultValue: null));
properties.add(DiagnosticsProperty<bool>('cursorOpacityAnimates', cursorOpacityAnimates, defaultValue: null));
properties.add(ColorProperty('cursorColor', cursorColor, defaultValue: null));
properties.add(ColorProperty('cursorErrorColor', cursorErrorColor, defaultValue: null));
properties.add(DiagnosticsProperty<Brightness>('keyboardAppearance', keyboardAppearance, defaultValue: null));
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('scrollPadding', scrollPadding, defaultValue: const EdgeInsets.all(20.0)));
properties.add(FlagProperty('selectionEnabled', value: selectionEnabled, defaultValue: true, ifFalse: 'selection disabled'));
Expand Down Expand Up @@ -946,7 +955,7 @@ class _TextFieldState extends State<TextField> with RestorationMixin implements

bool get _hasError => widget.decoration?.errorText != null || widget.decoration?.error != null || _hasIntrinsicError;

Color get _errorColor => widget.decoration?.errorStyle?.color ?? Theme.of(context).colorScheme.error;
Color get _errorColor => widget.cursorErrorColor ?? widget.decoration?.errorStyle?.color ?? Theme.of(context).colorScheme.error;

InputDecoration _getEffectiveDecoration() {
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
Expand Down
2 changes: 2 additions & 0 deletions packages/flutter/lib/src/material/text_form_field.dart
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ class TextFormField extends FormField<String> {
double? cursorHeight,
Radius? cursorRadius,
Color? cursorColor,
Color? cursorErrorColor,
Brightness? keyboardAppearance,
EdgeInsets scrollPadding = const EdgeInsets.all(20.0),
bool? enableInteractiveSelection,
Expand Down Expand Up @@ -236,6 +237,7 @@ class TextFormField extends FormField<String> {
cursorHeight: cursorHeight,
cursorRadius: cursorRadius,
cursorColor: cursorColor,
cursorErrorColor: cursorErrorColor,
scrollPadding: scrollPadding,
scrollPhysics: scrollPhysics,
keyboardAppearance: keyboardAppearance,
Expand Down
73 changes: 55 additions & 18 deletions packages/flutter/test/material/text_form_field_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1481,27 +1481,64 @@ void main() {
});

testWidgetsWithLeakTracking('Error color for cursor while validating', (WidgetTester tester) async {
const Color errorColor = Color(0xff123456);
await tester.pumpWidget(MaterialApp(
theme: ThemeData(
colorScheme: const ColorScheme.light(error: errorColor),
),
home: Material(
child: Center(
child: TextFormField(
enabled: true,
autovalidateMode: AutovalidateMode.always,
validator: (String? value) {
return 'Please enter value';
},
const Color themeErrorColor = Color(0xff111111);
const Color errorStyleColor = Color(0xff777777);
const Color cursorErrorColor = Color(0xffbbbbbb);

Widget buildWidget({Color? errorStyleColor, Color? cursorErrorColor}) {
return MaterialApp(
theme: ThemeData(
colorScheme: const ColorScheme.light(error: themeErrorColor),
),
home: Material(
child: Center(
child: TextFormField(
enabled: true,
autovalidateMode: AutovalidateMode.always,
decoration: InputDecoration(
errorStyle: TextStyle(
color: errorStyleColor,
),
),
cursorErrorColor: cursorErrorColor,
validator: (String? value) {
return 'Please enter value';
},
),
),
),
);
}

Future<void> runTest(Widget widget, {required Color expectedColor}) async {
await tester.pumpWidget(widget);
await tester.enterText(find.byType(TextField), 'a');
final EditableText textField = tester.widget(
find.byType(EditableText).first,
);
await tester.pump();
expect(textField.cursorColor, expectedColor);
}

await runTest(
buildWidget(),
expectedColor: themeErrorColor,
);
await runTest(
buildWidget(errorStyleColor: errorStyleColor),
expectedColor: errorStyleColor,
);
await runTest(
buildWidget(cursorErrorColor: cursorErrorColor),
expectedColor: cursorErrorColor,
);
await runTest(
buildWidget(
errorStyleColor: errorStyleColor,
cursorErrorColor: cursorErrorColor,
),
));
await tester.enterText(find.byType(TextField), 'a');
final EditableText textField = tester.widget(find.byType(EditableText).first);
await tester.pump();
expect(textField.cursorColor, errorColor);
expectedColor: cursorErrorColor,
);
});

testWidgetsWithLeakTracking('TextFormField onChanged is called when the form is reset', (WidgetTester tester) async {
Expand Down

0 comments on commit 7cd6fd4

Please sign in to comment.