@blac/preact
Advanced tools
+24
-110
| let preact_hooks = require("preact/hooks"); | ||
| let preact_compat = require("preact/compat"); | ||
| let _blac_core = require("@blac/core"); | ||
| let _blac_adapter = require("@blac/adapter"); | ||
@@ -25,3 +25,3 @@ //#region src/utils/instance-keys.ts | ||
| if (isIsolated) { | ||
| if (!componentRef.__blocInstanceId) componentRef.__blocInstanceId = (0, _blac_core.generateIsolatedKey)(); | ||
| if (!componentRef.__blocInstanceId) componentRef.__blocInstanceId = (0, _blac_adapter.generateIsolatedKey)(); | ||
| return componentRef.__blocInstanceId; | ||
@@ -74,4 +74,4 @@ } | ||
| function determineTrackingMode(options) { | ||
| const globalConfig$1 = getBlacPreactConfig(); | ||
| const autoTrackEnabled = options?.autoTrack !== void 0 ? options.autoTrack : globalConfig$1.autoTrack; | ||
| const globalConfig = getBlacPreactConfig(); | ||
| const autoTrackEnabled = options?.autoTrack !== void 0 ? options.autoTrack : globalConfig.autoTrack; | ||
| return { | ||
@@ -90,7 +90,4 @@ useManualDeps: options?.dependencies !== void 0, | ||
| * | ||
| * **Note:** This hook does NOT support stateless containers (StatelessCubit, StatelessVertex). | ||
| * Use `useBlocActions()` instead for stateless containers. | ||
| * | ||
| * @template T - The state container constructor type (inferred from BlocClass) | ||
| * @param BlocClass - The state container class to connect to (must not be stateless) | ||
| * @param BlocClass - The state container class to connect to | ||
| * @param options - Configuration options for tracking mode and instance management | ||
@@ -120,44 +117,29 @@ * @returns Tuple with [state, bloc instance, ref] | ||
| const componentRef = (0, preact_hooks.useRef)({}); | ||
| const initialPropsRef = (0, preact_hooks.useRef)(options?.props); | ||
| const isStateless = (0, _blac_core.isStatelessClass)(BlocClass); | ||
| const isIsolated = (0, _blac_core.isIsolatedClass)(BlocClass); | ||
| const isIsolated = (0, _blac_adapter.isIsolatedClass)(BlocClass); | ||
| const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] = (0, preact_hooks.useMemo)(() => { | ||
| if (isStateless) { | ||
| console.error(`[BlaC] useBloc() does not support stateless containers. "${BlocClass.name}" is a StatelessCubit or StatelessVertex. Use useBlocActions() instead for stateless containers.`); | ||
| const instance$1 = (0, _blac_core.acquire)(BlocClass, "default"); | ||
| return [ | ||
| instance$1, | ||
| () => () => {}, | ||
| () => ({}), | ||
| void 0, | ||
| null, | ||
| instance$1 | ||
| ]; | ||
| } | ||
| const instanceKey$1 = generateInstanceKey(componentRef.current, isIsolated, options?.instanceId); | ||
| const instance = (0, _blac_core.acquire)(BlocClass, instanceKey$1, { props: initialPropsRef.current }); | ||
| if (initialPropsRef.current !== void 0) instance.updateProps(initialPropsRef.current); | ||
| const instanceKey = generateInstanceKey(componentRef.current, isIsolated, options?.instanceId); | ||
| const instance = (0, _blac_adapter.acquire)(BlocClass, instanceKey); | ||
| const { useManualDeps, autoTrackEnabled } = determineTrackingMode(options); | ||
| let subscribeFn; | ||
| let getSnapshotFn; | ||
| let adapterState$1; | ||
| let adapterState; | ||
| if (useManualDeps && options?.dependencies) { | ||
| adapterState$1 = (0, _blac_core.manualDepsInit)(instance); | ||
| subscribeFn = (0, _blac_core.manualDepsSubscribe)(instance, adapterState$1, { dependencies: options.dependencies }); | ||
| getSnapshotFn = (0, _blac_core.manualDepsSnapshot)(instance, adapterState$1, { dependencies: options.dependencies }); | ||
| adapterState = (0, _blac_adapter.manualDepsInit)(instance); | ||
| subscribeFn = (0, _blac_adapter.manualDepsSubscribe)(instance, adapterState, { dependencies: options.dependencies }); | ||
| getSnapshotFn = (0, _blac_adapter.manualDepsSnapshot)(instance, adapterState, { dependencies: options.dependencies }); | ||
| } else if (!autoTrackEnabled) { | ||
| adapterState$1 = (0, _blac_core.noTrackInit)(instance); | ||
| subscribeFn = (0, _blac_core.noTrackSubscribe)(instance); | ||
| getSnapshotFn = (0, _blac_core.noTrackSnapshot)(instance); | ||
| adapterState = (0, _blac_adapter.noTrackInit)(instance); | ||
| subscribeFn = (0, _blac_adapter.noTrackSubscribe)(instance); | ||
| getSnapshotFn = (0, _blac_adapter.noTrackSnapshot)(instance); | ||
| } else { | ||
| adapterState$1 = (0, _blac_core.autoTrackInit)(instance); | ||
| subscribeFn = (0, _blac_core.autoTrackSubscribe)(instance, adapterState$1); | ||
| getSnapshotFn = (0, _blac_core.autoTrackSnapshot)(instance, adapterState$1); | ||
| adapterState = (0, _blac_adapter.autoTrackInit)(instance); | ||
| subscribeFn = (0, _blac_adapter.autoTrackSubscribe)(instance, adapterState); | ||
| getSnapshotFn = (0, _blac_adapter.autoTrackSnapshot)(instance, adapterState); | ||
| } | ||
| return [ | ||
| adapterState$1.proxiedBloc, | ||
| adapterState.proxiedBloc, | ||
| subscribeFn, | ||
| getSnapshotFn, | ||
| instanceKey$1, | ||
| adapterState$1, | ||
| instanceKey, | ||
| adapterState, | ||
| instance | ||
@@ -167,3 +149,2 @@ ]; | ||
| BlocClass, | ||
| isStateless, | ||
| isIsolated, | ||
@@ -174,18 +155,8 @@ options?.instanceId | ||
| const [, forceUpdate] = (0, preact_hooks.useReducer)((x) => x + 1, 0); | ||
| const externalDepsManager = (0, preact_hooks.useRef)(new _blac_core.ExternalDepsManager()); | ||
| const externalDepsManager = (0, preact_hooks.useRef)(new _blac_adapter.ExternalDepsManager()); | ||
| (0, preact_hooks.useEffect)(() => { | ||
| if (isStateless) return; | ||
| if (options?.props !== initialPropsRef.current) rawInstance.updateProps(options?.props); | ||
| }, [ | ||
| options?.props, | ||
| rawInstance, | ||
| isStateless | ||
| ]); | ||
| (0, preact_hooks.useEffect)(() => { | ||
| if (isStateless || !adapterState) return; | ||
| (0, _blac_core.disableGetterTracking)(adapterState, rawInstance); | ||
| (0, _blac_adapter.disableGetterTracking)(adapterState, rawInstance); | ||
| externalDepsManager.current.updateSubscriptions(adapterState.getterState, rawInstance, () => forceUpdate(0)); | ||
| }); | ||
| (0, preact_hooks.useEffect)(() => { | ||
| if (isStateless) return; | ||
| if (options?.onMount) options.onMount(bloc); | ||
@@ -195,3 +166,3 @@ return () => { | ||
| if (options?.onUnmount) options.onUnmount(bloc); | ||
| (0, _blac_core.release)(BlocClass, instanceKey); | ||
| (0, _blac_adapter.release)(BlocClass, instanceKey); | ||
| if (isIsolated && !rawInstance.isDisposed) rawInstance.dispose(); | ||
@@ -208,62 +179,5 @@ }; | ||
| //#endregion | ||
| //#region src/useBlocActions.ts | ||
| /** | ||
| * Preact hook that connects to a state container instance without triggering re-renders. | ||
| * Use this when you only need to call actions on the bloc without subscribing to state changes. | ||
| * | ||
| * **This is the recommended hook for stateless containers** (StatelessCubit, StatelessVertex). | ||
| * It also works with regular Cubit/Vertex when you don't need state subscriptions. | ||
| * | ||
| * @template T - The state container constructor type (inferred from BlocClass) | ||
| * @param BlocClass - The state container class to connect to (supports both stateful and stateless) | ||
| * @param options - Configuration options for instance management and lifecycle | ||
| * @returns The state container instance for calling actions | ||
| * | ||
| * @example Basic usage with stateless container | ||
| * ```ts | ||
| * class AnalyticsService extends StatelessCubit { | ||
| * trackEvent(name: string) { ... } | ||
| * } | ||
| * | ||
| * const analytics = useBlocActions(AnalyticsService); | ||
| * analytics.trackEvent('page_view'); | ||
| * ``` | ||
| * | ||
| * @example With stateful container (no re-renders) | ||
| * ```ts | ||
| * const myBloc = useBlocActions(MyBloc); | ||
| * myBloc.someMethod(); // Won't cause re-renders | ||
| * ``` | ||
| * | ||
| * @example With isolated instance | ||
| * ```ts | ||
| * const myBloc = useBlocActions(MyBloc, { | ||
| * instanceId: 'unique-id' | ||
| * }); | ||
| * ``` | ||
| */ | ||
| function useBlocActions(BlocClass, options) { | ||
| const componentRef = (0, preact_hooks.useRef)({}); | ||
| const initialPropsRef = (0, preact_hooks.useRef)(options?.props); | ||
| const [bloc, instanceKey] = (0, preact_hooks.useMemo)(() => { | ||
| const isIsolated = (0, _blac_core.isIsolatedClass)(BlocClass); | ||
| const instanceKey$1 = generateInstanceKey(componentRef.current, isIsolated, options?.instanceId); | ||
| return [(0, _blac_core.acquire)(BlocClass, instanceKey$1, { props: initialPropsRef.current }), instanceKey$1]; | ||
| }, [BlocClass]); | ||
| (0, preact_hooks.useEffect)(() => { | ||
| if (options?.onMount) options.onMount(bloc); | ||
| return () => { | ||
| if (options?.onUnmount) options.onUnmount(bloc); | ||
| (0, _blac_core.release)(BlocClass, instanceKey); | ||
| if ((0, _blac_core.isIsolatedClass)(BlocClass) && !bloc.isDisposed) bloc.dispose(); | ||
| }; | ||
| }, []); | ||
| return bloc; | ||
| } | ||
| //#endregion | ||
| exports.configureBlacPreact = configureBlacPreact; | ||
| exports.resetBlacPreactConfig = resetBlacPreactConfig; | ||
| exports.useBloc = useBloc; | ||
| exports.useBlocActions = useBlocActions; | ||
| //# sourceMappingURL=index.cjs.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.cjs","names":["globalConfig","instance","instanceKey","adapterState","ExternalDepsManager","instanceKey"],"sources":["../src/utils/instance-keys.ts","../src/config.ts","../src/useBloc.ts","../src/useBlocActions.ts"],"sourcesContent":["/**\n * Instance key generation utilities for Preact 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 - Preact 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","/**\n * Global configuration for @blac/preact\n */\n\nexport interface BlacPreactConfig {\n /** Enable automatic property tracking via Proxy (default: true) */\n autoTrack: boolean;\n}\n\nconst defaultConfig: BlacPreactConfig = {\n autoTrack: true,\n};\n\nlet globalConfig: BlacPreactConfig = { ...defaultConfig };\n\n/**\n * Configure global defaults for @blac/preact hooks.\n *\n * @example\n * ```ts\n * import { configureBlacPreact } from '@blac/preact';\n *\n * // Disable auto-tracking globally\n * configureBlacPreact({\n * autoTrack: false\n * });\n * ```\n *\n * @param config - Partial configuration to merge with defaults\n */\nexport function configureBlacPreact(config: Partial<BlacPreactConfig>): void {\n globalConfig = { ...globalConfig, ...config };\n}\n\n/**\n * Get the current global configuration.\n * @internal\n */\nexport function getBlacPreactConfig(): BlacPreactConfig {\n return globalConfig;\n}\n\n/**\n * Reset configuration to defaults (useful for testing).\n * @internal\n */\nexport function resetBlacPreactConfig(): void {\n globalConfig = { ...defaultConfig };\n}\n","import { useMemo, useEffect, useRef, useReducer } from 'preact/hooks';\nimport { useSyncExternalStore } from 'preact/compat';\nimport {\n type ExtractState,\n type AdapterState,\n type IsStatelessContainer,\n ExternalDepsManager,\n autoTrackSubscribe,\n manualDepsSubscribe,\n noTrackSubscribe,\n autoTrackSnapshot,\n manualDepsSnapshot,\n noTrackSnapshot,\n autoTrackInit,\n manualDepsInit,\n noTrackInit,\n disableGetterTracking,\n isIsolatedClass,\n isStatelessClass,\n StateContainerConstructor,\n InstanceState,\n acquire,\n release,\n} from '@blac/core';\nimport type { UseBlocOptions, UseBlocReturn, ComponentRef } from './types';\nimport { generateInstanceKey } from './utils/instance-keys';\nimport { getBlacPreactConfig } from './config';\n\ninterface TrackingMode {\n useManualDeps: boolean;\n autoTrackEnabled: boolean;\n}\n\nfunction determineTrackingMode<TBloc extends StateContainerConstructor>(\n options?: UseBlocOptions<TBloc>,\n): TrackingMode {\n const globalConfig = getBlacPreactConfig();\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 * Type that produces a TypeScript error when a stateless container is used.\n * Evaluates to `never` for stateless containers, causing a type mismatch.\n */\ntype StatefulContainer<T extends StateContainerConstructor> =\n IsStatelessContainer<T> extends true ? never : T;\n\n/**\n * Preact 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 * **Note:** This hook does NOT support stateless containers (StatelessCubit, StatelessVertex).\n * Use `useBlocActions()` instead for stateless containers.\n *\n * @template T - The state container constructor type (inferred from BlocClass)\n * @param BlocClass - The state container class to connect to (must not be stateless)\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 */\nexport function useBloc<\n T extends StateContainerConstructor = StateContainerConstructor,\n>(\n BlocClass: StatefulContainer<T>,\n options?: UseBlocOptions<T>,\n): UseBlocReturn<T, ExtractState<T>> {\n type TBloc = InstanceState<T>;\n\n const componentRef = useRef<ComponentRef>({});\n const initialPropsRef = useRef(options?.props);\n const isStateless = isStatelessClass(BlocClass);\n const isIsolated = isIsolatedClass(BlocClass);\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> | null,\n TBloc,\n ]\n >(() => {\n // Runtime check for stateless containers - log error but don't crash\n if (isStateless) {\n console.error(\n `[BlaC] useBloc() does not support stateless containers. ` +\n `\"${BlocClass.name}\" is a StatelessCubit or StatelessVertex. ` +\n `Use useBlocActions() instead for stateless containers.`,\n );\n const instance = acquire(BlocClass, 'default') as TBloc;\n // Return minimal working functions that won't subscribe to anything\n return [\n instance,\n () => () => {},\n () => ({}) as ExtractState<T>,\n undefined,\n null,\n instance,\n ];\n }\n\n const instanceKey = generateInstanceKey(\n componentRef.current,\n isIsolated,\n options?.instanceId,\n );\n\n const instance = acquire(BlocClass, 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<T>;\n let adapterState: AdapterState<T>;\n\n if (useManualDeps && options?.dependencies) {\n adapterState = manualDepsInit(instance);\n subscribeFn = manualDepsSubscribe(instance, adapterState, {\n dependencies: options.dependencies,\n });\n getSnapshotFn = manualDepsSnapshot(instance, adapterState, {\n dependencies: options.dependencies,\n });\n } else if (!autoTrackEnabled) {\n adapterState = noTrackInit(instance);\n subscribeFn = noTrackSubscribe(instance);\n getSnapshotFn = noTrackSnapshot(instance);\n } else {\n adapterState = autoTrackInit(instance);\n subscribeFn = autoTrackSubscribe(instance, adapterState);\n getSnapshotFn = autoTrackSnapshot(instance, adapterState);\n }\n\n return [\n adapterState.proxiedBloc as TBloc,\n subscribeFn,\n getSnapshotFn,\n instanceKey,\n adapterState,\n instance,\n ];\n }, [BlocClass, isStateless, isIsolated, 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 ExternalDepsManager());\n\n useEffect(() => {\n if (isStateless) return;\n if (options?.props !== initialPropsRef.current) {\n rawInstance.updateProps(options?.props);\n }\n }, [options?.props, rawInstance, isStateless]);\n\n useEffect(() => {\n if (isStateless || !adapterState) return;\n disableGetterTracking(adapterState, rawInstance);\n externalDepsManager.current.updateSubscriptions(\n adapterState.getterState,\n rawInstance,\n () => forceUpdate(0),\n );\n });\n\n useEffect(() => {\n if (isStateless) return;\n\n if (options?.onMount) {\n options.onMount(bloc as InstanceType<T>);\n }\n\n return () => {\n externalDepsManager.current.cleanup();\n\n if (options?.onUnmount) {\n options.onUnmount(bloc as InstanceType<T>);\n }\n\n release(BlocClass, 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 'preact/hooks';\nimport {\n StateContainerConstructor,\n isIsolatedClass,\n acquire,\n release,\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 * Preact 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 * **This is the recommended hook for stateless containers** (StatelessCubit, StatelessVertex).\n * It also works with regular Cubit/Vertex when you don't need state subscriptions.\n *\n * @template T - The state container constructor type (inferred from BlocClass)\n * @param BlocClass - The state container class to connect to (supports both stateful and stateless)\n * @param options - Configuration options for instance management and lifecycle\n * @returns The state container instance for calling actions\n *\n * @example Basic usage with stateless container\n * ```ts\n * class AnalyticsService extends StatelessCubit {\n * trackEvent(name: string) { ... }\n * }\n *\n * const analytics = useBlocActions(AnalyticsService);\n * analytics.trackEvent('page_view');\n * ```\n *\n * @example With stateful container (no re-renders)\n * ```ts\n * const myBloc = useBlocActions(MyBloc);\n * myBloc.someMethod(); // Won't cause re-renders\n * ```\n *\n * @example With isolated instance\n * ```ts\n * const myBloc = useBlocActions(MyBloc, {\n * instanceId: 'unique-id'\n * });\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 = acquire(BlocClass, 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 release(BlocClass, 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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"} | ||
| {"version":3,"file":"index.cjs","names":["ExternalDepsManager"],"sources":["../src/utils/instance-keys.ts","../src/config.ts","../src/useBloc.ts"],"sourcesContent":["/**\n * Instance key generation utilities for Preact integration\n */\n\nimport { generateIsolatedKey } from '@blac/adapter';\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 - Preact 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","/**\n * Global configuration for @blac/preact\n */\n\nexport interface BlacPreactConfig {\n /** Enable automatic property tracking via Proxy (default: true) */\n autoTrack: boolean;\n}\n\nconst defaultConfig: BlacPreactConfig = {\n autoTrack: true,\n};\n\nlet globalConfig: BlacPreactConfig = { ...defaultConfig };\n\n/**\n * Configure global defaults for @blac/preact hooks.\n *\n * @example\n * ```ts\n * import { configureBlacPreact } from '@blac/preact';\n *\n * // Disable auto-tracking globally\n * configureBlacPreact({\n * autoTrack: false\n * });\n * ```\n *\n * @param config - Partial configuration to merge with defaults\n */\nexport function configureBlacPreact(config: Partial<BlacPreactConfig>): void {\n globalConfig = { ...globalConfig, ...config };\n}\n\n/**\n * Get the current global configuration.\n * @internal\n */\nexport function getBlacPreactConfig(): BlacPreactConfig {\n return globalConfig;\n}\n\n/**\n * Reset configuration to defaults (useful for testing).\n * @internal\n */\nexport function resetBlacPreactConfig(): void {\n globalConfig = { ...defaultConfig };\n}\n","import { useMemo, useEffect, useRef, useReducer } from 'preact/hooks';\nimport { useSyncExternalStore } from 'preact/compat';\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 isIsolatedClass,\n type StateContainerConstructor,\n type InstanceState,\n acquire,\n release,\n} from '@blac/adapter';\nimport type { UseBlocOptions, UseBlocReturn, ComponentRef } from './types';\nimport { generateInstanceKey } from './utils/instance-keys';\nimport { getBlacPreactConfig } from './config';\n\ninterface TrackingMode {\n useManualDeps: boolean;\n autoTrackEnabled: boolean;\n}\n\nfunction determineTrackingMode<TBloc extends StateContainerConstructor>(\n options?: UseBlocOptions<TBloc>,\n): TrackingMode {\n const globalConfig = getBlacPreactConfig();\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 * Preact 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 isolated 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 const isIsolated = isIsolatedClass(BlocClass);\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 = generateInstanceKey(\n componentRef.current,\n isIsolated,\n options?.instanceId,\n );\n\n const instance = acquire(BlocClass, instanceKey) as TBloc;\n\n const { useManualDeps, autoTrackEnabled } =\n determineTrackingMode(options);\n\n let subscribeFn: (callback: () => void) => () => void;\n let getSnapshotFn: () => ExtractState<T>;\n let adapterState: AdapterState<T>;\n\n if (useManualDeps && options?.dependencies) {\n adapterState = manualDepsInit(instance);\n subscribeFn = manualDepsSubscribe(instance, adapterState, {\n dependencies: options.dependencies,\n });\n getSnapshotFn = manualDepsSnapshot(instance, adapterState, {\n dependencies: options.dependencies,\n });\n } else if (!autoTrackEnabled) {\n adapterState = noTrackInit(instance);\n subscribeFn = noTrackSubscribe(instance);\n getSnapshotFn = noTrackSnapshot(instance);\n } else {\n adapterState = autoTrackInit(instance);\n subscribeFn = autoTrackSubscribe(instance, adapterState);\n getSnapshotFn = autoTrackSnapshot(instance, adapterState);\n }\n\n return [\n adapterState.proxiedBloc as TBloc,\n subscribeFn,\n getSnapshotFn,\n instanceKey,\n adapterState,\n instance,\n ];\n }, [BlocClass, isIsolated, 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 ExternalDepsManager());\n\n useEffect(() => {\n disableGetterTracking(adapterState, rawInstance);\n externalDepsManager.current.updateSubscriptions(\n adapterState.getterState,\n rawInstance,\n () => forceUpdate(0),\n );\n });\n\n useEffect(() => {\n if (options?.onMount) {\n options.onMount(bloc as InstanceType<T>);\n }\n\n return () => {\n externalDepsManager.current.cleanup();\n\n if (options?.onUnmount) {\n options.onUnmount(bloc as InstanceType<T>);\n }\n\n release(BlocClass, 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"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,oBACd,cACA,YACA,YACoB;AAEpB,KAAI,eAAe,OACjB,QAAO,OAAO,eAAe,WAAW,OAAO,WAAW,GAAG;AAI/D,KAAI,YAAY;AACd,MAAI,CAAC,aAAa,iBAChB,cAAa,2DAAwC;AAEvD,SAAO,aAAa;;;;;;AC1BxB,MAAM,gBAAkC,EACtC,WAAW,MACZ;AAED,IAAI,eAAiC,EAAE,GAAG,eAAe;;;;;;;;;;;;;;;;AAiBzD,SAAgB,oBAAoB,QAAyC;AAC3E,gBAAe;EAAE,GAAG;EAAc,GAAG;EAAQ;;;;;;AAO/C,SAAgB,sBAAwC;AACtD,QAAO;;;;;;AAOT,SAAgB,wBAA8B;AAC5C,gBAAe,EAAE,GAAG,eAAe;;;;;AChBrC,SAAS,sBACP,SACc;CACd,MAAM,eAAe,qBAAqB;CAC1C,MAAM,mBACJ,SAAS,cAAc,SACnB,QAAQ,YACR,aAAa;AAEnB,QAAO;EACL,eAAe,SAAS,iBAAiB;EACzC;EACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCH,SAAgB,QAGd,WACA,SACmC;CAGnC,MAAM,wCAAoC,EAAE,CAAC;CAC7C,MAAM,gDAA6B,UAAU;CAE7C,MAAM,CAAC,MAAM,WAAW,aAAa,aAAa,cAAc,+CAUtD;EACN,MAAM,cAAc,oBAClB,aAAa,SACb,YACA,SAAS,WACV;EAED,MAAM,sCAAmB,WAAW,YAAY;EAEhD,MAAM,EAAE,eAAe,qBACrB,sBAAsB,QAAQ;EAEhC,IAAI;EACJ,IAAI;EACJ,IAAI;AAEJ,MAAI,iBAAiB,SAAS,cAAc;AAC1C,oDAA8B,SAAS;AACvC,wDAAkC,UAAU,cAAc,EACxD,cAAc,QAAQ,cACvB,CAAC;AACF,yDAAmC,UAAU,cAAc,EACzD,cAAc,QAAQ,cACvB,CAAC;aACO,CAAC,kBAAkB;AAC5B,iDAA2B,SAAS;AACpC,qDAA+B,SAAS;AACxC,sDAAgC,SAAS;SACpC;AACL,mDAA6B,SAAS;AACtC,uDAAiC,UAAU,aAAa;AACxD,wDAAkC,UAAU,aAAa;;AAG3D,SAAO;GACL,aAAa;GACb;GACA;GACA;GACA;GACA;GACD;IACA;EAAC;EAAW;EAAY,SAAS;EAAW,CAAC;CAElD,MAAM,gDAA6B,WAAW,YAAY;CAE1D,MAAM,GAAG,6CAA2B,MAAc,IAAI,GAAG,EAAE;CAE3D,MAAM,+CAA6B,IAAIA,mCAAqB,CAAC;AAE7D,mCAAgB;AACd,2CAAsB,cAAc,YAAY;AAChD,sBAAoB,QAAQ,oBAC1B,aAAa,aACb,mBACM,YAAY,EAAE,CACrB;GACD;AAEF,mCAAgB;AACd,MAAI,SAAS,QACX,SAAQ,QAAQ,KAAwB;AAG1C,eAAa;AACX,uBAAoB,QAAQ,SAAS;AAErC,OAAI,SAAS,UACX,SAAQ,UAAU,KAAwB;AAG5C,8BAAQ,WAAW,YAAY;AAE/B,OAAI,cAAc,CAAC,YAAY,WAC7B,aAAY,SAAS;;IAGxB,EAAE,CAAC;AAEN,QAAO;EAAC;EAAO;EAAM;EAAa"} |
+8
-73
@@ -1,7 +0,7 @@ | ||
| import type { ExtractProps } from '@blac/core'; | ||
| import { ExtractState } from '@blac/core'; | ||
| import type { ExtractState } from '@blac/core'; | ||
| import { ExtractState as ExtractState_2 } from '@blac/adapter'; | ||
| import { InstanceReadonlyState } from '@blac/core'; | ||
| import { IsStatelessContainer } from '@blac/core'; | ||
| import type { RefObject } from 'preact'; | ||
| import { StateContainerConstructor } from '@blac/core'; | ||
| import { StateContainerConstructor } from '@blac/adapter'; | ||
| import type { StateContainerConstructor as StateContainerConstructor_2 } from '@blac/core'; | ||
@@ -38,8 +38,2 @@ /** | ||
| /** | ||
| * Type that produces a TypeScript error when a stateless container is used. | ||
| * Evaluates to `never` for stateless containers, causing a type mismatch. | ||
| */ | ||
| declare type StatefulContainer<T extends StateContainerConstructor> = IsStatelessContainer<T> extends true ? never : T; | ||
| /** | ||
| * Preact hook that connects a component to a state container with automatic re-render on state changes. | ||
@@ -52,7 +46,4 @@ * | ||
| * | ||
| * **Note:** This hook does NOT support stateless containers (StatelessCubit, StatelessVertex). | ||
| * Use `useBlocActions()` instead for stateless containers. | ||
| * | ||
| * @template T - The state container constructor type (inferred from BlocClass) | ||
| * @param BlocClass - The state container class to connect to (must not be stateless) | ||
| * @param BlocClass - The state container class to connect to | ||
| * @param options - Configuration options for tracking mode and instance management | ||
@@ -80,65 +71,9 @@ * @returns Tuple with [state, bloc instance, ref] | ||
| */ | ||
| export declare function useBloc<T extends StateContainerConstructor = StateContainerConstructor>(BlocClass: StatefulContainer<T>, options?: UseBlocOptions<T>): UseBlocReturn<T, ExtractState<T>>; | ||
| export declare function useBloc<T extends StateContainerConstructor = StateContainerConstructor>(BlocClass: T, options?: UseBlocOptions<T>): UseBlocReturn<T, ExtractState_2<T>>; | ||
| /** | ||
| * Preact hook that connects to a state container instance without triggering re-renders. | ||
| * Use this when you only need to call actions on the bloc without subscribing to state changes. | ||
| * | ||
| * **This is the recommended hook for stateless containers** (StatelessCubit, StatelessVertex). | ||
| * It also works with regular Cubit/Vertex when you don't need state subscriptions. | ||
| * | ||
| * @template T - The state container constructor type (inferred from BlocClass) | ||
| * @param BlocClass - The state container class to connect to (supports both stateful and stateless) | ||
| * @param options - Configuration options for instance management and lifecycle | ||
| * @returns The state container instance for calling actions | ||
| * | ||
| * @example Basic usage with stateless container | ||
| * ```ts | ||
| * class AnalyticsService extends StatelessCubit { | ||
| * trackEvent(name: string) { ... } | ||
| * } | ||
| * | ||
| * const analytics = useBlocActions(AnalyticsService); | ||
| * analytics.trackEvent('page_view'); | ||
| * ``` | ||
| * | ||
| * @example With stateful container (no re-renders) | ||
| * ```ts | ||
| * const myBloc = useBlocActions(MyBloc); | ||
| * myBloc.someMethod(); // Won't cause re-renders | ||
| * ``` | ||
| * | ||
| * @example With isolated instance | ||
| * ```ts | ||
| * const myBloc = useBlocActions(MyBloc, { | ||
| * instanceId: 'unique-id' | ||
| * }); | ||
| * ``` | ||
| */ | ||
| export declare function useBlocActions<T extends StateContainerConstructor = StateContainerConstructor>(BlocClass: T, options?: UseBlocActionsOptions<InstanceType<T>>): InstanceType<T>; | ||
| /** | ||
| * Configuration options for useBlocActions hook | ||
| * @template TBloc - The state container type | ||
| * @template TProps - Props type passed to the container | ||
| */ | ||
| export declare interface UseBlocActionsOptions<TBloc, TProps = any> { | ||
| /** Props passed to bloc constructor or updateProps */ | ||
| props?: TProps; | ||
| /** Custom instance identifier for shared or isolated instances */ | ||
| instanceId?: string | number; | ||
| /** Callback invoked when bloc instance mounts */ | ||
| onMount?: (bloc: TBloc) => void; | ||
| /** Callback invoked when bloc instance unmounts */ | ||
| onUnmount?: (bloc: TBloc) => void; | ||
| } | ||
| /** | ||
| * Configuration options for useBloc hook | ||
| * @template TBloc - The state container type | ||
| * @template TProps - Props type passed to the container | ||
| */ | ||
| export declare interface UseBlocOptions<TBloc extends StateContainerConstructor, TProps = ExtractProps<TBloc>> { | ||
| /** Props passed to bloc constructor or updateProps */ | ||
| props?: TProps; | ||
| export declare interface UseBlocOptions<TBloc extends StateContainerConstructor_2> { | ||
| /** Custom instance identifier for shared or isolated instances */ | ||
@@ -166,4 +101,4 @@ instanceId?: string | number; | ||
| */ | ||
| export declare type UseBlocReturn<TBloc extends StateContainerConstructor, S = ExtractState<TBloc>> = [S, InstanceReadonlyState<TBloc>, RefObject<ComponentRef>]; | ||
| export declare type UseBlocReturn<TBloc extends StateContainerConstructor_2, S = ExtractState<TBloc>> = [S, InstanceReadonlyState<TBloc>, RefObject<ComponentRef>]; | ||
| export { } |
+8
-73
@@ -1,7 +0,7 @@ | ||
| import type { ExtractProps } from '@blac/core'; | ||
| import { ExtractState } from '@blac/core'; | ||
| import type { ExtractState } from '@blac/core'; | ||
| import { ExtractState as ExtractState_2 } from '@blac/adapter'; | ||
| import { InstanceReadonlyState } from '@blac/core'; | ||
| import { IsStatelessContainer } from '@blac/core'; | ||
| import type { RefObject } from 'preact'; | ||
| import { StateContainerConstructor } from '@blac/core'; | ||
| import { StateContainerConstructor } from '@blac/adapter'; | ||
| import type { StateContainerConstructor as StateContainerConstructor_2 } from '@blac/core'; | ||
@@ -38,8 +38,2 @@ /** | ||
| /** | ||
| * Type that produces a TypeScript error when a stateless container is used. | ||
| * Evaluates to `never` for stateless containers, causing a type mismatch. | ||
| */ | ||
| declare type StatefulContainer<T extends StateContainerConstructor> = IsStatelessContainer<T> extends true ? never : T; | ||
| /** | ||
| * Preact hook that connects a component to a state container with automatic re-render on state changes. | ||
@@ -52,7 +46,4 @@ * | ||
| * | ||
| * **Note:** This hook does NOT support stateless containers (StatelessCubit, StatelessVertex). | ||
| * Use `useBlocActions()` instead for stateless containers. | ||
| * | ||
| * @template T - The state container constructor type (inferred from BlocClass) | ||
| * @param BlocClass - The state container class to connect to (must not be stateless) | ||
| * @param BlocClass - The state container class to connect to | ||
| * @param options - Configuration options for tracking mode and instance management | ||
@@ -80,65 +71,9 @@ * @returns Tuple with [state, bloc instance, ref] | ||
| */ | ||
| export declare function useBloc<T extends StateContainerConstructor = StateContainerConstructor>(BlocClass: StatefulContainer<T>, options?: UseBlocOptions<T>): UseBlocReturn<T, ExtractState<T>>; | ||
| export declare function useBloc<T extends StateContainerConstructor = StateContainerConstructor>(BlocClass: T, options?: UseBlocOptions<T>): UseBlocReturn<T, ExtractState_2<T>>; | ||
| /** | ||
| * Preact hook that connects to a state container instance without triggering re-renders. | ||
| * Use this when you only need to call actions on the bloc without subscribing to state changes. | ||
| * | ||
| * **This is the recommended hook for stateless containers** (StatelessCubit, StatelessVertex). | ||
| * It also works with regular Cubit/Vertex when you don't need state subscriptions. | ||
| * | ||
| * @template T - The state container constructor type (inferred from BlocClass) | ||
| * @param BlocClass - The state container class to connect to (supports both stateful and stateless) | ||
| * @param options - Configuration options for instance management and lifecycle | ||
| * @returns The state container instance for calling actions | ||
| * | ||
| * @example Basic usage with stateless container | ||
| * ```ts | ||
| * class AnalyticsService extends StatelessCubit { | ||
| * trackEvent(name: string) { ... } | ||
| * } | ||
| * | ||
| * const analytics = useBlocActions(AnalyticsService); | ||
| * analytics.trackEvent('page_view'); | ||
| * ``` | ||
| * | ||
| * @example With stateful container (no re-renders) | ||
| * ```ts | ||
| * const myBloc = useBlocActions(MyBloc); | ||
| * myBloc.someMethod(); // Won't cause re-renders | ||
| * ``` | ||
| * | ||
| * @example With isolated instance | ||
| * ```ts | ||
| * const myBloc = useBlocActions(MyBloc, { | ||
| * instanceId: 'unique-id' | ||
| * }); | ||
| * ``` | ||
| */ | ||
| export declare function useBlocActions<T extends StateContainerConstructor = StateContainerConstructor>(BlocClass: T, options?: UseBlocActionsOptions<InstanceType<T>>): InstanceType<T>; | ||
| /** | ||
| * Configuration options for useBlocActions hook | ||
| * @template TBloc - The state container type | ||
| * @template TProps - Props type passed to the container | ||
| */ | ||
| export declare interface UseBlocActionsOptions<TBloc, TProps = any> { | ||
| /** Props passed to bloc constructor or updateProps */ | ||
| props?: TProps; | ||
| /** Custom instance identifier for shared or isolated instances */ | ||
| instanceId?: string | number; | ||
| /** Callback invoked when bloc instance mounts */ | ||
| onMount?: (bloc: TBloc) => void; | ||
| /** Callback invoked when bloc instance unmounts */ | ||
| onUnmount?: (bloc: TBloc) => void; | ||
| } | ||
| /** | ||
| * Configuration options for useBloc hook | ||
| * @template TBloc - The state container type | ||
| * @template TProps - Props type passed to the container | ||
| */ | ||
| export declare interface UseBlocOptions<TBloc extends StateContainerConstructor, TProps = ExtractProps<TBloc>> { | ||
| /** Props passed to bloc constructor or updateProps */ | ||
| props?: TProps; | ||
| export declare interface UseBlocOptions<TBloc extends StateContainerConstructor_2> { | ||
| /** Custom instance identifier for shared or isolated instances */ | ||
@@ -166,4 +101,4 @@ instanceId?: string | number; | ||
| */ | ||
| export declare type UseBlocReturn<TBloc extends StateContainerConstructor, S = ExtractState<TBloc>> = [S, InstanceReadonlyState<TBloc>, RefObject<ComponentRef>]; | ||
| export declare type UseBlocReturn<TBloc extends StateContainerConstructor_2, S = ExtractState<TBloc>> = [S, InstanceReadonlyState<TBloc>, RefObject<ComponentRef>]; | ||
| export { } |
+18
-103
| import { useEffect, useMemo, useReducer, useRef } from "preact/hooks"; | ||
| import { useSyncExternalStore } from "preact/compat"; | ||
| import { ExternalDepsManager, acquire, autoTrackInit, autoTrackSnapshot, autoTrackSubscribe, disableGetterTracking, generateIsolatedKey, isIsolatedClass, isStatelessClass, manualDepsInit, manualDepsSnapshot, manualDepsSubscribe, noTrackInit, noTrackSnapshot, noTrackSubscribe, release } from "@blac/core"; | ||
| import { ExternalDepsManager, acquire, autoTrackInit, autoTrackSnapshot, autoTrackSubscribe, disableGetterTracking, generateIsolatedKey, isIsolatedClass, manualDepsInit, manualDepsSnapshot, manualDepsSubscribe, noTrackInit, noTrackSnapshot, noTrackSubscribe, release } from "@blac/adapter"; | ||
@@ -73,4 +73,4 @@ //#region src/utils/instance-keys.ts | ||
| function determineTrackingMode(options) { | ||
| const globalConfig$1 = getBlacPreactConfig(); | ||
| const autoTrackEnabled = options?.autoTrack !== void 0 ? options.autoTrack : globalConfig$1.autoTrack; | ||
| const globalConfig = getBlacPreactConfig(); | ||
| const autoTrackEnabled = options?.autoTrack !== void 0 ? options.autoTrack : globalConfig.autoTrack; | ||
| return { | ||
@@ -89,7 +89,4 @@ useManualDeps: options?.dependencies !== void 0, | ||
| * | ||
| * **Note:** This hook does NOT support stateless containers (StatelessCubit, StatelessVertex). | ||
| * Use `useBlocActions()` instead for stateless containers. | ||
| * | ||
| * @template T - The state container constructor type (inferred from BlocClass) | ||
| * @param BlocClass - The state container class to connect to (must not be stateless) | ||
| * @param BlocClass - The state container class to connect to | ||
| * @param options - Configuration options for tracking mode and instance management | ||
@@ -119,44 +116,29 @@ * @returns Tuple with [state, bloc instance, ref] | ||
| const componentRef = useRef({}); | ||
| const initialPropsRef = useRef(options?.props); | ||
| const isStateless = isStatelessClass(BlocClass); | ||
| const isIsolated = isIsolatedClass(BlocClass); | ||
| const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] = useMemo(() => { | ||
| if (isStateless) { | ||
| console.error(`[BlaC] useBloc() does not support stateless containers. "${BlocClass.name}" is a StatelessCubit or StatelessVertex. Use useBlocActions() instead for stateless containers.`); | ||
| const instance$1 = acquire(BlocClass, "default"); | ||
| return [ | ||
| instance$1, | ||
| () => () => {}, | ||
| () => ({}), | ||
| void 0, | ||
| null, | ||
| instance$1 | ||
| ]; | ||
| } | ||
| const instanceKey$1 = generateInstanceKey(componentRef.current, isIsolated, options?.instanceId); | ||
| const instance = acquire(BlocClass, instanceKey$1, { props: initialPropsRef.current }); | ||
| if (initialPropsRef.current !== void 0) instance.updateProps(initialPropsRef.current); | ||
| const instanceKey = generateInstanceKey(componentRef.current, isIsolated, options?.instanceId); | ||
| const instance = acquire(BlocClass, instanceKey); | ||
| const { useManualDeps, autoTrackEnabled } = determineTrackingMode(options); | ||
| let subscribeFn; | ||
| let getSnapshotFn; | ||
| let adapterState$1; | ||
| let adapterState; | ||
| if (useManualDeps && options?.dependencies) { | ||
| adapterState$1 = manualDepsInit(instance); | ||
| subscribeFn = manualDepsSubscribe(instance, adapterState$1, { dependencies: options.dependencies }); | ||
| getSnapshotFn = manualDepsSnapshot(instance, adapterState$1, { dependencies: options.dependencies }); | ||
| adapterState = manualDepsInit(instance); | ||
| subscribeFn = manualDepsSubscribe(instance, adapterState, { dependencies: options.dependencies }); | ||
| getSnapshotFn = manualDepsSnapshot(instance, adapterState, { dependencies: options.dependencies }); | ||
| } else if (!autoTrackEnabled) { | ||
| adapterState$1 = noTrackInit(instance); | ||
| adapterState = noTrackInit(instance); | ||
| subscribeFn = noTrackSubscribe(instance); | ||
| getSnapshotFn = noTrackSnapshot(instance); | ||
| } else { | ||
| adapterState$1 = autoTrackInit(instance); | ||
| subscribeFn = autoTrackSubscribe(instance, adapterState$1); | ||
| getSnapshotFn = autoTrackSnapshot(instance, adapterState$1); | ||
| adapterState = autoTrackInit(instance); | ||
| subscribeFn = autoTrackSubscribe(instance, adapterState); | ||
| getSnapshotFn = autoTrackSnapshot(instance, adapterState); | ||
| } | ||
| return [ | ||
| adapterState$1.proxiedBloc, | ||
| adapterState.proxiedBloc, | ||
| subscribeFn, | ||
| getSnapshotFn, | ||
| instanceKey$1, | ||
| adapterState$1, | ||
| instanceKey, | ||
| adapterState, | ||
| instance | ||
@@ -166,3 +148,2 @@ ]; | ||
| BlocClass, | ||
| isStateless, | ||
| isIsolated, | ||
@@ -175,11 +156,2 @@ options?.instanceId | ||
| useEffect(() => { | ||
| if (isStateless) return; | ||
| if (options?.props !== initialPropsRef.current) rawInstance.updateProps(options?.props); | ||
| }, [ | ||
| options?.props, | ||
| rawInstance, | ||
| isStateless | ||
| ]); | ||
| useEffect(() => { | ||
| if (isStateless || !adapterState) return; | ||
| disableGetterTracking(adapterState, rawInstance); | ||
@@ -189,3 +161,2 @@ externalDepsManager.current.updateSubscriptions(adapterState.getterState, rawInstance, () => forceUpdate(0)); | ||
| useEffect(() => { | ||
| if (isStateless) return; | ||
| if (options?.onMount) options.onMount(bloc); | ||
@@ -207,59 +178,3 @@ return () => { | ||
| //#endregion | ||
| //#region src/useBlocActions.ts | ||
| /** | ||
| * Preact hook that connects to a state container instance without triggering re-renders. | ||
| * Use this when you only need to call actions on the bloc without subscribing to state changes. | ||
| * | ||
| * **This is the recommended hook for stateless containers** (StatelessCubit, StatelessVertex). | ||
| * It also works with regular Cubit/Vertex when you don't need state subscriptions. | ||
| * | ||
| * @template T - The state container constructor type (inferred from BlocClass) | ||
| * @param BlocClass - The state container class to connect to (supports both stateful and stateless) | ||
| * @param options - Configuration options for instance management and lifecycle | ||
| * @returns The state container instance for calling actions | ||
| * | ||
| * @example Basic usage with stateless container | ||
| * ```ts | ||
| * class AnalyticsService extends StatelessCubit { | ||
| * trackEvent(name: string) { ... } | ||
| * } | ||
| * | ||
| * const analytics = useBlocActions(AnalyticsService); | ||
| * analytics.trackEvent('page_view'); | ||
| * ``` | ||
| * | ||
| * @example With stateful container (no re-renders) | ||
| * ```ts | ||
| * const myBloc = useBlocActions(MyBloc); | ||
| * myBloc.someMethod(); // Won't cause re-renders | ||
| * ``` | ||
| * | ||
| * @example With isolated instance | ||
| * ```ts | ||
| * const myBloc = useBlocActions(MyBloc, { | ||
| * instanceId: 'unique-id' | ||
| * }); | ||
| * ``` | ||
| */ | ||
| function useBlocActions(BlocClass, options) { | ||
| const componentRef = useRef({}); | ||
| const initialPropsRef = useRef(options?.props); | ||
| const [bloc, instanceKey] = useMemo(() => { | ||
| const isIsolated = isIsolatedClass(BlocClass); | ||
| const instanceKey$1 = generateInstanceKey(componentRef.current, isIsolated, options?.instanceId); | ||
| return [acquire(BlocClass, instanceKey$1, { props: initialPropsRef.current }), instanceKey$1]; | ||
| }, [BlocClass]); | ||
| useEffect(() => { | ||
| if (options?.onMount) options.onMount(bloc); | ||
| return () => { | ||
| if (options?.onUnmount) options.onUnmount(bloc); | ||
| release(BlocClass, instanceKey); | ||
| if (isIsolatedClass(BlocClass) && !bloc.isDisposed) bloc.dispose(); | ||
| }; | ||
| }, []); | ||
| return bloc; | ||
| } | ||
| //#endregion | ||
| export { configureBlacPreact, resetBlacPreactConfig, useBloc, useBlocActions }; | ||
| export { configureBlacPreact, resetBlacPreactConfig, useBloc }; | ||
| //# sourceMappingURL=index.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.js","names":["globalConfig","instance","instanceKey","adapterState","instanceKey"],"sources":["../src/utils/instance-keys.ts","../src/config.ts","../src/useBloc.ts","../src/useBlocActions.ts"],"sourcesContent":["/**\n * Instance key generation utilities for Preact 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 - Preact 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","/**\n * Global configuration for @blac/preact\n */\n\nexport interface BlacPreactConfig {\n /** Enable automatic property tracking via Proxy (default: true) */\n autoTrack: boolean;\n}\n\nconst defaultConfig: BlacPreactConfig = {\n autoTrack: true,\n};\n\nlet globalConfig: BlacPreactConfig = { ...defaultConfig };\n\n/**\n * Configure global defaults for @blac/preact hooks.\n *\n * @example\n * ```ts\n * import { configureBlacPreact } from '@blac/preact';\n *\n * // Disable auto-tracking globally\n * configureBlacPreact({\n * autoTrack: false\n * });\n * ```\n *\n * @param config - Partial configuration to merge with defaults\n */\nexport function configureBlacPreact(config: Partial<BlacPreactConfig>): void {\n globalConfig = { ...globalConfig, ...config };\n}\n\n/**\n * Get the current global configuration.\n * @internal\n */\nexport function getBlacPreactConfig(): BlacPreactConfig {\n return globalConfig;\n}\n\n/**\n * Reset configuration to defaults (useful for testing).\n * @internal\n */\nexport function resetBlacPreactConfig(): void {\n globalConfig = { ...defaultConfig };\n}\n","import { useMemo, useEffect, useRef, useReducer } from 'preact/hooks';\nimport { useSyncExternalStore } from 'preact/compat';\nimport {\n type ExtractState,\n type AdapterState,\n type IsStatelessContainer,\n ExternalDepsManager,\n autoTrackSubscribe,\n manualDepsSubscribe,\n noTrackSubscribe,\n autoTrackSnapshot,\n manualDepsSnapshot,\n noTrackSnapshot,\n autoTrackInit,\n manualDepsInit,\n noTrackInit,\n disableGetterTracking,\n isIsolatedClass,\n isStatelessClass,\n StateContainerConstructor,\n InstanceState,\n acquire,\n release,\n} from '@blac/core';\nimport type { UseBlocOptions, UseBlocReturn, ComponentRef } from './types';\nimport { generateInstanceKey } from './utils/instance-keys';\nimport { getBlacPreactConfig } from './config';\n\ninterface TrackingMode {\n useManualDeps: boolean;\n autoTrackEnabled: boolean;\n}\n\nfunction determineTrackingMode<TBloc extends StateContainerConstructor>(\n options?: UseBlocOptions<TBloc>,\n): TrackingMode {\n const globalConfig = getBlacPreactConfig();\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 * Type that produces a TypeScript error when a stateless container is used.\n * Evaluates to `never` for stateless containers, causing a type mismatch.\n */\ntype StatefulContainer<T extends StateContainerConstructor> =\n IsStatelessContainer<T> extends true ? never : T;\n\n/**\n * Preact 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 * **Note:** This hook does NOT support stateless containers (StatelessCubit, StatelessVertex).\n * Use `useBlocActions()` instead for stateless containers.\n *\n * @template T - The state container constructor type (inferred from BlocClass)\n * @param BlocClass - The state container class to connect to (must not be stateless)\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 */\nexport function useBloc<\n T extends StateContainerConstructor = StateContainerConstructor,\n>(\n BlocClass: StatefulContainer<T>,\n options?: UseBlocOptions<T>,\n): UseBlocReturn<T, ExtractState<T>> {\n type TBloc = InstanceState<T>;\n\n const componentRef = useRef<ComponentRef>({});\n const initialPropsRef = useRef(options?.props);\n const isStateless = isStatelessClass(BlocClass);\n const isIsolated = isIsolatedClass(BlocClass);\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> | null,\n TBloc,\n ]\n >(() => {\n // Runtime check for stateless containers - log error but don't crash\n if (isStateless) {\n console.error(\n `[BlaC] useBloc() does not support stateless containers. ` +\n `\"${BlocClass.name}\" is a StatelessCubit or StatelessVertex. ` +\n `Use useBlocActions() instead for stateless containers.`,\n );\n const instance = acquire(BlocClass, 'default') as TBloc;\n // Return minimal working functions that won't subscribe to anything\n return [\n instance,\n () => () => {},\n () => ({}) as ExtractState<T>,\n undefined,\n null,\n instance,\n ];\n }\n\n const instanceKey = generateInstanceKey(\n componentRef.current,\n isIsolated,\n options?.instanceId,\n );\n\n const instance = acquire(BlocClass, 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<T>;\n let adapterState: AdapterState<T>;\n\n if (useManualDeps && options?.dependencies) {\n adapterState = manualDepsInit(instance);\n subscribeFn = manualDepsSubscribe(instance, adapterState, {\n dependencies: options.dependencies,\n });\n getSnapshotFn = manualDepsSnapshot(instance, adapterState, {\n dependencies: options.dependencies,\n });\n } else if (!autoTrackEnabled) {\n adapterState = noTrackInit(instance);\n subscribeFn = noTrackSubscribe(instance);\n getSnapshotFn = noTrackSnapshot(instance);\n } else {\n adapterState = autoTrackInit(instance);\n subscribeFn = autoTrackSubscribe(instance, adapterState);\n getSnapshotFn = autoTrackSnapshot(instance, adapterState);\n }\n\n return [\n adapterState.proxiedBloc as TBloc,\n subscribeFn,\n getSnapshotFn,\n instanceKey,\n adapterState,\n instance,\n ];\n }, [BlocClass, isStateless, isIsolated, 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 ExternalDepsManager());\n\n useEffect(() => {\n if (isStateless) return;\n if (options?.props !== initialPropsRef.current) {\n rawInstance.updateProps(options?.props);\n }\n }, [options?.props, rawInstance, isStateless]);\n\n useEffect(() => {\n if (isStateless || !adapterState) return;\n disableGetterTracking(adapterState, rawInstance);\n externalDepsManager.current.updateSubscriptions(\n adapterState.getterState,\n rawInstance,\n () => forceUpdate(0),\n );\n });\n\n useEffect(() => {\n if (isStateless) return;\n\n if (options?.onMount) {\n options.onMount(bloc as InstanceType<T>);\n }\n\n return () => {\n externalDepsManager.current.cleanup();\n\n if (options?.onUnmount) {\n options.onUnmount(bloc as InstanceType<T>);\n }\n\n release(BlocClass, 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 'preact/hooks';\nimport {\n StateContainerConstructor,\n isIsolatedClass,\n acquire,\n release,\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 * Preact 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 * **This is the recommended hook for stateless containers** (StatelessCubit, StatelessVertex).\n * It also works with regular Cubit/Vertex when you don't need state subscriptions.\n *\n * @template T - The state container constructor type (inferred from BlocClass)\n * @param BlocClass - The state container class to connect to (supports both stateful and stateless)\n * @param options - Configuration options for instance management and lifecycle\n * @returns The state container instance for calling actions\n *\n * @example Basic usage with stateless container\n * ```ts\n * class AnalyticsService extends StatelessCubit {\n * trackEvent(name: string) { ... }\n * }\n *\n * const analytics = useBlocActions(AnalyticsService);\n * analytics.trackEvent('page_view');\n * ```\n *\n * @example With stateful container (no re-renders)\n * ```ts\n * const myBloc = useBlocActions(MyBloc);\n * myBloc.someMethod(); // Won't cause re-renders\n * ```\n *\n * @example With isolated instance\n * ```ts\n * const myBloc = useBlocActions(MyBloc, {\n * instanceId: 'unique-id'\n * });\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 = acquire(BlocClass, 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 release(BlocClass, 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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"} | ||
| {"version":3,"file":"index.js","names":[],"sources":["../src/utils/instance-keys.ts","../src/config.ts","../src/useBloc.ts"],"sourcesContent":["/**\n * Instance key generation utilities for Preact integration\n */\n\nimport { generateIsolatedKey } from '@blac/adapter';\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 - Preact 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","/**\n * Global configuration for @blac/preact\n */\n\nexport interface BlacPreactConfig {\n /** Enable automatic property tracking via Proxy (default: true) */\n autoTrack: boolean;\n}\n\nconst defaultConfig: BlacPreactConfig = {\n autoTrack: true,\n};\n\nlet globalConfig: BlacPreactConfig = { ...defaultConfig };\n\n/**\n * Configure global defaults for @blac/preact hooks.\n *\n * @example\n * ```ts\n * import { configureBlacPreact } from '@blac/preact';\n *\n * // Disable auto-tracking globally\n * configureBlacPreact({\n * autoTrack: false\n * });\n * ```\n *\n * @param config - Partial configuration to merge with defaults\n */\nexport function configureBlacPreact(config: Partial<BlacPreactConfig>): void {\n globalConfig = { ...globalConfig, ...config };\n}\n\n/**\n * Get the current global configuration.\n * @internal\n */\nexport function getBlacPreactConfig(): BlacPreactConfig {\n return globalConfig;\n}\n\n/**\n * Reset configuration to defaults (useful for testing).\n * @internal\n */\nexport function resetBlacPreactConfig(): void {\n globalConfig = { ...defaultConfig };\n}\n","import { useMemo, useEffect, useRef, useReducer } from 'preact/hooks';\nimport { useSyncExternalStore } from 'preact/compat';\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 isIsolatedClass,\n type StateContainerConstructor,\n type InstanceState,\n acquire,\n release,\n} from '@blac/adapter';\nimport type { UseBlocOptions, UseBlocReturn, ComponentRef } from './types';\nimport { generateInstanceKey } from './utils/instance-keys';\nimport { getBlacPreactConfig } from './config';\n\ninterface TrackingMode {\n useManualDeps: boolean;\n autoTrackEnabled: boolean;\n}\n\nfunction determineTrackingMode<TBloc extends StateContainerConstructor>(\n options?: UseBlocOptions<TBloc>,\n): TrackingMode {\n const globalConfig = getBlacPreactConfig();\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 * Preact 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 isolated 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 const isIsolated = isIsolatedClass(BlocClass);\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 = generateInstanceKey(\n componentRef.current,\n isIsolated,\n options?.instanceId,\n );\n\n const instance = acquire(BlocClass, instanceKey) as TBloc;\n\n const { useManualDeps, autoTrackEnabled } =\n determineTrackingMode(options);\n\n let subscribeFn: (callback: () => void) => () => void;\n let getSnapshotFn: () => ExtractState<T>;\n let adapterState: AdapterState<T>;\n\n if (useManualDeps && options?.dependencies) {\n adapterState = manualDepsInit(instance);\n subscribeFn = manualDepsSubscribe(instance, adapterState, {\n dependencies: options.dependencies,\n });\n getSnapshotFn = manualDepsSnapshot(instance, adapterState, {\n dependencies: options.dependencies,\n });\n } else if (!autoTrackEnabled) {\n adapterState = noTrackInit(instance);\n subscribeFn = noTrackSubscribe(instance);\n getSnapshotFn = noTrackSnapshot(instance);\n } else {\n adapterState = autoTrackInit(instance);\n subscribeFn = autoTrackSubscribe(instance, adapterState);\n getSnapshotFn = autoTrackSnapshot(instance, adapterState);\n }\n\n return [\n adapterState.proxiedBloc as TBloc,\n subscribeFn,\n getSnapshotFn,\n instanceKey,\n adapterState,\n instance,\n ];\n }, [BlocClass, isIsolated, 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 ExternalDepsManager());\n\n useEffect(() => {\n disableGetterTracking(adapterState, rawInstance);\n externalDepsManager.current.updateSubscriptions(\n adapterState.getterState,\n rawInstance,\n () => forceUpdate(0),\n );\n });\n\n useEffect(() => {\n if (options?.onMount) {\n options.onMount(bloc as InstanceType<T>);\n }\n\n return () => {\n externalDepsManager.current.cleanup();\n\n if (options?.onUnmount) {\n options.onUnmount(bloc as InstanceType<T>);\n }\n\n release(BlocClass, 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"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,oBACd,cACA,YACA,YACoB;AAEpB,KAAI,eAAe,OACjB,QAAO,OAAO,eAAe,WAAW,OAAO,WAAW,GAAG;AAI/D,KAAI,YAAY;AACd,MAAI,CAAC,aAAa,iBAChB,cAAa,mBAAmB,qBAAqB;AAEvD,SAAO,aAAa;;;;;;AC1BxB,MAAM,gBAAkC,EACtC,WAAW,MACZ;AAED,IAAI,eAAiC,EAAE,GAAG,eAAe;;;;;;;;;;;;;;;;AAiBzD,SAAgB,oBAAoB,QAAyC;AAC3E,gBAAe;EAAE,GAAG;EAAc,GAAG;EAAQ;;;;;;AAO/C,SAAgB,sBAAwC;AACtD,QAAO;;;;;;AAOT,SAAgB,wBAA8B;AAC5C,gBAAe,EAAE,GAAG,eAAe;;;;;AChBrC,SAAS,sBACP,SACc;CACd,MAAM,eAAe,qBAAqB;CAC1C,MAAM,mBACJ,SAAS,cAAc,SACnB,QAAQ,YACR,aAAa;AAEnB,QAAO;EACL,eAAe,SAAS,iBAAiB;EACzC;EACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCH,SAAgB,QAGd,WACA,SACmC;CAGnC,MAAM,eAAe,OAAqB,EAAE,CAAC;CAC7C,MAAM,aAAa,gBAAgB,UAAU;CAE7C,MAAM,CAAC,MAAM,WAAW,aAAa,aAAa,cAAc,eAC9D,cASQ;EACN,MAAM,cAAc,oBAClB,aAAa,SACb,YACA,SAAS,WACV;EAED,MAAM,WAAW,QAAQ,WAAW,YAAY;EAEhD,MAAM,EAAE,eAAe,qBACrB,sBAAsB,QAAQ;EAEhC,IAAI;EACJ,IAAI;EACJ,IAAI;AAEJ,MAAI,iBAAiB,SAAS,cAAc;AAC1C,kBAAe,eAAe,SAAS;AACvC,iBAAc,oBAAoB,UAAU,cAAc,EACxD,cAAc,QAAQ,cACvB,CAAC;AACF,mBAAgB,mBAAmB,UAAU,cAAc,EACzD,cAAc,QAAQ,cACvB,CAAC;aACO,CAAC,kBAAkB;AAC5B,kBAAe,YAAY,SAAS;AACpC,iBAAc,iBAAiB,SAAS;AACxC,mBAAgB,gBAAgB,SAAS;SACpC;AACL,kBAAe,cAAc,SAAS;AACtC,iBAAc,mBAAmB,UAAU,aAAa;AACxD,mBAAgB,kBAAkB,UAAU,aAAa;;AAG3D,SAAO;GACL,aAAa;GACb;GACA;GACA;GACA;GACA;GACD;IACA;EAAC;EAAW;EAAY,SAAS;EAAW,CAAC;CAElD,MAAM,QAAQ,qBAAqB,WAAW,YAAY;CAE1D,MAAM,GAAG,eAAe,YAAY,MAAc,IAAI,GAAG,EAAE;CAE3D,MAAM,sBAAsB,OAAO,IAAI,qBAAqB,CAAC;AAE7D,iBAAgB;AACd,wBAAsB,cAAc,YAAY;AAChD,sBAAoB,QAAQ,oBAC1B,aAAa,aACb,mBACM,YAAY,EAAE,CACrB;GACD;AAEF,iBAAgB;AACd,MAAI,SAAS,QACX,SAAQ,QAAQ,KAAwB;AAG1C,eAAa;AACX,uBAAoB,QAAQ,SAAS;AAErC,OAAI,SAAS,UACX,SAAQ,UAAU,KAAwB;AAG5C,WAAQ,WAAW,YAAY;AAE/B,OAAI,cAAc,CAAC,YAAY,WAC7B,aAAY,SAAS;;IAGxB,EAAE,CAAC;AAEN,QAAO;EAAC;EAAO;EAAM;EAAa"} |
+6
-4
| { | ||
| "name": "@blac/preact", | ||
| "version": "2.0.2", | ||
| "version": "2.0.3", | ||
| "license": "MIT", | ||
@@ -49,5 +49,7 @@ "author": "Brendan Mullins <jsnanigans@gmail.com>", | ||
| ], | ||
| "dependencies": { | ||
| "@blac/adapter": "2.0.3" | ||
| }, | ||
| "peerDependencies": { | ||
| "preact": "^10.17.0", | ||
| "@blac/core": "2.0.1" | ||
| "preact": "^10.17.0" | ||
| }, | ||
@@ -68,3 +70,3 @@ "devDependencies": { | ||
| "vitest": "3.2.4", | ||
| "@blac/core": "2.0.1" | ||
| "@blac/core": "2.0.3" | ||
| }, | ||
@@ -71,0 +73,0 @@ "scripts": { |
41467
-36.64%431
-34.89%+ Added
+ Added
+ Added
- Removed