@blac/react 2.0.12 → 2.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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":["ALL_PATHS"],"sources":["../src/BlocProvider.tsx","../src/useBloc.ts","../src/config.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","import {\n useEffect,\n useId,\n useLayoutEffect,\n useMemo,\n useReducer,\n useRef,\n type RefObject,\n} from 'react';\nimport {\n acquire,\n release,\n resolveInstanceKey,\n type ExtractArgs,\n type ExtractState,\n type InstanceState,\n type StateContainer,\n type StateContainerConstructor,\n} from '@blac/core';\nimport {\n ALL_PATHS,\n emptyPathSet,\n trackRender,\n type PathSet,\n} from '@dirtytalk/structural';\nimport { useInstanceIdFromContext } from './BlocProvider';\nimport type { ComponentRef, UseBlocOptions, UseBlocReturn } from './types';\n\nlet nextConsumerId = 0;\n\n/**\n * React hook that connects a component to a state container with automatic\n * re-render on state changes.\n *\n * Two tracking modes:\n * - **Auto-tracking** (default): the returned state value is a proxy that\n * records read paths during render. The component re-renders when any\n * recorded path changes. Backed by `@dirtytalk/structural`'s\n * {@link trackRender} + the container's path-scoped `DirtyChannel`.\n * - **Manual select**: pass `options.select` to opt out of auto-tracking.\n * The hook re-renders only when the returned array's elements change\n * (per-index `Object.is`). This replaces the v1 `dependencies` option.\n *\n * Lifecycle:\n * - The bloc is acquired from the registry on mount and released on\n * unmount. The instance key is the explicit `options.instanceId`, then\n * the surrounding {@link BlocProvider} context, then the default key.\n * - `options.onMount` fires after the bloc is acquired; `options.onUnmount`\n * fires *before* the registry releases its ref, so the bloc is still\n * alive when the callback runs.\n *\n * @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\n * @returns Tuple of `[state, bloc, ref]`\n *\n * @example Basic usage\n * ```ts\n * const [state, bloc] = useBloc(MyBloc);\n * ```\n *\n * @example Manual select (replaces v1 `dependencies`)\n * ```ts\n * const [state, bloc] = useBloc(MyBloc, {\n * select: (state) => [state.count],\n * });\n * ```\n *\n * @example Named instance\n * ```ts\n * const [state, bloc] = useBloc(MyBloc, { instanceId: 'cart-42' });\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 // Stable per-consumer id (for the structural container's consumer registry).\n // Plain counter rather than `useId()` so we don't compete with internal hooks\n // for SSR id slots.\n const consumerIdRef = useRef<string | null>(null);\n if (consumerIdRef.current === null) {\n consumerIdRef.current = `useBloc-${nextConsumerId++}`;\n }\n const consumerId = consumerIdRef.current;\n // Reserve a useId slot — kept for forwards compatibility and to match\n // BlocProvider-driven SSR hydration alignment (no-op call).\n useId();\n\n // Refs that always carry the latest option callbacks, so the commit effect\n // can read them without re-keying.\n const selectRef = useRef(options?.select);\n selectRef.current = options?.select;\n const onMountRef = useRef(options?.onMount);\n onMountRef.current = options?.onMount;\n const onUnmountRef = useRef(options?.onUnmount);\n onUnmountRef.current = options?.onUnmount;\n\n // ---------------------------------------------------------------------------\n // Identity resolution\n //\n // Args are user-supplied; callers commonly pass a fresh object literal each\n // render. Memoising on `args` directly would bust every render. We compute a\n // structural key (JSON.stringify) for the useMemo dep instead — undefined\n // args (void-args blocs) collapse to an undefined key.\n // ---------------------------------------------------------------------------\n const args = (options as { args?: ExtractArgs<T> } | undefined)?.args;\n const argsRef = useRef(args);\n argsRef.current = args;\n const argsKey = args === undefined ? undefined : JSON.stringify(args);\n\n const explicitInstanceId = options?.instanceId;\n const ctxInstanceId = useInstanceIdFromContext();\n\n const { bloc, instanceKey } = useMemo<{\n bloc: TBloc;\n instanceKey: string;\n }>(() => {\n const explicitKey =\n explicitInstanceId !== undefined\n ? String(explicitInstanceId)\n : ctxInstanceId;\n const resolvedKey = resolveInstanceKey(\n BlocClass,\n explicitKey,\n argsRef.current,\n );\n const refId = `useBloc@${consumerId}`;\n const instance = acquire(\n BlocClass,\n resolvedKey,\n refId,\n argsRef.current,\n ) as TBloc;\n return { bloc: instance, instanceKey: resolvedKey };\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n }, [BlocClass, explicitInstanceId, ctxInstanceId, argsKey]);\n\n // ---------------------------------------------------------------------------\n // Channel subscription\n //\n // We bypass `bloc.subscribe(listener)` (the legacy listener surface, which\n // StateContainer overrides) and talk directly to `bloc.channel`. The channel\n // is the StructuralContainer's path-scoped DirtyChannel; subscribing with a\n // dynamic interest function lets us narrow wakeups per consumer.\n // ---------------------------------------------------------------------------\n const [, force] = useReducer((x: number) => x + 1, 0);\n const pathRef = useRef<PathSet>(emptyPathSet());\n // For select-mode: cache the last selected array so we can compare against\n // the next one before forcing a re-render.\n const lastSelectionRef = useRef<unknown[] | null>(null);\n\n useEffect(() => {\n // Subscribe via the channel directly. For auto-track we re-register the\n // current path interest on each commit (below); for select-mode we use\n // ALL_PATHS and compare selections in the callback.\n const channel = (bloc as unknown as StateContainer).channel;\n const isSelectMode = selectRef.current !== undefined;\n\n if (isSelectMode) {\n const unsub = channel.subscribe(\n () => ALL_PATHS,\n () => {\n const select = selectRef.current;\n if (!select) {\n force();\n return;\n }\n const next = select(\n (bloc as unknown as StateContainer).state as ExtractState<T>,\n bloc as InstanceState<T>,\n );\n const prev = lastSelectionRef.current;\n if (prev !== null && shallowArrayEqual(prev, next)) return;\n lastSelectionRef.current = next;\n force();\n },\n );\n return unsub;\n }\n\n // Auto-track mode: wake on any change that intersects the recorded paths.\n // `() => pathRef.current` is called by the channel on every flush, so we\n // always see the latest interest without re-subscribing.\n const unsub = channel.subscribe(\n () => pathRef.current,\n () => force(),\n );\n // Also register the consumer paths with the container for skeleton\n // recomputation, so the source-side diff can skip us when our paths don't\n // intersect the change.\n (bloc as unknown as StateContainer).registerConsumerPaths(\n consumerId,\n pathRef.current,\n );\n return () => {\n unsub();\n (bloc as unknown as StateContainer).unregisterConsumer(consumerId);\n };\n }, [bloc, consumerId]);\n\n // ---------------------------------------------------------------------------\n // Mount / unmount lifecycle.\n //\n // Order on unmount: onUnmount(bloc) -> release(...). The bloc must still be\n // alive when onUnmount runs (release may dispose it).\n // ---------------------------------------------------------------------------\n useEffect(() => {\n onMountRef.current?.(bloc as InstanceType<T>);\n return () => {\n onUnmountRef.current?.(bloc as InstanceType<T>);\n const refId = `useBloc@${consumerId}`;\n release(BlocClass, instanceKey, false, refId);\n };\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n }, [bloc, instanceKey]);\n\n // ---------------------------------------------------------------------------\n // Snapshot\n //\n // - Auto-track: wrap state in trackRender, record paths into pathRef, and\n // re-register with the container so the skeleton picks up new interest.\n // - Select-mode: return state directly; the subscription callback compares\n // selections to decide whether to re-render.\n // ---------------------------------------------------------------------------\n const rawState = (bloc as unknown as StateContainer).state as ExtractState<T>;\n let state: ExtractState<T>;\n if (selectRef.current !== undefined) {\n state = rawState;\n // Seed the last selection on the first render so we don't fire an\n // immediate \"different from null\" wakeup on the first emit.\n if (lastSelectionRef.current === null) {\n lastSelectionRef.current = selectRef.current(\n rawState,\n bloc as InstanceState<T>,\n );\n }\n } else {\n const tracked = trackRender(\n rawState,\n (bloc as unknown as StateContainer).interner,\n );\n state = tracked.value as ExtractState<T>;\n pathRef.current = tracked.paths;\n // NOTE: registerConsumerPaths is intentionally NOT called here. The\n // proxy hasn't been accessed yet, so `tracked.paths` is an empty Set\n // that the proxy will mutate during JSX evaluation. Registering at\n // this point would store an empty interest with the container and\n // freeze the skeleton at that snapshot — subsequent emits would\n // diff against an empty skeleton and silently drop wakeups. The\n // useLayoutEffect below registers the populated set after render.\n }\n\n // After the render commits the proxy has been touched and pathRef.current\n // is the consumer's actual interest. Re-register with the container so the\n // skeleton reflects the latest paths. useLayoutEffect runs before the\n // browser paints (and before any emit triggered by another effect), so the\n // skeleton is fresh by the time the next emit fires.\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n useLayoutEffect(() => {\n if (selectRef.current !== undefined) return;\n (bloc as unknown as StateContainer).registerConsumerPaths(\n consumerId,\n pathRef.current,\n );\n });\n\n return [\n state,\n bloc,\n componentRef as RefObject<ComponentRef>,\n ] as UseBlocReturn<T, ExtractState<T>>;\n}\n\nconst shallowArrayEqual = (a: unknown[], b: unknown[]): boolean => {\n if (a === b) return true;\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!Object.is(a[i], b[i])) return false;\n }\n return true;\n};\n","/**\n * Global configuration for `@blac/react`.\n *\n * The hook's tracking model is fixed: when `useBloc` is called without a\n * `select`, render-time auto-tracking is used (via\n * `@dirtytalk/structural`'s `trackRender`). When `select` is provided,\n * re-renders are driven by per-index `Object.is` over the returned array.\n *\n * Reserved for forwards-compatible knobs; currently empty.\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface BlacReactConfig {}\n\nconst defaultConfig: BlacReactConfig = {};\n\nlet globalConfig: BlacReactConfig = { ...defaultConfig };\n\n/**\n * Configure global defaults for `@blac/react` hooks.\n *\n * @param config - Partial configuration to merge with current globals\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"],"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,165 +1,183 @@
1
- import React, { useEffect, useMemo, useReducer, useRef, useSyncExternalStore } from "react";
2
- import { ExternalDepsManager, acquire, autoTrackInit, autoTrackSnapshot, autoTrackSubscribe, disableGetterTracking, manualDepsInit, manualDepsSnapshot, manualDepsSubscribe, noTrackInit, noTrackSnapshot, noTrackSubscribe, release } from "@blac/adapter";
3
- let globalConfig = { autoTrack: true };
1
+ import { createContext, useContext, useEffect, useId, useLayoutEffect, useMemo, useReducer, useRef } from "react";
2
+ import { acquire, release, resolveInstanceKey } from "@blac/core";
3
+ import { ALL_PATHS, emptyPathSet, trackRender } from "@dirtytalk/structural";
4
+ import { jsx } from "react/jsx-runtime";
5
+ //#region src/BlocProvider.tsx
6
+ const InstanceIdContext = createContext(void 0);
4
7
  /**
5
- * Configure global defaults for @blac/react hooks.
8
+ * Provides an `instanceId` to descendant `useBloc` calls via React context.
6
9
  *
7
- * @example
8
- * ```ts
9
- * import { configureBlacReact } from '@blac/react';
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.
10
14
  *
11
- * // Disable auto-tracking globally
12
- * configureBlacReact({
13
- * autoTrack: false
14
- * });
15
+ * @example
16
+ * ```tsx
17
+ * <BlocProvider instanceId="customer-42">
18
+ * <CustomerView />
19
+ * </BlocProvider>
15
20
  * ```
16
- *
17
- * @param config - Partial configuration to merge with defaults
18
21
  */
