-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Keep mobile keyboard on ENTER: delay focus until MCE init #4775
Conversation
37053b2
to
eb6ee66
Compare
Here's what I'm seeing in this branch: https://cloudup.com/cj4vQAOSyir Are you seeing the same? |
@jasmussen So another issue seems to we the keyboard overlapping the target when it first pops up... I'll try to fix. |
It seems Gutenberg is in general suffering from a few scroll issues. When I open the inserter, the whole screen scrolls to the bottom. |
dd96f0e
to
6e75cd6
Compare
For our records, something similar happens with just plain contenteditables: https://codepen.io/iseulde/full/qxOoGd/ |
Okay, I think this is a lot better now. We'll get the scroll container dynamically (changes based on viewport width at 600px). The scroll container must be padded so we can scroll into view when there is little content. Maybe there's a better solution here. |
YOU DID IT! This experience is amazing. Not only did you fix the issue when creating new paragraphs, you fixed the bounce too, I didn't think that was possible. You deserve a literal medal. These emoji don't do justice 🏅🥇🎖 |
The only thing that bothers me still is that sometimes, when you focus some text that will later be covered by the keyboard, it won't scroll up. |
On mobile Safari, the viewport doesn't change height when the soft keyboard shows up. It does on Android. I don't know how much there is we can do here. |
This issue is now fixed btw. :)
That's because the publish toolbar hides on iOS, but it does not on Android. Not sure how to fix ATM or what the mechanism that hides it on iOS is. |
blocks/rich-text/index.js
Outdated
// the position can be scrolled to in the next focussed | ||
// instance. | ||
if ( window.innerWidth < 784 ) { | ||
scrollPosition = this.editor.getBody().getBoundingClientRect(); |
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 probably a left over?
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 do you mean with left over? :)
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.
Nevermind, I thought it was a local variable
blocks/rich-text/index.js
Outdated
@@ -212,6 +219,30 @@ export default class RichText extends Component { | |||
} | |||
|
|||
onFocus() { | |||
// For small screens (virtual keyboard), always scroll the focussed | |||
// editor into view. Unfortunately we cannot detect virtual keyboards. | |||
if ( window.innerWidth < 784 ) { |
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.
Maybe we could extract this to a module const to avoid repetition?
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.
Yeah, that's probably best. To utils?
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.
Hm, I just tried the iPad horizontally, and this won't cover it. We need a better way to detect virtual keyboards.
@@ -884,6 +933,7 @@ export default class RichText extends Component { | |||
{ ...ariaProps } | |||
className={ className } | |||
key={ key } | |||
onMount={ this.onTinyMCEMount } |
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.
Should we move these scrolling behaviors to the TinyMCE component instead of adding this Prop?
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 did, but then we need to pass the scrollPosition
variable which is tied to this component. To me it looked cleaner this way, but I'm fine either way.
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.
True, I missed the global variable
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.
Global sounds bad :) I'd prefer to call it module variable ;)
@@ -70,6 +70,7 @@ | |||
|
|||
.edit-post-layout__content { | |||
position: relative; | |||
padding-bottom: 100vh; |
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's the purpose of 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.
This could use a comment as well. :) To pad the scroll box if there is not enough content to scroll.
@@ -504,6 +480,7 @@ const mapStateToProps = ( state, { uid } ) => ( { | |||
isFirstMultiSelected: isFirstMultiSelectedBlock( state, uid ), | |||
isHovered: isBlockHovered( state, uid ) && ! isMultiSelecting( state ), | |||
focus: getBlockFocus( state, uid ), | |||
shouldFocusNext: shouldBlockFocusNext( state, uid ), |
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.
So if I understand properly this new prop is useful because there's a new dispatch
that will be performed later to move the focus? Why can't we batch these two dispatches together?
- Maybe a unique actions
- Using a store enhancer or a middleware (I believe these things already exist)
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.
Yes, we let the editor that needs focus call onFocus
by itself later. There's no newly added dispatch here, we are just moving focus too early. I'm not sure how an enhancer or middleware would help.
Added some reviewers as this kind of change is always tricky :) |
editor/store/reducer.js
Outdated
focus: {}, | ||
isMultiSelecting: false, | ||
// We must wait for Editable to initialise. | ||
next: action.blocks[ 0 ].uid, |
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.
Not sure I understand here, you're adding a next
saying that this block will be focused next but for me focus: {}
means the same thing?
So wondering why the focus
prop is not passed at the same moment to the block without 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.
The focus
props for the previous block will be taken away, which will cause the area to blur.
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.
So you want to focus before blur right? Maybe we can just delay the .blur
call?
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.
How do we do that across instances? I guess we could maybe use something like scrollPosition
. scrollPosition || blur
.
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.
Actually, I wonder if this .blur
called is needed at all, might be good to check the commit adding 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.
The .blur()
call was added here #683 (comment) where the reason seems based on "logic" than on a use case (cc @aduth). If removing the blur fixes the mobile issue, I'd vote for that :)
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.
Calling blur might be bad in any case, because the focus should always move somewhere else. This will just move it to the body element.
310ebe7
to
4bf3b8e
Compare
I removed the blur call entirely, and while this seems to work on iPhone, the keyboard still hides on enter on iPad... Looking... |
This does not happen with the last version d1966db... What is happing here... 😭 |
@youknowriad It seems that it just sometimes works, sometimes it does not, because focus is first moved to the block wrapper: gutenberg/editor/components/block-list/block.js Lines 125 to 128 in 56e192c
With the |
I'm also wondering why the autofocus is needed at all. I don't immediately see regressions taking it away. Having a look at the history here. |
7a4f87c
to
2d1c883
Compare
Rebased. |
Figuring out how to fix the e2e test. |
@youknowriad It seems the e2e tests just start typing too fast after click/enter. I always see the first character splitting off from the rest. Unsure how to fix at this time. I tried different selectors and delay. |
ad part of #4872 I noticed that one of the tests was a false positive in master and the issue was surfaced by my changes (and I suspect it surfaced here as well). And it looks like there's no way to fix it at the moment so I commented and added a comment to explain the reasons. Maybe if you rebase the tests will pass. |
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.
Nice work. :) Left some questions.
@@ -592,6 +616,11 @@ export default class RichText extends Component { | |||
if ( event.shiftKey || ! this.props.onSplit ) { | |||
this.editor.execCommand( 'InsertLineBreak', false, event ); | |||
} else { | |||
// For type writing offect, save the root node offset so it | |||
// can the position can be scrolled to in the next focussed | |||
// instance. |
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.
Copy errors here.
blocks/rich-text/index.js
Outdated
@@ -212,6 +219,23 @@ export default class RichText extends Component { | |||
} | |||
|
|||
onFocus() { | |||
// For virtual keyboards, always scroll the focussed editor into view. | |||
// Unfortunately we cannot detect virtual keyboards, so we check UA. |
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 don't have an external keyboard right now, but what happens if I use Gutenberg with a mobile device with an external keyboard? Most systems hide the virtual keyboard whenever an external keyboard is connected; any chance the auto-scrolling can be detrimental in those cases?
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 wouldn't be super annoying I think. The focussed field won't go out of view, and in these cases, we're still dealing with a relatively small screen. The bigger the screen, the worse this gets. While not ideal, this is the best I can come up with. I've played with other approaches as well, e.g. trying to detect touch (vs clicks), but failed to make it work. The order of which touch, click, and focus events are fired makes it seems impossible to do.
blocks/rich-text/index.js
Outdated
@@ -212,6 +219,23 @@ export default class RichText extends Component { | |||
} | |||
|
|||
onFocus() { | |||
// For virtual keyboards, always scroll the focussed editor into view. | |||
// Unfortunately we cannot detect virtual keyboards, so we check UA. | |||
if ( /iPad|iPhone|iPod|Android/i.test( window.navigator.userAgent ) ) { |
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.
Is it worth making a isMobile
util?
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.
Maybe. Would be nice if there are more uses. In this specific case we're not really trying to detect "mobile" devices, but rather devices with virtual keyboards.
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.
Agreed. My thinking was also something like:
isMobile = () => regex.test( … )
hasVirtualKeyboard = isMobile // best approximation
I'm trying to rebase, but the last focus changes are causing some bugs here. Haven't figured out why yet, I may need to revisit this tomorrow. |
Let us know when/if you'd like a last look, once that's resolved. |
2d1c883
to
1e62288
Compare
Looking at this again. Sometimes instead of scrolling down on enter, it scrolls up... I can't remember this before the rebase. Trying to debug but not sure what's going on. |
Got a bursting headache from this issue |
1e62288
to
f281688
Compare
There were some changes in parallel to this PR so conflicts happened. I rebased handled the conflicts and I did a new set of tests. |
// If there is a contenteditable element, it will move focus by itself. | ||
if ( ! target.querySelector( '[contenteditable="true"]' ) ) { | ||
target.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 is the only change that's bothering me in this PR because it introduces a special case in how blocks focus is handled.
I'm thinking all this behavior focusTabbable when a new block is selected
could be moved into a separate component and also handle the usecase we need for this PR: Focus the first tabbable element at the right position :)
Anyway, might not be that easy to do, so maybe for now, just consolidate the test of the presence of a TinyMCE editor to match the test already done here https://github.com/WordPress/gutenberg/pull/4775/files#diff-bc66d5701a2fea1111bb8f1bf0150910R244 const editor = tinymce.get( target.getAttribute( 'id' ) );
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.
Also, I believe this assumes that the TinyMCE will focus itself which I believe is not always what we want. Imagine inserting a block containing a plainText followed by the richText. TinyMCE won't be focused in that case.
I believe this is only necessary if we use the onSplit
behavior of the RichText element.
// For type writing offect, save the root node offset so it | ||
// can the position can be scrolled to in the next focussed | ||
// instance. | ||
offsetTop = rootNode.getBoundingClientRect().top; |
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.
While this works at the moment, nothing guarantee that it will work in the future, imagine if the onSplit
callback of the RichText component inserts something else that a RichText
, say a PlainText
or another unrelated block, how this would work? would it be a problem?
I don't have an alternative implementation at the moment but it seems fragile to rely on a global variable like this and assume a certain behavior in parent blocks.
Thank you for helping with this PR. It's going to improve the user experience so much, and it feels like it's nearly there, experience wise. |
I found a similar (but slightly different) issue to #4775 (comment) If I create a post and keep inserting paragraphs, then the space at the end of the post keeps growing and I can scroll the post out of view, whether I use the arrows or not. This is in Chrome 61. |
There's the same space at the bottom of posts on android too, you can swipe up and lose the post off the top of the screen (although that's probably the same thing Joen reports near the beginning :) ) |
Ella has done such a great job on this PR, and deserves multiple medals for so drastically improving the writing behavior on phones, notably the iPhone. It would be a great thing if we were to pull together and ship this PR as soon as we can, as the way this fixes the phone experience would be great to test in master for a bit. It's also definitely clear that this is a technically difficult PR with many different moving pieces. How can we help ship this together, so it's not only on Ella's shoulders? Does it make sense to look at the individual pieces that make up this PR and ship them as smaller PRs separately? Is there a way we reduce this PR to a smaller, half-way step, and then pull together to ship the remaining pieces afterwards? CC: @aduth @youknowriad @gziolo @karmatosed @jorgefilipecosta |
Done in #5769. |
Description
This PR fixes #2601 and part of #4731. It delays moving the focus an editable that is still initialising, which fixes the keyboard hiding on ENTER.
How Has This Been Tested?
Open Gutenberg in iOS with an on screen keyboard. Keyboard should not hide on ENTER.
Checklist: