@blac/react
Advanced tools
+2
-2
@@ -82,3 +82,3 @@ let react = require("react"); | ||
| const instanceKey$1 = generateInstanceKey(componentRef.current, isIsolated, options?.instanceId); | ||
| const instance = BlocClass.resolve(instanceKey$1, initialPropsRef.current); | ||
| const instance = BlocClass.resolve(instanceKey$1, { props: initialPropsRef.current }); | ||
| if (initialPropsRef.current !== void 0) instance.updateProps(initialPropsRef.current); | ||
@@ -175,3 +175,3 @@ const { useManualDeps, autoTrackEnabled } = determineTrackingMode(options); | ||
| const instanceKey$1 = generateInstanceKey(componentRef.current, isIsolated, options?.instanceId); | ||
| return [BlocClass.resolve(instanceKey$1, initialPropsRef.current), instanceKey$1]; | ||
| return [BlocClass.resolve(instanceKey$1, { props: initialPropsRef.current }), instanceKey$1]; | ||
| }, [BlocClass]); | ||
@@ -178,0 +178,0 @@ (0, react.useEffect)(() => { |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.cjs","names":["instanceKey","subscribeFn: (callback: () => void) => () => void","getSnapshotFn: () => ExtractState<TBloc>","adapterState: AdapterState<TBloc>","adapterState","ExternalDependencyManager","instanceKey"],"sources":["../src/utils/instance-keys.ts","../src/useBloc.ts","../src/useBlocActions.ts"],"sourcesContent":["/**\n * Instance key generation utilities for React integration\n */\n\nimport { generateIsolatedKey } from '@blac/core';\nimport type { ComponentRef } from '../types';\n\n/**\n * Generate an instance key for a bloc\n *\n * Logic:\n * - If user provides instanceId, use it (convert number to string)\n * - If isolated, generate or reuse a unique key for this component\n * - Otherwise, return undefined (use default key)\n *\n * @param componentRef - React component reference (persists across remounts)\n * @param isIsolated - Whether the bloc is isolated\n * @param providedId - User-provided instance ID (from options)\n * @returns Instance key string or undefined for default\n */\nexport function generateInstanceKey(\n componentRef: ComponentRef,\n isIsolated: boolean,\n providedId?: string | number,\n): string | undefined {\n // User explicitly provided an ID - use it\n if (providedId !== undefined) {\n return typeof providedId === 'number' ? String(providedId) : providedId;\n }\n\n // Isolated bloc - generate unique key per component\n if (isIsolated) {\n if (!componentRef.__blocInstanceId) {\n componentRef.__blocInstanceId = generateIsolatedKey();\n }\n return componentRef.__blocInstanceId;\n }\n\n // Shared bloc - use default key (undefined)\n return undefined;\n}\n","import {\n useMemo,\n useSyncExternalStore,\n useEffect,\n useRef,\n useReducer,\n} from 'react';\nimport {\n type BlocConstructor,\n StateContainer,\n type ExtractState,\n type AdapterState,\n ExternalDependencyManager,\n createAutoTrackSubscribe,\n createManualDepsSubscribe,\n createNoTrackSubscribe,\n createAutoTrackSnapshot,\n createManualDepsSnapshot,\n createNoTrackSnapshot,\n initAutoTrackState,\n initManualDepsState,\n initNoTrackState,\n disableGetterTracking,\n isIsolatedClass,\n} from '@blac/core';\nimport type { UseBlocOptions, UseBlocReturn, ComponentRef } from './types';\nimport { generateInstanceKey } from './utils/instance-keys';\n\ninterface TrackingMode {\n useManualDeps: boolean;\n autoTrackEnabled: boolean;\n}\n\nfunction determineTrackingMode<TBloc extends StateContainer<any, any>>(\n options?: UseBlocOptions<TBloc>,\n): TrackingMode {\n return {\n useManualDeps: options?.dependencies !== undefined,\n autoTrackEnabled: options?.autoTrack !== false,\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 S - Optional state type override for generic StateContainers (defaults to never)\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 isolated instance\n * ```ts\n * const [state, myBloc] = useBloc(MyBloc, {\n * instanceId: 'unique-id'\n * });\n * ```\n *\n * @example With generic bloc and explicit state type\n * ```ts\n * const [state, myBloc] = useBloc<MyStateType>(GenericBloc);\n * // state is typed as MyStateType\n * ```\n */\nexport function useBloc<\n S = never,\n T extends new (...args: any[]) => StateContainer<any, any> = new (\n ...args: any[]\n ) => StateContainer<any, any>,\n>(\n BlocClass: T & BlocConstructor<InstanceType<T>>,\n options?: UseBlocOptions<InstanceType<T>>,\n): UseBlocReturn<InstanceType<T>, S> {\n type TBloc = InstanceType<T>;\n const componentRef = useRef<ComponentRef>({});\n const isIsolated = isIsolatedClass(BlocClass);\n\n const initialPropsRef = useRef(options?.props);\n\n const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] =\n useMemo<\n readonly [\n TBloc,\n (callback: () => void) => () => void,\n () => ExtractState<TBloc>,\n string | undefined,\n AdapterState<TBloc>,\n TBloc,\n ]\n >(() => {\n const instanceKey = generateInstanceKey(\n componentRef.current,\n isIsolated,\n options?.instanceId,\n );\n\n const instance = BlocClass.resolve(instanceKey, initialPropsRef.current);\n\n if (initialPropsRef.current !== undefined) {\n instance.updateProps(initialPropsRef.current);\n }\n\n const { useManualDeps, autoTrackEnabled } =\n determineTrackingMode(options);\n\n let subscribeFn: (callback: () => void) => () => void;\n let getSnapshotFn: () => ExtractState<TBloc>;\n let adapterState: AdapterState<TBloc>;\n\n if (useManualDeps && options?.dependencies) {\n adapterState = initManualDepsState(instance);\n subscribeFn = createManualDepsSubscribe(instance, adapterState, {\n dependencies: options.dependencies,\n });\n getSnapshotFn = createManualDepsSnapshot(instance, adapterState, {\n dependencies: options.dependencies,\n });\n } else if (!autoTrackEnabled) {\n adapterState = initNoTrackState(instance);\n subscribeFn = createNoTrackSubscribe(instance);\n getSnapshotFn = createNoTrackSnapshot(instance);\n } else {\n adapterState = initAutoTrackState(instance);\n subscribeFn = createAutoTrackSubscribe(instance, adapterState);\n getSnapshotFn = createAutoTrackSnapshot(instance, adapterState);\n }\n\n return [\n adapterState.proxiedBloc!,\n subscribeFn,\n getSnapshotFn,\n instanceKey,\n adapterState,\n instance,\n ];\n }, [BlocClass, options?.instanceId]);\n\n const state = useSyncExternalStore(subscribe, getSnapshot);\n\n const [, forceUpdate] = useReducer((x: number) => x + 1, 0);\n\n const externalDepsManager = useRef(new ExternalDependencyManager());\n\n useEffect(() => {\n if (options?.props !== initialPropsRef.current) {\n rawInstance.updateProps(options?.props);\n }\n }, [options?.props, rawInstance]);\n\n useEffect(() => {\n disableGetterTracking(adapterState, rawInstance);\n externalDepsManager.current.updateSubscriptions(\n adapterState.getterTracker,\n rawInstance,\n forceUpdate,\n );\n });\n\n useEffect(() => {\n if (options?.onMount) {\n options.onMount(bloc);\n }\n\n return () => {\n externalDepsManager.current.cleanup();\n\n if (options?.onUnmount) {\n options.onUnmount(bloc);\n }\n\n BlocClass.release(instanceKey);\n\n if (isIsolated && !rawInstance.isDisposed) {\n rawInstance.dispose();\n }\n };\n }, []);\n\n return [state, bloc, componentRef] as UseBlocReturn<TBloc, S>;\n}\n","import { useMemo, useEffect, useRef } from 'react';\nimport {\n type BlocConstructor,\n type StateOverride,\n StateContainer,\n isIsolatedClass,\n} from '@blac/core';\nimport type { ComponentRef } from './types';\nimport { generateInstanceKey } from './utils/instance-keys';\n\n/**\n * Configuration options for useBlocActions hook\n * @template TBloc - The state container type\n * @template TProps - Props type passed to the container\n */\nexport interface UseBlocActionsOptions<TBloc, TProps = any> {\n /** Props passed to bloc constructor or updateProps */\n props?: TProps;\n /** Custom instance identifier for shared or isolated instances */\n instanceId?: string | number;\n /** Callback invoked when bloc instance mounts */\n onMount?: (bloc: TBloc) => void;\n /** Callback invoked when bloc instance unmounts */\n onUnmount?: (bloc: TBloc) => void;\n}\n\n/**\n * React hook that connects to a state container instance without triggering re-renders.\n * Use this when you only need to call actions on the bloc without subscribing to state changes.\n *\n * @template S - Optional state type override for generic StateContainers (defaults to never)\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 instance management and lifecycle\n * @returns The state container instance for calling actions\n *\n * @example Basic usage\n * ```ts\n * const myBloc = useBlocActions(MyBloc);\n * // Call methods on the bloc without re-rendering\n * myBloc.someMethod();\n * ```\n *\n * @example With isolated instance\n * ```ts\n * const myBloc = useBlocActions(MyBloc, {\n * instanceId: 'unique-id'\n * });\n * ```\n *\n * @example With generic bloc and explicit state type\n * ```ts\n * const myBloc = useBlocActions<MyStateType>(GenericBloc);\n * // myBloc.state is typed as MyStateType\n * ```\n */\nexport function useBlocActions<\n S = never,\n T extends new (...args: any[]) => StateContainer<any, any> = new (\n ...args: any[]\n ) => StateContainer<any, any>,\n>(\n BlocClass: T & BlocConstructor<InstanceType<T>>,\n options?: UseBlocActionsOptions<InstanceType<T>>,\n): StateOverride<InstanceType<T>, S> {\n const componentRef = useRef<ComponentRef>({});\n const initialPropsRef = useRef(options?.props);\n\n const [bloc, instanceKey] = useMemo(() => {\n const isIsolated = isIsolatedClass(BlocClass);\n\n const instanceKey = generateInstanceKey(\n componentRef.current,\n isIsolated,\n options?.instanceId,\n );\n\n const instance = BlocClass.resolve(instanceKey, initialPropsRef.current);\n\n return [instance, instanceKey] as const;\n }, [BlocClass]);\n\n useEffect(() => {\n if (options?.onMount) {\n options.onMount(bloc);\n }\n\n return () => {\n if (options?.onUnmount) {\n options.onUnmount(bloc);\n }\n\n BlocClass.release(instanceKey);\n\n if (isIsolatedClass(BlocClass) && !bloc.isDisposed) {\n bloc.dispose();\n }\n };\n }, []);\n\n return bloc as StateOverride<InstanceType<T>, S>;\n}\n"],"mappings":"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"} | ||
| {"version":3,"file":"index.cjs","names":["instanceKey","subscribeFn: (callback: () => void) => () => void","getSnapshotFn: () => ExtractState<TBloc>","adapterState: AdapterState<TBloc>","adapterState","ExternalDependencyManager","instanceKey"],"sources":["../src/utils/instance-keys.ts","../src/useBloc.ts","../src/useBlocActions.ts"],"sourcesContent":["/**\n * Instance key generation utilities for React integration\n */\n\nimport { generateIsolatedKey } from '@blac/core';\nimport type { ComponentRef } from '../types';\n\n/**\n * Generate an instance key for a bloc\n *\n * Logic:\n * - If user provides instanceId, use it (convert number to string)\n * - If isolated, generate or reuse a unique key for this component\n * - Otherwise, return undefined (use default key)\n *\n * @param componentRef - React component reference (persists across remounts)\n * @param isIsolated - Whether the bloc is isolated\n * @param providedId - User-provided instance ID (from options)\n * @returns Instance key string or undefined for default\n */\nexport function generateInstanceKey(\n componentRef: ComponentRef,\n isIsolated: boolean,\n providedId?: string | number,\n): string | undefined {\n // User explicitly provided an ID - use it\n if (providedId !== undefined) {\n return typeof providedId === 'number' ? String(providedId) : providedId;\n }\n\n // Isolated bloc - generate unique key per component\n if (isIsolated) {\n if (!componentRef.__blocInstanceId) {\n componentRef.__blocInstanceId = generateIsolatedKey();\n }\n return componentRef.__blocInstanceId;\n }\n\n // Shared bloc - use default key (undefined)\n return undefined;\n}\n","import {\n useMemo,\n useSyncExternalStore,\n useEffect,\n useRef,\n useReducer,\n} from 'react';\nimport {\n type ExtractState,\n type AdapterState,\n ExternalDependencyManager,\n createAutoTrackSubscribe,\n createManualDepsSubscribe,\n createNoTrackSubscribe,\n createAutoTrackSnapshot,\n createManualDepsSnapshot,\n createNoTrackSnapshot,\n initAutoTrackState,\n initManualDepsState,\n initNoTrackState,\n disableGetterTracking,\n isIsolatedClass,\n StateContainerConstructor,\n} from '@blac/core';\nimport type { UseBlocOptions, UseBlocReturn, ComponentRef } from './types';\nimport { generateInstanceKey } from './utils/instance-keys';\n\ninterface TrackingMode {\n useManualDeps: boolean;\n autoTrackEnabled: boolean;\n}\n\nfunction determineTrackingMode<TBloc extends StateContainerConstructor>(\n options?: UseBlocOptions<TBloc>,\n): TrackingMode {\n return {\n useManualDeps: options?.dependencies !== undefined,\n autoTrackEnabled: options?.autoTrack !== false,\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 S - Optional state type override for generic StateContainers (defaults to never)\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 isolated instance\n * ```ts\n * const [state, myBloc] = useBloc(MyBloc, {\n * instanceId: 'unique-id'\n * });\n * ```\n *\n * @example With generic bloc and explicit state type\n * ```ts\n * const [state, myBloc] = useBloc<MyStateType>(GenericBloc);\n * // state is typed as MyStateType\n * ```\n */\nexport function useBloc<\n T extends StateContainerConstructor<any, any> = StateContainerConstructor<\n any,\n any\n >,\n>(\n BlocClass: T,\n options?: UseBlocOptions<T>,\n): UseBlocReturn<T, ExtractState<T>> {\n type TBloc = InstanceType<T>;\n const componentRef = useRef<ComponentRef>({});\n const isIsolated = isIsolatedClass(BlocClass);\n\n const initialPropsRef = useRef(options?.props);\n\n const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] =\n useMemo<\n readonly [\n TBloc,\n (callback: () => void) => () => void,\n () => ExtractState<TBloc>,\n string | undefined,\n AdapterState<TBloc>,\n TBloc,\n ]\n >(() => {\n const instanceKey = generateInstanceKey(\n componentRef.current,\n isIsolated,\n options?.instanceId,\n );\n\n const instance = (BlocClass as any).resolve(instanceKey, {\n props: initialPropsRef.current,\n }) as TBloc;\n\n if (initialPropsRef.current !== undefined) {\n instance.updateProps(initialPropsRef.current);\n }\n\n const { useManualDeps, autoTrackEnabled } =\n determineTrackingMode(options);\n\n let subscribeFn: (callback: () => void) => () => void;\n let getSnapshotFn: () => ExtractState<TBloc>;\n let adapterState: AdapterState<TBloc>;\n\n if (useManualDeps && options?.dependencies) {\n adapterState = initManualDepsState(instance);\n subscribeFn = createManualDepsSubscribe<any>(instance, adapterState, {\n dependencies: options.dependencies,\n });\n getSnapshotFn = createManualDepsSnapshot<any>(instance, adapterState, {\n dependencies: options.dependencies,\n });\n } else if (!autoTrackEnabled) {\n adapterState = initNoTrackState(instance);\n subscribeFn = createNoTrackSubscribe(instance);\n getSnapshotFn = createNoTrackSnapshot(instance);\n } else {\n adapterState = initAutoTrackState(instance);\n subscribeFn = createAutoTrackSubscribe(instance, adapterState);\n getSnapshotFn = createAutoTrackSnapshot(instance, adapterState);\n }\n\n return [\n adapterState.proxiedBloc!,\n subscribeFn,\n getSnapshotFn,\n instanceKey,\n adapterState,\n instance,\n ];\n }, [BlocClass, options?.instanceId]);\n\n const state = useSyncExternalStore(subscribe, getSnapshot);\n\n const [, forceUpdate] = useReducer((x: number) => x + 1, 0);\n\n const externalDepsManager = useRef(new ExternalDependencyManager());\n\n useEffect(() => {\n if (options?.props !== initialPropsRef.current) {\n rawInstance.updateProps(options?.props);\n }\n }, [options?.props, rawInstance]);\n\n useEffect(() => {\n disableGetterTracking(adapterState, rawInstance);\n externalDepsManager.current.updateSubscriptions(\n adapterState.getterTracker,\n rawInstance,\n forceUpdate,\n );\n });\n\n useEffect(() => {\n if (options?.onMount) {\n options.onMount(bloc);\n }\n\n return () => {\n externalDepsManager.current.cleanup();\n\n if (options?.onUnmount) {\n options.onUnmount(bloc);\n }\n\n (BlocClass as any).release(instanceKey);\n\n if (isIsolated && !rawInstance.isDisposed) {\n rawInstance.dispose();\n }\n };\n }, []);\n\n return [state, bloc, componentRef] as UseBlocReturn<T, ExtractState<T>>;\n}\n","import { useMemo, useEffect, useRef } from 'react';\nimport { StateContainerConstructor, isIsolatedClass } from '@blac/core';\nimport type { ComponentRef } from './types';\nimport { generateInstanceKey } from './utils/instance-keys';\n\n/**\n * Configuration options for useBlocActions hook\n * @template TBloc - The state container type\n * @template TProps - Props type passed to the container\n */\nexport interface UseBlocActionsOptions<TBloc, TProps = any> {\n /** Props passed to bloc constructor or updateProps */\n props?: TProps;\n /** Custom instance identifier for shared or isolated instances */\n instanceId?: string | number;\n /** Callback invoked when bloc instance mounts */\n onMount?: (bloc: TBloc) => void;\n /** Callback invoked when bloc instance unmounts */\n onUnmount?: (bloc: TBloc) => void;\n}\n\n/**\n * React hook that connects to a state container instance without triggering re-renders.\n * Use this when you only need to call actions on the bloc without subscribing to state changes.\n *\n * @template S - Optional state type override for generic StateContainers (defaults to never)\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 instance management and lifecycle\n * @returns The state container instance for calling actions\n *\n * @example Basic usage\n * ```ts\n * const myBloc = useBlocActions(MyBloc);\n * // Call methods on the bloc without re-rendering\n * myBloc.someMethod();\n * ```\n *\n * @example With isolated instance\n * ```ts\n * const myBloc = useBlocActions(MyBloc, {\n * instanceId: 'unique-id'\n * });\n * ```\n *\n * @example With generic bloc and explicit state type\n * ```ts\n * const myBloc = useBlocActions<MyStateType>(GenericBloc);\n * // myBloc.state is typed as MyStateType\n * ```\n */\nexport function useBlocActions<\n T extends StateContainerConstructor = StateContainerConstructor,\n>(\n BlocClass: T,\n options?: UseBlocActionsOptions<InstanceType<T>>,\n): InstanceType<T> {\n const componentRef = useRef<ComponentRef>({});\n const initialPropsRef = useRef(options?.props);\n\n const [bloc, instanceKey] = useMemo(() => {\n const isIsolated = isIsolatedClass(BlocClass);\n\n const instanceKey = generateInstanceKey(\n componentRef.current,\n isIsolated,\n options?.instanceId,\n );\n\n const instance = (BlocClass as any).resolve(instanceKey, {\n props: initialPropsRef.current,\n });\n\n return [instance, instanceKey] as const;\n }, [BlocClass]);\n\n useEffect(() => {\n if (options?.onMount) {\n options.onMount(bloc);\n }\n\n return () => {\n if (options?.onUnmount) {\n options.onUnmount(bloc);\n }\n\n (BlocClass as any).release(instanceKey);\n\n if (isIsolatedClass(BlocClass) && !bloc.isDisposed) {\n bloc.dispose();\n }\n };\n }, []);\n\n return bloc as InstanceType<T>;\n}\n"],"mappings":"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"} |
+10
-18
@@ -1,6 +0,5 @@ | ||
| import { BlocConstructor } from '@blac/core'; | ||
| import type { ExtractState } from '@blac/core'; | ||
| import type { ExtractProps } from '@blac/core'; | ||
| import { ExtractState } from '@blac/core'; | ||
| import type { RefObject } from 'react'; | ||
| import { StateContainer } from '@blac/core'; | ||
| import { StateOverride } from '@blac/core'; | ||
| import { StateContainerConstructor } from '@blac/core'; | ||
@@ -48,3 +47,3 @@ /* Excluded from this release type: ComponentRef */ | ||
| */ | ||
| export declare function useBloc<S = never, T extends new (...args: any[]) => StateContainer<any, any> = new (...args: any[]) => StateContainer<any, any>>(BlocClass: T & BlocConstructor<InstanceType<T>>, options?: UseBlocOptions<InstanceType<T>>): UseBlocReturn<InstanceType<T>, S>; | ||
| export declare function useBloc<T extends StateContainerConstructor<any, any> = StateContainerConstructor<any, any>>(BlocClass: T, options?: UseBlocOptions<T>): UseBlocReturn<T, ExtractState<T>>; | ||
@@ -81,3 +80,3 @@ /** | ||
| */ | ||
| export declare function useBlocActions<S = never, T extends new (...args: any[]) => StateContainer<any, any> = new (...args: any[]) => StateContainer<any, any>>(BlocClass: T & BlocConstructor<InstanceType<T>>, options?: UseBlocActionsOptions<InstanceType<T>>): StateOverride<InstanceType<T>, S>; | ||
| export declare function useBlocActions<T extends StateContainerConstructor = StateContainerConstructor>(BlocClass: T, options?: UseBlocActionsOptions<InstanceType<T>>): InstanceType<T>; | ||
@@ -105,3 +104,3 @@ /** | ||
| */ | ||
| export declare interface UseBlocOptions<TBloc, TProps = any> { | ||
| export declare interface UseBlocOptions<TBloc extends StateContainerConstructor, TProps = ExtractProps<TBloc>> { | ||
| /** Props passed to bloc constructor or updateProps */ | ||
@@ -112,3 +111,3 @@ props?: TProps; | ||
| /** Manual dependency array like useEffect (disables autoTrack) */ | ||
| dependencies?: (state: ExtractState<TBloc>, bloc: TBloc) => unknown[]; | ||
| dependencies?: (state: ExtractState<TBloc>, bloc: InstanceType<TBloc>) => unknown[]; | ||
| /** Enable automatic property tracking via Proxy (default: true) */ | ||
@@ -119,5 +118,5 @@ autoTrack?: boolean; | ||
| /** Callback invoked when bloc instance mounts */ | ||
| onMount?: (bloc: TBloc) => void; | ||
| onMount?: (bloc: InstanceType<TBloc>) => void; | ||
| /** Callback invoked when bloc instance unmounts */ | ||
| onUnmount?: (bloc: TBloc) => void; | ||
| onUnmount?: (bloc: InstanceType<TBloc>) => void; | ||
| } | ||
@@ -132,12 +131,5 @@ | ||
| * @template TBloc - The state container type | ||
| * @template S - Optional state type override for generic StateContainers (defaults to never) | ||
| */ | ||
| export declare type UseBlocReturn<TBloc extends StateContainer<any, any>, S = never> = [ | ||
| [ | ||
| S | ||
| ] extends [never] ? ExtractState<TBloc> : S, | ||
| StateOverride<TBloc, S>, | ||
| RefObject<ComponentRef> | ||
| ]; | ||
| export declare type UseBlocReturn<TBloc extends StateContainerConstructor<any, any>, S = ExtractState<TBloc>> = [S, InstanceType<TBloc>, RefObject<ComponentRef>]; | ||
| export { } |
+10
-18
@@ -1,6 +0,5 @@ | ||
| import { BlocConstructor } from '@blac/core'; | ||
| import type { ExtractState } from '@blac/core'; | ||
| import type { ExtractProps } from '@blac/core'; | ||
| import { ExtractState } from '@blac/core'; | ||
| import type { RefObject } from 'react'; | ||
| import { StateContainer } from '@blac/core'; | ||
| import { StateOverride } from '@blac/core'; | ||
| import { StateContainerConstructor } from '@blac/core'; | ||
@@ -48,3 +47,3 @@ /* Excluded from this release type: ComponentRef */ | ||
| */ | ||
| export declare function useBloc<S = never, T extends new (...args: any[]) => StateContainer<any, any> = new (...args: any[]) => StateContainer<any, any>>(BlocClass: T & BlocConstructor<InstanceType<T>>, options?: UseBlocOptions<InstanceType<T>>): UseBlocReturn<InstanceType<T>, S>; | ||
| export declare function useBloc<T extends StateContainerConstructor<any, any> = StateContainerConstructor<any, any>>(BlocClass: T, options?: UseBlocOptions<T>): UseBlocReturn<T, ExtractState<T>>; | ||
@@ -81,3 +80,3 @@ /** | ||
| */ | ||
| export declare function useBlocActions<S = never, T extends new (...args: any[]) => StateContainer<any, any> = new (...args: any[]) => StateContainer<any, any>>(BlocClass: T & BlocConstructor<InstanceType<T>>, options?: UseBlocActionsOptions<InstanceType<T>>): StateOverride<InstanceType<T>, S>; | ||
| export declare function useBlocActions<T extends StateContainerConstructor = StateContainerConstructor>(BlocClass: T, options?: UseBlocActionsOptions<InstanceType<T>>): InstanceType<T>; | ||
@@ -105,3 +104,3 @@ /** | ||
| */ | ||
| export declare interface UseBlocOptions<TBloc, TProps = any> { | ||
| export declare interface UseBlocOptions<TBloc extends StateContainerConstructor, TProps = ExtractProps<TBloc>> { | ||
| /** Props passed to bloc constructor or updateProps */ | ||
@@ -112,3 +111,3 @@ props?: TProps; | ||
| /** Manual dependency array like useEffect (disables autoTrack) */ | ||
| dependencies?: (state: ExtractState<TBloc>, bloc: TBloc) => unknown[]; | ||
| dependencies?: (state: ExtractState<TBloc>, bloc: InstanceType<TBloc>) => unknown[]; | ||
| /** Enable automatic property tracking via Proxy (default: true) */ | ||
@@ -119,5 +118,5 @@ autoTrack?: boolean; | ||
| /** Callback invoked when bloc instance mounts */ | ||
| onMount?: (bloc: TBloc) => void; | ||
| onMount?: (bloc: InstanceType<TBloc>) => void; | ||
| /** Callback invoked when bloc instance unmounts */ | ||
| onUnmount?: (bloc: TBloc) => void; | ||
| onUnmount?: (bloc: InstanceType<TBloc>) => void; | ||
| } | ||
@@ -132,12 +131,5 @@ | ||
| * @template TBloc - The state container type | ||
| * @template S - Optional state type override for generic StateContainers (defaults to never) | ||
| */ | ||
| export declare type UseBlocReturn<TBloc extends StateContainer<any, any>, S = never> = [ | ||
| [ | ||
| S | ||
| ] extends [never] ? ExtractState<TBloc> : S, | ||
| StateOverride<TBloc, S>, | ||
| RefObject<ComponentRef> | ||
| ]; | ||
| export declare type UseBlocReturn<TBloc extends StateContainerConstructor<any, any>, S = ExtractState<TBloc>> = [S, InstanceType<TBloc>, RefObject<ComponentRef>]; | ||
| export { } |
+2
-2
@@ -82,3 +82,3 @@ import { useEffect, useMemo, useReducer, useRef, useSyncExternalStore } from "react"; | ||
| const instanceKey$1 = generateInstanceKey(componentRef.current, isIsolated, options?.instanceId); | ||
| const instance = BlocClass.resolve(instanceKey$1, initialPropsRef.current); | ||
| const instance = BlocClass.resolve(instanceKey$1, { props: initialPropsRef.current }); | ||
| if (initialPropsRef.current !== void 0) instance.updateProps(initialPropsRef.current); | ||
@@ -175,3 +175,3 @@ const { useManualDeps, autoTrackEnabled } = determineTrackingMode(options); | ||
| const instanceKey$1 = generateInstanceKey(componentRef.current, isIsolated, options?.instanceId); | ||
| return [BlocClass.resolve(instanceKey$1, initialPropsRef.current), instanceKey$1]; | ||
| return [BlocClass.resolve(instanceKey$1, { props: initialPropsRef.current }), instanceKey$1]; | ||
| }, [BlocClass]); | ||
@@ -178,0 +178,0 @@ useEffect(() => { |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.js","names":["instanceKey","subscribeFn: (callback: () => void) => () => void","getSnapshotFn: () => ExtractState<TBloc>","adapterState: AdapterState<TBloc>","adapterState","instanceKey"],"sources":["../src/utils/instance-keys.ts","../src/useBloc.ts","../src/useBlocActions.ts"],"sourcesContent":["/**\n * Instance key generation utilities for React integration\n */\n\nimport { generateIsolatedKey } from '@blac/core';\nimport type { ComponentRef } from '../types';\n\n/**\n * Generate an instance key for a bloc\n *\n * Logic:\n * - If user provides instanceId, use it (convert number to string)\n * - If isolated, generate or reuse a unique key for this component\n * - Otherwise, return undefined (use default key)\n *\n * @param componentRef - React component reference (persists across remounts)\n * @param isIsolated - Whether the bloc is isolated\n * @param providedId - User-provided instance ID (from options)\n * @returns Instance key string or undefined for default\n */\nexport function generateInstanceKey(\n componentRef: ComponentRef,\n isIsolated: boolean,\n providedId?: string | number,\n): string | undefined {\n // User explicitly provided an ID - use it\n if (providedId !== undefined) {\n return typeof providedId === 'number' ? String(providedId) : providedId;\n }\n\n // Isolated bloc - generate unique key per component\n if (isIsolated) {\n if (!componentRef.__blocInstanceId) {\n componentRef.__blocInstanceId = generateIsolatedKey();\n }\n return componentRef.__blocInstanceId;\n }\n\n // Shared bloc - use default key (undefined)\n return undefined;\n}\n","import {\n useMemo,\n useSyncExternalStore,\n useEffect,\n useRef,\n useReducer,\n} from 'react';\nimport {\n type BlocConstructor,\n StateContainer,\n type ExtractState,\n type AdapterState,\n ExternalDependencyManager,\n createAutoTrackSubscribe,\n createManualDepsSubscribe,\n createNoTrackSubscribe,\n createAutoTrackSnapshot,\n createManualDepsSnapshot,\n createNoTrackSnapshot,\n initAutoTrackState,\n initManualDepsState,\n initNoTrackState,\n disableGetterTracking,\n isIsolatedClass,\n} from '@blac/core';\nimport type { UseBlocOptions, UseBlocReturn, ComponentRef } from './types';\nimport { generateInstanceKey } from './utils/instance-keys';\n\ninterface TrackingMode {\n useManualDeps: boolean;\n autoTrackEnabled: boolean;\n}\n\nfunction determineTrackingMode<TBloc extends StateContainer<any, any>>(\n options?: UseBlocOptions<TBloc>,\n): TrackingMode {\n return {\n useManualDeps: options?.dependencies !== undefined,\n autoTrackEnabled: options?.autoTrack !== false,\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 S - Optional state type override for generic StateContainers (defaults to never)\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 isolated instance\n * ```ts\n * const [state, myBloc] = useBloc(MyBloc, {\n * instanceId: 'unique-id'\n * });\n * ```\n *\n * @example With generic bloc and explicit state type\n * ```ts\n * const [state, myBloc] = useBloc<MyStateType>(GenericBloc);\n * // state is typed as MyStateType\n * ```\n */\nexport function useBloc<\n S = never,\n T extends new (...args: any[]) => StateContainer<any, any> = new (\n ...args: any[]\n ) => StateContainer<any, any>,\n>(\n BlocClass: T & BlocConstructor<InstanceType<T>>,\n options?: UseBlocOptions<InstanceType<T>>,\n): UseBlocReturn<InstanceType<T>, S> {\n type TBloc = InstanceType<T>;\n const componentRef = useRef<ComponentRef>({});\n const isIsolated = isIsolatedClass(BlocClass);\n\n const initialPropsRef = useRef(options?.props);\n\n const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] =\n useMemo<\n readonly [\n TBloc,\n (callback: () => void) => () => void,\n () => ExtractState<TBloc>,\n string | undefined,\n AdapterState<TBloc>,\n TBloc,\n ]\n >(() => {\n const instanceKey = generateInstanceKey(\n componentRef.current,\n isIsolated,\n options?.instanceId,\n );\n\n const instance = BlocClass.resolve(instanceKey, initialPropsRef.current);\n\n if (initialPropsRef.current !== undefined) {\n instance.updateProps(initialPropsRef.current);\n }\n\n const { useManualDeps, autoTrackEnabled } =\n determineTrackingMode(options);\n\n let subscribeFn: (callback: () => void) => () => void;\n let getSnapshotFn: () => ExtractState<TBloc>;\n let adapterState: AdapterState<TBloc>;\n\n if (useManualDeps && options?.dependencies) {\n adapterState = initManualDepsState(instance);\n subscribeFn = createManualDepsSubscribe(instance, adapterState, {\n dependencies: options.dependencies,\n });\n getSnapshotFn = createManualDepsSnapshot(instance, adapterState, {\n dependencies: options.dependencies,\n });\n } else if (!autoTrackEnabled) {\n adapterState = initNoTrackState(instance);\n subscribeFn = createNoTrackSubscribe(instance);\n getSnapshotFn = createNoTrackSnapshot(instance);\n } else {\n adapterState = initAutoTrackState(instance);\n subscribeFn = createAutoTrackSubscribe(instance, adapterState);\n getSnapshotFn = createAutoTrackSnapshot(instance, adapterState);\n }\n\n return [\n adapterState.proxiedBloc!,\n subscribeFn,\n getSnapshotFn,\n instanceKey,\n adapterState,\n instance,\n ];\n }, [BlocClass, options?.instanceId]);\n\n const state = useSyncExternalStore(subscribe, getSnapshot);\n\n const [, forceUpdate] = useReducer((x: number) => x + 1, 0);\n\n const externalDepsManager = useRef(new ExternalDependencyManager());\n\n useEffect(() => {\n if (options?.props !== initialPropsRef.current) {\n rawInstance.updateProps(options?.props);\n }\n }, [options?.props, rawInstance]);\n\n useEffect(() => {\n disableGetterTracking(adapterState, rawInstance);\n externalDepsManager.current.updateSubscriptions(\n adapterState.getterTracker,\n rawInstance,\n forceUpdate,\n );\n });\n\n useEffect(() => {\n if (options?.onMount) {\n options.onMount(bloc);\n }\n\n return () => {\n externalDepsManager.current.cleanup();\n\n if (options?.onUnmount) {\n options.onUnmount(bloc);\n }\n\n BlocClass.release(instanceKey);\n\n if (isIsolated && !rawInstance.isDisposed) {\n rawInstance.dispose();\n }\n };\n }, []);\n\n return [state, bloc, componentRef] as UseBlocReturn<TBloc, S>;\n}\n","import { useMemo, useEffect, useRef } from 'react';\nimport {\n type BlocConstructor,\n type StateOverride,\n StateContainer,\n isIsolatedClass,\n} from '@blac/core';\nimport type { ComponentRef } from './types';\nimport { generateInstanceKey } from './utils/instance-keys';\n\n/**\n * Configuration options for useBlocActions hook\n * @template TBloc - The state container type\n * @template TProps - Props type passed to the container\n */\nexport interface UseBlocActionsOptions<TBloc, TProps = any> {\n /** Props passed to bloc constructor or updateProps */\n props?: TProps;\n /** Custom instance identifier for shared or isolated instances */\n instanceId?: string | number;\n /** Callback invoked when bloc instance mounts */\n onMount?: (bloc: TBloc) => void;\n /** Callback invoked when bloc instance unmounts */\n onUnmount?: (bloc: TBloc) => void;\n}\n\n/**\n * React hook that connects to a state container instance without triggering re-renders.\n * Use this when you only need to call actions on the bloc without subscribing to state changes.\n *\n * @template S - Optional state type override for generic StateContainers (defaults to never)\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 instance management and lifecycle\n * @returns The state container instance for calling actions\n *\n * @example Basic usage\n * ```ts\n * const myBloc = useBlocActions(MyBloc);\n * // Call methods on the bloc without re-rendering\n * myBloc.someMethod();\n * ```\n *\n * @example With isolated instance\n * ```ts\n * const myBloc = useBlocActions(MyBloc, {\n * instanceId: 'unique-id'\n * });\n * ```\n *\n * @example With generic bloc and explicit state type\n * ```ts\n * const myBloc = useBlocActions<MyStateType>(GenericBloc);\n * // myBloc.state is typed as MyStateType\n * ```\n */\nexport function useBlocActions<\n S = never,\n T extends new (...args: any[]) => StateContainer<any, any> = new (\n ...args: any[]\n ) => StateContainer<any, any>,\n>(\n BlocClass: T & BlocConstructor<InstanceType<T>>,\n options?: UseBlocActionsOptions<InstanceType<T>>,\n): StateOverride<InstanceType<T>, S> {\n const componentRef = useRef<ComponentRef>({});\n const initialPropsRef = useRef(options?.props);\n\n const [bloc, instanceKey] = useMemo(() => {\n const isIsolated = isIsolatedClass(BlocClass);\n\n const instanceKey = generateInstanceKey(\n componentRef.current,\n isIsolated,\n options?.instanceId,\n );\n\n const instance = BlocClass.resolve(instanceKey, initialPropsRef.current);\n\n return [instance, instanceKey] as const;\n }, [BlocClass]);\n\n useEffect(() => {\n if (options?.onMount) {\n options.onMount(bloc);\n }\n\n return () => {\n if (options?.onUnmount) {\n options.onUnmount(bloc);\n }\n\n BlocClass.release(instanceKey);\n\n if (isIsolatedClass(BlocClass) && !bloc.isDisposed) {\n bloc.dispose();\n }\n };\n }, []);\n\n return bloc as StateOverride<InstanceType<T>, S>;\n}\n"],"mappings":"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"} | ||
| {"version":3,"file":"index.js","names":["instanceKey","subscribeFn: (callback: () => void) => () => void","getSnapshotFn: () => ExtractState<TBloc>","adapterState: AdapterState<TBloc>","adapterState","instanceKey"],"sources":["../src/utils/instance-keys.ts","../src/useBloc.ts","../src/useBlocActions.ts"],"sourcesContent":["/**\n * Instance key generation utilities for React integration\n */\n\nimport { generateIsolatedKey } from '@blac/core';\nimport type { ComponentRef } from '../types';\n\n/**\n * Generate an instance key for a bloc\n *\n * Logic:\n * - If user provides instanceId, use it (convert number to string)\n * - If isolated, generate or reuse a unique key for this component\n * - Otherwise, return undefined (use default key)\n *\n * @param componentRef - React component reference (persists across remounts)\n * @param isIsolated - Whether the bloc is isolated\n * @param providedId - User-provided instance ID (from options)\n * @returns Instance key string or undefined for default\n */\nexport function generateInstanceKey(\n componentRef: ComponentRef,\n isIsolated: boolean,\n providedId?: string | number,\n): string | undefined {\n // User explicitly provided an ID - use it\n if (providedId !== undefined) {\n return typeof providedId === 'number' ? String(providedId) : providedId;\n }\n\n // Isolated bloc - generate unique key per component\n if (isIsolated) {\n if (!componentRef.__blocInstanceId) {\n componentRef.__blocInstanceId = generateIsolatedKey();\n }\n return componentRef.__blocInstanceId;\n }\n\n // Shared bloc - use default key (undefined)\n return undefined;\n}\n","import {\n useMemo,\n useSyncExternalStore,\n useEffect,\n useRef,\n useReducer,\n} from 'react';\nimport {\n type ExtractState,\n type AdapterState,\n ExternalDependencyManager,\n createAutoTrackSubscribe,\n createManualDepsSubscribe,\n createNoTrackSubscribe,\n createAutoTrackSnapshot,\n createManualDepsSnapshot,\n createNoTrackSnapshot,\n initAutoTrackState,\n initManualDepsState,\n initNoTrackState,\n disableGetterTracking,\n isIsolatedClass,\n StateContainerConstructor,\n} from '@blac/core';\nimport type { UseBlocOptions, UseBlocReturn, ComponentRef } from './types';\nimport { generateInstanceKey } from './utils/instance-keys';\n\ninterface TrackingMode {\n useManualDeps: boolean;\n autoTrackEnabled: boolean;\n}\n\nfunction determineTrackingMode<TBloc extends StateContainerConstructor>(\n options?: UseBlocOptions<TBloc>,\n): TrackingMode {\n return {\n useManualDeps: options?.dependencies !== undefined,\n autoTrackEnabled: options?.autoTrack !== false,\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 S - Optional state type override for generic StateContainers (defaults to never)\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 isolated instance\n * ```ts\n * const [state, myBloc] = useBloc(MyBloc, {\n * instanceId: 'unique-id'\n * });\n * ```\n *\n * @example With generic bloc and explicit state type\n * ```ts\n * const [state, myBloc] = useBloc<MyStateType>(GenericBloc);\n * // state is typed as MyStateType\n * ```\n */\nexport function useBloc<\n T extends StateContainerConstructor<any, any> = StateContainerConstructor<\n any,\n any\n >,\n>(\n BlocClass: T,\n options?: UseBlocOptions<T>,\n): UseBlocReturn<T, ExtractState<T>> {\n type TBloc = InstanceType<T>;\n const componentRef = useRef<ComponentRef>({});\n const isIsolated = isIsolatedClass(BlocClass);\n\n const initialPropsRef = useRef(options?.props);\n\n const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] =\n useMemo<\n readonly [\n TBloc,\n (callback: () => void) => () => void,\n () => ExtractState<TBloc>,\n string | undefined,\n AdapterState<TBloc>,\n TBloc,\n ]\n >(() => {\n const instanceKey = generateInstanceKey(\n componentRef.current,\n isIsolated,\n options?.instanceId,\n );\n\n const instance = (BlocClass as any).resolve(instanceKey, {\n props: initialPropsRef.current,\n }) as TBloc;\n\n if (initialPropsRef.current !== undefined) {\n instance.updateProps(initialPropsRef.current);\n }\n\n const { useManualDeps, autoTrackEnabled } =\n determineTrackingMode(options);\n\n let subscribeFn: (callback: () => void) => () => void;\n let getSnapshotFn: () => ExtractState<TBloc>;\n let adapterState: AdapterState<TBloc>;\n\n if (useManualDeps && options?.dependencies) {\n adapterState = initManualDepsState(instance);\n subscribeFn = createManualDepsSubscribe<any>(instance, adapterState, {\n dependencies: options.dependencies,\n });\n getSnapshotFn = createManualDepsSnapshot<any>(instance, adapterState, {\n dependencies: options.dependencies,\n });\n } else if (!autoTrackEnabled) {\n adapterState = initNoTrackState(instance);\n subscribeFn = createNoTrackSubscribe(instance);\n getSnapshotFn = createNoTrackSnapshot(instance);\n } else {\n adapterState = initAutoTrackState(instance);\n subscribeFn = createAutoTrackSubscribe(instance, adapterState);\n getSnapshotFn = createAutoTrackSnapshot(instance, adapterState);\n }\n\n return [\n adapterState.proxiedBloc!,\n subscribeFn,\n getSnapshotFn,\n instanceKey,\n adapterState,\n instance,\n ];\n }, [BlocClass, options?.instanceId]);\n\n const state = useSyncExternalStore(subscribe, getSnapshot);\n\n const [, forceUpdate] = useReducer((x: number) => x + 1, 0);\n\n const externalDepsManager = useRef(new ExternalDependencyManager());\n\n useEffect(() => {\n if (options?.props !== initialPropsRef.current) {\n rawInstance.updateProps(options?.props);\n }\n }, [options?.props, rawInstance]);\n\n useEffect(() => {\n disableGetterTracking(adapterState, rawInstance);\n externalDepsManager.current.updateSubscriptions(\n adapterState.getterTracker,\n rawInstance,\n forceUpdate,\n );\n });\n\n useEffect(() => {\n if (options?.onMount) {\n options.onMount(bloc);\n }\n\n return () => {\n externalDepsManager.current.cleanup();\n\n if (options?.onUnmount) {\n options.onUnmount(bloc);\n }\n\n (BlocClass as any).release(instanceKey);\n\n if (isIsolated && !rawInstance.isDisposed) {\n rawInstance.dispose();\n }\n };\n }, []);\n\n return [state, bloc, componentRef] as UseBlocReturn<T, ExtractState<T>>;\n}\n","import { useMemo, useEffect, useRef } from 'react';\nimport { StateContainerConstructor, isIsolatedClass } from '@blac/core';\nimport type { ComponentRef } from './types';\nimport { generateInstanceKey } from './utils/instance-keys';\n\n/**\n * Configuration options for useBlocActions hook\n * @template TBloc - The state container type\n * @template TProps - Props type passed to the container\n */\nexport interface UseBlocActionsOptions<TBloc, TProps = any> {\n /** Props passed to bloc constructor or updateProps */\n props?: TProps;\n /** Custom instance identifier for shared or isolated instances */\n instanceId?: string | number;\n /** Callback invoked when bloc instance mounts */\n onMount?: (bloc: TBloc) => void;\n /** Callback invoked when bloc instance unmounts */\n onUnmount?: (bloc: TBloc) => void;\n}\n\n/**\n * React hook that connects to a state container instance without triggering re-renders.\n * Use this when you only need to call actions on the bloc without subscribing to state changes.\n *\n * @template S - Optional state type override for generic StateContainers (defaults to never)\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 instance management and lifecycle\n * @returns The state container instance for calling actions\n *\n * @example Basic usage\n * ```ts\n * const myBloc = useBlocActions(MyBloc);\n * // Call methods on the bloc without re-rendering\n * myBloc.someMethod();\n * ```\n *\n * @example With isolated instance\n * ```ts\n * const myBloc = useBlocActions(MyBloc, {\n * instanceId: 'unique-id'\n * });\n * ```\n *\n * @example With generic bloc and explicit state type\n * ```ts\n * const myBloc = useBlocActions<MyStateType>(GenericBloc);\n * // myBloc.state is typed as MyStateType\n * ```\n */\nexport function useBlocActions<\n T extends StateContainerConstructor = StateContainerConstructor,\n>(\n BlocClass: T,\n options?: UseBlocActionsOptions<InstanceType<T>>,\n): InstanceType<T> {\n const componentRef = useRef<ComponentRef>({});\n const initialPropsRef = useRef(options?.props);\n\n const [bloc, instanceKey] = useMemo(() => {\n const isIsolated = isIsolatedClass(BlocClass);\n\n const instanceKey = generateInstanceKey(\n componentRef.current,\n isIsolated,\n options?.instanceId,\n );\n\n const instance = (BlocClass as any).resolve(instanceKey, {\n props: initialPropsRef.current,\n });\n\n return [instance, instanceKey] as const;\n }, [BlocClass]);\n\n useEffect(() => {\n if (options?.onMount) {\n options.onMount(bloc);\n }\n\n return () => {\n if (options?.onUnmount) {\n options.onUnmount(bloc);\n }\n\n (BlocClass as any).release(instanceKey);\n\n if (isIsolatedClass(BlocClass) && !bloc.isDisposed) {\n bloc.dispose();\n }\n };\n }, []);\n\n return bloc as InstanceType<T>;\n}\n"],"mappings":"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"} |
+3
-3
| { | ||
| "name": "@blac/react", | ||
| "version": "2.0.0-rc.13", | ||
| "version": "2.0.0-rc.14", | ||
| "license": "MIT", | ||
@@ -52,3 +52,3 @@ "author": "Brendan Mullins <jsnanigans@gmail.com>", | ||
| "react": "^18.0.0 || ^19.0.0", | ||
| "@blac/core": "2.0.0-rc.13" | ||
| "@blac/core": "2.0.0-rc.14" | ||
| }, | ||
@@ -82,3 +82,3 @@ "peerDependenciesMeta": { | ||
| "vitest": "3.2.4", | ||
| "@blac/core": "2.0.0-rc.13" | ||
| "@blac/core": "2.0.0-rc.14" | ||
| }, | ||
@@ -85,0 +85,0 @@ "scripts": { |
54174
-1.33%492
-1.8%