@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,50 @@
|
|
|
1
|
+
import type { CSSProperties, Ref, ShallowRef } from 'vue'
|
|
2
|
+
|
|
3
|
+
export interface TransitionBase {
|
|
4
|
+
/** 被应用的目标元素 */
|
|
5
|
+
target: ShallowRef<HTMLElement | undefined> | HTMLElement
|
|
6
|
+
/** 进入动画结束回调 */
|
|
7
|
+
afterEnter?: () => void
|
|
8
|
+
/** 进入动画被取消回调 */
|
|
9
|
+
enterCanceled?: () => void
|
|
10
|
+
/** 离开动画结束回调 */
|
|
11
|
+
afterLeave?: () => void
|
|
12
|
+
/** 离开动画被取消回调 */
|
|
13
|
+
leaveCanceled?: () => void
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface CssTransitionOptions extends TransitionBase {
|
|
17
|
+
/** 类的名称, 会生成 `${name}-enter-${'to' | 'active' | 'from'}`, `${name}-leave-${'to' | 'active' | 'from'}这几种类` */
|
|
18
|
+
name: ShallowRef<string> | string | Ref<string>
|
|
19
|
+
/** 保留进入类 */
|
|
20
|
+
keepEnterTo?: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface StyleTransitionOptions extends TransitionBase {
|
|
24
|
+
// /** 动画进入前的样式 */
|
|
25
|
+
// enterFrom?: CSSProperties
|
|
26
|
+
// /** 动画离开后的样式 */
|
|
27
|
+
// leaveTo?: CSSProperties
|
|
28
|
+
/** 进入后的样式 */
|
|
29
|
+
enterTo: CSSProperties
|
|
30
|
+
/** 进入过渡时的样式 */
|
|
31
|
+
enterActive: CSSProperties
|
|
32
|
+
/** 离开过渡时的样式 */
|
|
33
|
+
leaveActive: CSSProperties
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface Returned {
|
|
37
|
+
/**
|
|
38
|
+
* 切换进入/离开动画
|
|
39
|
+
* @param active 是否激活
|
|
40
|
+
*/
|
|
41
|
+
toggle(active: boolean | ((active: boolean) => boolean)): void
|
|
42
|
+
/**
|
|
43
|
+
* 标记进入动画, toggle(true)的别名
|
|
44
|
+
*/
|
|
45
|
+
enter(): void
|
|
46
|
+
/**
|
|
47
|
+
* 标记离开动画, toggle(false)的别名
|
|
48
|
+
*/
|
|
49
|
+
leave(): void
|
|
50
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { createToggle } from '@veltra/utils'
|
|
2
|
+
import { isRef, watch, onBeforeUnmount, computed } from 'vue'
|
|
3
|
+
|
|
4
|
+
import type { Returned, CssTransitionOptions } from './type'
|
|
5
|
+
|
|
6
|
+
const increaseTransitionCount = (el: HTMLElement & { _count?: number }) => {
|
|
7
|
+
el._count = (el._count ?? 0) + 1
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const decreaseTransitionCount = (el: HTMLElement & { _count?: number }) => {
|
|
11
|
+
el._count = (el._count ?? 0) - 1
|
|
12
|
+
if (el._count <= 0) {
|
|
13
|
+
delete el._count
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 使用css过渡
|
|
19
|
+
* @param options 过渡选项
|
|
20
|
+
*/
|
|
21
|
+
export function useCssTransition(options: CssTransitionOptions): Returned {
|
|
22
|
+
const {
|
|
23
|
+
name,
|
|
24
|
+
target,
|
|
25
|
+
afterEnter,
|
|
26
|
+
afterLeave,
|
|
27
|
+
enterCanceled,
|
|
28
|
+
leaveCanceled,
|
|
29
|
+
keepEnterTo = false
|
|
30
|
+
} = options
|
|
31
|
+
|
|
32
|
+
const getDom = (): (HTMLElement & { _count?: number }) | undefined =>
|
|
33
|
+
isRef(target) ? target.value : target
|
|
34
|
+
|
|
35
|
+
const classes = computed(() => {
|
|
36
|
+
const _name = typeof name === 'string' ? name : name.value
|
|
37
|
+
return {
|
|
38
|
+
/** 进入前的类 */
|
|
39
|
+
enterFrom: `${_name}-enter-from`,
|
|
40
|
+
/** 进入后最终的类 */
|
|
41
|
+
enterTo: `${_name}-enter-to`,
|
|
42
|
+
/** 【进入动画】持续时的类 */
|
|
43
|
+
enterActive: `${_name}-enter-active`,
|
|
44
|
+
/** 离开前的类 */
|
|
45
|
+
leaveFrom: `${_name}-leave-from`,
|
|
46
|
+
/** 离开类 */
|
|
47
|
+
leaveTo: `${_name}-leave-to`,
|
|
48
|
+
/** 【离开动画】持续时的类 */
|
|
49
|
+
leaveActive: `${_name}-leave-active`
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
/** 开始进入动画 */
|
|
54
|
+
const startTransitionIn = () => {
|
|
55
|
+
const { enterActive, enterTo, enterFrom } = classes.value
|
|
56
|
+
const dom = getDom()
|
|
57
|
+
|
|
58
|
+
dom?.classList.add(enterFrom)
|
|
59
|
+
|
|
60
|
+
requestAnimationFrame(() => {
|
|
61
|
+
dom?.classList.add(enterActive)
|
|
62
|
+
requestAnimationFrame(() => {
|
|
63
|
+
dom?.classList.remove(enterFrom)
|
|
64
|
+
dom?.classList.add(enterTo)
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** 开始离开动画 */
|
|
70
|
+
const startTransitionOut = () => {
|
|
71
|
+
const { leaveTo, leaveActive, leaveFrom, enterTo } = classes.value
|
|
72
|
+
const dom = getDom()
|
|
73
|
+
|
|
74
|
+
// 标记动画进入离开状态
|
|
75
|
+
if (keepEnterTo) {
|
|
76
|
+
dom?.classList.remove(enterTo)
|
|
77
|
+
}
|
|
78
|
+
dom?.classList.add(leaveFrom, leaveActive)
|
|
79
|
+
|
|
80
|
+
requestAnimationFrame(() => {
|
|
81
|
+
dom?.classList.add(leaveActive)
|
|
82
|
+
requestAnimationFrame(() => {
|
|
83
|
+
dom?.classList.remove(leaveFrom)
|
|
84
|
+
dom?.classList.add(leaveTo)
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const [active, toggle] = createToggle(false, (_active) => {
|
|
90
|
+
_active ? startTransitionIn() : startTransitionOut()
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const transitionEndHandler = (e: TransitionEvent) => {
|
|
94
|
+
e.stopPropagation()
|
|
95
|
+
|
|
96
|
+
const { leaveActive, enterActive, enterTo, leaveTo } = classes.value
|
|
97
|
+
const dom = getDom()
|
|
98
|
+
|
|
99
|
+
if (dom !== e.target) return
|
|
100
|
+
|
|
101
|
+
decreaseTransitionCount(dom)
|
|
102
|
+
|
|
103
|
+
if (dom._count) return
|
|
104
|
+
|
|
105
|
+
// 激活状态,移除enter-active类
|
|
106
|
+
if (active.value) {
|
|
107
|
+
if (keepEnterTo) {
|
|
108
|
+
dom?.classList.remove(enterActive)
|
|
109
|
+
} else {
|
|
110
|
+
dom?.classList.remove(enterActive, enterTo)
|
|
111
|
+
}
|
|
112
|
+
afterEnter?.()
|
|
113
|
+
} else {
|
|
114
|
+
dom?.classList.remove(leaveActive, leaveTo)
|
|
115
|
+
afterLeave?.()
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const transitionRunHandler = (e: TransitionEvent) => {
|
|
120
|
+
e.stopPropagation()
|
|
121
|
+
const dom = getDom()
|
|
122
|
+
if (dom !== e.target) return
|
|
123
|
+
increaseTransitionCount(dom)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const transitionCancelHandler = (e: TransitionEvent) => {
|
|
127
|
+
e.stopPropagation()
|
|
128
|
+
const dom = getDom()
|
|
129
|
+
|
|
130
|
+
if (dom !== e.target) return
|
|
131
|
+
decreaseTransitionCount(dom)
|
|
132
|
+
|
|
133
|
+
if (dom._count) return
|
|
134
|
+
|
|
135
|
+
const { leaveActive, enterActive } = classes.value
|
|
136
|
+
|
|
137
|
+
// 激活状态,移除enter-active类
|
|
138
|
+
if (active.value) {
|
|
139
|
+
dom?.classList.remove(enterActive)
|
|
140
|
+
enterCanceled?.()
|
|
141
|
+
} else {
|
|
142
|
+
dom?.classList.remove(leaveActive)
|
|
143
|
+
leaveCanceled?.()
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** 添加事件 */
|
|
148
|
+
const addEvent = (el?: HTMLElement) => {
|
|
149
|
+
el?.addEventListener('transitioncancel', transitionCancelHandler)
|
|
150
|
+
el?.addEventListener('transitionend', transitionEndHandler)
|
|
151
|
+
el?.addEventListener('transitionrun', transitionRunHandler)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** 移除事件 */
|
|
155
|
+
const removeEvent = (el?: HTMLElement) => {
|
|
156
|
+
el?.removeEventListener('transitioncancel', transitionCancelHandler)
|
|
157
|
+
el?.removeEventListener('transitionend', transitionEndHandler)
|
|
158
|
+
el?.removeEventListener('transitionrun', transitionRunHandler)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (isRef(target)) {
|
|
162
|
+
watch(target, (_target, oldTarget) => {
|
|
163
|
+
if (oldTarget) {
|
|
164
|
+
removeEvent(oldTarget)
|
|
165
|
+
}
|
|
166
|
+
_target && addEvent(_target)
|
|
167
|
+
})
|
|
168
|
+
} else if (target) {
|
|
169
|
+
addEvent(target)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
onBeforeUnmount(() => {
|
|
173
|
+
const dom = getDom()
|
|
174
|
+
removeEvent(dom)
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
toggle,
|
|
179
|
+
enter() {
|
|
180
|
+
toggle(true)
|
|
181
|
+
},
|
|
182
|
+
leave() {
|
|
183
|
+
toggle(false)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { createToggle, nextFrame, setStyles } from '@veltra/utils'
|
|
2
|
+
import { isRef, watch, type CSSProperties } from 'vue'
|
|
3
|
+
|
|
4
|
+
import type { Returned, StyleTransitionOptions } from './type'
|
|
5
|
+
import { watchTransition } from './utils'
|
|
6
|
+
|
|
7
|
+
export function useStyleTransition(options: StyleTransitionOptions): Returned {
|
|
8
|
+
const {
|
|
9
|
+
// enterFrom,
|
|
10
|
+
// leaveTo,
|
|
11
|
+
enterTo,
|
|
12
|
+
enterActive,
|
|
13
|
+
leaveActive,
|
|
14
|
+
target,
|
|
15
|
+
afterEnter,
|
|
16
|
+
afterLeave,
|
|
17
|
+
enterCanceled,
|
|
18
|
+
leaveCanceled
|
|
19
|
+
} = options
|
|
20
|
+
|
|
21
|
+
const getDom = (): HTMLElement | undefined => (isRef(target) ? target.value : target)
|
|
22
|
+
|
|
23
|
+
/** 进入动画前的初始状态 */
|
|
24
|
+
const transitionOriginStyle: CSSProperties = {}
|
|
25
|
+
|
|
26
|
+
/** 获取过渡样式的初始样式 */
|
|
27
|
+
const getOriginStyles = (styles: CSSProperties) => {
|
|
28
|
+
return Object.fromEntries(
|
|
29
|
+
Object.keys(styles).map((key) => {
|
|
30
|
+
return [key, transitionOriginStyle[key as keyof CSSProperties]]
|
|
31
|
+
})
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
// 监听dom并获取dom在进入动画之前的样式
|
|
35
|
+
// ...Object.keys(enterFrom ?? {}),
|
|
36
|
+
// ...Object.keys(leaveTo ?? {}),
|
|
37
|
+
watch(
|
|
38
|
+
() => getDom(),
|
|
39
|
+
(dom) => {
|
|
40
|
+
if (dom) {
|
|
41
|
+
const map = dom.attributeStyleMap
|
|
42
|
+
~[...Object.keys(enterTo), ...Object.keys(enterActive)].forEach((key) => {
|
|
43
|
+
transitionOriginStyle[key] = map.get(key)
|
|
44
|
+
})
|
|
45
|
+
} else {
|
|
46
|
+
Object.keys(transitionOriginStyle).forEach((key) => {
|
|
47
|
+
delete transitionOriginStyle[key as keyof CSSProperties]
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
{ immediate: true }
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 添加过渡进入时并持续时的样式
|
|
56
|
+
* @param dom 元素
|
|
57
|
+
*/
|
|
58
|
+
const addEnterActive = (dom: HTMLElement) => {
|
|
59
|
+
setStyles(dom, enterActive)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 移除过渡进入时并持续时的样式
|
|
64
|
+
* @param dom 元素
|
|
65
|
+
*/
|
|
66
|
+
const removeEnterActive = (dom: HTMLElement) => {
|
|
67
|
+
setStyles(dom, getOriginStyles(enterActive))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 添加过渡离开并持续时的样式
|
|
72
|
+
* @param dom 元素
|
|
73
|
+
*/
|
|
74
|
+
const addLeaveActive = (dom: HTMLElement) => {
|
|
75
|
+
setStyles(dom, leaveActive)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 移除过渡离开并持续时的样式
|
|
80
|
+
* @param dom 元素
|
|
81
|
+
*/
|
|
82
|
+
const removeLeaveActive = (dom: HTMLElement) => {
|
|
83
|
+
setStyles(dom, getOriginStyles(leaveActive))
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 添加过渡目标样式
|
|
88
|
+
* @param dom 元素
|
|
89
|
+
*/
|
|
90
|
+
// const addEnterFromStyle = (dom: HTMLElement) => {
|
|
91
|
+
// enterFrom && setStyles(dom, enterFrom)
|
|
92
|
+
// }
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 移除过渡目标样式
|
|
96
|
+
* @param dom 元素
|
|
97
|
+
*/
|
|
98
|
+
// const removeEnterFromStyle = (dom: HTMLElement) => {
|
|
99
|
+
// if (!enterFrom) return
|
|
100
|
+
|
|
101
|
+
// const canRemovedStyles = {}
|
|
102
|
+
|
|
103
|
+
// for (const key in enterFrom) {
|
|
104
|
+
// if (key in enterTo) continue
|
|
105
|
+
// canRemovedStyles[key] = enterFrom[key]
|
|
106
|
+
// }
|
|
107
|
+
|
|
108
|
+
// setStyles(dom, getOriginStyles(canRemovedStyles))
|
|
109
|
+
// }
|
|
110
|
+
/**
|
|
111
|
+
* 添加过渡目标样式
|
|
112
|
+
* @param dom 元素
|
|
113
|
+
*/
|
|
114
|
+
const addEnterToStyle = (dom: HTMLElement) => {
|
|
115
|
+
setStyles(dom, enterTo)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* 移除过渡目标样式
|
|
120
|
+
* @param dom 元素
|
|
121
|
+
*/
|
|
122
|
+
const removeEnterToStyle = (dom: HTMLElement) => {
|
|
123
|
+
setStyles(dom, getOriginStyles(enterTo))
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** 开始进入动画 */
|
|
127
|
+
const startEnter = () => {
|
|
128
|
+
const dom = getDom()
|
|
129
|
+
if (!dom) return
|
|
130
|
+
addEnterActive(dom)
|
|
131
|
+
// 在下一帧插入动画运动目标状态
|
|
132
|
+
nextFrame(() => {
|
|
133
|
+
addEnterToStyle(dom)
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** 开始离开动画 */
|
|
138
|
+
const startLeave = () => {
|
|
139
|
+
const dom = getDom()
|
|
140
|
+
if (!dom) return
|
|
141
|
+
|
|
142
|
+
// 标记动画进入离开状态
|
|
143
|
+
addLeaveActive(dom)
|
|
144
|
+
|
|
145
|
+
// 在下一帧移除动画运动目标状态恢复原状或者应用新的状态
|
|
146
|
+
nextFrame(() => {
|
|
147
|
+
removeEnterToStyle(dom)
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const [active, toggle] = createToggle(false, (active) => {
|
|
152
|
+
active ? startEnter() : startLeave()
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
watchTransition(getDom, {
|
|
156
|
+
styleKeys: Object.keys(enterTo),
|
|
157
|
+
onCancel(el) {
|
|
158
|
+
// 激活状态,移除enter-active类
|
|
159
|
+
if (active.value) {
|
|
160
|
+
removeEnterActive(el)
|
|
161
|
+
enterCanceled?.()
|
|
162
|
+
} else {
|
|
163
|
+
removeLeaveActive(el)
|
|
164
|
+
leaveCanceled?.()
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
onEnd(el) {
|
|
169
|
+
if (active.value) {
|
|
170
|
+
removeEnterActive(el)
|
|
171
|
+
// removeEnterFromStyle(el)
|
|
172
|
+
afterEnter?.()
|
|
173
|
+
} else {
|
|
174
|
+
removeLeaveActive(el)
|
|
175
|
+
|
|
176
|
+
afterLeave?.()
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
return { toggle, enter: () => toggle(true), leave: () => toggle(false) }
|
|
182
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { watch, onBeforeUnmount } from 'vue'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 监听过渡
|
|
5
|
+
* @param domGetter 元素获取函数
|
|
6
|
+
* @param config 配置
|
|
7
|
+
* @returns
|
|
8
|
+
*/
|
|
9
|
+
export function watchTransition(
|
|
10
|
+
domGetter: () => HTMLElement | undefined,
|
|
11
|
+
config: {
|
|
12
|
+
styleKeys: string[]
|
|
13
|
+
onEnd: (dom: HTMLElement) => void
|
|
14
|
+
onCancel: (dom: HTMLElement) => void
|
|
15
|
+
}
|
|
16
|
+
): void {
|
|
17
|
+
const runCallback = (e: TransitionEvent, cb: (el: HTMLElement) => void) => {
|
|
18
|
+
e.stopPropagation()
|
|
19
|
+
if (e.target !== domGetter() || !config.styleKeys.includes(e.propertyName)) {
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
cb(e.target as HTMLElement)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const transitionEndHandler = (e: TransitionEvent) => {
|
|
27
|
+
if (!domGetter()) return
|
|
28
|
+
runCallback(e, config.onEnd)
|
|
29
|
+
}
|
|
30
|
+
const transitionCancelHandler = (e: TransitionEvent) => {
|
|
31
|
+
if (!domGetter()) return
|
|
32
|
+
runCallback(e, config.onCancel)
|
|
33
|
+
}
|
|
34
|
+
const addEvent = (el: HTMLElement) => {
|
|
35
|
+
el.addEventListener('transitionend', transitionEndHandler, false)
|
|
36
|
+
// el.addEventListener('transitioncancel', transitionCancelHandler, false)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const removeEvent = (el?: HTMLElement) => {
|
|
40
|
+
el?.removeEventListener('transitionend', transitionEndHandler)
|
|
41
|
+
el?.removeEventListener('transitioncancel', transitionCancelHandler)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
watch(
|
|
45
|
+
domGetter,
|
|
46
|
+
(target, oldTarget) => {
|
|
47
|
+
removeEvent(oldTarget)
|
|
48
|
+
target && addEvent(target)
|
|
49
|
+
},
|
|
50
|
+
{ immediate: true }
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
onBeforeUnmount(() => {
|
|
54
|
+
removeEvent(domGetter())
|
|
55
|
+
})
|
|
56
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import {
|
|
2
|
+
elementScroll,
|
|
3
|
+
observeElementOffset,
|
|
4
|
+
observeElementRect,
|
|
5
|
+
type VirtualItem,
|
|
6
|
+
Virtualizer
|
|
7
|
+
} from '@tanstack/vue-virtual'
|
|
8
|
+
import {
|
|
9
|
+
computed,
|
|
10
|
+
isRef,
|
|
11
|
+
onScopeDispose,
|
|
12
|
+
shallowRef,
|
|
13
|
+
watch,
|
|
14
|
+
type ComputedRef,
|
|
15
|
+
type Ref,
|
|
16
|
+
type ShallowRef
|
|
17
|
+
} from 'vue'
|
|
18
|
+
|
|
19
|
+
interface Options {
|
|
20
|
+
/** 指定启用虚拟列表的阈值 */
|
|
21
|
+
virtualThreshold?: number | Ref<number | undefined>
|
|
22
|
+
/** 数量 */
|
|
23
|
+
count: Ref<number>
|
|
24
|
+
/** 滚动容器 */
|
|
25
|
+
scrollEl: ShallowRef<HTMLElement | null>
|
|
26
|
+
/** 估算高度(宽度) */
|
|
27
|
+
estimateSize?: (index: number) => number
|
|
28
|
+
/** 列表项之间的间距 */
|
|
29
|
+
gap?: number
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type CustomVirtualItem = Omit<VirtualItem, 'key'> & { key: number | string }
|
|
33
|
+
|
|
34
|
+
export type VirtualReturned = {
|
|
35
|
+
/** 虚拟列表 */
|
|
36
|
+
virtualList: ShallowRef<CustomVirtualItem[]>
|
|
37
|
+
/** 总高度 */
|
|
38
|
+
totalHeight: ShallowRef<number>
|
|
39
|
+
/** 测量元素高度 */
|
|
40
|
+
measureElement: (el: any) => void
|
|
41
|
+
/** 滚动到指定索引 */
|
|
42
|
+
scrollTo: (index: number) => void
|
|
43
|
+
/** 是否启用虚拟列表 */
|
|
44
|
+
virtualEnabled: ComputedRef<boolean>
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const defaultEstimateSize = () => 34
|
|
48
|
+
|
|
49
|
+
export function useVirtual(options: Options): VirtualReturned {
|
|
50
|
+
const { count, scrollEl, estimateSize, virtualThreshold, gap } = options
|
|
51
|
+
|
|
52
|
+
const enabled = computed(() => {
|
|
53
|
+
if (isRef(virtualThreshold)) {
|
|
54
|
+
return virtualThreshold.value ? count.value > virtualThreshold.value : true
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return virtualThreshold ? count.value > virtualThreshold : true
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const virtualList = shallowRef<CustomVirtualItem[]>([])
|
|
61
|
+
|
|
62
|
+
/** 总高度 */
|
|
63
|
+
const totalHeight = shallowRef(0)
|
|
64
|
+
|
|
65
|
+
function updateVirtualList() {
|
|
66
|
+
if (enabled.value) {
|
|
67
|
+
totalHeight.value = v.getTotalSize()
|
|
68
|
+
virtualList.value = v.getVirtualItems() as CustomVirtualItem[]
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const virtualizerOptions = computed(() => {
|
|
73
|
+
return {
|
|
74
|
+
enabled: enabled.value,
|
|
75
|
+
count: count.value,
|
|
76
|
+
getScrollElement: () => scrollEl.value,
|
|
77
|
+
estimateSize: estimateSize ?? defaultEstimateSize,
|
|
78
|
+
overscan: 3,
|
|
79
|
+
gap,
|
|
80
|
+
observeElementRect: observeElementRect,
|
|
81
|
+
observeElementOffset: observeElementOffset,
|
|
82
|
+
scrollToFn: elementScroll,
|
|
83
|
+
onChange: updateVirtualList
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
const v = new Virtualizer(virtualizerOptions.value)
|
|
88
|
+
|
|
89
|
+
updateVirtualList()
|
|
90
|
+
|
|
91
|
+
const cleanup = v._didMount()
|
|
92
|
+
|
|
93
|
+
watch(
|
|
94
|
+
scrollEl,
|
|
95
|
+
(el) => {
|
|
96
|
+
el && v._willUpdate()
|
|
97
|
+
},
|
|
98
|
+
{ immediate: true }
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
watch(
|
|
102
|
+
() => virtualizerOptions.value,
|
|
103
|
+
(o) => {
|
|
104
|
+
v.setOptions(o)
|
|
105
|
+
|
|
106
|
+
v._willUpdate()
|
|
107
|
+
|
|
108
|
+
updateVirtualList()
|
|
109
|
+
}
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
onScopeDispose(() => {
|
|
113
|
+
cleanup()
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
function scrollTo(index: number) {
|
|
117
|
+
v.scrollToIndex(index, { align: 'center' })
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** 测量元素高度 */
|
|
121
|
+
function measureElement(el: Element) {
|
|
122
|
+
if (!el) return
|
|
123
|
+
|
|
124
|
+
v.measureElement(el)
|
|
125
|
+
|
|
126
|
+
return undefined
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return { virtualEnabled: enabled, virtualList, totalHeight, measureElement, scrollTo }
|
|
130
|
+
}
|