@blac/react 2.0.12 → 2.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -50,6 +50,126 @@ const [state, bloc, ref] = useBloc(MyBloc, options?);
50
50
  - `bloc` — bloc instance for calling methods
51
51
  - `ref` — internal component ref (advanced usage)
52
52
 
53
+ ### The Three Input Lanes
54
+
55
+ BlaC blocs receive external data through three distinct channels:
56
+
57
+ | Lane | Purpose | Keying | Lifetime | Example |
58
+ |---|---|---|---|---|
59
+ | **`args`** | Typed creation data; derives instance identity | **Yes** (structural hash or `static key`) | Once at `init()` | `userId`, `endpoint` |
60
+ | **`deps`** | Non-serializable refs, callbacks, handles | **Never** | Live, per-consumer merged | `ref`, `onComplete` callback, `emblaApi` |
61
+ | **events** | Values that change over time or are late-bound | N/A | Called from effects | `cubit.slidesChanged(v)` from `useEffect` |
62
+
63
+ ### `args`: Typed Construction Data
64
+
65
+ When a bloc declares `Args`, you must pass them at the call site. Args are fed to the bloc's `init(args)` method before the first state snapshot, and they derive the instance identity by default.
66
+
67
+ ```tsx
68
+ import { Cubit } from '@blac/core';
69
+ import { useBloc } from '@blac/react';
70
+
71
+ class UserCardCubit extends Cubit<UserCardState, { userId: string }> {
72
+ // Constructor is zero-arg; framework calls init(args) before first snapshot
73
+ init(args: { userId: string }) {
74
+ this.userId = args.userId;
75
+ void this.loadUser(args.userId);
76
+ }
77
+ }
78
+
79
+ function UserCard({ userId }: { userId: string }) {
80
+ // args is required and type-checked when Args != void
81
+ const [state, cubit] = useBloc(UserCardCubit, { args: { userId } });
82
+ return <div>{state.user?.name}</div>;
83
+ }
84
+ ```
85
+
86
+ **Key properties:**
87
+ - **Required when declared** — omitting `args` or passing the wrong shape is a type error.
88
+ - **Drives identity** — different `args` ⇒ different instance. Same `args` ⇒ same instance (dev-warn if args mismatch on same-keyed second call).
89
+ - **Serializable only** — non-serializable values (refs, callbacks) belong in the `deps` lane (below).
90
+ - **Per-component private instances** — use with `autoInstance: true` to give each mount its own instance, disposed on unmount:
91
+
92
+ ```tsx
93
+ const [state, cubit] = useBloc(FormCubit, { args: options, autoInstance: true });
94
+ ```
95
+
96
+ ### `deps`: Non-Serializable Refs and Callbacks
97
+
98
+ Use `deps` to inject refs, stable callbacks, and long-lived controller handles. Unlike `args`, `deps` are:
99
+ - **Never keying** — different refs don't fork the instance
100
+ - **Per-consumer merged** — each component contributes its own slice
101
+ - **Read lazily** — accessed via `this.deps.x` when needed, may be undefined
102
+ - **Live** — can change over time
103
+
104
+ ```tsx
105
+ const inputRef = useRef<HTMLInputElement>(null);
106
+ const onComplete = useCallback(() => { /* ... */ }, []);
107
+
108
+ const [state, cubit] = useBloc(FileUploadCubit, {
109
+ args: { endpoint },
110
+ deps: { inputRef, onComplete },
111
+ });
112
+ ```
113
+
114
+ The bloc reads them lazily and guards for absence:
115
+
116
+ ```ts
117
+ class FileUploadCubit extends Cubit<UploadState, { endpoint: string }, {
118
+ inputRef?: RefObject<HTMLInputElement>;
119
+ onComplete?: () => void;
120
+ }> {
121
+ async upload() {
122
+ this.deps.inputRef?.current?.click?.();
123
+ // ... perform upload ...
124
+ this.deps.onComplete?.();
125
+ }
126
+ }
127
+ ```
128
+
129
+ **Multi-consumer merge:** when multiple components provide the same cubit with different `deps`, their keys are shallow-merged:
130
+
131
+ ```tsx
132
+ // Component A provides inputRef
133
+ useBloc(FormCubit, { args: { formId }, deps: { inputRef } });
134
+
135
+ // Component B provides onSubmit
136
+ useBloc(FormCubit, { args: { formId }, deps: { onSubmit } });
137
+
138
+ // cubit.deps === { inputRef, onSubmit } (merged from both)
139
+ ```
140
+
141
+ **Avoid raw callbacks** — the callback staleness gotcha. Prefer:
142
+ 1. **Don't inject callbacks — invert** (best): expose state and let React call the fresh callback in its own effect.
143
+ 2. **Stabilize at source** — wrap in `useCallback`.
144
+ 3. **As an event** — push the callback via a bloc method called from your effect.
145
+
146
+ ### events: Methods Called from Effects
147
+
148
+ For data that changes over the instance's life (a `slides` array, a `theme` selection), call an ordinary bloc method from an effect — not a provider-owned input. This keeps ownership explicit and eliminates render-time mutation.
149
+
150
+ ```ts
151
+ class CarouselCubit extends Cubit<CarouselState> {
152
+ slidesChanged(slides: Slide[]) {
153
+ this.patch({ slides, total: slides.length });
154
+ }
155
+ }
156
+ ```
157
+
158
+ ```tsx
159
+ function Carousel({ slides }: { slides: Slide[] }) {
160
+ const [state, cubit] = useBloc(CarouselCubit, { args: { id } });
161
+
162
+ // ONE component owns syncing this live value
163
+ useEffect(() => {
164
+ cubit.slidesChanged(slides);
165
+ }, [cubit, slides]);
166
+
167
+ return /* ... */;
168
+ }
169
+ ```
170
+
171
+ **Convention:** *one component owns syncing any given live value.* Two components calling the same event from both their effects on the same shared instance is a design smell (and rare, because keyed `args` usually route multi-consumer cases to distinct instances).
172
+
53
173
  ### Tracking Modes
