@blac/react 2.0.12 → 2.0.13

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.
@@ -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/index.cjs CHANGED
@@ -24,6 +24,38 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  let react = require("react");
25
25
  react = __toESM(react);
26
26
  let _blac_adapter = require("@blac/adapter");
27
+ let _blac_core = require("@blac/core");
28
+ let react_jsx_runtime = require("react/jsx-runtime");
29
+ //#region src/BlocProvider.tsx
30
+ const InstanceIdContext = (0, react.createContext)(void 0);
31
+ /**
32
+ * Provides an `instanceId` to descendant `useBloc` calls via React context.
33
+ *
34
+ * Descendants calling `useBloc(C)` without an `instanceId` resolve to the id
35
+ * supplied here. An explicit `instanceId` on the `useBloc` call still wins.
36
+ * `autoInstance` / `static isolated = true` also override the context, because
37
+ * those force a per-mount auto-key.
38
+ *
39
+ * @example
40
+ * ```tsx
41
+ * <BlocProvider instanceId="customer-42">
42
+ * <CustomerView />
43
+ * </BlocProvider>
44
+ * ```
45
+ */
46
+ function BlocProvider({ instanceId, children }) {
47
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstanceIdContext.Provider, {
48
+ value: String(instanceId),
49
+ children
50
+ });
51
+ }
52
+ /**
53
+ * Reads the nearest {@link BlocProvider} instance id, or `undefined` when
54
+ * called outside a provider.
55
+ */
56
+ function useInstanceIdFromContext() {
57
+ return (0, react.useContext)(InstanceIdContext);
58
+ }
27
59
  let globalConfig = { autoTrack: true };
