1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | /// @docImport 'input_border.dart'; |
6 | /// @docImport 'material.dart'; |
7 | /// @docImport 'scaffold.dart'; |
8 | /// @docImport 'text_form_field.dart'; |
9 | /// @docImport 'text_theme.dart'; |
10 | library; |
11 | |
12 | import 'dart:ui' as ui show BoxHeightStyle, BoxWidthStyle; |
13 | |
14 | import 'package:flutter/cupertino.dart'; |
15 | import 'package:flutter/foundation.dart'; |
16 | import 'package:flutter/gestures.dart'; |
17 | import 'package:flutter/rendering.dart'; |
18 | import 'package:flutter/services.dart'; |
19 | |
20 | import 'adaptive_text_selection_toolbar.dart'; |
21 | import 'color_scheme.dart'; |
22 | import 'colors.dart'; |
23 | import 'debug.dart'; |
24 | import 'desktop_text_selection.dart'; |
25 | import 'input_decorator.dart'; |
26 | import 'magnifier.dart'; |
27 | import 'material_localizations.dart'; |
28 | import 'material_state.dart'; |
29 | import 'selectable_text.dart' show iOSHorizontalOffset; |
30 | import 'spell_check_suggestions_toolbar.dart'; |
31 | import 'text_selection.dart'; |
32 | import 'theme.dart'; |
33 | |
34 | export 'package:flutter/services.dart' |
35 | show SmartDashesType, SmartQuotesType, TextCapitalization, TextInputAction, TextInputType; |
36 | |
37 | // Examples can assume: |
38 | // late BuildContext context; |
39 | // late FocusNode myFocusNode; |
40 | |
41 | /// Signature for the [TextField.buildCounter] callback. |
42 | typedef InputCounterWidgetBuilder = |
43 | Widget? Function( |
44 | /// The build context for the TextField. |
45 | BuildContext context, { |
46 | |
47 | /// The length of the string currently in the input. |
48 | required int currentLength, |
49 | |
50 | /// The maximum string length that can be entered into the TextField. |
51 | required int? maxLength, |
52 | |
53 | /// Whether or not the TextField is currently focused. Mainly provided for |
54 | /// the [liveRegion] parameter in the [Semantics] widget for accessibility. |
55 | required bool isFocused, |
56 | }); |
57 | |
58 | class _TextFieldSelectionGestureDetectorBuilder extends TextSelectionGestureDetectorBuilder { |
59 | _TextFieldSelectionGestureDetectorBuilder({required _TextFieldState state}) |
60 | : _state = state, |
61 | super(delegate: state); |
62 | |
63 | final _TextFieldState _state; |
64 | |
65 | @override |
66 | bool get onUserTapAlwaysCalled => _state.widget.onTapAlwaysCalled; |
67 | |
68 | @override |
69 | void onUserTap() { |
70 | _state.widget.onTap?.call(); |
71 | } |
72 | } |
73 | |
74 | /// A Material Design text field. |
75 | /// |
76 | /// A text field lets the user enter text, either with hardware keyboard or with |
77 | /// an onscreen keyboard. |
78 | /// |
79 | /// The text field calls the [onChanged] callback whenever the user changes the |
80 | /// text in the field. If the user indicates that they are done typing in the |
81 | /// field (e.g., by pressing a button on the soft keyboard), the text field |
82 | /// calls the [onSubmitted] callback. |
83 | /// |
84 | /// To control the text that is displayed in the text field, use the |
85 | /// [controller]. For example, to set the initial value of the text field, use |
86 | /// a [controller] that already contains some text. The [controller] can also |
87 | /// control the selection and composing region (and to observe changes to the |
88 | /// text, selection, and composing region). |
89 | /// |
90 | /// By default, a text field has a [decoration] that draws a divider below the |
91 | /// text field. You can use the [decoration] property to control the decoration, |
92 | /// for example by adding a label or an icon. If you set the [decoration] |
93 | /// property to null, the decoration will be removed entirely, including the |
94 | /// extra padding introduced by the decoration to save space for the labels. |
95 | /// |
96 | /// If [decoration] is non-null (which is the default), the text field requires |
97 | /// one of its ancestors to be a [Material] widget. |
98 | /// |
99 | /// To integrate the [TextField] into a [Form] with other [FormField] widgets, |
100 | /// consider using [TextFormField]. |
101 | /// |
102 | /// {@template flutter.material.textfield.wantKeepAlive} |
103 | /// When the widget has focus, it will prevent itself from disposing via its |
104 | /// underlying [EditableText]'s [AutomaticKeepAliveClientMixin.wantKeepAlive] in |
105 | /// order to avoid losing the selection. Removing the focus will allow it to be |
106 | /// disposed. |
107 | /// {@endtemplate} |
108 | /// |
109 | /// Remember to call [TextEditingController.dispose] on the [TextEditingController] |
110 | /// when it is no longer needed. This will ensure we discard any resources used |
111 | /// by the object. |
112 | /// |
113 | /// If this field is part of a scrolling container that lazily constructs its |
114 | /// children, like a [ListView] or a [CustomScrollView], then a [controller] |
115 | /// should be specified. The controller's lifetime should be managed by a |
116 | /// stateful widget ancestor of the scrolling container. |
117 | /// |
118 | /// ## Obscured Input |
119 | /// |
120 | /// {@tool dartpad} |
121 | /// This example shows how to create a [TextField] that will obscure input. The |
122 | /// [InputDecoration] surrounds the field in a border using [OutlineInputBorder] |
123 | /// and adds a label. |
124 | /// |
125 | /// ** See code in examples/api/lib/material/text_field/text_field.0.dart ** |
126 | /// {@end-tool} |
127 | /// |
128 | /// ## Reading values |
129 | /// |
130 | /// A common way to read a value from a TextField is to use the [onSubmitted] |
131 | /// callback. This callback is applied to the text field's current value when |
132 | /// the user finishes editing. |
133 | /// |
134 | /// {@tool dartpad} |
135 | /// This sample shows how to get a value from a TextField via the [onSubmitted] |
136 | /// callback. |
137 | /// |
138 | /// ** See code in examples/api/lib/material/text_field/text_field.1.dart ** |
139 | /// {@end-tool} |
140 | /// |
141 | /// {@macro flutter.widgets.EditableText.lifeCycle} |
142 | /// |
143 | /// For most applications the [onSubmitted] callback will be sufficient for |
144 | /// reacting to user input. |
145 | /// |
146 | /// The [onEditingComplete] callback also runs when the user finishes editing. |
147 | /// It's different from [onSubmitted] because it has a default value which |
148 | /// updates the text controller and yields the keyboard focus. Applications that |
149 | /// require different behavior can override the default [onEditingComplete] |
150 | /// callback. |
151 | /// |
152 | /// Keep in mind you can also always read the current string from a TextField's |
153 | /// [TextEditingController] using [TextEditingController.text]. |
154 | /// |
155 | /// ## Handling emojis and other complex characters |
156 | /// {@macro flutter.widgets.EditableText.onChanged} |
157 | /// |
158 | /// In the live Dartpad example above, try typing the emoji 👨👩👦 |
159 | /// into the field and submitting. Because the example code measures the length |
160 | /// with `value.characters.length`, the emoji is correctly counted as a single |
161 | /// character. |
162 | /// |
163 | /// {@macro flutter.widgets.editableText.showCaretOnScreen} |
164 | /// |
165 | /// {@macro flutter.widgets.editableText.accessibility} |
166 | /// |
167 | /// {@tool dartpad} |
168 | /// This sample shows how to style a text field to match a filled or outlined |
169 | /// Material Design 3 text field. |
170 | /// |
171 | /// ** See code in examples/api/lib/material/text_field/text_field.2.dart ** |
172 | /// {@end-tool} |
173 | /// |
174 | /// ## Scrolling Considerations |
175 | /// |
176 | /// If this [TextField] is not a descendant of [Scaffold] and is being used |
177 | /// within a [Scrollable] or nested [Scrollable]s, consider placing a |
178 | /// [ScrollNotificationObserver] above the root [Scrollable] that contains this |
179 | /// [TextField] to ensure proper scroll coordination for [TextField] and its |
180 | /// components like [TextSelectionOverlay]. |
181 | /// |
182 | /// See also: |
183 | /// |
184 | /// * [TextFormField], which integrates with the [Form] widget. |
185 | /// * [InputDecorator], which shows the labels and other visual elements that |
186 | /// surround the actual text editing widget. |
187 | /// * [EditableText], which is the raw text editing control at the heart of a |
188 | /// [TextField]. The [EditableText] widget is rarely used directly unless |
189 | /// you are implementing an entirely different design language, such as |
190 | /// Cupertino. |
191 | /// * <https://material.io/design/components/text-fields.html> |
192 | /// * Cookbook: [Create and style a text field](https://docs.flutter.dev/cookbook/forms/text-input) |
193 | /// * Cookbook: [Handle changes to a text field](https://docs.flutter.dev/cookbook/forms/text-field-changes) |
194 | /// * Cookbook: [Retrieve the value of a text field](https://docs.flutter.dev/cookbook/forms/retrieve-input) |
195 | /// * Cookbook: [Focus and text fields](https://docs.flutter.dev/cookbook/forms/focus) |
196 | class TextField extends StatefulWidget { |
197 | /// Creates a Material Design text field. |
198 | /// |
199 | /// If [decoration] is non-null (which is the default), the text field requires |
200 | /// one of its ancestors to be a [Material] widget. |
201 | /// |
202 | /// To remove the decoration entirely (including the extra padding introduced |
203 | /// by the decoration to save space for the labels), set the [decoration] to |
204 | /// null. |
205 | /// |
206 | /// The [maxLines] property can be set to null to remove the restriction on |
207 | /// the number of lines. By default, it is one, meaning this is a single-line |
208 | /// text field. [maxLines] must not be zero. |
209 | /// |
210 | /// The [maxLength] property is set to null by default, which means the |
211 | /// number of characters allowed in the text field is not restricted. If |
212 | /// [maxLength] is set a character counter will be displayed below the |
213 | /// field showing how many characters have been entered. If the value is |
214 | /// set to a positive integer it will also display the maximum allowed |
215 | /// number of characters to be entered. If the value is set to |
216 | /// [TextField.noMaxLength] then only the current length is displayed. |
217 | /// |
218 | /// After [maxLength] characters have been input, additional input |
219 | /// is ignored, unless [maxLengthEnforcement] is set to |
220 | /// [MaxLengthEnforcement.none]. |
221 | /// The text field enforces the length with a [LengthLimitingTextInputFormatter], |
222 | /// which is evaluated after the supplied [inputFormatters], if any. |
223 | /// The [maxLength] value must be either null or greater than zero. |
224 | /// |
225 | /// If [maxLengthEnforcement] is set to [MaxLengthEnforcement.none], then more |
226 | /// than [maxLength] characters may be entered, and the error counter and |
227 | /// divider will switch to the [decoration].errorStyle when the limit is |
228 | /// exceeded. |
229 | /// |
230 | /// The text cursor is not shown if [showCursor] is false or if [showCursor] |
231 | /// is null (the default) and [readOnly] is true. |
232 | /// |
233 | /// The [selectionHeightStyle] and [selectionWidthStyle] properties allow |
234 | /// changing the shape of the selection highlighting. These properties default |
235 | /// to [EditableText.defaultSelectionHeightStyle] and |
236 | /// [EditableText.defaultSelectionHeightStyle], respectively. |
237 | /// |
238 | /// See also: |
239 | /// |
240 | /// * [maxLength], which discusses the precise meaning of "number of |
241 | /// characters" and how it may differ from the intuitive meaning. |
242 | const TextField({ |
243 | super.key, |
244 | this.groupId = EditableText, |
245 | this.controller, |
246 | this.focusNode, |
247 | this.undoController, |
248 | this.decoration = const InputDecoration(), |
249 | TextInputType? keyboardType, |
250 | this.textInputAction, |
251 | this.textCapitalization = TextCapitalization.none, |
252 | this.style, |
253 | this.strutStyle, |
254 | this.textAlign = TextAlign.start, |
255 | this.textAlignVertical, |
256 | this.textDirection, |
257 | this.readOnly = false, |
258 | @Deprecated( |
259 | 'Use `contextMenuBuilder` instead. ' |
260 | 'This feature was deprecated after v3.3.0-0.5.pre.' , |
261 | ) |
262 | this.toolbarOptions, |
263 | this.showCursor, |
264 | this.autofocus = false, |
265 | this.statesController, |
266 | this.obscuringCharacter = '•' , |
267 | this.obscureText = false, |
268 | this.autocorrect, |
269 | SmartDashesType? smartDashesType, |
270 | SmartQuotesType? smartQuotesType, |
271 | this.enableSuggestions = true, |
272 | this.maxLines = 1, |
273 | this.minLines, |
274 | this.expands = false, |
275 | this.maxLength, |
276 | this.maxLengthEnforcement, |
277 | this.onChanged, |
278 | this.onEditingComplete, |
279 | this.onSubmitted, |
280 | this.onAppPrivateCommand, |
281 | this.inputFormatters, |
282 | this.enabled, |
283 | this.ignorePointers, |
284 | this.cursorWidth = 2.0, |
285 | this.cursorHeight, |
286 | this.cursorRadius, |
287 | this.cursorOpacityAnimates, |
288 | this.cursorColor, |
289 | this.cursorErrorColor, |
290 | this.selectionHeightStyle, |
291 | this.selectionWidthStyle, |
292 | this.keyboardAppearance, |
293 | this.scrollPadding = const EdgeInsets.all(20.0), |
294 | this.dragStartBehavior = DragStartBehavior.start, |
295 | bool? enableInteractiveSelection, |
296 | this.selectAllOnFocus, |
297 | this.selectionControls, |
298 | this.onTap, |
299 | this.onTapAlwaysCalled = false, |
300 | this.onTapOutside, |
301 | this.onTapUpOutside, |
302 | this.mouseCursor, |
303 | this.buildCounter, |
304 | this.scrollController, |
305 | this.scrollPhysics, |
306 | this.autofillHints = const <String>[], |
307 | this.contentInsertionConfiguration, |
308 | this.clipBehavior = Clip.hardEdge, |
309 | this.restorationId, |
310 | @Deprecated( |
311 | 'Use `stylusHandwritingEnabled` instead. ' |
312 | 'This feature was deprecated after v3.27.0-0.2.pre.' , |
313 | ) |
314 | this.scribbleEnabled = true, |
315 | this.stylusHandwritingEnabled = EditableText.defaultStylusHandwritingEnabled, |
316 | this.enableIMEPersonalizedLearning = true, |
317 | this.contextMenuBuilder = _defaultContextMenuBuilder, |
318 | this.canRequestFocus = true, |
319 | this.spellCheckConfiguration, |
320 | this.magnifierConfiguration, |
321 | this.hintLocales, |
322 | }) : assert(obscuringCharacter.length == 1), |
323 | smartDashesType = |
324 | smartDashesType ?? (obscureText ? SmartDashesType.disabled : SmartDashesType.enabled), |
325 | smartQuotesType = |
326 | smartQuotesType ?? (obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled), |
327 | assert(maxLines == null || maxLines > 0), |
328 | assert(minLines == null || minLines > 0), |
329 | assert( |
330 | (maxLines == null) || (minLines == null) || (maxLines >= minLines), |
331 | "minLines can't be greater than maxLines" , |
332 | ), |
333 | assert( |
334 | !expands || (maxLines == null && minLines == null), |
335 | 'minLines and maxLines must be null when expands is true.' , |
336 | ), |
337 | assert(!obscureText || maxLines == 1, 'Obscured fields cannot be multiline.' ), |
338 | assert(maxLength == null || maxLength == TextField.noMaxLength || maxLength > 0), |
339 | // Assert the following instead of setting it directly to avoid surprising the user by silently changing the value they set. |
340 | assert( |
341 | !identical(textInputAction, TextInputAction.newline) || |
342 | maxLines == 1 || |
343 | !identical(keyboardType, TextInputType.text), |
344 | 'Use keyboardType TextInputType.multiline when using TextInputAction.newline on a multiline TextField.' , |
345 | ), |
346 | keyboardType = |
347 | keyboardType ?? (maxLines == 1 ? TextInputType.text : TextInputType.multiline), |
348 | enableInteractiveSelection = enableInteractiveSelection ?? (!readOnly || !obscureText); |
349 | |
350 | /// The configuration for the magnifier of this text field. |
351 | /// |
352 | /// By default, builds a [CupertinoTextMagnifier] on iOS and [TextMagnifier] |
353 | /// on Android, and builds nothing on all other platforms. To suppress the |
354 | /// magnifier, consider passing [TextMagnifierConfiguration.disabled]. |
355 | /// |
356 | /// {@macro flutter.widgets.magnifier.intro} |
357 | /// |
358 | /// {@tool dartpad} |
359 | /// This sample demonstrates how to customize the magnifier that this text field uses. |
360 | /// |
361 | /// ** See code in examples/api/lib/widgets/text_magnifier/text_magnifier.0.dart ** |
362 | /// {@end-tool} |
363 | final TextMagnifierConfiguration? magnifierConfiguration; |
364 | |
365 | /// {@macro flutter.widgets.editableText.groupId} |
366 | final Object groupId; |
367 | |
368 | /// Controls the text being edited. |
369 | /// |
370 | /// If null, this widget will create its own [TextEditingController]. |
371 | final TextEditingController? controller; |
372 | |
373 | /// Defines the keyboard focus for this widget. |
374 | /// |
375 | /// The [focusNode] is a long-lived object that's typically managed by a |
376 | /// [StatefulWidget] parent. See [FocusNode] for more information. |
377 | /// |
378 | /// To give the keyboard focus to this widget, provide a [focusNode] and then |
379 | /// use the current [FocusScope] to request the focus: |
380 | /// |
381 | /// ```dart |
382 | /// FocusScope.of(context).requestFocus(myFocusNode); |
383 | /// ``` |
384 | /// |
385 | /// This happens automatically when the widget is tapped. |
386 | /// |
387 | /// To be notified when the widget gains or loses the focus, add a listener |
388 | /// to the [focusNode]: |
389 | /// |
390 | /// ```dart |
391 | /// myFocusNode.addListener(() { print(myFocusNode.hasFocus); }); |
392 | /// ``` |
393 | /// |
394 | /// If null, this widget will create its own [FocusNode]. |
395 | /// |
396 | /// ## Keyboard |
397 | /// |
398 | /// Requesting the focus will typically cause the keyboard to be shown |
399 | /// if it's not showing already. |
400 | /// |
401 | /// On Android, the user can hide the keyboard - without changing the focus - |
402 | /// with the system back button. They can restore the keyboard's visibility |
403 | /// by tapping on a text field. The user might hide the keyboard and |
404 | /// switch to a physical keyboard, or they might just need to get it |
405 | /// out of the way for a moment, to expose something it's |
406 | /// obscuring. In this case requesting the focus again will not |
407 | /// cause the focus to change, and will not make the keyboard visible. |
408 | /// |
409 | /// This widget builds an [EditableText] and will ensure that the keyboard is |
410 | /// showing when it is tapped by calling [EditableTextState.requestKeyboard()]. |
411 | final FocusNode? focusNode; |
412 | |
413 | /// The decoration to show around the text field. |
414 | /// |
415 | /// By default, draws a horizontal line under the text field but can be |
416 | /// configured to show an icon, label, hint text, and error text. |
417 | /// |
418 | /// Specify null to remove the decoration entirely (including the |
419 | /// extra padding introduced by the decoration to save space for the labels). |
420 | final InputDecoration? decoration; |
421 | |
422 | /// {@macro flutter.widgets.editableText.keyboardType} |
423 | final TextInputType keyboardType; |
424 | |
425 | /// {@template flutter.widgets.TextField.textInputAction} |
426 | /// The type of action button to use for the keyboard. |
427 | /// |
428 | /// Defaults to [TextInputAction.newline] if [keyboardType] is |
429 | /// [TextInputType.multiline] and [TextInputAction.done] otherwise. |
430 | /// {@endtemplate} |
431 | final TextInputAction? textInputAction; |
432 | |
433 | /// {@macro flutter.widgets.editableText.textCapitalization} |
434 | final TextCapitalization textCapitalization; |
435 | |
436 | /// The style to use for the text being edited. |
437 | /// |
438 | /// This text style is also used as the base style for the [decoration]. |
439 | /// |
440 | /// If null, [TextTheme.bodyLarge] will be used. When the text field is disabled, |
441 | /// [TextTheme.bodyLarge] with an opacity of 0.38 will be used instead. |
442 | /// |
443 | /// If null and [ThemeData.useMaterial3] is false, [TextTheme.titleMedium] will |
444 | /// be used. When the text field is disabled, [TextTheme.titleMedium] with |
445 | /// [ThemeData.disabledColor] will be used instead. |
446 | final TextStyle? style; |
447 | |
448 | /// {@macro flutter.widgets.editableText.strutStyle} |
449 | final StrutStyle? strutStyle; |
450 | |
451 | /// {@macro flutter.widgets.editableText.textAlign} |
452 | final TextAlign textAlign; |
453 | |
454 | /// {@macro flutter.material.InputDecorator.textAlignVertical} |
455 | final TextAlignVertical? textAlignVertical; |
456 | |
457 | /// {@macro flutter.widgets.editableText.textDirection} |
458 | final TextDirection? textDirection; |
459 | |
460 | /// {@macro flutter.widgets.editableText.autofocus} |
461 | final bool autofocus; |
462 | |
463 | /// Represents the interactive "state" of this widget in terms of a set of |
464 | /// [WidgetState]s, including [WidgetState.disabled], [WidgetState.hovered], |
465 | /// [WidgetState.error], and [WidgetState.focused]. |
466 | /// |
467 | /// Classes based on this one can provide their own |
468 | /// [WidgetStatesController] to which they've added listeners. |
469 | /// They can also update the controller's [WidgetStatesController.value] |
470 | /// however, this may only be done when it's safe to call |
471 | /// [State.setState], like in an event handler. |
472 | /// |
473 | /// The controller's [WidgetStatesController.value] represents the set of |
474 | /// states that a widget's visual properties, typically [WidgetStateProperty] |
475 | /// values, are resolved against. It is _not_ the intrinsic state of the widget. |
476 | /// The widget is responsible for ensuring that the controller's |
477 | /// [WidgetStatesController.value] tracks its intrinsic state. For example |
478 | /// one cannot request the keyboard focus for a widget by adding [WidgetState.focused] |
479 | /// to its controller. When the widget gains the or loses the focus it will |
480 | /// [WidgetStatesController.update] its controller's [WidgetStatesController.value] |
481 | /// and notify listeners of the change. |
482 | final MaterialStatesController? statesController; |
483 | |
484 | /// {@macro flutter.widgets.editableText.obscuringCharacter} |
485 | final String obscuringCharacter; |
486 | |
487 | /// {@macro flutter.widgets.editableText.obscureText} |
488 | final bool obscureText; |
489 | |
490 | /// {@macro flutter.widgets.editableText.autocorrect} |
491 | final bool? autocorrect; |
492 | |
493 | /// {@macro flutter.services.TextInputConfiguration.smartDashesType} |
494 | final SmartDashesType smartDashesType; |
495 | |
496 | /// {@macro flutter.services.TextInputConfiguration.smartQuotesType} |
497 | final SmartQuotesType smartQuotesType; |
498 | |
499 | /// {@macro flutter.services.TextInputConfiguration.enableSuggestions} |
500 | final bool enableSuggestions; |
501 | |
502 | /// {@macro flutter.widgets.editableText.maxLines} |
503 | /// * [expands], which determines whether the field should fill the height of |
504 | /// its parent. |
505 | final int? maxLines; |
506 | |
507 | /// {@macro flutter.widgets.editableText.minLines} |
508 | /// * [expands], which determines whether the field should fill the height of |
509 | /// its parent. |
510 | final int? minLines; |
511 | |
512 | /// {@macro flutter.widgets.editableText.expands} |
513 | final bool expands; |
514 | |
515 | /// {@macro flutter.widgets.editableText.readOnly} |
516 | final bool readOnly; |
517 | |
518 | /// Configuration of toolbar options. |
519 | /// |
520 | /// If not set, select all and paste will default to be enabled. Copy and cut |
521 | /// will be disabled if [obscureText] is true. If [readOnly] is true, |
522 | /// paste and cut will be disabled regardless. |
523 | @Deprecated( |
524 | 'Use `contextMenuBuilder` instead. ' |
525 | 'This feature was deprecated after v3.3.0-0.5.pre.' , |
526 | ) |
527 | final ToolbarOptions? toolbarOptions; |
528 | |
529 | /// {@macro flutter.widgets.editableText.showCursor} |
530 | final bool? showCursor; |
531 | |
532 | /// If [maxLength] is set to this value, only the "current input length" |
533 | /// part of the character counter is shown. |
534 | static const int noMaxLength = -1; |
535 | |
536 | /// The maximum number of characters (Unicode grapheme clusters) to allow in |
537 | /// the text field. |
538 | /// |
539 | /// If set, a character counter will be displayed below the |
540 | /// field showing how many characters have been entered. If set to a number |
541 | /// greater than 0, it will also display the maximum number allowed. If set |
542 | /// to [TextField.noMaxLength] then only the current character count is displayed. |
543 | /// |
544 | /// After [maxLength] characters have been input, additional input |
545 | /// is ignored, unless [maxLengthEnforcement] is set to |
546 | /// [MaxLengthEnforcement.none]. |
547 | /// |
548 | /// The text field enforces the length with a [LengthLimitingTextInputFormatter], |
549 | /// which is evaluated after the supplied [inputFormatters], if any. |
550 | /// |
551 | /// This value must be either null, [TextField.noMaxLength], or greater than 0. |
552 | /// If null (the default) then there is no limit to the number of characters |
553 | /// that can be entered. If set to [TextField.noMaxLength], then no limit will |
554 | /// be enforced, but the number of characters entered will still be displayed. |
555 | /// |
556 | /// Whitespace characters (e.g. newline, space, tab) are included in the |
557 | /// character count. |
558 | /// |
559 | /// If [maxLengthEnforcement] is [MaxLengthEnforcement.none], then more than |
560 | /// [maxLength] characters may be entered, but the error counter and divider |
561 | /// will switch to the [decoration]'s [InputDecoration.errorStyle] when the |
562 | /// limit is exceeded. |
563 | /// |
564 | /// {@macro flutter.services.lengthLimitingTextInputFormatter.maxLength} |
565 | final int? maxLength; |
566 | |
567 | /// Determines how the [maxLength] limit should be enforced. |
568 | /// |
569 | /// {@macro flutter.services.textFormatter.effectiveMaxLengthEnforcement} |
570 | /// |
571 | /// {@macro flutter.services.textFormatter.maxLengthEnforcement} |
572 | final MaxLengthEnforcement? maxLengthEnforcement; |
573 | |
574 | /// {@macro flutter.widgets.editableText.onChanged} |
575 | /// |
576 | /// See also: |
577 | /// |
578 | /// * [inputFormatters], which are called before [onChanged] |
579 | /// runs and can validate and change ("format") the input value. |
580 | /// * [onEditingComplete], [onSubmitted]: |
581 | /// which are more specialized input change notifications. |
582 | final ValueChanged<String>? onChanged; |
583 | |
584 | /// {@macro flutter.widgets.editableText.onEditingComplete} |
585 | final VoidCallback? onEditingComplete; |
586 | |
587 | /// {@macro flutter.widgets.editableText.onSubmitted} |
588 | /// |
589 | /// See also: |
590 | /// |
591 | /// * [TextInputAction.next] and [TextInputAction.previous], which |
592 | /// automatically shift the focus to the next/previous focusable item when |
593 | /// the user is done editing. |
594 | final ValueChanged<String>? onSubmitted; |
595 | |
596 | /// {@macro flutter.widgets.editableText.onAppPrivateCommand} |
597 | final AppPrivateCommandCallback? onAppPrivateCommand; |
598 | |
599 | /// {@macro flutter.widgets.editableText.inputFormatters} |
600 | final List<TextInputFormatter>? inputFormatters; |
601 | |
602 | /// If false the text field is "disabled": it ignores taps and its |
603 | /// [decoration] is rendered in grey. |
604 | /// |
605 | /// If non-null this property overrides the [decoration]'s |
606 | /// [InputDecoration.enabled] property. |
607 | /// |
608 | /// When a text field is disabled, all of its children widgets are also |
609 | /// disabled, including the [InputDecoration.suffixIcon]. If you need to keep |
610 | /// the suffix icon interactive while disabling the text field, consider using |
611 | /// [readOnly] and [enableInteractiveSelection] instead: |
612 | /// |
613 | /// ```dart |
614 | /// TextField( |
615 | /// enabled: true, |
616 | /// readOnly: true, |
617 | /// enableInteractiveSelection: false, |
618 | /// decoration: InputDecoration( |
619 | /// suffixIcon: IconButton( |
620 | /// onPressed: () { |
621 | /// // This will work because the TextField is enabled |
622 | /// }, |
623 | /// icon: const Icon(Icons.edit_outlined), |
624 | /// ), |
625 | /// ), |
626 | /// ) |
627 | /// ``` |
628 | final bool? enabled; |
629 | |
630 | /// Determines whether this widget ignores pointer events. |
631 | /// |
632 | /// Defaults to null, and when null, does nothing. |
633 | final bool? ignorePointers; |
634 | |
635 | /// {@macro flutter.widgets.editableText.cursorWidth} |
636 | final double cursorWidth; |
637 | |
638 | /// {@macro flutter.widgets.editableText.cursorHeight} |
639 | final double? cursorHeight; |
640 | |
641 | /// {@macro flutter.widgets.editableText.cursorRadius} |
642 | final Radius? cursorRadius; |
643 | |
644 | /// {@macro flutter.widgets.editableText.cursorOpacityAnimates} |
645 | final bool? cursorOpacityAnimates; |
646 | |
647 | /// The color of the cursor. |
648 | /// |
649 | /// The cursor indicates the current location of text insertion point in |
650 | /// the field. |
651 | /// |
652 | /// If this is null it will default to the ambient |
653 | /// [DefaultSelectionStyle.cursorColor]. If that is null, and the |
654 | /// [ThemeData.platform] is [TargetPlatform.iOS] or [TargetPlatform.macOS] |
655 | /// it will use [CupertinoThemeData.primaryColor]. Otherwise it will use |
656 | /// the value of [ColorScheme.primary] of [ThemeData.colorScheme]. |
657 | final Color? cursorColor; |
658 | |
659 | /// The color of the cursor when the [InputDecorator] is showing an error. |
660 | /// |
661 | /// If this is null it will default to [TextStyle.color] of |
662 | /// [InputDecoration.errorStyle]. If that is null, it will use |
663 | /// [ColorScheme.error] of [ThemeData.colorScheme]. |
664 | final Color? cursorErrorColor; |
665 | |
666 | /// Controls how tall the selection highlight boxes are computed to be. |
667 | /// |
668 | /// See [ui.BoxHeightStyle] for details on available styles. |
669 | final ui.BoxHeightStyle? selectionHeightStyle; |
670 | |
671 | /// Controls how wide the selection highlight boxes are computed to be. |
672 | /// |
673 | /// See [ui.BoxWidthStyle] for details on available styles. |
674 | final ui.BoxWidthStyle? selectionWidthStyle; |
675 | |
676 | /// The appearance of the keyboard. |
677 | /// |
678 | /// This setting is only honored on iOS devices. |
679 | /// |
680 | /// If unset, defaults to [ThemeData.brightness]. |
681 | final Brightness? keyboardAppearance; |
682 | |
683 | /// {@macro flutter.widgets.editableText.scrollPadding} |
684 | final EdgeInsets scrollPadding; |
685 | |
686 | /// {@macro flutter.widgets.editableText.enableInteractiveSelection} |
687 | final bool enableInteractiveSelection; |
688 | |
689 | /// {@macro flutter.widgets.editableText.selectAllOnFocus} |
690 | final bool? selectAllOnFocus; |
691 | |
692 | /// {@macro flutter.widgets.editableText.selectionControls} |
693 | final TextSelectionControls? selectionControls; |
694 | |
695 | /// {@macro flutter.widgets.scrollable.dragStartBehavior} |
696 | final DragStartBehavior dragStartBehavior; |
697 | |
698 | /// {@macro flutter.widgets.editableText.selectionEnabled} |
699 | bool get selectionEnabled => enableInteractiveSelection; |
700 | |
701 | /// {@template flutter.material.textfield.onTap} |
702 | /// Called for the first tap in a series of taps. |
703 | /// |
704 | /// The text field builds a [GestureDetector] to handle input events like tap, |
705 | /// to trigger focus requests, to move the caret, adjust the selection, etc. |
706 | /// Handling some of those events by wrapping the text field with a competing |
707 | /// GestureDetector is problematic. |
708 | /// |
709 | /// To unconditionally handle taps, without interfering with the text field's |
710 | /// internal gesture detector, provide this callback. |
711 | /// |
712 | /// If the text field is created with [enabled] false, taps will not be |
713 | /// recognized. |
714 | /// |
715 | /// To be notified when the text field gains or loses the focus, provide a |
716 | /// [focusNode] and add a listener to that. |
717 | /// |
718 | /// To listen to arbitrary pointer events without competing with the |
719 | /// text field's internal gesture detector, use a [Listener]. |
720 | /// {@endtemplate} |
721 | /// |
722 | /// If [onTapAlwaysCalled] is enabled, this will also be called for consecutive |
723 | /// taps. |
724 | final GestureTapCallback? onTap; |
725 | |
726 | /// Whether [onTap] should be called for every tap. |
727 | /// |
728 | /// Defaults to false, so [onTap] is only called for each distinct tap. When |
729 | /// enabled, [onTap] is called for every tap including consecutive taps. |
730 | final bool onTapAlwaysCalled; |
731 | |
732 | /// {@macro flutter.widgets.editableText.onTapOutside} |
733 | /// |
734 | /// {@tool dartpad} |
735 | /// This example shows how to use a `TextFieldTapRegion` to wrap a set of |
736 | /// "spinner" buttons that increment and decrement a value in the [TextField] |
737 | /// without causing the text field to lose keyboard focus. |
738 | /// |
739 | /// This example includes a generic `SpinnerField<T>` class that you can copy |
740 | /// into your own project and customize. |
741 | /// |
742 | /// ** See code in examples/api/lib/widgets/tap_region/text_field_tap_region.0.dart ** |
743 | /// {@end-tool} |
744 | /// |
745 | /// See also: |
746 | /// |
747 | /// * [TapRegion] for how the region group is determined. |
748 | final TapRegionCallback? onTapOutside; |
749 | |
750 | /// {@macro flutter.widgets.editableText.onTapUpOutside} |
751 | final TapRegionUpCallback? onTapUpOutside; |
752 | |
753 | /// The cursor for a mouse pointer when it enters or is hovering over the |
754 | /// widget. |
755 | /// |
756 | /// If [mouseCursor] is a [WidgetStateMouseCursor], |
757 | /// [WidgetStateProperty.resolve] is used for the following [WidgetState]s: |
758 | /// |
759 | /// * [WidgetState.error]. |
760 | /// * [WidgetState.hovered]. |
761 | /// * [WidgetState.focused]. |
762 | /// * [WidgetState.disabled]. |
763 | /// |
764 | /// If this property is null, [WidgetStateMouseCursor.textable] will be used. |
765 | /// |
766 | /// The [mouseCursor] is the only property of [TextField] that controls the |
767 | /// appearance of the mouse pointer. All other properties related to "cursor" |
768 | /// stand for the text cursor, which is usually a blinking vertical line at |
769 | /// the editing position. |
770 | final MouseCursor? mouseCursor; |
771 | |
772 | /// Callback that generates a custom [InputDecoration.counter] widget. |
773 | /// |
774 | /// See [InputCounterWidgetBuilder] for an explanation of the passed in |
775 | /// arguments. The returned widget will be placed below the line in place of |
776 | /// the default widget built when [InputDecoration.counterText] is specified. |
777 | /// |
778 | /// The returned widget will be wrapped in a [Semantics] widget for |
779 | /// accessibility, but it also needs to be accessible itself. For example, |
780 | /// if returning a Text widget, set the [Text.semanticsLabel] property. |
781 | /// |
782 | /// {@tool snippet} |
783 | /// ```dart |
784 | /// Widget counter( |
785 | /// BuildContext context, |
786 | /// { |
787 | /// required int currentLength, |
788 | /// required int? maxLength, |
789 | /// required bool isFocused, |
790 | /// } |
791 | /// ) { |
792 | /// return Text( |
793 | /// '$currentLength of $maxLength characters', |
794 | /// semanticsLabel: 'character count', |
795 | /// ); |
796 | /// } |
797 | /// ``` |
798 | /// {@end-tool} |
799 | /// |
800 | /// If buildCounter returns null, then no counter and no Semantics widget will |
801 | /// be created at all. |
802 | final InputCounterWidgetBuilder? buildCounter; |
803 | |
804 | /// {@macro flutter.widgets.editableText.scrollPhysics} |
805 | final ScrollPhysics? scrollPhysics; |
806 | |
807 | /// {@macro flutter.widgets.editableText.scrollController} |
808 | final ScrollController? scrollController; |
809 | |
810 | /// {@macro flutter.widgets.editableText.autofillHints} |
811 | /// {@macro flutter.services.AutofillConfiguration.autofillHints} |
812 | final Iterable<String>? autofillHints; |
813 | |
814 | /// {@macro flutter.material.Material.clipBehavior} |
815 | /// |
816 | /// Defaults to [Clip.hardEdge]. |
817 | final Clip clipBehavior; |
818 | |
819 | /// {@template flutter.material.textfield.restorationId} |
820 | /// Restoration ID to save and restore the state of the text field. |
821 | /// |
822 | /// If non-null, the text field will persist and restore its current scroll |
823 | /// offset and - if no [controller] has been provided - the content of the |
824 | /// text field. If a [controller] has been provided, it is the responsibility |
825 | /// of the owner of that controller to persist and restore it, e.g. by using |
826 | /// a [RestorableTextEditingController]. |
827 | /// |
828 | /// The state of this widget is persisted in a [RestorationBucket] claimed |
829 | /// from the surrounding [RestorationScope] using the provided restoration ID. |
830 | /// |
831 | /// See also: |
832 | /// |
833 | /// * [RestorationManager], which explains how state restoration works in |
834 | /// Flutter. |
835 | /// {@endtemplate} |
836 | final String? restorationId; |
837 | |
838 | /// {@macro flutter.widgets.editableText.scribbleEnabled} |
839 | @Deprecated( |
840 | 'Use `stylusHandwritingEnabled` instead. ' |
841 | 'This feature was deprecated after v3.27.0-0.2.pre.' , |
842 | ) |
843 | final bool scribbleEnabled; |
844 | |
845 | /// {@macro flutter.widgets.editableText.stylusHandwritingEnabled} |
846 | final bool stylusHandwritingEnabled; |
847 | |
848 | /// {@macro flutter.services.TextInputConfiguration.enableIMEPersonalizedLearning} |
849 | final bool enableIMEPersonalizedLearning; |
850 | |
851 | /// {@macro flutter.widgets.editableText.contentInsertionConfiguration} |
852 | final ContentInsertionConfiguration? contentInsertionConfiguration; |
853 | |
854 | /// {@macro flutter.widgets.EditableText.contextMenuBuilder} |
855 | /// |
856 | /// If not provided, will build a default menu based on the platform. |
857 | /// |
858 | /// See also: |
859 | /// |
860 | /// * [AdaptiveTextSelectionToolbar], which is built by default. |
861 | /// * [BrowserContextMenu], which allows the browser's context menu on web to |
862 | /// be disabled and Flutter-rendered context menus to appear. |
863 | final EditableTextContextMenuBuilder? contextMenuBuilder; |
864 | |
865 | /// Determine whether this text field can request the primary focus. |
866 | /// |
867 | /// Defaults to true. If false, the text field will not request focus |
868 | /// when tapped, or when its context menu is displayed. If false it will not |
869 | /// be possible to move the focus to the text field with tab key. |
870 | final bool canRequestFocus; |
871 | |
872 | /// {@macro flutter.widgets.undoHistory.controller} |
873 | final UndoHistoryController? undoController; |
874 | |
875 | /// {@macro flutter.services.TextInputConfiguration.hintLocales} |
876 | final List<Locale>? hintLocales; |
877 | |
878 | static Widget _defaultContextMenuBuilder( |
879 | BuildContext context, |
880 | EditableTextState editableTextState, |
881 | ) { |
882 | if (defaultTargetPlatform == TargetPlatform.iOS && SystemContextMenu.isSupported(context)) { |
883 | return SystemContextMenu.editableText(editableTextState: editableTextState); |
884 | } |
885 | return AdaptiveTextSelectionToolbar.editableText(editableTextState: editableTextState); |
886 | } |
887 | |
888 | /// {@macro flutter.widgets.EditableText.spellCheckConfiguration} |
889 | /// |
890 | /// If [SpellCheckConfiguration.misspelledTextStyle] is not specified in this |
891 | /// configuration, then [materialMisspelledTextStyle] is used by default. |
892 | final SpellCheckConfiguration? spellCheckConfiguration; |
893 | |
894 | /// The [TextStyle] used to indicate misspelled words in the Material style. |
895 | /// |
896 | /// See also: |
897 | /// * [SpellCheckConfiguration.misspelledTextStyle], the style configured to |
898 | /// mark misspelled words with. |
899 | /// * [CupertinoTextField.cupertinoMisspelledTextStyle], the style configured |
900 | /// to mark misspelled words with in the Cupertino style. |
901 | static const TextStyle materialMisspelledTextStyle = TextStyle( |
902 | decoration: TextDecoration.underline, |
903 | decorationColor: Colors.red, |
904 | decorationStyle: TextDecorationStyle.wavy, |
905 | ); |
906 | |
907 | /// Default builder for [TextField]'s spell check suggestions toolbar. |
908 | /// |
909 | /// On Apple platforms, builds an iOS-style toolbar. Everywhere else, builds |
910 | /// an Android-style toolbar. |
911 | /// |
912 | /// See also: |
913 | /// * [spellCheckConfiguration], where this is typically specified for |
914 | /// [TextField]. |
915 | /// * [SpellCheckConfiguration.spellCheckSuggestionsToolbarBuilder], the |
916 | /// parameter for which this is the default value for [TextField]. |
917 | /// * [CupertinoTextField.defaultSpellCheckSuggestionsToolbarBuilder], which |
918 | /// is like this but specifies the default for [CupertinoTextField]. |
919 | @visibleForTesting |
920 | static Widget defaultSpellCheckSuggestionsToolbarBuilder( |
921 | BuildContext context, |
922 | EditableTextState editableTextState, |
923 | ) { |
924 | switch (defaultTargetPlatform) { |
925 | case TargetPlatform.iOS: |
926 | case TargetPlatform.macOS: |
927 | return CupertinoSpellCheckSuggestionsToolbar.editableText( |
928 | editableTextState: editableTextState, |
929 | ); |
930 | case TargetPlatform.android: |
931 | case TargetPlatform.fuchsia: |
932 | case TargetPlatform.linux: |
933 | case TargetPlatform.windows: |
934 | return SpellCheckSuggestionsToolbar.editableText(editableTextState: editableTextState); |
935 | } |
936 | } |
937 | |
938 | /// Returns a new [SpellCheckConfiguration] where the given configuration has |
939 | /// had any missing values replaced with their defaults for the Android |
940 | /// platform. |
941 | static SpellCheckConfiguration inferAndroidSpellCheckConfiguration( |
942 | SpellCheckConfiguration? configuration, |
943 | ) { |
944 | if (configuration == null || configuration == const SpellCheckConfiguration.disabled()) { |
945 | return const SpellCheckConfiguration.disabled(); |
946 | } |
947 | return configuration.copyWith( |
948 | misspelledTextStyle: |
949 | configuration.misspelledTextStyle ?? TextField.materialMisspelledTextStyle, |
950 | spellCheckSuggestionsToolbarBuilder: |
951 | configuration.spellCheckSuggestionsToolbarBuilder ?? |
952 | TextField.defaultSpellCheckSuggestionsToolbarBuilder, |
953 | ); |
954 | } |
955 | |
956 | @override |
957 | State<TextField> createState() => _TextFieldState(); |
958 | |
959 | @override |
960 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
961 | super.debugFillProperties(properties); |
962 | properties.add( |
963 | DiagnosticsProperty<TextEditingController>('controller' , controller, defaultValue: null), |
964 | ); |
965 | properties.add(DiagnosticsProperty<FocusNode>('focusNode' , focusNode, defaultValue: null)); |
966 | properties.add( |
967 | DiagnosticsProperty<UndoHistoryController>( |
968 | 'undoController' , |
969 | undoController, |
970 | defaultValue: null, |
971 | ), |
972 | ); |
973 | properties.add(DiagnosticsProperty<bool>('enabled' , enabled, defaultValue: null)); |
974 | properties.add( |
975 | DiagnosticsProperty<InputDecoration>( |
976 | 'decoration' , |
977 | decoration, |
978 | defaultValue: const InputDecoration(), |
979 | ), |
980 | ); |
981 | properties.add( |
982 | DiagnosticsProperty<TextInputType>( |
983 | 'keyboardType' , |
984 | keyboardType, |
985 | defaultValue: TextInputType.text, |
986 | ), |
987 | ); |
988 | properties.add(DiagnosticsProperty<TextStyle>('style' , style, defaultValue: null)); |
989 | properties.add(DiagnosticsProperty<bool>('autofocus' , autofocus, defaultValue: false)); |
990 | properties.add( |
991 | DiagnosticsProperty<String>('obscuringCharacter' , obscuringCharacter, defaultValue: '•' ), |
992 | ); |
993 | properties.add(DiagnosticsProperty<bool>('obscureText' , obscureText, defaultValue: false)); |
994 | properties.add(DiagnosticsProperty<bool>('autocorrect' , autocorrect, defaultValue: null)); |
995 | properties.add( |
996 | EnumProperty<SmartDashesType>( |
997 | 'smartDashesType' , |
998 | smartDashesType, |
999 | defaultValue: obscureText ? SmartDashesType.disabled : SmartDashesType.enabled, |
1000 | ), |
1001 | ); |
1002 | properties.add( |
1003 | EnumProperty<SmartQuotesType>( |
1004 | 'smartQuotesType' , |
1005 | smartQuotesType, |
1006 | defaultValue: obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled, |
1007 | ), |
1008 | ); |
1009 | properties.add( |
1010 | DiagnosticsProperty<bool>('enableSuggestions' , enableSuggestions, defaultValue: true), |
1011 | ); |
1012 | properties.add(IntProperty('maxLines' , maxLines, defaultValue: 1)); |
1013 | properties.add(IntProperty('minLines' , minLines, defaultValue: null)); |
1014 | properties.add(DiagnosticsProperty<bool>('expands' , expands, defaultValue: false)); |
1015 | properties.add(IntProperty('maxLength' , maxLength, defaultValue: null)); |
1016 | properties.add( |
1017 | EnumProperty<MaxLengthEnforcement>( |
1018 | 'maxLengthEnforcement' , |
1019 | maxLengthEnforcement, |
1020 | defaultValue: null, |
1021 | ), |
1022 | ); |
1023 | properties.add( |
1024 | EnumProperty<TextInputAction>('textInputAction' , textInputAction, defaultValue: null), |
1025 | ); |
1026 | properties.add( |
1027 | EnumProperty<TextCapitalization>( |
1028 | 'textCapitalization' , |
1029 | textCapitalization, |
1030 | defaultValue: TextCapitalization.none, |
1031 | ), |
1032 | ); |
1033 | properties.add(EnumProperty<TextAlign>('textAlign' , textAlign, defaultValue: TextAlign.start)); |
1034 | properties.add( |
1035 | DiagnosticsProperty<TextAlignVertical>( |
1036 | 'textAlignVertical' , |
1037 | textAlignVertical, |
1038 | defaultValue: null, |
1039 | ), |
1040 | ); |
1041 | properties.add(EnumProperty<TextDirection>('textDirection' , textDirection, defaultValue: null)); |
1042 | properties.add(DoubleProperty('cursorWidth' , cursorWidth, defaultValue: 2.0)); |
1043 | properties.add(DoubleProperty('cursorHeight' , cursorHeight, defaultValue: null)); |
1044 | properties.add(DiagnosticsProperty<Radius>('cursorRadius' , cursorRadius, defaultValue: null)); |
1045 | properties.add( |
1046 | DiagnosticsProperty<bool>('cursorOpacityAnimates' , cursorOpacityAnimates, defaultValue: null), |
1047 | ); |
1048 | properties.add(ColorProperty('cursorColor' , cursorColor, defaultValue: null)); |
1049 | properties.add(ColorProperty('cursorErrorColor' , cursorErrorColor, defaultValue: null)); |
1050 | properties.add( |
1051 | DiagnosticsProperty<Brightness>('keyboardAppearance' , keyboardAppearance, defaultValue: null), |
1052 | ); |
1053 | properties.add( |
1054 | DiagnosticsProperty<EdgeInsetsGeometry>( |
1055 | 'scrollPadding' , |
1056 | scrollPadding, |
1057 | defaultValue: const EdgeInsets.all(20.0), |
1058 | ), |
1059 | ); |
1060 | properties.add( |
1061 | FlagProperty( |
1062 | 'selectionEnabled' , |
1063 | value: selectionEnabled, |
1064 | defaultValue: true, |
1065 | ifFalse: 'selection disabled' , |
1066 | ), |
1067 | ); |
1068 | properties.add( |
1069 | DiagnosticsProperty<TextSelectionControls>( |
1070 | 'selectionControls' , |
1071 | selectionControls, |
1072 | defaultValue: null, |
1073 | ), |
1074 | ); |
1075 | properties.add( |
1076 | DiagnosticsProperty<ScrollController>( |
1077 | 'scrollController' , |
1078 | scrollController, |
1079 | defaultValue: null, |
1080 | ), |
1081 | ); |
1082 | properties.add( |
1083 | DiagnosticsProperty<ScrollPhysics>('scrollPhysics' , scrollPhysics, defaultValue: null), |
1084 | ); |
1085 | properties.add( |
1086 | DiagnosticsProperty<Clip>('clipBehavior' , clipBehavior, defaultValue: Clip.hardEdge), |
1087 | ); |
1088 | properties.add( |
1089 | DiagnosticsProperty<bool>('scribbleEnabled' , scribbleEnabled, defaultValue: true), |
1090 | ); |
1091 | properties.add( |
1092 | DiagnosticsProperty<bool>( |
1093 | 'stylusHandwritingEnabled' , |
1094 | stylusHandwritingEnabled, |
1095 | defaultValue: EditableText.defaultStylusHandwritingEnabled, |
1096 | ), |
1097 | ); |
1098 | properties.add( |
1099 | DiagnosticsProperty<bool>( |
1100 | 'enableIMEPersonalizedLearning' , |
1101 | enableIMEPersonalizedLearning, |
1102 | defaultValue: true, |
1103 | ), |
1104 | ); |
1105 | properties.add( |
1106 | DiagnosticsProperty<SpellCheckConfiguration>( |
1107 | 'spellCheckConfiguration' , |
1108 | spellCheckConfiguration, |
1109 | defaultValue: null, |
1110 | ), |
1111 | ); |
1112 | properties.add( |
1113 | DiagnosticsProperty<List<String>>( |
1114 | 'contentCommitMimeTypes' , |
1115 | contentInsertionConfiguration?.allowedMimeTypes ?? const <String>[], |
1116 | defaultValue: contentInsertionConfiguration == null |
1117 | ? const <String>[] |
1118 | : kDefaultContentInsertionMimeTypes, |
1119 | ), |
1120 | ); |
1121 | properties.add( |
1122 | DiagnosticsProperty<List<Locale>?>('hintLocales' , hintLocales, defaultValue: null), |
1123 | ); |
1124 | } |
1125 | } |
1126 | |
1127 | class _TextFieldState extends State<TextField> |
1128 | with RestorationMixin |
1129 | implements TextSelectionGestureDetectorBuilderDelegate, AutofillClient { |
1130 | RestorableTextEditingController? _controller; |
1131 | TextEditingController get _effectiveController => widget.controller ?? _controller!.value; |
1132 | |
1133 | FocusNode? _focusNode; |
1134 | FocusNode get _effectiveFocusNode => widget.focusNode ?? (_focusNode ??= FocusNode()); |
1135 | |
1136 | MaxLengthEnforcement get _effectiveMaxLengthEnforcement => |
1137 | widget.maxLengthEnforcement ?? |
1138 | LengthLimitingTextInputFormatter.getDefaultMaxLengthEnforcement(Theme.of(context).platform); |
1139 | |
1140 | bool _isHovering = false; |
1141 | |
1142 | bool get needsCounter => |
1143 | widget.maxLength != null && |
1144 | widget.decoration != null && |
1145 | widget.decoration!.counterText == null; |
1146 | |
1147 | bool _showSelectionHandles = false; |
1148 | |
1149 | late _TextFieldSelectionGestureDetectorBuilder _selectionGestureDetectorBuilder; |
1150 | |
1151 | // API for TextSelectionGestureDetectorBuilderDelegate. |
1152 | @override |
1153 | late bool forcePressEnabled; |
1154 | |
1155 | @override |
1156 | final GlobalKey<EditableTextState> editableTextKey = GlobalKey<EditableTextState>(); |
1157 | |
1158 | @override |
1159 | bool get selectionEnabled => widget.selectionEnabled && _isEnabled; |
1160 | // End of API for TextSelectionGestureDetectorBuilderDelegate. |
1161 | |
1162 | bool get _isEnabled => widget.enabled ?? widget.decoration?.enabled ?? true; |
1163 | |
1164 | int get _currentLength => _effectiveController.value.text.characters.length; |
1165 | |
1166 | bool get _hasIntrinsicError => |
1167 | widget.maxLength != null && |
1168 | widget.maxLength! > 0 && |
1169 | (widget.controller == null |
1170 | ? !restorePending && _effectiveController.value.text.characters.length > widget.maxLength! |
1171 | : _effectiveController.value.text.characters.length > widget.maxLength!); |
1172 | |
1173 | bool get _hasError => |
1174 | widget.decoration?.errorText != null || |
1175 | widget.decoration?.error != null || |
1176 | _hasIntrinsicError; |
1177 | |
1178 | Color get _errorColor => |
1179 | widget.cursorErrorColor ?? |
1180 | _getEffectiveDecoration().errorStyle?.color ?? |
1181 | Theme.of(context).colorScheme.error; |
1182 | |
1183 | InputDecoration _getEffectiveDecoration() { |
1184 | final MaterialLocalizations localizations = MaterialLocalizations.of(context); |
1185 | final ThemeData themeData = Theme.of(context); |
1186 | final InputDecoration effectiveDecoration = (widget.decoration ?? const InputDecoration()) |
1187 | .applyDefaults(themeData.inputDecorationTheme) |
1188 | .copyWith( |
1189 | enabled: _isEnabled, |
1190 | hintMaxLines: |
1191 | widget.decoration?.hintMaxLines ?? |
1192 | themeData.inputDecorationTheme.hintMaxLines ?? |
1193 | widget.maxLines, |
1194 | ); |
1195 | |
1196 | // No need to build anything if counter or counterText were given directly. |
1197 | if (effectiveDecoration.counter != null || effectiveDecoration.counterText != null) { |
1198 | return effectiveDecoration; |
1199 | } |
1200 | |
1201 | // If buildCounter was provided, use it to generate a counter widget. |
1202 | Widget? counter; |
1203 | final int currentLength = _currentLength; |
1204 | if (effectiveDecoration.counter == null && |
1205 | effectiveDecoration.counterText == null && |
1206 | widget.buildCounter != null) { |
1207 | final bool isFocused = _effectiveFocusNode.hasFocus; |
1208 | final Widget? builtCounter = widget.buildCounter!( |
1209 | context, |
1210 | currentLength: currentLength, |
1211 | maxLength: widget.maxLength, |
1212 | isFocused: isFocused, |
1213 | ); |
1214 | // If buildCounter returns null, don't add a counter widget to the field. |
1215 | if (builtCounter != null) { |
1216 | counter = Semantics(container: true, liveRegion: isFocused, child: builtCounter); |
1217 | } |
1218 | return effectiveDecoration.copyWith(counter: counter); |
1219 | } |
1220 | |
1221 | if (widget.maxLength == null) { |
1222 | return effectiveDecoration; |
1223 | } // No counter widget |
1224 | |
1225 | String counterText = ' $currentLength' ; |
1226 | String semanticCounterText = '' ; |
1227 | |
1228 | // Handle a real maxLength (positive number) |
1229 | if (widget.maxLength! > 0) { |
1230 | // Show the maxLength in the counter |
1231 | counterText += '/ ${widget.maxLength}' ; |
1232 | final int remaining = (widget.maxLength! - currentLength).clamp(0, widget.maxLength!); |
1233 | semanticCounterText = localizations.remainingTextFieldCharacterCount(remaining); |
1234 | } |
1235 | |
1236 | if (_hasIntrinsicError) { |
1237 | return effectiveDecoration.copyWith( |
1238 | errorText: effectiveDecoration.errorText ?? '' , |
1239 | counterStyle: |
1240 | effectiveDecoration.errorStyle ?? |
1241 | (themeData.useMaterial3 |
1242 | ? _m3CounterErrorStyle(context) |
1243 | : _m2CounterErrorStyle(context)), |
1244 | counterText: counterText, |
1245 | semanticCounterText: semanticCounterText, |
1246 | ); |
1247 | } |
1248 | |
1249 | return effectiveDecoration.copyWith( |
1250 | counterText: counterText, |
1251 | semanticCounterText: semanticCounterText, |
1252 | ); |
1253 | } |
1254 | |
1255 | @override |
1256 | void initState() { |
1257 | super.initState(); |
1258 | _selectionGestureDetectorBuilder = _TextFieldSelectionGestureDetectorBuilder(state: this); |
1259 | if (widget.controller == null) { |
1260 | _createLocalController(); |
1261 | } |
1262 | _effectiveFocusNode.canRequestFocus = widget.canRequestFocus && _isEnabled; |
1263 | _effectiveFocusNode.addListener(_handleFocusChanged); |
1264 | _initStatesController(); |
1265 | } |
1266 | |
1267 | bool get _canRequestFocus { |
1268 | final NavigationMode mode = |
1269 | MediaQuery.maybeNavigationModeOf(context) ?? NavigationMode.traditional; |
1270 | return switch (mode) { |
1271 | NavigationMode.traditional => widget.canRequestFocus && _isEnabled, |
1272 | NavigationMode.directional => true, |
1273 | }; |
1274 | } |
1275 | |
1276 | @override |
1277 | void didChangeDependencies() { |
1278 | super.didChangeDependencies(); |
1279 | _effectiveFocusNode.canRequestFocus = _canRequestFocus; |
1280 | } |
1281 | |
1282 | @override |
1283 | void didUpdateWidget(TextField oldWidget) { |
1284 | super.didUpdateWidget(oldWidget); |
1285 | if (widget.controller == null && oldWidget.controller != null) { |
1286 | _createLocalController(oldWidget.controller!.value); |
1287 | } else if (widget.controller != null && oldWidget.controller == null) { |
1288 | unregisterFromRestoration(_controller!); |
1289 | _controller!.dispose(); |
1290 | _controller = null; |
1291 | } |
1292 | |
1293 | if (widget.focusNode != oldWidget.focusNode) { |
1294 | (oldWidget.focusNode ?? _focusNode)?.removeListener(_handleFocusChanged); |
1295 | (widget.focusNode ?? _focusNode)?.addListener(_handleFocusChanged); |
1296 | } |
1297 | |
1298 | _effectiveFocusNode.canRequestFocus = _canRequestFocus; |
1299 | |
1300 | if (_effectiveFocusNode.hasFocus && widget.readOnly != oldWidget.readOnly && _isEnabled) { |
1301 | if (_effectiveController.selection.isCollapsed) { |
1302 | _showSelectionHandles = !widget.readOnly; |
1303 | } |
1304 | } |
1305 | |
1306 | if (widget.statesController == oldWidget.statesController) { |
1307 | _statesController.update(MaterialState.disabled, !_isEnabled); |
1308 | _statesController.update(MaterialState.hovered, _isHovering); |
1309 | _statesController.update(MaterialState.focused, _effectiveFocusNode.hasFocus); |
1310 | _statesController.update(MaterialState.error, _hasError); |
1311 | } else { |
1312 | oldWidget.statesController?.removeListener(_handleStatesControllerChange); |
1313 | if (widget.statesController != null) { |
1314 | _internalStatesController?.dispose(); |
1315 | _internalStatesController = null; |
1316 | } |
1317 | _initStatesController(); |
1318 | } |
1319 | } |
1320 | |
1321 | @override |
1322 | void restoreState(RestorationBucket? oldBucket, bool initialRestore) { |
1323 | if (_controller != null) { |
1324 | _registerController(); |
1325 | } |
1326 | } |
1327 | |
1328 | void _registerController() { |
1329 | assert(_controller != null); |
1330 | registerForRestoration(_controller!, 'controller' ); |
1331 | } |
1332 | |
1333 | void _createLocalController([TextEditingValue? value]) { |
1334 | assert(_controller == null); |
1335 | _controller = value == null |
1336 | ? RestorableTextEditingController() |
1337 | : RestorableTextEditingController.fromValue(value); |
1338 | if (!restorePending) { |
1339 | _registerController(); |
1340 | } |
1341 | } |
1342 | |
1343 | @override |
1344 | String? get restorationId => widget.restorationId; |
1345 | |
1346 | @override |
1347 | void dispose() { |
1348 | _effectiveFocusNode.removeListener(_handleFocusChanged); |
1349 | _focusNode?.dispose(); |
1350 | _controller?.dispose(); |
1351 | _statesController.removeListener(_handleStatesControllerChange); |
1352 | _internalStatesController?.dispose(); |
1353 | super.dispose(); |
1354 | } |
1355 | |
1356 | EditableTextState? get _editableText => editableTextKey.currentState; |
1357 | |
1358 | void _requestKeyboard() { |
1359 | _editableText?.requestKeyboard(); |
1360 | } |
1361 | |
1362 | bool _shouldShowSelectionHandles(SelectionChangedCause? cause) { |
1363 | // When the text field is activated by something that doesn't trigger the |
1364 | // selection toolbar, we shouldn't show the handles either. |
1365 | if (!_selectionGestureDetectorBuilder.shouldShowSelectionToolbar || |
1366 | !_selectionGestureDetectorBuilder.shouldShowSelectionHandles) { |
1367 | return false; |
1368 | } |
1369 | |
1370 | if (cause == SelectionChangedCause.keyboard) { |
1371 | return false; |
1372 | } |
1373 | |
1374 | if (widget.readOnly && _effectiveController.selection.isCollapsed) { |
1375 | return false; |
1376 | } |
1377 | |
1378 | if (!_isEnabled) { |
1379 | return false; |
1380 | } |
1381 | |
1382 | if (cause == SelectionChangedCause.longPress || |
1383 | cause == SelectionChangedCause.stylusHandwriting) { |
1384 | return true; |
1385 | } |
1386 | |
1387 | if (_effectiveController.text.isNotEmpty) { |
1388 | return true; |
1389 | } |
1390 | |
1391 | return false; |
1392 | } |
1393 | |
1394 | void _handleFocusChanged() { |
1395 | setState(() { |
1396 | // Rebuild the widget on focus change to show/hide the text selection |
1397 | // highlight. |
1398 | }); |
1399 | _statesController.update(MaterialState.focused, _effectiveFocusNode.hasFocus); |
1400 | } |
1401 | |
1402 | void _handleSelectionChanged(TextSelection selection, SelectionChangedCause? cause) { |
1403 | final bool willShowSelectionHandles = _shouldShowSelectionHandles(cause); |
1404 | if (willShowSelectionHandles != _showSelectionHandles) { |
1405 | setState(() { |
1406 | _showSelectionHandles = willShowSelectionHandles; |
1407 | }); |
1408 | } |
1409 | |
1410 | switch (Theme.of(context).platform) { |
1411 | case TargetPlatform.iOS: |
1412 | case TargetPlatform.macOS: |
1413 | case TargetPlatform.linux: |
1414 | case TargetPlatform.windows: |
1415 | case TargetPlatform.fuchsia: |
1416 | case TargetPlatform.android: |
1417 | if (cause == SelectionChangedCause.longPress) { |
1418 | _editableText?.bringIntoView(selection.extent); |
1419 | } |
1420 | } |
1421 | |
1422 | switch (Theme.of(context).platform) { |
1423 | case TargetPlatform.iOS: |
1424 | case TargetPlatform.fuchsia: |
1425 | case TargetPlatform.android: |
1426 | break; |
1427 | case TargetPlatform.macOS: |
1428 | case TargetPlatform.linux: |
1429 | case TargetPlatform.windows: |
1430 | if (cause == SelectionChangedCause.drag) { |
1431 | _editableText?.hideToolbar(); |
1432 | } |
1433 | } |
1434 | } |
1435 | |
1436 | /// Toggle the toolbar when a selection handle is tapped. |
1437 | void _handleSelectionHandleTapped() { |
1438 | if (_effectiveController.selection.isCollapsed) { |
1439 | _editableText!.toggleToolbar(); |
1440 | } |
1441 | } |
1442 | |
1443 | void _handleHover(bool hovering) { |
1444 | if (hovering != _isHovering) { |
1445 | setState(() { |
1446 | _isHovering = hovering; |
1447 | }); |
1448 | _statesController.update(MaterialState.hovered, _isHovering); |
1449 | } |
1450 | } |
1451 | |
1452 | // Material states controller. |
1453 | MaterialStatesController? _internalStatesController; |
1454 | |
1455 | void _handleStatesControllerChange() { |
1456 | // Force a rebuild to resolve MaterialStateProperty properties. |
1457 | setState(() {}); |
1458 | } |
1459 | |
1460 | MaterialStatesController get _statesController => |
1461 | widget.statesController ?? _internalStatesController!; |
1462 | |
1463 | void _initStatesController() { |
1464 | if (widget.statesController == null) { |
1465 | _internalStatesController = MaterialStatesController(); |
1466 | } |
1467 | _statesController.update(MaterialState.disabled, !_isEnabled); |
1468 | _statesController.update(MaterialState.hovered, _isHovering); |
1469 | _statesController.update(MaterialState.focused, _effectiveFocusNode.hasFocus); |
1470 | _statesController.update(MaterialState.error, _hasError); |
1471 | _statesController.addListener(_handleStatesControllerChange); |
1472 | } |
1473 | |
1474 | // AutofillClient implementation start. |
1475 | @override |
1476 | String get autofillId => _editableText!.autofillId; |
1477 | |
1478 | @override |
1479 | void autofill(TextEditingValue newEditingValue) => _editableText!.autofill(newEditingValue); |
1480 | |
1481 | @override |
1482 | TextInputConfiguration get textInputConfiguration { |
1483 | final List<String>? autofillHints = widget.autofillHints?.toList(growable: false); |
1484 | final AutofillConfiguration autofillConfiguration = autofillHints != null |
1485 | ? AutofillConfiguration( |
1486 | uniqueIdentifier: autofillId, |
1487 | autofillHints: autofillHints, |
1488 | currentEditingValue: _effectiveController.value, |
1489 | hintText: (widget.decoration ?? const InputDecoration()).hintText, |
1490 | ) |
1491 | : AutofillConfiguration.disabled; |
1492 | |
1493 | return _editableText!.textInputConfiguration.copyWith( |
1494 | autofillConfiguration: autofillConfiguration, |
1495 | ); |
1496 | } |
1497 | // AutofillClient implementation end. |
1498 | |
1499 | TextStyle _getInputStyleForState(TextStyle style) { |
1500 | final ThemeData theme = Theme.of(context); |
1501 | final TextStyle stateStyle = MaterialStateProperty.resolveAs( |
1502 | theme.useMaterial3 ? _m3StateInputStyle(context)! : _m2StateInputStyle(context)!, |
1503 | _statesController.value, |
1504 | ); |
1505 | final TextStyle providedStyle = MaterialStateProperty.resolveAs(style, _statesController.value); |
1506 | return providedStyle.merge(stateStyle); |
1507 | } |
1508 | |
1509 | @override |
1510 | Widget build(BuildContext context) { |
1511 | assert(debugCheckHasMaterial(context)); |
1512 | assert(debugCheckHasMaterialLocalizations(context)); |
1513 | assert(debugCheckHasDirectionality(context)); |
1514 | assert( |
1515 | !(widget.style != null && |
1516 | !widget.style!.inherit && |
1517 | (widget.style!.fontSize == null || widget.style!.textBaseline == null)), |
1518 | 'inherit false style must supply fontSize and textBaseline' , |
1519 | ); |
1520 | |
1521 | final ThemeData theme = Theme.of(context); |
1522 | final DefaultSelectionStyle selectionStyle = DefaultSelectionStyle.of(context); |
1523 | final TextStyle? providedStyle = MaterialStateProperty.resolveAs( |
1524 | widget.style, |
1525 | _statesController.value, |
1526 | ); |
1527 | final TextStyle style = _getInputStyleForState( |
1528 | theme.useMaterial3 ? _m3InputStyle(context) : theme.textTheme.titleMedium!, |
1529 | ).merge(providedStyle); |
1530 | final Brightness keyboardAppearance = widget.keyboardAppearance ?? theme.brightness; |
1531 | final TextEditingController controller = _effectiveController; |
1532 | final FocusNode focusNode = _effectiveFocusNode; |
1533 | final List<TextInputFormatter> formatters = <TextInputFormatter>[ |
1534 | ...?widget.inputFormatters, |
1535 | if (widget.maxLength != null) |
1536 | LengthLimitingTextInputFormatter( |
1537 | widget.maxLength, |
1538 | maxLengthEnforcement: _effectiveMaxLengthEnforcement, |
1539 | ), |
1540 | ]; |
1541 | |
1542 | // Set configuration as disabled if not otherwise specified. If specified, |
1543 | // ensure that configuration uses the correct style for misspelled words for |
1544 | // the current platform, unless a custom style is specified. |
1545 | final SpellCheckConfiguration spellCheckConfiguration; |
1546 | switch (defaultTargetPlatform) { |
1547 | case TargetPlatform.iOS: |
1548 | case TargetPlatform.macOS: |
1549 | spellCheckConfiguration = CupertinoTextField.inferIOSSpellCheckConfiguration( |
1550 | widget.spellCheckConfiguration, |
1551 | ); |
1552 | case TargetPlatform.android: |
1553 | case TargetPlatform.fuchsia: |
1554 | case TargetPlatform.linux: |
1555 | case TargetPlatform.windows: |
1556 | spellCheckConfiguration = TextField.inferAndroidSpellCheckConfiguration( |
1557 | widget.spellCheckConfiguration, |
1558 | ); |
1559 | } |
1560 | |
1561 | TextSelectionControls? textSelectionControls = widget.selectionControls; |
1562 | final bool paintCursorAboveText; |
1563 | bool? cursorOpacityAnimates = widget.cursorOpacityAnimates; |
1564 | Offset? cursorOffset; |
1565 | final Color cursorColor; |
1566 | final Color selectionColor; |
1567 | Color? autocorrectionTextRectColor; |
1568 | Radius? cursorRadius = widget.cursorRadius; |
1569 | VoidCallback? handleDidGainAccessibilityFocus; |
1570 | VoidCallback? handleDidLoseAccessibilityFocus; |
1571 | |
1572 | switch (theme.platform) { |
1573 | case TargetPlatform.iOS: |
1574 | final CupertinoThemeData cupertinoTheme = CupertinoTheme.of(context); |
1575 | forcePressEnabled = true; |
1576 | textSelectionControls ??= cupertinoTextSelectionHandleControls; |
1577 | paintCursorAboveText = true; |
1578 | cursorOpacityAnimates ??= true; |
1579 | cursorColor = _hasError |
1580 | ? _errorColor |
1581 | : widget.cursorColor ?? selectionStyle.cursorColor ?? cupertinoTheme.primaryColor; |
1582 | selectionColor = |
1583 | selectionStyle.selectionColor ?? cupertinoTheme.primaryColor.withOpacity(0.40); |
1584 | cursorRadius ??= const Radius.circular(2.0); |
1585 | cursorOffset = Offset(iOSHorizontalOffset / MediaQuery.devicePixelRatioOf(context), 0); |
1586 | autocorrectionTextRectColor = selectionColor; |
1587 | |
1588 | case TargetPlatform.macOS: |
1589 | final CupertinoThemeData cupertinoTheme = CupertinoTheme.of(context); |
1590 | forcePressEnabled = false; |
1591 | textSelectionControls ??= cupertinoDesktopTextSelectionHandleControls; |
1592 | paintCursorAboveText = true; |
1593 | cursorOpacityAnimates ??= false; |
1594 | cursorColor = _hasError |
1595 | ? _errorColor |
1596 | : widget.cursorColor ?? selectionStyle.cursorColor ?? cupertinoTheme.primaryColor; |
1597 | selectionColor = |
1598 | selectionStyle.selectionColor ?? cupertinoTheme.primaryColor.withOpacity(0.40); |
1599 | cursorRadius ??= const Radius.circular(2.0); |
1600 | cursorOffset = Offset(iOSHorizontalOffset / MediaQuery.devicePixelRatioOf(context), 0); |
1601 | handleDidGainAccessibilityFocus = () { |
1602 | // Automatically activate the TextField when it receives accessibility focus. |
1603 | if (!_effectiveFocusNode.hasFocus && _effectiveFocusNode.canRequestFocus) { |
1604 | _effectiveFocusNode.requestFocus(); |
1605 | } |
1606 | }; |
1607 | handleDidLoseAccessibilityFocus = () { |
1608 | _effectiveFocusNode.unfocus(); |
1609 | }; |
1610 | |
1611 | case TargetPlatform.android: |
1612 | case TargetPlatform.fuchsia: |
1613 | forcePressEnabled = false; |
1614 | textSelectionControls ??= materialTextSelectionHandleControls; |
1615 | paintCursorAboveText = false; |
1616 | cursorOpacityAnimates ??= false; |
1617 | cursorColor = _hasError |
1618 | ? _errorColor |
1619 | : widget.cursorColor ?? selectionStyle.cursorColor ?? theme.colorScheme.primary; |
1620 | selectionColor = |
1621 | selectionStyle.selectionColor ?? theme.colorScheme.primary.withOpacity(0.40); |
1622 | |
1623 | case TargetPlatform.linux: |
1624 | forcePressEnabled = false; |
1625 | textSelectionControls ??= desktopTextSelectionHandleControls; |
1626 | paintCursorAboveText = false; |
1627 | cursorOpacityAnimates ??= false; |
1628 | cursorColor = _hasError |
1629 | ? _errorColor |
1630 | : widget.cursorColor ?? selectionStyle.cursorColor ?? theme.colorScheme.primary; |
1631 | selectionColor = |
1632 | selectionStyle.selectionColor ?? theme.colorScheme.primary.withOpacity(0.40); |
1633 | handleDidGainAccessibilityFocus = () { |
1634 | // Automatically activate the TextField when it receives accessibility focus. |
1635 | if (!_effectiveFocusNode.hasFocus && _effectiveFocusNode.canRequestFocus) { |
1636 | _effectiveFocusNode.requestFocus(); |
1637 | } |
1638 | }; |
1639 | handleDidLoseAccessibilityFocus = () { |
1640 | _effectiveFocusNode.unfocus(); |
1641 | }; |
1642 | |
1643 | case TargetPlatform.windows: |
1644 | forcePressEnabled = false; |
1645 | textSelectionControls ??= desktopTextSelectionHandleControls; |
1646 | paintCursorAboveText = false; |
1647 | cursorOpacityAnimates ??= false; |
1648 | cursorColor = _hasError |
1649 | ? _errorColor |
1650 | : widget.cursorColor ?? selectionStyle.cursorColor ?? theme.colorScheme.primary; |
1651 | selectionColor = |
1652 | selectionStyle.selectionColor ?? theme.colorScheme.primary.withOpacity(0.40); |
1653 | handleDidGainAccessibilityFocus = () { |
1654 | // Automatically activate the TextField when it receives accessibility focus. |
1655 | if (!_effectiveFocusNode.hasFocus && _effectiveFocusNode.canRequestFocus) { |
1656 | _effectiveFocusNode.requestFocus(); |
1657 | } |
1658 | }; |
1659 | handleDidLoseAccessibilityFocus = () { |
1660 | _effectiveFocusNode.unfocus(); |
1661 | }; |
1662 | } |
1663 | |
1664 | Widget child = RepaintBoundary( |
1665 | child: UnmanagedRestorationScope( |
1666 | bucket: bucket, |
1667 | child: EditableText( |
1668 | key: editableTextKey, |
1669 | readOnly: widget.readOnly || !_isEnabled, |
1670 | toolbarOptions: widget.toolbarOptions, |
1671 | showCursor: widget.showCursor, |
1672 | showSelectionHandles: _showSelectionHandles, |
1673 | controller: controller, |
1674 | focusNode: focusNode, |
1675 | undoController: widget.undoController, |
1676 | keyboardType: widget.keyboardType, |
1677 | textInputAction: widget.textInputAction, |
1678 | textCapitalization: widget.textCapitalization, |
1679 | style: style, |
1680 | strutStyle: widget.strutStyle, |
1681 | textAlign: widget.textAlign, |
1682 | textDirection: widget.textDirection, |
1683 | autofocus: widget.autofocus, |
1684 | obscuringCharacter: widget.obscuringCharacter, |
1685 | obscureText: widget.obscureText, |
1686 | autocorrect: widget.autocorrect, |
1687 | smartDashesType: widget.smartDashesType, |
1688 | smartQuotesType: widget.smartQuotesType, |
1689 | enableSuggestions: widget.enableSuggestions, |
1690 | maxLines: widget.maxLines, |
1691 | minLines: widget.minLines, |
1692 | expands: widget.expands, |
1693 | // Only show the selection highlight when the text field is focused. |
1694 | selectionColor: focusNode.hasFocus ? selectionColor : null, |
1695 | selectionControls: widget.selectionEnabled ? textSelectionControls : null, |
1696 | onChanged: widget.onChanged, |
1697 | onSelectionChanged: _handleSelectionChanged, |
1698 | onEditingComplete: widget.onEditingComplete, |
1699 | onSubmitted: widget.onSubmitted, |
1700 | onAppPrivateCommand: widget.onAppPrivateCommand, |
1701 | groupId: widget.groupId, |
1702 | onSelectionHandleTapped: _handleSelectionHandleTapped, |
1703 | onTapOutside: widget.onTapOutside, |
1704 | onTapUpOutside: widget.onTapUpOutside, |
1705 | inputFormatters: formatters, |
1706 | rendererIgnoresPointer: true, |
1707 | mouseCursor: MouseCursor.defer, // TextField will handle the cursor |
1708 | cursorWidth: widget.cursorWidth, |
1709 | cursorHeight: widget.cursorHeight, |
1710 | cursorRadius: cursorRadius, |
1711 | cursorColor: cursorColor, |
1712 | selectionHeightStyle: widget.selectionHeightStyle, |
1713 | selectionWidthStyle: widget.selectionWidthStyle, |
1714 | cursorOpacityAnimates: cursorOpacityAnimates, |
1715 | cursorOffset: cursorOffset, |
1716 | paintCursorAboveText: paintCursorAboveText, |
1717 | backgroundCursorColor: CupertinoColors.inactiveGray, |
1718 | scrollPadding: widget.scrollPadding, |
1719 | keyboardAppearance: keyboardAppearance, |
1720 | enableInteractiveSelection: widget.enableInteractiveSelection, |
1721 | selectAllOnFocus: widget.selectAllOnFocus, |
1722 | dragStartBehavior: widget.dragStartBehavior, |
1723 | scrollController: widget.scrollController, |
1724 | scrollPhysics: widget.scrollPhysics, |
1725 | autofillHints: widget.autofillHints, |
1726 | autofillClient: this, |
1727 | autocorrectionTextRectColor: autocorrectionTextRectColor, |
1728 | clipBehavior: widget.clipBehavior, |
1729 | restorationId: 'editable' , |
1730 | scribbleEnabled: widget.scribbleEnabled, |
1731 | stylusHandwritingEnabled: widget.stylusHandwritingEnabled, |
1732 | enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning, |
1733 | contentInsertionConfiguration: widget.contentInsertionConfiguration, |
1734 | contextMenuBuilder: widget.contextMenuBuilder, |
1735 | spellCheckConfiguration: spellCheckConfiguration, |
1736 | magnifierConfiguration: |
1737 | widget.magnifierConfiguration ?? TextMagnifier.adaptiveMagnifierConfiguration, |
1738 | hintLocales: widget.hintLocales, |
1739 | ), |
1740 | ), |
1741 | ); |
1742 | |
1743 | if (widget.decoration != null) { |
1744 | child = AnimatedBuilder( |
1745 | animation: Listenable.merge(<Listenable>[focusNode, controller]), |
1746 | builder: (BuildContext context, Widget? child) { |
1747 | return InputDecorator( |
1748 | decoration: _getEffectiveDecoration(), |
1749 | baseStyle: widget.style, |
1750 | textAlign: widget.textAlign, |
1751 | textAlignVertical: widget.textAlignVertical, |
1752 | isHovering: _isHovering, |
1753 | isFocused: focusNode.hasFocus, |
1754 | isEmpty: controller.value.text.isEmpty, |
1755 | expands: widget.expands, |
1756 | child: child, |
1757 | ); |
1758 | }, |
1759 | child: child, |
1760 | ); |
1761 | } |
1762 | final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor>( |
1763 | widget.mouseCursor ?? MaterialStateMouseCursor.textable, |
1764 | _statesController.value, |
1765 | ); |
1766 | |
1767 | final int? semanticsMaxValueLength; |
1768 | if (_effectiveMaxLengthEnforcement != MaxLengthEnforcement.none && |
1769 | widget.maxLength != null && |
1770 | widget.maxLength! > 0) { |
1771 | semanticsMaxValueLength = widget.maxLength; |
1772 | } else { |
1773 | semanticsMaxValueLength = null; |
1774 | } |
1775 | |
1776 | return MouseRegion( |
1777 | cursor: effectiveMouseCursor, |
1778 | onEnter: (PointerEnterEvent event) => _handleHover(true), |
1779 | onExit: (PointerExitEvent event) => _handleHover(false), |
1780 | child: TextFieldTapRegion( |
1781 | child: IgnorePointer( |
1782 | ignoring: widget.ignorePointers ?? !_isEnabled, |
1783 | child: AnimatedBuilder( |
1784 | animation: controller, // changes the _currentLength |
1785 | builder: (BuildContext context, Widget? child) { |
1786 | return Semantics( |
1787 | enabled: _isEnabled, |
1788 | maxValueLength: semanticsMaxValueLength, |
1789 | currentValueLength: _currentLength, |
1790 | onTap: widget.readOnly |
1791 | ? null |
1792 | : () { |
1793 | if (!_effectiveController.selection.isValid) { |
1794 | _effectiveController.selection = TextSelection.collapsed( |
1795 | offset: _effectiveController.text.length, |
1796 | ); |
1797 | } |
1798 | _requestKeyboard(); |
1799 | }, |
1800 | onDidGainAccessibilityFocus: handleDidGainAccessibilityFocus, |
1801 | onDidLoseAccessibilityFocus: handleDidLoseAccessibilityFocus, |
1802 | onFocus: _isEnabled |
1803 | ? () { |
1804 | assert( |
1805 | _effectiveFocusNode.canRequestFocus, |
1806 | 'Received SemanticsAction.focus from the engine. However, the FocusNode ' |
1807 | 'of this text field cannot gain focus. This likely indicates a bug. ' |
1808 | 'If this text field cannot be focused (e.g. because it is not ' |
1809 | 'enabled), then its corresponding semantics node must be configured ' |
1810 | 'such that the assistive technology cannot request focus on it.' , |
1811 | ); |
1812 | |
1813 | if (_effectiveFocusNode.canRequestFocus && !_effectiveFocusNode.hasFocus) { |
1814 | _effectiveFocusNode.requestFocus(); |
1815 | } else if (!widget.readOnly) { |
1816 | // If the platform requested focus, that means that previously the |
1817 | // platform believed that the text field did not have focus (even |
1818 | // though Flutter's widget system believed otherwise). This likely |
1819 | // means that the on-screen keyboard is hidden, or more generally, |
1820 | // there is no current editing session in this field. To correct |
1821 | // that, keyboard must be requested. |
1822 | // |
1823 | // A concrete scenario where this can happen is when the user |
1824 | // dismisses the keyboard on the web. The editing session is |
1825 | // closed by the engine, but the text field widget stays focused |
1826 | // in the framework. |
1827 | _requestKeyboard(); |
1828 | } |
1829 | } |
1830 | : null, |
1831 | child: child, |
1832 | ); |
1833 | }, |
1834 | child: _selectionGestureDetectorBuilder.buildGestureDetector( |
1835 | behavior: HitTestBehavior.translucent, |
1836 | child: child, |
1837 | ), |
1838 | ), |
1839 | ), |
1840 | ), |
1841 | ); |
1842 | } |
1843 | } |
1844 | |
1845 | TextStyle? _m2StateInputStyle(BuildContext context) => |
1846 | MaterialStateTextStyle.resolveWith((Set<MaterialState> states) { |
1847 | final ThemeData theme = Theme.of(context); |
1848 | if (states.contains(MaterialState.disabled)) { |
1849 | return TextStyle(color: theme.disabledColor); |
1850 | } |
1851 | return TextStyle(color: theme.textTheme.titleMedium?.color); |
1852 | }); |
1853 | |
1854 | TextStyle _m2CounterErrorStyle(BuildContext context) => |
1855 | Theme.of(context).textTheme.bodySmall!.copyWith(color: Theme.of(context).colorScheme.error); |
1856 | |
1857 | // BEGIN GENERATED TOKEN PROPERTIES - TextField |
1858 | |
1859 | // Do not edit by hand. The code between the "BEGIN GENERATED" and |
1860 | // "END GENERATED" comments are generated from data in the Material |
1861 | // Design token database by the script: |
1862 | // dev/tools/gen_defaults/bin/gen_defaults.dart. |
1863 | |
1864 | // dart format off |
1865 | TextStyle? _m3StateInputStyle(BuildContext context) => MaterialStateTextStyle.resolveWith((Set<MaterialState> states) { |
1866 | if (states.contains(MaterialState.disabled)) { |
1867 | return TextStyle(color: Theme.of(context).textTheme.bodyLarge!.color?.withOpacity(0.38)); |
1868 | } |
1869 | return TextStyle(color: Theme.of(context).textTheme.bodyLarge!.color); |
1870 | }); |
1871 | |
1872 | TextStyle _m3InputStyle(BuildContext context) => Theme.of(context).textTheme.bodyLarge!; |
1873 | |
1874 | TextStyle _m3CounterErrorStyle(BuildContext context) => |
1875 | Theme.of(context).textTheme.bodySmall!.copyWith(color: Theme.of(context).colorScheme.error); |
1876 | // dart format on |
1877 | |
1878 | // END GENERATED TOKEN PROPERTIES - TextField |
1879 | |