Files
element-plus/packages/components/tooltip/src/tooltip.vue
Xiao 8d2f08108b fix(components): [popper] fix arrow overflow issue (#20049)
* fix(components): [popper] fix arrow overflow issue

Add popperArrowProp to popperContentProps to ensure usePopper
correctly applies arrow options, preventing the arrow from
overflowing the popper container edges.

* fix(components): [popper] change arrowOffset to ref for reactivity

* feat(components): [tooltip] add arrow-offset property

- Allow users to control the padding of the tooltip arrow
- Prevents the arrow from touching the popper's edge

Reference: https://popper.js.org/docs/v2/modifiers/arrow/#padding

* fix(components): [popper] add reactive watch for arrowOffset prop

* feat(components): [popper] remove unused arrowOffset prop

Removes unused arrowOffset prop and related logic
- Removes arrowOffset from PopperContentInjectionContext
- Removes arrowOffset prop watching and state management
- Cleans up related test cases

* docs(components): [tooltip] update version requirement

update version requirement for arrow-offset prop

* docs(components): [tooltip] update `arrow-offset` version

* Update docs/en-US/component/tooltip.md

Co-authored-by: kooriookami <38392315+kooriookami@users.noreply.github.com>

---------

Co-authored-by: sea <45450994+warmthsea@users.noreply.github.com>
Co-authored-by: kooriookami <38392315+kooriookami@users.noreply.github.com>
2025-05-07 10:02:28 +00:00

201 lines
4.6 KiB
Vue

<template>
<el-popper ref="popperRef" :role="role">
<el-tooltip-trigger
:disabled="disabled"
:trigger="trigger"
:trigger-keys="triggerKeys"
:virtual-ref="virtualRef"
:virtual-triggering="virtualTriggering"
>
<slot v-if="$slots.default" />
</el-tooltip-trigger>
<el-tooltip-content
ref="contentRef"
:aria-label="ariaLabel"
:boundaries-padding="boundariesPadding"
:content="content"
:disabled="disabled"
:effect="effect"
:enterable="enterable"
:fallback-placements="fallbackPlacements"
:hide-after="hideAfter"
:gpu-acceleration="gpuAcceleration"
:offset="offset"
:persistent="persistent"
:popper-class="kls"
:popper-style="popperStyle"
:placement="placement"
:popper-options="popperOptions"
:arrow-offset="arrowOffset"
:pure="pure"
:raw-content="rawContent"
:reference-el="referenceEl"
:trigger-target-el="triggerTargetEl"
:show-after="showAfter"
:strategy="strategy"
:teleported="teleported"
:transition="transition"
:virtual-triggering="virtualTriggering"
:z-index="zIndex"
:append-to="appendTo"
>
<slot name="content">
<span v-if="rawContent" v-html="content" />
<span v-else>{{ content }}</span>
</slot>
<el-popper-arrow v-if="showArrow" />
</el-tooltip-content>
</el-popper>
</template>
<script lang="ts" setup>
import {
computed,
onDeactivated,
provide,
readonly,
ref,
toRef,
unref,
watch,
} from 'vue'
import { ElPopper, ElPopperArrow } from '@element-plus/components/popper'
import { isBoolean } from '@element-plus/utils'
import {
useDelayedToggle,
useId,
useNamespace,
usePopperContainer,
} from '@element-plus/hooks'
import { TOOLTIP_INJECTION_KEY } from './constants'
import { tooltipEmits, useTooltipModelToggle, useTooltipProps } from './tooltip'
import ElTooltipTrigger from './trigger.vue'
import ElTooltipContent from './content.vue'
import type { TooltipContentInstance } from './content'
import type { PopperInstance } from '@element-plus/components/popper'
defineOptions({
name: 'ElTooltip',
})
const props = defineProps(useTooltipProps)
const emit = defineEmits(tooltipEmits)
usePopperContainer()
const ns = useNamespace('tooltip')
const id = useId()
const popperRef = ref<PopperInstance>()
const contentRef = ref<TooltipContentInstance>()
const updatePopper = () => {
const popperComponent = unref(popperRef)
if (popperComponent) {
popperComponent.popperInstanceRef?.update()
}
}
const open = ref(false)
const toggleReason = ref<Event>()
const { show, hide, hasUpdateHandler } = useTooltipModelToggle({
indicator: open,
toggleReason,
})
const { onOpen, onClose } = useDelayedToggle({
showAfter: toRef(props, 'showAfter'),
hideAfter: toRef(props, 'hideAfter'),
autoClose: toRef(props, 'autoClose'),
open: show,
close: hide,
})
const controlled = computed(
() => isBoolean(props.visible) && !hasUpdateHandler.value
)
const kls = computed(() => {
return [ns.b(), props.popperClass!]
})
provide(TOOLTIP_INJECTION_KEY, {
controlled,
id,
open: readonly(open),
trigger: toRef(props, 'trigger'),
onOpen: (event?: Event) => {
onOpen(event)
},
onClose: (event?: Event) => {
onClose(event)
},
onToggle: (event?: Event) => {
if (unref(open)) {
onClose(event)
} else {
onOpen(event)
}
},
onShow: () => {
emit('show', toggleReason.value)
},
onHide: () => {
emit('hide', toggleReason.value)
},
onBeforeShow: () => {
emit('before-show', toggleReason.value)
},
onBeforeHide: () => {
emit('before-hide', toggleReason.value)
},
updatePopper,
})
watch(
() => props.disabled,
(disabled) => {
if (disabled && open.value) {
open.value = false
}
}
)
const isFocusInsideContent = (event?: FocusEvent) => {
return contentRef.value?.isFocusInsideContent(event)
}
onDeactivated(() => open.value && hide())
defineExpose({
/**
* @description el-popper component instance
*/
popperRef,
/**
* @description el-tooltip-content component instance
*/
contentRef,
/**
* @description validate current focus event is trigger inside el-tooltip-content
*/
isFocusInsideContent,
/**
* @description update el-popper component instance
*/
updatePopper,
/**
* @description expose onOpen function to mange el-tooltip open state
*/
onOpen,
/**
* @description expose onOpen function to mange el-tooltip open state
*/
onClose,
/**
* @description expose hide function
*/
hide,
})
</script>