Skip to content

Commit b8770b0

Browse files
committed
已修改 src/index.js
已修改 src/style/index.scss Add src/PopupRootComponent.js
1 parent 50bd368 commit b8770b0

File tree

3 files changed

+262
-23
lines changed

3 files changed

+262
-23
lines changed

src/PopupRootComponent.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import React from 'react';
2+
3+
export default class PopupRootContainer extends React.Component {
4+
render() {
5+
6+
const style = {
7+
position: 'absolute',
8+
left: 0,
9+
top: 0,
10+
width: '100%',
11+
}
12+
13+
return (
14+
<div style={style} {...this.props} />
15+
);
16+
}
17+
}

src/index.js

Lines changed: 227 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
11
import React from 'react';
2-
import ReactDOM from 'react-dom';
2+
import ReactDOM, { findDOMNode } from 'react-dom';
33
import PropTypes from 'prop-types';
44
import Popup from 'react-widget-popup';
5+
import Portal from 'react-widget-portal';
56
import getPlacement from 'bplokjs-placement';
67
import Deferred from 'bplokjs-deferred';
8+
import { listen } from 'bplokjs-dom-utils/events';
9+
// import classnames from 'classnames';
10+
// import omit from 'omit.js';
11+
const contains = require('bplokjs-dom-utils/contains')
12+
13+
import PopupRootComponent from './PopupRootComponent';
14+
15+
const isMobile = typeof navigator !== 'undefined' && !!navigator.userAgent.match(
16+
/(Android|iPhone|iPad|iPod|iOS|UCWEB)/i
17+
);
718

819
const propTypes = {
20+
children: PropTypes.any,
921
offset: PropTypes.oneOfType([PropTypes.number, PropTypes.array]),
1022
action: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]),
1123
showAction: PropTypes.any,
@@ -16,6 +28,21 @@ const propTypes = {
1628
PropTypes.number,
1729
PropTypes.object,
1830
]),
31+
getPopupContainer: PropTypes.func,
32+
getDocument: PropTypes.func,
33+
prefixCls: PropTypes.string,
34+
popupClassName: PropTypes.string,
35+
popupMaskClassName: PropTypes.string,
36+
//maskProps: PropTypes.object,
37+
defaultPopupVisible: PropTypes.bool,
38+
popupProps: PropTypes.object,
39+
mask: PropTypes.bool,
40+
maskClosable: PropTypes.bool,
41+
popupRootComponent: PropTypes.any,
42+
destroyPopupOnHide: PropTypes.bool,
43+
popupStyle: PropTypes.object,
44+
popupMaskStyle: PropTypes.object,
45+
zIndex: PropTypes.number,
1946
}
2047

2148
function noop() { }
@@ -34,6 +61,16 @@ export default class Trigger extends React.Component {
3461
hideAction: [],
3562
delay: 0,
3663
onPopupVisibleChange: noop,
64+
getDocument: () => window.document,
65+
prefixCls: "rw-trigger",
66+
mask: false,
67+
maskClosable: true,
68+
destroyPopupOnHide: true,
69+
popupProps: {},
70+
popupRootComponent: PopupRootComponent,
71+
popupStyle: {},
72+
popupMaskStyle: {},
73+
zIndex: null,
3774
}
3875

