-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Description
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