diff --git a/src/addons/Portal/Portal.js b/src/addons/Portal/Portal.js index 01cbb2808d..076113b13b 100644 --- a/src/addons/Portal/Portal.js +++ b/src/addons/Portal/Portal.js @@ -1,5 +1,4 @@ import EventStack from '@semantic-ui-react/event-stack' -import keyboardKey from 'keyboard-key' import _ from 'lodash' import PropTypes from 'prop-types' import * as React from 'react' @@ -132,18 +131,6 @@ function Portal(props) { } } - const handleEscape = (e) => { - if (!closeOnEscape) { - return - } - if (keyboardKey.getCode(e) !== keyboardKey.Escape) { - return - } - - debug('handleEscape()') - closePortal(e) - } - // ---------------------------------------- // Component Event Handlers // ---------------------------------------- @@ -277,6 +264,8 @@ function Portal(props) { onMount={() => _.invoke(props, 'onMount', null, props)} onUnmount={() => _.invoke(props, 'onUnmount', null, props)} ref={contentRef} + onClose={closePortal} + closeOnEscape={closeOnEscape} > {children} @@ -295,7 +284,6 @@ function Portal(props) { /> - )} {trigger && diff --git a/src/addons/Portal/PortalInner.d.ts b/src/addons/Portal/PortalInner.d.ts index 518b2ffe6a..017c6e92c3 100644 --- a/src/addons/Portal/PortalInner.d.ts +++ b/src/addons/Portal/PortalInner.d.ts @@ -26,6 +26,12 @@ export interface StrictPortalInnerProps { * @param {object} data - All props. */ onUnmount?: (nothing: null, data: PortalInnerProps) => void + + /** Callback called when inner component decides that (respecting the configuration) Portal should close */ + onClose: (event: React.MouseEvent) => void + + /** Controls whether the onClose callback should be invoked when escape is pressed. */ + closeOnEscape: boolean } declare const PortalInner: React.FC diff --git a/src/addons/Portal/PortalInner.js b/src/addons/Portal/PortalInner.js index 854818f379..d004b9a75c 100644 --- a/src/addons/Portal/PortalInner.js +++ b/src/addons/Portal/PortalInner.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types' import * as React from 'react' import { createPortal } from 'react-dom' +import keyboardKey from 'keyboard-key' import { isBrowser, makeDebugger, useEventCallback } from '../../lib' import usePortalElement from './usePortalElement' @@ -12,6 +13,7 @@ const debug = makeDebugger('PortalInner') * An inner component that allows you to render children outside their parent. */ const PortalInner = React.forwardRef(function (props, ref) { + const { closeOnEscape, onClose } = props const handleMount = useEventCallback(() => _.invoke(props, 'onMount', null, props)) const handleUnmount = useEventCallback(() => _.invoke(props, 'onUnmount', null, props)) @@ -27,6 +29,28 @@ const PortalInner = React.forwardRef(function (props, ref) { } }, []) + React.useEffect(() => { + if (!closeOnEscape) { + return + } + + /** + * @param {React.KeyboardEvent} e + */ + const handleKeyDown = (e) => { + if (keyboardKey.getCode(e) !== keyboardKey.Escape) { + return + } + debug('handleEscape()') + onClose(e) + } + + window.addEventListener('keydown', handleKeyDown) + return () => { + window.removeEventListener('keydown', handleKeyDown) + } + }, [closeOnEscape, onClose]) + if (!isBrowser()) { return null } @@ -57,6 +81,12 @@ PortalInner.propTypes = { * @param {object} data - All props. */ onUnmount: PropTypes.func, + + /** Callback called when inner component decides that (respecting the configuration) Portal should close */ + onClose: PropTypes.func.isRequired, + + /** Controls whether the portal should close when escape is pressed is displayed. */ + closeOnEscape: PropTypes.bool.isRequired, } export default PortalInner diff --git a/test/specs/addons/Portal/PortalInner-test.js b/test/specs/addons/Portal/PortalInner-test.js index 2eadf22b2f..e9ddd588f2 100644 --- a/test/specs/addons/Portal/PortalInner-test.js +++ b/test/specs/addons/Portal/PortalInner-test.js @@ -6,10 +6,12 @@ import { isBrowser } from 'src/lib' import * as common from 'test/specs/commonTests' import { sandbox } from 'test/utils' +const doNothing = () => {} + describe('PortalInner', () => { common.isConformant(PortalInner, { rendersChildren: false, - requiredProps: { children:

}, + requiredProps: { children:

, closeOnEscape: false, onClose: doNothing }, forwardsRef: false, }) @@ -24,7 +26,7 @@ describe('PortalInner', () => { it('renders `null` when during Server-Side Rendering', () => { mount( - +

, ).should.be.blank() @@ -37,7 +39,7 @@ describe('PortalInner', () => { const elementRef = React.createRef() const wrapper = mount( - +

, ) @@ -57,7 +59,7 @@ describe('PortalInner', () => { const elementRef = React.createRef() const wrapper = mount( - + , ) @@ -75,7 +77,7 @@ describe('PortalInner', () => { const portalRef = React.createRef() const wrapper = mount( - + , ) @@ -91,7 +93,7 @@ describe('PortalInner', () => { it('called when mounting', () => { const onMount = sandbox.spy() mount( - +

, ) @@ -104,7 +106,7 @@ describe('PortalInner', () => { it('is called only once when unmounting', () => { const onUnmount = sandbox.spy() const wrapper = mount( - +

, )