54
174
 
55
175
  **Auto-tracking (default):** Only re-renders when accessed properties change.
@@ -59,11 +179,11 @@ const [state] = useBloc(UserBloc);
59
179
  return <h1>{state.name}</h1>; // only re-renders when name changes
60
180
  ```
61
181
 
62
- **Manual dependencies:** Explicit dependency array (disables auto-tracking).
182
+ **Manual dependencies (select):** Explicit dependency array (disables auto-tracking).
63
183
 
64
184
  ```tsx
65
185
  const [state] = useBloc(CounterCubit, {
66
- dependencies: (state) => [state.count],
186
+ select: (state) => [state.count],
67
187
  });
68
188
  ```
69
189
 
@@ -77,21 +197,55 @@ const [state] = useBloc(MyBloc, { autoTrack: false });
77
197
 
78
198
  | Option | Type | Description |
79
199
  | -------------- | ---------------------------- | ---------------------------------------------------- |
200
+ | `args` | `Args` type | Required when bloc declares `Args != void`; forbidden when `void` |
201
+ | `deps` | `{ [key]: value }` | Per-consumer slice of non-serializable handles |
80
202
  | `autoTrack` | `boolean` | Enable proxy-based auto-tracking (default: `true`) |
81
- | `dependencies` | `(state, bloc) => unknown[]` | Manual dependency selector |
82
- | `instanceId` | `string \| number` | Use a named instance instead of the shared singleton |
203
+ | `select` | `(state, bloc) => unknown[]` | Manual dependency selector (renamed from `dependencies`) |
204
+ | `autoInstance` | `boolean` | Per-mount instance keyed by `useId()` instead of shared (default: `false`) |
205
+ | `instanceId` | `string \| number` | Explicit identity key (overrides args-derived keying) |
83
206
  | `onMount` | `(bloc) => void` | Called when component mounts |
84
207
  | `onUnmount` | `(bloc) => void` | Called when component unmounts |
85
208
 
86
- ### Instance Sharing
209
+ ### Identity and Keying
87
210
 
88
- By default, all components using `useBloc(MyBloc)` share the same instance. Use `instanceId` for separate instances:
211
+ Instance identity is resolved in precedence order:
212
+
213
+ 1. **Explicit `instanceId`** — hard override if provided
214
+ 2. **`autoInstance: true`** — per-mount instance keyed by `useId()`
215
+ 3. **`static key(args)` → structural hash of `args`** — default when bloc declares `Args` (if `static key` is defined on the class, it takes precedence; otherwise a stable hash of all `args`)
216
+ 4. **`<BlocProvider>` context id** — inherited from parent provider
217
+ 5. **`'default'`** — singleton fallback
218
+
219
+ Blocs declare explicit identity via a static class property:
220
+
221
+ ```ts
222
+ class DocumentCubit extends Cubit<DocState, { docId: string; readonly: boolean }> {
223
+ static key = (args) => args.docId;
224
+ // Identity is docId; readonly config rides along but doesn't fork instances
225
+ }
226
+ ```
227
+
228
+ ### Instance Sharing and Lifecycle
229
+
230
+ By default, all components using `useBloc(MyBloc)` with the same identity share one instance. Use `autoInstance` for per-component private instances:
89
231
 
90
232
  ```tsx
