diff --git a/packages/flutter/lib/src/cupertino/text_field.dart b/packages/flutter/lib/src/cupertino/text_field.dart index c5d3497be4..4c83037d0c 100644 --- a/packages/flutter/lib/src/cupertino/text_field.dart +++ b/packages/flutter/lib/src/cupertino/text_field.dart @@ -239,6 +239,7 @@ class CupertinoTextField extends StatefulWidget { this.prefixMode = OverlayVisibilityMode.always, this.suffix, this.suffixMode = OverlayVisibilityMode.always, + this.crossAxisAlignment = CrossAxisAlignment.center, this.clearButtonMode = OverlayVisibilityMode.never, this.clearButtonSemanticLabel, TextInputType? keyboardType, @@ -367,6 +368,7 @@ class CupertinoTextField extends StatefulWidget { this.prefixMode = OverlayVisibilityMode.always, this.suffix, this.suffixMode = OverlayVisibilityMode.always, + this.crossAxisAlignment = CrossAxisAlignment.center, this.clearButtonMode = OverlayVisibilityMode.never, this.clearButtonSemanticLabel, TextInputType? keyboardType, @@ -516,6 +518,13 @@ class CupertinoTextField extends StatefulWidget { /// Has no effect when [suffix] is null. final OverlayVisibilityMode suffixMode; + /// Controls the vertical alignment of the [prefix] and the [suffix] widget in relation to content. + /// + /// Defaults to [CrossAxisAlignment.center]. + /// + /// Has no effect when both the [prefix] and [suffix] are null. + final CrossAxisAlignment crossAxisAlignment; + /// Show an iOS-style clear button to clear the current text entry. /// /// Can be made to appear depending on various text states of the @@ -1213,28 +1222,31 @@ class _CupertinoTextFieldState extends State with Restoratio (true, true) => widget.suffix ?? _buildClearButton(), (false, true) => _buildClearButton(), }; - return Row(children: [ - // Insert a prefix at the front if the prefix visibility mode matches - // the current text state. - if (prefixWidget != null) prefixWidget, - // In the middle part, stack the placeholder on top of the main EditableText - // if needed. - Expanded( - child: Stack( - // Ideally this should be baseline aligned. However that comes at - // the cost of the ability to compute the intrinsic dimensions of - // this widget. - // See also https://github.com/flutter/flutter/issues/13715. - alignment: AlignmentDirectional.center, - textDirection: widget.textDirection, - children: [ - if (placeholder != null) placeholder, - editableText, - ], + return Row( + crossAxisAlignment: widget.crossAxisAlignment, + children: [ + // Insert a prefix at the front if the prefix visibility mode matches + // the current text state. + if (prefixWidget != null) prefixWidget, + // In the middle part, stack the placeholder on top of the main EditableText + // if needed. + Expanded( + child: Stack( + // Ideally this should be baseline aligned. However that comes at + // the cost of the ability to compute the intrinsic dimensions of + // this widget. + // See also https://github.com/flutter/flutter/issues/13715. + alignment: AlignmentDirectional.center, + textDirection: widget.textDirection, + children: [ + if (placeholder != null) placeholder, + editableText, + ], + ), ), - ), - if (suffixWidget != null) suffixWidget - ]); + if (suffixWidget != null) suffixWidget, + ], + ); }, ); } diff --git a/packages/flutter/test/cupertino/text_field_test.dart b/packages/flutter/test/cupertino/text_field_test.dart index 1efdb3e123..9ea584adda 100644 --- a/packages/flutter/test/cupertino/text_field_test.dart +++ b/packages/flutter/test/cupertino/text_field_test.dart @@ -8067,6 +8067,73 @@ void main() { ); }); + testWidgets( + 'CrossAxisAlignment start positions the prefix and suffix at the top of the field', + (WidgetTester tester) async { + await tester.pumpWidget( + const CupertinoApp( + home: Center( + child: CupertinoTextField( + padding: EdgeInsets.zero, // Preventing delta position.dy + prefix: Icon(CupertinoIcons.add), + suffix: Icon(CupertinoIcons.clear), + crossAxisAlignment: CrossAxisAlignment.start, + ), + ), + ), + ); + + final CupertinoTextField cupertinoTextField = tester.widget( + find.byType(CupertinoTextField), + ); + + expect(find.widgetWithIcon(CupertinoTextField, CupertinoIcons.clear), findsOneWidget); + expect(find.widgetWithIcon(CupertinoTextField, CupertinoIcons.add), findsOneWidget); + expect(cupertinoTextField.crossAxisAlignment, CrossAxisAlignment.start); + + final double editableDy = tester.getTopLeft(find.byType(EditableText)).dy; + final double prefixDy = tester.getTopLeft(find.byIcon(CupertinoIcons.add)).dy; + final double suffixDy = tester.getTopLeft(find.byIcon(CupertinoIcons.clear)).dy; + + expect(prefixDy, editableDy); + expect(suffixDy, editableDy); + }, + ); + + testWidgets( + 'CrossAxisAlignment end positions the prefix and suffix at the bottom of the field', + (WidgetTester tester) async { + await tester.pumpWidget( + const CupertinoApp( + home: Center( + child: CupertinoTextField( + padding: EdgeInsets.zero, // Preventing delta position.dy + prefix: SizedBox.square(dimension: 48, child: Icon(CupertinoIcons.add)), + suffix: SizedBox.square(dimension: 48, child: Icon(CupertinoIcons.clear)), + crossAxisAlignment: CrossAxisAlignment.end, + ), + ), + ), + ); + + final CupertinoTextField cupertinoTextField = tester.widget( + find.byType(CupertinoTextField), + ); + + expect(find.widgetWithIcon(CupertinoTextField, CupertinoIcons.clear), findsOneWidget); + expect(find.widgetWithIcon(CupertinoTextField, CupertinoIcons.add), findsOneWidget); + expect(cupertinoTextField.crossAxisAlignment, CrossAxisAlignment.end); + + + final double editableDy = tester.getTopLeft(find.byType(EditableText)).dy; + final double prefixDy = tester.getTopLeft(find.byIcon(CupertinoIcons.add)).dy; + final double suffixDy = tester.getTopLeft(find.byIcon(CupertinoIcons.clear)).dy; + + expect(prefixDy, lessThan(editableDy)); + expect(suffixDy, lessThan(editableDy)); + }, + ); + testWidgets('text selection style 1', (WidgetTester tester) async { final TextEditingController controller = TextEditingController( text: 'Atwater Peel Sherbrooke Bonaventure\nhi\nwassssup!',