19
- function configureBlacReact(config) {
20
- globalConfig = {
21
- ...globalConfig,
22
- ...config
23
- };
22
+ function BlocProvider({ instanceId, children }) {
23
+ return /* @__PURE__ */ jsx(InstanceIdContext.Provider, {
24
+ value: String(instanceId),
25
+ children
26
+ });
24
27
  }
25
28
  /**
26
- * Get the current global configuration.
27
- * @internal
29
+ * Reads the nearest {@link BlocProvider} instance id, or `undefined` when
30
+ * called outside a provider.
28
31
  */
29
- function getBlacReactConfig() {
30
- return globalConfig;
32
+ function useInstanceIdFromContext() {
33
+ return useContext(InstanceIdContext);
31
34
  }
32
35
  //#endregion
33
36
  //#region src/useBloc.ts
34
37
  let nextConsumerId = 0;
35
- function getComponentName() {
36
- try {
37
- const owner = (React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE ?? React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED)?.ReactCurrentOwner?.current;
38
- if (owner?.type) return owner.type.displayName || owner.type.name || void 0;
39
- } catch {}
40
- }
41
- function determineTrackingMode(options) {
42
- const globalConfig = getBlacReactConfig();
43
- const autoTrackEnabled = options?.autoTrack !== void 0 ? options.autoTrack : globalConfig.autoTrack;
44
- return {
45
- useManualDeps: options?.dependencies !== void 0,
46
- autoTrackEnabled
47
- };
48
- }
49
38
  /**
50
- * React hook that connects a component to a state container with automatic re-render on state changes.
39
+ * React hook that connects a component to a state container with automatic
40
+ * re-render on state changes.
41
+ *
42
+ * Two tracking modes:
43
+ * - **Auto-tracking** (default): the returned state value is a proxy that
44
+ * records read paths during render. The component re-renders when any
45
+ * recorded path changes. Backed by `@dirtytalk/structural`'s
46
+ * {@link trackRender} + the container's path-scoped `DirtyChannel`.
47
+ * - **Manual select**: pass `options.select` to opt out of auto-tracking.
48
+ * The hook re-renders only when the returned array's elements change
49
+ * (per-index `Object.is`). This replaces the v1 `dependencies` option.
51
50
  *
52
- * Supports three tracking modes:
53
- * - **Auto-tracking** (default): Automatically detects accessed state properties via Proxy
54
- * - **Manual dependencies**: Explicit dependency array like useEffect
55
- * - **No tracking**: Returns full state without optimization
51
+ * Lifecycle:
52
+ * - The bloc is acquired from the registry on mount and released on
53
+ * unmount. The instance key is the explicit `options.instanceId`, then
54
+ * the surrounding {@link BlocProvider} context, then the default key.
55
+ * - `options.onMount` fires after the bloc is acquired; `options.onUnmount`
56
+ * fires *before* the registry releases its ref, so the bloc is still
57
+ * alive when the callback runs.
56
58
  *
57
59
  * @template T - The state container constructor type (inferred from BlocClass)
58
60
  * @param BlocClass - The state container class to connect to
59
- * @param options - Configuration options for tracking mode and instance management
60
- * @returns Tuple with [state, bloc instance, ref]
61
+ * @param options - Configuration options
62
+ * @returns Tuple of `[state, bloc, ref]`
61
63
  *
62
64
  * @example Basic usage
63
65
  * ```ts
64
- * const [state, myBloc, ref] = useBloc(MyBloc);
66
+ * const [state, bloc] = useBloc(MyBloc);
65
67
  * ```
66
68
  *
67
- * @example With manual dependencies
69
+ * @example Manual select (replaces v1 `dependencies`)
68
70
  * ```ts
69
- * const [state, myBloc] = useBloc(MyBloc, {
70
- * dependencies: (state) => [state.count]
71
+ * const [state, bloc] = useBloc(MyBloc, {
72
+ * select: (state) => [state.count],
71
73
  * });
72
74
  * ```
73
75
  *
74
- * @example With named instance
76
+ * @example Named instance
75
77
  * ```ts
76
- * const [state, myBloc] = useBloc(MyBloc, {
77
- * instanceId: 'unique-id'
78
- * });
78
+ * const [state, bloc] = useBloc(MyBloc, { instanceId: 'cart-42' });
79
79
  * ```
80
80
  */
81
81
  function useBloc(BlocClass, options) {
82
82
  const componentRef = useRef({});
83
83
  const consumerIdRef = useRef(null);
84
- if (consumerIdRef.current === null) consumerIdRef.current = String(nextConsumerId++);
85
- const componentNameRef = useRef(null);
86
- if (componentNameRef.current === null) componentNameRef.current = getComponentName() || null;
87
- const depsRef = useRef(options?.dependencies);
88
- depsRef.current = options?.dependencies;
89
- const instanceId = options?.instanceId;
90
- const autoTrack = options?.autoTrack;
91
- const dependencies = options?.dependencies;
92
- const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] = useMemo(() => {
93
- const instanceKey = instanceId !== void 0 ? String(instanceId) : void 0;
94
- const instance = acquire(BlocClass, instanceKey, `useBloc@${componentNameRef.current ?? "Unknown"}-${consumerIdRef.current}`);
95
- const { useManualDeps, autoTrackEnabled } = determineTrackingMode({
96
- autoTrack,
97
- dependencies
98
- });
99
- let subscribeFn;
100
- let getSnapshotFn;
101
- let adapterState;
102
- if (useManualDeps && dependencies) {
103
- adapterState = manualDepsInit(instance);
104
- const stableConfig = { dependencies: (state, bloc) => {
105
- const deps = depsRef.current;
106
- return deps ? deps(state, bloc) : [];
107
- } };
108
- subscribeFn = manualDepsSubscribe(instance, adapterState, stableConfig);
109
- getSnapshotFn = manualDepsSnapshot(instance, adapterState, stableConfig);
110
- } else if (!autoTrackEnabled) {
111
- adapterState = noTrackInit(instance);
112
- subscribeFn = noTrackSubscribe(instance);
113
- getSnapshotFn = noTrackSnapshot(instance);
114
- } else {
115
- adapterState = autoTrackInit(instance);
116
- subscribeFn = autoTrackSubscribe(instance, adapterState);
117
- getSnapshotFn = autoTrackSnapshot(instance, adapterState);
118
- }
119
- const safeSubscribeFn = (callback) => {
120
- if (instance.isDisposed) return () => {};
121
- return subscribeFn(callback);
84
+ if (consumerIdRef.current === null) consumerIdRef.current = `useBloc-${nextConsumerId++}`;
85
+ const consumerId = consumerIdRef.current;
86
+ useId();
87
+ const selectRef = useRef(options?.select);
88
+ selectRef.current = options?.select;
89
+ const onMountRef = useRef(options?.onMount);
90
+ onMountRef.current = options?.onMount;
91
+ const onUnmountRef = useRef(options?.onUnmount);
92
+ onUnmountRef.current = options?.onUnmount;
93
+ const args = options?.args;
94
+ const argsRef = useRef(args);
95
+ argsRef.current = args;
96
+ const argsKey = args === void 0 ? void 0 : JSON.stringify(args);
97
+ const explicitInstanceId = options?.instanceId;
98
+ const ctxInstanceId = useInstanceIdFromContext();
99
+ const { bloc, instanceKey } = useMemo(() => {
100
+ const resolvedKey = resolveInstanceKey(BlocClass, explicitInstanceId !== void 0 ? String(explicitInstanceId) : ctxInstanceId, argsRef.current);
101
+ return {
102
+ bloc: acquire(BlocClass, resolvedKey, `useBloc@${consumerId}`, argsRef.current),
103
+ instanceKey: resolvedKey
122
104
  };
123
- return [
124
- adapterState.proxiedBloc,
125
- safeSubscribeFn,
126
- getSnapshotFn,
127
- instanceKey,
128
- adapterState,
129
- instance
130
- ];
131
- }, [BlocClass, instanceId]);
132
- const state = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
133
- const [, forceUpdate] = useReducer((x) => x + 1, 0);
134
- const externalDepsManager = useRef(new ExternalDepsManager());
135
- useEffect(() => {
136
- const manager = externalDepsManager.current;
137
- disableGetterTracking(adapterState, rawInstance);
138
- manager.updateSubscriptions(adapterState.getterState, rawInstance, forceUpdate);
139
105
  }, [
140
- adapterState,
141
- rawInstance,
142
- forceUpdate
106
+ BlocClass,
107
+ explicitInstanceId,
108
+ ctxInstanceId,
109
+ argsKey
143
110
  ]);
111
+ const [, force] = useReducer((x) => x + 1, 0);
112
+ const pathRef = useRef(emptyPathSet());
113
+ const lastSelectionRef = useRef(null);
114
+ useEffect(() => {
115
+ const channel = bloc.channel;
116
+ if (selectRef.current !== void 0) return channel.subscribe(() => ALL_PATHS, () => {
117
+ const select = selectRef.current;
118
+ if (!select) {
119
+ force();
120
+ return;
121
+ }
122
+ const next = select(bloc.state, bloc);
123
+ const prev = lastSelectionRef.current;
124
+ if (prev !== null && shallowArrayEqual(prev, next)) return;
125
+ lastSelectionRef.current = next;
126
+ force();
127
+ });
128
+ const unsub = channel.subscribe(() => pathRef.current, () => force());
129
+ bloc.registerConsumerPaths(consumerId, pathRef.current);
130
+ return () => {
131
+ unsub();
132
+ bloc.unregisterConsumer(consumerId);
133
+ };
134
+ }, [bloc, consumerId]);
144
135
  useEffect(() => {
145
- const manager = externalDepsManager.current;
146
- 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);
136
+ onMountRef.current?.(bloc);
149
137
  return () => {
150
- if (devtools?.unregisterConsumer && rawInstance) devtools.unregisterConsumer(rawInstance.instanceId, consumerIdRef.current);
151
- manager.cleanup();
152
- if (options?.onUnmount) options.onUnmount(bloc);
153
- release(BlocClass, instanceKey, false, `useBloc@${componentNameRef.current ?? "Unknown"}-${consumerIdRef.current}`);
138
+ onUnmountRef.current?.(bloc);
139
+ release(BlocClass, instanceKey, false, `useBloc@${consumerId}`);
154
140
  };
155
- }, []);
141
+ }, [bloc, instanceKey]);
142
+ const rawState = bloc.state;
143
+ let state;
144
+ if (selectRef.current !== void 0) {
145
+ state = rawState;
146
+ if (lastSelectionRef.current === null) lastSelectionRef.current = selectRef.current(rawState, bloc);
147
+ } else {
148
+ const tracked = trackRender(rawState, bloc.interner);
149
+ state = tracked.value;
150
+ pathRef.current = tracked.paths;
151
+ }
152
+ useLayoutEffect(() => {
153
+ if (selectRef.current !== void 0) return;
154
+ bloc.registerConsumerPaths(consumerId, pathRef.current);
155
+ });
156
156
  return [
157
157
  state,
158
158
  bloc,
159
159
  componentRef
160
160
  ];
161
161
  }
162
+ const shallowArrayEqual = (a, b) => {
163
+ if (a === b) return true;
164
+ if (a.length !== b.length) return false;
165
+ for (let i = 0; i < a.length; i++) if (!Object.is(a[i], b[i])) return false;
166
+ return true;
167
+ };
168
+ let globalConfig = {};
169
+ /**
170
+ * Configure global defaults for `@blac/react` hooks.
171
+ *
172
+ * @param config - Partial configuration to merge with current globals
173
+ */
174
+ function configureBlacReact(config) {
175
+ globalConfig = {
176
+ ...globalConfig,
177
+ ...config
178
+ };
179
+ }
162
180
  //#endregion
163
- export { configureBlacReact, useBloc };
181
+ export { BlocProvider, configureBlacReact, useBloc, useInstanceIdFromContext };
164
182
 
165
183
  //# 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/useBloc.ts","../src/config.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","import {\n useEffect,\n useId,\n useLayoutEffect,\n useMemo,\n useReducer,\n useRef,\n type RefObject,\n} from 'react';\nimport {\n acquire,\n release,\n resolveInstanceKey,\n type ExtractArgs,\n type ExtractState,\n type InstanceState,\n type StateContainer,\n type StateContainerConstructor,\n} from '@blac/core';\nimport {\n ALL_PATHS,\n emptyPathSet,\n trackRender,\n type PathSet,\n} from '@dirtytalk/structural';\nimport { useInstanceIdFromContext } from './BlocProvider';\nimport type { ComponentRef, UseBlocOptions, UseBlocReturn } from './types';\n\nlet nextConsumerId = 0;\n\n/**\n * React hook that connects a component to a state container with automatic\n * re-render on state changes.\n *\n * Two tracking modes:\n * - **Auto-tracking** (default): the returned state value is a proxy that\n * records read paths during render. The component re-renders when any\n * recorded path changes. Backed by `@dirtytalk/structural`'s\n * {@link trackRender} + the container's path-scoped `DirtyChannel`.\n * - **Manual select**: pass `options.select` to opt out of auto-tracking.\n * The hook re-renders only when the returned array's elements change\n * (per-index `Object.is`). This replaces the v1 `dependencies` option.\n *\n * Lifecycle:\n * - The bloc is acquired from the registry on mount and released on\n * unmount. The instance key is the explicit `options.instanceId`, then\n * the surrounding {@link BlocProvider} context, then the default key.\n * - `options.onMount` fires after the bloc is acquired; `options.onUnmount`\n * fires *before* the registry releases its ref, so the bloc is still\n * alive when the callback runs.\n *\n * @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\n * @returns Tuple of `[state, bloc, ref]`\n *\n * @example Basic usage\n * ```ts\n * const [state, bloc] = useBloc(MyBloc);\n * ```\n *\n * @example Manual select (replaces v1 `dependencies`)\n * ```ts\n * const [state, bloc] = useBloc(MyBloc, {\n * select: (state) => [state.count],\n * });\n * ```\n *\n * @example Named instance\n * ```ts\n * const [state, bloc] = useBloc(MyBloc, { instanceId: 'cart-42' });\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 // Stable per-consumer id (for the structural container's consumer registry).\n // Plain counter rather than `useId()` so we don't compete with internal hooks\n // for SSR id slots.\n const consumerIdRef = useRef<string | null>(null);\n if (consumerIdRef.current === null) {\n consumerIdRef.current = `useBloc-${nextConsumerId++}`;\n }\n const consumerId = consumerIdRef.current;\n // Reserve a useId slot — kept for forwards compatibility and to match\n // BlocProvider-driven SSR hydration alignment (no-op call).\n useId();\n\n // Refs that always carry the latest option callbacks, so the commit effect\n // can read them without re-keying.\n const selectRef = useRef(options?.select);\n selectRef.current = options?.select;\n const onMountRef = useRef(options?.onMount);\n onMountRef.current = options?.onMount;\n const onUnmountRef = useRef(options?.onUnmount);\n onUnmountRef.current = options?.onUnmount;\n\n // ---------------------------------------------------------------------------\n // Identity resolution\n //\n // Args are user-supplied; callers commonly pass a fresh object literal each\n // render. Memoising on `args` directly would bust every render. We compute a\n // structural key (JSON.stringify) for the useMemo dep instead — undefined\n // args (void-args blocs) collapse to an undefined key.\n // ---------------------------------------------------------------------------\n const args = (options as { args?: ExtractArgs<T> } | undefined)?.args;\n const argsRef = useRef(args);\n argsRef.current = args;\n const argsKey = args === undefined ? undefined : JSON.stringify(args);\n\n const explicitInstanceId = options?.instanceId;\n const ctxInstanceId = useInstanceIdFromContext();\n\n const { bloc, instanceKey } = useMemo<{\n bloc: TBloc;\n instanceKey: string;\n }>(() => {\n const explicitKey =\n explicitInstanceId !== undefined\n ? String(explicitInstanceId)\n : ctxInstanceId;\n const resolvedKey = resolveInstanceKey(\n BlocClass,\n explicitKey,\n argsRef.current,\n );\n const refId = `useBloc@${consumerId}`;\n const instance = acquire(\n BlocClass,\n resolvedKey,\n refId,\n argsRef.current,\n ) as TBloc;\n return { bloc: instance, instanceKey: resolvedKey };\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n }, [BlocClass, explicitInstanceId, ctxInstanceId, argsKey]);\n\n // ---------------------------------------------------------------------------\n // Channel subscription\n //\n // We bypass `bloc.subscribe(listener)` (the legacy listener surface, which\n // StateContainer overrides) and talk directly to `bloc.channel`. The channel\n // is the StructuralContainer's path-scoped DirtyChannel; subscribing with a\n // dynamic interest function lets us narrow wakeups per consumer.\n // ---------------------------------------------------------------------------\n const [, force] = useReducer((x: number) => x + 1, 0);\n const pathRef = useRef<PathSet>(emptyPathSet());\n // For select-mode: cache the last selected array so we can compare against\n // the next one before forcing a re-render.\n const lastSelectionRef = useRef<unknown[] | null>(null);\n\n useEffect(() => {\n // Subscribe via the channel directly. For auto-track we re-register the\n // current path interest on each commit (below); for select-mode we use\n // ALL_PATHS and compare selections in the callback.\n const channel = (bloc as unknown as StateContainer).channel;\n const isSelectMode = selectRef.current !== undefined;\n\n if (isSelectMode) {\n const unsub = channel.subscribe(\n () => ALL_PATHS,\n () => {\n const select = selectRef.current;\n if (!select) {\n force();\n return;\n }\n const next = select(\n (bloc as unknown as StateContainer).state as ExtractState<T>,\n bloc as InstanceState<T>,\n );\n const prev = lastSelectionRef.current;\n if (prev !== null && shallowArrayEqual(prev, next)) return;\n lastSelectionRef.current = next;\n force();\n },\n );\n return unsub;\n }\n\n // Auto-track mode: wake on any change that intersects the recorded paths.\n // `() => pathRef.current` is called by the channel on every flush, so we\n // always see the latest interest without re-subscribing.\n const unsub = channel.subscribe(\n () => pathRef.current,\n () => force(),\n );\n // Also register the consumer paths with the container for skeleton\n // recomputation, so the source-side diff can skip us when our paths don't\n // intersect the change.\n (bloc as unknown as StateContainer).registerConsumerPaths(\n consumerId,\n pathRef.current,\n );\n return () => {\n unsub();\n (bloc as unknown as StateContainer).unregisterConsumer(consumerId);\n };\n }, [bloc, consumerId]);\n\n // ---------------------------------------------------------------------------\n // Mount / unmount lifecycle.\n //\n // Order on unmount: onUnmount(bloc) -> release(...). The bloc must still be\n // alive when onUnmount runs (release may dispose it).\n // ---------------------------------------------------------------------------\n useEffect(() => {\n onMountRef.current?.(bloc as InstanceType<T>);\n return () => {\n onUnmountRef.current?.(bloc as InstanceType<T>);\n const refId = `useBloc@${consumerId}`;\n release(BlocClass, instanceKey, false, refId);\n };\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n }, [bloc, instanceKey]);\n\n // ---------------------------------------------------------------------------\n // Snapshot\n //\n // - Auto-track: wrap state in trackRender, record paths into pathRef, and\n // re-register with the container so the skeleton picks up new interest.\n // - Select-mode: return state directly; the subscription callback compares\n // selections to decide whether to re-render.\n // ---------------------------------------------------------------------------\n const rawState = (bloc as unknown as StateContainer).state as ExtractState<T>;\n let state: ExtractState<T>;\n if (selectRef.current !== undefined) {\n state = rawState;\n // Seed the last selection on the first render so we don't fire an\n // immediate \"different from null\" wakeup on the first emit.\n if (lastSelectionRef.current === null) {\n lastSelectionRef.current = selectRef.current(\n rawState,\n bloc as InstanceState<T>,\n );\n }\n } else {\n const tracked = trackRender(\n rawState,\n (bloc as unknown as StateContainer).interner,\n );\n state = tracked.value as ExtractState<T>;\n pathRef.current = tracked.paths;\n // NOTE: registerConsumerPaths is intentionally NOT called here. The\n // proxy hasn't been accessed yet, so `tracked.paths` is an empty Set\n // that the proxy will mutate during JSX evaluation. Registering at\n // this point would store an empty interest with the container and\n // freeze the skeleton at that snapshot — subsequent emits would\n // diff against an empty skeleton and silently drop wakeups. The\n // useLayoutEffect below registers the populated set after render.\n }\n\n // After the render commits the proxy has been touched and pathRef.current\n // is the consumer's actual interest. Re-register with the container so the\n // skeleton reflects the latest paths. useLayoutEffect runs before the\n // browser paints (and before any emit triggered by another effect), so the\n // skeleton is fresh by the time the next emit fires.\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n useLayoutEffect(() => {\n if (selectRef.current !== undefined) return;\n (bloc as unknown as StateContainer).registerConsumerPaths(\n consumerId,\n pathRef.current,\n );\n });\n\n return [\n state,\n bloc,\n componentRef as RefObject<ComponentRef>,\n ] as UseBlocReturn<T, ExtractState<T>>;\n}\n\nconst shallowArrayEqual = (a: unknown[], b: unknown[]): boolean => {\n if (a === b) return true;\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!Object.is(a[i], b[i])) return false;\n }\n return true;\n};\n","/**\n * Global configuration for `@blac/react`.\n *\n * The hook's tracking model is fixed: when `useBloc` is called without a\n * `select`, render-time auto-tracking is used (via\n * `@dirtytalk/structural`'s `trackRender`). When `select` is provided,\n * re-renders are driven by per-index `Object.is` over the returned array.\n *\n * Reserved for forwards-compatible knobs; currently empty.\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface BlacReactConfig {}\n\nconst defaultConfig: BlacReactConfig = {};\n\nlet globalConfig: BlacReactConfig = { ...defaultConfig };\n\n/**\n * Configure global defaults for `@blac/react` hooks.\n *\n * @param config - Partial configuration to merge with current globals\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"],"mappings":"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"}
package/dist/testing.cjs CHANGED
@@ -1,5 +1,4 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- require("./index.cjs");
3
2
  let _blac_core = require("@blac/core");
4
3
  let _blac_core_testing = require("@blac/core/testing");
5
4
  let _testing_library_react = require("@testing-library/react");
@@ -10,7 +9,13 @@ function renderWithBloc(ui, options) {
10
9
  (0, _blac_core.setRegistry)(new _blac_core.StateContainerRegistry());
11
10
  const instance = (0, _blac_core_testing.createCubitStub)(BlocClass, stubOptions);
12
11
  (0, _blac_core_testing.registerOverride)(BlocClass, instance, instanceKey);
13
- const renderResult = (0, _testing_library_react.render)(ui);
12
+ let renderResult;
13
+ try {
14
+ renderResult = (0, _testing_library_react.render)(ui);
15
+ } catch (e) {
16
+ (0, _blac_core.setRegistry)(previous);
17
+ throw e;
18
+ }
14
19
  const originalUnmount = renderResult.unmount;
15
20
  renderResult.unmount = () => {
16
21
  originalUnmount();
@@ -25,8 +30,19 @@ function renderWithRegistry(ui, setup) {
25
30
  const previous = (0, _blac_core.getRegistry)();
26
31
  const testRegistry = new _blac_core.StateContainerRegistry();
27
32
  (0, _blac_core.setRegistry)(testRegistry);
28
- setup(testRegistry);
29
- const renderResult = (0, _testing_library_react.render)(ui);
33
+ try {
34
+ setup(testRegistry);
35
+ } catch (e) {
36
+ (0, _blac_core.setRegistry)(previous);
37
+ throw e;
38
+ }
39
+ let renderResult;
40
+ try {
41
+ renderResult = (0, _testing_library_react.render)(ui);
42
+ } catch (e) {
43
+ (0, _blac_core.setRegistry)(previous);
44
+ throw e;
45
+ }
30
46
  const originalUnmount = renderResult.unmount;
31
47
  renderResult.unmount = () => {
32
48
  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;CAE9B,CAAA,GAAA,WAAA,aAAY,IADaA,WAAAA,wBACD,CAAC;CAEzB,MAAM,YAAA,GAAA,mBAAA,iBAA2B,WAAW,YAAY;CACxD,CAAA,GAAA,mBAAA,kBAAiB,WAAW,UAAU,YAAY;CAElD,IAAI;CACJ,IAAI;EACF,gBAAA,GAAA,uBAAA,QAAsB,GAAG;UAClB,GAAG;EACV,CAAA,GAAA,WAAA,aAAY,SAAS;EACrB,MAAM;;CAGR,MAAM,kBAAkB,aAAa;CACrC,aAAa,gBAAgB;EAC3B,iBAAiB;EACjB,CAAA,GAAA,WAAA,aAAY,SAAS;;CAGvB,OAAO;EACL,GAAG;EACH,MAAM;EACP;;AAGH,SAAgB,mBACd,IACA,OACc;CACd,MAAM,YAAA,GAAA,WAAA,cAAwB;CAC9B,MAAM,eAAe,IAAIA,WAAAA,wBAAwB;CACjD,CAAA,GAAA,WAAA,aAAY,aAAa;CAEzB,IAAI;EACF,MAAM,aAAa;UACZ,GAAG;EACV,CAAA,GAAA,WAAA,aAAY,SAAS;EACrB,MAAM;;CAGR,IAAI;CACJ,IAAI;EACF,gBAAA,GAAA,uBAAA,QAAsB,GAAG;UAClB,GAAG;EACV,CAAA,GAAA,WAAA,aAAY,SAAS;EACrB,MAAM;;CAGR,MAAM,kBAAkB,aAAa;CACrC,aAAa,gBAAgB;EAC3B,iBAAiB;EACjB,CAAA,GAAA,WAAA,aAAY,SAAS;;CAGvB,OAAO"}
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;CAE9B,YAAY,IADa,wBACD,CAAC;CAEzB,MAAM,WAAW,gBAAgB,WAAW,YAAY;CACxD,iBAAiB,WAAW,UAAU,YAAY;CAElD,IAAI;CACJ,IAAI;EACF,eAAe,OAAO,GAAG;UAClB,GAAG;EACV,YAAY,SAAS;EACrB,MAAM;;CAGR,MAAM,kBAAkB,aAAa;CACrC,aAAa,gBAAgB;EAC3B,iBAAiB;EACjB,YAAY,SAAS;;CAGvB,OAAO;EACL,GAAG;EACH,MAAM;EACP;;AAGH,SAAgB,mBACd,IACA,OACc;CACd,MAAM,WAAW,aAAa;CAC9B,MAAM,eAAe,IAAI,wBAAwB;CACjD,YAAY,aAAa;CAEzB,IAAI;EACF,MAAM,aAAa;UACZ,GAAG;EACV,YAAY,SAAS;EACrB,MAAM;;CAGR,IAAI;CACJ,IAAI;EACF,eAAe,OAAO,GAAG;UAClB,GAAG;EACV,YAAY,SAAS;EACrB,MAAM;;CAGR,MAAM,kBAAkB,aAAa;CACrC,aAAa,gBAAgB;EAC3B,iBAAiB;EACjB,YAAY,SAAS;;CAGvB,OAAO"}