@aweebit/react-essentials 0.5.4 → 0.6.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 +290 -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 +69 -10
- package/dist/hooks/useEventListener.d.ts.map +1 -1
- package/dist/hooks/useEventListener.js +12 -13
- package/dist/hooks/useEventListener.js.map +1 -1
- package/dist/hooks/useForceUpdate.d.ts +3 -4
- package/dist/hooks/useForceUpdate.d.ts.map +1 -1
- package/dist/hooks/useForceUpdate.js +4 -4
- package/dist/hooks/useForceUpdate.js.map +1 -1
- package/dist/hooks/useReducerWithDeps.d.ts +13 -10
- package/dist/hooks/useReducerWithDeps.d.ts.map +1 -1
- package/dist/hooks/useReducerWithDeps.js +10 -11
- package/dist/hooks/useReducerWithDeps.js.map +1 -1
- package/dist/hooks/useStateWithDeps.d.ts +6 -7
- package/dist/hooks/useStateWithDeps.d.ts.map +1 -1
- package/dist/hooks/useStateWithDeps.js +8 -9
- 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 +52 -0
- package/dist/misc/createSafeContext.d.ts.map +1 -0
- package/dist/misc/createSafeContext.js +46 -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 +170 -0
- package/{lib → src}/hooks/useForceUpdate.ts +8 -6
- package/{lib → src}/hooks/useReducerWithDeps.ts +23 -17
- package/{lib → src}/hooks/useStateWithDeps.ts +8 -9
- package/src/index.ts +2 -0
- package/src/misc/createSafeContext.ts +83 -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/useIsomorphicLayoutEffect.ts +0 -7
- package/lib/index.ts +0 -5
|
@@ -6,18 +6,17 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { type DependencyList, type Dispatch, type SetStateAction } from 'react';
|
|
8
8
|
/**
|
|
9
|
-
* `useState` hook with an additional dependency array that resets the
|
|
10
|
-
* to
|
|
11
|
-
* change
|
|
9
|
+
* `useState` hook with an additional dependency array `deps` that resets the
|
|
10
|
+
* state to `initialState` when dependencies change
|
|
12
11
|
*
|
|
13
|
-
* @param initialState The
|
|
14
|
-
*
|
|
12
|
+
* @param initialState The value to which the state is set when the component is
|
|
13
|
+
* mounted or dependencies change
|
|
15
14
|
*
|
|
16
|
-
* It can also be a function
|
|
15
|
+
* It can also be a function that returns a state value. If the state is reset
|
|
17
16
|
* due to a change of dependencies, this function will be passed the previous
|
|
18
17
|
* state as its argument (will be `undefined` in the first call upon mount).
|
|
19
18
|
*
|
|
20
19
|
* @param deps Dependencies that reset the state to `initialState`
|
|
21
20
|
*/
|
|
22
|
-
export
|
|
21
|
+
export declare function useStateWithDeps<S>(initialState: S | ((previousState?: S) => S), deps: DependencyList): [S, Dispatch<SetStateAction<S>>];
|
|
23
22
|
//# sourceMappingURL=useStateWithDeps.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useStateWithDeps.d.ts","sourceRoot":"","sources":["../../
|
|
1
|
+
{"version":3,"file":"useStateWithDeps.d.ts","sourceRoot":"","sources":["../../src/hooks/useStateWithDeps.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,cAAc,EACpB,MAAM,OAAO,CAAC;AAIf;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAChC,YAAY,EAAE,CAAC,GAAG,CAAC,CAAC,aAAa,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAC5C,IAAI,EAAE,cAAc,GACnB,CAAC,CAAC,EAAE,QAAQ,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CA4ClC"}
|
|
@@ -5,23 +5,22 @@
|
|
|
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
|
-
* @param initialState The
|
|
16
|
-
*
|
|
14
|
+
* @param initialState The value to which the state is set when the component is
|
|
15
|
+
* mounted or dependencies change
|
|
17
16
|
*
|
|
18
|
-
* It can also be a function
|
|
17
|
+
* It can also be a function that returns a state value. If the state is reset
|
|
19
18
|
* due to a change of dependencies, this function will be passed the previous
|
|
20
19
|
* state as its argument (will be `undefined` in the first call upon mount).
|
|
21
20
|
*
|
|
22
21
|
* @param deps Dependencies that reset the state to `initialState`
|
|
23
22
|
*/
|
|
24
|
-
export
|
|
23
|
+
export function useStateWithDeps(initialState, deps) {
|
|
25
24
|
// It would be possible to use useState instead of
|
|
26
25
|
// useRef to store the state, however this would
|
|
27
26
|
// trigger re-renders whenever the state is reset due
|
|
@@ -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;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,gBAAgB,CAC9B,YAA4C,EAC5C,IAAoB;IAEpB,kDAAkD;IAClD,gDAAgD;IAChD,qDAAqD;IACrD,uDAAuD;IACvD,kDAAkD;IAClD,2DAA2D;IAC3D,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,52 @@
|
|
|
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
|
+
* @see {@linkcode createSafeContext}
|
|
20
|
+
*/
|
|
21
|
+
export type SafeContext<DisplayName extends string, T> = {
|
|
22
|
+
[K in `${DisplayName}Context`]: RestrictedContext<T>;
|
|
23
|
+
} & {
|
|
24
|
+
[K in `use${DisplayName}`]: () => T;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* For a given type `T`, returns a function that produces both a context of that
|
|
28
|
+
* type and a hook that returns the current context value if one was provided,
|
|
29
|
+
* or throws an error otherwise
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```tsx
|
|
33
|
+
* const { ItemsContext, useItems } = createSafeContext<string[]>()('Items');
|
|
34
|
+
*
|
|
35
|
+
* const Parent = () => (
|
|
36
|
+
* <ItemsContext value={['compass', 'newspaper', 'banana']}>
|
|
37
|
+
* <Child />
|
|
38
|
+
* </ItemsContext>
|
|
39
|
+
* );
|
|
40
|
+
*
|
|
41
|
+
* const Child = () => useItems().join(', ');
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @returns
|
|
45
|
+
* A function that accepts a single string argument `displayName` (e.g.
|
|
46
|
+
* `"Items"`) and returns an object with the following properties:
|
|
47
|
+
* - ``` `${displayName}Context` ``` (e.g. `ItemsContext`): the context
|
|
48
|
+
* - ``` `use${displayName}` ``` (e.g. `useItems`): a hook that returns the
|
|
49
|
+
* current context value if one was provided, or throws an error otherwise
|
|
50
|
+
*/
|
|
51
|
+
export declare function createSafeContext<T = never>(): <DisplayName extends string>(displayName: [T] extends [never] ? never : ArgumentFallback<DisplayName, never, string>) => SafeContext<DisplayName, T>;
|
|
52
|
+
//# 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;;GAEG;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;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;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,46 @@
|
|
|
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
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* const { ItemsContext, useItems } = createSafeContext<string[]>()('Items');
|
|
11
|
+
*
|
|
12
|
+
* const Parent = () => (
|
|
13
|
+
* <ItemsContext value={['compass', 'newspaper', 'banana']}>
|
|
14
|
+
* <Child />
|
|
15
|
+
* </ItemsContext>
|
|
16
|
+
* );
|
|
17
|
+
*
|
|
18
|
+
* const Child = () => useItems().join(', ');
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @returns
|
|
22
|
+
* A function that accepts a single string argument `displayName` (e.g.
|
|
23
|
+
* `"Items"`) and returns an object with the following properties:
|
|
24
|
+
* - ``` `${displayName}Context` ``` (e.g. `ItemsContext`): the context
|
|
25
|
+
* - ``` `use${displayName}` ``` (e.g. `useItems`): a hook that returns the
|
|
26
|
+
* current context value if one was provided, or throws an error otherwise
|
|
27
|
+
*/
|
|
28
|
+
export function createSafeContext() {
|
|
29
|
+
return (displayName) => {
|
|
30
|
+
const contextName = `${displayName}Context`;
|
|
31
|
+
const hookName = `use${displayName}`;
|
|
32
|
+
const Context = createContext(moValueSymbol);
|
|
33
|
+
Context.displayName = contextName;
|
|
34
|
+
return {
|
|
35
|
+
[contextName]: Context,
|
|
36
|
+
[hookName]: () => {
|
|
37
|
+
const value = useContext(Context);
|
|
38
|
+
if (value === moValueSymbol) {
|
|
39
|
+
throw new Error(`No ${contextName} value was provided`);
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
//# 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;AA2BxC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;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.6.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,170 @@
|
|
|
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
|
+
/**
|
|
11
|
+
* Adds `handler` as a listener for the event `eventName` of `element` with the
|
|
12
|
+
* provided `options` applied
|
|
13
|
+
*
|
|
14
|
+
* If `element` is `undefined`, `window` is used instead.
|
|
15
|
+
*
|
|
16
|
+
* If `element` is `null`, no event listener is added.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```tsx
|
|
20
|
+
* useEventListener('resize', () => {
|
|
21
|
+
* console.log(window.innerWidth, window.innerHeight);
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* useEventListener(
|
|
25
|
+
* 'visibilitychange',
|
|
26
|
+
* () => console.log(document.visibilityState),
|
|
27
|
+
* document
|
|
28
|
+
* );
|
|
29
|
+
*
|
|
30
|
+
* const buttonRef = useRef<HTMLButtonElement>(null);
|
|
31
|
+
* useEventListener("click", () => console.log("click"), buttonRef.current);
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @ignore
|
|
35
|
+
*/
|
|
36
|
+
export function useEventListener<
|
|
37
|
+
K extends keyof HTMLElementEventMap,
|
|
38
|
+
T extends HTMLElement,
|
|
39
|
+
>(
|
|
40
|
+
eventName: K,
|
|
41
|
+
handler: (this: NoInfer<T>, event: HTMLElementEventMap[K]) => void,
|
|
42
|
+
element: T | null,
|
|
43
|
+
options?: boolean | AddEventListenerOptions,
|
|
44
|
+
): void;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @see {@linkcode useEventListener}
|
|
48
|
+
* @ignore
|
|
49
|
+
*/
|
|
50
|
+
export function useEventListener<
|
|
51
|
+
K extends keyof SVGElementEventMap,
|
|
52
|
+
T extends SVGElement,
|
|
53
|
+
>(
|
|
54
|
+
eventName: K,
|
|
55
|
+
handler: (this: NoInfer<T>, event: SVGElementEventMap[K]) => void,
|
|
56
|
+
element: T | null,
|
|
57
|
+
options?: boolean | AddEventListenerOptions,
|
|
58
|
+
): void;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @see {@linkcode useEventListener}
|
|
62
|
+
* @ignore
|
|
63
|
+
*/
|
|
64
|
+
export function useEventListener<
|
|
65
|
+
K extends keyof MathMLElementEventMap,
|
|
66
|
+
T extends MathMLElement,
|
|
67
|
+
>(
|
|
68
|
+
eventName: K,
|
|
69
|
+
handler: (this: NoInfer<T>, event: MathMLElementEventMap[K]) => void,
|
|
70
|
+
element: T | null,
|
|
71
|
+
options?: boolean | AddEventListenerOptions,
|
|
72
|
+
): void;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @see {@linkcode useEventListener}
|
|
76
|
+
* @ignore
|
|
77
|
+
*/
|
|
78
|
+
export function useEventListener<K extends keyof DocumentEventMap>(
|
|
79
|
+
eventName: K,
|
|
80
|
+
handler: (this: Document, event: DocumentEventMap[K]) => void,
|
|
81
|
+
element: Document,
|
|
82
|
+
options?: boolean | AddEventListenerOptions,
|
|
83
|
+
): void;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @see {@linkcode useEventListener}
|
|
87
|
+
* @ignore
|
|
88
|
+
*/
|
|
89
|
+
export function useEventListener<K extends keyof WindowEventMap>(
|
|
90
|
+
eventName: K,
|
|
91
|
+
handler: (this: Window, event: WindowEventMap[K]) => void,
|
|
92
|
+
element?: Window,
|
|
93
|
+
options?: boolean | AddEventListenerOptions,
|
|
94
|
+
): void;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Adds `handler` as a listener for the event `eventName` of `element` with the
|
|
98
|
+
* provided `options` applied
|
|
99
|
+
*
|
|
100
|
+
* If `element` is `undefined`, `window` is used instead.
|
|
101
|
+
*
|
|
102
|
+
* If `element` is `null`, no event listener is added.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```tsx
|
|
106
|
+
* useEventListener('resize', () => {
|
|
107
|
+
* console.log(window.innerWidth, window.innerHeight);
|
|
108
|
+
* });
|
|
109
|
+
*
|
|
110
|
+
* useEventListener(
|
|
111
|
+
* 'visibilitychange',
|
|
112
|
+
* () => console.log(document.visibilityState),
|
|
113
|
+
* document
|
|
114
|
+
* );
|
|
115
|
+
*
|
|
116
|
+
* const buttonRef = useRef<HTMLButtonElement>(null);
|
|
117
|
+
* useEventListener("click", () => console.log("click"), buttonRef.current);
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
export function useEventListener<T extends EventTarget>(
|
|
121
|
+
eventName: string,
|
|
122
|
+
handler: (this: NoInfer<T>, event: Event) => void,
|
|
123
|
+
element?: T | null,
|
|
124
|
+
options?: boolean | AddEventListenerOptions,
|
|
125
|
+
): void;
|
|
126
|
+
|
|
127
|
+
export function useEventListener(
|
|
128
|
+
eventName: string,
|
|
129
|
+
handler: (this: EventTarget, event: Event) => void,
|
|
130
|
+
element?: EventTarget | null,
|
|
131
|
+
options?: boolean | AddEventListenerOptions,
|
|
132
|
+
) {
|
|
133
|
+
const handlerRef = useRef(handler);
|
|
134
|
+
handlerRef.current = handler;
|
|
135
|
+
|
|
136
|
+
const {
|
|
137
|
+
capture = false,
|
|
138
|
+
once = false,
|
|
139
|
+
passive,
|
|
140
|
+
signal,
|
|
141
|
+
} = typeof options === 'boolean' ? { capture: options } : (options ?? {});
|
|
142
|
+
|
|
143
|
+
const memoizedOptions = useMemo(
|
|
144
|
+
() => options,
|
|
145
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
146
|
+
[capture, once, passive, signal],
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
useEffect(() => {
|
|
150
|
+
if (element === null) {
|
|
151
|
+
// No element has been attached to the ref yet
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Define the listening target
|
|
156
|
+
const targetElement = element ?? window;
|
|
157
|
+
|
|
158
|
+
// Create event listener that calls handler function stored in ref
|
|
159
|
+
const listener: typeof handler = function (event) {
|
|
160
|
+
handlerRef.current.call(this, event);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
targetElement.addEventListener(eventName, listener, memoizedOptions);
|
|
164
|
+
|
|
165
|
+
// Remove event listener on cleanup
|
|
166
|
+
return () => {
|
|
167
|
+
targetElement.removeEventListener(eventName, listener, memoizedOptions);
|
|
168
|
+
};
|
|
169
|
+
}, [eventName, element, memoizedOptions]);
|
|
170
|
+
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { useReducer, useRef } from 'react';
|
|
2
2
|
|
|
3
|
+
/* eslint-disable */
|
|
4
|
+
import type { useStateWithDeps } from './useStateWithDeps.js';
|
|
5
|
+
import type { useReducerWithDeps } from './useReducerWithDeps.js';
|
|
6
|
+
/* eslint-enable */
|
|
7
|
+
|
|
3
8
|
/**
|
|
4
9
|
* Enables you to imperatively trigger re-rendering of components
|
|
5
10
|
*
|
|
@@ -12,9 +17,8 @@ import { useReducer, useRef } from 'react';
|
|
|
12
17
|
* Can be used for conditionally calling state setters when state needs to be
|
|
13
18
|
* reset. That is legal and better than using effects (see
|
|
14
19
|
* {@link https://react.dev/learn/-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes You Might Not Need an Effect > Adjusting some state when a prop changes}),
|
|
15
|
-
* but can often be avoided by using
|
|
16
|
-
*
|
|
17
|
-
* [`useReducerWithDeps`]({@link ./useReducerWithDeps.ts}).
|
|
20
|
+
* but can often be avoided by using {@linkcode useStateWithDeps} or
|
|
21
|
+
* {@linkcode useReducerWithDeps}.
|
|
18
22
|
*
|
|
19
23
|
* Important: the callback function is called once per render, not once per
|
|
20
24
|
* `forceUpdate` call! If React batches `forceUpdate` calls, then it will only
|
|
@@ -25,9 +29,7 @@ import { useReducer, useRef } from 'react';
|
|
|
25
29
|
* 1. A `forceUpdate` function that triggers a re-render
|
|
26
30
|
* 2. The number of times `forceUpdate` has been called so far
|
|
27
31
|
*/
|
|
28
|
-
export
|
|
29
|
-
callback?: () => void,
|
|
30
|
-
): [() => void, bigint] {
|
|
32
|
+
export function useForceUpdate(callback?: () => void): [() => void, bigint] {
|
|
31
33
|
// It is very unlikely that the number of updates will exceed
|
|
32
34
|
// Number.MAX_SAFE_INTEGER, but not impossible. That is why we use bigints.
|
|
33
35
|
const [counter, forceUpdate] = useReducer((prev) => prev + 1n, 0n);
|
|
@@ -1,16 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import { type DependencyList, useCallback, useRef } from 'react';
|
|
2
|
+
import { useStateWithDeps } from './useStateWithDeps.js';
|
|
3
|
+
|
|
4
|
+
// We cannot simply import the following types from @types/react since they are
|
|
5
|
+
// only available starting from React 19, but we also want to support React 18
|
|
6
|
+
// whose type declarations for useReducer are very different.
|
|
7
|
+
|
|
8
|
+
/** @ignore */
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
10
|
+
export type AnyActionArg = [] | [any];
|
|
11
|
+
|
|
12
|
+
/** @ignore */
|
|
13
|
+
export type ActionDispatch<ActionArg extends AnyActionArg> = (
|
|
14
|
+
...args: ActionArg
|
|
15
|
+
) => void;
|
|
9
16
|
|
|
10
17
|
/**
|
|
11
|
-
* `useReducer` hook with an additional dependency array that resets the
|
|
12
|
-
* to
|
|
13
|
-
* change
|
|
18
|
+
* `useReducer` hook with an additional dependency array `deps` that resets the
|
|
19
|
+
* state to `initialState` when dependencies change
|
|
14
20
|
*
|
|
15
21
|
* ### On linter support
|
|
16
22
|
*
|
|
@@ -19,7 +25,7 @@ import useStateWithDeps from './useStateWithDeps.js';
|
|
|
19
25
|
* However, as we would like to keep the hook as compatible with `useReducer` as
|
|
20
26
|
* possible, we don't want to artificially change the parameter's position.
|
|
21
27
|
* Therefore, there will be no warnings about missing dependencies.
|
|
22
|
-
* Because of that,
|
|
28
|
+
* Because of that, additional caution is advised!
|
|
23
29
|
* Be sure to check no dependencies are missing from the `deps` array.
|
|
24
30
|
*
|
|
25
31
|
* Related issue: {@link https://github.com/facebook/react/issues/25443}.
|
|
@@ -27,20 +33,20 @@ import useStateWithDeps from './useStateWithDeps.js';
|
|
|
27
33
|
* Unlike `eslint-plugin-react-hooks` maintained by React's team, the unofficial
|
|
28
34
|
* `useExhaustiveDependencies` rule provided for Biome by Biome's team
|
|
29
35
|
* does actually have support for dependency arrays at other positions, see
|
|
30
|
-
* {@link https://biomejs.dev/linter/rules/use-exhaustive-dependencies/#validating-dependencies}.
|
|
36
|
+
* {@link https://biomejs.dev/linter/rules/use-exhaustive-dependencies/#validating-dependencies useExhaustiveDependencies > Options > Validating dependencies}.
|
|
31
37
|
*
|
|
32
38
|
* @param reducer The reducer function that specifies how the state gets updated
|
|
33
39
|
*
|
|
34
|
-
* @param initialState The
|
|
35
|
-
*
|
|
40
|
+
* @param initialState The value to which the state is set when the component is
|
|
41
|
+
* mounted or dependencies change
|
|
36
42
|
*
|
|
37
|
-
* It can also be a function
|
|
43
|
+
* It can also be a function that returns a state value. If the state is reset
|
|
38
44
|
* due to a change of dependencies, this function will be passed the previous
|
|
39
45
|
* state as its argument (will be `undefined` in the first call upon mount).
|
|
40
46
|
*
|
|
41
47
|
* @param deps Dependencies that reset the state to `initialState`
|
|
42
48
|
*/
|
|
43
|
-
export
|
|
49
|
+
export function useReducerWithDeps<S, A extends AnyActionArg>(
|
|
44
50
|
reducer: (prevState: S, ...args: A) => S,
|
|
45
51
|
initialState: S | ((previousState?: S) => S),
|
|
46
52
|
deps: DependencyList,
|
|
@@ -12,24 +12,23 @@ import {
|
|
|
12
12
|
type Dispatch,
|
|
13
13
|
type SetStateAction,
|
|
14
14
|
} from 'react';
|
|
15
|
-
import { depsAreEqual, isFunction } from '../utils
|
|
16
|
-
import useForceUpdate from './useForceUpdate.js';
|
|
15
|
+
import { depsAreEqual, isFunction } from '../utils.js';
|
|
16
|
+
import { useForceUpdate } from './useForceUpdate.js';
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
* `useState` hook with an additional dependency array that resets the
|
|
20
|
-
* to
|
|
21
|
-
* change
|
|
19
|
+
* `useState` hook with an additional dependency array `deps` that resets the
|
|
20
|
+
* state to `initialState` when dependencies change
|
|
22
21
|
*
|
|
23
|
-
* @param initialState The
|
|
24
|
-
*
|
|
22
|
+
* @param initialState The value to which the state is set when the component is
|
|
23
|
+
* mounted or dependencies change
|
|
25
24
|
*
|
|
26
|
-
* It can also be a function
|
|
25
|
+
* It can also be a function that returns a state value. If the state is reset
|
|
27
26
|
* due to a change of dependencies, this function will be passed the previous
|
|
28
27
|
* state as its argument (will be `undefined` in the first call upon mount).
|
|
29
28
|
*
|
|
30
29
|
* @param deps Dependencies that reset the state to `initialState`
|
|
31
30
|
*/
|
|
32
|
-
export
|
|
31
|
+
export function useStateWithDeps<S>(
|
|
33
32
|
initialState: S | ((previousState?: S) => S),
|
|
34
33
|
deps: DependencyList,
|
|
35
34
|
): [S, Dispatch<SetStateAction<S>>] {
|
package/src/index.ts
ADDED