Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
135a8c22
Unverified
Commit
135a8c22
authored
Dec 09, 2020
by
Alex Li
Committed by
GitHub
Dec 09, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Introduce `MaxLengthEnforcement` (#68086)
parent
635dfc3e
Changes
8
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
740 additions
and
105 deletions
+740
-105
text_field.dart
packages/flutter/lib/src/cupertino/text_field.dart
+39
-11
text_field.dart
packages/flutter/lib/src/material/text_field.dart
+47
-15
text_form_field.dart
packages/flutter/lib/src/material/text_form_field.dart
+6
-0
text_formatter.dart
packages/flutter/lib/src/services/text_formatter.dart
+127
-12
text_field_test.dart
packages/flutter/test/cupertino/text_field_test.dart
+122
-0
text_field_test.dart
packages/flutter/test/material/text_field_test.dart
+128
-7
editable_text_test.dart
packages/flutter/test/widgets/editable_text_test.dart
+268
-59
form_test.dart
packages/flutter/test/widgets/form_test.dart
+3
-1
No files found.
packages/flutter/lib/src/cupertino/text_field.dart
View file @
135a8c22
...
...
@@ -260,6 +260,7 @@ class CupertinoTextField extends StatefulWidget {
this
.
expands
=
false
,
this
.
maxLength
,
this
.
maxLengthEnforced
=
true
,
this
.
maxLengthEnforcement
,
this
.
onChanged
,
this
.
onEditingComplete
,
this
.
onSubmitted
,
...
...
@@ -292,6 +293,10 @@ class CupertinoTextField extends StatefulWidget {
smartQuotesType
=
smartQuotesType
??
(
obscureText
?
SmartQuotesType
.
disabled
:
SmartQuotesType
.
enabled
),
assert
(
enableSuggestions
!=
null
),
assert
(
maxLengthEnforced
!=
null
),
assert
(
maxLengthEnforced
||
maxLengthEnforcement
==
null
,
'maxLengthEnforced is deprecated, use only maxLengthEnforcement'
,
),
assert
(
scrollPadding
!=
null
),
assert
(
dragStartBehavior
!=
null
),
assert
(
selectionHeightStyle
!=
null
),
...
...
@@ -402,6 +407,7 @@ class CupertinoTextField extends StatefulWidget {
this
.
expands
=
false
,
this
.
maxLength
,
this
.
maxLengthEnforced
=
true
,
this
.
maxLengthEnforcement
,
this
.
onChanged
,
this
.
onEditingComplete
,
this
.
onSubmitted
,
...
...
@@ -434,6 +440,10 @@ class CupertinoTextField extends StatefulWidget {
smartQuotesType
=
smartQuotesType
??
(
obscureText
?
SmartQuotesType
.
disabled
:
SmartQuotesType
.
enabled
),
assert
(
enableSuggestions
!=
null
),
assert
(
maxLengthEnforced
!=
null
),
assert
(
maxLengthEnforced
||
maxLengthEnforcement
==
null
,
'maxLengthEnforced is deprecated, use only maxLengthEnforcement'
,
),
assert
(
scrollPadding
!=
null
),
assert
(
dragStartBehavior
!=
null
),
assert
(
selectionHeightStyle
!=
null
),
...
...
@@ -619,12 +629,13 @@ class CupertinoTextField extends StatefulWidget {
/// The maximum number of characters (Unicode scalar values) to allow in the
/// text field.
///
/// If set, a character counter will be displayed below the
/// field, showing how many characters have been entered and how many are
/// allowed. After [maxLength] characters have been input, additional input
/// is ignored, unless [maxLengthEnforced] is set to false. The TextField
/// enforces the length with a [LengthLimitingTextInputFormatter], which is
/// evaluated after the supplied [inputFormatters], if any.
/// After [maxLength] characters have been input, additional input
/// is ignored, unless [maxLengthEnforcement] is set to
/// [MaxLengthEnforcement.none].
///
/// The TextField enforces the length with a
/// [LengthLimitingTextInputFormatter], which is evaluated after the supplied
/// [inputFormatters], if any.
///
/// This value must be either null or greater than zero. If set to null
/// (the default), there is no limit to the number of characters allowed.
...
...
@@ -635,14 +646,23 @@ class CupertinoTextField extends StatefulWidget {
/// {@macro flutter.services.lengthLimitingTextInputFormatter.maxLength}
final
int
?
maxLength
;
/// If [maxLength] is set, [maxLengthEnforced] indicates whether or not to
/// enforce the limit.
///
/// If true, prevents the field from allowing more than [maxLength]
/// characters.
///
/// If [maxLength] is set, [maxLengthEnforced] indicates whether or not to
/// enforce the limit, or merely provide a character counter and warning when
/// [maxLength] is exceeded.
final
bool
maxLengthEnforced
;
/// Determines how the [maxLength] limit should be enforced.
///
/// If [MaxLengthEnforcement.none] is set, additional input beyond [maxLength]
/// will not be enforced by the limit.
///
/// {@macro flutter.services.textFormatter.effectiveMaxLengthEnforcement}
///
/// {@macro flutter.services.textFormatter.maxLengthEnforcement}
final
MaxLengthEnforcement
?
maxLengthEnforcement
;
/// {@macro flutter.widgets.editableText.onChanged}
final
ValueChanged
<
String
>?
onChanged
;
...
...
@@ -761,6 +781,7 @@ class CupertinoTextField extends StatefulWidget {
properties
.
add
(
DiagnosticsProperty
<
bool
>(
'expands'
,
expands
,
defaultValue:
false
));
properties
.
add
(
IntProperty
(
'maxLength'
,
maxLength
,
defaultValue:
null
));
properties
.
add
(
FlagProperty
(
'maxLengthEnforced'
,
value:
maxLengthEnforced
,
ifTrue:
'max length enforced'
));
properties
.
add
(
EnumProperty
<
MaxLengthEnforcement
>(
'maxLengthEnforcement'
,
maxLengthEnforcement
,
defaultValue:
null
));
properties
.
add
(
DoubleProperty
(
'cursorWidth'
,
cursorWidth
,
defaultValue:
2.0
));
properties
.
add
(
DoubleProperty
(
'cursorHeight'
,
cursorHeight
,
defaultValue:
null
));
properties
.
add
(
DiagnosticsProperty
<
Radius
>(
'cursorRadius'
,
cursorRadius
,
defaultValue:
null
));
...
...
@@ -783,6 +804,9 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with Restoratio
FocusNode
?
_focusNode
;
FocusNode
get
_effectiveFocusNode
=>
widget
.
focusNode
??
(
_focusNode
??=
FocusNode
());
MaxLengthEnforcement
get
_effectiveMaxLengthEnforcement
=>
widget
.
maxLengthEnforcement
??
LengthLimitingTextInputFormatter
.
inferredDefaultMaxLengthEnforcement
;
bool
_showSelectionHandles
=
false
;
late
_CupertinoTextFieldSelectionGestureDetectorBuilder
_selectionGestureDetectorBuilder
;
...
...
@@ -1030,7 +1054,11 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with Restoratio
final
Offset
cursorOffset
=
Offset
(
_iOSHorizontalCursorOffsetPixels
/
MediaQuery
.
of
(
context
).
devicePixelRatio
,
0
);
final
List
<
TextInputFormatter
>
formatters
=
<
TextInputFormatter
>[
...?
widget
.
inputFormatters
,
if
(
widget
.
maxLength
!=
null
&&
widget
.
maxLengthEnforced
)
LengthLimitingTextInputFormatter
(
widget
.
maxLength
)
if
(
widget
.
maxLength
!=
null
&&
widget
.
maxLengthEnforced
)
LengthLimitingTextInputFormatter
(
widget
.
maxLength
,
maxLengthEnforcement:
_effectiveMaxLengthEnforcement
,
),
];
final
CupertinoThemeData
themeData
=
CupertinoTheme
.
of
(
context
);
...
...
packages/flutter/lib/src/material/text_field.dart
View file @
135a8c22
...
...
@@ -302,10 +302,11 @@ class TextField extends StatefulWidget {
/// [TextField.noMaxLength] then only the current length is displayed.
///
/// After [maxLength] characters have been input, additional input
/// is ignored, unless [maxLengthEnforced] is set to false. The text field
/// enforces the length with a [LengthLimitingTextInputFormatter], which is
/// evaluated after the supplied [inputFormatters], if any. The [maxLength]
/// value must be either null or greater than zero.
/// is ignored, unless [maxLengthEnforcement] is set to
/// [MaxLengthEnforcement.none].
/// The text field enforces the length with a [LengthLimitingTextInputFormatter],
/// which is evaluated after the supplied [inputFormatters], if any.
/// The [maxLength] value must be either null or greater than zero.
///
/// If [maxLengthEnforced] is set to false, then more than [maxLength]
/// characters may be entered, and the error counter and divider will
...
...
@@ -356,6 +357,7 @@ class TextField extends StatefulWidget {
this
.
expands
=
false
,
this
.
maxLength
,
this
.
maxLengthEnforced
=
true
,
this
.
maxLengthEnforcement
,
this
.
onChanged
,
this
.
onEditingComplete
,
this
.
onSubmitted
,
...
...
@@ -391,6 +393,10 @@ class TextField extends StatefulWidget {
assert
(
enableSuggestions
!=
null
),
assert
(
enableInteractiveSelection
!=
null
),
assert
(
maxLengthEnforced
!=
null
),
assert
(
maxLengthEnforced
||
maxLengthEnforcement
==
null
,
'maxLengthEnforced is deprecated, use only maxLengthEnforcement'
,
),
assert
(
scrollPadding
!=
null
),
assert
(
dragStartBehavior
!=
null
),
assert
(
selectionHeightStyle
!=
null
),
...
...
@@ -568,9 +574,11 @@ class TextField extends StatefulWidget {
/// to [TextField.noMaxLength] then only the current character count is displayed.
///
/// After [maxLength] characters have been input, additional input
/// is ignored, unless [maxLengthEnforced] is set to false. The text field
/// enforces the length with a [LengthLimitingTextInputFormatter], which is
/// evaluated after the supplied [inputFormatters], if any.
/// is ignored, unless [maxLengthEnforcement] is set to
/// [MaxLengthEnforcement.none].
///
/// The text field enforces the length with a [LengthLimitingTextInputFormatter],
/// which is evaluated after the supplied [inputFormatters], if any.
///
/// This value must be either null, [TextField.noMaxLength], or greater than 0.
/// If null (the default) then there is no limit to the number of characters
...
...
@@ -580,7 +588,8 @@ class TextField extends StatefulWidget {
/// Whitespace characters (e.g. newline, space, tab) are included in the
/// character count.
///
/// If [maxLengthEnforced] is set to false, then more than [maxLength]
/// If [maxLengthEnforced] is set to false or [maxLengthEnforcement] is
/// [MaxLengthEnforcement.none], then more than [maxLength]
/// characters may be entered, but the error counter and divider will switch
/// to the [decoration]'s [InputDecoration.errorStyle] when the limit is
/// exceeded.
...
...
@@ -588,14 +597,21 @@ class TextField extends StatefulWidget {
/// {@macro flutter.services.lengthLimitingTextInputFormatter.maxLength}
final
int
?
maxLength
;
/// If true, prevents the field from allowing more than [maxLength]
/// characters.
///
/// If [maxLength] is set, [maxLengthEnforced] indicates whether or not to
/// enforce the limit, or merely provide a character counter and warning when
/// [maxLength] is exceeded.
///
/// If true, prevents the field from allowing more than [maxLength]
/// characters.
final
bool
maxLengthEnforced
;
/// Determines how the [maxLength] limit should be enforced.
///
/// {@macro flutter.services.textFormatter.effectiveMaxLengthEnforcement}
///
/// {@macro flutter.services.textFormatter.maxLengthEnforcement}
final
MaxLengthEnforcement
?
maxLengthEnforcement
;
/// {@macro flutter.widgets.editableText.onChanged}
///
/// See also:
...
...
@@ -810,6 +826,7 @@ class TextField extends StatefulWidget {
properties
.
add
(
DiagnosticsProperty
<
bool
>(
'expands'
,
expands
,
defaultValue:
false
));
properties
.
add
(
IntProperty
(
'maxLength'
,
maxLength
,
defaultValue:
null
));
properties
.
add
(
FlagProperty
(
'maxLengthEnforced'
,
value:
maxLengthEnforced
,
defaultValue:
true
,
ifFalse:
'maxLength not enforced'
));
properties
.
add
(
EnumProperty
<
MaxLengthEnforcement
>(
'maxLengthEnforcement'
,
maxLengthEnforcement
,
defaultValue:
null
));
properties
.
add
(
EnumProperty
<
TextInputAction
>(
'textInputAction'
,
textInputAction
,
defaultValue:
null
));
properties
.
add
(
EnumProperty
<
TextCapitalization
>(
'textCapitalization'
,
textCapitalization
,
defaultValue:
TextCapitalization
.
none
));
properties
.
add
(
EnumProperty
<
TextAlign
>(
'textAlign'
,
textAlign
,
defaultValue:
TextAlign
.
start
));
...
...
@@ -835,6 +852,9 @@ class _TextFieldState extends State<TextField> with RestorationMixin implements
FocusNode
?
_focusNode
;
FocusNode
get
_effectiveFocusNode
=>
widget
.
focusNode
??
(
_focusNode
??=
FocusNode
());
MaxLengthEnforcement
get
_effectiveMaxLengthEnforcement
=>
widget
.
maxLengthEnforcement
??
LengthLimitingTextInputFormatter
.
inferredDefaultMaxLengthEnforcement
;
bool
_isHovering
=
false
;
bool
get
needsCounter
=>
widget
.
maxLength
!=
null
...
...
@@ -1095,7 +1115,11 @@ class _TextFieldState extends State<TextField> with RestorationMixin implements
final
FocusNode
focusNode
=
_effectiveFocusNode
;
final
List
<
TextInputFormatter
>
formatters
=
<
TextInputFormatter
>[
...?
widget
.
inputFormatters
,
if
(
widget
.
maxLength
!=
null
&&
widget
.
maxLengthEnforced
)
LengthLimitingTextInputFormatter
(
widget
.
maxLength
)
if
(
widget
.
maxLength
!=
null
&&
widget
.
maxLengthEnforced
)
LengthLimitingTextInputFormatter
(
widget
.
maxLength
,
maxLengthEnforcement:
_effectiveMaxLengthEnforcement
,
),
];
TextSelectionControls
?
textSelectionControls
=
widget
.
selectionControls
;
...
...
@@ -1226,6 +1250,16 @@ class _TextFieldState extends State<TextField> with RestorationMixin implements
},
);
final
int
?
semanticsMaxValueLength
;
if
(
widget
.
maxLengthEnforced
&&
_effectiveMaxLengthEnforcement
!=
MaxLengthEnforcement
.
none
&&
widget
.
maxLength
!=
null
&&
widget
.
maxLength
!
>
0
)
{
semanticsMaxValueLength
=
widget
.
maxLength
;
}
else
{
semanticsMaxValueLength
=
null
;
}
return
MouseRegion
(
cursor:
effectiveMouseCursor
,
onEnter:
(
PointerEnterEvent
event
)
=>
_handleHover
(
true
),
...
...
@@ -1236,9 +1270,7 @@ class _TextFieldState extends State<TextField> with RestorationMixin implements
animation:
controller
,
// changes the _currentLength
builder:
(
BuildContext
context
,
Widget
?
child
)
{
return
Semantics
(
maxValueLength:
widget
.
maxLengthEnforced
&&
widget
.
maxLength
!=
null
&&
widget
.
maxLength
!
>
0
?
widget
.
maxLength
:
null
,
maxValueLength:
semanticsMaxValueLength
,
currentValueLength:
_currentLength
,
onTap:
()
{
if
(!
_effectiveController
.
selection
.
isValid
)
...
...
packages/flutter/lib/src/material/text_form_field.dart
View file @
135a8c22
...
...
@@ -163,6 +163,7 @@ class TextFormField extends FormField<String> {
)
bool
autovalidate
=
false
,
bool
maxLengthEnforced
=
true
,
MaxLengthEnforcement
?
maxLengthEnforcement
,
int
?
maxLines
=
1
,
int
?
minLines
,
bool
expands
=
false
,
...
...
@@ -202,6 +203,10 @@ class TextFormField extends FormField<String> {
'autovalidate and autovalidateMode should not be used together.'
),
assert
(
maxLengthEnforced
!=
null
),
assert
(
maxLengthEnforced
||
maxLengthEnforcement
==
null
,
'maxLengthEnforced is deprecated, use only maxLengthEnforcement'
,
),
assert
(
scrollPadding
!=
null
),
assert
(
maxLines
==
null
||
maxLines
>
0
),
assert
(
minLines
==
null
||
minLines
>
0
),
...
...
@@ -259,6 +264,7 @@ class TextFormField extends FormField<String> {
smartQuotesType:
smartQuotesType
??
(
obscureText
?
SmartQuotesType
.
disabled
:
SmartQuotesType
.
enabled
),
enableSuggestions:
enableSuggestions
,
maxLengthEnforced:
maxLengthEnforced
,
maxLengthEnforcement:
maxLengthEnforcement
,
maxLines:
maxLines
,
minLines:
minLines
,
expands:
expands
,
...
...
packages/flutter/lib/src/services/text_formatter.dart
View file @
135a8c22
...
...
@@ -11,6 +11,49 @@ import 'package:flutter/foundation.dart';
import
'text_editing.dart'
;
import
'text_input.dart'
;
/// {@template flutter.services.textFormatter.maxLengthEnforcement}
/// ### [MaxLengthEnforcement.enforced] versus
/// [MaxLengthEnforcement.truncateAfterCompositionEnds]
///
/// Both [MaxLengthEnforcement.enforced] and
/// [MaxLengthEnforcement.truncateAfterCompositionEnds] make sure the final
/// length of the text does not exceed the max length specified. The difference
/// is that [MaxLengthEnforcement.enforced] truncates all text while
/// [MaxLengthEnforcement.truncateAfterCompositionEnds] allows composing text to
/// exceed the limit. Allowing this "placeholder" composing text to exceed the
/// limit may provide a better user experience on some platforms for entering
/// ideographic characters (e.g. CJK characters) via composing on phonetic
/// keyboards.
///
/// Some input methods (Gboard on Android for example) initiate text composition
/// even for Latin characters, in which case the best experience may be to
/// truncate those composing characters with [MaxLengthEnforcement.enforced].
///
/// In fields that strictly support only a small subset of characters, such as
/// verification code fields, [MaxLengthEnforcement.enforced] may provide the
/// best experience.
/// {@endtemplate}
///
/// See also:
///
/// * [TextField.maxLengthEnforcement] which is used in conjunction with
/// [TextField.maxLength] to limit the length of user input. [TextField] also
/// provides a character counter to provide visual feedback.
enum
MaxLengthEnforcement
{
/// No enforcement applied to the editing value. It's possible to exceed the
/// max length.
none
,
/// Keep the length of the text input from exceeding the max length even when
/// the text has an unfinished composing region.
enforced
,
/// Users can still input text if the current value is composing even after
/// reaching the max length limit. After composing ends, the value will be
/// truncated.
truncateAfterCompositionEnds
,
}
/// A [TextInputFormatter] can be optionally injected into an [EditableText]
/// to provide as-you-type validation and formatting of the text being edited.
///
...
...
@@ -322,8 +365,10 @@ class LengthLimitingTextInputFormatter extends TextInputFormatter {
///
/// The [maxLength] must be null, -1 or greater than zero. If it is null or -1
/// then no limit is enforced.
LengthLimitingTextInputFormatter
(
this
.
maxLength
)
:
assert
(
maxLength
==
null
||
maxLength
==
-
1
||
maxLength
>
0
);
LengthLimitingTextInputFormatter
(
this
.
maxLength
,
{
this
.
maxLengthEnforcement
,
})
:
assert
(
maxLength
==
null
||
maxLength
==
-
1
||
maxLength
>
0
);
/// The limit on the number of user-perceived characters that this formatter
/// will allow.
...
...
@@ -363,6 +408,47 @@ class LengthLimitingTextInputFormatter extends TextInputFormatter {
/// composing is not allowed.
final
int
?
maxLength
;
/// Determines how the [maxLength] limit should be enforced.
///
/// Defaults to [MaxLengthEnforcement.enforced].
///
/// {@macro flutter.services.textFormatter.maxLengthEnforcement}
final
MaxLengthEnforcement
?
maxLengthEnforcement
;
/// Return an effective [MaxLengthEnforcement] according the target platform.
///
/// {@template flutter.services.textFormatter.effectiveMaxLengthEnforcement}
/// ### Platform specific behaviors
///
/// Different platforms follow different behaviors by default, according to
/// their native behavior.
/// * Android, Windows: [MaxLengthEnforcement.enforced]. The native behavior
/// of these platforms is enforced. The composing will be handled by the
/// IME while users are entering CJK characters.
/// * iOS: [MaxLengthEnforcement.truncateAfterCompositionEnds]. iOS has no
/// default behavior and it requires users implement the behavior
/// themselves. Allow the composition to exceed to avoid breaking CJK input.
/// * Web, macOS, linux, fuchsia:
/// [MaxLengthEnforcement.truncateAfterCompositionEnds]. These platforms
/// allow the composition to exceed by default.
/// {@endtemplate}
static
MaxLengthEnforcement
get
inferredDefaultMaxLengthEnforcement
{
if
(
kIsWeb
)
{
return
MaxLengthEnforcement
.
truncateAfterCompositionEnds
;
}
else
{
switch
(
defaultTargetPlatform
)
{
case
TargetPlatform
.
android
:
case
TargetPlatform
.
windows
:
return
MaxLengthEnforcement
.
enforced
;
case
TargetPlatform
.
iOS
:
case
TargetPlatform
.
macOS
:
case
TargetPlatform
.
linux
:
case
TargetPlatform
.
fuchsia
:
return
MaxLengthEnforcement
.
truncateAfterCompositionEnds
;
}
}
}
/// Truncate the given TextEditingValue to maxLength user-perceived
/// characters.
///
...
...
@@ -376,13 +462,19 @@ class LengthLimitingTextInputFormatter extends TextInputFormatter {
iterator
.
expandNext
(
maxLength
);
}
final
String
truncated
=
iterator
.
current
;
return
TextEditingValue
(
text:
truncated
,
selection:
value
.
selection
.
copyWith
(
baseOffset:
math
.
min
(
value
.
selection
.
start
,
truncated
.
length
),
extentOffset:
math
.
min
(
value
.
selection
.
end
,
truncated
.
length
),
),
composing:
TextRange
.
empty
,
composing:
!
value
.
composing
.
isCollapsed
&&
truncated
.
length
>
value
.
composing
.
start
?
TextRange
(
start:
value
.
composing
.
start
,
end:
math
.
min
(
value
.
composing
.
end
,
truncated
.
length
),
)
:
TextRange
.
empty
,
);
}
...
...
@@ -393,20 +485,43 @@ class LengthLimitingTextInputFormatter extends TextInputFormatter {
)
{
final
int
?
maxLength
=
this
.
maxLength
;
if
(
maxLength
==
null
||
maxLength
==
-
1
||
newValue
.
text
.
characters
.
length
<=
maxLength
)
if
(
maxLength
==
null
||
maxLength
==
-
1
||
newValue
.
text
.
characters
.
length
<=
maxLength
)
{
return
newValue
;
}
assert
(
maxLength
>
0
);
// If already at the maximum and tried to enter even more, keep the old
// value.
if
(
oldValue
.
text
.
characters
.
length
==
maxLength
&&
!
oldValue
.
composing
.
isValid
)
{
return
oldValue
;
switch
(
maxLengthEnforcement
??
inferredDefaultMaxLengthEnforcement
)
{
case
MaxLengthEnforcement
.
none
:
return
newValue
;
case
MaxLengthEnforcement
.
enforced
:
// If already at the maximum and tried to enter even more, and has no
// selection, keep the old value.
if
(
oldValue
.
text
.
characters
.
length
==
maxLength
&&
!
oldValue
.
selection
.
isValid
)
{
return
oldValue
;
}
// Enforced to return a truncated value.
return
truncate
(
newValue
,
maxLength
);
case
MaxLengthEnforcement
.
truncateAfterCompositionEnds
:
// If already at the maximum and tried to enter even more, and the old
// value is not composing, keep the old value.
if
(
oldValue
.
text
.
characters
.
length
==
maxLength
&&
!
oldValue
.
composing
.
isValid
)
{
return
oldValue
;
}
// Temporarily exempt `newValue` from the maxLength limit if it has a
// composing text going and no enforcement to the composing value, until
// the composing is finished.
if
(
newValue
.
composing
.
isValid
)
{
return
newValue
;
}
return
truncate
(
newValue
,
maxLength
);
}
// Temporarily exempt `newValue` from the maxLength limit if it has a
// composing text going, until the composing is finished.
return
newValue
.
composing
.
isValid
?
newValue
:
truncate
(
newValue
,
maxLength
);
}
}
...
...
packages/flutter/test/cupertino/text_field_test.dart
View file @
135a8c22
...
...
@@ -4302,4 +4302,126 @@ void main() {
expect
(
formatters
.
isEmpty
,
isTrue
);
});
group
(
'MaxLengthEnforcement'
,
()
{
const
int
maxLength
=
5
;
Future
<
void
>
setupWidget
(
WidgetTester
tester
,
MaxLengthEnforcement
?
enforcement
,
)
async
{
final
Widget
widget
=
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
maxLength:
maxLength
,
maxLengthEnforcement:
enforcement
,
),
),
);
await
tester
.
pumpWidget
(
widget
);
await
tester
.
pumpAndSettle
();
}
testWidgets
(
'using none enforcement.'
,
(
WidgetTester
tester
)
async
{
const
MaxLengthEnforcement
enforcement
=
MaxLengthEnforcement
.
none
;
await
setupWidget
(
tester
,
enforcement
);
final
EditableTextState
state
=
tester
.
state
(
find
.
byType
(
EditableText
));
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'abc'
));
expect
(
state
.
currentTextEditingValue
.
text
,
'abc'
);
expect
(
state
.
currentTextEditingValue
.
composing
,
TextRange
.
empty
);
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'abcdef'
,
composing:
TextRange
(
start:
3
,
end:
6
)));
expect
(
state
.
currentTextEditingValue
.
text
,
'abcdef'
);
expect
(
state
.
currentTextEditingValue
.
composing
,
const
TextRange
(
start:
3
,
end:
6
));
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'abcdef'
));
expect
(
state
.
currentTextEditingValue
.
text
,
'abcdef'
);
expect
(
state
.
currentTextEditingValue
.
composing
,
TextRange
.
empty
);
});
testWidgets
(
'using enforced.'
,
(
WidgetTester
tester
)
async
{
const
MaxLengthEnforcement
enforcement
=
MaxLengthEnforcement
.
enforced
;
await
setupWidget
(
tester
,
enforcement
);
final
EditableTextState
state
=
tester
.
state
(
find
.
byType
(
EditableText
));
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'abc'
));
expect
(
state
.
currentTextEditingValue
.
text
,
'abc'
);
expect
(
state
.
currentTextEditingValue
.
composing
,
TextRange
.
empty
);
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'abcde'
,
composing:
TextRange
(
start:
3
,
end:
5
)));
expect
(
state
.
currentTextEditingValue
.
text
,
'abcde'
);
expect
(
state
.
currentTextEditingValue
.
composing
,
const
TextRange
(
start:
3
,
end:
5
));
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'abcdef'
,
composing:
TextRange
(
start:
3
,
end:
6
)));
expect
(
state
.
currentTextEditingValue
.
text
,
'abcde'
);
expect
(
state
.
currentTextEditingValue
.
composing
,
const
TextRange
(
start:
3
,
end:
5
));
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'abcdef'
));
expect
(
state
.
currentTextEditingValue
.
text
,
'abcde'
);
expect
(
state
.
currentTextEditingValue
.
composing
,
const
TextRange
(
start:
3
,
end:
5
));
});
testWidgets
(
'using truncateAfterCompositionEnds.'
,
(
WidgetTester
tester
)
async
{
const
MaxLengthEnforcement
enforcement
=
MaxLengthEnforcement
.
truncateAfterCompositionEnds
;
await
setupWidget
(
tester
,
enforcement
);
final
EditableTextState
state
=
tester
.
state
(
find
.
byType
(
EditableText
));
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'abc'
));
expect
(
state
.
currentTextEditingValue
.
text
,
'abc'
);
expect
(
state
.
currentTextEditingValue
.
composing
,
TextRange
.
empty
);
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'abcde'
,
composing:
TextRange
(
start:
3
,
end:
5
)));
expect
(
state
.
currentTextEditingValue
.
text
,
'abcde'
);
expect
(
state
.
currentTextEditingValue
.
composing
,
const
TextRange
(
start:
3
,
end:
5
));
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'abcdef'
,
composing:
TextRange
(
start:
3
,
end:
6
)));
expect
(
state
.
currentTextEditingValue
.
text
,
'abcdef'
);
expect
(
state
.
currentTextEditingValue
.
composing
,
const
TextRange
(
start:
3
,
end:
6
));
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'abcdef'
));
expect
(
state
.
currentTextEditingValue
.
text
,
'abcde'
);
expect
(
state
.
currentTextEditingValue
.
composing
,
TextRange
.
empty
);
});
testWidgets
(
'using default behavior for different platforms.'
,
(
WidgetTester
tester
)
async
{
await
setupWidget
(
tester
,
null
);
final
EditableTextState
state
=
tester
.
state
(
find
.
byType
(
EditableText
));
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'侬好啊'
));
expect
(
state
.
currentTextEditingValue
.
text
,
'侬好啊'
);
expect
(
state
.
currentTextEditingValue
.
composing
,
TextRange
.
empty
);
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'侬好啊旁友'
,
composing:
TextRange
(
start:
3
,
end:
5
)));
expect
(
state
.
currentTextEditingValue
.
text
,
'侬好啊旁友'
);
expect
(
state
.
currentTextEditingValue
.
composing
,
const
TextRange
(
start:
3
,
end:
5
));
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'侬好啊旁友们'
,
composing:
TextRange
(
start:
3
,
end:
6
)));
if
(
kIsWeb
||
defaultTargetPlatform
==
TargetPlatform
.
iOS
||
defaultTargetPlatform
==
TargetPlatform
.
macOS
||
defaultTargetPlatform
==
TargetPlatform
.
linux
||
defaultTargetPlatform
==
TargetPlatform
.
fuchsia
)
{
expect
(
state
.
currentTextEditingValue
.
text
,
'侬好啊旁友们'
);
expect
(
state
.
currentTextEditingValue
.
composing
,
const
TextRange
(
start:
3
,
end:
6
));
}
else
{
expect
(
state
.
currentTextEditingValue
.
text
,
'侬好啊旁友'
);
expect
(
state
.
currentTextEditingValue
.
composing
,
const
TextRange
(
start:
3
,
end:
5
));
}
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'侬好啊旁友'
));
expect
(
state
.
currentTextEditingValue
.
text
,
'侬好啊旁友'
);
expect
(
state
.
currentTextEditingValue
.
composing
,
TextRange
.
empty
);
});
});
}
packages/flutter/test/material/text_field_test.dart
View file @
135a8c22
...
...
@@ -2196,7 +2196,7 @@ void main() {
TextFormField
(
key:
textFieldKey
,
maxLength:
3
,
maxLengthEnforce
d:
fals
e
,
maxLengthEnforce
ment:
MaxLengthEnforcement
.
non
e
,
decoration:
InputDecoration
(
counterText:
''
,
errorText:
errorText
,
...
...
@@ -3671,7 +3671,7 @@ void main() {
child:
TextField
(
controller:
textController
,
maxLength:
10
,
maxLengthEnforce
d:
fals
e
,
maxLengthEnforce
ment:
MaxLengthEnforcement
.
non
e
,
),
));
...
...
@@ -3688,7 +3688,7 @@ void main() {
decoration:
const
InputDecoration
(
errorStyle:
testStyle
),
controller:
textController
,
maxLength:
10
,
maxLengthEnforce
d:
fals
e
,
maxLengthEnforce
ment:
MaxLengthEnforcement
.
non
e
,
),
));
...
...
@@ -3718,7 +3718,7 @@ void main() {
decoration:
const
InputDecoration
(
errorStyle:
testStyle
),
controller:
textController
,
maxLength:
10
,
maxLengthEnforce
d:
fals
e
,
maxLengthEnforce
ment:
MaxLengthEnforcement
.
non
e
,
),
));
...
...
@@ -3748,7 +3748,7 @@ void main() {
decoration:
const
InputDecoration
(
errorStyle:
testStyle
),
controller:
textController
,
maxLength:
10
,
maxLengthEnforce
d:
fals
e
,
maxLengthEnforce
ment:
MaxLengthEnforcement
.
non
e
,
),
));
...
...
@@ -7504,7 +7504,7 @@ void main() {
autocorrect:
false
,
maxLines:
10
,
maxLength:
100
,
maxLengthEnforce
d:
fals
e
,
maxLengthEnforce
ment:
MaxLengthEnforcement
.
non
e
,
smartDashesType:
SmartDashesType
.
disabled
,
smartQuotesType:
SmartQuotesType
.
disabled
,
enabled:
false
,
...
...
@@ -7532,7 +7532,7 @@ void main() {
'smartQuotesType: disabled'
,
'maxLines: 10'
,
'maxLength: 100'
,
'maxLength
not enforced
'
,
'maxLength
Enforcement: none
'
,
'textInputAction: done'
,
'textAlign: end'
,
'textDirection: ltr'
,
...
...
@@ -8745,4 +8745,125 @@ void main() {
// The label will always float above the content.
expect
(
tester
.
getTopLeft
(
find
.
text
(
'Label'
)).
dy
,
12.0
);
});
group
(
'MaxLengthEnforcement'
,
()
{
const
int
maxLength
=
5
;
Future
<
void
>
setupWidget
(
WidgetTester
tester
,
MaxLengthEnforcement
?
enforcement
,
)
async
{
final
Widget
widget
=
MaterialApp
(
home:
Material
(
child:
TextField
(
maxLength:
maxLength
,
maxLengthEnforcement:
enforcement
,
),
),
);
await
tester
.
pumpWidget
(
widget
);
await
tester
.
pumpAndSettle
();
}
testWidgets
(
'using none enforcement.'
,
(
WidgetTester
tester
)
async
{
const
MaxLengthEnforcement
enforcement
=
MaxLengthEnforcement
.
none
;
await
setupWidget
(
tester
,
enforcement
);
final
EditableTextState
state
=
tester
.
state
(
find
.
byType
(
EditableText
));
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'abc'
));
expect
(
state
.
currentTextEditingValue
.
text
,
'abc'
);
expect
(
state
.
currentTextEditingValue
.
composing
,
TextRange
.
empty
);
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'abcdef'
,
composing:
TextRange
(
start:
3
,
end:
6
)));
expect
(
state
.
currentTextEditingValue
.
text
,
'abcdef'
);
expect
(
state
.
currentTextEditingValue
.
composing
,
const
TextRange
(
start:
3
,
end:
6
));
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'abcdef'
));
expect
(
state
.
currentTextEditingValue
.
text
,
'abcdef'
);
expect
(
state
.
currentTextEditingValue
.
composing
,
TextRange
.
empty
);
});
testWidgets
(
'using enforced.'
,
(
WidgetTester
tester
)
async
{
const
MaxLengthEnforcement
enforcement
=
MaxLengthEnforcement
.
enforced
;
await
setupWidget
(
tester
,
enforcement
);
final
EditableTextState
state
=
tester
.
state
(
find
.
byType
(
EditableText
));
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'abc'
));
expect
(
state
.
currentTextEditingValue
.
text
,
'abc'
);
expect
(
state
.
currentTextEditingValue
.
composing
,
TextRange
.
empty
);
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'abcde'
,
composing:
TextRange
(
start:
3
,
end:
5
)));
expect
(
state
.
currentTextEditingValue
.
text
,
'abcde'
);
expect
(
state
.
currentTextEditingValue
.
composing
,
const
TextRange
(
start:
3
,
end:
5
));
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'abcdef'
,
composing:
TextRange
(
start:
3
,
end:
6
)));
expect
(
state
.
currentTextEditingValue
.
text
,
'abcde'
);
expect
(
state
.
currentTextEditingValue
.
composing
,
const
TextRange
(
start:
3
,
end:
5
));
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'abcdef'
));
expect
(
state
.
currentTextEditingValue
.
text
,
'abcde'
);
expect
(
state
.
currentTextEditingValue
.
composing
,
const
TextRange
(
start:
3
,
end:
5
));
});
testWidgets
(
'using truncateAfterCompositionEnds.'
,
(
WidgetTester
tester
)
async
{
const
MaxLengthEnforcement
enforcement
=
MaxLengthEnforcement
.
truncateAfterCompositionEnds
;
await
setupWidget
(
tester
,
enforcement
);
final
EditableTextState
state
=
tester
.
state
(
find
.
byType
(
EditableText
));
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'abc'
));
expect
(
state
.
currentTextEditingValue
.
text
,
'abc'
);
expect
(
state
.
currentTextEditingValue
.
composing
,
TextRange
.
empty
);
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'abcde'
,
composing:
TextRange
(
start:
3
,
end:
5
)));
expect
(
state
.
currentTextEditingValue
.
text
,
'abcde'
);
expect
(
state
.
currentTextEditingValue
.
composing
,
const
TextRange
(
start:
3
,
end:
5
));
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'abcdef'
,
composing:
TextRange
(
start:
3
,
end:
6
)));
expect
(
state
.
currentTextEditingValue
.
text
,
'abcdef'
);
expect
(
state
.
currentTextEditingValue
.
composing
,
const
TextRange
(
start:
3
,
end:
6
));
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'abcdef'
));
expect
(
state
.
currentTextEditingValue
.
text
,
'abcde'
);
expect
(
state
.
currentTextEditingValue
.
composing
,
TextRange
.
empty
);
});
testWidgets
(
'using default behavior for different platforms.'
,
(
WidgetTester
tester
)
async
{
await
setupWidget
(
tester
,
null
);
final
EditableTextState
state
=
tester
.
state
(
find
.
byType
(
EditableText
));
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'侬好啊'
));
expect
(
state
.
currentTextEditingValue
.
text
,
'侬好啊'
);
expect
(
state
.
currentTextEditingValue
.
composing
,
TextRange
.
empty
);
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'侬好啊旁友'
,
composing:
TextRange
(
start:
3
,
end:
5
)));
expect
(
state
.
currentTextEditingValue
.
text
,
'侬好啊旁友'
);
expect
(
state
.
currentTextEditingValue
.
composing
,
const
TextRange
(
start:
3
,
end:
5
));
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'侬好啊旁友们'
,
composing:
TextRange
(
start:
3
,
end:
6
)));
if
(
kIsWeb
||
defaultTargetPlatform
==
TargetPlatform
.
iOS
||
defaultTargetPlatform
==
TargetPlatform
.
macOS
||
defaultTargetPlatform
==
TargetPlatform
.
linux
||
defaultTargetPlatform
==
TargetPlatform
.
fuchsia
)
{
expect
(
state
.
currentTextEditingValue
.
text
,
'侬好啊旁友们'
);
expect
(
state
.
currentTextEditingValue
.
composing
,
const
TextRange
(
start:
3
,
end:
6
));
}
else
{
expect
(
state
.
currentTextEditingValue
.
text
,
'侬好啊旁友'
);
expect
(
state
.
currentTextEditingValue
.
composing
,
const
TextRange
(
start:
3
,
end:
5
));
}
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'侬好啊旁友'
));
expect
(
state
.
currentTextEditingValue
.
text
,
'侬好啊旁友'
);
expect
(
state
.
currentTextEditingValue
.
composing
,
TextRange
.
empty
);
});
});
}
packages/flutter/test/widgets/editable_text_test.dart
View file @
135a8c22
This diff is collapsed.
Click to expand it.
packages/flutter/test/widgets/form_test.dart
View file @
135a8c22
...
...
@@ -4,6 +4,7 @@
import
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/services.dart'
;
void
main
(
)
{
testWidgets
(
'onSaved callback is called'
,
(
WidgetTester
tester
)
async
{
...
...
@@ -848,7 +849,7 @@ void main() {
expect
(()
=>
builder
(),
throwsAssertionError
);
});
// Regression test for https://github.com/flutter/flutter/issues/6
5374
.
// Regression test for https://github.com/flutter/flutter/issues/6
3753
.
testWidgets
(
'Validate form should return correct validation if the value is composing'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
<
FormState
>
formKey
=
GlobalKey
<
FormState
>();
String
?
fieldValue
;
...
...
@@ -864,6 +865,7 @@ void main() {
key:
formKey
,
child:
TextFormField
(
maxLength:
5
,
maxLengthEnforcement:
MaxLengthEnforcement
.
truncateAfterCompositionEnds
,
onSaved:
(
String
?
value
)
{
fieldValue
=
value
;
},
validator:
(
String
?
value
)
=>
(
value
!=
null
&&
value
.
length
>
5
)
?
'Exceeded'
:
null
,
),
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment