@aweebit/react-essentials 0.7.0 → 0.8.1

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/utils.js CHANGED
@@ -1,7 +1,3 @@
1
- export function noop() { }
2
- export function isFunction(input) {
3
- return typeof input === 'function';
4
- }
5
1
  export function depsAreEqual(prevDeps, deps) {
6
2
  return (prevDeps.length === deps.length &&
7
3
  deps.every((dep, index) => Object.is(dep, prevDeps[index])));
package/dist/utils.js.map CHANGED
@@ -1 +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"}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAQA,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.7.0",
3
+ "version": "0.8.1",
4
4
  "type": "module",
5
5
  "repository": "github:aweebit/react-essentials",
6
6
  "main": "dist/index.js",
@@ -1,121 +1,114 @@
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
- ];
1
+ import { useEffect, useMemo, useRef, type RefObject } from 'react';
20
2
 
21
3
  /**
22
- * Adds `handler` as a listener for the event `eventName` of `target` with the
23
- * provided `options` applied
4
+ * The type of {@linkcode useEventListener}
24
5
  *
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
6
+ * @see
7
+ * {@linkcode useEventListener},
8
+ * {@linkcode UseEventListenerWithImplicitWindowTarget},
9
+ * {@linkcode UseEventListenerWithExplicitGlobalTarget},
10
+ * {@linkcode UseEventListenerWithAnyExplicitTarget}
46
11
  */
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;
12
+ export type UseEventListener = UseEventListenerWithImplicitWindowTarget &
13
+ UseEventListenerWithExplicitGlobalTarget &
14
+ UseEventListenerWithAnyExplicitTarget;
52
15
 
53
16
  /**
54
- * @see {@linkcode useEventListener}
55
- * @ignore
17
+ * @see
18
+ * {@linkcode useEventListener},
19
+ * {@linkcode UseEventListenerWithImplicitWindowTargetArgs}
56
20
  */
57
- export function useEventListener<K extends keyof WindowEventMap>(
58
- ...args: UseEventListenerOverloadArgs<WindowEventMap, K, Window>
59
- ): void;
21
+ export type UseEventListenerWithImplicitWindowTarget = <
22
+ K extends keyof WindowEventMap,
23
+ >(
24
+ ...args: UseEventListenerWithImplicitWindowTargetArgs<K>
25
+ ) => void;
60
26
 
61
27
  /**
62
- * @see {@linkcode useEventListener}
63
- * @ignore
28
+ * @see
29
+ * {@linkcode useEventListener},
30
+ * {@linkcode UseEventListenerWithExplicitTarget}
64
31
  */
65
- export function useEventListener<K extends keyof DocumentEventMap>(
66
- ...args: UseEventListenerOverloadArgs<DocumentEventMap, K, Document>
67
- ): void;
32
+ export type UseEventListenerWithExplicitGlobalTarget =
33
+ UseEventListenerWithExplicitTarget<Window, WindowEventMap> &
34
+ UseEventListenerWithExplicitTarget<Document, DocumentEventMap> &
35
+ UseEventListenerWithExplicitTarget<HTMLElement, HTMLElementEventMap> &
36
+ UseEventListenerWithExplicitTarget<SVGElement, SVGElementEventMap> &
37
+ UseEventListenerWithExplicitTarget<MathMLElement, MathMLElementEventMap>;
68
38
 
69
39
  /**
70
- * @see {@linkcode useEventListener}
71
- * @ignore
40
+ * @see
41
+ * {@linkcode useEventListener},
42
+ * {@linkcode UseEventListenerWithExplicitTargetArgs}
72
43
  */
73
- export function useEventListener<
74
- K extends keyof HTMLElementEventMap,
75
- T extends HTMLElement,
76
- >(...args: UseEventListenerOverloadArgs<HTMLElementEventMap, K, T>): void;
44
+ export type UseEventListenerWithExplicitTarget<
45
+ Target extends EventTarget,
46
+ EventMap = Record<string, Event>,
47
+ > = <T extends Target, K extends keyof EventMap>(
48
+ ...args: UseEventListenerWithExplicitTargetArgs<EventMap, T, K>
49
+ ) => void;
77
50
 
