fix(popover): can't be nested with other pop-able components, closes #872

This commit is contained in:
07akioni 2021-10-05 23:53:28 +08:00
parent 587ea809bb
commit 9b059bcd5d
9 changed files with 173 additions and 69 deletions

View File

@ -29,6 +29,7 @@
- Fix `n-date-picker`'s `date` type of `action` validate error.
- Fix `n-data-table` throws error when using `selection` and `summary` together, closes [#1276](https://github.com/TuSimple/naive-ui/issues/1276).
- Fix `n-data-table` selection column's width is collapsed when it is set to fixed, closes [#1283](https://github.com/TuSimple/naive-ui/issues/1283).
- Fix `n-popconfirm` can't be nested in `n-tooltip`, closes [#872](https://github.com/TuSimple/naive-ui/issues/872).
## 2.19.3 (2021-09-28)

View File

@ -29,6 +29,7 @@
- 修复 `n-date-picker``date` 类型的 `action` 验证错误
- 修复 `n-data-table``selection``summary` 一起使用时报错,关闭 [#1276](https://github.com/TuSimple/naive-ui/issues/1276)
- 修复 `n-data-table` 勾选列的宽度在设为 fixed 时候塌陷,关闭 [#1283](https://github.com/TuSimple/naive-ui/issues/1283)
- 修复 `n-popconfirm` 不能被嵌套于 `n-tooltip` 内,关闭 [#872](https://github.com/TuSimple/naive-ui/issues/872).
## 2.19.3 (2021-09-28)

View File

@ -134,7 +134,7 @@
"vdirs": "^0.1.4",
"vfonts": "^0.1.0",
"vooks": "^0.2.6",
"vueuc": "^0.4.12"
"vueuc": "^0.4.13"
},
"sideEffects": false,
"homepage": "https://www.naiveui.com",

View File

@ -37,6 +37,7 @@ export type PopconfirmProps = ExtractPublicPropTypes<typeof popconfirmProps>
export default defineComponent({
name: 'Popconfirm',
props: popconfirmProps,
__popover__: true,
setup (props) {
const { mergedClsPrefixRef } = useConfig()
const themeRef = useTheme(

View File

@ -20,6 +20,7 @@ manual-position
header
hoist-debug
nested-debug
nested2-debug
```
## API

View File

@ -0,0 +1,27 @@
# Nested Debug
```html
<n-tooltip placement="bottom">
<template #trigger>
<n-popover trigger="click">
<template #trigger>
<n-button>Test</n-button>
</template>
Popover
</n-popover>
</template>
Tooltip
</n-tooltip>
<n-tooltip trigger="hover">
<template #trigger>
<n-popconfirm>
<template #trigger>
<n-button>tooltip内嵌popconfirm</n-button>
</template>
一切都将一去杳然,任何人都无法将其捕获。
</n-popconfirm>
</template>
如果它长得像鸭子,走起来像鸭子,叫起来也像鸭子,那它一定是个鸭子。
</n-tooltip>
```

View File

@ -14,7 +14,7 @@ import {
cloneVNode,
watchEffect
} from 'vue'
import { VBinder, VTarget, FollowerPlacement } from 'vueuc'
import { VBinder, VTarget, FollowerPlacement, BinderInst } from 'vueuc'
import { useMergedState, useCompitable, useIsMounted, useMemo } from 'vooks'
import { call, keep, getFirstSlotVNode, warnOnce } from '../../_utils'
import type {
@ -36,19 +36,22 @@ const triggerEventMap = {
focus: ['onFocus', 'onBlur'],
click: ['onClick'],
hover: ['onMouseenter', 'onMouseleave'],
manual: []
manual: [],
nested: ['onFocus', 'onBlur', 'onMouseenter', 'onMouseleave', 'onClick']
} as const
export interface TriggerEventHandlers {
onClick: (e: MouseEvent) => void
onMouseenter: (e: MouseEvent) => void
onMouseleave: (e: MouseEvent) => void
onFocus: (e: FocusEvent) => void
onBlur: (e: FocusEvent) => void
}
function appendEvents (
vNode: VNode,
trigger: PopoverTrigger,
events: {
onClick: (e: MouseEvent) => void
onMouseenter: (e: MouseEvent) => void
onMouseleave: (e: MouseEvent) => void
onFocus: (e: FocusEvent) => void
onBlur: (e: FocusEvent) => void
}
trigger: PopoverTrigger | 'nested',
events: TriggerEventHandlers
): void {
triggerEventMap[trigger].forEach((eventName) => {
if (!vNode.props) vNode.props = {}
@ -151,6 +154,11 @@ export const popoverBaseProps = {
MaybeArray<(value: boolean) => void>
>,
zIndex: Number,
internalSyncTargetWithParent: Boolean,
internalInheritedEventHandlers: {
type: Array as PropType<TriggerEventHandlers[]>,
default: () => []
},
/** @deprecated */
onShow: [Function, Array] as PropType<
MaybeArray<(value: boolean) => void> | undefined
@ -183,6 +191,7 @@ export default defineComponent({
name: 'Popover',
inheritAttrs: false,
props: popoverProps,
__popover__: true,
setup (props) {
if (__DEV__) {
watchEffect(() => {
@ -219,6 +228,7 @@ export default defineComponent({
})
}
const isMountedRef = useIsMounted()
const binderInstRef = ref<BinderInst | null>(null)
// setup show
const controlledShowRef = computed(() => props.show)
const uncontrolledShowRef = ref(props.defaultShow)
@ -246,8 +256,6 @@ export default defineComponent({
if (props.overlap) return false
return compatibleShowArrowRef.value
})
// trigger
let triggerVNode: VNode | null = null
// bodyInstance
let bodyInstance: BodyInstance | null = null
const showTimerIdRef = ref<number | null>(null)
@ -372,7 +380,7 @@ export default defineComponent({
uncontrolledShowRef.value = value
}
function getTriggerElement (): HTMLElement {
return triggerVNode?.el as HTMLElement
return binderInstRef.value?.targetRef as HTMLElement
}
function setBodyInstance (value: BodyInstance | null): void {
bodyInstance = value
@ -391,6 +399,7 @@ export default defineComponent({
internalRenderBodyRef: toRef(props, 'internalRenderBody')
})
return {
binderInstRef,
positionManually: positionManuallyRef,
mergedShowConsideringDisabledProp: mergedShowConsideringDisabledPropRef,
// if to show popover body
@ -403,65 +412,127 @@ export default defineComponent({
handleMouseLeave,
handleFocus,
handleBlur,
setTriggerVNode (v: VNode | null) {
triggerVNode = v
},
syncPosition
}
},
render () {
return h(VBinder, null, {
default: () => {
const { positionManually, $slots: slots } = this
let triggerVNode: VNode | null
if (!positionManually) {
if (slots.activator) {
triggerVNode = getFirstSlotVNode(slots, 'activator')
} else {
triggerVNode = getFirstSlotVNode(slots, 'trigger')
}
if (triggerVNode) {
triggerVNode = cloneVNode(triggerVNode)
triggerVNode =
triggerVNode.type === textVNodeType
? h('span', [triggerVNode])
: triggerVNode
appendEvents(
triggerVNode,
positionManually ? 'manual' : this.trigger,
{
onClick: this.handleClick,
onMouseenter: this.handleMouseEnter,
onMouseleave: this.handleMouseLeave,
onFocus: this.handleFocus,
onBlur: this.handleBlur
}
)
}
this.setTriggerVNode(triggerVNode)
}
// We need to subscribe it. Sometimes rerender won't ge triggered.
// `mergedShowConsideringDisabledProp` is not the final disabled status.
// In ellpisis it's dynamic.
void this.mergedShowConsideringDisabledProp
const mergedShow = this.getMergedShow()
return [
positionManually
? null
: h(VTarget, null, {
default: () => triggerVNode
}),
h(
NPopoverBody,
keep(this.$props, bodyPropKeys, {
...this.$attrs,
showArrow: this.mergedShowArrow,
show: mergedShow
}),
slots
)
]
const { positionManually, $slots: slots } = this
let triggerVNode: VNode | null
let popoverInside = false
if (!positionManually) {
if (slots.activator) {
triggerVNode = getFirstSlotVNode(slots, 'activator')
} else {
triggerVNode = getFirstSlotVNode(slots, 'trigger')
}
})
if (triggerVNode) {
triggerVNode = cloneVNode(triggerVNode)
triggerVNode =
triggerVNode.type === textVNodeType
? h('span', [triggerVNode])
: triggerVNode
const handlers = {
onClick: this.handleClick,
onMouseenter: this.handleMouseEnter,
onMouseleave: this.handleMouseLeave,
onFocus: this.handleFocus,
onBlur: this.handleBlur
}
if ((triggerVNode.type as any)?.__popover__) {
popoverInside = true
// We assume that there's no DOM event handlers on popover element
if (!triggerVNode.props) {
triggerVNode.props = {
internalSyncTargetWithParent: true,
internalInheritedEventHandlers: []
}
}
triggerVNode.props.internalSyncTargetWithParent = true
if (!triggerVNode.props.internalInheritedEventHandlers) {
triggerVNode.props.internalInheritedEventHandlers = [handlers]
} else {
triggerVNode.props.internalInheritedEventHandlers = [
handlers,
...triggerVNode.props.internalInheritedEventHandlers
]
}
} else {
const { internalInheritedEventHandlers } = this
const ascendantAndCurrentHandlers: TriggerEventHandlers[] = [
handlers,
...internalInheritedEventHandlers
]
const mergedHandlers: TriggerEventHandlers = {
onBlur: (e: FocusEvent) => {
ascendantAndCurrentHandlers.forEach((_handlers) => {
_handlers.onBlur(e)
})
},
onFocus: (e: FocusEvent) => {
ascendantAndCurrentHandlers.forEach((_handlers) => {
_handlers.onBlur(e)
})
},
onClick: (e: MouseEvent) => {
ascendantAndCurrentHandlers.forEach((_handlers) => {
_handlers.onClick(e)
})
},
onMouseenter: (e: MouseEvent) => {
ascendantAndCurrentHandlers.forEach((_handlers) => {
_handlers.onMouseenter(e)
})
},
onMouseleave: (e: MouseEvent) => {
ascendantAndCurrentHandlers.forEach((_handlers) => {
_handlers.onMouseleave(e)
})
}
}
appendEvents(
triggerVNode,
internalInheritedEventHandlers
? 'nested'
: positionManually
? 'manual'
: this.trigger,
mergedHandlers
)
}
}
}
return (
<VBinder
ref="binderInstRef"
syncTarget={!popoverInside}
syncTargetWithParent={this.internalSyncTargetWithParent}
>
{{
default: () => {
// We need to subscribe it. Sometimes rerender won't ge triggered.
// `mergedShowConsideringDisabledProp` is not the final disabled status.
// In ellpisis it's dynamic.
void this.mergedShowConsideringDisabledProp
const mergedShow = this.getMergedShow()
return [
positionManually
? null
: h(VTarget, null, {
default: () => triggerVNode
}),
h(
NPopoverBody,
keep(this.$props, bodyPropKeys, {
...this.$attrs,
showArrow: this.mergedShowArrow,
show: mergedShow
}),
slots
)
]
}
}}
</VBinder>
)
}
})

View File

@ -32,6 +32,7 @@ export type PopselectProps = ExtractPublicPropTypes<typeof popselectProps>
export default defineComponent({
name: 'Popselect',
props: popselectProps,
__popover__: true,
setup (props) {
const themeRef = useTheme(
'Popselect',

View File

@ -21,6 +21,7 @@ export type TooltipProps = ExtractPublicPropTypes<typeof tooltipProps>
export default defineComponent({
name: 'Tooltip',
props: tooltipProps,
__popover__: true,
setup (props) {
const themeRef = useTheme(
'Tooltip',