text_field.dart 24 KB
Newer Older
1 2 3 4
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import 'dart:collection';

xster's avatar
xster committed
7
import 'package:flutter/cupertino.dart';
8
import 'package:flutter/rendering.dart';
9 10 11
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';

12
import 'debug.dart';
13
import 'feedback.dart';
14
import 'ink_well.dart' show InteractiveInkFeature;
15
import 'input_decorator.dart';
16
import 'material.dart';
17
import 'material_localizations.dart';
18 19 20
import 'text_selection.dart';
import 'theme.dart';

21
export 'package:flutter/services.dart' show TextInputType, TextInputAction, TextCapitalization;
22

23
/// A material design text field.
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
///
/// A text field lets the user enter text, either with hardware keyboard or with
/// an onscreen keyboard.
///
/// The text field calls the [onChanged] callback whenever the user changes the
/// text in the field. If the user indicates that they are done typing in the
/// field (e.g., by pressing a button on the soft keyboard), the text field
/// calls the [onSubmitted] callback.
///
/// To control the text that is displayed in the text field, use the
/// [controller]. For example, to set the initial value of the text field, use
/// a [controller] that already contains some text. The [controller] can also
/// control the selection and composing region (and to observe changes to the
/// text, selection, and composing region).
///
/// By default, a text field has a [decoration] that draws a divider below the
/// text field. You can use the [decoration] property to control the decoration,
/// for example by adding a label or an icon. If you set the [decoration]
/// property to null, the decoration will be removed entirely, including the
/// extra padding introduced by the decoration to save space for the labels.
///
/// If [decoration] is non-null (which is the default), the text field requires
46 47 48
/// one of its ancestors to be a [Material] widget. When the [TextField] is
/// tapped an ink splash that paints on the material is triggered, see
/// [ThemeData.splashFactory].
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
///
/// To integrate the [TextField] into a [Form] with other [FormField] widgets,
/// consider using [TextFormField].
///
/// See also:
///
///  * <https://material.google.com/components/text-fields.html>
///  * [TextFormField], which integrates with the [Form] widget.
///  * [InputDecorator], which shows the labels and other visual elements that
///    surround the actual text editing widget.
///  * [EditableText], which is the raw text editing control at the heart of a
///    [TextField]. (The [EditableText] widget is rarely used directly unless
///    you are implementing an entirely different design language, such as
///    Cupertino.)
class TextField extends StatefulWidget {
  /// Creates a Material Design text field.
  ///
  /// If [decoration] is non-null (which is the default), the text field requires
  /// one of its ancestors to be a [Material] widget.
  ///
  /// To remove the decoration entirely (including the extra padding introduced
  /// by the decoration to save space for the labels), set the [decoration] to
  /// null.
72 73
  ///
  /// The [maxLines] property can be set to null to remove the restriction on
74
  /// the number of lines. By default, it is one, meaning this is a single-line
75
  /// text field. [maxLines] must not be zero.
76 77 78 79 80 81 82 83 84 85 86 87 88 89
  ///
  /// The [maxLength] property is set to null by default, which means the
  /// number of characters allowed in the text field is not restricted. If
  /// [maxLength] is set, a character counter will be displayed below the
  /// field, showing how many characters have been entered and how many are
  /// allowed. After [maxLength] characters have been input, additional input
  /// is ignored, unless [maxLengthEnforced] is set to false. The TextField
  /// enforces the length with a [LengthLimitingTextInputFormatter], which is
  /// evaluated after the supplied [inputFormatters], if any. The [maxLength]
  /// value must be either null or greater than zero.
  ///
  /// If [maxLengthEnforced] is set to false, then more than [maxLength]
  /// characters may be entered, and the error counter and divider will
  /// switch to the [decoration.errorStyle] when the limit is exceeded.
90
  ///
91 92
  /// The [textAlign], [autofocus], [obscureText], and [autocorrect] arguments
  /// must not be null.
93 94 95 96 97
  ///
  /// See also:
  ///
  ///  * [maxLength], which discusses the precise meaning of "number of
  ///    characters" and how it may differ from the intuitive meaning.
98
  const TextField({
99 100 101
    Key key,
    this.controller,
    this.focusNode,
102
    this.decoration = const InputDecoration(),
103 104
    TextInputType keyboardType,
    this.textInputAction,
105
    this.textCapitalization = TextCapitalization.none,
106
    this.style,
107 108 109 110 111
    this.textAlign = TextAlign.start,
    this.autofocus = false,
    this.obscureText = false,
    this.autocorrect = true,
    this.maxLines = 1,
112
    this.maxLength,
113
    this.maxLengthEnforced = true,
114
    this.onChanged,
115
    this.onEditingComplete,
116
    this.onSubmitted,
117
    this.inputFormatters,
118
    this.enabled,
119 120 121
    this.cursorWidth = 2.0,
    this.cursorRadius,
    this.cursorColor,
122
    this.keyboardAppearance,
123
    this.scrollPadding = const EdgeInsets.all(20.0),
124
  }) : assert(textAlign != null),
125 126
       assert(autofocus != null),
       assert(obscureText != null),
127
       assert(autocorrect != null),
128
       assert(maxLengthEnforced != null),
129
       assert(scrollPadding != null),
130
       assert(maxLines == null || maxLines > 0),
131
       assert(maxLength == null || maxLength > 0),
132
       keyboardType = keyboardType ?? (maxLines == 1 ? TextInputType.text : TextInputType.multiline),
133
       super(key: key);
134 135 136