78
51
  /**
79
- * @see {@linkcode useEventListener}
80
- * @ignore
52
+ * @see
53
+ * {@linkcode useEventListener},
54
+ * {@linkcode UseEventListenerWithExplicitTarget}
81
55
  */
82
- export function useEventListener<
83
- K extends keyof SVGElementEventMap,
84
- T extends SVGElement,
85
- >(...args: UseEventListenerOverloadArgs<SVGElementEventMap, K, T>): void;
56
+ export type UseEventListenerWithAnyExplicitTarget =
57
+ UseEventListenerWithExplicitTarget<EventTarget>;
86
58
 
87
59
  /**
88
- * @see {@linkcode useEventListener}
89
- * @ignore
60
+ * @see
61
+ * {@linkcode useEventListener},
62
+ * {@linkcode UseEventListenerWithExplicitTargetArgs}
90
63
  */
91
- export function useEventListener<
92
- K extends keyof MathMLElementEventMap,
93
- T extends MathMLElement,
94
- >(...args: UseEventListenerOverloadArgs<MathMLElementEventMap, K, T>): void;
64
+ export type UseEventListenerWithImplicitWindowTargetArgs<
65
+ K extends keyof WindowEventMap,
66
+ > =
67
+ UseEventListenerWithExplicitTargetArgs<WindowEventMap, Window, K> extends [
68
+ unknown,
69
+ ...infer Args,
70
+ ]
71
+ ? Args
72
+ : never;
95
73
 
96
74
  /**
97
- * @see {@linkcode useEventListener}
75
+ * @see
76
+ * {@linkcode useEventListener}
98
77
  */
99
- export function useEventListener<K extends keyof WindowEventMap>(
78
+ export type UseEventListenerWithExplicitTargetArgs<
79
+ EventMap,
80
+ T extends EventTarget,
81
+ K extends keyof EventMap,
82
+ > = [
83
+ target: T | (RefObject<T> & { addEventListener?: never }) | null,
100
84
  eventName: K,
101
- handler: (this: Window, event: WindowEventMap[K]) => void,
102
- options?: AddEventListenerOptions | boolean,
103
- ): void;
85
+ handler: (this: NoInfer<T>, event: EventMap[K]) => void,
86
+ options?: AddEventListenerOptions | boolean | undefined,
87
+ ];
104
88
 
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;
89
+ type UseEventListenerWithImplicitWindowTargetArgsAny =
90
+ UseEventListenerWithImplicitWindowTargetArgs<keyof WindowEventMap>;
91
+
92
+ type UseEventListenerWithExplicitTargetArgsAny =
93
+ UseEventListenerWithExplicitTargetArgs<
94
+ Record<string, Event>,
95
+ EventTarget,
96
+ string
97
+ >;
114
98
 
115
99
  /**
116
100
  * Adds `handler` as a listener for the event `eventName` of `target` with the
117
101
  * provided `options` applied
118
102
  *
103
+ * The following call signatures are available:
104
+ *
105
+ * ```ts
106
+ * function useEventListener(eventName, handler, options?): void;
107
+ * function useEventListener(target, eventName, handler, options?): void;
108
+ * ```
109
+ *
110
+ * For the full definition of the hook's type, see {@linkcode UseEventListener}.
111
+ *
119
112
  * If `target` is not provided, `window` is used instead.
120
113
  *
121
114
  * If `target` is `null`, no event listener is added. This is useful when
@@ -133,26 +126,34 @@ export function useEventListener<T extends EventTarget>(
133
126
  * });
134
127
  *
135
128
  * const buttonRef = useRef<HTMLButtonElement>(null);
136
- * useEventListener(buttonRef.current, 'click', () => console.log('click'));
129
+ * useEventListener(buttonRef, 'click', () => console.log('click'));
137
130
  * ```
131
+ *
132
+ * @see
133
+ * {@linkcode UseEventListener}
138
134
  */
