Skip to content

Commit e7e902a

Browse files
committed
fix(Popup): close previous popup when moving to another nested component
1 parent c02237c commit e7e902a

File tree

4 files changed

+118
-61
lines changed

4 files changed

+118
-61
lines changed

packages/components/popup/Popup.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ const Popup = forwardRef<PopupInstanceFunctions, PopupProps>((originalProps, ref
112112

113113
const { triggerElementIsString, getTriggerElement, getTriggerNode, getPopupProps } = useTrigger({
114114
triggerElement,
115+
popupElement,
115116
content,
116117
disabled,
117118
trigger,

packages/components/popup/__tests__/popup.test.tsx

Lines changed: 104 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -264,62 +264,6 @@ describe('Popup 组件测试', () => {
264264
expect(popupContainer).toBeNull();
265265
});
266266

267-
test('测试浮层嵌套', async () => {
268-
const wrappedTriggerElement = '嵌套触发元素';
269-
const wrappedPopupTestId = 'wrapped-popup-test-id';
270-
const wrappedPopupText = '嵌套弹出层内容';
271-
const { getByText, queryByTestId } = render(
272-
<Popup
273-
placement="top"
274-
trigger="click"
275-
destroyOnClose
276-
content={
277-
<Popup
278-
placement="top"
279-
trigger="click"
280-
destroyOnClose
281-
content={<div data-testid={wrappedPopupTestId}>{wrappedPopupText}</div>}
282-
>
283-
<div data-testid={popupTestId}>{wrappedTriggerElement}</div>
284-
</Popup>
285-
}
286-
>
287-
{triggerElement}
288-
</Popup>,
289-
);
290-
291-
// 初始时,所有浮层都不存在
292-
const popupElement1 = await waitFor(() => queryByTestId(popupTestId));
293-
const wrappedPopupElement1 = await waitFor(() => queryByTestId(wrappedPopupTestId));
294-
expect(popupElement1).toBeNull();
295-
expect(wrappedPopupElement1).toBeNull();
296-
297-
// 触发浮层和嵌套浮层
298-
act(() => {
299-
fireEvent.click(getByText(triggerElement));
300-
});
301-
act(() => {
302-
fireEvent.click(getByText(wrappedTriggerElement));
303-
});
304-
305-
// 所有浮层都展示出来
306-
const popupElement2 = await waitFor(() => queryByTestId(popupTestId));
307-
const wrappedPopupElement2 = await waitFor(() => queryByTestId(wrappedPopupTestId));
308-
expect(popupElement2).not.toBeNull();
309-
expect(wrappedPopupElement2).not.toBeNull();
310-
311-
// 嵌套元素的浮层触发 mouseDown,不应该关闭任何浮层
312-
act(() => {
313-
fireEvent.mouseDown(queryByTestId(wrappedPopupTestId));
314-
});
315-
316-
// 所有浮层都展示出来
317-
const popupElement3 = await waitFor(() => queryByTestId(popupTestId));
318-
const wrappedPopupElement3 = await waitFor(() => queryByTestId(wrappedPopupTestId));
319-
expect(popupElement3).not.toBeNull();
320-
expect(wrappedPopupElement3).not.toBeNull();
321-
});
322-
323267
test('异常情况:浮层隐藏时点击其他地方,浮层不可以展示出来', async () => {
324268
const testClassName = 'test-class-name';
325269
render(
@@ -395,3 +339,107 @@ describe('Popup 组件测试', () => {
395339
});
396340
});
397341
});
342+
343+
describe('Popup 嵌套组件测试', () => {
344+
const popupTestId = 'popup-test-id';
345+
const wrappedPopupTestId = 'wrapped-popup-test-id';
346+
const triggerElement = '外层触发元素';
347+
const wrappedTriggerElement = '内层触发元素';
348+
const wrappedPopupText = '内层浮层内容';
349+
350+
const renderNestedPopup = (trigger: 'click' | 'hover') =>
351+
render(
352+
<Popup
353+
placement="top"
354+
trigger={trigger}
355+
destroyOnClose
356+
content={
357+
<Popup
358+
placement="top"
359+
trigger={trigger}
360+
destroyOnClose
361+
content={<div data-testid={wrappedPopupTestId}>{wrappedPopupText}</div>}
362+
>
363+
<div data-testid={popupTestId}>{wrappedTriggerElement}</div>
364+
</Popup>
365+
}
366+
>
367+
{triggerElement}
368+
</Popup>,
369+
);
370+
371+
test('trigger="click"', async () => {
372+
const { getByText, queryByTestId } = renderNestedPopup('click');
373+
374+
// 初始状态,浮层不存在
375+
expect(queryByTestId(popupTestId)).toBeNull();
376+
expect(queryByTestId(wrappedPopupTestId)).toBeNull();
377+
378+
// click 外层触发器
379+
act(() => {
380+
fireEvent.click(getByText(triggerElement));
381+
});
382+
const popupElement = await waitFor(() => queryByTestId(popupTestId));
383+
expect(popupElement).not.toBeNull();
384+
385+
// click 内层触发器
386+
act(() => {
387+
fireEvent.click(getByText(wrappedTriggerElement));
388+
});
389+
const wrappedPopupElement = await waitFor(() => queryByTestId(wrappedPopupTestId));
390+
expect(wrappedPopupElement).not.toBeNull();
391+
expect(wrappedPopupElement).toHaveTextContent(wrappedPopupText);
392+
393+
// mouseDown 内层内容不关闭
394+
act(() => {
395+
fireEvent.mouseDown(queryByTestId(wrappedPopupTestId) as HTMLElement);
396+
});
397+
await waitFor(() => {
398+
expect(popupElement).not.toBeNull();
399+
expect(wrappedPopupElement).not.toBeNull();
400+
});
401+
});
402+
403+
test('trigger="hover"', async () => {
404+
const { getByText, getByTestId, queryByTestId } = renderNestedPopup('hover');
405+
406+
// 初始状态,浮层不存在
407+
expect(queryByTestId(popupTestId)).toBeNull();
408+
expect(queryByTestId(wrappedPopupTestId)).toBeNull();
409+
410+
// hover 外层触发器
411+
act(() => {
412+
fireEvent.mouseEnter(getByText(triggerElement));
413+
});
414+
const popupElement = await waitFor(() => queryByTestId(popupTestId));
415+
expect(popupElement).not.toBeNull();
416+
417+
// hover 内层触发器
418+
act(() => {
419+
fireEvent.mouseEnter(getByTestId(popupTestId));
420+
});
421+
const wrappedPopupElement = await waitFor(() => queryByTestId(wrappedPopupTestId));
422+
expect(wrappedPopupElement).not.toBeNull();
423+
expect(wrappedPopupElement).toHaveTextContent(wrappedPopupText);
424+
425+
// mouseLeave 内层触发器
426+
act(() => {
427+
fireEvent.mouseLeave(getByTestId(popupTestId));
428+
});
429+
430+
// 等待内层浮层销毁
431+
await waitFor(() => {
432+
expect(queryByTestId(wrappedPopupTestId)).toBeNull();
433+
});
434+
435+
// mouseLeave 外层触发器
436+
act(() => {
437+
fireEvent.mouseLeave(getByText(triggerElement));
438+
});
439+
440+
// 等待外层浮层销毁
441+
await waitFor(() => {
442+
expect(queryByTestId(popupTestId)).toBeNull();
443+
});
444+
});
445+
});

packages/components/popup/hooks/useTrigger.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,16 @@ const isEventFromDisabledElement = (e: Event | React.SyntheticEvent, container:
1313
return !!(disabledEl && container.contains(disabledEl));
1414
};
1515

16-
export default function useTrigger({ triggerElement, content, disabled, trigger, visible, onVisibleChange, delay }) {
16+
export default function useTrigger({
17+
triggerElement,
18+
content,
19+
disabled,
20+
trigger,
21+
visible,
22+
onVisibleChange,
23+
delay,
24+
popupElement,
25+
}) {
1726
const { classPrefix } = useConfig();
1827

1928
const triggerElementIsString = typeof triggerElement === 'string';
@@ -53,8 +62,8 @@ export default function useTrigger({ triggerElement, content, disabled, trigger,
5362
const handleMouseLeave = (e: MouseEvent | React.MouseEvent) => {
5463
if (trigger !== 'hover' || hasPopupMouseDown.current) return;
5564
const relatedTarget = e.relatedTarget as HTMLElement;
56-
const isMovingToContent = relatedTarget?.closest?.(`.${classPrefix}-popup`);
57-
if (isMovingToContent) return;
65+
const isMovingToCurrentPopup = relatedTarget && popupElement && popupElement.contains(relatedTarget);
66+
if (isMovingToCurrentPopup) return;
5867
callFuncWithDelay({
5968
delay: exitDelay,
6069
callback: () => onVisibleChange(false, { e, trigger: 'trigger-element-hover' }),

packages/tdesign-react/CHANGELOG.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ spline: explain
1010
### 🚀 Features
1111

1212
- `DatePicker`: `presets` 支持 ReactNode,用于完全自定义渲染 @uyarn ([#4089](https://github.com/Tencent/tdesign-react/pull/4089))
13-
- `Dialog`: @RylanBot ([#3950](https://github.com/Tencent/tdesign-react/pull/3950))
13+
- `Dialog`: @RylanBot ([#3950](https://github.com/Tencent/tdesign-react/pull/3950))
1414
- 支持 `mode="full-screen"` 的弹窗
1515
- 支持 `draggable``mode="modeless"` 生效
1616
- `Form`: 支持 `getFieldsValue``getFieldValue` 返回未渲染的数值 @RylanBot ([#4050](https://github.com/Tencent/tdesign-react/pull/4050))
@@ -34,7 +34,6 @@ spline: explain
3434
- `InputNumber`: 修复大数计算前导零被错误清除的问题 @Liumingxun ([common#2394](https://github.com/Tencent/tdesign-common/pull/2394))
3535
- `Menu`: 优化 Safari 浏览器中点击展开图标没有变换方向的问题 @liweijie0812 ([#4056](https://github.com/Tencent/tdesign-react/pull/4056))
3636
- `Popup`:
37-
- 修复嵌套场景下,外层弹窗隐藏时,内层弹窗无法正常关闭的问题 @RylanBot ([#4085](https://github.com/Tencent/tdesign-react/pull/4085))
3837
- 修复 `triggerElement``disabled` 的场景下,`hover` 时无法正常显示弹出层的问题 @RylanBot ([#4085](https://github.com/Tencent/tdesign-react/pull/4085))
3938
- 修复 `content` 动态修改时,箭头位置不稳定的问题 @RylanBot ([#4062](https://github.com/Tencent/tdesign-react/pull/4062))
4039
- `Select`:

0 commit comments

Comments
 (0)