diff --git a/gulp/plugins/util/getComponentInfo.js b/gulp/plugins/util/getComponentInfo.js index 07c2dabeb5..b038c84a42 100644 --- a/gulp/plugins/util/getComponentInfo.js +++ b/gulp/plugins/util/getComponentInfo.js @@ -23,10 +23,12 @@ const getComponentInfo = (filepath) => { const componentType = path.basename(path.dirname(dir)).replace(/s$/, '') // start with react-docgen info - const components = parse(contents, resolver.findAllComponentDefinitions, [ - ...defaultHandlers, - parserCustomHandler, - ]) + const components = parse( + contents, + resolver.findAllComponentDefinitions, + [...defaultHandlers, parserCustomHandler], + { filename: filepath }, + ) if (!components.length) { throw new Error(`Could not find a component definition in "${filepath}".`) } diff --git a/package.json b/package.json index 076373a5c4..e54b9f25d2 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,8 @@ "dependencies": { "@babel/runtime": "^7.1.2", "@semantic-ui-react/event-stack": "^3.1.0", + "@stardust-ui/react-component-event-listener": "~0.35.0", + "@stardust-ui/react-component-nesting-registry": "~0.35.0", "classnames": "^2.2.6", "keyboard-key": "^1.0.4", "lodash": "^4.17.15", @@ -152,7 +154,7 @@ "react": "^16.8.6", "react-ace": "^6.4.0", "react-codesandboxer": "^3.1.3", - "react-docgen": "^4.1.0", + "react-docgen": "^4.1.1", "react-dom": "^16.8.6", "react-hot-loader": "^4.12.10", "react-router": "^5.0.0", diff --git a/src/addons/Portal/Portal.js b/src/addons/Portal/Portal.js index 99644d1b84..665d8205d3 100644 --- a/src/addons/Portal/Portal.js +++ b/src/addons/Portal/Portal.js @@ -1,4 +1,3 @@ -import EventStack from '@semantic-ui-react/event-stack' import keyboardKey from 'keyboard-key' import _ from 'lodash' import PropTypes from 'prop-types' @@ -53,9 +52,6 @@ class Portal extends Component { /** Initial value of open. */ defaultOpen: PropTypes.bool, - /** Event pool namespace that is used to handle component events */ - eventPool: PropTypes.string, - /** The node where the portal should mount. */ mountNode: PropTypes.any, @@ -119,7 +115,6 @@ class Portal extends Component { static defaultProps = { closeOnDocumentClick: true, closeOnEscape: true, - eventPool: 'default', openOnTriggerClick: true, } @@ -166,11 +161,12 @@ class Portal extends Component { } } - handleEscape = (e) => { + handleDocumentKeyDown = (e) => { if (!this.props.closeOnEscape) return if (keyboardKey.getCode(e) !== keyboardKey.Escape) return - debug('handleEscape()') + debug('handleDocumentKeyDown()') + e.stopPropagation() this.close(e) } @@ -333,38 +329,25 @@ class Portal extends Component { } render() { - const { children, eventPool, mountNode, trigger } = this.props + const { children, mountNode, trigger } = this.props const { open } = this.state return ( {open && ( - - - {children} - - - - - - - - + + {children} + )} {trigger && ( diff --git a/src/addons/Portal/PortalInner.js b/src/addons/Portal/PortalInner.js index 99e29e3345..cecf2b435e 100644 --- a/src/addons/Portal/PortalInner.js +++ b/src/addons/Portal/PortalInner.js @@ -1,65 +1,128 @@ +import { documentRef } from '@stardust-ui/react-component-event-listener' +import useEventListener from '@stardust-ui/react-component-event-listener/dist/commonjs/hooks/useEventListener' import _ from 'lodash' import PropTypes from 'prop-types' -import React, { Component } from 'react' +import React from 'react' import { createPortal } from 'react-dom' -import { customPropTypes, handleRef, isBrowser, makeDebugger } from '../../lib' +import { customPropTypes, handleRef } from '../../lib' import Ref from '../Ref' -const debug = makeDebugger('portalInner') - /** * An inner component that allows you to render children outside their parent. */ -class PortalInner extends Component { - static propTypes = { - /** Primary content. */ - children: PropTypes.node.isRequired, - - /** Called with a ref to the inner node. */ - innerRef: customPropTypes.ref, - - /** The node where the portal should mount. */ - mountNode: PropTypes.any, - - /** - * Called when the portal is mounted on the DOM - * - * @param {null} - * @param {object} data - All props. - */ - onMount: PropTypes.func, - - /** - * Called when the portal is unmounted from the DOM - * - * @param {null} - * @param {object} data - All props. - */ - onUnmount: PropTypes.func, - } - - componentDidMount() { - debug('componentDidMount()') - _.invoke(this.props, 'onMount', null, this.props) - } - - componentWillUnmount() { - debug('componentWillUnmount()') - _.invoke(this.props, 'onUnmount', null, this.props) - } - - handleRef = (c) => { - debug('handleRef', c) - handleRef(this.props.innerRef, c) - } - - render() { - if (!isBrowser()) return null - const { children, mountNode = document.body } = this.props - - return createPortal({children}, mountNode) - } +function PortalInner(props) { + const { children, innerRef, mountNode = document.body } = props + + const contentRef = React.useRef() + const handleContentRef = React.useCallback( + (c) => { + contentRef.current = c + handleRef(innerRef, c) + }, + [innerRef], + ) + React.useLayoutEffect(() => { + _.invoke(props, 'onMount', null, props) + + return () => { + _.invoke(props, 'onUnmount', null, props) + } + }, []) + + useEventListener({ + listener: props.onMouseLeave, + type: 'mouseleave', + targetRef: contentRef, + }) + useEventListener({ + listener: props.onMouseEnter, + targetRef: contentRef, + type: 'mouseenter', + }) + useEventListener({ + listener: props.onDocumentClick, + type: 'click', + targetRef: documentRef, + }) + useEventListener({ + listener: props.onDocumentKeyDown, + targetRef: documentRef, + type: 'keydown', + }) + useEventListener({ + listener: props.onDocumentMouseDown, + targetRef: documentRef, + type: 'mousedown', + }) + + return ( + + {createPortal({children}, mountNode)} + + ) +} + +PortalInner.propTypes = { + /** Primary content. */ + children: PropTypes.node.isRequired, + + /** Called with a ref to the inner node. */ + innerRef: customPropTypes.ref, + + /** The node where the portal should mount. */ + mountNode: PropTypes.any, + + /** + * Called on a document click. + * + * @param {MouseEvent} event + */ + onDocumentClick: PropTypes.func, + + /** + * Called on document key down. + * + * @param {KeyboardEvent} event + */ + onDocumentKeyDown: PropTypes.func, + + /** + * Called on a document mouse down. + * + * @param {MouseEvent} event + */ + onDocumentMouseDown: PropTypes.func, + + /** + * Called when the portal is mounted on the DOM. + * + * @param {null} + * @param {object} data - All props. + */ + onMount: PropTypes.func, + + /** + * Called when mouse leaves the portal. + * + * @param {MouseEvent} event + */ + onMouseLeave: PropTypes.func, + + /** + * Called when mouse enters the portal. + * + * @param {MouseEvent} event + */ + onMouseEnter: PropTypes.func, + + /** + * Called when the portal is unmounted from the DOM. + * + * @param {null} + * @param {object} data - All props. + */ + onUnmount: PropTypes.func, } export default PortalInner diff --git a/src/modules/Dropdown/Dropdown.js b/src/modules/Dropdown/Dropdown.js index 854cf37bb4..33f680d0cd 100644 --- a/src/modules/Dropdown/Dropdown.js +++ b/src/modules/Dropdown/Dropdown.js @@ -504,6 +504,7 @@ export default class Dropdown extends Component { if (!this.props.closeOnEscape) return if (keyboardKey.getCode(e) !== keyboardKey.Escape) return e.preventDefault() + e.stopPropagation() debug('closeOnEscape()') this.close(e) diff --git a/src/modules/Modal/Modal.js b/src/modules/Modal/Modal.js index 94f6936526..56a6132664 100644 --- a/src/modules/Modal/Modal.js +++ b/src/modules/Modal/Modal.js @@ -1,3 +1,4 @@ +import { EventListener } from '@stardust-ui/react-component-event-listener' import cx from 'classnames' import _ from 'lodash' import PropTypes from 'prop-types' @@ -9,7 +10,6 @@ import { childrenUtils, customPropTypes, doesNodeContainClick, - eventStack, getElementType, getUnhandledProps, isBrowser, @@ -71,9 +71,6 @@ class Modal extends Component { /** A Modal can appear in a dimmer. */ dimmer: PropTypes.oneOf([true, 'inverted', 'blurring']), - /** Event pool namespace that is used to handle component events */ - eventPool: PropTypes.string, - /** Modal displayed above the content in bold. */ header: customPropTypes.itemShorthand, @@ -143,7 +140,6 @@ class Modal extends Component { dimmer: true, closeOnDimmerClick: true, closeOnDocumentClick: false, - eventPool: 'Modal', } static autoControlledProps = ['open'] @@ -218,36 +214,18 @@ class Modal extends Component { } handlePortalMount = (e) => { - const { eventPool } = this.props - debug('handlePortalMount()', { eventPool }) + debug('handlePortalMount()') this.setState({ scrolling: false }) this.setPositionAndClassNames() - eventStack.sub('mousedown', this.handleDocumentMouseDown, { - pool: eventPool, - target: this.dimmerRef.current, - }) - eventStack.sub('click', this.handleDocumentClick, { - pool: eventPool, - target: this.dimmerRef.current, - }) _.invoke(this.props, 'onMount', e, this.props) } handlePortalUnmount = (e) => { - const { eventPool } = this.props - debug('handlePortalUnmount()', { eventPool }) + debug('handlePortalUnmount()') cancelAnimationFrame(this.animationRequestId) - eventStack.unsub('mousedown', this.handleDocumentMouseDown, { - pool: eventPool, - target: this.dimmerRef.current, - }) - eventStack.unsub('click', this.handleDocumentClick, { - pool: eventPool, - target: this.dimmerRef.current, - }) _.invoke(this.props, 'onUnmount', e, this.props) } @@ -348,7 +326,7 @@ class Modal extends Component { render() { const { open } = this.state - const { centered, closeOnDocumentClick, dimmer, eventPool, trigger } = this.props + const { centered, closeOnDocumentClick, dimmer, trigger } = this.props const mountNode = this.getMountNode() // Short circuit when server side rendering @@ -390,22 +368,38 @@ class Modal extends Component { // We cannot them wrap the modalJSX in an actual instead, we apply the dimmer classes to the . return ( - -
- {this.renderContent(rest)} -
-
+ + +
+ {this.renderContent(rest)} +
+
+ + {open && ( + + )} + {open && ( + + )} +
) } } diff --git a/src/modules/Popup/Popup.js b/src/modules/Popup/Popup.js index 821da136d5..b2f173385b 100644 --- a/src/modules/Popup/Popup.js +++ b/src/modules/Popup/Popup.js @@ -1,4 +1,5 @@ -import EventStack from '@semantic-ui-react/event-stack' +import { EventListener, windowRef } from '@stardust-ui/react-component-event-listener' +import { Unstable_NestingAuto as NestingAuto } from '@stardust-ui/react-component-nesting-registry' import cx from 'classnames' import _ from 'lodash' import PropTypes from 'prop-types' @@ -308,19 +309,31 @@ export default class Popup extends Component { } return ( - - - {childrenUtils.isNil(children) ? ( - - {PopupHeader.create(header, { autoGenerateKey: false })} - {PopupContent.create(content, { autoGenerateKey: false })} - - ) : ( - children - )} - {hideOnScroll && } - - + + {(getRefs, nestingRef) => ( + { + // eslint-disable-next-line no-param-reassign + nestingRef.current = c + popperRef(c) + }} + > + + {childrenUtils.isNil(children) ? ( + + {PopupHeader.create(header, { autoGenerateKey: false })} + {PopupContent.create(content, { autoGenerateKey: false })} + + ) : ( + children + )} + {hideOnScroll && ( + + )} + + + )} + ) } diff --git a/src/modules/Search/Search.js b/src/modules/Search/Search.js index d71205b125..c5a61fa3ec 100644 --- a/src/modules/Search/Search.js +++ b/src/modules/Search/Search.js @@ -292,6 +292,7 @@ export default class Search extends Component { closeOnEscape = (e) => { if (keyboardKey.getCode(e) !== keyboardKey.Escape) return e.preventDefault() + e.stopPropagation() this.close() } diff --git a/yarn.lock b/yarn.lock index 669421ab8f..8050450fe0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -991,6 +991,22 @@ prismjs "^1.16.0" prop-types "^15.6.1" +"@stardust-ui/react-component-event-listener@~0.35.0": + version "0.35.0" + resolved "https://registry.yarnpkg.com/@stardust-ui/react-component-event-listener/-/react-component-event-listener-0.35.0.tgz#29167d7c2f71d4840d671213fa2298219bef5448" + integrity sha512-QK/Krhx8ygvz++cKd0cB0dBe6fk/qrHPYuAQRc/H9YUxV5WVrNcZF/qEiKXTtP+1iOiTc8umpQV6WUrrYlptDw== + dependencies: + "@babel/runtime" "^7.1.2" + prop-types "^15.6.1" + +"@stardust-ui/react-component-nesting-registry@~0.35.0": + version "0.35.0" + resolved "https://registry.yarnpkg.com/@stardust-ui/react-component-nesting-registry/-/react-component-nesting-registry-0.35.0.tgz#6c9e592127cea8b34e93b1e5c1b1ff70fbfcb081" + integrity sha512-ps8lrn2i+kkhZTWR+4oGEDhvRCjN9KUE5XwtRoTQy0+aiuiWIEPcqFBbv5s3Mj3vUB9OoND/VHyzLP25IX+RFw== + dependencies: + "@babel/runtime" "^7.1.2" + prop-types "^15.6.1" + "@textlint/ast-node-types@^4.0.3": version "4.2.1" resolved "https://registry.yarnpkg.com/@textlint/ast-node-types/-/ast-node-types-4.2.1.tgz#978fa10e23468114462fc08ef29f96980c12a8ef" @@ -9968,10 +9984,10 @@ react-dev-utils@^4.0.1: strip-ansi "3.0.1" text-table "0.2.0" -react-docgen@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/react-docgen/-/react-docgen-4.1.0.tgz#218887feba5b2c36af337879a27e74bda90ed7cb" - integrity sha512-yoGY45tD4yPGmnOlJr4mjU6LTYRO1OEny9ZtK8LjnFMqr2M/uRuH88vPba7743oVDFuVNH5bnN2bh6eQX3qXjA== +react-docgen@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/react-docgen/-/react-docgen-4.1.1.tgz#8fef0212dbf14733e09edecef1de6b224d87219e" + integrity sha512-o1wdswIxbgJRI4pckskE7qumiFyqkbvCO++TylEDOo2RbMiueIOg8YzKU4X9++r0DjrbXePw/LHnh81GRBTWRw== dependencies: "@babel/core" "^7.0.0" "@babel/runtime" "^7.0.0"