91
- useBloc(EditorCubit, { instanceId: 'editor-1' }); // instance A
92
- useBloc(EditorCubit, { instanceId: 'editor-2' }); // instance B
233
+ // All users with userId=123 share one instance
234
+ useBloc(UserCardCubit, { args: { userId: 123 } });
235
+
236
+ // Each component mount gets its own instance, disposed on unmount
237
+ useBloc(FormCubit, { args: options, autoInstance: true });
238
+
239
+ // Explicit id (escape hatch for non-derivable identity)
240
+ useBloc(EditorCubit, { instanceId: 'editor-1' });
93
241
  ```
94
242
 
243
+ ### Breaking Changes (v2)
244
+
245
+ - **`dependencies` option renamed to `select`** — avoids confusion with the new `deps` (non-serializable handles) lane.
246
+ - **Zero-arg constructor + `init(args)` lifecycle** — all blocs now use `new Type()` with no constructor args. Blocs that declare `Args` receive them via `init(args)` called by the framework before the first state snapshot.
247
+ - **`args` is required when declared, forbidden when void** — enforced by the type system; no runtime guard needed.
248
+
95
249
  ## Configuration
96
250
 
97
251
  ```tsx
@@ -0,0 +1,33 @@
1
+ import { type ReactElement, type ReactNode } from 'react';
2
+ /**
3
+ * Props for {@link BlocProvider}.
4
+ */
5
+ export interface BlocProviderProps {
6
+ /**
7
+ * Instance id that descendants' `useBloc(C)` calls will resolve to when no
8
+ * explicit `instanceId` is given.
9
+ */
10
+ instanceId: string | number;
11
+ children: ReactNode;
12
+ }
13
+ /**
14
+ * Provides an `instanceId` to descendant `useBloc` calls via React context.
15
+ *
16
+ * Descendants calling `useBloc(C)` without an `instanceId` resolve to the id
17
+ * supplied here. An explicit `instanceId` on the `useBloc` call still wins.
18
+ * `autoInstance` / `static isolated = true` also override the context, because
19
+ * those force a per-mount auto-key.
20
+ *
21
+ * @example
22
+ * ```tsx
23
+ * <BlocProvider instanceId="customer-42">
24
+ * <CustomerView />
25
+ * </BlocProvider>
26
+ * ```
27
+ */
28
+ export declare function BlocProvider({ instanceId, children, }: BlocProviderProps): ReactElement;
29
+ /**
30
+ * Reads the nearest {@link BlocProvider} instance id, or `undefined` when
31
+ * called outside a provider.
32
+ */
33
+ export declare function useInstanceIdFromContext(): string | undefined;
package/dist/config.d.ts CHANGED
@@ -1,24 +1,19 @@
1
1
  /**
2
- * Global configuration for @blac/react
2
+ * Global configuration for `@blac/react`.
3
+ *
4
+ * The hook's tracking model is fixed: when `useBloc` is called without a
5
+ * `select`, render-time auto-tracking is used (via
6
+ * `@dirtytalk/structural`'s `trackRender`). When `select` is provided,
7
+ * re-renders are driven by per-index `Object.is` over the returned array.
8
+ *
9
+ * Reserved for forwards-compatible knobs; currently empty.
3
10
  */
