2022-03-25 17:39:01 +08:00
|
|
|
import { onMounted, ref, unref, watch } from 'vue'
|
2022-03-25 15:43:54 +08:00
|
|
|
import { isClient, unrefElement } from '@vueuse/core'
|
|
|
|
import { isNil } from 'lodash-unified'
|
2022-03-25 17:39:01 +08:00
|
|
|
import { arrow as arrowCore, computePosition } from '@floating-ui/dom'
|
2022-03-25 15:43:54 +08:00
|
|
|
|
|
|
|
import { buildProps } from '@element-plus/utils'
|
|
|
|
|
2022-03-25 17:39:01 +08:00
|
|
|
import type { Ref, ToRefs } from 'vue'
|
2022-03-25 15:43:54 +08:00
|
|
|
import type {
|
|
|
|
ComputePositionReturn,
|
|
|
|
Middleware,
|
2022-03-25 17:39:01 +08:00
|
|
|
Placement,
|
2022-03-25 15:43:54 +08:00
|
|
|
SideObject,
|
2022-03-25 17:39:01 +08:00
|
|
|
Strategy,
|
2022-03-25 15:43:54 +08:00
|
|
|
VirtualElement,
|
|
|
|
} from '@floating-ui/dom'
|
|
|
|
|
|
|
|
export const useFloatingProps = buildProps({} as const)
|
|
|
|
|
|
|
|
export type UseFloatingProps = ToRefs<{
|
|
|
|
middleware: Array<Middleware>
|
|
|
|
placement: Placement
|
|
|
|
strategy: Strategy
|
|
|
|
}>
|
|
|
|
|
|
|
|
type ElementRef = Parameters<typeof unrefElement>['0']
|
|
|
|
|
|
|
|
const unrefReference = (
|
|
|
|
elRef: ElementRef | Ref<VirtualElement | undefined>
|
|
|
|
) => {
|
|
|
|
if (!isClient) return
|
|
|
|
if (!elRef) return elRef
|
|
|
|
const unrefEl = unrefElement(elRef as ElementRef)
|
|
|
|
if (unrefEl) return unrefEl
|
|
|
|
return elRef as VirtualElement
|
|
|
|
}
|
|
|
|
|
|
|
|
export const getPositionDataWithUnit = <T extends Record<string, number>>(
|
|
|
|
record: T | undefined,
|
|
|
|
key: keyof T
|
|
|
|
) => {
|
|
|
|
const value = record?.[key]
|
|
|
|
return isNil(value) ? '' : `${value}px`
|
|
|
|
}
|
|
|
|
|
|
|
|
export const useFloating = ({
|
|
|
|
middleware,
|
|
|
|
placement,
|
|
|
|
strategy,
|
|
|
|
}: UseFloatingProps) => {
|
|
|
|
const referenceRef = ref<HTMLElement | VirtualElement>()
|
|
|
|
const contentRef = ref<HTMLElement>()
|
|
|
|
const x = ref<string>()
|
|
|
|
const y = ref<string>()
|
|
|
|
const middlewareData = ref<ComputePositionReturn['middlewareData']>({})
|
|
|
|
|
|
|
|
const states = {
|
|
|
|
x,
|
|
|
|
y,
|
|
|
|
placement,
|
|
|
|
strategy,
|
|
|
|
middlewareData,
|
|
|
|
} as const
|
|
|
|
|
|
|
|
const update = async () => {
|
|
|
|
if (!isClient) return
|
|
|
|
|
|
|
|
const referenceEl = unrefReference(referenceRef)
|
|
|
|
const contentEl = unrefElement(contentRef)
|
|
|
|
|
|
|
|
if (!referenceEl || !contentEl) return
|
|
|
|
|
|
|
|
const data = await computePosition(referenceEl, contentEl, {
|
|
|
|
placement: unref(placement),
|
|
|
|
strategy: unref(strategy),
|
|
|
|
middleware: unref(middleware),
|
|
|
|
})
|
|
|
|
|
|
|
|
;['x', 'y'].forEach(
|
|
|
|
(key) => (data[key] = getPositionDataWithUnit(data as any, key))
|
|
|
|
)
|
|
|
|
|
|
|
|
Object.keys(states).forEach((key) => {
|
|
|
|
states[key].value = data[key]
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
watch(referenceRef, () => update())
|
|
|
|
|
|
|
|
watch(contentRef, () => update())
|
|
|
|
|
|
|
|
onMounted(() => update())
|
|
|
|
|
|
|
|
return {
|
|
|
|
...states,
|
|
|
|
update,
|
|
|
|
referenceRef,
|
|
|
|
contentRef,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export type ArrowMiddlewareProps = {
|
|
|
|
arrowRef: Ref<HTMLElement | undefined>
|
|
|
|
padding?: number | SideObject
|
|
|
|
}
|
|
|
|
|
|
|
|
export const arrowMiddleware = ({
|
|
|
|
arrowRef,
|
|
|
|
padding,
|
|
|
|
}: ArrowMiddlewareProps): Middleware => {
|
|
|
|
return {
|
|
|
|
name: 'arrow',
|
|
|
|
options: {
|
|
|
|
element: arrowRef,
|
|
|
|
padding,
|
|
|
|
},
|
|
|
|
|
|
|
|
fn(args) {
|
|
|
|
const arrowEl = unref(arrowRef)
|
|
|
|
if (!arrowEl) return {}
|
|
|
|
|
|
|
|
return arrowCore({
|
|
|
|
element: arrowEl,
|
|
|
|
padding,
|
|
|
|
}).fn(args)
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|