@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,61 @@
|
|
|
1
|
+
# defineCtxState
|
|
2
|
+
|
|
3
|
+
定义当前组件作用域的共享数据,配合 `useCtxState` 在子 hooks 中获取。
|
|
4
|
+
|
|
5
|
+
## Type Signature
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
type DeepPartial<T> = {
|
|
9
|
+
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type IStateTuple<T> = readonly [
|
|
13
|
+
Readonly<T>,
|
|
14
|
+
(state: DeepPartial<T> | ((newState: T) => void)) => void,
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
function defineCtxState<T extends Record<string, any>>(data: T): IStateTuple<T>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Parameters
|
|
21
|
+
|
|
22
|
+
| Name | Type | Description |
|
|
23
|
+
|------|------|-------------|
|
|
24
|
+
| `data` | `T` | 需要共享的初始数据 |
|
|
25
|
+
|
|
26
|
+
## Returns
|
|
27
|
+
|
|
28
|
+
返回元组 `[state, setState]`:
|
|
29
|
+
- `state`: 共享数据的只读代理
|
|
30
|
+
- `setState`: 更新共享数据的方法
|
|
31
|
+
|
|
32
|
+
## Example
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { defineCtxState } from '@allkit/use'
|
|
36
|
+
|
|
37
|
+
const [state, setState] = defineCtxState({
|
|
38
|
+
listLoading: false,
|
|
39
|
+
userList: [],
|
|
40
|
+
pagination: {
|
|
41
|
+
current: 1,
|
|
42
|
+
size: 20,
|
|
43
|
+
total: 0
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
// 对象写法
|
|
48
|
+
setState({ listLoading: true })
|
|
49
|
+
|
|
50
|
+
// 函数写法
|
|
51
|
+
setState((state) => {
|
|
52
|
+
state.listLoading = true
|
|
53
|
+
state.pagination.current = 2
|
|
54
|
+
})
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Use Cases
|
|
58
|
+
|
|
59
|
+
- 功能模块入口页面需要与多个子 hooks 共享状态
|
|
60
|
+
- 避免通过 props 层层传递数据
|
|
61
|
+
- 实现 hooks 之间的数据通信
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# onMountedOrActivated
|
|
2
|
+
|
|
3
|
+
在 `mounted` 或 `activated` (keep-alive 缓存激活) 时执行回调。
|
|
4
|
+
|
|
5
|
+
## Type Signature
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
function onMountedOrActivated(hook: () => any): void
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Parameters
|
|
12
|
+
|
|
13
|
+
| Name | Type | Description |
|
|
14
|
+
|------|------|-------------|
|
|
15
|
+
| `hook` | `() => any` | 要执行的回调函数 |
|
|
16
|
+
|
|
17
|
+
## Example
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { onMountedOrActivated } from '@allkit/use'
|
|
21
|
+
|
|
22
|
+
// 在 mounted 和每次 activated 时执行
|
|
23
|
+
onMountedOrActivated(() => {
|
|
24
|
+
console.log('组件已挂载或激活')
|
|
25
|
+
// 刷新数据等操作
|
|
26
|
+
})
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Use Cases
|
|
30
|
+
|
|
31
|
+
- keep-alive 缓存组件需要在每次激活时刷新数据
|
|
32
|
+
- 需要在 mounted 和 activated 时都执行的初始化逻辑
|
|
33
|
+
- 配合 `useEventListener` 实现事件监听的自动管理
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# useCtxState
|
|
2
|
+
|
|
3
|
+
获取当前组件作用域 `defineCtxState` 定义的共享数据。
|
|
4
|
+
|
|
5
|
+
## Type Signature
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
function useCtxState<T = Record<string, any>>(): IStateTuple<T>
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Returns
|
|
12
|
+
|
|
13
|
+
返回元组 `[state, setState]`,与 `defineCtxState` 相同。
|
|
14
|
+
|
|
15
|
+
## Example
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { useCtxState } from '@allkit/use'
|
|
19
|
+
|
|
20
|
+
export const useList = () => {
|
|
21
|
+
const [state, setState] = useCtxState<ListState>()
|
|
22
|
+
|
|
23
|
+
const handleReset = () => {
|
|
24
|
+
setState((state) => {
|
|
25
|
+
state.searchForm = {}
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return () => <div>{state.userList.length}</div>
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Notes
|
|
34
|
+
|
|
35
|
+
- 必须在 `setup()` 函数中调用
|
|
36
|
+
- 只能获取同一组件实例中 `defineCtxState` 定义的数据
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# useEventListener
|
|
2
|
+
|
|
3
|
+
事件监听,支持 Vue 缓存(keep-alive),页面销毁自动回收。
|
|
4
|
+
|
|
5
|
+
## Type Signature
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
type UseEventListenerOptions = {
|
|
9
|
+
target?: TargetRef
|
|
10
|
+
capture?: boolean
|
|
11
|
+
passive?: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function useEventListener<K extends keyof DocumentEventMap>(
|
|
15
|
+
type: K,
|
|
16
|
+
listener: (event: DocumentEventMap[K]) => void,
|
|
17
|
+
options?: UseEventListenerOptions,
|
|
18
|
+
): () => void
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Parameters
|
|
22
|
+
|
|
23
|
+
| Name | Type | Default | Description |
|
|
24
|
+
|------|------|---------|-------------|
|
|
25
|
+
| `type` | `string` | - | 事件类型 (scroll, resize, click 等) |
|
|
26
|
+
| `listener` | `EventListener` | - | 监听函数 |
|
|
27
|
+
| `options.target` | `TargetRef` | `window` | 监听目标 |
|
|
28
|
+
| `options.capture` | `boolean` | `false` | 是否捕获 |
|
|
29
|
+
| `options.passive` | `boolean` | `false` | 是否被动 |
|
|
30
|
+
|
|
31
|
+
## Returns
|
|
32
|
+
|
|
33
|
+
返回清理函数,调用后移除事件监听。
|
|
34
|
+
|
|
35
|
+
## Example
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import { useEventListener } from '@allkit/use'
|
|
39
|
+
|
|
40
|
+
// 监听 window 滚动
|
|
41
|
+
const cleanup = useEventListener('scroll', () => {
|
|
42
|
+
console.log('scrolling')
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
// 监听指定元素
|
|
46
|
+
const boxRef = ref<HTMLElement>()
|
|
47
|
+
useEventListener('click', (e) => {
|
|
48
|
+
console.log('clicked', e)
|
|
49
|
+
}, { target: boxRef })
|
|
50
|
+
|
|
51
|
+
// 监听键盘事件
|
|
52
|
+
useEventListener('keydown', (e: KeyboardEvent) => {
|
|
53
|
+
if (e.key === 'Escape') {
|
|
54
|
+
// handle escape
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
// 组件卸载时自动清理
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Use Cases
|
|
62
|
+
|
|
63
|
+
- 监听 window/document 事件(resize、scroll、keydown 等)
|
|
64
|
+
- 监听 DOM 元素事件
|
|
65
|
+
- keep-alive 组件需要自动添加/移除事件监听
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# usePageVisibility
|
|
2
|
+
|
|
3
|
+
判断页面是否可见或不可见,用于处理用户切换标签页的场景。
|
|
4
|
+
|
|
5
|
+
## Type Signature
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
function usePageVisibility(): {
|
|
9
|
+
onPageShow: (cb: () => void) => void
|
|
10
|
+
onPageHidden: (cb: () => void) => void
|
|
11
|
+
}
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Returns
|
|
15
|
+
|
|
16
|
+
| Method | Description |
|
|
17
|
+
|--------|-------------|
|
|
18
|
+
| `onPageShow` | 页面显示时执行的回调 |
|
|
19
|
+
| `onPageHidden` | 页面隐藏时执行的回调 |
|
|
20
|
+
|
|
21
|
+
## Example
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { usePageVisibility } from '@allkit/use'
|
|
25
|
+
|
|
26
|
+
const { onPageShow, onPageHidden } = usePageVisibility()
|
|
27
|
+
|
|
28
|
+
// 页面显示可见
|
|
29
|
+
onPageShow(() => {
|
|
30
|
+
console.log('页面可见')
|
|
31
|
+
// 恢复轮询、视频播放等
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
// 页面隐藏
|
|
35
|
+
onPageHidden(() => {
|
|
36
|
+
console.log('页面隐藏')
|
|
37
|
+
// 暂停轮询、视频播放等
|
|
38
|
+
})
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Use Cases
|
|
42
|
+
|
|
43
|
+
- 用户切换浏览器标签页时暂停/恢复操作
|
|
44
|
+
- 页面隐藏时停止轮询请求,显示时恢复
|
|
45
|
+
- 视频播放器在页面隐藏时自动暂停
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# useScroll
|
|
2
|
+
|
|
3
|
+
监听页面布局滚动事件,提供滚动位置、方向、状态等信息。
|
|
4
|
+
|
|
5
|
+
## Type Signature
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
interface UseScrollOptions {
|
|
9
|
+
throttle?: number
|
|
10
|
+
idle?: number
|
|
11
|
+
offset?: {
|
|
12
|
+
left?: number
|
|
13
|
+
right?: number
|
|
14
|
+
top?: number
|
|
15
|
+
bottom?: number
|
|
16
|
+
}
|
|
17
|
+
onScroll?: (e: Event) => void
|
|
18
|
+
onStop?: (e: Event) => void
|
|
19
|
+
eventListenerOptions?: AddEventListenerOptions
|
|
20
|
+
behavior?: ScrollBehavior
|
|
21
|
+
onError?: (error: unknown) => void
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function useScroll(
|
|
25
|
+
element: MaybeElementRef<MaybeElement>,
|
|
26
|
+
options?: UseScrollOptions
|
|
27
|
+
): {
|
|
28
|
+
x: ComputedRef<number>
|
|
29
|
+
y: ComputedRef<number>
|
|
30
|
+
isScrolling: Ref<boolean>
|
|
31
|
+
arrivedState: Reactive<{ left: boolean; right: boolean; top: boolean; bottom: boolean }>
|
|
32
|
+
directions: Reactive<{ left: boolean; right: boolean; top: boolean; bottom: boolean }>
|
|
33
|
+
measure: () => void
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Parameters
|
|
38
|
+
|
|
39
|
+
| Name | Type | Default | Description |
|
|
40
|
+
|------|------|---------|-------------|
|
|
41
|
+
| `element` | `MaybeElementRef` | - | 监听的元素,支持 window、document、HTMLElement 及 Ref |
|
|
42
|
+
| `options.throttle` | `number` | `0` | 节流时间 |
|
|
43
|
+
| `options.idle` | `number` | `200` | 滚动结束检测时间 |
|
|
44
|
+
| `options.offset` | `object` | `{}` | 边界偏移 |
|
|
45
|
+
| `options.onScroll` | `function` | - | 滚动时回调 |
|
|
46
|
+
| `options.onStop` | `function` | - | 滚动结束时回调 |
|
|
47
|
+
|
|
48
|
+
## Returns
|
|
49
|
+
|
|
50
|
+
| Property | Type | Description |
|
|
51
|
+
|----------|------|-------------|
|
|
52
|
+
| `x` | `ComputedRef<number>` | 水平滚动距离 |
|
|
53
|
+
| `y` | `ComputedRef<number>` | 垂直滚动距离 |
|
|
54
|
+
| `isScrolling` | `Ref<boolean>` | 是否正在滚动 |
|
|
55
|
+
| `arrivedState` | `Reactive` | 是否到达边界 `{ left, right, top, bottom }` |
|
|
56
|
+
| `directions` | `Reactive` | 滚动方向 `{ left, right, top, bottom }` |
|
|
57
|
+
| `measure` | `function` | 手动测量滚动状态 |
|
|
58
|
+
|
|
59
|
+
## Example
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { useScroll } from '@allkit/use'
|
|
63
|
+
|
|
64
|
+
// 监听 window 滚动
|
|
65
|
+
const { y, isScrolling, arrivedState } = useScroll(window)
|
|
66
|
+
|
|
67
|
+
// 监听指定元素
|
|
68
|
+
const containerRef = ref<HTMLElement>()
|
|
69
|
+
const scrollState = useScroll(containerRef, {
|
|
70
|
+
throttle: 100,
|
|
71
|
+
onScroll: (e) => {
|
|
72
|
+
console.log('scrolling')
|
|
73
|
+
},
|
|
74
|
+
onStop: (e) => {
|
|
75
|
+
console.log('scroll stopped')
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
// 判断是否滚动到底部
|
|
80
|
+
if (arrivedState.bottom) {
|
|
81
|
+
// 加载更多数据
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Use Cases
|
|
86
|
+
|
|
87
|
+
- 无限滚动列表,滚动到底部加载更多
|
|
88
|
+
- 吸顶导航栏,滚动超过一定距离显示
|
|
89
|
+
- 滚动进度指示器
|
|
90
|
+
- 虚拟列表滚动位置追踪
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { nextTick, onMounted, onActivated } from 'vue'
|
|
2
|
+
|
|
3
|
+
export function onMountedOrActivated(hook: () => any) {
|
|
4
|
+
let mounted: boolean
|
|
5
|
+
|
|
6
|
+
onMounted(() => {
|
|
7
|
+
hook()
|
|
8
|
+
nextTick(() => {
|
|
9
|
+
mounted = true
|
|
10
|
+
})
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
onActivated(() => {
|
|
14
|
+
if (mounted) {
|
|
15
|
+
hook()
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type MaybeRef, type ComponentPublicInstance, type MaybeRefOrGetter } from 'vue'
|
|
2
|
+
|
|
3
|
+
export interface ConfigurableWindow {
|
|
4
|
+
/*
|
|
5
|
+
* Specify a custom `window` instance, e.g. working with iframes or in testing environments.
|
|
6
|
+
*/
|
|
7
|
+
window?: Window
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ConfigurableDocument {
|
|
11
|
+
/*
|
|
12
|
+
* Specify a custom `document` instance, e.g. working with iframes or in testing environments.
|
|
13
|
+
*/
|
|
14
|
+
document?: Document
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type VueInstance = ComponentPublicInstance
|
|
18
|
+
export type MaybeElement = Window | Document | HTMLElement | SVGElement | undefined | null
|
|
19
|
+
|
|
20
|
+
export type MaybeElementRef<T extends MaybeElement = MaybeElement> = MaybeRef<T>
|
|
21
|
+
export type MaybeComputedElementRef<T extends MaybeElement = MaybeElement> = MaybeRefOrGetter<T>
|
|
22
|
+
|
|
23
|
+
export type TargetRef = MaybeElementRef<MaybeElement> | MaybeRef<EventTarget>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { getCurrentInstance, reactive } from 'vue'
|
|
2
|
+
import type { ComponentInternalInstance } from 'vue'
|
|
3
|
+
|
|
4
|
+
type CtxComponentInternalInstance = ComponentInternalInstance & {
|
|
5
|
+
provides: Record<string, any>
|
|
6
|
+
type: {
|
|
7
|
+
name?: string
|
|
8
|
+
__scopeId?: string
|
|
9
|
+
__name?: string
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
type DeepPartial<T> = {
|
|
13
|
+
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type IStateTuple<T> = readonly [
|
|
17
|
+
Readonly<T>,
|
|
18
|
+
(state: DeepPartial<T> | ((newState: T) => void)) => void,
|
|
19
|
+
]
|
|
20
|
+
/**
|
|
21
|
+
* 定义当前组件作用域的共享数据
|
|
22
|
+
* @param data - 需要共享的数据
|
|
23
|
+
* @returns [state, setState]
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* // state: 共享数据的只读代理
|
|
27
|
+
* const [state, setState] = defineCtxState({ count: 0 })
|
|
28
|
+
*
|
|
29
|
+
* //setState: 更新共享数据的方法,会合并旧数据
|
|
30
|
+
* setState({ count: 2 })
|
|
31
|
+
* //推荐函数写法
|
|
32
|
+
* setState((state) => {state.count = 2})
|
|
33
|
+
*
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
*/
|
|
37
|
+
export const defineCtxState = <T extends Record<string, any>>(data: T) => {
|
|
38
|
+
const state = reactive(data) as T
|
|
39
|
+
const setState = (newState: DeepPartial<T> | ((newState: T) => void)) => {
|
|
40
|
+
if (typeof newState === 'function') {
|
|
41
|
+
newState(state as T)
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
Object.assign(state, newState)
|
|
45
|
+
}
|
|
46
|
+
const stateTuple = [state as T, setState] as const
|
|
47
|
+
const instance = getCurrentInstance() as CtxComponentInternalInstance
|
|
48
|
+
if (!instance) {
|
|
49
|
+
console.error('useCtxState must be called within a setup function')
|
|
50
|
+
return stateTuple
|
|
51
|
+
}
|
|
52
|
+
const { provides = {}, uid, type } = instance
|
|
53
|
+
const key = `state_${uid}_${type.name || type.__name || ''}`
|
|
54
|
+
provides[key] = stateTuple
|
|
55
|
+
return stateTuple
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 获取当前组件作用域{@link defineCtxState}定义的共享数据
|
|
60
|
+
*
|
|
61
|
+
* @see defineCtxState
|
|
62
|
+
* @returns [state, setState]
|
|
63
|
+
* @example
|
|
64
|
+
*
|
|
65
|
+
* ```ts
|
|
66
|
+
* // state: 共享数据的只读代理
|
|
67
|
+
* const [state, setState] = usCtxState({ count: 0 })
|
|
68
|
+
*
|
|
69
|
+
* //setState: 更新共享数据的方法
|
|
70
|
+
* setState({ count: 2 })
|
|
71
|
+
* setState((state) => {state.count = 2})
|
|
72
|
+
*
|
|
73
|
+
* ```
|
|
74
|
+
*
|
|
75
|
+
*/
|
|
76
|
+
export const useCtxState = <T = Record<string, any>>() => {
|
|
77
|
+
const instance = getCurrentInstance() as CtxComponentInternalInstance
|
|
78
|
+
if (!instance) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
'useCtxState is called when there is no active component instance to be associated with. Lifecycle injection APIs can only be used during execution of setup()',
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
const { provides = {}, uid, type } = instance
|
|
84
|
+
const key = `state_${uid}_${type.name || type.__name || ''}`
|
|
85
|
+
const stateTuple = provides[key] || []
|
|
86
|
+
return stateTuple as IStateTuple<T>
|
|
87
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { isRef, onDeactivated, onUnmounted, unref, watch, type WatchStopHandle } from 'vue'
|
|
2
|
+
import { isClient } from '@allkit/shared'
|
|
3
|
+
import { onMountedOrActivated } from '../onMountedOrActivated'
|
|
4
|
+
import { type TargetRef } from '../types'
|
|
5
|
+
|
|
6
|
+
export type UseEventListenerOptions = {
|
|
7
|
+
target?: TargetRef
|
|
8
|
+
capture?: boolean
|
|
9
|
+
passive?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function useEventListener<K extends keyof DocumentEventMap>(
|
|
13
|
+
type: K,
|
|
14
|
+
listener: (event: DocumentEventMap[K]) => void,
|
|
15
|
+
options?: UseEventListenerOptions,
|
|
16
|
+
): () => void
|
|
17
|
+
export function useEventListener(
|
|
18
|
+
type: string,
|
|
19
|
+
listener: EventListener,
|
|
20
|
+
options?: UseEventListenerOptions,
|
|
21
|
+
): () => void
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* useEventListener 事件监听,支持vue缓存,页面销毁自动回收
|
|
25
|
+
* @param type - 事件类型(scroll, resize, click, etc.)
|
|
26
|
+
* @param listener - 监听函数
|
|
27
|
+
* @param options - 选项
|
|
28
|
+
*/
|
|
29
|
+
export function useEventListener(
|
|
30
|
+
type: string,
|
|
31
|
+
listener: EventListener,
|
|
32
|
+
options: UseEventListenerOptions = {},
|
|
33
|
+
) {
|
|
34
|
+
if (!isClient) {
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const { target = window, passive = false, capture = false } = options
|
|
39
|
+
|
|
40
|
+
let cleaned = false
|
|
41
|
+
let attached: boolean
|
|
42
|
+
|
|
43
|
+
const add = (target?: TargetRef) => {
|
|
44
|
+
if (cleaned) {
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const element = unref(target)
|
|
49
|
+
if (element && !attached) {
|
|
50
|
+
element.addEventListener(type, listener, {
|
|
51
|
+
capture,
|
|
52
|
+
passive,
|
|
53
|
+
})
|
|
54
|
+
attached = true
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const remove = (target?: TargetRef) => {
|
|
59
|
+
if (cleaned) {
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
const element = unref(target)
|
|
63
|
+
|
|
64
|
+
if (element && attached) {
|
|
65
|
+
element.removeEventListener(type, listener, capture)
|
|
66
|
+
attached = false
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
onUnmounted(() => remove(target))
|
|
71
|
+
onDeactivated(() => remove(target))
|
|
72
|
+
onMountedOrActivated(() => add(target))
|
|
73
|
+
|
|
74
|
+
let stopWatch: WatchStopHandle
|
|
75
|
+
|
|
76
|
+
if (isRef(target)) {
|
|
77
|
+
stopWatch = watch(target, (val, oldVal) => {
|
|
78
|
+
remove(oldVal)
|
|
79
|
+
add(val)
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Clean up the event listener
|
|
85
|
+
*/
|
|
86
|
+
return () => {
|
|
87
|
+
stopWatch?.()
|
|
88
|
+
remove(target)
|
|
89
|
+
cleaned = true
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { onBeforeUnmount, onMounted } from 'vue'
|
|
2
|
+
|
|
3
|
+
type visibleCbFunc = () => void
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 判断页面是否可见或者不可见
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* let {onPageShow,onPageHidden}=usePageVisibility()
|
|
10
|
+
* //页面显示可见
|
|
11
|
+
* onPageShow(()=>{})
|
|
12
|
+
* //页面隐藏
|
|
13
|
+
* onPageHidden(()=>{})
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export const usePageVisibility = () => {
|
|
17
|
+
let onShowCb: visibleCbFunc
|
|
18
|
+
let onHiddenCb: visibleCbFunc
|
|
19
|
+
const onPageShow = (cb: visibleCbFunc) => {
|
|
20
|
+
onShowCb = cb
|
|
21
|
+
}
|
|
22
|
+
const onPageHidden = (cb: visibleCbFunc) => {
|
|
23
|
+
onHiddenCb = cb
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const onVisibilitychangeListener = () => {
|
|
27
|
+
if (document.visibilityState === 'hidden') {
|
|
28
|
+
onHiddenCb && onHiddenCb()
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
onShowCb && onShowCb()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
onMounted(() => {
|
|
35
|
+
document.addEventListener('visibilitychange', onVisibilitychangeListener)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
onBeforeUnmount(() => {
|
|
39
|
+
document.removeEventListener('visibilitychange', onVisibilitychangeListener)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
onPageShow,
|
|
44
|
+
onPageHidden,
|
|
45
|
+
}
|
|
46
|
+
}
|