@@ -7,7 +7,7 @@ import React from 'react';
77import { createPortal } from 'react-dom' ;
88import PropTypes from 'prop-types' ;
99import className from 'classnames' ;
10- import { getOffset } from '../helpers/iconHelpers' ;
10+ import { getOffset , debounce } from '../helpers/iconHelpers' ;
1111
1212class FipDropDownPortal extends React . PureComponent {
1313 static propTypes = {
@@ -66,10 +66,17 @@ class FipDropDownPortal extends React.PureComponent {
6666 // because it will be rendered by the
6767 // getDerivedStateFromProps lifecycle method
6868 this . state = { } ;
69+
70+ // A debounced function for resize and scroll
71+ this . debouncedSyncPortalPosition = debounce (
72+ this . syncPortalPosition ,
73+ 250 ,
74+ ) ;
6975 }
7076
7177 componentDidMount ( ) {
72- window . addEventListener ( 'resize' , this . syncPortalPosition ) ;
78+ window . addEventListener ( 'resize' , this . debouncedSyncPortalPosition ) ;
79+ window . addEventListener ( 'scroll' , this . debouncedSyncPortalPosition ) ;
7380 this . syncPortalPosition ( ) ;
7481 }
7582
@@ -78,15 +85,13 @@ class FipDropDownPortal extends React.PureComponent {
7885 }
7986 /* istanbul ignore next */
8087 componentWillUnmount ( ) {
81- window . removeEventListener ( 'resize' , this . syncPortalPosition ) ;
88+ window . removeEventListener ( 'resize' , this . debouncedSyncPortalPosition ) ;
89+ window . removeEventListener ( 'scroll' , this . debouncedSyncPortalPosition ) ;
8290 }
8391
8492 syncPortalPosition = ( ) => {
85- // if mounting not to self, then position the portal
86- if ( this . state . appendRoot !== 'self' ) {
87- // setTimeout(() => this.positionPortal(), 10);
88- this . positionPortal ( ) ;
89- }
93+ // reset the portal
94+ this . resetPortalPosition ( ) ;
9095
9196 // Fix window overflow
9297 this . fixWindowOverflow ( ) ;
@@ -113,17 +118,40 @@ class FipDropDownPortal extends React.PureComponent {
113118 this . props . domRef . current . style . display = display ;
114119 }
115120
121+ resetPortalPosition ( ) {
122+ const { current : dropDown } = this . props . domRef ;
123+ if ( this . state . appendRoot === 'self' ) {
124+ // The top would be none
125+ dropDown . style . top = '' ;
126+ } else {
127+ this . positionPortal ( ) ;
128+ }
129+ }
130+
116131 fixWindowOverflow = /* istanbul ignore next */ ( ) => {
117132 const popupWidth = this . props . domRef . current . offsetWidth ;
118- const windowWidth = window . innerWidth ;
119- const { left : popupOffsetLeft } = getOffset ( this . props . domRef . current ) ;
133+ const popupHeight = this . props . domRef . current . offsetHeight ;
134+ const { innerWidth : windowWidth , pageYOffset } = window ;
135+ const { clientHeight } = document . documentElement ;
136+
137+ const { left : popupOffsetLeft , top : popupOffsetTop } = getOffset (
138+ this . props . domRef . current ,
139+ ) ;
140+ const rootElm =
141+ this . state . appendRoot === 'self'
142+ ? this . props . domRef . current
143+ : this . state . appendRoot ;
144+ const rootOffset = getOffset ( rootElm ) ;
145+ const { current : btn } = this . props . btnRef ;
146+ const { current : dropDown } = this . props . domRef ;
147+ const btnOffset = getOffset ( btn ) ;
148+ const btnStyles = getComputedStyle ( btn ) ;
149+ const btnBorder =
150+ ( parseInt ( btnStyles . borderTop , 10 ) || 0 ) +
151+ ( parseInt ( btnStyles . borderBottom , 10 ) || 0 ) ;
152+
120153 // We need to calculate if the popup is going to overflow the window
121154 if ( popupOffsetLeft + popupWidth > windowWidth - 20 ) {
122- const btnOffset = getOffset ( this . props . btnRef . current ) ;
123- const rootOffset =
124- this . state . appendRoot === 'self'
125- ? getOffset ( this . props . domRef . current )
126- : getOffset ( this . state . appendRoot ) ;
127155 let preferredLeft =
128156 btnOffset . left +
129157 this . props . btnRef . current . offsetWidth -
@@ -134,7 +162,26 @@ class FipDropDownPortal extends React.PureComponent {
134162 }
135163
136164 // Now set the goddamn left value
137- this . props . domRef . current . style . left = `${ preferredLeft } px` ;
165+ dropDown . style . left = `${ preferredLeft } px` ;
166+ }
167+ // We need to calculate if opened popup is too low
168+ if (
169+ // the height of popup + popoffset top > view port height
170+ popupHeight + popupOffsetTop - pageYOffset > clientHeight &&
171+ // If we are to position on top of button, then make sure page view can handle
172+ // so button offset top - popup height > 0
173+ btnOffset . top - popupHeight > 0
174+ ) {
175+ // Now we position the popup on top of the button
176+ if ( this . state . appendRoot === 'self' ) {
177+ // When appending to self, position should be relative to the
178+ // button height and popup height
179+ dropDown . style . top = `-${ popupHeight - btnBorder } px` ;
180+ } else {
181+ dropDown . style . top = `${ btnOffset . top +
182+ btnBorder -
183+ popupHeight } px`; // 2px for border
184+ }
138185 }
139186 } ;
140187
0 commit comments