Skip to content

Commit 80a60c9

Browse files
dominikdosoudilDominik Dosoudil
andauthored
chore(Popup): replace event-stack (#4499)
* refactor(event-stack): replace event-stack with window.addEventListener in Popup * refactor(popup): Move hideOnScroll feature to Portal component to get rid of hacky timout that causes remounting Portal component. * perf(portal): wrap closePortal in useEventCallback to prevent scroll event resubscribed every render * fix(portal): Replace AbortController with removeEventListener to support older browsers --------- Co-authored-by: Dominik Dosoudil <[email protected]>
1 parent d70adfc commit 80a60c9

File tree

3 files changed

+41
-42
lines changed

3 files changed

+41
-42
lines changed

src/addons/Portal/Portal.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ export interface StrictPortalProps {
3737
/** Event pool namespace that is used to handle component events. */
3838
eventPool?: string
3939

40+
/** Hide the Popup when scrolling the window. */
41+
hideOnScroll?: boolean
42+
4043
/** The node where the portal should mount. */
4144
mountNode?: any
4245

src/addons/Portal/Portal.js

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
doesNodeContainClick,
1010
makeDebugger,
1111
useAutoControlledValue,
12+
useEventCallback,
1213
} from '../../lib'
1314
import useTrigger from './utils/useTrigger'
1415
import PortalInner from './PortalInner'
@@ -38,6 +39,7 @@ function Portal(props) {
3839
openOnTriggerClick = true,
3940
openOnTriggerFocus,
4041
openOnTriggerMouseEnter,
42+
hideOnScroll = false,
4143
} = props
4244

4345
const [open, setOpen] = useAutoControlledValue({
@@ -73,12 +75,12 @@ function Portal(props) {
7375
return setTimeout(() => openPortal(eventClone), delay || 0)
7476
}
7577

76-
const closePortal = (e) => {
78+
const closePortal = useEventCallback((e) => {
7779
debug('close()')
7880

7981
setOpen(false)
8082
_.invoke(props, 'onClose', e, { ...props, open: false })
81-
}
83+
})
8284

8385
const closePortalWithTimeout = (e, delay) => {
8486
debug('closeWithTimeout()', delay)
@@ -146,6 +148,30 @@ function Portal(props) {
146148
// Component Event Handlers
147149
// ----------------------------------------
148150

151+
React.useEffect(() => {
152+
if (!hideOnScroll) {
153+
return
154+
}
155+
156+
const handleScroll = (e) => {
157+
debug('handleHideOnScroll()')
158+
159+
// Do not hide the popup when scroll comes from inside the popup
160+
// https://github.com/Semantic-Org/Semantic-UI-React/issues/4305
161+
if (_.isElement(e.target) && contentRef.current.contains(e.target)) {
162+
return
163+
}
164+
165+
closePortal(e)
166+
}
167+
168+
window.addEventListener('scroll', handleScroll, { passive: true })
169+
170+
return () => {
171+
window.removeEventListener('scroll', handleScroll)
172+
}
173+
}, [closePortal, hideOnScroll])
174+
149175
const handlePortalMouseLeave = (e) => {
150176
if (!closeOnPortalMouseLeave) {
151177
return
@@ -318,6 +344,9 @@ Portal.propTypes = {
318344
/** Event pool namespace that is used to handle component events */
319345
eventPool: PropTypes.string,
320346

347+
/** Hide the Popup when scrolling the window. */
348+
hideOnScroll: PropTypes.bool,
349+
321350
/** The node where the portal should mount. */
322351
mountNode: PropTypes.any,
323352

src/modules/Popup/Popup.js

Lines changed: 7 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import EventStack from '@semantic-ui-react/event-stack'
21
import cx from 'clsx'
32
import _ from 'lodash'
43
import PropTypes from 'prop-types'
@@ -14,9 +13,9 @@ import {
1413
getUnhandledProps,
1514
makeDebugger,
1615
SUI,
16+
useIsomorphicLayoutEffect,
1717
useKeyOnly,
1818
useKeyOrValueAndKey,
19-
useIsomorphicLayoutEffect,
2019
useMergedRefs,
2120
usePrevious,
2221
} from '../../lib'
@@ -72,11 +71,10 @@ function getPortalProps(props) {
7271
* Splits props for Portal & Popup.
7372
*
7473
* @param {Object} unhandledProps
75-
* @param {Boolean} closed
7674
* @param {Boolean} disabled
7775
*/
78-
function partitionPortalProps(unhandledProps, closed, disabled) {
79-
if (closed || disabled) {
76+
function partitionPortalProps(unhandledProps, disabled) {
77+
if (disabled) {
8078
return {}
8179
}
8280

@@ -124,7 +122,7 @@ const Popup = React.forwardRef(function (props, ref) {
124122
eventsEnabled = true,
125123
flowing,
126124
header,
127-
hideOnScroll,
125+
hideOnScroll = false,
128126
inverted,
129127
offset,
130128
pinned = false,
@@ -139,18 +137,11 @@ const Popup = React.forwardRef(function (props, ref) {
139137
wide,
140138
} = props
141139

142-
const [closed, setClosed] = React.useState(false)
143-
144140
const unhandledProps = getUnhandledProps(Popup, props)
145-
const { contentRestProps, portalRestProps } = partitionPortalProps(
146-
unhandledProps,
147-
closed,
148-
disabled,
149-
)
141+
const { contentRestProps, portalRestProps } = partitionPortalProps(unhandledProps, disabled)
150142

151143
const elementRef = useMergedRefs(ref)
152144
const positionUpdate = React.useRef()
153-
const timeoutId = React.useRef()
154145
const triggerRef = React.useRef()
155146
const zIndexWasSynced = React.useRef(false)
156147

@@ -160,12 +151,6 @@ const Popup = React.forwardRef(function (props, ref) {
160151

161152
usePositioningEffect(popperDependencies, positionUpdate)
162153

163-
React.useEffect(() => {
164-
return () => {
165-
clearTimeout(timeoutId.current)
166-
}
167-
}, [])
168-
169154
// ----------------------------------------
170155
// Handlers
171156
// ----------------------------------------
@@ -180,24 +165,6 @@ const Popup = React.forwardRef(function (props, ref) {
180165
_.invoke(props, 'onOpen', e, { ...props, open: true })
181166
}
182167

183-
const handleHideOnScroll = (e) => {
184-
debug('handleHideOnScroll()')
185-
186-
// Do not hide the popup when scroll comes from inside the popup
187-
// https://github.com/Semantic-Org/Semantic-UI-React/issues/4305
188-
if (_.isElement(e.target) && elementRef.current.contains(e.target)) {
189-
return
190-
}
191-
192-
setClosed(true)
193-
194-
timeoutId.current = setTimeout(() => {
195-
setClosed(false)
196-
}, 50)
197-
198-
handleClose(e)
199-
}
200-
201168
const handlePortalMount = (e) => {
202169
debug('handlePortalMount()')
203170
_.invoke(props, 'onMount', e, props)
@@ -254,7 +221,6 @@ const Popup = React.forwardRef(function (props, ref) {
254221
) : (
255222
children
256223
)}
257-
{hideOnScroll && <EventStack on={handleHideOnScroll} name='scroll' target='window' />}
258224
</ElementType>
259225
)
260226

@@ -276,7 +242,7 @@ const Popup = React.forwardRef(function (props, ref) {
276242
})
277243
}
278244

279-
if (closed || disabled) {
245+
if (disabled) {
280246
return trigger
281247
}
282248

@@ -335,6 +301,7 @@ const Popup = React.forwardRef(function (props, ref) {
335301
onUnmount={handlePortalUnmount}
336302
trigger={trigger}
337303
triggerRef={triggerRef}
304+
hideOnScroll={hideOnScroll}
338305
>
339306
<Popper
340307
modifiers={modifiers}

0 commit comments

Comments
 (0)