tona-hooks 1.0.20 → 1.0.22

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 CHANGED
@@ -1,15 +1,169 @@
1
1
  # tona-hooks
2
2
 
3
- 博客园皮肤开发用的 React Hooks 工具库。
3
+ <p align="center">
4
+ <img src="../../assets/tona.png" alt="Tona" width="100" />
5
+ </p>
4
6
 
5
- ## 安装
7
+ <p align="center">
8
+ React hooks collection for CNBlogs theme development.
9
+ </p>
10
+
11
+ <p align="center">
12
+ <a href="https://www.npmjs.com/package/tona-hooks"><img src="https://img.shields.io/npm/v/tona-hooks?style=flat-square" alt="npm version"></a>
13
+ <a href="LICENSE"><img src="https://img.shields.io/npm/l/tona-hooks?style=flat-square" alt="license"></a>
14
+ <a href="https://preactjs.com"><img src="https://img.shields.io/badge/Preact->=10.0.0-673AB8?style=flat-square&logo=preact" alt="Preact"></a>
15
+ </p>
16
+
17
+ **English** | [中文](./README.zh-CN.md)
18
+
19
+ ## Features
20
+
21
+ - **Preact Compatible** - Built for Preact, works with React
22
+ - **CNBlogs Optimized** - Hooks designed specifically for CNBlogs environment
23
+ - **TypeScript Support** - Full type definitions included
24
+ - **Lightweight** - Tree-shakeable and minimal footprint
25
+
26
+ ## Installation
6
27
 
7
28
  ```bash
8
29
  npm install tona-hooks
9
30
  ```
10
31
 
11
- ## 使用
32
+ ```bash
33
+ pnpm add tona-hooks
34
+ ```
35
+
36
+ ```bash
37
+ yarn add tona-hooks
38
+ ```
39
+
40
+ ## Usage
41
+
42
+ ```typescript
43
+ import { useQueryDOM, useLocalStorage, useScroll } from 'tona-hooks'
44
+ ```
45
+
46
+ ## Available Hooks
47
+
48
+ ### `useAjaxComplete`
49
+
50
+ Listen for AJAX requests completion.
51
+
52
+ ```typescript
53
+ import { useAjaxComplete } from 'tona-hooks'
54
+
55
+ useAjaxComplete((event, xhr, settings) => {
56
+ console.log('AJAX completed:', settings.url)
57
+ })
58
+ ```
59
+
60
+ ### `useEffectOnce`
61
+
62
+ Run effect only once on mount.
63
+
64
+ ```typescript
65
+ import { useEffectOnce } from 'tona-hooks'
66
+
67
+ useEffectOnce(() => {
68
+ console.log('Component mounted')
69
+ })
70
+ ```
71
+
72
+ ### `useEventCallback`
73
+
74
+ Create a stable callback reference.
75
+
76
+ ```typescript
77
+ import { useEventCallback } from 'tona-hooks'
78
+
79
+ const handleClick = useEventCallback(() => {
80
+ console.log('Clicked!')
81
+ })
82
+ ```
83
+
84
+ ### `useIsomorphicLayoutEffect`
85
+
86
+ Safe layout effect for SSR and browser.
87
+
88
+ ```typescript
89
+ import { useIsomorphicLayoutEffect } from 'tona-hooks'
90
+
91
+ useIsomorphicLayoutEffect(() => {
92
+ // DOM measurements
93
+ }, [])
94
+ ```
12
95
 
13
- ```javascript
96
+ ### `useLocalStorage`
97
+
98
+ Persist state to localStorage.
99
+
100
+ ```typescript
101
+ import { useLocalStorage } from 'tona-hooks'
102
+
103
+ const [value, setValue] = useLocalStorage('key', 'defaultValue')
104
+ ```
105
+
106
+ ### `useQueryDOM`
107
+
108
+ Query and observe DOM elements.
109
+
110
+ ```typescript
14
111
  import { useQueryDOM } from 'tona-hooks'
