@ug666/ui-react 0.1.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/chunk-MGXIF756.js +313 -0
- package/dist/chunk-MGXIF756.js.map +1 -0
- package/dist/hooks/index.cjs +351 -0
- package/dist/hooks/index.cjs.map +1 -0
- package/dist/hooks/index.d.cts +232 -0
- package/dist/hooks/index.d.ts +232 -0
- package/dist/hooks/index.js +31 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/index.cjs +3799 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1363 -0
- package/dist/index.d.ts +1363 -0
- package/dist/index.js +3383 -0
- package/dist/index.js.map +1 -0
- package/dist/tokens.css +9 -0
- package/package.json +53 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/hooks/index.ts
|
|
21
|
+
var hooks_exports = {};
|
|
22
|
+
__export(hooks_exports, {
|
|
23
|
+
useClickOutside: () => useClickOutside,
|
|
24
|
+
useCopyToClipboard: () => useCopyToClipboard,
|
|
25
|
+
useDebounce: () => useDebounce,
|
|
26
|
+
useEventListener: () => useEventListener,
|
|
27
|
+
useForm: () => useForm,
|
|
28
|
+
useHotkeys: () => useHotkeys,
|
|
29
|
+
useInterval: () => useInterval,
|
|
30
|
+
useLocalStorage: () => useLocalStorage,
|
|
31
|
+
useMediaQuery: () => useMediaQuery,
|
|
32
|
+
useMounted: () => useMounted,
|
|
33
|
+
usePagination: () => usePagination,
|
|
34
|
+
useTimeout: () => useTimeout,
|
|
35
|
+
useToggle: () => useToggle
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(hooks_exports);
|
|
38
|
+
|
|
39
|
+
// src/hooks/use-debounce.ts
|
|
40
|
+
var import_react = require("react");
|
|
41
|
+
function useDebounce(value, delay = 300) {
|
|
42
|
+
const [debounced, setDebounced] = (0, import_react.useState)(value);
|
|
43
|
+
(0, import_react.useEffect)(() => {
|
|
44
|
+
const timer = setTimeout(() => setDebounced(value), delay);
|
|
45
|
+
return () => clearTimeout(timer);
|
|
46
|
+
}, [value, delay]);
|
|
47
|
+
return debounced;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/hooks/use-local-storage.ts
|
|
51
|
+
var import_react2 = require("react");
|
|
52
|
+
function useLocalStorage(key, initialValue) {
|
|
53
|
+
const [storedValue, setStoredValue] = (0, import_react2.useState)(() => {
|
|
54
|
+
if (typeof window === "undefined") return initialValue;
|
|
55
|
+
try {
|
|
56
|
+
const item = window.localStorage.getItem(key);
|
|
57
|
+
return item ? JSON.parse(item) : initialValue;
|
|
58
|
+
} catch {
|
|
59
|
+
return initialValue;
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
const setValue = (0, import_react2.useCallback)((value) => {
|
|
63
|
+
setStoredValue((prev) => {
|
|
64
|
+
const next = value instanceof Function ? value(prev) : value;
|
|
65
|
+
if (typeof window !== "undefined") {
|
|
66
|
+
window.localStorage.setItem(key, JSON.stringify(next));
|
|
67
|
+
}
|
|
68
|
+
return next;
|
|
69
|
+
});
|
|
70
|
+
}, [key]);
|
|
71
|
+
const removeValue = (0, import_react2.useCallback)(() => {
|
|
72
|
+
setStoredValue(initialValue);
|
|
73
|
+
if (typeof window !== "undefined") {
|
|
74
|
+
window.localStorage.removeItem(key);
|
|
75
|
+
}
|
|
76
|
+
}, [key, initialValue]);
|
|
77
|
+
return [storedValue, setValue, removeValue];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/hooks/use-media-query.ts
|
|
81
|
+
var import_react3 = require("react");
|
|
82
|
+
function useMediaQuery(query) {
|
|
83
|
+
const [matches, setMatches] = (0, import_react3.useState)(() => {
|
|
84
|
+
if (typeof window === "undefined") return false;
|
|
85
|
+
return window.matchMedia(query).matches;
|
|
86
|
+
});
|
|
87
|
+
(0, import_react3.useEffect)(() => {
|
|
88
|
+
if (typeof window === "undefined") return;
|
|
89
|
+
const mediaQueryList = window.matchMedia(query);
|
|
90
|
+
setMatches(mediaQueryList.matches);
|
|
91
|
+
const handleChange = (event) => {
|
|
92
|
+
setMatches(event.matches);
|
|
93
|
+
};
|
|
94
|
+
mediaQueryList.addEventListener("change", handleChange);
|
|
95
|
+
return () => mediaQueryList.removeEventListener("change", handleChange);
|
|
96
|
+
}, [query]);
|
|
97
|
+
return matches;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/hooks/use-click-outside.ts
|
|
101
|
+
var import_react4 = require("react");
|
|
102
|
+
function useClickOutside(ref, handler) {
|
|
103
|
+
(0, import_react4.useEffect)(() => {
|
|
104
|
+
const handleMouseDown = (event) => {
|
|
105
|
+
if (ref.current && !ref.current.contains(event.target)) {
|
|
106
|
+
handler();
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
const handleTouchStart = (event) => {
|
|
110
|
+
if (ref.current && !ref.current.contains(event.target)) {
|
|
111
|
+
handler();
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
document.addEventListener("mousedown", handleMouseDown);
|
|
115
|
+
document.addEventListener("touchstart", handleTouchStart);
|
|
116
|
+
return () => {
|
|
117
|
+
document.removeEventListener("mousedown", handleMouseDown);
|
|
118
|
+
document.removeEventListener("touchstart", handleTouchStart);
|
|
119
|
+
};
|
|
120
|
+
}, [ref, handler]);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// src/hooks/use-copy-to-clipboard.ts
|
|
124
|
+
var import_react5 = require("react");
|
|
125
|
+
function useCopyToClipboard() {
|
|
126
|
+
const [copied, setCopied] = (0, import_react5.useState)(false);
|
|
127
|
+
const timerRef = (0, import_react5.useRef)(null);
|
|
128
|
+
(0, import_react5.useEffect)(() => {
|
|
129
|
+
return () => {
|
|
130
|
+
if (timerRef.current) {
|
|
131
|
+
clearTimeout(timerRef.current);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}, []);
|
|
135
|
+
const copy = (0, import_react5.useCallback)(async (text) => {
|
|
136
|
+
if (typeof navigator === "undefined" || !navigator.clipboard) {
|
|
137
|
+
console.error("\u526A\u8D34\u677F API \u4E0D\u53EF\u7528");
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
await navigator.clipboard.writeText(text);
|
|
142
|
+
setCopied(true);
|
|
143
|
+
if (timerRef.current) {
|
|
144
|
+
clearTimeout(timerRef.current);
|
|
145
|
+
}
|
|
146
|
+
timerRef.current = setTimeout(() => setCopied(false), 2e3);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
console.error("\u590D\u5236\u5230\u526A\u8D34\u677F\u5931\u8D25:", error);
|
|
149
|
+
}
|
|
150
|
+
}, []);
|
|
151
|
+
return { copied, copy };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/hooks/use-toggle.ts
|
|
155
|
+
var import_react6 = require("react");
|
|
156
|
+
function useToggle(initial = false) {
|
|
157
|
+
const [value, setValue] = (0, import_react6.useState)(initial);
|
|
158
|
+
const toggle = (0, import_react6.useCallback)(() => {
|
|
159
|
+
setValue((prev) => !prev);
|
|
160
|
+
}, []);
|
|
161
|
+
return [value, toggle, setValue];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// src/hooks/use-pagination.ts
|
|
165
|
+
var import_react7 = require("react");
|
|
166
|
+
function usePagination({
|
|
167
|
+
total,
|
|
168
|
+
pageSize = 10,
|
|
169
|
+
initialPage = 1
|
|
170
|
+
}) {
|
|
171
|
+
const totalPages = (0, import_react7.useMemo)(
|
|
172
|
+
() => Math.max(1, Math.ceil(total / pageSize)),
|
|
173
|
+
[total, pageSize]
|
|
174
|
+
);
|
|
175
|
+
const [page, setPageState] = (0, import_react7.useState)(
|
|
176
|
+
() => Math.min(Math.max(1, initialPage), Math.max(1, Math.ceil(total / pageSize)))
|
|
177
|
+
);
|
|
178
|
+
(0, import_react7.useEffect)(() => {
|
|
179
|
+
setPageState((prev) => Math.min(prev, totalPages));
|
|
180
|
+
}, [totalPages]);
|
|
181
|
+
const setPage = (0, import_react7.useCallback)(
|
|
182
|
+
(p) => {
|
|
183
|
+
setPageState(Math.min(Math.max(1, p), totalPages));
|
|
184
|
+
},
|
|
185
|
+
[totalPages]
|
|
186
|
+
);
|
|
187
|
+
const nextPage = (0, import_react7.useCallback)(() => {
|
|
188
|
+
setPageState((prev) => Math.min(prev + 1, totalPages));
|
|
189
|
+
}, [totalPages]);
|
|
190
|
+
const prevPage = (0, import_react7.useCallback)(() => {
|
|
191
|
+
setPageState((prev) => Math.max(prev - 1, 1));
|
|
192
|
+
}, []);
|
|
193
|
+
const startIndex = (page - 1) * pageSize;
|
|
194
|
+
const endIndex = Math.min(startIndex + pageSize, total);
|
|
195
|
+
return {
|
|
196
|
+
page,
|
|
197
|
+
totalPages,
|
|
198
|
+
setPage,
|
|
199
|
+
nextPage,
|
|
200
|
+
prevPage,
|
|
201
|
+
hasNext: page < totalPages,
|
|
202
|
+
hasPrev: page > 1,
|
|
203
|
+
startIndex,
|
|
204
|
+
endIndex
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// src/hooks/use-form.ts
|
|
209
|
+
var import_react8 = require("react");
|
|
210
|
+
function useForm(initialValues) {
|
|
211
|
+
const [values, setValues] = (0, import_react8.useState)(initialValues);
|
|
212
|
+
const [errors, setErrors] = (0, import_react8.useState)({});
|
|
213
|
+
const isDirty = (0, import_react8.useMemo)(
|
|
214
|
+
() => Object.keys(initialValues).some(
|
|
215
|
+
(key) => values[key] !== initialValues[key]
|
|
216
|
+
),
|
|
217
|
+
[values, initialValues]
|
|
218
|
+
);
|
|
219
|
+
const handleChange = (0, import_react8.useCallback)((name, value) => {
|
|
220
|
+
setValues((prev) => ({ ...prev, [name]: value }));
|
|
221
|
+
setErrors((prev) => {
|
|
222
|
+
if (!prev[name]) return prev;
|
|
223
|
+
const next = { ...prev };
|
|
224
|
+
delete next[name];
|
|
225
|
+
return next;
|
|
226
|
+
});
|
|
227
|
+
}, []);
|
|
228
|
+
const setError = (0, import_react8.useCallback)((name, message) => {
|
|
229
|
+
setErrors((prev) => ({ ...prev, [name]: message }));
|
|
230
|
+
}, []);
|
|
231
|
+
const clearErrors = (0, import_react8.useCallback)(() => {
|
|
232
|
+
setErrors({});
|
|
233
|
+
}, []);
|
|
234
|
+
const reset = (0, import_react8.useCallback)(() => {
|
|
235
|
+
setValues(initialValues);
|
|
236
|
+
setErrors({});
|
|
237
|
+
}, [initialValues]);
|
|
238
|
+
return { values, errors, handleChange, setError, clearErrors, reset, isDirty };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// src/hooks/use-mounted.ts
|
|
242
|
+
var import_react9 = require("react");
|
|
243
|
+
function useMounted() {
|
|
244
|
+
const [mounted, setMounted] = (0, import_react9.useState)(false);
|
|
245
|
+
(0, import_react9.useEffect)(() => {
|
|
246
|
+
setMounted(true);
|
|
247
|
+
}, []);
|
|
248
|
+
return mounted;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// src/hooks/use-interval.ts
|
|
252
|
+
var import_react10 = require("react");
|
|
253
|
+
function useInterval(callback, delay) {
|
|
254
|
+
const savedCallback = (0, import_react10.useRef)(callback);
|
|
255
|
+
(0, import_react10.useEffect)(() => {
|
|
256
|
+
savedCallback.current = callback;
|
|
257
|
+
}, [callback]);
|
|
258
|
+
(0, import_react10.useEffect)(() => {
|
|
259
|
+
if (delay === null) return;
|
|
260
|
+
const id = setInterval(() => savedCallback.current(), delay);
|
|
261
|
+
return () => clearInterval(id);
|
|
262
|
+
}, [delay]);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// src/hooks/use-timeout.ts
|
|
266
|
+
var import_react11 = require("react");
|
|
267
|
+
function useTimeout(callback, delay) {
|
|
268
|
+
const savedCallback = (0, import_react11.useRef)(callback);
|
|
269
|
+
(0, import_react11.useEffect)(() => {
|
|
270
|
+
savedCallback.current = callback;
|
|
271
|
+
}, [callback]);
|
|
272
|
+
(0, import_react11.useEffect)(() => {
|
|
273
|
+
if (delay === null) return;
|
|
274
|
+
const id = setTimeout(() => savedCallback.current(), delay);
|
|
275
|
+
return () => clearTimeout(id);
|
|
276
|
+
}, [delay]);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// src/hooks/use-hotkeys.ts
|
|
280
|
+
var import_react12 = require("react");
|
|
281
|
+
function parseHotkey(keys) {
|
|
282
|
+
const parts = keys.toLowerCase().split("+").map((s) => s.trim());
|
|
283
|
+
const ctrl = parts.includes("ctrl");
|
|
284
|
+
const meta = parts.includes("cmd") || parts.includes("meta");
|
|
285
|
+
const alt = parts.includes("alt");
|
|
286
|
+
const shift = parts.includes("shift");
|
|
287
|
+
const key = parts.find(
|
|
288
|
+
(p) => !["ctrl", "cmd", "meta", "alt", "shift"].includes(p)
|
|
289
|
+
) ?? "";
|
|
290
|
+
return { ctrl, meta, alt, shift, key };
|
|
291
|
+
}
|
|
292
|
+
function useHotkeys(keys, callback, options = {}) {
|
|
293
|
+
const { preventDefault = false, target = null } = options;
|
|
294
|
+
const savedCallback = (0, import_react12.useRef)(callback);
|
|
295
|
+
(0, import_react12.useEffect)(() => {
|
|
296
|
+
savedCallback.current = callback;
|
|
297
|
+
}, [callback]);
|
|
298
|
+
(0, import_react12.useEffect)(() => {
|
|
299
|
+
const parsed = parseHotkey(keys);
|
|
300
|
+
const eventTarget = target ?? window;
|
|
301
|
+
function handler(event) {
|
|
302
|
+
const eventKey = event.key.toLowerCase();
|
|
303
|
+
const match = eventKey === parsed.key && event.ctrlKey === parsed.ctrl && event.metaKey === parsed.meta && event.altKey === parsed.alt && event.shiftKey === parsed.shift;
|
|
304
|
+
if (match) {
|
|
305
|
+
if (preventDefault) event.preventDefault();
|
|
306
|
+
savedCallback.current(event);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
eventTarget.addEventListener("keydown", handler);
|
|
310
|
+
return () => {
|
|
311
|
+
eventTarget.removeEventListener("keydown", handler);
|
|
312
|
+
};
|
|
313
|
+
}, [keys, preventDefault, target]);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// src/hooks/use-event-listener.ts
|
|
317
|
+
var import_react13 = require("react");
|
|
318
|
+
function useEventListener(event, handler, target) {
|
|
319
|
+
const savedHandler = (0, import_react13.useRef)(handler);
|
|
320
|
+
(0, import_react13.useEffect)(() => {
|
|
321
|
+
savedHandler.current = handler;
|
|
322
|
+
}, [handler]);
|
|
323
|
+
(0, import_react13.useEffect)(() => {
|
|
324
|
+
if (target === null) return;
|
|
325
|
+
const eventTarget = target ?? window;
|
|
326
|
+
function listener(event2) {
|
|
327
|
+
savedHandler.current(event2);
|
|
328
|
+
}
|
|
329
|
+
eventTarget.addEventListener(event, listener);
|
|
330
|
+
return () => {
|
|
331
|
+
eventTarget.removeEventListener(event, listener);
|
|
332
|
+
};
|
|
333
|
+
}, [event, target]);
|
|
334
|
+
}
|
|
335
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
336
|
+
0 && (module.exports = {
|
|
337
|
+
useClickOutside,
|
|
338
|
+
useCopyToClipboard,
|
|
339
|
+
useDebounce,
|
|
340
|
+
useEventListener,
|
|
341
|
+
useForm,
|
|
342
|
+
useHotkeys,
|
|
343
|
+
useInterval,
|
|
344
|
+
useLocalStorage,
|
|
345
|
+
useMediaQuery,
|
|
346
|
+
useMounted,
|
|
347
|
+
usePagination,
|
|
348
|
+
useTimeout,
|
|
349
|
+
useToggle
|
|
350
|
+
});
|
|
351
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/hooks/index.ts","../../src/hooks/use-debounce.ts","../../src/hooks/use-local-storage.ts","../../src/hooks/use-media-query.ts","../../src/hooks/use-click-outside.ts","../../src/hooks/use-copy-to-clipboard.ts","../../src/hooks/use-toggle.ts","../../src/hooks/use-pagination.ts","../../src/hooks/use-form.ts","../../src/hooks/use-mounted.ts","../../src/hooks/use-interval.ts","../../src/hooks/use-timeout.ts","../../src/hooks/use-hotkeys.ts","../../src/hooks/use-event-listener.ts"],"sourcesContent":["/**\n * @description: @ug666/ui-react/hooks 子入口 — 所有 Hooks 统一导出\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-17\n */\nexport { useDebounce } from './use-debounce'\nexport {\n useLocalStorage,\n type UseLocalStorageReturn,\n type SetLocalStorageValue,\n} from './use-local-storage'\nexport { useMediaQuery } from './use-media-query'\nexport { useClickOutside } from './use-click-outside'\nexport { useCopyToClipboard, type UseCopyToClipboardReturn } from './use-copy-to-clipboard'\nexport { useToggle, type UseToggleReturn } from './use-toggle'\nexport {\n usePagination,\n type UsePaginationOptions,\n type UsePaginationReturn,\n} from './use-pagination'\nexport { useForm, type UseFormErrors, type UseFormReturn } from './use-form'\nexport { useMounted } from './use-mounted'\nexport { useInterval } from './use-interval'\nexport { useTimeout } from './use-timeout'\nexport { useHotkeys, type UseHotkeysOptions } from './use-hotkeys'\nexport { useEventListener } from './use-event-listener'\n","/**\n * @description: 防抖 hook\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-15\n */\nimport { useState, useEffect } from 'react'\n\n/**\n * 防抖值,延迟 delay 毫秒后才更新\n * @example\n * const keyword = useDebounce(inputValue, 300)\n */\nexport function useDebounce<T>(value: T, delay = 300): T {\n const [debounced, setDebounced] = useState(value)\n\n useEffect(() => {\n const timer = setTimeout(() => setDebounced(value), delay)\n return () => clearTimeout(timer)\n }, [value, delay])\n\n return debounced\n}\n","/**\n * @description: localStorage 持久化 hook\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-15\n */\nimport { useState, useCallback } from 'react'\n\nexport type SetLocalStorageValue<T> = (value: T | ((prev: T) => T)) => void\nexport type UseLocalStorageReturn<T> = [T, SetLocalStorageValue<T>, () => void]\n\n/**\n * 自动读写 localStorage 的 useState\n * @example\n * const [token, setToken, removeToken] = useLocalStorage('token', '')\n */\nexport function useLocalStorage<T>(key: string, initialValue: T): UseLocalStorageReturn<T> {\n const [storedValue, setStoredValue] = useState<T>(() => {\n if (typeof window === 'undefined') return initialValue\n try {\n const item = window.localStorage.getItem(key)\n return item ? (JSON.parse(item) as T) : initialValue\n } catch {\n return initialValue\n }\n })\n\n const setValue = useCallback((value: T | ((prev: T) => T)) => {\n setStoredValue(prev => {\n const next = value instanceof Function ? value(prev) : value\n if (typeof window !== 'undefined') {\n window.localStorage.setItem(key, JSON.stringify(next))\n }\n return next\n })\n }, [key])\n\n const removeValue = useCallback(() => {\n setStoredValue(initialValue)\n if (typeof window !== 'undefined') {\n window.localStorage.removeItem(key)\n }\n }, [key, initialValue])\n\n return [storedValue, setValue, removeValue]\n}\n","/**\n * @description: CSS media query 监听 hook\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-15\n */\nimport { useState, useEffect } from 'react'\n\n/**\n * 监听 CSS media query 变化,返回当前是否匹配\n * @example\n * const isMobile = useMediaQuery('(max-width: 768px)')\n * const prefersDark = useMediaQuery('(prefers-color-scheme: dark)')\n */\nexport function useMediaQuery(query: string): boolean {\n const [matches, setMatches] = useState<boolean>(() => {\n if (typeof window === 'undefined') return false\n return window.matchMedia(query).matches\n })\n\n useEffect(() => {\n if (typeof window === 'undefined') return\n\n const mediaQueryList = window.matchMedia(query)\n setMatches(mediaQueryList.matches)\n\n const handleChange = (event: MediaQueryListEvent) => {\n setMatches(event.matches)\n }\n\n mediaQueryList.addEventListener('change', handleChange)\n return () => mediaQueryList.removeEventListener('change', handleChange)\n }, [query])\n\n return matches\n}\n","/**\n * @description: 点击元素外部触发回调的 hook\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-15\n */\nimport { useEffect } from 'react'\nimport type { RefObject } from 'react'\n\n/**\n * 点击 ref 元素外部时触发 handler\n * @example\n * const ref = useRef<HTMLDivElement>(null)\n * useClickOutside(ref, () => setOpen(false))\n */\nexport function useClickOutside<T extends HTMLElement>(\n ref: RefObject<T | null>,\n handler: () => void,\n): void {\n useEffect(() => {\n const handleMouseDown = (event: MouseEvent) => {\n if (ref.current && !ref.current.contains(event.target as Node)) {\n handler()\n }\n }\n\n const handleTouchStart = (event: TouchEvent) => {\n if (ref.current && !ref.current.contains(event.target as Node)) {\n handler()\n }\n }\n\n document.addEventListener('mousedown', handleMouseDown)\n document.addEventListener('touchstart', handleTouchStart)\n\n return () => {\n document.removeEventListener('mousedown', handleMouseDown)\n document.removeEventListener('touchstart', handleTouchStart)\n }\n }, [ref, handler])\n}\n","/**\n * @description: 复制文本到剪贴板的 hook\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-15\n */\nimport { useCallback, useEffect, useRef, useState } from 'react'\n\nexport interface UseCopyToClipboardReturn {\n copied: boolean\n copy: (text: string) => Promise<void>\n}\n\n/**\n * 复制文本到剪贴板,copied 状态 2 秒后自动重置\n * @example\n * const { copied, copy } = useCopyToClipboard()\n * <button onClick={() => copy('hello')}>{copied ? '已复制' : '复制'}</button>\n */\nexport function useCopyToClipboard(): UseCopyToClipboardReturn {\n const [copied, setCopied] = useState(false)\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null)\n\n useEffect(() => {\n return () => {\n if (timerRef.current) {\n clearTimeout(timerRef.current)\n }\n }\n }, [])\n\n const copy = useCallback(async (text: string) => {\n if (typeof navigator === 'undefined' || !navigator.clipboard) {\n console.error('剪贴板 API 不可用')\n return\n }\n\n try {\n await navigator.clipboard.writeText(text)\n setCopied(true)\n if (timerRef.current) {\n clearTimeout(timerRef.current)\n }\n // 2 秒后自动重置状态\n timerRef.current = setTimeout(() => setCopied(false), 2000)\n } catch (error) {\n console.error('复制到剪贴板失败:', error)\n }\n }, [])\n\n return { copied, copy }\n}\n","/**\n * @description: 布尔值切换 hook\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-15\n */\nimport { useState, useCallback } from 'react'\n\nexport type UseToggleReturn = [boolean, () => void, (value: boolean) => void]\n\n/**\n * 简单的布尔值切换,返回 [value, toggle, setValue]\n * @example\n * const [isOpen, toggleOpen, setIsOpen] = useToggle(false)\n * <button onClick={toggleOpen}>切换</button>\n */\nexport function useToggle(\n initial = false,\n): UseToggleReturn {\n const [value, setValue] = useState(initial)\n\n const toggle = useCallback(() => {\n setValue(prev => !prev)\n }, [])\n\n return [value, toggle, setValue]\n}\n","/**\n * @description: 分页状态管理 hook\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-15\n */\nimport { useCallback, useEffect, useMemo, useState } from 'react'\n\nexport interface UsePaginationOptions {\n /** 数据总条数 */\n total: number\n /** 每页条数,默认 10 */\n pageSize?: number\n /** 初始页码,默认 1 */\n initialPage?: number\n}\n\nexport interface UsePaginationReturn {\n /** 当前页码 (从 1 开始) */\n page: number\n /** 总页数 */\n totalPages: number\n /** 跳转到指定页 */\n setPage: (p: number) => void\n /** 下一页 */\n nextPage: () => void\n /** 上一页 */\n prevPage: () => void\n /** 是否有下一页 */\n hasNext: boolean\n /** 是否有上一页 */\n hasPrev: boolean\n /** 当前页起始索引 (0-based,用于切片) */\n startIndex: number\n /** 当前页结束索引 (exclusive,用于切片) */\n endIndex: number\n}\n\n/**\n * 分页状态管理,提供页码、边界判断和数据切片索引\n * @example\n * const { page, totalPages, nextPage, prevPage, startIndex, endIndex } = usePagination({ total: 100, pageSize: 10 })\n * const pageData = data.slice(startIndex, endIndex)\n */\nexport function usePagination({\n total,\n pageSize = 10,\n initialPage = 1,\n}: UsePaginationOptions): UsePaginationReturn {\n const totalPages = useMemo(\n () => Math.max(1, Math.ceil(total / pageSize)),\n [total, pageSize],\n )\n\n const [page, setPageState] = useState(() =>\n Math.min(Math.max(1, initialPage), Math.max(1, Math.ceil(total / pageSize))),\n )\n\n useEffect(() => {\n setPageState(prev => Math.min(prev, totalPages))\n }, [totalPages])\n\n const setPage = useCallback(\n (p: number) => {\n setPageState(Math.min(Math.max(1, p), totalPages))\n },\n [totalPages],\n )\n\n const nextPage = useCallback(() => {\n setPageState(prev => Math.min(prev + 1, totalPages))\n }, [totalPages])\n\n const prevPage = useCallback(() => {\n setPageState(prev => Math.max(prev - 1, 1))\n }, [])\n\n const startIndex = (page - 1) * pageSize\n const endIndex = Math.min(startIndex + pageSize, total)\n\n return {\n page,\n totalPages,\n setPage,\n nextPage,\n prevPage,\n hasNext: page < totalPages,\n hasPrev: page > 1,\n startIndex,\n endIndex,\n }\n}\n","/**\n * @description: 极简表单状态管理 hook,不依赖第三方库\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-15\n */\nimport { useState, useCallback, useMemo } from 'react'\n\nexport type UseFormErrors<T extends Record<string, unknown>> = Partial<Record<keyof T, string>>\n\nexport interface UseFormReturn<T extends Record<string, unknown>> {\n /** 当前表单值 */\n values: T\n /** 字段错误信息 */\n errors: UseFormErrors<T>\n /** 更新单个字段值 */\n handleChange: <K extends keyof T>(name: K, value: T[K]) => void\n /** 设置字段错误 */\n setError: (name: keyof T, message: string) => void\n /** 清除所有错误 */\n clearErrors: () => void\n /** 重置表单到初始值 */\n reset: () => void\n /** 表单是否被修改过 */\n isDirty: boolean\n}\n\n/**\n * 极简表单状态管理,提供值管理、错误处理和脏状态检测\n * @example\n * const form = useForm({ username: '', password: '' })\n * <input value={form.values.username} onChange={e => form.handleChange('username', e.target.value)} />\n * {form.errors.username && <span>{form.errors.username}</span>}\n */\nexport function useForm<T extends Record<string, unknown>>(\n initialValues: T,\n): UseFormReturn<T> {\n const [values, setValues] = useState<T>(initialValues)\n const [errors, setErrors] = useState<UseFormErrors<T>>({})\n\n const isDirty = useMemo(\n () =>\n (Object.keys(initialValues) as (keyof T)[]).some(\n key => values[key] !== initialValues[key],\n ),\n [values, initialValues],\n )\n\n const handleChange = useCallback(<K extends keyof T>(name: K, value: T[K]) => {\n setValues(prev => ({ ...prev, [name]: value }))\n // 修改字段时清除该字段的错误\n setErrors(prev => {\n if (!prev[name]) return prev\n const next = { ...prev }\n delete next[name]\n return next\n })\n }, [])\n\n const setError = useCallback((name: keyof T, message: string) => {\n setErrors(prev => ({ ...prev, [name]: message }))\n }, [])\n\n const clearErrors = useCallback(() => {\n setErrors({})\n }, [])\n\n const reset = useCallback(() => {\n setValues(initialValues)\n setErrors({})\n }, [initialValues])\n\n return { values, errors, handleChange, setError, clearErrors, reset, isDirty }\n}\n","/**\n * @description: 组件挂载状态 hook — 用于避免 SSR 环境下的 hydration 报错\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-17\n */\nimport { useState, useEffect } from 'react'\n\n/**\n * 返回组件是否已在客户端挂载完成。\n *\n * 在 SSR 场景中,服务端渲染时始终返回 false,\n * 客户端 hydration 完成后变为 true,可用于条件渲染仅客户端内容。\n *\n * @returns 组件是否已挂载\n *\n * @example\n * ```tsx\n * const isMounted = useMounted()\n *\n * if (!isMounted) return null // 服务端不渲染\n * return <ClientOnlyComponent />\n * ```\n */\nexport function useMounted(): boolean {\n const [mounted, setMounted] = useState(false)\n\n useEffect(() => {\n setMounted(true)\n }, [])\n\n return mounted\n}\n","/**\n * @description: 定时间隔 hook — delay 为 null 时暂停,支持动态调整间隔\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-17\n */\nimport { useEffect, useRef } from 'react'\n\n/**\n * 以指定间隔重复调用回调函数。\n *\n * 当 delay 为 null 时定时器自动暂停,可动态修改 delay 或 callback。\n * 参考 Dan Abramov 的 useInterval 实现,使用 ref 保持 callback 最新引用。\n *\n * @param callback 每次触发时调用的函数\n * @param delay 间隔毫秒数;传入 null 则暂停定时器\n *\n * @example\n * ```tsx\n * const [count, setCount] = useState(0)\n * const [running, setRunning] = useState(true)\n *\n * useInterval(() => setCount(c => c + 1), running ? 1000 : null)\n * ```\n */\nexport function useInterval(callback: () => void, delay: number | null): void {\n const savedCallback = useRef<() => void>(callback)\n\n // 每次渲染都更新 ref,确保 callback 始终最新\n useEffect(() => {\n savedCallback.current = callback\n }, [callback])\n\n useEffect(() => {\n if (delay === null) return\n const id = setInterval(() => savedCallback.current(), delay)\n return () => clearInterval(id)\n }, [delay])\n}\n","/**\n * @description: 延迟执行 hook — delay 为 null 时暂停,组件卸载自动清理\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-17\n */\nimport { useEffect, useRef } from 'react'\n\n/**\n * 在指定延迟后执行一次回调函数。\n *\n * 当 delay 为 null 时定时器不启动(暂停模式)。\n * 每次 delay 变化都会重置定时器;组件卸载时自动清理。\n *\n * @param callback 延迟结束后调用的函数\n * @param delay 延迟毫秒数;传入 null 则不执行\n *\n * @example\n * ```tsx\n * const [show, setShow] = useState(true)\n *\n * // 3 秒后自动隐藏提示\n * useTimeout(() => setShow(false), show ? 3000 : null)\n * ```\n */\nexport function useTimeout(callback: () => void, delay: number | null): void {\n const savedCallback = useRef<() => void>(callback)\n\n // 每次渲染都更新 ref,确保 callback 始终最新\n useEffect(() => {\n savedCallback.current = callback\n }, [callback])\n\n useEffect(() => {\n if (delay === null) return\n const id = setTimeout(() => savedCallback.current(), delay)\n return () => clearTimeout(id)\n }, [delay])\n}\n","/**\n * @description: 快捷键绑定 hook — 支持 \"ctrl+k\"、\"cmd+s\"、\"escape\" 等组合键\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-17\n */\nimport { useEffect, useRef } from 'react'\n\nexport interface UseHotkeysOptions {\n /** 是否阻止默认浏览器行为,默认 false */\n preventDefault?: boolean\n /** 监听目标,默认 window */\n target?: HTMLElement | null\n}\n\n/**\n * 解析快捷键字符串,返回规范化的修饰键 + 主键结构。\n * 支持 ctrl/cmd/alt/shift + 任意按键,键名不区分大小写。\n * \"cmd\" 在 Mac 映射为 Meta,Windows 同样可响应 Meta 键。\n */\nfunction parseHotkey(keys: string): {\n ctrl: boolean\n meta: boolean\n alt: boolean\n shift: boolean\n key: string\n} {\n const parts = keys.toLowerCase().split('+').map((s) => s.trim())\n const ctrl = parts.includes('ctrl')\n const meta = parts.includes('cmd') || parts.includes('meta')\n const alt = parts.includes('alt')\n const shift = parts.includes('shift')\n const key = parts.find(\n (p) => !['ctrl', 'cmd', 'meta', 'alt', 'shift'].includes(p),\n ) ?? ''\n\n return { ctrl, meta, alt, shift, key }\n}\n\n/**\n * 将快捷键字符串绑定到回调函数。\n *\n * 支持修饰键组合:ctrl、cmd(Meta)、alt、shift,加主键(不区分大小写)。\n * 组件卸载时自动解绑。\n *\n * @param keys 快捷键字符串,如 \"ctrl+k\"、\"cmd+s\"、\"escape\"\n * @param callback 触发时调用的函数\n * @param options 额外配置项\n *\n * @example\n * ```tsx\n * // 全局搜索快捷键\n * useHotkeys('ctrl+k', () => setSearchOpen(true), { preventDefault: true })\n *\n * // ESC 关闭弹窗\n * useHotkeys('escape', () => setOpen(false))\n *\n * // 仅在特定元素内响应\n * useHotkeys('ctrl+enter', handleSubmit, { target: formRef.current })\n * ```\n */\nexport function useHotkeys(\n keys: string,\n callback: (event: KeyboardEvent) => void,\n options: UseHotkeysOptions = {},\n): void {\n const { preventDefault = false, target = null } = options\n const savedCallback = useRef<(event: KeyboardEvent) => void>(callback)\n\n useEffect(() => {\n savedCallback.current = callback\n }, [callback])\n\n useEffect(() => {\n const parsed = parseHotkey(keys)\n const eventTarget: EventTarget = target ?? window\n\n function handler(event: KeyboardEvent): void {\n const eventKey = event.key.toLowerCase()\n const match =\n eventKey === parsed.key &&\n event.ctrlKey === parsed.ctrl &&\n event.metaKey === parsed.meta &&\n event.altKey === parsed.alt &&\n event.shiftKey === parsed.shift\n\n if (match) {\n if (preventDefault) event.preventDefault()\n savedCallback.current(event)\n }\n }\n\n eventTarget.addEventListener('keydown', handler as EventListener)\n return () => {\n eventTarget.removeEventListener('keydown', handler as EventListener)\n }\n }, [keys, preventDefault, target])\n}\n","/**\n * @description: 事件监听 hook — 自动管理事件绑定与清理,支持 Window/Document/HTMLElement\n * @author: UG - 一个斗码大陆苦逼的三段码之气的少年,并没有神秘戒指中码老的帮助,但总有一天,我会成为斗码大陆中码帝一样的存在。三十年河东,三十年河西,莫欺少年穷。\n * @date: 2026-04-17\n */\nimport { useEffect, useRef } from 'react'\n\n/**\n * 在指定目标上绑定事件监听器,组件卸载时自动清理。\n *\n * 支持三种目标类型:\n * - 不传 target → 默认监听 window\n * - 传入 HTMLElement → 监听该元素\n * - 传入 null → 跳过绑定(适合 ref 未初始化的场景)\n *\n * @typeParam K WindowEventMap 中的事件名称\n * @param event 事件名称,如 \"scroll\"、\"resize\"、\"click\"\n * @param handler 事件处理函数\n * @param target 监听目标;默认 window;传 null 跳过绑定\n *\n * @example\n * ```tsx\n * // 监听全局滚动\n * useEventListener('scroll', () => setScrollY(window.scrollY))\n *\n * // 监听特定元素的点击\n * const divRef = useRef<HTMLDivElement>(null)\n * useEventListener('click', handleClick, divRef.current)\n *\n * // 传入 null 跳过(ref 尚未挂载时)\n * useEventListener('mouseenter', onEnter, ref.current)\n * ```\n */\nexport function useEventListener<K extends keyof WindowEventMap>(\n event: K,\n handler: (event: WindowEventMap[K]) => void,\n target?: EventTarget | null,\n): void {\n const savedHandler = useRef<(event: WindowEventMap[K]) => void>(handler)\n\n useEffect(() => {\n savedHandler.current = handler\n }, [handler])\n\n useEffect(() => {\n // target 显式传入 null 时跳过绑定\n if (target === null) return\n const eventTarget: EventTarget = target ?? window\n\n function listener(event: Event): void {\n savedHandler.current(event as WindowEventMap[K])\n }\n\n eventTarget.addEventListener(event, listener)\n return () => {\n eventTarget.removeEventListener(event, listener)\n }\n }, [event, target])\n}\n"],"mappings":"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","names":["import_react","import_react","import_react","import_react","import_react","import_react","import_react","import_react","import_react","import_react","import_react","import_react","event"]}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { RefObject } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 防抖值,延迟 delay 毫秒后才更新
|
|
5
|
+
* @example
|
|
6
|
+
* const keyword = useDebounce(inputValue, 300)
|
|
7
|
+
*/
|
|
8
|
+
declare function useDebounce<T>(value: T, delay?: number): T;
|
|
9
|
+
|
|
10
|
+
type SetLocalStorageValue<T> = (value: T | ((prev: T) => T)) => void;
|
|
11
|
+
type UseLocalStorageReturn<T> = [T, SetLocalStorageValue<T>, () => void];
|
|
12
|
+
/**
|
|
13
|
+
* 自动读写 localStorage 的 useState
|
|
14
|
+
* @example
|
|
15
|
+
* const [token, setToken, removeToken] = useLocalStorage('token', '')
|
|
16
|
+
*/
|
|
17
|
+
declare function useLocalStorage<T>(key: string, initialValue: T): UseLocalStorageReturn<T>;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 监听 CSS media query 变化,返回当前是否匹配
|
|
21
|
+
* @example
|
|
22
|
+
* const isMobile = useMediaQuery('(max-width: 768px)')
|
|
23
|
+
* const prefersDark = useMediaQuery('(prefers-color-scheme: dark)')
|
|
24
|
+
*/
|
|
25
|
+
declare function useMediaQuery(query: string): boolean;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 点击 ref 元素外部时触发 handler
|
|
29
|
+
* @example
|
|
30
|
+
* const ref = useRef<HTMLDivElement>(null)
|
|
31
|
+
* useClickOutside(ref, () => setOpen(false))
|
|
32
|
+
*/
|
|
33
|
+
declare function useClickOutside<T extends HTMLElement>(ref: RefObject<T | null>, handler: () => void): void;
|
|
34
|
+
|
|
35
|
+
interface UseCopyToClipboardReturn {
|
|
36
|
+
copied: boolean;
|
|
37
|
+
copy: (text: string) => Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 复制文本到剪贴板,copied 状态 2 秒后自动重置
|
|
41
|
+
* @example
|
|
42
|
+
* const { copied, copy } = useCopyToClipboard()
|
|
43
|
+
* <button onClick={() => copy('hello')}>{copied ? '已复制' : '复制'}</button>
|
|
44
|
+
*/
|
|
45
|
+
declare function useCopyToClipboard(): UseCopyToClipboardReturn;
|
|
46
|
+
|
|
47
|
+
type UseToggleReturn = [boolean, () => void, (value: boolean) => void];
|
|
48
|
+
/**
|
|
49
|
+
* 简单的布尔值切换,返回 [value, toggle, setValue]
|
|
50
|
+
* @example
|
|
51
|
+
* const [isOpen, toggleOpen, setIsOpen] = useToggle(false)
|
|
52
|
+
* <button onClick={toggleOpen}>切换</button>
|
|
53
|
+
*/
|
|
54
|
+
declare function useToggle(initial?: boolean): UseToggleReturn;
|
|
55
|
+
|
|
56
|
+
interface UsePaginationOptions {
|
|
57
|
+
/** 数据总条数 */
|
|
58
|
+
total: number;
|
|
59
|
+
/** 每页条数,默认 10 */
|
|
60
|
+
pageSize?: number;
|
|
61
|
+
/** 初始页码,默认 1 */
|
|
62
|
+
initialPage?: number;
|
|
63
|
+
}
|
|
64
|
+
interface UsePaginationReturn {
|
|
65
|
+
/** 当前页码 (从 1 开始) */
|
|
66
|
+
page: number;
|
|
67
|
+
/** 总页数 */
|
|
68
|
+
totalPages: number;
|
|
69
|
+
/** 跳转到指定页 */
|
|
70
|
+
setPage: (p: number) => void;
|
|
71
|
+
/** 下一页 */
|
|
72
|
+
nextPage: () => void;
|
|
73
|
+
/** 上一页 */
|
|
74
|
+
prevPage: () => void;
|
|
75
|
+
/** 是否有下一页 */
|
|
76
|
+
hasNext: boolean;
|
|
77
|
+
/** 是否有上一页 */
|
|
78
|
+
hasPrev: boolean;
|
|
79
|
+
/** 当前页起始索引 (0-based,用于切片) */
|
|
80
|
+
startIndex: number;
|
|
81
|
+
/** 当前页结束索引 (exclusive,用于切片) */
|
|
82
|
+
endIndex: number;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 分页状态管理,提供页码、边界判断和数据切片索引
|
|
86
|
+
* @example
|
|
87
|
+
* const { page, totalPages, nextPage, prevPage, startIndex, endIndex } = usePagination({ total: 100, pageSize: 10 })
|
|
88
|
+
* const pageData = data.slice(startIndex, endIndex)
|
|
89
|
+
*/
|
|
90
|
+
declare function usePagination({ total, pageSize, initialPage, }: UsePaginationOptions): UsePaginationReturn;
|
|
91
|
+
|
|
92
|
+
type UseFormErrors<T extends Record<string, unknown>> = Partial<Record<keyof T, string>>;
|
|
93
|
+
interface UseFormReturn<T extends Record<string, unknown>> {
|
|
94
|
+
/** 当前表单值 */
|
|
95
|
+
values: T;
|
|
96
|
+
/** 字段错误信息 */
|
|
97
|
+
errors: UseFormErrors<T>;
|
|
98
|
+
/** 更新单个字段值 */
|
|
99
|
+
handleChange: <K extends keyof T>(name: K, value: T[K]) => void;
|
|
100
|
+
/** 设置字段错误 */
|
|
101
|
+
setError: (name: keyof T, message: string) => void;
|
|
102
|
+
/** 清除所有错误 */
|
|
103
|
+
clearErrors: () => void;
|
|
104
|
+
/** 重置表单到初始值 */
|
|
105
|
+
reset: () => void;
|
|
106
|
+
/** 表单是否被修改过 */
|
|
107
|
+
isDirty: boolean;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* 极简表单状态管理,提供值管理、错误处理和脏状态检测
|
|
111
|
+
* @example
|
|
112
|
+
* const form = useForm({ username: '', password: '' })
|
|
113
|
+
* <input value={form.values.username} onChange={e => form.handleChange('username', e.target.value)} />
|
|
114
|
+
* {form.errors.username && <span>{form.errors.username}</span>}
|
|
115
|
+
*/
|
|
116
|
+
declare function useForm<T extends Record<string, unknown>>(initialValues: T): UseFormReturn<T>;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* 返回组件是否已在客户端挂载完成。
|
|
120
|
+
*
|
|
121
|
+
* 在 SSR 场景中,服务端渲染时始终返回 false,
|
|
122
|
+
* 客户端 hydration 完成后变为 true,可用于条件渲染仅客户端内容。
|
|
123
|
+
*
|
|
124
|
+
* @returns 组件是否已挂载
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```tsx
|
|
128
|
+
* const isMounted = useMounted()
|
|
129
|
+
*
|
|
130
|
+
* if (!isMounted) return null // 服务端不渲染
|
|
131
|
+
* return <ClientOnlyComponent />
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
declare function useMounted(): boolean;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 以指定间隔重复调用回调函数。
|
|
138
|
+
*
|
|
139
|
+
* 当 delay 为 null 时定时器自动暂停,可动态修改 delay 或 callback。
|
|
140
|
+
* 参考 Dan Abramov 的 useInterval 实现,使用 ref 保持 callback 最新引用。
|
|
141
|
+
*
|
|
142
|
+
* @param callback 每次触发时调用的函数
|
|
143
|
+
* @param delay 间隔毫秒数;传入 null 则暂停定时器
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```tsx
|
|
147
|
+
* const [count, setCount] = useState(0)
|
|
148
|
+
* const [running, setRunning] = useState(true)
|
|
149
|
+
*
|
|
150
|
+
* useInterval(() => setCount(c => c + 1), running ? 1000 : null)
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
declare function useInterval(callback: () => void, delay: number | null): void;
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* 在指定延迟后执行一次回调函数。
|
|
157
|
+
*
|
|
158
|
+
* 当 delay 为 null 时定时器不启动(暂停模式)。
|
|
159
|
+
* 每次 delay 变化都会重置定时器;组件卸载时自动清理。
|
|
160
|
+
*
|
|
161
|
+
* @param callback 延迟结束后调用的函数
|
|
162
|
+
* @param delay 延迟毫秒数;传入 null 则不执行
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```tsx
|
|
166
|
+
* const [show, setShow] = useState(true)
|
|
167
|
+
*
|
|
168
|
+
* // 3 秒后自动隐藏提示
|
|
169
|
+
* useTimeout(() => setShow(false), show ? 3000 : null)
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
declare function useTimeout(callback: () => void, delay: number | null): void;
|
|
173
|
+
|
|
174
|
+
interface UseHotkeysOptions {
|
|
175
|
+
/** 是否阻止默认浏览器行为,默认 false */
|
|
176
|
+
preventDefault?: boolean;
|
|
177
|
+
/** 监听目标,默认 window */
|
|
178
|
+
target?: HTMLElement | null;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* 将快捷键字符串绑定到回调函数。
|
|
182
|
+
*
|
|
183
|
+
* 支持修饰键组合:ctrl、cmd(Meta)、alt、shift,加主键(不区分大小写)。
|
|
184
|
+
* 组件卸载时自动解绑。
|
|
185
|
+
*
|
|
186
|
+
* @param keys 快捷键字符串,如 "ctrl+k"、"cmd+s"、"escape"
|
|
187
|
+
* @param callback 触发时调用的函数
|
|
188
|
+
* @param options 额外配置项
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* ```tsx
|
|
192
|
+
* // 全局搜索快捷键
|
|
193
|
+
* useHotkeys('ctrl+k', () => setSearchOpen(true), { preventDefault: true })
|
|
194
|
+
*
|
|
195
|
+
* // ESC 关闭弹窗
|
|
196
|
+
* useHotkeys('escape', () => setOpen(false))
|
|
197
|
+
*
|
|
198
|
+
* // 仅在特定元素内响应
|
|
199
|
+
* useHotkeys('ctrl+enter', handleSubmit, { target: formRef.current })
|
|
200
|
+
* ```
|
|
201
|
+
*/
|
|
202
|
+
declare function useHotkeys(keys: string, callback: (event: KeyboardEvent) => void, options?: UseHotkeysOptions): void;
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* 在指定目标上绑定事件监听器,组件卸载时自动清理。
|
|
206
|
+
*
|
|
207
|
+
* 支持三种目标类型:
|
|
208
|
+
* - 不传 target → 默认监听 window
|
|
209
|
+
* - 传入 HTMLElement → 监听该元素
|
|
210
|
+
* - 传入 null → 跳过绑定(适合 ref 未初始化的场景)
|
|
211
|
+
*
|
|
212
|
+
* @typeParam K WindowEventMap 中的事件名称
|
|
213
|
+
* @param event 事件名称,如 "scroll"、"resize"、"click"
|
|
214
|
+
* @param handler 事件处理函数
|
|
215
|
+
* @param target 监听目标;默认 window;传 null 跳过绑定
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* ```tsx
|
|
219
|
+
* // 监听全局滚动
|
|
220
|
+
* useEventListener('scroll', () => setScrollY(window.scrollY))
|
|
221
|
+
*
|
|
222
|
+
* // 监听特定元素的点击
|
|
223
|
+
* const divRef = useRef<HTMLDivElement>(null)
|
|
224
|
+
* useEventListener('click', handleClick, divRef.current)
|
|
225
|
+
*
|
|
226
|
+
* // 传入 null 跳过(ref 尚未挂载时)
|
|
227
|
+
* useEventListener('mouseenter', onEnter, ref.current)
|
|
228
|
+
* ```
|
|
229
|
+
*/
|
|
230
|
+
declare function useEventListener<K extends keyof WindowEventMap>(event: K, handler: (event: WindowEventMap[K]) => void, target?: EventTarget | null): void;
|
|
231
|
+
|
|
232
|
+
export { type SetLocalStorageValue, type UseCopyToClipboardReturn, type UseFormErrors, type UseFormReturn, type UseHotkeysOptions, type UseLocalStorageReturn, type UsePaginationOptions, type UsePaginationReturn, type UseToggleReturn, useClickOutside, useCopyToClipboard, useDebounce, useEventListener, useForm, useHotkeys, useInterval, useLocalStorage, useMediaQuery, useMounted, usePagination, useTimeout, useToggle };
|