Unverified Commit 375c7268 authored by Gary Qian's avatar Gary Qian Committed by GitHub

Add clipBeheavior support to TextFields (#90423)

parent c603d8aa
...@@ -294,6 +294,7 @@ class CupertinoTextField extends StatefulWidget { ...@@ -294,6 +294,7 @@ class CupertinoTextField extends StatefulWidget {
this.scrollController, this.scrollController,
this.scrollPhysics, this.scrollPhysics,
this.autofillHints = const <String>[], this.autofillHints = const <String>[],
this.clipBehavior = Clip.hardEdge,
this.restorationId, this.restorationId,
this.enableIMEPersonalizedLearning = true, this.enableIMEPersonalizedLearning = true,
}) : assert(textAlign != null), }) : assert(textAlign != null),
...@@ -450,6 +451,7 @@ class CupertinoTextField extends StatefulWidget { ...@@ -450,6 +451,7 @@ class CupertinoTextField extends StatefulWidget {
this.scrollController, this.scrollController,
this.scrollPhysics, this.scrollPhysics,
this.autofillHints = const <String>[], this.autofillHints = const <String>[],
this.clipBehavior = Clip.hardEdge,
this.restorationId, this.restorationId,
this.enableIMEPersonalizedLearning = true, this.enableIMEPersonalizedLearning = true,
}) : assert(textAlign != null), }) : assert(textAlign != null),
...@@ -493,6 +495,7 @@ class CupertinoTextField extends StatefulWidget { ...@@ -493,6 +495,7 @@ class CupertinoTextField extends StatefulWidget {
!identical(keyboardType, TextInputType.text), !identical(keyboardType, TextInputType.text),
'Use keyboardType TextInputType.multiline when using TextInputAction.newline on a multiline TextField.', 'Use keyboardType TextInputType.multiline when using TextInputAction.newline on a multiline TextField.',
), ),
assert(clipBehavior != null),
assert(enableIMEPersonalizedLearning != null), assert(enableIMEPersonalizedLearning != null),
keyboardType = keyboardType ?? (maxLines == 1 ? TextInputType.text : TextInputType.multiline), keyboardType = keyboardType ?? (maxLines == 1 ? TextInputType.text : TextInputType.multiline),
toolbarOptions = toolbarOptions ?? (obscureText ? toolbarOptions = toolbarOptions ?? (obscureText ?
...@@ -786,6 +789,11 @@ class CupertinoTextField extends StatefulWidget { ...@@ -786,6 +789,11 @@ class CupertinoTextField extends StatefulWidget {
/// {@macro flutter.services.AutofillConfiguration.autofillHints} /// {@macro flutter.services.AutofillConfiguration.autofillHints}
final Iterable<String>? autofillHints; final Iterable<String>? autofillHints;
/// {@macro flutter.material.Material.clipBehavior}
///
/// Defaults to [Clip.hardEdge].
final Clip clipBehavior;
/// {@macro flutter.material.textfield.restorationId} /// {@macro flutter.material.textfield.restorationId}
final String? restorationId; final String? restorationId;
...@@ -833,6 +841,7 @@ class CupertinoTextField extends StatefulWidget { ...@@ -833,6 +841,7 @@ class CupertinoTextField extends StatefulWidget {
properties.add(EnumProperty<TextAlign>('textAlign', textAlign, defaultValue: TextAlign.start)); properties.add(EnumProperty<TextAlign>('textAlign', textAlign, defaultValue: TextAlign.start));
properties.add(DiagnosticsProperty<TextAlignVertical>('textAlignVertical', textAlignVertical, defaultValue: null)); properties.add(DiagnosticsProperty<TextAlignVertical>('textAlignVertical', textAlignVertical, defaultValue: null));
properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null)); properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
properties.add(DiagnosticsProperty<Clip>('clipBehavior', clipBehavior, defaultValue: Clip.hardEdge));
properties.add(DiagnosticsProperty<bool>('enableIMEPersonalizedLearning', enableIMEPersonalizedLearning, defaultValue: true)); properties.add(DiagnosticsProperty<bool>('enableIMEPersonalizedLearning', enableIMEPersonalizedLearning, defaultValue: true));
} }
} }
...@@ -1265,6 +1274,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with Restoratio ...@@ -1265,6 +1274,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with Restoratio
scrollPhysics: widget.scrollPhysics, scrollPhysics: widget.scrollPhysics,
enableInteractiveSelection: widget.enableInteractiveSelection, enableInteractiveSelection: widget.enableInteractiveSelection,
autofillClient: this, autofillClient: this,
clipBehavior: widget.clipBehavior,
restorationId: 'editable', restorationId: 'editable',
enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning, enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning,
), ),
......
...@@ -346,6 +346,7 @@ class TextField extends StatefulWidget { ...@@ -346,6 +346,7 @@ class TextField extends StatefulWidget {
this.scrollController, this.scrollController,
this.scrollPhysics, this.scrollPhysics,
this.autofillHints = const <String>[], this.autofillHints = const <String>[],
this.clipBehavior = Clip.hardEdge,
this.restorationId, this.restorationId,
this.enableIMEPersonalizedLearning = true, this.enableIMEPersonalizedLearning = true,
}) : assert(textAlign != null), }) : assert(textAlign != null),
...@@ -387,6 +388,7 @@ class TextField extends StatefulWidget { ...@@ -387,6 +388,7 @@ class TextField extends StatefulWidget {
!identical(keyboardType, TextInputType.text), !identical(keyboardType, TextInputType.text),
'Use keyboardType TextInputType.multiline when using TextInputAction.newline on a multiline TextField.', 'Use keyboardType TextInputType.multiline when using TextInputAction.newline on a multiline TextField.',
), ),
assert(clipBehavior != null),
assert(enableIMEPersonalizedLearning != null), assert(enableIMEPersonalizedLearning != null),
keyboardType = keyboardType ?? (maxLines == 1 ? TextInputType.text : TextInputType.multiline), keyboardType = keyboardType ?? (maxLines == 1 ? TextInputType.text : TextInputType.multiline),
toolbarOptions = toolbarOptions ?? (obscureText ? toolbarOptions = toolbarOptions ?? (obscureText ?
...@@ -761,6 +763,11 @@ class TextField extends StatefulWidget { ...@@ -761,6 +763,11 @@ class TextField extends StatefulWidget {
/// {@macro flutter.services.AutofillConfiguration.autofillHints} /// {@macro flutter.services.AutofillConfiguration.autofillHints}
final Iterable<String>? autofillHints; final Iterable<String>? autofillHints;
/// {@macro flutter.material.Material.clipBehavior}
///
/// Defaults to [Clip.hardEdge].
final Clip clipBehavior;
/// {@template flutter.material.textfield.restorationId} /// {@template flutter.material.textfield.restorationId}
/// Restoration ID to save and restore the state of the text field. /// Restoration ID to save and restore the state of the text field.
/// ///
...@@ -823,6 +830,7 @@ class TextField extends StatefulWidget { ...@@ -823,6 +830,7 @@ class TextField extends StatefulWidget {
properties.add(DiagnosticsProperty<TextSelectionControls>('selectionControls', selectionControls, defaultValue: null)); properties.add(DiagnosticsProperty<TextSelectionControls>('selectionControls', selectionControls, defaultValue: null));
properties.add(DiagnosticsProperty<ScrollController>('scrollController', scrollController, defaultValue: null)); properties.add(DiagnosticsProperty<ScrollController>('scrollController', scrollController, defaultValue: null));
properties.add(DiagnosticsProperty<ScrollPhysics>('scrollPhysics', scrollPhysics, defaultValue: null)); properties.add(DiagnosticsProperty<ScrollPhysics>('scrollPhysics', scrollPhysics, defaultValue: null));
properties.add(DiagnosticsProperty<Clip>('clipBehavior', clipBehavior, defaultValue: Clip.hardEdge));
properties.add(DiagnosticsProperty<bool>('enableIMEPersonalizedLearning', enableIMEPersonalizedLearning, defaultValue: true)); properties.add(DiagnosticsProperty<bool>('enableIMEPersonalizedLearning', enableIMEPersonalizedLearning, defaultValue: true));
} }
} }
...@@ -1265,6 +1273,7 @@ class _TextFieldState extends State<TextField> with RestorationMixin implements ...@@ -1265,6 +1273,7 @@ class _TextFieldState extends State<TextField> with RestorationMixin implements
scrollPhysics: widget.scrollPhysics, scrollPhysics: widget.scrollPhysics,
autofillClient: this, autofillClient: this,
autocorrectionTextRectColor: autocorrectionTextRectColor, autocorrectionTextRectColor: autocorrectionTextRectColor,
clipBehavior: widget.clipBehavior,
restorationId: 'editable', restorationId: 'editable',
enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning, enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning,
), ),
......
...@@ -23,6 +23,7 @@ import 'package:flutter_test/flutter_test.dart'; ...@@ -23,6 +23,7 @@ import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart'; import '../rendering/mock_canvas.dart';
import '../widgets/clipboard_utils.dart'; import '../widgets/clipboard_utils.dart';
import '../widgets/editable_text_utils.dart' show OverflowWidgetTextEditingController;
import '../widgets/semantics_tester.dart'; import '../widgets/semantics_tester.dart';
// On web, the context menu (aka toolbar) is provided by the browser. // On web, the context menu (aka toolbar) is provided by the browser.
...@@ -4819,4 +4820,46 @@ void main() { ...@@ -4819,4 +4820,46 @@ void main() {
final EditableText rtlWidget = tester.widget(find.byType(EditableText)); final EditableText rtlWidget = tester.widget(find.byType(EditableText));
expect(rtlWidget.textDirection, TextDirection.rtl); expect(rtlWidget.textDirection, TextDirection.rtl);
}); });
testWidgets('clipBehavior has expected defaults', (WidgetTester tester) async {
await tester.pumpWidget(
const CupertinoApp(
home: CupertinoTextField(
),
),
);
final CupertinoTextField textField = tester.firstWidget(find.byType(CupertinoTextField));
expect(textField.clipBehavior, Clip.hardEdge);
});
testWidgets('Overflow clipBehavior none golden', (WidgetTester tester) async {
final Widget widget = CupertinoApp(
home: RepaintBoundary(
key: const ValueKey<int>(1),
child: SizedBox(
height: 200.0,
width: 200.0,
child: Center(
child: CupertinoTextField(
controller: OverflowWidgetTextEditingController(),
clipBehavior: Clip.none,
),
),
),
),
);
await tester.pumpWidget(widget);
final CupertinoTextField textField = tester.firstWidget(find.byType(CupertinoTextField));
expect(textField.clipBehavior, Clip.none);
final EditableText editableText = tester.firstWidget(find.byType(EditableText));
expect(editableText.clipBehavior, Clip.none);
await expectLater(
find.byKey(const ValueKey<int>(1)),
matchesGoldenFile('overflow_clipbehavior_none.cupertino.0.png'),
);
});
} }
...@@ -24,7 +24,7 @@ import 'package:flutter/services.dart'; ...@@ -24,7 +24,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import '../widgets/clipboard_utils.dart'; import '../widgets/clipboard_utils.dart';
import '../widgets/editable_text_utils.dart' show findRenderEditable, globalize, textOffsetToPosition; import '../widgets/editable_text_utils.dart' show findRenderEditable, globalize, textOffsetToPosition, OverflowWidgetTextEditingController;
import '../widgets/semantics_tester.dart'; import '../widgets/semantics_tester.dart';
import 'feedback_tester.dart'; import 'feedback_tester.dart';
...@@ -552,6 +552,48 @@ void main() { ...@@ -552,6 +552,48 @@ void main() {
expect(textField.cursorRadius, const Radius.circular(3.0)); expect(textField.cursorRadius, const Radius.circular(3.0));
}); });
testWidgets('clipBehavior has expected defaults', (WidgetTester tester) async {
await tester.pumpWidget(
overlay(
child: const TextField(
),
),
);
final TextField textField = tester.firstWidget(find.byType(TextField));
expect(textField.clipBehavior, Clip.hardEdge);
});
testWidgets('Overflow clipBehavior none golden', (WidgetTester tester) async {
final Widget widget = overlay(
child: RepaintBoundary(
key: const ValueKey<int>(1),
child: SizedBox(
height: 200,
width: 200,
child: Center(
child: TextField(
controller: OverflowWidgetTextEditingController(),
clipBehavior: Clip.none,
),
),
),
),
);
await tester.pumpWidget(widget);
final TextField textField = tester.firstWidget(find.byType(TextField));
expect(textField.clipBehavior, Clip.none);
final EditableText editableText = tester.firstWidget(find.byType(EditableText));
expect(editableText.clipBehavior, Clip.none);
await expectLater(
find.byKey(const ValueKey<int>(1)),
matchesGoldenFile('overflow_clipbehavior_none.material.0.png'),
);
});
testWidgets('Material cursor android golden', (WidgetTester tester) async { testWidgets('Material cursor android golden', (WidgetTester tester) async {
final Widget widget = overlay( final Widget widget = overlay(
child: const RepaintBoundary( child: const RepaintBoundary(
......
...@@ -45,3 +45,27 @@ Offset textOffsetToPosition(WidgetTester tester, int offset) { ...@@ -45,3 +45,27 @@ Offset textOffsetToPosition(WidgetTester tester, int offset) {
expect(endpoints.length, 1); expect(endpoints.length, 1);
return endpoints[0].point + const Offset(0.0, -2.0); return endpoints[0].point + const Offset(0.0, -2.0);
} }
// Simple controller that builds a WidgetSpan with 100 height.
class OverflowWidgetTextEditingController extends TextEditingController {
@override
TextSpan buildTextSpan({
required BuildContext context,
TextStyle? style,
required bool withComposing,
}) {
return TextSpan(
style: style,
children: <InlineSpan>[
const TextSpan(text: 'Hi'),
WidgetSpan(
alignment: PlaceholderAlignment.bottom,
child: Container(
color: Colors.redAccent,
height: 100.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