diff --git a/packages/renderless/src/search/index.ts b/packages/renderless/src/search/index.ts index fa0b5dc50..66562963a 100644 --- a/packages/renderless/src/search/index.ts +++ b/packages/renderless/src/search/index.ts @@ -76,7 +76,11 @@ export const searchEnterKey = export const clickOutside = ({ parent, props, state }: Pick) => (event: Event) => { - if (!parent.$el.contains(event.target)) { + // 优先使用 event.composedPath() 来判断事件源是否在组件内部,以兼容 Shadow DOM。 + // 在 Shadow DOM 中,事件冒泡穿过 Shadow Root 后,event.target 会被重定向为 host 元素, + // 导致传统的 contains 判断失效。composedPath 则能提供真实的事件路径。 + const path = event.composedPath && event.composedPath() + if (path ? !path.includes(parent.$el) : !parent.$el.contains(event.target)) { state.show = false props.mini && !state.currentValue && (state.collapse = true) } diff --git a/packages/vue-directive/src/clickoutside.ts b/packages/vue-directive/src/clickoutside.ts index e87cac687..af7f77874 100644 --- a/packages/vue-directive/src/clickoutside.ts +++ b/packages/vue-directive/src/clickoutside.ts @@ -35,16 +35,18 @@ if (!isServer) { const createDocumentHandler = (el, binding, vnode) => function (mouseup = {}, mousedown = {}) { - let popperElm = vnode.context.popperElm || (vnode.context.state && vnode.context.state.popperElm) + const popperElm = vnode.context.popperElm || (vnode.context.state && vnode.context.state.popperElm) - if ( - !mouseup?.target || - !mousedown?.target || - el.contains(mouseup.target) || - el.contains(mousedown.target) || - el === mouseup.target || - (popperElm && (popperElm.contains(mouseup.target) || popperElm.contains(mousedown.target))) - ) { + // 使用 event.composedPath() 来处理 Shadow DOM 场景。 + // composedPath() 会返回事件的完整路径,即使事件穿过了 Shadow DOM 的边界。 + // 这确保了即使 popperElm 在 Shadow DOM 外部(或组件本身在 Shadow DOM 内部), + // 我们也能准确判断点击是否发生在组件或其 popper 内部。 + const mousedownPath = (mousedown?.composedPath && mousedown.composedPath()) || [mousedown?.target] + const mouseupPath = (mouseup?.composedPath && mouseup.composedPath()) || [mouseup.target] + const isClickInEl = mousedownPath.includes(el) || mouseupPath.includes(el) + const isClickInPopper = popperElm && (mousedownPath.includes(popperElm) || mouseupPath.includes(popperElm)) + + if (!mousedown.target || !mouseup.target || isClickInEl || isClickInPopper) { return } @@ -56,10 +58,6 @@ const createDocumentHandler = (el, binding, vnode) => } /** - * v-clickoutside - * @desc 点击元素外面才会触发的事件 - * @example - * 两个修饰符,mousedown、mouseup * 当没有修饰符时,需要同时满足在目标元素外同步按下和释放鼠标才会触发回调。 * ```html *
// 在元素外部点击时触发 @@ -110,7 +108,6 @@ export default { if (nodeList.length === 0 && startClick) { startClick = null } - delete el[nameSpace] } }