  /// Controls the text being edited.
  ///
137
  /// If null, this widget will create its own [TextEditingController].
138 139 140 141 142 143 144 145 146
  final TextEditingController controller;

  /// Controls whether this widget has keyboard focus.
  ///
  /// If null, this widget will create its own [FocusNode].
  final FocusNode focusNode;

  /// The decoration to show around the text field.
  ///
147
  /// By default, draws a horizontal line under the text field but can be
148 149
  /// configured to show an icon, label, hint text, and error text.
  ///
150
  /// Specify null to remove the decoration entirely (including the
151 152 153 154
  /// extra padding introduced by the decoration to save space for the labels).
  final InputDecoration decoration;

  /// The type of keyboard to use for editing the text.
155
  ///
156 157
  /// Defaults to [TextInputType.text] if [maxLines] is one and
  /// [TextInputType.multiline] otherwise.
158 159
  final TextInputType keyboardType;

160 161
  /// The type of action button to use for the keyboard.
  ///
162 163
  /// Defaults to [TextInputAction.newline] if [keyboardType] is
  /// [TextInputType.multiline] and [TextInputAction.done] otherwise.
164 165
  final TextInputAction textInputAction;

166
  /// Configures how the platform keyboard will select an uppercase or
167 168 169 170 171 172
  /// lowercase keyboard.
  ///
  /// Only supports text keyboards, other keyboard types will ignore this
  /// configuration. Capitalization is locale-aware.
  ///
  /// Defaults to [TextCapitalization.none]. Must not be null.
173
  ///
174
  /// See also:
175
  ///
176 177 178
  ///   * [TextCapitalization], for a description of each capitalization behavior.
  final TextCapitalization textCapitalization;

179 180 181 182
  /// The style to use for the text being edited.
  ///
  /// This text style is also used as the base style for the [decoration].
  ///
183
  /// If null, defaults to the `subhead` text style from the current [Theme].
184 185
  final TextStyle style;

186
  /// How the text being edited should be aligned horizontally.
Ian Hickson's avatar
Ian Hickson committed
187 188
  ///
  /// Defaults to [TextAlign.start].
189 190 191
  final TextAlign textAlign;

  /// Whether this text field should focus itself if nothing else is already
192 193
  /// focused.
  ///
194
  /// If true, the keyboard will open as soon as this text field obtains focus.
195 196
  /// Otherwise, the keyboard is only shown after the user taps the text field.
  ///
197
  /// Defaults to false. Cannot be null.
198 199 200 201 202 203
  // See https://github.com/flutter/flutter/issues/7035 for the rationale for this
  // keyboard behavior.
  final bool autofocus;

