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';
10library;
11
12import 'dart:ui' as ui show BoxHeightStyle, BoxWidthStyle;
13
14import 'package:flutter/cupertino.dart';
15import 'package:flutter/foundation.dart';
16import 'package:flutter/gestures.dart';
17import 'package:flutter/rendering.dart';
18import 'package:flutter/services.dart';
19
20import 'adaptive_text_selection_toolbar.dart';
21import 'color_scheme.dart';
22import 'colors.dart';
23import 'debug.dart';
24import 'desktop_text_selection.dart';
25import 'input_decorator.dart';
26import 'magnifier.dart';
27import 'material_localizations.dart';
28import 'material_state.dart';
29import 'selectable_text.dart' show iOSHorizontalOffset;
30import 'spell_check_suggestions_toolbar.dart';
31import 'text_selection.dart';
32import 'theme.dart';
33
34export '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.
42typedef 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
58class _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)
196class 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
1127class _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
1845TextStyle? _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
1854TextStyle _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
1865TextStyle? _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
1872TextStyle _m3InputStyle(BuildContext context) => Theme.of(context).textTheme.bodyLarge!;
1873
1874TextStyle _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