@aweebit/react-essentials 0.5.4 → 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.
- package/README.md +836 -0
- package/dist/hooks/index.d.ts +5 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +5 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/useEventListener.d.ts +65 -11
- package/dist/hooks/useEventListener.d.ts.map +1 -1
- package/dist/hooks/useEventListener.js +49 -18
- package/dist/hooks/useEventListener.js.map +1 -1
- package/dist/hooks/useForceUpdate.d.ts +52 -4
- package/dist/hooks/useForceUpdate.d.ts.map +1 -1
- package/dist/hooks/useForceUpdate.js +58 -9
- package/dist/hooks/useForceUpdate.js.map +1 -1
- package/dist/hooks/useReducerWithDeps.d.ts +17 -11
- package/dist/hooks/useReducerWithDeps.d.ts.map +1 -1
- package/dist/hooks/useReducerWithDeps.js +14 -12
- package/dist/hooks/useReducerWithDeps.js.map +1 -1
- package/dist/hooks/useStateWithDeps.d.ts +41 -7
- package/dist/hooks/useStateWithDeps.d.ts.map +1 -1
- package/dist/hooks/useStateWithDeps.js +47 -15
- package/dist/hooks/useStateWithDeps.js.map +1 -1
- package/dist/index.d.ts +2 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -5
- package/dist/index.js.map +1 -1
- package/dist/misc/createSafeContext.d.ts +85 -0
- package/dist/misc/createSafeContext.d.ts.map +1 -0
- package/dist/misc/createSafeContext.js +77 -0
- package/dist/misc/createSafeContext.js.map +1 -0
- package/dist/misc/index.d.ts +2 -0
- package/dist/misc/index.d.ts.map +1 -0
- package/dist/misc/index.js +2 -0
- package/dist/misc/index.js.map +1 -0
- package/dist/utils.d.ts +7 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/{utils/index.js → utils.js} +1 -2
- package/dist/utils.js.map +1 -0
- package/package.json +25 -15
- package/src/hooks/index.ts +4 -0
- package/src/hooks/useEventListener.ts +201 -0
- package/src/hooks/useForceUpdate.ts +91 -0
- package/{lib → src}/hooks/useReducerWithDeps.ts +27 -18
- package/src/hooks/useStateWithDeps.ts +111 -0
- package/src/index.ts +2 -0
- package/src/misc/createSafeContext.ts +116 -0
- package/src/misc/index.ts +1 -0
- package/{lib/utils/index.ts → src/utils.ts} +9 -2
- package/dist/hooks/useIsomorphicLayoutEffect.d.ts +0 -4
- package/dist/hooks/useIsomorphicLayoutEffect.d.ts.map +0 -1
- package/dist/hooks/useIsomorphicLayoutEffect.js +0 -5
- package/dist/hooks/useIsomorphicLayoutEffect.js.map +0 -1
- package/dist/utils/index.d.ts +0 -5
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js.map +0 -1
- package/lib/hooks/useEventListener.ts +0 -101
- package/lib/hooks/useForceUpdate.ts +0 -40
- package/lib/hooks/useIsomorphicLayoutEffect.ts +0 -7
- package/lib/hooks/useStateWithDeps.ts +0 -79
- package/lib/index.ts +0 -5
|
@@ -5,29 +5,61 @@
|
|
|
5
5
|
* @copyright 2020 Peter Juras
|
|
6
6
|
*/
|
|
7
7
|
import { useCallback, useRef, } from 'react';
|
|
8
|
-
import { depsAreEqual, isFunction } from '../utils
|
|
9
|
-
import useForceUpdate from './useForceUpdate.js';
|
|
8
|
+
import { depsAreEqual, isFunction } from '../utils.js';
|
|
9
|
+
import { useForceUpdate } from './useForceUpdate.js';
|
|
10
10
|
/**
|
|
11
|
-
* `useState` hook with an additional dependency array that resets the
|
|
12
|
-
* to
|
|
13
|
-
* change
|
|
11
|
+
* `useState` hook with an additional dependency array `deps` that resets the
|
|
12
|
+
* state to `initialState` when dependencies change
|
|
14
13
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* For motivation and more examples, see
|
|
15
|
+
* https://github.com/facebook/react/issues/33041.
|
|
17
16
|
*
|
|
18
|
-
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```tsx
|
|
19
|
+
* type Activity = 'breakfast' | 'exercise' | 'swim' | 'board games' | 'dinner';
|
|
20
|
+
*
|
|
21
|
+
* const timeOfDayOptions = ['morning', 'afternoon', 'evening'] as const;
|
|
22
|
+
* type TimeOfDay = (typeof timeOfDayOptions)[number];
|
|
23
|
+
*
|
|
24
|
+
* const activityOptionsByTimeOfDay: {
|
|
25
|
+
* [K in TimeOfDay]: [Activity, ...Activity[]];
|
|
26
|
+
* } = {
|
|
27
|
+
* morning: ['breakfast', 'exercise', 'swim'],
|
|
28
|
+
* afternoon: ['exercise', 'swim', 'board games'],
|
|
29
|
+
* evening: ['board games', 'dinner'],
|
|
30
|
+
* };
|
|
31
|
+
*
|
|
32
|
+
* export function Example() {
|
|
33
|
+
* const [timeOfDay, setTimeOfDay] = useState<TimeOfDay>('morning');
|
|
34
|
+
*
|
|
35
|
+
* const activityOptions = activityOptionsByTimeOfDay[timeOfDay];
|
|
36
|
+
* const [activity, setActivity] = useStateWithDeps<Activity>(
|
|
37
|
+
* (prev) => {
|
|
38
|
+
* // Make sure activity is always valid for the current timeOfDay value,
|
|
39
|
+
* // but also don't reset it unless necessary:
|
|
40
|
+
* return prev && activityOptions.includes(prev) ? prev : activityOptions[0];
|
|
41
|
+
* },
|
|
42
|
+
* [activityOptions],
|
|
43
|
+
* );
|
|
44
|
+
*
|
|
45
|
+
* return '...';
|
|
46
|
+
* }
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* @param initialState The value to which the state is set when the component is
|
|
50
|
+
* mounted or dependencies change
|
|
51
|
+
*
|
|
52
|
+
* It can also be a function that returns a state value. If the state is reset
|
|
19
53
|
* due to a change of dependencies, this function will be passed the previous
|
|
20
54
|
* state as its argument (will be `undefined` in the first call upon mount).
|
|
21
55
|
*
|
|
22
56
|
* @param deps Dependencies that reset the state to `initialState`
|
|
23
57
|
*/
|
|
24
|
-
export
|
|
25
|
-
// It would be possible to use useState instead of
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
// re-renders, the state is stored in a ref and an
|
|
30
|
-
// update is triggered via forceUpdate below when necessary
|
|
58
|
+
export function useStateWithDeps(initialState, deps) {
|
|
59
|
+
// It would be possible to use useState instead of useRef to store the state,
|
|
60
|
+
// however this would trigger re-renders whenever the state is reset due to a
|
|
61
|
+
// change in dependencies. In order to avoid these re-renders, the state is
|
|
62
|
+
// stored in a ref, and updates are triggered with forceUpdate when necessary.
|
|
31
63
|
const state = useRef(undefined);
|
|
32
64
|
const prevDeps = useRef(deps);
|
|
33
65
|
const isMounted = useRef(false);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useStateWithDeps.js","sourceRoot":"","sources":["../../
|
|
1
|
+
{"version":3,"file":"useStateWithDeps.js","sourceRoot":"","sources":["../../src/hooks/useStateWithDeps.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,WAAW,EACX,MAAM,GAIP,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,MAAM,UAAU,gBAAgB,CAC9B,YAA4C,EAC5C,IAAoB;IAEpB,6EAA6E;IAC7E,6EAA6E;IAC7E,2EAA2E;IAC3E,8EAA8E;IAC9E,MAAM,KAAK,GAAG,MAAM,CAAC,SAAc,CAAC,CAAC;IAErC,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAC9B,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAEhC,mEAAmE;IACnE,IAAI,CAAC,SAAS,CAAC,OAAO,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC;QAChE,wBAAwB;QACxB,IAAI,SAAY,CAAC;QACjB,IAAI,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;YAC7B,SAAS,GAAG,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC1C,CAAC;aAAM,CAAC;YACN,SAAS,GAAG,YAAY,CAAC;QAC3B,CAAC;QACD,KAAK,CAAC,OAAO,GAAG,SAAS,CAAC;QAC1B,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAC;QACxB,SAAS,CAAC,OAAO,GAAG,IAAI,CAAC;IAC3B,CAAC;IAED,MAAM,CAAC,WAAW,CAAC,GAAG,cAAc,EAAE,CAAC;IAEvC,MAAM,WAAW,GAAG,WAAW,CAAC,SAAS,WAAW,CAClD,QAAuC;QAEvC,IAAI,SAAY,CAAC;QACjB,IAAI,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YACzB,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACtC,CAAC;aAAM,CAAC;YACN,SAAS,GAAG,QAAQ,CAAC;QACvB,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,SAAS,CAAC,EAAE,CAAC;YACzC,KAAK,CAAC,OAAO,GAAG,SAAS,CAAC;YAC1B,WAAW,EAAE,CAAC;QAChB,CAAC;IACH,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,kDAAkD;IAE1D,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;AACtC,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
3
|
-
export { default as useIsomorphicLayoutEffect } from './hooks/useIsomorphicLayoutEffect.js';
|
|
4
|
-
export { default as useReducerWithDeps } from './hooks/useReducerWithDeps.js';
|
|
5
|
-
export { default as useStateWithDeps } from './hooks/useStateWithDeps.js';
|
|
1
|
+
export * from './hooks/index.js';
|
|
2
|
+
export * from './misc/index.js';
|
|
6
3
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
3
|
-
export { default as useIsomorphicLayoutEffect } from './hooks/useIsomorphicLayoutEffect.js';
|
|
4
|
-
export { default as useReducerWithDeps } from './hooks/useReducerWithDeps.js';
|
|
5
|
-
export { default as useStateWithDeps } from './hooks/useStateWithDeps.js';
|
|
1
|
+
export * from './hooks/index.js';
|
|
2
|
+
export * from './misc/index.js';
|
|
6
3
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { type Context, type Provider } from 'react';
|
|
2
|
+
import type { ArgumentFallback } from '../utils.js';
|
|
3
|
+
/**
|
|
4
|
+
* A React context with a required `displayName` and the obsolete `Consumer`
|
|
5
|
+
* property purposefully omitted so that it is impossible to pass the context
|
|
6
|
+
* as an argument to `useContext` or `use` (the hook produced with
|
|
7
|
+
* {@linkcode createSafeContext} should be used instead)
|
|
8
|
+
*
|
|
9
|
+
* @see {@linkcode createSafeContext}
|
|
10
|
+
*/
|
|
11
|
+
export type RestrictedContext<T> = Context<T> extends Provider<T> ? {
|
|
12
|
+
Provider: Provider<T>;
|
|
13
|
+
displayName: string;
|
|
14
|
+
} & Provider<T> : {
|
|
15
|
+
Provider: Provider<T>;
|
|
16
|
+
displayName: string;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* The return type of {@linkcode createSafeContext}
|
|
20
|
+
*
|
|
21
|
+
* @see {@linkcode createSafeContext}
|
|
22
|
+
*/
|
|
23
|
+
export type SafeContext<DisplayName extends string, T> = {
|
|
24
|
+
[K in `${DisplayName}Context`]: RestrictedContext<T>;
|
|
25
|
+
} & {
|
|
26
|
+
[K in `use${DisplayName}`]: () => T;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* For a given type `T`, returns a function that produces both a context of that
|
|
30
|
+
* type and a hook that returns the current context value if one was provided,
|
|
31
|
+
* or throws an error otherwise
|
|
32
|
+
*
|
|
33
|
+
* The advantages over vanilla `createContext` are that no default value has to
|
|
34
|
+
* be provided, and that a meaningful context name is displayed in dev tools
|
|
35
|
+
* instead of generic `Context.Provider`.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```tsx
|
|
39
|
+
* enum Direction {
|
|
40
|
+
* Up,
|
|
41
|
+
* Down,
|
|
42
|
+
* Left,
|
|
43
|
+
* Right,
|
|
44
|
+
* }
|
|
45
|
+
*
|
|
46
|
+
* // Before
|
|
47
|
+
* const DirectionContext = createContext<Direction | undefined>(undefined);
|
|
48
|
+
* DirectionContext.displayName = 'DirectionContext';
|
|
49
|
+
*
|
|
50
|
+
* const useDirection = () => {
|
|
51
|
+
* const direction = useContext(DirectionContext);
|
|
52
|
+
* if (direction === undefined) {
|
|
53
|
+
* // Called outside of a <DirectionContext.Provider> boundary!
|
|
54
|
+
* // Or maybe undefined was explicitly provided as the context value
|
|
55
|
+
* // (ideally that shouldn't be allowed, but it is because we had to include
|
|
56
|
+
* // undefined in the context type so as to provide a meaningful default)
|
|
57
|
+
* throw new Error('No DirectionContext value was provided');
|
|
58
|
+
* }
|
|
59
|
+
* // Thanks to the undefined check, the type is now narrowed down to Direction
|
|
60
|
+
* return direction;
|
|
61
|
+
* };
|
|
62
|
+
*
|
|
63
|
+
* // After
|
|
64
|
+
* const { DirectionContext, useDirection } =
|
|
65
|
+
* createSafeContext<Direction>()('Direction'); // That's it :)
|
|
66
|
+
*
|
|
67
|
+
* const Parent = () => (
|
|
68
|
+
* // Providing undefined as the value is not allowed 👍
|
|
69
|
+
* <Direction.Provider value={Direction.Up}>
|
|
70
|
+
* <Child />
|
|
71
|
+
* </Direction.Provider>
|
|
72
|
+
* );
|
|
73
|
+
*
|
|
74
|
+
* const Child = () => `Current direction: ${Direction[useDirection()]}`;
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* @returns
|
|
78
|
+
* A function that accepts a single string argument `displayName` (e.g.
|
|
79
|
+
* `"Direction"`) and returns an object with the following properties:
|
|
80
|
+
* - ``` `${displayName}Context` ``` (e.g. `DirectionContext`): the context
|
|
81
|
+
* - ``` `use${displayName}` ``` (e.g. `useDirection`): a hook that returns the
|
|
82
|
+
* current context value if one was provided, or throws an error otherwise
|
|
83
|
+
*/
|
|
84
|
+
export declare function createSafeContext<T = never>(): <DisplayName extends string>(displayName: [T] extends [never] ? never : ArgumentFallback<DisplayName, never, string>) => SafeContext<DisplayName, T>;
|
|
85
|
+
//# sourceMappingURL=createSafeContext.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createSafeContext.d.ts","sourceRoot":"","sources":["../../src/misc/createSafeContext.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,QAAQ,EAA6B,MAAM,OAAO,CAAC;AAC/E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAIpD;;;;;;;GAOG;AAIH,MAAM,MAAM,iBAAiB,CAAC,CAAC,IAC7B,OAAO,CAAC,CAAC,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,GAC1B;IAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,QAAQ,CAAC,CAAC,CAAC,GAC5D;IAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC;AAErD;;;;GAIG;AACH,MAAM,MAAM,WAAW,CAAC,WAAW,SAAS,MAAM,EAAE,CAAC,IAAI;KACtD,CAAC,IAAI,GAAG,WAAW,SAAS,GAAG,iBAAiB,CAAC,CAAC,CAAC;CACrD,GAAG;KACD,CAAC,IAAI,MAAM,WAAW,EAAE,GAAG,MAAM,CAAC;CACpC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,GAAG,KAAK,MACjC,WAAW,SAAS,MAAM,EAChC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GAC5B,KAAK,GACL,gBAAgB,CAAC,WAAW,EAAE,KAAK,EAAE,MAAM,CAAC,KAC/C,WAAW,CAAC,WAAW,EAAE,CAAC,CAAC,CAsB/B"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
const moValueSymbol = Symbol('noValue');
|
|
3
|
+
/**
|
|
4
|
+
* For a given type `T`, returns a function that produces both a context of that
|
|
5
|
+
* type and a hook that returns the current context value if one was provided,
|
|
6
|
+
* or throws an error otherwise
|
|
7
|
+
*
|
|
8
|
+
* The advantages over vanilla `createContext` are that no default value has to
|
|
9
|
+
* be provided, and that a meaningful context name is displayed in dev tools
|
|
10
|
+
* instead of generic `Context.Provider`.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* enum Direction {
|
|
15
|
+
* Up,
|
|
16
|
+
* Down,
|
|
17
|
+
* Left,
|
|
18
|
+
* Right,
|
|
19
|
+
* }
|
|
20
|
+
*
|
|
21
|
+
* // Before
|
|
22
|
+
* const DirectionContext = createContext<Direction | undefined>(undefined);
|
|
23
|
+
* DirectionContext.displayName = 'DirectionContext';
|
|
24
|
+
*
|
|
25
|
+
* const useDirection = () => {
|
|
26
|
+
* const direction = useContext(DirectionContext);
|
|
27
|
+
* if (direction === undefined) {
|
|
28
|
+
* // Called outside of a <DirectionContext.Provider> boundary!
|
|
29
|
+
* // Or maybe undefined was explicitly provided as the context value
|
|
30
|
+
* // (ideally that shouldn't be allowed, but it is because we had to include
|
|
31
|
+
* // undefined in the context type so as to provide a meaningful default)
|
|
32
|
+
* throw new Error('No DirectionContext value was provided');
|
|
33
|
+
* }
|
|
34
|
+
* // Thanks to the undefined check, the type is now narrowed down to Direction
|
|
35
|
+
* return direction;
|
|
36
|
+
* };
|
|
37
|
+
*
|
|
38
|
+
* // After
|
|
39
|
+
* const { DirectionContext, useDirection } =
|
|
40
|
+
* createSafeContext<Direction>()('Direction'); // That's it :)
|
|
41
|
+
*
|
|
42
|
+
* const Parent = () => (
|
|
43
|
+
* // Providing undefined as the value is not allowed 👍
|
|
44
|
+
* <Direction.Provider value={Direction.Up}>
|
|
45
|
+
* <Child />
|
|
46
|
+
* </Direction.Provider>
|
|
47
|
+
* );
|
|
48
|
+
*
|
|
49
|
+
* const Child = () => `Current direction: ${Direction[useDirection()]}`;
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* @returns
|
|
53
|
+
* A function that accepts a single string argument `displayName` (e.g.
|
|
54
|
+
* `"Direction"`) and returns an object with the following properties:
|
|
55
|
+
* - ``` `${displayName}Context` ``` (e.g. `DirectionContext`): the context
|
|
56
|
+
* - ``` `use${displayName}` ``` (e.g. `useDirection`): a hook that returns the
|
|
57
|
+
* current context value if one was provided, or throws an error otherwise
|
|
58
|
+
*/
|
|
59
|
+
export function createSafeContext() {
|
|
60
|
+
return (displayName) => {
|
|
61
|
+
const contextName = `${displayName}Context`;
|
|
62
|
+
const hookName = `use${displayName}`;
|
|
63
|
+
const Context = createContext(moValueSymbol);
|
|
64
|
+
Context.displayName = contextName;
|
|
65
|
+
return {
|
|
66
|
+
[contextName]: Context,
|
|
67
|
+
[hookName]: () => {
|
|
68
|
+
const value = useContext(Context);
|
|
69
|
+
if (value === moValueSymbol) {
|
|
70
|
+
throw new Error(`No ${contextName} value was provided`);
|
|
71
|
+
}
|
|
72
|
+
return value;
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=createSafeContext.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createSafeContext.js","sourceRoot":"","sources":["../../src/misc/createSafeContext.ts"],"names":[],"mappings":"AAAA,OAAO,EAA+B,aAAa,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAG/E,MAAM,aAAa,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;AA6BxC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO,CACL,WAEgD,EACnB,EAAE;QAC/B,MAAM,WAAW,GAAG,GAAG,WAA0B,SAAkB,CAAC;QACpE,MAAM,QAAQ,GAAG,MAAM,WAA0B,EAAW,CAAC;QAE7D,MAAM,OAAO,GAAG,aAAa,CAA2B,aAAa,CAAC,CAAC;QACvE,OAAO,CAAC,WAAW,GAAG,WAAW,CAAC;QAElC,OAAO;YACL,CAAC,WAAW,CAAC,EAAE,OAA+B;YAC9C,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE;gBACf,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;gBAClC,IAAI,KAAK,KAAK,aAAa,EAAE,CAAC;oBAC5B,MAAM,IAAI,KAAK,CAAC,MAAM,WAAW,qBAAqB,CAAC,CAAC;gBAC1D,CAAC;gBACD,OAAO,KAAK,CAAC;YACf,CAAC;SAKF,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/misc/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/misc/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC"}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { DependencyList } from 'react';
|
|
2
|
+
export type Callable = (...args: never) => unknown;
|
|
3
|
+
export type ArgumentFallback<T extends Base, Default extends Base, Base = unknown> = [T] extends [never] ? Default : [Base] extends [T] ? Default : T;
|
|
4
|
+
export declare function noop(): void;
|
|
5
|
+
export declare function isFunction(input: unknown): input is Callable;
|
|
6
|
+
export declare function depsAreEqual(prevDeps: DependencyList, deps: DependencyList): boolean;
|
|
7
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAE5C,MAAM,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,EAAE,KAAK,KAAK,OAAO,CAAC;AAEnD,MAAM,MAAM,gBAAgB,CAC1B,CAAC,SAAS,IAAI,EACd,OAAO,SAAS,IAAI,EACpB,IAAI,GAAG,OAAO,IACZ,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,OAAO,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,OAAO,GAAG,CAAC,CAAC;AAErE,wBAAgB,IAAI,SAAK;AAEzB,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CAE5D;AAED,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,cAAc,EACxB,IAAI,EAAE,cAAc,GACnB,OAAO,CAKT"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
export function noop() { }
|
|
2
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
3
2
|
export function isFunction(input) {
|
|
4
3
|
return typeof input === 'function';
|
|
5
4
|
}
|
|
@@ -7,4 +6,4 @@ export function depsAreEqual(prevDeps, deps) {
|
|
|
7
6
|
return (prevDeps.length === deps.length &&
|
|
8
7
|
deps.every((dep, index) => Object.is(dep, prevDeps[index])));
|
|
9
8
|
}
|
|
10
|
-
//# sourceMappingURL=
|
|
9
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAUA,MAAM,UAAU,IAAI,KAAI,CAAC;AAEzB,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,OAAO,OAAO,KAAK,KAAK,UAAU,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,QAAwB,EACxB,IAAoB;IAEpB,OAAO,CACL,QAAQ,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM;QAC/B,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAC5D,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aweebit/react-essentials",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": "github:aweebit/react-essentials",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -10,28 +10,38 @@
|
|
|
10
10
|
"prettier": "npm run prettier:base -- .",
|
|
11
11
|
"lint": "eslint --max-warnings=0",
|
|
12
12
|
"build": "rimraf dist && tsc -b -f",
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"!(*.{jsx,jsx,ts,tsx})": "npm run prettier -- --ignore-unknown",
|
|
17
|
-
"**.{jsx,jsx,ts,tsx}": "npm run lint --"
|
|
13
|
+
"doc": "rimraf README.md && typedoc",
|
|
14
|
+
"prepack": "npm run build && npm run doc",
|
|
15
|
+
"prepare": "husky"
|
|
18
16
|
},
|
|
19
17
|
"peerDependencies": {
|
|
20
|
-
"react": ">=18
|
|
18
|
+
"@types/react": ">=18",
|
|
19
|
+
"react": ">=18",
|
|
20
|
+
"typescript": ">=5.4"
|
|
21
21
|
},
|
|
22
|
-
"
|
|
23
|
-
"@types/react":
|
|
22
|
+
"peerDependenciesMeta": {
|
|
23
|
+
"@types/react": {
|
|
24
|
+
"optional": true
|
|
25
|
+
},
|
|
26
|
+
"typescript": {
|
|
27
|
+
"optional": true
|
|
28
|
+
}
|
|
24
29
|
},
|
|
25
30
|
"devDependencies": {
|
|
26
|
-
"@eslint/js": "^9.
|
|
27
|
-
"
|
|
31
|
+
"@eslint/js": "^9.36.0",
|
|
32
|
+
"@types/react": "^18.3.24",
|
|
33
|
+
"eslint": "^9.36.0",
|
|
34
|
+
"eslint-config-prettier": "^10.1.8",
|
|
28
35
|
"eslint-plugin-react-hooks": "^5.2.0",
|
|
29
36
|
"husky": "^9.1.7",
|
|
30
|
-
"lint-staged": "^16.1.
|
|
31
|
-
"prettier": "3.
|
|
37
|
+
"lint-staged": "^16.1.6",
|
|
38
|
+
"prettier": "3.6.2",
|
|
39
|
+
"react": "^18.3.1",
|
|
32
40
|
"rimraf": "^6.0.1",
|
|
33
|
-
"
|
|
34
|
-
"
|
|
41
|
+
"typedoc": "^0.28.13",
|
|
42
|
+
"typedoc-plugin-markdown": "^4.9.0",
|
|
43
|
+
"typescript": "~5.9.2",
|
|
44
|
+
"typescript-eslint": "^8.44.0"
|
|
35
45
|
},
|
|
36
46
|
"license": "MIT"
|
|
37
47
|
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Based on {@link https://github.com/juliencrn/usehooks-ts}
|
|
3
|
+
*
|
|
4
|
+
* @license MIT
|
|
5
|
+
* @copyright 2020 Julien CARON
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useEffect, useMemo, useRef } from 'react';
|
|
9
|
+
|
|
10
|
+
type UseEventListenerOverloadArgs<
|
|
11
|
+
EventMap,
|
|
12
|
+
K extends keyof EventMap,
|
|
13
|
+
T extends EventTarget,
|
|
14
|
+
> = [
|
|
15
|
+
target: T | null,
|
|
16
|
+
eventName: K,
|
|
17
|
+
handler: (this: NoInfer<T>, event: EventMap[K]) => void,
|
|
18
|
+
options?: AddEventListenerOptions | boolean,
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Adds `handler` as a listener for the event `eventName` of `target` with the
|
|
23
|
+
* provided `options` applied
|
|
24
|
+
*
|
|
25
|
+
* If `target` is not provided, `window` is used instead.
|
|
26
|
+
*
|
|
27
|
+
* If `target` is `null`, no event listener is added. This is useful when
|
|
28
|
+
* working with DOM element refs, or when the event listener needs to be removed
|
|
29
|
+
* temporarily.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```tsx
|
|
33
|
+
* useEventListener('resize', () => {
|
|
34
|
+
* console.log(window.innerWidth, window.innerHeight);
|
|
35
|
+
* });
|
|
36
|
+
*
|
|
37
|
+
* useEventListener(document, 'visibilitychange', () => {
|
|
38
|
+
* console.log(document.visibilityState);
|
|
39
|
+
* });
|
|
40
|
+
*
|
|
41
|
+
* const buttonRef = useRef<HTMLButtonElement>(null);
|
|
42
|
+
* useEventListener(buttonRef.current, 'click', () => console.log('click'));
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @ignore
|
|
46
|
+
*/
|
|
47
|
+
export function useEventListener<K extends keyof WindowEventMap>(
|
|
48
|
+
eventName: K,
|
|
49
|
+
handler: (this: Window, event: WindowEventMap[K]) => void,
|
|
50
|
+
options?: AddEventListenerOptions | boolean,
|
|
51
|
+
): void;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @see {@linkcode useEventListener}
|
|
55
|
+
* @ignore
|
|
56
|
+
*/
|
|
57
|
+
export function useEventListener<K extends keyof WindowEventMap>(
|
|
58
|
+
...args: UseEventListenerOverloadArgs<WindowEventMap, K, Window>
|
|
59
|
+
): void;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @see {@linkcode useEventListener}
|
|
63
|
+
* @ignore
|
|
64
|
+
*/
|
|
65
|
+
export function useEventListener<K extends keyof DocumentEventMap>(
|
|
66
|
+
...args: UseEventListenerOverloadArgs<DocumentEventMap, K, Document>
|
|
67
|
+
): void;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @see {@linkcode useEventListener}
|
|
71
|
+
* @ignore
|
|
72
|
+
*/
|
|
73
|
+
export function useEventListener<
|
|
74
|
+
K extends keyof HTMLElementEventMap,
|
|
75
|
+
T extends HTMLElement,
|
|
76
|
+
>(...args: UseEventListenerOverloadArgs<HTMLElementEventMap, K, T>): void;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @see {@linkcode useEventListener}
|
|
80
|
+
* @ignore
|
|
81
|
+
*/
|
|
82
|
+
export function useEventListener<
|
|
83
|
+
K extends keyof SVGElementEventMap,
|
|
84
|
+
T extends SVGElement,
|
|
85
|
+
>(...args: UseEventListenerOverloadArgs<SVGElementEventMap, K, T>): void;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @see {@linkcode useEventListener}
|
|
89
|
+
* @ignore
|
|
90
|
+
*/
|
|
91
|
+
export function useEventListener<
|
|
92
|
+
K extends keyof MathMLElementEventMap,
|
|
93
|
+
T extends MathMLElement,
|
|
94
|
+
>(...args: UseEventListenerOverloadArgs<MathMLElementEventMap, K, T>): void;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @see {@linkcode useEventListener}
|
|
98
|
+
*/
|
|
99
|
+
export function useEventListener<K extends keyof WindowEventMap>(
|
|
100
|
+
eventName: K,
|
|
101
|
+
handler: (this: Window, event: WindowEventMap[K]) => void,
|
|
102
|
+
options?: AddEventListenerOptions | boolean,
|
|
103
|
+
): void;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @see {@linkcode useEventListener}
|
|
107
|
+
*/
|
|
108
|
+
export function useEventListener<T extends EventTarget>(
|
|
109
|
+
target: T | null,
|
|
110
|
+
eventName: string,
|
|
111
|
+
handler: (this: NoInfer<T>, event: Event) => void,
|
|
112
|
+
options?: AddEventListenerOptions | boolean,
|
|
113
|
+
): void;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Adds `handler` as a listener for the event `eventName` of `target` with the
|
|
117
|
+
* provided `options` applied
|
|
118
|
+
*
|
|
119
|
+
* If `target` is not provided, `window` is used instead.
|
|
120
|
+
*
|
|
121
|
+
* If `target` is `null`, no event listener is added. This is useful when
|
|
122
|
+
* working with DOM element refs, or when the event listener needs to be removed
|
|
123
|
+
* temporarily.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```tsx
|
|
127
|
+
* useEventListener('resize', () => {
|
|
128
|
+
* console.log(window.innerWidth, window.innerHeight);
|
|
129
|
+
* });
|
|
130
|
+
*
|
|
131
|
+
* useEventListener(document, 'visibilitychange', () => {
|
|
132
|
+
* console.log(document.visibilityState);
|
|
133
|
+
* });
|
|
134
|
+
*
|
|
135
|
+
* const buttonRef = useRef<HTMLButtonElement>(null);
|
|
136
|
+
* useEventListener(buttonRef.current, 'click', () => console.log('click'));
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
export function useEventListener(
|
|
140
|
+
...args: UseEventListenerArgsWithoutTarget | UseEventListenerArgsWithTarget
|
|
141
|
+
) {
|
|
142
|
+
let eventName: string;
|
|
143
|
+
let handler: (this: EventTarget, event: Event) => void;
|
|
144
|
+
let target: EventTarget | null | undefined;
|
|
145
|
+
let options: AddEventListenerOptions | boolean | undefined;
|
|
146
|
+
|
|
147
|
+
if (typeof args[0] === 'string') {
|
|
148
|
+
[eventName, handler, options] = args as UseEventListenerArgsWithoutTarget;
|
|
149
|
+
} else {
|
|
150
|
+
[target, eventName, handler, options] =
|
|
151
|
+
args as UseEventListenerArgsWithTarget;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const handlerRef = useRef(handler);
|
|
155
|
+
handlerRef.current = handler;
|
|
156
|
+
|
|
157
|
+
const {
|
|
158
|
+
capture = false,
|
|
159
|
+
once = false,
|
|
160
|
+
passive,
|
|
161
|
+
signal,
|
|
162
|
+
} = typeof options === 'boolean' ? { capture: options } : (options ?? {});
|
|
163
|
+
|
|
164
|
+
const memoizedOptions = useMemo(
|
|
165
|
+
() => options,
|
|
166
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
167
|
+
[capture, once, passive, signal],
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
useEffect(() => {
|
|
171
|
+
if (target === null) {
|
|
172
|
+
// No element has been attached to the ref yet
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const definedTarget = target ?? window;
|
|
177
|
+
|
|
178
|
+
const listener: typeof handler = function (event) {
|
|
179
|
+
handlerRef.current.call(this, event);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
definedTarget.addEventListener(eventName, listener, memoizedOptions);
|
|
183
|
+
|
|
184
|
+
return () => {
|
|
185
|
+
definedTarget.removeEventListener(eventName, listener, memoizedOptions);
|
|
186
|
+
};
|
|
187
|
+
}, [eventName, target, memoizedOptions]);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
type UseEventListenerArgsWithoutTarget = [
|
|
191
|
+
eventName: string,
|
|
192
|
+
handler: (event: Event) => void,
|
|
193
|
+
options?: AddEventListenerOptions | boolean | undefined,
|
|
194
|
+
];
|
|
195
|
+
|
|
196
|
+
type UseEventListenerArgsWithTarget = [
|
|
197
|
+
target: EventTarget | null,
|
|
198
|
+
eventName: string,
|
|
199
|
+
handler: (event: Event) => void,
|
|
200
|
+
options?: AddEventListenerOptions | boolean | undefined,
|
|
201
|
+
];
|