From e9769031663209f21c53daf996dc4a29a4c0db97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Fri, 8 Nov 2024 14:32:22 +0800 Subject: [PATCH 1/3] docs: add debug demo --- docs/demos/portal.md | 8 +++ docs/examples/portal.tsx | 105 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 docs/demos/portal.md create mode 100644 docs/examples/portal.tsx 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..9f330c18 --- /dev/null +++ b/docs/examples/portal.tsx @@ -0,0 +1,105 @@ +/* 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); + }} + > + 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 + +
+ } + > + + + + + + + ); +}; + +export default Test; From c77929384b64aca76c10b3abf952ef6aa79e3405 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Fri, 8 Nov 2024 15:01:49 +0800 Subject: [PATCH 2/3] fix: trigger open logic --- docs/examples/portal.tsx | 5 +++++ src/Popup/index.tsx | 3 +++ src/index.tsx | 12 +++++++++++- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/docs/examples/portal.tsx b/docs/examples/portal.tsx index 9f330c18..a1b66576 100644 --- a/docs/examples/portal.tsx +++ b/docs/examples/portal.tsx @@ -44,6 +44,8 @@ const PortalPopup = () => style={popupBorderStyle} onMouseDown={(e) => { console.log('Portal Down', e); + e.stopPropagation(); + e.preventDefault(); }} > i am a portal element @@ -84,6 +86,9 @@ const Test = () => { } + onPopupVisibleChange={(visible) => { + console.log('visible change:', visible); + }} > 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 From 90301549c4a1152db4b6ed4df5add466aa5cb297 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Fri, 8 Nov 2024 15:12:00 +0800 Subject: [PATCH 3/3] test: add test case --- tests/basic.test.jsx | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) 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(); + }); });