Skip to content
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

Merged
merged 11 commits into from
May 23, 2018

Conversation

youknowriad
Copy link
Contributor

@youknowriad youknowriad commented May 17, 2018

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,

if ( event.type === 'scroll' && this.nodes.content.contains( event.target ) ) {
return;
}
this.rafHandle = window.requestAnimationFrame( () => this.computePopoverPosition() );
Copy link
Member

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.

Copy link
Contributor Author

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

forcedXAxis || xAxis,
];
if (
! this.state.popoverLeft ||
Copy link
Member

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.popoverLeftshouldn't this case be covered by this.state.popoverLeft !== popoverLeft? I think I'm missing something.

Copy link
Contributor Author

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 :)

@jorgefilipecosta
Copy link
Member

Automatic test cases need an update, but manually testing things are looking good and I did not notice any problem in my initial tests :)

@youknowriad
Copy link
Contributor Author

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.

@youknowriad youknowriad self-assigned this May 17, 2018
@youknowriad youknowriad added the Needs Design Feedback Needs general design feedback. label May 17, 2018
@youknowriad youknowriad force-pushed the update/popover-maxwidth-height branch from 8532bad to 5884da6 Compare May 22, 2018 13:37
this.toggleWindowEvents( true );
this.focus();
setTimeout( () => this.focus() );
Copy link
Contributor Author

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.

Copy link
Member

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 );

Copy link
Contributor Author

@youknowriad youknowriad May 22, 2018

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.

Copy link
Member

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.

@youknowriad
Copy link
Contributor Author

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 ) {
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

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.

expect( computePopoverPosition( anchorRect, contentSize, 'top right' ) ).toEqual( {
contentHeight: null,
contentWidth: null,
isMobile: false,
Copy link
Member

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?

Copy link
Contributor Author

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,
Copy link
Member

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 ) {
Copy link
Member

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 :)

Copy link
Contributor Author

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.

Copy link
Member

@jorgefilipecosta jorgefilipecosta left a 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.

@youknowriad youknowriad merged commit c8f8806 into master May 23, 2018
@youknowriad youknowriad deleted the update/popover-maxwidth-height branch May 23, 2018 09:27
@mtias mtias added this to the 3.0 milestone Jun 4, 2018
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() );
Copy link
Member

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 ?

Copy link
Contributor Author

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.

Copy link
Member

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?

Copy link
Contributor Author

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
Copy link
Member

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.

Copy link
Member

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 ) {
Copy link
Member

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 ) ) {
Copy link
Member

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Design Feedback Needs general design feedback.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Block ellipsis menu opens below the toolbar
4 participants