点击嵌套 Popover 的下拉导致外层 Popover 关闭怎么解决
【免费下载链接】mantineA fully featured React components library项目地址: https://gitcode.com/GitHub_Trending/ma/mantine
在 Mantine 中,当Popover.Dropdown里放了一个Select、DatePicker、Menu等自带下拉的组件时,点击内层下拉会立刻把外层 Popover 关掉。本文给出这个问题的原因和官方修复方法:把内层组件的下拉改为不经过 Portal 渲染(withinPortal={false}),使内层下拉成为外层 Popover 的后代节点,外层就不会再把它当作"外部点击"。
问题现象
典型结构是外层Popover的Popover.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
两个机制共同导致了这个问题:
下拉默认渲染在 Portal 中。Mantine 7.0.0 起,
Popover、HoverCard、Tooltip等所有 overlay 组件的withinPortal默认为true(见 7.0.0 changelog),下拉通过Portal挂到document.body上。这样做的目的是让下拉能浮在所有元素之上,即使父元素有overflow: hidden也能正确定位。Popover 用 use-click-outside 检测外部点击。
Popover组件内部通过 use-click-outside hook 监听mousedown、touchstart事件,只有当点击路径同时包含 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 |
Autocomplete | withinPortal: false | autocomplete.mdx |
| 其他 overlay 组件 | popoverProps={{ withinPortal: false }}(如ColorInput在 7.0.0 起通过popoverProps传递) | 7.0.0 changelog |
官方 FAQ 的原话是:这个选项可能是另一个 prop 的一部分(例如Select的comboboxProps),需要查看你正在使用的组件文档确认应该用哪个 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),仅供参考