@blac/react 2.0.5 → 2.0.7

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,8 +1,8 @@
1
1
  # @blac/react
2
2
 
3
- > ⚠️ **Warning:** This project is currently under active development. The API may change in future releases. Use with caution in production environments.
3
+ React integration for BlaC with automatic re-render optimization.
4
4
 
5
- React integration for the BlaC state management library with automatic re-render optimization.
5
+ **[Full documentation](https://jsnanigans.github.io/blac/react/getting-started)**
6
6
 
7
7
  ## Installation
8
8
 
@@ -42,9 +42,7 @@ function Counter() {
42
42
  }
43
43
  ```
44
44
 
45
- ## Hooks
46
-
47
- ### useBloc
45
+ ## useBloc
48
46
 
49
47
  Connects a component to a state container with automatic re-renders on state changes.
50
48
 
@@ -54,29 +52,26 @@ const [state, bloc, ref] = useBloc(MyBloc);
54
52
 
55
53
  **Returns:** `[state, bloc, ref]`
56
54
 
57
- - `state` - Current state (reactive)
58
- - `bloc` - The bloc instance for calling methods
59
- - `ref` - Component reference for isolated instances
55
+ - `state` - Current state snapshot
56
+ - `bloc` - Bloc instance (proxied for tracking; call methods on it)
57
+ - `ref` - Internal component ref (advanced usage)
60
58
 
61
- #### Tracking Modes
59
+ ### Tracking Modes
62
60
 
63
- **Auto-tracking (default):** Automatically detects which properties you access and only re-renders when those change.
61
+ **Auto-tracking (default):** Tracks accessed properties and getters.
64
62
 
65
63
  ```tsx
66
64
  function UserProfile() {
67
- const [state, user] = useBloc(UserBloc);
68
-
69
- // Only re-renders when state.name changes
70
- // (state.email changes won't cause re-render)
65
+ const [state] = useBloc(UserBloc);
71
66
  return <h1>{state.name}</h1>;
72
67
  }
73
68
  ```
74
69
 
75
- **Manual dependencies:** Explicit dependency array like `useEffect`.
70
+ **Manual dependencies:** Provide a dependency selector (disables auto-tracking).
76
71
 
77
72
  ```tsx
78
73
  function Counter() {
79
- const [state] = useBloc(CounterBloc, {
74
+ const [state] = useBloc(CounterCubit, {
80
75
  dependencies: (state) => [state.count],
81
76
  });
82
77
 
@@ -84,187 +79,72 @@ function Counter() {
84
79
  }
85
80
  ```
86
81
 
87
- **No tracking:** Returns full state, re-renders on any change.
82
+ **No tracking:** Re-render on any state change.
88
83
 
89
84
  ```tsx
90
85
  function FullState() {
91
86
  const [state] = useBloc(MyBloc, { autoTrack: false });
92
-
93
87
  return <pre>{JSON.stringify(state)}</pre>;
94
88
  }
95
89
  ```
96
90
 
97
- #### Options
91
+ ### Options
98
92
 
99
93
  ```tsx
100
94
  useBloc(MyBloc, {
101
- // Tracking mode
102
- autoTrack: true, // Enable/disable auto-tracking (default: true)
103
- dependencies: (state) => [state.prop], // Manual dependencies
104
-
105
- // Instance management
106
- instanceId: 'unique-id', // Custom instance identifier
107
-
108
- // Lifecycle callbacks
95
+ autoTrack: true,
96
+ dependencies: (state, bloc) => [state.count, bloc],
97
+ instanceId: 'editor-1',
109
98
  onMount: (bloc) => console.log('Mounted', bloc),
110
99
  onUnmount: (bloc) => console.log('Unmounted', bloc),
111
100
  });
112
101
  ```
113
102
 
114
- ### useBlocActions
115
-
116
- Connects to a state container without subscribing to state changes. Use for calling actions without re-renders, or for stateless containers.
117
-
118
- ```tsx
119
- const bloc = useBlocActions(MyBloc);
120
- ```
121
-
122
- **Use cases:**
123
-
124
- ```tsx
125
- // Stateless services (analytics, navigation, etc.)
126
- function TrackButton() {
127
- const analytics = useBlocActions(AnalyticsService);
128
-
129
- return (
130
- <button onClick={() => analytics.trackClick('button')}>Click me</button>
131
- );
132
- }
133
-
134
- // Actions-only (no state subscription)
135
- function IncrementButton() {
136
- const counter = useBlocActions(CounterCubit);
137
-
138
- return <button onClick={counter.increment}>+</button>;
139
- }
140
- ```
141
-
142
- #### Options
143
-
144
- ```tsx
145
- useBlocActions(MyBloc, {
146
- instanceId: 'unique-id', // Custom instance identifier
147
- onMount: (bloc) => bloc.initialize(),
148
- onUnmount: (bloc) => bloc.cleanup(),
149
- });
150
- ```
103
+ | Option | Type | Description |
104
+ | -------------- | ---------------------------- | ------------------------------------ |
105
+ | `autoTrack` | `boolean` | Enable auto-tracking (default: true) |
106
+ | `dependencies` | `(state, bloc) => unknown[]` | Manual dependency selector |
107
+ | `instanceId` | `string \| number` | Custom instance identifier |
108
+ | `onMount` | `(bloc) => void` | Called when component mounts |
109
+ | `onUnmount` | `(bloc) => void` | Called when component unmounts |
151
110
 
152
111
  ## Instance Management
153
112
 
154
113
  ### Shared Instances (Default)
155
114
 
156
- By default, all components using the same bloc class share one instance:
157
-
158
- ```tsx
159
- function ComponentA() {
160
- const [state] = useBloc(CounterCubit);
161
- // Uses shared instance
162
- }
163
-
164
- function ComponentB() {
165
- const [state] = useBloc(CounterCubit);
166
- // Uses same shared instance
167
- }
168
- ```
169
-
170
- ### Isolated Instances
171
-
172
- For component-scoped state, use the `@blac({ isolated: true })` decorator:
173
-
174
115
  ```tsx
175
- import { Cubit, blac } from '@blac/core';
176
-
177
- @blac({ isolated: true })
178
- class FormCubit extends Cubit<FormState> {
179
- // ...
116
+ function A() {
117
+ useBloc(CounterCubit); // shared instance
180
118
  }
181
119
 
182
- function FormA() {
183
- const [state] = useBloc(FormCubit);
184
- // Gets its own instance
185
- }
186
-
187
- function FormB() {
188
- const [state] = useBloc(FormCubit);
189
- // Gets a different instance
120
+ function B() {
121
+ useBloc(CounterCubit); // same shared instance
190
122
  }
191
123
  ```
192
124
 
193
125
  ### Named Instances
194
126
 
195
- Share instances across specific components with `instanceId`:
196
-
197
127
  ```tsx
198
- function EditorA() {
199
- const [state] = useBloc(EditorCubit, { instanceId: 'editor-1' });
200
- }
201
-
202
- function EditorB() {
203
- const [state] = useBloc(EditorCubit, { instanceId: 'editor-1' });
204
- // Same instance as EditorA
205
- }
206
-
207
- function EditorC() {
208
- const [state] = useBloc(EditorCubit, { instanceId: 'editor-2' });
209
- // Different instance
210
- }
128
+ useBloc(EditorCubit, { instanceId: 'editor-1' });
129
+ useBloc(EditorCubit, { instanceId: 'editor-1' }); // same instance
130
+ useBloc(EditorCubit, { instanceId: 'editor-2' }); // different instance
211
131
  ```
212
132
 
213
133
  ## Configuration
214
134
 
215
- Configure global behavior:
135
+ Configure global defaults:
216
136
 
217
137
  ```tsx
218
138
  import { configureBlacReact } from '@blac/react';
219
139
 
220
140
  configureBlacReact({
221
- autoTrack: true, // Enable auto-tracking by default (default: true)
141
+ autoTrack: true,
222
142
  });
223
143
  ```
224
144
 
225
- ## API Reference
226
-
227
- ### useBloc
228
-
229
- ```tsx
230
- function useBloc<T extends StateContainerConstructor>(
231
- BlocClass: T,
232
- options?: UseBlocOptions<T>,
233
- ): [ExtractState<T>, InstanceType<T>, ComponentRef];
234
- ```
235
-
236
- ### useBlocActions
237
-
238
- ```tsx
239
- function useBlocActions<T extends StateContainerConstructor>(
240
- BlocClass: T,
241
- options?: UseBlocActionsOptions<InstanceType<T>>,
242
- ): InstanceType<T>;
243
- ```
244
-
245
- ### UseBlocOptions
246
-
247
- | Option | Type | Description |
248
- | -------------- | ------------------ | ------------------------------------ |
249
- | `autoTrack` | `boolean` | Enable auto-tracking (default: true) |
250
- | `dependencies` | `(state) => any[]` | Manual dependency selector |
251
- | `instanceId` | `string \| number` | Custom instance identifier |
252
- | `onMount` | `(bloc) => void` | Called when component mounts |
253
- | `onUnmount` | `(bloc) => void` | Called when component unmounts |
254
-
255
- ### UseBlocActionsOptions
256
-
257
- | Option | Type | Description |
258
- | ------------ | ------------------ | ------------------------------ |
259
- | `instanceId` | `string \| number` | Custom instance identifier |
260
- | `onMount` | `(bloc) => void` | Called when component mounts |
261
- | `onUnmount` | `(bloc) => void` | Called when component unmounts |
262
-
263
145
  ## Compatibility
264
146
 
265
- - React 18.0+ or React 19.0+
266
- - Works with React Compiler
267
- - Supports concurrent rendering via `useSyncExternalStore`
147
+ - React 18+ (uses `useSyncExternalStore`)
268
148
 
269
149
  ## License
270
150
 
package/dist/index.cjs CHANGED
@@ -1,35 +1,7 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
1
2
  let react = require("react");
2
3
  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 };
4
+ let globalConfig = { autoTrack: true };
33
5
  /**
34
6
  * Configure global defaults for @blac/react hooks.
35
7
  *
@@ -58,7 +30,6 @@ function configureBlacReact(config) {
58
30
  function getBlacReactConfig() {
59
31
  return globalConfig;
60
32
  }
61
-
62
33
  //#endregion
63
34
  //#region src/useBloc.ts
64
35
  function determineTrackingMode(options) {
@@ -94,7 +65,7 @@ function determineTrackingMode(options) {
94
65
  * });
95
66
  * ```
96
67
  *
97
- * @example With isolated instance
68
+ * @example With named instance
98
69
  * ```ts
99
70
  * const [state, myBloc] = useBloc(MyBloc, {
100
71
  * instanceId: 'unique-id'
@@ -103,18 +74,29 @@ function determineTrackingMode(options) {
103
74
  */
104
75
  function useBloc(BlocClass, options) {
105
76
  const componentRef = (0, react.useRef)({});
106
- const isIsolated = (0, _blac_adapter.isIsolatedClass)(BlocClass);
77
+ const depsRef = (0, react.useRef)(options?.dependencies);
78
+ depsRef.current = options?.dependencies;
79
+ const instanceId = options?.instanceId;
80
+ const autoTrack = options?.autoTrack;
81
+ const dependencies = options?.dependencies;
107
82
  const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] = (0, react.useMemo)(() => {
108
- const instanceKey = generateInstanceKey(componentRef.current, isIsolated, options?.instanceId);
83
+ const instanceKey = instanceId !== void 0 ? String(instanceId) : void 0;
109
84
  const instance = (0, _blac_adapter.acquire)(BlocClass, instanceKey);
110
- const { useManualDeps, autoTrackEnabled } = determineTrackingMode(options);
85
+ const { useManualDeps, autoTrackEnabled } = determineTrackingMode({
86
+ autoTrack,
87
+ dependencies
88
+ });
111
89
  let subscribeFn;
112
90
  let getSnapshotFn;
113
91
  let adapterState;
114
- if (useManualDeps && options?.dependencies) {
92
+ if (useManualDeps && dependencies) {
115
93
  adapterState = (0, _blac_adapter.manualDepsInit)(instance);
116
- subscribeFn = (0, _blac_adapter.manualDepsSubscribe)(instance, adapterState, { dependencies: options.dependencies });
117
- getSnapshotFn = (0, _blac_adapter.manualDepsSnapshot)(instance, adapterState, { dependencies: options.dependencies });
94
+ const stableConfig = { dependencies: (state, bloc) => {
95
+ const deps = depsRef.current;
96
+ return deps ? deps(state, bloc) : [];
97
+ } };
98
+ subscribeFn = (0, _blac_adapter.manualDepsSubscribe)(instance, adapterState, stableConfig);
99
+ getSnapshotFn = (0, _blac_adapter.manualDepsSnapshot)(instance, adapterState, stableConfig);
118
100
  } else if (!autoTrackEnabled) {
119
101
  adapterState = (0, _blac_adapter.noTrackInit)(instance);
120
102
  subscribeFn = (0, _blac_adapter.noTrackSubscribe)(instance);
@@ -124,33 +106,34 @@ function useBloc(BlocClass, options) {
124
106
  subscribeFn = (0, _blac_adapter.autoTrackSubscribe)(instance, adapterState);
125
107
  getSnapshotFn = (0, _blac_adapter.autoTrackSnapshot)(instance, adapterState);
126
108
  }
109
+ const safeSubscribeFn = (callback) => {
110
+ if (instance.isDisposed) return () => {};
111
+ return subscribeFn(callback);
112
+ };
127
113
  return [
128
114
  adapterState.proxiedBloc,
129
- subscribeFn,
115
+ safeSubscribeFn,
130
116
  getSnapshotFn,
131
117
  instanceKey,
132
118
  adapterState,
133
119
  instance
134
120
  ];
135
- }, [
136
- BlocClass,
137
- isIsolated,
138
- options?.instanceId
139
- ]);
140
- const state = (0, react.useSyncExternalStore)(subscribe, getSnapshot);
121
+ }, [BlocClass, instanceId]);
122
+ const state = (0, react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
141
123
  const [, forceUpdate] = (0, react.useReducer)((x) => x + 1, 0);
142
124
  const externalDepsManager = (0, react.useRef)(new _blac_adapter.ExternalDepsManager());
143
125
  (0, react.useEffect)(() => {
126
+ const manager = externalDepsManager.current;
144
127
  (0, _blac_adapter.disableGetterTracking)(adapterState, rawInstance);
145
- externalDepsManager.current.updateSubscriptions(adapterState.getterState, rawInstance, forceUpdate);
128
+ manager.updateSubscriptions(adapterState.getterState, rawInstance, forceUpdate);
146
129
  });
147
130
  (0, react.useEffect)(() => {
131
+ const manager = externalDepsManager.current;
148
132
  if (options?.onMount) options.onMount(bloc);
149
133
  return () => {
150
- externalDepsManager.current.cleanup();
134
+ manager.cleanup();
151
135
  if (options?.onUnmount) options.onUnmount(bloc);
152
136
  (0, _blac_adapter.release)(BlocClass, instanceKey);
153
- if (isIsolated && !rawInstance.isDisposed) rawInstance.dispose();
154
137
  };
155
138
  }, []);
156
139
  return [
@@ -159,8 +142,8 @@ function useBloc(BlocClass, options) {
159
142
  componentRef
160
143
  ];
161
144
  }
162
-
163
145
  //#endregion
164
146
  exports.configureBlacReact = configureBlacReact;
165
147
  exports.useBloc = useBloc;
148
+
166
149
  //# sourceMappingURL=index.cjs.map
@@ -1 +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;;;;;ACHT,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"}
1
+ {"version":3,"file":"index.cjs","names":["ExternalDepsManager"],"sources":["../src/config.ts","../src/useBloc.ts"],"sourcesContent":["/**\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 type StateContainerConstructor,\n type InstanceState,\n acquire,\n release,\n} from '@blac/adapter';\nimport type { UseBlocOptions, UseBlocReturn, ComponentRef } from './types';\nimport { getBlacReactConfig } from './config';\n\ninterface TrackingMode {\n useManualDeps: boolean;\n autoTrackEnabled: boolean;\n}\n\nfunction determineTrackingMode<TBloc extends StateContainerConstructor>(\n options?: Pick<UseBlocOptions<TBloc>, 'autoTrack' | 'dependencies'>,\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 named 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 depsRef = useRef(options?.dependencies);\n depsRef.current = options?.dependencies;\n const instanceId = options?.instanceId;\n const autoTrack = options?.autoTrack;\n const dependencies = options?.dependencies;\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 =\n instanceId !== undefined ? String(instanceId) : undefined;\n\n const instance = acquire(BlocClass, instanceKey) as TBloc;\n\n const { useManualDeps, autoTrackEnabled } = determineTrackingMode({\n autoTrack,\n dependencies,\n });\n\n let subscribeFn: (callback: () => void) => () => void;\n let getSnapshotFn: () => ExtractState<T>;\n let adapterState: AdapterState<T>;\n\n if (useManualDeps && dependencies) {\n adapterState = manualDepsInit(instance);\n const stableConfig = {\n dependencies: (state: ExtractState<T>, bloc: InstanceState<T>) => {\n const deps = depsRef.current;\n return deps ? deps(state, bloc) : [];\n },\n };\n subscribeFn = manualDepsSubscribe(instance, adapterState, stableConfig);\n getSnapshotFn = manualDepsSnapshot(\n instance,\n adapterState,\n stableConfig,\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 const safeSubscribeFn = (callback: () => void) => {\n if (instance.isDisposed) return () => {};\n return subscribeFn(callback);\n };\n\n return [\n adapterState.proxiedBloc as TBloc,\n safeSubscribeFn,\n getSnapshotFn,\n instanceKey,\n adapterState,\n instance,\n ];\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [BlocClass, instanceId]);\n\n const state = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n const [, forceUpdate] = useReducer((x: number) => x + 1, 0);\n\n const externalDepsManager = useRef(new ExternalDepsManager());\n\n useEffect(() => {\n const manager = externalDepsManager.current;\n disableGetterTracking(adapterState, rawInstance);\n manager.updateSubscriptions(\n adapterState.getterState,\n rawInstance,\n forceUpdate,\n );\n });\n\n useEffect(() => {\n const manager = externalDepsManager.current;\n if (options?.onMount) {\n options.onMount(bloc as InstanceType<T>);\n }\n\n return () => {\n manager.cleanup();\n\n if (options?.onUnmount) {\n options.onUnmount(bloc as InstanceType<T>);\n }\n\n release(BlocClass, instanceKey);\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":";;;AAaA,IAAI,eAAgC,EAHlC,WAAW,MAG2C;;;;;;;;;;;;;;;;AAiBxD,SAAgB,mBAAmB,QAAwC;AACzE,gBAAe;EAAE,GAAG;EAAc,GAAG;EAAQ;;;;;;AAO/C,SAAgB,qBAAsC;AACpD,QAAO;;;;ACLT,SAAS,sBACP,SACc;CACd,MAAM,eAAe,oBAAoB;CACzC,MAAM,mBACJ,SAAS,cAAc,KAAA,IACnB,QAAQ,YACR,aAAa;AAEnB,QAAO;EACL,eAAe,SAAS,iBAAiB,KAAA;EACzC;EACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCH,SAAgB,QAGd,WACA,SACmC;CAGnC,MAAM,gBAAA,GAAA,MAAA,QAAoC,EAAE,CAAC;CAC7C,MAAM,WAAA,GAAA,MAAA,QAAiB,SAAS,aAAa;AAC7C,SAAQ,UAAU,SAAS;CAC3B,MAAM,aAAa,SAAS;CAC5B,MAAM,YAAY,SAAS;CAC3B,MAAM,eAAe,SAAS;CAE9B,MAAM,CAAC,MAAM,WAAW,aAAa,aAAa,cAAc,gBAAA,GAAA,MAAA,eAUtD;EACN,MAAM,cACJ,eAAe,KAAA,IAAY,OAAO,WAAW,GAAG,KAAA;EAElD,MAAM,YAAA,GAAA,cAAA,SAAmB,WAAW,YAAY;EAEhD,MAAM,EAAE,eAAe,qBAAqB,sBAAsB;GAChE;GACA;GACD,CAAC;EAEF,IAAI;EACJ,IAAI;EACJ,IAAI;AAEJ,MAAI,iBAAiB,cAAc;AACjC,mBAAA,GAAA,cAAA,gBAA8B,SAAS;GACvC,MAAM,eAAe,EACnB,eAAe,OAAwB,SAA2B;IAChE,MAAM,OAAO,QAAQ;AACrB,WAAO,OAAO,KAAK,OAAO,KAAK,GAAG,EAAE;MAEvC;AACD,kBAAA,GAAA,cAAA,qBAAkC,UAAU,cAAc,aAAa;AACvE,oBAAA,GAAA,cAAA,oBACE,UACA,cACA,aACD;aACQ,CAAC,kBAAkB;AAC5B,mBAAA,GAAA,cAAA,aAA2B,SAAS;AACpC,kBAAA,GAAA,cAAA,kBAA+B,SAAS;AACxC,oBAAA,GAAA,cAAA,iBAAgC,SAAS;SACpC;AACL,mBAAA,GAAA,cAAA,eAA6B,SAAS;AACtC,kBAAA,GAAA,cAAA,oBAAiC,UAAU,aAAa;AACxD,oBAAA,GAAA,cAAA,mBAAkC,UAAU,aAAa;;EAG3D,MAAM,mBAAmB,aAAyB;AAChD,OAAI,SAAS,WAAY,cAAa;AACtC,UAAO,YAAY,SAAS;;AAG9B,SAAO;GACL,aAAa;GACb;GACA;GACA;GACA;GACA;GACD;IAEA,CAAC,WAAW,WAAW,CAAC;CAE7B,MAAM,SAAA,GAAA,MAAA,sBAA6B,WAAW,aAAa,YAAY;CAEvE,MAAM,GAAG,gBAAA,GAAA,MAAA,aAA2B,MAAc,IAAI,GAAG,EAAE;CAE3D,MAAM,uBAAA,GAAA,MAAA,QAA6B,IAAIA,cAAAA,qBAAqB,CAAC;AAE7D,EAAA,GAAA,MAAA,iBAAgB;EACd,MAAM,UAAU,oBAAoB;AACpC,GAAA,GAAA,cAAA,uBAAsB,cAAc,YAAY;AAChD,UAAQ,oBACN,aAAa,aACb,aACA,YACD;GACD;AAEF,EAAA,GAAA,MAAA,iBAAgB;EACd,MAAM,UAAU,oBAAoB;AACpC,MAAI,SAAS,QACX,SAAQ,QAAQ,KAAwB;AAG1C,eAAa;AACX,WAAQ,SAAS;AAEjB,OAAI,SAAS,UACX,SAAQ,UAAU,KAAwB;AAG5C,IAAA,GAAA,cAAA,SAAQ,WAAW,YAAY;;IAGhC,EAAE,CAAC;AAEN,QAAO;EAAC;EAAO;EAAM;EAAa"}
package/dist/index.js CHANGED
@@ -1,35 +1,6 @@
1
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 };
2
+ import { ExternalDepsManager, acquire, autoTrackInit, autoTrackSnapshot, autoTrackSubscribe, disableGetterTracking, manualDepsInit, manualDepsSnapshot, manualDepsSubscribe, noTrackInit, noTrackSnapshot, noTrackSubscribe, release } from "@blac/adapter";
3
+ let globalConfig = { autoTrack: true };
33
4
  /**
34
5
  * Configure global defaults for @blac/react hooks.
35
6
  *
@@ -58,7 +29,6 @@ function configureBlacReact(config) {
58
29
  function getBlacReactConfig() {
59
30
  return globalConfig;
60
31
  }
61
-
62
32
  //#endregion
63
33
  //#region src/useBloc.ts
64
34
  function determineTrackingMode(options) {
@@ -94,7 +64,7 @@ function determineTrackingMode(options) {
94
64
  * });
95
65
  * ```
96
66
  *
97
- * @example With isolated instance
67
+ * @example With named instance
98
68
  * ```ts
99
69
  * const [state, myBloc] = useBloc(MyBloc, {
100
70
  * instanceId: 'unique-id'
@@ -103,18 +73,29 @@ function determineTrackingMode(options) {
103
73
  */
104
74
  function useBloc(BlocClass, options) {
105
75
  const componentRef = useRef({});
106
- const isIsolated = isIsolatedClass(BlocClass);
76
+ const depsRef = useRef(options?.dependencies);
77
+ depsRef.current = options?.dependencies;
78
+ const instanceId = options?.instanceId;
79
+ const autoTrack = options?.autoTrack;
80
+ const dependencies = options?.dependencies;
107
81
  const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] = useMemo(() => {
108
- const instanceKey = generateInstanceKey(componentRef.current, isIsolated, options?.instanceId);
82
+ const instanceKey = instanceId !== void 0 ? String(instanceId) : void 0;
109
83
  const instance = acquire(BlocClass, instanceKey);
110
- const { useManualDeps, autoTrackEnabled } = determineTrackingMode(options);
84
+ const { useManualDeps, autoTrackEnabled } = determineTrackingMode({
85
+ autoTrack,
86
+ dependencies
87
+ });
111
88
  let subscribeFn;
112
89
  let getSnapshotFn;
113
90
  let adapterState;
114
- if (useManualDeps && options?.dependencies) {
91
+ if (useManualDeps && dependencies) {
115
92
  adapterState = manualDepsInit(instance);
116
- subscribeFn = manualDepsSubscribe(instance, adapterState, { dependencies: options.dependencies });
117
- getSnapshotFn = manualDepsSnapshot(instance, adapterState, { dependencies: options.dependencies });
93
+ const stableConfig = { dependencies: (state, bloc) => {
94
+ const deps = depsRef.current;
95
+ return deps ? deps(state, bloc) : [];
96
+ } };
97
+ subscribeFn = manualDepsSubscribe(instance, adapterState, stableConfig);
98
+ getSnapshotFn = manualDepsSnapshot(instance, adapterState, stableConfig);
118
99
  } else if (!autoTrackEnabled) {
119
100
  adapterState = noTrackInit(instance);
120
101
  subscribeFn = noTrackSubscribe(instance);
@@ -124,33 +105,34 @@ function useBloc(BlocClass, options) {
124
105
  subscribeFn = autoTrackSubscribe(instance, adapterState);
125
106
  getSnapshotFn = autoTrackSnapshot(instance, adapterState);
126
107
  }
108
+ const safeSubscribeFn = (callback) => {
109
+ if (instance.isDisposed) return () => {};
110
+ return subscribeFn(callback);
111
+ };
127
112
  return [
128
113
  adapterState.proxiedBloc,
129
- subscribeFn,
114
+ safeSubscribeFn,
130
115
  getSnapshotFn,
131
116
  instanceKey,
132
117
  adapterState,
133
118
  instance
134
119
  ];
135
- }, [
136
- BlocClass,
137
- isIsolated,
138
- options?.instanceId
139
- ]);
140
- const state = useSyncExternalStore(subscribe, getSnapshot);
120
+ }, [BlocClass, instanceId]);
121
+ const state = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
141
122
  const [, forceUpdate] = useReducer((x) => x + 1, 0);
142
123
  const externalDepsManager = useRef(new ExternalDepsManager());
143
124
  useEffect(() => {
125
+ const manager = externalDepsManager.current;
144
126
  disableGetterTracking(adapterState, rawInstance);
145
- externalDepsManager.current.updateSubscriptions(adapterState.getterState, rawInstance, forceUpdate);
127
+ manager.updateSubscriptions(adapterState.getterState, rawInstance, forceUpdate);
146
128
  });
147
129
  useEffect(() => {
130
+ const manager = externalDepsManager.current;
148
131
  if (options?.onMount) options.onMount(bloc);
149
132
  return () => {
150
- externalDepsManager.current.cleanup();
133
+ manager.cleanup();
151
134
  if (options?.onUnmount) options.onUnmount(bloc);
152
135
  release(BlocClass, instanceKey);
153
- if (isIsolated && !rawInstance.isDisposed) rawInstance.dispose();
154
136
  };
155
137
  }, []);
156
138
  return [
@@ -159,7 +141,7 @@ function useBloc(BlocClass, options) {
159
141
  componentRef
160
142
  ];
161
143
  }
162
-
163
144
  //#endregion
164
145
  export { configureBlacReact, useBloc };
146
+
165
147
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +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;;;;;ACHT,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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/config.ts","../src/useBloc.ts"],"sourcesContent":["/**\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 type StateContainerConstructor,\n type InstanceState,\n acquire,\n release,\n} from '@blac/adapter';\nimport type { UseBlocOptions, UseBlocReturn, ComponentRef } from './types';\nimport { getBlacReactConfig } from './config';\n\ninterface TrackingMode {\n useManualDeps: boolean;\n autoTrackEnabled: boolean;\n}\n\nfunction determineTrackingMode<TBloc extends StateContainerConstructor>(\n options?: Pick<UseBlocOptions<TBloc>, 'autoTrack' | 'dependencies'>,\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 named 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 depsRef = useRef(options?.dependencies);\n depsRef.current = options?.dependencies;\n const instanceId = options?.instanceId;\n const autoTrack = options?.autoTrack;\n const dependencies = options?.dependencies;\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 =\n instanceId !== undefined ? String(instanceId) : undefined;\n\n const instance = acquire(BlocClass, instanceKey) as TBloc;\n\n const { useManualDeps, autoTrackEnabled } = determineTrackingMode({\n autoTrack,\n dependencies,\n });\n\n let subscribeFn: (callback: () => void) => () => void;\n let getSnapshotFn: () => ExtractState<T>;\n let adapterState: AdapterState<T>;\n\n if (useManualDeps && dependencies) {\n adapterState = manualDepsInit(instance);\n const stableConfig = {\n dependencies: (state: ExtractState<T>, bloc: InstanceState<T>) => {\n const deps = depsRef.current;\n return deps ? deps(state, bloc) : [];\n },\n };\n subscribeFn = manualDepsSubscribe(instance, adapterState, stableConfig);\n getSnapshotFn = manualDepsSnapshot(\n instance,\n adapterState,\n stableConfig,\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 const safeSubscribeFn = (callback: () => void) => {\n if (instance.isDisposed) return () => {};\n return subscribeFn(callback);\n };\n\n return [\n adapterState.proxiedBloc as TBloc,\n safeSubscribeFn,\n getSnapshotFn,\n instanceKey,\n adapterState,\n instance,\n ];\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [BlocClass, instanceId]);\n\n const state = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n const [, forceUpdate] = useReducer((x: number) => x + 1, 0);\n\n const externalDepsManager = useRef(new ExternalDepsManager());\n\n useEffect(() => {\n const manager = externalDepsManager.current;\n disableGetterTracking(adapterState, rawInstance);\n manager.updateSubscriptions(\n adapterState.getterState,\n rawInstance,\n forceUpdate,\n );\n });\n\n useEffect(() => {\n const manager = externalDepsManager.current;\n if (options?.onMount) {\n options.onMount(bloc as InstanceType<T>);\n }\n\n return () => {\n manager.cleanup();\n\n if (options?.onUnmount) {\n options.onUnmount(bloc as InstanceType<T>);\n }\n\n release(BlocClass, instanceKey);\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":";;AAaA,IAAI,eAAgC,EAHlC,WAAW,MAG2C;;;;;;;;;;;;;;;;AAiBxD,SAAgB,mBAAmB,QAAwC;AACzE,gBAAe;EAAE,GAAG;EAAc,GAAG;EAAQ;;;;;;AAO/C,SAAgB,qBAAsC;AACpD,QAAO;;;;ACLT,SAAS,sBACP,SACc;CACd,MAAM,eAAe,oBAAoB;CACzC,MAAM,mBACJ,SAAS,cAAc,KAAA,IACnB,QAAQ,YACR,aAAa;AAEnB,QAAO;EACL,eAAe,SAAS,iBAAiB,KAAA;EACzC;EACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCH,SAAgB,QAGd,WACA,SACmC;CAGnC,MAAM,eAAe,OAAqB,EAAE,CAAC;CAC7C,MAAM,UAAU,OAAO,SAAS,aAAa;AAC7C,SAAQ,UAAU,SAAS;CAC3B,MAAM,aAAa,SAAS;CAC5B,MAAM,YAAY,SAAS;CAC3B,MAAM,eAAe,SAAS;CAE9B,MAAM,CAAC,MAAM,WAAW,aAAa,aAAa,cAAc,eAC9D,cASQ;EACN,MAAM,cACJ,eAAe,KAAA,IAAY,OAAO,WAAW,GAAG,KAAA;EAElD,MAAM,WAAW,QAAQ,WAAW,YAAY;EAEhD,MAAM,EAAE,eAAe,qBAAqB,sBAAsB;GAChE;GACA;GACD,CAAC;EAEF,IAAI;EACJ,IAAI;EACJ,IAAI;AAEJ,MAAI,iBAAiB,cAAc;AACjC,kBAAe,eAAe,SAAS;GACvC,MAAM,eAAe,EACnB,eAAe,OAAwB,SAA2B;IAChE,MAAM,OAAO,QAAQ;AACrB,WAAO,OAAO,KAAK,OAAO,KAAK,GAAG,EAAE;MAEvC;AACD,iBAAc,oBAAoB,UAAU,cAAc,aAAa;AACvE,mBAAgB,mBACd,UACA,cACA,aACD;aACQ,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;;EAG3D,MAAM,mBAAmB,aAAyB;AAChD,OAAI,SAAS,WAAY,cAAa;AACtC,UAAO,YAAY,SAAS;;AAG9B,SAAO;GACL,aAAa;GACb;GACA;GACA;GACA;GACA;GACD;IAEA,CAAC,WAAW,WAAW,CAAC;CAE7B,MAAM,QAAQ,qBAAqB,WAAW,aAAa,YAAY;CAEvE,MAAM,GAAG,eAAe,YAAY,MAAc,IAAI,GAAG,EAAE;CAE3D,MAAM,sBAAsB,OAAO,IAAI,qBAAqB,CAAC;AAE7D,iBAAgB;EACd,MAAM,UAAU,oBAAoB;AACpC,wBAAsB,cAAc,YAAY;AAChD,UAAQ,oBACN,aAAa,aACb,aACA,YACD;GACD;AAEF,iBAAgB;EACd,MAAM,UAAU,oBAAoB;AACpC,MAAI,SAAS,QACX,SAAQ,QAAQ,KAAwB;AAG1C,eAAa;AACX,WAAQ,SAAS;AAEjB,OAAI,SAAS,UACX,SAAQ,UAAU,KAAwB;AAG5C,WAAQ,WAAW,YAAY;;IAGhC,EAAE,CAAC;AAEN,QAAO;EAAC;EAAO;EAAM;EAAa"}
package/dist/types.d.ts CHANGED
@@ -6,7 +6,7 @@ import type { RefObject } from 'react';
6
6
  * @template TBloc - The state container type
7
7
  */
8
8
  export interface UseBlocOptions<TBloc extends StateContainerConstructor> {
9
- /** Custom instance identifier for shared or isolated instances */
9
+ /** Custom instance identifier */
10
10
  instanceId?: string | number;
11
11
  /** Manual dependency array like useEffect (disables autoTrack) */
12
12
  dependencies?: (state: ExtractState<TBloc>, bloc: InstanceReadonlyState<TBloc>) => unknown[];
@@ -30,7 +30,4 @@ export type UseBlocReturn<TBloc extends StateContainerConstructor, S = ExtractSt
30
30
  * Internal ref structure for component-bloc binding
31
31
  * @internal
32
32
  */
33
- export type ComponentRef = {
34
- /** Cached bloc instance ID for this component */
35
- __blocInstanceId?: string;
36
- };
33
+ export type ComponentRef = Record<string, never>;
package/dist/useBloc.d.ts CHANGED
@@ -25,7 +25,7 @@ import type { UseBlocOptions, UseBlocReturn } from './types';
25
25
  * });
26
26
  * ```
27
27
  *
28
- * @example With isolated instance
28
+ * @example With named instance
29
29
  * ```ts
30
30
  * const [state, myBloc] = useBloc(MyBloc, {
31
31
  * instanceId: 'unique-id'
@@ -1,18 +1,10 @@
1
1
  /**
2
2
  * Instance key generation utilities for React integration
3
3
  */
4
- import type { ComponentRef } from '../types';
5
4
  /**
6
5
  * Generate an instance key for a bloc
7
6
  *
8
- * Logic:
9
- * - If user provides instanceId, use it (convert number to string)
10
- * - If isolated, generate or reuse a unique key for this component
11
- * - Otherwise, return undefined (use default key)
12
- *
13
- * @param componentRef - React component reference (persists across remounts)
14
- * @param isIsolated - Whether the bloc is isolated
15
7
  * @param providedId - User-provided instance ID (from options)
16
8
  * @returns Instance key string or undefined for default
17
9
  */
18
- export declare function generateInstanceKey(componentRef: ComponentRef, isIsolated: boolean, providedId?: string | number): string | undefined;
10
+ export declare function generateInstanceKey(providedId?: string | number): string | undefined;
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@blac/react",
3
- "version": "2.0.5",
3
+ "version": "2.0.7",
4
4
  "license": "MIT",
5
5
  "author": "Brendan Mullins <jsnanigans@gmail.com>",
6
+ "description": "React bindings for BlaC — useBloc hook with automatic re-render optimization",
6
7
  "repository": {
7
8
  "type": "git",
8
9
  "url": "git+https://github.com/jsnanigans/blac.git",
@@ -12,6 +13,7 @@
12
13
  "bugs": {
13
14
  "url": "https://github.com/jsnanigans/blac/issues"
14
15
  },
16
+ "type": "module",
15
17
  "main": "./dist/index.cjs",
16
18
  "module": "./dist/index.js",
17
19
  "types": "./dist/index.d.ts",
@@ -32,6 +34,9 @@
32
34
  "README.md",
33
35
  "LICENSE"
34
36
  ],
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
35
40
  "sideEffects": false,
36
41
  "keywords": [
37
42
  "react",
@@ -43,11 +48,12 @@
43
48
  "bloc-pattern"
44
49
  ],
45
50
  "dependencies": {
46
- "@blac/adapter": "2.0.5"
51
+ "@blac/adapter": "^2.0.7"
47
52
  },
48
53
  "peerDependencies": {
49
54
  "@types/react": "^18.0.0 || ^19.0.0",
50
- "react": "^18.0.0 || ^19.0.0"
55
+ "react": "^18.0.0 || ^19.0.0",
56
+ "@blac/core": "^2.0.7"
51
57
  },
52
58
  "peerDependenciesMeta": {
53
59
  "@types/react": {
@@ -63,6 +69,7 @@
63
69
  "@testing-library/react": "^16.3.0",
64
70
  "@testing-library/user-event": "^14.6.1",
65
71
  "@types/react": "^19.1.9",
72
+ "@types/react-dom": "^19.1.7",
66
73
  "@vitejs/plugin-react": "^4.7.0",
67
74
  "@vitest/browser": "^3.2.4",
68
75
  "@vitest/coverage-v8": "^3.2.4",
@@ -71,13 +78,14 @@
71
78
  "happy-dom": "^18.0.1",
72
79
  "jsdom": "^26.1.0",
73
80
  "prettier": "^3.6.2",
81
+ "publint": "^0.3.12",
74
82
  "react": "^19.1.1",
75
83
  "react-dom": "^19.1.1",
76
84
  "tsdown": "^0.15.7",
77
85
  "typescript": "^5.9.2",
78
- "publint": "^0.3.12",
86
+ "vite": "^7.1.10",
79
87
  "vitest": "3.2.4",
80
- "@blac/core": "2.0.5"
88
+ "@blac/core": "2.0.7"
81
89
  },
82
90
  "scripts": {
83
91
  "dev": "tsdown --watch",
@@ -93,7 +101,7 @@
93
101
  "test:compiler": "vitest run --config vitest.config.compiler.ts",
94
102
  "test:watch:compiler": "vitest --watch --config vitest.config.compiler.ts",
95
103
  "test:both": "pnpm test && echo '\n\n=== Running tests WITH React Compiler ===\n' && pnpm test:compiler",
96
- "verify": "publint",
104
+ "verify": "node ../../scripts/run-workspace-bin.mjs publint",
97
105
  "typecheck": "tsc --noEmit",
98
106
  "deploy": "pnpm publish --access public"
99
107
  }