  /// Whether to hide the text being edited (e.g., for passwords).
  ///
204 205
  /// When this is set to true, all the characters in the text field are
  /// replaced by U+2022 BULLET characters (•).
206
  ///
207
  /// Defaults to false. Cannot be null.
208 209
  final bool obscureText;

210 211 212 213 214
  /// Whether to enable autocorrection.
  ///
  /// Defaults to true. Cannot be null.
  final bool autocorrect;

215 216 217 218
  /// The maximum number of lines for the text to span, wrapping if necessary.
  ///
  /// If this is 1 (the default), the text will not wrap, but will scroll
  /// horizontally instead.
219 220 221
  ///
  /// If this is null, there is no limit to the number of lines. If it is not
  /// null, the value must be greater than zero.
222 223
  final int maxLines;

224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
  /// The maximum number of characters (Unicode scalar values) to allow in the
  /// text field.
  ///
  /// If set, a character counter will be displayed below the
  /// field, showing how many characters have been entered and how many are
  /// allowed. After [maxLength] characters have been input, additional input
  /// is ignored, unless [maxLengthEnforced] is set to false. The TextField
  /// enforces the length with a [LengthLimitingTextInputFormatter], which is
  /// evaluated after the supplied [inputFormatters], if any.
  ///
  /// This value must be either null or greater than zero. If set to null
  /// (the default), there is no limit to the number of characters allowed.
  ///
  /// Whitespace characters (e.g. newline, space, tab) are included in the
  /// character count.
  ///
  /// If [maxLengthEnforced] is set to false, then more than [maxLength]
  /// characters may be entered, but the error counter and divider will
  /// switch to the [decoration.errorStyle] when the limit is exceeded.
  ///
244 245
  /// ## Limitations
  ///
246 247 248 249 250 251 252 253 254 255
  /// The TextField does not currently count Unicode grapheme clusters (i.e.
  /// characters visible to the user), it counts Unicode scalar values, which
  /// leaves out a number of useful possible characters (like many emoji and
  /// composed characters), so this will be inaccurate in the presence of those
  /// characters. If you expect to encounter these kinds of characters, be
  /// generous in the maxLength used.
  ///
  /// For instance, the character "ö" can be represented as '\u{006F}\u{0308}',
  /// which is the letter "o" followed by a composed diaeresis "¨", or it can
  /// be represented as '\u{00F6}', which is the Unicode scalar value "LATIN
256
  /// SMALL LETTER O WITH DIAERESIS". In the first case, the text field will
257 258 259 260 261 262 263 264 265 266
  /// count two characters, and the second case will be counted as one
  /// character, even though the user can see no difference in the input.
  ///
  /// Similarly, some emoji are represented by multiple scalar values. The
  /// Unicode "THUMBS UP SIGN + MEDIUM SKIN TONE MODIFIER", "👍🏽", should be
  /// counted as a single character, but because it is a combination of two
  /// Unicode scalar values, '\u{1F44D}\u{1F3FD}', it is counted as two
  /// characters.
  ///
  /// See also:
267
  ///
268 269 270 271 272 273 274 275 276 277 278 279
  ///  * [LengthLimitingTextInputFormatter] for more information on how it
  ///    counts characters, and how it may differ from the intuitive meaning.
  final int maxLength;

  /// If true, prevents the field from allowing more than [maxLength]
  /// characters.
  ///
  /// If [maxLength] is set, [maxLengthEnforced] indicates whether or not to
  /// enforce the limit, or merely provide a character counter and warning when
  /// [maxLength] is exceeded.
  final bool maxLengthEnforced;

280 281 282
  /// Called when the text being edited changes.
  final ValueChanged<String> onChanged;

283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300
  /// Called when the user submits editable content (e.g., user presses the "done"
  /// button on the keyboard).
  ///
  /// The default implementation of [onEditingComplete] executes 2 different
  /// behaviors based on the situation:
  ///
  ///  - When a completion action is pressed, such as "done", "go", "send", or
  ///    "search", the user's content is submitted to the [controller] and then
  ///    focus is given up.
  ///
  ///  - When a non-completion action is pressed, such as "next" or "previous",
  ///    the user's content is submitted to the [controller], but focus is not
  ///    given up because developers may want to immediately move focus to
  ///    another input widget within [onSubmitted].
  ///
  /// Providing [onEditingComplete] prevents the aforementioned default behavior.
  final VoidCallback onEditingComplete;

301 302 303 304
  /// Called when the user indicates that they are done editing the text in the
  /// field.
  final ValueChanged<String> onSubmitted;

xster's avatar
xster committed
305 306 307
  /// Optional input validation and formatting overrides.
  ///
  /// Formatters are run in the provided order when the text input changes.
308 309
  final List<TextInputFormatter> inputFormatters;

310 311 312 313 314 315 316
  /// If false the textfield is "disabled": it ignores taps and its
  /// [decoration] is rendered in grey.
  ///
  /// If non-null this property overrides the [decoration]'s
  /// [Decoration.enabled] property.
  final bool enabled;

317 318 319 320 321 322 323 324 325 326 327 328
  /// How thick the cursor will be.
  ///
  /// Defaults to 2.0.
  final double cursorWidth;

