@pawover/kit 0.5.1 → 0.7.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.
Files changed (71) hide show
  1. package/README.en.md +123 -0
  2. package/README.md +123 -1
  3. package/entry/eslint-rules.cjs +1 -0
  4. package/entry/eslint-rules.d.cts +1 -0
  5. package/entry/eslint-rules.d.ts +2 -0
  6. package/entry/eslint-rules.js +2 -0
  7. package/entry/hooks-alova.cjs +1 -0
  8. package/entry/hooks-alova.d.cts +1 -0
  9. package/entry/hooks-alova.d.ts +1 -0
  10. package/entry/hooks-alova.js +1 -0
  11. package/entry/hooks-react.cjs +1 -0
  12. package/entry/hooks-react.d.cts +1 -0
  13. package/entry/hooks-react.d.ts +1 -0
  14. package/entry/hooks-react.js +1 -0
  15. package/entry/index.cjs +1 -0
  16. package/entry/index.d.cts +1 -0
  17. package/entry/index.d.ts +1 -0
  18. package/entry/index.js +1 -0
  19. package/entry/math.cjs +1 -0
  20. package/entry/math.d.cts +1 -0
  21. package/entry/math.d.ts +1 -0
  22. package/entry/math.js +1 -0
  23. package/entry/types-react.cjs +1 -0
  24. package/entry/types-react.d.cts +1 -0
  25. package/entry/types-react.d.ts +1 -0
  26. package/entry/types-react.js +1 -0
  27. package/entry/types.cjs +1 -0
  28. package/entry/types.d.cts +1 -0
  29. package/entry/types.d.ts +1 -0
  30. package/entry/types.js +1 -0
  31. package/entry/vite.cjs +1 -0
  32. package/entry/vite.d.cts +1 -0
  33. package/entry/vite.d.ts +1 -0
  34. package/entry/vite.js +1 -0
  35. package/entry/zod.cjs +1 -0
  36. package/entry/zod.d.cts +1 -0
  37. package/entry/zod.d.ts +1 -0
  38. package/entry/zod.js +1 -0
  39. package/package.json +78 -63
  40. package/packages/hooks/dist/alova.cjs +0 -53
  41. package/packages/hooks/dist/alova.d.cts +0 -1
  42. package/packages/hooks/dist/alova.d.ts +0 -33
  43. package/packages/hooks/dist/alova.js +0 -50
  44. package/packages/hooks/dist/index.cjs +0 -0
  45. package/packages/hooks/dist/index.d.cts +0 -1
  46. package/packages/hooks/dist/index.d.ts +0 -1
  47. package/packages/hooks/dist/index.js +0 -0
  48. package/packages/hooks/dist/react.cjs +0 -169
  49. package/packages/hooks/dist/react.d.cts +0 -1
  50. package/packages/hooks/dist/react.d.ts +0 -95
  51. package/packages/hooks/dist/react.js +0 -163
  52. package/packages/utils/dist/index.cjs +0 -2300
  53. package/packages/utils/dist/index.d.cts +0 -1
  54. package/packages/utils/dist/index.d.ts +0 -2739
  55. package/packages/utils/dist/index.js +0 -2287
  56. package/packages/utils/dist/math-BznvO4qI.cjs +0 -1029
  57. package/packages/utils/dist/math-DRbCtLQH.js +0 -1012
  58. package/packages/utils/dist/math.cjs +0 -3
  59. package/packages/utils/dist/math.d.cts +0 -1
  60. package/packages/utils/dist/math.d.ts +0 -60
  61. package/packages/utils/dist/math.js +0 -2
  62. package/packages/utils/dist/vite.cjs +0 -44
  63. package/packages/utils/dist/vite.d.cts +0 -1
  64. package/packages/utils/dist/vite.d.ts +0 -28
  65. package/packages/utils/dist/vite.js +0 -43
  66. package/packages/zod/dist/index.cjs +0 -91
  67. package/packages/zod/dist/index.d.cts +0 -1
  68. package/packages/zod/dist/index.d.ts +0 -57
  69. package/packages/zod/dist/index.js +0 -61
  70. /package/{packages/hooks/dist/metadata.json → entry/hooks-metadata.json} +0 -0
  71. /package/{packages/utils/dist → entry}/metadata.json +0 -0
