@allkit/use 0.0.1
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/README.md +1 -0
- package/dist/README.md +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/onMountedOrActivated/index.d.ts +1 -0
- package/dist/package.json +22 -0
- package/dist/types.d.ts +12 -0
- package/dist/use.es.d.ts +2 -0
- package/dist/use.es.js +209 -0
- package/dist/use.umd.js +1 -0
- package/dist/useCtxState/index.d.ts +45 -0
- package/dist/useEventListener/index.d.ts +8 -0
- package/dist/usePageVisibility/index.d.ts +17 -0
- package/dist/useScroll/index.d.ts +81 -0
- package/package.json +21 -0
- package/scripts/build.mjs +99 -0
- package/skill/SKILL.md +40 -0
- package/skill/examples/demo.tsx +83 -0
- package/skill/references/defineCtxState.md +61 -0
- package/skill/references/onMountedOrActivated.md +33 -0
- package/skill/references/useCtxState.md +36 -0
- package/skill/references/useEventListener.md +65 -0
- package/skill/references/usePageVisibility.md +45 -0
- package/skill/references/useScroll.md +90 -0
- package/src/index.ts +5 -0
- package/src/onMountedOrActivated/index.ts +18 -0
- package/src/types.ts +23 -0
- package/src/useCtxState/index.ts +87 -0
- package/src/useEventListener/index.ts +91 -0
- package/src/usePageVisibility/index.ts +46 -0
- package/src/useScroll/index.ts +299 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { computed, reactive, ref, toValue, type MaybeRefOrGetter } from 'vue'
|
|
2
|
+
import { debounce, throttle as throttleFn, isClient } from '@allkit/shared'
|
|
3
|
+
import { onMountedOrActivated } from '../onMountedOrActivated'
|
|
4
|
+
import {
|
|
5
|
+
type ConfigurableWindow,
|
|
6
|
+
type MaybeElement,
|
|
7
|
+
type MaybeElementRef,
|
|
8
|
+
type VueInstance,
|
|
9
|
+
} from '../types'
|
|
10
|
+
import { useEventListener } from '../useEventListener'
|
|
11
|
+
|
|
12
|
+
const noop = () => ({})
|
|
13
|
+
|
|
14
|
+
function unrefElement<T extends MaybeElement>(elRef: MaybeElementRef<T>) {
|
|
15
|
+
const plain = toValue(elRef)
|
|
16
|
+
return (plain as unknown as VueInstance)?.$el ?? plain
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface UseScrollOptions extends ConfigurableWindow {
|
|
20
|
+
/**
|
|
21
|
+
* Throttle time for scroll event, it’s disabled by default.
|
|
22
|
+
*
|
|
23
|
+
* @defaultValue 0
|
|
24
|
+
*/
|
|
25
|
+
throttle?: number
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The check time when scrolling ends.
|
|
29
|
+
* This configuration will be setting to (throttle + idle) when the `throttle` is configured.
|
|
30
|
+
*
|
|
31
|
+
* @defaultValue 200
|
|
32
|
+
*/
|
|
33
|
+
idle?: number
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Offset arrived states by x pixels
|
|
37
|
+
*
|
|
38
|
+
*/
|
|
39
|
+
offset?: {
|
|
40
|
+
left?: number
|
|
41
|
+
right?: number
|
|
42
|
+
top?: number
|
|
43
|
+
bottom?: number
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Trigger it when scrolling.
|
|
48
|
+
*
|
|
49
|
+
*/
|
|
50
|
+
onScroll?: (e: Event) => void
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Trigger it when scrolling ends.
|
|
54
|
+
*
|
|
55
|
+
*/
|
|
56
|
+
onStop?: (e: Event) => void
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Listener options for scroll event.
|
|
60
|
+
*
|
|
61
|
+
* @defaultValue `{capture: false, passive: true}`
|
|
62
|
+
*/
|
|
63
|
+
eventListenerOptions?: AddEventListenerOptions
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Optionally specify a scroll behavior of `auto` (default, not smooth scrolling) or
|
|
67
|
+
* `smooth` (for smooth scrolling) which takes effect when changing the `x` or `y` refs.
|
|
68
|
+
*
|
|
69
|
+
* @defaultValue 'auto'
|
|
70
|
+
*/
|
|
71
|
+
behavior?: MaybeRefOrGetter<ScrollBehavior>
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* On error callback
|
|
75
|
+
*
|
|
76
|
+
* Default log error to `console.error`
|
|
77
|
+
*/
|
|
78
|
+
onError?: (error: unknown) => void
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* We have to check if the scroll amount is close enough to some threshold in order to
|
|
83
|
+
* more accurately calculate arrivedState. This is because scrollTop/scrollLeft are non-rounded
|
|
84
|
+
* numbers, while scrollHeight/scrollWidth and clientHeight/clientWidth are rounded.
|
|
85
|
+
* https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight#determine_if_an_element_has_been_totally_scrolled
|
|
86
|
+
*/
|
|
87
|
+
const ARRIVED_STATE_THRESHOLD_PIXELS = 1
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Reactive scroll.
|
|
91
|
+
*
|
|
92
|
+
* @see https://vueuse.org/useScroll
|
|
93
|
+
*/
|
|
94
|
+
|
|
95
|
+
const defaultWindow = isClient ? window : undefined
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 监听页面布局滚动 事件
|
|
99
|
+
* @param element -监听的元素,支持window,document,HTMLElement 以及 Ref引用
|
|
100
|
+
* @param options -{@link UseScrollOptions}
|
|
101
|
+
* @returns x,y轴滚动距离,方向,状态,以及是否到达边界
|
|
102
|
+
*/
|
|
103
|
+
export function useScroll(element: MaybeElementRef<MaybeElement>, options: UseScrollOptions = {}) {
|
|
104
|
+
const {
|
|
105
|
+
throttle = 0,
|
|
106
|
+
idle = 200,
|
|
107
|
+
onStop = noop,
|
|
108
|
+
onScroll = noop,
|
|
109
|
+
offset = {
|
|
110
|
+
left: 0,
|
|
111
|
+
right: 0,
|
|
112
|
+
top: 0,
|
|
113
|
+
bottom: 0,
|
|
114
|
+
},
|
|
115
|
+
eventListenerOptions = {
|
|
116
|
+
capture: false,
|
|
117
|
+
passive: true,
|
|
118
|
+
},
|
|
119
|
+
behavior = 'auto',
|
|
120
|
+
window = defaultWindow,
|
|
121
|
+
onError = (e) => {
|
|
122
|
+
console.error(e)
|
|
123
|
+
},
|
|
124
|
+
} = options
|
|
125
|
+
|
|
126
|
+
const internalX = ref(0)
|
|
127
|
+
const internalY = ref(0)
|
|
128
|
+
|
|
129
|
+
// Use a computed for x and y because we want to write the value to the refs
|
|
130
|
+
// during a `scrollTo()` without firing additional `scrollTo()`s in the process.
|
|
131
|
+
const x = computed({
|
|
132
|
+
get() {
|
|
133
|
+
return internalX.value
|
|
134
|
+
},
|
|
135
|
+
set(x) {
|
|
136
|
+
scrollTo(x, undefined)
|
|
137
|
+
},
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
const y = computed({
|
|
141
|
+
get() {
|
|
142
|
+
return internalY.value
|
|
143
|
+
},
|
|
144
|
+
set(y) {
|
|
145
|
+
scrollTo(undefined, y)
|
|
146
|
+
},
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
function scrollTo(_x: number | undefined, _y: number | undefined) {
|
|
150
|
+
if (!window) return
|
|
151
|
+
|
|
152
|
+
const _element = toValue(element)
|
|
153
|
+
if (!_element) return
|
|
154
|
+
;(_element instanceof Document ? window.document.body : _element)?.scrollTo({
|
|
155
|
+
top: toValue(_y) ?? y.value,
|
|
156
|
+
left: toValue(_x) ?? x.value,
|
|
157
|
+
behavior: toValue(behavior),
|
|
158
|
+
})
|
|
159
|
+
const scrollContainer =
|
|
160
|
+
(_element as Window)?.document?.documentElement ||
|
|
161
|
+
(_element as Document)?.documentElement ||
|
|
162
|
+
(_element as Element)
|
|
163
|
+
if (x.value != null) internalX.value = scrollContainer.scrollLeft
|
|
164
|
+
if (y.value != null) internalY.value = scrollContainer.scrollTop
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const isScrolling = ref(false)
|
|
168
|
+
const arrivedState = reactive({
|
|
169
|
+
left: true,
|
|
170
|
+
right: false,
|
|
171
|
+
top: true,
|
|
172
|
+
bottom: false,
|
|
173
|
+
})
|
|
174
|
+
const directions = reactive({
|
|
175
|
+
left: false,
|
|
176
|
+
right: false,
|
|
177
|
+
top: false,
|
|
178
|
+
bottom: false,
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
const onScrollEnd = (e: Event) => {
|
|
182
|
+
// dedupe if support native scrollend event
|
|
183
|
+
if (!isScrolling.value) return
|
|
184
|
+
|
|
185
|
+
isScrolling.value = false
|
|
186
|
+
directions.left = false
|
|
187
|
+
directions.right = false
|
|
188
|
+
directions.top = false
|
|
189
|
+
directions.bottom = false
|
|
190
|
+
onStop(e)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const onScrollEndDebounced = debounce(onScrollEnd, throttle + idle)
|
|
194
|
+
|
|
195
|
+
const setArrivedState = (
|
|
196
|
+
target: HTMLElement | SVGElement | Window | Document | null | undefined,
|
|
197
|
+
) => {
|
|
198
|
+
if (!window) return
|
|
199
|
+
|
|
200
|
+
const el: Element = ((target as Window)?.document?.documentElement ||
|
|
201
|
+
(target as Document)?.documentElement ||
|
|
202
|
+
unrefElement(target as HTMLElement | SVGElement)) as Element
|
|
203
|
+
|
|
204
|
+
const { display, flexDirection } = getComputedStyle(el)
|
|
205
|
+
|
|
206
|
+
const scrollLeft = el.scrollLeft
|
|
207
|
+
directions.left = scrollLeft < internalX.value
|
|
208
|
+
directions.right = scrollLeft > internalX.value
|
|
209
|
+
|
|
210
|
+
const left = Math.abs(scrollLeft) <= (offset.left || 0)
|
|
211
|
+
const right =
|
|
212
|
+
Math.abs(scrollLeft) + el.clientWidth >=
|
|
213
|
+
el.scrollWidth - (offset.right || 0) - ARRIVED_STATE_THRESHOLD_PIXELS
|
|
214
|
+
|
|
215
|
+
if (display === 'flex' && flexDirection === 'row-reverse') {
|
|
216
|
+
arrivedState.left = right
|
|
217
|
+
arrivedState.right = left
|
|
218
|
+
} else {
|
|
219
|
+
arrivedState.left = left
|
|
220
|
+
arrivedState.right = right
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
internalX.value = scrollLeft
|
|
224
|
+
|
|
225
|
+
let scrollTop = el.scrollTop
|
|
226
|
+
|
|
227
|
+
// patch for mobile compatible
|
|
228
|
+
if (target === window.document && !scrollTop) scrollTop = window.document.body.scrollTop
|
|
229
|
+
|
|
230
|
+
directions.top = scrollTop < internalY.value
|
|
231
|
+
directions.bottom = scrollTop > internalY.value
|
|
232
|
+
const top = Math.abs(scrollTop) <= (offset.top || 0)
|
|
233
|
+
const bottom =
|
|
234
|
+
Math.abs(scrollTop) + el.clientHeight >=
|
|
235
|
+
el.scrollHeight - (offset.bottom || 0) - ARRIVED_STATE_THRESHOLD_PIXELS
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* reverse columns and rows behave exactly the other way around,
|
|
239
|
+
* bottom is treated as top and top is treated as the negative version of bottom
|
|
240
|
+
*/
|
|
241
|
+
if (display === 'flex' && flexDirection === 'column-reverse') {
|
|
242
|
+
arrivedState.top = bottom
|
|
243
|
+
arrivedState.bottom = top
|
|
244
|
+
} else {
|
|
245
|
+
arrivedState.top = top
|
|
246
|
+
arrivedState.bottom = bottom
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
internalY.value = scrollTop
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const onScrollHandler = (e: Event) => {
|
|
253
|
+
if (!window) return
|
|
254
|
+
|
|
255
|
+
const eventTarget = ((e.target as Document).documentElement ?? e.target) as HTMLElement
|
|
256
|
+
|
|
257
|
+
setArrivedState(eventTarget)
|
|
258
|
+
|
|
259
|
+
isScrolling.value = true
|
|
260
|
+
onScrollEndDebounced(e)
|
|
261
|
+
onScroll(e)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
useEventListener('scroll', throttle ? throttleFn(onScrollHandler, throttle) : onScrollHandler, {
|
|
265
|
+
target: element,
|
|
266
|
+
...eventListenerOptions,
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
onMountedOrActivated(() => {
|
|
270
|
+
try {
|
|
271
|
+
const _element = toValue(element)
|
|
272
|
+
|
|
273
|
+
if (!_element) return
|
|
274
|
+
setArrivedState(_element)
|
|
275
|
+
} catch (e) {
|
|
276
|
+
onError(e)
|
|
277
|
+
}
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
useEventListener('scrollend', onScrollEnd, {
|
|
281
|
+
target: element,
|
|
282
|
+
...eventListenerOptions,
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
x,
|
|
287
|
+
y,
|
|
288
|
+
isScrolling,
|
|
289
|
+
arrivedState,
|
|
290
|
+
directions,
|
|
291
|
+
measure() {
|
|
292
|
+
const _element = toValue(element)
|
|
293
|
+
|
|
294
|
+
if (window && _element) setArrivedState(_element)
|
|
295
|
+
},
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export type UseScrollReturn = ReturnType<typeof useScroll>
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "@allkit/tsconfig",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"baseUrl": "./",
|
|
5
|
+
"paths": {
|
|
6
|
+
"@/*": [
|
|
7
|
+
"src/*"
|
|
8
|
+
],
|
|
9
|
+
"~/*": [
|
|
10
|
+
"src/*"
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
"include": [
|
|
15
|
+
"src/**/*.ts",
|
|
16
|
+
"src/**/*.d.ts",
|
|
17
|
+
"src/**/*.tsx",
|
|
18
|
+
"src/**/*.vue",
|
|
19
|
+
"types/*.d.ts",
|
|
20
|
+
"../../types/"
|
|
21
|
+
]
|
|
22
|
+
}
|