@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.
Files changed (57) hide show
  1. package/README.md +290 -0
  2. package/dist/hooks/index.d.ts +5 -0
  3. package/dist/hooks/index.d.ts.map +1 -0
  4. package/dist/hooks/index.js +5 -0
  5. package/dist/hooks/index.js.map +1 -0
  6. package/dist/hooks/useEventListener.d.ts +69 -10
  7. package/dist/hooks/useEventListener.d.ts.map +1 -1
  8. package/dist/hooks/useEventListener.js +12 -13
  9. package/dist/hooks/useEventListener.js.map +1 -1
  10. package/dist/hooks/useForceUpdate.d.ts +3 -4
  11. package/dist/hooks/useForceUpdate.d.ts.map +1 -1
  12. package/dist/hooks/useForceUpdate.js +4 -4
  13. package/dist/hooks/useForceUpdate.js.map +1 -1
  14. package/dist/hooks/useReducerWithDeps.d.ts +13 -10
  15. package/dist/hooks/useReducerWithDeps.d.ts.map +1 -1
  16. package/dist/hooks/useReducerWithDeps.js +10 -11
  17. package/dist/hooks/useReducerWithDeps.js.map +1 -1
  18. package/dist/hooks/useStateWithDeps.d.ts +6 -7
  19. package/dist/hooks/useStateWithDeps.d.ts.map +1 -1
  20. package/dist/hooks/useStateWithDeps.js +8 -9
  21. package/dist/hooks/useStateWithDeps.js.map +1 -1
  22. package/dist/index.d.ts +2 -5
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +2 -5
  25. package/dist/index.js.map +1 -1
  26. package/dist/misc/createSafeContext.d.ts +52 -0
  27. package/dist/misc/createSafeContext.d.ts.map +1 -0
  28. package/dist/misc/createSafeContext.js +46 -0
  29. package/dist/misc/createSafeContext.js.map +1 -0
  30. package/dist/misc/index.d.ts +2 -0
  31. package/dist/misc/index.d.ts.map +1 -0
  32. package/dist/misc/index.js +2 -0
  33. package/dist/misc/index.js.map +1 -0
  34. package/dist/utils.d.ts +7 -0
  35. package/dist/utils.d.ts.map +1 -0
  36. package/dist/{utils/index.js → utils.js} +1 -2
  37. package/dist/utils.js.map +1 -0
  38. package/package.json +25 -15
  39. package/src/hooks/index.ts +4 -0
  40. package/src/hooks/useEventListener.ts +170 -0
  41. package/{lib → src}/hooks/useForceUpdate.ts +8 -6
  42. package/{lib → src}/hooks/useReducerWithDeps.ts +23 -17
  43. package/{lib → src}/hooks/useStateWithDeps.ts +8 -9
  44. package/src/index.ts +2 -0
  45. package/src/misc/createSafeContext.ts +83 -0
  46. package/src/misc/index.ts +1 -0
  47. package/{lib/utils/index.ts → src/utils.ts} +9 -2
  48. package/dist/hooks/useIsomorphicLayoutEffect.d.ts +0 -4
  49. package/dist/hooks/useIsomorphicLayoutEffect.d.ts.map +0 -1
  50. package/dist/hooks/useIsomorphicLayoutEffect.js +0 -5
  51. package/dist/hooks/useIsomorphicLayoutEffect.js.map +0 -1
  52. package/dist/utils/index.d.ts +0 -5
  53. package/dist/utils/index.d.ts.map +0 -1
  54. package/dist/utils/index.js.map +0 -1
  55. package/lib/hooks/useEventListener.ts +0 -101
  56. package/lib/hooks/useIsomorphicLayoutEffect.ts +0 -7
  57. package/lib/index.ts +0 -5
@@ -0,0 +1,83 @@
1
+ import { type Context, type Provider, createContext, useContext } from 'react';
2
+ import type { ArgumentFallback } from '../utils.js';
3
+
4
+ const moValueSymbol = Symbol('noValue');
5
+
6
+ /**
7
+ * A React context with a required `displayName` and the obsolete `Consumer`
8
+ * property purposefully omitted so that it is impossible to pass the context
9
+ * as an argument to `useContext` or `use` (the hook produced with
10
+ * {@linkcode createSafeContext} should be used instead)
11
+ *
12
+ * @see {@linkcode createSafeContext}
13
+ */
14
+ // The type is conditional so that both React 18 and 19 are correctly supported.
15
+ // The code duplication is necessary for the type to be displayed correctly by
16
+ // TypeDoc.
17
+ export type RestrictedContext<T> =
18
+ Context<T> extends Provider<T>
19
+ ? { Provider: Provider<T>; displayName: string } & Provider<T>
20
+ : { Provider: Provider<T>; displayName: string };
21
+
22
+ /**
23
+ * @see {@linkcode createSafeContext}
24
+ */
25
+ export type SafeContext<DisplayName extends string, T> = {
26
+ [K in `${DisplayName}Context`]: RestrictedContext<T>;
27
+ } & {
28
+ [K in `use${DisplayName}`]: () => T;
29
+ };
30
+
31
+ /**
32
+ * For a given type `T`, returns a function that produces both a context of that
33
+ * type and a hook that returns the current context value if one was provided,
34
+ * or throws an error otherwise
35
+ *
36
+ * @example
37
+ * ```tsx
38
+ * const { ItemsContext, useItems } = createSafeContext<string[]>()('Items');
39
+ *
40
+ * const Parent = () => (
41
+ * <ItemsContext value={['compass', 'newspaper', 'banana']}>
42
+ * <Child />
43
+ * </ItemsContext>
44
+ * );
45
+ *
46
+ * const Child = () => useItems().join(', ');
47
+ * ```
48
+ *
49
+ * @returns
50
+ * A function that accepts a single string argument `displayName` (e.g.
51
+ * `"Items"`) and returns an object with the following properties:
52
+ * - ``` `${displayName}Context` ``` (e.g. `ItemsContext`): the context
53
+ * - ``` `use${displayName}` ``` (e.g. `useItems`): a hook that returns the
54
+ * current context value if one was provided, or throws an error otherwise
55
+ */
56
+ export function createSafeContext<T = never>() {
57
+ return <DisplayName extends string>(
58
+ displayName: [T] extends [never]
59
+ ? never
60
+ : ArgumentFallback<DisplayName, never, string>,
61
+ ): SafeContext<DisplayName, T> => {
62
+ const contextName = `${displayName as DisplayName}Context` as const;
63
+ const hookName = `use${displayName as DisplayName}` as const;
64
+
65
+ const Context = createContext<T | typeof moValueSymbol>(moValueSymbol);
66
+ Context.displayName = contextName;
67
+
68
+ return {
69
+ [contextName]: Context as RestrictedContext<T>,
70
+ [hookName]: () => {
71
+ const value = useContext(Context);
72
+ if (value === moValueSymbol) {
73
+ throw new Error(`No ${contextName} value was provided`);
74
+ }
75
+ return value;
76
+ },
77
+ } as {
78
+ [K in typeof contextName]: RestrictedContext<T>;
79
+ } & {
80
+ [K in typeof hookName]: () => T;
81
+ };
82
+ };
83
+ }
@@ -0,0 +1 @@
1
+ export * from './createSafeContext.js';
@@ -1,9 +1,16 @@
1
1
  import type { DependencyList } from 'react';
2
2
 
3
+ export type Callable = (...args: never) => unknown;
4
+
5
+ export type ArgumentFallback<
6
+ T extends Base,
7
+ Default extends Base,
8
+ Base = unknown,
9
+ > = [T] extends [never] ? Default : [Base] extends [T] ? Default : T;
10
+
3
11
  export function noop() {}
4
12
 
