Skip to content

Commit f970ed0

Browse files
yesmeckbenjycui
authored andcommitted
feat: support React 16 (#69)
1 parent 010d15d commit f970ed0

File tree

4 files changed

+119
-32
lines changed

4 files changed

+119
-32
lines changed

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,15 @@
4343
},
4444
"devDependencies": {
4545
"async": "~1.5.0",
46+
"core-js": "^2.5.1",
4647
"expect.js": "0.3.x",
4748
"jquery": "~1.11.3",
4849
"object-assign": "~4.0.1",
4950
"pre-commit": "1.x",
50-
"rc-tools": "6.x",
5151
"rc-test": "6.x",
52-
"react": "15.x",
53-
"react-dom": "15.x"
52+
"rc-tools": "6.x",
53+
"react": "^16.0.0-rc.3",
54+
"react-dom": "^16.0.0-rc.3"
5455
},
5556
"pre-commit": [
5657
"lint"

src/Portal.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import { createPortal } from 'react-dom';
4+
5+
export default class Portal extends React.Component {
6+
static propTypes = {
7+
getContainer: PropTypes.func.isRequired,
8+
children: PropTypes.node.isRequired,
9+
}
10+
11+
componentDidMount() {
12+
this.createContainer();
13+
}
14+
15+
componentWillUnmount() {
16+
this.removeContainer();
17+
}
18+
19+
createContainer() {
20+
this._container = this.props.getContainer();
21+
this.forceUpdate();
22+
}
23+
24+
removeContainer() {
25+
if (this._container) {
26+
this._container.parentNode.removeChild(this._container);
27+
}
28+
}
29+
30+
render() {
31+
if (this._container) {
32+
return createPortal(this.props.children, this._container);
33+
}
34+
return null;
35+
}
36+
}

src/index.js

Lines changed: 77 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import React from 'react';
22
import PropTypes from 'prop-types';
3-
import { findDOMNode } from 'react-dom';
3+
import { findDOMNode, createPortal } from 'react-dom';
44
import createReactClass from 'create-react-class';
55
import contains from 'rc-util/lib/Dom/contains';
66
import addEventListener from 'rc-util/lib/Dom/addEventListener';
77
import Popup from './Popup';
88
import { getAlignFromPlacement, getPopupClassNameFromAlign } from './utils';
99
import getContainerRenderMixin from 'rc-util/lib/getContainerRenderMixin';
10+
import Portal from './Portal';
1011

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

26+
const IS_REACT_16 = !!createPortal;
27+
28+
const mixins = [];
29+
30+
if (!IS_REACT_16) {
31+
mixins.push(
32+
getContainerRenderMixin({
33+
autoMount: false,
34+
35+
isVisible(instance) {
36+
return instance.state.popupVisible;
37+
},
38+
39+
getContainer(instance) {
40+
return instance.getContainer();
41+
},
42+
})
43+
);
44+
}
45+
2546
const Trigger = createReactClass({
2647
displayName: 'Trigger',
2748
propTypes: {
@@ -66,30 +87,7 @@ const Trigger = createReactClass({
6687
maskAnimation: PropTypes.string,
6788
},
6889

69-
mixins: [
70-
getContainerRenderMixin({
71-
autoMount: false,
72-
73-
isVisible(instance) {
74-
return instance.state.popupVisible;
75-
},
76-
77-
getContainer(instance) {
78-
const { props } = instance;
79-
const popupContainer = document.createElement('div');
80-
// Make sure default popup container will never cause scrollbar appearing
81-
// https://github.com/react-component/trigger/issues/41
82-
popupContainer.style.position = 'absolute';
83-
popupContainer.style.top = '0';
84-
popupContainer.style.left = '0';
85-
popupContainer.style.width = '100%';
86-
const mountNode = props.getPopupContainer ?
87-
props.getPopupContainer(findDOMNode(instance)) : props.getDocument().body;
88-
mountNode.appendChild(popupContainer);
89-
return popupContainer;
90-
},
91-
}),
92-
],
90+
mixins,
9391

9492
getDefaultProps() {
9593
return {
@@ -154,11 +152,16 @@ const Trigger = createReactClass({
154152
componentDidUpdate(_, prevState) {
155153
const props = this.props;
156154
const state = this.state;
157-
this.renderComponent(null, () => {
155+
const triggerAfterPopupVisibleChange = () => {
158156
if (prevState.popupVisible !== state.popupVisible) {
159157
props.afterPopupVisibleChange(state.popupVisible);
160158
}
161-
});
159+
};
160+
if (!IS_REACT_16) {
161+
this.renderComponent(null, triggerAfterPopupVisibleChange);
162+
} else {
163+
triggerAfterPopupVisibleChange();
164+
}
162165

163166
// We must listen to `mousedown` or `touchstart`, edge case:
164167
// https://github.com/ant-design/ant-design/issues/5804
@@ -342,12 +345,28 @@ const Trigger = createReactClass({
342345
transitionName={props.popupTransitionName}
343346
maskAnimation={props.maskAnimation}
344347
maskTransitionName={props.maskTransitionName}
348+
ref={this.savePopup}
345349
>
346350
{typeof props.popup === 'function' ? props.popup() : props.popup}
347351
</Popup>
348352
);
349353
},
350354

355+
getContainer() {
356+
const { props } = this;
357+
const popupContainer = document.createElement('div');
358+
// Make sure default popup container will never cause scrollbar appearing
359+
// https://github.com/react-component/trigger/issues/41
360+
popupContainer.style.position = 'absolute';
361+
popupContainer.style.top = '0';
362+
popupContainer.style.left = '0';
363+
popupContainer.style.width = '100%';
364+
const mountNode = props.getPopupContainer ?
365+
props.getPopupContainer(findDOMNode(this)) : props.getDocument().body;
366+
mountNode.appendChild(popupContainer);
367+
return popupContainer;
368+
},
369+
351370
setPopupVisible(popupVisible) {
352371
this.clearDelayTimer();
353372
if (this.state.popupVisible !== popupVisible) {
@@ -451,11 +470,18 @@ const Trigger = createReactClass({
451470
this.setPopupVisible(false);
452471
},
453472

473+
savePopup(node) {
474+
if (IS_REACT_16) {
475+
this._component = node;
476+
}
477+
},
478+
454479
render() {
480+
const { popupVisible } = this.state;
455481
const props = this.props;
456482
const children = props.children;
457483
const child = React.Children.only(children);
458-
const newChildProps = {};
484+
const newChildProps = { key: 'trigger' };
459485
if (this.isClickToHide() || this.isClickToShow()) {
460486
newChildProps.onClick = this.onClick;
461487
newChildProps.onMouseDown = this.onMouseDown;
@@ -483,7 +509,29 @@ const Trigger = createReactClass({
483509
newChildProps.onBlur = this.createTwoChains('onBlur');
484510
}
485511

486-
return React.cloneElement(child, newChildProps);
512+
const trigger = React.cloneElement(child, newChildProps);
513+
514+
if (!IS_REACT_16) {
515+
return trigger;
516+
}
517+
518+
let portal;
519+
// prevent unmounting after it's rendered
520+
if (popupVisible || this._component) {
521+
portal = (
522+
<Portal
523+
key="portal"
524+
getContainer={this.getContainer}
525+
>
526+
{this.getComponent()}
527+
</Portal>
528+
);
529+
}
530+
531+
return [
532+
trigger,
533+
portal,
534+
];
487535
},
488536
});
489537

tests/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
/* eslint no-console:0 */
22

3+
import 'core-js/es6/map';
4+
import 'core-js/es6/set';
35
import expect from 'expect.js';
46
import React from 'react';
57
import ReactDOM from 'react-dom';

0 commit comments

Comments
 (0)