点击嵌套 Popover 的下拉导致外层 Popover 关闭怎么解决
2026/9/12 17:39:52 网站建设 项目流程

点击嵌套 Popover 的下拉导致外层 Popover 关闭怎么解决

【免费下载链接】mantineA fully featured React components library项目地址: https://gitcode.com/GitHub_Trending/ma/mantine

在 Mantine 中,当Popover.Dropdown里放了一个SelectDatePickerMenu等自带下拉的组件时,点击内层下拉会立刻把外层 Popover 关掉。本文给出这个问题的原因和官方修复方法:把内层组件的下拉改为不经过 Portal 渲染(withinPortal={false}),使内层下拉成为外层 Popover 的后代节点,外层就不会再把它当作"外部点击"。

问题现象

典型结构是外层PopoverPopover.Dropdown中渲染一个Select

import { Button, Popover, Select } from '@mantine/core'; function Demo() { return ( <Popover width={200} position="bottom" withArrow shadow="md"> <Popover.Target> <Button>Toggle popover</Button> </Popover.Target> <Popover.Dropdown> <Select placeholder="Choose your framework" data={[ { value: 'react', label: 'React' }, { value: 'vue', label: 'Vue' }, { value: 'angular', label: 'Angular' }, ]} /> </Popover.Dropdown> </Popover> ); }

打开外层 Popover 后,点击Select输入框时,Select的下拉还没展开,外层 Popover 就已经关闭了。可参考仓库中的官方 FAQ nested-popover-closes。

原因:内层下拉被 Portal 挂到了 document.body

两个机制共同导致了这个问题:

  1. 下拉默认渲染在 Portal 中。Mantine 7.0.0 起,PopoverHoverCardTooltip等所有 overlay 组件的withinPortal默认为true(见 7.0.0 changelog),下拉通过Portal挂到document.body上。这样做的目的是让下拉能浮在所有元素之上,即使父元素有overflow: hidden也能正确定位。

  2. Popover 用 use-click-outside 检测外部点击。Popover组件内部通过 use-click-outside hook 监听mousedowntouchstart事件,只有当点击路径同时包含 target 和 dropdown 两个节点时才不关闭(见 Popover 源码)。而内层Select的下拉被渲染到了document.body,不在外层 Popover 的 target/dropdown 节点之内,于是点击内层下拉被外层判定为"外部点击",外层 Popover 随之关闭。

这一点与 Popover 官方文档 的 "Nested popovers" 小节一致:嵌套 Popover 要求子组件不通过 Portal 渲染,否则 outside clicks 会关掉所有 Popover。

修复:给内层组件设置 withinPortal={false}

官方 FAQ 的修复方法是:在内层下拉组件上设置withinPortal={false},让它渲染在外层Popover.Dropdown内部而不是document.body

注意withinPortal不一定直接暴露在你的组件上。以Select为例,这个选项藏在comboboxProps里(Select 文档 的 "Inside Popover" 小节):

import { Button, Popover, Select } from '@mantine/core'; function Demo() { return ( <Popover width={200} position="bottom" withArrow shadow="md"> <Popover.Target> <Button>Toggle popover</Button> </Popover.Target> <Popover.Dropdown> <Select comboboxProps={{ withinPortal: false }} placeholder="Choose your framework" data={[ { value: 'react', label: 'React' }, { value: 'vue', label: 'Vue' }, { value: 'angular', label: 'Angular' }, ]} /> </Popover.Dropdown> </Popover> ); }

完整的修复示例见 NestedPopovers.demo.tsx 中的NestedPopoversWorking

不同组件对应的 prop

withinPortal={false}要设置在哪,取决于你使用的组件如何把 prop 传给底层的Popover/Combobox

内层组件写法依据
Select/MultiSelect/Cascader等 Combobox 组件comboboxProps={{ withinPortal: false }}select.mdx、cascader.mdx
AutocompletewithinPortal: falseautocomplete.mdx
其他 overlay 组件popoverProps={{ withinPortal: false }}(如ColorInput在 7.0.0 起通过popoverProps传递)7.0.0 changelog

官方 FAQ 的原话是:这个选项可能是另一个 prop 的一部分(例如SelectcomboboxProps),需要查看你正在使用的组件文档确认应该用哪个 prop。

验证方式

修改后在浏览器中实际操作:打开外层 Popover,再点击内层Select输入框。预期结果是内层下拉正常展开、选中选项时外层 Popover 保持打开,而不再是"内层下拉一出现外层就消失"。

限制说明

  • 修复对象是内层下拉组件,不要给外层Popover设置withinPortal={false},那是改变外层自身的渲染位置,不是本问题的解决方式。
  • 该方案只解决"点击内层下拉导致外层关闭"的问题。外层 Popover 自身仍受closeOnClickOutside(默认true)控制,真正点击到两个 Popover 之外的区域时外层依旧会关闭,这是正常行为。
  • 如果内层组件文档中没有暴露对应的透传 prop,以该组件的文档为准查找禁用 Portal 的方式;不要在源文档未说明的情况下猜测 prop 名。

更多上下文见 Popover 文档的 Nested popovers 小节 和 FAQ 原文。

【免费下载链接】mantineA fully featured React components library项目地址: https://gitcode.com/GitHub_Trending/ma/mantine

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询