-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Add requestFocusOnTap to DropdownMenu
#117504
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
Add requestFocusOnTap to DropdownMenu
#117504
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Giving the keyboard focus to a Dropdown menu on mobile doesn't seem like the common case, so maybe we should make this a nullable property and compute a default value (if needed) based on the target platform. This would be an incompatible change however DropdownMenu hasn't been out for long.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed! On mobile platforms, the text field will be focusable only when the showVirtualKeyboard is true now. Thanks so much for the suggestion. Please let me know if there're any problems:)
4a1170a to
2644834
Compare
HansMuller
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good, just want to maybe tweak the showVirtualKeyboard flag a little.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of showVirtualKeyboard maybe this should be takesFocus, since one might set it to false on desktop platforms to prevent searching or filtering?
@gspencergoog - do we have a conventional name for a flag that means "request the focus when clicked or tapped"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, we don't have a set name for that, since text fields are the only thing that do that.
takesFocus doesn't include the part about needing user interaction to take focus, it sounds like a synonym for canRequestFocus which is what we currently use for the ability to take focus.
Maybe requestFocusOnTap instead?
But the docs here say "If set to true ... the text field is focusable", implying that if it is set to false, then the text field is not focusable, which I don't think is the case, correct? It's still focusable, it just doesn't request focus on tap, meaning that you could tab to get to it, and it would focus, you just can't tap on it to focus it, am I right?
Also, it seems odd to have it be ignored on some platforms and not others. It seems like desktop platforms could just default to false, and mobile default to true.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
requestFocusOnTap sounds better and agreed that the default value would be false for mobile, true otherwise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, we don't have a set name for that, since text fields are the only thing that do that.
takesFocusdoesn't include the part about needing user interaction to take focus, it sounds like a synonym forcanRequestFocuswhich is what we currently use for the ability to take focus.Maybe
requestFocusOnTapinstead?But the docs here say "If set to true ... the text field is focusable", implying that if it is set to false, then the text field is not focusable, which I don't think is the case, correct? It's still focusable, it just doesn't request focus on tap, meaning that you could tab to get to it, and it would focus, you just can't tap on it to focus it, am I right?
Also, it seems odd to have it be ignored on some platforms and not others. It seems like desktop platforms could just default to false, and mobile default to true.
requestFocusOnTap sounds good to me! Thanks for the suggestion. And yes, I think "focusable" here is not accurate. I need to change the doc.
And will set the default value to false for mobile platforms and true for desktop platforms. Thanks!
|
Actually, in thinking about it some more, it seems to me that this PR isn't the right solution: a field that doesn't allow the text field to be focused really shouldn't use a text field at all, since it doesn't allow editing, and doesn't allow focus. It's much more of just a label showing the currently selected value of the dropdown. In the first field in the video, it looks to me like a cursor appears in the field after selection and that seems inconsistent, since there are only a fixed number of allowed values, and you can't search, right? Could this instead just be a different kind of field with a different constructor? Or maybe I'm just misunderstanding the issue. Does it allow searching if it doesn't allow focus? Can you search only after you select something (which would seem odd)? |
Yeah, it is a little odd that a text field cannot be edited. By default, the ComboBox should be able to search/filter and I think this flag is more like a small feature to turn off these interactions. Does this make sense?
Ah that's the selection handle, not a cursor. Seems that I forgot to set the
No, I think your understanding is totally correct. If it is unfocused, then no interactions at all (no searching/editing). It should only be able to make a selection. |
|
I think the approach taken here is a good one because the same code will work well, for the common case, on both mobile and desktop. If we add a separate constructor for the requestFocusOnTap:false case, then developers will have to write different code on different platforms (for the common case), which is undesirable. With the code as it is, the implementation could eventually just create a Text widget instead of a TextField, when requestFocusOnTap is false. |
This is a good point. OK, let's proceed with the parameter, then. |
HansMuller
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, just need to deal with the complexity of the focus node's lifetime.
I think @gspencergoog might have an idiomatic way of dealing with a disposable property that might be passed in (so leave it alone) or auto-created (so dispose it).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It probably doesn't bear mentioning, but: the widget will only create a (private) FocusNode if this parameter is null and requestFocusOnTap is true.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| /// keyboard shows on a touch event. | |
| /// keyboard is shown in response to a touch event. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| /// will show. | |
| /// will be shown. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| /// If this is null or false, the text field will not request focus when the menu is open, | |
| /// If this is null or false, the text field will not request focus when the menu is opened, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| /// By default, this is true on desktop platforms, and false on mobile platforms. | |
| /// By default, this property is true on desktop platforms and false on mobile platforms. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is tricky. If the focusNode was provided (via widget.focusNode) then we should not dispose it. If requestFocusOnTap is false, then we probably don't need to create a focus node. If the dropdown gets rebuilt with a new value of focusNode or requestFocusOnTap we'll need to update things in didUpdateWidget. For example, the widget might be built with widget.focusNode:null, and then rebuilt with a non-null focus node. A bunch of tests to make sure we do the right thing will be needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will update didUpdateWidget and tests. Thanks a lot!
But for this condition,
If requestFocusOnTap is false, then we probably don't need to create a focus node.
Currently, if the requestFocusOnTap is false, I use the focusNode to unfocus (_textFocusNode.unfocus();) the text field when it is tapped. Is this the correct way to keep the TextField unfocused? It looks like we don't need to create a focus node when requestFocusOnTap is true because the text field will automatically request focus when it's tapped.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You probably shouldn't unfocus the text field like that, since there are edge cases where you'll have it flashing as focused for a frame, or having to unfocus at times when you didn't expect to (e.g. when popping a route).
The long term answer is probably to use a Text widget instead of a TextField when it is not focusable. That is much easier said than done, however, since the Text widget doesn't have an InputDecorator, and we probably want the decoration, and there are a lot of things that the text field does for you here.
In the meantime, though, would setting the Probably not: seems like that's kind of what you want, but I think that it ignores taps then, which might not be ideal.readOnly attribute on the text field be sufficient?
Another alternative would be to always supply a focus node to the text field, and set Ugh. No, that won't work, because it is overwritten by the text field, and it only turns it off if the field is disabled.canRequestFocus on the node to be false.
Okay, how about adding an optional canRequestFocus to the text field, and having that override the text field's internal logic if it is set. Then you can pass "false" for that attribute to the text field, and it won't be allowed to accept focus.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sounds good to me! Will make the change. Thanks so much:)
|
The idiomatic way to handle the case where you have an optional passed-in focus node, but need to have one always available is to do something like what is done on MenuItemButton, where there's an internal focus node used if one isn't given. It's not concise, but I haven't been able to think of a way to make it into a mixin or something that wasn't just as verbose. |
I see. Will fix this part! |
779147f to
eb10201
Compare
focusNode and showVirtualKeyboard propertiesrequestFocusOnTap to DropdownMenu
eb10201 to
fb61270
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| /// Defaults to true. Set to false if you want the [TextField] manages to do | |
| /// Defaults to true. If false, the text field will not request focus when tapped, or when its context menu is displayed. If false it will not be possible to move the focus to the text field with tab key. |
Not sure if I've got this all right. Can the developer still give the text field the focus by having its focus node request the focus? ... it looks like the answer is "no"; we should clear up that case here too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right. The text field cannot be focused if false. Sorry for missing this part.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed the padding style for the trailing icon button from EdgeInsets.symmetric(horizontal: 4.0) to EdgeInsets.all(4.0) for some vertical space, so the height of the text field gets changed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isMobile should really be a late final that's initialized with a switch. per https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#avoid-using-if-chains-or--or--with-enum-values
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed. Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What 'done' button?
This should read something like: the desktop platform closed the menu when .
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry about the confusion. It should be the done action when editing is finished. Just updated this comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The requestFocusOnTap parameter doesn't appear to be needed here. Hopefully we have a test that proves that one can override the default value of requestFocus for mobile (to true) and still get the dropdown menu.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed the parameter! The last test is to override the default value for all platforms to check if the dropdown menu still works. Just added some more checks in that test to make sure we can make a selection when requestFocusOnTap is true for mobile or requestFocusOnTap is false for desktop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should only use firstWidget when more than one widget is expected and the first one is the one we want. Otherwise tester.widget
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| testWidgets('If the requestFocusOnTap is true, the text input field can request focus, ' | |
| testWidgets('If requestFocusOnTap is true, the text input field can request focus, ' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Presumably variant: TargetPlatformVariant.all() is the default?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just want to test that the dropdown menu works on all platforms when a value of requestFocusOnTap is provided.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should verify that the focus doesn't change if the text field is tapped or its context menu is shown (long press or right-button on desktop).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added more tests.
HansMuller
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Setting this field to true or false, rather than allowing the implementation to choose based on the platform, can be useful for applications that want to override the default behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By default, on mobile platforms, tapping on the text field and opening the menu will not case a focus request and the virtual keyboard will not appear. The default behavior for desktop platforms is for the dropdown to take the focus.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although it doesn't matter very much, this would probably be. slightly better with a short circuit return:
if (widget.requestFocusOnTap != null) {
return widget.requestFocusOnTap;
}
switch (Theme.of(context).platform) {
case TargetPlatform.iOS:
case TargetPlatform.android:
case TargetPlatform.fuchsia:
return false
break;
case TargetPlatform.macOS:
case TargetPlatform.linux:
case TargetPlatform.windows:
return true;
}|
Am I missing something here? |
Fixes #116587
This PR is to add
requestFocusOnTapforDropdownMenu.By default, if the app is running on mobile platforms, the text input field will not request focus when the menu is open. If the app is running on desktop platforms, the text input field will request focus when the menu is open. When setting
requestFocusOnTapto false and tapping the text field, the text field will remain unfocused, and the virtual keyboard on mobile platforms will not appear. SettingrequestFocusOnTapto false is typically useful when users only have limited options in the menu list and searching is unnecessary.The first DropdownMenu is the default DropdownMenu behavior. The second one set
requestFocusOnTapto true.Screen.Recording.2023-01-26.at.12.46.55.AM.mov
This PR also adds some small style change to make sure the trailing icon button has a circular highlight when it is clicked and removes
textEditingController.dispose()because I don't see the dispose method inTextEditingController.Pre-launch Checklist
///).If you need help, consider asking for advice on the #hackers-new channel on Discord.