@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 +35 -73
- package/dist/index.cjs +40 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +17 -3
- package/dist/index.js.map +1 -1
- package/dist/testing.cjs +1 -0
- package/dist/testing.cjs.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
# @blac/react
|
|
2
2
|
|
|
3
|
-
React
|
|
3
|
+
React bindings for BlaC — `useBloc` hook with proxy-based automatic re-render optimization.
|
|
4
4
|
|
|
5
|
-
**[
|
|
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`
|
|
56
|
-
- `bloc`
|
|
57
|
-
- `ref`
|
|
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):**
|
|
55
|
+
**Auto-tracking (default):** Only re-renders when accessed properties change.
|
|
62
56
|
|
|
63
57
|
```tsx
|
|
64
|
-
|
|
65
|
-
|
|
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:**
|
|
62
|
+
**Manual dependencies:** Explicit dependency array (disables auto-tracking).
|
|
71
63
|
|
|
72
64
|
```tsx
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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-
|
|
70
|
+
**No tracking:** Re-renders on every state change.
|
|
83
71
|
|
|
84
72
|
```tsx
|
|
85
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
86
|
+
### Instance Sharing
|
|
112
87
|
|
|
113
|
-
|
|
88
|
+
By default, all components using `useBloc(MyBloc)` share the same instance. Use `instanceId` for separate instances:
|
|
114
89
|
|
|
115
90
|
```tsx
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
##
|
|
103
|
+
## Testing
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
import { renderWithBloc } from '@blac/react/testing';
|
|
107
|
+
```
|
|
146
108
|
|
|
147
|
-
|
|
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
|
|
package/dist/index.cjs.map
CHANGED
|
@@ -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":";;;;;;;;;;;;;;;;;;;;;;;;;;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,SAHHA,MAAAA,QACE,mEACFA,MAAAA,QAAc,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,gBAAA,GAAA,MAAA,QAAoC,EAAE,CAAC;CAG7C,MAAM,iBAAA,GAAA,MAAA,QAAsC,KAAK;AACjD,KAAI,cAAc,YAAY,KAC5B,eAAc,UAAU,OAAO,iBAAiB;CAElD,MAAM,oBAAA,GAAA,MAAA,QAAyC,KAAK;AACpD,KAAI,iBAAiB,YAAY,KAC/B,kBAAiB,UAAU,kBAAkB,IAAI;CAGnD,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;EAGlD,MAAM,YAAA,GAAA,cAAA,SAAmB,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,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,IAAIC,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;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,IAAA,GAAA,cAAA,SAAQ,WAAW,aAAa,OADlB,WAAW,iBAAiB,WAAW,UAAU,GAAG,cAAc,UACnC;;IAG9C,EAAE,CAAC;AAEN,QAAO;EAAC;EAAO;EAAM;EAAa"}
|
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
package/dist/testing.cjs.map
CHANGED
|
@@ -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":"
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
105
|
+
"@blac/core": "2.0.12"
|
|
106
106
|
},
|
|
107
107
|
"scripts": {
|
|
108
108
|
"dev": "vp pack --watch",
|