139
- export function useEventListener(
140
- ...args: UseEventListenerArgsWithoutTarget | UseEventListenerArgsWithTarget
135
+ export const useEventListener: UseEventListener = function useEventListener(
136
+ ...args:
137
+ | UseEventListenerWithImplicitWindowTargetArgsAny
138
+ | UseEventListenerWithExplicitTargetArgsAny
141
139
  ) {
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
- }
140
+ const [target, eventName, handler, options]: [
141
+ target: EventTarget | RefObject<EventTarget> | null,
142
+ eventName: string,
143
+ handler: (this: never, event: Event) => void,
144
+ options?: AddEventListenerOptions | boolean | undefined,
145
+ ] =
146
+ typeof args[0] === 'string'
147
+ ? [window, ...(args as UseEventListenerWithImplicitWindowTargetArgsAny)]
148
+ : (args as UseEventListenerWithExplicitTargetArgsAny);
149
+
150
+ const unwrappedTarget =
151
+ target && !('addEventListener' in target) ? target.current : target;
153
152
 
154
153
  const handlerRef = useRef(handler);
155
- handlerRef.current = handler;
154
+ useEffect(() => {
155
+ handlerRef.current = handler;
156
+ }, [handler]);
156
157
 
157
158
  const {
158
159
  capture = false,
@@ -168,34 +169,19 @@ export function useEventListener(
168
169
  );
169
170
 
170
171
  useEffect(() => {
171
- if (target === null) {
172
+ if (unwrappedTarget === null) {
172
173
  // No element has been attached to the ref yet
173
174
  return;
174
175
  }
175
176
 
176
- const definedTarget = target ?? window;
177
-
178
177
  const listener: typeof handler = function (event) {
179
178
  handlerRef.current.call(this, event);
180
179
  };
181
180
 
182
- definedTarget.addEventListener(eventName, listener, memoizedOptions);
181
+ unwrappedTarget.addEventListener(eventName, listener, memoizedOptions);
183
182
 
184
183
  return () => {
185
- definedTarget.removeEventListener(eventName, listener, memoizedOptions);
184
+ unwrappedTarget.removeEventListener(eventName, listener, memoizedOptions);
186
185
  };
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
- ];
186
+ }, [unwrappedTarget, eventName, memoizedOptions]);
187
+ } as UseEventListener;
@@ -11,54 +11,10 @@ import type { useReducerWithDeps } from './useReducerWithDeps.js';
11
11
  * This hook is designed in the most general way possible in order to cover all
12
12
  * imaginable use cases.
13
13
  *
14
- * @example
15
- * Sometimes, React's immutability constraints mean too much unnecessary copying
16
- * of data when new data arrives at a high frequency. In such cases, it might be
17
- * desirable to ignore the constraints by embracing imperative patterns.
18
- * Here is an example of a scenario where that can make sense:
19
- *
20
- * ```tsx
21
- * type SensorData = { timestamp: number; value: number };
22
- * const sensorDataRef = useRef<SensorData[]>([]);
23
- * const mostRecentSensorDataTimestampRef = useRef<number>(0);
24
- *
25
- * const [forceUpdate, updateCount] = useForceUpdate();
26
- * // Limiting the frequency of forced re-renders with some throttle function:
27
- * const throttledForceUpdateRef = useRef(throttle(forceUpdate));
28
- *
29
- * useEffect(() => {
30
- * return sensorDataObservable.subscribe((data: SensorData) => {
31
- * // Imagine new sensor data arrives every 1 millisecond. If we were following
32
- * // React's immutability rules by creating a new array every time, the data
33
- * // that's already there would have to be copied many times before the new
34
- * // data would even get a chance to be reflected in the UI for the first time
35
- * // because it typically takes much longer than 1 millisecond for a new frame
36
- * // to be displayed. To prevent the waste of computational resources, we just
37
- * // mutate the existing array every time instead:
38
- * sensorDataRef.current.push(data);
39
- * if (data.timestamp > mostRecentSensorDataTimestampRef.current) {
40
- * mostRecentSensorDataTimestampRef.current = data.timestamp;
41
- * }
42
- * throttledForceUpdateRef.current();
43
- * });
44
- * }, []);
45
- *
46
- * const [timeWindow, setTimeWindow] = useState(1000);
47
- * const selectedSensorData = useMemo(
48
- * () => {
49
- * // Keep this line if you don't want to disable the
50
- * // react-hooks/exhaustive-deps ESLint rule:
51
- * updateCount;
52
- * const threshold = mostRecentSensorDataTimestampRef.current - timeWindow;
53
- * return sensorDataRef.current.filter(
54
- * ({ timestamp }) => timestamp >= threshold,
55
- * );
56
- * },
57
- * // sensorDataRef.current always references the same array, so listing it as a
58
- * // dependency is pointless. Instead, updateCount should be used:
59
- * [updateCount, timeWindow],
60
- * );
61
- * ```
14
+ * @deprecated
15
+ * This hook encourages patterns that are unsafe in Concurrent React.
16
+ * For details and ideas on how to get rid of it, please check the discussion at
17
+ * https://www.reddit.com/r/react/comments/1nqcsri/comment/ng76cv5/.
62
18
  *
63
19
  * @param callback An optional callback function to call during renders that
64
20
  * were triggered with `forceUpdate()`
@@ -1,4 +1,4 @@
1
- import { type DependencyList, useCallback, useRef } from 'react';
1
+ import { type DependencyList, useRef } from 'react';
2
2
  import { useStateWithDeps } from './useStateWithDeps.js';
3
3
 
4
4
  // We cannot simply import the following types from @types/react since they are
@@ -18,6 +18,13 @@ export type ActionDispatch<ActionArg extends AnyActionArg> = (
18
18
  * `useReducer` hook with an additional dependency array `deps` that resets the
19
19
  * state to `initialState` when dependencies change
20
20
  *
21
+ * This hook is the reducer pattern counterpart of {@linkcode useStateWithDeps}.
22
+ *
23
+ * Due to React's limitations, a change in dependencies always causes two
24
+ * renders when using this hook. The result of the first render is thrown away
25
+ * as described in
26
+ * [useState > Storing information from previous renders](https://react.dev/reference/react/useState#storing-information-from-previous-renders).
27
+ *
21
28
  * For motivation and examples, see
22
29
  * https://github.com/facebook/react/issues/33041.
23
30
  *
@@ -60,9 +67,9 @@ export function useReducerWithDeps<S, A extends AnyActionArg>(
60
67
  // Only the initially provided reducer is used
61
68
  const reducerRef = useRef(reducer);
62
69
 
63
- const dispatch = useCallback(function dispatch(...args: A): void {
70
+ function dispatch(...args: A): void {
64
71
  setState((previousState) => reducerRef.current(previousState, ...args));
65
- }, []); // eslint-disable-line react-hooks/exhaustive-deps
72
+ }
66
73
 
67
- return [state, dispatch];
74
+ return [state, useRef(dispatch).current];
68
75
  }
@@ -1,24 +1,20 @@
1
- /**
2
- * @file Based on {@link https://github.com/peterjuras/use-state-with-deps}
3
- *
4
- * @license MIT
5
- * @copyright 2020 Peter Juras
6
- */
7
-
8
1
  import {
9
- useCallback,
10
- useRef,
2
+ useState,
11
3
  type DependencyList,
12
4
  type Dispatch,
13
5
  type SetStateAction,
14
6
  } from 'react';
15
- import { depsAreEqual, isFunction } from '../utils.js';
16
- import { useForceUpdate } from './useForceUpdate.js';
7
+ import { depsAreEqual } from '../utils.js';
17
8
 
18
9
  /**
19
10
  * `useState` hook with an additional dependency array `deps` that resets the
20
11
  * state to `initialState` when dependencies change
21
12
  *
13
+ * Due to React's limitations, a change in dependencies always causes two
14
+ * renders when using this hook. The result of the first render is thrown away
15
+ * as described in
16
+ * [useState > Storing information from previous renders](https://react.dev/reference/react/useState#storing-information-from-previous-renders).
17
+ *
22
18
  * For motivation and more examples, see
23
19
  * https://github.com/facebook/react/issues/33041.
24
20
  *
@@ -37,7 +33,7 @@ import { useForceUpdate } from './useForceUpdate.js';
37
33
  * evening: ['board games', 'dinner'],
38
34
  * };
39
35
  *
40
- * export function Example() {
36
+ * function Example() {
41
37
  * const [timeOfDay, setTimeOfDay] = useState<TimeOfDay>('morning');
42
38
  *
43
39
  * const activityOptions = activityOptionsByTimeOfDay[timeOfDay];
@@ -67,45 +63,14 @@ export function useStateWithDeps<S>(
67
63
  initialState: S | ((previousState?: S) => S),
68
64
  deps: DependencyList,
69
65
  ): [S, Dispatch<SetStateAction<S>>] {
70
- // It would be possible to use useState instead of useRef to store the state,
71
- // however this would trigger re-renders whenever the state is reset due to a
72
- // change in dependencies. In order to avoid these re-renders, the state is
73
- // stored in a ref, and updates are triggered with forceUpdate when necessary.
74
- const state = useRef(undefined as S);
66
+ const [state, setState] = useState(initialState);
75
67
 
76
- const prevDeps = useRef(deps);
77
- const isMounted = useRef(false);
68
+ const [prevDeps, setPrevDeps] = useState(deps);
78
69
 
79
- // If first render, or if dependencies have changed since last time
80
- if (!isMounted.current || !depsAreEqual(prevDeps.current, deps)) {
81
- // Update state and deps
82
- let nextState: S;
83
- if (isFunction(initialState)) {
84
- nextState = initialState(state.current);
85
- } else {
86
- nextState = initialState;
87
- }
88
- state.current = nextState;
89
- prevDeps.current = deps;
90
- isMounted.current = true;
70
+ if (!depsAreEqual(deps, prevDeps)) {
71
+ setPrevDeps(deps);
72
+ setState(initialState);
91
73
  }
92
74
 
93
- const [forceUpdate] = useForceUpdate();
94
-
95
- const updateState = useCallback(function updateState(
96
- newState: S | ((previousState: S) => S),
97
- ): void {
98
- let nextState: S;
99
- if (isFunction(newState)) {
100
- nextState = newState(state.current);
101
- } else {
102
- nextState = newState;
103
- }
104
- if (!Object.is(state.current, nextState)) {
105
- state.current = nextState;
106
- forceUpdate();
107
- }
108
- }, []); // eslint-disable-line react-hooks/exhaustive-deps
109
-
110
- return [state.current, updateState];
75
+ return [state, setState];
111
76
  }
@@ -9,7 +9,8 @@ const moValueSymbol = Symbol('noValue');
9
9
  * as an argument to `useContext` or `use` (the hook produced with
10
10
  * {@linkcode createSafeContext} should be used instead)
11
11
  *
12
- * @see {@linkcode createSafeContext}
12
+ * @see
13
+ * {@linkcode createSafeContext}
13
14
  */
14
15
  // The type is conditional so that both React 18 and 19 are correctly supported.
15
16
  // The code duplication is necessary for the type to be displayed correctly by
@@ -22,7 +23,9 @@ export type RestrictedContext<T> =
22
23
  /**
23
24
  * The return type of {@linkcode createSafeContext}
24
25
  *
25
- * @see {@linkcode createSafeContext}
26
+ * @see
27
+ * {@linkcode createSafeContext},
28
+ * {@linkcode RestrictedContext}
26
29
  */
27
30
  export type SafeContext<DisplayName extends string, T> = {
28
31
  [K in `${DisplayName}Context`]: RestrictedContext<T>;
@@ -85,6 +88,9 @@ export type SafeContext<DisplayName extends string, T> = {
85
88
  * - ``` `${displayName}Context` ``` (e.g. `DirectionContext`): the context
86
89
  * - ``` `use${displayName}` ``` (e.g. `useDirection`): a hook that returns the
87
90
  * current context value if one was provided, or throws an error otherwise
91
+ *
92
+ * @see
93
+ * {@linkcode SafeContext}
88
94
  */
89
95
  export function createSafeContext<T = never>() {
90
96
  return <DisplayName extends string>(
package/src/utils.ts CHANGED
@@ -1,19 +1,11 @@
1
1
  import type { DependencyList } from 'react';
2
2
 
3
- export type Callable = (...args: never) => unknown;
4
-
5
3
  export type ArgumentFallback<
6
4
  T extends Base,
7
5
  Default extends Base,
8
6
  Base = unknown,
9
7
  > = [T] extends [never] ? Default : [Base] extends [T] ? Default : T;
10
8
 
11
- export function noop() {}
12
-
13
- export function isFunction(input: unknown): input is Callable {
14
- return typeof input === 'function';
15
- }
16
-
17
9
  export function depsAreEqual(
18
10
  prevDeps: DependencyList,
19
11
  deps: DependencyList,