diff --git a/src/addons/Portal/Portal.d.ts b/src/addons/Portal/Portal.d.ts index 09d5b75bc4..c45f14a724 100644 --- a/src/addons/Portal/Portal.d.ts +++ b/src/addons/Portal/Portal.d.ts @@ -37,6 +37,9 @@ export interface StrictPortalProps { /** Event pool namespace that is used to handle component events. */ eventPool?: string + /** Hide the Popup when scrolling the window. */ + hideOnScroll?: boolean + /** The node where the portal should mount. */ mountNode?: any diff --git a/src/addons/Portal/Portal.js b/src/addons/Portal/Portal.js index 4ad57da0cd..01cbb2808d 100644 --- a/src/addons/Portal/Portal.js +++ b/src/addons/Portal/Portal.js @@ -9,6 +9,7 @@ import { doesNodeContainClick, makeDebugger, useAutoControlledValue, + useEventCallback, } from '../../lib' import useTrigger from './utils/useTrigger' import PortalInner from './PortalInner' @@ -38,6 +39,7 @@ function Portal(props) { openOnTriggerClick = true, openOnTriggerFocus, openOnTriggerMouseEnter, + hideOnScroll = false, } = props const [open, setOpen] = useAutoControlledValue({ @@ -73,12 +75,12 @@ function Portal(props) { return setTimeout(() => openPortal(eventClone), delay || 0) } - const closePortal = (e) => { + const closePortal = useEventCallback((e) => { debug('close()') setOpen(false) _.invoke(props, 'onClose', e, { ...props, open: false }) - } + }) const closePortalWithTimeout = (e, delay) => { debug('closeWithTimeout()', delay) @@ -146,6 +148,30 @@ function Portal(props) { // Component Event Handlers // ---------------------------------------- + React.useEffect(() => { + if (!hideOnScroll) { + return + } + + const handleScroll = (e) => { + debug('handleHideOnScroll()') + + // Do not hide the popup when scroll comes from inside the popup + // https://github.com/Semantic-Org/Semantic-UI-React/issues/4305 + if (_.isElement(e.target) && contentRef.current.contains(e.target)) { + return + } + + closePortal(e) + } + + window.addEventListener('scroll', handleScroll, { passive: true }) + + return () => { + window.removeEventListener('scroll', handleScroll) + } + }, [closePortal, hideOnScroll]) + const handlePortalMouseLeave = (e) => { if (!closeOnPortalMouseLeave) { return @@ -318,6 +344,9 @@ Portal.propTypes = { /** Event pool namespace that is used to handle component events */ eventPool: PropTypes.string, + /** Hide the Popup when scrolling the window. */ + hideOnScroll: PropTypes.bool, + /** The node where the portal should mount. */ mountNode: PropTypes.any, diff --git a/src/modules/Popup/Popup.js b/src/modules/Popup/Popup.js index ccd61053d0..591748acdd 100644 --- a/src/modules/Popup/Popup.js +++ b/src/modules/Popup/Popup.js @@ -1,4 +1,3 @@ -import EventStack from '@semantic-ui-react/event-stack' import cx from 'clsx' import _ from 'lodash' import PropTypes from 'prop-types' @@ -14,9 +13,9 @@ import { getUnhandledProps, makeDebugger, SUI, + useIsomorphicLayoutEffect, useKeyOnly, useKeyOrValueAndKey, - useIsomorphicLayoutEffect, useMergedRefs, usePrevious, } from '../../lib' @@ -72,11 +71,10 @@ function getPortalProps(props) { * Splits props for Portal & Popup. * * @param {Object} unhandledProps - * @param {Boolean} closed * @param {Boolean} disabled */ -function partitionPortalProps(unhandledProps, closed, disabled) { - if (closed || disabled) { +function partitionPortalProps(unhandledProps, disabled) { + if (disabled) { return {} } @@ -124,7 +122,7 @@ const Popup = React.forwardRef(function (props, ref) { eventsEnabled = true, flowing, header, - hideOnScroll, + hideOnScroll = false, inverted, offset, pinned = false, @@ -139,18 +137,11 @@ const Popup = React.forwardRef(function (props, ref) { wide, } = props - const [closed, setClosed] = React.useState(false) - const unhandledProps = getUnhandledProps(Popup, props) - const { contentRestProps, portalRestProps } = partitionPortalProps( - unhandledProps, - closed, - disabled, - ) + const { contentRestProps, portalRestProps } = partitionPortalProps(unhandledProps, disabled) const elementRef = useMergedRefs(ref) const positionUpdate = React.useRef() - const timeoutId = React.useRef() const triggerRef = React.useRef() const zIndexWasSynced = React.useRef(false) @@ -160,12 +151,6 @@ const Popup = React.forwardRef(function (props, ref) { usePositioningEffect(popperDependencies, positionUpdate) - React.useEffect(() => { - return () => { - clearTimeout(timeoutId.current) - } - }, []) - // ---------------------------------------- // Handlers // ---------------------------------------- @@ -180,24 +165,6 @@ const Popup = React.forwardRef(function (props, ref) { _.invoke(props, 'onOpen', e, { ...props, open: true }) } - const handleHideOnScroll = (e) => { - debug('handleHideOnScroll()') - - // Do not hide the popup when scroll comes from inside the popup - // https://github.com/Semantic-Org/Semantic-UI-React/issues/4305 - if (_.isElement(e.target) && elementRef.current.contains(e.target)) { - return - } - - setClosed(true) - - timeoutId.current = setTimeout(() => { - setClosed(false) - }, 50) - - handleClose(e) - } - const handlePortalMount = (e) => { debug('handlePortalMount()') _.invoke(props, 'onMount', e, props) @@ -254,7 +221,6 @@ const Popup = React.forwardRef(function (props, ref) { ) : ( children )} - {hideOnScroll && } ) @@ -276,7 +242,7 @@ const Popup = React.forwardRef(function (props, ref) { }) } - if (closed || disabled) { + if (disabled) { return trigger } @@ -335,6 +301,7 @@ const Popup = React.forwardRef(function (props, ref) { onUnmount={handlePortalUnmount} trigger={trigger} triggerRef={triggerRef} + hideOnScroll={hideOnScroll} >