From 1142b7f7e942d2422a1c89615c866e8a170e3f71 Mon Sep 17 00:00:00 2001 From: Dave Woodward Date: Mon, 31 Jul 2017 13:51:37 -0400 Subject: [PATCH 01/13] Install popper.js directly. --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 1108e62b27..632802d8a7 100644 --- a/package.json +++ b/package.json @@ -144,6 +144,7 @@ "node-sass": "^4.5.0", "object.entries": "^1.0.4", "phantomjs-prebuilt": "^2.1.7", + "popper.js": "^1.10.8", "pre-commit": "^1.2.2", "prompt": "^1.0.0", "react": ">=15.4.1", From c0d78383fa161709c89bcea74eb38553d5a58693 Mon Sep 17 00:00:00 2001 From: Dave Woodward Date: Mon, 31 Jul 2017 13:53:32 -0400 Subject: [PATCH 02/13] WIP of using popper for positioning the Dialog DOM element. --- components/utilities/dialog/index.jsx | 196 +++++++++----------------- 1 file changed, 66 insertions(+), 130 deletions(-) diff --git a/components/utilities/dialog/index.jsx b/components/utilities/dialog/index.jsx index a549cea749..7f52884fb8 100644 --- a/components/utilities/dialog/index.jsx +++ b/components/utilities/dialog/index.jsx @@ -7,6 +7,9 @@ import React from 'react'; import PropTypes from 'prop-types'; import ReactDOM from 'react-dom'; +import Popper from 'popper.js'; +import isEqual from 'lodash.isequal'; + // ### classNames // [github.com/JedWatson/classnames](https://github.com/JedWatson/classnames) // This project uses `classnames`, "a simple javascript utility for conditionally @@ -17,8 +20,6 @@ import classNames from 'classnames'; // Listen for clicks that occur somewhere in the document, outside of the element itself import onClickOutside from 'react-onclickoutside'; -import TetherDrop from 'tether-drop'; - import EventUtil from '../../../utilities/event'; import KEYS from '../../../utilities/key-code'; import DOMElementFocus from '../../../utilities/dom-element-focus'; @@ -200,16 +201,58 @@ const Dialog = React.createClass({ }, componentWillMount () { - this.dialogElement = document.createElement('span'); - document.querySelector('body').appendChild(this.dialogElement); }, componentDidMount () { - this.renderDialog(); + /* Instantiate a _popper instance here */ + this._createPopper(); }, componentDidUpdate () { - this.renderDialog(); + /* Schedule an update to _popper here */ + /* Do something else from popper here maybe? */ + }, + + componentWillUnmount () { + this._destroyPopper(); + }, + + _createPopper () { + const reference = this.target(); + const popper = this.dialogContent; + const placement = 'top'; // one of Popper.placements + const eventsEnabled = true; // a boolean + const modifiers = { + applyStyle: { enabled: false }, + updateState: { + enabled: true, + order: 900, + fn: data => { + if ( + (this.state.data && !isEqual(data.offsets, this.state.data.offsets)) || + !this.state.data + ) { + this.setState({ data }); + } + return data; + } + } + // arrow property can also point to an element + }; + if (!reference || !popper) { + console.error('Target node not found!', reference); + console.error('Popper node not found!', popper); + } + this._popper = new Popper(reference, popper, { + placement, + eventsEnabled, + modifiers + }); + this._popper.scheduleUpdate(); + }, + + _destroyPopper () { + if (this._popper) this._popper.destroy(); }, handleClickOutside () { @@ -243,9 +286,9 @@ const Dialog = React.createClass({ }, renderDialogContents () { - if (!this.state.isOpen) { - return ; - } + // if (!this.state.isOpen) { + // return ; + // } let style = { transform: 'none', @@ -281,98 +324,10 @@ const Dialog = React.createClass({ ); }, - getHorizontalAlign (align) { - if (align.indexOf('left') > -1) { - return 'left'; - } else if (align.indexOf('right') > -1) { - return 'right'; - } - return 'center'; - }, - - getVerticalAlign (align) { - if (align.indexOf('bottom') > -1) { - return 'bottom'; - } else if (align.indexOf('top') > -1) { - return 'top'; - } - return 'middle'; - }, - - isHorizontalAlign (align) { - return ( - align === 'left' || - align === 'right' || - align === 'center' - ); - }, - - isVerticalAlign (align) { - return ( - align === 'bottom' || - align === 'top' || - align === 'middle' - ); - }, - - getPosition () { - if (this.props.align) { - let align = []; - if (this.props.align) { - const splits = this.props.align.split(' '); - if (this.isHorizontalAlign(splits[0])) { - const verticalAlign = splits.length > 1 ? this.getVerticalAlign(splits[1]) : this.getVerticalAlign(''); - align = [ - this.getHorizontalAlign(splits[0]), - verticalAlign - ]; - } else { - const horizontalAlign = splits.length > 1 ? this.getHorizontalAlign(splits[1]) : this.getHorizontalAlign(''); - align = [ - this.getVerticalAlign(splits[0]), - horizontalAlign - ]; - } - } - - return align.join(' '); - } - - const positions = []; - if (this.props.verticalAlign === 'top' || this.props.verticalAlign === 'bottom') { - positions.push(this.props.verticalAlign); - positions.push(this.props.horizontalAlign); - } else { - positions.push(this.props.horizontalAlign); - positions.push(this.props.verticalAlign); - } - - return positions.join(' '); - }, - target () { return this.props.targetElement ? ReactDOM.findDOMNode(this.props.targetElement) : ReactDOM.findDOMNode(this).parentNode; // eslint-disable-line react/no-find-dom-node }, - tetherDropOptions () { - // Please reference http://github.hubspot.com/drop/ for options. - const position = this.getPosition(); - - return { - beforeClose: this.beforeClose, - constrainToWindow: this.props.flippable, - constrainToScrollParent: this.props.constrainToScrollParent, - content: this.dialogElement, - openOn: 'always', - position, - remove: true, - target: this.target(), - tetherOptions: { - offset: this.props.offset - } - }; - }, - handleOpen () { this.setState({ isOpen: true }); @@ -392,52 +347,33 @@ const Dialog = React.createClass({ renderDialog () { // By default ReactDOM is used to create a portal mount on the `body` tag. This can be overridden with the `portalMount` prop. - let mount = ReactDOM.render; + // let mount = ReactDOM.render; if (this.props.portalMount) { mount = this.props.portalMount; } // nextElement, container, callback - this.portal = mount(this.renderDialogContents(), this.dialogElement); - - if (this.dialogElement && - this.dialogElement.parentNode && - this.dialogElement.parentNode.parentNode && - this.dialogElement.parentNode.parentNode.className && - this.dialogElement.parentNode.parentNode.className.indexOf('drop ') > -1) { - this.dialogElement.parentNode.parentNode.style.zIndex = 10001; - } + // this.portal = mount(this.renderDialogContents(), this.dialogElement); + return this.renderDialogContents(); - if (this.drop !== null && this.drop !== undefined) { - if (this.drop && this.drop) { - this.drop.position(); - } - } else if (window && document) { - this.drop = new TetherDrop(this.tetherDropOptions()); - this.drop.once('open', this.handleOpen); - } - }, + // This used to set the zindex of the dialog element to 10001 - componentWillUnmount () { - if (this.props.variant === 'popover') { - DOMElementFocus.teardownScopedFocus(); - DOMElementFocus.returnFocusToStoredElement(); - } - - this.drop.destroy(); - ReactDOM.unmountComponentAtNode(this.dialogElement); + /* Probably position stuff using popper here? */ + }, - if (this.dialogElement.parentNode) { - this.dialogElement.parentNode.removeChild(this.dialogElement); - } + // componentWillUnmount () { + // if (this.props.variant === 'popover') { + // DOMElementFocus.teardownScopedFocus(); + // DOMElementFocus.returnFocusToStoredElement(); + // } - this.handleClose(undefined, { componentWillUnmount: true }); - }, + // this.handleClose(undefined, { componentWillUnmount: true }); + // }, render () { // Must use `` in order for `this.drop` to not be undefined when unmounting - return