@blac/react 2.0.0 → 2.0.3

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.
@@ -0,0 +1,100 @@
1
+ import type { ExtractState } from '@blac/core';
2
+ import { ExtractState as ExtractState_2 } from '@blac/adapter';
3
+ import { InstanceReadonlyState } from '@blac/core';
4
+ import type { RefObject } from 'react';
5
+ import { StateContainerConstructor } from '@blac/adapter';
6
+ import type { StateContainerConstructor as StateContainerConstructor_2 } from '@blac/core';
7
+
8
+ /**
9
+ * Global configuration for @blac/react
10
+ */
11
+ export declare interface BlacReactConfig {
12
+ /** Enable automatic property tracking via Proxy (default: true) */
13
+ autoTrack: boolean;
14
+ }
15
+
16
+ /* Excluded from this release type: ComponentRef */
17
+
18
+ /**
19
+ * Configure global defaults for @blac/react hooks.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * import { configureBlacReact } from '@blac/react';
24
+ *
25
+ * // Disable auto-tracking globally
26
+ * configureBlacReact({
27
+ * autoTrack: false
28
+ * });
29
+ * ```
30
+ *
31
+ * @param config - Partial configuration to merge with defaults
32
+ */
33
+ export declare function configureBlacReact(config: Partial<BlacReactConfig>): void;
34
+
35
+ /* Excluded from this release type: resetBlacReactConfig */
36
+
37
+ /**
38
+ * React hook that connects a component to a state container with automatic re-render on state changes.
39
+ *
40
+ * Supports three tracking modes:
41
+ * - **Auto-tracking** (default): Automatically detects accessed state properties via Proxy
42
+ * - **Manual dependencies**: Explicit dependency array like useEffect
43
+ * - **No tracking**: Returns full state without optimization
44
+ *
45
+ * @template T - The state container constructor type (inferred from BlocClass)
46
+ * @param BlocClass - The state container class to connect to
47
+ * @param options - Configuration options for tracking mode and instance management
48
+ * @returns Tuple with [state, bloc instance, ref]
49
+ *
50
+ * @example Basic usage
51
+ * ```ts
52
+ * const [state, myBloc, ref] = useBloc(MyBloc);
53
+ * ```
54
+ *
55
+ * @example With manual dependencies
56
+ * ```ts
57
+ * const [state, myBloc] = useBloc(MyBloc, {
58
+ * dependencies: (state) => [state.count]
59
+ * });
60
+ * ```
61
+ *
62
+ * @example With isolated instance
63
+ * ```ts
64
+ * const [state, myBloc] = useBloc(MyBloc, {
65
+ * instanceId: 'unique-id'
66
+ * });
67
+ * ```
68
+ */
69
+ export declare function useBloc<T extends StateContainerConstructor = StateContainerConstructor>(BlocClass: T, options?: UseBlocOptions<T>): UseBlocReturn<T, ExtractState_2<T>>;
70
+
71
+ /**
72
+ * Configuration options for useBloc hook
73
+ * @template TBloc - The state container type
74
+ */
75
+ export declare interface UseBlocOptions<TBloc extends StateContainerConstructor_2> {
76
+ /** Custom instance identifier for shared or isolated instances */
77
+ instanceId?: string | number;
78
+ /** Manual dependency array like useEffect (disables autoTrack) */
79
+ dependencies?: (state: ExtractState<TBloc>, bloc: InstanceReadonlyState<TBloc>) => unknown[];
80
+ /** Enable automatic property tracking via Proxy (default: true) */
81
+ autoTrack?: boolean;
82
+ /** Disable caching for getter tracking */
83
+ disableGetterCache?: boolean;
84
+ /** Callback invoked when bloc instance mounts */
85
+ onMount?: (bloc: InstanceType<TBloc>) => void;
86
+ /** Callback invoked when bloc instance unmounts */
87
+ onUnmount?: (bloc: InstanceType<TBloc>) => void;
88
+ }
89
+
90
+ /**
91
+ * Tuple return type from useBloc hook containing state, bloc instance, and ref
92
+ * - [0] Current state value (with optional state type override)
93
+ * - [1] State container instance (bloc) for calling actions
94
+ * - [2] Ref object for accessing component ref (advanced use cases)
95
+ *
96
+ * @template TBloc - The state container type
97
+ */
98
+ export declare type UseBlocReturn<TBloc extends StateContainerConstructor_2, S = ExtractState<TBloc>> = [S, InstanceReadonlyState<TBloc>, RefObject<ComponentRef>];
99
+
100
+ export { }
package/dist/index.d.ts CHANGED
@@ -1,99 +1,100 @@
1
- import { BlocBase } from '@blac/core';
2
- import { BlocConstructor } from '@blac/core';
3
- import { BlocHookDependencyArrayFn } from '@blac/core';
4
- import { BlocState } from '@blac/core';
5
- import { InferPropsFromGeneric } from '@blac/core';
1
+ import type { ExtractState } from '@blac/core';
2
+ import { ExtractState as ExtractState_2 } from '@blac/adapter';
3
+ import { InstanceReadonlyState } from '@blac/core';
4
+ import type { RefObject } from 'react';
5
+ import { StateContainerConstructor } from '@blac/adapter';
6
+ import type { StateContainerConstructor as StateContainerConstructor_2 } from '@blac/core';
6
7
 
7
8
  /**
8
- * Configuration options for the useBloc hook
9
- * @template B - Bloc generic type
10
- * @property {string} [id] - Optional identifier for the Bloc instance
11
- * @property {BlocHookDependencyArrayFn<B>} [dependencySelector] - Function to select dependencies for re-renders
12
- * @property {InferPropsFromGeneric<B>} [props] - Props to pass to the Bloc
13
- * @property {(bloc: B) => void} [onMount] - Callback function invoked when the Bloc is mounted
9
+ * Global configuration for @blac/react
14
10
  */
15
- declare interface BlocHookOptions<B extends BlocBase<any>> {
16
- id?: string;
17
- dependencySelector?: BlocHookDependencyArrayFn<BlocState<B>>;
18
- props?: InferPropsFromGeneric<B>;
19
- onMount?: (bloc: B) => void;
11
+ export declare interface BlacReactConfig {
12
+ /** Enable automatic property tracking via Proxy (default: true) */
13
+ autoTrack: boolean;
20
14
  }
21
15
 
16
+ /* Excluded from this release type: ComponentRef */
17
+
22
18
  /**
23
- * Creates an external store that wraps a Bloc instance, providing a React-compatible interface
24
- * for subscribing to and accessing bloc state.
19
+ * Configure global defaults for @blac/react hooks.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * import { configureBlacReact } from '@blac/react';
24
+ *
25
+ * // Disable auto-tracking globally
26
+ * configureBlacReact({
27
+ * autoTrack: false
28
+ * });
29
+ * ```
25
30
  *
26
- * @template B - The type of the Bloc instance
27
- * @template S - The type of the Bloc state
28
- * @param bloc - The Bloc instance to wrap
29
- * @param dependencyArray - Function that returns an array of dependencies for the subscription
30
- * @param rid - Unique identifier for the subscription
31
- * @returns An ExternalStore instance that provides methods to subscribe to and access bloc state
31
+ * @param config - Partial configuration to merge with defaults
32
32
  */
33
- export declare const externalBlocStore: <B extends InstanceType<BlocConstructor<any>>>(resolvedBloc: B, dependencyArray: BlocHookDependencyArrayFn<BlocState<B>>, rid: string) => ExternalStore<B>;
33
+ export declare function configureBlacReact(config: Partial<BlacReactConfig>): void;
34
+
35
+ /* Excluded from this release type: resetBlacReactConfig */
34
36
 
35
37
  /**
36
- * Interface defining an external store that can be used to subscribe to and access bloc state.
37
- * This interface follows the React external store pattern for state management.
38
+ * React hook that connects a component to a state container with automatic re-render on state changes.
39
+ *
40
+ * Supports three tracking modes:
41
+ * - **Auto-tracking** (default): Automatically detects accessed state properties via Proxy
42
+ * - **Manual dependencies**: Explicit dependency array like useEffect
43
+ * - **No tracking**: Returns full state without optimization
44
+ *
45
+ * @template T - The state container constructor type (inferred from BlocClass)
46
+ * @param BlocClass - The state container class to connect to
47
+ * @param options - Configuration options for tracking mode and instance management
48
+ * @returns Tuple with [state, bloc instance, ref]
49
+ *
50
+ * @example Basic usage
51
+ * ```ts
52
+ * const [state, myBloc, ref] = useBloc(MyBloc);
53
+ * ```
38
54
  *
39
- * @template B - The type of the Bloc instance
40
- * @template S - The type of the Bloc state
55
+ * @example With manual dependencies
56
+ * ```ts
57
+ * const [state, myBloc] = useBloc(MyBloc, {
58
+ * dependencies: (state) => [state.count]
59
+ * });
60
+ * ```
61
+ *
62
+ * @example With isolated instance
63
+ * ```ts
64
+ * const [state, myBloc] = useBloc(MyBloc, {
65
+ * instanceId: 'unique-id'
66
+ * });
67
+ * ```
41
68
  */
42
- declare interface ExternalStore<B extends InstanceType<BlocConstructor<any>>> {
43
- /**
44
- * Subscribes to changes in the store and returns an unsubscribe function.
45
- * @param onStoreChange - Callback function that will be called whenever the store changes
46
- * @returns A function that can be called to unsubscribe from store changes
47
- */
48
- subscribe: (onStoreChange: (state: BlocState<B>) => void) => () => void;
49
- /**
50
- * Gets the current snapshot of the store state.
51
- * @returns The current state of the store
52
- */
53
- getSnapshot: () => BlocState<B>;
54
- /**
55
- * Gets the server snapshot of the store state.
56
- * This is optional and defaults to the same value as getSnapshot.
57
- * @returns The server state of the store
58
- */
59
- getServerSnapshot?: () => BlocState<B>;
60
- }
69
+ export declare function useBloc<T extends StateContainerConstructor = StateContainerConstructor>(BlocClass: T, options?: UseBlocOptions<T>): UseBlocReturn<T, ExtractState_2<T>>;
61
70
 
62
71
  /**
63
- * Type definition for the return type of the useBloc hook
64
- * @template B - Bloc constructor type
72
+ * Configuration options for useBloc hook
73
+ * @template TBloc - The state container type
65
74
  */
66
- declare type HookTypes<B extends BlocConstructor<BlocBase<any>>> = [
67
- BlocState<InstanceType<B>>,
68
- InstanceType<B>
69
- ];
75
+ export declare interface UseBlocOptions<TBloc extends StateContainerConstructor_2> {
76
+ /** Custom instance identifier for shared or isolated instances */
77
+ instanceId?: string | number;
78
+ /** Manual dependency array like useEffect (disables autoTrack) */
79
+ dependencies?: (state: ExtractState<TBloc>, bloc: InstanceReadonlyState<TBloc>) => unknown[];
80
+ /** Enable automatic property tracking via Proxy (default: true) */
81
+ autoTrack?: boolean;
82
+ /** Disable caching for getter tracking */
83
+ disableGetterCache?: boolean;
84
+ /** Callback invoked when bloc instance mounts */
85
+ onMount?: (bloc: InstanceType<TBloc>) => void;
86
+ /** Callback invoked when bloc instance unmounts */
87
+ onUnmount?: (bloc: InstanceType<TBloc>) => void;
88
+ }
70
89
 
71
90
  /**
72
- * Default dependency selector that wraps the entire state in an array
73
- * @template T - State type
74
- * @param {T} s - Current state
75
- * @returns {Array<Array<T>>} Dependency array containing the entire state
76
- */
77
- /**
78
- * React hook for integrating with Blac state management
79
- *
80
- * This hook connects a React component to a Bloc state container, providing
81
- * automatic re-rendering when relevant state changes, dependency tracking,
82
- * and proper lifecycle management.
91
+ * Tuple return type from useBloc hook containing state, bloc instance, and ref
92
+ * - [0] Current state value (with optional state type override)
93
+ * - [1] State container instance (bloc) for calling actions
94
+ * - [2] Ref object for accessing component ref (advanced use cases)
83
95
  *
84
- * @template B - Bloc constructor type
85
- * @template O - BlocHookOptions type
86
- * @param {B} bloc - Bloc constructor class
87
- * @param {O} [options] - Configuration options for the hook
88
- * @returns {HookTypes<B>} Tuple containing [state, bloc instance]
89
- *
90
- * @example
91
- * const [state, counterBloc] = useBloc(CounterBloc);
92
- * // Access state
93
- * console.log(state.count);
94
- * // Call bloc methods
95
- * counterBloc.increment();
96
+ * @template TBloc - The state container type
96
97
  */
97
- export declare function useBloc<B extends BlocConstructor<BlocBase<any>>>(bloc: B, options?: BlocHookOptions<InstanceType<B>>): HookTypes<B>;
98
+ export declare type UseBlocReturn<TBloc extends StateContainerConstructor_2, S = ExtractState<TBloc>> = [S, InstanceReadonlyState<TBloc>, RefObject<ComponentRef>];
98
99
 
99
100
  export { }
package/dist/index.js ADDED
@@ -0,0 +1,172 @@
1
+ import { useEffect, useMemo, useReducer, useRef, useSyncExternalStore } from "react";
2
+ import { ExternalDepsManager, acquire, autoTrackInit, autoTrackSnapshot, autoTrackSubscribe, disableGetterTracking, generateIsolatedKey, isIsolatedClass, manualDepsInit, manualDepsSnapshot, manualDepsSubscribe, noTrackInit, noTrackSnapshot, noTrackSubscribe, release } from "@blac/adapter";
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 = getBlacReactConfig();
73
+ const autoTrackEnabled = options?.autoTrack !== void 0 ? options.autoTrack : globalConfig.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
+ * @template T - The state container constructor type (inferred from BlocClass)
88
+ * @param BlocClass - The state container class to connect to
89
+ * @param options - Configuration options for tracking mode and instance management
90
+ * @returns Tuple with [state, bloc instance, ref]
91
+ *
92
+ * @example Basic usage
93
+ * ```ts
94
+ * const [state, myBloc, ref] = useBloc(MyBloc);
95
+ * ```
96
+ *
97
+ * @example With manual dependencies
98
+ * ```ts
99
+ * const [state, myBloc] = useBloc(MyBloc, {
100
+ * dependencies: (state) => [state.count]
101
+ * });
102
+ * ```
103
+ *
104
+ * @example With isolated instance
105
+ * ```ts
106
+ * const [state, myBloc] = useBloc(MyBloc, {
107
+ * instanceId: 'unique-id'
108
+ * });
109
+ * ```
110
+ */
111
+ function useBloc(BlocClass, options) {
112
+ const componentRef = useRef({});
113
+ const isIsolated = isIsolatedClass(BlocClass);
114
+ const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] = useMemo(() => {
115
+ const instanceKey = generateInstanceKey(componentRef.current, isIsolated, options?.instanceId);
116
+ const instance = acquire(BlocClass, instanceKey);
117
+ const { useManualDeps, autoTrackEnabled } = determineTrackingMode(options);
118
+ let subscribeFn;
119
+ let getSnapshotFn;
120
+ let adapterState;
121
+ if (useManualDeps && options?.dependencies) {
122
+ adapterState = manualDepsInit(instance);
123
+ subscribeFn = manualDepsSubscribe(instance, adapterState, { dependencies: options.dependencies });
124
+ getSnapshotFn = manualDepsSnapshot(instance, adapterState, { dependencies: options.dependencies });
125
+ } else if (!autoTrackEnabled) {
126
+ adapterState = noTrackInit(instance);
127
+ subscribeFn = noTrackSubscribe(instance);
128
+ getSnapshotFn = noTrackSnapshot(instance);
129
+ } else {
130
+ adapterState = autoTrackInit(instance);
131
+ subscribeFn = autoTrackSubscribe(instance, adapterState);
132
+ getSnapshotFn = autoTrackSnapshot(instance, adapterState);
133
+ }
134
+ return [
135
+ adapterState.proxiedBloc,
136
+ subscribeFn,
137
+ getSnapshotFn,
138
+ instanceKey,
139
+ adapterState,
140
+ instance
141
+ ];
142
+ }, [
143
+ BlocClass,
144
+ isIsolated,
145
+ options?.instanceId
146
+ ]);
147
+ const state = useSyncExternalStore(subscribe, getSnapshot);
148
+ const [, forceUpdate] = useReducer((x) => x + 1, 0);
149
+ const externalDepsManager = useRef(new ExternalDepsManager());
150
+ useEffect(() => {
151
+ disableGetterTracking(adapterState, rawInstance);
152
+ externalDepsManager.current.updateSubscriptions(adapterState.getterState, rawInstance, forceUpdate);
153
+ });
154
+ useEffect(() => {
155
+ if (options?.onMount) options.onMount(bloc);
156
+ return () => {
157
+ externalDepsManager.current.cleanup();
158
+ if (options?.onUnmount) options.onUnmount(bloc);
159
+ release(BlocClass, instanceKey);
160
+ if (isIsolated && !rawInstance.isDisposed) rawInstance.dispose();
161
+ };
162
+ }, []);
163
+ return [
164
+ state,
165
+ bloc,
166
+ componentRef
167
+ ];
168
+ }
169
+
170
+ //#endregion
171
+ export { configureBlacReact, resetBlacReactConfig, useBloc };
172
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/utils/instance-keys.ts","../src/config.ts","../src/useBloc.ts"],"sourcesContent":["/**\n * Instance key generation utilities for React integration\n */\n\nimport { generateIsolatedKey } from '@blac/adapter';\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 ExternalDepsManager,\n autoTrackSubscribe,\n manualDepsSubscribe,\n noTrackSubscribe,\n autoTrackSnapshot,\n manualDepsSnapshot,\n noTrackSnapshot,\n autoTrackInit,\n manualDepsInit,\n noTrackInit,\n disableGetterTracking,\n isIsolatedClass,\n type StateContainerConstructor,\n type InstanceState,\n acquire,\n release,\n} from '@blac/adapter';\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 * 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 * @template T - The state container constructor type (inferred from BlocClass)\n * @param BlocClass - The state container class to connect to\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: T,\n options?: UseBlocOptions<T>,\n): UseBlocReturn<T, ExtractState<T>> {\n type TBloc = InstanceState<T>;\n\n const componentRef = useRef<ComponentRef>({});\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>,\n TBloc,\n ]\n >(() => {\n const instanceKey = generateInstanceKey(\n componentRef.current,\n isIsolated,\n options?.instanceId,\n );\n\n const instance = acquire(BlocClass, instanceKey) as TBloc;\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, 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 disableGetterTracking(adapterState, rawInstance);\n externalDepsManager.current.updateSubscriptions(\n adapterState.getterState,\n rawInstance,\n forceUpdate,\n );\n });\n\n useEffect(() => {\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"],"mappings":";;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,oBACd,cACA,YACA,YACoB;AAEpB,KAAI,eAAe,OACjB,QAAO,OAAO,eAAe,WAAW,OAAO,WAAW,GAAG;AAI/D,KAAI,YAAY;AACd,MAAI,CAAC,aAAa,iBAChB,cAAa,mBAAmB,qBAAqB;AAEvD,SAAO,aAAa;;;;;;AC1BxB,MAAM,gBAAiC,EACrC,WAAW,MACZ;AAED,IAAI,eAAgC,EAAE,GAAG,eAAe;;;;;;;;;;;;;;;;AAiBxD,SAAgB,mBAAmB,QAAwC;AACzE,gBAAe;EAAE,GAAG;EAAc,GAAG;EAAQ;;;;;;AAO/C,SAAgB,qBAAsC;AACpD,QAAO;;;;;;AAOT,SAAgB,uBAA6B;AAC3C,gBAAe,EAAE,GAAG,eAAe;;;;;ACXrC,SAAS,sBACP,SACc;CACd,MAAM,eAAe,oBAAoB;CACzC,MAAM,mBACJ,SAAS,cAAc,SACnB,QAAQ,YACR,aAAa;AAEnB,QAAO;EACL,eAAe,SAAS,iBAAiB;EACzC;EACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCH,SAAgB,QAGd,WACA,SACmC;CAGnC,MAAM,eAAe,OAAqB,EAAE,CAAC;CAC7C,MAAM,aAAa,gBAAgB,UAAU;CAE7C,MAAM,CAAC,MAAM,WAAW,aAAa,aAAa,cAAc,eAC9D,cASQ;EACN,MAAM,cAAc,oBAClB,aAAa,SACb,YACA,SAAS,WACV;EAED,MAAM,WAAW,QAAQ,WAAW,YAAY;EAEhD,MAAM,EAAE,eAAe,qBACrB,sBAAsB,QAAQ;EAEhC,IAAI;EACJ,IAAI;EACJ,IAAI;AAEJ,MAAI,iBAAiB,SAAS,cAAc;AAC1C,kBAAe,eAAe,SAAS;AACvC,iBAAc,oBAAoB,UAAU,cAAc,EACxD,cAAc,QAAQ,cACvB,CAAC;AACF,mBAAgB,mBAAmB,UAAU,cAAc,EACzD,cAAc,QAAQ,cACvB,CAAC;aACO,CAAC,kBAAkB;AAC5B,kBAAe,YAAY,SAAS;AACpC,iBAAc,iBAAiB,SAAS;AACxC,mBAAgB,gBAAgB,SAAS;SACpC;AACL,kBAAe,cAAc,SAAS;AACtC,iBAAc,mBAAmB,UAAU,aAAa;AACxD,mBAAgB,kBAAkB,UAAU,aAAa;;AAG3D,SAAO;GACL,aAAa;GACb;GACA;GACA;GACA;GACA;GACD;IACA;EAAC;EAAW;EAAY,SAAS;EAAW,CAAC;CAElD,MAAM,QAAQ,qBAAqB,WAAW,YAAY;CAE1D,MAAM,GAAG,eAAe,YAAY,MAAc,IAAI,GAAG,EAAE;CAE3D,MAAM,sBAAsB,OAAO,IAAI,qBAAqB,CAAC;AAE7D,iBAAgB;AACd,wBAAsB,cAAc,YAAY;AAChD,sBAAoB,QAAQ,oBAC1B,aAAa,aACb,aACA,YACD;GACD;AAEF,iBAAgB;AACd,MAAI,SAAS,QACX,SAAQ,QAAQ,KAAwB;AAG1C,eAAa;AACX,uBAAoB,QAAQ,SAAS;AAErC,OAAI,SAAS,UACX,SAAQ,UAAU,KAAwB;AAG5C,WAAQ,WAAW,YAAY;AAE/B,OAAI,cAAc,CAAC,YAAY,WAC7B,aAAY,SAAS;;IAIxB,EAAE,CAAC;AAEN,QAAO;EAAC;EAAO;EAAM;EAAa"}
package/package.json CHANGED
@@ -1,15 +1,38 @@
1
1
  {
2
2
  "name": "@blac/react",
3
- "version": "2.0.0",
3
+ "version": "2.0.3",
4
4
  "license": "MIT",
5
5
  "author": "Brendan Mullins <jsnanigans@gmail.com>",
6
- "main": "dist/index.cjs.js",
7
- "module": "dist/index.es.js",
8
- "typings": "dist/index.d.ts",
9
- "types": "dist/index.d.ts",
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
- "src",
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,42 +40,66 @@
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": {},
49
+ "dependencies": {
50
+ "@blac/adapter": "2.0.3"
51
+ },
28
52
  "peerDependencies": {
29
- "react": "^18.0.0",
30
- "@blac/core": "2.0.0"
53
+ "@types/react": "^18.0.0 || ^19.0.0",
54
+ "react": "^18.0.0 || ^19.0.0"
55
+ },
56
+ "peerDependenciesMeta": {
57
+ "@types/react": {
58
+ "optional": true
59
+ },
60
+ "@types/react-dom": {
61
+ "optional": true
62
+ }
31
63
  },
32
64
  "devDependencies": {
33
- "@testing-library/dom": "^10.4.0",
34
- "@testing-library/jest-dom": "^6.6.3",
65
+ "@testing-library/dom": "^10.4.1",
66
+ "@testing-library/jest-dom": "^6.6.4",
35
67
  "@testing-library/react": "^16.3.0",
36
68
  "@testing-library/user-event": "^14.6.1",
37
- "@types/react": "^18.3.3",
38
- "@vitejs/plugin-react": "^4.4.1",
39
- "@vitest/browser": "^3.1.3",
40
- "@vitest/coverage-v8": "^3.1.3",
41
- "happy-dom": "^17.4.7",
42
- "jsdom": "^24.1.1",
43
- "prettier": "^3.5.3",
44
- "react-dom": "^18.0.0",
45
- "typescript": "^5.8.3",
46
- "vite": "^5.3.1",
47
- "vite-plugin-dts": "^4.5.3",
48
- "vitest": "^1.6.0"
69
+ "@types/react": "^19.1.9",
70
+ "@vitejs/plugin-react": "^4.7.0",
71
+ "@vitest/browser": "^3.2.4",
72
+ "@vitest/coverage-v8": "^3.2.4",
73
+ "babel-plugin-react-compiler": "^1.0.0",
74
+ "eslint-plugin-react-compiler": "19.1.0-rc.2",
75
+ "happy-dom": "^18.0.1",
76
+ "jsdom": "^26.1.0",
77
+ "prettier": "^3.6.2",
78
+ "react": "^19.1.1",
79
+ "react-dom": "^19.1.1",
80
+ "tsdown": "^0.15.7",
81
+ "typescript": "^5.9.2",
82
+ "vitest": "3.2.4",
83
+ "@blac/core": "2.0.3"
49
84
  },
50
85
  "scripts": {
51
- "prettier": "prettier --write ./src",
86
+ "dev": "tsdown --watch",
87
+ "build": "pnpm build:js && pnpm build:types && pnpm build:dts",
88
+ "build:js": "tsdown",
89
+ "build:types": "tsc -p tsconfig.build.json",
90
+ "build:dts": "api-extractor run --local && cp dist/index.d.ts dist/index.d.cts && rm -rf dist/.types",
91
+ "clean": "rm -rf dist",
92
+ "format": "prettier --write \".\"",
93
+ "lint": "eslint src --ext .ts,.tsx",
94
+ "lint:fix": "eslint src --ext .ts,.tsx --fix",
52
95
  "test": "vitest run --config vitest.config.ts",
53
96
  "test:watch": "vitest --watch --config vitest.config.ts",
97
+ "test:memory": "NODE_OPTIONS='--expose-gc' vitest run --config vitest.config.performance.ts",
98
+ "test:performance": "vitest run --config vitest.config.performance.ts",
99
+ "test:compiler": "vitest run --config vitest.config.compiler.ts",
100
+ "test:watch:compiler": "vitest --watch --config vitest.config.compiler.ts",
101
+ "test:both": "pnpm test && echo '\n\n=== Running tests WITH React Compiler ===\n' && pnpm test:compiler",
54
102
  "typecheck": "tsc --noEmit",
55
- "build": "vite build",
56
- "deploy": "pnpm run build && pnpm publish --access public"
103
+ "deploy": "pnpm publish --access public"
57
104
  }
58
105
  }
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"),n=require("react"),E=(u,r,i)=>{const o=u;return{subscribe:B=>{const s=o._observer.subscribe({fn:()=>{try{B(o.state)}catch(l){console.error({e:l,resolvedBloc:u,dependencyArray:r})}},dependencyArray:r,id:i});return()=>{s()}},getSnapshot:()=>o.state,getServerSnapshot:()=>o.state}},M=E;function _(u,r){const{dependencySelector:i,id:o,props:B}=r??{},s=n.useMemo(()=>Math.random().toString(36),[]),l=n.useRef(new Set),g=n.useRef(new Set),R=n.useRef(new Set),h=n.useRef(new Set),I={},m=u.isolated?s:o,b=n.useRef(null);n.useMemo(()=>{b.current=S.Blac.getBloc(u,{id:m,props:B,instanceRef:s})},[u,m,s]);const t=b.current;if(!t)throw new Error(`useBloc: could not resolve bloc: ${u.name||u}`);s===t._instanceRef&&(r!=null&&r.props)&&S.Blac.instance.findRegisteredBlocInstance(u,m)===t&&t.props!==r.props&&(t.props=r.props);const p=(e,c)=>{if(i)return i(e,c);if(t.defaultDependencySelector)return t.defaultDependencySelector(e,c);if(typeof e!="object")return[[e]];const a=[];for(const d of l.current)d in e&&a.push(e[d]);const w=[];for(const d of R.current)if(d in t)try{const y=t[d];switch(typeof y){case"function":continue;default:w.push(y);continue}}catch(y){S.Blac.instance.log("useBloc Error",y)}return setTimeout(()=>{g.current=new Set,h.current=new Set}),[a,w]},v=n.useMemo(()=>M(t,p,s),[t,s]),f=n.useSyncExternalStore(v.subscribe,v.getSnapshot,v.getServerSnapshot),x=n.useMemo(()=>{try{if(typeof f=="object")return new Proxy(f,{get(e,c){return g.current.add(c),l.current.add(c),f[c]}})}catch(e){S.Blac.instance.log("useBloc Error",e)}return f},[f]),C=n.useMemo(()=>new Proxy(t,{get(e,c){const a=t[c];return typeof a!="function"&&h.current.add(c),a}}),[t]);return n.useLayoutEffect(()=>{l.current=new Set(g.current),R.current=new Set(h.current)},[I]),n.useEffect(()=>{var c;const e=b.current;if(e)return e._addConsumer(s),(c=r==null?void 0:r.onMount)==null||c.call(r,e),()=>{e._removeConsumer(s)}},[u,s]),[x,C]}exports.externalBlocStore=M;exports.useBloc=_;
2
- //# sourceMappingURL=index.cjs.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.cjs.js","sources":["../src/externalBlocStore.ts","../src/useBloc.tsx"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { BlocBase, BlocConstructor, BlocHookDependencyArrayFn, BlocState } from '@blac/core';\n\n/**\n * Interface defining an external store that can be used to subscribe to and access bloc state.\n * This interface follows the React external store pattern for state management.\n * \n * @template B - The type of the Bloc instance\n * @template S - The type of the Bloc state\n */\nexport interface ExternalStore<\n B extends InstanceType<BlocConstructor<any>>\n> {\n /**\n * Subscribes to changes in the store and returns an unsubscribe function.\n * @param onStoreChange - Callback function that will be called whenever the store changes\n * @returns A function that can be called to unsubscribe from store changes\n */\n subscribe: (onStoreChange: (state: BlocState<B>) => void) => () => void;\n\n /**\n * Gets the current snapshot of the store state.\n * @returns The current state of the store\n */\n getSnapshot: () => BlocState<B>;\n\n /**\n * Gets the server snapshot of the store state.\n * This is optional and defaults to the same value as getSnapshot.\n * @returns The server state of the store\n */\n getServerSnapshot?: () => BlocState<B>;\n}\n\n/**\n * Creates an external store that wraps a Bloc instance, providing a React-compatible interface\n * for subscribing to and accessing bloc state.\n * \n * @template B - The type of the Bloc instance\n * @template S - The type of the Bloc state\n * @param bloc - The Bloc instance to wrap\n * @param dependencyArray - Function that returns an array of dependencies for the subscription\n * @param rid - Unique identifier for the subscription\n * @returns An ExternalStore instance that provides methods to subscribe to and access bloc state\n */\nconst externalBlocStore = <\n B extends InstanceType<BlocConstructor<any>>\n>(\n resolvedBloc: B,\n dependencyArray: BlocHookDependencyArrayFn<BlocState<B>>,\n rid: string,\n): ExternalStore<B> => {\n // TODO: Revisit this type assertion. Ideally, 'resolvedBloc' should conform to a type\n // that guarantees '_observer' and 'state' properties without needing 'as unknown as ...'.\n // This might require adjustments in the core @blac/core types.\n const asBlocBase = resolvedBloc as unknown as BlocBase<BlocState<B>>;\n return {\n subscribe: (listener: (state: BlocState<B>) => void) => {\n // Subscribe to the bloc's observer with the provided listener function\n // This will trigger the callback whenever the bloc's state changes\n const unSub = asBlocBase._observer.subscribe({\n fn: () => {\n try {\n listener(asBlocBase.state);\n } catch (e) {\n // Log any errors that occur during the listener callback\n // This ensures errors in listeners don't break the entire application\n console.error({\n e,\n resolvedBloc,\n dependencyArray,\n });\n }\n },\n // Pass the dependency array to control when the subscription is updated\n dependencyArray,\n // Use the provided id to identify this subscription\n id: rid,\n });\n\n // Return an unsubscribe function that can be called to clean up the subscription\n return () => {\n unSub();\n };\n },\n // Return an immutable snapshot of the current bloc state\n getSnapshot: (): BlocState<B> => asBlocBase.state,\n // Server snapshot mirrors the client snapshot in this implementation\n getServerSnapshot: (): BlocState<B> => asBlocBase.state,\n };\n};\n\nexport default externalBlocStore;\n","/* eslint-disable @typescript-eslint/restrict-template-expressions */\n/* eslint-disable @typescript-eslint/no-explicit-any */\nimport {\n Blac,\n BlocBase,\n BlocBaseAbstract,\n BlocConstructor,\n BlocHookDependencyArrayFn,\n BlocState,\n InferPropsFromGeneric,\n} from '@blac/core';\nimport {\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useSyncExternalStore,\n} from 'react';\nimport externalBlocStore from './externalBlocStore';\n\n/**\n * Type definition for the return type of the useBloc hook\n * @template B - Bloc constructor type\n */\ntype HookTypes<B extends BlocConstructor<BlocBase<any>>> = [\n BlocState<InstanceType<B>>,\n InstanceType<B>,\n];\n\n/**\n * Configuration options for the useBloc hook\n * @template B - Bloc generic type\n * @property {string} [id] - Optional identifier for the Bloc instance\n * @property {BlocHookDependencyArrayFn<B>} [dependencySelector] - Function to select dependencies for re-renders\n * @property {InferPropsFromGeneric<B>} [props] - Props to pass to the Bloc\n * @property {(bloc: B) => void} [onMount] - Callback function invoked when the Bloc is mounted\n */\nexport interface BlocHookOptions<B extends BlocBase<any>> {\n id?: string;\n dependencySelector?: BlocHookDependencyArrayFn<BlocState<B>>;\n props?: InferPropsFromGeneric<B>;\n onMount?: (bloc: B) => void;\n}\n\n/**\n * Default dependency selector that wraps the entire state in an array\n * @template T - State type\n * @param {T} s - Current state\n * @returns {Array<Array<T>>} Dependency array containing the entire state\n */\n\n/**\n * React hook for integrating with Blac state management\n *\n * This hook connects a React component to a Bloc state container, providing\n * automatic re-rendering when relevant state changes, dependency tracking,\n * and proper lifecycle management.\n *\n * @template B - Bloc constructor type\n * @template O - BlocHookOptions type\n * @param {B} bloc - Bloc constructor class\n * @param {O} [options] - Configuration options for the hook\n * @returns {HookTypes<B>} Tuple containing [state, bloc instance]\n *\n * @example\n * const [state, counterBloc] = useBloc(CounterBloc);\n * // Access state\n * console.log(state.count);\n * // Call bloc methods\n * counterBloc.increment();\n */\nexport default function useBloc<B extends BlocConstructor<BlocBase<any>>>(\n bloc: B,\n options?: BlocHookOptions<InstanceType<B>>,\n): HookTypes<B> {\n const { dependencySelector, id: blocId, props } = options ?? {};\n const rid = useMemo(() => {\n return Math.random().toString(36);\n }, []);\n\n // Track used state keys\n const usedKeys = useRef<Set<string>>(new Set());\n const instanceKeys = useRef<Set<string>>(new Set());\n\n // Track used class properties\n const usedClassPropKeys = useRef<Set<string>>(new Set());\n const instanceClassPropKeys = useRef<Set<string>>(new Set());\n\n const renderInstance = {};\n\n // Determine ID for isolated or shared blocs\n const base = bloc as unknown as BlocBaseAbstract;\n const isIsolated = base.isolated;\n const effectiveBlocId = isIsolated ? rid : blocId;\n\n // Use useRef to hold the bloc instance reference consistently across renders\n const blocRef = useRef<InstanceType<B> | null>(null);\n\n // Initialize or get the bloc instance ONCE using useMemo based on the effective ID\n // This avoids re-running Blac.getBloc on every render.\n useMemo(() => {\n blocRef.current = Blac.getBloc(bloc, {\n id: effectiveBlocId,\n props,\n instanceRef: rid, // Pass component ID for consumer tracking\n });\n }, [bloc, effectiveBlocId, rid]); // Dependencies ensure this runs only when bloc type or ID changes\n\n const resolvedBloc = blocRef.current;\n\n if (!resolvedBloc) {\n // This should ideally not happen if Blac.getBloc works correctly\n throw new Error(`useBloc: could not resolve bloc: ${bloc.name || bloc}`);\n }\n\n // Update props ONLY if this hook instance created the bloc (or is the designated main instance)\n // We rely on Blac.getBloc to handle initial props correctly during creation.\n // Subsequent calls should not overwrite props.\n // Check if this instanceRef matches the one stored on the bloc when it was created/first retrieved.\n if (\n rid === resolvedBloc._instanceRef &&\n options?.props &&\n Blac.instance.findRegisteredBlocInstance(bloc, effectiveBlocId) ===\n resolvedBloc\n ) {\n // Avoid double-setting props if Blac.getBloc already set them during creation\n if (resolvedBloc.props !== options.props) {\n resolvedBloc.props = options.props;\n }\n }\n\n // Configure dependency tracking for re-renders\n const dependencyArray: BlocHookDependencyArrayFn<BlocState<InstanceType<B>>> = (\n newState,\n oldState,\n ): ReturnType<BlocHookDependencyArrayFn<BlocState<InstanceType<B>>>> => {\n // Use custom dependency selector if provided\n if (dependencySelector) {\n return dependencySelector(newState, oldState);\n }\n\n // Fall back to bloc's default dependency selector if available\n if (resolvedBloc.defaultDependencySelector) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return resolvedBloc.defaultDependencySelector(newState, oldState);\n }\n\n // For primitive states, use default selector\n if (typeof newState !== 'object') {\n // Default behavior for primitive states: re-render if the state itself changes.\n return [[newState]];\n }\n\n // For object states, track which properties were actually used\n const usedStateValues: string[] = [];\n for (const key of usedKeys.current) {\n if (key in newState) {\n usedStateValues.push(newState[key as keyof typeof newState]);\n }\n }\n\n // Track used class properties for dependency tracking, this enables rerenders when class getters change\n const usedClassValues: unknown[] = [];\n for (const key of usedClassPropKeys.current) {\n if (key in resolvedBloc) {\n try {\n const value = resolvedBloc[key as keyof InstanceType<B>];\n switch (typeof value) {\n case 'function':\n continue;\n default:\n usedClassValues.push(value);\n continue;\n }\n } catch (error) {\n Blac.instance.log('useBloc Error', error);\n }\n }\n }\n\n setTimeout(() => {\n instanceKeys.current = new Set();\n instanceClassPropKeys.current = new Set();\n });\n return [usedStateValues, usedClassValues];\n };\n\n // Set up external store subscription for state updates\n const store = useMemo(\n () => externalBlocStore(resolvedBloc, dependencyArray, rid),\n [resolvedBloc, rid],\n ); // dependencyArray removed as it changes frequently\n\n // Subscribe to state changes using React's external store API\n const state = useSyncExternalStore<BlocState<InstanceType<B>>>(\n store.subscribe,\n store.getSnapshot,\n store.getServerSnapshot,\n );\n\n // Create a proxy for state to track property access\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n const returnState: BlocState<InstanceType<B>> = useMemo(() => {\n try {\n if (typeof state === 'object') {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return new Proxy(state as any, {\n get(_, prop) {\n instanceKeys.current.add(prop as string);\n usedKeys.current.add(prop as string);\n const value = state[prop as keyof typeof state];\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return value;\n },\n });\n }\n } catch (error) {\n Blac.instance.log('useBloc Error', error);\n }\n return state;\n }, [state]);\n\n // Create a proxy for the bloc instance to track property access\n const returnClass = useMemo(() => {\n return new Proxy(resolvedBloc, {\n get(_, prop) {\n const value = resolvedBloc[prop as keyof InstanceType<B>];\n // Track which class properties are accessed (excluding methods)\n if (typeof value !== 'function') {\n instanceClassPropKeys.current.add(prop as string);\n }\n return value;\n },\n });\n }, [resolvedBloc]);\n\n // Clean up tracked keys after each render\n useLayoutEffect(() => {\n // inherit the keys from the previous render\n usedKeys.current = new Set(instanceKeys.current);\n usedClassPropKeys.current = new Set(instanceClassPropKeys.current);\n }, [renderInstance]);\n\n // Set up bloc lifecycle management\n useEffect(() => {\n const currentBlocInstance = blocRef.current; // Capture instance for cleanup\n if (!currentBlocInstance) return;\n\n currentBlocInstance._addConsumer(rid);\n\n // Call onMount callback if provided\n options?.onMount?.(currentBlocInstance);\n\n // Cleanup: remove this component as a consumer using the captured instance\n return () => {\n currentBlocInstance._removeConsumer(rid);\n };\n }, [bloc, rid]); // Do not add options.onMount to deps, it will cause a loop\n\n return [returnState, returnClass];\n}\n"],"names":["externalBlocStore","resolvedBloc","dependencyArray","rid","asBlocBase","listener","unSub","e","externalBlocStore$1","useBloc","bloc","options","dependencySelector","blocId","props","useMemo","usedKeys","useRef","instanceKeys","usedClassPropKeys","instanceClassPropKeys","renderInstance","effectiveBlocId","blocRef","Blac","newState","oldState","usedStateValues","key","usedClassValues","value","error","store","state","useSyncExternalStore","returnState","_","prop","returnClass","useLayoutEffect","useEffect","currentBlocInstance","_a"],"mappings":"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"}