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):


![image](https://github.com/user-attachments/assets/fe74de74-6a6d-4a28-9574-a28f3e5c6084)


### After:

The paddings for the input content, the helper and the counter are
compliant with the M3 spec (16 pixels):


![image](https://github.com/user-attachments/assets/602554da-dc55-4c24-b7af-1c4951a301e9)


## 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:
Bruno Leroux 2025-04-22 22:26:27 +02:00 committed by GitHub
parent cb3fd95ff6
commit 69c5526b23
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 235 additions and 65 deletions

View File

@ -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;

View File

@ -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,

View File

@ -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));
},
);

View File

@ -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', () {

View File

@ -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 {