Skip to content

Commit 8feb251

Browse files
authored
fix: Portal event bubble (#204)
* fix: Portal event popup * test: Add test case
1 parent 4cc8bc0 commit 8feb251

File tree

5 files changed

+70
-2
lines changed

5 files changed

+70
-2
lines changed

examples/ant-design.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
/* eslint no-console:0 */
22
import '../assets/index.less';
33
import * as React from 'react';
4+
import Select from 'rc-select';
5+
import 'rc-select/assets/index.less';
46
import Dialog from '../src/DialogWrap';
57

68
const clearPath =
@@ -133,7 +135,11 @@ const MyControl = () => {
133135
<button type="button" onClick={toggleCloseIcon}>
134136
use custom icon, is using icon: {(useIcon && 'true') || 'false'}.
135137
</button>
136-
<div style={{ height: 200 }} />
138+
<div style={{ height: 200 }}>
139+
<Select dropdownStyle={{ zIndex: 9999999 }}>
140+
<Select.Option value="light">Light</Select.Option>
141+
</Select>
142+
</div>
137143
</Dialog>
138144
);
139145

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
"np": "^6.4.0",
7070
"prettier": "^2.1.1",
7171
"rc-drawer": "4.1.0",
72+
"rc-select": "^11.4.1",
7273
"react": "^16.9.0",
7374
"react-dom": "^16.9.0",
7475
"react-draggable": "^4.4.3",

src/Dialog/Content.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export interface ContentProps extends IDialogChildProps {
1111
motionName: string;
1212
ariaId: string;
1313
onVisibleChanged: (visible: boolean) => void;
14+
onClick: React.MouseEventHandler;
1415
}
1516

1617
export interface ContentRef {
@@ -41,6 +42,7 @@ const Content = React.forwardRef<ContentRef, ContentProps>((props, ref) => {
4142
ariaId,
4243
onClose,
4344
onVisibleChanged,
45+
onClick,
4446
mousePosition,
4547
} = props;
4648

@@ -142,6 +144,7 @@ const Content = React.forwardRef<ContentRef, ContentProps>((props, ref) => {
142144
ref={motionRef}
143145
style={{ ...motionStyle, ...style, ...contentStyle }}
144146
className={classNames(prefixCls, className, motionClassName)}
147+
onClick={onClick}
145148
>
146149
<div tabIndex={0} ref={sentinelStartRef} style={sentinelStyle} aria-hidden="true" />
147150
{modalRender ? modalRender(content) : content}

src/Dialog/index.tsx

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,29 @@ export default function Dialog(props: IDialogChildProps) {
8888
onClose?.(e);
8989
}
9090

91+
// >>> Content
92+
const contentClickRef = useRef(false);
93+
const contentTimeoutRef = useRef<number>();
94+
95+
// We need record content click incase content popup out of dialog
96+
const onContentClick: React.MouseEventHandler = () => {
97+
clearTimeout(contentTimeoutRef.current);
98+
contentClickRef.current = true;
99+
100+
contentTimeoutRef.current = setTimeout(() => {
101+
contentClickRef.current = false;
102+
});
103+
};
104+
91105
// >>> Wrapper
92106
// Close only when element not on dialog
93107
let onWrapperClick: (e: React.SyntheticEvent) => void = null;
94108
if (maskClosable) {
95109
onWrapperClick = (e) => {
96-
if (!contains(contentRef.current.getDOM(), e.target as HTMLElement)) {
110+
if (
111+
!contentClickRef.current &&
112+
!contains(contentRef.current.getDOM(), e.target as HTMLElement)
113+
) {
97114
onInternalClose(e);
98115
}
99116
};
@@ -126,6 +143,7 @@ export default function Dialog(props: IDialogChildProps) {
126143
useEffect(
127144
() => () => {
128145
switchScrollingEffect();
146+
clearTimeout(contentTimeoutRef.current);
129147
},
130148
[],
131149
);
@@ -156,6 +174,7 @@ export default function Dialog(props: IDialogChildProps) {
156174
>
157175
<Content
158176
{...props}
177+
onClick={onContentClick}
159178
ref={contentRef}
160179
closable={closable}
161180
ariaId={ariaIdRef.current}

tests/portal.spec.tsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/* eslint-disable react/no-render-return-value, max-classes-per-file, func-names, no-console */
2+
import React from 'react';
3+
import Select from 'rc-select';
4+
import { act } from 'react-dom/test-utils';
5+
import { mount } from 'enzyme';
6+
import Dialog from '../src';
7+
8+
/**
9+
* Since overflow scroll test need a clear env which may affect by other test.
10+
* Use a clean env instead.
11+
*/
12+
describe('Dialog.Portal', () => {
13+
beforeEach(() => {
14+
jest.useFakeTimers();
15+
});
16+
17+
afterEach(() => {
18+
jest.useRealTimers();
19+
});
20+
21+
it('event should bubble', () => {
22+
const onClose = jest.fn();
23+
24+
const wrapper = mount(
25+
<Dialog onClose={onClose} visible>
26+
<Select virtual={false} open>
27+
<Select.Option value="bamboo">Bamboo</Select.Option>
28+
</Select>
29+
</Dialog>,
30+
);
31+
32+
act(() => {
33+
jest.runAllTimers();
34+
});
35+
36+
wrapper.find('.rc-select-item-option-content').simulate('click');
37+
expect(onClose).not.toHaveBeenCalled();
38+
});
39+
});

0 commit comments

Comments
 (0)