Skip to content

Commit cc6b63d

Browse files
authored
fix: Click window should not immediately close if open is first time trigger (#386)
* fix: global click should delay for check * test: fix test logic * test: add test case
1 parent 783e8b4 commit cc6b63d

File tree

5 files changed

+160
-63
lines changed

5 files changed

+160
-63
lines changed

docs/examples/container.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ const popupPlacement = 'top';
6060
export default () => {
6161
console.log('Demo Render!');
6262

63+
const [visible, setVisible] = React.useState(false);
6364
const [scale, setScale] = React.useState('1');
6465
const [targetVisible, setTargetVisible] = React.useState(true);
6566

@@ -100,6 +101,13 @@ export default () => {
100101
>
101102
Target Visible: ({String(targetVisible)})
102103
</button>
104+
<button
105+
onClick={() => {
106+
setVisible(true);
107+
}}
108+
>
109+
Trigger Visible
110+
</button>
103111
</div>
104112
<div
105113
id="demo-holder"
@@ -154,7 +162,10 @@ export default () => {
154162
}
155163
popupTransitionName="rc-trigger-popup-zoom"
156164
popupStyle={{ boxShadow: '0 0 5px red' }}
157-
// popupVisible
165+
popupVisible={visible}
166+
onPopupVisibleChange={(nextVisible) => {
167+
setVisible(nextVisible);
168+
}}
158169
// getPopupContainer={() => popHolderRef.current}
159170
popupPlacement={popupPlacement}
160171
builtinPlacements={builtinPlacements}

src/hooks/useWinClick.ts

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { warning } from 'rc-util';
2+
import raf from 'rc-util/lib/raf';
3+
import * as React from 'react';
4+
import { getWin } from '../util';
5+
6+
export default function useWinClick(
7+
open: boolean,
8+
clickToHide: boolean,
9+
targetEle: HTMLElement,
10+
popupEle: HTMLElement,
11+
mask: boolean,
12+
maskClosable: boolean,
13+
inPopupOrChild: (target: EventTarget) => boolean,
14+
triggerOpen: (open: boolean) => void,
15+
) {
16+
const openRef = React.useRef(open);
17+
18+
// Window click to hide should be lock to avoid trigger lock immediately
19+
const lockRef = React.useRef(false);
20+
if (openRef.current !== open) {
21+
lockRef.current = true;
22+
openRef.current = open;
23+
}
24+
25+
React.useEffect(() => {
26+
const id = raf(() => {
27+
lockRef.current = false;
28+
});
29+
30+
return () => {
31+
raf.cancel(id);
32+
};
33+
}, [open]);
34+
35+
// Click to hide is special action since click popup element should not hide
36+
React.useEffect(() => {
37+
if (clickToHide && popupEle && (!mask || maskClosable)) {
38+
let clickInside = false;
39+
40+
// User may mouseDown inside and drag out of popup and mouse up
41+
// Record here to prevent close
42+
const onWindowMouseDown = ({ target }: MouseEvent) => {
43+
clickInside = inPopupOrChild(target);
44+
};
45+
46+
const onWindowClick = ({ target }: MouseEvent) => {
47+
if (
48+
!lockRef.current &&
49+
openRef.current &&
50+
!clickInside &&
51+
!inPopupOrChild(target)
52+
) {
53+
triggerOpen(false);
54+
}
55+
};
56+
57+
const win = getWin(popupEle);
58+
59+
const targetRoot = targetEle?.getRootNode();
60+
61+
win.addEventListener('mousedown', onWindowMouseDown);
62+
win.addEventListener('click', onWindowClick);
63+
64+
// shadow root
65+
const inShadow = targetRoot && targetRoot !== targetEle.ownerDocument;
66+
if (inShadow) {
67+
(targetRoot as ShadowRoot).addEventListener(
68+
'mousedown',
69+
onWindowMouseDown,
70+
);
71+
(targetRoot as ShadowRoot).addEventListener('click', onWindowClick);
72+
}
73+
74+
// Warning if target and popup not in same root
75+
if (process.env.NODE_ENV !== 'production') {
76+
const popupRoot = popupEle.getRootNode();
77+
78+
warning(
79+
targetRoot === popupRoot,
80+
`trigger element and popup element should in same shadow root.`,
81+
);
82+
}
83+
84+
return () => {
85+
win.removeEventListener('mousedown', onWindowMouseDown);
86+
win.removeEventListener('click', onWindowClick);
87+
88+
if (inShadow) {
89+
(targetRoot as ShadowRoot).removeEventListener(
90+
'mousedown',
91+
onWindowMouseDown,
92+
);
93+
(targetRoot as ShadowRoot).removeEventListener(
94+
'click',
95+
onWindowClick,
96+
);
97+
}
98+
};
99+
}
100+
}, [clickToHide, targetEle, popupEle, mask, maskClosable]);
101+
}

src/index.tsx

Lines changed: 12 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ import useEvent from 'rc-util/lib/hooks/useEvent';
77
import useId from 'rc-util/lib/hooks/useId';
88
import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect';
99
import isMobile from 'rc-util/lib/isMobile';
10-
import warning from 'rc-util/lib/warning';
1110
import * as React from 'react';
1211
import type { TriggerContextProps } from './context';
1312
import TriggerContext from './context';
1413
import useAction from './hooks/useAction';
1514
import useAlign from './hooks/useAlign';
1615
import useWatch from './hooks/useWatch';
16+
import useWinClick from './hooks/useWinClick';
1717
import type {
1818
ActionType,
1919
AlignType,
@@ -25,7 +25,7 @@ import type {
2525
} from './interface';
2626
import Popup from './Popup';
2727
import TriggerWrapper from './TriggerWrapper';
28-
import { getAlignPopupClassName, getMotion, getWin } from './util';
28+
import { getAlignPopupClassName, getMotion } from './util';
2929

3030
export type {
3131
BuildInPlacements,
@@ -498,66 +498,16 @@ export function generateTrigger(
498498
}
499499

500500
// Click to hide is special action since click popup element should not hide
501-
React.useEffect(() => {
502-
if (clickToHide && popupEle && (!mask || maskClosable)) {
503-
let clickInside = false;
504-
505-
// User may mouseDown inside and drag out of popup and mouse up
506-
// Record here to prevent close
507-
const onWindowMouseDown = ({ target }: MouseEvent) => {
508-
clickInside = inPopupOrChild(target);
509-
};
510-
511-
const onWindowClick = ({ target }: MouseEvent) => {
512-
if (openRef.current && !clickInside && !inPopupOrChild(target)) {
513-
triggerOpen(false);
514-
}
515-
};
516-
517-
const win = getWin(popupEle);
518-
519-
const targetRoot = targetEle?.getRootNode();
520-
521-
win.addEventListener('mousedown', onWindowMouseDown);
522-
win.addEventListener('click', onWindowClick);
523-
524-
// shadow root
525-
const inShadow = targetRoot && targetRoot !== targetEle.ownerDocument;
526-
if (inShadow) {
527-
(targetRoot as ShadowRoot).addEventListener(
528-
'mousedown',
529-
onWindowMouseDown,
530-
);
531-
(targetRoot as ShadowRoot).addEventListener('click', onWindowClick);
532-
}
533-
534-
// Warning if target and popup not in same root
535-
if (process.env.NODE_ENV !== 'production') {
536-
const popupRoot = popupEle.getRootNode();
537-
538-
warning(
539-
targetRoot === popupRoot,
540-
`trigger element and popup element should in same shadow root.`,
541-
);
542-
}
543-
544-
return () => {
545-
win.removeEventListener('mousedown', onWindowMouseDown);
546-
win.removeEventListener('click', onWindowClick);
547-
548-
if (inShadow) {
549-
(targetRoot as ShadowRoot).removeEventListener(
550-
'mousedown',
551-
onWindowMouseDown,
552-
);
553-
(targetRoot as ShadowRoot).removeEventListener(
554-
'click',
555-
onWindowClick,
556-
);
557-
}
558-
};
559-
}
560-
}, [clickToHide, targetEle, popupEle, mask, maskClosable]);
501+
useWinClick(
502+
mergedOpen,
503+
clickToHide,
504+
targetEle,
505+
popupEle,
506+
mask,
507+
maskClosable,
508+
inPopupOrChild,
509+
triggerOpen,
510+
);
561511

562512
// ======================= Action: Hover ========================
563513
const hoverToShow = showActions.has('hover');

tests/basic.test.jsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -987,5 +987,37 @@ describe('Trigger.Basic', () => {
987987
await awaitFakeTimer();
988988
expect(onPopupVisibleChange).not.toHaveBeenCalled();
989989
});
990+
991+
// https://github.com/ant-design/ant-design/issues/42526
992+
it('not hide when click button', async () => {
993+
const Demo = () => {
994+
const [open, setOpen] = React.useState(false);
995+
return (
996+
<>
997+
<button
998+
onClick={() => {
999+
setOpen(true);
1000+
}}
1001+
/>
1002+
<Trigger
1003+
onPopupVisibleChange={setOpen}
1004+
action="click"
1005+
popupVisible={open}
1006+
popup={<strong>trigger</strong>}
1007+
>
1008+
<div className="target" />
1009+
</Trigger>
1010+
</>
1011+
);
1012+
};
1013+
1014+
const { container } = render(<Demo />);
1015+
1016+
fireEvent.click(container.querySelector('button'));
1017+
fireEvent.click(window);
1018+
await awaitFakeTimer();
1019+
expect(document.querySelector('.rc-trigger-popup')).toBeTruthy();
1020+
expect(document.querySelector('.rc-trigger-popup-hidden')).toBeFalsy();
1021+
});
9901022
});
9911023
});

tests/shadow.test.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ describe('Trigger.Shadow', () => {
3535
);
3636

3737
const renderShadow = (props?: any) => {
38+
const noRelatedSpan = document.createElement('span');
39+
document.body.appendChild(noRelatedSpan);
40+
3841
const host = document.createElement('div');
3942
document.body.appendChild(host);
4043

0 commit comments

Comments
 (0)