Files
element-plus/packages/components/tooltip/src/content.vue
jeremywu dd19cae2bc refactor(components): popper composables (#5035)
* refactor(components): popper composables

- Refactor popper composables

* updates

* updates for tooltip

* Updates for popper. TODO: fix controlled tooltip animation

* Fix controlled mode popper animation issue

* Add new feature for customizing tooltip theme

* Fix popover and popconfirm error

* - Add Collection component for wrapping a collection of component
- Add FocusTrap component for trap focus for popups
- Add RovingFocus component for roving focus component type
- Adjust dropdown component based on these newly added components
- Add popper-trigger component for placing the trigger
- TODO: Finish current dropdown component, and all component's tests plus documents

* Refactor popper

* Complete organizing popper

* Almost finish dropdown

* Update popper tests

* update only-child test

* Finish focus trap component test

* Finish tooltip content test

* Finish tooltip trigger tests

* Finish tooltip tests

* finish tests for Collection and RovingFocusGroup

* Fix test cases for timeselect & select & popover

* Fix popover, popconfirm, menu bug and test cases

* Fix select-v2 test error caused by updating popper

* Fix date-picker test issue for updating popper

* fix test cases

* Fix eslint

* Rebase dev & fix tests

* Remove unused code
2022-01-04 09:15:15 +08:00

188 lines
5.4 KiB
Vue

<template>
<el-teleport
v-if="shouldRenderTeleport"
:disabled="!teleported"
:container="POPPER_CONTAINER_SELECTOR"
>
<transition :name="transition" @after-leave="onTransitionLeave">
<el-popper-content
v-if="shouldRenderPopperContent"
v-show="shouldShowPopperContent"
ref="contentRef"
v-bind="$attrs"
:aria-hidden="ariaHidden"
:boundaries-padding="boundariesPadding"
:fallback-placements="fallbackPlacements"
:gpu-acceleration="gpuAcceleration"
:offset="offset"
:placement="placement"
:popper-options="popperOptions"
:strategy="strategy"
:effect="effect"
:enterable="enterable"
:pure="pure"
:popper-class="popperClass"
:popper-style="[popperStyle, contentStyle]"
:reference-el="referenceEl"
:z-index="zIndex"
@mouseenter="onContentEnter"
@mouseleave="onContentLeave"
>
<slot />
<el-visually-hidden :id="id" role="tooltip">
{{ ariaLabel }}
</el-visually-hidden>
</el-popper-content>
</transition>
</el-teleport>
</template>
<script lang="ts">
import { computed, defineComponent, inject, nextTick, ref, unref } from 'vue'
import { ElPopperContent } from '@element-plus/components/popper'
import { ElVisuallyHidden } from '@element-plus/components/visual-hidden'
import { ElTeleport } from '@element-plus/components/teleport'
import { composeEventHandlers } from '@element-plus/utils/dom'
import {
useEscapeKeydown,
POPPER_CONTAINER_SELECTOR,
useDelayedRender,
} from '@element-plus/hooks'
import { useTooltipContentProps } from './tooltip'
import { TOOLTIP_INJECTION_KEY } from './tokens'
export default defineComponent({
name: 'ElTooltipContent',
components: {
ElTeleport,
ElPopperContent,
ElVisuallyHidden,
},
inheritAttrs: false,
props: useTooltipContentProps,
setup(props) {
const contentRef = ref<InstanceType<typeof ElPopperContent> | null>(null)
const intermediateOpen = ref(false)
const entering = ref(false)
const leaving = ref(false)
const { controlled, id, open, trigger, onClose, onOpen, onShow, onHide } =
inject(TOOLTIP_INJECTION_KEY, undefined)!
const persistentRef = computed(() => {
// For testing, we would always want the content to be rendered
// to the DOM, so we need to return true here.
if (process.env.NODE_ENV === 'test') {
return true
}
return props.persistent
})
const contentStyle = computed(() => (props.style ?? {}) as any)
const shouldRenderTeleport = computed(() => {
if (unref(persistentRef)) return true
return unref(unref(entering) ? open : intermediateOpen)
})
const shouldRenderPopperContent = computed(() => {
if (unref(persistentRef)) return true
return unref(unref(leaving) ? open : intermediateOpen)
})
const shouldShowPopperContent = computed(() => {
// This is for control persistent mode transition
// When persistent this element will always be rendered, we simply use v-show to control the transition
if (unref(persistentRef)) {
return unref(unref(leaving) ? open : intermediateOpen)
}
return true
})
const ariaHidden = computed(
() =>
!(unref(shouldRenderPopperContent) && unref(shouldShowPopperContent))
)
useEscapeKeydown(onClose)
useDelayedRender({
indicator: open,
intermediateIndicator: intermediateOpen,
shouldSetIntermediate: (step) => {
// we don't want to set the intermediateOpen because we want the transition to finish.
// After transition finishes, with the hook after-leave we can call intermediate.value = false
return step === 'hide' ? false : true
},
beforeShow: () => {
// indicates interruption of hide transition
if (unref(leaving)) {
leaving.value = false
intermediateOpen.value = false
}
entering.value = true
},
beforeHide: () => {
// indicates interruption of show transition
if (unref(entering)) {
entering.value = false
return
}
leaving.value = true
},
afterShow: () => {
if (!unref(open)) return
entering.value = false
onShow()
nextTick(() => {
unref(contentRef)?.updatePopper()
})
},
afterHide: () => {
if (unref(open)) return
// prevent the content from hiding if it's still open
onHide()
},
})
const onTransitionLeave = () => {
if (unref(open)) return
leaving.value = false
intermediateOpen.value = false
}
const stopWhenControlled = () => {
if (unref(controlled)) return true
}
const onContentEnter = composeEventHandlers(stopWhenControlled, () => {
if (props.enterable) {
onOpen()
}
})
const onContentLeave = composeEventHandlers(stopWhenControlled, () => {
if (unref(trigger) === 'hover') {
onClose()
}
})
return {
ariaHidden,
entering,
leaving,
id,
intermediateOpen,
contentStyle,
contentRef,
shouldRenderTeleport,
shouldRenderPopperContent,
shouldShowPopperContent,
open,
POPPER_CONTAINER_SELECTOR,
onContentEnter,
onContentLeave,
onTransitionLeave,
}
},
})
</script>