-
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
Popover: Refactor popover to clarify computations #6799
Conversation
… max-width/max-height depending on the available space
components/popover/index.js
Outdated
if ( event.type === 'scroll' && this.nodes.content.contains( event.target ) ) { | ||
return; | ||
} | ||
this.rafHandle = window.requestAnimationFrame( () => this.computePopoverPosition() ); |
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: maybe we should save the id returned by requestAnimationFrame and invoke window.cancelAnimationFrame() when unMounting if we have a valid id to cancel.
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's already the case I believe
components/popover/index.js
Outdated
forcedXAxis || xAxis, | ||
]; | ||
if ( | ||
! this.state.popoverLeft || |
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 not understanding why we have the condition ! this.state.popoverLeft
shouldn't this case be covered by this.state.popoverLeft !== popoverLeft? I think I'm missing something.
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's a leftover :)
Automatic test cases need an update, but manually testing things are looking good and I did not notice any problem in my initial tests :) |
I fixed the tests, extracted the computing to an utility to ease unit-testing. I'm adding a design feedback. I think this improves things above popovers. It's not solving everything but it's a good improvement I think. |
8532bad
to
5884da6
Compare
components/popover/index.js
Outdated
this.toggleWindowEvents( true ); | ||
this.focus(); | ||
setTimeout( () => this.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.
I'd love to get rid of this timeout but for some mysterious reason, it's not working properly without it.
- Try removing the timeout
- Open the inserter
- The search input is not focused.
I tried debugging and for some reason the call to input.focus()
is not focusing the input (console logging the document.actiiveElement
.
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 tried to debug, and I did not find a good solution to this, It looks like although the parent component was mounted the child is not right away in a state that we can focus, this discussion seems related https://stackoverflow.com/questions/35522220/react-ref-with-focus-doesnt-work-without-settimeout-my-example.
What complicates, even more, the things is that if we use withSafeTimeout, we create the z-index bug ( i tested it again).
Maybe we can move the setTimeout inside the focus function so inside the timeout we don't reference this, props, etc.. we reference the dom node something like:
const focusNode = ( domNode ) => setTimeout( () => domNode.focus() );
const domNode = this.contentNode.current;
// Find first tabbable node within content and shift focus, falling
// back to the popover panel itself.
const firstTabbable = focus.tabbable.find( domNode )[ 0 ];
focusNode( firstTabbable ? firstTabbable : domNode );
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 expect this to create any issue regarding "this" because it's during mounting but even though, I like your proposal let's move it to focus
and add 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.
Maybe we can also add a comment specifying why we are using a set timeout, and why withSafeTimeout was not possible to use.
I think we get this in as it solves a lot of issues. thoughts @jorgefilipecosta |
@@ -794,7 +794,7 @@ export class RichText extends Component { | |||
changeFormats( formats ) { | |||
forEach( formats, ( formatValue, format ) => { | |||
if ( format === 'link' ) { | |||
if ( formatValue !== undefined ) { | |||
if ( !! formatValue ) { |
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 not understanding the relation of this changes in RichText with the changes in popover. Is it because of the link component? Without this changes we cause an unexpected bahaviour or they are just code improvement?
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 relation is indirect. There's a bug in master where if you click "Escape" on a newly created link (empty), the modal is not closed. This change ensures the modal is closed when you do so.
The reason it's in this PR is that the popover change makes the e2e test fail while it's not failing in master (false positive). I'm not certain I understand exactly why it's not failing in master tests because, in my manual testing, it's not working properly.
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.
Thank you for the clarification, e2e tests on our CI sometimes pass when they should fail. For example in #6877 I added a test case that should fail because the fix was not yet merged, running tests in my computer the test fails all the time, but in our CI it is reported as passing, not certain why.
components/popover/test/utils.js
Outdated
expect( computePopoverPosition( anchorRect, contentSize, 'top right' ) ).toEqual( { | ||
contentHeight: null, | ||
contentWidth: null, | ||
isMobile: 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.
All the test cases test the non-mobile case should we add a mobile test case?
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, good catch it was less important but I'll see if I can tweak the window size.
}; | ||
const bottomAlignment = { | ||
popoverTop: anchorRect.bottom, | ||
contentHeight: anchorRect.bottom + HEIGHT_OFFSET + height > window.innerHeight ? window.innerHeight - HEIGHT_OFFSET - anchorRect.bottom : height, |
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 window.innerHeight, and window.innerWidth can be parameters with default values to the window properties. I think it may simplify the addition of test cases in the future.
// Choosing the x axis | ||
let chosenXAxis; | ||
let contentWidth = null; | ||
if ( xAxis === 'center' && centerAlignment.contentWidth === width ) { |
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: Maybe we can use a switch.
On the switches we would do:
case 'center':
chosenXAxis = centerAlignment.contentWidth === width ? 'center' : undefined;
After the switch, we would check if chosenXAxis was undefined if yes we would use our last else. It is just a personal preference normally I prefer switches instead of a series of else.
It may also make sense to extract some parts of the function into smaller ones e.g: one part that deals with the x and other that deals with y, just to make computePopoverPosition smaller :)
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 like the switch in this case personally, I like "dumb" code :P but I like the splitting proposal which I did.
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.
Excellent work on this refactor. Testing the popover it looks like a big improvement. 👍
There is some debugging that could be made like understand why withSafeTimeout interferes with z-index if time allows in the future. But given the number of issues closed and the noticeable improvements for the user that we have I feel we should get it in soon so we can test it more before the next release.
return; | ||
} | ||
|
||
// Without the setTimeout, the dom node is not being focused | ||
// Related https://stackoverflow.com/questions/35522220/react-ref-with-focus-doesnt-work-without-settimeout-my-example | ||
const focusNode = ( domNode ) => setTimeout( () => domNode.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.
Where are we canceling this timeout if the component is unmounted? Why aren't we using withSafeTimeout
?
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 use withSafeTimeout
originally but It breaks the design in a super weird way. (see 66b3444) The usage of the higher order component even if it's not doing anything to the DOM was creating some weird z-index issues, especially visible on mobile.
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 about:
Where are we canceling this timeout if the component is unmounted?
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 already done in master
return; | ||
} | ||
|
||
// Without the setTimeout, the dom node is not being focused |
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 treating the symptom, not the cause, and the comment reads as such.
The real cause is a combination of:
- When initially mounted, the popover doesn't have a Slot context in which to render. When this becomes available, it re-renders the content into the slot (an unmount and remount operation).
- When initially mounted, the container has a style of
visibility: hidden
, so focusing would not select the container.
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.
See also: #8218
@@ -100,27 +96,22 @@ class Popover extends Component { | |||
return; | |||
} | |||
|
|||
const { content } = this.nodes; | |||
if ( ! content ) { | |||
if ( ! this.contentNode.current ) { |
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.
Related to earlier comment, we shouldn't need to check this here. If the component causes Popover#focus
to be called during its componentDidMount
, we should be able to assume that the ref.current
is assigned.
} | ||
|
||
throttledComputePopoverPosition( event ) { | ||
if ( event.type === 'scroll' && this.contentNode.current.contains( event.target ) ) { |
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 took me more than a minute to grasp why this condition is here. It seems obvious in retrospect, but a comment would have been nice.
and support setting max-width/max-height depending on the available space
This will fix the issues we have where popovers are cut over the edges of the browser window by adapting the height/width of the popovers.
Still having trouble with a z-index issue at the moment.
closes #4519, #6026, #6026, #4949, #5276, #6504,