@blac/react 2.0.10 → 2.0.12

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,19 +1,17 @@
1
1
  # @blac/react
2
2
 
3
- React integration for BlaC with automatic re-render optimization.
3
+ React bindings for BlaC — `useBloc` hook with proxy-based automatic re-render optimization.
4
4
 
5
- **[Full documentation](https://jsnanigans.github.io/blac/react/getting-started)**
5
+ **[Documentation](https://blac-docs.pages.dev/react/getting-started)** · **[npm](https://www.npmjs.com/package/@blac/react)**
6
6
 
7
7
  ## Installation
8
8
 
9
9
  ```bash
10
- npm install @blac/react @blac/core
11
- # or
12
10
  pnpm add @blac/react @blac/core
13
- # or
14
- yarn add @blac/react @blac/core
15
11
  ```
16
12
 
13
+ Requires React 18+ (`useSyncExternalStore`).
14
+
17
15
  ## Quick Start
18
16
 
19
17
  ```tsx
@@ -24,14 +22,12 @@ class CounterCubit extends Cubit<{ count: number }> {
24
22
  constructor() {
25
23
  super({ count: 0 });
26
24
  }
27
-
28
25
  increment = () => this.emit({ count: this.state.count + 1 });
29
26
  decrement = () => this.emit({ count: this.state.count - 1 });
30
27
  }
31
28
 
32
29
  function Counter() {
33
30
  const [state, counter] = useBloc(CounterCubit);
34
-
35
31
  return (
36
32
  <div>
37
33
  <p>Count: {state.count}</p>
@@ -44,107 +40,73 @@ function Counter() {
44
40
 
45
41
  ## useBloc
46
42
 
47
- Connects a component to a state container with automatic re-renders on state changes.
48
-
49
43
  ```tsx
50
- const [state, bloc, ref] = useBloc(MyBloc);
44
+ const [state, bloc, ref] = useBloc(MyBloc, options?);
51
45
  ```
52
46
 
53
47
  **Returns:** `[state, bloc, ref]`
54
48
 
55
- - `state` - Current state snapshot
56
- - `bloc` - Bloc instance (proxied for tracking; call methods on it)
57
- - `ref` - Internal component ref (advanced usage)
49
+ - `state` — current state snapshot (proxied for auto-tracking)
50
+ - `bloc` — bloc instance for calling methods
51
+ - `ref` — internal component ref (advanced usage)
58
52
 
59
53
  ### Tracking Modes
60
54
 
61
- **Auto-tracking (default):** Tracks accessed properties and getters.
55
+ **Auto-tracking (default):** Only re-renders when accessed properties change.
62
56
 
63
57
  ```tsx
64
- function UserProfile() {
65
- const [state] = useBloc(UserBloc);
66
- return <h1>{state.name}</h1>;
67
- }
58
+ const [state] = useBloc(UserBloc);
59
+ return <h1>{state.name}</h1>; // only re-renders when name changes
68
60
  ```
69
61
 
70
- **Manual dependencies:** Provide a dependency selector (disables auto-tracking).
62
+ **Manual dependencies:** Explicit dependency array (disables auto-tracking).
71
63
 
72
64
  ```tsx
73
- function Counter() {
74
- const [state] = useBloc(CounterCubit, {
75
- dependencies: (state) => [state.count],
76
- });
77
-
78
- return <p>{state.count}</p>;
79
- }
65
+ const [state] = useBloc(CounterCubit, {
66
+ dependencies: (state) => [state.count],
67
+ });
80
68
  ```
81
69
 
82
- **No tracking:** Re-render on any state change.
70
+ **No tracking:** Re-renders on every state change.
83
71
 
84
72
  ```tsx
85
- function FullState() {
86
- const [state] = useBloc(MyBloc, { autoTrack: false });
87
- return <pre>{JSON.stringify(state)}</pre>;
88
- }
73
+ const [state] = useBloc(MyBloc, { autoTrack: false });
89
74
  ```
90
75
 
91
76
  ### Options
92
77
 
93
- ```tsx
94
- useBloc(MyBloc, {
95
- autoTrack: true,
96
- dependencies: (state, bloc) => [state.count, bloc],
97
- instanceId: 'editor-1',
98
- onMount: (bloc) => console.log('Mounted', bloc),
99
- onUnmount: (bloc) => console.log('Unmounted', bloc),
100
- });
101
- ```
102
-
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 |
78
+ | Option | Type | Description |
79
+ | -------------- | ---------------------------- | ---------------------------------------------------- |
80
+ | `autoTrack` | `boolean` | Enable proxy-based auto-tracking (default: `true`) |
81
+ | `dependencies` | `(state, bloc) => unknown[]` | Manual dependency selector |
82
+ | `instanceId` | `string \| number` | Use a named instance instead of the shared singleton |
83
+ | `onMount` | `(bloc) => void` | Called when component mounts |
84
+ | `onUnmount` | `(bloc) => void` | Called when component unmounts |
110
85
 
111
- ## Instance Management
86
+ ### Instance Sharing
112
87
 
113
- ### Shared Instances (Default)
88
+ By default, all components using `useBloc(MyBloc)` share the same instance. Use `instanceId` for separate instances:
114
89
 
115
90
  ```tsx
116
- function A() {
117
- useBloc(CounterCubit); // shared instance
118
- }
119
-
120
- function B() {
121
- useBloc(CounterCubit); // same shared instance
122
- }
123
- ```
124
-
125
- ### Named Instances
126
-
127
- ```tsx
128
- useBloc(EditorCubit, { instanceId: 'editor-1' });
129
- useBloc(EditorCubit, { instanceId: 'editor-1' }); // same instance
130
- useBloc(EditorCubit, { instanceId: 'editor-2' }); // different instance
91
+ useBloc(EditorCubit, { instanceId: 'editor-1' }); // instance A
92
+ useBloc(EditorCubit, { instanceId: 'editor-2' }); // instance B
131
93
  ```
132
94
 
133
95
  ## Configuration
134
96
 
135
- Configure global defaults:
136
-
137
97
  ```tsx
138
98
  import { configureBlacReact } from '@blac/react';
139
99
 
140
- configureBlacReact({
141
- autoTrack: true,
142
- });
100
+ configureBlacReact({ autoTrack: true });
143
101
  ```
144
102
 
145
- ## Compatibility
103
+ ## Testing
104
+
105
+ ```tsx
106
+ import { renderWithBloc } from '@blac/react/testing';
107
+ ```
146
108
 
147
- - React 18+ (uses `useSyncExternalStore`)
109
+ See the [testing docs](https://blac-docs.pages.dev/testing/react) for details.
148
110
 
149
111
  ## License
150
112
 
package/dist/index.cjs CHANGED
@@ -1,5 +1,28 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region \0rolldown/runtime.js
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
+ get: ((k) => from[k]).bind(null, key),
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
20
+ value: mod,
21
+ enumerable: true
22
+ }) : target, mod));
23
+ //#endregion
2
24
  let react = require("react");
25
+ react = __toESM(react);
3
26
  let _blac_adapter = require("@blac/adapter");
4
27
  let globalConfig = { autoTrack: true };
5
28
  /**
@@ -32,6 +55,13 @@ function getBlacReactConfig() {
32
55
  }
33
56
  //#endregion
34
57
  //#region src/useBloc.ts
58
+ let nextConsumerId = 0;
59
+ function getComponentName() {
60
+ try {
61
+ const owner = (react.default.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE ?? react.default.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED)?.ReactCurrentOwner?.current;
62
+ if (owner?.type) return owner.type.displayName || owner.type.name || void 0;
63
+ } catch {}
64
+ }
35
65
  function determineTrackingMode(options) {
36
66
  const globalConfig = getBlacReactConfig();
37
67
  const autoTrackEnabled = options?.autoTrack !== void 0 ? options.autoTrack : globalConfig.autoTrack;
@@ -74,6 +104,10 @@ function determineTrackingMode(options) {
74
104
  */
75
105
  function useBloc(BlocClass, options) {
76
106
  const componentRef = (0, react.useRef)({});
107
+ const consumerIdRef = (0, react.useRef)(null);
108
+ if (consumerIdRef.current === null) consumerIdRef.current = String(nextConsumerId++);
109
+ const componentNameRef = (0, react.useRef)(null);
110
+ if (componentNameRef.current === null) componentNameRef.current = getComponentName() || null;
77
111
  const depsRef = (0, react.useRef)(options?.dependencies);
78
112
  depsRef.current = options?.dependencies;
79
113
  const instanceId = options?.instanceId;
@@ -81,7 +115,7 @@ function useBloc(BlocClass, options) {
81
115
  const dependencies = options?.dependencies;
82
116
  const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] = (0, react.useMemo)(() => {
83
117
  const instanceKey = instanceId !== void 0 ? String(instanceId) : void 0;
84
- const instance = (0, _blac_adapter.acquire)(BlocClass, instanceKey);
118
+ const instance = (0, _blac_adapter.acquire)(BlocClass, instanceKey, `useBloc@${componentNameRef.current ?? "Unknown"}-${consumerIdRef.current}`);
85
119
  const { useManualDeps, autoTrackEnabled } = determineTrackingMode({
86
120
  autoTrack,
87
121
  dependencies
@@ -133,11 +167,14 @@ function useBloc(BlocClass, options) {
133
167
  ]);
134
168
  (0, react.useEffect)(() => {
135
169
  const manager = externalDepsManager.current;
170
+ const devtools = typeof window !== "undefined" ? window.__BLAC_DEVTOOLS__ : void 0;
171
+ if (devtools?.registerConsumer && rawInstance) devtools.registerConsumer(rawInstance.instanceId, consumerIdRef.current, componentNameRef.current || "Unknown");
136
172
  if (options?.onMount) options.onMount(bloc);
137
173
  return () => {
174
+ if (devtools?.unregisterConsumer && rawInstance) devtools.unregisterConsumer(rawInstance.instanceId, consumerIdRef.current);
138
175
  manager.cleanup();
139
176
  if (options?.onUnmount) options.onUnmount(bloc);
140
- (0, _blac_adapter.release)(BlocClass, instanceKey);
177
+ (0, _blac_adapter.release)(BlocClass, instanceKey, false, `useBloc@${componentNameRef.current ?? "Unknown"}-${consumerIdRef.current}`);
141
178
  };
142
179
  }, []);
143
180
  return [
@@ -147,6 +184,7 @@ function useBloc(BlocClass, options) {
147
184
  ];
148
185
  }
149
186
  //#endregion
187
+ exports.__toESM = __toESM;
150
188
  exports.configureBlacReact = configureBlacReact;
151
189
  exports.useBloc = useBloc;
152
190
 
@@ -1 +1 @@
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 // oxlint-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 }, [adapterState, rawInstance, forceUpdate]);\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 // oxlint-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;IACA;EAAC;EAAc;EAAa;EAAY,CAAC;AAE5C,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"}
1
+ {"version":3,"file":"index.cjs","names":["React","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 React, {\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\nlet nextConsumerId = 0;\n\nfunction getComponentName(): string | undefined {\n try {\n const internals =\n (React as any)\n .__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE ??\n (React as any).__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;\n const owner = internals?.ReactCurrentOwner?.current;\n if (owner?.type) {\n return owner.type.displayName || owner.type.name || undefined;\n }\n } catch {\n // ignore — React internals may not be available\n }\n return undefined;\n}\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\n // Capture component name from React fiber during render (for devtools consumer tracking)\n const consumerIdRef = useRef<string | null>(null);\n if (consumerIdRef.current === null) {\n consumerIdRef.current = String(nextConsumerId++);\n }\n const componentNameRef = useRef<string | null>(null);\n if (componentNameRef.current === null) {\n componentNameRef.current = getComponentName() || null;\n }\n\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 refId = `useBloc@${componentNameRef.current ?? 'Unknown'}-${consumerIdRef.current}`;\n const instance = acquire(BlocClass, instanceKey, refId) 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 // oxlint-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 }, [adapterState, rawInstance, forceUpdate]);\n\n useEffect(() => {\n const manager = externalDepsManager.current;\n\n // Register as consumer in devtools (if available)\n const devtools =\n typeof window !== 'undefined'\n ? (window as any).__BLAC_DEVTOOLS__\n : undefined;\n if (devtools?.registerConsumer && rawInstance) {\n devtools.registerConsumer(\n (rawInstance as any).instanceId,\n consumerIdRef.current,\n componentNameRef.current || 'Unknown',\n );\n }\n\n if (options?.onMount) {\n options.onMount(bloc as InstanceType<T>);\n }\n\n return () => {\n // Unregister consumer from devtools\n if (devtools?.unregisterConsumer && rawInstance) {\n devtools.unregisterConsumer(\n (rawInstance as any).instanceId,\n consumerIdRef.current,\n );\n }\n\n manager.cleanup();\n\n if (options?.onUnmount) {\n options.onUnmount(bloc as InstanceType<T>);\n }\n\n const refId = `useBloc@${componentNameRef.current ?? 'Unknown'}-${consumerIdRef.current}`;\n release(BlocClass, instanceKey, false, refId);\n };\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return [state, bloc, componentRef] as UseBlocReturn<T, ExtractState<T>>;\n}\n"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { useEffect, useMemo, useReducer, useRef, useSyncExternalStore } from "react";
1
+ import React, { useEffect, useMemo, useReducer, useRef, useSyncExternalStore } from "react";
2
2
  import { ExternalDepsManager, acquire, autoTrackInit, autoTrackSnapshot, autoTrackSubscribe, disableGetterTracking, manualDepsInit, manualDepsSnapshot, manualDepsSubscribe, noTrackInit, noTrackSnapshot, noTrackSubscribe, release } from "@blac/adapter";
3
3
  let globalConfig = { autoTrack: true };
4
4
  /**
@@ -31,6 +31,13 @@ function getBlacReactConfig() {
31
31
  }
32
32
  //#endregion
33
33
  //#region src/useBloc.ts
34
+ let nextConsumerId = 0;
35
+ function getComponentName() {
36
+ try {
37
+ const owner = (React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE ?? React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED)?.ReactCurrentOwner?.current;
38
+ if (owner?.type) return owner.type.displayName || owner.type.name || void 0;
39
+ } catch {}
40
+ }
34
41
  function determineTrackingMode(options) {
35
42
  const globalConfig = getBlacReactConfig();
36
43
  const autoTrackEnabled = options?.autoTrack !== void 0 ? options.autoTrack : globalConfig.autoTrack;
@@ -73,6 +80,10 @@ function determineTrackingMode(options) {
73
80
  */
74
81
  function useBloc(BlocClass, options) {
75
82
  const componentRef = useRef({});
83
+ const consumerIdRef = useRef(null);
84
+ if (consumerIdRef.current === null) consumerIdRef.current = String(nextConsumerId++);
85
+ const componentNameRef = useRef(null);
86
+ if (componentNameRef.current === null) componentNameRef.current = getComponentName() || null;
76
87
  const depsRef = useRef(options?.dependencies);
77
88
  depsRef.current = options?.dependencies;
78
89
  const instanceId = options?.instanceId;
@@ -80,7 +91,7 @@ function useBloc(BlocClass, options) {
80
91
  const dependencies = options?.dependencies;
81
92
  const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] = useMemo(() => {
82
93
  const instanceKey = instanceId !== void 0 ? String(instanceId) : void 0;
83
- const instance = acquire(BlocClass, instanceKey);
94
+ const instance = acquire(BlocClass, instanceKey, `useBloc@${componentNameRef.current ?? "Unknown"}-${consumerIdRef.current}`);
84
95
  const { useManualDeps, autoTrackEnabled } = determineTrackingMode({
85
96
  autoTrack,
86
97
  dependencies
@@ -132,11 +143,14 @@ function useBloc(BlocClass, options) {
132
143
  ]);
133
144
  useEffect(() => {
134
145
  const manager = externalDepsManager.current;
146
+ const devtools = typeof window !== "undefined" ? window.__BLAC_DEVTOOLS__ : void 0;
147
+ if (devtools?.registerConsumer && rawInstance) devtools.registerConsumer(rawInstance.instanceId, consumerIdRef.current, componentNameRef.current || "Unknown");
135
148
  if (options?.onMount) options.onMount(bloc);
136
149
  return () => {
150
+ if (devtools?.unregisterConsumer && rawInstance) devtools.unregisterConsumer(rawInstance.instanceId, consumerIdRef.current);
137
151
  manager.cleanup();
138
152
  if (options?.onUnmount) options.onUnmount(bloc);
139
- release(BlocClass, instanceKey);
153
+ release(BlocClass, instanceKey, false, `useBloc@${componentNameRef.current ?? "Unknown"}-${consumerIdRef.current}`);
140
154
  };
141
155
  }, []);
142
156
  return [
package/dist/index.js.map CHANGED
@@ -1 +1 @@
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 // oxlint-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 }, [adapterState, rawInstance, forceUpdate]);\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 // oxlint-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;IACA;EAAC;EAAc;EAAa;EAAY,CAAC;AAE5C,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"}
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 React, {\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\nlet nextConsumerId = 0;\n\nfunction getComponentName(): string | undefined {\n try {\n const internals =\n (React as any)\n .__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE ??\n (React as any).__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;\n const owner = internals?.ReactCurrentOwner?.current;\n if (owner?.type) {\n return owner.type.displayName || owner.type.name || undefined;\n }\n } catch {\n // ignore — React internals may not be available\n }\n return undefined;\n}\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\n // Capture component name from React fiber during render (for devtools consumer tracking)\n const consumerIdRef = useRef<string | null>(null);\n if (consumerIdRef.current === null) {\n consumerIdRef.current = String(nextConsumerId++);\n }\n const componentNameRef = useRef<string | null>(null);\n if (componentNameRef.current === null) {\n componentNameRef.current = getComponentName() || null;\n }\n\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 refId = `useBloc@${componentNameRef.current ?? 'Unknown'}-${consumerIdRef.current}`;\n const instance = acquire(BlocClass, instanceKey, refId) 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 // oxlint-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 }, [adapterState, rawInstance, forceUpdate]);\n\n useEffect(() => {\n const manager = externalDepsManager.current;\n\n // Register as consumer in devtools (if available)\n const devtools =\n typeof window !== 'undefined'\n ? (window as any).__BLAC_DEVTOOLS__\n : undefined;\n if (devtools?.registerConsumer && rawInstance) {\n devtools.registerConsumer(\n (rawInstance as any).instanceId,\n consumerIdRef.current,\n componentNameRef.current || 'Unknown',\n );\n }\n\n if (options?.onMount) {\n options.onMount(bloc as InstanceType<T>);\n }\n\n return () => {\n // Unregister consumer from devtools\n if (devtools?.unregisterConsumer && rawInstance) {\n devtools.unregisterConsumer(\n (rawInstance as any).instanceId,\n consumerIdRef.current,\n );\n }\n\n manager.cleanup();\n\n if (options?.onUnmount) {\n options.onUnmount(bloc as InstanceType<T>);\n }\n\n const refId = `useBloc@${componentNameRef.current ?? 'Unknown'}-${consumerIdRef.current}`;\n release(BlocClass, instanceKey, false, refId);\n };\n // oxlint-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;;;;ACVT,IAAI,iBAAiB;AAErB,SAAS,mBAAuC;AAC9C,KAAI;EAKF,MAAM,SAHH,MACE,mEACF,MAAc,qDACQ,mBAAmB;AAC5C,MAAI,OAAO,KACT,QAAO,MAAM,KAAK,eAAe,MAAM,KAAK,QAAQ,KAAA;SAEhD;;AAWV,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;CAG7C,MAAM,gBAAgB,OAAsB,KAAK;AACjD,KAAI,cAAc,YAAY,KAC5B,eAAc,UAAU,OAAO,iBAAiB;CAElD,MAAM,mBAAmB,OAAsB,KAAK;AACpD,KAAI,iBAAiB,YAAY,KAC/B,kBAAiB,UAAU,kBAAkB,IAAI;CAGnD,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;EAGlD,MAAM,WAAW,QAAQ,WAAW,aADtB,WAAW,iBAAiB,WAAW,UAAU,GAAG,cAAc,UACzB;EAEvD,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;IACA;EAAC;EAAc;EAAa;EAAY,CAAC;AAE5C,iBAAgB;EACd,MAAM,UAAU,oBAAoB;EAGpC,MAAM,WACJ,OAAO,WAAW,cACb,OAAe,oBAChB,KAAA;AACN,MAAI,UAAU,oBAAoB,YAChC,UAAS,iBACN,YAAoB,YACrB,cAAc,SACd,iBAAiB,WAAW,UAC7B;AAGH,MAAI,SAAS,QACX,SAAQ,QAAQ,KAAwB;AAG1C,eAAa;AAEX,OAAI,UAAU,sBAAsB,YAClC,UAAS,mBACN,YAAoB,YACrB,cAAc,QACf;AAGH,WAAQ,SAAS;AAEjB,OAAI,SAAS,UACX,SAAQ,UAAU,KAAwB;AAI5C,WAAQ,WAAW,aAAa,OADlB,WAAW,iBAAiB,WAAW,UAAU,GAAG,cAAc,UACnC;;IAG9C,EAAE,CAAC;AAEN,QAAO;EAAC;EAAO;EAAM;EAAa"}
package/dist/testing.cjs CHANGED
@@ -1,4 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ require("./index.cjs");
2
3
  let _blac_core = require("@blac/core");
3
4
  let _blac_core_testing = require("@blac/core/testing");
4
5
  let _testing_library_react = require("@testing-library/react");
@@ -1 +1 @@
1
- {"version":3,"file":"testing.cjs","names":["StateContainerRegistry"],"sources":["../src/testing.ts"],"sourcesContent":["import type { ReactElement } from 'react';\nimport type { RenderResult } from '@testing-library/react';\nimport type { StateContainerConstructor } from '@blac/core';\nimport { StateContainerRegistry, getRegistry, setRegistry } from '@blac/core';\nimport {\n registerOverride,\n createCubitStub,\n type CubitStubOptions,\n} from '@blac/core/testing';\nimport { render } from '@testing-library/react';\n\ninterface RenderWithBlocOptions<\n T extends StateContainerConstructor,\n> extends CubitStubOptions<T> {\n bloc: T;\n instanceKey?: string;\n}\n\nexport function renderWithBloc<T extends StateContainerConstructor>(\n ui: ReactElement,\n options: RenderWithBlocOptions<T>,\n): RenderResult & { bloc: InstanceType<T> } {\n const { bloc: BlocClass, instanceKey, ...stubOptions } = options;\n\n const previous = getRegistry();\n const testRegistry = new StateContainerRegistry();\n setRegistry(testRegistry);\n\n const instance = createCubitStub(BlocClass, stubOptions);\n registerOverride(BlocClass, instance, instanceKey);\n\n const renderResult = render(ui);\n\n const originalUnmount = renderResult.unmount;\n renderResult.unmount = () => {\n originalUnmount();\n setRegistry(previous);\n };\n\n return {\n ...renderResult,\n bloc: instance,\n };\n}\n\nexport function renderWithRegistry(\n ui: ReactElement,\n setup: (registry: StateContainerRegistry) => void,\n): RenderResult {\n const previous = getRegistry();\n const testRegistry = new StateContainerRegistry();\n setRegistry(testRegistry);\n\n setup(testRegistry);\n\n const renderResult = render(ui);\n\n const originalUnmount = renderResult.unmount;\n renderResult.unmount = () => {\n originalUnmount();\n setRegistry(previous);\n };\n\n return renderResult;\n}\n"],"mappings":";;;;;AAkBA,SAAgB,eACd,IACA,SAC0C;CAC1C,MAAM,EAAE,MAAM,WAAW,aAAa,GAAG,gBAAgB;CAEzD,MAAM,YAAA,GAAA,WAAA,cAAwB;AAE9B,EAAA,GAAA,WAAA,aADqB,IAAIA,WAAAA,wBAAwB,CACxB;CAEzB,MAAM,YAAA,GAAA,mBAAA,iBAA2B,WAAW,YAAY;AACxD,EAAA,GAAA,mBAAA,kBAAiB,WAAW,UAAU,YAAY;CAElD,MAAM,gBAAA,GAAA,uBAAA,QAAsB,GAAG;CAE/B,MAAM,kBAAkB,aAAa;AACrC,cAAa,gBAAgB;AAC3B,mBAAiB;AACjB,GAAA,GAAA,WAAA,aAAY,SAAS;;AAGvB,QAAO;EACL,GAAG;EACH,MAAM;EACP;;AAGH,SAAgB,mBACd,IACA,OACc;CACd,MAAM,YAAA,GAAA,WAAA,cAAwB;CAC9B,MAAM,eAAe,IAAIA,WAAAA,wBAAwB;AACjD,EAAA,GAAA,WAAA,aAAY,aAAa;AAEzB,OAAM,aAAa;CAEnB,MAAM,gBAAA,GAAA,uBAAA,QAAsB,GAAG;CAE/B,MAAM,kBAAkB,aAAa;AACrC,cAAa,gBAAgB;AAC3B,mBAAiB;AACjB,GAAA,GAAA,WAAA,aAAY,SAAS;;AAGvB,QAAO"}
1
+ {"version":3,"file":"testing.cjs","names":["StateContainerRegistry"],"sources":["../src/testing.ts"],"sourcesContent":["import type { ReactElement } from 'react';\nimport type { RenderResult } from '@testing-library/react';\nimport type { StateContainerConstructor } from '@blac/core';\nimport { StateContainerRegistry, getRegistry, setRegistry } from '@blac/core';\nimport {\n registerOverride,\n createCubitStub,\n type CubitStubOptions,\n} from '@blac/core/testing';\nimport { render } from '@testing-library/react';\n\ninterface RenderWithBlocOptions<\n T extends StateContainerConstructor,\n> extends CubitStubOptions<T> {\n bloc: T;\n instanceKey?: string;\n}\n\nexport function renderWithBloc<T extends StateContainerConstructor>(\n ui: ReactElement,\n options: RenderWithBlocOptions<T>,\n): RenderResult & { bloc: InstanceType<T> } {\n const { bloc: BlocClass, instanceKey, ...stubOptions } = options;\n\n const previous = getRegistry();\n const testRegistry = new StateContainerRegistry();\n setRegistry(testRegistry);\n\n const instance = createCubitStub(BlocClass, stubOptions);\n registerOverride(BlocClass, instance, instanceKey);\n\n const renderResult = render(ui);\n\n const originalUnmount = renderResult.unmount;\n renderResult.unmount = () => {\n originalUnmount();\n setRegistry(previous);\n };\n\n return {\n ...renderResult,\n bloc: instance,\n };\n}\n\nexport function renderWithRegistry(\n ui: ReactElement,\n setup: (registry: StateContainerRegistry) => void,\n): RenderResult {\n const previous = getRegistry();\n const testRegistry = new StateContainerRegistry();\n setRegistry(testRegistry);\n\n setup(testRegistry);\n\n const renderResult = render(ui);\n\n const originalUnmount = renderResult.unmount;\n renderResult.unmount = () => {\n originalUnmount();\n setRegistry(previous);\n };\n\n return renderResult;\n}\n"],"mappings":";;;;;;AAkBA,SAAgB,eACd,IACA,SAC0C;CAC1C,MAAM,EAAE,MAAM,WAAW,aAAa,GAAG,gBAAgB;CAEzD,MAAM,YAAA,GAAA,WAAA,cAAwB;AAE9B,EAAA,GAAA,WAAA,aADqB,IAAIA,WAAAA,wBAAwB,CACxB;CAEzB,MAAM,YAAA,GAAA,mBAAA,iBAA2B,WAAW,YAAY;AACxD,EAAA,GAAA,mBAAA,kBAAiB,WAAW,UAAU,YAAY;CAElD,MAAM,gBAAA,GAAA,uBAAA,QAAsB,GAAG;CAE/B,MAAM,kBAAkB,aAAa;AACrC,cAAa,gBAAgB;AAC3B,mBAAiB;AACjB,GAAA,GAAA,WAAA,aAAY,SAAS;;AAGvB,QAAO;EACL,GAAG;EACH,MAAM;EACP;;AAGH,SAAgB,mBACd,IACA,OACc;CACd,MAAM,YAAA,GAAA,WAAA,cAAwB;CAC9B,MAAM,eAAe,IAAIA,WAAAA,wBAAwB;AACjD,EAAA,GAAA,WAAA,aAAY,aAAa;AAEzB,OAAM,aAAa;CAEnB,MAAM,gBAAA,GAAA,uBAAA,QAAsB,GAAG;CAE/B,MAAM,kBAAkB,aAAa;AACrC,cAAa,gBAAgB;AAC3B,mBAAiB;AACjB,GAAA,GAAA,WAAA,aAAY,SAAS;;AAGvB,QAAO"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blac/react",
3
- "version": "2.0.10",
3
+ "version": "2.0.12",
4
4
  "license": "MIT",
5
5
  "author": "Brendan Mullins <jsnanigans@gmail.com>",
6
6
  "description": "React bindings for BlaC — useBloc hook with automatic re-render optimization",
@@ -65,13 +65,13 @@
65
65
  "bloc-pattern"
66
66
  ],
67
67
  "dependencies": {
68
- "@blac/adapter": "^2.0.11"
68
+ "@blac/adapter": "^2.0.13"
69
69
  },
70
70
  "peerDependencies": {
71
71
  "@testing-library/react": "^14.0.0 || ^15.0.0 || ^16.0.0",
72
72
  "@types/react": "^18.0.0 || ^19.0.0",
73
73
  "react": "^18.0.0 || ^19.0.0",
74
- "@blac/core": "^2.0.10"
74
+ "@blac/core": "^2.0.12"
75
75
  },
76
76
  "peerDependenciesMeta": {
77
77
  "@types/react": {
@@ -102,7 +102,7 @@
102
102
  "vite": "npm:@voidzero-dev/vite-plus-core@latest",
103
103
  "vitest": "npm:@voidzero-dev/vite-plus-test@latest",
104
104
  "vite-plus": "latest",
105
- "@blac/core": "2.0.10"
105
+ "@blac/core": "2.0.12"
106
106
  },
107
107
  "scripts": {
108
108
  "dev": "vp pack --watch",