Skip to content

Commit 42dedf2

Browse files
authored
fix: Popup mouseDown inside and mouseUp outside should not close popup (#376)
* fix: click out side * test: add test case
1 parent 6b889e7 commit 42dedf2

File tree

3 files changed

+87
-10
lines changed

3 files changed

+87
-10
lines changed

docs/examples/container.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ export default () => {
153153
</div>
154154
}
155155
popupStyle={{ boxShadow: '0 0 5px red' }}
156-
popupVisible
156+
// popupVisible
157157
// getPopupContainer={() => popHolderRef.current}
158158
popupPlacement={popupPlacement}
159159
builtinPlacements={builtinPlacements}

src/index.tsx

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,22 @@ import useWatch from './hooks/useWatch';
1717
import type {
1818
ActionType,
1919
AlignType,
20+
AnimationType,
2021
ArrowType,
2122
ArrowTypeOuter,
22-
AnimationType,
2323
BuildInPlacements,
2424
TransitionNameType,
2525
} from './interface';
2626
import Popup from './Popup';
2727
import TriggerWrapper from './TriggerWrapper';
2828
import { getAlignPopupClassName, getMotion, getWin } from './util';
2929

30-
export type { BuildInPlacements, AlignType, ActionType, ArrowTypeOuter as ArrowType };
30+
export type {
31+
BuildInPlacements,
32+
AlignType,
33+
ActionType,
34+
ArrowTypeOuter as ArrowType,
35+
};
3136

3237
export interface TriggerRef {
3338
forceAlign: VoidFunction;
@@ -495,8 +500,16 @@ export function generateTrigger(
495500
// Click to hide is special action since click popup element should not hide
496501
React.useEffect(() => {
497502
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+
498511
const onWindowClick = ({ target }: MouseEvent) => {
499-
if (openRef.current && !inPopupOrChild(target)) {
512+
if (openRef.current && !clickInside && !inPopupOrChild(target)) {
500513
triggerOpen(false);
501514
}
502515
};
@@ -505,11 +518,16 @@ export function generateTrigger(
505518

506519
const targetRoot = targetEle?.getRootNode();
507520

521+
win.addEventListener('mousedown', onWindowMouseDown);
508522
win.addEventListener('click', onWindowClick);
509523

510524
// shadow root
511525
const inShadow = targetRoot && targetRoot !== targetEle.ownerDocument;
512526
if (inShadow) {
527+
(targetRoot as ShadowRoot).addEventListener(
528+
'mousedown',
529+
onWindowMouseDown,
530+
);
513531
(targetRoot as ShadowRoot).addEventListener('click', onWindowClick);
514532
}
515533

@@ -524,9 +542,14 @@ export function generateTrigger(
524542
}
525543

526544
return () => {
545+
win.removeEventListener('mousedown', onWindowMouseDown);
527546
win.removeEventListener('click', onWindowClick);
528547

529548
if (inShadow) {
549+
(targetRoot as ShadowRoot).removeEventListener(
550+
'mousedown',
551+
onWindowMouseDown,
552+
);
530553
(targetRoot as ShadowRoot).removeEventListener(
531554
'click',
532555
onWindowClick,
@@ -627,12 +650,14 @@ export function generateTrigger(
627650
...passedProps,
628651
});
629652

630-
const innerArrow: ArrowType = arrow ? {
631-
// true and Object likely
632-
...(arrow !== true ? arrow : {}),
633-
x: arrowX,
634-
y: arrowY
635-
}: null;
653+
const innerArrow: ArrowType = arrow
654+
? {
655+
// true and Object likely
656+
...(arrow !== true ? arrow : {}),
657+
x: arrowX,
658+
y: arrowY,
659+
}
660+
: null;
636661

637662
// Render
638663
return (

tests/basic.test.jsx

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -936,4 +936,56 @@ describe('Trigger.Basic', () => {
936936
await awaitFakeTimer();
937937
expect(document.querySelector('.rc-trigger-popup-hidden')).toBeTruthy();
938938
});
939+
940+
describe('click window to hide', () => {
941+
it('should hide', async () => {
942+
const onPopupVisibleChange = jest.fn();
943+
944+
const { container } = render(
945+
<Trigger
946+
onPopupVisibleChange={onPopupVisibleChange}
947+
action="click"
948+
popup={<strong>trigger</strong>}
949+
>
950+
<div className="target" />
951+
</Trigger>,
952+
);
953+
954+
fireEvent.click(container.querySelector('.target'));
955+
await awaitFakeTimer();
956+
expect(onPopupVisibleChange).toHaveBeenCalledWith(true);
957+
onPopupVisibleChange.mockReset();
958+
959+
// Click outside to close
960+
fireEvent.mouseDown(document.body);
961+
fireEvent.click(document.body);
962+
await awaitFakeTimer();
963+
expect(onPopupVisibleChange).toHaveBeenCalledWith(false);
964+
});
965+
966+
it('should not hide when mouseDown inside but mouseUp outside', async () => {
967+
const onPopupVisibleChange = jest.fn();
968+
969+
const { container } = render(
970+
<Trigger
971+
onPopupVisibleChange={onPopupVisibleChange}
972+
action="click"
973+
popup={<strong>trigger</strong>}
974+
>
975+
<div className="target" />
976+
</Trigger>,
977+
);
978+
979+
fireEvent.click(container.querySelector('.target'));
980+
await awaitFakeTimer();
981+
expect(onPopupVisibleChange).toHaveBeenCalledWith(true);
982+
onPopupVisibleChange.mockReset();
983+
984+
// Click outside to close
985+
fireEvent.mouseDown(document.querySelector('strong'));
986+
fireEvent.click(document.body);
987+
await awaitFakeTimer();
988+
expect(onPopupVisibleChange).not.toHaveBeenCalled();
989+
});
990+
});
939991
});

0 commit comments

Comments
 (0)