@blac/react 2.1.2 → 2.1.4
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/LICENSE +1 -1
- package/README.md +2 -2
- package/dist/RegistryProvider-B9-mQQRg.cjs +51 -0
- package/dist/RegistryProvider-B9-mQQRg.cjs.map +1 -0
- package/dist/RegistryProvider-DS4ltjlH.js +40 -0
- package/dist/RegistryProvider-DS4ltjlH.js.map +1 -0
- package/dist/depSession.d.cts +6 -0
- package/dist/depSession.d.ts +6 -0
- package/dist/index.cjs +29 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +29 -15
- package/dist/index.js.map +1 -1
- package/dist/testing.cjs +34 -1
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +14 -1
- package/dist/testing.d.ts +14 -1
- package/dist/testing.js +33 -2
- package/dist/testing.js.map +1 -1
- package/dist/useBloc.d.cts +2 -5
- package/dist/useBloc.d.ts +2 -5
- package/package.json +4 -4
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
React bindings for BlaC — `useBloc` hook with proxy-based automatic re-render optimization.
|
|
4
4
|
|
|
5
|
-
**[Documentation](https://blac
|
|
5
|
+
**[Documentation](https://blac.42.dog/react/getting-started)** · **[npm](https://www.npmjs.com/package/@blac/react)**
|
|
6
6
|
|
|
7
7
|
> [!WARNING]
|
|
8
8
|
> **BlaC v2 is in pre-release (beta).** While in beta, **breaking API changes may
|
|
@@ -343,7 +343,7 @@ class UserCardCubit extends Cubit<UserCardState, { userId: string }> {
|
|
|
343
343
|
import { renderWithBloc } from '@blac/react/testing';
|
|
344
344
|
```
|
|
345
345
|
|
|
346
|
-
See the [testing docs](https://blac
|
|
346
|
+
See the [testing docs](https://blac.42.dog/testing/react) for details.
|
|
347
347
|
|
|
348
348
|
## License
|
|
349
349
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
let react = require("react");
|
|
2
|
+
let _blac_core = require("@blac/core");
|
|
3
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
4
|
+
//#region src/RegistryProvider.tsx
|
|
5
|
+
const RegistryContext = (0, react.createContext)(null);
|
|
6
|
+
/**
|
|
7
|
+
* Scopes descendant `useBloc` calls to a specific registry instead of the
|
|
8
|
+
* module-global default.
|
|
9
|
+
*
|
|
10
|
+
* Useful for test isolation, SSR isolation, and micro-frontends that must
|
|
11
|
+
* not share bloc instances with the host page.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* <RegistryProvider registry={new StateContainerRegistry()}>
|
|
16
|
+
* <App />
|
|
17
|
+
* </RegistryProvider>
|
|
18
|
+
* ```
|
|
19
|
+
* @public
|
|
20
|
+
*/
|
|
21
|
+
function RegistryProvider({ registry, children }) {
|
|
22
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RegistryContext.Provider, {
|
|
23
|
+
value: registry,
|
|
24
|
+
children
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The registry `useBloc` uses here: the nearest {@link RegistryProvider}'s,
|
|
29
|
+
* else `getRegistry()`. The plain `@blac/core` helpers (`acquire`, `ensure`,
|
|
30
|
+
* `watch`, …) can't read React context and always use `getRegistry()`; call
|
|
31
|
+
* this registry's methods directly to stay inside a provider's scope.
|
|
32
|
+
* @public
|
|
33
|
+
*/
|
|
34
|
+
function useRegistry() {
|
|
35
|
+
return (0, react.useContext)(RegistryContext) ?? (0, _blac_core.getRegistry)();
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
38
|
+
Object.defineProperty(exports, "RegistryProvider", {
|
|
39
|
+
enumerable: true,
|
|
40
|
+
get: function() {
|
|
41
|
+
return RegistryProvider;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
Object.defineProperty(exports, "useRegistry", {
|
|
45
|
+
enumerable: true,
|
|
46
|
+
get: function() {
|
|
47
|
+
return useRegistry;
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
//# sourceMappingURL=RegistryProvider-B9-mQQRg.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RegistryProvider-B9-mQQRg.cjs","names":[],"sources":["../src/RegistryProvider.tsx"],"sourcesContent":["import {\n createContext,\n useContext,\n type ReactElement,\n type ReactNode,\n} from 'react';\nimport { getRegistry, type StateContainerRegistry } from '@blac/core';\n\nconst RegistryContext = createContext<StateContainerRegistry | null>(null);\n\n/**\n * Props for {@link RegistryProvider}.\n * @public\n */\nexport interface RegistryProviderProps {\n registry: StateContainerRegistry;\n children: ReactNode;\n}\n\n/**\n * Scopes descendant `useBloc` calls to a specific registry instead of the\n * module-global default.\n *\n * Useful for test isolation, SSR isolation, and micro-frontends that must\n * not share bloc instances with the host page.\n *\n * @example\n * ```tsx\n * <RegistryProvider registry={new StateContainerRegistry()}>\n * <App />\n * </RegistryProvider>\n * ```\n * @public\n */\nexport function RegistryProvider({\n registry,\n children,\n}: RegistryProviderProps): ReactElement {\n return (\n <RegistryContext.Provider value={registry}>\n {children}\n </RegistryContext.Provider>\n );\n}\n\n/**\n * The registry `useBloc` uses here: the nearest {@link RegistryProvider}'s,\n * else `getRegistry()`. The plain `@blac/core` helpers (`acquire`, `ensure`,\n * `watch`, …) can't read React context and always use `getRegistry()`; call\n * this registry's methods directly to stay inside a provider's scope.\n * @public\n */\nexport function useRegistry(): StateContainerRegistry {\n return useContext(RegistryContext) ?? getRegistry();\n}\n"],"mappings":";;;;AAQA,MAAM,mBAAA,GAAA,MAAA,eAA+D,IAAI;;;;;;;;;;;;;;;;AA0BzE,SAAgB,iBAAiB,EAC/B,UACA,YACsC;CACtC,OACE,iBAAA,GAAA,kBAAA,KAAC,gBAAgB,UAAjB;EAA0B,OAAO;EAC9B;CACuB,CAAA;AAE9B;;;;;;;;AASA,SAAgB,cAAsC;CACpD,QAAA,GAAA,MAAA,YAAkB,eAAe,MAAA,GAAA,WAAA,aAAiB;AACpD"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
import { getRegistry } from "@blac/core";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
//#region src/RegistryProvider.tsx
|
|
5
|
+
const RegistryContext = createContext(null);
|
|
6
|
+
/**
|
|
7
|
+
* Scopes descendant `useBloc` calls to a specific registry instead of the
|
|
8
|
+
* module-global default.
|
|
9
|
+
*
|
|
10
|
+
* Useful for test isolation, SSR isolation, and micro-frontends that must
|
|
11
|
+
* not share bloc instances with the host page.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* <RegistryProvider registry={new StateContainerRegistry()}>
|
|
16
|
+
* <App />
|
|
17
|
+
* </RegistryProvider>
|
|
18
|
+
* ```
|
|
19
|
+
* @public
|
|
20
|
+
*/
|
|
21
|
+
function RegistryProvider({ registry, children }) {
|
|
22
|
+
return /* @__PURE__ */ jsx(RegistryContext.Provider, {
|
|
23
|
+
value: registry,
|
|
24
|
+
children
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The registry `useBloc` uses here: the nearest {@link RegistryProvider}'s,
|
|
29
|
+
* else `getRegistry()`. The plain `@blac/core` helpers (`acquire`, `ensure`,
|
|
30
|
+
* `watch`, …) can't read React context and always use `getRegistry()`; call
|
|
31
|
+
* this registry's methods directly to stay inside a provider's scope.
|
|
32
|
+
* @public
|
|
33
|
+
*/
|
|
34
|
+
function useRegistry() {
|
|
35
|
+
return useContext(RegistryContext) ?? getRegistry();
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
38
|
+
export { useRegistry as n, RegistryProvider as t };
|
|
39
|
+
|
|
40
|
+
//# sourceMappingURL=RegistryProvider-DS4ltjlH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RegistryProvider-DS4ltjlH.js","names":[],"sources":["../src/RegistryProvider.tsx"],"sourcesContent":["import {\n createContext,\n useContext,\n type ReactElement,\n type ReactNode,\n} from 'react';\nimport { getRegistry, type StateContainerRegistry } from '@blac/core';\n\nconst RegistryContext = createContext<StateContainerRegistry | null>(null);\n\n/**\n * Props for {@link RegistryProvider}.\n * @public\n */\nexport interface RegistryProviderProps {\n registry: StateContainerRegistry;\n children: ReactNode;\n}\n\n/**\n * Scopes descendant `useBloc` calls to a specific registry instead of the\n * module-global default.\n *\n * Useful for test isolation, SSR isolation, and micro-frontends that must\n * not share bloc instances with the host page.\n *\n * @example\n * ```tsx\n * <RegistryProvider registry={new StateContainerRegistry()}>\n * <App />\n * </RegistryProvider>\n * ```\n * @public\n */\nexport function RegistryProvider({\n registry,\n children,\n}: RegistryProviderProps): ReactElement {\n return (\n <RegistryContext.Provider value={registry}>\n {children}\n </RegistryContext.Provider>\n );\n}\n\n/**\n * The registry `useBloc` uses here: the nearest {@link RegistryProvider}'s,\n * else `getRegistry()`. The plain `@blac/core` helpers (`acquire`, `ensure`,\n * `watch`, …) can't read React context and always use `getRegistry()`; call\n * this registry's methods directly to stay inside a provider's scope.\n * @public\n */\nexport function useRegistry(): StateContainerRegistry {\n return useContext(RegistryContext) ?? getRegistry();\n}\n"],"mappings":";;;;AAQA,MAAM,kBAAkB,cAA6C,IAAI;;;;;;;;;;;;;;;;AA0BzE,SAAgB,iBAAiB,EAC/B,UACA,YACsC;CACtC,OACE,oBAAC,gBAAgB,UAAjB;EAA0B,OAAO;EAC9B;CACuB,CAAA;AAE9B;;;;;;;;AASA,SAAgB,cAAsC;CACpD,OAAO,WAAW,eAAe,KAAK,YAAY;AACpD"}
|
package/dist/depSession.d.cts
CHANGED
|
@@ -14,6 +14,7 @@ export declare class DepSession {
|
|
|
14
14
|
private readonly consumerId;
|
|
15
15
|
private readonly onChange;
|
|
16
16
|
private primary;
|
|
17
|
+
private primaryParts;
|
|
17
18
|
private primaryPaths;
|
|
18
19
|
private entries;
|
|
19
20
|
private subs;
|
|
@@ -26,6 +27,11 @@ export declare class DepSession {
|
|
|
26
27
|
private get refId();
|
|
27
28
|
begin(primary: StateContainer, paths: PathSet): void;
|
|
28
29
|
record(dep: StateContainer, paths: PathSet, Type: StateContainerConstructor, key: string, args: unknown, disarm: () => void): void;
|
|
30
|
+
/**
|
|
31
|
+
* Merge each container's per-call sets once the render's reads are done.
|
|
32
|
+
* Returns the primary's paths, widened by deps that resolved to it.
|
|
33
|
+
*/
|
|
34
|
+
settle(): PathSet;
|
|
29
35
|
/** Stops every dep tracked this render from recording further reads. */
|
|
30
36
|
disarm(): void;
|
|
31
37
|
/** Forces the next `reconcile` to run in full. */
|
package/dist/depSession.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export declare class DepSession {
|
|
|
14
14
|
private readonly consumerId;
|
|
15
15
|
private readonly onChange;
|
|
16
16
|
private primary;
|
|
17
|
+
private primaryParts;
|
|
17
18
|
private primaryPaths;
|
|
18
19
|
private entries;
|
|
19
20
|
private subs;
|
|
@@ -26,6 +27,11 @@ export declare class DepSession {
|
|
|
26
27
|
private get refId();
|
|
27
28
|
begin(primary: StateContainer, paths: PathSet): void;
|
|
28
29
|
record(dep: StateContainer, paths: PathSet, Type: StateContainerConstructor, key: string, args: unknown, disarm: () => void): void;
|
|
30
|
+
/**
|
|
31
|
+
* Merge each container's per-call sets once the render's reads are done.
|
|
32
|
+
* Returns the primary's paths, widened by deps that resolved to it.
|
|
33
|
+
*/
|
|
34
|
+
settle(): PathSet;
|
|
29
35
|
/** Stops every dep tracked this render from recording further reads. */
|
|
30
36
|
disarm(): void;
|
|
31
37
|
/** Forces the next `reconcile` to run in full. */
|
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_RegistryProvider = require("./RegistryProvider-
|
|
2
|
+
const require_RegistryProvider = require("./RegistryProvider-B9-mQQRg.cjs");
|
|
3
3
|
let react = require("react");
|
|
4
|
+
let _blac_core = require("@blac/core");
|
|
4
5
|
let _dirtytalk_structural = require("@dirtytalk/structural");
|
|
5
6
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
6
7
|
let _blac_core_internal = require("@blac/core/internal");
|
|
@@ -152,6 +153,7 @@ var DepSession = class {
|
|
|
152
153
|
this.consumerId = consumerId;
|
|
153
154
|
this.onChange = onChange;
|
|
154
155
|
this.primary = null;
|
|
156
|
+
this.primaryParts = [];
|
|
155
157
|
this.primaryPaths = NO_PATHS$1;
|
|
156
158
|
this.entries = null;
|
|
157
159
|
this.subs = null;
|
|
@@ -168,24 +170,34 @@ var DepSession = class {
|
|
|
168
170
|
this.entries?.clear();
|
|
169
171
|
if (this.disarms !== null) this.disarms.length = 0;
|
|
170
172
|
this.primary = primary;
|
|
171
|
-
this.
|
|
173
|
+
this.primaryParts = [paths];
|
|
172
174
|
}
|
|
173
175
|
record(dep, paths, Type, key, args, disarm) {
|
|
174
176
|
(this.disarms ??= []).push(disarm);
|
|
175
177
|
if (dep === this.primary) {
|
|
176
|
-
this.
|
|
178
|
+
this.primaryParts.push(paths);
|
|
177
179
|
return;
|
|
178
180
|
}
|
|
179
181
|
const entries = this.entries ??= /* @__PURE__ */ new Map();
|
|
180
182
|
const existing = entries.get(dep);
|
|
181
|
-
if (existing !== void 0) existing.
|
|
183
|
+
if (existing !== void 0) existing.parts.push(paths);
|
|
182
184
|
else entries.set(dep, {
|
|
185
|
+
parts: [paths],
|
|
183
186
|
paths,
|
|
184
187
|
Type,
|
|
185
188
|
key,
|
|
186
189
|
args
|
|
187
190
|
});
|
|
188
191
|
}
|
|
192
|
+
/**
|
|
193
|
+
* Merge each container's per-call sets once the render's reads are done.
|
|
194
|
+
* Returns the primary's paths, widened by deps that resolved to it.
|
|
195
|
+
*/
|
|
196
|
+
settle() {
|
|
197
|
+
this.primaryPaths = mergeParts(this.primaryParts);
|
|
198
|
+
if (this.entries !== null) for (const entry of this.entries.values()) entry.paths = mergeParts(entry.parts);
|
|
199
|
+
return this.primaryPaths;
|
|
200
|
+
}
|
|
189
201
|
/** Stops every dep tracked this render from recording further reads. */
|
|
190
202
|
disarm() {
|
|
191
203
|
const disarms = this.disarms;
|
|
@@ -273,10 +285,13 @@ var DepSession = class {
|
|
|
273
285
|
function sameEntry(a, b) {
|
|
274
286
|
return (0, _dirtytalk_structural.pathSetEquals)(a.paths, b.paths) && a.key === b.key && Object.is(a.args, b.args);
|
|
275
287
|
}
|
|
276
|
-
function
|
|
277
|
-
if (
|
|
278
|
-
const out = new Set(
|
|
279
|
-
for (const
|
|
288
|
+
function mergeParts(parts) {
|
|
289
|
+
if (parts.length === 1) return parts[0];
|
|
290
|
+
const out = /* @__PURE__ */ new Set();
|
|
291
|
+
for (const part of parts) {
|
|
292
|
+
if (part === _dirtytalk_structural.ALL_PATHS) return _dirtytalk_structural.ALL_PATHS;
|
|
293
|
+
for (const id of part) out.add(id);
|
|
294
|
+
}
|
|
280
295
|
return out;
|
|
281
296
|
}
|
|
282
297
|
/**
|
|
@@ -290,7 +305,7 @@ function unionPaths(a, b) {
|
|
|
290
305
|
*/
|
|
291
306
|
function makeDepWrapper(handle, registry, session, tracked, onDepHandle) {
|
|
292
307
|
const brand = handle[_blac_core_internal.DEP_BRAND];
|
|
293
|
-
const perDep = /* @__PURE__ */ new
|
|
308
|
+
const perDep = /* @__PURE__ */ new WeakMap();
|
|
294
309
|
const proxyCache = new _dirtytalk_structural.ProxyCache();
|
|
295
310
|
const resolve = (options) => {
|
|
296
311
|
const args = options?.args ?? brand.defaultArgs;
|
|
@@ -357,11 +372,8 @@ let nextConsumerId = 0;
|
|
|
357
372
|
* fires *before* the registry releases its ref, so the bloc is still
|
|
358
373
|
* alive when the callback runs.
|
|
359
374
|
*
|
|
360
|
-
* Per-mount private instance:
|
|
361
|
-
*
|
|
362
|
-
* const id = useId();
|
|
363
|
-
* const [state, bloc] = useBloc(MyBloc, { args: { _id: id } });
|
|
364
|
-
* ```
|
|
375
|
+
* Per-mount private instance: mark the class `@blac({ isolated: true })`, or
|
|
376
|
+
* pass a unique key arg yourself (e.g. `useId()` in `args`).
|
|
365
377
|
*
|
|
366
378
|
* @typeParam T - The state container constructor type (inferred from BlocClass)
|
|
367
379
|
* @param BlocClass - The state container class to connect to
|
|
@@ -396,7 +408,8 @@ function useBloc(BlocClass, options) {
|
|
|
396
408
|
consumer.ownArgs = options?.args;
|
|
397
409
|
consumer.providerArgs = useProvidedArgs(BlocClass);
|
|
398
410
|
const effectiveArgs = resolveEffectiveArgs(consumer);
|
|
399
|
-
const
|
|
411
|
+
const sharedKey = registry.resolveKey(BlocClass, void 0, effectiveArgs);
|
|
412
|
+
const instanceKey = (0, _blac_core.isIsolatedClass)(BlocClass) ? `${sharedKey}#${consumer.id}` : sharedKey;
|
|
400
413
|
if (consumer.BlocClass !== BlocClass || consumer.instanceKey !== instanceKey || consumer.registry !== registry || consumer.boundNonce !== consumer.rebindNonce) bind(consumer, BlocClass, instanceKey, registry, effectiveArgs);
|
|
401
414
|
(0, react.useSyncExternalStore)(consumer.subscribe, consumer.getSnapshot, consumer.getSnapshot);
|
|
402
415
|
const container = consumer.container;
|
|
@@ -574,6 +587,7 @@ function commit(consumer) {
|
|
|
574
587
|
}
|
|
575
588
|
const primary = consumer.container;
|
|
576
589
|
if (primary.state !== consumer.renderedState) consumer.bump();
|
|
590
|
+
consumer.paths = deps.settle();
|
|
577
591
|
if (deps.isUnchanged()) return;
|
|
578
592
|
primary.registerConsumerPaths(consumer.id, consumer.paths);
|
|
579
593
|
setInterest(consumer, consumer.paths);
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["WITH_TRACKED_STATE","DEP_BRAND","NO_PATHS","ALL_PATHS","DEP_BRAND","ProxyCache","useRegistry","ALL_PATHS","ProxyCache","APPLY_DEPS","REMOVE_DEPS_OWNER"],"sources":["../src/BlocProvider.tsx","../src/buildTrackedProxy.ts","../src/depSession.ts","../src/useBloc.ts","../src/useBlocDeps.ts"],"sourcesContent":["import {\n createContext,\n useContext,\n useMemo,\n type ReactElement,\n type ReactNode,\n} from 'react';\nimport type { ExtractArgs, StateContainerConstructor } from '@blac/core';\n\n/**\n * Internal context value: a Map from bloc constructor to the args object\n * provided by the nearest BlocProvider for that bloc class.\n *\n * Each provider copies its parent's map and adds its own entry, so nested\n * providers for different blocs compose without clobbering each other.\n */\ntype ProvidedArgsMap = Map<StateContainerConstructor, unknown>;\n\nconst ProvidedArgsContext = createContext<ProvidedArgsMap>(new Map());\n\n/**\n * Props for {@link BlocProvider}.\n * @public\n */\nexport interface BlocProviderProps<T extends StateContainerConstructor> {\n /**\n * The bloc class whose args are being provided to descendants.\n */\n bloc: T;\n /**\n * Args that descendant `useBloc(bloc)` calls will resolve to when no\n * own `args` are given.\n */\n args: ExtractArgs<T>;\n children: ReactNode;\n}\n\n/**\n * Provides args to descendant `useBloc` calls for a specific bloc class via\n * React context. Own `args` on the `useBloc` call always win.\n *\n * Multiple `BlocProvider` wrappers for different bloc classes compose: each\n * merges its entry into the inherited map, so nested providers for different\n * blocs do not interfere.\n *\n * @example\n * ```tsx\n * <BlocProvider bloc={UserBloc} args={{ userId: 'alice' }}>\n * <UserProfile />\n * </BlocProvider>\n * ```\n *\n * @example Per-mount private instance\n * ```tsx\n * const id = useId();\n * <BlocProvider bloc={CartBloc} args={{ _id: id }}>\n * <CartWidget />\n * </BlocProvider>\n * ```\n * @public\n */\nexport function BlocProvider<T extends StateContainerConstructor>({\n bloc,\n args,\n children,\n}: BlocProviderProps<T>): ReactElement {\n const parentMap = useContext(ProvidedArgsContext);\n\n // Keyed on the args content, not identity, so an inline `args={{ ... }}`\n // literal does not rebuild the map (and re-render every consumer) on\n // every render, while a real field change still reaches `useProvidedArgs`.\n const argsKey = JSON.stringify(args);\n\n // Merge our entry into a new Map so sibling/parent providers for other blocs\n // are preserved.\n const mergedMap = useMemo(() => {\n const next = new Map(parentMap);\n next.set(bloc, args);\n return next;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [parentMap, bloc, argsKey]);\n\n return (\n <ProvidedArgsContext.Provider value={mergedMap}>\n {children}\n </ProvidedArgsContext.Provider>\n );\n}\n\n/**\n * Returns the args provided by the nearest {@link BlocProvider} for the given\n * bloc class, or `undefined` when called outside a matching provider.\n *\n * Used by `useBloc` to inherit provider args when no own `args` are given.\n * @public\n */\nexport function useProvidedArgs<T extends StateContainerConstructor>(\n BlocClass: T,\n): ExtractArgs<T> | undefined {\n const map = useContext(ProvidedArgsContext);\n return map.get(BlocClass) as ExtractArgs<T> | undefined;\n}\n","import { DEP_BRAND, WITH_TRACKED_STATE } from '@blac/core/internal';\n\ninterface TrackedStateTarget {\n [WITH_TRACKED_STATE]<R>(\n tracked: unknown,\n fn: () => R,\n onDepHandle?: (handle: object) => unknown,\n ): R;\n}\n\nfunction supportsTrackedState(value: object): value is TrackedStateTarget {\n return (\n typeof (value as TrackedStateTarget)[WITH_TRACKED_STATE] === 'function'\n );\n}\n\ntype Getter = (this: object) => unknown;\ntype Method = (...args: unknown[]) => unknown;\n\n// Prototype getters per class prototype. The chain is static, so walking it\n// once per class (not once per mounted component) keeps the get trap O(1).\nconst gettersByProto = new WeakMap<object, Map<string | symbol, Getter>>();\n\nfunction collectGetters(proto: object): Map<string | symbol, Getter> {\n const cached = gettersByProto.get(proto);\n if (cached !== undefined) return cached;\n const getters = new Map<string | symbol, Getter>();\n let p: object | null = proto;\n while (p !== null && p !== Object.prototype) {\n for (const key of Reflect.ownKeys(p)) {\n const desc = Object.getOwnPropertyDescriptor(p, key);\n // A descriptor getter, always `.call`ed with an explicit receiver.\n // oxlint-disable-next-line typescript/unbound-method\n if (desc?.get && !getters.has(key)) getters.set(key, desc.get);\n }\n p = Object.getPrototypeOf(p);\n }\n gettersByProto.set(proto, getters);\n return getters;\n}\n\n// Methods on the prototype, so a mount allocates the handler and the proxy\n// only, not a set of closures.\nclass BlocHandler implements ProxyHandler<object> {\n // Bound methods are cached so `bloc.method` keeps a stable identity across\n // reads — an unstable one would defeat memoisation in consumers. Keyed by\n // the underlying function too, so a reassigned method is re-bound.\n private boundMethods:\n | Map<string | symbol, { fn: Method; bound: Method }>\n | undefined = undefined;\n\n constructor(\n private readonly getters: Map<string | symbol, Getter>,\n private readonly tracksState: boolean,\n private readonly trackedStateRef: { current: unknown },\n private readonly onDepHandle: ((handle: object) => unknown) | undefined,\n ) {}\n\n get(target: object, key: string | symbol): unknown {\n const getter = this.getters.get(key);\n if (getter !== undefined) {\n const tracked = this.trackedStateRef.current;\n // `[WITH_TRACKED_STATE]` invokes the getter with `this` = the real\n // instance, so no per-read thunk is needed.\n return this.wrapDepHandle(\n tracked == null || !this.tracksState\n ? getter.call(target)\n : (target as TrackedStateTarget)[WITH_TRACKED_STATE](\n tracked,\n getter,\n this.onDepHandle,\n ),\n );\n }\n\n // Receiver is the real instance, so own fields keep their `#private`\n // brand. Methods are bound to it too, otherwise `bloc.method()` would\n // call with `this` = proxy and fail the same brand check.\n const value = Reflect.get(target, key, target);\n if (typeof value === 'function') {\n const fn = value as Method;\n this.boundMethods ??= new Map();\n let cached = this.boundMethods.get(key);\n if (cached === undefined || cached.fn !== fn) {\n cached = { fn, bound: fn.bind(target) };\n this.boundMethods.set(key, cached);\n }\n return this.wrapDepHandle(cached.bound);\n }\n return this.wrapDepHandle(value);\n }\n\n set(target: object, key: string | symbol, value: unknown): boolean {\n return Reflect.set(target, key, value, target);\n }\n\n private wrapDepHandle(value: unknown): unknown {\n if (\n this.onDepHandle !== undefined &&\n (typeof value === 'function' || typeof value === 'object') &&\n value !== null &&\n (value as Record<symbol, unknown>)[DEP_BRAND] !== undefined\n ) {\n return this.onDepHandle(value as object);\n }\n return value;\n }\n}\n\n/**\n * Build a per-consumer proxy for a bloc instance.\n *\n * Getters run with `this` bound to the real instance, not the proxy, so ES\n * `#private` fields and methods work in user blocs. Path recording is done by\n * `[WITH_TRACKED_STATE]`, which makes the instance's own `state` getter\n * report the render's tracking proxy for the call's duration; nested getter\n * chains stay tracked because the override remains set.\n *\n * One allocation per bloc acquisition (inside `useMemo`), so the proxy is\n * stable across renders.\n *\n * @param onDepHandle - Invoked when a read returns a branded dep handle;\n * returns the value (the session-bound wrapper) to expose in its place.\n * Must cache wrappers per handle to avoid re-allocation.\n */\nexport function buildTrackedProxy<T extends object>(\n instance: T,\n trackedStateRef: { current: unknown },\n onDepHandle?: (handle: object) => unknown,\n): T {\n // Both string- and symbol-keyed prototype getters (excluding\n // Object.prototype). Arrow-function class properties are own values, not\n // getters, and pass through unmodified.\n const handler = new BlocHandler(\n collectGetters(Object.getPrototypeOf(instance) as object),\n supportsTrackedState(instance),\n trackedStateRef,\n onDepHandle,\n );\n return new Proxy(instance, handler as ProxyHandler<T>);\n}\n","import type {\n StateContainer,\n StateContainerConstructor,\n StateContainerRegistry,\n} from '@blac/core';\nimport { DEP_BRAND } from '@blac/core/internal';\nimport {\n ALL_PATHS,\n emptyPathSet,\n pathSetEquals,\n trackRender,\n ProxyCache,\n type PathSet,\n type PathSubscription,\n} from '@dirtytalk/structural';\nimport { buildTrackedProxy } from './buildTrackedProxy';\n\nconst NO_PATHS: PathSet = emptyPathSet();\n\ninterface DepEntry {\n paths: PathSet;\n Type: StateContainerConstructor;\n key: string;\n args: unknown;\n}\n\ninterface DepSub {\n subscription: PathSubscription;\n registry: StateContainerRegistry;\n Type: StateContainerConstructor;\n key: string;\n}\n\n/**\n * The containers one `useBloc` consumer reads during a render: its primary\n * bloc plus every dep reached through `this.<handle>.track()`.\n *\n * The render records entries; the commit calls `reconcile`, which subscribes\n * and takes a ref on new deps and drops the ones no longer read. `dispose`\n * releases everything. Refs are only taken at commit, so an uncommitted render\n * never leaks one.\n */\nexport class DepSession {\n // Most consumers read no deps, so everything dep-related is allocated on the\n // first dep a render records.\n private primary: StateContainer | null = null;\n private primaryPaths: PathSet = NO_PATHS;\n private entries: Map<StateContainer, DepEntry> | null = null;\n private subs: Map<StateContainer, DepSub> | null = null;\n private reconciled = false;\n private lastPrimary: StateContainer | null = null;\n private lastPrimaryPaths: PathSet = NO_PATHS;\n private lastEntries: Map<StateContainer, DepEntry> | null = null;\n private disarms: Array<() => void> | null = null;\n\n constructor(\n private readonly consumerId: string,\n private readonly onChange: () => void,\n ) {}\n\n private get refId(): string {\n return `useBloc@${this.consumerId}:dep`;\n }\n\n begin(primary: StateContainer, paths: PathSet): void {\n this.entries?.clear();\n if (this.disarms !== null) this.disarms.length = 0;\n this.primary = primary;\n this.primaryPaths = paths;\n }\n\n record(\n dep: StateContainer,\n paths: PathSet,\n Type: StateContainerConstructor,\n key: string,\n args: unknown,\n disarm: () => void,\n ): void {\n (this.disarms ??= []).push(disarm);\n // A dep that resolves to the primary only widens the primary's entry.\n if (dep === this.primary) {\n this.primaryPaths = unionPaths(this.primaryPaths, paths);\n return;\n }\n const entries = (this.entries ??= new Map());\n const existing = entries.get(dep);\n if (existing !== undefined) {\n existing.paths = unionPaths(existing.paths, paths);\n } else {\n entries.set(dep, { paths, Type, key, args });\n }\n }\n\n /** Stops every dep tracked this render from recording further reads. */\n disarm(): void {\n const disarms = this.disarms;\n if (disarms === null) return;\n for (const disarm of disarms) disarm();\n disarms.length = 0;\n }\n\n /** Forces the next `reconcile` to run in full. */\n invalidate(): void {\n this.reconciled = false;\n }\n\n isUnchanged(): boolean {\n if (\n !this.reconciled ||\n this.lastPrimary !== this.primary ||\n !pathSetEquals(this.lastPrimaryPaths, this.primaryPaths)\n ) {\n return false;\n }\n const last = this.lastEntries;\n const entries = this.entries;\n if ((last?.size ?? 0) !== (entries?.size ?? 0)) return false;\n if (last === null || entries === null) return true;\n for (const [container, entry] of entries) {\n const prev = last.get(container);\n if (prev === undefined || !sameEntry(prev, entry)) return false;\n }\n return true;\n }\n\n reconcile(registry: StateContainerRegistry): void {\n const { consumerId } = this;\n const entries = this.entries;\n this.reconciled = true;\n this.lastPrimary = this.primary;\n this.lastPrimaryPaths = this.primaryPaths;\n this.lastEntries =\n entries === null || entries.size === 0 ? null : new Map(entries);\n\n const subs = this.subs;\n if (subs !== null) {\n for (const [dep, sub] of subs) {\n if (entries?.has(dep) !== true) {\n this.drop(dep, sub);\n subs.delete(dep);\n }\n }\n }\n if (entries === null) return;\n\n for (const [dep, entry] of entries) {\n // StrictMode's simulated unmount can dispose a dep this render captured;\n // re-render so the session resolves the live instance.\n if (dep.$blac.disposed) {\n this.onChange();\n continue;\n }\n dep.registerConsumerPaths(consumerId, entry.paths);\n const existing = subs?.get(dep);\n if (existing) {\n existing.subscription.setInterest(entry.paths);\n continue;\n }\n const refId = this.refId;\n const live = registry.acquire(entry.Type, entry.key, {\n canCreate: true,\n countRef: true,\n refId,\n args: entry.args,\n });\n // The key now maps to another instance; re-render against that one.\n if (live !== dep) {\n registry.release(entry.Type, entry.key, false, refId);\n this.onChange();\n continue;\n }\n const subscription = dep.subscribePaths(this.onChange);\n subscription.setInterest(entry.paths);\n (this.subs ??= new Map()).set(dep, {\n subscription,\n registry,\n Type: entry.Type,\n key: entry.key,\n });\n }\n }\n\n dispose(): void {\n const subs = this.subs;\n if (subs !== null) {\n for (const [dep, sub] of subs) this.drop(dep, sub);\n subs.clear();\n }\n this.reconciled = false;\n }\n\n private drop(dep: StateContainer, sub: DepSub): void {\n sub.subscription.unsubscribe();\n dep.unregisterConsumer(this.consumerId);\n sub.registry.release(sub.Type, sub.key, false, this.refId);\n }\n}\n\nfunction sameEntry(a: DepEntry, b: DepEntry): boolean {\n return (\n pathSetEquals(a.paths, b.paths) &&\n a.key === b.key &&\n Object.is(a.args, b.args)\n );\n}\n\nfunction unionPaths(a: PathSet, b: PathSet): PathSet {\n if (a === ALL_PATHS || b === ALL_PATHS) return ALL_PATHS;\n const out = new Set<number>(a as Set<number>);\n for (const id of b as Set<number>) out.add(id);\n return out;\n}\n\ninterface DepAccessOptionsLike {\n args?: unknown;\n}\n\n/** Structural shape of a branded `depend()` handle as seen from React. */\nexport interface DepHandleLike {\n track(options?: DepAccessOptionsLike): [unknown, StateContainer];\n untracked(options?: DepAccessOptionsLike): StateContainer;\n readonly [DEP_BRAND]: {\n Type: StateContainerConstructor;\n defaultArgs?: unknown;\n };\n}\n\n/**\n * Wrap a dep handle read inside a tracked getter. During a render (`tracked`\n * is set) `.track()` records the dep's read paths into `session` and returns\n * a tracked proxy, so the dep's own getters track too. Outside a render it\n * returns the live `[state, dep]`, like the core handle.\n *\n * Args resolve per call, so one handle can reach several instances; proxies\n * are cached per resolved instance.\n */\nexport function makeDepWrapper(\n handle: DepHandleLike,\n registry: StateContainerRegistry,\n session: DepSession,\n tracked: { current: unknown },\n onDepHandle: (handle: object) => unknown,\n): DepHandleLike {\n const brand = handle[DEP_BRAND];\n const perDep = new Map<\n StateContainer,\n { ref: { current: unknown }; proxy: StateContainer }\n >();\n const proxyCache = new ProxyCache();\n\n const resolve = (options?: DepAccessOptionsLike) => {\n const args = options?.args ?? brand.defaultArgs;\n const key = registry.resolveKey(brand.Type, undefined, args);\n const dep = registry.acquire(brand.Type, key, {\n canCreate: true,\n countRef: false,\n args,\n sweepIfUnowned: tracked.current != null,\n }) as unknown as StateContainer;\n return { dep, key, args };\n };\n\n const wrapper = {\n untracked: (options?: DepAccessOptionsLike) => resolve(options).dep,\n track: (options?: DepAccessOptionsLike) => {\n const { dep, key, args } = resolve(options);\n if (tracked.current == null) return [dep.state, dep];\n\n const result = trackRender(dep.state, dep.interner, proxyCache);\n let cache = perDep.get(dep);\n if (cache === undefined) {\n const ref = { current: result.value as unknown };\n cache = { ref, proxy: buildTrackedProxy(dep, ref, onDepHandle) };\n perDep.set(dep, cache);\n } else {\n cache.ref.current = result.value;\n }\n session.record(dep, result.paths, brand.Type, key, args, result.disarm);\n return [result.value, cache.proxy];\n },\n } as DepHandleLike;\n\n Object.defineProperty(wrapper, DEP_BRAND, {\n value: brand,\n enumerable: false,\n writable: false,\n configurable: false,\n });\n\n return wrapper;\n}\n","import { useLayoutEffect, useRef, useSyncExternalStore } from 'react';\nimport {\n type ExtractArgs,\n type ExtractState,\n type InstanceState,\n type StateContainer,\n type StateContainerConstructor,\n type StateContainerRegistry,\n} from '@blac/core';\nimport {\n ALL_PATHS,\n emptyPathSet,\n trackRender,\n ProxyCache,\n type PathSet,\n type PathSubscription,\n} from '@dirtytalk/structural';\nimport { useProvidedArgs } from './BlocProvider';\nimport { useRegistry } from './RegistryProvider';\nimport { buildTrackedProxy } from './buildTrackedProxy';\nimport { DepSession, makeDepWrapper, type DepHandleLike } from './depSession';\nimport type { UseBlocOptions, UseBlocReturn } from './types';\n\nlet nextConsumerId = 0;\n\n/**\n * React hook that connects a component to a state container with automatic\n * re-render on state changes.\n *\n * Two tracking modes:\n * - **Auto-tracking** (default): the returned state value is a proxy that\n * records read paths during render. The component re-renders when any\n * recorded path changes.\n * - **Manual select**: pass `options.select` to opt out of auto-tracking.\n * The hook re-renders only when the returned array's elements change\n * (per-index `Object.is`).\n *\n * Lifecycle:\n * - The bloc is acquired from the registry on mount and released on\n * unmount. The instance key is derived from `options.args` (own args),\n * then the surrounding {@link BlocProvider} context args for this bloc,\n * then the default key (no args).\n * - `options.onMount` fires after the bloc is acquired; `options.onUnmount`\n * fires *before* the registry releases its ref, so the bloc is still\n * alive when the callback runs.\n *\n * Per-mount private instance:\n * ```ts\n * const id = useId();\n * const [state, bloc] = useBloc(MyBloc, { args: { _id: id } });\n * ```\n *\n * @typeParam T - The state container constructor type (inferred from BlocClass)\n * @param BlocClass - The state container class to connect to\n * @param options - Configuration options\n * @returns Tuple of `[state, bloc]`\n *\n * @example Basic usage\n * ```ts\n * const [state, bloc] = useBloc(MyBloc);\n * ```\n *\n * @example Manual select\n * ```ts\n * const [state, bloc] = useBloc(MyBloc, {\n * select: (state) => [state.count],\n * });\n * ```\n *\n * @example Args-based shared instance\n * ```ts\n * const [state, bloc] = useBloc(UserBloc, { args: { userId: 'alice' } });\n * ```\n * @public\n */\nexport function useBloc<\n T extends StateContainerConstructor = StateContainerConstructor,\n>(\n BlocClass: T,\n options?: UseBlocOptions<T>,\n): UseBlocReturn<T, ExtractState<T>> {\n const registry = useRegistry();\n\n const consumerRef = useRef<Consumer | null>(null);\n const consumer = (consumerRef.current ??= createConsumer());\n\n consumer.select = options?.select;\n consumer.onMount = options?.onMount;\n consumer.onUnmount = options?.onUnmount;\n\n consumer.ownArgs = (options as { args?: ExtractArgs<T> } | undefined)?.args;\n consumer.providerArgs = useProvidedArgs(BlocClass);\n const effectiveArgs = resolveEffectiveArgs(consumer);\n const instanceKey = registry.resolveKey(BlocClass, undefined, effectiveArgs);\n\n // A memo kept on the consumer rather than in `useMemo`: no hook, deps array\n // or factory closure per mount. `rebindNonce` is bumped by the commit when\n // the rendered instance was disposed and recreated before commit (a\n // same-commit owner handoff, or StrictMode's double effect); the tracked\n // proxy can't be retargeted in place. Keyed on `BlocClass` as well as the\n // key: classes without args or a `static key` share the default key, so\n // `useBloc(cond ? A : B)` must still re-subscribe on a class swap.\n if (\n consumer.BlocClass !== BlocClass ||\n consumer.instanceKey !== instanceKey ||\n consumer.registry !== registry ||\n consumer.boundNonce !== consumer.rebindNonce\n ) {\n bind(consumer, BlocClass, instanceKey, registry, effectiveArgs);\n }\n\n useSyncExternalStore(\n consumer.subscribe,\n consumer.getSnapshot,\n consumer.getSnapshot,\n );\n\n const container = consumer.container;\n const rawState = container.state as ExtractState<T>;\n consumer.renderedState = rawState;\n const select = consumer.select;\n consumer.isSelectMode = select !== undefined;\n let state: ExtractState<T>;\n if (select !== undefined) {\n state = rawState;\n // Recomputed every render: the selector may close over props.\n consumer.selection = select(rawState, container as InstanceState<T>);\n setInterest(consumer, ALL_PATHS);\n } else {\n const tracked = trackRender(\n rawState,\n container.interner,\n (consumer.proxyCache ??= new ProxyCache()),\n );\n state = tracked.value as ExtractState<T>;\n consumer.tracked.current = tracked.value;\n consumer.paths = tracked.paths;\n // Run at commit, so reads that outlive the render (effects, handlers)\n // don't add paths.\n consumer.disarm = tracked.disarm;\n // `tracked.paths` is still empty here and fills during JSX, so paths are\n // registered at commit, not now.\n consumer.deps.begin(container, tracked.paths);\n }\n\n // Stable callbacks that read this render's inputs from the consumer.\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n useLayoutEffect(consumer.commit);\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n useLayoutEffect(consumer.mount, NO_DEPS);\n\n return [state, consumer.trackedBloc] as UseBlocReturn<T, ExtractState<T>>;\n}\n\nconst noop = (): void => {};\n\n// Initial `interest` and `paths`; neither is ever mutated in place.\nconst NO_PATHS: PathSet = emptyPathSet();\nconst NO_DEPS: [] = [];\n\n/**\n * Everything one `useBloc` call keeps between renders. Its callbacks are\n * created once, so a render allocates no closures.\n *\n * `bump` increments `version` before calling `notify`: uSES requires\n * `getSnapshot()` to reflect the change by the time it is notified.\n */\ninterface Consumer {\n id: string;\n primaryRefId: string;\n // The render's inputs, read by the commit of that render.\n BlocClass: StateContainerConstructor;\n instanceKey: string;\n registry: StateContainerRegistry;\n /** Written only by `bind`. */\n container: StateContainer;\n trackedBloc: unknown;\n subscribe: (onStoreChange: () => void) => () => void;\n boundNonce: number;\n rebindNonce: number;\n renderedState: unknown;\n version: number;\n /** `noop` while unsubscribed. */\n notify: () => void;\n bump: () => void;\n getSnapshot: () => number;\n onChange: () => void;\n unsubscribe: () => void;\n commit: () => void;\n mount: () => () => void;\n /** The committed `paths`, or `ALL_PATHS` in select mode. */\n interest: PathSet;\n /** `null` while unsubscribed. */\n subscription: PathSubscription | null;\n subscribedTo: StateContainer | null;\n paths: PathSet;\n isSelectMode: boolean;\n selection: unknown[] | null;\n deps: DepSession;\n // Typed loosely: `T` is per call site.\n select: ((state: any, bloc: any) => unknown[]) | undefined;\n onMount: ((bloc: any) => void) | undefined;\n onUnmount: ((bloc: any) => void) | undefined;\n ownArgs: unknown;\n providerArgs: unknown;\n /** The render's tracking proxy; `null` outside a tracked render. */\n tracked: { current: unknown };\n disarm: (() => void) | null;\n proxyCache: ProxyCache | null;\n // The instance the consumer holds a ref on, claimed at commit so acquire\n // and release pair exactly.\n ownedClass: StateContainerConstructor | null;\n ownedKey: string;\n ownedRegistry: StateContainerRegistry | null;\n ownedBloc: unknown;\n}\n\nfunction createConsumer(): Consumer {\n const id = `useBloc-${nextConsumerId++}`;\n const consumer: Consumer = {\n id,\n primaryRefId: `useBloc@${id}`,\n BlocClass: null as unknown as StateContainerConstructor,\n instanceKey: '',\n registry: null as unknown as StateContainerRegistry,\n container: null as unknown as StateContainer,\n trackedBloc: null,\n subscribe: null as unknown as Consumer['subscribe'],\n boundNonce: 0,\n rebindNonce: 0,\n renderedState: undefined,\n version: 0,\n notify: noop,\n bump: () => {\n consumer.version++;\n consumer.notify();\n },\n getSnapshot: () => consumer.version,\n onChange: () => onChange(consumer),\n unsubscribe: () => unsubscribe(consumer),\n commit: () => commit(consumer),\n mount: () => teardown,\n interest: NO_PATHS,\n subscription: null,\n subscribedTo: null,\n paths: NO_PATHS,\n isSelectMode: false,\n selection: null,\n deps: null as unknown as DepSession,\n select: undefined,\n onMount: undefined,\n onUnmount: undefined,\n ownArgs: undefined,\n providerArgs: undefined,\n tracked: { current: null },\n disarm: null,\n proxyCache: null,\n ownedClass: null,\n ownedKey: '',\n ownedRegistry: null,\n ownedBloc: null,\n };\n const teardown = (): void => {\n release(consumer);\n consumer.deps.dispose();\n };\n consumer.deps = new DepSession(id, consumer.bump);\n return consumer;\n}\n\nfunction bind(\n consumer: Consumer,\n BlocClass: StateContainerConstructor,\n instanceKey: string,\n registry: StateContainerRegistry,\n args: unknown,\n): void {\n // No ref during render; the commit claims it. The registry sweeps the\n // instance if no commit ever does (SSR, discarded renders).\n const instance = registry.acquire(BlocClass, instanceKey, {\n canCreate: true,\n countRef: false,\n args: args as ExtractArgs<StateContainerConstructor>,\n sweepIfUnowned: true,\n }) as unknown as StateContainer;\n\n // Dep proxies get `onDepHandle` too, so nested `.track()` calls (A→B→C)\n // record into this consumer's session.\n let depWrappers: Map<object, unknown> | undefined;\n const onDepHandle = (handle: object): unknown => {\n depWrappers ??= new Map();\n const cached = depWrappers.get(handle);\n if (cached !== undefined) return cached;\n const wrapper = makeDepWrapper(\n handle as DepHandleLike,\n registry,\n consumer.deps,\n consumer.tracked,\n onDepHandle,\n );\n depWrappers.set(handle, wrapper);\n return wrapper;\n };\n\n consumer.BlocClass = BlocClass;\n consumer.instanceKey = instanceKey;\n consumer.registry = registry;\n consumer.boundNonce = consumer.rebindNonce;\n consumer.container = instance;\n consumer.trackedBloc = buildTrackedProxy(\n instance,\n consumer.tracked,\n onDepHandle,\n );\n // A new identity per instance, so uSES re-subscribes.\n consumer.subscribe = (onStoreChange) => subscribe(consumer, onStoreChange);\n}\n\nfunction subscribe(consumer: Consumer, onStoreChange: () => void): () => void {\n consumer.notify = onStoreChange;\n const container = consumer.container;\n const subscription = container.subscribePaths(consumer.onChange);\n subscription.setInterest(consumer.interest);\n consumer.subscription = subscription;\n consumer.subscribedTo = container;\n container.registerConsumerPaths(consumer.id, consumer.paths);\n // An emit raised during render is still pending in the channel, so its\n // next flush delivers it to this subscriber.\n return consumer.unsubscribe;\n}\n\nfunction unsubscribe(consumer: Consumer): void {\n consumer.notify = noop;\n consumer.subscription?.unsubscribe();\n consumer.subscription = null;\n consumer.subscribedTo?.unregisterConsumer(consumer.id);\n consumer.subscribedTo = null;\n}\n\nfunction onChange(consumer: Consumer): void {\n if (consumer.isSelectMode) {\n const select = consumer.select;\n const container = consumer.subscribedTo;\n if (select && container !== null) {\n const next = select(container.state, container);\n const prev = consumer.selection;\n if (prev !== null && shallowArrayEqual(prev, next)) return;\n consumer.selection = next;\n }\n }\n consumer.bump();\n}\n\nfunction claim(consumer: Consumer): void {\n const { BlocClass, instanceKey, registry } = consumer;\n if (\n consumer.ownedClass === BlocClass &&\n consumer.ownedKey === instanceKey &&\n consumer.ownedRegistry === registry\n ) {\n return;\n }\n release(consumer);\n const live = registry.acquire(BlocClass, instanceKey, {\n canCreate: true,\n countRef: true,\n refId: consumer.primaryRefId,\n args: resolveEffectiveArgs(\n consumer,\n ) as ExtractArgs<StateContainerConstructor>,\n });\n consumer.ownedClass = BlocClass;\n consumer.ownedKey = instanceKey;\n consumer.ownedRegistry = registry;\n consumer.ownedBloc = live;\n consumer.onMount?.(live);\n // The render captured a stale instance; re-render against the live one.\n if (live !== (consumer.container as unknown)) {\n consumer.rebindNonce++;\n consumer.bump();\n }\n}\n\nfunction release(consumer: Consumer): void {\n const { ownedClass, ownedRegistry } = consumer;\n if (ownedClass === null || ownedRegistry === null) return;\n // Before release, so the bloc is still alive in the callback.\n consumer.onUnmount?.(consumer.ownedBloc);\n consumer.ownedClass = null;\n consumer.ownedRegistry = null;\n ownedRegistry.release(\n ownedClass,\n consumer.ownedKey,\n false,\n consumer.primaryRefId,\n );\n}\n\nfunction commit(consumer: Consumer): void {\n claim(consumer);\n // Getters called after commit read live state, not this render's proxy.\n consumer.tracked.current = null;\n const disarm = consumer.disarm;\n if (disarm !== null) {\n consumer.disarm = null;\n disarm();\n }\n const deps = consumer.deps;\n deps.disarm();\n if (consumer.isSelectMode) {\n // A later switch back to auto-track must reconcile in full.\n deps.invalidate();\n return;\n }\n const primary = consumer.container;\n // A change between render and commit was diffed against the previous\n // interest, which may lack paths only this render read.\n if (primary.state !== consumer.renderedState) consumer.bump();\n if (deps.isUnchanged()) return;\n\n primary.registerConsumerPaths(consumer.id, consumer.paths);\n setInterest(consumer, consumer.paths);\n deps.reconcile(consumer.registry);\n}\n\nconst setInterest = (consumer: Consumer, interest: PathSet): void => {\n consumer.interest = interest;\n consumer.subscription?.setInterest(interest);\n};\n\n// Own args win over provider args. Shared by render and the ownership effect,\n// which recreates a disposed instance and must not drop its args.\nconst resolveEffectiveArgs = (consumer: Consumer): unknown =>\n consumer.ownArgs !== undefined ? consumer.ownArgs : consumer.providerArgs;\n\nconst shallowArrayEqual = (a: unknown[], b: unknown[]): boolean => {\n if (a === b) return true;\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!Object.is(a[i], b[i])) return false;\n }\n return true;\n};\n","import { useEffect, useId, useRef } from 'react';\nimport type { StateContainer } from '@blac/core';\nimport { APPLY_DEPS, REMOVE_DEPS_OWNER } from '@blac/core/internal';\n\n/**\n * Feed live values from the component into a bloc's `deps` lane.\n *\n * Each calling component is one owner: its slice is shallow-merged into the\n * bloc's merged `deps` view and withdrawn on unmount. The bloc observes\n * changes through `onDepsChanged`.\n *\n * ```tsx\n * const [state, bloc] = useBloc(CanvasCubit);\n * useBlocDeps(bloc, { canvas, onTick });\n * ```\n *\n * Use this for values a bloc must *read* but should not own — DOM handles,\n * callbacks, values from other hooks. Pass identity-stable values (`useMemo` /\n * `useCallback`) as you would to any hook; a new function identity each render\n * counts as a change.\n */\nexport function useBlocDeps<D extends object>(\n bloc: StateContainer<any, any, D>,\n slice: Partial<D>,\n): void {\n const ownerId = useId();\n const latest = useRef(slice);\n latest.current = slice;\n\n // `APPLY_DEPS` shallow-compares and no-ops on an unchanged slice, so applying\n // on every commit is cheaper than asking callers to maintain a dep array.\n useEffect(() => {\n bloc[APPLY_DEPS](ownerId, latest.current);\n });\n\n useEffect(() => {\n return () => {\n bloc[REMOVE_DEPS_OWNER](ownerId);\n };\n }, [bloc, ownerId]);\n}\n"],"mappings":";;;;;;;AAkBA,MAAM,uBAAA,GAAA,MAAA,+BAAqD,IAAI,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;AA2CpE,SAAgB,aAAkD,EAChE,MACA,MACA,YACqC;CACrC,MAAM,aAAA,GAAA,MAAA,YAAuB,mBAAmB;CAShD,MAAM,aAAA,GAAA,MAAA,eAA0B;EAC9B,MAAM,OAAO,IAAI,IAAI,SAAS;EAC9B,KAAK,IAAI,MAAM,IAAI;EACnB,OAAO;CAET,GAAG;EAAC;EAAW;EATC,KAAK,UAAU,IASJ;CAAC,CAAC;CAE7B,OACE,iBAAA,GAAA,kBAAA,KAAC,oBAAoB,UAArB;EAA8B,OAAO;EAClC;CAC2B,CAAA;AAElC;;;;;;;;AASA,SAAgB,gBACd,WAC4B;CAE5B,QAAA,GAAA,MAAA,YADuB,mBACd,EAAE,IAAI,SAAS;AAC1B;;;AC3FA,SAAS,qBAAqB,OAA4C;CACxE,OACE,OAAQ,MAA6BA,oBAAAA,wBAAwB;AAEjE;AAOA,MAAM,iCAAiB,IAAI,QAA8C;AAEzE,SAAS,eAAe,OAA6C;CACnE,MAAM,SAAS,eAAe,IAAI,KAAK;CACvC,IAAI,WAAW,KAAA,GAAW,OAAO;CACjC,MAAM,0BAAU,IAAI,IAA6B;CACjD,IAAI,IAAmB;CACvB,OAAO,MAAM,QAAQ,MAAM,OAAO,WAAW;EAC3C,KAAK,MAAM,OAAO,QAAQ,QAAQ,CAAC,GAAG;GACpC,MAAM,OAAO,OAAO,yBAAyB,GAAG,GAAG;GAGnD,IAAI,MAAM,OAAO,CAAC,QAAQ,IAAI,GAAG,GAAG,QAAQ,IAAI,KAAK,KAAK,GAAG;EAC/D;EACA,IAAI,OAAO,eAAe,CAAC;CAC7B;CACA,eAAe,IAAI,OAAO,OAAO;CACjC,OAAO;AACT;AAIA,IAAM,cAAN,MAAkD;CAQhD,YACE,SACA,aACA,iBACA,aACA;EAJiB,KAAA,UAAA;EACA,KAAA,cAAA;EACA,KAAA,kBAAA;EACA,KAAA,cAAA;sBANH,KAAA;CAOb;CAEH,IAAI,QAAgB,KAA+B;EACjD,MAAM,SAAS,KAAK,QAAQ,IAAI,GAAG;EACnC,IAAI,WAAW,KAAA,GAAW;GACxB,MAAM,UAAU,KAAK,gBAAgB;GAGrC,OAAO,KAAK,cACV,WAAW,QAAQ,CAAC,KAAK,cACrB,OAAO,KAAK,MAAM,IACjB,OAA8BA,oBAAAA,oBAC7B,SACA,QACA,KAAK,WACP,CACN;EACF;EAKA,MAAM,QAAQ,QAAQ,IAAI,QAAQ,KAAK,MAAM;EAC7C,IAAI,OAAO,UAAU,YAAY;GAC/B,MAAM,KAAK;GACX,KAAK,iCAAiB,IAAI,IAAI;GAC9B,IAAI,SAAS,KAAK,aAAa,IAAI,GAAG;GACtC,IAAI,WAAW,KAAA,KAAa,OAAO,OAAO,IAAI;IAC5C,SAAS;KAAE;KAAI,OAAO,GAAG,KAAK,MAAM;IAAE;IACtC,KAAK,aAAa,IAAI,KAAK,MAAM;GACnC;GACA,OAAO,KAAK,cAAc,OAAO,KAAK;EACxC;EACA,OAAO,KAAK,cAAc,KAAK;CACjC;CAEA,IAAI,QAAgB,KAAsB,OAAyB;EACjE,OAAO,QAAQ,IAAI,QAAQ,KAAK,OAAO,MAAM;CAC/C;CAEA,cAAsB,OAAyB;EAC7C,IACE,KAAK,gBAAgB,KAAA,MACpB,OAAO,UAAU,cAAc,OAAO,UAAU,aACjD,UAAU,QACT,MAAkCC,oBAAAA,eAAe,KAAA,GAElD,OAAO,KAAK,YAAY,KAAe;EAEzC,OAAO;CACT;AACF;;;;;;;;;;;;;;;;;AAkBA,SAAgB,kBACd,UACA,iBACA,aACG;CAIH,MAAM,UAAU,IAAI,YAClB,eAAe,OAAO,eAAe,QAAQ,CAAW,GACxD,qBAAqB,QAAQ,GAC7B,iBACA,WACF;CACA,OAAO,IAAI,MAAM,UAAU,OAA0B;AACvD;;;AC3HA,MAAMC,cAAAA,GAAAA,sBAAAA,cAAiC;;;;;;;;;;AAyBvC,IAAa,aAAb,MAAwB;CAatB,YACE,YACA,UACA;EAFiB,KAAA,aAAA;EACA,KAAA,WAAA;iBAZsB;sBACTA;iBACwB;cACL;oBAC9B;qBACwB;0BACTA;qBACwB;iBAChB;CAKzC;CAEH,IAAY,QAAgB;EAC1B,OAAO,WAAW,KAAK,WAAW;CACpC;CAEA,MAAM,SAAyB,OAAsB;EACnD,KAAK,SAAS,MAAM;EACpB,IAAI,KAAK,YAAY,MAAM,KAAK,QAAQ,SAAS;EACjD,KAAK,UAAU;EACf,KAAK,eAAe;CACtB;CAEA,OACE,KACA,OACA,MACA,KACA,MACA,QACM;EACN,CAAC,KAAK,YAAY,CAAC,GAAG,KAAK,MAAM;EAEjC,IAAI,QAAQ,KAAK,SAAS;GACxB,KAAK,eAAe,WAAW,KAAK,cAAc,KAAK;GACvD;EACF;EACA,MAAM,UAAW,KAAK,4BAAY,IAAI,IAAI;EAC1C,MAAM,WAAW,QAAQ,IAAI,GAAG;EAChC,IAAI,aAAa,KAAA,GACf,SAAS,QAAQ,WAAW,SAAS,OAAO,KAAK;OAEjD,QAAQ,IAAI,KAAK;GAAE;GAAO;GAAM;GAAK;EAAK,CAAC;CAE/C;;CAGA,SAAe;EACb,MAAM,UAAU,KAAK;EACrB,IAAI,YAAY,MAAM;EACtB,KAAK,MAAM,UAAU,SAAS,OAAO;EACrC,QAAQ,SAAS;CACnB;;CAGA,aAAmB;EACjB,KAAK,aAAa;CACpB;CAEA,cAAuB;EACrB,IACE,CAAC,KAAK,cACN,KAAK,gBAAgB,KAAK,WAC1B,EAAA,GAAA,sBAAA,eAAe,KAAK,kBAAkB,KAAK,YAAY,GAEvD,OAAO;EAET,MAAM,OAAO,KAAK;EAClB,MAAM,UAAU,KAAK;EACrB,KAAK,MAAM,QAAQ,QAAQ,SAAS,QAAQ,IAAI,OAAO;EACvD,IAAI,SAAS,QAAQ,YAAY,MAAM,OAAO;EAC9C,KAAK,MAAM,CAAC,WAAW,UAAU,SAAS;GACxC,MAAM,OAAO,KAAK,IAAI,SAAS;GAC/B,IAAI,SAAS,KAAA,KAAa,CAAC,UAAU,MAAM,KAAK,GAAG,OAAO;EAC5D;EACA,OAAO;CACT;CAEA,UAAU,UAAwC;EAChD,MAAM,EAAE,eAAe;EACvB,MAAM,UAAU,KAAK;EACrB,KAAK,aAAa;EAClB,KAAK,cAAc,KAAK;EACxB,KAAK,mBAAmB,KAAK;EAC7B,KAAK,cACH,YAAY,QAAQ,QAAQ,SAAS,IAAI,OAAO,IAAI,IAAI,OAAO;EAEjE,MAAM,OAAO,KAAK;EAClB,IAAI,SAAS;QACN,MAAM,CAAC,KAAK,QAAQ,MACvB,IAAI,SAAS,IAAI,GAAG,MAAM,MAAM;IAC9B,KAAK,KAAK,KAAK,GAAG;IAClB,KAAK,OAAO,GAAG;GACjB;;EAGJ,IAAI,YAAY,MAAM;EAEtB,KAAK,MAAM,CAAC,KAAK,UAAU,SAAS;GAGlC,IAAI,IAAI,MAAM,UAAU;IACtB,KAAK,SAAS;IACd;GACF;GACA,IAAI,sBAAsB,YAAY,MAAM,KAAK;GACjD,MAAM,WAAW,MAAM,IAAI,GAAG;GAC9B,IAAI,UAAU;IACZ,SAAS,aAAa,YAAY,MAAM,KAAK;IAC7C;GACF;GACA,MAAM,QAAQ,KAAK;GAQnB,IAPa,SAAS,QAAQ,MAAM,MAAM,MAAM,KAAK;IACnD,WAAW;IACX,UAAU;IACV;IACA,MAAM,MAAM;GACd,CAEO,MAAM,KAAK;IAChB,SAAS,QAAQ,MAAM,MAAM,MAAM,KAAK,OAAO,KAAK;IACpD,KAAK,SAAS;IACd;GACF;GACA,MAAM,eAAe,IAAI,eAAe,KAAK,QAAQ;GACrD,aAAa,YAAY,MAAM,KAAK;GACpC,CAAC,KAAK,yBAAS,IAAI,IAAI,GAAG,IAAI,KAAK;IACjC;IACA;IACA,MAAM,MAAM;IACZ,KAAK,MAAM;GACb,CAAC;EACH;CACF;CAEA,UAAgB;EACd,MAAM,OAAO,KAAK;EAClB,IAAI,SAAS,MAAM;GACjB,KAAK,MAAM,CAAC,KAAK,QAAQ,MAAM,KAAK,KAAK,KAAK,GAAG;GACjD,KAAK,MAAM;EACb;EACA,KAAK,aAAa;CACpB;CAEA,KAAa,KAAqB,KAAmB;EACnD,IAAI,aAAa,YAAY;EAC7B,IAAI,mBAAmB,KAAK,UAAU;EACtC,IAAI,SAAS,QAAQ,IAAI,MAAM,IAAI,KAAK,OAAO,KAAK,KAAK;CAC3D;AACF;AAEA,SAAS,UAAU,GAAa,GAAsB;CACpD,QAAA,GAAA,sBAAA,eACgB,EAAE,OAAO,EAAE,KAAK,KAC9B,EAAE,QAAQ,EAAE,OACZ,OAAO,GAAG,EAAE,MAAM,EAAE,IAAI;AAE5B;AAEA,SAAS,WAAW,GAAY,GAAqB;CACnD,IAAI,MAAMC,sBAAAA,aAAa,MAAMA,sBAAAA,WAAW,OAAOA,sBAAAA;CAC/C,MAAM,MAAM,IAAI,IAAY,CAAgB;CAC5C,KAAK,MAAM,MAAM,GAAkB,IAAI,IAAI,EAAE;CAC7C,OAAO;AACT;;;;;;;;;;AAyBA,SAAgB,eACd,QACA,UACA,SACA,SACA,aACe;CACf,MAAM,QAAQ,OAAOC,oBAAAA;CACrB,MAAM,yBAAS,IAAI,IAGjB;CACF,MAAM,aAAa,IAAIC,sBAAAA,WAAW;CAElC,MAAM,WAAW,YAAmC;EAClD,MAAM,OAAO,SAAS,QAAQ,MAAM;EACpC,MAAM,MAAM,SAAS,WAAW,MAAM,MAAM,KAAA,GAAW,IAAI;EAO3D,OAAO;GAAE,KANG,SAAS,QAAQ,MAAM,MAAM,KAAK;IAC5C,WAAW;IACX,UAAU;IACV;IACA,gBAAgB,QAAQ,WAAW;GACrC,CACW;GAAG;GAAK;EAAK;CAC1B;CAEA,MAAM,UAAU;EACd,YAAY,YAAmC,QAAQ,OAAO,EAAE;EAChE,QAAQ,YAAmC;GACzC,MAAM,EAAE,KAAK,KAAK,SAAS,QAAQ,OAAO;GAC1C,IAAI,QAAQ,WAAW,MAAM,OAAO,CAAC,IAAI,OAAO,GAAG;GAEnD,MAAM,UAAA,GAAA,sBAAA,aAAqB,IAAI,OAAO,IAAI,UAAU,UAAU;GAC9D,IAAI,QAAQ,OAAO,IAAI,GAAG;GAC1B,IAAI,UAAU,KAAA,GAAW;IACvB,MAAM,MAAM,EAAE,SAAS,OAAO,MAAiB;IAC/C,QAAQ;KAAE;KAAK,OAAO,kBAAkB,KAAK,KAAK,WAAW;IAAE;IAC/D,OAAO,IAAI,KAAK,KAAK;GACvB,OACE,MAAM,IAAI,UAAU,OAAO;GAE7B,QAAQ,OAAO,KAAK,OAAO,OAAO,MAAM,MAAM,KAAK,MAAM,OAAO,MAAM;GACtE,OAAO,CAAC,OAAO,OAAO,MAAM,KAAK;EACnC;CACF;CAEA,OAAO,eAAe,SAASD,oBAAAA,WAAW;EACxC,OAAO;EACP,YAAY;EACZ,UAAU;EACV,cAAc;CAChB,CAAC;CAED,OAAO;AACT;;;AC5QA,IAAI,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoDrB,SAAgB,QAGd,WACA,SACmC;CACnC,MAAM,WAAWE,yBAAAA,YAAY;CAE7B,MAAM,eAAA,GAAA,MAAA,QAAsC,IAAI;CAChD,MAAM,WAAY,YAAY,YAAY,eAAe;CAEzD,SAAS,SAAS,SAAS;CAC3B,SAAS,UAAU,SAAS;CAC5B,SAAS,YAAY,SAAS;CAE9B,SAAS,UAAW,SAAmD;CACvE,SAAS,eAAe,gBAAgB,SAAS;CACjD,MAAM,gBAAgB,qBAAqB,QAAQ;CACnD,MAAM,cAAc,SAAS,WAAW,WAAW,KAAA,GAAW,aAAa;CAS3E,IACE,SAAS,cAAc,aACvB,SAAS,gBAAgB,eACzB,SAAS,aAAa,YACtB,SAAS,eAAe,SAAS,aAEjC,KAAK,UAAU,WAAW,aAAa,UAAU,aAAa;CAGhE,CAAA,GAAA,MAAA,sBACE,SAAS,WACT,SAAS,aACT,SAAS,WACX;CAEA,MAAM,YAAY,SAAS;CAC3B,MAAM,WAAW,UAAU;CAC3B,SAAS,gBAAgB;CACzB,MAAM,SAAS,SAAS;CACxB,SAAS,eAAe,WAAW,KAAA;CACnC,IAAI;CACJ,IAAI,WAAW,KAAA,GAAW;EACxB,QAAQ;EAER,SAAS,YAAY,OAAO,UAAU,SAA6B;EACnE,YAAY,UAAUC,sBAAAA,SAAS;CACjC,OAAO;EACL,MAAM,WAAA,GAAA,sBAAA,aACJ,UACA,UAAU,UACT,SAAS,eAAe,IAAIC,sBAAAA,WAAW,CAC1C;EACA,QAAQ,QAAQ;EAChB,SAAS,QAAQ,UAAU,QAAQ;EACnC,SAAS,QAAQ,QAAQ;EAGzB,SAAS,SAAS,QAAQ;EAG1B,SAAS,KAAK,MAAM,WAAW,QAAQ,KAAK;CAC9C;CAIA,CAAA,GAAA,MAAA,iBAAgB,SAAS,MAAM;CAE/B,CAAA,GAAA,MAAA,iBAAgB,SAAS,OAAO,OAAO;CAEvC,OAAO,CAAC,OAAO,SAAS,WAAW;AACrC;AAEA,MAAM,aAAmB,CAAC;AAG1B,MAAM,YAAA,GAAA,sBAAA,cAAiC;AACvC,MAAM,UAAc,CAAC;AA2DrB,SAAS,iBAA2B;CAClC,MAAM,KAAK,WAAW;CACtB,MAAM,WAAqB;EACzB;EACA,cAAc,WAAW;EACzB,WAAW;EACX,aAAa;EACb,UAAU;EACV,WAAW;EACX,aAAa;EACb,WAAW;EACX,YAAY;EACZ,aAAa;EACb,eAAe,KAAA;EACf,SAAS;EACT,QAAQ;EACR,YAAY;GACV,SAAS;GACT,SAAS,OAAO;EAClB;EACA,mBAAmB,SAAS;EAC5B,gBAAgB,SAAS,QAAQ;EACjC,mBAAmB,YAAY,QAAQ;EACvC,cAAc,OAAO,QAAQ;EAC7B,aAAa;EACb,UAAU;EACV,cAAc;EACd,cAAc;EACd,OAAO;EACP,cAAc;EACd,WAAW;EACX,MAAM;EACN,QAAQ,KAAA;EACR,SAAS,KAAA;EACT,WAAW,KAAA;EACX,SAAS,KAAA;EACT,cAAc,KAAA;EACd,SAAS,EAAE,SAAS,KAAK;EACzB,QAAQ;EACR,YAAY;EACZ,YAAY;EACZ,UAAU;EACV,eAAe;EACf,WAAW;CACb;CACA,MAAM,iBAAuB;EAC3B,QAAQ,QAAQ;EAChB,SAAS,KAAK,QAAQ;CACxB;CACA,SAAS,OAAO,IAAI,WAAW,IAAI,SAAS,IAAI;CAChD,OAAO;AACT;AAEA,SAAS,KACP,UACA,WACA,aACA,UACA,MACM;CAGN,MAAM,WAAW,SAAS,QAAQ,WAAW,aAAa;EACxD,WAAW;EACX,UAAU;EACJ;EACN,gBAAgB;CAClB,CAAC;CAID,IAAI;CACJ,MAAM,eAAe,WAA4B;EAC/C,gCAAgB,IAAI,IAAI;EACxB,MAAM,SAAS,YAAY,IAAI,MAAM;EACrC,IAAI,WAAW,KAAA,GAAW,OAAO;EACjC,MAAM,UAAU,eACd,QACA,UACA,SAAS,MACT,SAAS,SACT,WACF;EACA,YAAY,IAAI,QAAQ,OAAO;EAC/B,OAAO;CACT;CAEA,SAAS,YAAY;CACrB,SAAS,cAAc;CACvB,SAAS,WAAW;CACpB,SAAS,aAAa,SAAS;CAC/B,SAAS,YAAY;CACrB,SAAS,cAAc,kBACrB,UACA,SAAS,SACT,WACF;CAEA,SAAS,aAAa,kBAAkB,UAAU,UAAU,aAAa;AAC3E;AAEA,SAAS,UAAU,UAAoB,eAAuC;CAC5E,SAAS,SAAS;CAClB,MAAM,YAAY,SAAS;CAC3B,MAAM,eAAe,UAAU,eAAe,SAAS,QAAQ;CAC/D,aAAa,YAAY,SAAS,QAAQ;CAC1C,SAAS,eAAe;CACxB,SAAS,eAAe;CACxB,UAAU,sBAAsB,SAAS,IAAI,SAAS,KAAK;CAG3D,OAAO,SAAS;AAClB;AAEA,SAAS,YAAY,UAA0B;CAC7C,SAAS,SAAS;CAClB,SAAS,cAAc,YAAY;CACnC,SAAS,eAAe;CACxB,SAAS,cAAc,mBAAmB,SAAS,EAAE;CACrD,SAAS,eAAe;AAC1B;AAEA,SAAS,SAAS,UAA0B;CAC1C,IAAI,SAAS,cAAc;EACzB,MAAM,SAAS,SAAS;EACxB,MAAM,YAAY,SAAS;EAC3B,IAAI,UAAU,cAAc,MAAM;GAChC,MAAM,OAAO,OAAO,UAAU,OAAO,SAAS;GAC9C,MAAM,OAAO,SAAS;GACtB,IAAI,SAAS,QAAQ,kBAAkB,MAAM,IAAI,GAAG;GACpD,SAAS,YAAY;EACvB;CACF;CACA,SAAS,KAAK;AAChB;AAEA,SAAS,MAAM,UAA0B;CACvC,MAAM,EAAE,WAAW,aAAa,aAAa;CAC7C,IACE,SAAS,eAAe,aACxB,SAAS,aAAa,eACtB,SAAS,kBAAkB,UAE3B;CAEF,QAAQ,QAAQ;CAChB,MAAM,OAAO,SAAS,QAAQ,WAAW,aAAa;EACpD,WAAW;EACX,UAAU;EACV,OAAO,SAAS;EAChB,MAAM,qBACJ,QACF;CACF,CAAC;CACD,SAAS,aAAa;CACtB,SAAS,WAAW;CACpB,SAAS,gBAAgB;CACzB,SAAS,YAAY;CACrB,SAAS,UAAU,IAAI;CAEvB,IAAI,SAAU,SAAS,WAAuB;EAC5C,SAAS;EACT,SAAS,KAAK;CAChB;AACF;AAEA,SAAS,QAAQ,UAA0B;CACzC,MAAM,EAAE,YAAY,kBAAkB;CACtC,IAAI,eAAe,QAAQ,kBAAkB,MAAM;CAEnD,SAAS,YAAY,SAAS,SAAS;CACvC,SAAS,aAAa;CACtB,SAAS,gBAAgB;CACzB,cAAc,QACZ,YACA,SAAS,UACT,OACA,SAAS,YACX;AACF;AAEA,SAAS,OAAO,UAA0B;CACxC,MAAM,QAAQ;CAEd,SAAS,QAAQ,UAAU;CAC3B,MAAM,SAAS,SAAS;CACxB,IAAI,WAAW,MAAM;EACnB,SAAS,SAAS;EAClB,OAAO;CACT;CACA,MAAM,OAAO,SAAS;CACtB,KAAK,OAAO;CACZ,IAAI,SAAS,cAAc;EAEzB,KAAK,WAAW;EAChB;CACF;CACA,MAAM,UAAU,SAAS;CAGzB,IAAI,QAAQ,UAAU,SAAS,eAAe,SAAS,KAAK;CAC5D,IAAI,KAAK,YAAY,GAAG;CAExB,QAAQ,sBAAsB,SAAS,IAAI,SAAS,KAAK;CACzD,YAAY,UAAU,SAAS,KAAK;CACpC,KAAK,UAAU,SAAS,QAAQ;AAClC;AAEA,MAAM,eAAe,UAAoB,aAA4B;CACnE,SAAS,WAAW;CACpB,SAAS,cAAc,YAAY,QAAQ;AAC7C;AAIA,MAAM,wBAAwB,aAC5B,SAAS,YAAY,KAAA,IAAY,SAAS,UAAU,SAAS;AAE/D,MAAM,qBAAqB,GAAc,MAA0B;CACjE,IAAI,MAAM,GAAG,OAAO;CACpB,IAAI,EAAE,WAAW,EAAE,QAAQ,OAAO;CAClC,KAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,KAC5B,IAAI,CAAC,OAAO,GAAG,EAAE,IAAI,EAAE,EAAE,GAAG,OAAO;CAErC,OAAO;AACT;;;;;;;;;;;;;;;;;;;;ACraA,SAAgB,YACd,MACA,OACM;CACN,MAAM,WAAA,GAAA,MAAA,OAAgB;CACtB,MAAM,UAAA,GAAA,MAAA,QAAgB,KAAK;CAC3B,OAAO,UAAU;CAIjB,CAAA,GAAA,MAAA,iBAAgB;EACd,KAAKC,oBAAAA,YAAY,SAAS,OAAO,OAAO;CAC1C,CAAC;CAED,CAAA,GAAA,MAAA,iBAAgB;EACd,aAAa;GACX,KAAKC,oBAAAA,mBAAmB,OAAO;EACjC;CACF,GAAG,CAAC,MAAM,OAAO,CAAC;AACpB"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["WITH_TRACKED_STATE","DEP_BRAND","NO_PATHS","ALL_PATHS","DEP_BRAND","ProxyCache","useRegistry","ALL_PATHS","ProxyCache","APPLY_DEPS","REMOVE_DEPS_OWNER"],"sources":["../src/BlocProvider.tsx","../src/buildTrackedProxy.ts","../src/depSession.ts","../src/useBloc.ts","../src/useBlocDeps.ts"],"sourcesContent":["import {\n createContext,\n useContext,\n useMemo,\n type ReactElement,\n type ReactNode,\n} from 'react';\nimport type { ExtractArgs, StateContainerConstructor } from '@blac/core';\n\n/**\n * Internal context value: a Map from bloc constructor to the args object\n * provided by the nearest BlocProvider for that bloc class.\n *\n * Each provider copies its parent's map and adds its own entry, so nested\n * providers for different blocs compose without clobbering each other.\n */\ntype ProvidedArgsMap = Map<StateContainerConstructor, unknown>;\n\nconst ProvidedArgsContext = createContext<ProvidedArgsMap>(new Map());\n\n/**\n * Props for {@link BlocProvider}.\n * @public\n */\nexport interface BlocProviderProps<T extends StateContainerConstructor> {\n /**\n * The bloc class whose args are being provided to descendants.\n */\n bloc: T;\n /**\n * Args that descendant `useBloc(bloc)` calls will resolve to when no\n * own `args` are given.\n */\n args: ExtractArgs<T>;\n children: ReactNode;\n}\n\n/**\n * Provides args to descendant `useBloc` calls for a specific bloc class via\n * React context. Own `args` on the `useBloc` call always win.\n *\n * Multiple `BlocProvider` wrappers for different bloc classes compose: each\n * merges its entry into the inherited map, so nested providers for different\n * blocs do not interfere.\n *\n * @example\n * ```tsx\n * <BlocProvider bloc={UserBloc} args={{ userId: 'alice' }}>\n * <UserProfile />\n * </BlocProvider>\n * ```\n *\n * @example Per-mount private instance\n * ```tsx\n * const id = useId();\n * <BlocProvider bloc={CartBloc} args={{ _id: id }}>\n * <CartWidget />\n * </BlocProvider>\n * ```\n * @public\n */\nexport function BlocProvider<T extends StateContainerConstructor>({\n bloc,\n args,\n children,\n}: BlocProviderProps<T>): ReactElement {\n const parentMap = useContext(ProvidedArgsContext);\n\n // Keyed on the args content, not identity, so an inline `args={{ ... }}`\n // literal does not rebuild the map (and re-render every consumer) on\n // every render, while a real field change still reaches `useProvidedArgs`.\n const argsKey = JSON.stringify(args);\n\n // Merge our entry into a new Map so sibling/parent providers for other blocs\n // are preserved.\n const mergedMap = useMemo(() => {\n const next = new Map(parentMap);\n next.set(bloc, args);\n return next;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [parentMap, bloc, argsKey]);\n\n return (\n <ProvidedArgsContext.Provider value={mergedMap}>\n {children}\n </ProvidedArgsContext.Provider>\n );\n}\n\n/**\n * Returns the args provided by the nearest {@link BlocProvider} for the given\n * bloc class, or `undefined` when called outside a matching provider.\n *\n * Used by `useBloc` to inherit provider args when no own `args` are given.\n * @public\n */\nexport function useProvidedArgs<T extends StateContainerConstructor>(\n BlocClass: T,\n): ExtractArgs<T> | undefined {\n const map = useContext(ProvidedArgsContext);\n return map.get(BlocClass) as ExtractArgs<T> | undefined;\n}\n","import { DEP_BRAND, WITH_TRACKED_STATE } from '@blac/core/internal';\n\ninterface TrackedStateTarget {\n [WITH_TRACKED_STATE]<R>(\n tracked: unknown,\n fn: () => R,\n onDepHandle?: (handle: object) => unknown,\n ): R;\n}\n\nfunction supportsTrackedState(value: object): value is TrackedStateTarget {\n return (\n typeof (value as TrackedStateTarget)[WITH_TRACKED_STATE] === 'function'\n );\n}\n\ntype Getter = (this: object) => unknown;\ntype Method = (...args: unknown[]) => unknown;\n\n// Prototype getters per class prototype. The chain is static, so walking it\n// once per class (not once per mounted component) keeps the get trap O(1).\nconst gettersByProto = new WeakMap<object, Map<string | symbol, Getter>>();\n\nfunction collectGetters(proto: object): Map<string | symbol, Getter> {\n const cached = gettersByProto.get(proto);\n if (cached !== undefined) return cached;\n const getters = new Map<string | symbol, Getter>();\n let p: object | null = proto;\n while (p !== null && p !== Object.prototype) {\n for (const key of Reflect.ownKeys(p)) {\n const desc = Object.getOwnPropertyDescriptor(p, key);\n // A descriptor getter, always `.call`ed with an explicit receiver.\n // oxlint-disable-next-line typescript/unbound-method\n if (desc?.get && !getters.has(key)) getters.set(key, desc.get);\n }\n p = Object.getPrototypeOf(p);\n }\n gettersByProto.set(proto, getters);\n return getters;\n}\n\n// Methods on the prototype, so a mount allocates the handler and the proxy\n// only, not a set of closures.\nclass BlocHandler implements ProxyHandler<object> {\n // Bound methods are cached so `bloc.method` keeps a stable identity across\n // reads — an unstable one would defeat memoisation in consumers. Keyed by\n // the underlying function too, so a reassigned method is re-bound.\n private boundMethods:\n | Map<string | symbol, { fn: Method; bound: Method }>\n | undefined = undefined;\n\n constructor(\n private readonly getters: Map<string | symbol, Getter>,\n private readonly tracksState: boolean,\n private readonly trackedStateRef: { current: unknown },\n private readonly onDepHandle: ((handle: object) => unknown) | undefined,\n ) {}\n\n get(target: object, key: string | symbol): unknown {\n const getter = this.getters.get(key);\n if (getter !== undefined) {\n const tracked = this.trackedStateRef.current;\n // `[WITH_TRACKED_STATE]` invokes the getter with `this` = the real\n // instance, so no per-read thunk is needed.\n return this.wrapDepHandle(\n tracked == null || !this.tracksState\n ? getter.call(target)\n : (target as TrackedStateTarget)[WITH_TRACKED_STATE](\n tracked,\n getter,\n this.onDepHandle,\n ),\n );\n }\n\n // Receiver is the real instance, so own fields keep their `#private`\n // brand. Methods are bound to it too, otherwise `bloc.method()` would\n // call with `this` = proxy and fail the same brand check.\n const value = Reflect.get(target, key, target);\n if (typeof value === 'function') {\n const fn = value as Method;\n this.boundMethods ??= new Map();\n let cached = this.boundMethods.get(key);\n if (cached === undefined || cached.fn !== fn) {\n cached = { fn, bound: fn.bind(target) };\n this.boundMethods.set(key, cached);\n }\n return this.wrapDepHandle(cached.bound);\n }\n return this.wrapDepHandle(value);\n }\n\n set(target: object, key: string | symbol, value: unknown): boolean {\n return Reflect.set(target, key, value, target);\n }\n\n private wrapDepHandle(value: unknown): unknown {\n if (\n this.onDepHandle !== undefined &&\n (typeof value === 'function' || typeof value === 'object') &&\n value !== null &&\n (value as Record<symbol, unknown>)[DEP_BRAND] !== undefined\n ) {\n return this.onDepHandle(value as object);\n }\n return value;\n }\n}\n\n/**\n * Build a per-consumer proxy for a bloc instance.\n *\n * Getters run with `this` bound to the real instance, not the proxy, so ES\n * `#private` fields and methods work in user blocs. Path recording is done by\n * `[WITH_TRACKED_STATE]`, which makes the instance's own `state` getter\n * report the render's tracking proxy for the call's duration; nested getter\n * chains stay tracked because the override remains set.\n *\n * One allocation per bloc acquisition (inside `useMemo`), so the proxy is\n * stable across renders.\n *\n * @param onDepHandle - Invoked when a read returns a branded dep handle;\n * returns the value (the session-bound wrapper) to expose in its place.\n * Must cache wrappers per handle to avoid re-allocation.\n */\nexport function buildTrackedProxy<T extends object>(\n instance: T,\n trackedStateRef: { current: unknown },\n onDepHandle?: (handle: object) => unknown,\n): T {\n // Both string- and symbol-keyed prototype getters (excluding\n // Object.prototype). Arrow-function class properties are own values, not\n // getters, and pass through unmodified.\n const handler = new BlocHandler(\n collectGetters(Object.getPrototypeOf(instance) as object),\n supportsTrackedState(instance),\n trackedStateRef,\n onDepHandle,\n );\n return new Proxy(instance, handler as ProxyHandler<T>);\n}\n","import type {\n StateContainer,\n StateContainerConstructor,\n StateContainerRegistry,\n} from '@blac/core';\nimport { DEP_BRAND } from '@blac/core/internal';\nimport {\n ALL_PATHS,\n emptyPathSet,\n pathSetEquals,\n trackRender,\n ProxyCache,\n type PathSet,\n type PathSubscription,\n} from '@dirtytalk/structural';\nimport { buildTrackedProxy } from './buildTrackedProxy';\n\nconst NO_PATHS: PathSet = emptyPathSet();\n\ninterface DepEntry {\n // One live set per `.track()` call, merged at commit: reads keep landing in\n // each call's own set until the render ends.\n parts: PathSet[];\n paths: PathSet;\n Type: StateContainerConstructor;\n key: string;\n args: unknown;\n}\n\ninterface DepSub {\n subscription: PathSubscription;\n registry: StateContainerRegistry;\n Type: StateContainerConstructor;\n key: string;\n}\n\n/**\n * The containers one `useBloc` consumer reads during a render: its primary\n * bloc plus every dep reached through `this.<handle>.track()`.\n *\n * The render records entries; the commit calls `reconcile`, which subscribes\n * and takes a ref on new deps and drops the ones no longer read. `dispose`\n * releases everything. Refs are only taken at commit, so an uncommitted render\n * never leaks one.\n */\nexport class DepSession {\n // Most consumers read no deps, so everything dep-related is allocated on the\n // first dep a render records.\n private primary: StateContainer | null = null;\n private primaryParts: PathSet[] = [];\n private primaryPaths: PathSet = NO_PATHS;\n private entries: Map<StateContainer, DepEntry> | null = null;\n private subs: Map<StateContainer, DepSub> | null = null;\n private reconciled = false;\n private lastPrimary: StateContainer | null = null;\n private lastPrimaryPaths: PathSet = NO_PATHS;\n private lastEntries: Map<StateContainer, DepEntry> | null = null;\n private disarms: Array<() => void> | null = null;\n\n constructor(\n private readonly consumerId: string,\n private readonly onChange: () => void,\n ) {}\n\n private get refId(): string {\n return `useBloc@${this.consumerId}:dep`;\n }\n\n begin(primary: StateContainer, paths: PathSet): void {\n this.entries?.clear();\n if (this.disarms !== null) this.disarms.length = 0;\n this.primary = primary;\n this.primaryParts = [paths];\n }\n\n record(\n dep: StateContainer,\n paths: PathSet,\n Type: StateContainerConstructor,\n key: string,\n args: unknown,\n disarm: () => void,\n ): void {\n (this.disarms ??= []).push(disarm);\n // A dep that resolves to the primary only widens the primary's entry.\n if (dep === this.primary) {\n this.primaryParts.push(paths);\n return;\n }\n const entries = (this.entries ??= new Map());\n const existing = entries.get(dep);\n if (existing !== undefined) {\n existing.parts.push(paths);\n } else {\n entries.set(dep, { parts: [paths], paths, Type, key, args });\n }\n }\n\n /**\n * Merge each container's per-call sets once the render's reads are done.\n * Returns the primary's paths, widened by deps that resolved to it.\n */\n settle(): PathSet {\n this.primaryPaths = mergeParts(this.primaryParts);\n if (this.entries !== null) {\n for (const entry of this.entries.values()) {\n entry.paths = mergeParts(entry.parts);\n }\n }\n return this.primaryPaths;\n }\n\n /** Stops every dep tracked this render from recording further reads. */\n disarm(): void {\n const disarms = this.disarms;\n if (disarms === null) return;\n for (const disarm of disarms) disarm();\n disarms.length = 0;\n }\n\n /** Forces the next `reconcile` to run in full. */\n invalidate(): void {\n this.reconciled = false;\n }\n\n isUnchanged(): boolean {\n if (\n !this.reconciled ||\n this.lastPrimary !== this.primary ||\n !pathSetEquals(this.lastPrimaryPaths, this.primaryPaths)\n ) {\n return false;\n }\n const last = this.lastEntries;\n const entries = this.entries;\n if ((last?.size ?? 0) !== (entries?.size ?? 0)) return false;\n if (last === null || entries === null) return true;\n for (const [container, entry] of entries) {\n const prev = last.get(container);\n if (prev === undefined || !sameEntry(prev, entry)) return false;\n }\n return true;\n }\n\n reconcile(registry: StateContainerRegistry): void {\n const { consumerId } = this;\n const entries = this.entries;\n this.reconciled = true;\n this.lastPrimary = this.primary;\n this.lastPrimaryPaths = this.primaryPaths;\n this.lastEntries =\n entries === null || entries.size === 0 ? null : new Map(entries);\n\n const subs = this.subs;\n if (subs !== null) {\n for (const [dep, sub] of subs) {\n if (entries?.has(dep) !== true) {\n this.drop(dep, sub);\n subs.delete(dep);\n }\n }\n }\n if (entries === null) return;\n\n for (const [dep, entry] of entries) {\n // StrictMode's simulated unmount can dispose a dep this render captured;\n // re-render so the session resolves the live instance.\n if (dep.$blac.disposed) {\n this.onChange();\n continue;\n }\n dep.registerConsumerPaths(consumerId, entry.paths);\n const existing = subs?.get(dep);\n if (existing) {\n existing.subscription.setInterest(entry.paths);\n continue;\n }\n const refId = this.refId;\n const live = registry.acquire(entry.Type, entry.key, {\n canCreate: true,\n countRef: true,\n refId,\n args: entry.args,\n });\n // The key now maps to another instance; re-render against that one.\n if (live !== dep) {\n registry.release(entry.Type, entry.key, false, refId);\n this.onChange();\n continue;\n }\n const subscription = dep.subscribePaths(this.onChange);\n subscription.setInterest(entry.paths);\n (this.subs ??= new Map()).set(dep, {\n subscription,\n registry,\n Type: entry.Type,\n key: entry.key,\n });\n }\n }\n\n dispose(): void {\n const subs = this.subs;\n if (subs !== null) {\n for (const [dep, sub] of subs) this.drop(dep, sub);\n subs.clear();\n }\n this.reconciled = false;\n }\n\n private drop(dep: StateContainer, sub: DepSub): void {\n sub.subscription.unsubscribe();\n dep.unregisterConsumer(this.consumerId);\n sub.registry.release(sub.Type, sub.key, false, this.refId);\n }\n}\n\nfunction sameEntry(a: DepEntry, b: DepEntry): boolean {\n return (\n pathSetEquals(a.paths, b.paths) &&\n a.key === b.key &&\n Object.is(a.args, b.args)\n );\n}\n\nfunction mergeParts(parts: PathSet[]): PathSet {\n if (parts.length === 1) return parts[0];\n const out = new Set<number>();\n for (const part of parts) {\n if (part === ALL_PATHS) return ALL_PATHS;\n for (const id of part) out.add(id);\n }\n return out;\n}\n\ninterface DepAccessOptionsLike {\n args?: unknown;\n}\n\n/** Structural shape of a branded `depend()` handle as seen from React. */\nexport interface DepHandleLike {\n track(options?: DepAccessOptionsLike): [unknown, StateContainer];\n untracked(options?: DepAccessOptionsLike): StateContainer;\n readonly [DEP_BRAND]: {\n Type: StateContainerConstructor;\n defaultArgs?: unknown;\n };\n}\n\n/**\n * Wrap a dep handle read inside a tracked getter. During a render (`tracked`\n * is set) `.track()` records the dep's read paths into `session` and returns\n * a tracked proxy, so the dep's own getters track too. Outside a render it\n * returns the live `[state, dep]`, like the core handle.\n *\n * Args resolve per call, so one handle can reach several instances; proxies\n * are cached per resolved instance.\n */\nexport function makeDepWrapper(\n handle: DepHandleLike,\n registry: StateContainerRegistry,\n session: DepSession,\n tracked: { current: unknown },\n onDepHandle: (handle: object) => unknown,\n): DepHandleLike {\n const brand = handle[DEP_BRAND];\n // Weak, so instances a getter stops reaching (e.g. by switching args) can\n // be collected once disposed.\n const perDep = new WeakMap<\n StateContainer,\n { ref: { current: unknown }; proxy: StateContainer }\n >();\n const proxyCache = new ProxyCache();\n\n const resolve = (options?: DepAccessOptionsLike) => {\n const args = options?.args ?? brand.defaultArgs;\n const key = registry.resolveKey(brand.Type, undefined, args);\n const dep = registry.acquire(brand.Type, key, {\n canCreate: true,\n countRef: false,\n args,\n sweepIfUnowned: tracked.current != null,\n }) as unknown as StateContainer;\n return { dep, key, args };\n };\n\n const wrapper = {\n untracked: (options?: DepAccessOptionsLike) => resolve(options).dep,\n track: (options?: DepAccessOptionsLike) => {\n const { dep, key, args } = resolve(options);\n if (tracked.current == null) return [dep.state, dep];\n\n const result = trackRender(dep.state, dep.interner, proxyCache);\n let cache = perDep.get(dep);\n if (cache === undefined) {\n const ref = { current: result.value as unknown };\n cache = { ref, proxy: buildTrackedProxy(dep, ref, onDepHandle) };\n perDep.set(dep, cache);\n } else {\n cache.ref.current = result.value;\n }\n session.record(dep, result.paths, brand.Type, key, args, result.disarm);\n return [result.value, cache.proxy];\n },\n } as DepHandleLike;\n\n Object.defineProperty(wrapper, DEP_BRAND, {\n value: brand,\n enumerable: false,\n writable: false,\n configurable: false,\n });\n\n return wrapper;\n}\n","import { useLayoutEffect, useRef, useSyncExternalStore } from 'react';\nimport {\n isIsolatedClass,\n type ExtractArgs,\n type ExtractState,\n type InstanceState,\n type StateContainer,\n type StateContainerConstructor,\n type StateContainerRegistry,\n} from '@blac/core';\nimport {\n ALL_PATHS,\n emptyPathSet,\n trackRender,\n ProxyCache,\n type PathSet,\n type PathSubscription,\n} from '@dirtytalk/structural';\nimport { useProvidedArgs } from './BlocProvider';\nimport { useRegistry } from './RegistryProvider';\nimport { buildTrackedProxy } from './buildTrackedProxy';\nimport { DepSession, makeDepWrapper, type DepHandleLike } from './depSession';\nimport type { UseBlocOptions, UseBlocReturn } from './types';\n\nlet nextConsumerId = 0;\n\n/**\n * React hook that connects a component to a state container with automatic\n * re-render on state changes.\n *\n * Two tracking modes:\n * - **Auto-tracking** (default): the returned state value is a proxy that\n * records read paths during render. The component re-renders when any\n * recorded path changes.\n * - **Manual select**: pass `options.select` to opt out of auto-tracking.\n * The hook re-renders only when the returned array's elements change\n * (per-index `Object.is`).\n *\n * Lifecycle:\n * - The bloc is acquired from the registry on mount and released on\n * unmount. The instance key is derived from `options.args` (own args),\n * then the surrounding {@link BlocProvider} context args for this bloc,\n * then the default key (no args).\n * - `options.onMount` fires after the bloc is acquired; `options.onUnmount`\n * fires *before* the registry releases its ref, so the bloc is still\n * alive when the callback runs.\n *\n * Per-mount private instance: mark the class `@blac({ isolated: true })`, or\n * pass a unique key arg yourself (e.g. `useId()` in `args`).\n *\n * @typeParam T - The state container constructor type (inferred from BlocClass)\n * @param BlocClass - The state container class to connect to\n * @param options - Configuration options\n * @returns Tuple of `[state, bloc]`\n *\n * @example Basic usage\n * ```ts\n * const [state, bloc] = useBloc(MyBloc);\n * ```\n *\n * @example Manual select\n * ```ts\n * const [state, bloc] = useBloc(MyBloc, {\n * select: (state) => [state.count],\n * });\n * ```\n *\n * @example Args-based shared instance\n * ```ts\n * const [state, bloc] = useBloc(UserBloc, { args: { userId: 'alice' } });\n * ```\n * @public\n */\nexport function useBloc<\n T extends StateContainerConstructor = StateContainerConstructor,\n>(\n BlocClass: T,\n options?: UseBlocOptions<T>,\n): UseBlocReturn<T, ExtractState<T>> {\n const registry = useRegistry();\n\n const consumerRef = useRef<Consumer | null>(null);\n const consumer = (consumerRef.current ??= createConsumer());\n\n consumer.select = options?.select;\n consumer.onMount = options?.onMount;\n consumer.onUnmount = options?.onUnmount;\n\n consumer.ownArgs = (options as { args?: ExtractArgs<T> } | undefined)?.args;\n consumer.providerArgs = useProvidedArgs(BlocClass);\n const effectiveArgs = resolveEffectiveArgs(consumer);\n const sharedKey = registry.resolveKey(BlocClass, undefined, effectiveArgs);\n const instanceKey = isIsolatedClass(BlocClass)\n ? `${sharedKey}#${consumer.id}`\n : sharedKey;\n\n // A memo kept on the consumer rather than in `useMemo`: no hook, deps array\n // or factory closure per mount. `rebindNonce` is bumped by the commit when\n // the rendered instance was disposed and recreated before commit (a\n // same-commit owner handoff, or StrictMode's double effect); the tracked\n // proxy can't be retargeted in place. Keyed on `BlocClass` as well as the\n // key: classes without args or a `static key` share the default key, so\n // `useBloc(cond ? A : B)` must still re-subscribe on a class swap.\n if (\n consumer.BlocClass !== BlocClass ||\n consumer.instanceKey !== instanceKey ||\n consumer.registry !== registry ||\n consumer.boundNonce !== consumer.rebindNonce\n ) {\n bind(consumer, BlocClass, instanceKey, registry, effectiveArgs);\n }\n\n useSyncExternalStore(\n consumer.subscribe,\n consumer.getSnapshot,\n consumer.getSnapshot,\n );\n\n const container = consumer.container;\n const rawState = container.state as ExtractState<T>;\n consumer.renderedState = rawState;\n const select = consumer.select;\n consumer.isSelectMode = select !== undefined;\n let state: ExtractState<T>;\n if (select !== undefined) {\n state = rawState;\n // Recomputed every render: the selector may close over props.\n consumer.selection = select(rawState, container as InstanceState<T>);\n setInterest(consumer, ALL_PATHS);\n } else {\n const tracked = trackRender(\n rawState,\n container.interner,\n (consumer.proxyCache ??= new ProxyCache()),\n );\n state = tracked.value as ExtractState<T>;\n consumer.tracked.current = tracked.value;\n consumer.paths = tracked.paths;\n // Run at commit, so reads that outlive the render (effects, handlers)\n // don't add paths.\n consumer.disarm = tracked.disarm;\n // `tracked.paths` is still empty here and fills during JSX, so paths are\n // registered at commit, not now.\n consumer.deps.begin(container, tracked.paths);\n }\n\n // Stable callbacks that read this render's inputs from the consumer.\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n useLayoutEffect(consumer.commit);\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n useLayoutEffect(consumer.mount, NO_DEPS);\n\n return [state, consumer.trackedBloc] as UseBlocReturn<T, ExtractState<T>>;\n}\n\nconst noop = (): void => {};\n\n// Initial `interest` and `paths`; neither is ever mutated in place.\nconst NO_PATHS: PathSet = emptyPathSet();\nconst NO_DEPS: [] = [];\n\n/**\n * Everything one `useBloc` call keeps between renders. Its callbacks are\n * created once, so a render allocates no closures.\n *\n * `bump` increments `version` before calling `notify`: uSES requires\n * `getSnapshot()` to reflect the change by the time it is notified.\n */\ninterface Consumer {\n id: string;\n primaryRefId: string;\n // The render's inputs, read by the commit of that render.\n BlocClass: StateContainerConstructor;\n instanceKey: string;\n registry: StateContainerRegistry;\n /** Written only by `bind`. */\n container: StateContainer;\n trackedBloc: unknown;\n subscribe: (onStoreChange: () => void) => () => void;\n boundNonce: number;\n rebindNonce: number;\n renderedState: unknown;\n version: number;\n /** `noop` while unsubscribed. */\n notify: () => void;\n bump: () => void;\n getSnapshot: () => number;\n onChange: () => void;\n unsubscribe: () => void;\n commit: () => void;\n mount: () => () => void;\n /** The committed `paths`, or `ALL_PATHS` in select mode. */\n interest: PathSet;\n /** `null` while unsubscribed. */\n subscription: PathSubscription | null;\n subscribedTo: StateContainer | null;\n paths: PathSet;\n isSelectMode: boolean;\n selection: unknown[] | null;\n deps: DepSession;\n // Typed loosely: `T` is per call site.\n select: ((state: any, bloc: any) => unknown[]) | undefined;\n onMount: ((bloc: any) => void) | undefined;\n onUnmount: ((bloc: any) => void) | undefined;\n ownArgs: unknown;\n providerArgs: unknown;\n /** The render's tracking proxy; `null` outside a tracked render. */\n tracked: { current: unknown };\n disarm: (() => void) | null;\n proxyCache: ProxyCache | null;\n // The instance the consumer holds a ref on, claimed at commit so acquire\n // and release pair exactly.\n ownedClass: StateContainerConstructor | null;\n ownedKey: string;\n ownedRegistry: StateContainerRegistry | null;\n ownedBloc: unknown;\n}\n\nfunction createConsumer(): Consumer {\n const id = `useBloc-${nextConsumerId++}`;\n const consumer: Consumer = {\n id,\n primaryRefId: `useBloc@${id}`,\n BlocClass: null as unknown as StateContainerConstructor,\n instanceKey: '',\n registry: null as unknown as StateContainerRegistry,\n container: null as unknown as StateContainer,\n trackedBloc: null,\n subscribe: null as unknown as Consumer['subscribe'],\n boundNonce: 0,\n rebindNonce: 0,\n renderedState: undefined,\n version: 0,\n notify: noop,\n bump: () => {\n consumer.version++;\n consumer.notify();\n },\n getSnapshot: () => consumer.version,\n onChange: () => onChange(consumer),\n unsubscribe: () => unsubscribe(consumer),\n commit: () => commit(consumer),\n mount: () => teardown,\n interest: NO_PATHS,\n subscription: null,\n subscribedTo: null,\n paths: NO_PATHS,\n isSelectMode: false,\n selection: null,\n deps: null as unknown as DepSession,\n select: undefined,\n onMount: undefined,\n onUnmount: undefined,\n ownArgs: undefined,\n providerArgs: undefined,\n tracked: { current: null },\n disarm: null,\n proxyCache: null,\n ownedClass: null,\n ownedKey: '',\n ownedRegistry: null,\n ownedBloc: null,\n };\n const teardown = (): void => {\n release(consumer);\n consumer.deps.dispose();\n };\n consumer.deps = new DepSession(id, consumer.bump);\n return consumer;\n}\n\nfunction bind(\n consumer: Consumer,\n BlocClass: StateContainerConstructor,\n instanceKey: string,\n registry: StateContainerRegistry,\n args: unknown,\n): void {\n // No ref during render; the commit claims it. The registry sweeps the\n // instance if no commit ever does (SSR, discarded renders).\n const instance = registry.acquire(BlocClass, instanceKey, {\n canCreate: true,\n countRef: false,\n args: args as ExtractArgs<StateContainerConstructor>,\n sweepIfUnowned: true,\n }) as unknown as StateContainer;\n\n // Dep proxies get `onDepHandle` too, so nested `.track()` calls (A→B→C)\n // record into this consumer's session.\n let depWrappers: Map<object, unknown> | undefined;\n const onDepHandle = (handle: object): unknown => {\n depWrappers ??= new Map();\n const cached = depWrappers.get(handle);\n if (cached !== undefined) return cached;\n const wrapper = makeDepWrapper(\n handle as DepHandleLike,\n registry,\n consumer.deps,\n consumer.tracked,\n onDepHandle,\n );\n depWrappers.set(handle, wrapper);\n return wrapper;\n };\n\n consumer.BlocClass = BlocClass;\n consumer.instanceKey = instanceKey;\n consumer.registry = registry;\n consumer.boundNonce = consumer.rebindNonce;\n consumer.container = instance;\n consumer.trackedBloc = buildTrackedProxy(\n instance,\n consumer.tracked,\n onDepHandle,\n );\n // A new identity per instance, so uSES re-subscribes.\n consumer.subscribe = (onStoreChange) => subscribe(consumer, onStoreChange);\n}\n\nfunction subscribe(consumer: Consumer, onStoreChange: () => void): () => void {\n consumer.notify = onStoreChange;\n const container = consumer.container;\n const subscription = container.subscribePaths(consumer.onChange);\n subscription.setInterest(consumer.interest);\n consumer.subscription = subscription;\n consumer.subscribedTo = container;\n container.registerConsumerPaths(consumer.id, consumer.paths);\n // An emit raised during render is still pending in the channel, so its\n // next flush delivers it to this subscriber.\n return consumer.unsubscribe;\n}\n\nfunction unsubscribe(consumer: Consumer): void {\n consumer.notify = noop;\n consumer.subscription?.unsubscribe();\n consumer.subscription = null;\n consumer.subscribedTo?.unregisterConsumer(consumer.id);\n consumer.subscribedTo = null;\n}\n\nfunction onChange(consumer: Consumer): void {\n if (consumer.isSelectMode) {\n const select = consumer.select;\n const container = consumer.subscribedTo;\n if (select && container !== null) {\n const next = select(container.state, container);\n const prev = consumer.selection;\n if (prev !== null && shallowArrayEqual(prev, next)) return;\n consumer.selection = next;\n }\n }\n consumer.bump();\n}\n\nfunction claim(consumer: Consumer): void {\n const { BlocClass, instanceKey, registry } = consumer;\n if (\n consumer.ownedClass === BlocClass &&\n consumer.ownedKey === instanceKey &&\n consumer.ownedRegistry === registry\n ) {\n return;\n }\n release(consumer);\n const live = registry.acquire(BlocClass, instanceKey, {\n canCreate: true,\n countRef: true,\n refId: consumer.primaryRefId,\n args: resolveEffectiveArgs(\n consumer,\n ) as ExtractArgs<StateContainerConstructor>,\n });\n consumer.ownedClass = BlocClass;\n consumer.ownedKey = instanceKey;\n consumer.ownedRegistry = registry;\n consumer.ownedBloc = live;\n consumer.onMount?.(live);\n // The render captured a stale instance; re-render against the live one.\n if (live !== (consumer.container as unknown)) {\n consumer.rebindNonce++;\n consumer.bump();\n }\n}\n\nfunction release(consumer: Consumer): void {\n const { ownedClass, ownedRegistry } = consumer;\n if (ownedClass === null || ownedRegistry === null) return;\n // Before release, so the bloc is still alive in the callback.\n consumer.onUnmount?.(consumer.ownedBloc);\n consumer.ownedClass = null;\n consumer.ownedRegistry = null;\n ownedRegistry.release(\n ownedClass,\n consumer.ownedKey,\n false,\n consumer.primaryRefId,\n );\n}\n\nfunction commit(consumer: Consumer): void {\n claim(consumer);\n // Getters called after commit read live state, not this render's proxy.\n consumer.tracked.current = null;\n const disarm = consumer.disarm;\n if (disarm !== null) {\n consumer.disarm = null;\n disarm();\n }\n const deps = consumer.deps;\n deps.disarm();\n if (consumer.isSelectMode) {\n // A later switch back to auto-track must reconcile in full.\n deps.invalidate();\n return;\n }\n const primary = consumer.container;\n // A change between render and commit was diffed against the previous\n // interest, which may lack paths only this render read.\n if (primary.state !== consumer.renderedState) consumer.bump();\n consumer.paths = deps.settle();\n if (deps.isUnchanged()) return;\n\n primary.registerConsumerPaths(consumer.id, consumer.paths);\n setInterest(consumer, consumer.paths);\n deps.reconcile(consumer.registry);\n}\n\nconst setInterest = (consumer: Consumer, interest: PathSet): void => {\n consumer.interest = interest;\n consumer.subscription?.setInterest(interest);\n};\n\n// Own args win over provider args. Shared by render and the ownership effect,\n// which recreates a disposed instance and must not drop its args.\nconst resolveEffectiveArgs = (consumer: Consumer): unknown =>\n consumer.ownArgs !== undefined ? consumer.ownArgs : consumer.providerArgs;\n\nconst shallowArrayEqual = (a: unknown[], b: unknown[]): boolean => {\n if (a === b) return true;\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!Object.is(a[i], b[i])) return false;\n }\n return true;\n};\n","import { useEffect, useId, useRef } from 'react';\nimport type { StateContainer } from '@blac/core';\nimport { APPLY_DEPS, REMOVE_DEPS_OWNER } from '@blac/core/internal';\n\n/**\n * Feed live values from the component into a bloc's `deps` lane.\n *\n * Each calling component is one owner: its slice is shallow-merged into the\n * bloc's merged `deps` view and withdrawn on unmount. The bloc observes\n * changes through `onDepsChanged`.\n *\n * ```tsx\n * const [state, bloc] = useBloc(CanvasCubit);\n * useBlocDeps(bloc, { canvas, onTick });\n * ```\n *\n * Use this for values a bloc must *read* but should not own — DOM handles,\n * callbacks, values from other hooks. Pass identity-stable values (`useMemo` /\n * `useCallback`) as you would to any hook; a new function identity each render\n * counts as a change.\n */\nexport function useBlocDeps<D extends object>(\n bloc: StateContainer<any, any, D>,\n slice: Partial<D>,\n): void {\n const ownerId = useId();\n const latest = useRef(slice);\n latest.current = slice;\n\n // `APPLY_DEPS` shallow-compares and no-ops on an unchanged slice, so applying\n // on every commit is cheaper than asking callers to maintain a dep array.\n useEffect(() => {\n bloc[APPLY_DEPS](ownerId, latest.current);\n });\n\n useEffect(() => {\n return () => {\n bloc[REMOVE_DEPS_OWNER](ownerId);\n };\n }, [bloc, ownerId]);\n}\n"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { n as useRegistry, t as RegistryProvider } from "./RegistryProvider-
|
|
1
|
+
import { n as useRegistry, t as RegistryProvider } from "./RegistryProvider-DS4ltjlH.js";
|
|
2
2
|
import { createContext, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useSyncExternalStore } from "react";
|
|
3
|
+
import { isIsolatedClass } from "@blac/core";
|
|
3
4
|
import { ALL_PATHS, ProxyCache, emptyPathSet, pathSetEquals, trackRender, untracked } from "@dirtytalk/structural";
|
|
4
5
|
import { jsx } from "react/jsx-runtime";
|
|
5
6
|
import { APPLY_DEPS, DEP_BRAND, REMOVE_DEPS_OWNER, WITH_TRACKED_STATE } from "@blac/core/internal";
|
|
@@ -151,6 +152,7 @@ var DepSession = class {
|
|
|
151
152
|
this.consumerId = consumerId;
|
|
152
153
|
this.onChange = onChange;
|
|
153
154
|
this.primary = null;
|
|
155
|
+
this.primaryParts = [];
|
|
154
156
|
this.primaryPaths = NO_PATHS$1;
|
|
155
157
|
this.entries = null;
|
|
156
158
|
this.subs = null;
|
|
@@ -167,24 +169,34 @@ var DepSession = class {
|
|
|
167
169
|
this.entries?.clear();
|
|
168
170
|
if (this.disarms !== null) this.disarms.length = 0;
|
|
169
171
|
this.primary = primary;
|
|
170
|
-
this.
|
|
172
|
+
this.primaryParts = [paths];
|
|
171
173
|
}
|
|
172
174
|
record(dep, paths, Type, key, args, disarm) {
|
|
173
175
|
(this.disarms ??= []).push(disarm);
|
|
174
176
|
if (dep === this.primary) {
|
|
175
|
-
this.
|
|
177
|
+
this.primaryParts.push(paths);
|
|
176
178
|
return;
|
|
177
179
|
}
|
|
178
180
|
const entries = this.entries ??= /* @__PURE__ */ new Map();
|
|
179
181
|
const existing = entries.get(dep);
|
|
180
|
-
if (existing !== void 0) existing.
|
|
182
|
+
if (existing !== void 0) existing.parts.push(paths);
|
|
181
183
|
else entries.set(dep, {
|
|
184
|
+
parts: [paths],
|
|
182
185
|
paths,
|
|
183
186
|
Type,
|
|
184
187
|
key,
|
|
185
188
|
args
|
|
186
189
|
});
|
|
187
190
|
}
|
|
191
|
+
/**
|
|
192
|
+
* Merge each container's per-call sets once the render's reads are done.
|
|
193
|
+
* Returns the primary's paths, widened by deps that resolved to it.
|
|
194
|
+
*/
|
|
195
|
+
settle() {
|
|
196
|
+
this.primaryPaths = mergeParts(this.primaryParts);
|
|
197
|
+
if (this.entries !== null) for (const entry of this.entries.values()) entry.paths = mergeParts(entry.parts);
|
|
198
|
+
return this.primaryPaths;
|
|
199
|
+
}
|
|
188
200
|
/** Stops every dep tracked this render from recording further reads. */
|
|
189
201
|
disarm() {
|
|
190
202
|
const disarms = this.disarms;
|
|
@@ -272,10 +284,13 @@ var DepSession = class {
|
|
|
272
284
|
function sameEntry(a, b) {
|
|
273
285
|
return pathSetEquals(a.paths, b.paths) && a.key === b.key && Object.is(a.args, b.args);
|
|
274
286
|
}
|
|
275
|
-
function
|
|
276
|
-
if (
|
|
277
|
-
const out = new Set(
|
|
278
|
-
for (const
|
|
287
|
+
function mergeParts(parts) {
|
|
288
|
+
if (parts.length === 1) return parts[0];
|
|
289
|
+
const out = /* @__PURE__ */ new Set();
|
|
290
|
+
for (const part of parts) {
|
|
291
|
+
if (part === ALL_PATHS) return ALL_PATHS;
|
|
292
|
+
for (const id of part) out.add(id);
|
|
293
|
+
}
|
|
279
294
|
return out;
|
|
280
295
|
}
|
|
281
296
|
/**
|
|
@@ -289,7 +304,7 @@ function unionPaths(a, b) {
|
|
|
289
304
|
*/
|
|
290
305
|
function makeDepWrapper(handle, registry, session, tracked, onDepHandle) {
|
|
291
306
|
const brand = handle[DEP_BRAND];
|
|
292
|
-
const perDep = /* @__PURE__ */ new
|
|
307
|
+
const perDep = /* @__PURE__ */ new WeakMap();
|
|
293
308
|
const proxyCache = new ProxyCache();
|
|
294
309
|
const resolve = (options) => {
|
|
295
310
|
const args = options?.args ?? brand.defaultArgs;
|
|
@@ -356,11 +371,8 @@ let nextConsumerId = 0;
|
|
|
356
371
|
* fires *before* the registry releases its ref, so the bloc is still
|
|
357
372
|
* alive when the callback runs.
|
|
358
373
|
*
|
|
359
|
-
* Per-mount private instance:
|
|
360
|
-
*
|
|
361
|
-
* const id = useId();
|
|
362
|
-
* const [state, bloc] = useBloc(MyBloc, { args: { _id: id } });
|
|
363
|
-
* ```
|
|
374
|
+
* Per-mount private instance: mark the class `@blac({ isolated: true })`, or
|
|
375
|
+
* pass a unique key arg yourself (e.g. `useId()` in `args`).
|
|
364
376
|
*
|
|
365
377
|
* @typeParam T - The state container constructor type (inferred from BlocClass)
|
|
366
378
|
* @param BlocClass - The state container class to connect to
|
|
@@ -395,7 +407,8 @@ function useBloc(BlocClass, options) {
|
|
|
395
407
|
consumer.ownArgs = options?.args;
|
|
396
408
|
consumer.providerArgs = useProvidedArgs(BlocClass);
|
|
397
409
|
const effectiveArgs = resolveEffectiveArgs(consumer);
|
|
398
|
-
const
|
|
410
|
+
const sharedKey = registry.resolveKey(BlocClass, void 0, effectiveArgs);
|
|
411
|
+
const instanceKey = isIsolatedClass(BlocClass) ? `${sharedKey}#${consumer.id}` : sharedKey;
|
|
399
412
|
if (consumer.BlocClass !== BlocClass || consumer.instanceKey !== instanceKey || consumer.registry !== registry || consumer.boundNonce !== consumer.rebindNonce) bind(consumer, BlocClass, instanceKey, registry, effectiveArgs);
|
|
400
413
|
useSyncExternalStore(consumer.subscribe, consumer.getSnapshot, consumer.getSnapshot);
|
|
401
414
|
const container = consumer.container;
|
|
@@ -573,6 +586,7 @@ function commit(consumer) {
|
|
|
573
586
|
}
|
|
574
587
|
const primary = consumer.container;
|
|
575
588
|
if (primary.state !== consumer.renderedState) consumer.bump();
|
|
589
|
+
consumer.paths = deps.settle();
|
|
576
590
|
if (deps.isUnchanged()) return;
|
|
577
591
|
primary.registerConsumerPaths(consumer.id, consumer.paths);
|
|
578
592
|
setInterest(consumer, consumer.paths);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["NO_PATHS"],"sources":["../src/BlocProvider.tsx","../src/buildTrackedProxy.ts","../src/depSession.ts","../src/useBloc.ts","../src/useBlocDeps.ts"],"sourcesContent":["import {\n createContext,\n useContext,\n useMemo,\n type ReactElement,\n type ReactNode,\n} from 'react';\nimport type { ExtractArgs, StateContainerConstructor } from '@blac/core';\n\n/**\n * Internal context value: a Map from bloc constructor to the args object\n * provided by the nearest BlocProvider for that bloc class.\n *\n * Each provider copies its parent's map and adds its own entry, so nested\n * providers for different blocs compose without clobbering each other.\n */\ntype ProvidedArgsMap = Map<StateContainerConstructor, unknown>;\n\nconst ProvidedArgsContext = createContext<ProvidedArgsMap>(new Map());\n\n/**\n * Props for {@link BlocProvider}.\n * @public\n */\nexport interface BlocProviderProps<T extends StateContainerConstructor> {\n /**\n * The bloc class whose args are being provided to descendants.\n */\n bloc: T;\n /**\n * Args that descendant `useBloc(bloc)` calls will resolve to when no\n * own `args` are given.\n */\n args: ExtractArgs<T>;\n children: ReactNode;\n}\n\n/**\n * Provides args to descendant `useBloc` calls for a specific bloc class via\n * React context. Own `args` on the `useBloc` call always win.\n *\n * Multiple `BlocProvider` wrappers for different bloc classes compose: each\n * merges its entry into the inherited map, so nested providers for different\n * blocs do not interfere.\n *\n * @example\n * ```tsx\n * <BlocProvider bloc={UserBloc} args={{ userId: 'alice' }}>\n * <UserProfile />\n * </BlocProvider>\n * ```\n *\n * @example Per-mount private instance\n * ```tsx\n * const id = useId();\n * <BlocProvider bloc={CartBloc} args={{ _id: id }}>\n * <CartWidget />\n * </BlocProvider>\n * ```\n * @public\n */\nexport function BlocProvider<T extends StateContainerConstructor>({\n bloc,\n args,\n children,\n}: BlocProviderProps<T>): ReactElement {\n const parentMap = useContext(ProvidedArgsContext);\n\n // Keyed on the args content, not identity, so an inline `args={{ ... }}`\n // literal does not rebuild the map (and re-render every consumer) on\n // every render, while a real field change still reaches `useProvidedArgs`.\n const argsKey = JSON.stringify(args);\n\n // Merge our entry into a new Map so sibling/parent providers for other blocs\n // are preserved.\n const mergedMap = useMemo(() => {\n const next = new Map(parentMap);\n next.set(bloc, args);\n return next;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [parentMap, bloc, argsKey]);\n\n return (\n <ProvidedArgsContext.Provider value={mergedMap}>\n {children}\n </ProvidedArgsContext.Provider>\n );\n}\n\n/**\n * Returns the args provided by the nearest {@link BlocProvider} for the given\n * bloc class, or `undefined` when called outside a matching provider.\n *\n * Used by `useBloc` to inherit provider args when no own `args` are given.\n * @public\n */\nexport function useProvidedArgs<T extends StateContainerConstructor>(\n BlocClass: T,\n): ExtractArgs<T> | undefined {\n const map = useContext(ProvidedArgsContext);\n return map.get(BlocClass) as ExtractArgs<T> | undefined;\n}\n","import { DEP_BRAND, WITH_TRACKED_STATE } from '@blac/core/internal';\n\ninterface TrackedStateTarget {\n [WITH_TRACKED_STATE]<R>(\n tracked: unknown,\n fn: () => R,\n onDepHandle?: (handle: object) => unknown,\n ): R;\n}\n\nfunction supportsTrackedState(value: object): value is TrackedStateTarget {\n return (\n typeof (value as TrackedStateTarget)[WITH_TRACKED_STATE] === 'function'\n );\n}\n\ntype Getter = (this: object) => unknown;\ntype Method = (...args: unknown[]) => unknown;\n\n// Prototype getters per class prototype. The chain is static, so walking it\n// once per class (not once per mounted component) keeps the get trap O(1).\nconst gettersByProto = new WeakMap<object, Map<string | symbol, Getter>>();\n\nfunction collectGetters(proto: object): Map<string | symbol, Getter> {\n const cached = gettersByProto.get(proto);\n if (cached !== undefined) return cached;\n const getters = new Map<string | symbol, Getter>();\n let p: object | null = proto;\n while (p !== null && p !== Object.prototype) {\n for (const key of Reflect.ownKeys(p)) {\n const desc = Object.getOwnPropertyDescriptor(p, key);\n // A descriptor getter, always `.call`ed with an explicit receiver.\n // oxlint-disable-next-line typescript/unbound-method\n if (desc?.get && !getters.has(key)) getters.set(key, desc.get);\n }\n p = Object.getPrototypeOf(p);\n }\n gettersByProto.set(proto, getters);\n return getters;\n}\n\n// Methods on the prototype, so a mount allocates the handler and the proxy\n// only, not a set of closures.\nclass BlocHandler implements ProxyHandler<object> {\n // Bound methods are cached so `bloc.method` keeps a stable identity across\n // reads — an unstable one would defeat memoisation in consumers. Keyed by\n // the underlying function too, so a reassigned method is re-bound.\n private boundMethods:\n | Map<string | symbol, { fn: Method; bound: Method }>\n | undefined = undefined;\n\n constructor(\n private readonly getters: Map<string | symbol, Getter>,\n private readonly tracksState: boolean,\n private readonly trackedStateRef: { current: unknown },\n private readonly onDepHandle: ((handle: object) => unknown) | undefined,\n ) {}\n\n get(target: object, key: string | symbol): unknown {\n const getter = this.getters.get(key);\n if (getter !== undefined) {\n const tracked = this.trackedStateRef.current;\n // `[WITH_TRACKED_STATE]` invokes the getter with `this` = the real\n // instance, so no per-read thunk is needed.\n return this.wrapDepHandle(\n tracked == null || !this.tracksState\n ? getter.call(target)\n : (target as TrackedStateTarget)[WITH_TRACKED_STATE](\n tracked,\n getter,\n this.onDepHandle,\n ),\n );\n }\n\n // Receiver is the real instance, so own fields keep their `#private`\n // brand. Methods are bound to it too, otherwise `bloc.method()` would\n // call with `this` = proxy and fail the same brand check.\n const value = Reflect.get(target, key, target);\n if (typeof value === 'function') {\n const fn = value as Method;\n this.boundMethods ??= new Map();\n let cached = this.boundMethods.get(key);\n if (cached === undefined || cached.fn !== fn) {\n cached = { fn, bound: fn.bind(target) };\n this.boundMethods.set(key, cached);\n }\n return this.wrapDepHandle(cached.bound);\n }\n return this.wrapDepHandle(value);\n }\n\n set(target: object, key: string | symbol, value: unknown): boolean {\n return Reflect.set(target, key, value, target);\n }\n\n private wrapDepHandle(value: unknown): unknown {\n if (\n this.onDepHandle !== undefined &&\n (typeof value === 'function' || typeof value === 'object') &&\n value !== null &&\n (value as Record<symbol, unknown>)[DEP_BRAND] !== undefined\n ) {\n return this.onDepHandle(value as object);\n }\n return value;\n }\n}\n\n/**\n * Build a per-consumer proxy for a bloc instance.\n *\n * Getters run with `this` bound to the real instance, not the proxy, so ES\n * `#private` fields and methods work in user blocs. Path recording is done by\n * `[WITH_TRACKED_STATE]`, which makes the instance's own `state` getter\n * report the render's tracking proxy for the call's duration; nested getter\n * chains stay tracked because the override remains set.\n *\n * One allocation per bloc acquisition (inside `useMemo`), so the proxy is\n * stable across renders.\n *\n * @param onDepHandle - Invoked when a read returns a branded dep handle;\n * returns the value (the session-bound wrapper) to expose in its place.\n * Must cache wrappers per handle to avoid re-allocation.\n */\nexport function buildTrackedProxy<T extends object>(\n instance: T,\n trackedStateRef: { current: unknown },\n onDepHandle?: (handle: object) => unknown,\n): T {\n // Both string- and symbol-keyed prototype getters (excluding\n // Object.prototype). Arrow-function class properties are own values, not\n // getters, and pass through unmodified.\n const handler = new BlocHandler(\n collectGetters(Object.getPrototypeOf(instance) as object),\n supportsTrackedState(instance),\n trackedStateRef,\n onDepHandle,\n );\n return new Proxy(instance, handler as ProxyHandler<T>);\n}\n","import type {\n StateContainer,\n StateContainerConstructor,\n StateContainerRegistry,\n} from '@blac/core';\nimport { DEP_BRAND } from '@blac/core/internal';\nimport {\n ALL_PATHS,\n emptyPathSet,\n pathSetEquals,\n trackRender,\n ProxyCache,\n type PathSet,\n type PathSubscription,\n} from '@dirtytalk/structural';\nimport { buildTrackedProxy } from './buildTrackedProxy';\n\nconst NO_PATHS: PathSet = emptyPathSet();\n\ninterface DepEntry {\n paths: PathSet;\n Type: StateContainerConstructor;\n key: string;\n args: unknown;\n}\n\ninterface DepSub {\n subscription: PathSubscription;\n registry: StateContainerRegistry;\n Type: StateContainerConstructor;\n key: string;\n}\n\n/**\n * The containers one `useBloc` consumer reads during a render: its primary\n * bloc plus every dep reached through `this.<handle>.track()`.\n *\n * The render records entries; the commit calls `reconcile`, which subscribes\n * and takes a ref on new deps and drops the ones no longer read. `dispose`\n * releases everything. Refs are only taken at commit, so an uncommitted render\n * never leaks one.\n */\nexport class DepSession {\n // Most consumers read no deps, so everything dep-related is allocated on the\n // first dep a render records.\n private primary: StateContainer | null = null;\n private primaryPaths: PathSet = NO_PATHS;\n private entries: Map<StateContainer, DepEntry> | null = null;\n private subs: Map<StateContainer, DepSub> | null = null;\n private reconciled = false;\n private lastPrimary: StateContainer | null = null;\n private lastPrimaryPaths: PathSet = NO_PATHS;\n private lastEntries: Map<StateContainer, DepEntry> | null = null;\n private disarms: Array<() => void> | null = null;\n\n constructor(\n private readonly consumerId: string,\n private readonly onChange: () => void,\n ) {}\n\n private get refId(): string {\n return `useBloc@${this.consumerId}:dep`;\n }\n\n begin(primary: StateContainer, paths: PathSet): void {\n this.entries?.clear();\n if (this.disarms !== null) this.disarms.length = 0;\n this.primary = primary;\n this.primaryPaths = paths;\n }\n\n record(\n dep: StateContainer,\n paths: PathSet,\n Type: StateContainerConstructor,\n key: string,\n args: unknown,\n disarm: () => void,\n ): void {\n (this.disarms ??= []).push(disarm);\n // A dep that resolves to the primary only widens the primary's entry.\n if (dep === this.primary) {\n this.primaryPaths = unionPaths(this.primaryPaths, paths);\n return;\n }\n const entries = (this.entries ??= new Map());\n const existing = entries.get(dep);\n if (existing !== undefined) {\n existing.paths = unionPaths(existing.paths, paths);\n } else {\n entries.set(dep, { paths, Type, key, args });\n }\n }\n\n /** Stops every dep tracked this render from recording further reads. */\n disarm(): void {\n const disarms = this.disarms;\n if (disarms === null) return;\n for (const disarm of disarms) disarm();\n disarms.length = 0;\n }\n\n /** Forces the next `reconcile` to run in full. */\n invalidate(): void {\n this.reconciled = false;\n }\n\n isUnchanged(): boolean {\n if (\n !this.reconciled ||\n this.lastPrimary !== this.primary ||\n !pathSetEquals(this.lastPrimaryPaths, this.primaryPaths)\n ) {\n return false;\n }\n const last = this.lastEntries;\n const entries = this.entries;\n if ((last?.size ?? 0) !== (entries?.size ?? 0)) return false;\n if (last === null || entries === null) return true;\n for (const [container, entry] of entries) {\n const prev = last.get(container);\n if (prev === undefined || !sameEntry(prev, entry)) return false;\n }\n return true;\n }\n\n reconcile(registry: StateContainerRegistry): void {\n const { consumerId } = this;\n const entries = this.entries;\n this.reconciled = true;\n this.lastPrimary = this.primary;\n this.lastPrimaryPaths = this.primaryPaths;\n this.lastEntries =\n entries === null || entries.size === 0 ? null : new Map(entries);\n\n const subs = this.subs;\n if (subs !== null) {\n for (const [dep, sub] of subs) {\n if (entries?.has(dep) !== true) {\n this.drop(dep, sub);\n subs.delete(dep);\n }\n }\n }\n if (entries === null) return;\n\n for (const [dep, entry] of entries) {\n // StrictMode's simulated unmount can dispose a dep this render captured;\n // re-render so the session resolves the live instance.\n if (dep.$blac.disposed) {\n this.onChange();\n continue;\n }\n dep.registerConsumerPaths(consumerId, entry.paths);\n const existing = subs?.get(dep);\n if (existing) {\n existing.subscription.setInterest(entry.paths);\n continue;\n }\n const refId = this.refId;\n const live = registry.acquire(entry.Type, entry.key, {\n canCreate: true,\n countRef: true,\n refId,\n args: entry.args,\n });\n // The key now maps to another instance; re-render against that one.\n if (live !== dep) {\n registry.release(entry.Type, entry.key, false, refId);\n this.onChange();\n continue;\n }\n const subscription = dep.subscribePaths(this.onChange);\n subscription.setInterest(entry.paths);\n (this.subs ??= new Map()).set(dep, {\n subscription,\n registry,\n Type: entry.Type,\n key: entry.key,\n });\n }\n }\n\n dispose(): void {\n const subs = this.subs;\n if (subs !== null) {\n for (const [dep, sub] of subs) this.drop(dep, sub);\n subs.clear();\n }\n this.reconciled = false;\n }\n\n private drop(dep: StateContainer, sub: DepSub): void {\n sub.subscription.unsubscribe();\n dep.unregisterConsumer(this.consumerId);\n sub.registry.release(sub.Type, sub.key, false, this.refId);\n }\n}\n\nfunction sameEntry(a: DepEntry, b: DepEntry): boolean {\n return (\n pathSetEquals(a.paths, b.paths) &&\n a.key === b.key &&\n Object.is(a.args, b.args)\n );\n}\n\nfunction unionPaths(a: PathSet, b: PathSet): PathSet {\n if (a === ALL_PATHS || b === ALL_PATHS) return ALL_PATHS;\n const out = new Set<number>(a as Set<number>);\n for (const id of b as Set<number>) out.add(id);\n return out;\n}\n\ninterface DepAccessOptionsLike {\n args?: unknown;\n}\n\n/** Structural shape of a branded `depend()` handle as seen from React. */\nexport interface DepHandleLike {\n track(options?: DepAccessOptionsLike): [unknown, StateContainer];\n untracked(options?: DepAccessOptionsLike): StateContainer;\n readonly [DEP_BRAND]: {\n Type: StateContainerConstructor;\n defaultArgs?: unknown;\n };\n}\n\n/**\n * Wrap a dep handle read inside a tracked getter. During a render (`tracked`\n * is set) `.track()` records the dep's read paths into `session` and returns\n * a tracked proxy, so the dep's own getters track too. Outside a render it\n * returns the live `[state, dep]`, like the core handle.\n *\n * Args resolve per call, so one handle can reach several instances; proxies\n * are cached per resolved instance.\n */\nexport function makeDepWrapper(\n handle: DepHandleLike,\n registry: StateContainerRegistry,\n session: DepSession,\n tracked: { current: unknown },\n onDepHandle: (handle: object) => unknown,\n): DepHandleLike {\n const brand = handle[DEP_BRAND];\n const perDep = new Map<\n StateContainer,\n { ref: { current: unknown }; proxy: StateContainer }\n >();\n const proxyCache = new ProxyCache();\n\n const resolve = (options?: DepAccessOptionsLike) => {\n const args = options?.args ?? brand.defaultArgs;\n const key = registry.resolveKey(brand.Type, undefined, args);\n const dep = registry.acquire(brand.Type, key, {\n canCreate: true,\n countRef: false,\n args,\n sweepIfUnowned: tracked.current != null,\n }) as unknown as StateContainer;\n return { dep, key, args };\n };\n\n const wrapper = {\n untracked: (options?: DepAccessOptionsLike) => resolve(options).dep,\n track: (options?: DepAccessOptionsLike) => {\n const { dep, key, args } = resolve(options);\n if (tracked.current == null) return [dep.state, dep];\n\n const result = trackRender(dep.state, dep.interner, proxyCache);\n let cache = perDep.get(dep);\n if (cache === undefined) {\n const ref = { current: result.value as unknown };\n cache = { ref, proxy: buildTrackedProxy(dep, ref, onDepHandle) };\n perDep.set(dep, cache);\n } else {\n cache.ref.current = result.value;\n }\n session.record(dep, result.paths, brand.Type, key, args, result.disarm);\n return [result.value, cache.proxy];\n },\n } as DepHandleLike;\n\n Object.defineProperty(wrapper, DEP_BRAND, {\n value: brand,\n enumerable: false,\n writable: false,\n configurable: false,\n });\n\n return wrapper;\n}\n","import { useLayoutEffect, useRef, useSyncExternalStore } from 'react';\nimport {\n type ExtractArgs,\n type ExtractState,\n type InstanceState,\n type StateContainer,\n type StateContainerConstructor,\n type StateContainerRegistry,\n} from '@blac/core';\nimport {\n ALL_PATHS,\n emptyPathSet,\n trackRender,\n ProxyCache,\n type PathSet,\n type PathSubscription,\n} from '@dirtytalk/structural';\nimport { useProvidedArgs } from './BlocProvider';\nimport { useRegistry } from './RegistryProvider';\nimport { buildTrackedProxy } from './buildTrackedProxy';\nimport { DepSession, makeDepWrapper, type DepHandleLike } from './depSession';\nimport type { UseBlocOptions, UseBlocReturn } from './types';\n\nlet nextConsumerId = 0;\n\n/**\n * React hook that connects a component to a state container with automatic\n * re-render on state changes.\n *\n * Two tracking modes:\n * - **Auto-tracking** (default): the returned state value is a proxy that\n * records read paths during render. The component re-renders when any\n * recorded path changes.\n * - **Manual select**: pass `options.select` to opt out of auto-tracking.\n * The hook re-renders only when the returned array's elements change\n * (per-index `Object.is`).\n *\n * Lifecycle:\n * - The bloc is acquired from the registry on mount and released on\n * unmount. The instance key is derived from `options.args` (own args),\n * then the surrounding {@link BlocProvider} context args for this bloc,\n * then the default key (no args).\n * - `options.onMount` fires after the bloc is acquired; `options.onUnmount`\n * fires *before* the registry releases its ref, so the bloc is still\n * alive when the callback runs.\n *\n * Per-mount private instance:\n * ```ts\n * const id = useId();\n * const [state, bloc] = useBloc(MyBloc, { args: { _id: id } });\n * ```\n *\n * @typeParam T - The state container constructor type (inferred from BlocClass)\n * @param BlocClass - The state container class to connect to\n * @param options - Configuration options\n * @returns Tuple of `[state, bloc]`\n *\n * @example Basic usage\n * ```ts\n * const [state, bloc] = useBloc(MyBloc);\n * ```\n *\n * @example Manual select\n * ```ts\n * const [state, bloc] = useBloc(MyBloc, {\n * select: (state) => [state.count],\n * });\n * ```\n *\n * @example Args-based shared instance\n * ```ts\n * const [state, bloc] = useBloc(UserBloc, { args: { userId: 'alice' } });\n * ```\n * @public\n */\nexport function useBloc<\n T extends StateContainerConstructor = StateContainerConstructor,\n>(\n BlocClass: T,\n options?: UseBlocOptions<T>,\n): UseBlocReturn<T, ExtractState<T>> {\n const registry = useRegistry();\n\n const consumerRef = useRef<Consumer | null>(null);\n const consumer = (consumerRef.current ??= createConsumer());\n\n consumer.select = options?.select;\n consumer.onMount = options?.onMount;\n consumer.onUnmount = options?.onUnmount;\n\n consumer.ownArgs = (options as { args?: ExtractArgs<T> } | undefined)?.args;\n consumer.providerArgs = useProvidedArgs(BlocClass);\n const effectiveArgs = resolveEffectiveArgs(consumer);\n const instanceKey = registry.resolveKey(BlocClass, undefined, effectiveArgs);\n\n // A memo kept on the consumer rather than in `useMemo`: no hook, deps array\n // or factory closure per mount. `rebindNonce` is bumped by the commit when\n // the rendered instance was disposed and recreated before commit (a\n // same-commit owner handoff, or StrictMode's double effect); the tracked\n // proxy can't be retargeted in place. Keyed on `BlocClass` as well as the\n // key: classes without args or a `static key` share the default key, so\n // `useBloc(cond ? A : B)` must still re-subscribe on a class swap.\n if (\n consumer.BlocClass !== BlocClass ||\n consumer.instanceKey !== instanceKey ||\n consumer.registry !== registry ||\n consumer.boundNonce !== consumer.rebindNonce\n ) {\n bind(consumer, BlocClass, instanceKey, registry, effectiveArgs);\n }\n\n useSyncExternalStore(\n consumer.subscribe,\n consumer.getSnapshot,\n consumer.getSnapshot,\n );\n\n const container = consumer.container;\n const rawState = container.state as ExtractState<T>;\n consumer.renderedState = rawState;\n const select = consumer.select;\n consumer.isSelectMode = select !== undefined;\n let state: ExtractState<T>;\n if (select !== undefined) {\n state = rawState;\n // Recomputed every render: the selector may close over props.\n consumer.selection = select(rawState, container as InstanceState<T>);\n setInterest(consumer, ALL_PATHS);\n } else {\n const tracked = trackRender(\n rawState,\n container.interner,\n (consumer.proxyCache ??= new ProxyCache()),\n );\n state = tracked.value as ExtractState<T>;\n consumer.tracked.current = tracked.value;\n consumer.paths = tracked.paths;\n // Run at commit, so reads that outlive the render (effects, handlers)\n // don't add paths.\n consumer.disarm = tracked.disarm;\n // `tracked.paths` is still empty here and fills during JSX, so paths are\n // registered at commit, not now.\n consumer.deps.begin(container, tracked.paths);\n }\n\n // Stable callbacks that read this render's inputs from the consumer.\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n useLayoutEffect(consumer.commit);\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n useLayoutEffect(consumer.mount, NO_DEPS);\n\n return [state, consumer.trackedBloc] as UseBlocReturn<T, ExtractState<T>>;\n}\n\nconst noop = (): void => {};\n\n// Initial `interest` and `paths`; neither is ever mutated in place.\nconst NO_PATHS: PathSet = emptyPathSet();\nconst NO_DEPS: [] = [];\n\n/**\n * Everything one `useBloc` call keeps between renders. Its callbacks are\n * created once, so a render allocates no closures.\n *\n * `bump` increments `version` before calling `notify`: uSES requires\n * `getSnapshot()` to reflect the change by the time it is notified.\n */\ninterface Consumer {\n id: string;\n primaryRefId: string;\n // The render's inputs, read by the commit of that render.\n BlocClass: StateContainerConstructor;\n instanceKey: string;\n registry: StateContainerRegistry;\n /** Written only by `bind`. */\n container: StateContainer;\n trackedBloc: unknown;\n subscribe: (onStoreChange: () => void) => () => void;\n boundNonce: number;\n rebindNonce: number;\n renderedState: unknown;\n version: number;\n /** `noop` while unsubscribed. */\n notify: () => void;\n bump: () => void;\n getSnapshot: () => number;\n onChange: () => void;\n unsubscribe: () => void;\n commit: () => void;\n mount: () => () => void;\n /** The committed `paths`, or `ALL_PATHS` in select mode. */\n interest: PathSet;\n /** `null` while unsubscribed. */\n subscription: PathSubscription | null;\n subscribedTo: StateContainer | null;\n paths: PathSet;\n isSelectMode: boolean;\n selection: unknown[] | null;\n deps: DepSession;\n // Typed loosely: `T` is per call site.\n select: ((state: any, bloc: any) => unknown[]) | undefined;\n onMount: ((bloc: any) => void) | undefined;\n onUnmount: ((bloc: any) => void) | undefined;\n ownArgs: unknown;\n providerArgs: unknown;\n /** The render's tracking proxy; `null` outside a tracked render. */\n tracked: { current: unknown };\n disarm: (() => void) | null;\n proxyCache: ProxyCache | null;\n // The instance the consumer holds a ref on, claimed at commit so acquire\n // and release pair exactly.\n ownedClass: StateContainerConstructor | null;\n ownedKey: string;\n ownedRegistry: StateContainerRegistry | null;\n ownedBloc: unknown;\n}\n\nfunction createConsumer(): Consumer {\n const id = `useBloc-${nextConsumerId++}`;\n const consumer: Consumer = {\n id,\n primaryRefId: `useBloc@${id}`,\n BlocClass: null as unknown as StateContainerConstructor,\n instanceKey: '',\n registry: null as unknown as StateContainerRegistry,\n container: null as unknown as StateContainer,\n trackedBloc: null,\n subscribe: null as unknown as Consumer['subscribe'],\n boundNonce: 0,\n rebindNonce: 0,\n renderedState: undefined,\n version: 0,\n notify: noop,\n bump: () => {\n consumer.version++;\n consumer.notify();\n },\n getSnapshot: () => consumer.version,\n onChange: () => onChange(consumer),\n unsubscribe: () => unsubscribe(consumer),\n commit: () => commit(consumer),\n mount: () => teardown,\n interest: NO_PATHS,\n subscription: null,\n subscribedTo: null,\n paths: NO_PATHS,\n isSelectMode: false,\n selection: null,\n deps: null as unknown as DepSession,\n select: undefined,\n onMount: undefined,\n onUnmount: undefined,\n ownArgs: undefined,\n providerArgs: undefined,\n tracked: { current: null },\n disarm: null,\n proxyCache: null,\n ownedClass: null,\n ownedKey: '',\n ownedRegistry: null,\n ownedBloc: null,\n };\n const teardown = (): void => {\n release(consumer);\n consumer.deps.dispose();\n };\n consumer.deps = new DepSession(id, consumer.bump);\n return consumer;\n}\n\nfunction bind(\n consumer: Consumer,\n BlocClass: StateContainerConstructor,\n instanceKey: string,\n registry: StateContainerRegistry,\n args: unknown,\n): void {\n // No ref during render; the commit claims it. The registry sweeps the\n // instance if no commit ever does (SSR, discarded renders).\n const instance = registry.acquire(BlocClass, instanceKey, {\n canCreate: true,\n countRef: false,\n args: args as ExtractArgs<StateContainerConstructor>,\n sweepIfUnowned: true,\n }) as unknown as StateContainer;\n\n // Dep proxies get `onDepHandle` too, so nested `.track()` calls (A→B→C)\n // record into this consumer's session.\n let depWrappers: Map<object, unknown> | undefined;\n const onDepHandle = (handle: object): unknown => {\n depWrappers ??= new Map();\n const cached = depWrappers.get(handle);\n if (cached !== undefined) return cached;\n const wrapper = makeDepWrapper(\n handle as DepHandleLike,\n registry,\n consumer.deps,\n consumer.tracked,\n onDepHandle,\n );\n depWrappers.set(handle, wrapper);\n return wrapper;\n };\n\n consumer.BlocClass = BlocClass;\n consumer.instanceKey = instanceKey;\n consumer.registry = registry;\n consumer.boundNonce = consumer.rebindNonce;\n consumer.container = instance;\n consumer.trackedBloc = buildTrackedProxy(\n instance,\n consumer.tracked,\n onDepHandle,\n );\n // A new identity per instance, so uSES re-subscribes.\n consumer.subscribe = (onStoreChange) => subscribe(consumer, onStoreChange);\n}\n\nfunction subscribe(consumer: Consumer, onStoreChange: () => void): () => void {\n consumer.notify = onStoreChange;\n const container = consumer.container;\n const subscription = container.subscribePaths(consumer.onChange);\n subscription.setInterest(consumer.interest);\n consumer.subscription = subscription;\n consumer.subscribedTo = container;\n container.registerConsumerPaths(consumer.id, consumer.paths);\n // An emit raised during render is still pending in the channel, so its\n // next flush delivers it to this subscriber.\n return consumer.unsubscribe;\n}\n\nfunction unsubscribe(consumer: Consumer): void {\n consumer.notify = noop;\n consumer.subscription?.unsubscribe();\n consumer.subscription = null;\n consumer.subscribedTo?.unregisterConsumer(consumer.id);\n consumer.subscribedTo = null;\n}\n\nfunction onChange(consumer: Consumer): void {\n if (consumer.isSelectMode) {\n const select = consumer.select;\n const container = consumer.subscribedTo;\n if (select && container !== null) {\n const next = select(container.state, container);\n const prev = consumer.selection;\n if (prev !== null && shallowArrayEqual(prev, next)) return;\n consumer.selection = next;\n }\n }\n consumer.bump();\n}\n\nfunction claim(consumer: Consumer): void {\n const { BlocClass, instanceKey, registry } = consumer;\n if (\n consumer.ownedClass === BlocClass &&\n consumer.ownedKey === instanceKey &&\n consumer.ownedRegistry === registry\n ) {\n return;\n }\n release(consumer);\n const live = registry.acquire(BlocClass, instanceKey, {\n canCreate: true,\n countRef: true,\n refId: consumer.primaryRefId,\n args: resolveEffectiveArgs(\n consumer,\n ) as ExtractArgs<StateContainerConstructor>,\n });\n consumer.ownedClass = BlocClass;\n consumer.ownedKey = instanceKey;\n consumer.ownedRegistry = registry;\n consumer.ownedBloc = live;\n consumer.onMount?.(live);\n // The render captured a stale instance; re-render against the live one.\n if (live !== (consumer.container as unknown)) {\n consumer.rebindNonce++;\n consumer.bump();\n }\n}\n\nfunction release(consumer: Consumer): void {\n const { ownedClass, ownedRegistry } = consumer;\n if (ownedClass === null || ownedRegistry === null) return;\n // Before release, so the bloc is still alive in the callback.\n consumer.onUnmount?.(consumer.ownedBloc);\n consumer.ownedClass = null;\n consumer.ownedRegistry = null;\n ownedRegistry.release(\n ownedClass,\n consumer.ownedKey,\n false,\n consumer.primaryRefId,\n );\n}\n\nfunction commit(consumer: Consumer): void {\n claim(consumer);\n // Getters called after commit read live state, not this render's proxy.\n consumer.tracked.current = null;\n const disarm = consumer.disarm;\n if (disarm !== null) {\n consumer.disarm = null;\n disarm();\n }\n const deps = consumer.deps;\n deps.disarm();\n if (consumer.isSelectMode) {\n // A later switch back to auto-track must reconcile in full.\n deps.invalidate();\n return;\n }\n const primary = consumer.container;\n // A change between render and commit was diffed against the previous\n // interest, which may lack paths only this render read.\n if (primary.state !== consumer.renderedState) consumer.bump();\n if (deps.isUnchanged()) return;\n\n primary.registerConsumerPaths(consumer.id, consumer.paths);\n setInterest(consumer, consumer.paths);\n deps.reconcile(consumer.registry);\n}\n\nconst setInterest = (consumer: Consumer, interest: PathSet): void => {\n consumer.interest = interest;\n consumer.subscription?.setInterest(interest);\n};\n\n// Own args win over provider args. Shared by render and the ownership effect,\n// which recreates a disposed instance and must not drop its args.\nconst resolveEffectiveArgs = (consumer: Consumer): unknown =>\n consumer.ownArgs !== undefined ? consumer.ownArgs : consumer.providerArgs;\n\nconst shallowArrayEqual = (a: unknown[], b: unknown[]): boolean => {\n if (a === b) return true;\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!Object.is(a[i], b[i])) return false;\n }\n return true;\n};\n","import { useEffect, useId, useRef } from 'react';\nimport type { StateContainer } from '@blac/core';\nimport { APPLY_DEPS, REMOVE_DEPS_OWNER } from '@blac/core/internal';\n\n/**\n * Feed live values from the component into a bloc's `deps` lane.\n *\n * Each calling component is one owner: its slice is shallow-merged into the\n * bloc's merged `deps` view and withdrawn on unmount. The bloc observes\n * changes through `onDepsChanged`.\n *\n * ```tsx\n * const [state, bloc] = useBloc(CanvasCubit);\n * useBlocDeps(bloc, { canvas, onTick });\n * ```\n *\n * Use this for values a bloc must *read* but should not own — DOM handles,\n * callbacks, values from other hooks. Pass identity-stable values (`useMemo` /\n * `useCallback`) as you would to any hook; a new function identity each render\n * counts as a change.\n */\nexport function useBlocDeps<D extends object>(\n bloc: StateContainer<any, any, D>,\n slice: Partial<D>,\n): void {\n const ownerId = useId();\n const latest = useRef(slice);\n latest.current = slice;\n\n // `APPLY_DEPS` shallow-compares and no-ops on an unchanged slice, so applying\n // on every commit is cheaper than asking callers to maintain a dep array.\n useEffect(() => {\n bloc[APPLY_DEPS](ownerId, latest.current);\n });\n\n useEffect(() => {\n return () => {\n bloc[REMOVE_DEPS_OWNER](ownerId);\n };\n }, [bloc, ownerId]);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["NO_PATHS"],"sources":["../src/BlocProvider.tsx","../src/buildTrackedProxy.ts","../src/depSession.ts","../src/useBloc.ts","../src/useBlocDeps.ts"],"sourcesContent":["import {\n createContext,\n useContext,\n useMemo,\n type ReactElement,\n type ReactNode,\n} from 'react';\nimport type { ExtractArgs, StateContainerConstructor } from '@blac/core';\n\n/**\n * Internal context value: a Map from bloc constructor to the args object\n * provided by the nearest BlocProvider for that bloc class.\n *\n * Each provider copies its parent's map and adds its own entry, so nested\n * providers for different blocs compose without clobbering each other.\n */\ntype ProvidedArgsMap = Map<StateContainerConstructor, unknown>;\n\nconst ProvidedArgsContext = createContext<ProvidedArgsMap>(new Map());\n\n/**\n * Props for {@link BlocProvider}.\n * @public\n */\nexport interface BlocProviderProps<T extends StateContainerConstructor> {\n /**\n * The bloc class whose args are being provided to descendants.\n */\n bloc: T;\n /**\n * Args that descendant `useBloc(bloc)` calls will resolve to when no\n * own `args` are given.\n */\n args: ExtractArgs<T>;\n children: ReactNode;\n}\n\n/**\n * Provides args to descendant `useBloc` calls for a specific bloc class via\n * React context. Own `args` on the `useBloc` call always win.\n *\n * Multiple `BlocProvider` wrappers for different bloc classes compose: each\n * merges its entry into the inherited map, so nested providers for different\n * blocs do not interfere.\n *\n * @example\n * ```tsx\n * <BlocProvider bloc={UserBloc} args={{ userId: 'alice' }}>\n * <UserProfile />\n * </BlocProvider>\n * ```\n *\n * @example Per-mount private instance\n * ```tsx\n * const id = useId();\n * <BlocProvider bloc={CartBloc} args={{ _id: id }}>\n * <CartWidget />\n * </BlocProvider>\n * ```\n * @public\n */\nexport function BlocProvider<T extends StateContainerConstructor>({\n bloc,\n args,\n children,\n}: BlocProviderProps<T>): ReactElement {\n const parentMap = useContext(ProvidedArgsContext);\n\n // Keyed on the args content, not identity, so an inline `args={{ ... }}`\n // literal does not rebuild the map (and re-render every consumer) on\n // every render, while a real field change still reaches `useProvidedArgs`.\n const argsKey = JSON.stringify(args);\n\n // Merge our entry into a new Map so sibling/parent providers for other blocs\n // are preserved.\n const mergedMap = useMemo(() => {\n const next = new Map(parentMap);\n next.set(bloc, args);\n return next;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [parentMap, bloc, argsKey]);\n\n return (\n <ProvidedArgsContext.Provider value={mergedMap}>\n {children}\n </ProvidedArgsContext.Provider>\n );\n}\n\n/**\n * Returns the args provided by the nearest {@link BlocProvider} for the given\n * bloc class, or `undefined` when called outside a matching provider.\n *\n * Used by `useBloc` to inherit provider args when no own `args` are given.\n * @public\n */\nexport function useProvidedArgs<T extends StateContainerConstructor>(\n BlocClass: T,\n): ExtractArgs<T> | undefined {\n const map = useContext(ProvidedArgsContext);\n return map.get(BlocClass) as ExtractArgs<T> | undefined;\n}\n","import { DEP_BRAND, WITH_TRACKED_STATE } from '@blac/core/internal';\n\ninterface TrackedStateTarget {\n [WITH_TRACKED_STATE]<R>(\n tracked: unknown,\n fn: () => R,\n onDepHandle?: (handle: object) => unknown,\n ): R;\n}\n\nfunction supportsTrackedState(value: object): value is TrackedStateTarget {\n return (\n typeof (value as TrackedStateTarget)[WITH_TRACKED_STATE] === 'function'\n );\n}\n\ntype Getter = (this: object) => unknown;\ntype Method = (...args: unknown[]) => unknown;\n\n// Prototype getters per class prototype. The chain is static, so walking it\n// once per class (not once per mounted component) keeps the get trap O(1).\nconst gettersByProto = new WeakMap<object, Map<string | symbol, Getter>>();\n\nfunction collectGetters(proto: object): Map<string | symbol, Getter> {\n const cached = gettersByProto.get(proto);\n if (cached !== undefined) return cached;\n const getters = new Map<string | symbol, Getter>();\n let p: object | null = proto;\n while (p !== null && p !== Object.prototype) {\n for (const key of Reflect.ownKeys(p)) {\n const desc = Object.getOwnPropertyDescriptor(p, key);\n // A descriptor getter, always `.call`ed with an explicit receiver.\n // oxlint-disable-next-line typescript/unbound-method\n if (desc?.get && !getters.has(key)) getters.set(key, desc.get);\n }\n p = Object.getPrototypeOf(p);\n }\n gettersByProto.set(proto, getters);\n return getters;\n}\n\n// Methods on the prototype, so a mount allocates the handler and the proxy\n// only, not a set of closures.\nclass BlocHandler implements ProxyHandler<object> {\n // Bound methods are cached so `bloc.method` keeps a stable identity across\n // reads — an unstable one would defeat memoisation in consumers. Keyed by\n // the underlying function too, so a reassigned method is re-bound.\n private boundMethods:\n | Map<string | symbol, { fn: Method; bound: Method }>\n | undefined = undefined;\n\n constructor(\n private readonly getters: Map<string | symbol, Getter>,\n private readonly tracksState: boolean,\n private readonly trackedStateRef: { current: unknown },\n private readonly onDepHandle: ((handle: object) => unknown) | undefined,\n ) {}\n\n get(target: object, key: string | symbol): unknown {\n const getter = this.getters.get(key);\n if (getter !== undefined) {\n const tracked = this.trackedStateRef.current;\n // `[WITH_TRACKED_STATE]` invokes the getter with `this` = the real\n // instance, so no per-read thunk is needed.\n return this.wrapDepHandle(\n tracked == null || !this.tracksState\n ? getter.call(target)\n : (target as TrackedStateTarget)[WITH_TRACKED_STATE](\n tracked,\n getter,\n this.onDepHandle,\n ),\n );\n }\n\n // Receiver is the real instance, so own fields keep their `#private`\n // brand. Methods are bound to it too, otherwise `bloc.method()` would\n // call with `this` = proxy and fail the same brand check.\n const value = Reflect.get(target, key, target);\n if (typeof value === 'function') {\n const fn = value as Method;\n this.boundMethods ??= new Map();\n let cached = this.boundMethods.get(key);\n if (cached === undefined || cached.fn !== fn) {\n cached = { fn, bound: fn.bind(target) };\n this.boundMethods.set(key, cached);\n }\n return this.wrapDepHandle(cached.bound);\n }\n return this.wrapDepHandle(value);\n }\n\n set(target: object, key: string | symbol, value: unknown): boolean {\n return Reflect.set(target, key, value, target);\n }\n\n private wrapDepHandle(value: unknown): unknown {\n if (\n this.onDepHandle !== undefined &&\n (typeof value === 'function' || typeof value === 'object') &&\n value !== null &&\n (value as Record<symbol, unknown>)[DEP_BRAND] !== undefined\n ) {\n return this.onDepHandle(value as object);\n }\n return value;\n }\n}\n\n/**\n * Build a per-consumer proxy for a bloc instance.\n *\n * Getters run with `this` bound to the real instance, not the proxy, so ES\n * `#private` fields and methods work in user blocs. Path recording is done by\n * `[WITH_TRACKED_STATE]`, which makes the instance's own `state` getter\n * report the render's tracking proxy for the call's duration; nested getter\n * chains stay tracked because the override remains set.\n *\n * One allocation per bloc acquisition (inside `useMemo`), so the proxy is\n * stable across renders.\n *\n * @param onDepHandle - Invoked when a read returns a branded dep handle;\n * returns the value (the session-bound wrapper) to expose in its place.\n * Must cache wrappers per handle to avoid re-allocation.\n */\nexport function buildTrackedProxy<T extends object>(\n instance: T,\n trackedStateRef: { current: unknown },\n onDepHandle?: (handle: object) => unknown,\n): T {\n // Both string- and symbol-keyed prototype getters (excluding\n // Object.prototype). Arrow-function class properties are own values, not\n // getters, and pass through unmodified.\n const handler = new BlocHandler(\n collectGetters(Object.getPrototypeOf(instance) as object),\n supportsTrackedState(instance),\n trackedStateRef,\n onDepHandle,\n );\n return new Proxy(instance, handler as ProxyHandler<T>);\n}\n","import type {\n StateContainer,\n StateContainerConstructor,\n StateContainerRegistry,\n} from '@blac/core';\nimport { DEP_BRAND } from '@blac/core/internal';\nimport {\n ALL_PATHS,\n emptyPathSet,\n pathSetEquals,\n trackRender,\n ProxyCache,\n type PathSet,\n type PathSubscription,\n} from '@dirtytalk/structural';\nimport { buildTrackedProxy } from './buildTrackedProxy';\n\nconst NO_PATHS: PathSet = emptyPathSet();\n\ninterface DepEntry {\n // One live set per `.track()` call, merged at commit: reads keep landing in\n // each call's own set until the render ends.\n parts: PathSet[];\n paths: PathSet;\n Type: StateContainerConstructor;\n key: string;\n args: unknown;\n}\n\ninterface DepSub {\n subscription: PathSubscription;\n registry: StateContainerRegistry;\n Type: StateContainerConstructor;\n key: string;\n}\n\n/**\n * The containers one `useBloc` consumer reads during a render: its primary\n * bloc plus every dep reached through `this.<handle>.track()`.\n *\n * The render records entries; the commit calls `reconcile`, which subscribes\n * and takes a ref on new deps and drops the ones no longer read. `dispose`\n * releases everything. Refs are only taken at commit, so an uncommitted render\n * never leaks one.\n */\nexport class DepSession {\n // Most consumers read no deps, so everything dep-related is allocated on the\n // first dep a render records.\n private primary: StateContainer | null = null;\n private primaryParts: PathSet[] = [];\n private primaryPaths: PathSet = NO_PATHS;\n private entries: Map<StateContainer, DepEntry> | null = null;\n private subs: Map<StateContainer, DepSub> | null = null;\n private reconciled = false;\n private lastPrimary: StateContainer | null = null;\n private lastPrimaryPaths: PathSet = NO_PATHS;\n private lastEntries: Map<StateContainer, DepEntry> | null = null;\n private disarms: Array<() => void> | null = null;\n\n constructor(\n private readonly consumerId: string,\n private readonly onChange: () => void,\n ) {}\n\n private get refId(): string {\n return `useBloc@${this.consumerId}:dep`;\n }\n\n begin(primary: StateContainer, paths: PathSet): void {\n this.entries?.clear();\n if (this.disarms !== null) this.disarms.length = 0;\n this.primary = primary;\n this.primaryParts = [paths];\n }\n\n record(\n dep: StateContainer,\n paths: PathSet,\n Type: StateContainerConstructor,\n key: string,\n args: unknown,\n disarm: () => void,\n ): void {\n (this.disarms ??= []).push(disarm);\n // A dep that resolves to the primary only widens the primary's entry.\n if (dep === this.primary) {\n this.primaryParts.push(paths);\n return;\n }\n const entries = (this.entries ??= new Map());\n const existing = entries.get(dep);\n if (existing !== undefined) {\n existing.parts.push(paths);\n } else {\n entries.set(dep, { parts: [paths], paths, Type, key, args });\n }\n }\n\n /**\n * Merge each container's per-call sets once the render's reads are done.\n * Returns the primary's paths, widened by deps that resolved to it.\n */\n settle(): PathSet {\n this.primaryPaths = mergeParts(this.primaryParts);\n if (this.entries !== null) {\n for (const entry of this.entries.values()) {\n entry.paths = mergeParts(entry.parts);\n }\n }\n return this.primaryPaths;\n }\n\n /** Stops every dep tracked this render from recording further reads. */\n disarm(): void {\n const disarms = this.disarms;\n if (disarms === null) return;\n for (const disarm of disarms) disarm();\n disarms.length = 0;\n }\n\n /** Forces the next `reconcile` to run in full. */\n invalidate(): void {\n this.reconciled = false;\n }\n\n isUnchanged(): boolean {\n if (\n !this.reconciled ||\n this.lastPrimary !== this.primary ||\n !pathSetEquals(this.lastPrimaryPaths, this.primaryPaths)\n ) {\n return false;\n }\n const last = this.lastEntries;\n const entries = this.entries;\n if ((last?.size ?? 0) !== (entries?.size ?? 0)) return false;\n if (last === null || entries === null) return true;\n for (const [container, entry] of entries) {\n const prev = last.get(container);\n if (prev === undefined || !sameEntry(prev, entry)) return false;\n }\n return true;\n }\n\n reconcile(registry: StateContainerRegistry): void {\n const { consumerId } = this;\n const entries = this.entries;\n this.reconciled = true;\n this.lastPrimary = this.primary;\n this.lastPrimaryPaths = this.primaryPaths;\n this.lastEntries =\n entries === null || entries.size === 0 ? null : new Map(entries);\n\n const subs = this.subs;\n if (subs !== null) {\n for (const [dep, sub] of subs) {\n if (entries?.has(dep) !== true) {\n this.drop(dep, sub);\n subs.delete(dep);\n }\n }\n }\n if (entries === null) return;\n\n for (const [dep, entry] of entries) {\n // StrictMode's simulated unmount can dispose a dep this render captured;\n // re-render so the session resolves the live instance.\n if (dep.$blac.disposed) {\n this.onChange();\n continue;\n }\n dep.registerConsumerPaths(consumerId, entry.paths);\n const existing = subs?.get(dep);\n if (existing) {\n existing.subscription.setInterest(entry.paths);\n continue;\n }\n const refId = this.refId;\n const live = registry.acquire(entry.Type, entry.key, {\n canCreate: true,\n countRef: true,\n refId,\n args: entry.args,\n });\n // The key now maps to another instance; re-render against that one.\n if (live !== dep) {\n registry.release(entry.Type, entry.key, false, refId);\n this.onChange();\n continue;\n }\n const subscription = dep.subscribePaths(this.onChange);\n subscription.setInterest(entry.paths);\n (this.subs ??= new Map()).set(dep, {\n subscription,\n registry,\n Type: entry.Type,\n key: entry.key,\n });\n }\n }\n\n dispose(): void {\n const subs = this.subs;\n if (subs !== null) {\n for (const [dep, sub] of subs) this.drop(dep, sub);\n subs.clear();\n }\n this.reconciled = false;\n }\n\n private drop(dep: StateContainer, sub: DepSub): void {\n sub.subscription.unsubscribe();\n dep.unregisterConsumer(this.consumerId);\n sub.registry.release(sub.Type, sub.key, false, this.refId);\n }\n}\n\nfunction sameEntry(a: DepEntry, b: DepEntry): boolean {\n return (\n pathSetEquals(a.paths, b.paths) &&\n a.key === b.key &&\n Object.is(a.args, b.args)\n );\n}\n\nfunction mergeParts(parts: PathSet[]): PathSet {\n if (parts.length === 1) return parts[0];\n const out = new Set<number>();\n for (const part of parts) {\n if (part === ALL_PATHS) return ALL_PATHS;\n for (const id of part) out.add(id);\n }\n return out;\n}\n\ninterface DepAccessOptionsLike {\n args?: unknown;\n}\n\n/** Structural shape of a branded `depend()` handle as seen from React. */\nexport interface DepHandleLike {\n track(options?: DepAccessOptionsLike): [unknown, StateContainer];\n untracked(options?: DepAccessOptionsLike): StateContainer;\n readonly [DEP_BRAND]: {\n Type: StateContainerConstructor;\n defaultArgs?: unknown;\n };\n}\n\n/**\n * Wrap a dep handle read inside a tracked getter. During a render (`tracked`\n * is set) `.track()` records the dep's read paths into `session` and returns\n * a tracked proxy, so the dep's own getters track too. Outside a render it\n * returns the live `[state, dep]`, like the core handle.\n *\n * Args resolve per call, so one handle can reach several instances; proxies\n * are cached per resolved instance.\n */\nexport function makeDepWrapper(\n handle: DepHandleLike,\n registry: StateContainerRegistry,\n session: DepSession,\n tracked: { current: unknown },\n onDepHandle: (handle: object) => unknown,\n): DepHandleLike {\n const brand = handle[DEP_BRAND];\n // Weak, so instances a getter stops reaching (e.g. by switching args) can\n // be collected once disposed.\n const perDep = new WeakMap<\n StateContainer,\n { ref: { current: unknown }; proxy: StateContainer }\n >();\n const proxyCache = new ProxyCache();\n\n const resolve = (options?: DepAccessOptionsLike) => {\n const args = options?.args ?? brand.defaultArgs;\n const key = registry.resolveKey(brand.Type, undefined, args);\n const dep = registry.acquire(brand.Type, key, {\n canCreate: true,\n countRef: false,\n args,\n sweepIfUnowned: tracked.current != null,\n }) as unknown as StateContainer;\n return { dep, key, args };\n };\n\n const wrapper = {\n untracked: (options?: DepAccessOptionsLike) => resolve(options).dep,\n track: (options?: DepAccessOptionsLike) => {\n const { dep, key, args } = resolve(options);\n if (tracked.current == null) return [dep.state, dep];\n\n const result = trackRender(dep.state, dep.interner, proxyCache);\n let cache = perDep.get(dep);\n if (cache === undefined) {\n const ref = { current: result.value as unknown };\n cache = { ref, proxy: buildTrackedProxy(dep, ref, onDepHandle) };\n perDep.set(dep, cache);\n } else {\n cache.ref.current = result.value;\n }\n session.record(dep, result.paths, brand.Type, key, args, result.disarm);\n return [result.value, cache.proxy];\n },\n } as DepHandleLike;\n\n Object.defineProperty(wrapper, DEP_BRAND, {\n value: brand,\n enumerable: false,\n writable: false,\n configurable: false,\n });\n\n return wrapper;\n}\n","import { useLayoutEffect, useRef, useSyncExternalStore } from 'react';\nimport {\n isIsolatedClass,\n type ExtractArgs,\n type ExtractState,\n type InstanceState,\n type StateContainer,\n type StateContainerConstructor,\n type StateContainerRegistry,\n} from '@blac/core';\nimport {\n ALL_PATHS,\n emptyPathSet,\n trackRender,\n ProxyCache,\n type PathSet,\n type PathSubscription,\n} from '@dirtytalk/structural';\nimport { useProvidedArgs } from './BlocProvider';\nimport { useRegistry } from './RegistryProvider';\nimport { buildTrackedProxy } from './buildTrackedProxy';\nimport { DepSession, makeDepWrapper, type DepHandleLike } from './depSession';\nimport type { UseBlocOptions, UseBlocReturn } from './types';\n\nlet nextConsumerId = 0;\n\n/**\n * React hook that connects a component to a state container with automatic\n * re-render on state changes.\n *\n * Two tracking modes:\n * - **Auto-tracking** (default): the returned state value is a proxy that\n * records read paths during render. The component re-renders when any\n * recorded path changes.\n * - **Manual select**: pass `options.select` to opt out of auto-tracking.\n * The hook re-renders only when the returned array's elements change\n * (per-index `Object.is`).\n *\n * Lifecycle:\n * - The bloc is acquired from the registry on mount and released on\n * unmount. The instance key is derived from `options.args` (own args),\n * then the surrounding {@link BlocProvider} context args for this bloc,\n * then the default key (no args).\n * - `options.onMount` fires after the bloc is acquired; `options.onUnmount`\n * fires *before* the registry releases its ref, so the bloc is still\n * alive when the callback runs.\n *\n * Per-mount private instance: mark the class `@blac({ isolated: true })`, or\n * pass a unique key arg yourself (e.g. `useId()` in `args`).\n *\n * @typeParam T - The state container constructor type (inferred from BlocClass)\n * @param BlocClass - The state container class to connect to\n * @param options - Configuration options\n * @returns Tuple of `[state, bloc]`\n *\n * @example Basic usage\n * ```ts\n * const [state, bloc] = useBloc(MyBloc);\n * ```\n *\n * @example Manual select\n * ```ts\n * const [state, bloc] = useBloc(MyBloc, {\n * select: (state) => [state.count],\n * });\n * ```\n *\n * @example Args-based shared instance\n * ```ts\n * const [state, bloc] = useBloc(UserBloc, { args: { userId: 'alice' } });\n * ```\n * @public\n */\nexport function useBloc<\n T extends StateContainerConstructor = StateContainerConstructor,\n>(\n BlocClass: T,\n options?: UseBlocOptions<T>,\n): UseBlocReturn<T, ExtractState<T>> {\n const registry = useRegistry();\n\n const consumerRef = useRef<Consumer | null>(null);\n const consumer = (consumerRef.current ??= createConsumer());\n\n consumer.select = options?.select;\n consumer.onMount = options?.onMount;\n consumer.onUnmount = options?.onUnmount;\n\n consumer.ownArgs = (options as { args?: ExtractArgs<T> } | undefined)?.args;\n consumer.providerArgs = useProvidedArgs(BlocClass);\n const effectiveArgs = resolveEffectiveArgs(consumer);\n const sharedKey = registry.resolveKey(BlocClass, undefined, effectiveArgs);\n const instanceKey = isIsolatedClass(BlocClass)\n ? `${sharedKey}#${consumer.id}`\n : sharedKey;\n\n // A memo kept on the consumer rather than in `useMemo`: no hook, deps array\n // or factory closure per mount. `rebindNonce` is bumped by the commit when\n // the rendered instance was disposed and recreated before commit (a\n // same-commit owner handoff, or StrictMode's double effect); the tracked\n // proxy can't be retargeted in place. Keyed on `BlocClass` as well as the\n // key: classes without args or a `static key` share the default key, so\n // `useBloc(cond ? A : B)` must still re-subscribe on a class swap.\n if (\n consumer.BlocClass !== BlocClass ||\n consumer.instanceKey !== instanceKey ||\n consumer.registry !== registry ||\n consumer.boundNonce !== consumer.rebindNonce\n ) {\n bind(consumer, BlocClass, instanceKey, registry, effectiveArgs);\n }\n\n useSyncExternalStore(\n consumer.subscribe,\n consumer.getSnapshot,\n consumer.getSnapshot,\n );\n\n const container = consumer.container;\n const rawState = container.state as ExtractState<T>;\n consumer.renderedState = rawState;\n const select = consumer.select;\n consumer.isSelectMode = select !== undefined;\n let state: ExtractState<T>;\n if (select !== undefined) {\n state = rawState;\n // Recomputed every render: the selector may close over props.\n consumer.selection = select(rawState, container as InstanceState<T>);\n setInterest(consumer, ALL_PATHS);\n } else {\n const tracked = trackRender(\n rawState,\n container.interner,\n (consumer.proxyCache ??= new ProxyCache()),\n );\n state = tracked.value as ExtractState<T>;\n consumer.tracked.current = tracked.value;\n consumer.paths = tracked.paths;\n // Run at commit, so reads that outlive the render (effects, handlers)\n // don't add paths.\n consumer.disarm = tracked.disarm;\n // `tracked.paths` is still empty here and fills during JSX, so paths are\n // registered at commit, not now.\n consumer.deps.begin(container, tracked.paths);\n }\n\n // Stable callbacks that read this render's inputs from the consumer.\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n useLayoutEffect(consumer.commit);\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n useLayoutEffect(consumer.mount, NO_DEPS);\n\n return [state, consumer.trackedBloc] as UseBlocReturn<T, ExtractState<T>>;\n}\n\nconst noop = (): void => {};\n\n// Initial `interest` and `paths`; neither is ever mutated in place.\nconst NO_PATHS: PathSet = emptyPathSet();\nconst NO_DEPS: [] = [];\n\n/**\n * Everything one `useBloc` call keeps between renders. Its callbacks are\n * created once, so a render allocates no closures.\n *\n * `bump` increments `version` before calling `notify`: uSES requires\n * `getSnapshot()` to reflect the change by the time it is notified.\n */\ninterface Consumer {\n id: string;\n primaryRefId: string;\n // The render's inputs, read by the commit of that render.\n BlocClass: StateContainerConstructor;\n instanceKey: string;\n registry: StateContainerRegistry;\n /** Written only by `bind`. */\n container: StateContainer;\n trackedBloc: unknown;\n subscribe: (onStoreChange: () => void) => () => void;\n boundNonce: number;\n rebindNonce: number;\n renderedState: unknown;\n version: number;\n /** `noop` while unsubscribed. */\n notify: () => void;\n bump: () => void;\n getSnapshot: () => number;\n onChange: () => void;\n unsubscribe: () => void;\n commit: () => void;\n mount: () => () => void;\n /** The committed `paths`, or `ALL_PATHS` in select mode. */\n interest: PathSet;\n /** `null` while unsubscribed. */\n subscription: PathSubscription | null;\n subscribedTo: StateContainer | null;\n paths: PathSet;\n isSelectMode: boolean;\n selection: unknown[] | null;\n deps: DepSession;\n // Typed loosely: `T` is per call site.\n select: ((state: any, bloc: any) => unknown[]) | undefined;\n onMount: ((bloc: any) => void) | undefined;\n onUnmount: ((bloc: any) => void) | undefined;\n ownArgs: unknown;\n providerArgs: unknown;\n /** The render's tracking proxy; `null` outside a tracked render. */\n tracked: { current: unknown };\n disarm: (() => void) | null;\n proxyCache: ProxyCache | null;\n // The instance the consumer holds a ref on, claimed at commit so acquire\n // and release pair exactly.\n ownedClass: StateContainerConstructor | null;\n ownedKey: string;\n ownedRegistry: StateContainerRegistry | null;\n ownedBloc: unknown;\n}\n\nfunction createConsumer(): Consumer {\n const id = `useBloc-${nextConsumerId++}`;\n const consumer: Consumer = {\n id,\n primaryRefId: `useBloc@${id}`,\n BlocClass: null as unknown as StateContainerConstructor,\n instanceKey: '',\n registry: null as unknown as StateContainerRegistry,\n container: null as unknown as StateContainer,\n trackedBloc: null,\n subscribe: null as unknown as Consumer['subscribe'],\n boundNonce: 0,\n rebindNonce: 0,\n renderedState: undefined,\n version: 0,\n notify: noop,\n bump: () => {\n consumer.version++;\n consumer.notify();\n },\n getSnapshot: () => consumer.version,\n onChange: () => onChange(consumer),\n unsubscribe: () => unsubscribe(consumer),\n commit: () => commit(consumer),\n mount: () => teardown,\n interest: NO_PATHS,\n subscription: null,\n subscribedTo: null,\n paths: NO_PATHS,\n isSelectMode: false,\n selection: null,\n deps: null as unknown as DepSession,\n select: undefined,\n onMount: undefined,\n onUnmount: undefined,\n ownArgs: undefined,\n providerArgs: undefined,\n tracked: { current: null },\n disarm: null,\n proxyCache: null,\n ownedClass: null,\n ownedKey: '',\n ownedRegistry: null,\n ownedBloc: null,\n };\n const teardown = (): void => {\n release(consumer);\n consumer.deps.dispose();\n };\n consumer.deps = new DepSession(id, consumer.bump);\n return consumer;\n}\n\nfunction bind(\n consumer: Consumer,\n BlocClass: StateContainerConstructor,\n instanceKey: string,\n registry: StateContainerRegistry,\n args: unknown,\n): void {\n // No ref during render; the commit claims it. The registry sweeps the\n // instance if no commit ever does (SSR, discarded renders).\n const instance = registry.acquire(BlocClass, instanceKey, {\n canCreate: true,\n countRef: false,\n args: args as ExtractArgs<StateContainerConstructor>,\n sweepIfUnowned: true,\n }) as unknown as StateContainer;\n\n // Dep proxies get `onDepHandle` too, so nested `.track()` calls (A→B→C)\n // record into this consumer's session.\n let depWrappers: Map<object, unknown> | undefined;\n const onDepHandle = (handle: object): unknown => {\n depWrappers ??= new Map();\n const cached = depWrappers.get(handle);\n if (cached !== undefined) return cached;\n const wrapper = makeDepWrapper(\n handle as DepHandleLike,\n registry,\n consumer.deps,\n consumer.tracked,\n onDepHandle,\n );\n depWrappers.set(handle, wrapper);\n return wrapper;\n };\n\n consumer.BlocClass = BlocClass;\n consumer.instanceKey = instanceKey;\n consumer.registry = registry;\n consumer.boundNonce = consumer.rebindNonce;\n consumer.container = instance;\n consumer.trackedBloc = buildTrackedProxy(\n instance,\n consumer.tracked,\n onDepHandle,\n );\n // A new identity per instance, so uSES re-subscribes.\n consumer.subscribe = (onStoreChange) => subscribe(consumer, onStoreChange);\n}\n\nfunction subscribe(consumer: Consumer, onStoreChange: () => void): () => void {\n consumer.notify = onStoreChange;\n const container = consumer.container;\n const subscription = container.subscribePaths(consumer.onChange);\n subscription.setInterest(consumer.interest);\n consumer.subscription = subscription;\n consumer.subscribedTo = container;\n container.registerConsumerPaths(consumer.id, consumer.paths);\n // An emit raised during render is still pending in the channel, so its\n // next flush delivers it to this subscriber.\n return consumer.unsubscribe;\n}\n\nfunction unsubscribe(consumer: Consumer): void {\n consumer.notify = noop;\n consumer.subscription?.unsubscribe();\n consumer.subscription = null;\n consumer.subscribedTo?.unregisterConsumer(consumer.id);\n consumer.subscribedTo = null;\n}\n\nfunction onChange(consumer: Consumer): void {\n if (consumer.isSelectMode) {\n const select = consumer.select;\n const container = consumer.subscribedTo;\n if (select && container !== null) {\n const next = select(container.state, container);\n const prev = consumer.selection;\n if (prev !== null && shallowArrayEqual(prev, next)) return;\n consumer.selection = next;\n }\n }\n consumer.bump();\n}\n\nfunction claim(consumer: Consumer): void {\n const { BlocClass, instanceKey, registry } = consumer;\n if (\n consumer.ownedClass === BlocClass &&\n consumer.ownedKey === instanceKey &&\n consumer.ownedRegistry === registry\n ) {\n return;\n }\n release(consumer);\n const live = registry.acquire(BlocClass, instanceKey, {\n canCreate: true,\n countRef: true,\n refId: consumer.primaryRefId,\n args: resolveEffectiveArgs(\n consumer,\n ) as ExtractArgs<StateContainerConstructor>,\n });\n consumer.ownedClass = BlocClass;\n consumer.ownedKey = instanceKey;\n consumer.ownedRegistry = registry;\n consumer.ownedBloc = live;\n consumer.onMount?.(live);\n // The render captured a stale instance; re-render against the live one.\n if (live !== (consumer.container as unknown)) {\n consumer.rebindNonce++;\n consumer.bump();\n }\n}\n\nfunction release(consumer: Consumer): void {\n const { ownedClass, ownedRegistry } = consumer;\n if (ownedClass === null || ownedRegistry === null) return;\n // Before release, so the bloc is still alive in the callback.\n consumer.onUnmount?.(consumer.ownedBloc);\n consumer.ownedClass = null;\n consumer.ownedRegistry = null;\n ownedRegistry.release(\n ownedClass,\n consumer.ownedKey,\n false,\n consumer.primaryRefId,\n );\n}\n\nfunction commit(consumer: Consumer): void {\n claim(consumer);\n // Getters called after commit read live state, not this render's proxy.\n consumer.tracked.current = null;\n const disarm = consumer.disarm;\n if (disarm !== null) {\n consumer.disarm = null;\n disarm();\n }\n const deps = consumer.deps;\n deps.disarm();\n if (consumer.isSelectMode) {\n // A later switch back to auto-track must reconcile in full.\n deps.invalidate();\n return;\n }\n const primary = consumer.container;\n // A change between render and commit was diffed against the previous\n // interest, which may lack paths only this render read.\n if (primary.state !== consumer.renderedState) consumer.bump();\n consumer.paths = deps.settle();\n if (deps.isUnchanged()) return;\n\n primary.registerConsumerPaths(consumer.id, consumer.paths);\n setInterest(consumer, consumer.paths);\n deps.reconcile(consumer.registry);\n}\n\nconst setInterest = (consumer: Consumer, interest: PathSet): void => {\n consumer.interest = interest;\n consumer.subscription?.setInterest(interest);\n};\n\n// Own args win over provider args. Shared by render and the ownership effect,\n// which recreates a disposed instance and must not drop its args.\nconst resolveEffectiveArgs = (consumer: Consumer): unknown =>\n consumer.ownArgs !== undefined ? consumer.ownArgs : consumer.providerArgs;\n\nconst shallowArrayEqual = (a: unknown[], b: unknown[]): boolean => {\n if (a === b) return true;\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!Object.is(a[i], b[i])) return false;\n }\n return true;\n};\n","import { useEffect, useId, useRef } from 'react';\nimport type { StateContainer } from '@blac/core';\nimport { APPLY_DEPS, REMOVE_DEPS_OWNER } from '@blac/core/internal';\n\n/**\n * Feed live values from the component into a bloc's `deps` lane.\n *\n * Each calling component is one owner: its slice is shallow-merged into the\n * bloc's merged `deps` view and withdrawn on unmount. The bloc observes\n * changes through `onDepsChanged`.\n *\n * ```tsx\n * const [state, bloc] = useBloc(CanvasCubit);\n * useBlocDeps(bloc, { canvas, onTick });\n * ```\n *\n * Use this for values a bloc must *read* but should not own — DOM handles,\n * callbacks, values from other hooks. Pass identity-stable values (`useMemo` /\n * `useCallback`) as you would to any hook; a new function identity each render\n * counts as a change.\n */\nexport function useBlocDeps<D extends object>(\n bloc: StateContainer<any, any, D>,\n slice: Partial<D>,\n): void {\n const ownerId = useId();\n const latest = useRef(slice);\n latest.current = slice;\n\n // `APPLY_DEPS` shallow-compares and no-ops on an unchanged slice, so applying\n // on every commit is cheaper than asking callers to maintain a dep array.\n useEffect(() => {\n bloc[APPLY_DEPS](ownerId, latest.current);\n });\n\n useEffect(() => {\n return () => {\n bloc[REMOVE_DEPS_OWNER](ownerId);\n };\n }, [bloc, ownerId]);\n}\n"],"mappings":";;;;;;;AAkBA,MAAM,sBAAsB,8BAA+B,IAAI,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;AA2CpE,SAAgB,aAAkD,EAChE,MACA,MACA,YACqC;CACrC,MAAM,YAAY,WAAW,mBAAmB;CAShD,MAAM,YAAY,cAAc;EAC9B,MAAM,OAAO,IAAI,IAAI,SAAS;EAC9B,KAAK,IAAI,MAAM,IAAI;EACnB,OAAO;CAET,GAAG;EAAC;EAAW;EATC,KAAK,UAAU,IASJ;CAAC,CAAC;CAE7B,OACE,oBAAC,oBAAoB,UAArB;EAA8B,OAAO;EAClC;CAC2B,CAAA;AAElC;;;;;;;;AASA,SAAgB,gBACd,WAC4B;CAE5B,OADY,WAAW,mBACd,EAAE,IAAI,SAAS;AAC1B;;;AC3FA,SAAS,qBAAqB,OAA4C;CACxE,OACE,OAAQ,MAA6B,wBAAwB;AAEjE;AAOA,MAAM,iCAAiB,IAAI,QAA8C;AAEzE,SAAS,eAAe,OAA6C;CACnE,MAAM,SAAS,eAAe,IAAI,KAAK;CACvC,IAAI,WAAW,KAAA,GAAW,OAAO;CACjC,MAAM,0BAAU,IAAI,IAA6B;CACjD,IAAI,IAAmB;CACvB,OAAO,MAAM,QAAQ,MAAM,OAAO,WAAW;EAC3C,KAAK,MAAM,OAAO,QAAQ,QAAQ,CAAC,GAAG;GACpC,MAAM,OAAO,OAAO,yBAAyB,GAAG,GAAG;GAGnD,IAAI,MAAM,OAAO,CAAC,QAAQ,IAAI,GAAG,GAAG,QAAQ,IAAI,KAAK,KAAK,GAAG;EAC/D;EACA,IAAI,OAAO,eAAe,CAAC;CAC7B;CACA,eAAe,IAAI,OAAO,OAAO;CACjC,OAAO;AACT;AAIA,IAAM,cAAN,MAAkD;CAQhD,YACE,SACA,aACA,iBACA,aACA;EAJiB,KAAA,UAAA;EACA,KAAA,cAAA;EACA,KAAA,kBAAA;EACA,KAAA,cAAA;sBANH,KAAA;CAOb;CAEH,IAAI,QAAgB,KAA+B;EACjD,MAAM,SAAS,KAAK,QAAQ,IAAI,GAAG;EACnC,IAAI,WAAW,KAAA,GAAW;GACxB,MAAM,UAAU,KAAK,gBAAgB;GAGrC,OAAO,KAAK,cACV,WAAW,QAAQ,CAAC,KAAK,cACrB,OAAO,KAAK,MAAM,IACjB,OAA8B,oBAC7B,SACA,QACA,KAAK,WACP,CACN;EACF;EAKA,MAAM,QAAQ,QAAQ,IAAI,QAAQ,KAAK,MAAM;EAC7C,IAAI,OAAO,UAAU,YAAY;GAC/B,MAAM,KAAK;GACX,KAAK,iCAAiB,IAAI,IAAI;GAC9B,IAAI,SAAS,KAAK,aAAa,IAAI,GAAG;GACtC,IAAI,WAAW,KAAA,KAAa,OAAO,OAAO,IAAI;IAC5C,SAAS;KAAE;KAAI,OAAO,GAAG,KAAK,MAAM;IAAE;IACtC,KAAK,aAAa,IAAI,KAAK,MAAM;GACnC;GACA,OAAO,KAAK,cAAc,OAAO,KAAK;EACxC;EACA,OAAO,KAAK,cAAc,KAAK;CACjC;CAEA,IAAI,QAAgB,KAAsB,OAAyB;EACjE,OAAO,QAAQ,IAAI,QAAQ,KAAK,OAAO,MAAM;CAC/C;CAEA,cAAsB,OAAyB;EAC7C,IACE,KAAK,gBAAgB,KAAA,MACpB,OAAO,UAAU,cAAc,OAAO,UAAU,aACjD,UAAU,QACT,MAAkC,eAAe,KAAA,GAElD,OAAO,KAAK,YAAY,KAAe;EAEzC,OAAO;CACT;AACF;;;;;;;;;;;;;;;;;AAkBA,SAAgB,kBACd,UACA,iBACA,aACG;CAIH,MAAM,UAAU,IAAI,YAClB,eAAe,OAAO,eAAe,QAAQ,CAAW,GACxD,qBAAqB,QAAQ,GAC7B,iBACA,WACF;CACA,OAAO,IAAI,MAAM,UAAU,OAA0B;AACvD;;;AC3HA,MAAMA,aAAoB,aAAa;;;;;;;;;;AA4BvC,IAAa,aAAb,MAAwB;CActB,YACE,YACA,UACA;EAFiB,KAAA,aAAA;EACA,KAAA,WAAA;iBAbsB;sBACP,CAAC;sBACHA;iBACwB;cACL;oBAC9B;qBACwB;0BACTA;qBACwB;iBAChB;CAKzC;CAEH,IAAY,QAAgB;EAC1B,OAAO,WAAW,KAAK,WAAW;CACpC;CAEA,MAAM,SAAyB,OAAsB;EACnD,KAAK,SAAS,MAAM;EACpB,IAAI,KAAK,YAAY,MAAM,KAAK,QAAQ,SAAS;EACjD,KAAK,UAAU;EACf,KAAK,eAAe,CAAC,KAAK;CAC5B;CAEA,OACE,KACA,OACA,MACA,KACA,MACA,QACM;EACN,CAAC,KAAK,YAAY,CAAC,GAAG,KAAK,MAAM;EAEjC,IAAI,QAAQ,KAAK,SAAS;GACxB,KAAK,aAAa,KAAK,KAAK;GAC5B;EACF;EACA,MAAM,UAAW,KAAK,4BAAY,IAAI,IAAI;EAC1C,MAAM,WAAW,QAAQ,IAAI,GAAG;EAChC,IAAI,aAAa,KAAA,GACf,SAAS,MAAM,KAAK,KAAK;OAEzB,QAAQ,IAAI,KAAK;GAAE,OAAO,CAAC,KAAK;GAAG;GAAO;GAAM;GAAK;EAAK,CAAC;CAE/D;;;;;CAMA,SAAkB;EAChB,KAAK,eAAe,WAAW,KAAK,YAAY;EAChD,IAAI,KAAK,YAAY,MACnB,KAAK,MAAM,SAAS,KAAK,QAAQ,OAAO,GACtC,MAAM,QAAQ,WAAW,MAAM,KAAK;EAGxC,OAAO,KAAK;CACd;;CAGA,SAAe;EACb,MAAM,UAAU,KAAK;EACrB,IAAI,YAAY,MAAM;EACtB,KAAK,MAAM,UAAU,SAAS,OAAO;EACrC,QAAQ,SAAS;CACnB;;CAGA,aAAmB;EACjB,KAAK,aAAa;CACpB;CAEA,cAAuB;EACrB,IACE,CAAC,KAAK,cACN,KAAK,gBAAgB,KAAK,WAC1B,CAAC,cAAc,KAAK,kBAAkB,KAAK,YAAY,GAEvD,OAAO;EAET,MAAM,OAAO,KAAK;EAClB,MAAM,UAAU,KAAK;EACrB,KAAK,MAAM,QAAQ,QAAQ,SAAS,QAAQ,IAAI,OAAO;EACvD,IAAI,SAAS,QAAQ,YAAY,MAAM,OAAO;EAC9C,KAAK,MAAM,CAAC,WAAW,UAAU,SAAS;GACxC,MAAM,OAAO,KAAK,IAAI,SAAS;GAC/B,IAAI,SAAS,KAAA,KAAa,CAAC,UAAU,MAAM,KAAK,GAAG,OAAO;EAC5D;EACA,OAAO;CACT;CAEA,UAAU,UAAwC;EAChD,MAAM,EAAE,eAAe;EACvB,MAAM,UAAU,KAAK;EACrB,KAAK,aAAa;EAClB,KAAK,cAAc,KAAK;EACxB,KAAK,mBAAmB,KAAK;EAC7B,KAAK,cACH,YAAY,QAAQ,QAAQ,SAAS,IAAI,OAAO,IAAI,IAAI,OAAO;EAEjE,MAAM,OAAO,KAAK;EAClB,IAAI,SAAS;QACN,MAAM,CAAC,KAAK,QAAQ,MACvB,IAAI,SAAS,IAAI,GAAG,MAAM,MAAM;IAC9B,KAAK,KAAK,KAAK,GAAG;IAClB,KAAK,OAAO,GAAG;GACjB;;EAGJ,IAAI,YAAY,MAAM;EAEtB,KAAK,MAAM,CAAC,KAAK,UAAU,SAAS;GAGlC,IAAI,IAAI,MAAM,UAAU;IACtB,KAAK,SAAS;IACd;GACF;GACA,IAAI,sBAAsB,YAAY,MAAM,KAAK;GACjD,MAAM,WAAW,MAAM,IAAI,GAAG;GAC9B,IAAI,UAAU;IACZ,SAAS,aAAa,YAAY,MAAM,KAAK;IAC7C;GACF;GACA,MAAM,QAAQ,KAAK;GAQnB,IAPa,SAAS,QAAQ,MAAM,MAAM,MAAM,KAAK;IACnD,WAAW;IACX,UAAU;IACV;IACA,MAAM,MAAM;GACd,CAEO,MAAM,KAAK;IAChB,SAAS,QAAQ,MAAM,MAAM,MAAM,KAAK,OAAO,KAAK;IACpD,KAAK,SAAS;IACd;GACF;GACA,MAAM,eAAe,IAAI,eAAe,KAAK,QAAQ;GACrD,aAAa,YAAY,MAAM,KAAK;GACpC,CAAC,KAAK,yBAAS,IAAI,IAAI,GAAG,IAAI,KAAK;IACjC;IACA;IACA,MAAM,MAAM;IACZ,KAAK,MAAM;GACb,CAAC;EACH;CACF;CAEA,UAAgB;EACd,MAAM,OAAO,KAAK;EAClB,IAAI,SAAS,MAAM;GACjB,KAAK,MAAM,CAAC,KAAK,QAAQ,MAAM,KAAK,KAAK,KAAK,GAAG;GACjD,KAAK,MAAM;EACb;EACA,KAAK,aAAa;CACpB;CAEA,KAAa,KAAqB,KAAmB;EACnD,IAAI,aAAa,YAAY;EAC7B,IAAI,mBAAmB,KAAK,UAAU;EACtC,IAAI,SAAS,QAAQ,IAAI,MAAM,IAAI,KAAK,OAAO,KAAK,KAAK;CAC3D;AACF;AAEA,SAAS,UAAU,GAAa,GAAsB;CACpD,OACE,cAAc,EAAE,OAAO,EAAE,KAAK,KAC9B,EAAE,QAAQ,EAAE,OACZ,OAAO,GAAG,EAAE,MAAM,EAAE,IAAI;AAE5B;AAEA,SAAS,WAAW,OAA2B;CAC7C,IAAI,MAAM,WAAW,GAAG,OAAO,MAAM;CACrC,MAAM,sBAAM,IAAI,IAAY;CAC5B,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI,SAAS,WAAW,OAAO;EAC/B,KAAK,MAAM,MAAM,MAAM,IAAI,IAAI,EAAE;CACnC;CACA,OAAO;AACT;;;;;;;;;;AAyBA,SAAgB,eACd,QACA,UACA,SACA,SACA,aACe;CACf,MAAM,QAAQ,OAAO;CAGrB,MAAM,yBAAS,IAAI,QAGjB;CACF,MAAM,aAAa,IAAI,WAAW;CAElC,MAAM,WAAW,YAAmC;EAClD,MAAM,OAAO,SAAS,QAAQ,MAAM;EACpC,MAAM,MAAM,SAAS,WAAW,MAAM,MAAM,KAAA,GAAW,IAAI;EAO3D,OAAO;GAAE,KANG,SAAS,QAAQ,MAAM,MAAM,KAAK;IAC5C,WAAW;IACX,UAAU;IACV;IACA,gBAAgB,QAAQ,WAAW;GACrC,CACW;GAAG;GAAK;EAAK;CAC1B;CAEA,MAAM,UAAU;EACd,YAAY,YAAmC,QAAQ,OAAO,EAAE;EAChE,QAAQ,YAAmC;GACzC,MAAM,EAAE,KAAK,KAAK,SAAS,QAAQ,OAAO;GAC1C,IAAI,QAAQ,WAAW,MAAM,OAAO,CAAC,IAAI,OAAO,GAAG;GAEnD,MAAM,SAAS,YAAY,IAAI,OAAO,IAAI,UAAU,UAAU;GAC9D,IAAI,QAAQ,OAAO,IAAI,GAAG;GAC1B,IAAI,UAAU,KAAA,GAAW;IACvB,MAAM,MAAM,EAAE,SAAS,OAAO,MAAiB;IAC/C,QAAQ;KAAE;KAAK,OAAO,kBAAkB,KAAK,KAAK,WAAW;IAAE;IAC/D,OAAO,IAAI,KAAK,KAAK;GACvB,OACE,MAAM,IAAI,UAAU,OAAO;GAE7B,QAAQ,OAAO,KAAK,OAAO,OAAO,MAAM,MAAM,KAAK,MAAM,OAAO,MAAM;GACtE,OAAO,CAAC,OAAO,OAAO,MAAM,KAAK;EACnC;CACF;CAEA,OAAO,eAAe,SAAS,WAAW;EACxC,OAAO;EACP,YAAY;EACZ,UAAU;EACV,cAAc;CAChB,CAAC;CAED,OAAO;AACT;;;AClSA,IAAI,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDrB,SAAgB,QAGd,WACA,SACmC;CACnC,MAAM,WAAW,YAAY;CAE7B,MAAM,cAAc,OAAwB,IAAI;CAChD,MAAM,WAAY,YAAY,YAAY,eAAe;CAEzD,SAAS,SAAS,SAAS;CAC3B,SAAS,UAAU,SAAS;CAC5B,SAAS,YAAY,SAAS;CAE9B,SAAS,UAAW,SAAmD;CACvE,SAAS,eAAe,gBAAgB,SAAS;CACjD,MAAM,gBAAgB,qBAAqB,QAAQ;CACnD,MAAM,YAAY,SAAS,WAAW,WAAW,KAAA,GAAW,aAAa;CACzE,MAAM,cAAc,gBAAgB,SAAS,IACzC,GAAG,UAAU,GAAG,SAAS,OACzB;CASJ,IACE,SAAS,cAAc,aACvB,SAAS,gBAAgB,eACzB,SAAS,aAAa,YACtB,SAAS,eAAe,SAAS,aAEjC,KAAK,UAAU,WAAW,aAAa,UAAU,aAAa;CAGhE,qBACE,SAAS,WACT,SAAS,aACT,SAAS,WACX;CAEA,MAAM,YAAY,SAAS;CAC3B,MAAM,WAAW,UAAU;CAC3B,SAAS,gBAAgB;CACzB,MAAM,SAAS,SAAS;CACxB,SAAS,eAAe,WAAW,KAAA;CACnC,IAAI;CACJ,IAAI,WAAW,KAAA,GAAW;EACxB,QAAQ;EAER,SAAS,YAAY,OAAO,UAAU,SAA6B;EACnE,YAAY,UAAU,SAAS;CACjC,OAAO;EACL,MAAM,UAAU,YACd,UACA,UAAU,UACT,SAAS,eAAe,IAAI,WAAW,CAC1C;EACA,QAAQ,QAAQ;EAChB,SAAS,QAAQ,UAAU,QAAQ;EACnC,SAAS,QAAQ,QAAQ;EAGzB,SAAS,SAAS,QAAQ;EAG1B,SAAS,KAAK,MAAM,WAAW,QAAQ,KAAK;CAC9C;CAIA,gBAAgB,SAAS,MAAM;CAE/B,gBAAgB,SAAS,OAAO,OAAO;CAEvC,OAAO,CAAC,OAAO,SAAS,WAAW;AACrC;AAEA,MAAM,aAAmB,CAAC;AAG1B,MAAM,WAAoB,aAAa;AACvC,MAAM,UAAc,CAAC;AA2DrB,SAAS,iBAA2B;CAClC,MAAM,KAAK,WAAW;CACtB,MAAM,WAAqB;EACzB;EACA,cAAc,WAAW;EACzB,WAAW;EACX,aAAa;EACb,UAAU;EACV,WAAW;EACX,aAAa;EACb,WAAW;EACX,YAAY;EACZ,aAAa;EACb,eAAe,KAAA;EACf,SAAS;EACT,QAAQ;EACR,YAAY;GACV,SAAS;GACT,SAAS,OAAO;EAClB;EACA,mBAAmB,SAAS;EAC5B,gBAAgB,SAAS,QAAQ;EACjC,mBAAmB,YAAY,QAAQ;EACvC,cAAc,OAAO,QAAQ;EAC7B,aAAa;EACb,UAAU;EACV,cAAc;EACd,cAAc;EACd,OAAO;EACP,cAAc;EACd,WAAW;EACX,MAAM;EACN,QAAQ,KAAA;EACR,SAAS,KAAA;EACT,WAAW,KAAA;EACX,SAAS,KAAA;EACT,cAAc,KAAA;EACd,SAAS,EAAE,SAAS,KAAK;EACzB,QAAQ;EACR,YAAY;EACZ,YAAY;EACZ,UAAU;EACV,eAAe;EACf,WAAW;CACb;CACA,MAAM,iBAAuB;EAC3B,QAAQ,QAAQ;EAChB,SAAS,KAAK,QAAQ;CACxB;CACA,SAAS,OAAO,IAAI,WAAW,IAAI,SAAS,IAAI;CAChD,OAAO;AACT;AAEA,SAAS,KACP,UACA,WACA,aACA,UACA,MACM;CAGN,MAAM,WAAW,SAAS,QAAQ,WAAW,aAAa;EACxD,WAAW;EACX,UAAU;EACJ;EACN,gBAAgB;CAClB,CAAC;CAID,IAAI;CACJ,MAAM,eAAe,WAA4B;EAC/C,gCAAgB,IAAI,IAAI;EACxB,MAAM,SAAS,YAAY,IAAI,MAAM;EACrC,IAAI,WAAW,KAAA,GAAW,OAAO;EACjC,MAAM,UAAU,eACd,QACA,UACA,SAAS,MACT,SAAS,SACT,WACF;EACA,YAAY,IAAI,QAAQ,OAAO;EAC/B,OAAO;CACT;CAEA,SAAS,YAAY;CACrB,SAAS,cAAc;CACvB,SAAS,WAAW;CACpB,SAAS,aAAa,SAAS;CAC/B,SAAS,YAAY;CACrB,SAAS,cAAc,kBACrB,UACA,SAAS,SACT,WACF;CAEA,SAAS,aAAa,kBAAkB,UAAU,UAAU,aAAa;AAC3E;AAEA,SAAS,UAAU,UAAoB,eAAuC;CAC5E,SAAS,SAAS;CAClB,MAAM,YAAY,SAAS;CAC3B,MAAM,eAAe,UAAU,eAAe,SAAS,QAAQ;CAC/D,aAAa,YAAY,SAAS,QAAQ;CAC1C,SAAS,eAAe;CACxB,SAAS,eAAe;CACxB,UAAU,sBAAsB,SAAS,IAAI,SAAS,KAAK;CAG3D,OAAO,SAAS;AAClB;AAEA,SAAS,YAAY,UAA0B;CAC7C,SAAS,SAAS;CAClB,SAAS,cAAc,YAAY;CACnC,SAAS,eAAe;CACxB,SAAS,cAAc,mBAAmB,SAAS,EAAE;CACrD,SAAS,eAAe;AAC1B;AAEA,SAAS,SAAS,UAA0B;CAC1C,IAAI,SAAS,cAAc;EACzB,MAAM,SAAS,SAAS;EACxB,MAAM,YAAY,SAAS;EAC3B,IAAI,UAAU,cAAc,MAAM;GAChC,MAAM,OAAO,OAAO,UAAU,OAAO,SAAS;GAC9C,MAAM,OAAO,SAAS;GACtB,IAAI,SAAS,QAAQ,kBAAkB,MAAM,IAAI,GAAG;GACpD,SAAS,YAAY;EACvB;CACF;CACA,SAAS,KAAK;AAChB;AAEA,SAAS,MAAM,UAA0B;CACvC,MAAM,EAAE,WAAW,aAAa,aAAa;CAC7C,IACE,SAAS,eAAe,aACxB,SAAS,aAAa,eACtB,SAAS,kBAAkB,UAE3B;CAEF,QAAQ,QAAQ;CAChB,MAAM,OAAO,SAAS,QAAQ,WAAW,aAAa;EACpD,WAAW;EACX,UAAU;EACV,OAAO,SAAS;EAChB,MAAM,qBACJ,QACF;CACF,CAAC;CACD,SAAS,aAAa;CACtB,SAAS,WAAW;CACpB,SAAS,gBAAgB;CACzB,SAAS,YAAY;CACrB,SAAS,UAAU,IAAI;CAEvB,IAAI,SAAU,SAAS,WAAuB;EAC5C,SAAS;EACT,SAAS,KAAK;CAChB;AACF;AAEA,SAAS,QAAQ,UAA0B;CACzC,MAAM,EAAE,YAAY,kBAAkB;CACtC,IAAI,eAAe,QAAQ,kBAAkB,MAAM;CAEnD,SAAS,YAAY,SAAS,SAAS;CACvC,SAAS,aAAa;CACtB,SAAS,gBAAgB;CACzB,cAAc,QACZ,YACA,SAAS,UACT,OACA,SAAS,YACX;AACF;AAEA,SAAS,OAAO,UAA0B;CACxC,MAAM,QAAQ;CAEd,SAAS,QAAQ,UAAU;CAC3B,MAAM,SAAS,SAAS;CACxB,IAAI,WAAW,MAAM;EACnB,SAAS,SAAS;EAClB,OAAO;CACT;CACA,MAAM,OAAO,SAAS;CACtB,KAAK,OAAO;CACZ,IAAI,SAAS,cAAc;EAEzB,KAAK,WAAW;EAChB;CACF;CACA,MAAM,UAAU,SAAS;CAGzB,IAAI,QAAQ,UAAU,SAAS,eAAe,SAAS,KAAK;CAC5D,SAAS,QAAQ,KAAK,OAAO;CAC7B,IAAI,KAAK,YAAY,GAAG;CAExB,QAAQ,sBAAsB,SAAS,IAAI,SAAS,KAAK;CACzD,YAAY,UAAU,SAAS,KAAK;CACpC,KAAK,UAAU,SAAS,QAAQ;AAClC;AAEA,MAAM,eAAe,UAAoB,aAA4B;CACnE,SAAS,WAAW;CACpB,SAAS,cAAc,YAAY,QAAQ;AAC7C;AAIA,MAAM,wBAAwB,aAC5B,SAAS,YAAY,KAAA,IAAY,SAAS,UAAU,SAAS;AAE/D,MAAM,qBAAqB,GAAc,MAA0B;CACjE,IAAI,MAAM,GAAG,OAAO;CACpB,IAAI,EAAE,WAAW,EAAE,QAAQ,OAAO;CAClC,KAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,KAC5B,IAAI,CAAC,OAAO,GAAG,EAAE,IAAI,EAAE,EAAE,GAAG,OAAO;CAErC,OAAO;AACT;;;;;;;;;;;;;;;;;;;;ACvaA,SAAgB,YACd,MACA,OACM;CACN,MAAM,UAAU,MAAM;CACtB,MAAM,SAAS,OAAO,KAAK;CAC3B,OAAO,UAAU;CAIjB,gBAAgB;EACd,KAAK,YAAY,SAAS,OAAO,OAAO;CAC1C,CAAC;CAED,gBAAgB;EACd,aAAa;GACX,KAAK,mBAAmB,OAAO;EACjC;CACF,GAAG,CAAC,MAAM,OAAO,CAAC;AACpB"}
|
package/dist/testing.cjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_RegistryProvider = require("./RegistryProvider-
|
|
2
|
+
const require_RegistryProvider = require("./RegistryProvider-B9-mQQRg.cjs");
|
|
3
|
+
let react = require("react");
|
|
3
4
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
4
5
|
let _blac_core_testing = require("@blac/core/testing");
|
|
5
6
|
let _testing_library_react = require("@testing-library/react");
|
|
@@ -28,6 +29,36 @@ function renderWithRegistry(ui, setup) {
|
|
|
28
29
|
return registry;
|
|
29
30
|
}));
|
|
30
31
|
}
|
|
32
|
+
function seedRegistry(seeds) {
|
|
33
|
+
return (0, _blac_core_testing.withTestRegistry)((registry) => {
|
|
34
|
+
for (const [BlocClass, options] of seeds) (0, _blac_core_testing.registerOverride)(BlocClass, (0, _blac_core_testing.createCubitStub)(BlocClass, options), options?.args);
|
|
35
|
+
return registry;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Renders `ui` in a fresh registry holding a stub of each seeded bloc.
|
|
40
|
+
* Each seed is `[BlocClass, { args, state, methods, deps }?]`.
|
|
41
|
+
*/
|
|
42
|
+
function renderWithBlocs(ui, seeds) {
|
|
43
|
+
return renderInRegistry(ui, seedRegistry(seeds));
|
|
44
|
+
}
|
|
45
|
+
function SeededRegistry({ seeds, children }) {
|
|
46
|
+
const [registry] = (0, react.useState)(() => seedRegistry(seeds));
|
|
47
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_RegistryProvider.RegistryProvider, {
|
|
48
|
+
registry,
|
|
49
|
+
children
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Storybook decorator: renders each story in its own registry holding a stub
|
|
54
|
+
* of each seeded bloc. Seeds are `[BlocClass, { args, state, methods, deps }?]`.
|
|
55
|
+
*/
|
|
56
|
+
function withBlocs(seeds) {
|
|
57
|
+
return (Story) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SeededRegistry, {
|
|
58
|
+
seeds,
|
|
59
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Story, {})
|
|
60
|
+
});
|
|
61
|
+
}
|
|
31
62
|
function renderInRegistry(ui, registry) {
|
|
32
63
|
return (0, _testing_library_react.render)(ui, { wrapper: ({ children }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_RegistryProvider.RegistryProvider, {
|
|
33
64
|
registry,
|
|
@@ -36,6 +67,8 @@ function renderInRegistry(ui, registry) {
|
|
|
36
67
|
}
|
|
37
68
|
//#endregion
|
|
38
69
|
exports.renderWithBloc = renderWithBloc;
|
|
70
|
+
exports.renderWithBlocs = renderWithBlocs;
|
|
39
71
|
exports.renderWithRegistry = renderWithRegistry;
|
|
72
|
+
exports.withBlocs = withBlocs;
|
|
40
73
|
|
|
41
74
|
//# sourceMappingURL=testing.cjs.map
|
package/dist/testing.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.cjs","names":["RegistryProvider"],"sources":["../src/testing.tsx"],"sourcesContent":["import type
|
|
1
|
+
{"version":3,"file":"testing.cjs","names":["RegistryProvider"],"sources":["../src/testing.tsx"],"sourcesContent":["import { useState, type ComponentType, type ReactElement } from 'react';\nimport type { RenderResult } from '@testing-library/react';\nimport type {\n ExtractArgs,\n StateContainerConstructor,\n StateContainerRegistry,\n} from '@blac/core';\nimport {\n registerOverride,\n createCubitStub,\n withTestRegistry,\n type CubitStubOptions,\n} from '@blac/core/testing';\nimport { render } from '@testing-library/react';\nimport { RegistryProvider } from './RegistryProvider';\n\ninterface RenderWithBlocOptions<\n T extends StateContainerConstructor,\n> extends CubitStubOptions<T> {\n bloc: T;\n /** Args used to resolve which instance the stub is registered under. */\n args?: ExtractArgs<T>;\n}\n\nexport function renderWithBloc<T extends StateContainerConstructor>(\n ui: ReactElement,\n options: RenderWithBlocOptions<T>,\n): RenderResult & { bloc: InstanceType<T> } {\n const { bloc: BlocClass, args, ...stubOptions } = options;\n\n const { registry, instance } = withTestRegistry((registry) => {\n const instance = createCubitStub(BlocClass, {\n ...stubOptions,\n args,\n } as any);\n registerOverride(BlocClass, instance, args);\n return { registry, instance };\n });\n\n return { ...renderInRegistry(ui, registry), bloc: instance };\n}\n\nexport function renderWithRegistry(\n ui: ReactElement,\n setup: (registry: StateContainerRegistry) => void,\n): RenderResult {\n const registry = withTestRegistry((registry) => {\n setup(registry);\n return registry;\n });\n return renderInRegistry(ui, registry);\n}\n\ntype BlocSeeds<T extends readonly StateContainerConstructor[]> = {\n [K in keyof T]: [bloc: T[K], options?: CubitStubOptions<T[K]>];\n};\n\nfunction seedRegistry<T extends readonly StateContainerConstructor[]>(\n seeds: BlocSeeds<T>,\n): StateContainerRegistry {\n return withTestRegistry((registry) => {\n for (const [BlocClass, options] of seeds as ReadonlyArray<\n [StateContainerConstructor, CubitStubOptions<any>?]\n >) {\n registerOverride(\n BlocClass,\n createCubitStub(BlocClass, options),\n options?.args,\n );\n }\n return registry;\n });\n}\n\n/**\n * Renders `ui` in a fresh registry holding a stub of each seeded bloc.\n * Each seed is `[BlocClass, { args, state, methods, deps }?]`.\n */\nexport function renderWithBlocs<\n const T extends readonly StateContainerConstructor[],\n>(ui: ReactElement, seeds: BlocSeeds<T>): RenderResult {\n return renderInRegistry(ui, seedRegistry(seeds));\n}\n\nfunction SeededRegistry({\n seeds,\n children,\n}: {\n seeds: BlocSeeds<readonly StateContainerConstructor[]>;\n children: ReactElement;\n}): ReactElement {\n const [registry] = useState(() => seedRegistry(seeds));\n return <RegistryProvider registry={registry}>{children}</RegistryProvider>;\n}\n\n/**\n * Storybook decorator: renders each story in its own registry holding a stub\n * of each seeded bloc. Seeds are `[BlocClass, { args, state, methods, deps }?]`.\n */\nexport function withBlocs<const T extends readonly StateContainerConstructor[]>(\n seeds: BlocSeeds<T>,\n): (Story: ComponentType) => ReactElement {\n return (Story) => (\n <SeededRegistry seeds={seeds}>\n <Story />\n </SeededRegistry>\n );\n}\n\nfunction renderInRegistry(\n ui: ReactElement,\n registry: StateContainerRegistry,\n): RenderResult {\n return render(ui, {\n wrapper: ({ children }) => (\n <RegistryProvider registry={registry}>{children}</RegistryProvider>\n ),\n });\n}\n"],"mappings":";;;;;;;AAwBA,SAAgB,eACd,IACA,SAC0C;CAC1C,MAAM,EAAE,MAAM,WAAW,MAAM,GAAG,gBAAgB;CAElD,MAAM,EAAE,UAAU,cAAA,GAAA,mBAAA,mBAA+B,aAAa;EAC5D,MAAM,YAAA,GAAA,mBAAA,iBAA2B,WAAW;GAC1C,GAAG;GACH;EACF,CAAQ;EACR,CAAA,GAAA,mBAAA,kBAAiB,WAAW,UAAU,IAAI;EAC1C,OAAO;GAAE;GAAU;EAAS;CAC9B,CAAC;CAED,OAAO;EAAE,GAAG,iBAAiB,IAAI,QAAQ;EAAG,MAAM;CAAS;AAC7D;AAEA,SAAgB,mBACd,IACA,OACc;CAKd,OAAO,iBAAiB,KAAA,GAAA,mBAAA,mBAJW,aAAa;EAC9C,MAAM,QAAQ;EACd,OAAO;CACT,CACmC,CAAC;AACtC;AAMA,SAAS,aACP,OACwB;CACxB,QAAA,GAAA,mBAAA,mBAAyB,aAAa;EACpC,KAAK,MAAM,CAAC,WAAW,YAAY,OAGjC,CAAA,GAAA,mBAAA,kBACE,YAAA,GAAA,mBAAA,iBACgB,WAAW,OAAO,GAClC,SAAS,IACX;EAEF,OAAO;CACT,CAAC;AACH;;;;;AAMA,SAAgB,gBAEd,IAAkB,OAAmC;CACrD,OAAO,iBAAiB,IAAI,aAAa,KAAK,CAAC;AACjD;AAEA,SAAS,eAAe,EACtB,OACA,YAIe;CACf,MAAM,CAAC,aAAA,GAAA,MAAA,gBAA2B,aAAa,KAAK,CAAC;CACrD,OAAO,iBAAA,GAAA,kBAAA,KAACA,yBAAAA,kBAAD;EAA4B;EAAW;CAA2B,CAAA;AAC3E;;;;;AAMA,SAAgB,UACd,OACwC;CACxC,QAAQ,UACN,iBAAA,GAAA,kBAAA,KAAC,gBAAD;EAAuB;YACrB,iBAAA,GAAA,kBAAA,KAAC,OAAD,CAAQ,CAAA;CACM,CAAA;AAEpB;AAEA,SAAS,iBACP,IACA,UACc;CACd,QAAA,GAAA,uBAAA,QAAc,IAAI,EAChB,UAAU,EAAE,eACV,iBAAA,GAAA,kBAAA,KAACA,yBAAAA,kBAAD;EAA4B;EAAW;CAA2B,CAAA,EAEtE,CAAC;AACH"}
|
package/dist/testing.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ComponentType, type ReactElement } from 'react';
|
|
2
2
|
import type { RenderResult } from '@testing-library/react';
|
|
3
3
|
import type { ExtractArgs, StateContainerConstructor, StateContainerRegistry } from '@blac/core';
|
|
4
4
|
import { type CubitStubOptions } from '@blac/core/testing';
|
|
@@ -11,4 +11,17 @@ export declare function renderWithBloc<T extends StateContainerConstructor>(ui:
|
|
|
11
11
|
bloc: InstanceType<T>;
|
|
12
12
|
};
|
|
13
13
|
export declare function renderWithRegistry(ui: ReactElement, setup: (registry: StateContainerRegistry) => void): RenderResult;
|
|
14
|
+
type BlocSeeds<T extends readonly StateContainerConstructor[]> = {
|
|
15
|
+
[K in keyof T]: [bloc: T[K], options?: CubitStubOptions<T[K]>];
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Renders `ui` in a fresh registry holding a stub of each seeded bloc.
|
|
19
|
+
* Each seed is `[BlocClass, { args, state, methods, deps }?]`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function renderWithBlocs<const T extends readonly StateContainerConstructor[]>(ui: ReactElement, seeds: BlocSeeds<T>): RenderResult;
|
|
22
|
+
/**
|
|
23
|
+
* Storybook decorator: renders each story in its own registry holding a stub
|
|
24
|
+
* of each seeded bloc. Seeds are `[BlocClass, { args, state, methods, deps }?]`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function withBlocs<const T extends readonly StateContainerConstructor[]>(seeds: BlocSeeds<T>): (Story: ComponentType) => ReactElement;
|
|
14
27
|
export {};
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ComponentType, type ReactElement } from 'react';
|
|
2
2
|
import type { RenderResult } from '@testing-library/react';
|
|
3
3
|
import type { ExtractArgs, StateContainerConstructor, StateContainerRegistry } from '@blac/core';
|
|
4
4
|
import { type CubitStubOptions } from '@blac/core/testing';
|
|
@@ -11,4 +11,17 @@ export declare function renderWithBloc<T extends StateContainerConstructor>(ui:
|
|
|
11
11
|
bloc: InstanceType<T>;
|
|
12
12
|
};
|
|
13
13
|
export declare function renderWithRegistry(ui: ReactElement, setup: (registry: StateContainerRegistry) => void): RenderResult;
|
|
14
|
+
type BlocSeeds<T extends readonly StateContainerConstructor[]> = {
|
|
15
|
+
[K in keyof T]: [bloc: T[K], options?: CubitStubOptions<T[K]>];
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Renders `ui` in a fresh registry holding a stub of each seeded bloc.
|
|
19
|
+
* Each seed is `[BlocClass, { args, state, methods, deps }?]`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function renderWithBlocs<const T extends readonly StateContainerConstructor[]>(ui: ReactElement, seeds: BlocSeeds<T>): RenderResult;
|
|
22
|
+
/**
|
|
23
|
+
* Storybook decorator: renders each story in its own registry holding a stub
|
|
24
|
+
* of each seeded bloc. Seeds are `[BlocClass, { args, state, methods, deps }?]`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function withBlocs<const T extends readonly StateContainerConstructor[]>(seeds: BlocSeeds<T>): (Story: ComponentType) => ReactElement;
|
|
14
27
|
export {};
|
package/dist/testing.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { t as RegistryProvider } from "./RegistryProvider-
|
|
1
|
+
import { t as RegistryProvider } from "./RegistryProvider-DS4ltjlH.js";
|
|
2
|
+
import { useState } from "react";
|
|
2
3
|
import { jsx } from "react/jsx-runtime";
|
|
3
4
|
import { createCubitStub, registerOverride, withTestRegistry } from "@blac/core/testing";
|
|
4
5
|
import { render } from "@testing-library/react";
|
|
@@ -27,6 +28,36 @@ function renderWithRegistry(ui, setup) {
|
|
|
27
28
|
return registry;
|
|
28
29
|
}));
|
|
29
30
|
}
|
|
31
|
+
function seedRegistry(seeds) {
|
|
32
|
+
return withTestRegistry((registry) => {
|
|
33
|
+
for (const [BlocClass, options] of seeds) registerOverride(BlocClass, createCubitStub(BlocClass, options), options?.args);
|
|
34
|
+
return registry;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Renders `ui` in a fresh registry holding a stub of each seeded bloc.
|
|
39
|
+
* Each seed is `[BlocClass, { args, state, methods, deps }?]`.
|
|
40
|
+
*/
|
|
41
|
+
function renderWithBlocs(ui, seeds) {
|
|
42
|
+
return renderInRegistry(ui, seedRegistry(seeds));
|
|
43
|
+
}
|
|
44
|
+
function SeededRegistry({ seeds, children }) {
|
|
45
|
+
const [registry] = useState(() => seedRegistry(seeds));
|
|
46
|
+
return /* @__PURE__ */ jsx(RegistryProvider, {
|
|
47
|
+
registry,
|
|
48
|
+
children
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Storybook decorator: renders each story in its own registry holding a stub
|
|
53
|
+
* of each seeded bloc. Seeds are `[BlocClass, { args, state, methods, deps }?]`.
|
|
54
|
+
*/
|
|
55
|
+
function withBlocs(seeds) {
|
|
56
|
+
return (Story) => /* @__PURE__ */ jsx(SeededRegistry, {
|
|
57
|
+
seeds,
|
|
58
|
+
children: /* @__PURE__ */ jsx(Story, {})
|
|
59
|
+
});
|
|
60
|
+
}
|
|
30
61
|
function renderInRegistry(ui, registry) {
|
|
31
62
|
return render(ui, { wrapper: ({ children }) => /* @__PURE__ */ jsx(RegistryProvider, {
|
|
32
63
|
registry,
|
|
@@ -34,6 +65,6 @@ function renderInRegistry(ui, registry) {
|
|
|
34
65
|
}) });
|
|
35
66
|
}
|
|
36
67
|
//#endregion
|
|
37
|
-
export { renderWithBloc, renderWithRegistry };
|
|
68
|
+
export { renderWithBloc, renderWithBlocs, renderWithRegistry, withBlocs };
|
|
38
69
|
|
|
39
70
|
//# sourceMappingURL=testing.js.map
|
package/dist/testing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.js","names":[],"sources":["../src/testing.tsx"],"sourcesContent":["import type
|
|
1
|
+
{"version":3,"file":"testing.js","names":[],"sources":["../src/testing.tsx"],"sourcesContent":["import { useState, type ComponentType, type ReactElement } from 'react';\nimport type { RenderResult } from '@testing-library/react';\nimport type {\n ExtractArgs,\n StateContainerConstructor,\n StateContainerRegistry,\n} from '@blac/core';\nimport {\n registerOverride,\n createCubitStub,\n withTestRegistry,\n type CubitStubOptions,\n} from '@blac/core/testing';\nimport { render } from '@testing-library/react';\nimport { RegistryProvider } from './RegistryProvider';\n\ninterface RenderWithBlocOptions<\n T extends StateContainerConstructor,\n> extends CubitStubOptions<T> {\n bloc: T;\n /** Args used to resolve which instance the stub is registered under. */\n args?: ExtractArgs<T>;\n}\n\nexport function renderWithBloc<T extends StateContainerConstructor>(\n ui: ReactElement,\n options: RenderWithBlocOptions<T>,\n): RenderResult & { bloc: InstanceType<T> } {\n const { bloc: BlocClass, args, ...stubOptions } = options;\n\n const { registry, instance } = withTestRegistry((registry) => {\n const instance = createCubitStub(BlocClass, {\n ...stubOptions,\n args,\n } as any);\n registerOverride(BlocClass, instance, args);\n return { registry, instance };\n });\n\n return { ...renderInRegistry(ui, registry), bloc: instance };\n}\n\nexport function renderWithRegistry(\n ui: ReactElement,\n setup: (registry: StateContainerRegistry) => void,\n): RenderResult {\n const registry = withTestRegistry((registry) => {\n setup(registry);\n return registry;\n });\n return renderInRegistry(ui, registry);\n}\n\ntype BlocSeeds<T extends readonly StateContainerConstructor[]> = {\n [K in keyof T]: [bloc: T[K], options?: CubitStubOptions<T[K]>];\n};\n\nfunction seedRegistry<T extends readonly StateContainerConstructor[]>(\n seeds: BlocSeeds<T>,\n): StateContainerRegistry {\n return withTestRegistry((registry) => {\n for (const [BlocClass, options] of seeds as ReadonlyArray<\n [StateContainerConstructor, CubitStubOptions<any>?]\n >) {\n registerOverride(\n BlocClass,\n createCubitStub(BlocClass, options),\n options?.args,\n );\n }\n return registry;\n });\n}\n\n/**\n * Renders `ui` in a fresh registry holding a stub of each seeded bloc.\n * Each seed is `[BlocClass, { args, state, methods, deps }?]`.\n */\nexport function renderWithBlocs<\n const T extends readonly StateContainerConstructor[],\n>(ui: ReactElement, seeds: BlocSeeds<T>): RenderResult {\n return renderInRegistry(ui, seedRegistry(seeds));\n}\n\nfunction SeededRegistry({\n seeds,\n children,\n}: {\n seeds: BlocSeeds<readonly StateContainerConstructor[]>;\n children: ReactElement;\n}): ReactElement {\n const [registry] = useState(() => seedRegistry(seeds));\n return <RegistryProvider registry={registry}>{children}</RegistryProvider>;\n}\n\n/**\n * Storybook decorator: renders each story in its own registry holding a stub\n * of each seeded bloc. Seeds are `[BlocClass, { args, state, methods, deps }?]`.\n */\nexport function withBlocs<const T extends readonly StateContainerConstructor[]>(\n seeds: BlocSeeds<T>,\n): (Story: ComponentType) => ReactElement {\n return (Story) => (\n <SeededRegistry seeds={seeds}>\n <Story />\n </SeededRegistry>\n );\n}\n\nfunction renderInRegistry(\n ui: ReactElement,\n registry: StateContainerRegistry,\n): RenderResult {\n return render(ui, {\n wrapper: ({ children }) => (\n <RegistryProvider registry={registry}>{children}</RegistryProvider>\n ),\n });\n}\n"],"mappings":";;;;;;AAwBA,SAAgB,eACd,IACA,SAC0C;CAC1C,MAAM,EAAE,MAAM,WAAW,MAAM,GAAG,gBAAgB;CAElD,MAAM,EAAE,UAAU,aAAa,kBAAkB,aAAa;EAC5D,MAAM,WAAW,gBAAgB,WAAW;GAC1C,GAAG;GACH;EACF,CAAQ;EACR,iBAAiB,WAAW,UAAU,IAAI;EAC1C,OAAO;GAAE;GAAU;EAAS;CAC9B,CAAC;CAED,OAAO;EAAE,GAAG,iBAAiB,IAAI,QAAQ;EAAG,MAAM;CAAS;AAC7D;AAEA,SAAgB,mBACd,IACA,OACc;CAKd,OAAO,iBAAiB,IAJP,kBAAkB,aAAa;EAC9C,MAAM,QAAQ;EACd,OAAO;CACT,CACmC,CAAC;AACtC;AAMA,SAAS,aACP,OACwB;CACxB,OAAO,kBAAkB,aAAa;EACpC,KAAK,MAAM,CAAC,WAAW,YAAY,OAGjC,iBACE,WACA,gBAAgB,WAAW,OAAO,GAClC,SAAS,IACX;EAEF,OAAO;CACT,CAAC;AACH;;;;;AAMA,SAAgB,gBAEd,IAAkB,OAAmC;CACrD,OAAO,iBAAiB,IAAI,aAAa,KAAK,CAAC;AACjD;AAEA,SAAS,eAAe,EACtB,OACA,YAIe;CACf,MAAM,CAAC,YAAY,eAAe,aAAa,KAAK,CAAC;CACrD,OAAO,oBAAC,kBAAD;EAA4B;EAAW;CAA2B,CAAA;AAC3E;;;;;AAMA,SAAgB,UACd,OACwC;CACxC,QAAQ,UACN,oBAAC,gBAAD;EAAuB;YACrB,oBAAC,OAAD,CAAQ,CAAA;CACM,CAAA;AAEpB;AAEA,SAAS,iBACP,IACA,UACc;CACd,OAAO,OAAO,IAAI,EAChB,UAAU,EAAE,eACV,oBAAC,kBAAD;EAA4B;EAAW;CAA2B,CAAA,EAEtE,CAAC;AACH"}
|
package/dist/useBloc.d.cts
CHANGED
|
@@ -21,11 +21,8 @@ import type { UseBlocOptions, UseBlocReturn } from './types';
|
|
|
21
21
|
* fires *before* the registry releases its ref, so the bloc is still
|
|
22
22
|
* alive when the callback runs.
|
|
23
23
|
*
|
|
24
|
-
* Per-mount private instance:
|
|
25
|
-
*
|
|
26
|
-
* const id = useId();
|
|
27
|
-
* const [state, bloc] = useBloc(MyBloc, { args: { _id: id } });
|
|
28
|
-
* ```
|
|
24
|
+
* Per-mount private instance: mark the class `@blac({ isolated: true })`, or
|
|
25
|
+
* pass a unique key arg yourself (e.g. `useId()` in `args`).
|
|
29
26
|
*
|
|
30
27
|
* @typeParam T - The state container constructor type (inferred from BlocClass)
|
|
31
28
|
* @param BlocClass - The state container class to connect to
|
package/dist/useBloc.d.ts
CHANGED
|
@@ -21,11 +21,8 @@ import type { UseBlocOptions, UseBlocReturn } from './types';
|
|
|
21
21
|
* fires *before* the registry releases its ref, so the bloc is still
|
|
22
22
|
* alive when the callback runs.
|
|
23
23
|
*
|
|
24
|
-
* Per-mount private instance:
|
|
25
|
-
*
|
|
26
|
-
* const id = useId();
|
|
27
|
-
* const [state, bloc] = useBloc(MyBloc, { args: { _id: id } });
|
|
28
|
-
* ```
|
|
24
|
+
* Per-mount private instance: mark the class `@blac({ isolated: true })`, or
|
|
25
|
+
* pass a unique key arg yourself (e.g. `useId()` in `args`).
|
|
29
26
|
*
|
|
30
27
|
* @typeParam T - The state container constructor type (inferred from BlocClass)
|
|
31
28
|
* @param BlocClass - The state container class to connect to
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blac/react",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.4",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Brendan Mullins <jsnanigans@gmail.com>",
|
|
6
6
|
"description": "React bindings for BlaC — useBloc hook with automatic re-render optimization",
|
|
@@ -78,13 +78,13 @@
|
|
|
78
78
|
}
|
|
79
79
|
],
|
|
80
80
|
"dependencies": {
|
|
81
|
-
"@dirtytalk/structural": "^0.1.
|
|
81
|
+
"@dirtytalk/structural": "^0.1.5"
|
|
82
82
|
},
|
|
83
83
|
"peerDependencies": {
|
|
84
84
|
"@testing-library/react": "^14.0.0 || ^15.0.0 || ^16.0.0",
|
|
85
85
|
"@types/react": "^18.0.0 || ^19.0.0",
|
|
86
86
|
"react": "^18.0.0 || ^19.0.0",
|
|
87
|
-
"@blac/core": "^2.1.
|
|
87
|
+
"@blac/core": "^2.1.3"
|
|
88
88
|
},
|
|
89
89
|
"peerDependenciesMeta": {
|
|
90
90
|
"@types/react": {
|
|
@@ -121,7 +121,7 @@
|
|
|
121
121
|
"vite": "npm:@voidzero-dev/vite-plus-core@0.1.24",
|
|
122
122
|
"vite-plus": "^0.1.24",
|
|
123
123
|
"vitest": "npm:@voidzero-dev/vite-plus-test@0.1.24",
|
|
124
|
-
"@blac/core": "2.1.
|
|
124
|
+
"@blac/core": "2.1.3"
|
|
125
125
|
},
|
|
126
126
|
"scripts": {
|
|
127
127
|
"dev": "vp pack --watch",
|