index.ts 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. import { isRef, onMounted, ref, unref, watchEffect } from 'vue'
  2. import { isClient, unrefElement } from '@vueuse/core'
  3. import { isNil } from 'lodash-unified'
  4. import { arrow as arrowCore, computePosition } from '@floating-ui/dom'
  5. import { buildProps, keysOf } from '@element-plus/utils'
  6. import type { Ref, ToRefs } from 'vue'
  7. import type {
  8. ComputePositionReturn,
  9. Middleware,
  10. Placement,
  11. SideObject,
  12. Strategy,
  13. VirtualElement,
  14. } from '@floating-ui/dom'
  15. export const useFloatingProps = buildProps({} as const)
  16. export type UseFloatingProps = ToRefs<{
  17. middleware: Array<Middleware>
  18. placement: Placement
  19. strategy: Strategy
  20. }>
  21. type ElementRef = Parameters<typeof unrefElement>['0']
  22. const unrefReference = (
  23. elRef: ElementRef | Ref<VirtualElement | undefined>
  24. ) => {
  25. if (!isClient) return
  26. if (!elRef) return elRef
  27. const unrefEl = unrefElement(elRef as ElementRef)
  28. if (unrefEl) return unrefEl
  29. return isRef(elRef) ? unrefEl : (elRef as VirtualElement)
  30. }
  31. export const getPositionDataWithUnit = <T extends Record<string, number>>(
  32. record: T | undefined,
  33. key: keyof T
  34. ) => {
  35. const value = record?.[key]
  36. return isNil(value) ? '' : `${value}px`
  37. }
  38. export const useFloating = ({
  39. middleware,
  40. placement,
  41. strategy,
  42. }: UseFloatingProps) => {
  43. const referenceRef = ref<HTMLElement | VirtualElement>()
  44. const contentRef = ref<HTMLElement>()
  45. const x = ref<number>()
  46. const y = ref<number>()
  47. const middlewareData = ref<ComputePositionReturn['middlewareData']>({})
  48. const states = {
  49. x,
  50. y,
  51. placement,
  52. strategy,
  53. middlewareData,
  54. } as const
  55. const update = async () => {
  56. if (!isClient) return
  57. const referenceEl = unrefReference(referenceRef)
  58. const contentEl = unrefElement(contentRef)
  59. if (!referenceEl || !contentEl) return
  60. const data = await computePosition(referenceEl, contentEl, {
  61. placement: unref(placement),
  62. strategy: unref(strategy),
  63. middleware: unref(middleware),
  64. })
  65. keysOf(states).forEach((key) => {
  66. states[key].value = data[key]
  67. })
  68. }
  69. onMounted(() => {
  70. watchEffect(() => {
  71. update()
  72. })
  73. })
  74. return {
  75. ...states,
  76. update,
  77. referenceRef,
  78. contentRef,
  79. }
  80. }
  81. export type ArrowMiddlewareProps = {
  82. arrowRef: Ref<HTMLElement | null | undefined>
  83. padding?: number | SideObject
  84. }
  85. export const arrowMiddleware = ({
  86. arrowRef,
  87. padding,
  88. }: ArrowMiddlewareProps): Middleware => {
  89. return {
  90. name: 'arrow',
  91. options: {
  92. element: arrowRef,
  93. padding,
  94. },
  95. fn(args) {
  96. const arrowEl = unref(arrowRef)
  97. if (!arrowEl) return {}
  98. return arrowCore({
  99. element: arrowEl,
  100. padding,
  101. }).fn(args)
  102. },
  103. }
  104. }