@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.
@@ -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
+ }