Skip to content

Commit

Permalink
fix: try to fix createPortal close case (#492)
Browse files Browse the repository at this point in the history
* docs: add debug demo

* fix: trigger open logic

* test: add test case
  • Loading branch information
zombieJ authored Nov 8, 2024
1 parent 8abc4f9 commit 0fa6243
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 3 deletions.
8 changes: 8 additions & 0 deletions docs/demos/portal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
title: Portal
nav:
title: Demo
path: /demo
---

<code src="../examples/portal.tsx"></code>
110 changes: 110 additions & 0 deletions docs/examples/portal.tsx
Original file line number Diff line number Diff line change
@@ -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(
<div
style={popupBorderStyle}
onMouseDown={(e) => {
console.log('Portal Down', e);
e.stopPropagation();
e.preventDefault();
}}
>
i am a portal element
</div>,
document.body,
);

const Test = () => {
const buttonRef = React.useRef<HTMLButtonElement>(null);
React.useEffect(() => {
const button = buttonRef.current;
if (button) {
button.addEventListener('mousedown', (e) => {
console.log('button natives down');
e.stopPropagation();
e.preventDefault();
});
}
}, []);

return (
<div
style={{
padding: 100,
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
gap: 100,
}}
>
<Trigger
popupPlacement="right"
action={['click']}
builtinPlacements={builtinPlacements}
popup={
<div style={popupBorderStyle}>
i am a click popup
<PortalPopup />
</div>
}
onPopupVisibleChange={(visible) => {
console.log('visible change:', visible);
}}
>
<button>Click Me</button>
</Trigger>

<button
onMouseDown={(e) => {
console.log('button down');
e.stopPropagation();
e.preventDefault();
}}
>
Stop Pop & Prevent Default
</button>
<button ref={buttonRef}>Native Stop Pop & Prevent Default</button>
</div>
);
};

export default Test;
3 changes: 3 additions & 0 deletions src/Popup/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface PopupProps {
onMouseEnter?: React.MouseEventHandler<HTMLDivElement>;
onMouseLeave?: React.MouseEventHandler<HTMLDivElement>;
onPointerEnter?: React.MouseEventHandler<HTMLDivElement>;
onMouseDownCapture?: React.MouseEventHandler<HTMLDivElement>;
zIndex?: number;

mask?: boolean;
Expand Down Expand Up @@ -105,6 +106,7 @@ const Popup = React.forwardRef<HTMLDivElement, PopupProps>((props, ref) => {
onMouseEnter,
onMouseLeave,
onPointerEnter,
onMouseDownCapture,

ready,
offsetX,
Expand Down Expand Up @@ -255,6 +257,7 @@ const Popup = React.forwardRef<HTMLDivElement, PopupProps>((props, ref) => {
onMouseLeave={onMouseLeave}
onPointerEnter={onPointerEnter}
onClick={onClick}
onMouseDownCapture={onMouseDownCapture}
>
{arrow && (
<Arrow
Expand Down
12 changes: 11 additions & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,9 @@ export function generateTrigger(
React.useState<VoidFunction>(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<React.MouseEvent, 'clientX' | 'clientY'>,
Expand Down Expand Up @@ -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
Expand Down
35 changes: 33 additions & 2 deletions tests/basic.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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(<div className="portal" />, document.body);

const Demo = () => {
return (
<>
<Trigger action="click" popup={<Portal />}>
<div className="target" />
</Trigger>
<div className="outer" />
</>
);
};

const { container } = render(<Demo />);
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();
});
});

0 comments on commit 0fa6243

Please sign in to comment.