-
Notifications
You must be signed in to change notification settings - Fork 28.6k
How to share focus with popovers and other sub-tasks #106923
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
CC @justinmc |
Hello @matthew-carroll. Thank you for filing this issue. Can you include the output of |
The Flutter version doesn't matter for this. It's a question about the approach. All necessary information is in the ticket. I'd like to hear the perspective from someone working on text input, e.g., @justinmc, @gspencergoog, @LongCatIsLooong |
Is this related to this problem with FocusTrap? #86972 Taps outside of an input will always steal focus from that input because of FocusTrap. |
That issue may or may not be related. But the root question here is how a Flutter developer should know when losing focus means "your input isn't active anymore" vs situations that mean "your input is still active, but something else has focus temporarily". You can see those examples in the original post. |
If elements in the popover are themselves focusable and have to use dedicated |
It sounds like you're saying that If so, don't Furthermore, what's the story around platform interop? You'll see in the original post that there are multiple situations where the popover is a system UI, e.g., a context menu or a compound character selection. |
That's right. I think the
As far as the text input plugin goes I think the flutter framework handles focus separately from the platform's focus system. For key input if the system decides to hand a key event to the FlutterView then the embedder sends the key event to the framework. |
Can we get an official Flutter team example of each situation, so that everyone is clear on the intended approach? E.g., a case where focus moves to an in-app popover and back, and a case where focus moves to an OS popover and back? I'm also wondering if Flutter should offer a generalized capability to achieve this |
I'll add another detail to this issue. I started to implement custom management of a I discovered that |
@matthew-carroll I've assigned labels to this issue. I'm not every familiar with the internals of |
@exaby73 I'm not sure if the labels are correct or not. I don't know if this is a bug or a new feature. And I don't know if I'm proposing something new, or if there's an intended approach here. That's why I asked the questions above. We need additional input from the Flutter team in multiple areas. |
Two ideas based on what I'm seeing on my end:
|
In short, no. There's no concept of multiple focus, or a "focus stack" other than what happens when you enter and leave a focus scope. You might be able to get something like the functionality you want by wrapping the contents of the overlay in a
Yes, the focus tree is, in fact, separate from the widget tree and doesn't have to mirror it (even though 99% of the time it does), but in general, it's a bad idea to manipulate The existence of public API on If what you'd like to do is respond to keypresses regardless of focus, you can add a handler to Some concept of a "focus stack" where there is a stack of currently focused nodes instead of a single primary focus is probably what you need, and the overlain dialogs could push and pop the focus to keep the focus visible below them. This would be non-trivial to implement in a non-breaking way. |
@gspencergoog can you take a look at what we're doing in SuperEditor related to this problem? I'm not sure I see how creating more focus scopes would solve the problem. You mentioned that you want to get rid of the properties on I think this SuperEditor use-case is likely to proliferate on desktop where we deal with many more intricate popover use-cases. Based on your description of the current state and intentions, it sounds like the focus system might be moving in the opposite direction from where it needs to go. Or, to state this problem another way: In a world full of popovers, focus structure does NOT match the widget tree structure. |
I'm sure it won't solve the problem. It would solve part of the problem, which is preserving the correct focus when the popover goes away, since the
There currently aren't any plans to change the focus system, so it's not really moving in any direction.
I'm not sure that having separate structures necessarily follows from having popovers. There are plenty of designs that would allow the two trees to continue have similar structure, which has the distinct advantage that most developers never need to know that the focus tree could have a different structure. |
Can you describe this with some specifics? The fact that there are 2 trees would seem to suggest that the focus structure is incompatible with the tree structure. For example, the |
I ended up merging in my linked changes into super_editor: superlistapp/super_editor#671 I included a smoke test for the behavior in questions: https://github.com/superlistapp/super_editor/pull/671/files#diff-e291e61fe66a09ed12351d0e94c6c4a789fa6ab6b4ce101fd460b5fffcded826 - hopefully these tests will be added to the test registry soon. We've ended up with two regrettable changes. First, we had to create a widget that parents a If Flutter thinks that it makes this use-case possible with existing tooling, I'd like to see a specific example. If such an example can be assembled, then I would recommend placing it in the framework tests to ensure that the use-case remains functional. |
We've run into another issue with sharing focus. As I mentioned in the previous comment, we were able to work around the focus problem with widgets that we fully control. However, widgets like |
@gspencergoog - I posted a stripped down example of a few possible approaches to shared focus: https://github.com/Flutter-Bounty-Hunters/investigation_overlay_focus Can you take a look at those approaches and let me know if I'm missing an intended approach to this problem? Currently, I don't see any way to work with dropdown menus from a popover toolbar when sharing focus. |
@matthew-carroll Thanks for the thorough examples. Would it help if popup menus didn't steal focus? There's no reason why they need to: buttons don't typically take focus when they are clicked on, and the fact that popup menus push a route is just an implementation detail. If they didn't push a route, and didn't steal focus, then this would be a non-issue, if I understand correctly. As another alternative, would it help to have a |
@gspencergoog I think that both of those changes sound reasonable at this point. Maybe Super Editor could try to integrate a framework PR to prove the fix? For the But if there's a better approach that fully solves the problem in a relatively simple way, I'm open to that, too. I'm not sure what it would be, because I think that fundamentally we're looking at focus relationships that truly don't reflect the widget/layout relationships. So I think that, maybe, it is what it is. |
This could be very nice :) |
I've created a PR to allow setting of the parent focus node explicitly, in #113655 . Let me know if that addresses your use case. |
@gspencergoog I'm trying to make a Code sampleimport 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Overlay(
initialEntries: [
OverlayEntry(
builder: (context) {
return FocusApp();
},
)
],
),
),
);
}
class FocusApp extends StatefulWidget {
const FocusApp({super.key});
@override
State<FocusApp> createState() => _FocusAppState();
}
class _FocusAppState extends State<FocusApp> {
final FocusNode _mainFocusNode = FocusNode();
OverlayEntry? _toolbarOverlayEntry;
String? _dropDownValue = "1";
@override
void initState() {
super.initState();
_mainFocusNode.requestFocus();
_mainFocusNode.addListener(_onFocusChanged);
}
@override
void dispose() {
_mainFocusNode.dispose();
super.dispose();
}
void _onFocusChanged() {
setState(() {});
}
void _onTap() {
_showToolbar();
}
void _showToolbar() {
if (_toolbarOverlayEntry != null) {
return;
}
_toolbarOverlayEntry = OverlayEntry(
builder: (context) {
// FocusScope with the new property.
return FocusScope(
parentNode: _mainFocusNode,
child: _buildToolbar(),
);
},
);
final overlay = Overlay.of(context);
overlay.insert(_toolbarOverlayEntry!);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
onTap: _onTap,
child: Focus(
focusNode: _mainFocusNode,
child: Container(
color: Colors.blue,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('Click here to show the overlay'),
SizedBox(height: 30),
Text('Is mainFocus focused: ${_mainFocusNode.hasFocus}'),
],
),
),
),
),
),
);
}
Widget _buildToolbar() {
return Material(
color: Colors.transparent,
elevation: 5,
clipBehavior: Clip.hardEdge,
child: Stack(
children: [
Positioned(
top: 10,
left: 10,
child: SizedBox(
child: DropdownButton<String>(
value: _dropDownValue,
icon: const Icon(Icons.arrow_drop_down),
onChanged: (value) {
setState(() {
_dropDownValue = value;
});
},
items: [
DropdownMenuItem<String>(
value: "1",
child: Text('Option 1'),
),
DropdownMenuItem<String>(
child: Text('Option 2'),
value: "2",
),
],
),
),
),
],
),
);
}
}
|
@gspencergoog do you have any suggestions to resolve the issue in @angelosilvestre's sample? After upgrading to Flutter 3.7 we're now also facing this problem in our app using @matthew-carroll's Super Editor package. Thanks! Sorry for the spam comment, just sad to have to hide this useful feature in our app for now. |
@justinmc I wanted to put this back on your radar, in case you're able to push this forward somewhere. It looks like Greg hasn't had an opportunity to circle back on this. It was brought to my attention that part of the Super Editor demo is still broken (it's been broken for a year now), and blocked by this issue. It looks like Hillel is blocked, too. Is there anyone who can get some forward movement on this, so we can get the Super Editor demo working, and also help our customers who depend upon the ability to open dropdown menus and enter text into popup overlays? |
This issue is missing a priority label. Please set a priority label when adding the |
There's a conflict in text-based focus behavior in Flutter and I'm wondering if there's an intended approach to this problem.
Typically, when entering text into something like a form field, the moment focus moves away from the field, the selection is gone. For example, you'll see a caret and type text into a "name" field. Then, focus moves to an "email" field. At this point, there is no caret or selection in the "name" field any longer, and there is a caret in the "email" field. However, this focus behavior is not universal.
When composing text, there are a number of occasions in which some kind of popover might appear. For example, a popover to select text styles, enter a URL for a link, or select a compound character. In these situations, the popover includes focusable elements, and the user should be able to move focus around those elements. But that focus change should not impact the selection within the text field. Additionally, the moment the popover disappears, focus should return to the text field with the selection unchanged.
Does Flutter have an approach for handling these different situations?
Visual examples:
Editing the link URL in a selection toolbar (this example use

SuperEditor
but without keyboard as the input source, not IME)Right-click on selected to show the context menu

Press and hold character key that can be written with different accents depending on the language (e.g. Portuguese)

The text was updated successfully, but these errors were encountered: