@veams/status-quo 1.7.0 → 1.8.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/.turbo/turbo-test.log +115 -15
- package/CHANGELOG.md +2 -0
- package/README.md +51 -7
- package/dist/config/status-quo-config.d.ts +22 -1
- package/dist/config/status-quo-config.js +46 -2
- package/dist/config/status-quo-config.js.map +1 -1
- package/dist/index.d.ts +12 -2
- package/dist/index.js +22 -2
- package/dist/index.js.map +1 -1
- package/dist/react/hooks/__tests__/state-provider.spec.js +2 -2
- package/dist/react/hooks/__tests__/state-provider.spec.js.map +1 -1
- package/dist/react/hooks/index.d.ts +6 -3
- package/dist/react/hooks/index.js +12 -3
- package/dist/react/hooks/index.js.map +1 -1
- package/dist/react/hooks/state-actions.d.ts +9 -1
- package/dist/react/hooks/state-actions.js +21 -2
- package/dist/react/hooks/state-actions.js.map +1 -1
- package/dist/react/hooks/state-factory.d.ts +7 -0
- package/dist/react/hooks/state-factory.js +23 -1
- package/dist/react/hooks/state-factory.js.map +1 -1
- package/dist/react/hooks/state-handler.d.ts +4 -0
- package/dist/react/hooks/state-handler.js +18 -1
- package/dist/react/hooks/state-handler.js.map +1 -1
- package/dist/react/hooks/state-provider.d.ts +18 -9
- package/dist/react/hooks/state-provider.js +25 -17
- package/dist/react/hooks/state-provider.js.map +1 -1
- package/dist/react/hooks/state-singleton.d.ts +8 -2
- package/dist/react/hooks/state-singleton.js +21 -3
- package/dist/react/hooks/state-singleton.js.map +1 -1
- package/dist/react/hooks/state-subscription-selector.d.ts +4 -0
- package/dist/react/hooks/state-subscription-selector.js +64 -8
- package/dist/react/hooks/state-subscription-selector.js.map +1 -1
- package/dist/react/hooks/state-subscription.d.ts +12 -0
- package/dist/react/hooks/state-subscription.js +49 -1
- package/dist/react/hooks/state-subscription.js.map +1 -1
- package/dist/react/index.d.ts +4 -1
- package/dist/react/index.js +5 -1
- package/dist/react/index.js.map +1 -1
- package/dist/store/__tests__/native-state-handler.spec.d.ts +1 -0
- package/dist/store/__tests__/native-state-handler.spec.js +210 -0
- package/dist/store/__tests__/native-state-handler.spec.js.map +1 -0
- package/dist/store/__tests__/observable-state-handler.spec.d.ts +7 -0
- package/dist/store/__tests__/observable-state-handler.spec.js.map +1 -1
- package/dist/store/base-state-handler.d.ts +42 -0
- package/dist/store/base-state-handler.js +73 -10
- package/dist/store/base-state-handler.js.map +1 -1
- package/dist/store/dev-tools.d.ts +42 -17
- package/dist/store/dev-tools.js +24 -8
- package/dist/store/dev-tools.js.map +1 -1
- package/dist/store/index.d.ts +7 -0
- package/dist/store/index.js +9 -0
- package/dist/store/index.js.map +1 -1
- package/dist/store/native-state-handler.d.ts +44 -0
- package/dist/store/native-state-handler.js +62 -0
- package/dist/store/native-state-handler.js.map +1 -0
- package/dist/store/observable-state-handler.d.ts +34 -0
- package/dist/store/observable-state-handler.js +45 -1
- package/dist/store/observable-state-handler.js.map +1 -1
- package/dist/store/signal-state-handler.d.ts +26 -0
- package/dist/store/signal-state-handler.js +35 -0
- package/dist/store/signal-state-handler.js.map +1 -1
- package/dist/store/state-singleton.d.ts +14 -0
- package/dist/store/state-singleton.js +20 -1
- package/dist/store/state-singleton.js.map +1 -1
- package/dist/types/types.d.ts +9 -0
- package/dist/types/types.js +3 -0
- package/dist/types/types.js.map +1 -1
- package/dist/utils/selector-cache.d.ts +17 -0
- package/dist/utils/selector-cache.js +28 -1
- package/dist/utils/selector-cache.js.map +1 -1
- package/package.json +12 -1
- package/src/config/status-quo-config.ts +64 -1
- package/src/index.ts +29 -0
- package/src/react/hooks/__tests__/state-provider.spec.tsx +2 -2
- package/src/react/hooks/index.ts +13 -8
- package/src/react/hooks/state-actions.tsx +23 -2
- package/src/react/hooks/state-factory.tsx +34 -0
- package/src/react/hooks/state-handler.tsx +15 -0
- package/src/react/hooks/state-provider.tsx +36 -40
- package/src/react/hooks/state-singleton.tsx +37 -7
- package/src/react/hooks/state-subscription-selector.tsx +85 -7
- package/src/react/hooks/state-subscription.tsx +75 -0
- package/src/react/index.ts +16 -1
- package/src/store/__tests__/native-state-handler.spec.ts +291 -0
- package/src/store/__tests__/observable-state-handler.spec.ts +8 -0
- package/src/store/base-state-handler.ts +89 -12
- package/src/store/dev-tools.ts +72 -27
- package/src/store/index.ts +16 -0
- package/src/store/native-state-handler.ts +98 -0
- package/src/store/observable-state-handler.ts +57 -0
- package/src/store/signal-state-handler.ts +47 -1
- package/src/store/state-singleton.ts +30 -0
- package/src/types/types.ts +16 -0
- package/src/utils/selector-cache.ts +37 -0
|
@@ -1,55 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global configuration and utility functions for Status Quo state management.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Function signature for comparing state objects to determine if they are distinct.
|
|
1
6
|
export type DistinctComparator<T = unknown> = (previous: T, next: T) => boolean;
|
|
2
7
|
|
|
8
|
+
// Options for configuring distinct emission behavior in state handlers.
|
|
3
9
|
export type DistinctOptions<T = unknown> = {
|
|
10
|
+
// Whether distinct updates are enabled.
|
|
4
11
|
enabled?: boolean;
|
|
12
|
+
// Custom function to compare states for equality.
|
|
5
13
|
comparator?: DistinctComparator<T>;
|
|
6
14
|
};
|
|
7
15
|
|
|
16
|
+
// Options for configuring Redux DevTools integration for a specific state handler.
|
|
8
17
|
export type DevToolsOptions = {
|
|
18
|
+
// Whether DevTools integration is enabled for this handler.
|
|
9
19
|
enabled?: boolean;
|
|
20
|
+
// Namespace for the store in the DevTools window.
|
|
10
21
|
namespace?: string;
|
|
11
22
|
};
|
|
12
23
|
|
|
24
|
+
// Global options for Redux DevTools integration across all handlers.
|
|
13
25
|
export type GlobalDevToolsOptions = {
|
|
26
|
+
// Whether DevTools integration is enabled globally.
|
|
14
27
|
enabled?: boolean;
|
|
15
28
|
};
|
|
16
29
|
|
|
30
|
+
// Main configuration object for the Status Quo system.
|
|
17
31
|
export type StatusQuoConfig<T = unknown> = {
|
|
32
|
+
// Global DevTools configuration.
|
|
18
33
|
devTools?: GlobalDevToolsOptions;
|
|
34
|
+
// Global distinct emission configuration.
|
|
19
35
|
distinct?: DistinctOptions<T>;
|
|
20
36
|
};
|
|
21
37
|
|
|
38
|
+
// Internal representation of resolved distinct options.
|
|
22
39
|
type ResolvedDistinctOptions<T = unknown> = {
|
|
40
|
+
// Final enabled status for distinct updates.
|
|
23
41
|
enabled: boolean;
|
|
42
|
+
// Final comparator function to use for equality checks.
|
|
24
43
|
comparator: DistinctComparator<T>;
|
|
25
44
|
};
|
|
26
45
|
|
|
46
|
+
// Internal representation of resolved DevTools options.
|
|
27
47
|
type ResolvedDevToolsOptions = {
|
|
48
|
+
// Final enabled status for DevTools.
|
|
28
49
|
enabled: boolean;
|
|
29
50
|
};
|
|
30
51
|
|
|
52
|
+
// Internal representation of the full resolved configuration.
|
|
31
53
|
type ResolvedStatusQuoConfig = {
|
|
54
|
+
// Resolved global DevTools configuration.
|
|
32
55
|
devTools: ResolvedDevToolsOptions;
|
|
56
|
+
// Resolved global distinct emission configuration.
|
|
33
57
|
distinct: ResolvedDistinctOptions;
|
|
34
58
|
};
|
|
35
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Default comparator function that uses referential equality (Object.is)
|
|
62
|
+
* and falls back to JSON stringification for structural equality.
|
|
63
|
+
*/
|
|
36
64
|
function distinctAsJson(previous: unknown, next: unknown) {
|
|
65
|
+
// Fast path for referential equality.
|
|
37
66
|
if (Object.is(previous, next)) {
|
|
38
67
|
return true;
|
|
39
68
|
}
|
|
40
69
|
|
|
70
|
+
// Structural comparison using JSON stringification as a fallback.
|
|
41
71
|
try {
|
|
42
72
|
return JSON.stringify(previous) === JSON.stringify(next);
|
|
43
73
|
} catch {
|
|
74
|
+
// If stringification fails, assume they are not equal.
|
|
44
75
|
return false;
|
|
45
76
|
}
|
|
46
77
|
}
|
|
47
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Creates the default Status Quo configuration.
|
|
81
|
+
*/
|
|
48
82
|
function createDefaultStatusQuoConfig(): ResolvedStatusQuoConfig {
|
|
49
83
|
return {
|
|
84
|
+
// DevTools integration is disabled by default.
|
|
50
85
|
devTools: {
|
|
51
86
|
enabled: false,
|
|
52
87
|
},
|
|
88
|
+
// Distinct emission is enabled by default with JSON-based structural equality.
|
|
53
89
|
distinct: {
|
|
54
90
|
enabled: true,
|
|
55
91
|
comparator: distinctAsJson,
|
|
@@ -57,13 +93,20 @@ function createDefaultStatusQuoConfig(): ResolvedStatusQuoConfig {
|
|
|
57
93
|
};
|
|
58
94
|
}
|
|
59
95
|
|
|
96
|
+
// Global configuration instance, initialized with defaults.
|
|
60
97
|
let statusQuoConfig = createDefaultStatusQuoConfig();
|
|
61
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Global setup function to configure Status Quo.
|
|
101
|
+
*/
|
|
62
102
|
export function setupStatusQuo<T = unknown>(config: StatusQuoConfig<T> = {}) {
|
|
103
|
+
// Merge the provided configuration with the current global config.
|
|
63
104
|
statusQuoConfig = {
|
|
105
|
+
// Update global DevTools status.
|
|
64
106
|
devTools: {
|
|
65
107
|
enabled: config.devTools?.enabled ?? false,
|
|
66
108
|
},
|
|
109
|
+
// Update global distinct emission status and comparator.
|
|
67
110
|
distinct: {
|
|
68
111
|
enabled: config.distinct?.enabled ?? true,
|
|
69
112
|
comparator: (config.distinct?.comparator ?? distinctAsJson) as DistinctComparator,
|
|
@@ -71,23 +114,39 @@ export function setupStatusQuo<T = unknown>(config: StatusQuoConfig<T> = {}) {
|
|
|
71
114
|
};
|
|
72
115
|
}
|
|
73
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Resolves the final distinct options for a specific state handler.
|
|
119
|
+
* Combines global configuration with handler-specific overrides.
|
|
120
|
+
*/
|
|
74
121
|
export function resolveDistinctOptions<T>(
|
|
122
|
+
// Handler-specific distinct options.
|
|
75
123
|
options?: DistinctOptions<T>,
|
|
124
|
+
// Boolean flag often used to override or determine enabled status.
|
|
76
125
|
useDistinctUntilChanged?: boolean
|
|
77
126
|
): ResolvedDistinctOptions<T> {
|
|
78
127
|
return {
|
|
128
|
+
// Determine the enabled status for distinct emissions.
|
|
79
129
|
enabled: options?.enabled ?? useDistinctUntilChanged ?? statusQuoConfig.distinct.enabled,
|
|
130
|
+
// Determine the comparator function to use.
|
|
80
131
|
comparator: (options?.comparator ??
|
|
81
132
|
statusQuoConfig.distinct.comparator),
|
|
82
133
|
};
|
|
83
134
|
}
|
|
84
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Resolves the final DevTools options for a specific state handler.
|
|
138
|
+
* Combines global configuration with handler-specific overrides.
|
|
139
|
+
*/
|
|
85
140
|
export function resolveDevToolsOptions(options?: DevToolsOptions): ResolvedDevToolsOptions {
|
|
86
141
|
return {
|
|
142
|
+
// Determine the enabled status for DevTools integration.
|
|
87
143
|
enabled: options?.enabled ?? statusQuoConfig.devTools.enabled,
|
|
88
144
|
};
|
|
89
145
|
}
|
|
90
146
|
|
|
147
|
+
/**
|
|
148
|
+
* Returns a copy of the current global configuration.
|
|
149
|
+
*/
|
|
91
150
|
export function getStatusQuoConfig() {
|
|
92
151
|
return {
|
|
93
152
|
devTools: {
|
|
@@ -100,7 +159,11 @@ export function getStatusQuoConfig() {
|
|
|
100
159
|
} as ResolvedStatusQuoConfig;
|
|
101
160
|
}
|
|
102
161
|
|
|
103
|
-
/**
|
|
162
|
+
/**
|
|
163
|
+
* Resets the Status Quo configuration to its default values.
|
|
164
|
+
* Useful for ensuring test isolation in unit tests.
|
|
165
|
+
* @internal testing helper
|
|
166
|
+
*/
|
|
104
167
|
export function resetStatusQuoForTests() {
|
|
105
168
|
statusQuoConfig = createDefaultStatusQuoConfig();
|
|
106
169
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main entry point for the Status Quo state management library.
|
|
3
|
+
* Exports core functionality for both store and React integration.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Import internal setup functions and core state handlers.
|
|
1
7
|
import { setupStatusQuo } from './config/status-quo-config.js';
|
|
2
8
|
import {
|
|
3
9
|
BaseStateHandler,
|
|
4
10
|
makeStateSingleton,
|
|
11
|
+
NativeStateHandler,
|
|
5
12
|
ObservableStateHandler,
|
|
6
13
|
SignalStateHandler,
|
|
7
14
|
} from './store';
|
|
8
15
|
|
|
16
|
+
// Import necessary types for external use.
|
|
9
17
|
import type {
|
|
10
18
|
DevToolsOptions,
|
|
11
19
|
GlobalDevToolsOptions,
|
|
@@ -16,21 +24,42 @@ import type {
|
|
|
16
24
|
import type { StateSingleton, StateSingletonOptions } from './store';
|
|
17
25
|
import type { StateSubscriptionHandler } from './types/types.js';
|
|
18
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Core state management functions and classes.
|
|
29
|
+
*/
|
|
19
30
|
export {
|
|
31
|
+
// Abstract base class for all state handlers.
|
|
20
32
|
BaseStateHandler,
|
|
33
|
+
// Factory function for creating singleton state handlers.
|
|
21
34
|
makeStateSingleton,
|
|
35
|
+
// Lightweight state handler using plain JavaScript.
|
|
36
|
+
NativeStateHandler,
|
|
37
|
+
// State handler powered by RxJS BehaviorSubjects.
|
|
22
38
|
ObservableStateHandler,
|
|
39
|
+
// Global configuration function for Status Quo.
|
|
23
40
|
setupStatusQuo,
|
|
41
|
+
// State handler powered by Preact Signals.
|
|
24
42
|
SignalStateHandler,
|
|
25
43
|
};
|
|
26
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Type definitions for public API.
|
|
47
|
+
*/
|
|
27
48
|
export type {
|
|
49
|
+
// Options for Redux DevTools integration.
|
|
28
50
|
DevToolsOptions,
|
|
51
|
+
// Global options for Redux DevTools.
|
|
29
52
|
GlobalDevToolsOptions,
|
|
53
|
+
// Function signature for state equality comparisons.
|
|
30
54
|
DistinctComparator,
|
|
55
|
+
// Options for configuring distinct updates.
|
|
31
56
|
DistinctOptions,
|
|
57
|
+
// Interface representing a singleton state handler.
|
|
32
58
|
StateSingleton,
|
|
59
|
+
// Options for configuring state singleton instances.
|
|
33
60
|
StateSingletonOptions,
|
|
61
|
+
// Interface representing a standard state subscription handler.
|
|
34
62
|
StateSubscriptionHandler,
|
|
63
|
+
// Main configuration object for the Status Quo system.
|
|
35
64
|
StatusQuoConfig,
|
|
36
65
|
};
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
useProvidedStateActions,
|
|
7
7
|
useProvidedStateHandler,
|
|
8
8
|
useProvidedStateSubscription,
|
|
9
|
-
} from '../
|
|
9
|
+
} from '../index.js';
|
|
10
10
|
|
|
11
11
|
import type { StateSubscriptionHandler } from '../../../types/types.js';
|
|
12
12
|
|
|
@@ -279,7 +279,7 @@ describe('StateProvider', () => {
|
|
|
279
279
|
act(() => {
|
|
280
280
|
root.render(<MissingProviderConsumer />);
|
|
281
281
|
});
|
|
282
|
-
}).toThrow('
|
|
282
|
+
}).toThrow('useProvidedStateHandler must be used within a StateProvider');
|
|
283
283
|
|
|
284
284
|
consoleErrorSpy.mockRestore();
|
|
285
285
|
});
|
package/src/react/hooks/index.ts
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Export available hooks for React components.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Export hook to access the actions of a state handler.
|
|
6
|
+
export { useProvidedStateActions, useStateActions } from './state-actions.js';
|
|
7
|
+
// Export hook to create and manage a state handler within a component.
|
|
2
8
|
export { useStateFactory } from './state-factory.js';
|
|
9
|
+
// Export hook to manage a state handler's lifecycle within a component's reference.
|
|
3
10
|
export { useStateHandler } from './state-handler.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
useProvidedStateHandler,
|
|
8
|
-
useProvidedStateSubscription,
|
|
9
|
-
} from './state-provider.js';
|
|
11
|
+
// Export hook and component to use a state handler through React Context.
|
|
12
|
+
export { StateProvider, useProvidedStateHandler } from './state-provider.js';
|
|
13
|
+
// Export hook to use a singleton state handler.
|
|
10
14
|
export { useStateSingleton } from './state-singleton.js';
|
|
11
|
-
|
|
15
|
+
// Export hook to subscribe to a state handler and receive its state updates.
|
|
16
|
+
export { useProvidedStateSubscription, useStateSubscription } from './state-subscription.js';
|
|
@@ -1,7 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility hook for accessing actions from a state handler instance.
|
|
3
|
+
*/
|
|
1
4
|
import { useMemo } from 'react';
|
|
2
5
|
|
|
3
6
|
import type { StateSubscriptionHandler } from '../../types/types.js';
|
|
7
|
+
import { useProvidedStateHandler } from './state-provider.js';
|
|
4
8
|
|
|
5
|
-
|
|
6
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Returns the actions of a state handler instance.
|
|
11
|
+
* memoized based on the state handler instance itself.
|
|
12
|
+
*/
|
|
13
|
+
export function useStateActions<V, A>(stateHandler: StateSubscriptionHandler<V, A>): A {
|
|
14
|
+
// Access and memoize the actions from the state handler.
|
|
15
|
+
// This ensures the action object remains referentially stable as long as the state handler is the same.
|
|
16
|
+
const actions = useMemo(() => stateHandler.getActions(), [stateHandler]);
|
|
17
|
+
|
|
18
|
+
// Return the set of actions.
|
|
19
|
+
return actions;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Returns the actions of the state handler provided by the nearest StateProvider.
|
|
24
|
+
*/
|
|
25
|
+
export function useProvidedStateActions<V, A>(): A {
|
|
26
|
+
const stateHandler = useProvidedStateHandler<V, A>();
|
|
27
|
+
return useStateActions(stateHandler);
|
|
7
28
|
}
|
|
@@ -1,42 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility hook for creating and subscribing to a state handler within a component.
|
|
3
|
+
* Combines creation and subscription logic in a single call.
|
|
4
|
+
*/
|
|
1
5
|
import { useStateHandler } from './state-handler.js';
|
|
2
6
|
import { useStateSubscription } from './state-subscription.js';
|
|
3
7
|
|
|
4
8
|
import type { StateSubscriptionHandler } from '../../types/types.js';
|
|
5
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Type signatures for selector and equality functions.
|
|
12
|
+
*/
|
|
6
13
|
type StateSelector<State, SelectedState> = (state: State) => SelectedState;
|
|
7
14
|
type EqualityFn<SelectedState> = (current: SelectedState, next: SelectedState) => boolean;
|
|
8
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Default identity selector returns the whole state.
|
|
18
|
+
*/
|
|
9
19
|
const identitySelector = <State,>(state: State) => state;
|
|
10
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Factory hook to create and subscribe to a state handler.
|
|
23
|
+
* Manages the handler instance using useStateHandler and its subscription with useStateSubscription.
|
|
24
|
+
*/
|
|
11
25
|
export function useStateFactory<V, A, P extends unknown[]>(
|
|
26
|
+
// Function to create a new state handler instance.
|
|
12
27
|
stateFactoryFunction: (...args: P) => StateSubscriptionHandler<V, A>,
|
|
28
|
+
// Parameters to pass to the factory function.
|
|
13
29
|
params?: P
|
|
14
30
|
): [V, A];
|
|
15
31
|
export function useStateFactory<V, A, P extends unknown[], Sel>(
|
|
32
|
+
// Function to create a new state handler instance.
|
|
16
33
|
stateFactoryFunction: (...args: P) => StateSubscriptionHandler<V, A>,
|
|
34
|
+
// Selector function to derive a specific value from the state.
|
|
17
35
|
selector: StateSelector<V, Sel>,
|
|
36
|
+
// Parameters to pass to the factory function.
|
|
18
37
|
params?: P
|
|
19
38
|
): [Sel, A];
|
|
20
39
|
export function useStateFactory<V, A, P extends unknown[], Sel>(
|
|
40
|
+
// Function to create a new state handler instance.
|
|
21
41
|
stateFactoryFunction: (...args: P) => StateSubscriptionHandler<V, A>,
|
|
42
|
+
// Selector function to derive a specific value from the state.
|
|
22
43
|
selector: StateSelector<V, Sel>,
|
|
44
|
+
// Optional equality function to compare selected values.
|
|
23
45
|
isEqual?: EqualityFn<Sel>,
|
|
46
|
+
// Parameters to pass to the factory function.
|
|
24
47
|
params?: P
|
|
25
48
|
): [Sel, A];
|
|
26
49
|
export function useStateFactory<V, A, P extends unknown[], Sel = V>(
|
|
50
|
+
// Implementation of the overloaded useStateFactory hook.
|
|
27
51
|
stateFactoryFunction: (...args: P) => StateSubscriptionHandler<V, A>,
|
|
52
|
+
// Mixed argument: can be a selector or params array.
|
|
28
53
|
selectorOrParams: StateSelector<V, Sel> | P = [] as unknown as P,
|
|
54
|
+
// Mixed argument: can be an equality function or params array.
|
|
29
55
|
isEqualOrParams: EqualityFn<Sel> | P = Object.is as EqualityFn<Sel>,
|
|
56
|
+
// Optional params array.
|
|
30
57
|
params: P = [] as unknown as P
|
|
31
58
|
) {
|
|
59
|
+
// Determine whether a selector was provided.
|
|
32
60
|
const hasSelector = typeof selectorOrParams === 'function';
|
|
61
|
+
// Fallback to identity selector if none is provided.
|
|
33
62
|
const selector = (hasSelector ? selectorOrParams : identitySelector) as StateSelector<V, Sel>;
|
|
63
|
+
// Determine whether a custom equality function was provided.
|
|
34
64
|
const hasCustomEquality = hasSelector && typeof isEqualOrParams === 'function';
|
|
65
|
+
// Fallback to default equality check (Object.is).
|
|
35
66
|
const isEqual = (hasCustomEquality ? isEqualOrParams : Object.is);
|
|
67
|
+
// Resolve the parameters for the state handler factory.
|
|
36
68
|
const stateFactoryParams = (
|
|
37
69
|
hasSelector ? (hasCustomEquality ? params : isEqualOrParams) : selectorOrParams
|
|
38
70
|
) as P;
|
|
71
|
+
// Create and persist the state handler instance using its factory and parameters.
|
|
39
72
|
const stateHandler = useStateHandler(stateFactoryFunction, stateFactoryParams);
|
|
40
73
|
|
|
74
|
+
// Subscribe to the state handler and return the selected state and actions.
|
|
41
75
|
return useStateSubscription(stateHandler, selector, isEqual);
|
|
42
76
|
}
|
|
@@ -1,16 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility hook for managing the lifecycle of a state handler instance.
|
|
3
|
+
* Ensures the instance is created once and persisted across component re-renders.
|
|
4
|
+
*/
|
|
1
5
|
import { useRef } from 'react';
|
|
2
6
|
|
|
3
7
|
import type { StateSubscriptionHandler } from '../../types/types.js';
|
|
4
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Returns a stable state handler instance based on a factory function.
|
|
11
|
+
* Uses a ref to ensure the factory function is only executed during initial render.
|
|
12
|
+
*/
|
|
5
13
|
export function useStateHandler<V, A, P extends unknown[]>(
|
|
14
|
+
// Function to create a new state handler instance.
|
|
6
15
|
stateFactoryFunction: (...args: P) => StateSubscriptionHandler<V, A>,
|
|
16
|
+
// Parameters to pass to the factory function.
|
|
7
17
|
params: P = [] as unknown as P
|
|
8
18
|
) {
|
|
19
|
+
// Use a ref to store the state handler instance.
|
|
20
|
+
// This prevents the state handler from being recreated on every re-render.
|
|
9
21
|
const stateHandlerRef = useRef<StateSubscriptionHandler<V, A> | null>(null);
|
|
10
22
|
|
|
23
|
+
// If the ref is currently null, we create the instance for the first time.
|
|
11
24
|
if (!stateHandlerRef.current) {
|
|
25
|
+
// Invoke the factory function with the provided parameters.
|
|
12
26
|
stateHandlerRef.current = stateFactoryFunction(...params);
|
|
13
27
|
}
|
|
14
28
|
|
|
29
|
+
// Return the stable state handler instance.
|
|
15
30
|
return stateHandlerRef.current;
|
|
16
31
|
}
|
|
@@ -1,56 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility hook for providing a state handler through React Context.
|
|
3
|
+
* Allows components deep in the component tree to access a common state handler instance.
|
|
4
|
+
*/
|
|
1
5
|
import React, { createContext, useContext } from 'react';
|
|
2
6
|
|
|
3
|
-
import { useStateActions } from './state-actions.js';
|
|
4
|
-
import { useStateSubscription } from './state-subscription.js';
|
|
5
|
-
|
|
6
|
-
import type { PropsWithChildren } from 'react';
|
|
7
7
|
import type { StateSubscriptionHandler } from '../../types/types.js';
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
export type StateProviderProps<V, A> = PropsWithChildren<{
|
|
9
|
+
/**
|
|
10
|
+
* Interface for the state provider component props.
|
|
11
|
+
*/
|
|
12
|
+
interface StateProviderProps<V, A> {
|
|
13
|
+
// Children components that will have access to the state handler.
|
|
14
|
+
children: React.ReactNode;
|
|
15
|
+
// The state handler instance to be provided to the component tree.
|
|
17
16
|
instance: StateSubscriptionHandler<V, A>;
|
|
18
|
-
}>;
|
|
19
|
-
|
|
20
|
-
export function useProvidedStateHandler<V, A>() {
|
|
21
|
-
const stateHandler = useContext(StateProviderContext);
|
|
22
|
-
|
|
23
|
-
if (!stateHandler) {
|
|
24
|
-
throw new Error('No StateProvider instance found in the current React tree.');
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return stateHandler as StateSubscriptionHandler<V, A>;
|
|
28
17
|
}
|
|
29
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Creates a React Context for storing and providing the state handler instance.
|
|
21
|
+
* Initialized with null as there is no default state handler.
|
|
22
|
+
*/
|
|
23
|
+
const StateContext = createContext<StateSubscriptionHandler<unknown, unknown> | null>(null);
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Provides a state handler instance to its descendant components using React Context.
|
|
27
|
+
*/
|
|
30
28
|
export function StateProvider<V, A>({ children, instance }: StateProviderProps<V, A>) {
|
|
29
|
+
// Use a context provider to share the state handler instance.
|
|
31
30
|
return (
|
|
32
|
-
<
|
|
31
|
+
<StateContext.Provider value={instance as StateSubscriptionHandler<unknown, unknown>}>
|
|
33
32
|
{children}
|
|
34
|
-
</
|
|
33
|
+
</StateContext.Provider>
|
|
35
34
|
);
|
|
36
35
|
}
|
|
37
36
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Custom hook to access the state handler provided by the StateProvider.
|
|
39
|
+
* Throws an error if the hook is used outside of a StateProvider.
|
|
40
|
+
*/
|
|
41
|
+
export function useProvidedStateHandler<V, A>(): StateSubscriptionHandler<V, A> {
|
|
42
|
+
// Retrieve the state handler from the nearest context provider.
|
|
43
|
+
const stateHandler = useContext(StateContext);
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
): [Sel, A];
|
|
49
|
-
export function useProvidedStateSubscription<V, A, Sel = V>(
|
|
50
|
-
selector: StateSelector<V, Sel> = identitySelector as StateSelector<V, Sel>,
|
|
51
|
-
isEqual: EqualityFn<Sel> = Object.is
|
|
52
|
-
) {
|
|
53
|
-
const stateHandler = useProvidedStateHandler<V, A>();
|
|
45
|
+
// If no state handler is found, it means the hook is being used incorrectly.
|
|
46
|
+
if (!stateHandler) {
|
|
47
|
+
throw new Error('useProvidedStateHandler must be used within a StateProvider');
|
|
48
|
+
}
|
|
54
49
|
|
|
55
|
-
return
|
|
50
|
+
// Cast and return the state handler instance.
|
|
51
|
+
return stateHandler as StateSubscriptionHandler<V, A>;
|
|
56
52
|
}
|
|
@@ -1,24 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility hook for subscribing to a state singleton within a component.
|
|
3
|
+
*/
|
|
1
4
|
import { useStateSubscription } from './state-subscription.js';
|
|
2
5
|
|
|
3
6
|
import type { StateSingleton } from '../../store/state-singleton.js';
|
|
4
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Type signatures for selector and equality functions.
|
|
10
|
+
*/
|
|
5
11
|
type StateSelector<State, SelectedState> = (state: State) => SelectedState;
|
|
6
12
|
type EqualityFn<SelectedState> = (current: SelectedState, next: SelectedState) => boolean;
|
|
7
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Default identity selector returns the whole state.
|
|
16
|
+
*/
|
|
8
17
|
const identitySelector = <State,>(state: State) => state;
|
|
9
18
|
|
|
10
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Singleton hook to subscribe to a state singleton.
|
|
21
|
+
*/
|
|
22
|
+
export function useStateSingleton<V, A>(
|
|
23
|
+
// The state singleton instance to subscribe to.
|
|
24
|
+
singleton: StateSingleton<V, A>
|
|
25
|
+
): [V, A];
|
|
11
26
|
export function useStateSingleton<V, A, Sel>(
|
|
12
|
-
|
|
27
|
+
// The state singleton instance to subscribe to.
|
|
28
|
+
singleton: StateSingleton<V, A>,
|
|
29
|
+
// Selector function to derive a specific value from the state.
|
|
13
30
|
selector: StateSelector<V, Sel>,
|
|
31
|
+
// Optional equality function to compare selected values.
|
|
14
32
|
isEqual?: EqualityFn<Sel>
|
|
15
33
|
): [Sel, A];
|
|
16
34
|
export function useStateSingleton<V, A, Sel = V>(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
35
|
+
// Implementation of the overloaded useStateSingleton hook.
|
|
36
|
+
singleton: StateSingleton<V, A>,
|
|
37
|
+
// Mixed argument: can be a selector or equality function.
|
|
38
|
+
selectorOrIsEqual: StateSelector<V, Sel> | EqualityFn<Sel> = identitySelector as StateSelector<
|
|
39
|
+
V,
|
|
40
|
+
Sel
|
|
41
|
+
>,
|
|
42
|
+
// Mixed argument: can be an equality function or undefined.
|
|
43
|
+
isEqual: EqualityFn<Sel> = Object.is as EqualityFn<Sel>
|
|
20
44
|
) {
|
|
21
|
-
|
|
45
|
+
// Determine whether a selector was provided as the first optional argument.
|
|
46
|
+
const hasSelector = typeof selectorOrIsEqual === 'function' && selectorOrIsEqual.length === 1;
|
|
47
|
+
// Fallback to identity selector if none is provided.
|
|
48
|
+
const selector = (hasSelector ? selectorOrIsEqual : identitySelector) as StateSelector<V, Sel>;
|
|
49
|
+
// Resolve the equality function to use.
|
|
50
|
+
const equalityFn = (hasSelector ? isEqual : selectorOrIsEqual) as EqualityFn<Sel>;
|
|
22
51
|
|
|
23
|
-
|
|
52
|
+
// Subscribe to the singleton state handler and return the selected state and actions.
|
|
53
|
+
return useStateSubscription(singleton, selector, equalityFn);
|
|
24
54
|
}
|