-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Framework: Extract arrow navigation to its own component #2424
Conversation
Codecov Report
@@ Coverage Diff @@
## master #2424 +/- ##
==========================================
+ Coverage 31.57% 34.26% +2.68%
==========================================
Files 175 177 +2
Lines 5304 6173 +869
Branches 914 1176 +262
==========================================
+ Hits 1675 2115 +440
- Misses 3081 3390 +309
- Partials 548 668 +120
Continue to review full report at Codecov.
|
I think this is a good direction and maybe we ought to take it a step further to have I think the general advantage here is in isolating text interactivity, since it's quite complex, and certainly not related to rendering the block's editable form. I'll plan to do a more thorough review shortly. |
@iseulde Had a similar idea where we provide a |
Another benefit to isolating these behaviors is that it makes them more testable... ahem... AHEM 😉 |
bdad318
to
291a62a
Compare
@iseulde @aduth @karmatosed @jasmussen @mtias I worked on this today and tried to resolve the navigation issues we were having #2104 and #2124 |
@youknowriad At first sight, that looks good and makes sense. I'll have a deeper look tonight or tomorrow. |
Merged isEdge from
Feel free to reset of course. |
Some parts may be testable, but not sure how we should test the selection stuff? Manual browser tests? Something à la PhantomJS? @youknowriad Tested a bit, but when the Editable has been focussed already, the caret moves to the last position. |
@iseulde Thanks for the change, this is better 👍 |
@iseulde Improved the caret moves, it should behave better now. The last problem I see is with the Table block, but I'm not sure how to fix it. I wonder if we could move forward anyway. |
* @param {Element} container DOM Element | ||
* @param {Boolean} start Position: Start or end of the element | ||
*/ | ||
export function placeCaretAtEdge( container, start = false ) { |
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.
IMHO, it would be better to use a hash for the options so it's more readable. (Same for the other functions.)
@youknowriad I think I'm still having the same problem... To reproduce:
|
@iseulde Thanks for the steps, this is really hard to get right. I noticed that when moving up, the |
This is looking good to me. |
blocks/url-input/index.js
Outdated
@@ -104,6 +104,10 @@ class UrlInput extends Component { | |||
|
|||
onKeyDown( event ) { | |||
const { selectedSuggestion, posts } = this.state; | |||
if ( ! this.state.showSuggestions || ! this.state.posts.length ) { |
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.
Can we have a comment here to explain what this is for? I don't know what showSuggestions has to do with the key event.
7e5b96e
to
8b2e9ca
Compare
unfortunately, As suggested by @iseulde This could be tested in potential integration tests. |
editor/utils/dom.js
Outdated
container.focus(); | ||
setTimeout( () => { | ||
if ( start ) { | ||
container.selectionStart = 0; |
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.
Do we know that container
still exists at this point? To the more pertinent point, do we need the setTimeout
? Can we comment as such to why it is necessary?
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, it was necessary at the time of writing (not sure why?), but I made some tweaks later, I'll check again.
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.
Cool, the timeout is not necessary anymore :)
editor/utils/dom.js
Outdated
return; | ||
} | ||
|
||
function placeCaretInContentEditable( element, atStart ) { |
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 do we create a function for this when we could inline it in place? Comments can serve as stand-in to self-documenting functions.
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.
yep probably not necessary
editor/writing-flow/index.js
Outdated
} | ||
|
||
getVisibleTabbables() { | ||
const tabblablesSelector = [ |
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.
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.
Using a library or extracting to a general helper seems like a good thing to me. But I'm not sure it will work as expected, if I understand correctly ally.js
returns different tabbables depending on the browser. We'll have to test I guess
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 this be tabbablesSelector
?
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 works well in Chrome, Safari, Firefox on macOS. The behavior in Edge is not so great. Pressing "Down" when in the middle of the last line of a paragraph does nothing. Sometimes when pressing from end of a paragraph to the next, it moves to the middle of the current paragraph instead (anecdotally, after an italicized text?)
The dance of onKeyDown
and onKeyUp
looks a bit awkward, and more importantly feels not quite right in usage (especially when not quickly pressing arrow buttons) but I sense there was some difficulty in getting this quite right, so maybe some iterative improvements can be made.
@@ -104,6 +104,12 @@ class UrlInput extends Component { | |||
|
|||
onKeyDown( event ) { | |||
const { selectedSuggestion, posts } = this.state; | |||
// If the suggestions are not shown, we shouldn't handle the arrow keys | |||
// We shouldn't preventDefault to allow block arrow keys navigation |
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.
Doesn't return false;
have the same effect as preventDefault
?
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 guess not since it fixed the issue (maybe it was the stopPropagation) but any way safer to just return
editor/writing-flow/index.js
Outdated
'input', | ||
].join( ', ' ); | ||
const isVisible = ( elem ) => elem.offsetWidth > 0 || elem.offsetHeight > 0 || elem.getClientRects().length > 0; | ||
return Array.from( this.container.querySelectorAll( tabbablesSelector ) ).filter( isVisible ); |
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.
Minor (stylistic): Can be expressed with array spread too:
[ ...this.container.querySelectorAll( tabbablesSelector ) ]
Yes, it was initially all handled in onKeyDown but #2424 (comment) |
I suspect the selection API behaves slightly differently in Edge. Unfortunately, I can't fix this right now (no VM and no bandwidth to get it). |
Ok merging. We'll address the remaining issues in other PRs |
|
||
if ( ( moveUp || moveDown ) && isEdge( target, moveUp ) ) { | ||
event.preventDefault(); | ||
this.setState( { shouldMove: 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.
Do we need to be using state for this, or could we assign an instance property? Thinking the latter would help to avoid two unnecessary rerenders.
this.shouldMove = 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.
You're probably right 👍
if ( ! this.state.showSuggestions || ! this.state.posts.length ) { | ||
return; | ||
} |
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'm led here via #19462 (comment) , and trying to understand what it was that we were trying to accomplish with this condition. I'm guessing at the time it was when we were first introducing WritingFlow
and using arrow keys to navigate between blocks. But in considering a link editor input: Why should we care whether there are suggestions present in deciding whether to prevent the default arrow key behavior? Based on what I describe in #19462 (comment), I sorta expect that arrow keys should always use the native behavior in the input, regardless of suggestions present. I don't really expect to be able to navigate between blocks in this context.
Trying to recall: Did we used to have the link fields inline within the blocks list, and this was how we tried to allow the arrow keys to let the user go to the next/previous focusable field? Most all of those are in popovers now, correct? Maybe we don't want to assume that, but I also don't think we need to optimize for WritingFlow here either.
In some ways it relates to #19488 (#18755), where I talk about what it is about these inputs which make us want them to be ignored by WritingFlow, ObserveTyping, etc. More and more, I think it's that being in a popover (modal) gives it its own context, where it should be treated as detached from the rest of the editor (and thus not inherit any of those typing behaviors).
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 sorta expect that arrow keys should always use the native behavior in the input, regardless of suggestions present.
If URLInput is not used in the popover (Button block at that time), it shouldn't be "an arrow navigation trap"
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 URLInput is not used in the popover (Button block at that time), it shouldn't be "an arrow navigation trap"
Yeah, thinking about it more, it feels like "popover" is more the exception (like previously mentioned with related #19488, #18755). It has me wondering whether it would be reasonable to treat popovers as entirely separate context, and to (by default) stop all event bubbling propagation from escaping. Maybe that's an extreme approach, but I kinda see it making sense as a totally independent document, almost like an iframe
.
related #2421 and transversal to #2296
This doesn't change the way arrow navigation works (like #2296 does) but just extracts it to its own component.
Pros
VisualEditorBlock
component handles way more than it shouldWritingFlow
component can wrap anything to allow arrow navigation between tabbables.Notes
Like suggested in How can we improve the writing flow and its stability? #2421 I think we should think more about how to "compose" all these behaviours. I think this component could also handle
Enter
andBackspace
navigation the same way we do for arrow navigation. It could be done separately and probably after merging Improved arrow navigation #2296Not done in this PR yet, I think the
isTyping
reducer/actions should be handled by this component (WriningFlow) because the PostTitle should be able to update this property too.