@boo-dreamer/hooks 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/dist/index.mjs ADDED
@@ -0,0 +1,578 @@
1
+ import { ref, reactive, watch, onUnmounted, onMounted } from "vue";
2
+ function useToggle(options = {}) {
3
+ const { defaultValue = false, reverseValue = !defaultValue } = options;
4
+ const state = ref(defaultValue);
5
+ const toggle = () => {
6
+ state.value = !state.value;
7
+ };
8
+ const setLeft = () => {
9
+ state.value = defaultValue;
10
+ };
11
+ const setRight = () => {
12
+ state.value = reverseValue;
13
+ };
14
+ return {
15
+ state,
16
+ toggle,
17
+ setLeft,
18
+ setRight
19
+ };
20
+ }
21
+ function useBoolean(defaultValue = false) {
22
+ const state = ref(defaultValue);
23
+ const setTrue = () => {
24
+ state.value = true;
25
+ };
26
+ const setFalse = () => {
27
+ state.value = false;
28
+ };
29
+ const toggle = () => {
30
+ state.value = !state.value;
31
+ };
32
+ const set = (value) => {
33
+ state.value = value;
34
+ };
35
+ return {
36
+ state,
37
+ setTrue,
38
+ setFalse,
39
+ toggle,
40
+ set
41
+ };
42
+ }
43
+ function useSetState(initialState) {
44
+ const state = reactive({ ...initialState });
45
+ const setState = (newState) => {
46
+ if (typeof newState === "function") {
47
+ const patch = newState(state);
48
+ Object.assign(state, patch);
49
+ } else {
50
+ Object.assign(state, newState);
51
+ }
52
+ };
53
+ return [state, setState];
54
+ }
55
+ function useCounter(initialValue = 0, options = {}) {
56
+ const { min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER } = options;
57
+ const count = ref(initialValue);
58
+ const inc = (delta = 1) => {
59
+ const next = count.value + delta;
60
+ if (next <= max) {
61
+ count.value = next;
62
+ } else {
63
+ count.value = max;
64
+ }
65
+ };
66
+ const dec = (delta = 1) => {
67
+ const next = count.value - delta;
68
+ if (next >= min) {
69
+ count.value = next;
70
+ } else {
71
+ count.value = min;
72
+ }
73
+ };
74
+ const set = (value) => {
75
+ if (value >= min && value <= max) {
76
+ count.value = value;
77
+ }
78
+ };
79
+ const reset = () => {
80
+ count.value = initialValue;
81
+ };
82
+ return {
83
+ count,
84
+ inc,
85
+ dec,
86
+ set,
87
+ reset
88
+ };
89
+ }
90
+ function useDebounce(value, options = {}) {
91
+ const { wait = 300, immediate = false } = options;
92
+ const debouncedValue = ref(
93
+ typeof value === "function" ? value() : value.value
94
+ );
95
+ let timeout = null;
96
+ const debounce = (fn) => {
97
+ if (timeout) {
98
+ clearTimeout(timeout);
99
+ }
100
+ if (immediate && !timeout) {
101
+ fn();
102
+ }
103
+ timeout = setTimeout(() => {
104
+ if (!immediate) {
105
+ fn();
106
+ }
107
+ timeout = null;
108
+ }, wait);
109
+ };
110
+ watch(
111
+ value,
112
+ (newVal) => {
113
+ debounce(() => {
114
+ debouncedValue.value = newVal;
115
+ });
116
+ },
117
+ { immediate: false }
118
+ );
119
+ return debouncedValue;
120
+ }
121
+ function useThrottle(value, options = {}) {
122
+ const { wait = 300, leading = true, trailing = true } = options;
123
+ const throttledValue = ref(
124
+ typeof value === "function" ? value() : value.value
125
+ );
126
+ let lastExec = 0;
127
+ let timeout = null;
128
+ const throttle = (fn) => {
129
+ const now = Date.now();
130
+ const elapsed = now - lastExec;
131
+ const execute = () => {
132
+ lastExec = Date.now();
133
+ fn();
134
+ };
135
+ if (elapsed >= wait) {
136
+ if (leading) {
137
+ execute();
138
+ } else if (trailing) {
139
+ if (timeout) clearTimeout(timeout);
140
+ timeout = setTimeout(execute, wait);
141
+ }
142
+ } else if (trailing) {
143
+ if (timeout) clearTimeout(timeout);
144
+ timeout = setTimeout(() => {
145
+ lastExec = Date.now();
146
+ fn();
147
+ }, wait - elapsed);
148
+ }
149
+ };
150
+ watch(
151
+ value,
152
+ (newVal) => {
153
+ throttle(() => {
154
+ throttledValue.value = newVal;
155
+ });
156
+ },
157
+ { immediate: false }
158
+ );
159
+ return throttledValue;
160
+ }
161
+ function useInterval(fn, delay, options = {}) {
162
+ const { immediate = false } = options;
163
+ const count = ref(0);
164
+ let timer = null;
165
+ const start = () => {
166
+ if (timer) return;
167
+ if (immediate) {
168
+ fn();
169
+ count.value++;
170
+ }
171
+ timer = setInterval(() => {
172
+ fn();
173
+ count.value++;
174
+ }, delay);
175
+ };
176
+ const stop = () => {
177
+ if (timer) {
178
+ clearInterval(timer);
179
+ timer = null;
180
+ }
181
+ };
182
+ const reset = () => {
183
+ stop();
184
+ count.value = 0;
185
+ };
186
+ onUnmounted(() => {
187
+ stop();
188
+ });
189
+ return {
190
+ count,
191
+ start,
192
+ stop,
193
+ reset
194
+ };
195
+ }
196
+ function useTimeout(fn, delay) {
197
+ const ready = ref(false);
198
+ let timer = null;
199
+ const start = () => {
200
+ if (timer) return;
201
+ ready.value = false;
202
+ timer = setTimeout(() => {
203
+ ready.value = true;
204
+ fn();
205
+ }, delay);
206
+ };
207
+ const stop = () => {
208
+ if (timer) {
209
+ clearTimeout(timer);
210
+ timer = null;
211
+ }
212
+ };
213
+ onUnmounted(() => {
214
+ stop();
215
+ });
216
+ return {
217
+ ready,
218
+ start,
219
+ stop
220
+ };
221
+ }
222
+ function useClickOutside(targetRef, handler) {
223
+ const listener = (event) => {
224
+ const el = targetRef.value;
225
+ if (!el) return;
226
+ if (!(el === event.target || el.contains(event.target))) {
227
+ handler(event);
228
+ }
229
+ };
230
+ onMounted(() => {
231
+ document.addEventListener("click", listener, true);
232
+ });
233
+ onUnmounted(() => {
234
+ document.removeEventListener("click", listener, true);
235
+ });
236
+ }
237
+ function useFullscreen(target) {
238
+ const isFullscreen = ref(false);
239
+ const getElement = () => {
240
+ return (target == null ? void 0 : target.value) || document.documentElement;
241
+ };
242
+ const enter = async () => {
243
+ const el = getElement();
244
+ if (el.requestFullscreen) {
245
+ await el.requestFullscreen();
246
+ isFullscreen.value = true;
247
+ }
248
+ };
249
+ const exit = async () => {
250
+ if (document.exitFullscreen) {
251
+ await document.exitFullscreen();
252
+ isFullscreen.value = false;
253
+ }
254
+ };
255
+ const toggle = async () => {
256
+ if (isFullscreen.value) {
257
+ await exit();
258
+ } else {
259
+ await enter();
260
+ }
261
+ };
262
+ document.addEventListener("fullscreenchange", () => {
263
+ isFullscreen.value = !!document.fullscreenElement;
264
+ });
265
+ return {
266
+ isFullscreen,
267
+ enter,
268
+ exit,
269
+ toggle
270
+ };
271
+ }
272
+ function useResize() {
273
+ const width = ref(window.innerWidth);
274
+ const height = ref(window.innerHeight);
275
+ const handler = () => {
276
+ width.value = window.innerWidth;
277
+ height.value = window.innerHeight;
278
+ };
279
+ onMounted(() => {
280
+ window.addEventListener("resize", handler);
281
+ });
282
+ onUnmounted(() => {
283
+ window.removeEventListener("resize", handler);
284
+ });
285
+ return {
286
+ width,
287
+ height
288
+ };
289
+ }
290
+ function useScroll(options = {}) {
291
+ const { target } = options;
292
+ const scrollX = ref(0);
293
+ const scrollY = ref(0);
294
+ const getElement = () => {
295
+ return (target == null ? void 0 : target.value) || window;
296
+ };
297
+ const handler = () => {
298
+ const el = getElement();
299
+ if (el === window) {
300
+ scrollX.value = window.scrollX;
301
+ scrollY.value = window.scrollY;
302
+ } else {
303
+ scrollX.value = el.scrollLeft;
304
+ scrollY.value = el.scrollTop;
305
+ }
306
+ };
307
+ const scrollTo = (x, y) => {
308
+ const el = getElement();
309
+ if (el === window) {
310
+ window.scrollTo(x, y);
311
+ } else {
312
+ el.scrollTo(x, y);
313
+ }
314
+ };
315
+ const scrollToTop = () => {
316
+ scrollTo(0, 0);
317
+ };
318
+ onMounted(() => {
319
+ const el = getElement();
320
+ el.addEventListener("scroll", handler, { passive: true });
321
+ handler();
322
+ });
323
+ onUnmounted(() => {
324
+ const el = getElement();
325
+ el.removeEventListener("scroll", handler);
326
+ });
327
+ return {
328
+ scrollX,
329
+ scrollY,
330
+ scrollTo,
331
+ scrollToTop
332
+ };
333
+ }
334
+ function useLocalStorage(key, options = {}) {
335
+ const { defaultValue = null, serializer } = options;
336
+ const read = () => {
337
+ try {
338
+ const item = localStorage.getItem(key);
339
+ if (item === null) return defaultValue;
340
+ return serializer ? serializer.read(item) : JSON.parse(item);
341
+ } catch {
342
+ return defaultValue;
343
+ }
344
+ };
345
+ const write = (value) => {
346
+ try {
347
+ if (value === null) {
348
+ localStorage.removeItem(key);
349
+ } else {
350
+ const serialized = serializer ? serializer.write(value) : JSON.stringify(value);
351
+ localStorage.setItem(key, serialized);
352
+ }
353
+ } catch {
354
+ }
355
+ };
356
+ const storedValue = ref(read());
357
+ watch(
358
+ storedValue,
359
+ (newVal) => {
360
+ write(newVal);
361
+ },
362
+ { deep: true }
363
+ );
364
+ window.addEventListener("storage", (e) => {
365
+ if (e.key === key) {
366
+ storedValue.value = read();
367
+ }
368
+ });
369
+ return storedValue;
370
+ }
371
+ function useSessionStorage(key, options = {}) {
372
+ const { defaultValue = null, serializer } = options;
373
+ const read = () => {
374
+ try {
375
+ const item = sessionStorage.getItem(key);
376
+ if (item === null) return defaultValue;
377
+ return serializer ? serializer.read(item) : JSON.parse(item);
378
+ } catch {
379
+ return defaultValue;
380
+ }
381
+ };
382
+ const write = (value) => {
383
+ try {
384
+ if (value === null) {
385
+ sessionStorage.removeItem(key);
386
+ } else {
387
+ const serialized = serializer ? serializer.write(value) : JSON.stringify(value);
388
+ sessionStorage.setItem(key, serialized);
389
+ }
390
+ } catch {
391
+ }
392
+ };
393
+ const storedValue = ref(read());
394
+ watch(
395
+ storedValue,
396
+ (newVal) => {
397
+ write(newVal);
398
+ },
399
+ { deep: true }
400
+ );
401
+ return storedValue;
402
+ }
403
+ function useAsync(promiseFn) {
404
+ const data = ref(null);
405
+ const error = ref(null);
406
+ const loading = ref(false);
407
+ const execute = async (...args) => {
408
+ loading.value = true;
409
+ error.value = null;
410
+ try {
411
+ const result = await promiseFn(...args);
412
+ data.value = result;
413
+ return result;
414
+ } catch (e) {
415
+ error.value = e;
416
+ throw e;
417
+ } finally {
418
+ loading.value = false;
419
+ }
420
+ };
421
+ return {
422
+ data,
423
+ error,
424
+ loading,
425
+ execute
426
+ };
427
+ }
428
+ function useFetch(url, options = {}) {
429
+ const { immediate = true, ...fetchOptions } = options;
430
+ const data = ref(null);
431
+ const error = ref(null);
432
+ const loading = ref(false);
433
+ const execute = async () => {
434
+ const requestUrl = typeof url === "string" ? url : url.value;
435
+ loading.value = true;
436
+ error.value = null;
437
+ try {
438
+ const response = await fetch(requestUrl, fetchOptions);
439
+ if (!response.ok) {
440
+ throw new Error(`HTTP error! status: ${response.status}`);
441
+ }
442
+ const result = await response.json();
443
+ data.value = result;
444
+ } catch (e) {
445
+ error.value = e;
446
+ } finally {
447
+ loading.value = false;
448
+ }
449
+ };
450
+ const refresh = () => execute();
451
+ if (immediate) {
452
+ execute();
453
+ }
454
+ return {
455
+ data,
456
+ error,
457
+ loading,
458
+ execute,
459
+ refresh
460
+ };
461
+ }
462
+ function useLoading(defaultLoading = false) {
463
+ const loading = ref(defaultLoading);
464
+ const startLoading = () => {
465
+ loading.value = true;
466
+ };
467
+ const stopLoading = () => {
468
+ loading.value = false;
469
+ };
470
+ const withLoading = async (fn) => {
471
+ startLoading();
472
+ try {
473
+ const result = await fn();
474
+ return result;
475
+ } finally {
476
+ stopLoading();
477
+ }
478
+ };
479
+ return {
480
+ loading,
481
+ withLoading,
482
+ startLoading,
483
+ stopLoading
484
+ };
485
+ }
486
+ function useClipboard() {
487
+ const text = ref("");
488
+ const isSupported = typeof navigator !== "undefined" && "clipboard" in navigator;
489
+ const copy = async (value) => {
490
+ if (!isSupported) {
491
+ console.warn("Clipboard API not supported");
492
+ return false;
493
+ }
494
+ try {
495
+ await navigator.clipboard.writeText(value);
496
+ text.value = value;
497
+ return true;
498
+ } catch (err) {
499
+ console.error("Failed to copy:", err);
500
+ return false;
501
+ }
502
+ };
503
+ return {
504
+ text,
505
+ isSupported,
506
+ copy
507
+ };
508
+ }
509
+ function usePermission(permission) {
510
+ const state = ref(void 0);
511
+ const isSupported = typeof navigator !== "undefined" && "permissions" in navigator;
512
+ if (isSupported) {
513
+ navigator.permissions.query({ name: permission }).then((result) => {
514
+ state.value = result.state;
515
+ result.addEventListener("change", () => {
516
+ state.value = result.state;
517
+ });
518
+ }).catch(() => {
519
+ state.value = "prompt";
520
+ });
521
+ }
522
+ return {
523
+ state,
524
+ isSupported
525
+ };
526
+ }
527
+ function useMediaQuery(query) {
528
+ const matches = ref(false);
529
+ let mediaQuery = null;
530
+ const updateMatch = () => {
531
+ if (mediaQuery) {
532
+ matches.value = mediaQuery.matches;
533
+ }
534
+ };
535
+ onMounted(() => {
536
+ mediaQuery = window.matchMedia(query);
537
+ updateMatch();
538
+ if (mediaQuery.addEventListener) {
539
+ mediaQuery.addEventListener("change", updateMatch);
540
+ } else {
541
+ mediaQuery.addListener(updateMatch);
542
+ }
543
+ });
544
+ onUnmounted(() => {
545
+ if (mediaQuery) {
546
+ if (mediaQuery.removeEventListener) {
547
+ mediaQuery.removeEventListener("change", updateMatch);
548
+ } else {
549
+ mediaQuery.removeListener(updateMatch);
550
+ }
551
+ }
552
+ });
553
+ return {
554
+ matches
555
+ };
556
+ }
557
+ export {
558
+ useAsync,
559
+ useBoolean,
560
+ useClickOutside,
561
+ useClipboard,
562
+ useCounter,
563
+ useDebounce,
564
+ useFetch,
565
+ useFullscreen,
566
+ useInterval,
567
+ useLoading,
568
+ useLocalStorage,
569
+ useMediaQuery,
570
+ usePermission,
571
+ useResize,
572
+ useScroll,
573
+ useSessionStorage,
574
+ useSetState,
575
+ useThrottle,
576
+ useTimeout,
577
+ useToggle
578
+ };
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@boo-dreamer/hooks",
3
+ "version": "0.0.1",
4
+ "description": "Boo Dreamer Vue3 组合式函数库",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.mjs",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.cjs",
13
+ "types": "./dist/index.d.ts"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "build": "vite build",
21
+ "dev": "vite build --watch",
22
+ "typecheck": "tsc --noEmit",
23
+ "clean": "rm -rf dist"
24
+ },
25
+ "keywords": [
26
+ "boo-dreamer",
27
+ "hooks",
28
+ "vue3",
29
+ "composition-api",
30
+ "typescript"
31
+ ],
32
+ "author": "",
33
+ "license": "MIT",
34
+ "engines": {
35
+ "node": ">=18.0.0"
36
+ },
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "peerDependencies": {
41
+ "vue": "^3.0.0"
42
+ }
43
+ }