Skip to content

Commit c15aa2d

Browse files
authored
fix:Support prevent close event children stopPropagation (#251)
* docs: Add external example * capture it * test: update test case
1 parent aca98d8 commit c15aa2d

File tree

7 files changed

+219
-133
lines changed

7 files changed

+219
-133
lines changed

examples/nested.js

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

33
import React from 'react';
4+
import ReactDOM from 'react-dom';
45
import Trigger from '../src';
56
import '../assets/index.less';
67

@@ -36,66 +37,96 @@ const popupBorderStyle = {
3637
padding: 10,
3738
};
3839

39-
class Test extends React.Component {
40-
saveContainerRef = (node) => {
41-
this.containerInstanceNode = node;
42-
};
40+
const OuterContent = ({ getContainer }) => {
41+
return ReactDOM.createPortal(
42+
<div>
43+
I am outer content
44+
<button
45+
onMouseDown={(e) => {
46+
e.stopPropagation();
47+
}}
48+
>
49+
Stop Pop
50+
</button>
51+
</div>,
52+
getContainer(),
53+
);
54+
};
55+
56+
const Test = () => {
57+
const containerRef = React.useRef();
58+
const outerDivRef = React.useRef();
4359

44-
render() {
45-
const innerTrigger = (
46-
<div style={popupBorderStyle}>
47-
<div ref={this.saveContainerRef} />
60+
const innerTrigger = (
61+
<div style={popupBorderStyle}>
62+
<div ref={containerRef} />
63+
<Trigger
64+
popupPlacement="bottom"
65+
action={['click']}
66+
builtinPlacements={builtinPlacements}
67+
getPopupContainer={() => containerRef.current}
68+
popup={<div style={popupBorderStyle}>I am inner Trigger Popup</div>}
69+
>
70+
<span href="#" style={{ margin: 20 }}>
71+
clickToShowInnerTrigger
72+
</span>
73+
</Trigger>
74+
</div>
75+
);
76+
return (
77+
<div style={{ margin: 200 }}>
78+
<div>
4879
<Trigger
49-
popupPlacement="bottom"
80+
popupPlacement="left"
5081
action={['click']}
5182
builtinPlacements={builtinPlacements}
52-
getPopupContainer={() => this.containerInstanceNode}
53-
popup={<div style={popupBorderStyle}>I am inner Trigger Popup</div>}
83+
popup={
84+
<div style={popupBorderStyle}>
85+
i am a click popup
86+
<OuterContent getContainer={() => outerDivRef.current} />
87+
</div>
88+
}
5489
>
55-
<span href="#" style={{ margin: 20 }}>
56-
clickToShowInnerTrigger
90+
<span>
91+
<Trigger
92+
popupPlacement="bottom"
93+
action={['hover']}
94+
builtinPlacements={builtinPlacements}
95+
popup={<div style={popupBorderStyle}>i am a hover popup</div>}
96+
>
97+
<span href="#" style={{ margin: 20 }}>
98+
trigger
99+
</span>
100+
</Trigger>
57101
</span>
58102
</Trigger>
59103
</div>
60-
);
61-
return (
62-
<div style={{ margin: 200 }}>
63-
<div>
64-
<Trigger
65-
popupPlacement="left"
66-
action={['click']}
67-
builtinPlacements={builtinPlacements}
68-
popup={<div style={popupBorderStyle}>i am a click popup</div>}
69-
>
70-
<span>
71-
<Trigger
72-
popupPlacement="bottom"
73-
action={['hover']}
74-
builtinPlacements={builtinPlacements}
75-
popup={<div style={popupBorderStyle}>i am a hover popup</div>}
76-
>
77-
<span href="#" style={{ margin: 20 }}>
78-
trigger
79-
</span>
80-
</Trigger>
81-
</span>
82-
</Trigger>
83-
</div>
84-
<div style={{ margin: 50 }}>
85-
<Trigger
86-
popupPlacement="right"
87-
action={['hover']}
88-
builtinPlacements={builtinPlacements}
89-
popup={innerTrigger}
90-
>
91-
<span href="#" style={{ margin: 20 }}>
92-
trigger
93-
</span>
94-
</Trigger>
95-
</div>
104+
<div style={{ margin: 50 }}>
105+
<Trigger
106+
popupPlacement="right"
107+
action={['hover']}
108+
builtinPlacements={builtinPlacements}
109+
popup={innerTrigger}
110+
>
111+
<span href="#" style={{ margin: 20 }}>
112+
trigger
113+
</span>
114+
</Trigger>
96115
</div>
97-
);
98-
}
99-
}
116+
117+
<div
118+
ref={outerDivRef}
119+
style={{
120+
position: 'fixed',
121+
right: 0,
122+
bottom: 0,
123+
width: 200,
124+
height: 200,
125+
background: 'red',
126+
}}
127+
/>
128+
</div>
129+
);
130+
};
100131

101132
export default Test;

src/Popup/PopupInner.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,8 @@ const PopupInner = React.forwardRef<PopupInnerRef, PopupInnerProps>(
218218
className={mergedClassName}
219219
onMouseEnter={onMouseEnter}
220220
onMouseLeave={onMouseLeave}
221-
onMouseDown={onMouseDown}
222-
onTouchStart={onTouchStart}
221+
onMouseDownCapture={onMouseDown}
222+
onTouchStartCapture={onTouchStart}
223223
style={{
224224
...motionStyle,
225225
...mergedStyle,

tests/basic.test.jsx

Lines changed: 71 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,13 @@
1+
/* eslint-disable max-classes-per-file */
2+
13
import React from 'react';
4+
import ReactDOM from 'react-dom';
25
import { mount } from 'enzyme';
36
import { act } from 'react-dom/test-utils';
47
import { spyElementPrototypes } from 'rc-util/lib/test/domHook';
58
import Portal from 'rc-util/lib/Portal';
69
import Trigger from '../src';
7-
8-
const autoAdjustOverflow = {
9-
adjustX: 1,
10-
adjustY: 1,
11-
};
12-
13-
const targetOffsetG = [0, 0];
14-
15-
export const placementAlignMap = {
16-
left: {
17-
points: ['cr', 'cl'],
18-
overflow: autoAdjustOverflow,
19-
offset: [-3, 0],
20-
targetOffsetG,
21-
},
22-
right: {
23-
points: ['cl', 'cr'],
24-
overflow: autoAdjustOverflow,
25-
offset: [3, 0],
26-
targetOffsetG,
27-
},
28-
top: {
29-
points: ['bc', 'tc'],
30-
overflow: autoAdjustOverflow,
31-
offset: [0, -3],
32-
targetOffsetG,
33-
},
34-
bottom: {
35-
points: ['tc', 'bc'],
36-
overflow: autoAdjustOverflow,
37-
offset: [0, 3],
38-
targetOffsetG,
39-
},
40-
topLeft: {
41-
points: ['bl', 'tl'],
42-
overflow: autoAdjustOverflow,
43-
offset: [0, -3],
44-
targetOffsetG,
45-
},
46-
topRight: {
47-
points: ['br', 'tr'],
48-
overflow: autoAdjustOverflow,
49-
offset: [0, -3],
50-
targetOffsetG,
51-
},
52-
bottomRight: {
53-
points: ['tr', 'br'],
54-
overflow: autoAdjustOverflow,
55-
offset: [0, 3],
56-
targetOffsetG,
57-
},
58-
bottomLeft: {
59-
points: ['tl', 'bl'],
60-
overflow: autoAdjustOverflow,
61-
offset: [0, 3],
62-
targetOffsetG,
63-
},
64-
};
10+
import { placementAlignMap } from './util';
6511

6612
describe('Trigger.Basic', () => {
6713
beforeEach(() => {
@@ -147,12 +93,7 @@ describe('Trigger.Basic', () => {
14793
);
14894

14995
wrapper.trigger();
150-
expect(
151-
wrapper
152-
.find('Popup')
153-
.find('.x-content')
154-
.text(),
155-
).toBe('tooltip2');
96+
expect(wrapper.find('Popup').find('.x-content').text()).toBe('tooltip2');
15697

15798
wrapper.trigger();
15899
expect(wrapper.isHidden()).toBeTruthy();
@@ -173,12 +114,7 @@ describe('Trigger.Basic', () => {
173114
);
174115

175116
wrapper.trigger();
176-
expect(
177-
wrapper
178-
.find('Popup')
179-
.find('.x-content')
180-
.text(),
181-
).toBe('tooltip3');
117+
expect(wrapper.find('Popup').find('.x-content').text()).toBe('tooltip3');
182118

183119
wrapper.trigger();
184120
expect(wrapper.isHidden()).toBeTruthy();
@@ -509,7 +445,7 @@ describe('Trigger.Basic', () => {
509445
});
510446

511447
describe('stretch', () => {
512-
const createTrigger = stretch =>
448+
const createTrigger = (stretch) =>
513449
mount(
514450
<Trigger
515451
action={['click']}
@@ -542,7 +478,7 @@ describe('Trigger.Basic', () => {
542478
domSpy.mockRestore();
543479
});
544480

545-
['width', 'height', 'minWidth', 'minHeight'].forEach(prop => {
481+
['width', 'height', 'minWidth', 'minHeight'].forEach((prop) => {
546482
it(prop, () => {
547483
const wrapper = createTrigger(prop);
548484

@@ -643,7 +579,7 @@ describe('Trigger.Basic', () => {
643579
<div>
644580
<button
645581
type="button"
646-
onMouseDown={e => {
582+
onMouseDown={(e) => {
647583
e.preventDefault();
648584
e.stopPropagation();
649585
}}
@@ -687,7 +623,7 @@ describe('Trigger.Basic', () => {
687623

688624
describe('getContainer', () => {
689625
it('not trigger when dom not ready', () => {
690-
const getPopupContainer = jest.fn(node => node.parentElement);
626+
const getPopupContainer = jest.fn((node) => node.parentElement);
691627

692628
function Demo() {
693629
return (
@@ -740,4 +676,65 @@ describe('Trigger.Basic', () => {
740676
wrapper.unmount();
741677
});
742678
});
679+
680+
// https://github.com/ant-design/ant-design/issues/30116
681+
it('createPortal should also work with stopPropagation', () => {
682+
const root = document.createElement('div');
683+
document.body.appendChild(root);
684+
685+
const div = document.createElement('div');
686+
document.body.appendChild(div);
687+
688+
const OuterContent = ({ container }) => {
689+
return ReactDOM.createPortal(
690+
<button
691+
onMouseDown={(e) => {
692+
e.stopPropagation();
693+
}}
694+
>
695+
Stop Pop
696+
</button>,
697+
container,
698+
);
699+
};
700+
701+
const Demo = () => {
702+
return (
703+
<Trigger
704+
action={['click']}
705+
popup={
706+
<strong className="x-content">
707+
tooltip2
708+
<OuterContent container={div} />
709+
</strong>
710+
}
711+
>
712+
<div className="target">click</div>
713+
</Trigger>
714+
);
715+
};
716+
717+
const wrapper = mount(<Demo />, { attachTo: root });
718+
719+
wrapper.find('.target').simulate('click');
720+
expect(wrapper.isHidden()).toBeFalsy();
721+
722+
// Click should not close
723+
wrapper.find('button').simulate('mouseDown');
724+
725+
// Mock document mouse click event
726+
act(() => {
727+
const mouseEvent = new MouseEvent('mousedown');
728+
document.dispatchEvent(mouseEvent);
729+
wrapper.update();
730+
});
731+
732+
wrapper.update();
733+
expect(wrapper.isHidden()).toBeFalsy();
734+
735+
wrapper.unmount();
736+
737+
document.body.removeChild(div);
738+
document.body.removeChild(root);
739+
});
743740
});

tests/mask.test.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22
import { mount } from 'enzyme';
33
import Trigger from '../src';
4-
import { placementAlignMap } from './basic.test';
4+
import { placementAlignMap } from './util';
55

66
describe('Trigger.Mask', () => {
77
beforeEach(() => {

tests/mobile.test.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import React from 'react';
22
import { act } from 'react-dom/test-utils';
33
import isMobile from 'rc-util/lib/isMobile';
44
import { mount } from 'enzyme';
5-
import Trigger, { TriggerProps } from '../src';
6-
import { placementAlignMap } from './basic.test';
5+
import type { TriggerProps } from '../src';
6+
import Trigger from '../src';
7+
import { placementAlignMap } from './util';
78

89
jest.mock('rc-util/lib/isMobile');
910

@@ -53,7 +54,7 @@ describe('Trigger.Mobile', () => {
5354
const wrapper = mount(
5455
getTrigger({
5556
mobile: {
56-
popupRender: node => (
57+
popupRender: (node) => (
5758
<>
5859
<div>Light</div>
5960
{node}

0 commit comments

Comments
 (0)