  /// How rounded the corners of the cursor should be.
  /// By default, the cursor has a null Radius
  final Radius cursorRadius;

  /// The color to use when painting the cursor.
  final Color cursorColor;

329
  /// The appearance of the keyboard.
330
  ///
331
  /// This setting is only honored on iOS devices.
332
  ///
333 334 335
  /// If unset, defaults to the brightness of [ThemeData.primaryColorBrightness].
  final Brightness keyboardAppearance;

336 337 338 339 340 341 342 343 344 345
  /// Configures padding to edges surrounding a [Scrollable] when the Textfield scrolls into view.
  ///
  /// When this widget receives focus and is not completely visible (for example scrolled partially
  /// off the screen or overlapped by the keyboard)
  /// then it will attempt to make itself visible by scrolling a surrounding [Scrollable], if one is present.
  /// This value controls how far from the edges of a [Scrollable] the TextField will be positioned after the scroll.
  ///
  /// Defaults to EdgeInserts.all(20.0).
  final EdgeInsets scrollPadding;

346
  @override
347
  _TextFieldState createState() => _TextFieldState();
348 349

  @override
350 351
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
352 353 354 355 356 357 358 359 360 361 362
    properties.add(DiagnosticsProperty<TextEditingController>('controller', controller, defaultValue: null));
    properties.add(DiagnosticsProperty<FocusNode>('focusNode', focusNode, defaultValue: null));
    properties.add(DiagnosticsProperty<InputDecoration>('decoration', decoration));
    properties.add(DiagnosticsProperty<TextInputType>('keyboardType', keyboardType, defaultValue: TextInputType.text));
    properties.add(DiagnosticsProperty<TextStyle>('style', style, defaultValue: null));
    properties.add(DiagnosticsProperty<bool>('autofocus', autofocus, defaultValue: false));
    properties.add(DiagnosticsProperty<bool>('obscureText', obscureText, defaultValue: false));
    properties.add(DiagnosticsProperty<bool>('autocorrect', autocorrect, defaultValue: false));
    properties.add(IntProperty('maxLines', maxLines, defaultValue: 1));
    properties.add(IntProperty('maxLength', maxLength, defaultValue: null));
    properties.add(FlagProperty('maxLengthEnforced', value: maxLengthEnforced, ifTrue: 'max length enforced'));
363 364 365
  }
}

366
class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixin {
367
  final GlobalKey<EditableTextState> _editableTextKey = GlobalKey<EditableTextState>();
368

369 370 371
  Set<InteractiveInkFeature> _splashes;
  InteractiveInkFeature _currentSplash;

372
  TextEditingController _controller;
373
  TextEditingController get _effectiveController => widget.controller ?? _controller;
374 375

  FocusNode _focusNode;
376
  FocusNode get _effectiveFocusNode => widget.focusNode ?? (_focusNode ??= FocusNode());
377

378 379 380 381 382
  bool get needsCounter => widget.maxLength != null
    && widget.decoration != null
    && widget.decoration.counterText == null;

