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