u
This commit is contained in:
1 parent
5a3f484b8b
commit
0172becffb
4 files changed
+688
-119
No files matched your search
@@ -1,31 +1,70 @@
|
||||
@use '../_styles/_variables.scss' as *;
|
||||
@use 'sass:color';
|
||||
|
||||
$popup-prefix: "#{$prefix}-popup";
|
||||
$popup-prefix: '#{$prefix}-popup';
|
||||
|
||||
.#{$popup-prefix} {
|
||||
position: absolute;
|
||||
background-color: #333;
|
||||
color: #fff;
|
||||
background-color: $bg-color-container;
|
||||
color: $text-color-base;
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
font-size: 13px;
|
||||
border-radius: 2px;
|
||||
z-index: 1000;
|
||||
box-sizing: border-box;
|
||||
|
||||
box-shadow: $shadow-popup;
|
||||
text-align: center;
|
||||
&-arrow {
|
||||
position: absolute;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
z-index: 1;
|
||||
&::before {
|
||||
position: absolute;
|
||||
content: '';
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
transform: rotate(45deg);
|
||||
// background: $bg-color-container;
|
||||
background: red;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
// 上方弹出,箭头在底部
|
||||
&[data-popup-placement^='top'] > &-arrow {
|
||||
bottom: -4px;
|
||||
}
|
||||
|
||||
&-arrow::before {
|
||||
position: absolute;
|
||||
content: '';
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: #333;
|
||||
transform: rotate(45deg);
|
||||
box-sizing: border-box;
|
||||
// 下方弹出,箭头在顶部
|
||||
&[data-popup-placement^='bottom'] > &-arrow {
|
||||
top: -4px;
|
||||
}
|
||||
|
||||
// 左侧弹出,箭头在右侧
|
||||
&[data-popup-placement^='left'] > &-arrow {
|
||||
right: -4px;
|
||||
}
|
||||
|
||||
// 右侧弹出,箭头在左侧
|
||||
&[data-popup-placement^='right'] > &-arrow {
|
||||
left: -4px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 普通淡入淡出动画 */
|
||||
.popup-fade-enter-from,
|
||||
.popup-fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.popup-fade-enter-to,
|
||||
.popup-fade-leave-from {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.popup-fade-enter-active,
|
||||
.popup-fade-leave-active {
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
@@ -1,140 +1,284 @@
|
||||
import type { VNode } from 'vue'
|
||||
import { arrow, offset, useFloating } from '@floating-ui/vue'
|
||||
import type { PropType, VNode } from 'vue'
|
||||
import { arrow, autoUpdate, flip, offset, useFloating } from '@floating-ui/vue'
|
||||
|
||||
import {
|
||||
cloneVNode,
|
||||
defineComponent,
|
||||
nextTick,
|
||||
onBeforeUnmount,
|
||||
onMounted,
|
||||
ref,
|
||||
Teleport,
|
||||
Transition,
|
||||
watch,
|
||||
} from 'vue'
|
||||
|
||||
import { getPrefixCls } from '../_utils/getPrefixCls'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'APopup',
|
||||
props: {
|
||||
trigger: {
|
||||
type: String as () => 'hover' | 'click' | 'focus',
|
||||
type: [String, Array] as PropType<
|
||||
'hover' | 'click' | 'focus' | 'contextMenu' | Array<'hover' | 'click' | 'focus' | 'contextMenu'>
|
||||
>,
|
||||
default: 'hover',
|
||||
},
|
||||
placement: {
|
||||
type: String as PropType<
|
||||
'top' | 'bottom' | 'left' | 'right'
|
||||
| 'top-start' | 'top-end' | 'bottom-start' | 'bottom-end'
|
||||
| 'left-start' | 'left-end' | 'right-start' | 'right-end'
|
||||
>,
|
||||
default: 'bottom',
|
||||
},
|
||||
showArrow: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
destroyOnClose: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
appendTo: {
|
||||
type: [String, Object] as PropType<string | HTMLElement>,
|
||||
default: 'body',
|
||||
},
|
||||
autoFitPopupMinWidth: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
autoFitPopupWidth: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
popupClass: {
|
||||
type: [String, Array, Object],
|
||||
default: '',
|
||||
},
|
||||
popupStyle: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
content: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
setup(props, { slots }) {
|
||||
const prefixCls = getPrefixCls('popup')
|
||||
|
||||
const triggerRef = ref<HTMLElement | null>(null)
|
||||
const floatingRef = ref<HTMLElement | null>(null)
|
||||
const arrowRef = ref<HTMLElement | null>(null)
|
||||
const triggerRef = ref<HTMLElement | null>(null) // 触发 引用
|
||||
const floatingRef = ref<HTMLElement | null>(null) // float 引用
|
||||
const arrowRef = ref<HTMLElement | null>(null) // 箭头引用
|
||||
const visible = ref(false)
|
||||
const hasRendered = ref(false)
|
||||
let cleanup: (() => void) | null = null
|
||||
|
||||
// 浮层样式和箭头数据
|
||||
const floatingStyles = ref({})
|
||||
const middlewareData = ref({ arrow: {} as any })
|
||||
const { floatingStyles, middlewareData, placement, update } = useFloating(
|
||||
triggerRef,
|
||||
floatingRef,
|
||||
{
|
||||
placement: props.placement,
|
||||
middleware: [offset(10), flip(), arrow({ element: arrowRef })],
|
||||
// whileElementsMounted: autoUpdate,
|
||||
},
|
||||
)
|
||||
|
||||
let floatingInstance: ReturnType<typeof useFloating> | null = null
|
||||
|
||||
// 初始化 FloatingUI,确保触发节点 DOM 已挂载
|
||||
const initFloating = async () => {
|
||||
if (!triggerRef.value || floatingInstance)
|
||||
return
|
||||
await nextTick()
|
||||
const { x, y, strategy, middlewareData: md, update } = useFloating(
|
||||
triggerRef,
|
||||
floatingRef,
|
||||
{
|
||||
placement: 'top',
|
||||
middleware: [offset(10), arrow({ element: arrowRef })],
|
||||
},
|
||||
)
|
||||
floatingInstance = { x, y, strategy, middlewareData: md, update }
|
||||
|
||||
// watch 可见性变化,更新位置
|
||||
watch(
|
||||
visible,
|
||||
(v) => {
|
||||
if (v && floatingInstance?.update)
|
||||
floatingInstance.update()
|
||||
},
|
||||
)
|
||||
|
||||
// watch x/y 变化,实时更新样式
|
||||
watch([x, y, md], () => {
|
||||
floatingStyles.value = {
|
||||
position: strategy.value,
|
||||
left: `${x.value ?? 0}px`,
|
||||
top: `${y.value ?? 0}px`,
|
||||
}
|
||||
middlewareData.value = md.value
|
||||
}, { immediate: true })
|
||||
// 获取真实DOM
|
||||
function getDOM(refValue: any): HTMLElement | null {
|
||||
if (!refValue)
|
||||
return null
|
||||
if (refValue instanceof HTMLElement)
|
||||
return refValue
|
||||
if (refValue.$el instanceof HTMLElement)
|
||||
return refValue.$el
|
||||
return null
|
||||
}
|
||||
|
||||
// popup 显隐
|
||||
const showPopup = () => { visible.value = true; initFloating() }
|
||||
const hidePopup = () => { visible.value = false }
|
||||
const togglePopup = () => { visible.value = !visible.value; initFloating() }
|
||||
// 清理监听滚动
|
||||
const clearAutoUpdate = () => {
|
||||
if (cleanup) {
|
||||
cleanup()
|
||||
cleanup = null
|
||||
}
|
||||
}
|
||||
|
||||
// 自动监听滚动
|
||||
const setupAutoUpdate = async () => {
|
||||
await nextTick()
|
||||
const triggerEl = getDOM(triggerRef.value)
|
||||
const floatingEl = getDOM(floatingRef.value)
|
||||
if (triggerEl && floatingEl) {
|
||||
clearAutoUpdate()
|
||||
cleanup = autoUpdate(triggerEl, floatingEl, update, {
|
||||
ancestorScroll: true,
|
||||
ancestorResize: true,
|
||||
elementResize: true,
|
||||
layoutShift: true,
|
||||
})
|
||||
update()
|
||||
}
|
||||
}
|
||||
|
||||
// 浮动框宽度适配
|
||||
const fitFloatingWidth = () => {
|
||||
const triggerEl = getDOM(triggerRef.value)
|
||||
const floatEl = getDOM(floatingRef.value)
|
||||
if (!triggerEl || !floatEl)
|
||||
return
|
||||
if (props.autoFitPopupMinWidth)
|
||||
floatEl.style.minWidth = `${triggerEl.offsetWidth}px`
|
||||
if (props.autoFitPopupWidth)
|
||||
floatEl.style.width = `${triggerEl.offsetWidth}px`
|
||||
}
|
||||
|
||||
// 准备浮动框
|
||||
const prepareFloating = async () => {
|
||||
if (!hasRendered.value)
|
||||
hasRendered.value = true
|
||||
fitFloatingWidth()
|
||||
}
|
||||
|
||||
const showPopup = async () => {
|
||||
await prepareFloating()
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
const hidePopup = () => {
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
const togglePopup = async () => {
|
||||
await prepareFloating()
|
||||
visible.value = !visible.value
|
||||
}
|
||||
|
||||
// 事件绑定
|
||||
const triggers = Array.isArray(props.trigger) ? props.trigger : [props.trigger]
|
||||
const eventProps: Record<string, (e: Event) => void> = {}
|
||||
if (props.trigger === 'hover') {
|
||||
|
||||
if (triggers.includes('hover')) {
|
||||
eventProps.onMouseenter = showPopup
|
||||
eventProps.onMouseleave = hidePopup
|
||||
}
|
||||
else if (props.trigger === 'click') {
|
||||
if (triggers.includes('click')) {
|
||||
eventProps.onClick = togglePopup
|
||||
}
|
||||
else if (props.trigger === 'focus') {
|
||||
if (triggers.includes('focus')) {
|
||||
eventProps.onFocus = showPopup
|
||||
eventProps.onBlur = hidePopup
|
||||
}
|
||||
if (triggers.includes('contextMenu')) {
|
||||
eventProps.onContextmenu = (e: Event) => {
|
||||
e.preventDefault()
|
||||
togglePopup()
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
// 点击其他地方关闭
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
const target = event.target as Node
|
||||
const triggerEl = getDOM(triggerRef.value)
|
||||
const floatingEl = getDOM(floatingRef.value)
|
||||
if (!triggerEl?.contains(target) && !floatingEl?.contains(target)) {
|
||||
visible.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取 Teleport 挂载目标
|
||||
const getTeleportTarget = () => {
|
||||
if (props.appendTo instanceof HTMLElement)
|
||||
return props.appendTo
|
||||
if (typeof props.appendTo === 'string') {
|
||||
const el = document.querySelector(props.appendTo)
|
||||
if (el)
|
||||
return el
|
||||
}
|
||||
return 'body'
|
||||
}
|
||||
|
||||
// 渲染 trigger slot
|
||||
const renderTriggerSlot = () => {
|
||||
const slotContent = slots.reference?.() || slots.default?.() || []
|
||||
let triggerSlot: VNode | null = null
|
||||
if (slotContent.length === 1)
|
||||
return cloneVNode(slotContent[0], { ref: triggerRef, ...eventProps })
|
||||
return (
|
||||
<span ref={triggerRef} style="display:inline-flex;" {...eventProps}>
|
||||
{slotContent.map(v => cloneVNode(v))}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
if (slotContent.length === 1) {
|
||||
const child = slotContent[0]
|
||||
const isHoisted = child.patchFlag != null && child.patchFlag === -1
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', handleClickOutside)
|
||||
})
|
||||
|
||||
if (!isHoisted) {
|
||||
triggerSlot = cloneVNode(child, { ref: triggerRef, ...eventProps })
|
||||
}
|
||||
else {
|
||||
triggerSlot = (
|
||||
<span ref={triggerRef} style="display:inline-flex;" {...eventProps}>
|
||||
{slotContent.map(v => cloneVNode(v))}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener('click', handleClickOutside)
|
||||
clearAutoUpdate()
|
||||
})
|
||||
|
||||
watch(visible, async (val) => {
|
||||
if (val) {
|
||||
setupAutoUpdate()
|
||||
}
|
||||
else {
|
||||
triggerSlot = (
|
||||
<span ref={triggerRef} style="display:inline-flex;" {...eventProps}>
|
||||
{slotContent.map(v => cloneVNode(v))}
|
||||
</span>
|
||||
)
|
||||
clearAutoUpdate()
|
||||
}
|
||||
})
|
||||
|
||||
return () => {
|
||||
// 懒加载
|
||||
const shouldRenderPopup = props.destroyOnClose ? visible.value : hasRendered.value
|
||||
|
||||
return (
|
||||
<>
|
||||
{triggerSlot}
|
||||
{renderTriggerSlot()}
|
||||
|
||||
<Teleport to="body">
|
||||
{visible.value && (
|
||||
<div ref={floatingRef} class={prefixCls} style={floatingStyles.value}>
|
||||
Popup 内容
|
||||
<div
|
||||
class={`${prefixCls}-arrow`}
|
||||
ref={arrowRef}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: middlewareData.value.arrow?.x != null
|
||||
? `${middlewareData.value.arrow.x}px`
|
||||
: undefined,
|
||||
top: middlewareData.value.arrow?.y != null
|
||||
? `${middlewareData.value.arrow.y}px`
|
||||
: undefined,
|
||||
}}
|
||||
/>
|
||||
<Teleport to={getTeleportTarget()}>
|
||||
{shouldRenderPopup && (
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '0px',
|
||||
left: '0px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Transition
|
||||
name="popup-fade"
|
||||
appear
|
||||
>
|
||||
<div
|
||||
v-show={visible.value}
|
||||
ref={floatingRef}
|
||||
class={[prefixCls, props.popupClass]}
|
||||
style={{
|
||||
...floatingStyles.value,
|
||||
position: 'absolute',
|
||||
...props.popupStyle,
|
||||
}}
|
||||
data-popup-placement={placement.value}
|
||||
>
|
||||
{slots.content ? slots.content() : props.content}
|
||||
{props.showArrow && (
|
||||
<div
|
||||
class={`${prefixCls}-arrow`}
|
||||
ref={arrowRef}
|
||||
style={{
|
||||
left:
|
||||
middlewareData.value.arrow?.x != null
|
||||
? `${middlewareData.value.arrow.x}px`
|
||||
: '',
|
||||
top:
|
||||
middlewareData.value.arrow?.y != null
|
||||
? `${middlewareData.value.arrow.y}px`
|
||||
: '',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
)}
|
||||
</Teleport>
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
import type { PropType, VNode } from 'vue'
|
||||
import { arrow, autoUpdate, flip, offset, useFloating } from '@floating-ui/vue'
|
||||
|
||||
import {
|
||||
cloneVNode,
|
||||
defineComponent,
|
||||
nextTick,
|
||||
onBeforeUnmount,
|
||||
onMounted,
|
||||
ref,
|
||||
Teleport,
|
||||
Transition,
|
||||
watch,
|
||||
} from 'vue'
|
||||
import { getPrefixCls } from '../_utils/getPrefixCls'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'APopup',
|
||||
props: {
|
||||
trigger: {
|
||||
type: [String, Array] as PropType<
|
||||
'hover' | 'click' | 'focus' | 'contextMenu' | Array<'hover' | 'click' | 'focus' | 'contextMenu'>
|
||||
>,
|
||||
default: 'hover',
|
||||
},
|
||||
placement: {
|
||||
type: String as PropType<
|
||||
'top' | 'bottom' | 'left' | 'right'
|
||||
| 'top-start' | 'top-end' | 'bottom-start' | 'bottom-end'
|
||||
| 'left-start' | 'left-end' | 'right-start' | 'right-end'
|
||||
>,
|
||||
default: 'bottom',
|
||||
},
|
||||
showArrow: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
destroyOnClose: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
appendTo: {
|
||||
type: [String, Object] as PropType<string | HTMLElement>,
|
||||
default: 'body',
|
||||
},
|
||||
autoFitPopupMinWidth: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
autoFitPopupWidth: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
popupClass: {
|
||||
type: [String, Array, Object],
|
||||
default: '',
|
||||
},
|
||||
popupStyle: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
content: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
setup(props, { slots }) {
|
||||
const prefixCls = getPrefixCls('popup')
|
||||
|
||||
const triggerRef = ref<HTMLElement | null>(null) // 触发 引用
|
||||
const floatingRef = ref<HTMLElement | null>(null) // float 引用
|
||||
const innerFloatingRef = ref<HTMLElement | null>(null) // 真正的 float 引用
|
||||
const arrowRef = ref<HTMLElement | null>(null) // 箭头引用
|
||||
const visible = ref(false)
|
||||
const hasRendered = ref(false)
|
||||
|
||||
const { floatingStyles, middlewareData, placement, update } = useFloating(
|
||||
triggerRef,
|
||||
floatingRef,
|
||||
{
|
||||
placement: props.placement,
|
||||
middleware: [offset(10), flip(), arrow({ element: arrowRef })],
|
||||
whileElementsMounted: autoUpdate,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
const prepareFloating = async () => {
|
||||
// 首次渲染标记
|
||||
if (!hasRendered.value)
|
||||
hasRendered.value = true
|
||||
|
||||
// 等待 DOM 更新
|
||||
await nextTick()
|
||||
|
||||
// 绑定浮动元素引用
|
||||
floatingRef.value = innerFloatingRef.value
|
||||
update() // 先更新位置
|
||||
|
||||
// 获取触发元素的原生 DOM
|
||||
const triggerEl: HTMLElement | null = (() => {
|
||||
const val = triggerRef.value
|
||||
if (!val)
|
||||
return null
|
||||
return (val as any)?.$el || val
|
||||
})()
|
||||
|
||||
// 自动设置宽度或最小宽度
|
||||
if (triggerEl && innerFloatingRef.value) {
|
||||
if (props.autoFitPopupMinWidth) {
|
||||
innerFloatingRef.value.style.minWidth = `${triggerEl.offsetWidth}px`
|
||||
}
|
||||
if (props.autoFitPopupWidth) {
|
||||
innerFloatingRef.value.style.width = `${triggerEl.offsetWidth}px`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const showPopup = async () => {
|
||||
await prepareFloating()
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
const hidePopup = () => {
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
const togglePopup = async () => {
|
||||
await prepareFloating()
|
||||
visible.value = !visible.value
|
||||
}
|
||||
|
||||
const triggers = Array.isArray(props.trigger) ? props.trigger : [props.trigger]
|
||||
const eventProps: Record<string, (e: Event) => void> = {}
|
||||
|
||||
if (triggers.includes('hover')) {
|
||||
eventProps.onMouseenter = showPopup
|
||||
eventProps.onMouseleave = hidePopup
|
||||
}
|
||||
if (triggers.includes('click')) {
|
||||
eventProps.onClick = togglePopup
|
||||
}
|
||||
if (triggers.includes('focus')) {
|
||||
eventProps.onFocus = showPopup
|
||||
eventProps.onBlur = hidePopup
|
||||
}
|
||||
if (triggers.includes('contextMenu')) {
|
||||
eventProps.onContextmenu = (e: Event) => {
|
||||
e.preventDefault()
|
||||
togglePopup()
|
||||
}
|
||||
}
|
||||
|
||||
// 点击其他地方关闭
|
||||
const onClickOutside = (event: MouseEvent) => {
|
||||
const target = event.target as Node
|
||||
const triggerEl = (triggerRef.value as any)?.$el || triggerRef.value
|
||||
const floatingEl = floatingRef.value
|
||||
|
||||
if (!triggerEl?.contains(target) && !floatingEl?.contains(target)) {
|
||||
visible.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', onClickOutside)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener('click', onClickOutside)
|
||||
})
|
||||
|
||||
// Teleport 挂载目标
|
||||
const teleportTarget = () => {
|
||||
if (props.appendTo instanceof HTMLElement)
|
||||
return props.appendTo
|
||||
if (typeof props.appendTo === 'string') {
|
||||
const el = document.querySelector(props.appendTo)
|
||||
if (el)
|
||||
return el
|
||||
}
|
||||
return 'body'
|
||||
}
|
||||
|
||||
return () => {
|
||||
const slotContent = slots.reference?.() || slots.default?.() || []
|
||||
let triggerSlot: VNode | null = null
|
||||
|
||||
if (slotContent.length === 1) {
|
||||
const child = slotContent[0]
|
||||
triggerSlot = cloneVNode(child, { ref: triggerRef, ...eventProps })
|
||||
}
|
||||
else {
|
||||
triggerSlot = (
|
||||
<span ref={triggerRef} style="display:inline-flex;" {...eventProps}>
|
||||
{slotContent.map(v => cloneVNode(v))}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
// 懒加载标志
|
||||
const shouldRenderPopup = props.destroyOnClose ? visible.value : hasRendered.value
|
||||
|
||||
return (
|
||||
<>
|
||||
{triggerSlot}
|
||||
|
||||
<Teleport to={teleportTarget()}>
|
||||
{shouldRenderPopup && (
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '0px',
|
||||
left: '0px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Transition
|
||||
name="popup-fade"
|
||||
appear
|
||||
onAfterLeave={() => {
|
||||
floatingRef.value = null
|
||||
update()
|
||||
}}
|
||||
>
|
||||
<div
|
||||
v-show={visible.value}
|
||||
ref={innerFloatingRef}
|
||||
class={[prefixCls, props.popupClass]}
|
||||
style={{
|
||||
...floatingStyles.value,
|
||||
position: 'absolute',
|
||||
...props.popupStyle,
|
||||
}}
|
||||
data-popup-placement={placement.value}
|
||||
>
|
||||
{slots.content ? slots.content() : props.content}
|
||||
{props.showArrow && (
|
||||
<div
|
||||
class={`${prefixCls}-arrow`}
|
||||
ref={arrowRef}
|
||||
style={{
|
||||
left:
|
||||
middlewareData.value.arrow?.x != null
|
||||
? `${middlewareData.value.arrow.x}px`
|
||||
: '',
|
||||
top:
|
||||
middlewareData.value.arrow?.y != null
|
||||
? `${middlewareData.value.arrow.y}px`
|
||||
: '',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
)}
|
||||
</Teleport>
|
||||
</>
|
||||
)
|
||||
}
|
||||
},
|
||||
})
|
||||
Reference in new issue
Block a user