4
11
  export interface BlacReactConfig {
5
- /** Enable automatic property tracking via Proxy (default: true) */
6
- autoTrack: boolean;
7
12
  }
8
13
  /**
9
- * Configure global defaults for @blac/react hooks.
10
- *
11
- * @example
12
- * ```ts
13
- * import { configureBlacReact } from '@blac/react';
14
- *
15
- * // Disable auto-tracking globally
16
- * configureBlacReact({
17
- * autoTrack: false
18
- * });
19
- * ```
14
+ * Configure global defaults for `@blac/react` hooks.
20
15
  *
21
- * @param config - Partial configuration to merge with defaults
16
+ * @param config - Partial configuration to merge with current globals
22
17
  */
23
18
  export declare function configureBlacReact(config: Partial<BlacReactConfig>): void;
24
19
  /**
package/dist/index.cjs CHANGED
@@ -1,191 +1,187 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- //#region \0rolldown/runtime.js
3
- var __create = Object.create;
4
- var __defProp = Object.defineProperty;
5
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
- var __getOwnPropNames = Object.getOwnPropertyNames;
7
- var __getProtoOf = Object.getPrototypeOf;
8
- var __hasOwnProp = Object.prototype.hasOwnProperty;
9
- var __copyProps = (to, from, except, desc) => {
10
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
- key = keys[i];
12
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
- get: ((k) => from[k]).bind(null, key),
14
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
- });
16
- }
17
- return to;
18
- };
19
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
20
- value: mod,
21
- enumerable: true
22
- }) : target, mod));
23
- //#endregion
24
2
  let react = require("react");
25
- react = __toESM(react);
26
- let _blac_adapter = require("@blac/adapter");
27
- let globalConfig = { autoTrack: true };
3
+ let _blac_core = require("@blac/core");
4
+ let _dirtytalk_structural = require("@dirtytalk/structural");
5
+ let react_jsx_runtime = require("react/jsx-runtime");
6
+ //#region src/BlocProvider.tsx
7
+ const InstanceIdContext = (0, react.createContext)(void 0);
28
8
  /**
29
- * Configure global defaults for @blac/react hooks.
9
+ * Provides an `instanceId` to descendant `useBloc` calls via React context.
30
10
  *
31
- * @example
32
- * ```ts
33
- * import { configureBlacReact } from '@blac/react';
11
+ * Descendants calling `useBloc(C)` without an `instanceId` resolve to the id
12
+ * supplied here. An explicit `instanceId` on the `useBloc` call still wins.
13
+ * `autoInstance` / `static isolated = true` also override the context, because
14
+ * those force a per-mount auto-key.
34
15
  *
35
- * // Disable auto-tracking globally
36
- * configureBlacReact({
37
- * autoTrack: false
38
- * });
16
+ * @example
17
+ * ```tsx
18
+ * <BlocProvider instanceId="customer-42">
19
+ * <CustomerView />
20
+ * </BlocProvider>
39
21
  * ```
40
- *
41
- * @param config - Partial configuration to merge with defaults
42
22
  */
43
- function configureBlacReact(config) {
44
- globalConfig = {
45
- ...globalConfig,
46
- ...config
47
- };
23
+ function BlocProvider({ instanceId, children }) {
24
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstanceIdContext.Provider, {
25
+ value: String(instanceId),
26
+ children
27
+ });
48
28
  }
49
29
  /**
50
- * Get the current global configuration.
51
- * @internal
30
+ * Reads the nearest {@link BlocProvider} instance id, or `undefined` when
31
+ * called outside a provider.
52
32
  */
