mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Make InputDecorator start padding compliant with M3 spec (#162157)
## Description This PR makes InputDecorator padding for the input content, the helper and the counter compliant with the M3 spec. The padding should be 16 pixels instead of 12 pixels (which is the current value). See https://m3.material.io/components/text-fields/specs#0d36c3fe-7948-4ec2-ab8a-4fe39cca19cc for filled text fields and https://m3.material.io/components/text-fields/specs#605e24f1-1c1f-4c00-b385-4bf50733a5ef for outlined text fields. ### Before: The paddings for the input content, the helper and the counter are not compliant with the M3 spec (12 pixels instead of 16 pixels):  ### After: The paddings for the input content, the helper and the counter are compliant with the M3 spec (16 pixels):  ## Related Issue Fixes [Outlined TextField Label start position doesn't meet Material Design Specs](https://github.com/flutter/flutter/issues/67707) ## Tests Adds 8 tests. Updates several tests.
This commit is contained in:
parent
cb3fd95ff6
commit
69c5526b23
@ -48,7 +48,7 @@ const double _kMinimumWidth = 112.0;
|
||||
|
||||
const double _kDefaultHorizontalPadding = 12.0;
|
||||
|
||||
const double _kLeadingIconToInputPadding = 4.0;
|
||||
const double _kInputStartGap = 4.0;
|
||||
|
||||
/// Defines a [DropdownMenu] menu button that represents one item view in the menu.
|
||||
///
|
||||
@ -641,12 +641,7 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
final double? leadingWidgetWidth = getWidth(_leadingKey);
|
||||
if (leadingWidgetWidth != null) {
|
||||
leadingPadding = leadingWidgetWidth + _kLeadingIconToInputPadding;
|
||||
} else {
|
||||
leadingPadding = leadingWidgetWidth;
|
||||
}
|
||||
leadingPadding = getWidth(_leadingKey);
|
||||
});
|
||||
}, debugLabel: 'DropdownMenu.refreshLeadingPadding');
|
||||
}
|
||||
@ -718,7 +713,9 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
|
||||
int? focusedIndex,
|
||||
bool enableScrollToHighlight = true,
|
||||
bool excludeSemantics = false,
|
||||
bool? useMaterial3,
|
||||
}) {
|
||||
final double effectiveInputStartGap = useMaterial3 ?? false ? _kInputStartGap : 0.0;
|
||||
final List<Widget> result = <Widget>[];
|
||||
for (int i = 0; i < filteredEntries.length; i++) {
|
||||
final DropdownMenuEntry<T> entry = filteredEntries[i];
|
||||
@ -735,14 +732,9 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
|
||||
: _kDefaultHorizontalPadding;
|
||||
ButtonStyle effectiveStyle =
|
||||
entry.style ??
|
||||
switch (textDirection) {
|
||||
TextDirection.rtl => MenuItemButton.styleFrom(
|
||||
padding: EdgeInsets.only(left: _kDefaultHorizontalPadding, right: padding),
|
||||
),
|
||||
TextDirection.ltr => MenuItemButton.styleFrom(
|
||||
padding: EdgeInsets.only(left: padding, right: _kDefaultHorizontalPadding),
|
||||
),
|
||||
};
|
||||
MenuItemButton.styleFrom(
|
||||
padding: EdgeInsetsDirectional.only(start: padding, end: _kDefaultHorizontalPadding),
|
||||
);
|
||||
|
||||
final ButtonStyle? themeStyle = MenuButtonTheme.of(context).style;
|
||||
|
||||
@ -797,7 +789,8 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
|
||||
|
||||
Widget label = entry.labelWidget ?? Text(entry.label);
|
||||
if (widget.width != null) {
|
||||
final double horizontalPadding = padding + _kDefaultHorizontalPadding;
|
||||
final double horizontalPadding =
|
||||
padding + _kDefaultHorizontalPadding + effectiveInputStartGap;
|
||||
label = ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: widget.width! - horizontalPadding),
|
||||
child: label,
|
||||
@ -809,13 +802,7 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
|
||||
child: MenuItemButton(
|
||||
key: enableScrollToHighlight ? buttonItemKeys[i] : null,
|
||||
style: effectiveStyle,
|
||||
leadingIcon:
|
||||
entry.leadingIcon != null
|
||||
? Padding(
|
||||
padding: const EdgeInsetsDirectional.only(end: _kLeadingIconToInputPadding),
|
||||
child: entry.leadingIcon,
|
||||
)
|
||||
: null,
|
||||
leadingIcon: entry.leadingIcon,
|
||||
trailingIcon: entry.trailingIcon,
|
||||
closeOnActivate: widget.closeBehavior == DropdownMenuCloseBehavior.all,
|
||||
onPressed:
|
||||
@ -834,7 +821,15 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
|
||||
}
|
||||
: null,
|
||||
requestFocusOnHover: false,
|
||||
child: label,
|
||||
// MenuItemButton implementation is based on M3 spec for menu which specifies a
|
||||
// horizontal padding of 12 pixels.
|
||||
// In the context of DropdownMenu the M3 spec specifies that the menu item and the text
|
||||
// field content should be aligned. The text field has a horizontal padding of 16 pixels.
|
||||
// To conform with the 16 pixels padding, a 4 pixels padding is added in front of the item label.
|
||||
child: Padding(
|
||||
padding: EdgeInsetsDirectional.only(start: effectiveInputStartGap),
|
||||
child: label,
|
||||
),
|
||||
),
|
||||
);
|
||||
result.add(menuItemButton);
|
||||
@ -924,6 +919,7 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bool useMaterial3 = Theme.of(context).useMaterial3;
|
||||
final TextDirection textDirection = Directionality.of(context);
|
||||
_initialMenu ??= _buildButtons(
|
||||
widget.dropdownMenuEntries,
|
||||
@ -931,6 +927,7 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
|
||||
enableScrollToHighlight: false,
|
||||
// The _initialMenu is invisible, we should not add semantics nodes to it
|
||||
excludeSemantics: true,
|
||||
useMaterial3: useMaterial3,
|
||||
);
|
||||
final DropdownMenuThemeData theme = DropdownMenuTheme.of(context);
|
||||
final DropdownMenuThemeData defaults = _DropdownMenuDefaultsM3(context);
|
||||
@ -963,6 +960,7 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
|
||||
filteredEntries,
|
||||
textDirection,
|
||||
focusedIndex: currentHighlight,
|
||||
useMaterial3: useMaterial3,
|
||||
);
|
||||
|
||||
final TextStyle? effectiveTextStyle = widget.textStyle ?? theme.textStyle ?? defaults.textStyle;
|
||||
|
@ -38,6 +38,13 @@ const Duration _kTransitionDuration = Duration(milliseconds: 167);
|
||||
const Curve _kTransitionCurve = Curves.fastOutSlowIn;
|
||||
const double _kFinalLabelScale = 0.75;
|
||||
|
||||
// From the M3 spec, horizontal padding is 12 pixels for the prefix icon and
|
||||
// 16 pixels for the input content.
|
||||
// InputDecorator default padding is set to 12 pixels because 16 pixels will move
|
||||
// the prefix icon too far.
|
||||
// An extra padding should be added for the input content to comply with the 16 pixels padding.
|
||||
const double _kInputExtraPadding = 4.0;
|
||||
|
||||
typedef _SubtextSize = ({double ascent, double bottomHeight, double subtextHeight});
|
||||
typedef _ChildBaselineGetter = double Function(RenderBox child, BoxConstraints constraints);
|
||||
|
||||
@ -565,6 +572,7 @@ class _Decoration {
|
||||
required this.isDense,
|
||||
required this.isEmpty,
|
||||
required this.visualDensity,
|
||||
required this.inputGap,
|
||||
required this.maintainHintSize,
|
||||
this.icon,
|
||||
this.input,
|
||||
@ -590,6 +598,7 @@ class _Decoration {
|
||||
final bool? isDense;
|
||||
final bool isEmpty;
|
||||
final VisualDensity visualDensity;
|
||||
final double inputGap;
|
||||
final bool maintainHintSize;
|
||||
final Widget? icon;
|
||||
final Widget? input;
|
||||
@ -623,6 +632,7 @@ class _Decoration {
|
||||
other.isDense == isDense &&
|
||||
other.isEmpty == isEmpty &&
|
||||
other.visualDensity == visualDensity &&
|
||||
other.inputGap == inputGap &&
|
||||
other.maintainHintSize == maintainHintSize &&
|
||||
other.icon == icon &&
|
||||
other.input == input &&
|
||||
@ -649,6 +659,7 @@ class _Decoration {
|
||||
isDense,
|
||||
isEmpty,
|
||||
visualDensity,
|
||||
inputGap,
|
||||
maintainHintSize,
|
||||
icon,
|
||||
input,
|
||||
@ -657,8 +668,7 @@ class _Decoration {
|
||||
prefix,
|
||||
suffix,
|
||||
prefixIcon,
|
||||
suffixIcon,
|
||||
Object.hash(helperError, counter, container),
|
||||
Object.hash(suffixIcon, helperError, counter, container),
|
||||
);
|
||||
}
|
||||
|
||||
@ -967,10 +977,14 @@ class _RenderDecoration extends RenderBox
|
||||
start:
|
||||
iconWidth +
|
||||
prefixSize.width +
|
||||
(prefixIcon == null ? contentPadding.start : prefixIconSize.width + prefixToInputGap),
|
||||
(prefixIcon == null
|
||||
? contentPadding.start + decoration.inputGap
|
||||
: prefixIconSize.width + prefixToInputGap),
|
||||
end:
|
||||
suffixSize.width +
|
||||
(suffixIcon == null ? contentPadding.end : suffixIconSize.width + inputToSuffixGap),
|
||||
(suffixIcon == null
|
||||
? contentPadding.end + decoration.inputGap
|
||||
: suffixIconSize.width + inputToSuffixGap),
|
||||
);
|
||||
|
||||
final double inputWidth = math.max(
|
||||
@ -987,7 +1001,11 @@ class _RenderDecoration extends RenderBox
|
||||
final double labelWidth = math.max(
|
||||
0.0,
|
||||
constraints.maxWidth -
|
||||
(iconWidth + contentPadding.horizontal + prefixIconSize.width + suffixIconSpace),
|
||||
(decoration.inputGap * 2 +
|
||||
iconWidth +
|
||||
contentPadding.horizontal +
|
||||
prefixIconSize.width +
|
||||
suffixIconSpace),
|
||||
);
|
||||
|
||||
// Increase the available width for the label when it is scaled down.
|
||||
@ -1168,13 +1186,13 @@ class _RenderDecoration extends RenderBox
|
||||
? math.max(_minWidth(input, height), _minWidth(hint, height))
|
||||
: _minWidth(input, height);
|
||||
return _minWidth(icon, height) +
|
||||
(prefixIcon != null ? prefixToInputGap : contentPadding.start) +
|
||||
(prefixIcon != null ? prefixToInputGap : contentPadding.start + decoration.inputGap) +
|
||||
_minWidth(prefixIcon, height) +
|
||||
_minWidth(prefix, height) +
|
||||
contentWidth +
|
||||
_minWidth(suffix, height) +
|
||||
_minWidth(suffixIcon, height) +
|
||||
(suffixIcon != null ? inputToSuffixGap : contentPadding.end);
|
||||
(suffixIcon != null ? inputToSuffixGap : contentPadding.end + decoration.inputGap);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -1184,13 +1202,13 @@ class _RenderDecoration extends RenderBox
|
||||
? math.max(_maxWidth(input, height), _maxWidth(hint, height))
|
||||
: _maxWidth(input, height);
|
||||
return _maxWidth(icon, height) +
|
||||
(prefixIcon != null ? prefixToInputGap : contentPadding.start) +
|
||||
(prefixIcon != null ? prefixToInputGap : contentPadding.start + decoration.inputGap) +
|
||||
_maxWidth(prefixIcon, height) +
|
||||
_maxWidth(prefix, height) +
|
||||
contentWidth +
|
||||
_maxWidth(suffix, height) +
|
||||
_maxWidth(suffixIcon, height) +
|
||||
(suffixIcon != null ? inputToSuffixGap : contentPadding.end);
|
||||
(suffixIcon != null ? inputToSuffixGap : contentPadding.end + decoration.inputGap);
|
||||
}
|
||||
|
||||
double _lineHeight(double width, List<RenderBox?> boxes) {
|
||||
@ -1375,10 +1393,13 @@ class _RenderDecoration extends RenderBox
|
||||
case TextDirection.ltr:
|
||||
start = contentPadding.start + _boxSize(icon).width;
|
||||
end = overallWidth - contentPadding.end;
|
||||
_boxParentData(helperError).offset = Offset(start, subtextBaseline - helperErrorBaseline);
|
||||
_boxParentData(helperError).offset = Offset(
|
||||
start + decoration.inputGap,
|
||||
subtextBaseline - helperErrorBaseline,
|
||||
);
|
||||
if (counter != null) {
|
||||
_boxParentData(counter).offset = Offset(
|
||||
end - counter.size.width,
|
||||
end - counter.size.width - decoration.inputGap,
|
||||
subtextBaseline - counterBaseline,
|
||||
);
|
||||
}
|
||||
@ -1386,11 +1407,14 @@ class _RenderDecoration extends RenderBox
|
||||
start = overallWidth - contentPadding.start - _boxSize(icon).width;
|
||||
end = contentPadding.end;
|
||||
_boxParentData(helperError).offset = Offset(
|
||||
start - helperError.size.width,
|
||||
start - helperError.size.width - decoration.inputGap,
|
||||
subtextBaseline - helperErrorBaseline,
|
||||
);
|
||||
if (counter != null) {
|
||||
_boxParentData(counter).offset = Offset(end, subtextBaseline - counterBaseline);
|
||||
_boxParentData(counter).offset = Offset(
|
||||
end + decoration.inputGap,
|
||||
subtextBaseline - counterBaseline,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1410,6 +1434,8 @@ class _RenderDecoration extends RenderBox
|
||||
start += contentPadding.start;
|
||||
start -= centerLayout(prefixIcon!, start - prefixIcon!.size.width);
|
||||
start -= prefixToInputGap;
|
||||
} else {
|
||||
start -= decoration.inputGap;
|
||||
}
|
||||
if (label != null) {
|
||||
if (decoration.alignLabelWithHint) {
|
||||
@ -1431,6 +1457,8 @@ class _RenderDecoration extends RenderBox
|
||||
end -= contentPadding.end;
|
||||
end += centerLayout(suffixIcon!, end);
|
||||
end += inputToSuffixGap;
|
||||
} else {
|
||||
end += decoration.inputGap;
|
||||
}
|
||||
if (suffix != null) {
|
||||
end += baselineLayout(suffix!, end);
|
||||
@ -1443,6 +1471,8 @@ class _RenderDecoration extends RenderBox
|
||||
start -= contentPadding.start;
|
||||
start += centerLayout(prefixIcon!, start);
|
||||
start += prefixToInputGap;
|
||||
} else {
|
||||
start += decoration.inputGap;
|
||||
}
|
||||
if (label != null) {
|
||||
if (decoration.alignLabelWithHint) {
|
||||
@ -1464,6 +1494,8 @@ class _RenderDecoration extends RenderBox
|
||||
end += contentPadding.end;
|
||||
end -= centerLayout(suffixIcon!, end - suffixIcon!.size.width);
|
||||
end -= inputToSuffixGap;
|
||||
} else {
|
||||
end -= decoration.inputGap;
|
||||
}
|
||||
if (suffix != null) {
|
||||
end -= baselineLayout(suffix!, end - suffix!.size.width);
|
||||
@ -2258,10 +2290,9 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
|
||||
Widget build(BuildContext context) {
|
||||
final ThemeData themeData = Theme.of(context);
|
||||
final VisualDensity visualDensity = decoration.visualDensity ?? themeData.visualDensity;
|
||||
final bool useMaterial3 = Theme.of(context).useMaterial3;
|
||||
final InputDecorationTheme defaults =
|
||||
themeData.useMaterial3
|
||||
? _InputDecoratorDefaultsM3(context)
|
||||
: _InputDecoratorDefaultsM2(context);
|
||||
useMaterial3 ? _InputDecoratorDefaultsM3(context) : _InputDecoratorDefaultsM2(context);
|
||||
final InputDecorationTheme inputDecorationTheme = themeData.inputDecorationTheme;
|
||||
final IconButtonThemeData iconButtonTheme = IconButtonTheme.of(context);
|
||||
|
||||
@ -2551,7 +2582,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
|
||||
if (decoration.filled ?? false) {
|
||||
contentPadding =
|
||||
decorationContentPadding ??
|
||||
(Theme.of(context).useMaterial3
|
||||
(useMaterial3
|
||||
? decorationIsDense
|
||||
? const EdgeInsetsDirectional.fromSTEB(12.0, 4.0, 12.0, 4.0)
|
||||
: const EdgeInsetsDirectional.fromSTEB(12.0, 8.0, 12.0, 8.0)
|
||||
@ -2564,7 +2595,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
|
||||
// the most noticeable layout change introduced by #13734.
|
||||
contentPadding =
|
||||
decorationContentPadding ??
|
||||
(Theme.of(context).useMaterial3
|
||||
(useMaterial3
|
||||
? decorationIsDense
|
||||
? const EdgeInsetsDirectional.fromSTEB(0.0, 4.0, 0.0, 4.0)
|
||||
: const EdgeInsetsDirectional.fromSTEB(0.0, 8.0, 0.0, 8.0)
|
||||
@ -2576,7 +2607,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
|
||||
floatingLabelHeight = 0.0;
|
||||
contentPadding =
|
||||
decorationContentPadding ??
|
||||
(Theme.of(context).useMaterial3
|
||||
(useMaterial3
|
||||
? decorationIsDense
|
||||
? const EdgeInsetsDirectional.fromSTEB(12.0, 16.0, 12.0, 8.0)
|
||||
: const EdgeInsetsDirectional.fromSTEB(12.0, 20.0, 12.0, 12.0)
|
||||
@ -2585,10 +2616,20 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
|
||||
: const EdgeInsetsDirectional.fromSTEB(12.0, 24.0, 12.0, 16.0));
|
||||
}
|
||||
|
||||
double inputGap = 0.0;
|
||||
if (useMaterial3) {
|
||||
if (border is OutlineInputBorder) {
|
||||
inputGap = border.gapPadding;
|
||||
} else {
|
||||
inputGap = border.isOutline || (decoration.filled ?? false) ? _kInputExtraPadding : 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
final _Decorator decorator = _Decorator(
|
||||
decoration: _Decoration(
|
||||
contentPadding: contentPadding,
|
||||
isCollapsed: decoration.isCollapsed ?? themeData.inputDecorationTheme.isCollapsed,
|
||||
inputGap: inputGap,
|
||||
floatingLabelHeight: floatingLabelHeight,
|
||||
floatingLabelAlignment: decoration.floatingLabelAlignment!,
|
||||
floatingLabelProgress: _floatingLabelAnimation.value,
|
||||
|
@ -624,7 +624,7 @@ void main() {
|
||||
|
||||
final Finder textField = find.byType(TextField);
|
||||
final double anchorWidth = tester.getSize(textField).width;
|
||||
expect(anchorWidth, closeTo(180.5, 0.1));
|
||||
expect(anchorWidth, closeTo(184.5, 0.1));
|
||||
|
||||
await tester.tap(find.byType(DropdownMenu<TestMenu>));
|
||||
await tester.pumpAndSettle();
|
||||
@ -634,7 +634,7 @@ void main() {
|
||||
.ancestor(of: find.byType(SingleChildScrollView), matching: find.byType(Material))
|
||||
.first;
|
||||
final double menuWidth = tester.getSize(menuMaterial).width;
|
||||
expect(menuWidth, closeTo(180.5, 0.1));
|
||||
expect(menuWidth, closeTo(184.5, 0.1));
|
||||
|
||||
// The text field should have same width as the menu
|
||||
// when the width property is not null.
|
||||
@ -741,10 +741,14 @@ void main() {
|
||||
|
||||
final double width = tester.getSize(find.byType(DropdownMenu<int>)).width;
|
||||
const double menuEntryPadding = 24.0; // See _kDefaultHorizontalPadding.
|
||||
const double decorationStartGap = 4.0; // See _kInputStartGap.
|
||||
const double leadingWidth = 16.0;
|
||||
const double trailingWidth = 56.0;
|
||||
|
||||
expect(width, entryLabelWidth + leadingWidth + trailingWidth + menuEntryPadding);
|
||||
expect(
|
||||
width,
|
||||
entryLabelWidth + leadingWidth + trailingWidth + menuEntryPadding + decorationStartGap,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('The width is determined by the label when it is longer than menu entries', (
|
||||
@ -994,7 +998,6 @@ void main() {
|
||||
.ancestor(of: find.byType(SingleChildScrollView), matching: find.byType(Padding))
|
||||
.first;
|
||||
final Size menuViewSize = tester.getSize(menuView);
|
||||
expect(menuViewSize.width, closeTo(180.6, 0.1));
|
||||
expect(menuViewSize.height, equals(304.0)); // 304 = 288 + vertical padding(2 * 8)
|
||||
|
||||
// Constrains the menu height.
|
||||
@ -1011,7 +1014,6 @@ void main() {
|
||||
.first;
|
||||
|
||||
final Size updatedMenuSize = tester.getSize(updatedMenu);
|
||||
expect(updatedMenuSize.width, closeTo(180.6, 0.1));
|
||||
expect(updatedMenuSize.height, equals(100.0));
|
||||
},
|
||||
);
|
||||
|
@ -2286,6 +2286,37 @@ void main() {
|
||||
|
||||
group('Material3 - InputDecoration label', () {
|
||||
group('for filled text field', () {
|
||||
testWidgets('label and input horizontal positions are M3 compliant in LTR', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
await tester.pumpWidget(
|
||||
buildInputDecorator(
|
||||
decoration: const InputDecoration(filled: true, labelText: labelText),
|
||||
),
|
||||
);
|
||||
|
||||
expect(getDecoratorRect(tester).size, const Size(800.0, 56.0));
|
||||
const double labelAndInputStart = 12.0 + 4.0; // Content left padding + default input gap.
|
||||
expect(getLabelRect(tester).left, labelAndInputStart);
|
||||
expect(getInputRect(tester).left, labelAndInputStart);
|
||||
});
|
||||
|
||||
testWidgets('label and input horizontal positions are M3 compliant in RTL', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
await tester.pumpWidget(
|
||||
buildInputDecorator(
|
||||
decoration: const InputDecoration(filled: true, labelText: labelText),
|
||||
textDirection: TextDirection.rtl,
|
||||
),
|
||||
);
|
||||
|
||||
expect(getDecoratorRect(tester).size, const Size(800.0, 56.0));
|
||||
const double labelAndInputStart = 12.0 + 4.0; // Content left padding + default input gap.
|
||||
expect(getLabelRect(tester).right, 800.0 - labelAndInputStart);
|
||||
expect(getInputRect(tester).right, 800.0 - labelAndInputStart);
|
||||
});
|
||||
|
||||
group('when field is enabled', () {
|
||||
testWidgets('label text has correct style', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
@ -2434,6 +2465,76 @@ void main() {
|
||||
});
|
||||
|
||||
group('for outlined text field', () {
|
||||
testWidgets('label and input horizontal positions are M3 compliant in LTR', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
await tester.pumpWidget(
|
||||
buildInputDecorator(
|
||||
decoration: const InputDecoration(border: OutlineInputBorder(), labelText: labelText),
|
||||
),
|
||||
);
|
||||
|
||||
expect(getDecoratorRect(tester).size, const Size(800.0, 56.0));
|
||||
const double labelAndInputStart = 12.0 + 4.0; // Content left padding + default input gap.
|
||||
expect(getLabelRect(tester).left, labelAndInputStart);
|
||||
expect(getInputRect(tester).left, labelAndInputStart);
|
||||
});
|
||||
|
||||
testWidgets('label and input horizontal positions are M3 compliant in RTL', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
await tester.pumpWidget(
|
||||
buildInputDecorator(
|
||||
decoration: const InputDecoration(border: OutlineInputBorder(), labelText: labelText),
|
||||
textDirection: TextDirection.rtl,
|
||||
),
|
||||
);
|
||||
|
||||
expect(getDecoratorRect(tester).size, const Size(800.0, 56.0));
|
||||
const double labelAndInputStart = 12.0 + 4.0; // Content left padding + default input gap.
|
||||
expect(getLabelRect(tester).right, 800 - labelAndInputStart);
|
||||
expect(getInputRect(tester).right, 800 - labelAndInputStart);
|
||||
});
|
||||
|
||||
testWidgets('label and input horizontal positions can be adjusted in LTR', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
const double customGap = 6.0;
|
||||
await tester.pumpWidget(
|
||||
buildInputDecorator(
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(gapPadding: customGap),
|
||||
labelText: labelText,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(getDecoratorRect(tester).size, const Size(800.0, 56.0));
|
||||
const double labelAndInputStart = 12.0 + customGap; // Content left padding + input gap.
|
||||
expect(getLabelRect(tester).left, labelAndInputStart);
|
||||
expect(getInputRect(tester).left, labelAndInputStart);
|
||||
});
|
||||
|
||||
testWidgets('label and input horizontal positions can be adjusted in RTL', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
const double customGap = 6.0;
|
||||
await tester.pumpWidget(
|
||||
buildInputDecorator(
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(gapPadding: customGap),
|
||||
labelText: labelText,
|
||||
),
|
||||
textDirection: TextDirection.rtl,
|
||||
),
|
||||
);
|
||||
|
||||
expect(getDecoratorRect(tester).size, const Size(800.0, 56.0));
|
||||
const double labelAndInputStart = 12.0 + customGap; // Content left padding + input gap.
|
||||
expect(getLabelRect(tester).right, 800.0 - labelAndInputStart);
|
||||
expect(getInputRect(tester).right, 800.0 - labelAndInputStart);
|
||||
});
|
||||
|
||||
group('when field is enabled', () {
|
||||
testWidgets('label text has correct style', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
@ -2602,6 +2703,35 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Label and input for non-filled and non-outlined text field have no horizontal padding in LTR',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
buildInputDecorator(decoration: const InputDecoration(labelText: labelText)),
|
||||
);
|
||||
|
||||
expect(getDecoratorRect(tester).size, const Size(800.0, 56.0));
|
||||
expect(getLabelRect(tester).left, 0.0);
|
||||
expect(getInputRect(tester).left, 0.0);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'Label and input for non-filled and non-outlined text field have no horizontal padding in RTL',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
buildInputDecorator(
|
||||
decoration: const InputDecoration(labelText: labelText),
|
||||
textDirection: TextDirection.rtl,
|
||||
),
|
||||
);
|
||||
|
||||
expect(getDecoratorRect(tester).size, const Size(800.0, 56.0));
|
||||
expect(getLabelRect(tester).right, 800.0);
|
||||
expect(getInputRect(tester).right, 800.0);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('floatingLabelStyle overrides default style', (WidgetTester tester) async {
|
||||
const TextStyle floatingLabelStyle = TextStyle(color: Colors.indigo, fontSize: 16.0);
|
||||
|
||||
@ -2881,43 +3011,43 @@ void main() {
|
||||
await pumpDecorator(focused: false);
|
||||
await tester.pump(kTransitionDuration);
|
||||
const Size labelSize = Size(82.5, 16);
|
||||
expect(getLabelRect(tester).topLeft, equals(const Offset(12, 20)));
|
||||
expect(getLabelRect(tester).topLeft, equals(const Offset(16, 20)));
|
||||
expect(getLabelRect(tester).size, equals(labelSize));
|
||||
|
||||
await pumpDecorator(focused: false, empty: false);
|
||||
await tester.pump(kTransitionDuration);
|
||||
expect(getLabelRect(tester).topLeft, equals(const Offset(12, -5.5)));
|
||||
expect(getLabelRect(tester).topLeft, equals(const Offset(16, -5.5)));
|
||||
expect(getLabelRect(tester).size, equals(labelSize * 0.75));
|
||||
|
||||
await pumpDecorator(focused: true);
|
||||
await tester.pump(kTransitionDuration);
|
||||
expect(getLabelRect(tester).topLeft, equals(const Offset(12, -5.5)));
|
||||
expect(getLabelRect(tester).topLeft, equals(const Offset(16, -5.5)));
|
||||
expect(getLabelRect(tester).size, equals(labelSize * 0.75));
|
||||
|
||||
await pumpDecorator(focused: true, empty: false);
|
||||
await tester.pump(kTransitionDuration);
|
||||
expect(getLabelRect(tester).topLeft, equals(const Offset(12, -5.5)));
|
||||
expect(getLabelRect(tester).topLeft, equals(const Offset(16, -5.5)));
|
||||
expect(getLabelRect(tester).size, equals(labelSize * 0.75));
|
||||
|
||||
await pumpDecorator(focused: false, enabled: false);
|
||||
await tester.pump(kTransitionDuration);
|
||||
expect(getLabelRect(tester).topLeft, equals(const Offset(12, 20)));
|
||||
expect(getLabelRect(tester).topLeft, equals(const Offset(16, 20)));
|
||||
expect(getLabelRect(tester).size, equals(labelSize));
|
||||
|
||||
await pumpDecorator(focused: false, empty: false, enabled: false);
|
||||
await tester.pump(kTransitionDuration);
|
||||
expect(getLabelRect(tester).topLeft, equals(const Offset(12, -5.5)));
|
||||
expect(getLabelRect(tester).topLeft, equals(const Offset(16, -5.5)));
|
||||
expect(getLabelRect(tester).size, equals(labelSize * 0.75));
|
||||
|
||||
// Focused and disabled happens with NavigationMode.directional.
|
||||
await pumpDecorator(focused: true, enabled: false);
|
||||
await tester.pump(kTransitionDuration);
|
||||
expect(getLabelRect(tester).topLeft, equals(const Offset(12, 20)));
|
||||
expect(getLabelRect(tester).topLeft, equals(const Offset(16, 20)));
|
||||
expect(getLabelRect(tester).size, equals(labelSize));
|
||||
|
||||
await pumpDecorator(focused: true, empty: false, enabled: false);
|
||||
await tester.pump(kTransitionDuration);
|
||||
expect(getLabelRect(tester).topLeft, equals(const Offset(12, -5.5)));
|
||||
expect(getLabelRect(tester).topLeft, equals(const Offset(16, -5.5)));
|
||||
expect(getLabelRect(tester).size, equals(labelSize * 0.75));
|
||||
});
|
||||
|
||||
@ -5128,9 +5258,8 @@ void main() {
|
||||
const double fullHeight = containerHeight + helperGap + helperHeight; // 76.0
|
||||
const double errorHeight = helperHeight;
|
||||
const double hintHeight = inputHeight;
|
||||
// TODO(bleroux): consider changing this padding because, from the M3 specification, it should be 16.
|
||||
const double helperStartPadding = 12.0;
|
||||
const double counterEndPadding = 12.0;
|
||||
const double helperStartPadding = 16.0;
|
||||
const double counterEndPadding = 16.0;
|
||||
|
||||
group('for filled text field', () {
|
||||
group('when field is enabled', () {
|
||||
|
@ -5529,12 +5529,12 @@ void main() {
|
||||
),
|
||||
);
|
||||
final double iconRight = tester.getTopRight(find.byType(Icon)).dx;
|
||||
// Per https://material.io/go/design-text-fields#text-fields-layout
|
||||
// There's a 16 dps gap between the right edge of the icon and the text field's
|
||||
// container, and the 12dps more padding between the left edge of the container
|
||||
// and the left edge of the input and label.
|
||||
expect(iconRight + 28.0, equals(tester.getTopLeft(find.text('label')).dx));
|
||||
expect(iconRight + 28.0, equals(tester.getTopLeft(find.byType(EditableText)).dx));
|
||||
// There's a 16 pixels gap between the right edge of the icon and the text field's
|
||||
// container, and, per https://material.io/go/design-text-fields#text-fields-layout,
|
||||
// 16 pixels more padding between the left edge of the container and the left edge
|
||||
// of the input and label.
|
||||
expect(iconRight + 16.0 + 16.0, equals(tester.getTopLeft(find.text('label')).dx));
|
||||
expect(iconRight + 16.0 + 16.0, equals(tester.getTopLeft(find.byType(EditableText)).dx));
|
||||
});
|
||||
|
||||
testWidgets('Collapsed hint text placement', (WidgetTester tester) async {
|
||||
|
Loading…
Reference in New Issue
Block a user