112
+
113
+ const element = useQueryDOM('#my-element')
15
114
  ```
115
+
116
+ ### `useRafState`
117
+
118
+ State updates synced with requestAnimationFrame.
119
+
120
+ ```typescript
121
+ import { useRafState } from 'tona-hooks'
122
+
123
+ const [position, setPosition] = useRafState({ x: 0, y: 0 })
124
+ ```
125
+
126
+ ### `useScroll`
127
+
128
+ Track scroll position of an element.
129
+
130
+ ```typescript
131
+ import { useScroll } from 'tona-hooks'
132
+
133
+ const [x, y] = useScroll(ref)
134
+ ```
135
+
136
+ ### `useUnmount`
137
+
138
+ Run cleanup on component unmount.
139
+
140
+ ```typescript
141
+ import { useUnmount } from 'tona-hooks'
142
+
143
+ useUnmount(() => {
144
+ console.log('Component unmounted')
145
+ })
146
+ ```
147
+
148
+ ### `useWindowScroll`
149
+
150
+ Track window scroll position.
151
+
152
+ ```typescript
153
+ import { useWindowScroll } from 'tona-hooks'
154
+
155
+ const [x, y] = useWindowScroll()
156
+ ```
157
+
158
+ ## Peer Dependencies
159
+
160
+ ```json
161
+ {
162
+ "preact": ">=10.0.0"
163
+ }
164
+ ```
165
+
166
+ ## Related
167
+
168
+ - [tona](https://github.com/guangzan/tona/tree/main/packages/core) - Core framework
169
+ - [tona-ui](https://github.com/guangzan/tona/tree/main/packages/ui) - UI components
@@ -0,0 +1,169 @@
1
+ # tona-hooks
2
+
3
+ <p align="center">
4
+ <img src="../../assets/tona.png" alt="Tona" width="100" />
5
+ </p>
6
+
7
+ <p align="center">
8
+ 用于博客园主题开发的 React Hooks 集合。
9
+ </p>
10
+
11
+ <p align="center">
12
+ <a href="https://www.npmjs.com/package/tona-hooks"><img src="https://img.shields.io/npm/v/tona-hooks?style=flat-square" alt="npm version"></a>
13
+ <a href="LICENSE"><img src="https://img.shields.io/npm/l/tona-hooks?style=flat-square" alt="license"></a>
14
+ <a href="https://preactjs.com"><img src="https://img.shields.io/badge/Preact->=10.0.0-673AB8?style=flat-square&logo=preact" alt="Preact"></a>
15
+ </p>
16
+
17
+ [English](./README.md) | **中文**
18
+
19
+ ## 特性
20
+
21
+ - **Preact 兼容** - 为 Preact 构建,兼容 React
22
+ - **博客园优化** - 专为博客园环境设计的 Hooks
23
+ - **TypeScript 支持** - 包含完整的类型定义
24
+ - **轻量级** - 可摇树优化,占用空间小
25
+
26
+ ## 安装
27
+
28
+ ```bash
29
+ npm install tona-hooks
30
+ ```
31
+
32
+ ```bash
33
+ pnpm add tona-hooks
34
+ ```
35
+
36
+ ```bash
37
+ yarn add tona-hooks
38
+ ```
39
+
40
+ ## 使用
41
+
42
+ ```typescript
43
+ import { useQueryDOM, useLocalStorage, useScroll } from 'tona-hooks'
44
+ ```
45
+
46
+ ## 可用的 Hooks
47
+
48
+ ### `useAjaxComplete`
49
+
50
+ 监听 AJAX 请求完成。
51
+
52
+ ```typescript
53
+ import { useAjaxComplete } from 'tona-hooks'
54
+
55
+ useAjaxComplete((event, xhr, settings) => {
56
+ console.log('AJAX completed:', settings.url)
57
+ })
58
+ ```
59
+
60
+ ### `useEffectOnce`
61
+
62
+ 仅在挂载时运行一次 effect。
63
+
64
+ ```typescript
65
+ import { useEffectOnce } from 'tona-hooks'
66
+
67
+ useEffectOnce(() => {
68
+ console.log('Component mounted')
69
+ })
70
+ ```
71
+
72
+ ### `useEventCallback`
73
+
74
+ 创建稳定的回调引用。
75
+
76
+ ```typescript
77
+ import { useEventCallback } from 'tona-hooks'
78
+
79
+ const handleClick = useEventCallback(() => {
80
+ console.log('Clicked!')
81
+ })
82
+ ```
83
+
84
+ ### `useIsomorphicLayoutEffect`
85
+
86
+ 适用于 SSR 和浏览器的安全 layout effect。
87
+
88
+ ```typescript
89
+ import { useIsomorphicLayoutEffect } from 'tona-hooks'
90
+
91
+ useIsomorphicLayoutEffect(() => {
92
+ // DOM 测量
93
+ }, [])
94
+ ```
95
+
96
+ ### `useLocalStorage`
97
+
98
+ 将状态持久化到 localStorage。
99
+
100
+ ```typescript
101
+ import { useLocalStorage } from 'tona-hooks'
102
+
103
+ const [value, setValue] = useLocalStorage('key', 'defaultValue')
104
+ ```
105
+
106
+ ### `useQueryDOM`
107
+
108
+ 查询和观察 DOM 元素。
109
+
110
+ ```typescript
111
+ import { useQueryDOM } from 'tona-hooks'
112
+
113
+ const element = useQueryDOM('#my-element')
114
+ ```
115
+
116
+ ### `useRafState`
117
+
118
+ 与 requestAnimationFrame 同步的状态更新。
119
+
120
+ ```typescript
121
+ import { useRafState } from 'tona-hooks'
122
+
123
+ const [position, setPosition] = useRafState({ x: 0, y: 0 })
124
+ ```
125
+
126
+ ### `useScroll`
127
+
128
+ 跟踪元素的滚动位置。
129
+
130
+ ```typescript
131
+ import { useScroll } from 'tona-hooks'
132
+
133
+ const [x, y] = useScroll(ref)
134
+ ```
135
+
136
+ ### `useUnmount`
137
+
138
+ 在组件卸载时运行清理。
139
+
140
+ ```typescript
141
+ import { useUnmount } from 'tona-hooks'
142
+
143
+ useUnmount(() => {
144
+ console.log('Component unmounted')
145
+ })
146
+ ```
147
+
148
+ ### `useWindowScroll`
149
+
150
+ 跟踪窗口滚动位置。
151
+
152
+ ```typescript
153
+ import { useWindowScroll } from 'tona-hooks'
154
+
155
+ const [x, y] = useWindowScroll()
156
+ ```
157
+
158
+ ## 对等依赖
159
+
160
+ ```json
161
+ {
162
+ "preact": ">=10.0.0"
163
+ }
164
+ ```
165
+
166
+ ## 相关
167
+
168
+ - [tona](https://github.com/guangzan/tona/tree/main/packages/core) - 核心框架
169
+ - [tona-ui](https://github.com/guangzan/tona/tree/main/packages/ui) - UI 组件
package/dist/index.d.ts CHANGED
@@ -1,9 +1,8 @@
1
1
  import { Dispatch, EffectCallback, useEffect } from "preact/hooks";
2
- import { SetStateAction } from "preact/compat";
2
+ import { Dispatch as Dispatch$1, SetStateAction } from "preact/compat";
3
3
  import { RefObject } from "preact";
4
4
 
5
5
  //#region src/use-ajax-complete.d.ts
6
-
7
6
  /**
8
7
  * 通用的 Ajax 完成监听 Hook
9
8
  * @param config 配置对象
@@ -27,6 +26,16 @@ declare function useEventCallback<Args extends unknown[], R>(fn: ((...args: Args
27
26
  //#region src/use-isomorphic-layout-effect.d.ts
28
27
  declare const useIsomorphicLayoutEffect: typeof useEffect;
29
28
  //#endregion
29
+ //#region src/use-local-storage.d.ts
30
+ type parserOptions<T> = {
31
+ raw: true;
32
+ } | {
33
+ raw: false;
34
+ serializer: (value: T) => string;
35
+ deserializer: (value: string) => T;
36
+ };
37
+ declare const useLocalStorage: <T>(key: string, initialValue?: T, options?: parserOptions<T>) => [T | undefined, Dispatch$1<SetStateAction<T | undefined>>, () => void];
38
+ //#endregion
30
39
  //#region src/use-query-dom.d.ts
31
40
  interface UseQueryDomOptions<T> {
32
41
  selector: string;
@@ -68,4 +77,4 @@ interface State {
68
77
  }
69
78
  declare const useWindowScroll: () => State;
70
79
  //#endregion
71
- export { useAjaxComplete, useEffectOnce, useEventCallback, useIsomorphicLayoutEffect, useQueryDOM, useRafState, useScroll, useUnmount, useWindowScroll };
80
+ export { useAjaxComplete, useEffectOnce, useEventCallback, useIsomorphicLayoutEffect, useLocalStorage, useQueryDOM, useRafState, useScroll, useUnmount, useWindowScroll };
package/dist/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "preact/hooks";
2
- import { useCallback as useCallback$1, useRef as useRef$1 } from "preact/compat";
3
-
2
+ import { useCallback as useCallback$1, useLayoutEffect as useLayoutEffect$1, useRef as useRef$1, useState as useState$1 } from "preact/compat";
4
3
  //#region src/use-ajax-complete.ts
5
4
  /**
6
5
  * 通用的 Ajax 完成监听 Hook
@@ -27,17 +26,14 @@ function useAjaxComplete(config) {
27
26
  };
28
27
  }, [config]);
29
28
  }
30
-
31
29
  //#endregion
32
30
  //#region src/use-effect-once.ts
33
31
  const useEffectOnce = (effect) => {
34
32
  useEffect(effect, []);
35
33
  };
36
-
37
34
  //#endregion
38
35
  //#region src/use-isomorphic-layout-effect.ts
39
36
  const useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
40
-
41
37
  //#endregion
42
38
  //#region src/use-event-callback.ts
43
39
  function useEventCallback(fn) {
@@ -49,7 +45,65 @@ function useEventCallback(fn) {
49
45
  }, [fn]);
50
46
  return useCallback$1((...args) => ref.current?.(...args), [ref]);
51
47
  }
52
-
48
+ //#endregion
49
+ //#region src/misc/util.ts
50
+ const noop = () => {};
51
+ function on(obj, ...args) {
52
+ if (obj?.addEventListener) obj.addEventListener(...args);
53
+ }
54
+ function off(obj, ...args) {
55
+ if (obj?.removeEventListener) obj.removeEventListener(...args);
56
+ }
57
+ const isBrowser = typeof window !== "undefined";
58
+ //#endregion
59
+ //#region src/use-local-storage.ts
60
+ const useLocalStorage = (key, initialValue, options) => {
61
+ if (!isBrowser) return [
62
+ initialValue,
63
+ noop,
64
+ noop
65
+ ];
66
+ if (!key) throw new Error("useLocalStorage key may not be falsy");
67
+ const deserializer = options ? options.raw ? (value) => value : options.deserializer : JSON.parse;
68
+ const initializer = useRef$1((key) => {
69
+ try {
70
+ const serializer = options ? options.raw ? String : options.serializer : JSON.stringify;
71
+ const localStorageValue = localStorage.getItem(key);
72
+ if (localStorageValue !== null) return deserializer(localStorageValue);
73
+ else {
74
+ initialValue && localStorage.setItem(key, serializer(initialValue));
75
+ return initialValue;
76
+ }
77
+ } catch {
78
+ return initialValue;
79
+ }
80
+ });
81
+ const [state, setState] = useState$1(() => initializer.current(key));
82
+ useLayoutEffect$1(() => setState(initializer.current(key)), [key]);
83
+ return [
84
+ state,
85
+ useCallback$1((valOrFunc) => {
86
+ try {
87
+ const newState = typeof valOrFunc === "function" ? valOrFunc(state) : valOrFunc;
88
+ if (typeof newState === "undefined") return;
89
+ let value;
90
+ if (options) if (options.raw) if (typeof newState === "string") value = newState;
91
+ else value = JSON.stringify(newState);
92
+ else if (options.serializer) value = options.serializer(newState);
93
+ else value = JSON.stringify(newState);
94
+ else value = JSON.stringify(newState);
95
+ localStorage.setItem(key, value);
96
+ setState(deserializer(value));
97
+ } catch {}
98
+ }, [key, setState]),
99
+ useCallback$1(() => {
100
+ try {
101
+ localStorage.removeItem(key);
102
+ setState(void 0);
103
+ } catch {}
104
+ }, [key, setState])
105
+ ];
106
+ };
53
107
  //#endregion
54
108
  //#region src/use-query-dom.ts
55
109
  function useQueryDOM({ selector, observe = false, queryFn, ajaxUrl }) {
@@ -150,7 +204,6 @@ function useQueryDOM({ selector, observe = false, queryFn, ajaxUrl }) {
150
204
  isPending
151
205
  };
152
206
  }
153
-
154
207
  //#endregion
155
208
  //#region src/use-unmount.ts
156
209
  const useUnmount = (fn) => {
@@ -158,7 +211,6 @@ const useUnmount = (fn) => {
158
211
  fnRef.current = fn;
159
212
  useEffectOnce(() => () => fnRef.current());
160
213
  };
161
-
162
214
  //#endregion
163
215
  //#region src/use-raf-state.ts
164
216
  const useRafState = (initialState) => {
@@ -175,21 +227,9 @@ const useRafState = (initialState) => {
175
227
  });
176
228
  return [state, setRafState];
177
229
  };
178
-
179
- //#endregion
180
- //#region src/misc/util.ts
181
- function on(obj, ...args) {
182
- if (obj?.addEventListener) obj.addEventListener(...args);
183
- }
184
- function off(obj, ...args) {
185
- if (obj?.removeEventListener) obj.removeEventListener(...args);
186
- }
187
- const isBrowser = typeof window !== "undefined";
188
-
189
230
  //#endregion
190
231
  //#region src/use-scroll.ts
191
232
  const useScroll = (ref) => {
192
- if (typeof ref !== "object" || typeof ref.current === "undefined") console.error("`useScroll` expects a single ref argument.");
193
233
  const [state, setState] = useRafState({
194
234
  x: 0,
195
235
  y: 0
@@ -211,7 +251,6 @@ const useScroll = (ref) => {
211
251
  }, [ref, setState]);
212
252
  return state;
213
253
  };
214
-
215
254
  //#endregion
216
255
  //#region src/use-window-scroll.ts
217
256
  const useWindowScroll = () => {
@@ -221,12 +260,12 @@ const useWindowScroll = () => {
221
260
  }));
222
261
  useEffect(() => {
223
262
  const handler = () => {
224
- setState((state$1) => {
263
+ setState((state) => {
225
264
  const { pageXOffset, pageYOffset } = window;
226
- return state$1.x !== pageXOffset || state$1.y !== pageYOffset ? {
265
+ return state.x !== pageXOffset || state.y !== pageYOffset ? {
227
266
  x: pageXOffset,
228
267
  y: pageYOffset
229
- } : state$1;
268
+ } : state;
230
269
  });
231
270
  };
232
271
  handler();
@@ -240,6 +279,5 @@ const useWindowScroll = () => {
240
279
  }, [setState]);
241
280
  return state;
242
281
  };
243
-
244
282
  //#endregion
245
- export { useAjaxComplete, useEffectOnce, useEventCallback, useIsomorphicLayoutEffect, useQueryDOM, useRafState, useScroll, useUnmount, useWindowScroll };
283
+ export { useAjaxComplete, useEffectOnce, useEventCallback, useIsomorphicLayoutEffect, useLocalStorage, useQueryDOM, useRafState, useScroll, useUnmount, useWindowScroll };
package/package.json CHANGED
@@ -1,46 +1,46 @@
1
1
  {
2
2
  "name": "tona-hooks",
3
- "version": "1.0.20",
3
+ "version": "1.0.22",
4
4
  "description": "",
5
+ "keywords": [
6
+ "博客园"
7
+ ],
8
+ "homepage": "https://github.com/guangzan/tona/tree/main/packages/hooks#readme",
9
+ "bugs": {
10
+ "url": "https://github.com/guangzan/tona/issues"
11
+ },
12
+ "license": "MIT",
5
13
  "author": {
6
14
  "name": "guangzan",
7
- "url": "https://www.cnblogs.com/guangzan",
8
- "email": "guangzan1999@outlook.com"
15
+ "email": "guangzan1999@outlook.com",
16
+ "url": "https://www.cnblogs.com/guangzan"
9
17
  },
10
- "license": "MIT",
11
- "homepage": "https://github.com/guangzan/tona/tree/main/packages/hooks#readme",
12
18
  "repository": {
13
19
  "type": "git",
14
20
  "url": "git+https://github.com/guangzan/tona.git",
15
21
  "directory": "packages/hooks"
16
22
  },
17
- "bugs": {
18
- "url": "https://github.com/guangzan/tona/issues"
19
- },
20
- "keywords": [
21
- "博客园"
23
+ "files": [
24
+ "dist"
22
25
  ],
23
26
  "type": "module",
27
+ "main": "./dist/index.js",
28
+ "module": "./dist/index.js",
29
+ "types": "./dist/index.d.ts",
24
30
  "exports": {
25
31
  ".": {
26
32
  "types": "./dist/index.d.ts",
27
33
  "import": "./dist/index.js"
28
34
  }
29
35
  },
30
- "main": "./dist/index.js",
31
- "module": "./dist/index.js",
32
- "types": "./dist/index.d.ts",
33
- "files": [
34
- "dist"
35
- ],
36
36
  "devDependencies": {
37
- "tsdown": "latest"
37
+ "vite-plus": "latest"
38
38
  },
39
39
  "peerDependencies": {
40
- "preact": "^10.28.2"
40
+ "preact": "^10.29.0"
41
41
  },
42
42
  "scripts": {
43
- "dev": "tsdown --watch",
44
- "build": "tsdown"
43
+ "dev": "vp pack --watch",
44
+ "build": "vp pack"
45
45
  }
46
46
  }