3976
static getDerivedStateFromProps(props, state) {
@@ -46,16 +83,98 @@ export default class Trigger extends React.Component {
4683
popupVisible: this.props.defaultPopupVisible,
4784
}
4885

86+
_popup = null
87+
4988
delayTimer = null
5089

5190
promise = null
5291

5392
componentDidMount() {
54-
this.resolvePopupDOM();
93+
if (this.state.popupVisible) {
94+
this.resolvePopupDOM();
95+
}
96+
97+
this.togglePopupCloseEvents();
5598
}
5699

57100
componentDidUpdate() {
58-
this.resolvePopupDOM();
101+
if (this.state.popupVisible) {
102+
this.resolvePopupDOM();
103+
}
104+
105+
this.togglePopupCloseEvents();
106+
}
107+
108+
componentWillUnmount() {
109+
this.clearDelayTimer();
110+
}
111+
112+
togglePopupCloseEvents() {
113+
const { getDocument } = this.props;
114+
const { popupVisible } = this.state;
115+
116+
if (popupVisible) {
117+
const currentDocument = getDocument();
118+
119+
if (!this.clickOutsideHandler && (this.isClickToHide() || this.isContextMenuToShow())) {
120+
this.clickOutsideHandler = listen(currentDocument,
121+
'mousedown', this.onDocumentClick);
122+
}
123+
124+
if (!this.touchOutsideHandler && isMobile) {
125+
this.touchOutsideHandler = listen(currentDocument,
126+
'click', this.onDocumentClick);
127+
}
128+
129+
// close popup when trigger type contains 'onContextMenu' and document is scrolling.
130+
if (!this.contextMenuOutsideHandler1 && this.isContextMenuToShow()) {
131+
this.contextMenuOutsideHandler1 = listen(currentDocument,
132+
'scroll', this.onContextMenuClose);
133+
}
134+
// close popup when trigger type contains 'onContextMenu' and window is blur.
135+
if (!this.contextMenuOutsideHandler2 && this.isContextMenuToShow()) {
136+
this.contextMenuOutsideHandler2 = listen(window,
137+
'blur', this.onContextMenuClose);
138+
}
139+
} else {
140+
this.clearOutsideHandler();
141+
}
142+
}
143+
144+
onDocumentClick = (event) => {
145+
if (this.props.mask && !this.props.maskClosable) {
146+
return;
147+
}
148+
149+
const target = event.target;
150+
const root = findDOMNode(this);
151+
const popupNode = this.getPopupDomNode();
152+
153+
if (!contains(root, target) && !contains(popupNode, target)) {
154+
this.close();
155+
}
156+
}
157+
158+
clearOutsideHandler() {
159+
if (this.clickOutsideHandler) {
160+
this.clickOutsideHandler();
161+
this.clickOutsideHandler = null;
162+
}
163+
164+
if (this.contextMenuOutsideHandler1) {
165+
this.contextMenuOutsideHandler1();
166+
this.contextMenuOutsideHandler1 = null;
167+
}
168+
169+
if (this.contextMenuOutsideHandler2) {
170+
this.contextMenuOutsideHandler2();
171+
this.contextMenuOutsideHandler2 = null;
172+
}
173+
174+
if (this.touchOutsideHandler) {
175+
this.touchOutsideHandler();
176+
this.touchOutsideHandler = null;
177+
}
59178
}
60179

61180
resolvePopupDOM() {
@@ -91,13 +210,13 @@ export default class Trigger extends React.Component {
91210

92211
if (this.promise) {
93212
this.promise.resolve({
94-
of: ReactDOM.findDOMNode(this),
213+
of: findDOMNode(this),
95214
...getPlacement(placement, pOffset)
96215
});
97216
}
98217
}
99218

100-
setPopupVisible(popupVisible) {
219+
_setPopupVisible(popupVisible) {
101220

102221
if (!('popupVisible' in this.props)) {
103222
this.setState({
@@ -108,6 +227,10 @@ export default class Trigger extends React.Component {
108227
this.props.onPopupVisibleChange(popupVisible);
109228
}
110229

230+
close() {
231+
this.delaySetPopupVisible(false);
232+
}
233+
111234
clearDelayTimer() {
112235
if (this.delayTimer) {
113236
clearTimeout(this.delayTimer);
@@ -136,11 +259,11 @@ export default class Trigger extends React.Component {
136259

137260
if (delay) {
138261
this.delayTimer = setTimeout(() => {
139-
this.setPopupVisible(visible);
262+
this._setPopupVisible(visible);
140263
this.delayTimer = null;
141264
}, delay);
142265
} else {
143-
this.setPopupVisible(visible);
266+
this._setPopupVisible(visible);
144267
}
145268
}
146269

@@ -212,13 +335,94 @@ export default class Trigger extends React.Component {
212335
}
213336
}
214337

215-
render() {
216-
const { popupVisible } = this.state;
338+
onContextMenuClose = () => {
339+
if (this.isContextMenuToShow()) {
340+
this.close();
341+
}
342+
}
343+
344+
savePopup = (popup) => {
345+
this._popup = popup;
346+
}
347+
348+
getPopup() {
349+
return this._popup;
350+
}
351+
352+
getPopupDomNode() {
353+
if (this._popup) {
354+
return this._popup ? this._popup.getPopupDOM() : null;
355+
}
356+
return null;
357+
}
358+
359+
getPopupMaskDomNode() {
360+
if (this._popup) {
361+
return this._popup ? this._popup.getPopupMaskDOM() : null;
362+
}
363+
return null;
364+
}
365+
366+
getPopupComponent() {
217367
const {
218-
children,
219368
popup,
369+
prefixCls,
370+
popupClassName,
371+
popupMaskClassName,
372+
popupProps,
373+
mask,
374+
popupStyle,
375+
popupMaskStyle,
376+
popupRootComponent: PopupRootComponent,
377+
destroyPopupOnHide,
378+
zIndex,
379+
popupTransitionClassNames,
380+
popupMaskTransitionClassNames,
220381
} = this.props;
221-
const child = React.Children.only(children);
382+
const { popupVisible } = this.state;
383+
384+
this.promise = Deferred();
385+
386+
const maskProps = popupProps.maskProps || {};
387+
388+
const newPopupStyle = { ...popupStyle };
389+
const newPopupMaskStyle = { ...popupMaskStyle };
390+
391+
if (zIndex != null) {
392+
newPopupStyle.zIndex = zIndex;
393+
newPopupMaskStyle.zIndex = zIndex;
394+
}
395+
396+
return (
397+
<Popup
398+
prefixCls={prefixCls}
399+
placement={this.promise}
400+
unmountOnExit={destroyPopupOnHide}
401+
style={newPopupStyle}
402+
className={popupClassName}
403+
{...popupProps}
404+
rootComponent={PopupRootComponent}
405+
mask={mask}
406+
visible={popupVisible}
407+
ref={this.savePopup}
408+
maskProps={{
409+
style: newPopupMaskStyle,
410+
className: popupMaskClassName,
411+
...maskProps,
412+
}}
413+
>
414+
{
415+
typeof popup === 'function' ? popup() : popup
416+
}
417+
</Popup>
418+
);
419+
}
420+
421+
render() {
422+
const { getPopupContainer } = this.props;
423+
const { popupVisible } = this.state;
424+
425+
const child = React.Children.only(this.props.children);
222426

223427
const newChildProps = {};
224428

@@ -291,23 +495,23 @@ export default class Trigger extends React.Component {
291495
};
292496
}
293497

294-
this.promise = Deferred();
295-
296-
const popupChildren = typeof popup === 'function' ? popup() : popup;
297-
298498
const trigger = React.cloneElement(child, newChildProps);
499+
let portal;
500+
501+
if (popupVisible || this._popup) {
502+
portal = (
503+
<Portal
504+
getContainer={getPopupContainer}
505+
>
506+
{this.getPopupComponent()}
507+
</Portal>
508+
);
509+
}
299510

300511
return (
301512
<>
302513
{trigger}
303-
<Popup
304-
placement={this.promise}
305-
visible={!!popupVisible}
306-
>
307-
{
308-
popupChildren
309-
}
310-
</Popup>
514+
{portal}
311515
</>
312516
);
313517
}

src/style/index.scss

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
.rw-trigger {
2+
position: absolute;
3+
left: 0;
4+
top: 0;
5+
z-index: 2000;
6+
}
7+
8+
.rw-trigger-mask {
9+
position:fixed;
10+
left:0;
11+
top:0;
12+
right:0;
13+
bottom:0;
14+
background: #000;
15+
opacity: 0.1;
16+
z-index: 2000;
17+
}
18+

0 commit comments

Comments
 (0)