Skip to content

Not possible to de-focus a text field without clicking another focusable widget #82515

@jonahwilliams

Description

@jonahwilliams

Consider a page with a focusable text field, such as the gallery. After focusing the text field, I would expect that one the web or desktop I could click outside the text field area to remove focus. However this is not possible today, I need to click another focusable widget such as a text field or leave the page.

Aside from platform adaoption, this makes it difficult or impossible to use the Shortcuts widget to on a page with text fields. The Shortcuts may not be able to gain focus again after the text field has been focused.

Since the text fields cannot receive tap events that don't hit them (in order to dispose of focus), the solution requires a new widget be introduced that can intercept focus from the text fields.

Solution: The parent focus trap. A widget introduced at either the app level or route level. On tap events, it always requests focus. This will only be granted if the tap does not hit a more specific focus node. Draft:

class FocusTrap extends StatefulWidget {
  /// Create a new [FocusTrap] widget.
  const FocusTrap({
    required this.child,
    this.focusNode,
    Key? key
  }) : super(key: key);

  final Widget child;
  final FocusNode? focusNode;

  @override
  State<FocusTrap> createState() => _FocusTrapState();
}

class _FocusTrapState extends State<FocusTrap> {
  final Map<Type, GestureRecognizerFactory> _gestures = <Type, GestureRecognizerFactory>{};
  late FocusNode _focusNode;

  FocusNode get focusNode => widget.focusNode ?? _focusNode;

  void _stealFocus(TapDownDetails details) {
    if (details.kind != PointerDeviceKind.mouse)
      return;
    focusNode.requestFocus();
  }

  @override
  void initState() {
    super.initState();
    _focusNode = FocusNode();
    _gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
        () => TapGestureRecognizer(debugOwner: this),
        (TapGestureRecognizer instance) {
          instance
            .onTapDown = _stealFocus;
        },
      );
  }

  @override
  Widget build(BuildContext context) {
    return Focus(
      autofocus: true,
      child: RawGestureDetector(
        child: widget.child,
        gestures: _gestures,
      ),
      focusNode: focusNode,
    );
  }
}

And currently introduced in the same place I was putting SelectionArea (since this is needed to make Ctrl+C and other shortcuts work reliably)

@@ -10,6 +10,7 @@ import 'package:flutter/scheduler.dart';
 import 'package:flutter/semantics.dart';

 import 'actions.dart';
+import 'base_focus.dart';
 import 'basic.dart';
 import 'focus_manager.dart';
 import 'focus_scope.dart';
@@ -841,11 +842,11 @@ class _ModalScopeState<T> extends State<_ModalScope<T>> {
                             key: widget.route._subtreeKey, // immutable
                             child: Builder(
                               builder: (BuildContext context) {
-                                return widget.route.buildPage(
+                                return FocusTrap(child: widget.route.buildPage(
                                   context,
                                   widget.route.animation!,
                                   widget.route.secondaryAnimation!,
-                                );
+                                ));
                               },
                             ),
                           ),

new behavior:

Flutter.Gallery.-.Google.Chrome.2021-05-13.20-56-00.mp4

blocks: #81839

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Issues that are less important to the Flutter projecta: qualityA truly polished experiencea: text inputEntering text in a text field or keyboard related problemsc: new featureNothing broken; request for a new capabilityf: material designflutter/packages/flutter/material repository.frameworkflutter/packages/flutter repository. See also f: labels.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions