@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.
package/README.md CHANGED
@@ -1,194 +1,268 @@
1
1
  # @blac/react
2
2
 
3
- A powerful React integration for the Blac state management library, providing seamless integration between React components and Blac's reactive state management system.
4
-
5
- ## Features
6
-
7
- - 🔄 Automatic re-rendering when relevant state changes
8
- - 🎯 Fine-grained dependency tracking
9
- - 🔍 Property access tracking for optimized updates
10
- - 🎨 TypeScript support with full type inference
11
- - ⚡️ Efficient state management with minimal boilerplate
12
- - 🔄 Support for isolated and shared bloc instances
13
- - 🎯 Custom dependency selectors for precise control
14
-
15
- ## Important: Arrow Functions Required
16
-
17
- All methods in Bloc or Cubit classes must use arrow function syntax (`method = () => {}`) instead of the traditional method syntax (`method() {}`). This is because arrow functions automatically bind `this` to the class instance. Without this binding, methods called from React components would lose their context and could not access instance properties like `this.state` or `this.emit()`.
18
-
19
- ```tsx
20
- // Correct way to define methods in your Bloc/Cubit classes
21
- class CounterBloc extends Cubit<CounterState> {
22
- increment = () => {
23
- this.emit({ ...this.state, count: this.state.count + 1 });
24
- }
25
-
26
- decrement = () => {
27
- this.emit({ ...this.state, count: this.state.count - 1 });
28
- }
29
- }
30
-
31
- // Incorrect way (will cause issues when called from React):
32
- class CounterBloc extends Cubit<CounterState> {
33
- increment() { // ❌ Will lose 'this' context when called from components
34
- this.emit({ ...this.state, count: this.state.count + 1 });
35
- }
36
- }
37
- ```
3
+ React integration for the BlaC state management library with automatic re-render optimization.
38
4
 
39
5
  ## Installation
40
6
 
41
7
  ```bash
42
- npm install @blac/react
8
+ npm install @blac/react @blac/core
43
9
  # or
44
- yarn add @blac/react
10
+ pnpm add @blac/react @blac/core
45
11
  # or
46
- pnpm add @blac/react
12
+ yarn add @blac/react @blac/core
47
13
  ```
48
14
 
49
15
  ## Quick Start
50
16
 
51
17
  ```tsx
18
+ import { Cubit } from '@blac/core';
52
19
  import { useBloc } from '@blac/react';
53
- import { CounterBloc } from './CounterBloc';
20
+
21
+ class CounterCubit extends Cubit<{ count: number }> {
22
+ constructor() {
23
+ super({ count: 0 });
24
+ }
25
+
26
+ increment = () => this.emit({ count: this.state.count + 1 });
27
+ decrement = () => this.emit({ count: this.state.count - 1 });
28
+ }
54
29
 
55
30
  function Counter() {
56
- const [state, counterBloc] = useBloc(CounterBloc);
31
+ const [state, counter] = useBloc(CounterCubit);
57
32
 
58
33
  return (
59
34
  <div>
60
35
  <p>Count: {state.count}</p>
61
- <button onClick={() => counterBloc.increment()}>Increment</button>
36
+ <button onClick={counter.increment}>+</button>
37
+ <button onClick={counter.decrement}>-</button>
62
38
  </div>
63
39
  );
64
40
  }
65
41
  ```
66
42
 
67
- ## Usage
43
+ ## Hooks
44
+
45
+ ### useBloc
46
+
47
+ Connects a component to a state container with automatic re-renders on state changes.
48
+
49
+ ```tsx
50
+ const [state, bloc, ref] = useBloc(MyBloc);
51
+ ```
52
+
53
+ **Returns:** `[state, bloc, ref]`
54
+
55
+ - `state` - Current state (reactive)
56
+ - `bloc` - The bloc instance for calling methods
57
+ - `ref` - Component reference for isolated instances
68
58
 
69
- ### Basic Usage
59
+ #### Tracking Modes
70
60
 