53
- function getBlacReactConfig() {
54
- return globalConfig;
33
+ function useInstanceIdFromContext() {
34
+ return (0, react.useContext)(InstanceIdContext);
55
35
  }
56
36
  //#endregion
57
37
  //#region src/useBloc.ts
58
38
  let nextConsumerId = 0;
59
- function getComponentName() {
60
- try {
61
- const owner = (react.default.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE ?? react.default.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED)?.ReactCurrentOwner?.current;
62
- if (owner?.type) return owner.type.displayName || owner.type.name || void 0;
63
- } catch {}
64
- }
65
- function determineTrackingMode(options) {
66
- const globalConfig = getBlacReactConfig();
67
- const autoTrackEnabled = options?.autoTrack !== void 0 ? options.autoTrack : globalConfig.autoTrack;
68
- return {
69
- useManualDeps: options?.dependencies !== void 0,
70
- autoTrackEnabled
71
- };
72
- }
73
39
  /**
74
- * React hook that connects a component to a state container with automatic re-render on state changes.
40
+ * React hook that connects a component to a state container with automatic
41
+ * re-render on state changes.
42
+ *
43
+ * Two tracking modes:
44
+ * - **Auto-tracking** (default): the returned state value is a proxy that
45
+ * records read paths during render. The component re-renders when any
46
+ * recorded path changes. Backed by `@dirtytalk/structural`'s
47
+ * {@link trackRender} + the container's path-scoped `DirtyChannel`.
48
+ * - **Manual select**: pass `options.select` to opt out of auto-tracking.
49
+ * The hook re-renders only when the returned array's elements change
50
+ * (per-index `Object.is`). This replaces the v1 `dependencies` option.
75
51
  *
76
- * Supports three tracking modes:
77
- * - **Auto-tracking** (default): Automatically detects accessed state properties via Proxy
78
- * - **Manual dependencies**: Explicit dependency array like useEffect
79
- * - **No tracking**: Returns full state without optimization
52
+ * Lifecycle:
53
+ * - The bloc is acquired from the registry on mount and released on
54
+ * unmount. The instance key is the explicit `options.instanceId`, then
55
+ * the surrounding {@link BlocProvider} context, then the default key.
56
+ * - `options.onMount` fires after the bloc is acquired; `options.onUnmount`
57
+ * fires *before* the registry releases its ref, so the bloc is still
58
+ * alive when the callback runs.
80
59
  *
81
60
  * @template T - The state container constructor type (inferred from BlocClass)
82
61
  * @param BlocClass - The state container class to connect to
83
- * @param options - Configuration options for tracking mode and instance management
84
- * @returns Tuple with [state, bloc instance, ref]
62
+ * @param options - Configuration options
63
+ * @returns Tuple of `[state, bloc, ref]`
85
64
  *
86
65
  * @example Basic usage
87
66
  * ```ts
88
- * const [state, myBloc, ref] = useBloc(MyBloc);
67
+ * const [state, bloc] = useBloc(MyBloc);
89
68
  * ```
90
69
  *
91
- * @example With manual dependencies
70
+ * @example Manual select (replaces v1 `dependencies`)
92
71
  * ```ts
93
- * const [state, myBloc] = useBloc(MyBloc, {
94
- * dependencies: (state) => [state.count]
72
+ * const [state, bloc] = useBloc(MyBloc, {
73
+ * select: (state) => [state.count],
95
74
  * });
96
75
  * ```
97
76
  *
98
- * @example With named instance
77
+ * @example Named instance
99
78
  * ```ts
100
- * const [state, myBloc] = useBloc(MyBloc, {
101
- * instanceId: 'unique-id'
102
- * });
79
+ * const [state, bloc] = useBloc(MyBloc, { instanceId: 'cart-42' });
103
80
  * ```
104
81
  */
105
82
  function useBloc(BlocClass, options) {
106
83
  const componentRef = (0, react.useRef)({});
107
84
  const consumerIdRef = (0, react.useRef)(null);
108
- if (consumerIdRef.current === null) consumerIdRef.current = String(nextConsumerId++);
109
- const componentNameRef = (0, react.useRef)(null);
110
- if (componentNameRef.current === null) componentNameRef.current = getComponentName() || null;
111
- const depsRef = (0, react.useRef)(options?.dependencies);
112
- depsRef.current = options?.dependencies;
113
- const instanceId = options?.instanceId;
114
- const autoTrack = options?.autoTrack;
115
- const dependencies = options?.dependencies;
116
- const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] = (0, react.useMemo)(() => {
117
- const instanceKey = instanceId !== void 0 ? String(instanceId) : void 0;
118
- const instance = (0, _blac_adapter.acquire)(BlocClass, instanceKey, `useBloc@${componentNameRef.current ?? "Unknown"}-${consumerIdRef.current}`);
119
- const { useManualDeps, autoTrackEnabled } = determineTrackingMode({
120
- autoTrack,
121
- dependencies
122
- });
123
- let subscribeFn;
124
- let getSnapshotFn;
125
- let adapterState;
126
- if (useManualDeps && dependencies) {
127
- adapterState = (0, _blac_adapter.manualDepsInit)(instance);
128
- const stableConfig = { dependencies: (state, bloc) => {
129
- const deps = depsRef.current;
130
- return deps ? deps(state, bloc) : [];
131
- } };
132
- subscribeFn = (0, _blac_adapter.manualDepsSubscribe)(instance, adapterState, stableConfig);
133
- getSnapshotFn = (0, _blac_adapter.manualDepsSnapshot)(instance, adapterState, stableConfig);
134
- } else if (!autoTrackEnabled) {
135
- adapterState = (0, _blac_adapter.noTrackInit)(instance);
136
- subscribeFn = (0, _blac_adapter.noTrackSubscribe)(instance);
137
- getSnapshotFn = (0, _blac_adapter.noTrackSnapshot)(instance);
138
- } else {
139
- adapterState = (0, _blac_adapter.autoTrackInit)(instance);
140
- subscribeFn = (0, _blac_adapter.autoTrackSubscribe)(instance, adapterState);
141
- getSnapshotFn = (0, _blac_adapter.autoTrackSnapshot)(instance, adapterState);
142
- }
143
- const safeSubscribeFn = (callback) => {
144
- if (instance.isDisposed) return () => {};
145
- return subscribeFn(callback);
85
+ if (consumerIdRef.current === null) consumerIdRef.current = `useBloc-${nextConsumerId++}`;
86
+ const consumerId = consumerIdRef.current;
87
+ (0, react.useId)();
88
+ const selectRef = (0, react.useRef)(options?.select);
89
+ selectRef.current = options?.select;
90
+ const onMountRef = (0, react.useRef)(options?.onMount);
91
+ onMountRef.current = options?.onMount;
92
+ const onUnmountRef = (0, react.useRef)(options?.onUnmount);
93
+ onUnmountRef.current = options?.onUnmount;
94
+ const args = options?.args;
95
+ const argsRef = (0, react.useRef)(args);
96
+ argsRef.current = args;
97
+ const argsKey = args === void 0 ? void 0 : JSON.stringify(args);
98
+ const explicitInstanceId = options?.instanceId;
99
+ const ctxInstanceId = useInstanceIdFromContext();
100
+ const { bloc, instanceKey } = (0, react.useMemo)(() => {
101
+ const resolvedKey = (0, _blac_core.resolveInstanceKey)(BlocClass, explicitInstanceId !== void 0 ? String(explicitInstanceId) : ctxInstanceId, argsRef.current);
102
+ return {
103
+ bloc: (0, _blac_core.acquire)(BlocClass, resolvedKey, `useBloc@${consumerId}`, argsRef.current),
104
+ instanceKey: resolvedKey
146
105
  };
147
- return [
148
- adapterState.proxiedBloc,
149
- safeSubscribeFn,
150
- getSnapshotFn,
151
- instanceKey,
152
- adapterState,
153
- instance
154
- ];
155
- }, [BlocClass, instanceId]);
156
- const state = (0, react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
157
- const [, forceUpdate] = (0, react.useReducer)((x) => x + 1, 0);
158
- const externalDepsManager = (0, react.useRef)(new _blac_adapter.ExternalDepsManager());
159
- (0, react.useEffect)(() => {
160
- const manager = externalDepsManager.current;
161
- (0, _blac_adapter.disableGetterTracking)(adapterState, rawInstance);
162
- manager.updateSubscriptions(adapterState.getterState, rawInstance, forceUpdate);
163
106
  }, [
164
- adapterState,
165
- rawInstance,
166
- forceUpdate
107
+ BlocClass,
108
+ explicitInstanceId,
109
+ ctxInstanceId,
110
+ argsKey
167
111
  ]);
112
+ const [, force] = (0, react.useReducer)((x) => x + 1, 0);
113
+ const pathRef = (0, react.useRef)((0, _dirtytalk_structural.emptyPathSet)());
114
+ const lastSelectionRef = (0, react.useRef)(null);
168
115
  (0, react.useEffect)(() => {
169
- const manager = externalDepsManager.current;
170
- const devtools = typeof window !== "undefined" ? window.__BLAC_DEVTOOLS__ : void 0;
171
- if (devtools?.registerConsumer && rawInstance) devtools.registerConsumer(rawInstance.instanceId, consumerIdRef.current, componentNameRef.current || "Unknown");
172
- if (options?.onMount) options.onMount(bloc);
116
+ const channel = bloc.channel;
117
+ if (selectRef.current !== void 0) return channel.subscribe(() => _dirtytalk_structural.ALL_PATHS, () => {
118
+ const select = selectRef.current;
119
+ if (!select) {
120
+ force();
121
+ return;
122
+ }
123
+ const next = select(bloc.state, bloc);
124
+ const prev = lastSelectionRef.current;
125
+ if (prev !== null && shallowArrayEqual(prev, next)) return;
126
+ lastSelectionRef.current = next;
127
+ force();
128
+ });
129
+ const unsub = channel.subscribe(() => pathRef.current, () => force());
130
+ bloc.registerConsumerPaths(consumerId, pathRef.current);
173
131
  return () => {
174
- if (devtools?.unregisterConsumer && rawInstance) devtools.unregisterConsumer(rawInstance.instanceId, consumerIdRef.current);
175
- manager.cleanup();
176
- if (options?.onUnmount) options.onUnmount(bloc);
177
- (0, _blac_adapter.release)(BlocClass, instanceKey, false, `useBloc@${componentNameRef.current ?? "Unknown"}-${consumerIdRef.current}`);
132
+ unsub();
133
+ bloc.unregisterConsumer(consumerId);
178
134
  };
179
- }, []);
135
+ }, [bloc, consumerId]);
136
+ (0, react.useEffect)(() => {
137
+ onMountRef.current?.(bloc);
138
+ return () => {
139
+ onUnmountRef.current?.(bloc);
140
+ (0, _blac_core.release)(BlocClass, instanceKey, false, `useBloc@${consumerId}`);
141
+ };
142
+ }, [bloc, instanceKey]);
143
+ const rawState = bloc.state;
144
+ let state;
145
+ if (selectRef.current !== void 0) {
146
+ state = rawState;
147
+ if (lastSelectionRef.current === null) lastSelectionRef.current = selectRef.current(rawState, bloc);
148
+ } else {
149
+ const tracked = (0, _dirtytalk_structural.trackRender)(rawState, bloc.interner);
150
+ state = tracked.value;
151
+ pathRef.current = tracked.paths;
152
+ }
153
+ (0, react.useLayoutEffect)(() => {
154
+ if (selectRef.current !== void 0) return;
155
+ bloc.registerConsumerPaths(consumerId, pathRef.current);
156
+ });
180
157
  return [
181
158
  state,
182
159
  bloc,
183
160
  componentRef
184
161
  ];
185
162
  }
163
+ const shallowArrayEqual = (a, b) => {
164
+ if (a === b) return true;
165
+ if (a.length !== b.length) return false;
166
+ for (let i = 0; i < a.length; i++) if (!Object.is(a[i], b[i])) return false;
167
+ return true;
168
+ };
169
+ let globalConfig = {};
170
+ /**
171
+ * Configure global defaults for `@blac/react` hooks.
172
+ *
173
+ * @param config - Partial configuration to merge with current globals
174
+ */
175
+ function configureBlacReact(config) {
176
+ globalConfig = {
177
+ ...globalConfig,
178
+ ...config
179
+ };
180
+ }
186
181
  //#endregion
187
- exports.__toESM = __toESM;
182
+ exports.BlocProvider = BlocProvider;
188
183
  exports.configureBlacReact = configureBlacReact;
189
184
  exports.useBloc = useBloc;
185
+ exports.useInstanceIdFromContext = useInstanceIdFromContext;
190
186
 
191
187
  //# sourceMappingURL=index.cjs.map