@blac/react 2.0.20 → 2.0.21
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 +41 -29
- package/dist/BlocProvider.d.cts +54 -0
- package/dist/BlocProvider.d.ts +3 -0
- package/dist/RegistryProvider.d.cts +31 -0
- package/dist/RegistryProvider.d.ts +31 -0
- package/dist/buildTrackedProxy.d.cts +22 -0
- package/dist/buildTrackedProxy.d.ts +12 -21
- package/dist/config.d.cts +31 -0
- package/dist/config.d.ts +3 -0
- package/dist/index.cjs +199 -132
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +200 -134
- package/dist/index.js.map +1 -1
- package/dist/types.d.cts +59 -0
- package/dist/types.d.ts +5 -10
- package/dist/useBloc.d.cts +54 -0
- package/dist/useBloc.d.ts +3 -2
- package/dist/useBlocDeps.d.cts +19 -0
- package/dist/useBlocDeps.d.ts +19 -0
- package/package.json +24 -22
package/dist/index.cjs
CHANGED
|
@@ -30,6 +30,7 @@ const ProvidedArgsContext = (0, react.createContext)(/* @__PURE__ */ new Map());
|
|
|
30
30
|
* <CartWidget />
|
|
31
31
|
* </BlocProvider>
|
|
32
32
|
* ```
|
|
33
|
+
* @public
|
|
33
34
|
*/
|
|
34
35
|
function BlocProvider({ bloc, args, children }) {
|
|
35
36
|
const parentMap = (0, react.useContext)(ProvidedArgsContext);
|
|
@@ -40,7 +41,7 @@ function BlocProvider({ bloc, args, children }) {
|
|
|
40
41
|
}, [
|
|
41
42
|
parentMap,
|
|
42
43
|
bloc,
|
|
43
|
-
args
|
|
44
|
+
(0, _blac_core.resolveInstanceKey)(bloc, args)
|
|
44
45
|
]);
|
|
45
46
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProvidedArgsContext.Provider, {
|
|
46
47
|
value: mergedMap,
|
|
@@ -52,36 +53,60 @@ function BlocProvider({ bloc, args, children }) {
|
|
|
52
53
|
* bloc class, or `undefined` when called outside a matching provider.
|
|
53
54
|
*
|
|
54
55
|
* Used by `useBloc` to inherit provider args when no own `args` are given.
|
|
56
|
+
* @public
|
|
55
57
|
*/
|
|
56
58
|
function useProvidedArgs(BlocClass) {
|
|
57
59
|
return (0, react.useContext)(ProvidedArgsContext).get(BlocClass);
|
|
58
60
|
}
|
|
59
61
|
//#endregion
|
|
62
|
+
//#region src/RegistryProvider.tsx
|
|
63
|
+
/**
|
|
64
|
+
* Internal context value: a registry to use in place of the module-global
|
|
65
|
+
* default (`getRegistry()`), or `null` when no provider is present.
|
|
66
|
+
*/
|
|
67
|
+
const RegistryContext = (0, react.createContext)(null);
|
|
68
|
+
/**
|
|
69
|
+
* Scopes descendant `useBloc` calls to a specific registry instead of the
|
|
70
|
+
* module-global default.
|
|
71
|
+
*
|
|
72
|
+
* Useful for test isolation, SSR isolation, and micro-frontends that must
|
|
73
|
+
* not share bloc instances with the host page.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```tsx
|
|
77
|
+
* <RegistryProvider registry={new StateContainerRegistry()}>
|
|
78
|
+
* <App />
|
|
79
|
+
* </RegistryProvider>
|
|
80
|
+
* ```
|
|
81
|
+
* @public
|
|
82
|
+
*/
|
|
83
|
+
function RegistryProvider({ registry, children }) {
|
|
84
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RegistryContext.Provider, {
|
|
85
|
+
value: registry,
|
|
86
|
+
children
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
//#endregion
|
|
60
90
|
//#region src/buildTrackedProxy.ts
|
|
91
|
+
function supportsTrackedState(value) {
|
|
92
|
+
return typeof value[_blac_core.WITH_TRACKED_STATE] === "function";
|
|
93
|
+
}
|
|
61
94
|
/**
|
|
62
|
-
* Build a per-consumer proxy
|
|
95
|
+
* Build a per-consumer proxy for a bloc instance.
|
|
63
96
|
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* - `thisProxy` — the inner `this`-proxy used as the receiver for getter
|
|
70
|
-
* calls. Intercepts `state` to return `trackedStateRef.current` when a
|
|
71
|
-
* tracking context is active; otherwise falls through to the live value.
|
|
72
|
-
* When `onDepHandle` is supplied, a read off `this` whose value carries the
|
|
73
|
-
* `DEP_BRAND` symbol (i.e. a `depend()` handle) is routed through
|
|
74
|
-
* `onDepHandle`, which returns a per-consumer wrapper whose `.track()` opts
|
|
75
|
-
* the consumer into cross-bloc reactivity. Core stays decoupled — detection
|
|
76
|
-
* is purely by the `DEP_BRAND` symbol.
|
|
97
|
+
* Getters run with `this` bound to the **real instance**, not a proxy, so ES
|
|
98
|
+
* `#private` fields and methods work in user blocs. Path recording is done by
|
|
99
|
+
* `[WITH_TRACKED_STATE]`, which makes the instance's own `state` getter report
|
|
100
|
+
* the render's tracking proxy for the duration of the call; nested getter
|
|
101
|
+
* chains stay tracked because the override is still set.
|
|
77
102
|
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
103
|
+
* One allocation per bloc acquisition (inside `useMemo`), so the proxy is
|
|
104
|
+
* stable across renders.
|
|
80
105
|
*
|
|
81
|
-
* @param onDepHandle - Optional callback invoked when a
|
|
82
|
-
* dep handle
|
|
83
|
-
*
|
|
84
|
-
*
|
|
106
|
+
* @param onDepHandle - Optional callback invoked when a read returns a branded
|
|
107
|
+
* dep handle. Receives the original handle and returns the value to expose in
|
|
108
|
+
* its place (the session-bound wrapper). The callback is responsible for
|
|
109
|
+
* caching wrappers per handle to avoid re-allocation.
|
|
85
110
|
*/
|
|
86
111
|
function buildTrackedProxy(instance, trackedStateRef, onDepHandle) {
|
|
87
112
|
const getterDescs = /* @__PURE__ */ new Map();
|
|
@@ -94,20 +119,29 @@ function buildTrackedProxy(instance, trackedStateRef, onDepHandle) {
|
|
|
94
119
|
}
|
|
95
120
|
proto = Object.getPrototypeOf(proto);
|
|
96
121
|
}
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
const value = Reflect.get(t, k, r);
|
|
122
|
+
const boundMethods = /* @__PURE__ */ new Map();
|
|
123
|
+
const wrapDepHandle = (value) => {
|
|
100
124
|
if (onDepHandle !== void 0 && (typeof value === "function" || typeof value === "object") && value !== null && value[_blac_core.DEP_BRAND] !== void 0) return onDepHandle(value);
|
|
101
125
|
return value;
|
|
102
|
-
} });
|
|
103
|
-
return {
|
|
104
|
-
proxy: new Proxy(instance, { get(target, key, receiver) {
|
|
105
|
-
const desc = getterDescs.get(key);
|
|
106
|
-
if (desc?.get) return desc.get.call(thisProxy);
|
|
107
|
-
return Reflect.get(target, key, receiver);
|
|
108
|
-
} }),
|
|
109
|
-
thisProxy
|
|
110
126
|
};
|
|
127
|
+
return { proxy: new Proxy(instance, { get(target, key) {
|
|
128
|
+
const getter = getterDescs.get(key)?.get;
|
|
129
|
+
if (getter) {
|
|
130
|
+
const tracked = trackedStateRef.current;
|
|
131
|
+
const read = () => getter.call(target);
|
|
132
|
+
return wrapDepHandle(tracked == null || !supportsTrackedState(target) ? read() : target[_blac_core.WITH_TRACKED_STATE](tracked, read, onDepHandle));
|
|
133
|
+
}
|
|
134
|
+
const value = Reflect.get(target, key, target);
|
|
135
|
+
if (typeof value === "function") {
|
|
136
|
+
let bound = boundMethods.get(key);
|
|
137
|
+
if (bound === void 0) {
|
|
138
|
+
bound = value.bind(target);
|
|
139
|
+
boundMethods.set(key, bound);
|
|
140
|
+
}
|
|
141
|
+
return wrapDepHandle(bound);
|
|
142
|
+
}
|
|
143
|
+
return wrapDepHandle(value);
|
|
144
|
+
} }) };
|
|
111
145
|
}
|
|
112
146
|
//#endregion
|
|
113
147
|
//#region src/useBloc.ts
|
|
@@ -123,7 +157,7 @@ const depRefId = (consumerId) => `useBloc@${consumerId}:dep`;
|
|
|
123
157
|
* - **Auto-tracking** (default): the returned state value is a proxy that
|
|
124
158
|
* records read paths during render. The component re-renders when any
|
|
125
159
|
* recorded path changes. Backed by `@dirtytalk/structural`'s
|
|
126
|
-
*
|
|
160
|
+
* `trackRender` + the container's path-scoped `DirtyChannel`.
|
|
127
161
|
* - **Manual select**: pass `options.select` to opt out of auto-tracking.
|
|
128
162
|
* The hook re-renders only when the returned array's elements change
|
|
129
163
|
* (per-index `Object.is`).
|
|
@@ -143,7 +177,7 @@ const depRefId = (consumerId) => `useBloc@${consumerId}:dep`;
|
|
|
143
177
|
* const [state, bloc] = useBloc(MyBloc, { args: { _id: id } });
|
|
144
178
|
* ```
|
|
145
179
|
*
|
|
146
|
-
* @
|
|
180
|
+
* @typeParam T - The state container constructor type (inferred from BlocClass)
|
|
147
181
|
* @param BlocClass - The state container class to connect to
|
|
148
182
|
* @param options - Configuration options
|
|
149
183
|
* @returns Tuple of `[state, bloc, ref]`
|
|
@@ -164,13 +198,13 @@ const depRefId = (consumerId) => `useBloc@${consumerId}:dep`;
|
|
|
164
198
|
* ```ts
|
|
165
199
|
* const [state, bloc] = useBloc(UserBloc, { args: { userId: 'alice' } });
|
|
166
200
|
* ```
|
|
201
|
+
* @public
|
|
167
202
|
*/
|
|
168
203
|
function useBloc(BlocClass, options) {
|
|
169
|
-
const
|
|
204
|
+
const registry = (0, react.useContext)(RegistryContext) ?? (0, _blac_core.getRegistry)();
|
|
170
205
|
const consumerIdRef = (0, react.useRef)(null);
|
|
171
206
|
if (consumerIdRef.current === null) consumerIdRef.current = `useBloc-${nextConsumerId++}`;
|
|
172
207
|
const consumerId = consumerIdRef.current;
|
|
173
|
-
(0, react.useId)();
|
|
174
208
|
const selectRef = (0, react.useRef)(options?.select);
|
|
175
209
|
selectRef.current = options?.select;
|
|
176
210
|
const onMountRef = (0, react.useRef)(options?.onMount);
|
|
@@ -201,27 +235,30 @@ function useBloc(BlocClass, options) {
|
|
|
201
235
|
key: providerArgs === void 0 ? void 0 : JSON.stringify(providerArgs)
|
|
202
236
|
};
|
|
203
237
|
const providerArgsKey = providerArgsKeyRef.current.key;
|
|
238
|
+
const resolveEffectiveArgs = () => ownArgsRef.current !== void 0 ? ownArgsRef.current : providerArgsRef.current;
|
|
204
239
|
const trackedStateRef = (0, react.useRef)(null);
|
|
205
|
-
const sessionRef = (0, react.useRef)(/* @__PURE__ */ new Map());
|
|
206
|
-
const depSubsRef = (0, react.useRef)(/* @__PURE__ */ new Map());
|
|
207
|
-
const depWrapperCacheRef = (0, react.useRef)(/* @__PURE__ */ new Map());
|
|
208
240
|
const proxyCacheRef = (0, react.useRef)(new _dirtytalk_structural.ProxyCache());
|
|
209
|
-
const
|
|
210
|
-
const
|
|
241
|
+
const depWrapperCacheRef = (0, react.useRef)(/* @__PURE__ */ new Map());
|
|
242
|
+
const rebindNonceRef = (0, react.useRef)(0);
|
|
243
|
+
const rebindNonce = rebindNonceRef.current;
|
|
244
|
+
const consumerRef = (0, react.useRef)(null);
|
|
245
|
+
consumerRef.current ??= createConsumer();
|
|
246
|
+
const consumer = consumerRef.current;
|
|
211
247
|
const ownedBlocRef = (0, react.useRef)(null);
|
|
212
248
|
const { bloc, instanceKey, trackedBloc } = (0, react.useMemo)(() => {
|
|
213
|
-
const effectiveArgs =
|
|
249
|
+
const effectiveArgs = resolveEffectiveArgs();
|
|
214
250
|
const resolvedKey = (0, _blac_core.resolveInstanceKey)(BlocClass, effectiveArgs);
|
|
215
|
-
const instance =
|
|
251
|
+
const instance = registry.acquire(BlocClass, resolvedKey, {
|
|
216
252
|
canCreate: true,
|
|
217
253
|
countRef: false,
|
|
218
|
-
args: effectiveArgs
|
|
254
|
+
args: effectiveArgs,
|
|
255
|
+
sweepIfUnowned: true
|
|
219
256
|
});
|
|
220
257
|
const onDepHandle = (handle) => {
|
|
221
258
|
const cache = depWrapperCacheRef.current;
|
|
222
259
|
const cached = cache.get(handle);
|
|
223
260
|
if (cached !== void 0) return cached;
|
|
224
|
-
const wrapper = makeDepWrapper(handle, consumerId, trackedStateRef,
|
|
261
|
+
const wrapper = makeDepWrapper(handle, consumerId, registry, trackedStateRef, consumer, onDepHandle);
|
|
225
262
|
cache.set(handle, wrapper);
|
|
226
263
|
return wrapper;
|
|
227
264
|
};
|
|
@@ -235,58 +272,50 @@ function useBloc(BlocClass, options) {
|
|
|
235
272
|
BlocClass,
|
|
236
273
|
ownArgsKey,
|
|
237
274
|
providerArgsKey,
|
|
238
|
-
rebindNonce
|
|
275
|
+
rebindNonce,
|
|
276
|
+
registry
|
|
239
277
|
]);
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
(0, react.useEffect)(() => {
|
|
247
|
-
const channel = bloc.channel;
|
|
248
|
-
if (selectRef.current !== void 0) {
|
|
249
|
-
const unsub = channel.subscribe(() => _dirtytalk_structural.ALL_PATHS, () => {
|
|
278
|
+
consumer.container = bloc;
|
|
279
|
+
(0, react.useSyncExternalStore)((0, react.useMemo)(() => (onStoreChange) => {
|
|
280
|
+
consumer.notify = onStoreChange;
|
|
281
|
+
const container = consumer.container;
|
|
282
|
+
const unsubscribe = container.channel.subscribe(() => consumer.interest, () => {
|
|
283
|
+
if (consumer.isSelectMode) {
|
|
250
284
|
const select = selectRef.current;
|
|
251
|
-
if (
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
const prev = lastSelectionRef.current;
|
|
257
|
-
if (prev !== null && shallowArrayEqual(prev, next)) return;
|
|
258
|
-
lastSelectionRef.current = next;
|
|
259
|
-
force();
|
|
260
|
-
});
|
|
261
|
-
const select = selectRef.current;
|
|
262
|
-
if (select) {
|
|
263
|
-
const next = select(bloc.state, bloc);
|
|
264
|
-
const prev = lastSelectionRef.current;
|
|
265
|
-
if (prev === null || !shallowArrayEqual(prev, next)) {
|
|
266
|
-
lastSelectionRef.current = next;
|
|
267
|
-
force();
|
|
285
|
+
if (select) {
|
|
286
|
+
const next = select(container.state, container);
|
|
287
|
+
const prev = consumer.selection;
|
|
288
|
+
if (prev !== null && shallowArrayEqual(prev, next)) return;
|
|
289
|
+
consumer.selection = next;
|
|
268
290
|
}
|
|
269
291
|
}
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
bloc.registerConsumerPaths(consumerId, pathRef.current);
|
|
274
|
-
if (bloc.state !== renderStateRef.current) force();
|
|
292
|
+
consumer.bump();
|
|
293
|
+
});
|
|
294
|
+
container.registerConsumerPaths(consumerId, consumer.paths);
|
|
275
295
|
return () => {
|
|
276
|
-
|
|
277
|
-
|
|
296
|
+
consumer.notify = noop;
|
|
297
|
+
unsubscribe();
|
|
298
|
+
container.unregisterConsumer(consumerId);
|
|
278
299
|
};
|
|
279
|
-
}, [
|
|
300
|
+
}, [
|
|
301
|
+
BlocClass,
|
|
302
|
+
instanceKey,
|
|
303
|
+
consumerId,
|
|
304
|
+
rebindNonce
|
|
305
|
+
]), consumer.getSnapshot, consumer.getServerSnapshot);
|
|
280
306
|
(0, react.useLayoutEffect)(() => {
|
|
281
|
-
const registry = (0, _blac_core.getRegistry)();
|
|
282
307
|
const live = registry.acquire(BlocClass, instanceKey, {
|
|
283
308
|
canCreate: true,
|
|
284
309
|
countRef: true,
|
|
285
|
-
refId: primaryRefId(consumerId)
|
|
310
|
+
refId: primaryRefId(consumerId),
|
|
311
|
+
args: resolveEffectiveArgs()
|
|
286
312
|
});
|
|
287
313
|
ownedBlocRef.current = live;
|
|
288
314
|
onMountRef.current?.(live);
|
|
289
|
-
if (live !== bloc)
|
|
315
|
+
if (live !== bloc) {
|
|
316
|
+
rebindNonceRef.current++;
|
|
317
|
+
consumer.bump();
|
|
318
|
+
}
|
|
290
319
|
return () => {
|
|
291
320
|
onUnmountRef.current?.(ownedBlocRef.current ?? bloc);
|
|
292
321
|
registry.release(BlocClass, instanceKey, false, primaryRefId(consumerId));
|
|
@@ -294,26 +323,26 @@ function useBloc(BlocClass, options) {
|
|
|
294
323
|
}, [
|
|
295
324
|
BlocClass,
|
|
296
325
|
instanceKey,
|
|
297
|
-
consumerId
|
|
326
|
+
consumerId,
|
|
327
|
+
registry
|
|
298
328
|
]);
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
lastSelectionRef.current = null;
|
|
304
|
-
}
|
|
329
|
+
const container = consumer.container;
|
|
330
|
+
const rawState = container.state;
|
|
331
|
+
const select = selectRef.current;
|
|
332
|
+
consumer.isSelectMode = select !== void 0;
|
|
305
333
|
let state;
|
|
306
|
-
if (
|
|
334
|
+
if (select !== void 0) {
|
|
307
335
|
state = rawState;
|
|
308
|
-
if (
|
|
336
|
+
if (consumer.selection === null) consumer.selection = select(rawState, bloc);
|
|
337
|
+
consumer.interest = _dirtytalk_structural.ALL_PATHS;
|
|
309
338
|
} else {
|
|
310
|
-
const tracked = (0, _dirtytalk_structural.trackRender)(rawState,
|
|
339
|
+
const tracked = (0, _dirtytalk_structural.trackRender)(rawState, container.interner, proxyCacheRef.current);
|
|
311
340
|
state = tracked.value;
|
|
312
341
|
trackedStateRef.current = tracked.value;
|
|
313
|
-
|
|
314
|
-
const session =
|
|
342
|
+
consumer.paths = tracked.paths;
|
|
343
|
+
const session = consumer.session;
|
|
315
344
|
session.clear();
|
|
316
|
-
session.set(
|
|
345
|
+
session.set(container, {
|
|
317
346
|
kind: "primary",
|
|
318
347
|
paths: tracked.paths
|
|
319
348
|
});
|
|
@@ -321,15 +350,15 @@ function useBloc(BlocClass, options) {
|
|
|
321
350
|
}
|
|
322
351
|
(0, react.useLayoutEffect)(() => {
|
|
323
352
|
trackedStateRef.current = null;
|
|
324
|
-
if (
|
|
325
|
-
|
|
353
|
+
if (consumer.isSelectMode) {
|
|
354
|
+
consumer.lastReconcile = null;
|
|
326
355
|
return;
|
|
327
356
|
}
|
|
328
|
-
const
|
|
329
|
-
const paths =
|
|
330
|
-
const session =
|
|
331
|
-
const last =
|
|
332
|
-
if (last !== null && last.primaryContainer ===
|
|
357
|
+
const primary = consumer.container;
|
|
358
|
+
const paths = consumer.paths;
|
|
359
|
+
const session = consumer.session;
|
|
360
|
+
const last = consumer.lastReconcile;
|
|
361
|
+
if (last !== null && last.primaryContainer === primary && (0, _dirtytalk_structural.pathSetEquals)(last.primaryPaths, paths)) {
|
|
333
362
|
let unchanged = last.deps.size === session.size - 1;
|
|
334
363
|
if (unchanged) for (const [depContainer, entry] of session) {
|
|
335
364
|
if (entry.kind === "primary") continue;
|
|
@@ -341,13 +370,13 @@ function useBloc(BlocClass, options) {
|
|
|
341
370
|
}
|
|
342
371
|
if (unchanged) return;
|
|
343
372
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
const subs =
|
|
373
|
+
primary.registerConsumerPaths(consumerId, paths);
|
|
374
|
+
consumer.interest = expandWithAncestors(paths, primary.interner);
|
|
375
|
+
const subs = consumer.depSubs;
|
|
347
376
|
for (const [depContainer, sub] of subs) if (!session.has(depContainer)) {
|
|
348
377
|
sub.unsubscribe();
|
|
349
378
|
depContainer.unregisterConsumer(consumerId);
|
|
350
|
-
|
|
379
|
+
registry.release(sub.Type, sub.key, false, sub.refId);
|
|
351
380
|
subs.delete(depContainer);
|
|
352
381
|
}
|
|
353
382
|
for (const [depContainer, entry] of session) {
|
|
@@ -359,14 +388,14 @@ function useBloc(BlocClass, options) {
|
|
|
359
388
|
existing.interestRef.current = interest;
|
|
360
389
|
continue;
|
|
361
390
|
}
|
|
362
|
-
|
|
391
|
+
registry.acquire(entry.Type, entry.key, {
|
|
363
392
|
canCreate: true,
|
|
364
393
|
countRef: true,
|
|
365
394
|
refId: entry.refId,
|
|
366
395
|
args: entry.args
|
|
367
396
|
});
|
|
368
397
|
const interestRef = { current: interest };
|
|
369
|
-
const unsubscribe = depContainer.channel.subscribe(() => interestRef.current, () =>
|
|
398
|
+
const unsubscribe = depContainer.channel.subscribe(() => interestRef.current, () => consumer.bump());
|
|
370
399
|
subs.set(depContainer, {
|
|
371
400
|
unsubscribe,
|
|
372
401
|
interestRef,
|
|
@@ -386,28 +415,46 @@ function useBloc(BlocClass, options) {
|
|
|
386
415
|
args: entry.args
|
|
387
416
|
});
|
|
388
417
|
}
|
|
389
|
-
|
|
390
|
-
primaryContainer:
|
|
418
|
+
consumer.lastReconcile = {
|
|
419
|
+
primaryContainer: primary,
|
|
391
420
|
primaryPaths: paths,
|
|
392
421
|
deps: depsSignature
|
|
393
422
|
};
|
|
394
423
|
});
|
|
395
424
|
(0, react.useEffect)(() => {
|
|
396
|
-
const subs =
|
|
425
|
+
const subs = consumer.depSubs;
|
|
397
426
|
return () => {
|
|
398
427
|
for (const [depContainer, sub] of subs) {
|
|
399
428
|
sub.unsubscribe();
|
|
400
429
|
depContainer.unregisterConsumer(consumerId);
|
|
401
|
-
|
|
430
|
+
registry.release(sub.Type, sub.key, false, sub.refId);
|
|
402
431
|
}
|
|
403
432
|
subs.clear();
|
|
404
433
|
};
|
|
405
|
-
}, [consumerId]);
|
|
406
|
-
return [
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
434
|
+
}, [consumerId, registry]);
|
|
435
|
+
return [state, trackedBloc];
|
|
436
|
+
}
|
|
437
|
+
const noop = () => {};
|
|
438
|
+
function createConsumer() {
|
|
439
|
+
const consumer = {
|
|
440
|
+
container: null,
|
|
441
|
+
version: 0,
|
|
442
|
+
notify: noop,
|
|
443
|
+
bump: () => {
|
|
444
|
+
consumer.version++;
|
|
445
|
+
consumer.notify();
|
|
446
|
+
},
|
|
447
|
+
getSnapshot: () => consumer.version,
|
|
448
|
+
getServerSnapshot: () => consumer.version,
|
|
449
|
+
interest: (0, _dirtytalk_structural.emptyPathSet)(),
|
|
450
|
+
paths: (0, _dirtytalk_structural.emptyPathSet)(),
|
|
451
|
+
isSelectMode: false,
|
|
452
|
+
selection: null,
|
|
453
|
+
session: /* @__PURE__ */ new Map(),
|
|
454
|
+
depSubs: /* @__PURE__ */ new Map(),
|
|
455
|
+
lastReconcile: null
|
|
456
|
+
};
|
|
457
|
+
return consumer;
|
|
411
458
|
}
|
|
412
459
|
/**
|
|
413
460
|
* Build the per-consumer wrapper that replaces a branded dep handle inside a
|
|
@@ -431,10 +478,9 @@ function useBloc(BlocClass, options) {
|
|
|
431
478
|
* the dep already has a non-primary session entry this render, reuse its proxy
|
|
432
479
|
* + union its paths instead of re-acquiring.
|
|
433
480
|
*/
|
|
434
|
-
function makeDepWrapper(handle, consumerId, trackedStateRef,
|
|
481
|
+
function makeDepWrapper(handle, consumerId, registry, trackedStateRef, consumer, onDepHandle) {
|
|
435
482
|
const brand = handle[_blac_core.DEP_BRAND];
|
|
436
483
|
const refId = depRefId(consumerId);
|
|
437
|
-
const registry = (0, _blac_core.getRegistry)();
|
|
438
484
|
const perDep = /* @__PURE__ */ new Map();
|
|
439
485
|
const proxyCache = new _dirtytalk_structural.ProxyCache();
|
|
440
486
|
const resolve = (options) => {
|
|
@@ -451,7 +497,7 @@ function makeDepWrapper(handle, consumerId, trackedStateRef, sessionRef, onDepHa
|
|
|
451
497
|
track: (options) => {
|
|
452
498
|
const { dep, key, args } = resolve(options);
|
|
453
499
|
if (trackedStateRef.current == null) return [dep.state, dep];
|
|
454
|
-
const session =
|
|
500
|
+
const session = consumer.session;
|
|
455
501
|
const existing = session.get(dep);
|
|
456
502
|
const tracked = (0, _dirtytalk_structural.trackRender)(dep.state, dep.interner, proxyCache);
|
|
457
503
|
let cache = perDep.get(dep);
|
|
@@ -534,21 +580,41 @@ function expandWithAncestors(paths, interner) {
|
|
|
534
580
|
}
|
|
535
581
|
return expanded;
|
|
536
582
|
}
|
|
537
|
-
|
|
583
|
+
//#endregion
|
|
584
|
+
//#region src/useBlocDeps.ts
|
|
538
585
|
/**
|
|
539
|
-
*
|
|
586
|
+
* Feed live values from the component into a bloc's `deps` lane.
|
|
587
|
+
*
|
|
588
|
+
* Each calling component is one owner: its slice is shallow-merged into the
|
|
589
|
+
* bloc's merged `deps` view, and withdrawn when the component unmounts. The
|
|
590
|
+
* bloc observes changes through `onDepsChanged`.
|
|
591
|
+
*
|
|
592
|
+
* ```tsx
|
|
593
|
+
* const [state, bloc] = useBloc(CanvasCubit);
|
|
594
|
+
* useBlocDeps(bloc, { canvas, onTick });
|
|
595
|
+
* ```
|
|
540
596
|
*
|
|
541
|
-
*
|
|
597
|
+
* Use this for values a bloc must *read* but should not own — DOM handles,
|
|
598
|
+
* callbacks, values from other hooks. Pass identity-stable values (`useMemo` /
|
|
599
|
+
* `useCallback`) as you would to any hook; a new function identity each render
|
|
600
|
+
* counts as a change.
|
|
542
601
|
*/
|
|
543
|
-
function
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
602
|
+
function useBlocDeps(bloc, slice) {
|
|
603
|
+
const ownerId = (0, react.useId)();
|
|
604
|
+
const latest = (0, react.useRef)(slice);
|
|
605
|
+
latest.current = slice;
|
|
606
|
+
(0, react.useEffect)(() => {
|
|
607
|
+
bloc[_blac_core.APPLY_DEPS](ownerId, latest.current);
|
|
608
|
+
});
|
|
609
|
+
(0, react.useEffect)(() => {
|
|
610
|
+
return () => {
|
|
611
|
+
bloc[_blac_core.REMOVE_DEPS_OWNER](ownerId);
|
|
612
|
+
};
|
|
613
|
+
}, [bloc, ownerId]);
|
|
548
614
|
}
|
|
549
615
|
//#endregion
|
|
550
616
|
exports.BlocProvider = BlocProvider;
|
|
551
|
-
exports.
|
|
617
|
+
exports.RegistryProvider = RegistryProvider;
|
|
552
618
|
Object.defineProperty(exports, "untracked", {
|
|
553
619
|
enumerable: true,
|
|
554
620
|
get: function() {
|
|
@@ -556,6 +622,7 @@ Object.defineProperty(exports, "untracked", {
|
|
|
556
622
|
}
|
|
557
623
|
});
|
|
558
624
|
exports.useBloc = useBloc;
|
|
625
|
+
exports.useBlocDeps = useBlocDeps;
|
|
559
626
|
exports.useProvidedArgs = useProvidedArgs;
|
|
560
627
|
|
|
561
628
|
//# sourceMappingURL=index.cjs.map
|