-
Notifications
You must be signed in to change notification settings - Fork 24.7k
Reworking keyboardShouldPersistTaps to have a middle ground #10628
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
Conversation
By analyzing the blame information on this pull request, we identified @sahrens and @simonayzman to be potential reviewers. |
I think we should just make Feels more like a bug fix with cc @mkonicek |
I absolutely agree, i just didn't know what was the stance on background compat. If the concensus is that it should be changed straight away, I'll happily change the PR. |
@MaxLap 👍 Let's wait a bit see what others think |
@janicduplessis I think the current behaviour desirable if your screen filled with touchables. you don't wanna trigger a tap while dismissing the keyboard. |
Does this actually affects touchables? Looks like it only should only affect TextInputs. |
@janicdupless no it doesn't affect touchables. But when you have a screen filled with touchables, you need to tap somewhere to dismiss the keyboard which was triggered due to an text input without triggering a tap on a touchable. |
@janicduplessis @satya164 to clarify, the new behavior basically means if a descendant handles the tap, the ScrollView won't, and therefore won't hide the keyboard. If a descendant button wants to also hide the keyboard, then it can do so itself pretty easily. |
Is there a way to request feedback from other people? It's not so much about reviewing the code, first about if this is something you want in RN. I think this change would be helpful to many people. I admit that some people may want the current swallow-every-tap behavior, which is why I left it there. I also do think the default behavior should the new one, maybe with some releases in between to let people specify which behavior they want. I didn't change the documentation yet as I wanted feedback first. |
Ping @mkonicek |
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.
Overall this change makes a lot of sense.
Should we set the default prop to 'handled'? Or maybe we can do it later once people have had some time to experiment with the 'handled' behavior in their apps.
scrollResponderHandleStartShouldSetResponder: function(e: Event): boolean { | ||
var currentlyFocusedTextInput = TextInputState.currentlyFocusedField(); | ||
|
||
if (this.props.keyboardShouldPersistTaps == 'handled' && |
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.
please use ===
in cases like this
@@ -180,7 +187,9 @@ var ScrollResponderMixin = { | |||
scrollResponderHandleStartShouldSetResponderCapture: function(e: Event): boolean { | |||
// First see if we want to eat taps while the keyboard is up | |||
var currentlyFocusedTextInput = TextInputState.currentlyFocusedField(); | |||
if (!this.props.keyboardShouldPersistTaps && | |||
var keyboardNeverPersistTaps = !this.props.keyboardShouldPersistTaps || |
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 could be more consise if you do this on a previous line:
const {keyboardShouldPersistTaps} = this.props;
@MaxLap @satya164 I tested this out more in an app and it does affect the interaction with Touchables. If you have a touchable in the screen it will handle the event and thus the keyboard won't be close. Because of this I think we should not change it to the default behavior. After thinking about it there are use cases for all 3 behaviors
However there is still an issue with the current 'never' behavior (which is I was mostly referring to in my earlier comments) where tapping a different text input while the keyboard is open dismisses the keyboard instead of selecting it. I'll open a new PR to fix this case separately as it doesn't depend on this. |
With that said I'm not the biggest fan of the string enum + boolean prop, what about we add a deprecation warning for the boolean values or create a new prop altogether and deprecate |
I did the changes requested in the review. I agree that have both the true/false and the "always"/"never" is probably not the best thing. A deprecation is probably a good idea? Is that the final call? Making the "never" sometimes still persist the keyboard may make the name of that value confusing. |
What happens when I tap non-touchable? Shouldn't the keyboard dismiss then too? Why limit this to touchables only?
Yeah, we should deprecate the boolean values and show a warning specifying which value to change to. |
@satya164 Maybe that wasnt clear but yea it would dismiss the keyboard when tapping non-touchables too. |
@janicduplessis great! |
Okay, so for the deprecation. Do I make a new prop or I keep using keyboardShouldPersistTaps? If I keep using it, is there a better way of doing the deprecation than this? It works, but the message starts by saying keyboardShouldPersistTaps is deprecated. Is there an alternative / should I code one or that's good enough?
The resulting message is: keyboardShouldPersistTaps supplied to ScrollView has been deprecated. Values true & false for keyboardShouldPersistTaps are deprecated. Use "always" & "never" respectively instead or the new "handled". |
I think better to check it in const { keyboardShouldPersistTaps } = this.props;
if (typeof keyboardShouldPersistTaps === 'boolean') {
console.warn(`'keyboardShouldPersistTaps={${keyboardShouldPersistTaps}}' is deprecated. Use 'keyboardShouldPersistTaps=${keyboardShouldPersistTaps ? "always" : "never"}' instead`);
} |
@satya164, fbjs has a warning module, which is considered best practice inside RN: const warning = require('warning');
...
warning(
typeof keyboardShouldPersistTaps !== 'boolean',
`'keyboardShouldPersistTaps={${keyboardShouldPersistTaps}}' is deprecated. Use 'keyboardShouldPersistTaps=${keyboardShouldPersistTaps ? "always" : "never"}' instead`
); |
@ericvicenti Well it wont actually open / close it, it's just my mental model of it and why I think that |
* is up dismisses the keyboard. When this happens, children won't receive the tap. | ||
* - 'always', the keyboard will not dismiss automatically, and the scroll view will not | ||
* catch taps, but children of the scroll view can catch taps. | ||
* - 'handled', they keyboard will not dismiss automatically when the tap was handled by |
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.
Typo: they -> the
@@ -474,6 +486,13 @@ var ScrollResponderMixin = { | |||
* The `keyboardWillShow` is called before input focus. | |||
*/ | |||
componentWillMount: function() { | |||
var keyboardShouldPersistTaps = this.props.keyboardShouldPersistTaps; |
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.
Nit: lets use destructuring here too for consistency
This adds new possible values to keyboardShouldPersistTaps. 'never' and 'always' are the same as false and true. 'handled' is similar to 'never' (false), but the behavior is done in the bubbling phase of the event instead of the capture phase. So if a descendant component decides to respond to the tap, the ScrollResponder won't receive the event. The default behavior isn't changed and remains at 'always' (true), but in my opinion, the goal should be to change the default to be the new 'handled' behavior since that feels the most intuitive. Note that true and false are still valid options.
The exact change would be to 'always', but handled most likely make more sense and helps spread awareness of that option.
Did the requested tweaks from the review. Thank you @janicduplessis I edited the examples to use string values. I replaced the As a side note, am I wrong in thinking that the props and propTypes that refer to keyboardShouldPersistTaps are useless in UIExplorerPage? (only @janicduplessis's reasoning makes sense to me, so I will stick with 'never'. |
You can run the UIExplorer app from the xcode project in Yea, looks like the prop |
Ok, went easier than expected (I'm on windows with android). The UIExample works great. I can't get the Movies example to work. It starts, but it can't seem to do its request to rotten tomatoes: undefined is not an object (evaluating 'responseData.total') at line 136 of SearchScreen.js. Because of this, the ListView is never displayed. I doubt there is an issue with my change since it's a single prop. |
Ok good, as long as uiexplorer works thats fine, anyway your changes don't affect the others. @ericvicenti Do you want to take care of shipping this since you'll probably want to update fb internal codebase as well to avoid the warning. We should also make sure to communicate this with the release team so they can add something in the release notes about it (not really a breaking change but still). |
Summary: When using text inputs inside a ScrollView with `keyboardShouldPersistTaps=false` (default behavior) tapping another text input dismisses the keyboard instead of keeping it open and focusing the new text input which I think is the better and expected behavior. See #10628 for more discussion about that. Note that this affects nothing but the behavior with text inputs unlike #10628. cc satya164 MaxLap ericvicenti Closes #10887 Differential Revision: D4178474 Pulled By: ericvicenti fbshipit-source-id: 0c62ea2fac0017d559d1f8674b0a686a5e1b3d2d
Anybody willing to make the review on this? Thank you |
Sorry about the delay, I'll ping Eric about it. |
Sorry for the delay on the re-review. This looks great and I'm landing it now! |
@ericvicenti has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator. |
Thank you everyone |
Summary: Right now, the ScrollView's keyboard hiding behavior is either all or nothing: Hide the keyboard on any tap, or do nothing ever. This PR introduces a third mode to keyboardShouldPersistTaps which is much closer to what I consider should be the default. In the new behavior, the tap responding is done in the bubbling phase (instead of the capture phase like =true). As a result, a child can handle the tap. If no child does, then the ScrollView will receive the tap and will hide the keyboard. As a result, changing TextInput focus works as a user expects, with a single tap and without keyboard hiding. But taping on Text or on the empty part of the ScrollView hides the keyboard and removes the focus. You can view the behavior in a monkey patched ScrollView demo on rnplay: https://rnplay.org/apps/E90UYw https://rnplay.org/apps/UGzhKA In order to have a uniform props set, i added 3 values to the keyboardShouldPersistTaps: 'never' and 'always' are the same as false and true. 'handled' is the new behavior. I don't Closes #10628 Differential Revision: D4294945 Pulled By: ericvicenti fbshipit-source-id: 1a753014156cac1a23fabfa8e1faa9a768868ef2
Summary: When using text inputs inside a ScrollView with `keyboardShouldPersistTaps=false` (default behavior) tapping another text input dismisses the keyboard instead of keeping it open and focusing the new text input which I think is the better and expected behavior. See facebook#10628 for more discussion about that. Note that this affects nothing but the behavior with text inputs unlike facebook#10628. cc satya164 MaxLap ericvicenti Closes facebook#10887 Differential Revision: D4178474 Pulled By: ericvicenti fbshipit-source-id: 0c62ea2fac0017d559d1f8674b0a686a5e1b3d2d
Summary: Right now, the ScrollView's keyboard hiding behavior is either all or nothing: Hide the keyboard on any tap, or do nothing ever. This PR introduces a third mode to keyboardShouldPersistTaps which is much closer to what I consider should be the default. In the new behavior, the tap responding is done in the bubbling phase (instead of the capture phase like =true). As a result, a child can handle the tap. If no child does, then the ScrollView will receive the tap and will hide the keyboard. As a result, changing TextInput focus works as a user expects, with a single tap and without keyboard hiding. But taping on Text or on the empty part of the ScrollView hides the keyboard and removes the focus. You can view the behavior in a monkey patched ScrollView demo on rnplay: https://rnplay.org/apps/E90UYw https://rnplay.org/apps/UGzhKA In order to have a uniform props set, i added 3 values to the keyboardShouldPersistTaps: 'never' and 'always' are the same as false and true. 'handled' is the new behavior. I don't Closes facebook#10628 Differential Revision: D4294945 Pulled By: ericvicenti fbshipit-source-id: 1a753014156cac1a23fabfa8e1faa9a768868ef2
Summary: Right now, the ScrollView's keyboard hiding behavior is either all or nothing: Hide the keyboard on any tap, or do nothing ever. This PR introduces a third mode to keyboardShouldPersistTaps which is much closer to what I consider should be the default. In the new behavior, the tap responding is done in the bubbling phase (instead of the capture phase like =true). As a result, a child can handle the tap. If no child does, then the ScrollView will receive the tap and will hide the keyboard. As a result, changing TextInput focus works as a user expects, with a single tap and without keyboard hiding. But taping on Text or on the empty part of the ScrollView hides the keyboard and removes the focus. You can view the behavior in a monkey patched ScrollView demo on rnplay: https://rnplay.org/apps/E90UYw https://rnplay.org/apps/UGzhKA In order to have a uniform props set, i added 3 values to the keyboardShouldPersistTaps: 'never' and 'always' are the same as false and true. 'handled' is the new behavior. I don't Closes facebook/react-native#10628 Differential Revision: D4294945 Pulled By: ericvicenti fbshipit-source-id: 1a753014156cac1a23fabfa8e1faa9a768868ef2
Right now, the ScrollView's keyboard hiding behavior is either all or nothing: Hide the keyboard on any tap, or do nothing ever. This PR introduces a third mode to keyboardShouldPersistTaps which is much closer to what I consider should be the default.
In the new behavior, the tap responding is done in the bubbling phase (instead of the capture phase like =true). As a result, a child can handle the tap. If no child does, then the ScrollView will receive the tap and will hide the keyboard. As a result, changing TextInput focus works as a user expects, with a single tap and without keyboard hiding. But taping on Text or on the empty part of the ScrollView hides the keyboard and removes the focus.
You can view the behavior in a monkey patched ScrollView demo on rnplay:
https://rnplay.org/apps/E90UYw
https://rnplay.org/apps/UGzhKA
In order to have a uniform props set, i added 3 values to the keyboardShouldPersistTaps:
'never' and 'always' are the same as false and true.
'handled' is the new behavior.
I don't know stance on backward compatibility of the project. The default behavior isn't changed and remains at 'always' (true), but in my opinion, the goal should be to change the default to be the new 'handled' behavior since that feels the most intuitive.
Note that true and false are still valid options.