71
- The `useBloc` hook provides a simple way to connect your React components to Blac's state management system:
61
+ **Auto-tracking (default):** Automatically detects which properties you access and only re-renders when those change.
72
62
 
73
63
  ```tsx
74
- const [state, bloc] = useBloc(YourBloc);
64
+ function UserProfile() {
65
+ const [state, user] = useBloc(UserBloc);
66
+
67
+ // Only re-renders when state.name changes
68
+ // (state.email changes won't cause re-render)
69
+ return <h1>{state.name}</h1>;
70
+ }
75
71
  ```
76
72
 
77
- ### Advanced Configuration
73
+ **Manual dependencies:** Explicit dependency array like `useEffect`.
78
74
 
79
- The hook accepts configuration options for more control:
75
+ ```tsx
76
+ function Counter() {
77
+ const [state] = useBloc(CounterBloc, {
78
+ dependencies: (state) => [state.count],
79
+ });
80
+
81
+ return <p>{state.count}</p>;
82
+ }
83
+ ```
84
+
85
+ **No tracking:** Returns full state, re-renders on any change.
80
86
 
81
87
  ```tsx
82
- const [state, bloc] = useBloc(YourBloc, {
83
- id: 'custom-id', // Optional: Custom identifier for the bloc
84
- props: { /* ... */ }, // Optional: Props to pass to the bloc
85
- onMount: (bloc) => { /* ... */ }, // Optional: Callback when bloc is mounted (similar to useEffect(<>, []))
86
- dependencySelector: (newState, oldState) => [/* ... */], // Optional: Custom dependency tracking
88
+ function FullState() {
89
+ const [state] = useBloc(MyBloc, { autoTrack: false });
90
+
91
+ return <pre>{JSON.stringify(state)}</pre>;
92
+ }
93
+ ```
94
+
95
+ #### Options
96
+
97
+ ```tsx
98
+ useBloc(MyBloc, {
99
+ // Tracking mode
100
+ autoTrack: true, // Enable/disable auto-tracking (default: true)
101
+ dependencies: (state) => [state.prop], // Manual dependencies
102
+
103
+ // Instance management
104
+ instanceId: 'unique-id', // Custom instance identifier
105
+
106
+ // Lifecycle callbacks
107
+ onMount: (bloc) => console.log('Mounted', bloc),
108
+ onUnmount: (bloc) => console.log('Unmounted', bloc),
87
109
  });
88
110
  ```
89
111
 
90
- ### Dependency Tracking
112
+ ### useBlocActions
91
113
 
92
- The hook automatically tracks which state properties are accessed in your component and only triggers re-renders when those specific properties change:
114
+ Connects to a state container without subscribing to state changes. Use for calling actions without re-renders, or for stateless containers.
93
115
 
94
116
  ```tsx
95
- function UserProfile() {
96
- const [state, userBloc] = useBloc(UserBloc);
117
+ const bloc = useBlocActions(MyBloc);
118
+ ```
97
119
 
98
- // Only re-renders when state.name changes
99
- return <h1>{state.name}</h1>;
120
+ **Use cases:**
121
+
122
+ ```tsx
123
+ // Stateless services (analytics, navigation, etc.)
124
+ function TrackButton() {
125
+ const analytics = useBlocActions(AnalyticsService);
126
+
127
+ return (
128
+ <button onClick={() => analytics.trackClick('button')}>Click me</button>
129
+ );
130
+ }
131
+
132
+ // Actions-only (no state subscription)
133
+ function IncrementButton() {
134
+ const counter = useBlocActions(CounterCubit);
135
+
136
+ return <button onClick={counter.increment}>+</button>;
100
137
  }
101
138
  ```
102
139
 
103
- This also works for getters on the Bloc or Cubit class:
140
+ #### Options
104
141
 
105
142
  ```tsx
106
- function UserProfile() {
107
- const [, userBloc] = useBloc(UserBloc);
143
+ useBlocActions(MyBloc, {
144
+ instanceId: 'unique-id', // Custom instance identifier
145
+ onMount: (bloc) => bloc.initialize(),
146
+ onUnmount: (bloc) => bloc.cleanup(),
147
+ });
148
+ ```
149
+
150
+ ## Instance Management
151
+
152
+ ### Shared Instances (Default)
108
153
 
109
- // Only re-renders when the return value from the getter formattedName changes
110
- return <h1>{userBloc.formattedName}</h1>;
154
+ By default, all components using the same bloc class share one instance:
155
+
156
+ ```tsx
157
+ function ComponentA() {
158
+ const [state] = useBloc(CounterCubit);
159
+ // Uses shared instance
160
+ }
161
+
162
+ function ComponentB() {
163
+ const [state] = useBloc(CounterCubit);
164
+ // Uses same shared instance
111
165
  }
112
166
  ```
113
167
 
114
- ### Custom Dependency Selector
168
+ ### Isolated Instances
115
169
 
116
- For more control over when your component re-renders, you can provide a custom dependency selector:
170
+ For component-scoped state, use the `@blac({ isolated: true })` decorator:
117
171
 
118
172
  ```tsx
119
- const [state, bloc] = useBloc(YourBloc, {
120
- dependencySelector: (newState, oldState) => [
121
- newState.specificField,
122
- newState.anotherField
123
- ]
173
+ import { Cubit, blac } from '@blac/core';
174
+
175
+ @blac({ isolated: true })
176
+ class FormCubit extends Cubit<FormState> {
177
+ // ...
178
+ }
179
+
180
+ function FormA() {
181
+ const [state] = useBloc(FormCubit);
182
+ // Gets its own instance
183
+ }
184
+
185
+ function FormB() {
186
+ const [state] = useBloc(FormCubit);
187
+ // Gets a different instance
188
+ }
189
+ ```
190
+
191
+ ### Named Instances
192
+
193
+ Share instances across specific components with `instanceId`:
194
+
195
+ ```tsx
196
+ function EditorA() {
197
+ const [state] = useBloc(EditorCubit, { instanceId: 'editor-1' });
198
+ }
199
+
200
+ function EditorB() {
201
+ const [state] = useBloc(EditorCubit, { instanceId: 'editor-1' });
202
+ // Same instance as EditorA
203
+ }
204
+
205
+ function EditorC() {
206
+ const [state] = useBloc(EditorCubit, { instanceId: 'editor-2' });
207
+ // Different instance
208
+ }
209
+ ```
210
+
211
+ ## Configuration
212
+
213
+ Configure global behavior:
214
+
215
+ ```tsx
216
+ import { configureBlacReact } from '@blac/react';
217
+
218
+ configureBlacReact({
219
+ autoTrack: true, // Enable auto-tracking by default (default: true)
124
220
  });
125
221
  ```
126
222
 
127
223
  ## API Reference
128
224
 
129
- ### useBloc Hook
225
+ ### useBloc
130
226
 
