@blac/react 2.0.19 → 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/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 pair for a bloc instance.
95
+ * Build a per-consumer proxy for a bloc instance.
63
96
  *
64
- * Returns two proxies:
65
- * - `proxy` — the stable outer proxy returned to the consumer. Getter
66
- * properties are invoked with `thisProxy` as `this` so that `this.state`
67
- * reads inside getters are redirected to the current render's tracking
68
- * proxy. Non-getter properties fall through to the live instance.
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
- * Both allocations happen exactly once per bloc acquisition (inside `useMemo`)
79
- * so the proxies are stable across renders.
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 getter reads a branded
82
- * dep handle off `this`. Receives the original handle and returns the value
83
- * to expose in its place (the session-bound wrapper). The callback is
84
- * responsible for caching wrappers per handle to avoid re-allocation.
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 thisProxy = new Proxy(instance, { get(t, k, r) {
98
- if (k === "state") return trackedStateRef.current ?? Reflect.get(t, k, r);
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
- * {@link trackRender} + the container's path-scoped `DirtyChannel`.
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
- * @template T - The state container constructor type (inferred from BlocClass)
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 componentRef = (0, react.useRef)({});
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 lastReconcileRef = (0, react.useRef)(null);
210
- const [rebindNonce, bumpRebind] = (0, react.useReducer)((x) => x + 1, 0);
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 = ownArgsRef.current !== void 0 ? ownArgsRef.current : providerArgsRef.current;
249
+ const effectiveArgs = resolveEffectiveArgs();
214
250
  const resolvedKey = (0, _blac_core.resolveInstanceKey)(BlocClass, effectiveArgs);
215
- const instance = (0, _blac_core.getRegistry)().acquire(BlocClass, resolvedKey, {
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, sessionRef, onDepHandle);
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
- const [, force] = (0, react.useReducer)((x) => x + 1, 0);
241
- const pathRef = (0, react.useRef)((0, _dirtytalk_structural.emptyPathSet)());
242
- const expandedInterestRef = (0, react.useRef)((0, _dirtytalk_structural.emptyPathSet)());
243
- const lastSelectionRef = (0, react.useRef)(null);
244
- const renderStateRef = (0, react.useRef)(void 0);
245
- const prevBlocRef = (0, react.useRef)(null);
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 (!select) {
252
- force();
253
- return;
254
- }
255
- const next = select(bloc.state, bloc);
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
- return unsub;
271
- }
272
- const unsub = channel.subscribe(() => expandedInterestRef.current, () => force());
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
- unsub();
277
- bloc.unregisterConsumer(consumerId);
296
+ consumer.notify = noop;
297
+ unsubscribe();
298
+ container.unregisterConsumer(consumerId);
278
299
  };
279
- }, [bloc, consumerId]);
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) bumpRebind();
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 rawState = bloc.state;
300
- renderStateRef.current = rawState;
301
- if (prevBlocRef.current !== bloc) {
302
- prevBlocRef.current = bloc;
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 (selectRef.current !== void 0) {
334
+ if (select !== void 0) {
307
335
  state = rawState;
308
- if (lastSelectionRef.current === null) lastSelectionRef.current = selectRef.current(rawState, bloc);
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, bloc.interner, proxyCacheRef.current);
339
+ const tracked = (0, _dirtytalk_structural.trackRender)(rawState, container.interner, proxyCacheRef.current);
311
340
  state = tracked.value;
312
341
  trackedStateRef.current = tracked.value;
313
- pathRef.current = tracked.paths;
314
- const session = sessionRef.current;
342
+ consumer.paths = tracked.paths;
343
+ const session = consumer.session;
315
344
  session.clear();
316
- session.set(bloc, {
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 (selectRef.current !== void 0) {
325
- lastReconcileRef.current = null;
353
+ if (consumer.isSelectMode) {
354
+ consumer.lastReconcile = null;
326
355
  return;
327
356
  }
328
- const container = bloc;
329
- const paths = pathRef.current;
330
- const session = sessionRef.current;
331
- const last = lastReconcileRef.current;
332
- if (last !== null && last.primaryContainer === container && (0, _dirtytalk_structural.pathSetEquals)(last.primaryPaths, paths)) {
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
- container.registerConsumerPaths(consumerId, paths);
345
- expandedInterestRef.current = expandWithAncestors(paths, container.interner);
346
- const subs = depSubsRef.current;
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
- (0, _blac_core.getRegistry)().release(sub.Type, sub.key, false, sub.refId);
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
- (0, _blac_core.getRegistry)().acquire(entry.Type, entry.key, {
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, () => force());
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
- lastReconcileRef.current = {
390
- primaryContainer: container,
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 = depSubsRef.current;
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
- (0, _blac_core.getRegistry)().release(sub.Type, sub.key, false, sub.refId);
430
+ registry.release(sub.Type, sub.key, false, sub.refId);
402
431
  }
403
432
  subs.clear();
404
433
  };
405
- }, [consumerId]);
406
- return [
407
- state,
408
- trackedBloc,
409
- componentRef
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, sessionRef, onDepHandle) {
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 = sessionRef.current;
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
- let globalConfig = {};
583
+ //#endregion
584
+ //#region src/useBlocDeps.ts
538
585
  /**
539
- * Configure global defaults for `@blac/react` hooks.
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
- * @param config - Partial configuration to merge with current globals
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 configureBlacReact(config) {
544
- globalConfig = {
545
- ...globalConfig,
546
- ...config
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.configureBlacReact = configureBlacReact;
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