@@ -1,53 +0,0 @@
1
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- let alova_client = require("alova/client");
3
- //#region src/alova/createBeforeRequestMiddleware.ts
4
- function createBeforeRequestMiddleware(middleware, onBeforeRequest) {
5
- return async (context, next) => {
6
- onBeforeRequest?.(context);
7
- if (middleware) return middleware(context, next);
8
- return next();
9
- };
10
- }
11
- //#endregion
12
- //#region src/alova/useAlovaPagination.ts
13
- function useAlovaPagination(methodHandler, hookOptions) {
14
- const options = {
15
- ...hookOptions,
16
- immediate: hookOptions?.immediate ?? true
17
- };
18
- if (options.onBeforeRequest) options.middleware = createBeforeRequestMiddleware(options.middleware, options.onBeforeRequest);
19
- const exposure = (0, alova_client.usePagination)(methodHandler, options);
20
- if (options.onSuccess) exposure.onSuccess(options.onSuccess);
21
- if (options.onError) exposure.onError(options.onError);
22
- if (options.onComplete) exposure.onComplete(options.onComplete);
23
- return exposure;
24
- }
25
- //#endregion
26
- //#region src/alova/useAlovaRequest.ts
27
- function useAlovaRequest(methodHandler, hookOptions) {
28
- const options = {
29
- ...hookOptions,
30
- immediate: hookOptions?.immediate ?? true
31
- };
32
- if (options.onBeforeRequest) options.middleware = createBeforeRequestMiddleware(options.middleware, options.onBeforeRequest);
33
- const exposure = (0, alova_client.useRequest)(methodHandler, options);
34
- if (options.onSuccess) exposure.onSuccess(options.onSuccess);
35
- if (options.onError) exposure.onError(options.onError);
36
- if (options.onComplete) exposure.onComplete(options.onComplete);
37
- return exposure;
38
- }
39
- //#endregion
40
- //#region src/alova/useAlovaWatcher.ts
41
- function useAlovaWatcher(methodHandler, watchingStates, hookOptions = {}) {
42
- const options = { ...hookOptions };
43
- if (options.onBeforeRequest) options.middleware = createBeforeRequestMiddleware(options.middleware, options.onBeforeRequest);
44
- const exposure = (0, alova_client.useWatcher)(methodHandler, watchingStates, options);
45
- if (options.onSuccess) exposure.onSuccess(options.onSuccess);
46
- if (options.onError) exposure.onError(options.onError);
47
- if (options.onComplete) exposure.onComplete(options.onComplete);
48
- return exposure;
49
- }
50
- //#endregion
51
- exports.useAlovaPagination = useAlovaPagination;
52
- exports.useAlovaRequest = useAlovaRequest;
53
- exports.useAlovaWatcher = useAlovaWatcher;
@@ -1 +0,0 @@
1
- export type * from './alova.d.ts'
@@ -1,33 +0,0 @@
1
- import { AlovaFrontMiddlewareContext, AlovaMethodHandler, CompleteHandler, ErrorHandler, PaginationHookConfig, RequestHookConfig, SuccessHandler, WatcherHookConfig } from "alova/client";
2
- import { AlovaGenerics, Method } from "alova";
3
- //#region src/alova/useAlovaPagination.d.ts
4
- interface HookOptions$2<AG extends AlovaGenerics, L extends any[], Args extends any[]> extends PaginationHookConfig<AG, L> {
5
- onBeforeRequest?: BeforeRequestHandler<AG, any[]> | undefined;
6
- onSuccess?: SuccessHandler<AG, Args> | undefined;
7
- onError?: ErrorHandler<AG, Args> | undefined;
8
- onComplete?: CompleteHandler<AG, Args> | undefined;
9
- }
10
- declare function useAlovaPagination<AG extends AlovaGenerics, L extends any[], Args extends any[]>(methodHandler: (page: number, pageSize: number, ...args: Args) => Method<AG>, hookOptions?: HookOptions$2<AG, L, Args> | undefined): import("alova/client").UsePaginationExposure<AG, L, Args>;
11
- //#endregion
12
- //#region src/alova/useAlovaRequest.d.ts
13
- interface HookOptions$1<AG extends AlovaGenerics, Args extends any[]> extends RequestHookConfig<AG, Args> {
14
- onBeforeRequest?: BeforeRequestHandler<AG, Args> | undefined;
15
- onSuccess?: SuccessHandler<AG, Args> | undefined;
16
- onError?: ErrorHandler<AG, Args> | undefined;
17
- onComplete?: CompleteHandler<AG, Args> | undefined;
18
- }
19
- declare function useAlovaRequest<AG extends AlovaGenerics, Args extends any[] = any[]>(methodHandler: Method<AG> | AlovaMethodHandler<AG, Args>, hookOptions?: HookOptions$1<AG, Args> | undefined): import("alova/client").UseHookExposure<AG, Args, unknown>;
20
- //#endregion
21
- //#region src/alova/useAlovaWatcher.d.ts
22
- interface HookOptions<AG extends AlovaGenerics, Args extends any[]> extends WatcherHookConfig<AG, Args> {
23
- onBeforeRequest?: BeforeRequestHandler<AG, Args> | undefined;
24
- onSuccess?: SuccessHandler<AG, Args> | undefined;
25
- onError?: ErrorHandler<AG, Args> | undefined;
26
- onComplete?: CompleteHandler<AG, Args> | undefined;
27
- }
28
- declare function useAlovaWatcher<AG extends AlovaGenerics, Args extends any[] = any[]>(methodHandler: Method<AG> | AlovaMethodHandler<AG, Args>, watchingStates: AG["StatesExport"]["Watched"][], hookOptions?: HookOptions<AG, Args>): import("alova/client").UseHookExposure<AG, Args, unknown>;
29
- //#endregion
30
- //#region src/alova/index.d.ts
31
- type BeforeRequestHandler<AG extends AlovaGenerics, Args extends any[] = any[]> = (context: AlovaFrontMiddlewareContext<AG, Args>) => void;
32
- //#endregion
33
- export { BeforeRequestHandler, useAlovaPagination, useAlovaRequest, useAlovaWatcher };
@@ -1,50 +0,0 @@
1
- import { usePagination, useRequest, useWatcher } from "alova/client";
2
- //#region src/alova/createBeforeRequestMiddleware.ts
3
- function createBeforeRequestMiddleware(middleware, onBeforeRequest) {
4
- return async (context, next) => {
5
- onBeforeRequest?.(context);
6
- if (middleware) return middleware(context, next);
7
- return next();
8
- };
9
- }
10
- //#endregion
11
- //#region src/alova/useAlovaPagination.ts
12
- function useAlovaPagination(methodHandler, hookOptions) {
13
- const options = {
14
- ...hookOptions,
15
- immediate: hookOptions?.immediate ?? true
16
- };
17
- if (options.onBeforeRequest) options.middleware = createBeforeRequestMiddleware(options.middleware, options.onBeforeRequest);
18
- const exposure = usePagination(methodHandler, options);
19
- if (options.onSuccess) exposure.onSuccess(options.onSuccess);
20
- if (options.onError) exposure.onError(options.onError);
21
- if (options.onComplete) exposure.onComplete(options.onComplete);
22
- return exposure;
23
- }
24
- //#endregion
25
- //#region src/alova/useAlovaRequest.ts
26
- function useAlovaRequest(methodHandler, hookOptions) {
27
- const options = {
28
- ...hookOptions,
29
- immediate: hookOptions?.immediate ?? true
30
- };
31
- if (options.onBeforeRequest) options.middleware = createBeforeRequestMiddleware(options.middleware, options.onBeforeRequest);
32
- const exposure = useRequest(methodHandler, options);
33
- if (options.onSuccess) exposure.onSuccess(options.onSuccess);
34
- if (options.onError) exposure.onError(options.onError);
35
- if (options.onComplete) exposure.onComplete(options.onComplete);
36
- return exposure;
37
- }
38
- //#endregion
39
- //#region src/alova/useAlovaWatcher.ts
40
- function useAlovaWatcher(methodHandler, watchingStates, hookOptions = {}) {
41
- const options = { ...hookOptions };
42
- if (options.onBeforeRequest) options.middleware = createBeforeRequestMiddleware(options.middleware, options.onBeforeRequest);
43
- const exposure = useWatcher(methodHandler, watchingStates, options);
44
- if (options.onSuccess) exposure.onSuccess(options.onSuccess);
45
- if (options.onError) exposure.onError(options.onError);
46
- if (options.onComplete) exposure.onComplete(options.onComplete);
47
- return exposure;
48
- }
49
- //#endregion
50
- export { useAlovaPagination, useAlovaRequest, useAlovaWatcher };
File without changes
@@ -1 +0,0 @@
1
- export type * from './index.d.ts'
@@ -1 +0,0 @@
1
- export {}
File without changes
@@ -1,169 +0,0 @@
1
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- let react = require("react");
3
- let _pawover_kit_utils = require("@pawover/kit-utils");
4
- let es_toolkit = require("es-toolkit");
5
- //#region src/react/useLatest.ts
6
- /**
7
- * 返回当前最新值的 Hook
8
- *
9
- * @param value
10
- */
11
- function useLatest(value) {
12
- const ref = (0, react.useRef)(value);
13
- ref.current = value;
14
- return ref;
15
- }
16
- //#endregion
17
- //#region src/react/useMount.ts
18
- /**
19
- * 在组件初始化时执行的 Hook
20
- * - 即使在严格模式(React StrictMode)也只执行一次
21
- * - 自动使用最新版 effect 函数
22
- *
23
- * @param effect 副作用函数(必须为同步函数;若为异步函数,清理逻辑需自行处理)
24
- * @example
25
- * useMount(() => {
26
- * console.log('组件挂载');
27
- * return () => console.log('组件卸载');
28
- * });
29
- *
30
- * useMount(async () => {
31
- * const data = await fetchData();
32
- * // 清理逻辑需通过 ref/AbortController 自行管理
33
- * // ❌ 不要 return cleanupFn(async 函数返回 Promise,无法作为清理函数)
34
- * });
35
- */
36
- function useMount(effect) {
37
- const isMountedRef = (0, react.useRef)(false);
38
- const effectRef = useLatest(effect);
39
- (0, react.useEffect)(() => {
40
- if (!_pawover_kit_utils.TypeUtil.isFunction(effectRef.current)) {
41
- console.error(`hook [useMount] Expected parameter to be a function, but got ${typeof effectRef.current}. This effect will not execute.`);
42
- return;
43
- }
44
- if (isMountedRef.current) return;
45
- isMountedRef.current = true;
46
- const cleanup = effectRef.current?.();
47
- return _pawover_kit_utils.TypeUtil.isFunction(cleanup) && !_pawover_kit_utils.TypeUtil.isPromise(cleanup) ? cleanup : void 0;
48
- }, [effectRef]);
49
- }
50
- //#endregion
51
- //#region src/react/useResponsive.ts
52
- /** 屏幕响应断点 token 配置 */
53
- const BREAK_POINT_TOKEN = {
54
- XS: 480,
55
- XSMax: 575,
56
- XSMin: 480,
57
- SM: 576,
58
- SMMax: 767,
59
- SMMin: 576,
60
- MD: 768,
61
- MDMax: 991,
62
- MDMin: 768,
63
- LG: 992,
64
- LGMax: 1199,
65
- LGMin: 992,
66
- XL: 1200,
67
- XLMax: 1599,
68
- XLMin: 1200,
69
- XXL: 1600,
70
- XXLMax: 1919,
71
- XXLMin: 1600,
72
- XXXL: 1920,
73
- XXXLMin: 1920
74
- };
75
- const SUBSCRIBER_SET = /* @__PURE__ */ new Set();
76
- const BREAK_POINTS = [
77
- "xxxl",
78
- "xxl",
79
- "xl",
80
- "lg",
81
- "md",
82
- "sm",
83
- "xs"
84
- ];
85
- const DEFAULT_VALUES = Object.freeze(_pawover_kit_utils.ArrayUtil.zipToObject(BREAK_POINTS, false));
86
- let responsiveValues = { ...DEFAULT_VALUES };
87
- let responsiveTokens = (0, es_toolkit.clone)(BREAK_POINT_TOKEN);
88
- function useResponsive(options) {
89
- const { breakPointTokens = {} } = options || {};
90
- const tokens = (0, react.useMemo)(() => Object.assign(BREAK_POINT_TOKEN, breakPointTokens), [breakPointTokens]);
91
- const [responsive, setResponsive] = (0, react.useState)(() => calculateResponsive(tokens));
92
- const current = _pawover_kit_utils.ObjectUtil.keys(DEFAULT_VALUES).find((key) => responsive[key] === true) || "xs";
93
- (0, react.useLayoutEffect)(() => {
94
- responsiveTokens = tokens;
95
- window.addEventListener("resize", resizeListener);
96
- const subscriber = () => {
97
- setResponsive(responsiveValues);
98
- };
99
- SUBSCRIBER_SET.add(subscriber);
100
- return () => {
101
- SUBSCRIBER_SET.delete(subscriber);
102
- if (!SUBSCRIBER_SET.size) window.removeEventListener("resize", resizeListener);
103
- };
104
- }, [tokens]);
105
- return {
106
- responsive,
107
- current,
108
- breakPointTokens: tokens
109
- };
110
- }
111
- function resizeListener() {
112
- const newValues = calculateResponsive(responsiveTokens);
113
- if (!(0, es_toolkit.isEqual)(responsiveValues, newValues)) {
114
- responsiveValues = newValues;
115
- for (const subscriber of SUBSCRIBER_SET) subscriber();
116
- }
117
- }
118
- function calculateResponsive(tokens) {
119
- const config = _pawover_kit_utils.ArrayUtil.zipToObject(BREAK_POINTS, BREAK_POINTS.map((t) => tokens[_pawover_kit_utils.StringUtil.toUpperCase(t)]));
120
- return _pawover_kit_utils.ObjectUtil.entriesMap(DEFAULT_VALUES, (key) => [key, window.innerWidth >= config[key]]);
121
- }
122
- //#endregion
123
- //#region src/react/useUnmount.ts
124
- /**
125
- * 在组件卸载时执行的 Hook
126
- *
127
- * @param effect 副作用函数
128
- */
129
- function useUnmount(effect) {
130
- const effectRef = useLatest(effect);
131
- (0, react.useEffect)(() => () => {
132
- if (!_pawover_kit_utils.TypeUtil.isFunction(effectRef.current)) {
133
- console.error(`hook [useUnmount] Expected parameter to be a function, but got ${typeof effectRef.current}. This effect will not execute.`);
134
- return;
135
- }
136
- effectRef.current?.();
137
- }, [effectRef]);
138
- }
139
- //#endregion
140
- //#region src/react/useTitle.ts
141
- /**
142
- * 设置页面标题
143
- * - 轻量级,适用于无路由库时设置页面标题
144
- * - 多个 `useTitle` 实例会互相干扰,需在顶层组件使用
145
- * - 无法处理 `document.title` 固有的竞态问题
146
- *
147
- * @param title 页面标题
148
- * @param options 配置选项
149
- */
150
- function useTitle(title, options) {
151
- const titleRef = (0, react.useRef)(_pawover_kit_utils.EnvUtil.isBrowser() ? document.title : "");
152
- (0, react.useEffect)(() => {
153
- if (!_pawover_kit_utils.TypeUtil.isString(title)) {
154
- console.error(`hook [useTitle] Expected parameter to be a string, but got ${typeof title}. This effect will not execute.`);
155
- return;
156
- }
157
- if (_pawover_kit_utils.EnvUtil.isBrowser()) document.title = title;
158
- }, [title]);
159
- useUnmount(() => {
160
- if (_pawover_kit_utils.EnvUtil.isBrowser() && options?.isRestoreOnUnmount) document.title = titleRef.current;
161
- });
162
- }
163
- //#endregion
164
- exports.BREAK_POINT_TOKEN = BREAK_POINT_TOKEN;
165
- exports.useLatest = useLatest;
166
- exports.useMount = useMount;
167
- exports.useResponsive = useResponsive;
168
- exports.useTitle = useTitle;
169
- exports.useUnmount = useUnmount;
@@ -1 +0,0 @@
1
- export type * from './react.d.ts'
@@ -1,95 +0,0 @@
1
- import { EffectCallback, RefObject } from "react";
2
- import { AnyAsyncFunction, AnyFunction } from "@pawover/types";
3
- import { TupleToUnion } from "type-fest";
4
- //#region src/react/useLatest.d.ts
5
- /**
6
- * 返回当前最新值的 Hook
7
- *
8
- * @param value
9
- */
10
- declare function useLatest<T>(value: T): RefObject<T>;
11
- //#endregion
12
- //#region src/react/useMount.d.ts
13
- /**
14
- * 在组件初始化时执行的 Hook
15
- * - 即使在严格模式(React StrictMode)也只执行一次
16
- * - 自动使用最新版 effect 函数
17
- *
18
- * @param effect 副作用函数(必须为同步函数;若为异步函数,清理逻辑需自行处理)
19
- * @example
20
- * useMount(() => {
21
- * console.log('组件挂载');
22
- * return () => console.log('组件卸载');
23
- * });
24
- *
25
- * useMount(async () => {
26
- * const data = await fetchData();
27
- * // 清理逻辑需通过 ref/AbortController 自行管理
28
- * // ❌ 不要 return cleanupFn(async 函数返回 Promise,无法作为清理函数)
29
- * });
30
- */
31
- declare function useMount(effect: EffectCallback | AnyAsyncFunction): void;
32
- //#endregion
33
- //#region src/react/useResponsive.d.ts
34
- type Breakpoint = TupleToUnion<typeof BREAK_POINTS>;
35
- type ResponsiveValues = Record<Breakpoint, boolean>;
36
- type BreakPointTokens = Record<keyof typeof BREAK_POINT_TOKEN, number>;
37
- /** 屏幕响应断点 token 配置 */
38
- declare const BREAK_POINT_TOKEN: {
39
- readonly XS: 480;
40
- readonly XSMax: 575;
41
- readonly XSMin: 480;
42
- readonly SM: 576;
43
- readonly SMMax: 767;
44
- readonly SMMin: 576;
45
- readonly MD: 768;
46
- readonly MDMax: 991;
47
- readonly MDMin: 768;
48
- readonly LG: 992;
49
- readonly LGMax: 1199;
50
- readonly LGMin: 992;
51
- readonly XL: 1200;
52
- readonly XLMax: 1599;
53
- readonly XLMin: 1200;
54
- readonly XXL: 1600;
55
- readonly XXLMax: 1919;
56
- readonly XXLMin: 1600;
57
- readonly XXXL: 1920;
58
- readonly XXXLMin: 1920;
59
- };
60
- declare const BREAK_POINTS: readonly ["xxxl", "xxl", "xl", "lg", "md", "sm", "xs"];
61
- interface ResponsiveHookOptions {
62
- /** 屏幕响应断点 token 配置 */
63
- breakPointTokens?: BreakPointTokens;
64
- }
65
- declare function useResponsive(options?: ResponsiveHookOptions | undefined): {
66
- responsive: ResponsiveValues;
67
- current: "xxxl" | "xxl" | "xl" | "lg" | "md" | "sm" | "xs";
68
- breakPointTokens: BreakPointTokens;
69
- };
70
- //#endregion
71
- //#region src/react/useTitle.d.ts
72
- interface TitleHookOptions {
73
- /** 件卸载时是否恢复原始标题 */
74
- isRestoreOnUnmount?: boolean;
75
- }
76
- /**
77
- * 设置页面标题
78
- * - 轻量级,适用于无路由库时设置页面标题
79
- * - 多个 `useTitle` 实例会互相干扰,需在顶层组件使用
80
- * - 无法处理 `document.title` 固有的竞态问题
81
- *
82
- * @param title 页面标题
83
- * @param options 配置选项
84
- */
85
- declare function useTitle(title: string, options?: TitleHookOptions | undefined): void;
86
- //#endregion
87
- //#region src/react/useUnmount.d.ts
88
- /**
89
- * 在组件卸载时执行的 Hook
90
- *
91
- * @param effect 副作用函数
92
- */
93
- declare function useUnmount(effect: AnyFunction): void;
94
- //#endregion
95
- export { BREAK_POINT_TOKEN, BreakPointTokens, ResponsiveHookOptions, useLatest, useMount, useResponsive, useTitle, useUnmount };
@@ -1,163 +0,0 @@
1
- import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
2
- import { ArrayUtil, EnvUtil, ObjectUtil, StringUtil, TypeUtil } from "@pawover/kit-utils";
3
- import { clone, isEqual } from "es-toolkit";
4
- //#region src/react/useLatest.ts
5
- /**
6
- * 返回当前最新值的 Hook
7
- *
8
- * @param value
9
- */
10
- function useLatest(value) {
11
- const ref = useRef(value);
12
- ref.current = value;
13
- return ref;
14
- }
15
- //#endregion
16
- //#region src/react/useMount.ts
17
- /**
18
- * 在组件初始化时执行的 Hook
19
- * - 即使在严格模式(React StrictMode)也只执行一次
20
- * - 自动使用最新版 effect 函数
21
- *
22
- * @param effect 副作用函数(必须为同步函数;若为异步函数,清理逻辑需自行处理)
23
- * @example
24
- * useMount(() => {
25
- * console.log('组件挂载');
26
- * return () => console.log('组件卸载');
27
- * });
28
- *
29
- * useMount(async () => {
30
- * const data = await fetchData();
31
- * // 清理逻辑需通过 ref/AbortController 自行管理
32
- * // ❌ 不要 return cleanupFn(async 函数返回 Promise,无法作为清理函数)
33
- * });
34
- */
35
- function useMount(effect) {
36
- const isMountedRef = useRef(false);
37
- const effectRef = useLatest(effect);
38
- useEffect(() => {
39
- if (!TypeUtil.isFunction(effectRef.current)) {
40
- console.error(`hook [useMount] Expected parameter to be a function, but got ${typeof effectRef.current}. This effect will not execute.`);
41
- return;
42
- }
43
- if (isMountedRef.current) return;
44
- isMountedRef.current = true;
45
- const cleanup = effectRef.current?.();
46
- return TypeUtil.isFunction(cleanup) && !TypeUtil.isPromise(cleanup) ? cleanup : void 0;
47
- }, [effectRef]);
48
- }
49
- //#endregion
50
- //#region src/react/useResponsive.ts
51
- /** 屏幕响应断点 token 配置 */
52
- const BREAK_POINT_TOKEN = {
53
- XS: 480,
54
- XSMax: 575,
55
- XSMin: 480,
56
- SM: 576,
57
- SMMax: 767,
58
- SMMin: 576,
59
- MD: 768,
60
- MDMax: 991,
61
- MDMin: 768,
62
- LG: 992,
63
- LGMax: 1199,
64
- LGMin: 992,
65
- XL: 1200,
66
- XLMax: 1599,
67
- XLMin: 1200,
68
- XXL: 1600,
69
- XXLMax: 1919,
70
- XXLMin: 1600,
71
- XXXL: 1920,
72
- XXXLMin: 1920
73
- };
74
- const SUBSCRIBER_SET = /* @__PURE__ */ new Set();
75
- const BREAK_POINTS = [
76
- "xxxl",
77
- "xxl",
78
- "xl",
79
- "lg",
80
- "md",
81
- "sm",
82
- "xs"
83
- ];
84
- const DEFAULT_VALUES = Object.freeze(ArrayUtil.zipToObject(BREAK_POINTS, false));
85
- let responsiveValues = { ...DEFAULT_VALUES };
86
- let responsiveTokens = clone(BREAK_POINT_TOKEN);
87
- function useResponsive(options) {
88
- const { breakPointTokens = {} } = options || {};
89
- const tokens = useMemo(() => Object.assign(BREAK_POINT_TOKEN, breakPointTokens), [breakPointTokens]);
90
- const [responsive, setResponsive] = useState(() => calculateResponsive(tokens));
91
- const current = ObjectUtil.keys(DEFAULT_VALUES).find((key) => responsive[key] === true) || "xs";
92
- useLayoutEffect(() => {
93
- responsiveTokens = tokens;
94
- window.addEventListener("resize", resizeListener);
95
- const subscriber = () => {
96
- setResponsive(responsiveValues);
97
- };
98
- SUBSCRIBER_SET.add(subscriber);
99
- return () => {
100
- SUBSCRIBER_SET.delete(subscriber);
101
- if (!SUBSCRIBER_SET.size) window.removeEventListener("resize", resizeListener);
102
- };
103
- }, [tokens]);
104
- return {
105
- responsive,
106
- current,
107
- breakPointTokens: tokens
108
- };
109
- }
110
- function resizeListener() {
111
- const newValues = calculateResponsive(responsiveTokens);
112
- if (!isEqual(responsiveValues, newValues)) {
113
- responsiveValues = newValues;
114
- for (const subscriber of SUBSCRIBER_SET) subscriber();
115
- }
116
- }
117
- function calculateResponsive(tokens) {
118
- const config = ArrayUtil.zipToObject(BREAK_POINTS, BREAK_POINTS.map((t) => tokens[StringUtil.toUpperCase(t)]));
119
- return ObjectUtil.entriesMap(DEFAULT_VALUES, (key) => [key, window.innerWidth >= config[key]]);
120
- }
121
- //#endregion
122
- //#region src/react/useUnmount.ts
123
- /**
124
- * 在组件卸载时执行的 Hook
125
- *
126
- * @param effect 副作用函数
127
- */
128
- function useUnmount(effect) {
129
- const effectRef = useLatest(effect);
130
- useEffect(() => () => {
131
- if (!TypeUtil.isFunction(effectRef.current)) {
132
- console.error(`hook [useUnmount] Expected parameter to be a function, but got ${typeof effectRef.current}. This effect will not execute.`);
133
- return;
134
- }
135
- effectRef.current?.();
136
- }, [effectRef]);
137
- }
138
- //#endregion
139
- //#region src/react/useTitle.ts
140
- /**
141
- * 设置页面标题
142
- * - 轻量级,适用于无路由库时设置页面标题
143
- * - 多个 `useTitle` 实例会互相干扰,需在顶层组件使用
144
- * - 无法处理 `document.title` 固有的竞态问题
145
- *
146
- * @param title 页面标题
147
- * @param options 配置选项
148
- */
149
- function useTitle(title, options) {
150
- const titleRef = useRef(EnvUtil.isBrowser() ? document.title : "");
151
- useEffect(() => {
152
- if (!TypeUtil.isString(title)) {
153
- console.error(`hook [useTitle] Expected parameter to be a string, but got ${typeof title}. This effect will not execute.`);
154
- return;
155
- }
156
- if (EnvUtil.isBrowser()) document.title = title;
157
- }, [title]);
158
- useUnmount(() => {
159
- if (EnvUtil.isBrowser() && options?.isRestoreOnUnmount) document.title = titleRef.current;
160
- });
161
- }
162
- //#endregion
163
- export { BREAK_POINT_TOKEN, useLatest, useMount, useResponsive, useTitle, useUnmount };