@veltra/compositions 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +14 -0
- package/dist/index.js +14 -0
- package/dist/use-component-props/index.d.ts +12 -0
- package/dist/use-component-props/index.js +52 -0
- package/dist/use-component-props/index.js.map +1 -0
- package/dist/use-config/index.d.ts +25 -0
- package/dist/use-config/index.js +51 -0
- package/dist/use-config/index.js.map +1 -0
- package/dist/use-drag/index.d.ts +47 -0
- package/dist/use-drag/index.js +87 -0
- package/dist/use-drag/index.js.map +1 -0
- package/dist/use-fallback-props/index.d.ts +31 -0
- package/dist/use-fallback-props/index.js +35 -0
- package/dist/use-fallback-props/index.js.map +1 -0
- package/dist/use-focus/index.d.ts +16 -0
- package/dist/use-focus/index.js +27 -0
- package/dist/use-focus/index.js.map +1 -0
- package/dist/use-form-component/index.d.ts +22 -0
- package/dist/use-form-component/index.js +15 -0
- package/dist/use-form-component/index.js.map +1 -0
- package/dist/use-lock/index.d.ts +28 -0
- package/dist/use-lock/index.js +36 -0
- package/dist/use-lock/index.js.map +1 -0
- package/dist/use-model/index.d.ts +35 -0
- package/dist/use-model/index.js +46 -0
- package/dist/use-model/index.js.map +1 -0
- package/dist/use-pop/index.d.ts +53 -0
- package/dist/use-pop/index.js +121 -0
- package/dist/use-pop/index.js.map +1 -0
- package/dist/use-reactive-size/index.d.ts +17 -0
- package/dist/use-reactive-size/index.js +40 -0
- package/dist/use-reactive-size/index.js.map +1 -0
- package/dist/use-resize-observer/index.d.ts +33 -0
- package/dist/use-resize-observer/index.js +92 -0
- package/dist/use-resize-observer/index.js.map +1 -0
- package/dist/use-transition/index.d.ts +18 -0
- package/dist/use-transition/index.js +11 -0
- package/dist/use-transition/index.js.map +1 -0
- package/dist/use-transition/type.d.ts +47 -0
- package/dist/use-transition/use-css-transition.js +129 -0
- package/dist/use-transition/use-css-transition.js.map +1 -0
- package/dist/use-transition/use-style-transition.js +127 -0
- package/dist/use-transition/use-style-transition.js.map +1 -0
- package/dist/use-transition/utils.js +41 -0
- package/dist/use-transition/utils.js.map +1 -0
- package/dist/use-virtual/index.d.ts +30 -0
- package/dist/use-virtual/index.js +67 -0
- package/dist/use-virtual/index.js.map +1 -0
- package/package.json +32 -0
- package/src/index.ts +25 -0
- package/src/use-component-props/index.ts +63 -0
- package/src/use-config/index.ts +77 -0
- package/src/use-drag/index.ts +151 -0
- package/src/use-fallback-props/index.ts +76 -0
- package/src/use-focus/index.ts +26 -0
- package/src/use-form-component/index.ts +31 -0
- package/src/use-lock/index.ts +50 -0
- package/src/use-model/index.ts +96 -0
- package/src/use-pop/index.ts +206 -0
- package/src/use-reactive-size/index.ts +53 -0
- package/src/use-resize-observer/index.ts +148 -0
- package/src/use-transition/index.ts +22 -0
- package/src/use-transition/type.ts +50 -0
- package/src/use-transition/use-css-transition.ts +186 -0
- package/src/use-transition/use-style-transition.ts +182 -0
- package/src/use-transition/utils.ts +56 -0
- package/src/use-virtual/index.ts +130 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { ComponentSize } from '@veltra/utils/types'
|
|
2
|
+
import { computed, type ComputedRef } from 'vue'
|
|
3
|
+
|
|
4
|
+
import { useConfig } from '../use-config'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 使用回滚属性,用于控制多级属性的使用优先级,如果多级属性中不存在该值,则使用全局配置中的属性,如再不存在则为undefined
|
|
8
|
+
* @param propsList 属性列表,最右边的属性优先级最高
|
|
9
|
+
* @param fallbackProps 要回滚的属性和默认值
|
|
10
|
+
*/
|
|
11
|
+
export function useFallbackProps<
|
|
12
|
+
F extends Record<string, any>,
|
|
13
|
+
R extends {
|
|
14
|
+
[key in keyof F]: ComputedRef<F[key]>
|
|
15
|
+
}
|
|
16
|
+
>(propsList: Record<string, any>[], fallbackProps: F): R {
|
|
17
|
+
const { config } = useConfig()
|
|
18
|
+
|
|
19
|
+
let result = {} as R
|
|
20
|
+
|
|
21
|
+
for (const key in fallbackProps) {
|
|
22
|
+
if (fallbackProps.hasOwnProperty(key)) {
|
|
23
|
+
const defaultValue = fallbackProps[key]
|
|
24
|
+
const ref = computed<any>(() => {
|
|
25
|
+
for (let i = propsList.length - 1; i > -1; --i) {
|
|
26
|
+
const props = propsList[i]!
|
|
27
|
+
|
|
28
|
+
if (props[key] !== undefined) {
|
|
29
|
+
return props[key]
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return config[key as string] ?? defaultValue
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
result[key as keyof F] = ref as R[keyof F]
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return result
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type FormFallbackProps = { size: ComponentSize; disabled: boolean; readonly: boolean }
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 表单组件的回滚属性
|
|
47
|
+
* @param propsList props列表
|
|
48
|
+
* @returns
|
|
49
|
+
*/
|
|
50
|
+
export function useFormFallbackProps(propsList: Record<string, any>[]): {
|
|
51
|
+
[key in keyof FormFallbackProps]: ComputedRef<FormFallbackProps[key]>
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 表单组件的回滚属性
|
|
56
|
+
* @param propsList props列表
|
|
57
|
+
* @param fallbackProps 回滚属性,可以只指定部分表单属性
|
|
58
|
+
* @returns
|
|
59
|
+
*/
|
|
60
|
+
export function useFormFallbackProps<F extends Partial<FormFallbackProps>>(
|
|
61
|
+
propsList: Record<string, any>[],
|
|
62
|
+
fallbackProps: F
|
|
63
|
+
): {
|
|
64
|
+
[key in keyof F]: key extends keyof FormFallbackProps
|
|
65
|
+
? ComputedRef<FormFallbackProps[key]>
|
|
66
|
+
: never
|
|
67
|
+
}
|
|
68
|
+
export function useFormFallbackProps(
|
|
69
|
+
propsList: Record<string, any>[],
|
|
70
|
+
fallbackProps?: Record<string, any>
|
|
71
|
+
): any {
|
|
72
|
+
if (!fallbackProps) {
|
|
73
|
+
fallbackProps = { size: 'default', disabled: false, readonly: false }
|
|
74
|
+
}
|
|
75
|
+
return useFallbackProps(propsList, fallbackProps)
|
|
76
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ref, type Ref } from 'vue'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 聚焦
|
|
5
|
+
* @param cb 回调
|
|
6
|
+
* @returns
|
|
7
|
+
*/
|
|
8
|
+
export function useFocus(cb?: (focused: boolean) => void): {
|
|
9
|
+
focus: Ref<boolean>
|
|
10
|
+
handleBlur: () => void
|
|
11
|
+
handleFocus: () => void
|
|
12
|
+
} {
|
|
13
|
+
const focus = ref(false)
|
|
14
|
+
|
|
15
|
+
const handleFocus = () => {
|
|
16
|
+
focus.value = true
|
|
17
|
+
cb?.(focus.value)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const handleBlur = () => {
|
|
21
|
+
focus.value = false
|
|
22
|
+
cb?.(focus.value)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return { focus, handleBlur, handleFocus }
|
|
26
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { FormContextInjectProps } from '@veltra/utils/types'
|
|
2
|
+
import { type InjectionKey, inject, provide } from 'vue'
|
|
3
|
+
|
|
4
|
+
type DIContext = {
|
|
5
|
+
/** 表单属性 */
|
|
6
|
+
formProps: FormContextInjectProps
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const FormComponentDIKey: InjectionKey<DIContext> = Symbol('FormComponentDIKey')
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 表单组件本身的组合式方法
|
|
13
|
+
* @param props 表单属性
|
|
14
|
+
* @returns
|
|
15
|
+
*/
|
|
16
|
+
export function useFormComponent(props: FormContextInjectProps): void
|
|
17
|
+
/**
|
|
18
|
+
* 表单内的组件的组合式方法
|
|
19
|
+
* @returns 提供一个form的上下文
|
|
20
|
+
*/
|
|
21
|
+
export function useFormComponent(): {
|
|
22
|
+
/** 是否在表单中 */
|
|
23
|
+
inForm: boolean
|
|
24
|
+
} & Partial<DIContext>
|
|
25
|
+
export function useFormComponent(props?: any): any {
|
|
26
|
+
if (props) {
|
|
27
|
+
return provide(FormComponentDIKey, { formProps: props })
|
|
28
|
+
}
|
|
29
|
+
const context = inject(FormComponentDIKey, undefined) || {}
|
|
30
|
+
return { inForm: !!context, ...context }
|
|
31
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { nextTick } from 'vue'
|
|
2
|
+
|
|
3
|
+
type Update = (fn: Function) => any
|
|
4
|
+
type Lock = (fn: Function) => Promise<void>
|
|
5
|
+
|
|
6
|
+
export interface Updater {
|
|
7
|
+
/**
|
|
8
|
+
* 更新
|
|
9
|
+
* @description 在非锁定时执行传入的函数
|
|
10
|
+
*/
|
|
11
|
+
update: Update
|
|
12
|
+
/**
|
|
13
|
+
* 更新并锁定
|
|
14
|
+
* @description 执行传入的函数,并锁定更新操作,直到函数执行完成
|
|
15
|
+
*/
|
|
16
|
+
updateAndLock: Lock
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 数据更新锁
|
|
21
|
+
* @description
|
|
22
|
+
* 更新锁主要用于防止组件数据更新时,循环触发更新。
|
|
23
|
+
*
|
|
24
|
+
* @returns 该函数返回两个函数:
|
|
25
|
+
* 1. update: 更新函数,在非锁定时执行传入的函数
|
|
26
|
+
* 2. lock: 锁定函数,执行时会锁定更新操作,直到锁定操作结束
|
|
27
|
+
*/
|
|
28
|
+
export function useUpdateLock(): Updater {
|
|
29
|
+
let lockedCount = 0
|
|
30
|
+
|
|
31
|
+
function update(fn: Function) {
|
|
32
|
+
if (lockedCount > 0) return
|
|
33
|
+
return fn()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function updateAndLock(fn: Function) {
|
|
37
|
+
lockedCount++
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
await fn()
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error(error)
|
|
43
|
+
}
|
|
44
|
+
await nextTick()
|
|
45
|
+
|
|
46
|
+
lockedCount--
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return { updateAndLock, update }
|
|
50
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { type Ref, ref, watch, shallowRef } from 'vue'
|
|
2
|
+
|
|
3
|
+
interface ModelOptions<Props extends Record<string, unknown>, Name extends keyof Props> {
|
|
4
|
+
/** 组件定义的属性 */
|
|
5
|
+
props: Props
|
|
6
|
+
/** 属性名称 */
|
|
7
|
+
propName?: Name
|
|
8
|
+
/** 事件触发函数 */
|
|
9
|
+
emit: (...args: any[]) => void
|
|
10
|
+
/** 是否为本地模式, 默认为true, 本地模式允许组件不受控来触发视图更新 */
|
|
11
|
+
local?: boolean | (() => boolean)
|
|
12
|
+
/** 默认值 */
|
|
13
|
+
defaultValue?: Props[Name]
|
|
14
|
+
/**
|
|
15
|
+
* 是否浅层响应
|
|
16
|
+
* @default false
|
|
17
|
+
*/
|
|
18
|
+
shallow?: boolean
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 返回一个基于提供的选项的响应式模型值。
|
|
23
|
+
* 该方法在将来可能会被替代, 目前使用是为了类型提示可用
|
|
24
|
+
* 如果 local 选项为true, 模型值将是响应式的,并与属性值同步。
|
|
25
|
+
* 如果 local 选项为 false,则模型值将是一个代理对象,具有 getter 和 setter。当值发生更改时,它会触发一个更新事件。
|
|
26
|
+
* @param options - 选项
|
|
27
|
+
* @returns - 一个模型值
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
export function useModel<
|
|
31
|
+
Props extends Record<string, any>,
|
|
32
|
+
Name extends keyof Props = 'modelValue'
|
|
33
|
+
>(
|
|
34
|
+
options: ModelOptions<Props, Name>
|
|
35
|
+
): Ref<Props[Name] | undefined> | { __v_isRef: boolean; value: Props[Name] } {
|
|
36
|
+
const {
|
|
37
|
+
props,
|
|
38
|
+
propName = 'modelValue',
|
|
39
|
+
emit,
|
|
40
|
+
local = true,
|
|
41
|
+
defaultValue,
|
|
42
|
+
shallow = false
|
|
43
|
+
} = options
|
|
44
|
+
|
|
45
|
+
if (local) {
|
|
46
|
+
const _default = props[propName] ?? defaultValue
|
|
47
|
+
const r = shallow ? shallowRef : ref
|
|
48
|
+
|
|
49
|
+
// 创建一个响应式对象
|
|
50
|
+
const _value = r(_default)
|
|
51
|
+
|
|
52
|
+
// 监听属性的变更
|
|
53
|
+
watch(
|
|
54
|
+
() => props[propName],
|
|
55
|
+
(v) => {
|
|
56
|
+
_value.value = v
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
const getLocal = () => {
|
|
61
|
+
return typeof local === 'function' ? local() : local
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const value = {
|
|
65
|
+
__v_isRef: true,
|
|
66
|
+
get value() {
|
|
67
|
+
return _value.value
|
|
68
|
+
},
|
|
69
|
+
set value(v) {
|
|
70
|
+
if (v !== _value.value) {
|
|
71
|
+
emit(`update:${propName as string}`, v)
|
|
72
|
+
}
|
|
73
|
+
if (getLocal()) {
|
|
74
|
+
_value.value = v
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return value
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 创建一个拥有getter和setter的对象
|
|
83
|
+
const value = {
|
|
84
|
+
__v_isRef: true,
|
|
85
|
+
|
|
86
|
+
get value(): Props[Name] {
|
|
87
|
+
return (props[propName] ?? defaultValue) as Props[Name]
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
set value(v: Props[Name]) {
|
|
91
|
+
emit(`update:${propName as string}`, v)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return value
|
|
96
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import {
|
|
2
|
+
computePosition,
|
|
3
|
+
flip,
|
|
4
|
+
shift,
|
|
5
|
+
arrow,
|
|
6
|
+
offset,
|
|
7
|
+
type ComputePositionReturn,
|
|
8
|
+
type Placement
|
|
9
|
+
} from '@floating-ui/dom'
|
|
10
|
+
import { getScrollParents, setStyles } from '@veltra/utils'
|
|
11
|
+
import { isRef, onBeforeUnmount, watch, type Ref, type ShallowRef } from 'vue'
|
|
12
|
+
|
|
13
|
+
type TipDirection = 'top' | 'bottom' | 'left' | 'right'
|
|
14
|
+
type TipAlign = 'center' | 'start' | 'end'
|
|
15
|
+
|
|
16
|
+
interface Options {
|
|
17
|
+
/** 触发元素 */
|
|
18
|
+
triggerRef: ShallowRef<HTMLElement | undefined>
|
|
19
|
+
/** 内容元素 */
|
|
20
|
+
contentRef: ShallowRef<HTMLElement | undefined>
|
|
21
|
+
/**
|
|
22
|
+
* 箭头元素,如果存在,则会在弹框的箭头位置显示箭头
|
|
23
|
+
*/
|
|
24
|
+
arrowRef?: ShallowRef<HTMLElement | undefined>
|
|
25
|
+
/** 方向 */
|
|
26
|
+
direction?: ShallowRef<TipDirection> | TipDirection
|
|
27
|
+
/** 对齐方式 */
|
|
28
|
+
alignment?: ShallowRef<TipAlign> | TipAlign
|
|
29
|
+
/**
|
|
30
|
+
* 箭头大小
|
|
31
|
+
* @default 10
|
|
32
|
+
*/
|
|
33
|
+
arrowSize?: number
|
|
34
|
+
/**
|
|
35
|
+
* 触发器元素位置变更时回调,
|
|
36
|
+
* 一般用于在触发器元素位置变更时更新弹框位置
|
|
37
|
+
*/
|
|
38
|
+
onTriggerPositionChange?: () => void
|
|
39
|
+
/** 更新元素前回调 */
|
|
40
|
+
onBeforeUpdate?: (triggerEl: HTMLElement, contentEl: HTMLElement) => void
|
|
41
|
+
/** 更新元素后回调 */
|
|
42
|
+
onAfterUpdate?: (position: ComputePositionReturn) => void
|
|
43
|
+
/** 弹框弹出时回调 */
|
|
44
|
+
onPop?: (position: ComputePositionReturn) => void
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface PopResult {
|
|
48
|
+
/**
|
|
49
|
+
* 更新弹框位置
|
|
50
|
+
*/
|
|
51
|
+
update: () => Promise<void>
|
|
52
|
+
/** 浮框容器id */
|
|
53
|
+
popperContainerId: string
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let popperContainer: HTMLElement | null = null
|
|
57
|
+
|
|
58
|
+
const popperContainerId = 'pop-container'
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 浮框组合式函数
|
|
62
|
+
* @param options 选项
|
|
63
|
+
* @returns
|
|
64
|
+
*/
|
|
65
|
+
export function usePop(options: Options): PopResult {
|
|
66
|
+
if (!popperContainer) {
|
|
67
|
+
popperContainer = document.createElement('div')
|
|
68
|
+
popperContainer.id = popperContainerId
|
|
69
|
+
document.body.appendChild(popperContainer)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const {
|
|
73
|
+
triggerRef,
|
|
74
|
+
contentRef,
|
|
75
|
+
arrowRef,
|
|
76
|
+
arrowSize = 10,
|
|
77
|
+
onTriggerPositionChange,
|
|
78
|
+
onAfterUpdate,
|
|
79
|
+
onBeforeUpdate,
|
|
80
|
+
direction,
|
|
81
|
+
alignment,
|
|
82
|
+
onPop
|
|
83
|
+
} = options
|
|
84
|
+
|
|
85
|
+
/** 箭头位置 */
|
|
86
|
+
const arrowPlacementDict = { top: 'bottom', right: 'left', bottom: 'top', left: 'right' }
|
|
87
|
+
|
|
88
|
+
function getMaybeRefValue<T>(value?: Ref<T> | T) {
|
|
89
|
+
return isRef(value) ? value.value : value
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 更新浮框位置
|
|
94
|
+
* @param callbackOnPop 是否在弹出时回调
|
|
95
|
+
*/
|
|
96
|
+
async function update(callbackOnPop = false) {
|
|
97
|
+
const triggerEl = triggerRef.value
|
|
98
|
+
const contentEl = contentRef.value
|
|
99
|
+
|
|
100
|
+
if (!(triggerEl instanceof HTMLElement) || !(contentEl instanceof HTMLElement)) {
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
onBeforeUpdate?.(triggerEl, contentEl)
|
|
105
|
+
|
|
106
|
+
// 计算位置 ↓↓↓
|
|
107
|
+
const middleware = [offset(arrowRef?.value ? arrowSize : 6), flip(), shift()]
|
|
108
|
+
|
|
109
|
+
if (arrowRef?.value) {
|
|
110
|
+
middleware.push(arrow({ element: arrowRef.value }))
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const _direction = getMaybeRefValue(direction) ?? 'top'
|
|
114
|
+
const _alignment = getMaybeRefValue(alignment) ?? 'center'
|
|
115
|
+
|
|
116
|
+
const position = await computePosition(triggerEl, contentEl, {
|
|
117
|
+
middleware,
|
|
118
|
+
|
|
119
|
+
placement: `${_direction}${_alignment === 'center' ? '' : `-${_alignment}`}` as Placement
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
const { x, y, middlewareData, placement } = position
|
|
123
|
+
|
|
124
|
+
setStyles(contentEl, { left: `${x}px`, top: `${y}px` })
|
|
125
|
+
callbackOnPop && onPop?.(position)
|
|
126
|
+
onAfterUpdate?.(position)
|
|
127
|
+
|
|
128
|
+
// 设置箭头位置 ↓↓↓
|
|
129
|
+
if (middlewareData.arrow) {
|
|
130
|
+
const { x: arrowX, y: arrowY } = middlewareData.arrow
|
|
131
|
+
|
|
132
|
+
const arrowPlacement =
|
|
133
|
+
arrowPlacementDict[placement.split('-')[0]! as keyof typeof arrowPlacementDict]
|
|
134
|
+
const size = `${arrowSize}px`
|
|
135
|
+
// 箭头半径
|
|
136
|
+
const arrowRadius = arrowSize / 2
|
|
137
|
+
|
|
138
|
+
setStyles(arrowRef!.value!, {
|
|
139
|
+
width: size,
|
|
140
|
+
height: size,
|
|
141
|
+
left: arrowX && `${arrowX - arrowRadius}px`,
|
|
142
|
+
top: arrowY && `${arrowY - arrowRadius}px`,
|
|
143
|
+
[arrowPlacement]: `-${arrowRadius}px`
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
// 设置箭头位置 ↑↑↑
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let scrollParents: HTMLElement[] = []
|
|
150
|
+
|
|
151
|
+
/** 为触发器元素的祖先元素添加滚动事件 */
|
|
152
|
+
function addScrollEvents() {
|
|
153
|
+
if (!triggerRef.value) return
|
|
154
|
+
if (onTriggerPositionChange) {
|
|
155
|
+
scrollParents = getScrollParents(triggerRef.value)
|
|
156
|
+
scrollParents.forEach((el) => {
|
|
157
|
+
el.addEventListener('scroll', onTriggerPositionChange)
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** 移除触发器元素祖先元素的滚动事件 */
|
|
163
|
+
function removeScrollEvents() {
|
|
164
|
+
if (onTriggerPositionChange) {
|
|
165
|
+
scrollParents.forEach((el) => {
|
|
166
|
+
el.removeEventListener('scroll', onTriggerPositionChange)
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
scrollParents = []
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function addResizeEvents() {
|
|
174
|
+
onTriggerPositionChange && window.addEventListener('resize', onTriggerPositionChange)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function removeResizeEvents() {
|
|
178
|
+
onTriggerPositionChange && window.removeEventListener('resize', onTriggerPositionChange)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
watch(
|
|
182
|
+
[contentRef, () => getMaybeRefValue(direction), () => getMaybeRefValue(alignment)],
|
|
183
|
+
([content]) => {
|
|
184
|
+
if (content) {
|
|
185
|
+
update(true)
|
|
186
|
+
addScrollEvents()
|
|
187
|
+
addResizeEvents()
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
removeScrollEvents()
|
|
191
|
+
removeResizeEvents()
|
|
192
|
+
}
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
onBeforeUnmount(() => {
|
|
196
|
+
removeScrollEvents()
|
|
197
|
+
removeResizeEvents()
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
/** 更新浮框位置 */
|
|
202
|
+
update,
|
|
203
|
+
/** 浮框容器id */
|
|
204
|
+
popperContainerId
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { computed, reactive } from 'vue'
|
|
2
|
+
|
|
3
|
+
import { useResizeObserver, type RefElement } from '../use-resize-observer'
|
|
4
|
+
|
|
5
|
+
interface ElementSize {
|
|
6
|
+
width: number
|
|
7
|
+
height: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 响应式尺寸
|
|
12
|
+
* @param targets 目标元素
|
|
13
|
+
* @returns 目标元素的宽高
|
|
14
|
+
*/
|
|
15
|
+
export function useReactiveSize(target: RefElement): ElementSize
|
|
16
|
+
export function useReactiveSize(targets: RefElement[]): ElementSize[]
|
|
17
|
+
export function useReactiveSize(targets: RefElement | RefElement[]): ElementSize | ElementSize[] {
|
|
18
|
+
const sizes = Array.isArray(targets)
|
|
19
|
+
? targets.map(() => {
|
|
20
|
+
return reactive({ width: 0, height: 0 })
|
|
21
|
+
})
|
|
22
|
+
: reactive({ width: 0, height: 0 })
|
|
23
|
+
|
|
24
|
+
const sizesMap = Array.isArray(targets)
|
|
25
|
+
? computed(() => {
|
|
26
|
+
const entries = targets
|
|
27
|
+
.map((target, index) => {
|
|
28
|
+
return [target.value!, (sizes as ElementSize[])[index]!]
|
|
29
|
+
})
|
|
30
|
+
.filter(([target]) => target) as [HTMLElement, ElementSize][]
|
|
31
|
+
|
|
32
|
+
return new WeakMap(entries)
|
|
33
|
+
})
|
|
34
|
+
: computed(() => {
|
|
35
|
+
return new WeakMap(targets.value ? [[targets.value, sizes as ElementSize]] : [])
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
useResizeObserver({
|
|
39
|
+
targets,
|
|
40
|
+
onResize(entries) {
|
|
41
|
+
entries.forEach((entry) => {
|
|
42
|
+
const borderBoxSize = entry.borderBoxSize[0]!
|
|
43
|
+
const size = sizesMap.value.get(entry.target as HTMLElement)
|
|
44
|
+
if (size && borderBoxSize) {
|
|
45
|
+
size.width = borderBoxSize.inlineSize
|
|
46
|
+
size.height = borderBoxSize.blockSize
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
return sizes
|
|
53
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { type Ref, type ShallowRef, onBeforeUnmount, watch } from 'vue'
|
|
2
|
+
|
|
3
|
+
export type RefElement =
|
|
4
|
+
| ShallowRef<HTMLElement | undefined | null>
|
|
5
|
+
| Ref<HTMLElement | undefined | null>
|
|
6
|
+
|
|
7
|
+
interface ResizeObserverOptions {
|
|
8
|
+
/** 目标节点 */
|
|
9
|
+
targets: RefElement | RefElement[]
|
|
10
|
+
/** resize事件 */
|
|
11
|
+
onResize: ResizeObserverCallback
|
|
12
|
+
/** 指定观察条件 */
|
|
13
|
+
when?: () => boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** 监听器 */
|
|
17
|
+
export type ResizeObserverReturn = {
|
|
18
|
+
/** 终止监听 */
|
|
19
|
+
disconnect: () => void
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 取消监听
|
|
24
|
+
* @param targets 目标节点
|
|
25
|
+
* @param observer 观察器
|
|
26
|
+
*/
|
|
27
|
+
function unobserve(targets: RefElement | RefElement[], observer?: ResizeObserver) {
|
|
28
|
+
if (Array.isArray(targets)) {
|
|
29
|
+
return targets.forEach((target) => unobserve(target, observer))
|
|
30
|
+
}
|
|
31
|
+
if (!targets.value || !observer) return
|
|
32
|
+
observer.unobserve(targets.value)
|
|
33
|
+
observer.disconnect()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 监听目标尺寸变化
|
|
38
|
+
* @param options 选项
|
|
39
|
+
*/
|
|
40
|
+
export function useResizeObserver(options: ResizeObserverOptions): ResizeObserverReturn {
|
|
41
|
+
const { targets, onResize } = options
|
|
42
|
+
|
|
43
|
+
let observer: ResizeObserver | undefined
|
|
44
|
+
|
|
45
|
+
if (Array.isArray(targets)) {
|
|
46
|
+
watch(
|
|
47
|
+
targets,
|
|
48
|
+
(val, oldVal) => {
|
|
49
|
+
// 清除旧的观察
|
|
50
|
+
oldVal.forEach((target) => {
|
|
51
|
+
target && observer?.unobserve(target)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
if (!observer && !!val.length) {
|
|
55
|
+
observer = new ResizeObserver(onResize)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
val.forEach((target) => {
|
|
59
|
+
target && observer?.observe(target)
|
|
60
|
+
})
|
|
61
|
+
},
|
|
62
|
+
{ immediate: true }
|
|
63
|
+
)
|
|
64
|
+
} else {
|
|
65
|
+
watch(
|
|
66
|
+
targets,
|
|
67
|
+
(val, oldVal) => {
|
|
68
|
+
oldVal && observer?.unobserve(oldVal)
|
|
69
|
+
if (!observer && val) {
|
|
70
|
+
observer = new ResizeObserver(onResize)
|
|
71
|
+
}
|
|
72
|
+
val && observer?.observe(val)
|
|
73
|
+
},
|
|
74
|
+
{ immediate: true }
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
onBeforeUnmount(() => {
|
|
79
|
+
unobserve(targets, observer)
|
|
80
|
+
observer = undefined
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
disconnect() {
|
|
85
|
+
unobserve(targets, observer)
|
|
86
|
+
observer = undefined
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 监听元素尺寸变化
|
|
93
|
+
*/
|
|
94
|
+
export function useObserverCallback(): {
|
|
95
|
+
observeEl: <El extends HTMLElement>(
|
|
96
|
+
el: El,
|
|
97
|
+
cb: (entry: Omit<ResizeObserverEntry, 'target'> & { target: El }) => void
|
|
98
|
+
) => void
|
|
99
|
+
unobserveEl: (el: HTMLElement) => void
|
|
100
|
+
} {
|
|
101
|
+
const observerElMap = new Map<HTMLElement, Function>()
|
|
102
|
+
|
|
103
|
+
const observer = new ResizeObserver((entries) => {
|
|
104
|
+
entries.forEach((entry) => {
|
|
105
|
+
const target = entry.target as HTMLElement
|
|
106
|
+
if (!target.dataset.ob) {
|
|
107
|
+
target.dataset.ob = 'true'
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
const fn = observerElMap.get(target)
|
|
111
|
+
|
|
112
|
+
fn?.(entry)
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* 监听元素尺寸
|
|
118
|
+
* @param el 元素
|
|
119
|
+
* @param cb 回调
|
|
120
|
+
*/
|
|
121
|
+
function observeEl<El extends HTMLElement>(
|
|
122
|
+
el: El,
|
|
123
|
+
cb: (entry: Omit<ResizeObserverEntry, 'target'> & { target: El }) => void
|
|
124
|
+
) {
|
|
125
|
+
observer.observe(el)
|
|
126
|
+
observerElMap.set(el, cb)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* 取消监听元素尺寸
|
|
131
|
+
* @param el 元素
|
|
132
|
+
*/
|
|
133
|
+
function unobserveEl(el: HTMLElement) {
|
|
134
|
+
observer.unobserve(el)
|
|
135
|
+
delete el.dataset.ob
|
|
136
|
+
observerElMap.delete(el)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
onBeforeUnmount(() => {
|
|
140
|
+
observerElMap.forEach((_, el) => {
|
|
141
|
+
observer.unobserve(el)
|
|
142
|
+
})
|
|
143
|
+
observerElMap.clear()
|
|
144
|
+
observer.disconnect()
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
return { observeEl, unobserveEl }
|
|
148
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Returned, CssTransitionOptions, StyleTransitionOptions } from './type'
|
|
2
|
+
import { useCssTransition } from './use-css-transition'
|
|
3
|
+
import { useStyleTransition } from './use-style-transition'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 使用CSS类过渡
|
|
7
|
+
* @param type 过渡类型
|
|
8
|
+
* @param options 过渡选项
|
|
9
|
+
*/
|
|
10
|
+
export function useTransition(type: 'css', options: CssTransitionOptions): Returned
|
|
11
|
+
/**
|
|
12
|
+
* 使用style样式过渡
|
|
13
|
+
* @param type 过渡类型
|
|
14
|
+
* @param options 过渡选项
|
|
15
|
+
*/
|
|
16
|
+
export function useTransition(type: 'style', options: StyleTransitionOptions): Returned
|
|
17
|
+
export function useTransition(type: string, options: any): Returned {
|
|
18
|
+
if (type === 'css') {
|
|
19
|
+
return useCssTransition(options)
|
|
20
|
+
}
|
|
21
|
+
return useStyleTransition(options)
|
|
22
|
+
}
|