@blac/react 2.0.0-rn-12 → 2.0.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/README.md +185 -177
- package/dist/index.cjs +260 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +165 -0
- package/dist/index.d.ts +151 -45
- package/dist/index.js +257 -0
- package/dist/index.js.map +1 -0
- package/package.json +57 -25
- package/dist/index.cjs.js +0 -2
- package/dist/index.cjs.js.map +0 -1
- package/dist/index.es.js +0 -111
- package/dist/index.es.js.map +0 -1
- package/src/__tests__/useBloc.props.test.tsx +0 -413
- package/src/__tests__/useBloc.proxyConfig.test.tsx +0 -228
- package/src/__tests__/useBloc.tracking.test.tsx +0 -198
- package/src/index.ts +0 -3
- package/src/useBloc.ts +0 -152
- package/src/useExternalBlocStore.ts +0 -140
package/dist/index.js
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { useEffect, useMemo, useReducer, useRef, useSyncExternalStore } from "react";
|
|
2
|
+
import { ExternalDepsManager, acquire, autoTrackInit, autoTrackSnapshot, autoTrackSubscribe, disableGetterTracking, generateIsolatedKey, isIsolatedClass, isStatelessClass, manualDepsInit, manualDepsSnapshot, manualDepsSubscribe, noTrackInit, noTrackSnapshot, noTrackSubscribe, release } from "@blac/core";
|
|
3
|
+
|
|
4
|
+
//#region src/utils/instance-keys.ts
|
|
5
|
+
/**
|
|
6
|
+
* Instance key generation utilities for React integration
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Generate an instance key for a bloc
|
|
10
|
+
*
|
|
11
|
+
* Logic:
|
|
12
|
+
* - If user provides instanceId, use it (convert number to string)
|
|
13
|
+
* - If isolated, generate or reuse a unique key for this component
|
|
14
|
+
* - Otherwise, return undefined (use default key)
|
|
15
|
+
*
|
|
16
|
+
* @param componentRef - React component reference (persists across remounts)
|
|
17
|
+
* @param isIsolated - Whether the bloc is isolated
|
|
18
|
+
* @param providedId - User-provided instance ID (from options)
|
|
19
|
+
* @returns Instance key string or undefined for default
|
|
20
|
+
*/
|
|
21
|
+
function generateInstanceKey(componentRef, isIsolated, providedId) {
|
|
22
|
+
if (providedId !== void 0) return typeof providedId === "number" ? String(providedId) : providedId;
|
|
23
|
+
if (isIsolated) {
|
|
24
|
+
if (!componentRef.__blocInstanceId) componentRef.__blocInstanceId = generateIsolatedKey();
|
|
25
|
+
return componentRef.__blocInstanceId;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/config.ts
|
|
31
|
+
const defaultConfig = { autoTrack: true };
|
|
32
|
+
let globalConfig = { ...defaultConfig };
|
|
33
|
+
/**
|
|
34
|
+
* Configure global defaults for @blac/react hooks.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* import { configureBlacReact } from '@blac/react';
|
|
39
|
+
*
|
|
40
|
+
* // Disable auto-tracking globally
|
|
41
|
+
* configureBlacReact({
|
|
42
|
+
* autoTrack: false
|
|
43
|
+
* });
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* @param config - Partial configuration to merge with defaults
|
|
47
|
+
*/
|
|
48
|
+
function configureBlacReact(config) {
|
|
49
|
+
globalConfig = {
|
|
50
|
+
...globalConfig,
|
|
51
|
+
...config
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Get the current global configuration.
|
|
56
|
+
* @internal
|
|
57
|
+
*/
|
|
58
|
+
function getBlacReactConfig() {
|
|
59
|
+
return globalConfig;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Reset configuration to defaults (useful for testing).
|
|
63
|
+
* @internal
|
|
64
|
+
*/
|
|
65
|
+
function resetBlacReactConfig() {
|
|
66
|
+
globalConfig = { ...defaultConfig };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/useBloc.ts
|
|
71
|
+
function determineTrackingMode(options) {
|
|
72
|
+
const globalConfig$1 = getBlacReactConfig();
|
|
73
|
+
const autoTrackEnabled = options?.autoTrack !== void 0 ? options.autoTrack : globalConfig$1.autoTrack;
|
|
74
|
+
return {
|
|
75
|
+
useManualDeps: options?.dependencies !== void 0,
|
|
76
|
+
autoTrackEnabled
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* React hook that connects a component to a state container with automatic re-render on state changes.
|
|
81
|
+
*
|
|
82
|
+
* Supports three tracking modes:
|
|
83
|
+
* - **Auto-tracking** (default): Automatically detects accessed state properties via Proxy
|
|
84
|
+
* - **Manual dependencies**: Explicit dependency array like useEffect
|
|
85
|
+
* - **No tracking**: Returns full state without optimization
|
|
86
|
+
*
|
|
87
|
+
* **Note:** This hook does NOT support stateless containers (StatelessCubit, StatelessVertex).
|
|
88
|
+
* Use `useBlocActions()` instead for stateless containers.
|
|
89
|
+
*
|
|
90
|
+
* @template T - The state container constructor type (inferred from BlocClass)
|
|
91
|
+
* @param BlocClass - The state container class to connect to (must not be stateless)
|
|
92
|
+
* @param options - Configuration options for tracking mode and instance management
|
|
93
|
+
* @returns Tuple with [state, bloc instance, ref]
|
|
94
|
+
*
|
|
95
|
+
* @example Basic usage
|
|
96
|
+
* ```ts
|
|
97
|
+
* const [state, myBloc, ref] = useBloc(MyBloc);
|
|
98
|
+
* ```
|
|
99
|
+
*
|
|
100
|
+
* @example With manual dependencies
|
|
101
|
+
* ```ts
|
|
102
|
+
* const [state, myBloc] = useBloc(MyBloc, {
|
|
103
|
+
* dependencies: (state) => [state.count]
|
|
104
|
+
* });
|
|
105
|
+
* ```
|
|
106
|
+
*
|
|
107
|
+
* @example With isolated instance
|
|
108
|
+
* ```ts
|
|
109
|
+
* const [state, myBloc] = useBloc(MyBloc, {
|
|
110
|
+
* instanceId: 'unique-id'
|
|
111
|
+
* });
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
function useBloc(BlocClass, options) {
|
|
115
|
+
const componentRef = useRef({});
|
|
116
|
+
const initialPropsRef = useRef(options?.props);
|
|
117
|
+
const isStateless = isStatelessClass(BlocClass);
|
|
118
|
+
const isIsolated = isIsolatedClass(BlocClass);
|
|
119
|
+
const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] = useMemo(() => {
|
|
120
|
+
if (isStateless) {
|
|
121
|
+
console.error(`[BlaC] useBloc() does not support stateless containers. "${BlocClass.name}" is a StatelessCubit or StatelessVertex. Use useBlocActions() instead for stateless containers.`);
|
|
122
|
+
const instance$1 = acquire(BlocClass, "default");
|
|
123
|
+
return [
|
|
124
|
+
instance$1,
|
|
125
|
+
() => () => {},
|
|
126
|
+
() => ({}),
|
|
127
|
+
void 0,
|
|
128
|
+
null,
|
|
129
|
+
instance$1
|
|
130
|
+
];
|
|
131
|
+
}
|
|
132
|
+
const instanceKey$1 = generateInstanceKey(componentRef.current, isIsolated, options?.instanceId);
|
|
133
|
+
const instance = acquire(BlocClass, instanceKey$1, { props: initialPropsRef.current });
|
|
134
|
+
if (initialPropsRef.current !== void 0) instance.updateProps(initialPropsRef.current);
|
|
135
|
+
const { useManualDeps, autoTrackEnabled } = determineTrackingMode(options);
|
|
136
|
+
let subscribeFn;
|
|
137
|
+
let getSnapshotFn;
|
|
138
|
+
let adapterState$1;
|
|
139
|
+
if (useManualDeps && options?.dependencies) {
|
|
140
|
+
adapterState$1 = manualDepsInit(instance);
|
|
141
|
+
subscribeFn = manualDepsSubscribe(instance, adapterState$1, { dependencies: options.dependencies });
|
|
142
|
+
getSnapshotFn = manualDepsSnapshot(instance, adapterState$1, { dependencies: options.dependencies });
|
|
143
|
+
} else if (!autoTrackEnabled) {
|
|
144
|
+
adapterState$1 = noTrackInit(instance);
|
|
145
|
+
subscribeFn = noTrackSubscribe(instance);
|
|
146
|
+
getSnapshotFn = noTrackSnapshot(instance);
|
|
147
|
+
} else {
|
|
148
|
+
adapterState$1 = autoTrackInit(instance);
|
|
149
|
+
subscribeFn = autoTrackSubscribe(instance, adapterState$1);
|
|
150
|
+
getSnapshotFn = autoTrackSnapshot(instance, adapterState$1);
|
|
151
|
+
}
|
|
152
|
+
return [
|
|
153
|
+
adapterState$1.proxiedBloc,
|
|
154
|
+
subscribeFn,
|
|
155
|
+
getSnapshotFn,
|
|
156
|
+
instanceKey$1,
|
|
157
|
+
adapterState$1,
|
|
158
|
+
instance
|
|
159
|
+
];
|
|
160
|
+
}, [
|
|
161
|
+
BlocClass,
|
|
162
|
+
isStateless,
|
|
163
|
+
isIsolated,
|
|
164
|
+
options?.instanceId
|
|
165
|
+
]);
|
|
166
|
+
const state = useSyncExternalStore(subscribe, getSnapshot);
|
|
167
|
+
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
|
168
|
+
const externalDepsManager = useRef(new ExternalDepsManager());
|
|
169
|
+
useEffect(() => {
|
|
170
|
+
if (isStateless) return;
|
|
171
|
+
if (options?.props !== initialPropsRef.current) rawInstance.updateProps(options?.props);
|
|
172
|
+
}, [
|
|
173
|
+
options?.props,
|
|
174
|
+
rawInstance,
|
|
175
|
+
isStateless
|
|
176
|
+
]);
|
|
177
|
+
useEffect(() => {
|
|
178
|
+
if (isStateless || !adapterState) return;
|
|
179
|
+
disableGetterTracking(adapterState, rawInstance);
|
|
180
|
+
externalDepsManager.current.updateSubscriptions(adapterState.getterState, rawInstance, forceUpdate);
|
|
181
|
+
});
|
|
182
|
+
useEffect(() => {
|
|
183
|
+
if (isStateless) return;
|
|
184
|
+
if (options?.onMount) options.onMount(bloc);
|
|
185
|
+
return () => {
|
|
186
|
+
externalDepsManager.current.cleanup();
|
|
187
|
+
if (options?.onUnmount) options.onUnmount(bloc);
|
|
188
|
+
release(BlocClass, instanceKey);
|
|
189
|
+
if (isIsolated && !rawInstance.isDisposed) rawInstance.dispose();
|
|
190
|
+
};
|
|
191
|
+
}, []);
|
|
192
|
+
return [
|
|
193
|
+
state,
|
|
194
|
+
bloc,
|
|
195
|
+
componentRef
|
|
196
|
+
];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
//#endregion
|
|
200
|
+
//#region src/useBlocActions.ts
|
|
201
|
+
/**
|
|
202
|
+
* React hook that connects to a state container instance without triggering re-renders.
|
|
203
|
+
* Use this when you only need to call actions on the bloc without subscribing to state changes.
|
|
204
|
+
*
|
|
205
|
+
* **This is the recommended hook for stateless containers** (StatelessCubit, StatelessVertex).
|
|
206
|
+
* It also works with regular Cubit/Vertex when you don't need state subscriptions.
|
|
207
|
+
*
|
|
208
|
+
* @template T - The state container constructor type (inferred from BlocClass)
|
|
209
|
+
* @param BlocClass - The state container class to connect to (supports both stateful and stateless)
|
|
210
|
+
* @param options - Configuration options for instance management and lifecycle
|
|
211
|
+
* @returns The state container instance for calling actions
|
|
212
|
+
*
|
|
213
|
+
* @example Basic usage with stateless container
|
|
214
|
+
* ```ts
|
|
215
|
+
* class AnalyticsService extends StatelessCubit {
|
|
216
|
+
* trackEvent(name: string) { ... }
|
|
217
|
+
* }
|
|
218
|
+
*
|
|
219
|
+
* const analytics = useBlocActions(AnalyticsService);
|
|
220
|
+
* analytics.trackEvent('page_view');
|
|
221
|
+
* ```
|
|
222
|
+
*
|
|
223
|
+
* @example With stateful container (no re-renders)
|
|
224
|
+
* ```ts
|
|
225
|
+
* const myBloc = useBlocActions(MyBloc);
|
|
226
|
+
* myBloc.someMethod(); // Won't cause re-renders
|
|
227
|
+
* ```
|
|
228
|
+
*
|
|
229
|
+
* @example With isolated instance
|
|
230
|
+
* ```ts
|
|
231
|
+
* const myBloc = useBlocActions(MyBloc, {
|
|
232
|
+
* instanceId: 'unique-id'
|
|
233
|
+
* });
|
|
234
|
+
* ```
|
|
235
|
+
*/
|
|
236
|
+
function useBlocActions(BlocClass, options) {
|
|
237
|
+
const componentRef = useRef({});
|
|
238
|
+
const initialPropsRef = useRef(options?.props);
|
|
239
|
+
const [bloc, instanceKey] = useMemo(() => {
|
|
240
|
+
const isIsolated = isIsolatedClass(BlocClass);
|
|
241
|
+
const instanceKey$1 = generateInstanceKey(componentRef.current, isIsolated, options?.instanceId);
|
|
242
|
+
return [acquire(BlocClass, instanceKey$1, { props: initialPropsRef.current }), instanceKey$1];
|
|
243
|
+
}, [BlocClass]);
|
|
244
|
+
useEffect(() => {
|
|
245
|
+
if (options?.onMount) options.onMount(bloc);
|
|
246
|
+
return () => {
|
|
247
|
+
if (options?.onUnmount) options.onUnmount(bloc);
|
|
248
|
+
release(BlocClass, instanceKey);
|
|
249
|
+
if (isIsolatedClass(BlocClass) && !bloc.isDisposed) bloc.dispose();
|
|
250
|
+
};
|
|
251
|
+
}, []);
|
|
252
|
+
return bloc;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
//#endregion
|
|
256
|
+
export { configureBlacReact, resetBlacReactConfig, useBloc, useBlocActions };
|
|
257
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["globalConfig","instance","instanceKey","adapterState","instanceKey"],"sources":["../src/utils/instance-keys.ts","../src/config.ts","../src/useBloc.ts","../src/useBlocActions.ts"],"sourcesContent":["/**\n * Instance key generation utilities for React integration\n */\n\nimport { generateIsolatedKey } from '@blac/core';\nimport type { ComponentRef } from '../types';\n\n/**\n * Generate an instance key for a bloc\n *\n * Logic:\n * - If user provides instanceId, use it (convert number to string)\n * - If isolated, generate or reuse a unique key for this component\n * - Otherwise, return undefined (use default key)\n *\n * @param componentRef - React component reference (persists across remounts)\n * @param isIsolated - Whether the bloc is isolated\n * @param providedId - User-provided instance ID (from options)\n * @returns Instance key string or undefined for default\n */\nexport function generateInstanceKey(\n componentRef: ComponentRef,\n isIsolated: boolean,\n providedId?: string | number,\n): string | undefined {\n // User explicitly provided an ID - use it\n if (providedId !== undefined) {\n return typeof providedId === 'number' ? String(providedId) : providedId;\n }\n\n // Isolated bloc - generate unique key per component\n if (isIsolated) {\n if (!componentRef.__blocInstanceId) {\n componentRef.__blocInstanceId = generateIsolatedKey();\n }\n return componentRef.__blocInstanceId;\n }\n\n // Shared bloc - use default key (undefined)\n return undefined;\n}\n","/**\n * Global configuration for @blac/react\n */\n\nexport interface BlacReactConfig {\n /** Enable automatic property tracking via Proxy (default: true) */\n autoTrack: boolean;\n}\n\nconst defaultConfig: BlacReactConfig = {\n autoTrack: true,\n};\n\nlet globalConfig: BlacReactConfig = { ...defaultConfig };\n\n/**\n * Configure global defaults for @blac/react hooks.\n *\n * @example\n * ```ts\n * import { configureBlacReact } from '@blac/react';\n *\n * // Disable auto-tracking globally\n * configureBlacReact({\n * autoTrack: false\n * });\n * ```\n *\n * @param config - Partial configuration to merge with defaults\n */\nexport function configureBlacReact(config: Partial<BlacReactConfig>): void {\n globalConfig = { ...globalConfig, ...config };\n}\n\n/**\n * Get the current global configuration.\n * @internal\n */\nexport function getBlacReactConfig(): BlacReactConfig {\n return globalConfig;\n}\n\n/**\n * Reset configuration to defaults (useful for testing).\n * @internal\n */\nexport function resetBlacReactConfig(): void {\n globalConfig = { ...defaultConfig };\n}\n","import {\n useMemo,\n useSyncExternalStore,\n useEffect,\n useRef,\n useReducer,\n} from 'react';\nimport {\n type ExtractState,\n type AdapterState,\n type IsStatelessContainer,\n ExternalDepsManager,\n autoTrackSubscribe,\n manualDepsSubscribe,\n noTrackSubscribe,\n autoTrackSnapshot,\n manualDepsSnapshot,\n noTrackSnapshot,\n autoTrackInit,\n manualDepsInit,\n noTrackInit,\n disableGetterTracking,\n isIsolatedClass,\n isStatelessClass,\n StateContainerConstructor,\n InstanceState,\n acquire,\n release,\n} from '@blac/core';\nimport type { UseBlocOptions, UseBlocReturn, ComponentRef } from './types';\nimport { generateInstanceKey } from './utils/instance-keys';\nimport { getBlacReactConfig } from './config';\n\ninterface TrackingMode {\n useManualDeps: boolean;\n autoTrackEnabled: boolean;\n}\n\nfunction determineTrackingMode<TBloc extends StateContainerConstructor>(\n options?: UseBlocOptions<TBloc>,\n): TrackingMode {\n const globalConfig = getBlacReactConfig();\n const autoTrackEnabled =\n options?.autoTrack !== undefined\n ? options.autoTrack\n : globalConfig.autoTrack;\n\n return {\n useManualDeps: options?.dependencies !== undefined,\n autoTrackEnabled,\n };\n}\n\n/**\n * Type that produces a TypeScript error when a stateless container is used.\n * Evaluates to `never` for stateless containers, causing a type mismatch.\n */\ntype StatefulContainer<T extends StateContainerConstructor> =\n IsStatelessContainer<T> extends true ? never : T;\n\n/**\n * React hook that connects a component to a state container with automatic re-render on state changes.\n *\n * Supports three tracking modes:\n * - **Auto-tracking** (default): Automatically detects accessed state properties via Proxy\n * - **Manual dependencies**: Explicit dependency array like useEffect\n * - **No tracking**: Returns full state without optimization\n *\n * **Note:** This hook does NOT support stateless containers (StatelessCubit, StatelessVertex).\n * Use `useBlocActions()` instead for stateless containers.\n *\n * @template T - The state container constructor type (inferred from BlocClass)\n * @param BlocClass - The state container class to connect to (must not be stateless)\n * @param options - Configuration options for tracking mode and instance management\n * @returns Tuple with [state, bloc instance, ref]\n *\n * @example Basic usage\n * ```ts\n * const [state, myBloc, ref] = useBloc(MyBloc);\n * ```\n *\n * @example With manual dependencies\n * ```ts\n * const [state, myBloc] = useBloc(MyBloc, {\n * dependencies: (state) => [state.count]\n * });\n * ```\n *\n * @example With isolated instance\n * ```ts\n * const [state, myBloc] = useBloc(MyBloc, {\n * instanceId: 'unique-id'\n * });\n * ```\n */\nexport function useBloc<\n T extends StateContainerConstructor = StateContainerConstructor,\n>(\n BlocClass: StatefulContainer<T>,\n options?: UseBlocOptions<T>,\n): UseBlocReturn<T, ExtractState<T>> {\n type TBloc = InstanceState<T>;\n\n const componentRef = useRef<ComponentRef>({});\n const initialPropsRef = useRef(options?.props);\n const isStateless = isStatelessClass(BlocClass);\n const isIsolated = isIsolatedClass(BlocClass);\n\n const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] =\n useMemo<\n readonly [\n TBloc,\n (callback: () => void) => () => void,\n () => ExtractState<T>,\n string | undefined,\n AdapterState<T> | null,\n TBloc,\n ]\n >(() => {\n // Runtime check for stateless containers - log error but don't crash\n if (isStateless) {\n console.error(\n `[BlaC] useBloc() does not support stateless containers. ` +\n `\"${BlocClass.name}\" is a StatelessCubit or StatelessVertex. ` +\n `Use useBlocActions() instead for stateless containers.`,\n );\n const instance = acquire(BlocClass, 'default') as TBloc;\n // Return minimal working functions that won't subscribe to anything\n return [\n instance,\n () => () => {},\n () => ({}) as ExtractState<T>,\n undefined,\n null,\n instance,\n ];\n }\n\n const instanceKey = generateInstanceKey(\n componentRef.current,\n isIsolated,\n options?.instanceId,\n );\n\n const instance = acquire(BlocClass, instanceKey, {\n props: initialPropsRef.current,\n }) as TBloc;\n\n if (initialPropsRef.current !== undefined) {\n instance.updateProps(initialPropsRef.current);\n }\n\n const { useManualDeps, autoTrackEnabled } =\n determineTrackingMode(options);\n\n let subscribeFn: (callback: () => void) => () => void;\n let getSnapshotFn: () => ExtractState<T>;\n let adapterState: AdapterState<T>;\n\n if (useManualDeps && options?.dependencies) {\n adapterState = manualDepsInit(instance);\n subscribeFn = manualDepsSubscribe(instance, adapterState, {\n dependencies: options.dependencies,\n });\n getSnapshotFn = manualDepsSnapshot(instance, adapterState, {\n dependencies: options.dependencies,\n });\n } else if (!autoTrackEnabled) {\n adapterState = noTrackInit(instance);\n subscribeFn = noTrackSubscribe(instance);\n getSnapshotFn = noTrackSnapshot(instance);\n } else {\n adapterState = autoTrackInit(instance);\n subscribeFn = autoTrackSubscribe(instance, adapterState);\n getSnapshotFn = autoTrackSnapshot(instance, adapterState);\n }\n\n return [\n adapterState.proxiedBloc as TBloc,\n subscribeFn,\n getSnapshotFn,\n instanceKey,\n adapterState,\n instance,\n ];\n }, [BlocClass, isStateless, isIsolated, options?.instanceId]);\n\n const state = useSyncExternalStore(subscribe, getSnapshot);\n\n const [, forceUpdate] = useReducer((x: number) => x + 1, 0);\n\n const externalDepsManager = useRef(new ExternalDepsManager());\n\n useEffect(() => {\n if (isStateless) return;\n if (options?.props !== initialPropsRef.current) {\n rawInstance.updateProps(options?.props);\n }\n }, [options?.props, rawInstance, isStateless]);\n\n useEffect(() => {\n if (isStateless || !adapterState) return;\n disableGetterTracking(adapterState, rawInstance);\n externalDepsManager.current.updateSubscriptions(\n adapterState.getterState,\n rawInstance,\n forceUpdate,\n );\n });\n\n useEffect(() => {\n if (isStateless) return;\n\n if (options?.onMount) {\n options.onMount(bloc as InstanceType<T>);\n }\n\n return () => {\n externalDepsManager.current.cleanup();\n\n if (options?.onUnmount) {\n options.onUnmount(bloc as InstanceType<T>);\n }\n\n release(BlocClass, instanceKey);\n\n if (isIsolated && !rawInstance.isDisposed) {\n rawInstance.dispose();\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return [state, bloc, componentRef] as UseBlocReturn<T, ExtractState<T>>;\n}\n","import { useMemo, useEffect, useRef } from 'react';\nimport {\n StateContainerConstructor,\n isIsolatedClass,\n acquire,\n release,\n} from '@blac/core';\nimport type { ComponentRef } from './types';\nimport { generateInstanceKey } from './utils/instance-keys';\n\n/**\n * Configuration options for useBlocActions hook\n * @template TBloc - The state container type\n * @template TProps - Props type passed to the container\n */\nexport interface UseBlocActionsOptions<TBloc, TProps = any> {\n /** Props passed to bloc constructor or updateProps */\n props?: TProps;\n /** Custom instance identifier for shared or isolated instances */\n instanceId?: string | number;\n /** Callback invoked when bloc instance mounts */\n onMount?: (bloc: TBloc) => void;\n /** Callback invoked when bloc instance unmounts */\n onUnmount?: (bloc: TBloc) => void;\n}\n\n/**\n * React hook that connects to a state container instance without triggering re-renders.\n * Use this when you only need to call actions on the bloc without subscribing to state changes.\n *\n * **This is the recommended hook for stateless containers** (StatelessCubit, StatelessVertex).\n * It also works with regular Cubit/Vertex when you don't need state subscriptions.\n *\n * @template T - The state container constructor type (inferred from BlocClass)\n * @param BlocClass - The state container class to connect to (supports both stateful and stateless)\n * @param options - Configuration options for instance management and lifecycle\n * @returns The state container instance for calling actions\n *\n * @example Basic usage with stateless container\n * ```ts\n * class AnalyticsService extends StatelessCubit {\n * trackEvent(name: string) { ... }\n * }\n *\n * const analytics = useBlocActions(AnalyticsService);\n * analytics.trackEvent('page_view');\n * ```\n *\n * @example With stateful container (no re-renders)\n * ```ts\n * const myBloc = useBlocActions(MyBloc);\n * myBloc.someMethod(); // Won't cause re-renders\n * ```\n *\n * @example With isolated instance\n * ```ts\n * const myBloc = useBlocActions(MyBloc, {\n * instanceId: 'unique-id'\n * });\n * ```\n */\nexport function useBlocActions<\n T extends StateContainerConstructor = StateContainerConstructor,\n>(\n BlocClass: T,\n options?: UseBlocActionsOptions<InstanceType<T>>,\n): InstanceType<T> {\n const componentRef = useRef<ComponentRef>({});\n const initialPropsRef = useRef(options?.props);\n\n const [bloc, instanceKey] = useMemo(() => {\n const isIsolated = isIsolatedClass(BlocClass);\n\n const instanceKey = generateInstanceKey(\n componentRef.current,\n isIsolated,\n options?.instanceId,\n );\n\n const instance = acquire(BlocClass, instanceKey, {\n props: initialPropsRef.current,\n });\n\n return [instance, instanceKey] as const;\n }, [BlocClass]);\n\n useEffect(() => {\n if (options?.onMount) {\n options.onMount(bloc);\n }\n\n return () => {\n if (options?.onUnmount) {\n options.onUnmount(bloc);\n }\n\n release(BlocClass, instanceKey);\n\n if (isIsolatedClass(BlocClass) && !bloc.isDisposed) {\n bloc.dispose();\n }\n };\n }, []);\n\n return bloc as InstanceType<T>;\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,15 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blac/react",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Brendan Mullins <jsnanigans@gmail.com>",
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/jsnanigans/blac.git",
|
|
9
|
+
"directory": "packages/blac-react"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/jsnanigans/blac#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/jsnanigans/blac/issues"
|
|
14
|
+
},
|
|
15
|
+
"main": "./dist/index.cjs",
|
|
16
|
+
"module": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"import": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"default": "./dist/index.js"
|
|
23
|
+
},
|
|
24
|
+
"require": {
|
|
25
|
+
"types": "./dist/index.d.cts",
|
|
26
|
+
"default": "./dist/index.cjs"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
},
|
|
10
30
|
"files": [
|
|
11
|
-
"dist",
|
|
12
|
-
"
|
|
31
|
+
"dist/index.js",
|
|
32
|
+
"dist/index.cjs",
|
|
33
|
+
"dist/index.d.ts",
|
|
34
|
+
"dist/index.d.cts",
|
|
35
|
+
"dist/*.map",
|
|
13
36
|
"README.md",
|
|
14
37
|
"LICENSE"
|
|
15
38
|
],
|
|
@@ -17,20 +40,16 @@
|
|
|
17
40
|
"keywords": [
|
|
18
41
|
"react",
|
|
19
42
|
"typescript",
|
|
20
|
-
"rxjs",
|
|
21
43
|
"state-management",
|
|
22
44
|
"reactjs",
|
|
23
45
|
"observer-pattern",
|
|
24
46
|
"bloc",
|
|
25
47
|
"bloc-pattern"
|
|
26
48
|
],
|
|
27
|
-
"dependencies": {},
|
|
28
49
|
"peerDependencies": {
|
|
29
50
|
"@types/react": "^18.0.0 || ^19.0.0",
|
|
30
|
-
"@types/react-dom": "^18.0.0 || ^19.0.0",
|
|
31
51
|
"react": "^18.0.0 || ^19.0.0",
|
|
32
|
-
"
|
|
33
|
-
"@blac/core": "2.0.0-rn-12"
|
|
52
|
+
"@blac/core": "2.0.1"
|
|
34
53
|
},
|
|
35
54
|
"peerDependenciesMeta": {
|
|
36
55
|
"@types/react": {
|
|
@@ -42,30 +61,43 @@
|
|
|
42
61
|
},
|
|
43
62
|
"devDependencies": {
|
|
44
63
|
"@testing-library/dom": "^10.4.1",
|
|
45
|
-
"@testing-library/jest-dom": "^6.6.
|
|
64
|
+
"@testing-library/jest-dom": "^6.6.4",
|
|
46
65
|
"@testing-library/react": "^16.3.0",
|
|
47
66
|
"@testing-library/user-event": "^14.6.1",
|
|
48
|
-
"@types/react": "^19.1.
|
|
67
|
+
"@types/react": "^19.1.9",
|
|
49
68
|
"@vitejs/plugin-react": "^4.7.0",
|
|
50
69
|
"@vitest/browser": "^3.2.4",
|
|
51
70
|
"@vitest/coverage-v8": "^3.2.4",
|
|
71
|
+
"babel-plugin-react-compiler": "^1.0.0",
|
|
72
|
+
"eslint-plugin-react-compiler": "19.1.0-rc.2",
|
|
52
73
|
"happy-dom": "^18.0.1",
|
|
53
|
-
"jsdom": "^
|
|
74
|
+
"jsdom": "^26.1.0",
|
|
54
75
|
"prettier": "^3.6.2",
|
|
55
|
-
"react": "^19.1.
|
|
56
|
-
"react-dom": "^19.1.
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
76
|
+
"react": "^19.1.1",
|
|
77
|
+
"react-dom": "^19.1.1",
|
|
78
|
+
"tsdown": "^0.15.7",
|
|
79
|
+
"typescript": "^5.9.2",
|
|
80
|
+
"vitest": "3.2.4",
|
|
81
|
+
"@blac/core": "2.0.1"
|
|
61
82
|
},
|
|
62
83
|
"scripts": {
|
|
63
|
-
"
|
|
64
|
-
"
|
|
84
|
+
"dev": "tsdown --watch",
|
|
85
|
+
"build": "pnpm build:js && pnpm build:types && pnpm build:dts",
|
|
86
|
+
"build:js": "tsdown",
|
|
87
|
+
"build:types": "tsc -p tsconfig.build.json",
|
|
88
|
+
"build:dts": "api-extractor run --local && cp dist/index.d.ts dist/index.d.cts && rm -rf dist/.types",
|
|
89
|
+
"clean": "rm -rf dist",
|
|
90
|
+
"format": "prettier --write \".\"",
|
|
91
|
+
"lint": "eslint src --ext .ts,.tsx",
|
|
92
|
+
"lint:fix": "eslint src --ext .ts,.tsx --fix",
|
|
65
93
|
"test": "vitest run --config vitest.config.ts",
|
|
66
94
|
"test:watch": "vitest --watch --config vitest.config.ts",
|
|
95
|
+
"test:memory": "NODE_OPTIONS='--expose-gc' vitest run --config vitest.config.performance.ts",
|
|
96
|
+
"test:performance": "vitest run --config vitest.config.performance.ts",
|
|
97
|
+
"test:compiler": "vitest run --config vitest.config.compiler.ts",
|
|
98
|
+
"test:watch:compiler": "vitest --watch --config vitest.config.compiler.ts",
|
|
99
|
+
"test:both": "pnpm test && echo '\n\n=== Running tests WITH React Compiler ===\n' && pnpm test:compiler",
|
|
67
100
|
"typecheck": "tsc --noEmit",
|
|
68
|
-
"
|
|
69
|
-
"deploy": "pnpm run build && pnpm publish --access public"
|
|
101
|
+
"deploy": "pnpm publish --access public"
|
|
70
102
|
}
|
|
71
103
|
}
|
package/dist/index.cjs.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const S=require("@blac/core"),r=require("react");function I(n,e){const b=r.useRef(0);b.current++;const l=r.useRef({}),c=r.useMemo(()=>new S.BlacAdapter({componentRef:l,blocConstructor:n},{id:e==null?void 0:e.id,dependencies:e==null?void 0:e.dependencies,props:e==null?void 0:e.props,onMount:e==null?void 0:e.onMount,onUnmount:e==null?void 0:e.onUnmount}),[]);c.resetConsumerTracking();const t=r.useRef(0);r.useEffect(()=>{t.current++,c.options={id:e==null?void 0:e.id,dependencies:e==null?void 0:e.dependencies,props:e==null?void 0:e.props,onMount:e==null?void 0:e.onMount,onUnmount:e==null?void 0:e.onUnmount},(e==null?void 0:e.props)!==void 0&&c.updateProps(e.props)},[e==null?void 0:e.props,e==null?void 0:e.id,e==null?void 0:e.dependencies,e==null?void 0:e.onMount,e==null?void 0:e.onUnmount]);const R=r.useRef(0);r.useEffect(()=>(R.current++,c.mount(),()=>{c.unmount()}),[c.blocInstance]);const B=r.useRef(0),u=r.useMemo(()=>(B.current++,d=>{const y=c.createSubscription({onChange:()=>{d()}});return()=>{y()}}),[c.blocInstance]),a=r.useRef(0),s=r.useRef(0),f=r.useSyncExternalStore(u,()=>(a.current++,c.blocInstance.state),()=>{const d=c.blocInstance;return s.current++,d.state}),m=r.useRef(0),M=r.useMemo(()=>(m.current++,c.getProxyState(f)),[f]),g=r.useRef(0),x=r.useMemo(()=>(g.current++,c.getProxyBlocInstance()),[c.blocInstance]);return r.useEffect(()=>{c.updateLastNotified(l.current)}),[M,x]}function U(n,e={}){const b=r.useRef(`external-${S.generateUUID()}`),l=r.useRef(new Set),c=r.useRef(new Set),t=r.useRef(null),R=r.useMemo(()=>{const u=S.Blac.getInstance();if(n.constructor.isolated||n.isolated){const s=new n(e==null?void 0:e.props),f=(e==null?void 0:e.id)||`${n.name}_${S.generateUUID()}`;return s._updateId(f),u.activateBloc(s),s}return u.getBloc(n,{id:e==null?void 0:e.id,props:e==null?void 0:e.props})},[n,e==null?void 0:e.id]),B=r.useMemo(()=>({getSnapshot:()=>{const u=t.current;return u?u.state:void 0},subscribe:u=>{const a=t.current;if(!a)return()=>{};const s=(m,M)=>{try{l.current.clear(),c.current.clear(),e.selector&&a&&e.selector(m,M,a),u.length>0?u(m):u()}catch(g){console.error("Listener error in useExternalBlocStore:",g)}};return a._observer.subscribe({id:b.current,fn:s})},getServerSnapshot:()=>{const u=t.current;return u?u.state:void 0}}),[]);return t.current=R,{externalStore:B,instance:t,usedKeys:l,usedClassPropKeys:c,rid:b.current}}exports.useBloc=I;exports.useBlocNext=I;exports.useExternalBlocStore=U;
|
|
2
|
-
//# sourceMappingURL=index.cjs.js.map
|
package/dist/index.cjs.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs.js","sources":["../src/useBloc.ts","../src/useExternalBlocStore.ts"],"sourcesContent":["import {\n AdapterOptions,\n BlacAdapter,\n BlocBase,\n BlocConstructor,\n BlocState,\n} from '@blac/core';\nimport { useEffect, useMemo, useRef, useSyncExternalStore } from 'react';\n\n/**\n * Type definition for the return type of the useBloc hook\n */\ntype HookTypes<B extends BlocConstructor<BlocBase<any>>> = [\n BlocState<InstanceType<B>>,\n InstanceType<B>,\n];\n\n/**\n * React hook for integrating with Blac state management\n */\nfunction useBloc<B extends BlocConstructor<BlocBase<any>>>(\n blocConstructor: B,\n options?: {\n props?: ConstructorParameters<B>[0];\n id?: string;\n dependencies?: (bloc: InstanceType<B>) => unknown[];\n onMount?: (bloc: InstanceType<B>) => void;\n onUnmount?: (bloc: InstanceType<B>) => void;\n },\n): HookTypes<B> {\n // Create a unique identifier for this hook instance\n const renderCount = useRef(0);\n renderCount.current++;\n\n const componentRef = useRef<object>({});\n\n // Track adapter creation\n const adapter = useMemo(() => {\n const newAdapter = new BlacAdapter<B>(\n {\n componentRef: componentRef,\n blocConstructor,\n },\n {\n id: options?.id,\n dependencies: options?.dependencies,\n props: options?.props,\n onMount: options?.onMount,\n onUnmount: options?.onUnmount,\n },\n );\n return newAdapter;\n }, []);\n\n // Reset tracking at the start of each render to ensure we only track\n // properties accessed during the current render\n adapter.resetConsumerTracking();\n\n // Track options changes and update props\n const optionsChangeCount = useRef(0);\n useEffect(() => {\n optionsChangeCount.current++;\n adapter.options = {\n id: options?.id,\n dependencies: options?.dependencies,\n props: options?.props,\n onMount: options?.onMount,\n onUnmount: options?.onUnmount,\n };\n\n if (options?.props !== undefined) {\n adapter.updateProps(options.props);\n }\n }, [options?.props, options?.id, options?.dependencies, options?.onMount, options?.onUnmount]);\n\n // Register as consumer and handle lifecycle\n const mountEffectCount = useRef(0);\n useEffect(() => {\n mountEffectCount.current++;\n adapter.mount();\n\n return () => {\n adapter.unmount();\n };\n }, [adapter.blocInstance]);\n\n // Subscribe to state changes using useSyncExternalStore\n const subscribeMemoCount = useRef(0);\n const subscribe = useMemo(() => {\n subscribeMemoCount.current++;\n let subscriptionCount = 0;\n\n return (onStoreChange: () => void) => {\n subscriptionCount++;\n const unsubscribe = adapter.createSubscription({\n onChange: () => {\n onStoreChange();\n },\n });\n\n return () => {\n unsubscribe();\n };\n };\n }, [adapter.blocInstance]);\n\n const snapshotCount = useRef(0);\n const serverSnapshotCount = useRef(0);\n\n const rawState: BlocState<InstanceType<B>> = useSyncExternalStore(\n subscribe,\n // Get snapshot\n () => {\n snapshotCount.current++;\n const bloc = adapter.blocInstance;\n const state = bloc.state;\n return state;\n },\n // Get server snapshot (same as client for now)\n () => {\n const bloc = adapter.blocInstance;\n serverSnapshotCount.current++;\n const state = bloc.state;\n return state;\n },\n );\n\n // Create proxies for fine-grained tracking (if enabled)\n const stateMemoCount = useRef(0);\n const finalState = useMemo(() => {\n stateMemoCount.current++;\n const proxyState = adapter.getProxyState(rawState);\n return proxyState;\n }, [rawState]);\n\n const blocMemoCount = useRef(0);\n const finalBloc = useMemo(() => {\n blocMemoCount.current++;\n const proxyBloc = adapter.getProxyBlocInstance();\n return proxyBloc;\n }, [adapter.blocInstance]);\n\n // Mark consumer as rendered after each render\n useEffect(() => {\n adapter.updateLastNotified(componentRef.current);\n });\n\n // Log final hook return\n return [finalState, finalBloc];\n}\n\nexport default useBloc;\n","import {\n Blac,\n BlocBase,\n BlocConstructor,\n BlocState,\n InferPropsFromGeneric,\n generateUUID,\n} from '@blac/core';\nimport { useCallback, useMemo, useRef } from 'react';\n\ninterface ExternalStoreOptions<B extends BlocBase<any>> {\n id?: string;\n props?: InferPropsFromGeneric<B>;\n selector?: (\n currentState: BlocState<B>,\n previousState: BlocState<B>,\n instance: B,\n ) => any[];\n}\n\ninterface ExternalStore<T> {\n getSnapshot: () => T | undefined;\n subscribe: (listener: () => void) => () => void;\n getServerSnapshot?: () => T | undefined;\n}\n\ninterface ExternalBlocStoreResult<B extends BlocBase<any>> {\n externalStore: ExternalStore<BlocState<B>>;\n instance: { current: B | null };\n usedKeys: { current: Set<string> };\n usedClassPropKeys: { current: Set<string> };\n rid: string;\n}\n\n/**\n * Hook for using an external bloc store\n * Provides low-level access to bloc instances for use with React's useSyncExternalStore\n */\nexport default function useExternalBlocStore<\n B extends BlocConstructor<BlocBase<any>>,\n>(\n blocConstructor: B,\n options: ExternalStoreOptions<InstanceType<B>> = {},\n): ExternalBlocStoreResult<InstanceType<B>> {\n const ridRef = useRef<string>(`external-${generateUUID()}`);\n const usedKeysRef = useRef<Set<string>>(new Set());\n const usedClassPropKeysRef = useRef<Set<string>>(new Set());\n const instanceRef = useRef<InstanceType<B> | null>(null);\n\n // Get or create bloc instance\n const bloc = useMemo(() => {\n const blac = Blac.getInstance();\n const base = blocConstructor as unknown as BlocBase<any>;\n\n // For isolated blocs, always create a new instance\n if (\n (base.constructor as any).isolated ||\n (blocConstructor as any).isolated\n ) {\n const newBloc = new blocConstructor(options?.props) as InstanceType<B>;\n const uniqueId =\n options?.id || `${blocConstructor.name}_${generateUUID()}`;\n newBloc._updateId(uniqueId);\n blac.activateBloc(newBloc);\n return newBloc;\n }\n\n // For shared blocs, use the existing getBloc logic\n return blac.getBloc(blocConstructor, {\n id: options?.id,\n props: options?.props,\n });\n }, [blocConstructor, options?.id]);\n\n // Create external store interface\n const externalStore = useMemo<ExternalStore<BlocState<InstanceType<B>>>>(\n () => ({\n getSnapshot: () => {\n const currentInstance = instanceRef.current;\n return currentInstance ? currentInstance.state : undefined;\n },\n subscribe: (listener: () => void) => {\n const currentInstance = instanceRef.current;\n if (!currentInstance) {\n // Return no-op unsubscribe if no instance\n return () => {};\n }\n\n // Wrap listener to handle errors gracefully\n const safeListener = (\n newState: BlocState<InstanceType<B>>,\n oldState: BlocState<InstanceType<B>>,\n ) => {\n try {\n // Reset tracking keys on each listener call\n usedKeysRef.current.clear();\n usedClassPropKeysRef.current.clear();\n\n // Call selector if provided\n if (options.selector && currentInstance) {\n options.selector(newState, oldState, currentInstance);\n }\n\n // Call listener with state if it expects it\n if (listener.length > 0) {\n (listener as any)(newState);\n } else {\n listener();\n }\n } catch (error) {\n console.error('Listener error in useExternalBlocStore:', error);\n // Don't rethrow to prevent breaking other listeners\n }\n };\n\n const unsubscribe = currentInstance._observer.subscribe({\n id: ridRef.current,\n fn: safeListener,\n });\n return unsubscribe;\n },\n getServerSnapshot: () => {\n const currentInstance = instanceRef.current;\n return currentInstance ? currentInstance.state : undefined;\n },\n }),\n [],\n );\n\n // Update instance ref\n instanceRef.current = bloc;\n\n return {\n externalStore,\n instance: instanceRef,\n usedKeys: usedKeysRef,\n usedClassPropKeys: usedClassPropKeysRef,\n rid: ridRef.current,\n };\n}\n"],"names":["useBloc","blocConstructor","options","renderCount","useRef","componentRef","adapter","useMemo","BlacAdapter","optionsChangeCount","useEffect","mountEffectCount","subscribeMemoCount","subscribe","onStoreChange","unsubscribe","snapshotCount","serverSnapshotCount","rawState","useSyncExternalStore","bloc","stateMemoCount","finalState","blocMemoCount","finalBloc","useExternalBlocStore","ridRef","generateUUID","usedKeysRef","usedClassPropKeysRef","instanceRef","blac","Blac","newBloc","uniqueId","externalStore","currentInstance","listener","safeListener","newState","oldState","error"],"mappings":"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"}
|
package/dist/index.es.js
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { BlacAdapter as v, generateUUID as h, Blac as w } from "@blac/core";
|
|
2
|
-
import { useRef as u, useMemo as b, useEffect as M, useSyncExternalStore as U } from "react";
|
|
3
|
-
function P(n, e) {
|
|
4
|
-
const m = u(0);
|
|
5
|
-
m.current++;
|
|
6
|
-
const f = u({}), r = b(() => new v(
|
|
7
|
-
{
|
|
8
|
-
componentRef: f,
|
|
9
|
-
blocConstructor: n
|
|
10
|
-
},
|
|
11
|
-
{
|
|
12
|
-
id: e == null ? void 0 : e.id,
|
|
13
|
-
dependencies: e == null ? void 0 : e.dependencies,
|
|
14
|
-
props: e == null ? void 0 : e.props,
|
|
15
|
-
onMount: e == null ? void 0 : e.onMount,
|
|
16
|
-
onUnmount: e == null ? void 0 : e.onUnmount
|
|
17
|
-
}
|
|
18
|
-
), []);
|
|
19
|
-
r.resetConsumerTracking();
|
|
20
|
-
const t = u(0);
|
|
21
|
-
M(() => {
|
|
22
|
-
t.current++, r.options = {
|
|
23
|
-
id: e == null ? void 0 : e.id,
|
|
24
|
-
dependencies: e == null ? void 0 : e.dependencies,
|
|
25
|
-
props: e == null ? void 0 : e.props,
|
|
26
|
-
onMount: e == null ? void 0 : e.onMount,
|
|
27
|
-
onUnmount: e == null ? void 0 : e.onUnmount
|
|
28
|
-
}, (e == null ? void 0 : e.props) !== void 0 && r.updateProps(e.props);
|
|
29
|
-
}, [e == null ? void 0 : e.props, e == null ? void 0 : e.id, e == null ? void 0 : e.dependencies, e == null ? void 0 : e.onMount, e == null ? void 0 : e.onUnmount]);
|
|
30
|
-
const x = u(0);
|
|
31
|
-
M(() => (x.current++, r.mount(), () => {
|
|
32
|
-
r.unmount();
|
|
33
|
-
}), [r.blocInstance]);
|
|
34
|
-
const B = u(0), c = b(() => (B.current++, (l) => {
|
|
35
|
-
const g = r.createSubscription({
|
|
36
|
-
onChange: () => {
|
|
37
|
-
l();
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
return () => {
|
|
41
|
-
g();
|
|
42
|
-
};
|
|
43
|
-
}), [r.blocInstance]), a = u(0), d = u(0), s = U(
|
|
44
|
-
c,
|
|
45
|
-
// Get snapshot
|
|
46
|
-
() => (a.current++, r.blocInstance.state),
|
|
47
|
-
// Get server snapshot (same as client for now)
|
|
48
|
-
() => {
|
|
49
|
-
const l = r.blocInstance;
|
|
50
|
-
return d.current++, l.state;
|
|
51
|
-
}
|
|
52
|
-
), S = u(0), I = b(() => (S.current++, r.getProxyState(s)), [s]), y = u(0), C = b(() => (y.current++, r.getProxyBlocInstance()), [r.blocInstance]);
|
|
53
|
-
return M(() => {
|
|
54
|
-
r.updateLastNotified(f.current);
|
|
55
|
-
}), [I, C];
|
|
56
|
-
}
|
|
57
|
-
function K(n, e = {}) {
|
|
58
|
-
const m = u(`external-${h()}`), f = u(/* @__PURE__ */ new Set()), r = u(/* @__PURE__ */ new Set()), t = u(null), x = b(() => {
|
|
59
|
-
const c = w.getInstance();
|
|
60
|
-
if (n.constructor.isolated || n.isolated) {
|
|
61
|
-
const d = new n(e == null ? void 0 : e.props), s = (e == null ? void 0 : e.id) || `${n.name}_${h()}`;
|
|
62
|
-
return d._updateId(s), c.activateBloc(d), d;
|
|
63
|
-
}
|
|
64
|
-
return c.getBloc(n, {
|
|
65
|
-
id: e == null ? void 0 : e.id,
|
|
66
|
-
props: e == null ? void 0 : e.props
|
|
67
|
-
});
|
|
68
|
-
}, [n, e == null ? void 0 : e.id]), B = b(
|
|
69
|
-
() => ({
|
|
70
|
-
getSnapshot: () => {
|
|
71
|
-
const c = t.current;
|
|
72
|
-
return c ? c.state : void 0;
|
|
73
|
-
},
|
|
74
|
-
subscribe: (c) => {
|
|
75
|
-
const a = t.current;
|
|
76
|
-
if (!a)
|
|
77
|
-
return () => {
|
|
78
|
-
};
|
|
79
|
-
const d = (S, I) => {
|
|
80
|
-
try {
|
|
81
|
-
f.current.clear(), r.current.clear(), e.selector && a && e.selector(S, I, a), c.length > 0 ? c(S) : c();
|
|
82
|
-
} catch (y) {
|
|
83
|
-
console.error("Listener error in useExternalBlocStore:", y);
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
return a._observer.subscribe({
|
|
87
|
-
id: m.current,
|
|
88
|
-
fn: d
|
|
89
|
-
});
|
|
90
|
-
},
|
|
91
|
-
getServerSnapshot: () => {
|
|
92
|
-
const c = t.current;
|
|
93
|
-
return c ? c.state : void 0;
|
|
94
|
-
}
|
|
95
|
-
}),
|
|
96
|
-
[]
|
|
97
|
-
);
|
|
98
|
-
return t.current = x, {
|
|
99
|
-
externalStore: B,
|
|
100
|
-
instance: t,
|
|
101
|
-
usedKeys: f,
|
|
102
|
-
usedClassPropKeys: r,
|
|
103
|
-
rid: m.current
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
export {
|
|
107
|
-
P as useBloc,
|
|
108
|
-
P as useBlocNext,
|
|
109
|
-
K as useExternalBlocStore
|
|
110
|
-
};
|
|
111
|
-
//# sourceMappingURL=index.es.js.map
|
package/dist/index.es.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.es.js","sources":["../src/useBloc.ts","../src/useExternalBlocStore.ts"],"sourcesContent":["import {\n AdapterOptions,\n BlacAdapter,\n BlocBase,\n BlocConstructor,\n BlocState,\n} from '@blac/core';\nimport { useEffect, useMemo, useRef, useSyncExternalStore } from 'react';\n\n/**\n * Type definition for the return type of the useBloc hook\n */\ntype HookTypes<B extends BlocConstructor<BlocBase<any>>> = [\n BlocState<InstanceType<B>>,\n InstanceType<B>,\n];\n\n/**\n * React hook for integrating with Blac state management\n */\nfunction useBloc<B extends BlocConstructor<BlocBase<any>>>(\n blocConstructor: B,\n options?: {\n props?: ConstructorParameters<B>[0];\n id?: string;\n dependencies?: (bloc: InstanceType<B>) => unknown[];\n onMount?: (bloc: InstanceType<B>) => void;\n onUnmount?: (bloc: InstanceType<B>) => void;\n },\n): HookTypes<B> {\n // Create a unique identifier for this hook instance\n const renderCount = useRef(0);\n renderCount.current++;\n\n const componentRef = useRef<object>({});\n\n // Track adapter creation\n const adapter = useMemo(() => {\n const newAdapter = new BlacAdapter<B>(\n {\n componentRef: componentRef,\n blocConstructor,\n },\n {\n id: options?.id,\n dependencies: options?.dependencies,\n props: options?.props,\n onMount: options?.onMount,\n onUnmount: options?.onUnmount,\n },\n );\n return newAdapter;\n }, []);\n\n // Reset tracking at the start of each render to ensure we only track\n // properties accessed during the current render\n adapter.resetConsumerTracking();\n\n // Track options changes and update props\n const optionsChangeCount = useRef(0);\n useEffect(() => {\n optionsChangeCount.current++;\n adapter.options = {\n id: options?.id,\n dependencies: options?.dependencies,\n props: options?.props,\n onMount: options?.onMount,\n onUnmount: options?.onUnmount,\n };\n\n if (options?.props !== undefined) {\n adapter.updateProps(options.props);\n }\n }, [options?.props, options?.id, options?.dependencies, options?.onMount, options?.onUnmount]);\n\n // Register as consumer and handle lifecycle\n const mountEffectCount = useRef(0);\n useEffect(() => {\n mountEffectCount.current++;\n adapter.mount();\n\n return () => {\n adapter.unmount();\n };\n }, [adapter.blocInstance]);\n\n // Subscribe to state changes using useSyncExternalStore\n const subscribeMemoCount = useRef(0);\n const subscribe = useMemo(() => {\n subscribeMemoCount.current++;\n let subscriptionCount = 0;\n\n return (onStoreChange: () => void) => {\n subscriptionCount++;\n const unsubscribe = adapter.createSubscription({\n onChange: () => {\n onStoreChange();\n },\n });\n\n return () => {\n unsubscribe();\n };\n };\n }, [adapter.blocInstance]);\n\n const snapshotCount = useRef(0);\n const serverSnapshotCount = useRef(0);\n\n const rawState: BlocState<InstanceType<B>> = useSyncExternalStore(\n subscribe,\n // Get snapshot\n () => {\n snapshotCount.current++;\n const bloc = adapter.blocInstance;\n const state = bloc.state;\n return state;\n },\n // Get server snapshot (same as client for now)\n () => {\n const bloc = adapter.blocInstance;\n serverSnapshotCount.current++;\n const state = bloc.state;\n return state;\n },\n );\n\n // Create proxies for fine-grained tracking (if enabled)\n const stateMemoCount = useRef(0);\n const finalState = useMemo(() => {\n stateMemoCount.current++;\n const proxyState = adapter.getProxyState(rawState);\n return proxyState;\n }, [rawState]);\n\n const blocMemoCount = useRef(0);\n const finalBloc = useMemo(() => {\n blocMemoCount.current++;\n const proxyBloc = adapter.getProxyBlocInstance();\n return proxyBloc;\n }, [adapter.blocInstance]);\n\n // Mark consumer as rendered after each render\n useEffect(() => {\n adapter.updateLastNotified(componentRef.current);\n });\n\n // Log final hook return\n return [finalState, finalBloc];\n}\n\nexport default useBloc;\n","import {\n Blac,\n BlocBase,\n BlocConstructor,\n BlocState,\n InferPropsFromGeneric,\n generateUUID,\n} from '@blac/core';\nimport { useCallback, useMemo, useRef } from 'react';\n\ninterface ExternalStoreOptions<B extends BlocBase<any>> {\n id?: string;\n props?: InferPropsFromGeneric<B>;\n selector?: (\n currentState: BlocState<B>,\n previousState: BlocState<B>,\n instance: B,\n ) => any[];\n}\n\ninterface ExternalStore<T> {\n getSnapshot: () => T | undefined;\n subscribe: (listener: () => void) => () => void;\n getServerSnapshot?: () => T | undefined;\n}\n\ninterface ExternalBlocStoreResult<B extends BlocBase<any>> {\n externalStore: ExternalStore<BlocState<B>>;\n instance: { current: B | null };\n usedKeys: { current: Set<string> };\n usedClassPropKeys: { current: Set<string> };\n rid: string;\n}\n\n/**\n * Hook for using an external bloc store\n * Provides low-level access to bloc instances for use with React's useSyncExternalStore\n */\nexport default function useExternalBlocStore<\n B extends BlocConstructor<BlocBase<any>>,\n>(\n blocConstructor: B,\n options: ExternalStoreOptions<InstanceType<B>> = {},\n): ExternalBlocStoreResult<InstanceType<B>> {\n const ridRef = useRef<string>(`external-${generateUUID()}`);\n const usedKeysRef = useRef<Set<string>>(new Set());\n const usedClassPropKeysRef = useRef<Set<string>>(new Set());\n const instanceRef = useRef<InstanceType<B> | null>(null);\n\n // Get or create bloc instance\n const bloc = useMemo(() => {\n const blac = Blac.getInstance();\n const base = blocConstructor as unknown as BlocBase<any>;\n\n // For isolated blocs, always create a new instance\n if (\n (base.constructor as any).isolated ||\n (blocConstructor as any).isolated\n ) {\n const newBloc = new blocConstructor(options?.props) as InstanceType<B>;\n const uniqueId =\n options?.id || `${blocConstructor.name}_${generateUUID()}`;\n newBloc._updateId(uniqueId);\n blac.activateBloc(newBloc);\n return newBloc;\n }\n\n // For shared blocs, use the existing getBloc logic\n return blac.getBloc(blocConstructor, {\n id: options?.id,\n props: options?.props,\n });\n }, [blocConstructor, options?.id]);\n\n // Create external store interface\n const externalStore = useMemo<ExternalStore<BlocState<InstanceType<B>>>>(\n () => ({\n getSnapshot: () => {\n const currentInstance = instanceRef.current;\n return currentInstance ? currentInstance.state : undefined;\n },\n subscribe: (listener: () => void) => {\n const currentInstance = instanceRef.current;\n if (!currentInstance) {\n // Return no-op unsubscribe if no instance\n return () => {};\n }\n\n // Wrap listener to handle errors gracefully\n const safeListener = (\n newState: BlocState<InstanceType<B>>,\n oldState: BlocState<InstanceType<B>>,\n ) => {\n try {\n // Reset tracking keys on each listener call\n usedKeysRef.current.clear();\n usedClassPropKeysRef.current.clear();\n\n // Call selector if provided\n if (options.selector && currentInstance) {\n options.selector(newState, oldState, currentInstance);\n }\n\n // Call listener with state if it expects it\n if (listener.length > 0) {\n (listener as any)(newState);\n } else {\n listener();\n }\n } catch (error) {\n console.error('Listener error in useExternalBlocStore:', error);\n // Don't rethrow to prevent breaking other listeners\n }\n };\n\n const unsubscribe = currentInstance._observer.subscribe({\n id: ridRef.current,\n fn: safeListener,\n });\n return unsubscribe;\n },\n getServerSnapshot: () => {\n const currentInstance = instanceRef.current;\n return currentInstance ? currentInstance.state : undefined;\n },\n }),\n [],\n );\n\n // Update instance ref\n instanceRef.current = bloc;\n\n return {\n externalStore,\n instance: instanceRef,\n usedKeys: usedKeysRef,\n usedClassPropKeys: usedClassPropKeysRef,\n rid: ridRef.current,\n };\n}\n"],"names":["useBloc","blocConstructor","options","renderCount","useRef","componentRef","adapter","useMemo","BlacAdapter","optionsChangeCount","useEffect","mountEffectCount","subscribeMemoCount","subscribe","onStoreChange","unsubscribe","snapshotCount","serverSnapshotCount","rawState","useSyncExternalStore","bloc","stateMemoCount","finalState","blocMemoCount","finalBloc","useExternalBlocStore","ridRef","generateUUID","usedKeysRef","usedClassPropKeysRef","instanceRef","blac","Blac","newBloc","uniqueId","externalStore","currentInstance","listener","safeListener","newState","oldState","error"],"mappings":"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"}
|