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