131
- ```typescript
132
- function useBloc<B extends BlocConstructor<BlocGeneric>>(
133
- bloc: B,
134
- options?: BlocHookOptions<InstanceType<B>>
135
- ): [BlocState<InstanceType<B>>, InstanceType<B>]
227
+ ```tsx
228
+ function useBloc<T extends StateContainerConstructor>(
229
+ BlocClass: T,
230
+ options?: UseBlocOptions<T>,
231
+ ): [ExtractState<T>, InstanceType<T>, ComponentRef];
136
232
  ```
137
233
 
138
- #### Options
234
+ ### useBlocActions
235
+
236
+ ```tsx
237
+ function useBlocActions<T extends StateContainerConstructor>(
238
+ BlocClass: T,
239
+ options?: UseBlocActionsOptions<InstanceType<T>>,
240
+ ): InstanceType<T>;
241
+ ```
242
+
243
+ ### UseBlocOptions
244
+
245
+ | Option | Type | Description |
246
+ | -------------- | ------------------ | ------------------------------------ |
247
+ | `autoTrack` | `boolean` | Enable auto-tracking (default: true) |
248
+ | `dependencies` | `(state) => any[]` | Manual dependency selector |
249
+ | `instanceId` | `string \| number` | Custom instance identifier |
250
+ | `onMount` | `(bloc) => void` | Called when component mounts |
251
+ | `onUnmount` | `(bloc) => void` | Called when component unmounts |
252
+
253
+ ### UseBlocActionsOptions
254
+
255
+ | Option | Type | Description |
256
+ | ------------ | ------------------ | ------------------------------ |
257
+ | `instanceId` | `string \| number` | Custom instance identifier |
258
+ | `onMount` | `(bloc) => void` | Called when component mounts |
259
+ | `onUnmount` | `(bloc) => void` | Called when component unmounts |
260
+
261
+ ## Compatibility
139
262
 
140
- - `id?: string` - Custom identifier for the bloc instance
141
- - `props?: InferPropsFromGeneric<B>` - Props to pass to the bloc
142
- - `onMount?: (bloc: B) => void` - Callback function invoked when the react component (the consumer) is connected to the bloc instance
143
- - `dependencySelector?: BlocHookDependencyArrayFn<B>` - Function to select dependencies for re-renders
144
-
145
- ## Best Practices
146
-
147
- 1. **Use Isolated Blocs**: When you need component-specific state, use isolated blocs:
148
- ```tsx
149
- class MyIsolatedBloc extends BlocBase {
150
- static isolated = true;
151
- // ... rest of your bloc implementation
152
- }
153
- ```
154
-
155
- 2. **Use Custom Identifiers**: When you need multiple independent instances of the same Bloc type, use custom identifiers to manage different state contexts:
156
- ```tsx
157
- // In a chat application with multiple chat rooms
158
- function ChatRoom({ roomId }: { roomId: string }) {
159
- const [state, chatBloc] = useBloc(ChatBloc, {
160
- id: `chat-${roomId}`, // Each room gets its own instance
161
- props: { roomId }
162
- });
163
-
164
- return (
165
- <div>
166
- <h2>Room: {roomId}</h2>
167
- {state.messages.map(msg => (
168
- <Message key={msg.id} message={msg} />
169
- ))}
170
- </div>
171
- );
172
- }
173
-
174
- // Usage:
175
- function ChatApp() {
176
- return (
177
- <div>
178
- <ChatRoom roomId="general" />
179
- <ChatRoom roomId="support" />
180
- </div>
181
- );
182
- }
183
- ```
184
-
185
- 3. **Optimize Re-renders**: Let the hook track dependencies automatically unless you have a specific need for custom dependency tracking.
186
-
187
- 4. **Type Safety**: Take advantage of TypeScript's type inference for better development experience and catch errors early.
188
-
189
- ## Contributing
190
-
191
- Contributions are welcome! Please feel free to submit a Pull Request.
263
+ - React 18.0+ or React 19.0+
264
+ - Works with React Compiler
265
+ - Supports concurrent rendering via `useSyncExternalStore`
192
266
 
193
267
  ## License
194
268
 
package/dist/index.cjs ADDED
@@ -0,0 +1,174 @@
1
+ let react = require("react");
2
+ let _blac_adapter = require("@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 = (0, _blac_adapter.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 = (0, react.useRef)({});
113
+ const isIsolated = (0, _blac_adapter.isIsolatedClass)(BlocClass);
114
+ const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] = (0, react.useMemo)(() => {
115
+ const instanceKey = generateInstanceKey(componentRef.current, isIsolated, options?.instanceId);
116
+ const instance = (0, _blac_adapter.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 = (0, _blac_adapter.manualDepsInit)(instance);
123
+ subscribeFn = (0, _blac_adapter.manualDepsSubscribe)(instance, adapterState, { dependencies: options.dependencies });
124
+ getSnapshotFn = (0, _blac_adapter.manualDepsSnapshot)(instance, adapterState, { dependencies: options.dependencies });
125
+ } else if (!autoTrackEnabled) {
126
+ adapterState = (0, _blac_adapter.noTrackInit)(instance);
127
+ subscribeFn = (0, _blac_adapter.noTrackSubscribe)(instance);
128
+ getSnapshotFn = (0, _blac_adapter.noTrackSnapshot)(instance);
129
+ } else {
130
+ adapterState = (0, _blac_adapter.autoTrackInit)(instance);
131
+ subscribeFn = (0, _blac_adapter.autoTrackSubscribe)(instance, adapterState);
132
+ getSnapshotFn = (0, _blac_adapter.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 = (0, react.useSyncExternalStore)(subscribe, getSnapshot);
148
+ const [, forceUpdate] = (0, react.useReducer)((x) => x + 1, 0);
149
+ const externalDepsManager = (0, react.useRef)(new _blac_adapter.ExternalDepsManager());
150
+ (0, react.useEffect)(() => {
151
+ (0, _blac_adapter.disableGetterTracking)(adapterState, rawInstance);
152
+ externalDepsManager.current.updateSubscriptions(adapterState.getterState, rawInstance, forceUpdate);
153
+ });
154
+ (0, react.useEffect)(() => {
155
+ if (options?.onMount) options.onMount(bloc);
156
+ return () => {
157
+ externalDepsManager.current.cleanup();
158
+ if (options?.onUnmount) options.onUnmount(bloc);
159
+ (0, _blac_adapter.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
+ exports.configureBlacReact = configureBlacReact;
172
+ exports.resetBlacReactConfig = resetBlacReactConfig;
173
+ exports.useBloc = useBloc;
174
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["ExternalDepsManager"],"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,2DAAwC;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,iCAAoC,EAAE,CAAC;CAC7C,MAAM,gDAA6B,UAAU;CAE7C,MAAM,CAAC,MAAM,WAAW,aAAa,aAAa,cAAc,wCAUtD;EACN,MAAM,cAAc,oBAClB,aAAa,SACb,YACA,SAAS,WACV;EAED,MAAM,sCAAmB,WAAW,YAAY;EAEhD,MAAM,EAAE,eAAe,qBACrB,sBAAsB,QAAQ;EAEhC,IAAI;EACJ,IAAI;EACJ,IAAI;AAEJ,MAAI,iBAAiB,SAAS,cAAc;AAC1C,oDAA8B,SAAS;AACvC,wDAAkC,UAAU,cAAc,EACxD,cAAc,QAAQ,cACvB,CAAC;AACF,yDAAmC,UAAU,cAAc,EACzD,cAAc,QAAQ,cACvB,CAAC;aACO,CAAC,kBAAkB;AAC5B,iDAA2B,SAAS;AACpC,qDAA+B,SAAS;AACxC,sDAAgC,SAAS;SACpC;AACL,mDAA6B,SAAS;AACtC,uDAAiC,UAAU,aAAa;AACxD,wDAAkC,UAAU,aAAa;;AAG3D,SAAO;GACL,aAAa;GACb;GACA;GACA;GACA;GACA;GACD;IACA;EAAC;EAAW;EAAY,SAAS;EAAW,CAAC;CAElD,MAAM,wCAA6B,WAAW,YAAY;CAE1D,MAAM,GAAG,sCAA2B,MAAc,IAAI,GAAG,EAAE;CAE3D,MAAM,wCAA6B,IAAIA,mCAAqB,CAAC;AAE7D,4BAAgB;AACd,2CAAsB,cAAc,YAAY;AAChD,sBAAoB,QAAQ,oBAC1B,aAAa,aACb,aACA,YACD;GACD;AAEF,4BAAgB;AACd,MAAI,SAAS,QACX,SAAQ,QAAQ,KAAwB;AAG1C,eAAa;AACX,uBAAoB,QAAQ,SAAS;AAErC,OAAI,SAAS,UACX,SAAQ,UAAU,KAAwB;AAG5C,8BAAQ,WAAW,YAAY;AAE/B,OAAI,cAAc,CAAC,YAAY,WAC7B,aAAY,SAAS;;IAIxB,EAAE,CAAC;AAEN,QAAO;EAAC;EAAO;EAAM;EAAa"}