@blac/react 2.0.0-rc-3 → 2.0.0-rc-5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +0 -7
- package/dist/index.es.js +115 -81
- package/dist/index.es.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +1 -1
- package/src/useBloc.tsx +65 -29
- package/src/{externalBlocStore.ts → useExternalBlocStore.ts} +21 -16
package/dist/index.cjs.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const u=require("react"),I=require("@blac/core"),M=(y,c)=>{var t;const{id:S,props:h,selector:v}=c??{},s=u.useMemo(()=>crypto.randomUUID(),[]),m=y.isolated?s:S,f=u.useRef(new Set),b=u.useRef(new Set),C=u.useCallback(()=>I.Blac.getBloc(y,{id:m,props:h,instanceRef:s}),[y,h]),o=u.useRef(C()),g=u.useMemo(()=>e=>{const n=o.current;if(!n)return[];if(v)return v(e);if(n.defaultDependencySelector)return n.defaultDependencySelector(e);if(typeof e!="object")return[[e]];const i=[];for(const l of f.current)l in e&&i.push(e[l]);const x=[];for(const l of b.current)if(l in n)try{const d=n[l];switch(typeof d){case"function":continue;default:x.push(d);continue}}catch(d){I.Blac.instance.log("useBloc Error",d)}return[i,x]},[]),r=u.useMemo(()=>({subscribe:e=>{const n=o.current;if(!n)return()=>{};let i=!1;const x={fn:()=>{try{i||(i=!0,f.current=new Set,b.current=new Set,i=!1),e(n.state)}catch(d){console.error({e:d,blocInstance:n,dependencyArray:g})}},dependencyArray:g,id:s};I.Blac.activateBloc(n);const l=n._observer.subscribe(x);return()=>{l()}},getSnapshot:()=>{var e;return((e=o.current)==null?void 0:e.state)||{}},getServerSnapshot:()=>{var e;return((e=o.current)==null?void 0:e.state)||{}}}),[(t=o.current)==null?void 0:t.uid]);return{usedKeys:f,usedClassPropKeys:b,externalStore:r,instance:o,rid:s}};function P(y,c){var o,g;const{externalStore:S,usedKeys:h,usedClassPropKeys:v,instance:s,rid:B}=M(y,c),a=u.useSyncExternalStore(S.subscribe,S.getSnapshot,S.getServerSnapshot),m=u.useRef(new WeakMap),f=u.useRef(new WeakMap),b=u.useMemo(()=>{if(typeof a!="object"||a===null)return a;let r=m.current.get(a);return r||(r=new Proxy(a,{get(t,e){return h.current.add(e),t[e]},has(t,e){return e in t},ownKeys(t){return Reflect.ownKeys(t)},getOwnPropertyDescriptor(t,e){return Reflect.getOwnPropertyDescriptor(t,e)}}),m.current.set(a,r)),r},[a]),C=u.useMemo(()=>{if(!s.current)return null;let r=f.current.get(s.current);return r||(r=new Proxy(s.current,{get(t,e){if(!t)return null;const n=t[e];return typeof n!="function"&&v.current.add(e),n}}),f.current.set(s.current,r)),r},[(o=s.current)==null?void 0:o.uid]);return u.useEffect(()=>{var t;const r=s.current;if(r)return r._addConsumer(B),(t=c==null?void 0:c.onMount)==null||t.call(c,r),()=>{var e;r&&((e=c==null?void 0:c.onUnmount)==null||e.call(c,r),r._removeConsumer(B))}},[(g=s.current)==null?void 0:g.uid,B]),[b,C]}exports.externalBlocStore=M;exports.useBloc=P;
|
|
2
2
|
//# sourceMappingURL=index.cjs.js.map
|
package/dist/index.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs.js","sources":["../src/externalBlocStore.ts","../src/useBloc.tsx"],"sourcesContent":["import { Blac, BlacObserver, BlocBase, BlocBaseAbstract, BlocConstructor, BlocHookDependencyArrayFn, BlocState } from '@blac/core';\nimport { useCallback, useMemo, useRef } from 'react';\nimport { BlocHookOptions } from './useBloc';\n\nexport interface ExternalStore<\n B extends BlocConstructor<BlocBase<any>>\n> {\n /**\n * Subscribes to changes in the store and returns an unsubscribe function.\n * @param onStoreChange - Callback function that will be called whenever the store changes\n * @returns A function that can be called to unsubscribe from store changes\n */\n subscribe: (onStoreChange: (state: BlocState<InstanceType<B>>) => void) => () => void;\n\n /**\n * Gets the current snapshot of the store state.\n * @returns The current state of the store\n */\n getSnapshot: () => BlocState<InstanceType<B>>;\n\n /**\n * Gets the server snapshot of the store state.\n * This is optional and defaults to the same value as getSnapshot.\n * @returns The server state of the store\n */\n getServerSnapshot?: () => BlocState<InstanceType<B>>;\n}\n\nexport interface ExternalBlacStore<\n B extends BlocConstructor<BlocBase<any>>\n> {\n usedKeys: React.RefObject<Set<string>>;\n usedClassPropKeys: React.RefObject<Set<string>>;\n externalStore: ExternalStore<B>;\n instance: React.RefObject<InstanceType<B>>;\n rid: string;\n}\n\n/**\n * Creates an external store that wraps a Bloc instance, providing a React-compatible interface\n * for subscribing to and accessing bloc state.\n * \n * @template B - The type of the Bloc instance\n * @template S - The type of the Bloc state\n * @param bloc - The Bloc instance to wrap\n * @param dependencyArray - Function that returns an array of dependencies for the subscription\n * @param rid - Unique identifier for the subscription\n * @returns An ExternalStore instance that provides methods to subscribe to and access bloc state\n */\nconst useExternalBlocStore = <\n B extends BlocConstructor<BlocBase<any>>\n>(\n bloc: B,\n options: BlocHookOptions<InstanceType<B>> | undefined,\n): ExternalBlacStore<B> => {\n const { id: blocId, props, selector } = options ?? {};\n\n const rid = useMemo(() => {\n return crypto.randomUUID();\n }, []);\n\n const base = bloc as unknown as BlocBaseAbstract;\n\n const isIsolated = base.isolated;\n const effectiveBlocId = isIsolated ? rid : blocId;\n\n const usedKeys = useRef<Set<string>>(new Set());\n const usedClassPropKeys = useRef<Set<string>>(new Set());\n\n const getBloc = useCallback(() => {\n return Blac.getBloc(bloc, {\n id: effectiveBlocId,\n props,\n instanceRef: rid\n });\n }, [bloc, props]);\n\n const blocInstance = useRef<InstanceType<B>>(getBloc());\n\n\n const dependencyArray: BlocHookDependencyArrayFn<BlocState<InstanceType<B>>> =\n useMemo(\n () =>\n (newState): ReturnType<BlocHookDependencyArrayFn<BlocState<InstanceType<B>>>> => {\n const instance = blocInstance.current;\n\n if (!instance) {\n return [];\n }\n\n // Use custom dependency selector if provided\n if (selector) {\n return selector(newState);\n }\n\n // Fall back to bloc's default dependency selector if available\n if (instance.defaultDependencySelector) {\n return instance.defaultDependencySelector(newState);\n }\n\n // For primitive states, use default selector\n if (typeof newState !== 'object') {\n // Default behavior for primitive states: re-render if the state itself changes.\n return [[newState]];\n }\n\n // For object states, track which properties were actually used\n const usedStateValues: string[] = [];\n for (const key of usedKeys.current) {\n if (key in newState) {\n usedStateValues.push(newState[key as keyof typeof newState]);\n }\n }\n\n // Track used class properties for dependency tracking, this enables rerenders when class getters change\n const usedClassValues: unknown[] = [];\n for (const key of usedClassPropKeys.current) {\n if (key in instance) {\n try {\n const value = instance[key as keyof InstanceType<B>];\n switch (typeof value) {\n case 'function':\n continue;\n default:\n usedClassValues.push(value);\n continue;\n }\n } catch (error) {\n Blac.instance.log('useBloc Error', error);\n }\n }\n }\n\n return [usedStateValues, usedClassValues];\n },\n [],\n );\n\n const state: ExternalStore<B> = useMemo(() => {\n return {\n subscribe: (listener: (state: BlocState<InstanceType<B>>) => void) => {\n const observer: BlacObserver<BlocState<InstanceType<B>>> = {\n fn: () => {\n try {\n usedKeys.current = new Set();\n usedClassPropKeys.current = new Set();\n\n listener(blocInstance.current.state);\n } catch (e) {\n // Log any errors that occur during the listener callback\n // This ensures errors in listeners don't break the entire application\n console.error({\n e,\n blocInstance,\n dependencyArray,\n });\n }\n },\n // Pass the dependency array to control when the subscription is updated\n dependencyArray,\n // Use the provided id to identify this subscription\n id: rid,\n }\n\n Blac.activateBloc(blocInstance.current);\n\n // Subscribe to the bloc's observer with the provided listener function\n // This will trigger the callback whenever the bloc's state changes\n const unSub = blocInstance.current._observer.subscribe(observer);\n\n // Return an unsubscribe function that can be called to clean up the subscription\n return () => {\n unSub();\n };\n },\n // Return an immutable snapshot of the current bloc state\n getSnapshot: (): BlocState<InstanceType<B>> => blocInstance.current.state,\n // Server snapshot mirrors the client snapshot in this implementation\n getServerSnapshot: (): BlocState<InstanceType<B>> => blocInstance.current.state,\n }\n }, []);\n\n return {\n usedKeys,\n usedClassPropKeys,\n externalStore: state,\n instance: blocInstance,\n rid,\n };\n};\n\nexport default useExternalBlocStore;\n","import {\n BlocBase,\n BlocConstructor,\n BlocHookDependencyArrayFn,\n BlocState,\n InferPropsFromGeneric,\n} from '@blac/core';\nimport {\n useEffect,\n useMemo,\n useSyncExternalStore\n} from 'react';\nimport useExternalBlocStore from './externalBlocStore';\n\n/**\n * Type definition for the return type of the useBloc hook\n * @template B - Bloc constructor type\n */\ntype HookTypes<B extends BlocConstructor<BlocBase<any>>> = [\n BlocState<InstanceType<B>>,\n InstanceType<B>,\n];\n\n/**\n * Configuration options for the useBloc hook\n */\nexport interface BlocHookOptions<B extends BlocBase<any>> {\n id?: string;\n selector?: BlocHookDependencyArrayFn<BlocState<B>>;\n props?: InferPropsFromGeneric<B>;\n onMount?: (bloc: B) => void;\n onUnmount?: (bloc: B) => void;\n}\n\n/**\n * Default dependency selector that wraps the entire state in an array\n * @template T - State type\n * @param {T} s - Current state\n * @returns {Array<Array<T>>} Dependency array containing the entire state\n */\n\nconst log = (...args: unknown[]) => {\n console.log('useBloc', ...args);\n};\n\n/**\n * React hook for integrating with Blac state management\n *\n * This hook connects a React component to a Bloc state container, providing\n * automatic re-rendering when relevant state changes, dependency tracking,\n * and proper lifecycle management.\n *\n * @template B - Bloc constructor type\n * @template O - BlocHookOptions type\n * @param {B} bloc - Bloc constructor class\n * @param {O} [options] - Configuration options for the hook\n * @returns {HookTypes<B>} Tuple containing [state, bloc instance]\n *\n * @example\n * const [state, counterBloc] = useBloc(CounterBloc);\n * // Access state\n * console.log(state.count);\n * // Call bloc methods\n * counterBloc.increment();\n */\nexport default function useBloc<B extends BlocConstructor<BlocBase<any>>>(\n bloc: B,\n options?: BlocHookOptions<InstanceType<B>>,\n): HookTypes<B> {\n // Determine ID for isolated or shared blocs\n // const base = bloc as unknown as BlocBaseAbstract;\n // const isIsolated = base.isolated;\n // const effectiveBlocId = isIsolated ? rid : blocId;\n const {\n externalStore,\n usedKeys,\n usedClassPropKeys,\n instance,\n rid,\n } = useExternalBlocStore(bloc, options);\n\n // Subscribe to state changes using React's external store API\n const state = useSyncExternalStore<BlocState<InstanceType<B>>>(\n externalStore.subscribe,\n externalStore.getSnapshot,\n externalStore.getServerSnapshot,\n );\n\n const returnState = useMemo(() => {\n return typeof state === 'object'\n ? new Proxy(state, {\n get(_, prop) {\n usedKeys.current.add(prop as string);\n const value = state[prop as keyof typeof state];\n return value;\n },\n })\n : state;\n }, [state]);\n\n const returnClass = useMemo(() => {\n return new Proxy(instance.current, {\n get(_, prop) {\n if (!instance.current) {\n return null;\n }\n const value = instance.current[prop as keyof InstanceType<B>];\n if (typeof value !== 'function') {\n usedClassPropKeys.current.add(prop as string);\n }\n return value;\n },\n });\n }, [instance.current?.uid]);\n\n // Set up bloc lifecycle management\n useEffect(() => {\n instance.current._addConsumer(rid);\n\n // Call onMount callback if provided\n options?.onMount?.(instance.current);\n\n // Cleanup: remove this component as a consumer using the captured instance\n return () => {\n if (!instance.current) {\n return;\n }\n options?.onUnmount?.(instance.current);\n instance.current._removeConsumer(rid);\n };\n }, [instance.current, rid]); // Do not add options.onMount to deps, it will cause a loop\n\n return [returnState, returnClass];\n}\n"],"names":["useExternalBlocStore","bloc","options","blocId","props","selector","rid","useMemo","effectiveBlocId","usedKeys","useRef","usedClassPropKeys","getBloc","useCallback","Blac","blocInstance","dependencyArray","newState","instance","usedStateValues","key","usedClassValues","value","error","state","listener","observer","e","unSub","useExternalBlocStore$1","useBloc","externalStore","useSyncExternalStore","returnState","_","prop","returnClass","_a","useEffect"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":["../src/useExternalBlocStore.ts","../src/useBloc.tsx"],"sourcesContent":["import { Blac, BlacObserver, BlocBase, BlocBaseAbstract, BlocConstructor, BlocHookDependencyArrayFn, BlocState } from '@blac/core';\nimport { useCallback, useMemo, useRef } from 'react';\nimport { BlocHookOptions } from './useBloc';\n\nexport interface ExternalStore<\n B extends BlocConstructor<BlocBase<any>>\n> {\n /**\n * Subscribes to changes in the store and returns an unsubscribe function.\n * @param onStoreChange - Callback function that will be called whenever the store changes\n * @returns A function that can be called to unsubscribe from store changes\n */\n subscribe: (onStoreChange: (state: BlocState<InstanceType<B>>) => void) => () => void;\n\n /**\n * Gets the current snapshot of the store state.\n * @returns The current state of the store\n */\n getSnapshot: () => BlocState<InstanceType<B>>;\n\n /**\n * Gets the server snapshot of the store state.\n * This is optional and defaults to the same value as getSnapshot.\n * @returns The server state of the store\n */\n getServerSnapshot?: () => BlocState<InstanceType<B>>;\n}\n\nexport interface ExternalBlacStore<\n B extends BlocConstructor<BlocBase<any>>\n> {\n usedKeys: React.RefObject<Set<string>>;\n usedClassPropKeys: React.RefObject<Set<string>>;\n externalStore: ExternalStore<B>;\n instance: React.RefObject<InstanceType<B>>;\n rid: string;\n}\n\n/**\n * Creates an external store that wraps a Bloc instance, providing a React-compatible interface\n * for subscribing to and accessing bloc state.\n */\nconst useExternalBlocStore = <\n B extends BlocConstructor<BlocBase<any>>\n>(\n bloc: B,\n options: BlocHookOptions<InstanceType<B>> | undefined,\n): ExternalBlacStore<B> => {\n const { id: blocId, props, selector } = options ?? {};\n\n const rid = useMemo(() => {\n return crypto.randomUUID();\n }, []);\n\n const base = bloc as unknown as BlocBaseAbstract;\n\n const isIsolated = base.isolated;\n const effectiveBlocId = isIsolated ? rid : blocId;\n\n const usedKeys = useRef<Set<string>>(new Set());\n const usedClassPropKeys = useRef<Set<string>>(new Set());\n\n const getBloc = useCallback(() => {\n return Blac.getBloc(bloc, {\n id: effectiveBlocId,\n props,\n instanceRef: rid\n });\n }, [bloc, props]);\n\n const blocInstance = useRef<InstanceType<B>>(getBloc());\n\n\n const dependencyArray: BlocHookDependencyArrayFn<BlocState<InstanceType<B>>> =\n useMemo(\n () =>\n (newState): ReturnType<BlocHookDependencyArrayFn<BlocState<InstanceType<B>>>> => {\n const instance = blocInstance.current;\n\n if (!instance) {\n return [];\n }\n\n // Use custom dependency selector if provided\n if (selector) {\n return selector(newState);\n }\n\n // Fall back to bloc's default dependency selector if available\n if (instance.defaultDependencySelector) {\n return instance.defaultDependencySelector(newState);\n }\n\n // For primitive states, use default selector\n if (typeof newState !== 'object') {\n // Default behavior for primitive states: re-render if the state itself changes.\n return [[newState]];\n }\n\n // For object states, track which properties were actually used\n const usedStateValues: string[] = [];\n for (const key of usedKeys.current) {\n if (key in newState) {\n usedStateValues.push(newState[key as keyof typeof newState]);\n }\n }\n\n // Track used class properties for dependency tracking, this enables rerenders when class getters change\n const usedClassValues: unknown[] = [];\n for (const key of usedClassPropKeys.current) {\n if (key in instance) {\n try {\n const value = instance[key as keyof InstanceType<B>];\n switch (typeof value) {\n case 'function':\n continue;\n default:\n usedClassValues.push(value);\n continue;\n }\n } catch (error) {\n Blac.instance.log('useBloc Error', error);\n }\n }\n }\n\n return [usedStateValues, usedClassValues];\n },\n [],\n );\n\n const state: ExternalStore<B> = useMemo(() => {\n return {\n subscribe: (listener: (state: BlocState<InstanceType<B>>) => void) => {\n const currentInstance = blocInstance.current;\n if (!currentInstance) {\n return () => {}; // Return no-op if no instance\n }\n\n // Use a flag to prevent multiple resets during the same listener execution\n let isResetting = false;\n\n const observer: BlacObserver<BlocState<InstanceType<B>>> = {\n fn: () => {\n try {\n if (!isResetting) {\n isResetting = true;\n usedKeys.current = new Set();\n usedClassPropKeys.current = new Set();\n isResetting = false;\n }\n\n listener(currentInstance.state);\n } catch (e) {\n // Log any errors that occur during the listener callback\n // This ensures errors in listeners don't break the entire application\n console.error({\n e,\n blocInstance: currentInstance,\n dependencyArray,\n });\n }\n },\n // Pass the dependency array to control when the subscription is updated\n dependencyArray,\n // Use the provided id to identify this subscription\n id: rid,\n }\n\n Blac.activateBloc(currentInstance);\n\n // Subscribe to the bloc's observer with the provided listener function\n // This will trigger the callback whenever the bloc's state changes\n const unSub = currentInstance._observer.subscribe(observer);\n\n // Return an unsubscribe function that can be called to clean up the subscription\n return () => {\n unSub();\n };\n },\n // Return an immutable snapshot of the current bloc state\n getSnapshot: (): BlocState<InstanceType<B>> => blocInstance.current?.state || ({} as BlocState<InstanceType<B>>),\n // Server snapshot mirrors the client snapshot in this implementation\n getServerSnapshot: (): BlocState<InstanceType<B>> => blocInstance.current?.state || ({} as BlocState<InstanceType<B>>),\n }\n }, [blocInstance.current?.uid]); // Re-create store when instance changes\n\n return {\n usedKeys,\n usedClassPropKeys,\n externalStore: state,\n instance: blocInstance,\n rid,\n };\n};\n\nexport default useExternalBlocStore;\n","import {\n BlocBase,\n BlocConstructor,\n BlocHookDependencyArrayFn,\n BlocState,\n InferPropsFromGeneric,\n} from '@blac/core';\nimport {\n useEffect,\n useMemo,\n useRef,\n useSyncExternalStore\n} from 'react';\nimport useExternalBlocStore from './useExternalBlocStore';\n\n/**\n * Type definition for the return type of the useBloc hook\n * @template B - Bloc constructor type\n */\ntype HookTypes<B extends BlocConstructor<BlocBase<any>>> = [\n BlocState<InstanceType<B>>,\n InstanceType<B>,\n];\n\n/**\n * Configuration options for the useBloc hook\n */\nexport interface BlocHookOptions<B extends BlocBase<any>> {\n id?: string;\n selector?: BlocHookDependencyArrayFn<BlocState<B>>;\n props?: InferPropsFromGeneric<B>;\n onMount?: (bloc: B) => void;\n onUnmount?: (bloc: B) => void;\n}\n\n/**\n * Default dependency selector that wraps the entire state in an array\n * @template T - State type\n * @param {T} s - Current state\n * @returns {Array<Array<T>>} Dependency array containing the entire state\n */\n\nconst log = (...args: unknown[]) => {\n console.log('useBloc', ...args);\n};\n\n/**\n * React hook for integrating with Blac state management\n *\n * This hook connects a React component to a Bloc state container, providing\n * automatic re-rendering when relevant state changes, dependency tracking,\n * and proper lifecycle management.\n *\n * @template B - Bloc constructor type\n * @template O - BlocHookOptions type\n * @param {B} bloc - Bloc constructor class\n * @param {O} [options] - Configuration options for the hook\n * @returns {HookTypes<B>} Tuple containing [state, bloc instance]\n *\n * @example\n * const [state, counterBloc] = useBloc(CounterBloc);\n * // Access state\n * console.log(state.count);\n * // Call bloc methods\n * counterBloc.increment();\n */\nexport default function useBloc<B extends BlocConstructor<BlocBase<any>>>(\n bloc: B,\n options?: BlocHookOptions<InstanceType<B>>,\n): HookTypes<B> {\n // Determine ID for isolated or shared blocs\n // const base = bloc as unknown as BlocBaseAbstract;\n // const isIsolated = base.isolated;\n // const effectiveBlocId = isIsolated ? rid : blocId;\n const {\n externalStore,\n usedKeys,\n usedClassPropKeys,\n instance,\n rid,\n } = useExternalBlocStore(bloc, options);\n\n // Subscribe to state changes using React's external store API\n const state = useSyncExternalStore<BlocState<InstanceType<B>>>(\n externalStore.subscribe,\n externalStore.getSnapshot,\n externalStore.getServerSnapshot,\n );\n\n // Cache proxies to avoid recreation on every render\n const stateProxyCache = useRef<WeakMap<object, object>>(new WeakMap());\n const classProxyCache = useRef<WeakMap<object, object>>(new WeakMap());\n\n const returnState = useMemo(() => {\n if (typeof state !== 'object' || state === null) {\n return state;\n }\n\n // Check cache first\n let proxy = stateProxyCache.current.get(state);\n if (!proxy) {\n proxy = new Proxy(state, {\n get(target, prop) {\n usedKeys.current.add(prop as string);\n const value = target[prop as keyof typeof target];\n return value;\n },\n // Handle symbols and non-enumerable properties\n has(target, prop) {\n return prop in target;\n },\n ownKeys(target) {\n return Reflect.ownKeys(target);\n },\n getOwnPropertyDescriptor(target, prop) {\n return Reflect.getOwnPropertyDescriptor(target, prop);\n }\n });\n stateProxyCache.current.set(state as object, proxy as object);\n }\n return proxy;\n }, [state]);\n\n const returnClass = useMemo(() => {\n if (!instance.current) {\n return null;\n }\n\n // Check cache first\n let proxy = classProxyCache.current.get(instance.current);\n if (!proxy) {\n proxy = new Proxy(instance.current, {\n get(target, prop) {\n if (!target) {\n return null;\n }\n const value = target[prop as keyof InstanceType<B>];\n if (typeof value !== 'function') {\n usedClassPropKeys.current.add(prop as string);\n }\n return value;\n },\n });\n classProxyCache.current.set(instance.current, proxy);\n }\n return proxy;\n }, [instance.current?.uid]);\n\n // Set up bloc lifecycle management\n useEffect(() => {\n const currentInstance = instance.current;\n if (!currentInstance) return;\n \n currentInstance._addConsumer(rid);\n\n // Call onMount callback if provided\n options?.onMount?.(currentInstance);\n\n // Cleanup: remove this component as a consumer using the captured instance\n return () => {\n if (!currentInstance) {\n return;\n }\n options?.onUnmount?.(currentInstance);\n currentInstance._removeConsumer(rid);\n };\n }, [instance.current?.uid, rid]); // Use UID to ensure we re-run when instance changes\n\n return [returnState as BlocState<InstanceType<B>>, returnClass as InstanceType<B>];\n}\n"],"names":["useExternalBlocStore","bloc","options","blocId","props","selector","rid","useMemo","effectiveBlocId","usedKeys","useRef","usedClassPropKeys","getBloc","useCallback","Blac","blocInstance","dependencyArray","newState","instance","usedStateValues","key","usedClassValues","value","error","state","listener","currentInstance","isResetting","observer","e","unSub","_a","useBloc","externalStore","useSyncExternalStore","stateProxyCache","classProxyCache","returnState","proxy","target","prop","returnClass","useEffect","_b"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -26,13 +26,6 @@ declare interface ExternalBlacStore<B extends BlocConstructor<BlocBase<any>>> {
|
|
|
26
26
|
/**
|
|
27
27
|
* Creates an external store that wraps a Bloc instance, providing a React-compatible interface
|
|
28
28
|
* for subscribing to and accessing bloc state.
|
|
29
|
-
*
|
|
30
|
-
* @template B - The type of the Bloc instance
|
|
31
|
-
* @template S - The type of the Bloc state
|
|
32
|
-
* @param bloc - The Bloc instance to wrap
|
|
33
|
-
* @param dependencyArray - Function that returns an array of dependencies for the subscription
|
|
34
|
-
* @param rid - Unique identifier for the subscription
|
|
35
|
-
* @returns An ExternalStore instance that provides methods to subscribe to and access bloc state
|
|
36
29
|
*/
|
|
37
30
|
export declare const externalBlocStore: <B extends BlocConstructor<BlocBase<any>>>(bloc: B, options: BlocHookOptions<InstanceType<B>> | undefined) => ExternalBlacStore<B>;
|
|
38
31
|
|
package/dist/index.es.js
CHANGED
|
@@ -1,114 +1,148 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
import { useMemo as g, useRef as h, useCallback as P, useSyncExternalStore as p, useEffect as K } from "react";
|
|
2
|
+
import { Blac as w } from "@blac/core";
|
|
3
|
+
const k = (d, c) => {
|
|
4
|
+
var t;
|
|
5
|
+
const { id: y, props: x, selector: v } = c ?? {}, s = g(() => crypto.randomUUID(), []), B = d.isolated ? s : y, l = h(/* @__PURE__ */ new Set()), S = h(/* @__PURE__ */ new Set()), I = P(() => w.getBloc(d, {
|
|
6
|
+
id: B,
|
|
7
|
+
props: x,
|
|
8
|
+
instanceRef: s
|
|
9
|
+
}), [d, x]), u = h(I()), b = g(
|
|
10
|
+
() => (e) => {
|
|
11
|
+
const n = u.current;
|
|
12
|
+
if (!n)
|
|
12
13
|
return [];
|
|
13
|
-
if (
|
|
14
|
-
return
|
|
15
|
-
if (
|
|
16
|
-
return
|
|
17
|
-
if (typeof
|
|
18
|
-
return [[
|
|
19
|
-
const
|
|
20
|
-
for (const
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
for (const
|
|
24
|
-
if (
|
|
14
|
+
if (v)
|
|
15
|
+
return v(e);
|
|
16
|
+
if (n.defaultDependencySelector)
|
|
17
|
+
return n.defaultDependencySelector(e);
|
|
18
|
+
if (typeof e != "object")
|
|
19
|
+
return [[e]];
|
|
20
|
+
const f = [];
|
|
21
|
+
for (const a of l.current)
|
|
22
|
+
a in e && f.push(e[a]);
|
|
23
|
+
const C = [];
|
|
24
|
+
for (const a of S.current)
|
|
25
|
+
if (a in n)
|
|
25
26
|
try {
|
|
26
|
-
const
|
|
27
|
-
switch (typeof
|
|
27
|
+
const i = n[a];
|
|
28
|
+
switch (typeof i) {
|
|
28
29
|
case "function":
|
|
29
30
|
continue;
|
|
30
31
|
default:
|
|
31
|
-
|
|
32
|
+
C.push(i);
|
|
32
33
|
continue;
|
|
33
34
|
}
|
|
34
|
-
} catch (
|
|
35
|
-
|
|
35
|
+
} catch (i) {
|
|
36
|
+
w.instance.log("useBloc Error", i);
|
|
36
37
|
}
|
|
37
|
-
return [
|
|
38
|
+
return [f, C];
|
|
38
39
|
},
|
|
39
40
|
[]
|
|
40
|
-
),
|
|
41
|
-
subscribe: (
|
|
42
|
-
const
|
|
41
|
+
), r = g(() => ({
|
|
42
|
+
subscribe: (e) => {
|
|
43
|
+
const n = u.current;
|
|
44
|
+
if (!n)
|
|
45
|
+
return () => {
|
|
46
|
+
};
|
|
47
|
+
let f = !1;
|
|
48
|
+
const C = {
|
|
43
49
|
fn: () => {
|
|
44
50
|
try {
|
|
45
|
-
|
|
46
|
-
} catch (
|
|
51
|
+
f || (f = !0, l.current = /* @__PURE__ */ new Set(), S.current = /* @__PURE__ */ new Set(), f = !1), e(n.state);
|
|
52
|
+
} catch (i) {
|
|
47
53
|
console.error({
|
|
48
|
-
e:
|
|
49
|
-
blocInstance:
|
|
50
|
-
dependencyArray:
|
|
54
|
+
e: i,
|
|
55
|
+
blocInstance: n,
|
|
56
|
+
dependencyArray: b
|
|
51
57
|
});
|
|
52
58
|
}
|
|
53
59
|
},
|
|
54
60
|
// Pass the dependency array to control when the subscription is updated
|
|
55
|
-
dependencyArray:
|
|
61
|
+
dependencyArray: b,
|
|
56
62
|
// Use the provided id to identify this subscription
|
|
57
|
-
id:
|
|
63
|
+
id: s
|
|
58
64
|
};
|
|
59
|
-
|
|
60
|
-
const
|
|
65
|
+
w.activateBloc(n);
|
|
66
|
+
const a = n._observer.subscribe(C);
|
|
61
67
|
return () => {
|
|
62
|
-
|
|
68
|
+
a();
|
|
63
69
|
};
|
|
64
70
|
},
|
|
65
71
|
// Return an immutable snapshot of the current bloc state
|
|
66
|
-
getSnapshot: () =>
|
|
72
|
+
getSnapshot: () => {
|
|
73
|
+
var e;
|
|
74
|
+
return ((e = u.current) == null ? void 0 : e.state) || {};
|
|
75
|
+
},
|
|
67
76
|
// Server snapshot mirrors the client snapshot in this implementation
|
|
68
|
-
getServerSnapshot: () =>
|
|
69
|
-
|
|
77
|
+
getServerSnapshot: () => {
|
|
78
|
+
var e;
|
|
79
|
+
return ((e = u.current) == null ? void 0 : e.state) || {};
|
|
80
|
+
}
|
|
81
|
+
}), [(t = u.current) == null ? void 0 : t.uid]);
|
|
70
82
|
return {
|
|
71
|
-
usedKeys:
|
|
72
|
-
usedClassPropKeys:
|
|
73
|
-
externalStore:
|
|
74
|
-
instance:
|
|
75
|
-
rid:
|
|
83
|
+
usedKeys: l,
|
|
84
|
+
usedClassPropKeys: S,
|
|
85
|
+
externalStore: r,
|
|
86
|
+
instance: u,
|
|
87
|
+
rid: s
|
|
76
88
|
};
|
|
77
|
-
}
|
|
78
|
-
function
|
|
79
|
-
var
|
|
89
|
+
};
|
|
90
|
+
function E(d, c) {
|
|
91
|
+
var u, b;
|
|
80
92
|
const {
|
|
81
|
-
externalStore:
|
|
82
|
-
usedKeys:
|
|
83
|
-
usedClassPropKeys:
|
|
84
|
-
instance:
|
|
85
|
-
rid:
|
|
86
|
-
} = k(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
),
|
|
91
|
-
|
|
92
|
-
return
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
93
|
+
externalStore: y,
|
|
94
|
+
usedKeys: x,
|
|
95
|
+
usedClassPropKeys: v,
|
|
96
|
+
instance: s,
|
|
97
|
+
rid: m
|
|
98
|
+
} = k(d, c), o = p(
|
|
99
|
+
y.subscribe,
|
|
100
|
+
y.getSnapshot,
|
|
101
|
+
y.getServerSnapshot
|
|
102
|
+
), B = h(/* @__PURE__ */ new WeakMap()), l = h(/* @__PURE__ */ new WeakMap()), S = g(() => {
|
|
103
|
+
if (typeof o != "object" || o === null)
|
|
104
|
+
return o;
|
|
105
|
+
let r = B.current.get(o);
|
|
106
|
+
return r || (r = new Proxy(o, {
|
|
107
|
+
get(t, e) {
|
|
108
|
+
return x.current.add(e), t[e];
|
|
109
|
+
},
|
|
110
|
+
// Handle symbols and non-enumerable properties
|
|
111
|
+
has(t, e) {
|
|
112
|
+
return e in t;
|
|
113
|
+
},
|
|
114
|
+
ownKeys(t) {
|
|
115
|
+
return Reflect.ownKeys(t);
|
|
116
|
+
},
|
|
117
|
+
getOwnPropertyDescriptor(t, e) {
|
|
118
|
+
return Reflect.getOwnPropertyDescriptor(t, e);
|
|
119
|
+
}
|
|
120
|
+
}), B.current.set(o, r)), r;
|
|
121
|
+
}, [o]), I = g(() => {
|
|
122
|
+
if (!s.current)
|
|
123
|
+
return null;
|
|
124
|
+
let r = l.current.get(s.current);
|
|
125
|
+
return r || (r = new Proxy(s.current, {
|
|
126
|
+
get(t, e) {
|
|
127
|
+
if (!t)
|
|
128
|
+
return null;
|
|
129
|
+
const n = t[e];
|
|
130
|
+
return typeof n != "function" && v.current.add(e), n;
|
|
131
|
+
}
|
|
132
|
+
}), l.current.set(s.current, r)), r;
|
|
133
|
+
}, [(u = s.current) == null ? void 0 : u.uid]);
|
|
102
134
|
return K(() => {
|
|
103
|
-
var
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
r.
|
|
107
|
-
|
|
108
|
-
|
|
135
|
+
var t;
|
|
136
|
+
const r = s.current;
|
|
137
|
+
if (r)
|
|
138
|
+
return r._addConsumer(m), (t = c == null ? void 0 : c.onMount) == null || t.call(c, r), () => {
|
|
139
|
+
var e;
|
|
140
|
+
r && ((e = c == null ? void 0 : c.onUnmount) == null || e.call(c, r), r._removeConsumer(m));
|
|
141
|
+
};
|
|
142
|
+
}, [(b = s.current) == null ? void 0 : b.uid, m]), [S, I];
|
|
109
143
|
}
|
|
110
144
|
export {
|
|
111
145
|
k as externalBlocStore,
|
|
112
|
-
|
|
146
|
+
E as useBloc
|
|
113
147
|
};
|
|
114
148
|
//# sourceMappingURL=index.es.js.map
|
package/dist/index.es.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.es.js","sources":["../src/externalBlocStore.ts","../src/useBloc.tsx"],"sourcesContent":["import { Blac, BlacObserver, BlocBase, BlocBaseAbstract, BlocConstructor, BlocHookDependencyArrayFn, BlocState } from '@blac/core';\nimport { useCallback, useMemo, useRef } from 'react';\nimport { BlocHookOptions } from './useBloc';\n\nexport interface ExternalStore<\n B extends BlocConstructor<BlocBase<any>>\n> {\n /**\n * Subscribes to changes in the store and returns an unsubscribe function.\n * @param onStoreChange - Callback function that will be called whenever the store changes\n * @returns A function that can be called to unsubscribe from store changes\n */\n subscribe: (onStoreChange: (state: BlocState<InstanceType<B>>) => void) => () => void;\n\n /**\n * Gets the current snapshot of the store state.\n * @returns The current state of the store\n */\n getSnapshot: () => BlocState<InstanceType<B>>;\n\n /**\n * Gets the server snapshot of the store state.\n * This is optional and defaults to the same value as getSnapshot.\n * @returns The server state of the store\n */\n getServerSnapshot?: () => BlocState<InstanceType<B>>;\n}\n\nexport interface ExternalBlacStore<\n B extends BlocConstructor<BlocBase<any>>\n> {\n usedKeys: React.RefObject<Set<string>>;\n usedClassPropKeys: React.RefObject<Set<string>>;\n externalStore: ExternalStore<B>;\n instance: React.RefObject<InstanceType<B>>;\n rid: string;\n}\n\n/**\n * Creates an external store that wraps a Bloc instance, providing a React-compatible interface\n * for subscribing to and accessing bloc state.\n * \n * @template B - The type of the Bloc instance\n * @template S - The type of the Bloc state\n * @param bloc - The Bloc instance to wrap\n * @param dependencyArray - Function that returns an array of dependencies for the subscription\n * @param rid - Unique identifier for the subscription\n * @returns An ExternalStore instance that provides methods to subscribe to and access bloc state\n */\nconst useExternalBlocStore = <\n B extends BlocConstructor<BlocBase<any>>\n>(\n bloc: B,\n options: BlocHookOptions<InstanceType<B>> | undefined,\n): ExternalBlacStore<B> => {\n const { id: blocId, props, selector } = options ?? {};\n\n const rid = useMemo(() => {\n return crypto.randomUUID();\n }, []);\n\n const base = bloc as unknown as BlocBaseAbstract;\n\n const isIsolated = base.isolated;\n const effectiveBlocId = isIsolated ? rid : blocId;\n\n const usedKeys = useRef<Set<string>>(new Set());\n const usedClassPropKeys = useRef<Set<string>>(new Set());\n\n const getBloc = useCallback(() => {\n return Blac.getBloc(bloc, {\n id: effectiveBlocId,\n props,\n instanceRef: rid\n });\n }, [bloc, props]);\n\n const blocInstance = useRef<InstanceType<B>>(getBloc());\n\n\n const dependencyArray: BlocHookDependencyArrayFn<BlocState<InstanceType<B>>> =\n useMemo(\n () =>\n (newState): ReturnType<BlocHookDependencyArrayFn<BlocState<InstanceType<B>>>> => {\n const instance = blocInstance.current;\n\n if (!instance) {\n return [];\n }\n\n // Use custom dependency selector if provided\n if (selector) {\n return selector(newState);\n }\n\n // Fall back to bloc's default dependency selector if available\n if (instance.defaultDependencySelector) {\n return instance.defaultDependencySelector(newState);\n }\n\n // For primitive states, use default selector\n if (typeof newState !== 'object') {\n // Default behavior for primitive states: re-render if the state itself changes.\n return [[newState]];\n }\n\n // For object states, track which properties were actually used\n const usedStateValues: string[] = [];\n for (const key of usedKeys.current) {\n if (key in newState) {\n usedStateValues.push(newState[key as keyof typeof newState]);\n }\n }\n\n // Track used class properties for dependency tracking, this enables rerenders when class getters change\n const usedClassValues: unknown[] = [];\n for (const key of usedClassPropKeys.current) {\n if (key in instance) {\n try {\n const value = instance[key as keyof InstanceType<B>];\n switch (typeof value) {\n case 'function':\n continue;\n default:\n usedClassValues.push(value);\n continue;\n }\n } catch (error) {\n Blac.instance.log('useBloc Error', error);\n }\n }\n }\n\n return [usedStateValues, usedClassValues];\n },\n [],\n );\n\n const state: ExternalStore<B> = useMemo(() => {\n return {\n subscribe: (listener: (state: BlocState<InstanceType<B>>) => void) => {\n const observer: BlacObserver<BlocState<InstanceType<B>>> = {\n fn: () => {\n try {\n usedKeys.current = new Set();\n usedClassPropKeys.current = new Set();\n\n listener(blocInstance.current.state);\n } catch (e) {\n // Log any errors that occur during the listener callback\n // This ensures errors in listeners don't break the entire application\n console.error({\n e,\n blocInstance,\n dependencyArray,\n });\n }\n },\n // Pass the dependency array to control when the subscription is updated\n dependencyArray,\n // Use the provided id to identify this subscription\n id: rid,\n }\n\n Blac.activateBloc(blocInstance.current);\n\n // Subscribe to the bloc's observer with the provided listener function\n // This will trigger the callback whenever the bloc's state changes\n const unSub = blocInstance.current._observer.subscribe(observer);\n\n // Return an unsubscribe function that can be called to clean up the subscription\n return () => {\n unSub();\n };\n },\n // Return an immutable snapshot of the current bloc state\n getSnapshot: (): BlocState<InstanceType<B>> => blocInstance.current.state,\n // Server snapshot mirrors the client snapshot in this implementation\n getServerSnapshot: (): BlocState<InstanceType<B>> => blocInstance.current.state,\n }\n }, []);\n\n return {\n usedKeys,\n usedClassPropKeys,\n externalStore: state,\n instance: blocInstance,\n rid,\n };\n};\n\nexport default useExternalBlocStore;\n","import {\n BlocBase,\n BlocConstructor,\n BlocHookDependencyArrayFn,\n BlocState,\n InferPropsFromGeneric,\n} from '@blac/core';\nimport {\n useEffect,\n useMemo,\n useSyncExternalStore\n} from 'react';\nimport useExternalBlocStore from './externalBlocStore';\n\n/**\n * Type definition for the return type of the useBloc hook\n * @template B - Bloc constructor type\n */\ntype HookTypes<B extends BlocConstructor<BlocBase<any>>> = [\n BlocState<InstanceType<B>>,\n InstanceType<B>,\n];\n\n/**\n * Configuration options for the useBloc hook\n */\nexport interface BlocHookOptions<B extends BlocBase<any>> {\n id?: string;\n selector?: BlocHookDependencyArrayFn<BlocState<B>>;\n props?: InferPropsFromGeneric<B>;\n onMount?: (bloc: B) => void;\n onUnmount?: (bloc: B) => void;\n}\n\n/**\n * Default dependency selector that wraps the entire state in an array\n * @template T - State type\n * @param {T} s - Current state\n * @returns {Array<Array<T>>} Dependency array containing the entire state\n */\n\nconst log = (...args: unknown[]) => {\n console.log('useBloc', ...args);\n};\n\n/**\n * React hook for integrating with Blac state management\n *\n * This hook connects a React component to a Bloc state container, providing\n * automatic re-rendering when relevant state changes, dependency tracking,\n * and proper lifecycle management.\n *\n * @template B - Bloc constructor type\n * @template O - BlocHookOptions type\n * @param {B} bloc - Bloc constructor class\n * @param {O} [options] - Configuration options for the hook\n * @returns {HookTypes<B>} Tuple containing [state, bloc instance]\n *\n * @example\n * const [state, counterBloc] = useBloc(CounterBloc);\n * // Access state\n * console.log(state.count);\n * // Call bloc methods\n * counterBloc.increment();\n */\nexport default function useBloc<B extends BlocConstructor<BlocBase<any>>>(\n bloc: B,\n options?: BlocHookOptions<InstanceType<B>>,\n): HookTypes<B> {\n // Determine ID for isolated or shared blocs\n // const base = bloc as unknown as BlocBaseAbstract;\n // const isIsolated = base.isolated;\n // const effectiveBlocId = isIsolated ? rid : blocId;\n const {\n externalStore,\n usedKeys,\n usedClassPropKeys,\n instance,\n rid,\n } = useExternalBlocStore(bloc, options);\n\n // Subscribe to state changes using React's external store API\n const state = useSyncExternalStore<BlocState<InstanceType<B>>>(\n externalStore.subscribe,\n externalStore.getSnapshot,\n externalStore.getServerSnapshot,\n );\n\n const returnState = useMemo(() => {\n return typeof state === 'object'\n ? new Proxy(state, {\n get(_, prop) {\n usedKeys.current.add(prop as string);\n const value = state[prop as keyof typeof state];\n return value;\n },\n })\n : state;\n }, [state]);\n\n const returnClass = useMemo(() => {\n return new Proxy(instance.current, {\n get(_, prop) {\n if (!instance.current) {\n return null;\n }\n const value = instance.current[prop as keyof InstanceType<B>];\n if (typeof value !== 'function') {\n usedClassPropKeys.current.add(prop as string);\n }\n return value;\n },\n });\n }, [instance.current?.uid]);\n\n // Set up bloc lifecycle management\n useEffect(() => {\n instance.current._addConsumer(rid);\n\n // Call onMount callback if provided\n options?.onMount?.(instance.current);\n\n // Cleanup: remove this component as a consumer using the captured instance\n return () => {\n if (!instance.current) {\n return;\n }\n options?.onUnmount?.(instance.current);\n instance.current._removeConsumer(rid);\n };\n }, [instance.current, rid]); // Do not add options.onMount to deps, it will cause a loop\n\n return [returnState, returnClass];\n}\n"],"names":["useExternalBlocStore","bloc","options","blocId","props","selector","rid","useMemo","effectiveBlocId","usedKeys","useRef","usedClassPropKeys","getBloc","useCallback","Blac","blocInstance","dependencyArray","newState","instance","usedStateValues","key","usedClassValues","value","error","state","listener","observer","e","unSub","useExternalBlocStore$1","useBloc","externalStore","useSyncExternalStore","returnState","_","prop","returnClass","_a","useEffect"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.es.js","sources":["../src/useExternalBlocStore.ts","../src/useBloc.tsx"],"sourcesContent":["import { Blac, BlacObserver, BlocBase, BlocBaseAbstract, BlocConstructor, BlocHookDependencyArrayFn, BlocState } from '@blac/core';\nimport { useCallback, useMemo, useRef } from 'react';\nimport { BlocHookOptions } from './useBloc';\n\nexport interface ExternalStore<\n B extends BlocConstructor<BlocBase<any>>\n> {\n /**\n * Subscribes to changes in the store and returns an unsubscribe function.\n * @param onStoreChange - Callback function that will be called whenever the store changes\n * @returns A function that can be called to unsubscribe from store changes\n */\n subscribe: (onStoreChange: (state: BlocState<InstanceType<B>>) => void) => () => void;\n\n /**\n * Gets the current snapshot of the store state.\n * @returns The current state of the store\n */\n getSnapshot: () => BlocState<InstanceType<B>>;\n\n /**\n * Gets the server snapshot of the store state.\n * This is optional and defaults to the same value as getSnapshot.\n * @returns The server state of the store\n */\n getServerSnapshot?: () => BlocState<InstanceType<B>>;\n}\n\nexport interface ExternalBlacStore<\n B extends BlocConstructor<BlocBase<any>>\n> {\n usedKeys: React.RefObject<Set<string>>;\n usedClassPropKeys: React.RefObject<Set<string>>;\n externalStore: ExternalStore<B>;\n instance: React.RefObject<InstanceType<B>>;\n rid: string;\n}\n\n/**\n * Creates an external store that wraps a Bloc instance, providing a React-compatible interface\n * for subscribing to and accessing bloc state.\n */\nconst useExternalBlocStore = <\n B extends BlocConstructor<BlocBase<any>>\n>(\n bloc: B,\n options: BlocHookOptions<InstanceType<B>> | undefined,\n): ExternalBlacStore<B> => {\n const { id: blocId, props, selector } = options ?? {};\n\n const rid = useMemo(() => {\n return crypto.randomUUID();\n }, []);\n\n const base = bloc as unknown as BlocBaseAbstract;\n\n const isIsolated = base.isolated;\n const effectiveBlocId = isIsolated ? rid : blocId;\n\n const usedKeys = useRef<Set<string>>(new Set());\n const usedClassPropKeys = useRef<Set<string>>(new Set());\n\n const getBloc = useCallback(() => {\n return Blac.getBloc(bloc, {\n id: effectiveBlocId,\n props,\n instanceRef: rid\n });\n }, [bloc, props]);\n\n const blocInstance = useRef<InstanceType<B>>(getBloc());\n\n\n const dependencyArray: BlocHookDependencyArrayFn<BlocState<InstanceType<B>>> =\n useMemo(\n () =>\n (newState): ReturnType<BlocHookDependencyArrayFn<BlocState<InstanceType<B>>>> => {\n const instance = blocInstance.current;\n\n if (!instance) {\n return [];\n }\n\n // Use custom dependency selector if provided\n if (selector) {\n return selector(newState);\n }\n\n // Fall back to bloc's default dependency selector if available\n if (instance.defaultDependencySelector) {\n return instance.defaultDependencySelector(newState);\n }\n\n // For primitive states, use default selector\n if (typeof newState !== 'object') {\n // Default behavior for primitive states: re-render if the state itself changes.\n return [[newState]];\n }\n\n // For object states, track which properties were actually used\n const usedStateValues: string[] = [];\n for (const key of usedKeys.current) {\n if (key in newState) {\n usedStateValues.push(newState[key as keyof typeof newState]);\n }\n }\n\n // Track used class properties for dependency tracking, this enables rerenders when class getters change\n const usedClassValues: unknown[] = [];\n for (const key of usedClassPropKeys.current) {\n if (key in instance) {\n try {\n const value = instance[key as keyof InstanceType<B>];\n switch (typeof value) {\n case 'function':\n continue;\n default:\n usedClassValues.push(value);\n continue;\n }\n } catch (error) {\n Blac.instance.log('useBloc Error', error);\n }\n }\n }\n\n return [usedStateValues, usedClassValues];\n },\n [],\n );\n\n const state: ExternalStore<B> = useMemo(() => {\n return {\n subscribe: (listener: (state: BlocState<InstanceType<B>>) => void) => {\n const currentInstance = blocInstance.current;\n if (!currentInstance) {\n return () => {}; // Return no-op if no instance\n }\n\n // Use a flag to prevent multiple resets during the same listener execution\n let isResetting = false;\n\n const observer: BlacObserver<BlocState<InstanceType<B>>> = {\n fn: () => {\n try {\n if (!isResetting) {\n isResetting = true;\n usedKeys.current = new Set();\n usedClassPropKeys.current = new Set();\n isResetting = false;\n }\n\n listener(currentInstance.state);\n } catch (e) {\n // Log any errors that occur during the listener callback\n // This ensures errors in listeners don't break the entire application\n console.error({\n e,\n blocInstance: currentInstance,\n dependencyArray,\n });\n }\n },\n // Pass the dependency array to control when the subscription is updated\n dependencyArray,\n // Use the provided id to identify this subscription\n id: rid,\n }\n\n Blac.activateBloc(currentInstance);\n\n // Subscribe to the bloc's observer with the provided listener function\n // This will trigger the callback whenever the bloc's state changes\n const unSub = currentInstance._observer.subscribe(observer);\n\n // Return an unsubscribe function that can be called to clean up the subscription\n return () => {\n unSub();\n };\n },\n // Return an immutable snapshot of the current bloc state\n getSnapshot: (): BlocState<InstanceType<B>> => blocInstance.current?.state || ({} as BlocState<InstanceType<B>>),\n // Server snapshot mirrors the client snapshot in this implementation\n getServerSnapshot: (): BlocState<InstanceType<B>> => blocInstance.current?.state || ({} as BlocState<InstanceType<B>>),\n }\n }, [blocInstance.current?.uid]); // Re-create store when instance changes\n\n return {\n usedKeys,\n usedClassPropKeys,\n externalStore: state,\n instance: blocInstance,\n rid,\n };\n};\n\nexport default useExternalBlocStore;\n","import {\n BlocBase,\n BlocConstructor,\n BlocHookDependencyArrayFn,\n BlocState,\n InferPropsFromGeneric,\n} from '@blac/core';\nimport {\n useEffect,\n useMemo,\n useRef,\n useSyncExternalStore\n} from 'react';\nimport useExternalBlocStore from './useExternalBlocStore';\n\n/**\n * Type definition for the return type of the useBloc hook\n * @template B - Bloc constructor type\n */\ntype HookTypes<B extends BlocConstructor<BlocBase<any>>> = [\n BlocState<InstanceType<B>>,\n InstanceType<B>,\n];\n\n/**\n * Configuration options for the useBloc hook\n */\nexport interface BlocHookOptions<B extends BlocBase<any>> {\n id?: string;\n selector?: BlocHookDependencyArrayFn<BlocState<B>>;\n props?: InferPropsFromGeneric<B>;\n onMount?: (bloc: B) => void;\n onUnmount?: (bloc: B) => void;\n}\n\n/**\n * Default dependency selector that wraps the entire state in an array\n * @template T - State type\n * @param {T} s - Current state\n * @returns {Array<Array<T>>} Dependency array containing the entire state\n */\n\nconst log = (...args: unknown[]) => {\n console.log('useBloc', ...args);\n};\n\n/**\n * React hook for integrating with Blac state management\n *\n * This hook connects a React component to a Bloc state container, providing\n * automatic re-rendering when relevant state changes, dependency tracking,\n * and proper lifecycle management.\n *\n * @template B - Bloc constructor type\n * @template O - BlocHookOptions type\n * @param {B} bloc - Bloc constructor class\n * @param {O} [options] - Configuration options for the hook\n * @returns {HookTypes<B>} Tuple containing [state, bloc instance]\n *\n * @example\n * const [state, counterBloc] = useBloc(CounterBloc);\n * // Access state\n * console.log(state.count);\n * // Call bloc methods\n * counterBloc.increment();\n */\nexport default function useBloc<B extends BlocConstructor<BlocBase<any>>>(\n bloc: B,\n options?: BlocHookOptions<InstanceType<B>>,\n): HookTypes<B> {\n // Determine ID for isolated or shared blocs\n // const base = bloc as unknown as BlocBaseAbstract;\n // const isIsolated = base.isolated;\n // const effectiveBlocId = isIsolated ? rid : blocId;\n const {\n externalStore,\n usedKeys,\n usedClassPropKeys,\n instance,\n rid,\n } = useExternalBlocStore(bloc, options);\n\n // Subscribe to state changes using React's external store API\n const state = useSyncExternalStore<BlocState<InstanceType<B>>>(\n externalStore.subscribe,\n externalStore.getSnapshot,\n externalStore.getServerSnapshot,\n );\n\n // Cache proxies to avoid recreation on every render\n const stateProxyCache = useRef<WeakMap<object, object>>(new WeakMap());\n const classProxyCache = useRef<WeakMap<object, object>>(new WeakMap());\n\n const returnState = useMemo(() => {\n if (typeof state !== 'object' || state === null) {\n return state;\n }\n\n // Check cache first\n let proxy = stateProxyCache.current.get(state);\n if (!proxy) {\n proxy = new Proxy(state, {\n get(target, prop) {\n usedKeys.current.add(prop as string);\n const value = target[prop as keyof typeof target];\n return value;\n },\n // Handle symbols and non-enumerable properties\n has(target, prop) {\n return prop in target;\n },\n ownKeys(target) {\n return Reflect.ownKeys(target);\n },\n getOwnPropertyDescriptor(target, prop) {\n return Reflect.getOwnPropertyDescriptor(target, prop);\n }\n });\n stateProxyCache.current.set(state as object, proxy as object);\n }\n return proxy;\n }, [state]);\n\n const returnClass = useMemo(() => {\n if (!instance.current) {\n return null;\n }\n\n // Check cache first\n let proxy = classProxyCache.current.get(instance.current);\n if (!proxy) {\n proxy = new Proxy(instance.current, {\n get(target, prop) {\n if (!target) {\n return null;\n }\n const value = target[prop as keyof InstanceType<B>];\n if (typeof value !== 'function') {\n usedClassPropKeys.current.add(prop as string);\n }\n return value;\n },\n });\n classProxyCache.current.set(instance.current, proxy);\n }\n return proxy;\n }, [instance.current?.uid]);\n\n // Set up bloc lifecycle management\n useEffect(() => {\n const currentInstance = instance.current;\n if (!currentInstance) return;\n \n currentInstance._addConsumer(rid);\n\n // Call onMount callback if provided\n options?.onMount?.(currentInstance);\n\n // Cleanup: remove this component as a consumer using the captured instance\n return () => {\n if (!currentInstance) {\n return;\n }\n options?.onUnmount?.(currentInstance);\n currentInstance._removeConsumer(rid);\n };\n }, [instance.current?.uid, rid]); // Use UID to ensure we re-run when instance changes\n\n return [returnState as BlocState<InstanceType<B>>, returnClass as InstanceType<B>];\n}\n"],"names":["useExternalBlocStore","bloc","options","blocId","props","selector","rid","useMemo","effectiveBlocId","usedKeys","useRef","usedClassPropKeys","getBloc","useCallback","Blac","blocInstance","dependencyArray","newState","instance","usedStateValues","key","usedClassValues","value","error","state","listener","currentInstance","isResetting","observer","e","unSub","_a","useBloc","externalStore","useSyncExternalStore","stateProxyCache","classProxyCache","returnState","proxy","target","prop","returnClass","useEffect","_b"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blac/react",
|
|
3
|
-
"version": "2.0.0-rc-
|
|
3
|
+
"version": "2.0.0-rc-5",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Brendan Mullins <jsnanigans@gmail.com>",
|
|
6
6
|
"main": "dist/index.cjs.js",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"@types/react-dom": "^18.0.0 || ^19.0.0",
|
|
31
31
|
"react": "^18.0.0 || ^19.0.0",
|
|
32
32
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
33
|
-
"@blac/core": "2.0.0-rc-
|
|
33
|
+
"@blac/core": "2.0.0-rc-5"
|
|
34
34
|
},
|
|
35
35
|
"peerDependenciesMeta": {
|
|
36
36
|
"@types/react": {
|
package/src/index.ts
CHANGED
package/src/useBloc.tsx
CHANGED
|
@@ -8,9 +8,10 @@ import {
|
|
|
8
8
|
import {
|
|
9
9
|
useEffect,
|
|
10
10
|
useMemo,
|
|
11
|
+
useRef,
|
|
11
12
|
useSyncExternalStore
|
|
12
13
|
} from 'react';
|
|
13
|
-
import useExternalBlocStore from './
|
|
14
|
+
import useExternalBlocStore from './useExternalBlocStore';
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Type definition for the return type of the useBloc hook
|
|
@@ -86,49 +87,84 @@ export default function useBloc<B extends BlocConstructor<BlocBase<any>>>(
|
|
|
86
87
|
externalStore.getServerSnapshot,
|
|
87
88
|
);
|
|
88
89
|
|
|
90
|
+
// Cache proxies to avoid recreation on every render
|
|
91
|
+
const stateProxyCache = useRef<WeakMap<object, object>>(new WeakMap());
|
|
92
|
+
const classProxyCache = useRef<WeakMap<object, object>>(new WeakMap());
|
|
93
|
+
|
|
89
94
|
const returnState = useMemo(() => {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
if (typeof state !== 'object' || state === null) {
|
|
96
|
+
return state;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Check cache first
|
|
100
|
+
let proxy = stateProxyCache.current.get(state);
|
|
101
|
+
if (!proxy) {
|
|
102
|
+
proxy = new Proxy(state, {
|
|
103
|
+
get(target, prop) {
|
|
104
|
+
usedKeys.current.add(prop as string);
|
|
105
|
+
const value = target[prop as keyof typeof target];
|
|
106
|
+
return value;
|
|
107
|
+
},
|
|
108
|
+
// Handle symbols and non-enumerable properties
|
|
109
|
+
has(target, prop) {
|
|
110
|
+
return prop in target;
|
|
111
|
+
},
|
|
112
|
+
ownKeys(target) {
|
|
113
|
+
return Reflect.ownKeys(target);
|
|
114
|
+
},
|
|
115
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
116
|
+
return Reflect.getOwnPropertyDescriptor(target, prop);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
stateProxyCache.current.set(state as object, proxy as object);
|
|
120
|
+
}
|
|
121
|
+
return proxy;
|
|
99
122
|
}, [state]);
|
|
100
123
|
|
|
101
124
|
const returnClass = useMemo(() => {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
125
|
+
if (!instance.current) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Check cache first
|
|
130
|
+
let proxy = classProxyCache.current.get(instance.current);
|
|
131
|
+
if (!proxy) {
|
|
132
|
+
proxy = new Proxy(instance.current, {
|
|
133
|
+
get(target, prop) {
|
|
134
|
+
if (!target) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
const value = target[prop as keyof InstanceType<B>];
|
|
138
|
+
if (typeof value !== 'function') {
|
|
139
|
+
usedClassPropKeys.current.add(prop as string);
|
|
140
|
+
}
|
|
141
|
+
return value;
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
classProxyCache.current.set(instance.current, proxy);
|
|
145
|
+
}
|
|
146
|
+
return proxy;
|
|
114
147
|
}, [instance.current?.uid]);
|
|
115
148
|
|
|
116
149
|
// Set up bloc lifecycle management
|
|
117
150
|
useEffect(() => {
|
|
118
|
-
instance.current
|
|
151
|
+
const currentInstance = instance.current;
|
|
152
|
+
if (!currentInstance) return;
|
|
153
|
+
|
|
154
|
+
currentInstance._addConsumer(rid);
|
|
119
155
|
|
|
120
156
|
// Call onMount callback if provided
|
|
121
|
-
options?.onMount?.(
|
|
157
|
+
options?.onMount?.(currentInstance);
|
|
122
158
|
|
|
123
159
|
// Cleanup: remove this component as a consumer using the captured instance
|
|
124
160
|
return () => {
|
|
125
|
-
if (!
|
|
161
|
+
if (!currentInstance) {
|
|
126
162
|
return;
|
|
127
163
|
}
|
|
128
|
-
options?.onUnmount?.(
|
|
129
|
-
|
|
164
|
+
options?.onUnmount?.(currentInstance);
|
|
165
|
+
currentInstance._removeConsumer(rid);
|
|
130
166
|
};
|
|
131
|
-
}, [instance.current, rid]); //
|
|
167
|
+
}, [instance.current?.uid, rid]); // Use UID to ensure we re-run when instance changes
|
|
132
168
|
|
|
133
|
-
return [returnState
|
|
169
|
+
return [returnState as BlocState<InstanceType<B>>, returnClass as InstanceType<B>];
|
|
134
170
|
}
|
|
@@ -39,13 +39,6 @@ export interface ExternalBlacStore<
|
|
|
39
39
|
/**
|
|
40
40
|
* Creates an external store that wraps a Bloc instance, providing a React-compatible interface
|
|
41
41
|
* for subscribing to and accessing bloc state.
|
|
42
|
-
*
|
|
43
|
-
* @template B - The type of the Bloc instance
|
|
44
|
-
* @template S - The type of the Bloc state
|
|
45
|
-
* @param bloc - The Bloc instance to wrap
|
|
46
|
-
* @param dependencyArray - Function that returns an array of dependencies for the subscription
|
|
47
|
-
* @param rid - Unique identifier for the subscription
|
|
48
|
-
* @returns An ExternalStore instance that provides methods to subscribe to and access bloc state
|
|
49
42
|
*/
|
|
50
43
|
const useExternalBlocStore = <
|
|
51
44
|
B extends BlocConstructor<BlocBase<any>>
|
|
@@ -139,19 +132,31 @@ const useExternalBlocStore = <
|
|
|
139
132
|
const state: ExternalStore<B> = useMemo(() => {
|
|
140
133
|
return {
|
|
141
134
|
subscribe: (listener: (state: BlocState<InstanceType<B>>) => void) => {
|
|
135
|
+
const currentInstance = blocInstance.current;
|
|
136
|
+
if (!currentInstance) {
|
|
137
|
+
return () => {}; // Return no-op if no instance
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Use a flag to prevent multiple resets during the same listener execution
|
|
141
|
+
let isResetting = false;
|
|
142
|
+
|
|
142
143
|
const observer: BlacObserver<BlocState<InstanceType<B>>> = {
|
|
143
144
|
fn: () => {
|
|
144
145
|
try {
|
|
145
|
-
|
|
146
|
-
|
|
146
|
+
if (!isResetting) {
|
|
147
|
+
isResetting = true;
|
|
148
|
+
usedKeys.current = new Set();
|
|
149
|
+
usedClassPropKeys.current = new Set();
|
|
150
|
+
isResetting = false;
|
|
151
|
+
}
|
|
147
152
|
|
|
148
|
-
listener(
|
|
153
|
+
listener(currentInstance.state);
|
|
149
154
|
} catch (e) {
|
|
150
155
|
// Log any errors that occur during the listener callback
|
|
151
156
|
// This ensures errors in listeners don't break the entire application
|
|
152
157
|
console.error({
|
|
153
158
|
e,
|
|
154
|
-
blocInstance,
|
|
159
|
+
blocInstance: currentInstance,
|
|
155
160
|
dependencyArray,
|
|
156
161
|
});
|
|
157
162
|
}
|
|
@@ -162,11 +167,11 @@ const useExternalBlocStore = <
|
|
|
162
167
|
id: rid,
|
|
163
168
|
}
|
|
164
169
|
|
|
165
|
-
Blac.activateBloc(
|
|
170
|
+
Blac.activateBloc(currentInstance);
|
|
166
171
|
|
|
167
172
|
// Subscribe to the bloc's observer with the provided listener function
|
|
168
173
|
// This will trigger the callback whenever the bloc's state changes
|
|
169
|
-
const unSub =
|
|
174
|
+
const unSub = currentInstance._observer.subscribe(observer);
|
|
170
175
|
|
|
171
176
|
// Return an unsubscribe function that can be called to clean up the subscription
|
|
172
177
|
return () => {
|
|
@@ -174,11 +179,11 @@ const useExternalBlocStore = <
|
|
|
174
179
|
};
|
|
175
180
|
},
|
|
176
181
|
// Return an immutable snapshot of the current bloc state
|
|
177
|
-
getSnapshot: (): BlocState<InstanceType<B>> => blocInstance.current
|
|
182
|
+
getSnapshot: (): BlocState<InstanceType<B>> => blocInstance.current?.state || ({} as BlocState<InstanceType<B>>),
|
|
178
183
|
// Server snapshot mirrors the client snapshot in this implementation
|
|
179
|
-
getServerSnapshot: (): BlocState<InstanceType<B>> => blocInstance.current
|
|
184
|
+
getServerSnapshot: (): BlocState<InstanceType<B>> => blocInstance.current?.state || ({} as BlocState<InstanceType<B>>),
|
|
180
185
|
}
|
|
181
|
-
}, []);
|
|
186
|
+
}, [blocInstance.current?.uid]); // Re-create store when instance changes
|
|
182
187
|
|
|
183
188
|
return {
|
|
184
189
|
usedKeys,
|