5
- // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
6
- export function isFunction(input: unknown): input is Function {
13
+ export function isFunction(input: unknown): input is Callable {
7
14
  return typeof input === 'function';
8
15
  }
9
16
 
@@ -1,4 +0,0 @@
1
- import { useLayoutEffect } from 'react';
2
- declare const useIsomorphicLayoutEffect: typeof useLayoutEffect;
3
- export default useIsomorphicLayoutEffect;
4
- //# sourceMappingURL=useIsomorphicLayoutEffect.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useIsomorphicLayoutEffect.d.ts","sourceRoot":"","sources":["../../lib/hooks/useIsomorphicLayoutEffect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,OAAO,CAAC;AAGxC,QAAA,MAAM,yBAAyB,wBACyB,CAAC;AAEzD,eAAe,yBAAyB,CAAC"}
@@ -1,5 +0,0 @@
1
- import { useLayoutEffect } from 'react';
2
- import { noop } from "../utils/index.js";
3
- const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : noop;
4
- export default useIsomorphicLayoutEffect;
5
- //# sourceMappingURL=useIsomorphicLayoutEffect.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useIsomorphicLayoutEffect.js","sourceRoot":"","sources":["../../lib/hooks/useIsomorphicLayoutEffect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,OAAO,CAAC;AACxC,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEzC,MAAM,yBAAyB,GAC7B,OAAO,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC;AAEzD,eAAe,yBAAyB,CAAC"}
@@ -1,5 +0,0 @@
1
- import type { DependencyList } from 'react';
2
- export declare function noop(): void;
3
- export declare function isFunction(input: unknown): input is Function;
4
- export declare function depsAreEqual(prevDeps: DependencyList, deps: DependencyList): boolean;
5
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../lib/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAE5C,wBAAgB,IAAI,SAAK;AAGzB,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 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../lib/utils/index.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,IAAI,KAAI,CAAC;AAEzB,sEAAsE;AACtE,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"}
@@ -1,101 +0,0 @@
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, useRef } from 'react';
9
- import useIsomorphicLayoutEffect from './useIsomorphicLayoutEffect.ts';
10
-
11
- /**
12
- * Adds `handler` as a listener for the event `eventName` of `element`
13
- * (or `window` by default) with the provided `options` applied
14
- *
15
- * It is the user's responsibility to make sure `element` and `options` values
16
- * are correctly memoized!
17
- */
18
-
19
- // SVGElement Event based useEventListener interface
20
- function useEventListener<
21
- K extends keyof SVGElementEventMap,
22
- T extends SVGElement,
23
- >(
24
- eventName: K,
25
- handler: (this: T, event: SVGElementEventMap[K]) => void,
26
- element: T | null,
27
- options?: boolean | AddEventListenerOptions,
28
- ): void;
29
-
30
- // HTMLElement Event based useEventListener interface
31
- function useEventListener<
32
- K extends keyof HTMLElementEventMap,
33
- T extends HTMLElement,
34
- >(
35
- eventName: K,
36
- handler: (this: T, event: HTMLElementEventMap[K]) => void,
37
- element: T | null,
38
- options?: boolean | AddEventListenerOptions,
39
- ): void;
40
-
41
- // Document Event based useEventListener interface
42
- function useEventListener<K extends keyof DocumentEventMap>(
43
- eventName: K,
44
- handler: (this: Document, event: DocumentEventMap[K]) => void,
45
- element: Document,
46
- options?: boolean | AddEventListenerOptions,
47
- ): void;
48
-
49
- // Window Event based useEventListener interface
50
- function useEventListener<K extends keyof WindowEventMap>(
51
- eventName: K,
52
- handler: (this: Window, event: WindowEventMap[K]) => void,
53
- element?: Window,
54
- options?: boolean | AddEventListenerOptions,
55
- ): void;
56
-
57
- // Fallback overload for all other event targets and types
58
- function useEventListener<T extends EventTarget>(
59
- eventName: string,
60
- handler: (this: T, event: Event) => void,
61
- element?: T | null,
62
- options?: boolean | AddEventListenerOptions,
63
- ): void;
64
-
65
- function useEventListener(
66
- eventName: string,
67
- handler: (this: EventTarget, event: Event) => void,
68
- element?: EventTarget | null,
69
- options?: boolean | AddEventListenerOptions,
70
- ) {
71
- // Create a ref that stores handler
72
- const savedHandler = useRef(handler);
73
-
74
- useIsomorphicLayoutEffect(() => {
75
- savedHandler.current = handler;
76
- }, [handler]);
77
-
78
- useEffect(() => {
79
- if (element === null) {
80
- // No element has been attached to the ref yet
81
- return;
82
- }
83
-
84
- // Define the listening target
85
- const targetElement = element ?? window;
86
-
87
- // Create event listener that calls handler function stored in ref
88
- const listener: typeof handler = function (event) {
89
- savedHandler.current.call(this, event);
90
- };
91
-
92
- targetElement.addEventListener(eventName, listener, options);
93
-
94
- // Remove event listener on cleanup
95
- return () => {
96
- targetElement.removeEventListener(eventName, listener, options);
97
- };
98
- }, [eventName, element, options]);
99
- }
100
-
101
- export default useEventListener;
@@ -1,7 +0,0 @@
1
- import { useLayoutEffect } from 'react';
2
- import { noop } from '../utils/index.ts';
3
-
4
- const useIsomorphicLayoutEffect =
5
- typeof window !== 'undefined' ? useLayoutEffect : noop;
6
-
7
- export default useIsomorphicLayoutEffect;
package/lib/index.ts DELETED
@@ -1,5 +0,0 @@
1
- export { default as useEventListener } from './hooks/useEventListener.js';
2
- export { default as useForceUpdate } from './hooks/useForceUpdate.js';
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';