Skip to content

Support React 16 #69

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 25, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,15 @@
},
"devDependencies": {
"async": "~1.5.0",
"core-js": "^2.5.1",
"expect.js": "0.3.x",
"jquery": "~1.11.3",
"object-assign": "~4.0.1",
"pre-commit": "1.x",
"rc-tools": "6.x",
"rc-test": "6.x",
"react": "15.x",
"react-dom": "15.x"
"rc-tools": "6.x",
"react": "^16.0.0-rc.3",
"react-dom": "^16.0.0-rc.3"
},
"pre-commit": [
"lint"
Expand Down
36 changes: 36 additions & 0 deletions src/Portal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from 'react';
import PropTypes from 'prop-types';
import { createPortal } from 'react-dom';

export default class Portal extends React.Component {
static propTypes = {
getContainer: PropTypes.func.isRequired,
children: PropTypes.node.isRequired,
}

componentDidMount() {
this.createContainer();
}

componentWillUnmount() {
this.removeContainer();
}

createContainer() {
this._container = this.props.getContainer();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@afc163 FYI,getPopupContainer 之类的 API,其实只会调用一次,并且之前一直就是这样。考虑到有 removeContainer 的需要,getPopupContainer 的确只能调用一次。

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

没懂,有啥问题。

如果父元素 dom 结构变了,可能需要调用多次吧。

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

不缓存 getPopupContainer 的返回值其实也可以,只是每次 render 都需要对比当前返回值和之前的返回值,并相应的 mount unmount 节点。

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

并且这样就要求用户的 getPopupContainer 必须稳定,不能出现以下的情况:

getPopupContainer() {
  const div = new div
  document.appendChild(div);
  return div;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这样就是 breaking changes 了。

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个先保持原有的行为吧。

this.forceUpdate();
}

removeContainer() {
if (this._container) {
this._container.parentNode.removeChild(this._container);
}
}

render() {
if (this._container) {
return createPortal(this.props.children, this._container);
}
return null;
}
}
106 changes: 77 additions & 29 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import React from 'react';
import PropTypes from 'prop-types';
import { findDOMNode } from 'react-dom';
import { findDOMNode, createPortal } from 'react-dom';
import createReactClass from 'create-react-class';
import contains from 'rc-util/lib/Dom/contains';
import addEventListener from 'rc-util/lib/Dom/addEventListener';
import Popup from './Popup';
import { getAlignFromPlacement, getPopupClassNameFromAlign } from './utils';
import getContainerRenderMixin from 'rc-util/lib/getContainerRenderMixin';
import Portal from './Portal';

function noop() {
}
Expand All @@ -22,6 +23,26 @@ function returnDocument() {
const ALL_HANDLERS = ['onClick', 'onMouseDown', 'onTouchStart', 'onMouseEnter',
'onMouseLeave', 'onFocus', 'onBlur'];

const IS_REACT_16 = !!createPortal;

const mixins = [];

if (!IS_REACT_16) {
mixins.push(
getContainerRenderMixin({
autoMount: false,

isVisible(instance) {
return instance.state.popupVisible;
},

getContainer(instance) {
return instance.getContainer();
},
})
);
}

const Trigger = createReactClass({
displayName: 'Trigger',
propTypes: {
Expand Down Expand Up @@ -66,30 +87,7 @@ const Trigger = createReactClass({
maskAnimation: PropTypes.string,
},

mixins: [
getContainerRenderMixin({
autoMount: false,

isVisible(instance) {
return instance.state.popupVisible;
},

getContainer(instance) {
const { props } = instance;
const popupContainer = document.createElement('div');
// Make sure default popup container will never cause scrollbar appearing
// https://github.com/react-component/trigger/issues/41
popupContainer.style.position = 'absolute';
popupContainer.style.top = '0';
popupContainer.style.left = '0';
popupContainer.style.width = '100%';
const mountNode = props.getPopupContainer ?
props.getPopupContainer(findDOMNode(instance)) : props.getDocument().body;
mountNode.appendChild(popupContainer);
return popupContainer;
},
}),
],
mixins,

getDefaultProps() {
return {
Expand Down Expand Up @@ -154,11 +152,16 @@ const Trigger = createReactClass({
componentDidUpdate(_, prevState) {
const props = this.props;
const state = this.state;
this.renderComponent(null, () => {
const triggerAfterPopupVisibleChange = () => {
if (prevState.popupVisible !== state.popupVisible) {
props.afterPopupVisibleChange(state.popupVisible);
}
});
};
if (!IS_REACT_16) {
this.renderComponent(null, triggerAfterPopupVisibleChange);
} else {
triggerAfterPopupVisibleChange();
}

// We must listen to `mousedown` or `touchstart`, edge case:
// https://github.com/ant-design/ant-design/issues/5804
Expand Down Expand Up @@ -341,12 +344,28 @@ const Trigger = createReactClass({
transitionName={props.popupTransitionName}
maskAnimation={props.maskAnimation}
maskTransitionName={props.maskTransitionName}
ref={this.savePopup}
>
{typeof props.popup === 'function' ? props.popup() : props.popup}
</Popup>
);
},

getContainer() {
const { props } = this;
const popupContainer = document.createElement('div');
// Make sure default popup container will never cause scrollbar appearing
// https://github.com/react-component/trigger/issues/41
popupContainer.style.position = 'absolute';
popupContainer.style.top = '0';
popupContainer.style.left = '0';
popupContainer.style.width = '100%';
const mountNode = props.getPopupContainer ?
props.getPopupContainer(findDOMNode(this)) : props.getDocument().body;
mountNode.appendChild(popupContainer);
return popupContainer;
},

setPopupVisible(popupVisible) {
this.clearDelayTimer();
if (this.state.popupVisible !== popupVisible) {
Expand Down Expand Up @@ -450,11 +469,18 @@ const Trigger = createReactClass({
this.setPopupVisible(false);
},

savePopup(node) {
if (IS_REACT_16) {
this._component = node;
}
},

render() {
const { popupVisible } = this.state;
const props = this.props;
const children = props.children;
const child = React.Children.only(children);
const newChildProps = {};
const newChildProps = { key: 'trigger' };
if (this.isClickToHide() || this.isClickToShow()) {
newChildProps.onClick = this.onClick;
newChildProps.onMouseDown = this.onMouseDown;
Expand Down Expand Up @@ -482,7 +508,29 @@ const Trigger = createReactClass({
newChildProps.onBlur = this.createTwoChains('onBlur');
}

return React.cloneElement(child, newChildProps);
const trigger = React.cloneElement(child, newChildProps);

if (!IS_REACT_16) {
return trigger;
}

let portal;
// prevent unmounting after it's rendered
if (popupVisible || this._component) {
portal = (
<Portal
key="portal"
getContainer={this.getContainer}
>
{this.getComponent()}
</Portal>
);
}

return [
trigger,
portal,
];
},
});

Expand Down
2 changes: 2 additions & 0 deletions tests/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
/* eslint no-console:0 */

import 'core-js/es6/map';
import 'core-js/es6/set';
import expect from 'expect.js';
import React from 'react';
import ReactDOM from 'react-dom';
Expand Down