  InputDecoration _getEffectiveDecoration() {
383
    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
384
    final InputDecoration effectiveDecoration = (widget.decoration ?? const InputDecoration())
385 386 387 388
      .applyDefaults(Theme.of(context).inputDecorationTheme)
      .copyWith(
        enabled: widget.enabled,
      );
389

390
    if (!needsCounter)
391
      return effectiveDecoration;
392

393 394 395 396
    final int currentLength = _effectiveController.value.text.runes.length;
    final String counterText = '$currentLength/${widget.maxLength}';
    final int remaining = (widget.maxLength - currentLength).clamp(0, widget.maxLength);
    final String semanticCounterText = localizations.remainingTextFieldCharacterCount(remaining);
397 398 399 400 401 402 403
    if (_effectiveController.value.text.runes.length > widget.maxLength) {
      final ThemeData themeData = Theme.of(context);
      return effectiveDecoration.copyWith(
        errorText: effectiveDecoration.errorText ?? '',
        counterStyle: effectiveDecoration.errorStyle
          ?? themeData.textTheme.caption.copyWith(color: themeData.errorColor),
        counterText: counterText,
404
        semanticCounterText: semanticCounterText,
405 406
      );
    }
407 408 409 410
    return effectiveDecoration.copyWith(
      counterText: counterText,
      semanticCounterText: semanticCounterText,
    );
411 412
  }

413 414 415
  @override
  void initState() {
    super.initState();
416
    if (widget.controller == null)
417
      _controller = TextEditingController();
418 419 420
  }

  @override
421
  void didUpdateWidget(TextField oldWidget) {
422
    super.didUpdateWidget(oldWidget);
423
    if (widget.controller == null && oldWidget.controller != null)
424
      _controller = TextEditingController.fromValue(oldWidget.controller.value);
425
    else if (widget.controller != null && oldWidget.controller == null)
426
      _controller = null;
427 428 429 430 431
    final bool isEnabled = widget.enabled ?? widget.decoration?.enabled ?? true;
    final bool wasEnabled = oldWidget.enabled ?? oldWidget.decoration?.enabled ?? true;
    if (wasEnabled && !isEnabled) {
      _effectiveFocusNode.unfocus();
    }
432 433 434 435 436 437 438 439 440 441 442 443
  }

  @override
  void dispose() {
    _focusNode?.dispose();
    super.dispose();
  }

  void _requestKeyboard() {
    _editableTextKey.currentState?.requestKeyboard();
  }

444
  void _handleSelectionChanged(TextSelection selection, SelectionChangedCause cause) {
445
    if (cause == SelectionChangedCause.longPress)
446 447 448
      Feedback.forLongPress(context);
  }

449 450
  InteractiveInkFeature _createInkFeature(TapDownDetails details) {
    final MaterialInkController inkController = Material.of(context);
Hans Muller's avatar
Hans Muller committed
451 452
    final BuildContext editableContext = _editableTextKey.currentContext;
    final RenderBox referenceBox = InputDecorator.containerOf(editableContext) ?? editableContext.findRenderObject();
453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475
    final Offset position = referenceBox.globalToLocal(details.globalPosition);
    final Color color = Theme.of(context).splashColor;

    InteractiveInkFeature splash;
    void handleRemoved() {
      if (_splashes != null) {
        assert(_splashes.contains(splash));
        _splashes.remove(splash);
        if (_currentSplash == splash)
          _currentSplash = null;
        updateKeepAlive();
      } // else we're probably in deactivate()
    }

    splash = Theme.of(context).splashFactory.create(
      controller: inkController,
      referenceBox: referenceBox,
      position: position,
      color: color,
      containedInkWell: true,
      // TODO(hansmuller): splash clip borderRadius should match the input decorator's border.
      borderRadius: BorderRadius.zero,
      onRemoved: handleRemoved,
476
      textDirection: Directionality.of(context),
477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507
    );

    return splash;
  }

  RenderEditable get _renderEditable => _editableTextKey.currentState.renderEditable;

  void _handleTapDown(TapDownDetails details) {
    _renderEditable.handleTapDown(details);
    _startSplash(details);
  }

  void _handleTap() {
    _renderEditable.handleTap();
    _requestKeyboard();
    _confirmCurrentSplash();
  }

  void _handleTapCancel() {
    _cancelCurrentSplash();
  }

  void _handleLongPress() {
    _renderEditable.handleLongPress();
    _confirmCurrentSplash();
  }

  void _startSplash(TapDownDetails details) {
    if (_effectiveFocusNode.hasFocus)
      return;
    final InteractiveInkFeature splash = _createInkFeature(details);
508
    _splashes ??= HashSet<InteractiveInkFeature>();
509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538
    _splashes.add(splash);
    _currentSplash = splash;
    updateKeepAlive();
  }

  void _confirmCurrentSplash() {
    _currentSplash?.confirm();
    _currentSplash = null;
  }

  void _cancelCurrentSplash() {
    _currentSplash?.cancel();
  }

