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