28
60
  /**
29
61
  * Configure global defaults for @blac/react hooks.
@@ -110,11 +142,18 @@ function useBloc(BlocClass, options) {
110
142
  if (componentNameRef.current === null) componentNameRef.current = getComponentName() || null;
111
143
  const depsRef = (0, react.useRef)(options?.dependencies);
112
144
  depsRef.current = options?.dependencies;
145
+ const onMountRef = (0, react.useRef)(options?.onMount);
146
+ onMountRef.current = options?.onMount;
147
+ const onUnmountRef = (0, react.useRef)(options?.onUnmount);
148
+ onUnmountRef.current = options?.onUnmount;
113
149
  const instanceId = options?.instanceId;
114
150
  const autoTrack = options?.autoTrack;
115
151
  const dependencies = options?.dependencies;
152
+ const autoInstanceId = react.default.useId();
153
+ const autoInstance = options?.autoInstance === true || (0, _blac_core.isIsolatedClass)(BlocClass);
154
+ const ctxInstanceId = useInstanceIdFromContext();
116
155
  const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] = (0, react.useMemo)(() => {
117
- const instanceKey = instanceId !== void 0 ? String(instanceId) : void 0;
156
+ const instanceKey = instanceId !== void 0 ? String(instanceId) : autoInstance ? autoInstanceId : ctxInstanceId;
118
157
  const instance = (0, _blac_adapter.acquire)(BlocClass, instanceKey, `useBloc@${componentNameRef.current ?? "Unknown"}-${consumerIdRef.current}`);
119
158
  const { useManualDeps, autoTrackEnabled } = determineTrackingMode({
120
159
  autoTrack,
@@ -140,22 +179,25 @@ function useBloc(BlocClass, options) {
140
179
  subscribeFn = (0, _blac_adapter.autoTrackSubscribe)(instance, adapterState);
141
180
  getSnapshotFn = (0, _blac_adapter.autoTrackSnapshot)(instance, adapterState);
142
181
  }
143
- const safeSubscribeFn = (callback) => {
144
- if (instance.isDisposed) return () => {};
145
- return subscribeFn(callback);
146
- };
147
182
  return [
148
183
  adapterState.proxiedBloc,
149
- safeSubscribeFn,
184
+ subscribeFn,
150
185
  getSnapshotFn,
151
186
  instanceKey,
152
187
  adapterState,
153
188
  instance
154
189
  ];
155
- }, [BlocClass, instanceId]);
190
+ }, [
191
+ BlocClass,
192
+ instanceId,
193
+ autoInstance,
194
+ autoInstanceId,
195
+ ctxInstanceId
196
+ ]);
156
197
  const state = (0, react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
157
198
  const [, forceUpdate] = (0, react.useReducer)((x) => x + 1, 0);
158
- const externalDepsManager = (0, react.useRef)(new _blac_adapter.ExternalDepsManager());
199
+ const externalDepsManager = (0, react.useRef)(null);
200
+ if (externalDepsManager.current === null) externalDepsManager.current = new _blac_adapter.ExternalDepsManager();
159
201
  (0, react.useEffect)(() => {
160
202
  const manager = externalDepsManager.current;
161
203
  (0, _blac_adapter.disableGetterTracking)(adapterState, rawInstance);
@@ -167,16 +209,23 @@ function useBloc(BlocClass, options) {
167
209
  ]);
168
210
  (0, react.useEffect)(() => {
169
211
  const manager = externalDepsManager.current;
212
+ const currentInstanceKey = instanceKey;
213
+ const currentRawInstance = rawInstance;
214
+ const currentBloc = bloc;
170
215
  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);
216
+ if (devtools?.registerConsumer && currentRawInstance) devtools.registerConsumer(currentRawInstance.instanceId, consumerIdRef.current, componentNameRef.current || "Unknown");
217
+ onMountRef.current?.(currentBloc);
173
218
  return () => {
174
- if (devtools?.unregisterConsumer && rawInstance) devtools.unregisterConsumer(rawInstance.instanceId, consumerIdRef.current);
219
+ if (devtools?.unregisterConsumer && currentRawInstance) devtools.unregisterConsumer(currentRawInstance.instanceId, consumerIdRef.current);
175
220
  manager.cleanup();
176
- if (options?.onUnmount) options.onUnmount(bloc);
177
- (0, _blac_adapter.release)(BlocClass, instanceKey, false, `useBloc@${componentNameRef.current ?? "Unknown"}-${consumerIdRef.current}`);
221
+ onUnmountRef.current?.(currentBloc);
222
+ (0, _blac_adapter.release)(BlocClass, currentInstanceKey, false, `useBloc@${componentNameRef.current ?? "Unknown"}-${consumerIdRef.current}`);
178
223
  };
179
- }, []);
224
+ }, [
225
+ bloc,
226
+ instanceKey,
227
+ rawInstance
228
+ ]);
180
229
  return [
181
230
  state,
182
231
  bloc,
@@ -184,8 +233,10 @@ function useBloc(BlocClass, options) {
184
233
  ];
185
234
  }
186
235
  //#endregion
236
+ exports.BlocProvider = BlocProvider;
187
237
  exports.__toESM = __toESM;
188
238
  exports.configureBlacReact = configureBlacReact;
189
239
  exports.useBloc = useBloc;
240
+ exports.useInstanceIdFromContext = useInstanceIdFromContext;
190
241
 
191
242
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["React","ExternalDepsManager"],"sources":["../src/config.ts","../src/useBloc.ts"],"sourcesContent":["/**\n * Global configuration for @blac/react\n */\n\nexport interface BlacReactConfig {\n /** Enable automatic property tracking via Proxy (default: true) */\n autoTrack: boolean;\n}\n\nconst defaultConfig: BlacReactConfig = {\n autoTrack: true,\n};\n\nlet globalConfig: BlacReactConfig = { ...defaultConfig };\n\n/**\n * Configure global defaults for @blac/react hooks.\n *\n * @example\n * ```ts\n * import { configureBlacReact } from '@blac/react';\n *\n * // Disable auto-tracking globally\n * configureBlacReact({\n * autoTrack: false\n * });\n * ```\n *\n * @param config - Partial configuration to merge with defaults\n */\nexport function configureBlacReact(config: Partial<BlacReactConfig>): void {\n globalConfig = { ...globalConfig, ...config };\n}\n\n/**\n * Get the current global configuration.\n * @internal\n */\nexport function getBlacReactConfig(): BlacReactConfig {\n return globalConfig;\n}\n\n/**\n * Reset configuration to defaults (useful for testing).\n * @internal\n */\nexport function resetBlacReactConfig(): void {\n globalConfig = { ...defaultConfig };\n}\n","import React, {\n useMemo,\n useSyncExternalStore,\n useEffect,\n useRef,\n useReducer,\n} from 'react';\nimport {\n type ExtractState,\n type AdapterState,\n ExternalDepsManager,\n autoTrackSubscribe,\n manualDepsSubscribe,\n noTrackSubscribe,\n autoTrackSnapshot,\n manualDepsSnapshot,\n noTrackSnapshot,\n autoTrackInit,\n manualDepsInit,\n noTrackInit,\n disableGetterTracking,\n type StateContainerConstructor,\n type InstanceState,\n acquire,\n release,\n} from '@blac/adapter';\nimport type { UseBlocOptions, UseBlocReturn, ComponentRef } from './types';\nimport { getBlacReactConfig } from './config';\n\nlet nextConsumerId = 0;\n\nfunction getComponentName(): string | undefined {\n try {\n const internals =\n (React as any)\n .__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE ??\n (React as any).__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;\n const owner = internals?.ReactCurrentOwner?.current;\n if (owner?.type) {\n return owner.type.displayName || owner.type.name || undefined;\n }\n } catch {\n // ignore — React internals may not be available\n }\n return undefined;\n}\n\ninterface TrackingMode {\n useManualDeps: boolean;\n autoTrackEnabled: boolean;\n}\n\nfunction determineTrackingMode<TBloc extends StateContainerConstructor>(\n options?: Pick<UseBlocOptions<TBloc>, 'autoTrack' | 'dependencies'>,\n): TrackingMode {\n const globalConfig = getBlacReactConfig();\n const autoTrackEnabled =\n options?.autoTrack !== undefined\n ? options.autoTrack\n : globalConfig.autoTrack;\n\n return {\n useManualDeps: options?.dependencies !== undefined,\n autoTrackEnabled,\n };\n}\n\n/**\n * React hook that connects a component to a state container with automatic re-render on state changes.\n *\n * Supports three tracking modes:\n * - **Auto-tracking** (default): Automatically detects accessed state properties via Proxy\n * - **Manual dependencies**: Explicit dependency array like useEffect\n * - **No tracking**: Returns full state without optimization\n *\n * @template T - The state container constructor type (inferred from BlocClass)\n * @param BlocClass - The state container class to connect to\n * @param options - Configuration options for tracking mode and instance management\n * @returns Tuple with [state, bloc instance, ref]\n *\n * @example Basic usage\n * ```ts\n * const [state, myBloc, ref] = useBloc(MyBloc);\n * ```\n *\n * @example With manual dependencies\n * ```ts\n * const [state, myBloc] = useBloc(MyBloc, {\n * dependencies: (state) => [state.count]\n * });\n * ```\n *\n * @example With named instance\n * ```ts\n * const [state, myBloc] = useBloc(MyBloc, {\n * instanceId: 'unique-id'\n * });\n * ```\n */\nexport function useBloc<\n T extends StateContainerConstructor = StateContainerConstructor,\n>(\n BlocClass: T,\n options?: UseBlocOptions<T>,\n): UseBlocReturn<T, ExtractState<T>> {\n type TBloc = InstanceState<T>;\n\n const componentRef = useRef<ComponentRef>({});\n\n // Capture component name from React fiber during render (for devtools consumer tracking)\n const consumerIdRef = useRef<string | null>(null);\n if (consumerIdRef.current === null) {\n consumerIdRef.current = String(nextConsumerId++);\n }\n const componentNameRef = useRef<string | null>(null);\n if (componentNameRef.current === null) {\n componentNameRef.current = getComponentName() || null;\n }\n\n const depsRef = useRef(options?.dependencies);\n depsRef.current = options?.dependencies;\n const instanceId = options?.instanceId;\n const autoTrack = options?.autoTrack;\n const dependencies = options?.dependencies;\n\n const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] =\n useMemo<\n readonly [\n TBloc,\n (callback: () => void) => () => void,\n () => ExtractState<T>,\n string | undefined,\n AdapterState<T>,\n TBloc,\n ]\n >(() => {\n const instanceKey =\n instanceId !== undefined ? String(instanceId) : undefined;\n\n const refId = `useBloc@${componentNameRef.current ?? 'Unknown'}-${consumerIdRef.current}`;\n const instance = acquire(BlocClass, instanceKey, refId) as TBloc;\n\n const { useManualDeps, autoTrackEnabled } = determineTrackingMode({\n autoTrack,\n dependencies,\n });\n\n let subscribeFn: (callback: () => void) => () => void;\n let getSnapshotFn: () => ExtractState<T>;\n let adapterState: AdapterState<T>;\n\n if (useManualDeps && dependencies) {\n adapterState = manualDepsInit(instance);\n const stableConfig = {\n dependencies: (state: ExtractState<T>, bloc: InstanceState<T>) => {\n const deps = depsRef.current;\n return deps ? deps(state, bloc) : [];\n },\n };\n subscribeFn = manualDepsSubscribe(instance, adapterState, stableConfig);\n getSnapshotFn = manualDepsSnapshot(\n instance,\n adapterState,\n stableConfig,\n );\n } else if (!autoTrackEnabled) {\n adapterState = noTrackInit(instance);\n subscribeFn = noTrackSubscribe(instance);\n getSnapshotFn = noTrackSnapshot(instance);\n } else {\n adapterState = autoTrackInit(instance);\n subscribeFn = autoTrackSubscribe(instance, adapterState);\n getSnapshotFn = autoTrackSnapshot(instance, adapterState);\n }\n\n const safeSubscribeFn = (callback: () => void) => {\n if (instance.isDisposed) return () => {};\n return subscribeFn(callback);\n };\n\n return [\n adapterState.proxiedBloc as TBloc,\n safeSubscribeFn,\n getSnapshotFn,\n instanceKey,\n adapterState,\n instance,\n ];\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n }, [BlocClass, instanceId]);\n\n const state = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n const [, forceUpdate] = useReducer((x: number) => x + 1, 0);\n\n const externalDepsManager = useRef(new ExternalDepsManager());\n\n useEffect(() => {\n const manager = externalDepsManager.current;\n disableGetterTracking(adapterState, rawInstance);\n manager.updateSubscriptions(\n adapterState.getterState,\n rawInstance,\n forceUpdate,\n );\n }, [adapterState, rawInstance, forceUpdate]);\n\n useEffect(() => {\n const manager = externalDepsManager.current;\n\n // Register as consumer in devtools (if available)\n const devtools =\n typeof window !== 'undefined'\n ? (window as any).__BLAC_DEVTOOLS__\n : undefined;\n if (devtools?.registerConsumer && rawInstance) {\n devtools.registerConsumer(\n (rawInstance as any).instanceId,\n consumerIdRef.current,\n componentNameRef.current || 'Unknown',\n );\n }\n\n if (options?.onMount) {\n options.onMount(bloc as InstanceType<T>);\n }\n\n return () => {\n // Unregister consumer from devtools\n if (devtools?.unregisterConsumer && rawInstance) {\n devtools.unregisterConsumer(\n (rawInstance as any).instanceId,\n consumerIdRef.current,\n );\n }\n\n manager.cleanup();\n\n if (options?.onUnmount) {\n options.onUnmount(bloc as InstanceType<T>);\n }\n\n const refId = `useBloc@${componentNameRef.current ?? 'Unknown'}-${consumerIdRef.current}`;\n release(BlocClass, instanceKey, false, refId);\n };\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return [state, bloc, componentRef] as UseBlocReturn<T, ExtractState<T>>;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["React","ExternalDepsManager"],"sources":["../src/BlocProvider.tsx","../src/config.ts","../src/useBloc.ts"],"sourcesContent":["import {\n createContext,\n useContext,\n type ReactElement,\n type ReactNode,\n} from 'react';\n\nconst InstanceIdContext = createContext<string | undefined>(undefined);\n\n/**\n * Props for {@link BlocProvider}.\n */\nexport interface BlocProviderProps {\n /**\n * Instance id that descendants' `useBloc(C)` calls will resolve to when no\n * explicit `instanceId` is given.\n */\n instanceId: string | number;\n children: ReactNode;\n}\n\n/**\n * Provides an `instanceId` to descendant `useBloc` calls via React context.\n *\n * Descendants calling `useBloc(C)` without an `instanceId` resolve to the id\n * supplied here. An explicit `instanceId` on the `useBloc` call still wins.\n * `autoInstance` / `static isolated = true` also override the context, because\n * those force a per-mount auto-key.\n *\n * @example\n * ```tsx\n * <BlocProvider instanceId=\"customer-42\">\n * <CustomerView />\n * </BlocProvider>\n * ```\n */\nexport function BlocProvider({\n instanceId,\n children,\n}: BlocProviderProps): ReactElement {\n return (\n <InstanceIdContext.Provider value={String(instanceId)}>\n {children}\n </InstanceIdContext.Provider>\n );\n}\n\n/**\n * Reads the nearest {@link BlocProvider} instance id, or `undefined` when\n * called outside a provider.\n */\nexport function useInstanceIdFromContext(): string | undefined {\n return useContext(InstanceIdContext);\n}\n","/**\n * Global configuration for @blac/react\n */\n\nexport interface BlacReactConfig {\n /** Enable automatic property tracking via Proxy (default: true) */\n autoTrack: boolean;\n}\n\nconst defaultConfig: BlacReactConfig = {\n autoTrack: true,\n};\n\nlet globalConfig: BlacReactConfig = { ...defaultConfig };\n\n/**\n * Configure global defaults for @blac/react hooks.\n *\n * @example\n * ```ts\n * import { configureBlacReact } from '@blac/react';\n *\n * // Disable auto-tracking globally\n * configureBlacReact({\n * autoTrack: false\n * });\n * ```\n *\n * @param config - Partial configuration to merge with defaults\n */\nexport function configureBlacReact(config: Partial<BlacReactConfig>): void {\n globalConfig = { ...globalConfig, ...config };\n}\n\n/**\n * Get the current global configuration.\n * @internal\n */\nexport function getBlacReactConfig(): BlacReactConfig {\n return globalConfig;\n}\n\n/**\n * Reset configuration to defaults (useful for testing).\n * @internal\n */\nexport function resetBlacReactConfig(): void {\n globalConfig = { ...defaultConfig };\n}\n","import React, {\n useMemo,\n useSyncExternalStore,\n useEffect,\n useRef,\n useReducer,\n} from 'react';\nimport {\n type ExtractState,\n type AdapterState,\n ExternalDepsManager,\n autoTrackSubscribe,\n manualDepsSubscribe,\n noTrackSubscribe,\n autoTrackSnapshot,\n manualDepsSnapshot,\n noTrackSnapshot,\n autoTrackInit,\n manualDepsInit,\n noTrackInit,\n disableGetterTracking,\n type StateContainerConstructor,\n type InstanceState,\n acquire,\n release,\n} from '@blac/adapter';\nimport { isIsolatedClass } from '@blac/core';\nimport { useInstanceIdFromContext } from './BlocProvider';\nimport type { UseBlocOptions, UseBlocReturn, ComponentRef } from './types';\nimport { getBlacReactConfig } from './config';\n\nlet nextConsumerId = 0;\n\nfunction getComponentName(): string | undefined {\n try {\n const internals =\n (React as any)\n .__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE ??\n (React as any).__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;\n const owner = internals?.ReactCurrentOwner?.current;\n if (owner?.type) {\n return owner.type.displayName || owner.type.name || undefined;\n }\n } catch {\n // ignore — React internals may not be available\n }\n return undefined;\n}\n\ninterface TrackingMode {\n useManualDeps: boolean;\n autoTrackEnabled: boolean;\n}\n\nfunction determineTrackingMode<TBloc extends StateContainerConstructor>(\n options?: Pick<UseBlocOptions<TBloc>, 'autoTrack' | 'dependencies'>,\n): TrackingMode {\n const globalConfig = getBlacReactConfig();\n const autoTrackEnabled =\n options?.autoTrack !== undefined\n ? options.autoTrack\n : globalConfig.autoTrack;\n\n return {\n useManualDeps: options?.dependencies !== undefined,\n autoTrackEnabled,\n };\n}\n\n/**\n * React hook that connects a component to a state container with automatic re-render on state changes.\n *\n * Supports three tracking modes:\n * - **Auto-tracking** (default): Automatically detects accessed state properties via Proxy\n * - **Manual dependencies**: Explicit dependency array like useEffect\n * - **No tracking**: Returns full state without optimization\n *\n * @template T - The state container constructor type (inferred from BlocClass)\n * @param BlocClass - The state container class to connect to\n * @param options - Configuration options for tracking mode and instance management\n * @returns Tuple with [state, bloc instance, ref]\n *\n * @example Basic usage\n * ```ts\n * const [state, myBloc, ref] = useBloc(MyBloc);\n * ```\n *\n * @example With manual dependencies\n * ```ts\n * const [state, myBloc] = useBloc(MyBloc, {\n * dependencies: (state) => [state.count]\n * });\n * ```\n *\n * @example With named instance\n * ```ts\n * const [state, myBloc] = useBloc(MyBloc, {\n * instanceId: 'unique-id'\n * });\n * ```\n */\nexport function useBloc<\n T extends StateContainerConstructor = StateContainerConstructor,\n>(\n BlocClass: T,\n options?: UseBlocOptions<T>,\n): UseBlocReturn<T, ExtractState<T>> {\n type TBloc = InstanceState<T>;\n\n const componentRef = useRef<ComponentRef>({});\n\n // Capture component name from React fiber during render (for devtools consumer tracking)\n const consumerIdRef = useRef<string | null>(null);\n if (consumerIdRef.current === null) {\n consumerIdRef.current = String(nextConsumerId++);\n }\n const componentNameRef = useRef<string | null>(null);\n if (componentNameRef.current === null) {\n componentNameRef.current = getComponentName() || null;\n }\n\n const depsRef = useRef(options?.dependencies);\n depsRef.current = options?.dependencies;\n const onMountRef = useRef(options?.onMount);\n onMountRef.current = options?.onMount;\n const onUnmountRef = useRef(options?.onUnmount);\n onUnmountRef.current = options?.onUnmount;\n const instanceId = options?.instanceId;\n const autoTrack = options?.autoTrack;\n const dependencies = options?.dependencies;\n\n // Auto-keyed per-mount instance: either declared on the class\n // (`static isolated = true`) or opted into per call (`autoInstance: true`).\n // `useId()` is always called to satisfy the rules of hooks.\n const autoInstanceId = React.useId();\n const autoInstance =\n options?.autoInstance === true || isIsolatedClass(BlocClass);\n // Resolve `instanceId` from a surrounding <BlocProvider> when not given\n // explicitly and not auto-keyed.\n const ctxInstanceId = useInstanceIdFromContext();\n\n const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] =\n useMemo<\n readonly [\n TBloc,\n (callback: () => void) => () => void,\n () => ExtractState<T>,\n string | undefined,\n AdapterState<T>,\n TBloc,\n ]\n >(() => {\n const instanceKey =\n instanceId !== undefined\n ? String(instanceId)\n : autoInstance\n ? autoInstanceId\n : ctxInstanceId;\n\n const refId = `useBloc@${componentNameRef.current ?? 'Unknown'}-${consumerIdRef.current}`;\n const instance = acquire(BlocClass, instanceKey, refId) as TBloc;\n\n const { useManualDeps, autoTrackEnabled } = determineTrackingMode({\n autoTrack,\n dependencies,\n });\n\n let subscribeFn: (callback: () => void) => () => void;\n let getSnapshotFn: () => ExtractState<T>;\n let adapterState: AdapterState<T>;\n\n if (useManualDeps && dependencies) {\n adapterState = manualDepsInit(instance);\n const stableConfig = {\n dependencies: (state: ExtractState<T>, bloc: InstanceState<T>) => {\n const deps = depsRef.current;\n return deps ? deps(state, bloc) : [];\n },\n };\n subscribeFn = manualDepsSubscribe(instance, adapterState, stableConfig);\n getSnapshotFn = manualDepsSnapshot(\n instance,\n adapterState,\n stableConfig,\n );\n } else if (!autoTrackEnabled) {\n adapterState = noTrackInit(instance);\n subscribeFn = noTrackSubscribe(instance);\n getSnapshotFn = noTrackSnapshot(instance);\n } else {\n adapterState = autoTrackInit(instance);\n subscribeFn = autoTrackSubscribe(instance, adapterState);\n getSnapshotFn = autoTrackSnapshot(instance, adapterState);\n }\n\n return [\n adapterState.proxiedBloc as TBloc,\n subscribeFn,\n getSnapshotFn,\n instanceKey,\n adapterState,\n instance,\n ];\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n }, [BlocClass, instanceId, autoInstance, autoInstanceId, ctxInstanceId]);\n\n const state = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n const [, forceUpdate] = useReducer((x: number) => x + 1, 0);\n\n const externalDepsManager = useRef<ExternalDepsManager>(null);\n if (externalDepsManager.current === null) {\n (\n externalDepsManager as React.MutableRefObject<ExternalDepsManager>\n ).current = new ExternalDepsManager();\n }\n\n useEffect(() => {\n const manager = externalDepsManager.current!;\n disableGetterTracking(adapterState, rawInstance);\n manager.updateSubscriptions(\n adapterState.getterState,\n rawInstance,\n forceUpdate,\n );\n }, [adapterState, rawInstance, forceUpdate]);\n\n useEffect(() => {\n const manager = externalDepsManager.current!;\n const currentInstanceKey = instanceKey;\n const currentRawInstance = rawInstance;\n const currentBloc = bloc;\n\n // Register as consumer in devtools (if available)\n const devtools =\n typeof window !== 'undefined'\n ? (window as any).__BLAC_DEVTOOLS__\n : undefined;\n if (devtools?.registerConsumer && currentRawInstance) {\n devtools.registerConsumer(\n (currentRawInstance as any).instanceId,\n consumerIdRef.current,\n componentNameRef.current || 'Unknown',\n );\n }\n\n onMountRef.current?.(currentBloc as InstanceType<T>);\n\n return () => {\n if (devtools?.unregisterConsumer && currentRawInstance) {\n devtools.unregisterConsumer(\n (currentRawInstance as any).instanceId,\n consumerIdRef.current,\n );\n }\n\n manager.cleanup();\n\n onUnmountRef.current?.(currentBloc as InstanceType<T>);\n\n const refId = `useBloc@${componentNameRef.current ?? 'Unknown'}-${consumerIdRef.current}`;\n release(BlocClass, currentInstanceKey, false, refId);\n };\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n }, [bloc, instanceKey, rawInstance]);\n\n return [state, bloc, componentRef] as UseBlocReturn<T, ExtractState<T>>;\n}\n"],"mappings":"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"}
package/dist/index.d.cts CHANGED
@@ -9,3 +9,4 @@ export { useBloc } from './useBloc';
9
9
  export { configureBlacReact } from './config';
10
10
  export type { BlacReactConfig } from './config';
11
11
  export type { UseBlocOptions, UseBlocReturn } from './types';
12
+ export { BlocProvider, useInstanceIdFromContext, type BlocProviderProps, } from './BlocProvider';
package/dist/index.d.ts CHANGED
@@ -9,3 +9,4 @@ export { useBloc } from './useBloc';
9
9
  export { configureBlacReact } from './config';
10
10
  export type { BlacReactConfig } from './config';
11
11
  export type { UseBlocOptions, UseBlocReturn } from './types';
12
+ export { BlocProvider, useInstanceIdFromContext, type BlocProviderProps, } from './BlocProvider';
package/dist/index.js CHANGED
@@ -1,5 +1,37 @@
1
- import React, { useEffect, useMemo, useReducer, useRef, useSyncExternalStore } from "react";
1
+ import React, { createContext, useContext, useEffect, useMemo, useReducer, useRef, useSyncExternalStore } from "react";
2
2
  import { ExternalDepsManager, acquire, autoTrackInit, autoTrackSnapshot, autoTrackSubscribe, disableGetterTracking, manualDepsInit, manualDepsSnapshot, manualDepsSubscribe, noTrackInit, noTrackSnapshot, noTrackSubscribe, release } from "@blac/adapter";
3
+ import { isIsolatedClass } from "@blac/core";
4
+ import { jsx } from "react/jsx-runtime";
5
+ //#region src/BlocProvider.tsx
6
+ const InstanceIdContext = createContext(void 0);
7
+ /**
8
+ * Provides an `instanceId` to descendant `useBloc` calls via React context.
9
+ *
10
+ * Descendants calling `useBloc(C)` without an `instanceId` resolve to the id
11
+ * supplied here. An explicit `instanceId` on the `useBloc` call still wins.
12
+ * `autoInstance` / `static isolated = true` also override the context, because
13
+ * those force a per-mount auto-key.
14
+ *
15
+ * @example
16
+ * ```tsx
17
+ * <BlocProvider instanceId="customer-42">
18
+ * <CustomerView />
19
+ * </BlocProvider>
20
+ * ```
21
+ */
22
+ function BlocProvider({ instanceId, children }) {
23
+ return /* @__PURE__ */ jsx(InstanceIdContext.Provider, {
24
+ value: String(instanceId),
25
+ children
26
+ });
27
+ }
28
+ /**
29
+ * Reads the nearest {@link BlocProvider} instance id, or `undefined` when
30
+ * called outside a provider.
31
+ */
32
+ function useInstanceIdFromContext() {
33
+ return useContext(InstanceIdContext);
34
+ }
3
35
  let globalConfig = { autoTrack: true };
4
36
  /**
5
37
  * Configure global defaults for @blac/react hooks.
@@ -86,11 +118,18 @@ function useBloc(BlocClass, options) {
86
118
  if (componentNameRef.current === null) componentNameRef.current = getComponentName() || null;
87
119
  const depsRef = useRef(options?.dependencies);
88
120
  depsRef.current = options?.dependencies;
121
+ const onMountRef = useRef(options?.onMount);
122
+ onMountRef.current = options?.onMount;
123
+ const onUnmountRef = useRef(options?.onUnmount);
124
+ onUnmountRef.current = options?.onUnmount;
89
125
  const instanceId = options?.instanceId;
90
126
  const autoTrack = options?.autoTrack;
91
127
  const dependencies = options?.dependencies;
128
+ const autoInstanceId = React.useId();
129
+ const autoInstance = options?.autoInstance === true || isIsolatedClass(BlocClass);
130
+ const ctxInstanceId = useInstanceIdFromContext();
92
131
  const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] = useMemo(() => {
93
- const instanceKey = instanceId !== void 0 ? String(instanceId) : void 0;
132
+ const instanceKey = instanceId !== void 0 ? String(instanceId) : autoInstance ? autoInstanceId : ctxInstanceId;
94
133
  const instance = acquire(BlocClass, instanceKey, `useBloc@${componentNameRef.current ?? "Unknown"}-${consumerIdRef.current}`);
95
134
  const { useManualDeps, autoTrackEnabled } = determineTrackingMode({
96
135
  autoTrack,
@@ -116,22 +155,25 @@ function useBloc(BlocClass, options) {
116
155
  subscribeFn = autoTrackSubscribe(instance, adapterState);
117
156
  getSnapshotFn = autoTrackSnapshot(instance, adapterState);
118
157
  }
119
- const safeSubscribeFn = (callback) => {
120
- if (instance.isDisposed) return () => {};
121
- return subscribeFn(callback);
122
- };
123
158
  return [
124
159
  adapterState.proxiedBloc,
125
- safeSubscribeFn,
160
+ subscribeFn,
126
161
  getSnapshotFn,
127
162
  instanceKey,
128
163
  adapterState,
129
164
  instance
130
165
  ];
131
- }, [BlocClass, instanceId]);
166
+ }, [
167
+ BlocClass,
168
+ instanceId,
169
+ autoInstance,
170
+ autoInstanceId,
171
+ ctxInstanceId
172
+ ]);
132
173
  const state = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
133
174
  const [, forceUpdate] = useReducer((x) => x + 1, 0);
134
- const externalDepsManager = useRef(new ExternalDepsManager());
175
+ const externalDepsManager = useRef(null);
176
+ if (externalDepsManager.current === null) externalDepsManager.current = new ExternalDepsManager();
135
177
  useEffect(() => {
136
178
  const manager = externalDepsManager.current;
137
179
  disableGetterTracking(adapterState, rawInstance);
@@ -143,16 +185,23 @@ function useBloc(BlocClass, options) {
143
185
  ]);
144
186
  useEffect(() => {
145
187
  const manager = externalDepsManager.current;
188
+ const currentInstanceKey = instanceKey;
189
+ const currentRawInstance = rawInstance;
190
+ const currentBloc = bloc;
146
191
  const devtools = typeof window !== "undefined" ? window.__BLAC_DEVTOOLS__ : void 0;
147
- if (devtools?.registerConsumer && rawInstance) devtools.registerConsumer(rawInstance.instanceId, consumerIdRef.current, componentNameRef.current || "Unknown");
148
- if (options?.onMount) options.onMount(bloc);
192
+ if (devtools?.registerConsumer && currentRawInstance) devtools.registerConsumer(currentRawInstance.instanceId, consumerIdRef.current, componentNameRef.current || "Unknown");
193
+ onMountRef.current?.(currentBloc);
149
194
  return () => {
150
- if (devtools?.unregisterConsumer && rawInstance) devtools.unregisterConsumer(rawInstance.instanceId, consumerIdRef.current);
195
+ if (devtools?.unregisterConsumer && currentRawInstance) devtools.unregisterConsumer(currentRawInstance.instanceId, consumerIdRef.current);
151
196
  manager.cleanup();
152
- if (options?.onUnmount) options.onUnmount(bloc);
153
- release(BlocClass, instanceKey, false, `useBloc@${componentNameRef.current ?? "Unknown"}-${consumerIdRef.current}`);
197
+ onUnmountRef.current?.(currentBloc);
198
+ release(BlocClass, currentInstanceKey, false, `useBloc@${componentNameRef.current ?? "Unknown"}-${consumerIdRef.current}`);
154
199
  };
155
- }, []);
200
+ }, [
201
+ bloc,
202
+ instanceKey,
203
+ rawInstance
204
+ ]);
156
205
  return [
157
206
  state,
158
207
  bloc,
@@ -160,6 +209,6 @@ function useBloc(BlocClass, options) {
160
209
  ];
161
210
  }
162
211
  //#endregion
163
- export { configureBlacReact, useBloc };
212
+ export { BlocProvider, configureBlacReact, useBloc, useInstanceIdFromContext };
164
213
 
165
214
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/config.ts","../src/useBloc.ts"],"sourcesContent":["/**\n * Global configuration for @blac/react\n */\n\nexport interface BlacReactConfig {\n /** Enable automatic property tracking via Proxy (default: true) */\n autoTrack: boolean;\n}\n\nconst defaultConfig: BlacReactConfig = {\n autoTrack: true,\n};\n\nlet globalConfig: BlacReactConfig = { ...defaultConfig };\n\n/**\n * Configure global defaults for @blac/react hooks.\n *\n * @example\n * ```ts\n * import { configureBlacReact } from '@blac/react';\n *\n * // Disable auto-tracking globally\n * configureBlacReact({\n * autoTrack: false\n * });\n * ```\n *\n * @param config - Partial configuration to merge with defaults\n */\nexport function configureBlacReact(config: Partial<BlacReactConfig>): void {\n globalConfig = { ...globalConfig, ...config };\n}\n\n/**\n * Get the current global configuration.\n * @internal\n */\nexport function getBlacReactConfig(): BlacReactConfig {\n return globalConfig;\n}\n\n/**\n * Reset configuration to defaults (useful for testing).\n * @internal\n */\nexport function resetBlacReactConfig(): void {\n globalConfig = { ...defaultConfig };\n}\n","import React, {\n useMemo,\n useSyncExternalStore,\n useEffect,\n useRef,\n useReducer,\n} from 'react';\nimport {\n type ExtractState,\n type AdapterState,\n ExternalDepsManager,\n autoTrackSubscribe,\n manualDepsSubscribe,\n noTrackSubscribe,\n autoTrackSnapshot,\n manualDepsSnapshot,\n noTrackSnapshot,\n autoTrackInit,\n manualDepsInit,\n noTrackInit,\n disableGetterTracking,\n type StateContainerConstructor,\n type InstanceState,\n acquire,\n release,\n} from '@blac/adapter';\nimport type { UseBlocOptions, UseBlocReturn, ComponentRef } from './types';\nimport { getBlacReactConfig } from './config';\n\nlet nextConsumerId = 0;\n\nfunction getComponentName(): string | undefined {\n try {\n const internals =\n (React as any)\n .__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE ??\n (React as any).__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;\n const owner = internals?.ReactCurrentOwner?.current;\n if (owner?.type) {\n return owner.type.displayName || owner.type.name || undefined;\n }\n } catch {\n // ignore — React internals may not be available\n }\n return undefined;\n}\n\ninterface TrackingMode {\n useManualDeps: boolean;\n autoTrackEnabled: boolean;\n}\n\nfunction determineTrackingMode<TBloc extends StateContainerConstructor>(\n options?: Pick<UseBlocOptions<TBloc>, 'autoTrack' | 'dependencies'>,\n): TrackingMode {\n const globalConfig = getBlacReactConfig();\n const autoTrackEnabled =\n options?.autoTrack !== undefined\n ? options.autoTrack\n : globalConfig.autoTrack;\n\n return {\n useManualDeps: options?.dependencies !== undefined,\n autoTrackEnabled,\n };\n}\n\n/**\n * React hook that connects a component to a state container with automatic re-render on state changes.\n *\n * Supports three tracking modes:\n * - **Auto-tracking** (default): Automatically detects accessed state properties via Proxy\n * - **Manual dependencies**: Explicit dependency array like useEffect\n * - **No tracking**: Returns full state without optimization\n *\n * @template T - The state container constructor type (inferred from BlocClass)\n * @param BlocClass - The state container class to connect to\n * @param options - Configuration options for tracking mode and instance management\n * @returns Tuple with [state, bloc instance, ref]\n *\n * @example Basic usage\n * ```ts\n * const [state, myBloc, ref] = useBloc(MyBloc);\n * ```\n *\n * @example With manual dependencies\n * ```ts\n * const [state, myBloc] = useBloc(MyBloc, {\n * dependencies: (state) => [state.count]\n * });\n * ```\n *\n * @example With named instance\n * ```ts\n * const [state, myBloc] = useBloc(MyBloc, {\n * instanceId: 'unique-id'\n * });\n * ```\n */\nexport function useBloc<\n T extends StateContainerConstructor = StateContainerConstructor,\n>(\n BlocClass: T,\n options?: UseBlocOptions<T>,\n): UseBlocReturn<T, ExtractState<T>> {\n type TBloc = InstanceState<T>;\n\n const componentRef = useRef<ComponentRef>({});\n\n // Capture component name from React fiber during render (for devtools consumer tracking)\n const consumerIdRef = useRef<string | null>(null);\n if (consumerIdRef.current === null) {\n consumerIdRef.current = String(nextConsumerId++);\n }\n const componentNameRef = useRef<string | null>(null);\n if (componentNameRef.current === null) {\n componentNameRef.current = getComponentName() || null;\n }\n\n const depsRef = useRef(options?.dependencies);\n depsRef.current = options?.dependencies;\n const instanceId = options?.instanceId;\n const autoTrack = options?.autoTrack;\n const dependencies = options?.dependencies;\n\n const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] =\n useMemo<\n readonly [\n TBloc,\n (callback: () => void) => () => void,\n () => ExtractState<T>,\n string | undefined,\n AdapterState<T>,\n TBloc,\n ]\n >(() => {\n const instanceKey =\n instanceId !== undefined ? String(instanceId) : undefined;\n\n const refId = `useBloc@${componentNameRef.current ?? 'Unknown'}-${consumerIdRef.current}`;\n const instance = acquire(BlocClass, instanceKey, refId) as TBloc;\n\n const { useManualDeps, autoTrackEnabled } = determineTrackingMode({\n autoTrack,\n dependencies,\n });\n\n let subscribeFn: (callback: () => void) => () => void;\n let getSnapshotFn: () => ExtractState<T>;\n let adapterState: AdapterState<T>;\n\n if (useManualDeps && dependencies) {\n adapterState = manualDepsInit(instance);\n const stableConfig = {\n dependencies: (state: ExtractState<T>, bloc: InstanceState<T>) => {\n const deps = depsRef.current;\n return deps ? deps(state, bloc) : [];\n },\n };\n subscribeFn = manualDepsSubscribe(instance, adapterState, stableConfig);\n getSnapshotFn = manualDepsSnapshot(\n instance,\n adapterState,\n stableConfig,\n );\n } else if (!autoTrackEnabled) {\n adapterState = noTrackInit(instance);\n subscribeFn = noTrackSubscribe(instance);\n getSnapshotFn = noTrackSnapshot(instance);\n } else {\n adapterState = autoTrackInit(instance);\n subscribeFn = autoTrackSubscribe(instance, adapterState);\n getSnapshotFn = autoTrackSnapshot(instance, adapterState);\n }\n\n const safeSubscribeFn = (callback: () => void) => {\n if (instance.isDisposed) return () => {};\n return subscribeFn(callback);\n };\n\n return [\n adapterState.proxiedBloc as TBloc,\n safeSubscribeFn,\n getSnapshotFn,\n instanceKey,\n adapterState,\n instance,\n ];\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n }, [BlocClass, instanceId]);\n\n const state = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n const [, forceUpdate] = useReducer((x: number) => x + 1, 0);\n\n const externalDepsManager = useRef(new ExternalDepsManager());\n\n useEffect(() => {\n const manager = externalDepsManager.current;\n disableGetterTracking(adapterState, rawInstance);\n manager.updateSubscriptions(\n adapterState.getterState,\n rawInstance,\n forceUpdate,\n );\n }, [adapterState, rawInstance, forceUpdate]);\n\n useEffect(() => {\n const manager = externalDepsManager.current;\n\n // Register as consumer in devtools (if available)\n const devtools =\n typeof window !== 'undefined'\n ? (window as any).__BLAC_DEVTOOLS__\n : undefined;\n if (devtools?.registerConsumer && rawInstance) {\n devtools.registerConsumer(\n (rawInstance as any).instanceId,\n consumerIdRef.current,\n componentNameRef.current || 'Unknown',\n );\n }\n\n if (options?.onMount) {\n options.onMount(bloc as InstanceType<T>);\n }\n\n return () => {\n // Unregister consumer from devtools\n if (devtools?.unregisterConsumer && rawInstance) {\n devtools.unregisterConsumer(\n (rawInstance as any).instanceId,\n consumerIdRef.current,\n );\n }\n\n manager.cleanup();\n\n if (options?.onUnmount) {\n options.onUnmount(bloc as InstanceType<T>);\n }\n\n const refId = `useBloc@${componentNameRef.current ?? 'Unknown'}-${consumerIdRef.current}`;\n release(BlocClass, instanceKey, false, refId);\n };\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return [state, bloc, componentRef] as UseBlocReturn<T, ExtractState<T>>;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/BlocProvider.tsx","../src/config.ts","../src/useBloc.ts"],"sourcesContent":["import {\n createContext,\n useContext,\n type ReactElement,\n type ReactNode,\n} from 'react';\n\nconst InstanceIdContext = createContext<string | undefined>(undefined);\n\n/**\n * Props for {@link BlocProvider}.\n */\nexport interface BlocProviderProps {\n /**\n * Instance id that descendants' `useBloc(C)` calls will resolve to when no\n * explicit `instanceId` is given.\n */\n instanceId: string | number;\n children: ReactNode;\n}\n\n/**\n * Provides an `instanceId` to descendant `useBloc` calls via React context.\n *\n * Descendants calling `useBloc(C)` without an `instanceId` resolve to the id\n * supplied here. An explicit `instanceId` on the `useBloc` call still wins.\n * `autoInstance` / `static isolated = true` also override the context, because\n * those force a per-mount auto-key.\n *\n * @example\n * ```tsx\n * <BlocProvider instanceId=\"customer-42\">\n * <CustomerView />\n * </BlocProvider>\n * ```\n */\nexport function BlocProvider({\n instanceId,\n children,\n}: BlocProviderProps): ReactElement {\n return (\n <InstanceIdContext.Provider value={String(instanceId)}>\n {children}\n </InstanceIdContext.Provider>\n );\n}\n\n/**\n * Reads the nearest {@link BlocProvider} instance id, or `undefined` when\n * called outside a provider.\n */\nexport function useInstanceIdFromContext(): string | undefined {\n return useContext(InstanceIdContext);\n}\n","/**\n * Global configuration for @blac/react\n */\n\nexport interface BlacReactConfig {\n /** Enable automatic property tracking via Proxy (default: true) */\n autoTrack: boolean;\n}\n\nconst defaultConfig: BlacReactConfig = {\n autoTrack: true,\n};\n\nlet globalConfig: BlacReactConfig = { ...defaultConfig };\n\n/**\n * Configure global defaults for @blac/react hooks.\n *\n * @example\n * ```ts\n * import { configureBlacReact } from '@blac/react';\n *\n * // Disable auto-tracking globally\n * configureBlacReact({\n * autoTrack: false\n * });\n * ```\n *\n * @param config - Partial configuration to merge with defaults\n */\nexport function configureBlacReact(config: Partial<BlacReactConfig>): void {\n globalConfig = { ...globalConfig, ...config };\n}\n\n/**\n * Get the current global configuration.\n * @internal\n */\nexport function getBlacReactConfig(): BlacReactConfig {\n return globalConfig;\n}\n\n/**\n * Reset configuration to defaults (useful for testing).\n * @internal\n */\nexport function resetBlacReactConfig(): void {\n globalConfig = { ...defaultConfig };\n}\n","import React, {\n useMemo,\n useSyncExternalStore,\n useEffect,\n useRef,\n useReducer,\n} from 'react';\nimport {\n type ExtractState,\n type AdapterState,\n ExternalDepsManager,\n autoTrackSubscribe,\n manualDepsSubscribe,\n noTrackSubscribe,\n autoTrackSnapshot,\n manualDepsSnapshot,\n noTrackSnapshot,\n autoTrackInit,\n manualDepsInit,\n noTrackInit,\n disableGetterTracking,\n type StateContainerConstructor,\n type InstanceState,\n acquire,\n release,\n} from '@blac/adapter';\nimport { isIsolatedClass } from '@blac/core';\nimport { useInstanceIdFromContext } from './BlocProvider';\nimport type { UseBlocOptions, UseBlocReturn, ComponentRef } from './types';\nimport { getBlacReactConfig } from './config';\n\nlet nextConsumerId = 0;\n\nfunction getComponentName(): string | undefined {\n try {\n const internals =\n (React as any)\n .__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE ??\n (React as any).__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;\n const owner = internals?.ReactCurrentOwner?.current;\n if (owner?.type) {\n return owner.type.displayName || owner.type.name || undefined;\n }\n } catch {\n // ignore — React internals may not be available\n }\n return undefined;\n}\n\ninterface TrackingMode {\n useManualDeps: boolean;\n autoTrackEnabled: boolean;\n}\n\nfunction determineTrackingMode<TBloc extends StateContainerConstructor>(\n options?: Pick<UseBlocOptions<TBloc>, 'autoTrack' | 'dependencies'>,\n): TrackingMode {\n const globalConfig = getBlacReactConfig();\n const autoTrackEnabled =\n options?.autoTrack !== undefined\n ? options.autoTrack\n : globalConfig.autoTrack;\n\n return {\n useManualDeps: options?.dependencies !== undefined,\n autoTrackEnabled,\n };\n}\n\n/**\n * React hook that connects a component to a state container with automatic re-render on state changes.\n *\n * Supports three tracking modes:\n * - **Auto-tracking** (default): Automatically detects accessed state properties via Proxy\n * - **Manual dependencies**: Explicit dependency array like useEffect\n * - **No tracking**: Returns full state without optimization\n *\n * @template T - The state container constructor type (inferred from BlocClass)\n * @param BlocClass - The state container class to connect to\n * @param options - Configuration options for tracking mode and instance management\n * @returns Tuple with [state, bloc instance, ref]\n *\n * @example Basic usage\n * ```ts\n * const [state, myBloc, ref] = useBloc(MyBloc);\n * ```\n *\n * @example With manual dependencies\n * ```ts\n * const [state, myBloc] = useBloc(MyBloc, {\n * dependencies: (state) => [state.count]\n * });\n * ```\n *\n * @example With named instance\n * ```ts\n * const [state, myBloc] = useBloc(MyBloc, {\n * instanceId: 'unique-id'\n * });\n * ```\n */\nexport function useBloc<\n T extends StateContainerConstructor = StateContainerConstructor,\n>(\n BlocClass: T,\n options?: UseBlocOptions<T>,\n): UseBlocReturn<T, ExtractState<T>> {\n type TBloc = InstanceState<T>;\n\n const componentRef = useRef<ComponentRef>({});\n\n // Capture component name from React fiber during render (for devtools consumer tracking)\n const consumerIdRef = useRef<string | null>(null);\n if (consumerIdRef.current === null) {\n consumerIdRef.current = String(nextConsumerId++);\n }\n const componentNameRef = useRef<string | null>(null);\n if (componentNameRef.current === null) {\n componentNameRef.current = getComponentName() || null;\n }\n\n const depsRef = useRef(options?.dependencies);\n depsRef.current = options?.dependencies;\n const onMountRef = useRef(options?.onMount);\n onMountRef.current = options?.onMount;\n const onUnmountRef = useRef(options?.onUnmount);\n onUnmountRef.current = options?.onUnmount;\n const instanceId = options?.instanceId;\n const autoTrack = options?.autoTrack;\n const dependencies = options?.dependencies;\n\n // Auto-keyed per-mount instance: either declared on the class\n // (`static isolated = true`) or opted into per call (`autoInstance: true`).\n // `useId()` is always called to satisfy the rules of hooks.\n const autoInstanceId = React.useId();\n const autoInstance =\n options?.autoInstance === true || isIsolatedClass(BlocClass);\n // Resolve `instanceId` from a surrounding <BlocProvider> when not given\n // explicitly and not auto-keyed.\n const ctxInstanceId = useInstanceIdFromContext();\n\n const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] =\n useMemo<\n readonly [\n TBloc,\n (callback: () => void) => () => void,\n () => ExtractState<T>,\n string | undefined,\n AdapterState<T>,\n TBloc,\n ]\n >(() => {\n const instanceKey =\n instanceId !== undefined\n ? String(instanceId)\n : autoInstance\n ? autoInstanceId\n : ctxInstanceId;\n\n const refId = `useBloc@${componentNameRef.current ?? 'Unknown'}-${consumerIdRef.current}`;\n const instance = acquire(BlocClass, instanceKey, refId) as TBloc;\n\n const { useManualDeps, autoTrackEnabled } = determineTrackingMode({\n autoTrack,\n dependencies,\n });\n\n let subscribeFn: (callback: () => void) => () => void;\n let getSnapshotFn: () => ExtractState<T>;\n let adapterState: AdapterState<T>;\n\n if (useManualDeps && dependencies) {\n adapterState = manualDepsInit(instance);\n const stableConfig = {\n dependencies: (state: ExtractState<T>, bloc: InstanceState<T>) => {\n const deps = depsRef.current;\n return deps ? deps(state, bloc) : [];\n },\n };\n subscribeFn = manualDepsSubscribe(instance, adapterState, stableConfig);\n getSnapshotFn = manualDepsSnapshot(\n instance,\n adapterState,\n stableConfig,\n );\n } else if (!autoTrackEnabled) {\n adapterState = noTrackInit(instance);\n subscribeFn = noTrackSubscribe(instance);\n getSnapshotFn = noTrackSnapshot(instance);\n } else {\n adapterState = autoTrackInit(instance);\n subscribeFn = autoTrackSubscribe(instance, adapterState);\n getSnapshotFn = autoTrackSnapshot(instance, adapterState);\n }\n\n return [\n adapterState.proxiedBloc as TBloc,\n subscribeFn,\n getSnapshotFn,\n instanceKey,\n adapterState,\n instance,\n ];\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n }, [BlocClass, instanceId, autoInstance, autoInstanceId, ctxInstanceId]);\n\n const state = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n const [, forceUpdate] = useReducer((x: number) => x + 1, 0);\n\n const externalDepsManager = useRef<ExternalDepsManager>(null);\n if (externalDepsManager.current === null) {\n (\n externalDepsManager as React.MutableRefObject<ExternalDepsManager>\n ).current = new ExternalDepsManager();\n }\n\n useEffect(() => {\n const manager = externalDepsManager.current!;\n disableGetterTracking(adapterState, rawInstance);\n manager.updateSubscriptions(\n adapterState.getterState,\n rawInstance,\n forceUpdate,\n );\n }, [adapterState, rawInstance, forceUpdate]);\n\n useEffect(() => {\n const manager = externalDepsManager.current!;\n const currentInstanceKey = instanceKey;\n const currentRawInstance = rawInstance;\n const currentBloc = bloc;\n\n // Register as consumer in devtools (if available)\n const devtools =\n typeof window !== 'undefined'\n ? (window as any).__BLAC_DEVTOOLS__\n : undefined;\n if (devtools?.registerConsumer && currentRawInstance) {\n devtools.registerConsumer(\n (currentRawInstance as any).instanceId,\n consumerIdRef.current,\n componentNameRef.current || 'Unknown',\n );\n }\n\n onMountRef.current?.(currentBloc as InstanceType<T>);\n\n return () => {\n if (devtools?.unregisterConsumer && currentRawInstance) {\n devtools.unregisterConsumer(\n (currentRawInstance as any).instanceId,\n consumerIdRef.current,\n );\n }\n\n manager.cleanup();\n\n onUnmountRef.current?.(currentBloc as InstanceType<T>);\n\n const refId = `useBloc@${componentNameRef.current ?? 'Unknown'}-${consumerIdRef.current}`;\n release(BlocClass, currentInstanceKey, false, refId);\n };\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n }, [bloc, instanceKey, rawInstance]);\n\n return [state, bloc, componentRef] as UseBlocReturn<T, ExtractState<T>>;\n}\n"],"mappings":"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"}
package/dist/testing.cjs CHANGED
@@ -10,7 +10,13 @@ function renderWithBloc(ui, options) {
10
10
  (0, _blac_core.setRegistry)(new _blac_core.StateContainerRegistry());
11
11
  const instance = (0, _blac_core_testing.createCubitStub)(BlocClass, stubOptions);
12
12
  (0, _blac_core_testing.registerOverride)(BlocClass, instance, instanceKey);
13
- const renderResult = (0, _testing_library_react.render)(ui);
13
+ let renderResult;
14
+ try {
15
+ renderResult = (0, _testing_library_react.render)(ui);
16
+ } catch (e) {
17
+ (0, _blac_core.setRegistry)(previous);
18
+ throw e;
19
+ }
14
20
  const originalUnmount = renderResult.unmount;
15
21
  renderResult.unmount = () => {
16
22
  originalUnmount();
@@ -25,8 +31,19 @@ function renderWithRegistry(ui, setup) {
25
31
  const previous = (0, _blac_core.getRegistry)();
26
32
  const testRegistry = new _blac_core.StateContainerRegistry();
27
33
  (0, _blac_core.setRegistry)(testRegistry);
28
- setup(testRegistry);
29
- const renderResult = (0, _testing_library_react.render)(ui);
34
+ try {
35
+ setup(testRegistry);
36
+ } catch (e) {
37
+ (0, _blac_core.setRegistry)(previous);
38
+ throw e;
39
+ }
40
+ let renderResult;
41
+ try {
42
+ renderResult = (0, _testing_library_react.render)(ui);
43
+ } catch (e) {
44
+ (0, _blac_core.setRegistry)(previous);
45
+ throw e;
46
+ }
30
47
  const originalUnmount = renderResult.unmount;
31
48
  renderResult.unmount = () => {
32
49
  originalUnmount();
@@ -1 +1 @@
1
- {"version":3,"file":"testing.cjs","names":["StateContainerRegistry"],"sources":["../src/testing.ts"],"sourcesContent":["import type { ReactElement } from 'react';\nimport type { RenderResult } from '@testing-library/react';\nimport type { StateContainerConstructor } from '@blac/core';\nimport { StateContainerRegistry, getRegistry, setRegistry } from '@blac/core';\nimport {\n registerOverride,\n createCubitStub,\n type CubitStubOptions,\n} from '@blac/core/testing';\nimport { render } from '@testing-library/react';\n\ninterface RenderWithBlocOptions<\n T extends StateContainerConstructor,\n> extends CubitStubOptions<T> {\n bloc: T;\n instanceKey?: string;\n}\n\nexport function renderWithBloc<T extends StateContainerConstructor>(\n ui: ReactElement,\n options: RenderWithBlocOptions<T>,\n): RenderResult & { bloc: InstanceType<T> } {\n const { bloc: BlocClass, instanceKey, ...stubOptions } = options;\n\n const previous = getRegistry();\n const testRegistry = new StateContainerRegistry();\n setRegistry(testRegistry);\n\n const instance = createCubitStub(BlocClass, stubOptions);\n registerOverride(BlocClass, instance, instanceKey);\n\n const renderResult = render(ui);\n\n const originalUnmount = renderResult.unmount;\n renderResult.unmount = () => {\n originalUnmount();\n setRegistry(previous);\n };\n\n return {\n ...renderResult,\n bloc: instance,\n };\n}\n\nexport function renderWithRegistry(\n ui: ReactElement,\n setup: (registry: StateContainerRegistry) => void,\n): RenderResult {\n const previous = getRegistry();\n const testRegistry = new StateContainerRegistry();\n setRegistry(testRegistry);\n\n setup(testRegistry);\n\n const renderResult = render(ui);\n\n const originalUnmount = renderResult.unmount;\n renderResult.unmount = () => {\n originalUnmount();\n setRegistry(previous);\n };\n\n return renderResult;\n}\n"],"mappings":";;;;;;AAkBA,SAAgB,eACd,IACA,SAC0C;CAC1C,MAAM,EAAE,MAAM,WAAW,aAAa,GAAG,gBAAgB;CAEzD,MAAM,YAAA,GAAA,WAAA,cAAwB;AAE9B,EAAA,GAAA,WAAA,aADqB,IAAIA,WAAAA,wBAAwB,CACxB;CAEzB,MAAM,YAAA,GAAA,mBAAA,iBAA2B,WAAW,YAAY;AACxD,EAAA,GAAA,mBAAA,kBAAiB,WAAW,UAAU,YAAY;CAElD,MAAM,gBAAA,GAAA,uBAAA,QAAsB,GAAG;CAE/B,MAAM,kBAAkB,aAAa;AACrC,cAAa,gBAAgB;AAC3B,mBAAiB;AACjB,GAAA,GAAA,WAAA,aAAY,SAAS;;AAGvB,QAAO;EACL,GAAG;EACH,MAAM;EACP;;AAGH,SAAgB,mBACd,IACA,OACc;CACd,MAAM,YAAA,GAAA,WAAA,cAAwB;CAC9B,MAAM,eAAe,IAAIA,WAAAA,wBAAwB;AACjD,EAAA,GAAA,WAAA,aAAY,aAAa;AAEzB,OAAM,aAAa;CAEnB,MAAM,gBAAA,GAAA,uBAAA,QAAsB,GAAG;CAE/B,MAAM,kBAAkB,aAAa;AACrC,cAAa,gBAAgB;AAC3B,mBAAiB;AACjB,GAAA,GAAA,WAAA,aAAY,SAAS;;AAGvB,QAAO"}
1
+ {"version":3,"file":"testing.cjs","names":["StateContainerRegistry"],"sources":["../src/testing.ts"],"sourcesContent":["import type { ReactElement } from 'react';\nimport type { RenderResult } from '@testing-library/react';\nimport type { StateContainerConstructor } from '@blac/core';\nimport { StateContainerRegistry, getRegistry, setRegistry } from '@blac/core';\nimport {\n registerOverride,\n createCubitStub,\n type CubitStubOptions,\n} from '@blac/core/testing';\nimport { render } from '@testing-library/react';\n\ninterface RenderWithBlocOptions<\n T extends StateContainerConstructor,\n> extends CubitStubOptions<T> {\n bloc: T;\n instanceKey?: string;\n}\n\nexport function renderWithBloc<T extends StateContainerConstructor>(\n ui: ReactElement,\n options: RenderWithBlocOptions<T>,\n): RenderResult & { bloc: InstanceType<T> } {\n const { bloc: BlocClass, instanceKey, ...stubOptions } = options;\n\n const previous = getRegistry();\n const testRegistry = new StateContainerRegistry();\n setRegistry(testRegistry);\n\n const instance = createCubitStub(BlocClass, stubOptions);\n registerOverride(BlocClass, instance, instanceKey);\n\n let renderResult: RenderResult;\n try {\n renderResult = render(ui);\n } catch (e) {\n setRegistry(previous);\n throw e;\n }\n\n const originalUnmount = renderResult.unmount;\n renderResult.unmount = () => {\n originalUnmount();\n setRegistry(previous);\n };\n\n return {\n ...renderResult,\n bloc: instance,\n };\n}\n\nexport function renderWithRegistry(\n ui: ReactElement,\n setup: (registry: StateContainerRegistry) => void,\n): RenderResult {\n const previous = getRegistry();\n const testRegistry = new StateContainerRegistry();\n setRegistry(testRegistry);\n\n try {\n setup(testRegistry);\n } catch (e) {\n setRegistry(previous);\n throw e;\n }\n\n let renderResult: RenderResult;\n try {\n renderResult = render(ui);\n } catch (e) {\n setRegistry(previous);\n throw e;\n }\n\n const originalUnmount = renderResult.unmount;\n renderResult.unmount = () => {\n originalUnmount();\n setRegistry(previous);\n };\n\n return renderResult;\n}\n"],"mappings":";;;;;;AAkBA,SAAgB,eACd,IACA,SAC0C;CAC1C,MAAM,EAAE,MAAM,WAAW,aAAa,GAAG,gBAAgB;CAEzD,MAAM,YAAA,GAAA,WAAA,cAAwB;AAE9B,EAAA,GAAA,WAAA,aADqB,IAAIA,WAAAA,wBAAwB,CACxB;CAEzB,MAAM,YAAA,GAAA,mBAAA,iBAA2B,WAAW,YAAY;AACxD,EAAA,GAAA,mBAAA,kBAAiB,WAAW,UAAU,YAAY;CAElD,IAAI;AACJ,KAAI;AACF,kBAAA,GAAA,uBAAA,QAAsB,GAAG;UAClB,GAAG;AACV,GAAA,GAAA,WAAA,aAAY,SAAS;AACrB,QAAM;;CAGR,MAAM,kBAAkB,aAAa;AACrC,cAAa,gBAAgB;AAC3B,mBAAiB;AACjB,GAAA,GAAA,WAAA,aAAY,SAAS;;AAGvB,QAAO;EACL,GAAG;EACH,MAAM;EACP;;AAGH,SAAgB,mBACd,IACA,OACc;CACd,MAAM,YAAA,GAAA,WAAA,cAAwB;CAC9B,MAAM,eAAe,IAAIA,WAAAA,wBAAwB;AACjD,EAAA,GAAA,WAAA,aAAY,aAAa;AAEzB,KAAI;AACF,QAAM,aAAa;UACZ,GAAG;AACV,GAAA,GAAA,WAAA,aAAY,SAAS;AACrB,QAAM;;CAGR,IAAI;AACJ,KAAI;AACF,kBAAA,GAAA,uBAAA,QAAsB,GAAG;UAClB,GAAG;AACV,GAAA,GAAA,WAAA,aAAY,SAAS;AACrB,QAAM;;CAGR,MAAM,kBAAkB,aAAa;AACrC,cAAa,gBAAgB;AAC3B,mBAAiB;AACjB,GAAA,GAAA,WAAA,aAAY,SAAS;;AAGvB,QAAO"}
package/dist/testing.js CHANGED
@@ -8,7 +8,13 @@ function renderWithBloc(ui, options) {
8
8
  setRegistry(new StateContainerRegistry());
9
9
  const instance = createCubitStub(BlocClass, stubOptions);
10
10
  registerOverride(BlocClass, instance, instanceKey);
11
- const renderResult = render(ui);
11
+ let renderResult;
12
+ try {
13
+ renderResult = render(ui);
14
+ } catch (e) {
15
+ setRegistry(previous);
16
+ throw e;
17
+ }
12
18
  const originalUnmount = renderResult.unmount;
13
19
  renderResult.unmount = () => {
14
20
  originalUnmount();
@@ -23,8 +29,19 @@ function renderWithRegistry(ui, setup) {
23
29
  const previous = getRegistry();
24
30
  const testRegistry = new StateContainerRegistry();
25
31
  setRegistry(testRegistry);
26
- setup(testRegistry);
27
- const renderResult = render(ui);
32
+ try {
33
+ setup(testRegistry);
34
+ } catch (e) {
35
+ setRegistry(previous);
36
+ throw e;
37
+ }
38
+ let renderResult;
39
+ try {
40
+ renderResult = render(ui);
41
+ } catch (e) {
42
+ setRegistry(previous);
43
+ throw e;
44
+ }
28
45
  const originalUnmount = renderResult.unmount;
29
46
  renderResult.unmount = () => {
30
47
  originalUnmount();
@@ -1 +1 @@
1
- {"version":3,"file":"testing.js","names":[],"sources":["../src/testing.ts"],"sourcesContent":["import type { ReactElement } from 'react';\nimport type { RenderResult } from '@testing-library/react';\nimport type { StateContainerConstructor } from '@blac/core';\nimport { StateContainerRegistry, getRegistry, setRegistry } from '@blac/core';\nimport {\n registerOverride,\n createCubitStub,\n type CubitStubOptions,\n} from '@blac/core/testing';\nimport { render } from '@testing-library/react';\n\ninterface RenderWithBlocOptions<\n T extends StateContainerConstructor,\n> extends CubitStubOptions<T> {\n bloc: T;\n instanceKey?: string;\n}\n\nexport function renderWithBloc<T extends StateContainerConstructor>(\n ui: ReactElement,\n options: RenderWithBlocOptions<T>,\n): RenderResult & { bloc: InstanceType<T> } {\n const { bloc: BlocClass, instanceKey, ...stubOptions } = options;\n\n const previous = getRegistry();\n const testRegistry = new StateContainerRegistry();\n setRegistry(testRegistry);\n\n const instance = createCubitStub(BlocClass, stubOptions);\n registerOverride(BlocClass, instance, instanceKey);\n\n const renderResult = render(ui);\n\n const originalUnmount = renderResult.unmount;\n renderResult.unmount = () => {\n originalUnmount();\n setRegistry(previous);\n };\n\n return {\n ...renderResult,\n bloc: instance,\n };\n}\n\nexport function renderWithRegistry(\n ui: ReactElement,\n setup: (registry: StateContainerRegistry) => void,\n): RenderResult {\n const previous = getRegistry();\n const testRegistry = new StateContainerRegistry();\n setRegistry(testRegistry);\n\n setup(testRegistry);\n\n const renderResult = render(ui);\n\n const originalUnmount = renderResult.unmount;\n renderResult.unmount = () => {\n originalUnmount();\n setRegistry(previous);\n };\n\n return renderResult;\n}\n"],"mappings":";;;;AAkBA,SAAgB,eACd,IACA,SAC0C;CAC1C,MAAM,EAAE,MAAM,WAAW,aAAa,GAAG,gBAAgB;CAEzD,MAAM,WAAW,aAAa;AAE9B,aADqB,IAAI,wBAAwB,CACxB;CAEzB,MAAM,WAAW,gBAAgB,WAAW,YAAY;AACxD,kBAAiB,WAAW,UAAU,YAAY;CAElD,MAAM,eAAe,OAAO,GAAG;CAE/B,MAAM,kBAAkB,aAAa;AACrC,cAAa,gBAAgB;AAC3B,mBAAiB;AACjB,cAAY,SAAS;;AAGvB,QAAO;EACL,GAAG;EACH,MAAM;EACP;;AAGH,SAAgB,mBACd,IACA,OACc;CACd,MAAM,WAAW,aAAa;CAC9B,MAAM,eAAe,IAAI,wBAAwB;AACjD,aAAY,aAAa;AAEzB,OAAM,aAAa;CAEnB,MAAM,eAAe,OAAO,GAAG;CAE/B,MAAM,kBAAkB,aAAa;AACrC,cAAa,gBAAgB;AAC3B,mBAAiB;AACjB,cAAY,SAAS;;AAGvB,QAAO"}
1
+ {"version":3,"file":"testing.js","names":[],"sources":["../src/testing.ts"],"sourcesContent":["import type { ReactElement } from 'react';\nimport type { RenderResult } from '@testing-library/react';\nimport type { StateContainerConstructor } from '@blac/core';\nimport { StateContainerRegistry, getRegistry, setRegistry } from '@blac/core';\nimport {\n registerOverride,\n createCubitStub,\n type CubitStubOptions,\n} from '@blac/core/testing';\nimport { render } from '@testing-library/react';\n\ninterface RenderWithBlocOptions<\n T extends StateContainerConstructor,\n> extends CubitStubOptions<T> {\n bloc: T;\n instanceKey?: string;\n}\n\nexport function renderWithBloc<T extends StateContainerConstructor>(\n ui: ReactElement,\n options: RenderWithBlocOptions<T>,\n): RenderResult & { bloc: InstanceType<T> } {\n const { bloc: BlocClass, instanceKey, ...stubOptions } = options;\n\n const previous = getRegistry();\n const testRegistry = new StateContainerRegistry();\n setRegistry(testRegistry);\n\n const instance = createCubitStub(BlocClass, stubOptions);\n registerOverride(BlocClass, instance, instanceKey);\n\n let renderResult: RenderResult;\n try {\n renderResult = render(ui);\n } catch (e) {\n setRegistry(previous);\n throw e;\n }\n\n const originalUnmount = renderResult.unmount;\n renderResult.unmount = () => {\n originalUnmount();\n setRegistry(previous);\n };\n\n return {\n ...renderResult,\n bloc: instance,\n };\n}\n\nexport function renderWithRegistry(\n ui: ReactElement,\n setup: (registry: StateContainerRegistry) => void,\n): RenderResult {\n const previous = getRegistry();\n const testRegistry = new StateContainerRegistry();\n setRegistry(testRegistry);\n\n try {\n setup(testRegistry);\n } catch (e) {\n setRegistry(previous);\n throw e;\n }\n\n let renderResult: RenderResult;\n try {\n renderResult = render(ui);\n } catch (e) {\n setRegistry(previous);\n throw e;\n }\n\n const originalUnmount = renderResult.unmount;\n renderResult.unmount = () => {\n originalUnmount();\n setRegistry(previous);\n };\n\n return renderResult;\n}\n"],"mappings":";;;;AAkBA,SAAgB,eACd,IACA,SAC0C;CAC1C,MAAM,EAAE,MAAM,WAAW,aAAa,GAAG,gBAAgB;CAEzD,MAAM,WAAW,aAAa;AAE9B,aADqB,IAAI,wBAAwB,CACxB;CAEzB,MAAM,WAAW,gBAAgB,WAAW,YAAY;AACxD,kBAAiB,WAAW,UAAU,YAAY;CAElD,IAAI;AACJ,KAAI;AACF,iBAAe,OAAO,GAAG;UAClB,GAAG;AACV,cAAY,SAAS;AACrB,QAAM;;CAGR,MAAM,kBAAkB,aAAa;AACrC,cAAa,gBAAgB;AAC3B,mBAAiB;AACjB,cAAY,SAAS;;AAGvB,QAAO;EACL,GAAG;EACH,MAAM;EACP;;AAGH,SAAgB,mBACd,IACA,OACc;CACd,MAAM,WAAW,aAAa;CAC9B,MAAM,eAAe,IAAI,wBAAwB;AACjD,aAAY,aAAa;AAEzB,KAAI;AACF,QAAM,aAAa;UACZ,GAAG;AACV,cAAY,SAAS;AACrB,QAAM;;CAGR,IAAI;AACJ,KAAI;AACF,iBAAe,OAAO,GAAG;UAClB,GAAG;AACV,cAAY,SAAS;AACrB,QAAM;;CAGR,MAAM,kBAAkB,aAAa;AACrC,cAAa,gBAAgB;AAC3B,mBAAiB;AACjB,cAAY,SAAS;;AAGvB,QAAO"}
package/dist/types.d.ts CHANGED
@@ -8,6 +8,12 @@ import type { RefObject } from 'react';
8
8
  export interface UseBlocOptions<TBloc extends StateContainerConstructor> {
9
9
  /** Custom instance identifier */
10
10
  instanceId?: string | number;
11
+ /**
12
+ * When true, this call site gets its own per-mount instance, auto-keyed via
13
+ * React's `useId()`. Equivalent to declaring `static isolated = true` on the
14
+ * bloc class. Ignored when an explicit `instanceId` is provided.
15
+ */
16
+ autoInstance?: boolean;
11
17
  /** Manual dependency array like useEffect (disables autoTrack) */
12
18
  dependencies?: (state: ExtractState<TBloc>, bloc: InstanceReadonlyState<TBloc>) => unknown[];
13
19
  /** Enable automatic property tracking via Proxy (default: true) */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blac/react",
3
- "version": "2.0.12",
3
+ "version": "2.0.13",
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",
@@ -65,13 +65,13 @@
65
65
  "bloc-pattern"
66
66
  ],
67
67
  "dependencies": {
68
- "@blac/adapter": "^2.0.13"
68
+ "@blac/adapter": "^2.0.14"
69
69
  },
70
70
  "peerDependencies": {
71
71
  "@testing-library/react": "^14.0.0 || ^15.0.0 || ^16.0.0",
72
72
  "@types/react": "^18.0.0 || ^19.0.0",
73
73
  "react": "^18.0.0 || ^19.0.0",
74
- "@blac/core": "^2.0.12"
74
+ "@blac/core": "^2.0.13"
75
75
  },
76
76
  "peerDependenciesMeta": {
77
77
  "@types/react": {
@@ -102,7 +102,7 @@
102
102
  "vite": "npm:@voidzero-dev/vite-plus-core@latest",
103
103
  "vitest": "npm:@voidzero-dev/vite-plus-test@latest",
104
104
  "vite-plus": "latest",
105
- "@blac/core": "2.0.12"
105
+ "@blac/core": "2.0.13"
106
106
  },
107
107
  "scripts": {
108
108
  "dev": "vp pack --watch",