Skip to content
This repository was archived by the owner on Nov 1, 2019. It is now read-only.

Adding partial support for internal scrolling - #539

Open
danielstro wants to merge 28 commits into
masterfrom
danielstr/popup-scroll/372
Open

Adding partial support for internal scrolling#539
danielstro wants to merge 28 commits into
masterfrom
danielstr/popup-scroll/372

Conversation

@danielstro

Copy link
Copy Markdown
Contributor

Added support for internal scrolling and onExitBounds event.
see #372

@danielstro
danielstro requested a review from alisey October 25, 2017 13:56

@alisey alisey left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are the scenarios we are 100% sure we want to address? If it's just the matter of the popup looking weird when it's not visibly attached to anything, then we should probably check containment of the anchor.

Let's say, we have a scrollable horizontal menu, and each item is a dropdown. It's totally fine for the popups to be outside of the scroll container. Things only start to look weird when the menu item itself starts to leave the container.

It's also interesting to look at how native components deal with this problem, because we might want to be able to replicate their behavior using our Popup.

Here's an example http://jsfiddle.net/vLdknthc/
Chrome and Firefox don't adjust the popup position no matter what happens. Chrome blocks user-initiated scrolling on the entire page while the popup is open, and Firefox dismisses the popup when you try to scroll.

Comment thread src/components/popup/popup.tsx Outdated

const rect = this.portal && this.portal.getPortal && this.portal.getPortal()!.getBoundingClientRect();
if (rect) {
this.isOutOfBounds = isOutOfBounds(newStyle.top, newStyle.left, rect.height, rect.width) ? true : false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My first thought was "out of bounds of what?". OK, out of bounds of the window. But fully or partially?

I suggest renaming to isFullyContainedWithinWindow() or something along those lines.

Also probably don't need ternary here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, not sure why I used one here.. refactor leftovers or lack of sleep?

Comment thread src/components/portal/portal.tsx Outdated
}

public getPortal(): Element | null {
if (this.container && this.container.children.length > 0) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

container.firstChild

Comment thread src/components/popup/popup.tsx Outdated
};

private portal: Portal | null;
private isOutOfBounds = false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this variable does not always reflect the current state and only becomes true during a transition, maybe rename to isExitingBounds?

Comment thread src/components/popup/popup.tsx Outdated
if (isOutOfBounds(this.props.anchor.y, this.props.anchor.x, 0, 0)) {
this.forceUpdate();
}
} else if ((e.target as Node).contains(this.props.anchor)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not that I think it's a problem, just worth mentioning: contains is inclusive, i.e. x.contains(x) is true, so we will also update when the anchor itself is being scrolled.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a problem. We don't want to re-render the popup in this situation.

Comment thread src/components/popup/popup.tsx Outdated
}

private onScroll = (e: Event) => {
if (this.props.anchor) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also check that the popup is open?

Comment thread src/components/popup/popup.tsx Outdated

private onScroll = (e: Event) => {
if (this.props.anchor) {
if (isPoint(this.props.anchor)) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a bug here. I need to figure out what to do if the anchor is a point.

Comment thread src/components/portal/portal.tsx Outdated
}

public getPortal(): Element | null {
if (this.container && this.container.children.length > 0) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

firstElementChild also here?

Comment thread src/components/popup/popup.tsx Outdated
if (this.props.anchor && this.props.open) {
if (e.target === document || isPoint(this.props.anchor)) {
const rect = this.getPortalRect();
if (rect && isFullyContainedWithinWindow(rect.top, rect.left, rect.height, rect.width)) {

@alisey alisey Oct 26, 2017

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't isFullyContainedWithinWindow be negated here?

It seems that two forceUpdate calls here serve different purposes: 1) triggering outOfBounds event (in which case I'm not sure why we don't call it directly without forcing update), and 2) updating the popup position. Maybe you could add a comment to explain why we treat these two cases differently, i.e. check out of bounds in one case but not in the other?

@danielstro danielstro Oct 29, 2017

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should, but this code isn't working anyway, I just didn't fix it yet..

@danielstro danielstro self-assigned this Oct 29, 2017
@danielstro
danielstro requested a review from JoMarton October 29, 2017 12:51

@JoMarton JoMarton left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add horizontal scrolling test
Also add a test for getPortal in Portal component

Comment thread test/components/popup.spec.tsx Outdated
});

it('calls onExitBounds when the popup leaves the viewport', async () => {
let div: HTMLDivElement;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please rename this....very confusing with actual html div

Comment thread test/components/popup.spec.tsx Outdated
scrollDiv!.scrollTop = 51;

return waitFor(() => {
expect(onExitBounds).to.have.been.called;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should it be called once? on every scroll? what happens when it comes back into bounds?

Comment thread test/components/popup.spec.tsx Outdated
<div style={{height: '50px'}}>Body</div>
</Popup>).withDriver(PopupTestDriver);

await waitForDom(() => expect(popup.root).to.be.present());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add tests to verify the expected position of the popup.
Also you could verify that the outOfBound event wasn't fired yet

Comment thread src/components/popup/popup.tsx Outdated
return newStyle;
}

private getPortalRect(): ClientRect | null {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to be more exact it is the rect of the portal contents

Comment thread src/components/portal/portal.tsx Outdated
}
}

public getPortal(): Element | null {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to further explore if this is the best solution

return elem.hasOwnProperty('x') && elem.hasOwnProperty('y');
}

function isFullyContainedWithinWindow(top: number, left: number, height: number, width: number): boolean {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please have tests for this function

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added tests

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants