@hairy/react-lib 1.46.0 → 1.49.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/index.mjs ADDED
@@ -0,0 +1,446 @@
1
+ import { Deferred, isFunction, noop } from "@hairy/utils";
2
+ import { ref, useSnapshot } from "valtio";
3
+ import { Children, createElement, isValidElement, useCallback, useEffect, useInsertionEffect, useMemo, useReducer, useRef, useState } from "react";
4
+ import { proxyMap } from "valtio/utils";
5
+ import mitt from "mitt";
6
+ import { useMount } from "react-use";
7
+
8
+ //#region src/utils/cls.ts
9
+ const hasOwn = {}.hasOwnProperty;
10
+ /**
11
+ * A simple JavaScript utility for conditionally joining classNames together.
12
+ */
13
+ function cls(...args) {
14
+ let classes = "";
15
+ for (let i = 0; i < args.length; i++) {
16
+ const arg = args[i];
17
+ if (arg) classes = cls.append(classes, cls.parse(arg));
18
+ }
19
+ return classes;
20
+ }
21
+ cls.parse = function(arg) {
22
+ if (typeof arg === "string") return arg;
23
+ if (typeof arg !== "object") return "";
24
+ if (Array.isArray(arg)) return cls.apply(null, arg);
25
+ if (arg.toString !== Object.prototype.toString && !arg.toString.toString().includes("[native code]")) return cls.toString();
26
+ let classes = "";
27
+ for (const key in arg) if (hasOwn.call(arg, key) && arg[key]) classes = cls.append(classes, key);
28
+ return classes;
29
+ };
30
+ cls.append = function(value, newClass) {
31
+ if (!newClass) return value;
32
+ return value ? `${value} ${newClass}` : newClass;
33
+ };
34
+
35
+ //#endregion
36
+ //#region src/utils/track.ts
37
+ /**
38
+ * @requires `Trigger` component to be mounted in the tree.
39
+ *
40
+ * @example
41
+ * ```tsx
42
+ * // Obtain externally
43
+ * import { track } from '@hairy/lib-react'
44
+ * const context = await track(() => useContext(YourContext))
45
+ * console.log(context) // { ... }
46
+ */
47
+ function track(fn, ...args) {
48
+ const deferred = ref(new Deferred());
49
+ const exposer = {
50
+ fn,
51
+ args,
52
+ deferred,
53
+ id: ++Trigger.id
54
+ };
55
+ Trigger.tasks.set(exposer.id, exposer);
56
+ deferred.then(() => Trigger.tasks.delete(exposer.id));
57
+ return deferred;
58
+ }
59
+
60
+ //#endregion
61
+ //#region src/utils/wrapper.ts
62
+ function wrapper(asChild, props, children) {
63
+ return asChild ? createElement(asChild, props, children) : children;
64
+ }
65
+
66
+ //#endregion
67
+ //#region src/components/condition/Case.ts
68
+ function Case(props) {
69
+ const { cond, children, tag, as: asChild, ...attrs } = props;
70
+ return wrapper(tag || asChild, attrs, children);
71
+ }
72
+
73
+ //#endregion
74
+ //#region src/components/condition/Default.ts
75
+ function Default(props) {
76
+ const { children, tag, as: asChild, ...attrs } = props;
77
+ return wrapper(tag || asChild, attrs, children);
78
+ }
79
+
80
+ //#endregion
81
+ //#region src/components/condition/Then.ts
82
+ function Then(props) {
83
+ const { children, cond, else: _else, then, tag, as: asChild, ...attrs } = props;
84
+ return Object.keys(props).includes("cond") ? wrapper(If, props, children) : wrapper(tag || asChild, attrs, children);
85
+ }
86
+
87
+ //#endregion
88
+ //#region src/components/condition/If.ts
89
+ function If(props) {
90
+ const { then, cond, else: _else, children = props.then, tag, as: asChild, ...attrs } = props;
91
+ const elements = Children.toArray(children);
92
+ const thenChild = elements.find((c) => c.type === Then);
93
+ const elseChild = elements.find((c) => c.type === Else);
94
+ return wrapper(tag || asChild, attrs, thenChild || elseChild ? cond ? thenChild : elseChild : cond ? children : _else);
95
+ }
96
+
97
+ //#endregion
98
+ //#region src/components/condition/Else.ts
99
+ function Else(props) {
100
+ const { children, tag, as: asChild, ...attrs } = props;
101
+ return Object.keys(props).includes("cond") ? wrapper(If, props, children) : wrapper(tag || asChild, attrs, children);
102
+ }
103
+
104
+ //#endregion
105
+ //#region src/components/condition/Switch.ts
106
+ function Switch(props) {
107
+ const isUseValue = props.value !== void 0;
108
+ let matchingCase;
109
+ let defaultCase;
110
+ Children.forEach(props.children, (child) => {
111
+ if (!isValidElement(child) || matchingCase) return;
112
+ if (child.type === Case) {
113
+ const cond = child?.props?.cond;
114
+ if (isUseValue ? props.value === cond : cond) {
115
+ matchingCase = child;
116
+ return;
117
+ }
118
+ }
119
+ if (!defaultCase && child.type === Default) defaultCase = child;
120
+ });
121
+ return matchingCase ?? defaultCase ?? null;
122
+ }
123
+
124
+ //#endregion
125
+ //#region src/components/condition/Unless.ts
126
+ function Unless(props) {
127
+ const { cond, then, else: _else, tag, as: asChild, children = props.then, ...attrs } = props;
128
+ const elements = Children.toArray(children);
129
+ const thenChild = elements.find((c) => c.type === Then);
130
+ const elseChild = elements.find((c) => c.type === Else);
131
+ return wrapper(tag || asChild, attrs, thenChild || elseChild ? !cond ? elseChild : thenChild : !cond ? children : _else);
132
+ }
133
+
134
+ //#endregion
135
+ //#region src/components/utils/Injector.ts
136
+ function Injector(props) {
137
+ return useMemo(() => props.install.map(repack).reverse(), [props.install]).reduce((child, { component: Component, props }) => createElement(Component, props, child), props.children);
138
+ }
139
+ function repack(c) {
140
+ return c.component ? c : { component: c };
141
+ }
142
+
143
+ //#endregion
144
+ //#region src/components/utils/Trigger.ts
145
+ const pendingTasks = proxyMap();
146
+ function createTracker(exposer) {
147
+ const Component = () => {
148
+ try {
149
+ exposer.deferred.resolve(exposer.fn(...exposer.args));
150
+ } catch (error) {
151
+ exposer.deferred.reject(error);
152
+ }
153
+ return null;
154
+ };
155
+ Component.key = exposer.id;
156
+ return Component;
157
+ }
158
+ function renderTracker(Tracker) {
159
+ return createElement(Tracker, { key: Tracker.key });
160
+ }
161
+ /**
162
+ * @example
163
+ * ```tsx
164
+ * import { Trigger } from '@hairy/lib-react'
165
+ *
166
+ * // Use triggers to capture context
167
+ * function App() {
168
+ * return (
169
+ * <YourContext.Provider>
170
+ * <Trigger />
171
+ * </YourContext.Provider>
172
+ * )
173
+ * }
174
+ *
175
+ * // Obtain externally
176
+ * import { track } from '@hairy/lib-react'
177
+ * const context = await track(() => useContext(YourContext))
178
+ * console.log(context) // { ... }
179
+ * ```
180
+ */
181
+ function Trigger() {
182
+ return [...useSnapshot(pendingTasks).values()].map(createTracker).map(renderTracker);
183
+ }
184
+ Trigger.id = 0;
185
+ Trigger.tasks = pendingTasks;
186
+
187
+ //#endregion
188
+ //#region src/hooks/tryUseCallback.ts
189
+ const tryUseCallback = (callback, deps) => {
190
+ try {
191
+ return useCallback(callback, deps);
192
+ } catch {
193
+ return callback;
194
+ }
195
+ };
196
+
197
+ //#endregion
198
+ //#region src/hooks/tryUseEffect.ts
199
+ const tryUseEffect = (effect, deps) => {
200
+ try {
201
+ useEffect(effect, deps);
202
+ } catch {}
203
+ };
204
+
205
+ //#endregion
206
+ //#region src/hooks/tryUseInsertionEffect.ts
207
+ const tryUseInsertionEffect = (callback, deps) => {
208
+ try {
209
+ useInsertionEffect(callback, deps);
210
+ } catch {}
211
+ };
212
+
213
+ //#endregion
214
+ //#region src/hooks/tryUseReducer.ts
215
+ const tryUseReducer = (reducer, initializerArg, initializer) => {
216
+ try {
217
+ return useReducer(reducer, initializerArg, initializer);
218
+ } catch {
219
+ return [initializerArg, () => {}];
220
+ }
221
+ };
222
+
223
+ //#endregion
224
+ //#region src/hooks/tryUseRef.ts
225
+ const tryUseRef = (initialValue) => {
226
+ try {
227
+ return useRef(initialValue);
228
+ } catch {
229
+ return { current: initialValue };
230
+ }
231
+ };
232
+
233
+ //#endregion
234
+ //#region src/hooks/tryUseState.ts
235
+ const tryUseState = (initialState) => {
236
+ try {
237
+ return useState(initialState);
238
+ } catch {
239
+ return [isFunction(initialState) ? initialState() : initialState, noop];
240
+ }
241
+ };
242
+
243
+ //#endregion
244
+ //#region src/hooks/useUpdate.ts
245
+ const updateReducer = (num) => (num + 1) % 1e6;
246
+ function useUpdate() {
247
+ const [, update] = tryUseReducer(updateReducer, 0);
248
+ return update;
249
+ }
250
+
251
+ //#endregion
252
+ //#region src/hooks/tryUseUpdate.ts
253
+ function tryUseUpdate() {
254
+ try {
255
+ return useUpdate();
256
+ } catch {
257
+ return () => {};
258
+ }
259
+ }
260
+
261
+ //#endregion
262
+ //#region src/hooks/useAsyncCallback.ts
263
+ function useAsyncCallback(fun) {
264
+ const [state, set] = useState({ loading: false });
265
+ async function refetch(...args) {
266
+ set({ loading: true });
267
+ return fun(...args).then((value) => {
268
+ set({ loading: false });
269
+ return value;
270
+ }).catch((err) => {
271
+ set({
272
+ loading: false,
273
+ error: err
274
+ });
275
+ return Promise.reject(err);
276
+ });
277
+ }
278
+ return [
279
+ state.loading,
280
+ refetch,
281
+ state.error
282
+ ];
283
+ }
284
+
285
+ //#endregion
286
+ //#region src/hooks/useAsyncState.ts
287
+ function useAsyncState(fun, deps = [], options) {
288
+ const [value, set] = useState(options?.initial);
289
+ const [loading, refetch, error] = useAsyncCallback(async (...args) => fun(...args).then(set));
290
+ useEffect(() => {
291
+ refetch();
292
+ }, deps);
293
+ return [{
294
+ value,
295
+ loading,
296
+ error
297
+ }, refetch];
298
+ }
299
+
300
+ //#endregion
301
+ //#region src/hooks/useDebounce.ts
302
+ function useDebounce(value, delay) {
303
+ const [debouncedValue, setDebouncedValue] = useState(value);
304
+ useEffect(() => {
305
+ const handler = setTimeout(() => setDebouncedValue(value), delay);
306
+ return () => clearTimeout(handler);
307
+ }, [value, delay]);
308
+ return debouncedValue;
309
+ }
310
+
311
+ //#endregion
312
+ //#region src/hooks/useEventBus.ts
313
+ const emitter = mitt();
314
+ function useEventBus(key) {
315
+ const onRef = useRef(void 0);
316
+ function on(listener) {
317
+ emitter.on(key, listener);
318
+ onRef.current = listener;
319
+ useEffect(() => {
320
+ if (!onRef.current) return;
321
+ emitter.off(key, onRef.current);
322
+ emitter.on(key, listener);
323
+ onRef.current = listener;
324
+ return () => emitter.off(key, listener);
325
+ }, [listener]);
326
+ }
327
+ function emit(event) {
328
+ emitter.emit(key, event);
329
+ }
330
+ function off(listener) {
331
+ emitter.off(key, listener);
332
+ }
333
+ return {
334
+ on,
335
+ emit,
336
+ off
337
+ };
338
+ }
339
+
340
+ //#endregion
341
+ //#region src/hooks/useFetchIntercept.ts
342
+ function useFetchResponseIntercept(intercept) {
343
+ useMount(() => fetchResponseIntercept(intercept));
344
+ }
345
+ function useFetchRequestIntercept(intercept) {
346
+ useMount(() => fetchRequestIntercept(intercept));
347
+ }
348
+ function fetchResponseIntercept(intercept) {
349
+ const { fetch: originalFetch } = window;
350
+ window.fetch = async (...args) => {
351
+ const [resource, config] = args;
352
+ return intercept(await originalFetch(resource, config), config);
353
+ };
354
+ }
355
+ function fetchRequestIntercept(intercept) {
356
+ const { fetch: originalFetch } = window;
357
+ window.fetch = async (...args) => {
358
+ const [resource, config] = args;
359
+ return intercept(originalFetch, resource, config);
360
+ };
361
+ }
362
+
363
+ //#endregion
364
+ //#region src/hooks/useMounted.ts
365
+ function useMounted() {
366
+ const [mounted, setMounted] = useState(false);
367
+ useEffect(() => setMounted(true), []);
368
+ return mounted;
369
+ }
370
+
371
+ //#endregion
372
+ //#region src/hooks/usePrevious.ts
373
+ function usePrevious(value) {
374
+ const ref = useRef(void 0);
375
+ useEffect(() => {
376
+ ref.current = value;
377
+ });
378
+ return ref.current;
379
+ }
380
+
381
+ //#endregion
382
+ //#region src/hooks/useWatch.ts
383
+ function useWatch(source, callback, options = {}) {
384
+ const firstUpdate = useRef(false);
385
+ const then = useRef(void 0);
386
+ const oldValue = usePrevious(source);
387
+ useEffect(() => {
388
+ if (!firstUpdate.current) recordFirst();
389
+ else callback(source, oldValue);
390
+ }, useMemo(() => Array.isArray(source) ? source : [source], [source]));
391
+ async function recordFirst() {
392
+ if (then.current) return;
393
+ then.current = Promise.resolve(source);
394
+ then.current.then(() => firstUpdate.current = true);
395
+ if (options.immediate) then.current.then((value) => callback(value, oldValue));
396
+ }
397
+ }
398
+
399
+ //#endregion
400
+ //#region src/hooks/useOffsetPagination.ts
401
+ function useOffsetPagination(options) {
402
+ const [page, setPage] = useState(options.page || 1);
403
+ const [pageSize, setPageSize] = useState(options.pageSize || 10);
404
+ const total = options.total || 0;
405
+ const pageCount = useMemo(() => Math.max(1, Math.ceil(total / pageSize)), [total, pageSize]);
406
+ const isFirstPage = useMemo(() => page === 1, [page]);
407
+ const isLastPage = useMemo(() => page === pageCount, [page, pageCount]);
408
+ function next() {
409
+ setPage((page) => Math.min(pageCount, page + 1));
410
+ }
411
+ function prev() {
412
+ setPage((page) => Math.max(1, page - 1));
413
+ }
414
+ function pageChange(page) {
415
+ setPage(() => Math.max(1, Math.min(page, pageCount)));
416
+ }
417
+ function pageSizeChange(limit) {
418
+ setPageSize(limit);
419
+ pageChange(1);
420
+ }
421
+ const pagination = {
422
+ next,
423
+ prev,
424
+ page,
425
+ pageSize,
426
+ isFirstPage,
427
+ pageSizeChange,
428
+ pageChange,
429
+ isLastPage,
430
+ pageCount,
431
+ total
432
+ };
433
+ useWatch(page, () => options.onChange?.(pagination));
434
+ useWatch(pageSize, () => options.onPageSizeChange?.(pagination));
435
+ useWatch(pageCount, () => options.onPageCountChange?.(pagination));
436
+ return pagination;
437
+ }
438
+
439
+ //#endregion
440
+ //#region src/hooks/useWhenever.ts
441
+ function useWhenever(source, cb, options) {
442
+ useWatch(source, (value, oldValue) => value && cb(value, oldValue), options);
443
+ }
444
+
445
+ //#endregion
446
+ export { Case, Default, Else, If, Injector, Switch, Then, Trigger, Unless, cls, track, tryUseCallback, tryUseEffect, tryUseInsertionEffect, tryUseReducer, tryUseRef, tryUseState, tryUseUpdate, useAsyncCallback, useAsyncState, useDebounce, useEventBus, useFetchRequestIntercept, useFetchResponseIntercept, useMounted, useOffsetPagination, useUpdate, useWatch, useWhenever, wrapper };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hairy/react-lib",
3
3
  "type": "module",
4
- "version": "1.46.0",
4
+ "version": "1.49.0",
5
5
  "description": "Library for react",
6
6
  "author": "Hairyf <wwu710632@gmail.com>",
7
7
  "license": "MIT",
@@ -14,22 +14,38 @@
14
14
  "bugs": "https://github.com/hairyf/hairylib/issues",
15
15
  "keywords": [],
16
16
  "sideEffects": false,
17
- "main": "./dist/index.js",
18
- "publishConfig": {
19
- "jsdelivr": "./dist/index.global.js"
17
+ "exports": {
18
+ ".": {
19
+ "import": "./dist/index.mjs",
20
+ "require": "./dist/index.cjs"
21
+ },
22
+ "./package.json": "./package.json"
20
23
  },
24
+ "main": "./dist/index.mjs",
25
+ "module": "./dist/index.mjs",
26
+ "types": "./dist/index.d.mts",
21
27
  "files": [
22
28
  "dist"
23
29
  ],
30
+ "publishConfig": {
31
+ "jsdelivr": "./dist/index.iife.js"
32
+ },
24
33
  "peerDependencies": {
25
34
  "react": "^18.2.0",
26
35
  "react-dom": "^18.2.0",
27
- "react-i18next": "^14.1.2"
36
+ "react-i18next": "^14.1.2",
37
+ "react-use": "^17.0.0"
38
+ },
39
+ "peerDependenciesMeta": {
40
+ "react-i18next": {
41
+ "optional": true
42
+ }
28
43
  },
29
44
  "dependencies": {
30
45
  "html-parse-stringify": "^3.0.1",
31
46
  "mitt": "^3.0.1",
32
- "valtio": "^2"
47
+ "valtio": "^2",
48
+ "@hairy/utils": "1.49.0"
33
49
  },
34
50
  "devDependencies": {
35
51
  "@types/react": "^19.1.3",
@@ -37,23 +53,12 @@
37
53
  "react": "^19.1.0",
38
54
  "react-dom": "^19.1.0",
39
55
  "react-i18next": "^14.1.2",
40
- "react-use": "^17.6.0",
41
- "@hairy/utils": "1.46.0"
56
+ "react-use": "^17.6.0"
42
57
  },
43
58
  "scripts": {
44
- "build": "tsup",
45
- "dev": "tsup --watch",
59
+ "build": "tsdown",
60
+ "dev": "tsdown --watch",
46
61
  "start": "tsx src/index.ts"
47
62
  },
48
- "module": "./dist/index.js",
49
- "types": "./dist/index.d.ts",
50
- "unpkg": "./dist/index.global.js",
51
- "exports": {
52
- ".": {
53
- "import": "./dist/index.js",
54
- "require": "./dist/index.cjs",
55
- "types": "./dist/index.d.ts"
56
- },
57
- "./*": "./*"
58
- }
63
+ "unpkg": "./dist/index.iife.js"
59
64
  }