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