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

fix(Popup) popup appearing in wrong position when open prop is set #2775

Merged
merged 5 commits into from
May 19, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 40 additions & 19 deletions src/modules/Popup/Popup.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
import Portal from '../../addons/Portal'
import PopupContent from './PopupContent'
import PopupHeader from './PopupHeader'
import Ref from '../../addons/Ref'

const debug = makeDebugger('popup')

Expand Down Expand Up @@ -164,27 +165,28 @@ export default class Popup extends Component {
const { pageYOffset, pageXOffset } = window
const { clientWidth, clientHeight } = document.documentElement

const coords = this.coords || this.triggerRef.getBoundingClientRect()
if (_.includes(positions, 'right')) {
style.right = Math.round(clientWidth - (this.coords.right + pageXOffset))
style.right = Math.round(clientWidth - (coords.right + pageXOffset))
style.left = 'auto'
} else if (_.includes(positions, 'left')) {
style.left = Math.round(this.coords.left + pageXOffset)
style.left = Math.round(coords.left + pageXOffset)
style.right = 'auto'
} else { // if not left nor right, we are horizontally centering the element
const xOffset = (this.coords.width - this.popupCoords.width) / 2
style.left = Math.round(this.coords.left + xOffset + pageXOffset)
const xOffset = (coords.width - this.popupCoords.width) / 2
style.left = Math.round(coords.left + xOffset + pageXOffset)
style.right = 'auto'
}

if (_.includes(positions, 'top')) {
style.bottom = Math.round(clientHeight - (this.coords.top + pageYOffset))
style.bottom = Math.round(clientHeight - (coords.top + pageYOffset))
style.top = 'auto'
} else if (_.includes(positions, 'bottom')) {
style.top = Math.round(this.coords.bottom + pageYOffset)
style.top = Math.round(coords.bottom + pageYOffset)
style.bottom = 'auto'
} else { // if not top nor bottom, we are vertically centering the element
const yOffset = (this.coords.height + this.popupCoords.height) / 2
style.top = Math.round((this.coords.bottom + pageYOffset) - yOffset)
const yOffset = (coords.height + this.popupCoords.height) / 2
style.top = Math.round((coords.bottom + pageYOffset) - yOffset)
style.bottom = 'auto'

const xOffset = this.popupCoords.width + 8
Expand Down Expand Up @@ -246,7 +248,7 @@ export default class Popup extends Component {
}

setPopupStyle() {
if (!this.coords || !this.popupCoords) return
if ((!this.coords && !this.triggerRef) || !this.popupCoords) return
let position = this.props.position
let style = this.computePopupStyle(position)
const { keepInViewPort } = this.props
Expand Down Expand Up @@ -324,13 +326,15 @@ export default class Popup extends Component {
const { hideOnScroll } = this.props

if (hideOnScroll) eventStack.sub('scroll', this.hideOnScroll, { target: window })
this.setPosition()
_.invoke(this.props, 'onMount', e, this.props)
}

handlePortalUnmount = (e) => {
debug('handlePortalUnmount()')
const { hideOnScroll } = this.props

cancelAnimationFrame(this.animationRequestId)
if (hideOnScroll) eventStack.unsub('scroll', this.hideOnScroll, { target: window })
_.invoke(this.props, 'onUnmount', e, this.props)
}
Expand All @@ -341,6 +345,21 @@ export default class Popup extends Component {
this.setPopupStyle()
}

handleTriggerRef = (triggerRef) => {
debug('triggerMounted()')
if (triggerRef) {
this.triggerRef = triggerRef
}
}

setPosition = () => {
if (this.triggerRef) {
this.setPopupStyle(this.props.position)
}

this.animationRequestId = requestAnimationFrame(this.setPosition)
Copy link
Member

Choose a reason for hiding this comment

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

haven't you introduced an infinite loop now? When does this ever get short circuited?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good point. fixed in 7c2104f

Choose a reason for hiding this comment

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

Unfortunately the infinite loop is still there - all the time the popup is displayed setPosition() is called in a loop, it then calls setPopupStyle which re-sets component's state which causes re-render - takes ~20% CPU when the popup is displayed.

}

render() {
const {
basic,
Expand Down Expand Up @@ -394,16 +413,18 @@ export default class Popup extends Component {
debug('portal props:', mergedPortalProps)

return (
<Portal
{...mergedPortalProps}
trigger={trigger}
onClose={this.handleClose}
onMount={this.handlePortalMount}
onOpen={this.handleOpen}
onUnmount={this.handlePortalUnmount}
>
{popupJSX}
</Portal>
<Ref innerRef={this.handleTriggerRef}>
<Portal
{...mergedPortalProps}
trigger={trigger}
onClose={this.handleClose}
onMount={this.handlePortalMount}
onOpen={this.handleOpen}
onUnmount={this.handlePortalUnmount}
>
{popupJSX}
</Portal>
</Ref>
)
}
}
35 changes: 20 additions & 15 deletions test/specs/modules/Popup/Popup-test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import _ from 'lodash'
import React from 'react'

import Portal from 'src/addons/Portal/Portal'
import Ref from 'src/addons/Ref/Ref'
import { SUI } from 'src/lib'
import Popup, { POSITIONS } from 'src/modules/Popup/Popup'
import PopupHeader from 'src/modules/Popup/PopupHeader'
Expand All @@ -21,7 +21,10 @@ const wrapperShallow = (...args) => (wrapper = shallow(...args))

const assertIn = (node, selector, isPresent = true) => {
const didFind = node.querySelector(selector) !== null
didFind.should.equal(isPresent, `${didFind ? 'Found' : 'Did not find'} "${selector}" in the ${node}.`)
didFind.should.equal(
isPresent,
`${didFind ? 'Found' : 'Did not find'} "${selector}" in the ${node}.`,
)
}
const assertInBody = (...args) => assertIn(document.body, ...args)

Expand All @@ -48,7 +51,7 @@ describe('Popup', () => {
it('renders a Portal', () => {
wrapperShallow(<Popup />)
.type()
.should.equal(Portal)
.should.equal(Ref)
})

it('renders to the document body', () => {
Expand All @@ -59,9 +62,7 @@ describe('Popup', () => {
it('renders child text', () => {
wrapperMount(<Popup open>child text</Popup>)

document.querySelector('.ui.popup.visible')
.innerText
.should.equal('child text')
document.querySelector('.ui.popup.visible').innerText.should.equal('child text')
})

it('renders child components', () => {
Expand Down Expand Up @@ -139,14 +140,9 @@ describe('Popup', () => {

describe('position', () => {
POSITIONS.forEach((position) => {
it('is always within the viewport', () => {
it('is always within the viewport when the trigger is clicked', () => {
wrapperMount(
<Popup
content='_'
position={position}
trigger={<button>foo</button>}
on='click'
/>,
<Popup content='_' position={position} trigger={<button>foo</button>} on='click' />,
)
wrapper.find('button').simulate('click')

Expand All @@ -158,6 +154,14 @@ describe('Popup', () => {
expect(bottom).to.be.at.most(document.documentElement.clientHeight)
expect(right).to.be.at.most(document.documentElement.clientWidth)
})
it('is positioned properly when open property is set', () => {
wrapperMount(<Popup content='_' position={position} open trigger={<button>foo</button>} />)
const element = document.querySelector('.popup.ui')
element.style.should.not.have.property('top', '')
element.style.should.not.have.property('left', '')
element.style.should.not.have.property('bottom', '')
element.style.should.not.have.property('right', '')
})
it('is the original if no horizontal position fits within the viewport', () => {
wrapperMount(
<Popup
Expand Down Expand Up @@ -304,8 +308,9 @@ describe('Popup', () => {

it('it appears on multiple', (done) => {
const trigger = <button>foo</button>
const button = wrapperMount(<Popup on={['click', 'hover']} content='foo' header='bar' trigger={trigger} />)
.find('button')
const button = wrapperMount(
<Popup on={['click', 'hover']} content='foo' header='bar' trigger={trigger} />,
).find('button')

button.simulate('click')
assertInBody('.ui.popup.visible')
Expand Down