diff --git a/docs/demos/portal.md b/docs/demos/portal.md
new file mode 100644
index 00000000..5611bd1d
--- /dev/null
+++ b/docs/demos/portal.md
@@ -0,0 +1,8 @@
+---
+title: Portal
+nav:
+ title: Demo
+ path: /demo
+---
+
+
diff --git a/docs/examples/portal.tsx b/docs/examples/portal.tsx
new file mode 100644
index 00000000..a1b66576
--- /dev/null
+++ b/docs/examples/portal.tsx
@@ -0,0 +1,110 @@
+/* eslint no-console:0 */
+
+import Trigger from 'rc-trigger';
+import React from 'react';
+import { createPortal } from 'react-dom';
+import '../../assets/index.less';
+
+const builtinPlacements = {
+ left: {
+ points: ['cr', 'cl'],
+ },
+ right: {
+ points: ['cl', 'cr'],
+ },
+ top: {
+ points: ['bc', 'tc'],
+ },
+ bottom: {
+ points: ['tc', 'bc'],
+ },
+ topLeft: {
+ points: ['bl', 'tl'],
+ },
+ topRight: {
+ points: ['br', 'tr'],
+ },
+ bottomRight: {
+ points: ['tr', 'br'],
+ },
+ bottomLeft: {
+ points: ['tl', 'bl'],
+ },
+};
+
+const popupBorderStyle = {
+ border: '1px solid red',
+ padding: 10,
+ background: 'rgba(255, 0, 0, 0.1)',
+};
+
+const PortalPopup = () =>
+ createPortal(
+
{
+ console.log('Portal Down', e);
+ e.stopPropagation();
+ e.preventDefault();
+ }}
+ >
+ i am a portal element
+
,
+ document.body,
+ );
+
+const Test = () => {
+ const buttonRef = React.useRef(null);
+ React.useEffect(() => {
+ const button = buttonRef.current;
+ if (button) {
+ button.addEventListener('mousedown', (e) => {
+ console.log('button natives down');
+ e.stopPropagation();
+ e.preventDefault();
+ });
+ }
+ }, []);
+
+ return (
+
+
+ i am a click popup
+
+
+ }
+ onPopupVisibleChange={(visible) => {
+ console.log('visible change:', visible);
+ }}
+ >
+
+
+
+
+
+
+ );
+};
+
+export default Test;
diff --git a/src/Popup/index.tsx b/src/Popup/index.tsx
index e5d64d42..a1052ab2 100644
--- a/src/Popup/index.tsx
+++ b/src/Popup/index.tsx
@@ -20,6 +20,7 @@ export interface PopupProps {
onMouseEnter?: React.MouseEventHandler;
onMouseLeave?: React.MouseEventHandler;
onPointerEnter?: React.MouseEventHandler;
+ onMouseDownCapture?: React.MouseEventHandler;
zIndex?: number;
mask?: boolean;
@@ -105,6 +106,7 @@ const Popup = React.forwardRef((props, ref) => {
onMouseEnter,
onMouseLeave,
onPointerEnter,
+ onMouseDownCapture,
ready,
offsetX,
@@ -255,6 +257,7 @@ const Popup = React.forwardRef((props, ref) => {
onMouseLeave={onMouseLeave}
onPointerEnter={onPointerEnter}
onClick={onClick}
+ onMouseDownCapture={onMouseDownCapture}
>
{arrow && (
(null);
// =========================== Align ============================
- const [mousePos, setMousePos] = React.useState<[x: number, y: number] | null>(null);
+ const [mousePos, setMousePos] = React.useState<
+ [x: number, y: number] | null
+ >(null);
const setMousePosByEvent = (
event: Pick,
@@ -720,6 +722,14 @@ export function generateTrigger(
fresh={fresh}
// Click
onClick={onPopupClick}
+ onMouseDownCapture={() => {
+ // Additional check for click to hide
+ // Since `createPortal` will not included in the popup element
+ // So we use capture to handle this
+ if (clickToHide) {
+ triggerOpen(true);
+ }
+ }}
// Mask
mask={mask}
// Motion
diff --git a/tests/basic.test.jsx b/tests/basic.test.jsx
index aa4e87d4..e5eb0aa3 100644
--- a/tests/basic.test.jsx
+++ b/tests/basic.test.jsx
@@ -3,7 +3,7 @@
import { act, cleanup, fireEvent, render } from '@testing-library/react';
import { spyElementPrototypes } from 'rc-util/lib/test/domHook';
import React, { StrictMode, createRef } from 'react';
-import ReactDOM from 'react-dom';
+import ReactDOM, { createPortal } from 'react-dom';
import Trigger from '../src';
import { awaitFakeTimer, placementAlignMap } from './util';
@@ -107,7 +107,7 @@ describe('Trigger.Basic', () => {
expect(document.querySelector('.x-content').textContent).toBe('tooltip2');
trigger(container, '.target');
- expect(isPopupHidden).toBeTruthy();
+ expect(isPopupHidden()).toBeTruthy();
});
it('click works with function', () => {
@@ -1198,4 +1198,35 @@ describe('Trigger.Basic', () => {
trigger(container, '.target');
expect(document.querySelector('.x-content').textContent).toBe('false');
});
+
+ it('createPortal should not close', async () => {
+ const Portal = () =>
+ createPortal(, document.body);
+
+ const Demo = () => {
+ return (
+ <>
+ }>
+
+
+
+ >
+ );
+ };
+
+ const { container } = render();
+ fireEvent.click(container.querySelector('.target'));
+ await awaitFakeTimer();
+ expect(isPopupHidden()).toBeFalsy();
+
+ // Click portal should not close
+ fireEvent.click(document.querySelector('.portal'));
+ await awaitFakeTimer();
+ expect(isPopupHidden()).toBeFalsy();
+
+ // Click outside to close
+ fireEvent.mouseDown(container.querySelector('.outer'));
+ await awaitFakeTimer();
+ expect(isPopupHidden()).toBeTruthy();
+ });
});