Skip to content

Commit 0efbecb

Browse files
blerouxIvoneDjaja
authored andcommitted
Allow label to be used to compute InputDecorator Intrinsic width (flutter#178101)
## Description This PR adds `InputDecorator.maintainLabelSize` (similar to `InputDecorator.maintainHintSize`) to allow the label to be used in the intrinsic width calculation (if could be used for the intrinsic height calculation later if needed). I opted for this flag (and defaulting to false) because changing the default calculation would probably break various usages. See flutter#178099 (comment) for why this change will be helpful to simplify and fix DropdownMenu implementation. ## Before The label might be cut off: <img width="126" height="71" alt="Screenshot 2025-11-05 at 20 16 43" src="https://github.com/user-attachments/assets/61d9f817-5c58-43f9-9307-976f9c124ec7" /> ## After The label is entirely visible because it is part of the intrinsic width calculation: <img width="126" height="71" alt="Screenshot 2025-11-05 at 20 16 09" src="https://github.com/user-attachments/assets/47360e17-3cde-4f05-8a6b-cc9e86644ffc" /> ## Related Issue Fixes [DropdownMenu menu panel does not close when pressing ESC and requestFocusOnTap is false](flutter#177993) Part of flutter#123797 ## Tests - Adds 4 tests. - Updates 1 non-related test where I spotted some nits.
1 parent bd6f555 commit 0efbecb

File tree

2 files changed

+129
-14
lines changed

2 files changed

+129
-14
lines changed

packages/flutter/lib/src/material/input_decorator.dart

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,7 @@ class _Decoration {
596596
required this.visualDensity,
597597
required this.inputGap,
598598
required this.maintainHintSize,
599+
required this.maintainLabelSize,
599600
this.icon,
600601
this.input,
601602
this.label,
@@ -622,6 +623,7 @@ class _Decoration {
622623
final VisualDensity visualDensity;
623624
final double inputGap;
624625
final bool maintainHintSize;
626+
final bool maintainLabelSize;
625627
final Widget? icon;
626628
final Widget? input;
627629
final Widget? label;
@@ -656,6 +658,7 @@ class _Decoration {
656658
other.visualDensity == visualDensity &&
657659
other.inputGap == inputGap &&
658660
other.maintainHintSize == maintainHintSize &&
661+
other.maintainLabelSize == maintainLabelSize &&
659662
other.icon == icon &&
660663
other.input == input &&
661664
other.label == label &&
@@ -683,14 +686,14 @@ class _Decoration {
683686
visualDensity,
684687
inputGap,
685688
maintainHintSize,
689+
maintainLabelSize,
686690
icon,
687691
input,
688692
label,
689693
hint,
690694
prefix,
691695
suffix,
692-
prefixIcon,
693-
Object.hash(suffixIcon, helperError, counter, container),
696+
Object.hash(prefixIcon, suffixIcon, helperError, counter, container),
694697
);
695698
}
696699

@@ -1212,9 +1215,12 @@ class _RenderDecoration extends RenderBox
12121215

12131216
@override
12141217
double computeMinIntrinsicWidth(double height) {
1215-
final double contentWidth = decoration.isEmpty || decoration.maintainHintSize
1218+
final double inputWidth = decoration.isEmpty || decoration.maintainHintSize
12161219
? math.max(_minWidth(input, height), _minWidth(hint, height))
12171220
: _minWidth(input, height);
1221+
final double contentWidth = decoration.maintainLabelSize
1222+
? math.max(inputWidth, _minWidth(label, height))
1223+
: inputWidth;
12181224
return _minWidth(icon, height) +
12191225
(prefixIcon != null ? prefixToInputGap : contentPadding.start + decoration.inputGap) +
12201226
_minWidth(prefixIcon, height) +
@@ -1227,9 +1233,12 @@ class _RenderDecoration extends RenderBox
12271233

12281234
@override
12291235
double computeMaxIntrinsicWidth(double height) {
1230-
final double contentWidth = decoration.isEmpty || decoration.maintainHintSize
1236+
final double inputWidth = decoration.isEmpty || decoration.maintainHintSize
12311237
? math.max(_maxWidth(input, height), _maxWidth(hint, height))
12321238
: _maxWidth(input, height);
1239+
final double contentWidth = decoration.maintainLabelSize
1240+
? math.max(inputWidth, _maxWidth(label, height))
1241+
: inputWidth;
12331242
return _maxWidth(icon, height) +
12341243
(prefixIcon != null ? prefixToInputGap : contentPadding.start + decoration.inputGap) +
12351244
_maxWidth(prefixIcon, height) +
@@ -2651,6 +2660,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
26512660
isEmpty: isEmpty,
26522661
visualDensity: visualDensity,
26532662
maintainHintSize: maintainHintSize,
2663+
maintainLabelSize: decoration.maintainLabelSize,
26542664
icon: icon,
26552665
input: input,
26562666
label: label,
@@ -2787,6 +2797,7 @@ class InputDecoration {
27872797
)
27882798
this.maintainHintHeight = true,
27892799
this.maintainHintSize = true,
2800+
this.maintainLabelSize = false,
27902801
this.error,
27912802
this.errorText,
27922803
this.errorStyle,
@@ -2884,6 +2895,7 @@ class InputDecoration {
28842895
)
28852896
this.maintainHintHeight = true,
28862897
this.maintainHintSize = true,
2898+
this.maintainLabelSize = false,
28872899
this.filled = false,
28882900
this.fillColor,
28892901
this.focusColor,
@@ -3174,14 +3186,23 @@ class InputDecoration {
31743186
final bool maintainHintHeight;
31753187

31763188
/// Whether the input field's size should always be greater than or equal to
3177-
/// the size of the [hintText], even if the [hintText] is not visible.
3189+
/// the size of the [hint] or [hintText], even if the [hint] or [hintText] are not visible.
31783190
///
3179-
/// The [InputDecorator] widget ignores [hintText] during layout when
3180-
/// it's not visible, if this flag is set to false.
3191+
/// The [InputDecorator] widget ignores [hint] and [hintText] during layout when
3192+
/// they are not visible, if this flag is set to false.
31813193
///
31823194
/// Defaults to true.
31833195
final bool maintainHintSize;
31843196

3197+
/// Whether the input field's size should always be greater than or equal to
3198+
/// the size of the [label] or [labelText], even if the [label] or [labelText] are not visible.
3199+
///
3200+
/// The [InputDecorator] widget ignores [label] and [labelText] during layout when
3201+
/// this flag is set to false.
3202+
///
3203+
/// Defaults to false for compatibility reason.
3204+
final bool maintainLabelSize;
3205+
31853206
/// Optional widget that appears below the [InputDecorator.child] and the border.
31863207
///
31873208
/// If non-null, the border's color animates to red and the [helperText] is not shown.
@@ -3893,6 +3914,7 @@ class InputDecoration {
38933914
int? hintMaxLines,
38943915
bool? maintainHintHeight,
38953916
bool? maintainHintSize,
3917+
bool? maintainLabelSize,
38963918
Widget? error,
38973919
String? errorText,
38983920
TextStyle? errorStyle,
@@ -3953,6 +3975,7 @@ class InputDecoration {
39533975
hintFadeDuration: hintFadeDuration ?? this.hintFadeDuration,
39543976
maintainHintHeight: maintainHintHeight ?? this.maintainHintHeight,
39553977
maintainHintSize: maintainHintSize ?? this.maintainHintSize,
3978+
maintainLabelSize: maintainLabelSize ?? this.maintainLabelSize,
39563979
error: error ?? this.error,
39573980
errorText: errorText ?? this.errorText,
39583981
errorStyle: errorStyle ?? this.errorStyle,
@@ -4077,6 +4100,7 @@ class InputDecoration {
40774100
other.hintFadeDuration == hintFadeDuration &&
40784101
other.maintainHintHeight == maintainHintHeight &&
40794102
other.maintainHintSize == maintainHintSize &&
4103+
other.maintainLabelSize == maintainLabelSize &&
40804104
other.error == error &&
40814105
other.errorText == errorText &&
40824106
other.errorStyle == errorStyle &&
@@ -4139,6 +4163,7 @@ class InputDecoration {
41394163
hintFadeDuration,
41404164
maintainHintHeight,
41414165
maintainHintSize,
4166+
maintainLabelSize,
41424167
error,
41434168
errorText,
41444169
errorStyle,
@@ -4199,6 +4224,7 @@ class InputDecoration {
41994224
if (hintFadeDuration != null) 'hintFadeDuration: "$hintFadeDuration"',
42004225
if (!maintainHintHeight) 'maintainHintHeight: false',
42014226
if (!maintainHintSize) 'maintainHintSize: false',
4227+
if (maintainLabelSize) 'maintainLabelSize: true',
42024228
if (error != null) 'error: "$error"',
42034229
if (errorText != null) 'errorText: "$errorText"',
42044230
if (errorStyle != null) 'errorStyle: "$errorStyle"',

packages/flutter/test/material/input_decorator_test.dart

Lines changed: 96 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9411,26 +9411,115 @@ void main() {
94119411
);
94129412

94139413
// Regression test for https://github.com/flutter/flutter/issues/93337.
9414+
testWidgets('depends on hint width when decorator is not empty and maintainHintSize is true', (
9415+
WidgetTester tester,
9416+
) async {
9417+
const InputDecoration decorationWithHint = InputDecoration(
9418+
contentPadding: EdgeInsets.zero,
9419+
hintText: 'Hint',
9420+
);
9421+
const double contentWidth = 20.0;
9422+
9423+
await tester.pumpWidget(
9424+
buildInputDecorator(
9425+
decoration: decorationWithHint,
9426+
useIntrinsicWidth: true,
9427+
child: const SizedBox(width: contentWidth),
9428+
),
9429+
);
9430+
9431+
const double hintTextWidth = 66.0;
9432+
expect(getDecoratorRect(tester).width, hintTextWidth);
9433+
});
9434+
94149435
testWidgets(
9415-
'depends on content width when decorator is not empty and maintainHintSize is true',
9436+
'does not depend on label width when decorator is empty and maintainLabelSize is false',
94169437
(WidgetTester tester) async {
9417-
const InputDecoration decorationWithHint = InputDecoration(
9438+
const double labelWidth = 30;
9439+
const InputDecoration decorationWithLabel = InputDecoration(
94189440
contentPadding: EdgeInsets.zero,
9419-
hintText: 'Hint',
9441+
label: SizedBox(width: labelWidth),
9442+
);
9443+
9444+
await tester.pumpWidget(
9445+
buildInputDecorator(
9446+
decoration: decorationWithLabel,
9447+
useIntrinsicWidth: true,
9448+
isEmpty: true,
9449+
child: const SizedBox.shrink(),
9450+
),
9451+
);
9452+
9453+
// The label width is ignored even if larger than the content width.
9454+
expect(getDecoratorRect(tester).width, 0);
9455+
},
9456+
);
9457+
9458+
testWidgets('depends on label width when decorator is empty and maintainLabelSize is true', (
9459+
WidgetTester tester,
9460+
) async {
9461+
const double labelWidth = 30;
9462+
const InputDecoration decorationWithLabel = InputDecoration(
9463+
contentPadding: EdgeInsets.zero,
9464+
label: SizedBox(width: labelWidth),
9465+
maintainLabelSize: true,
9466+
);
9467+
9468+
await tester.pumpWidget(
9469+
buildInputDecorator(
9470+
decoration: decorationWithLabel,
9471+
useIntrinsicWidth: true,
9472+
isEmpty: true,
9473+
child: const SizedBox.shrink(),
9474+
),
9475+
);
9476+
9477+
expect(getDecoratorRect(tester).width, labelWidth);
9478+
});
9479+
9480+
testWidgets(
9481+
'does not depend on label width when decorator is not empty and maintainLabelSize is false',
9482+
(WidgetTester tester) async {
9483+
const double contentWidth = 20.0;
9484+
const double labelWidth = 30;
9485+
const InputDecoration decorationWithLabel = InputDecoration(
9486+
contentPadding: EdgeInsets.zero,
9487+
label: SizedBox(width: labelWidth),
9488+
);
9489+
9490+
await tester.pumpWidget(
9491+
buildInputDecorator(
9492+
decoration: decorationWithLabel,
9493+
useIntrinsicWidth: true,
9494+
child: const SizedBox(width: contentWidth),
9495+
),
94209496
);
9497+
9498+
// The label width is ignored even if larger than the content width.
9499+
expect(getDecoratorRect(tester).width, contentWidth);
9500+
},
9501+
);
9502+
9503+
testWidgets(
9504+
'depends on label width when decorator is not empty and maintainLabelSize is true',
9505+
(WidgetTester tester) async {
94219506
const double contentWidth = 20.0;
9507+
const double labelWidth = 30;
9508+
const InputDecoration decorationWithLabel = InputDecoration(
9509+
contentPadding: EdgeInsets.zero,
9510+
label: SizedBox(width: labelWidth),
9511+
maintainLabelSize: true,
9512+
);
94229513

94239514
await tester.pumpWidget(
94249515
buildInputDecorator(
9425-
decoration: decorationWithHint,
9516+
decoration: decorationWithLabel,
94269517
useIntrinsicWidth: true,
94279518
child: const SizedBox(width: contentWidth),
94289519
),
94299520
);
94309521

9431-
// The hint width is ignored even if larger than the content width.
9432-
const double hintTextWidth = 66.0;
9433-
expect(getDecoratorRect(tester).width, hintTextWidth);
9522+
expect(getDecoratorRect(tester).width, labelWidth);
94349523
},
94359524
);
94369525
});

0 commit comments

Comments
 (0)