@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.
- package/dist/BlocProvider.d.ts +33 -0
- package/dist/index.cjs +65 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +65 -16
- package/dist/index.js.map +1 -1
- package/dist/testing.cjs +20 -3
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.js +20 -3
- package/dist/testing.js.map +1 -1
- package/dist/types.d.ts +6 -0
- package/package.json +4 -4
|
@@ -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) :
|
|
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
|
-
|
|
184
|
+
subscribeFn,
|
|
150
185
|
getSnapshotFn,
|
|
151
186
|
instanceKey,
|
|
152
187
|
adapterState,
|
|
153
188
|
instance
|
|
154
189
|
];
|
|
155
|
-
}, [
|
|
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)(
|
|
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 &&
|
|
172
|
-
|
|
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 &&
|
|
219
|
+
if (devtools?.unregisterConsumer && currentRawInstance) devtools.unregisterConsumer(currentRawInstance.instanceId, consumerIdRef.current);
|
|
175
220
|
manager.cleanup();
|
|
176
|
-
|
|
177
|
-
(0, _blac_adapter.release)(BlocClass,
|
|
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
|
package/dist/index.cjs.map
CHANGED
|
@@ -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) :
|
|
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
|
-
|
|
160
|
+
subscribeFn,
|
|
126
161
|
getSnapshotFn,
|
|
127
162
|
instanceKey,
|
|
128
163
|
adapterState,
|
|
129
164
|
instance
|
|
130
165
|
];
|
|
131
|
-
}, [
|
|
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(
|
|
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 &&
|
|
148
|
-
|
|
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 &&
|
|
195
|
+
if (devtools?.unregisterConsumer && currentRawInstance) devtools.unregisterConsumer(currentRawInstance.instanceId, consumerIdRef.current);
|
|
151
196
|
manager.cleanup();
|
|
152
|
-
|
|
153
|
-
release(BlocClass,
|
|
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
|
-
|
|
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
|
-
|
|
29
|
-
|
|
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();
|
package/dist/testing.cjs.map
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
27
|
-
|
|
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();
|
package/dist/testing.js.map
CHANGED
|
@@ -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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
105
|
+
"@blac/core": "2.0.13"
|
|
106
106
|
},
|
|
107
107
|
"scripts": {
|
|
108
108
|
"dev": "vp pack --watch",
|