  @override
  bool get wantKeepAlive => _splashes != null && _splashes.isNotEmpty;

  @override
  void deactivate() {
    if (_splashes != null) {
      final Set<InteractiveInkFeature> splashes = _splashes;
      _splashes = null;
      for (InteractiveInkFeature splash in splashes)
        splash.dispose();
      _currentSplash = null;
    }
    assert(_currentSplash == null);
    super.deactivate();
  }

539 540
  @override
  Widget build(BuildContext context) {
541
    super.build(context); // See AutomaticKeepAliveClientMixin.
542
    assert(debugCheckHasMaterial(context));
543 544
    // TODO(jonahwilliams): uncomment out this check once we have migrated tests.
    // assert(debugCheckHasMaterialLocalizations(context));
545
    assert(debugCheckHasDirectionality(context));
546
    final ThemeData themeData = Theme.of(context);
547
    final TextStyle style = widget.style ?? themeData.textTheme.subhead;
548
    final Brightness keyboardAppearance = widget.keyboardAppearance ?? themeData.primaryColorBrightness;
549 550
    final TextEditingController controller = _effectiveController;
    final FocusNode focusNode = _effectiveFocusNode;
551 552
    final List<TextInputFormatter> formatters = widget.inputFormatters ?? <TextInputFormatter>[];
    if (widget.maxLength != null && widget.maxLengthEnforced)
553
      formatters.add(LengthLimitingTextInputFormatter(widget.maxLength));
554

555 556
    Widget child = RepaintBoundary(
      child: EditableText(
557 558 559
        key: _editableTextKey,
        controller: controller,
        focusNode: focusNode,
560
        keyboardType: widget.keyboardType,
561
        textInputAction: widget.textInputAction,
562
        textCapitalization: widget.textCapitalization,
563
        style: style,
564 565 566
        textAlign: widget.textAlign,
        autofocus: widget.autofocus,
        obscureText: widget.obscureText,
567
        autocorrect: widget.autocorrect,
568
        maxLines: widget.maxLines,
569
        selectionColor: themeData.textSelectionColor,
xster's avatar
xster committed
570 571 572
        selectionControls: themeData.platform == TargetPlatform.iOS
            ? cupertinoTextSelectionControls
            : materialTextSelectionControls,
573
        onChanged: widget.onChanged,
574
        onEditingComplete: widget.onEditingComplete,
575
        onSubmitted: widget.onSubmitted,
576
        onSelectionChanged: _handleSelectionChanged,
577
        inputFormatters: formatters,
578
        rendererIgnoresPointer: true,
579 580 581
        cursorWidth: widget.cursorWidth,
        cursorRadius: widget.cursorRadius,
        cursorColor: widget.cursorColor ?? Theme.of(context).cursorColor,
582
        scrollPadding: widget.scrollPadding,
583
        keyboardAppearance: keyboardAppearance,
584 585 586
      ),
    );

587
    if (widget.decoration != null) {
588 589
      child = AnimatedBuilder(
        animation: Listenable.merge(<Listenable>[ focusNode, controller ]),
590
        builder: (BuildContext context, Widget child) {
591
          return InputDecorator(
592
            decoration: _getEffectiveDecoration(),
593 594
            baseStyle: widget.style,
            textAlign: widget.textAlign,
595 596 597 598 599 600 601 602 603
            isFocused: focusNode.hasFocus,
            isEmpty: controller.value.text.isEmpty,
            child: child,
          );
        },
        child: child,
      );
    }

604
    return Semantics(
605
      onTap: () {
606
        if (!_effectiveController.selection.isValid)
607
          _effectiveController.selection = TextSelection.collapsed(offset: _effectiveController.text.length);
608 609
        _requestKeyboard();
      },
610
      child: IgnorePointer(
611
        ignoring: !(widget.enabled ?? widget.decoration?.enabled ?? true),
612
        child: GestureDetector(
613 614 615 616 617 618 619 620
          behavior: HitTestBehavior.translucent,
          onTapDown: _handleTapDown,
          onTap: _handleTap,
          onTapCancel: _handleTapCancel,
          onLongPress: _handleLongPress,
          excludeFromSemantics: true,
          child: child,
        ),
621
      ),
622 623
    );
  }
624
}