@blac/react 2.0.0-rc-13 → 2.0.0-rc-14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs.js +2 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +3 -4
- package/dist/index.es.js +105 -76
- package/dist/index.es.js.map +1 -1
- package/package.json +11 -11
- package/src/__tests__/rerenderLogging.test.tsx +257 -0
- package/src/__tests__/useBloc.disposal.test.tsx +377 -0
- package/src/__tests__/useBloc.instanceChange.test.tsx +200 -0
- package/src/__tests__/useBloc.props.test.tsx +166 -144
- package/src/__tests__/useBloc.proxyConfig.test.tsx +5 -5
- package/src/__tests__/useBloc.staticProps.test.tsx +297 -0
- package/src/__tests__/useBloc.tracking.test.tsx +7 -3
- package/src/useBloc.ts +108 -33
- package/src/useExternalBlocStore.ts +14 -9
- package/README.md +0 -265
package/dist/index.cjs.js
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const b=require("@blac/core"),t=require("react");function P(u,a){const I=t.useRef(0);I.current++;const S=t.useRef({}),c=t.useRef("");if(!c.current)try{const M=(new Error().stack||"").split(`
|
|
2
|
+
`);for(let l=2;l<M.length&&l<15;l++){const h=M[l];let r=h.match(/at\s+(?:Object\.)?([A-Z][a-zA-Z0-9_$]*)/);if(r||(r=h.match(/([A-Z][a-zA-Z0-9_$]*)\.tsx/)),r||(r=h.match(/render([A-Z][a-zA-Z0-9_$]*)/),r&&r[1]&&(r[1]=r[1])),r&&r[1]!=="Object"&&!r[1].startsWith("use")&&!r[1].startsWith("Use")){c.current=r[1];break}}if(!c.current){const l=u.name;l&&l!=="Object"?c.current=l.replace(/(Cubit|Bloc)$/,"")||"Component":c.current="Component"}}catch{c.current="Component"}const e=a,y=t.useMemo(()=>e?.instanceId?e.instanceId:e?.staticProps&&b.generateInstanceIdFromProps(e.staticProps)||null,[e?.instanceId,e?.staticProps]),n=t.useMemo(()=>{const o=new b.BlacAdapter({componentRef:S,blocConstructor:u},{instanceId:e?.instanceId,dependencies:e?.dependencies,staticProps:e?.staticProps,onMount:e?.onMount,onUnmount:e?.onUnmount});return c.current&&o.setComponentName(c.current),o},[u,y]);n.resetTracking(),n.notifyRender();const s=t.useRef(0);t.useEffect(()=>{s.current++,n.options={instanceId:e?.instanceId,dependencies:e?.dependencies,staticProps:e?.staticProps,onMount:e?.onMount,onUnmount:e?.onUnmount}},[n,e?.dependencies,e?.onMount,e?.onUnmount]);const i=t.useRef(0);t.useEffect(()=>(i.current++,n.mount(),()=>{n.unmount()}),[n]);const f=t.useRef(0),m=t.useMemo(()=>(f.current++,o=>{const R=n.createSubscription({onChange:()=>{o()}});return()=>{R()}}),[n]),B=t.useRef(0),d=t.useRef(0),p=t.useSyncExternalStore(m,()=>(B.current++,n.blocInstance.state),()=>{const o=n.blocInstance;return d.current++,o.state}),g=t.useRef(0),x=t.useMemo(()=>(g.current++,n.getStateProxy()),[p,n]),C=t.useRef(0),U=t.useMemo(()=>(C.current++,n.getBlocProxy()),[n]);return[x,U]}function A(u,a={}){const I=t.useRef(`external-${b.generateUUID()}`),S=t.useRef(new Set),c=t.useRef(new Set),e=t.useRef(null),y=t.useMemo(()=>{const s=b.Blac.getInstance();if(u.constructor.isolated||u.isolated){const f=new u(a?.staticProps),m=a?.id||`${u.name}_${b.generateUUID()}`;return f._id=m,s.activateBloc(f),f}return s.getBloc(u,{id:a?.id,constructorParams:a?.staticProps})},[u,a?.id]),n=t.useMemo(()=>({getSnapshot:()=>{const s=e.current;return s?s.state:void 0},subscribe:s=>{const i=e.current;if(!i)return()=>{};const f=(d,p)=>{try{S.current.clear(),c.current.clear(),a.selector&&i&&a.selector(d,p,i),s.length>0?s(d):s()}catch(g){console.error("Listener error in useExternalBlocStore:",g)}};let m=i.state;return i.subscribe(d=>{const p=m;m=d,f(d,p)})},getServerSnapshot:()=>{const s=e.current;return s?s.state:void 0}}),[]);return e.current=y,{externalStore:n,instance:e,usedKeys:S,usedClassPropKeys:c,rid:I.current}}exports.useBloc=P;exports.useBlocNext=P;exports.useExternalBlocStore=A;
|
|
2
3
|
//# sourceMappingURL=index.cjs.js.map
|
package/dist/index.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs.js","sources":["../src/useBloc.ts","../src/useExternalBlocStore.ts"],"sourcesContent":["import {\n AdapterOptions,\n BlacAdapter,\n BlocBase,\n BlocConstructor,\n BlocState,\n} from '@blac/core';\nimport { useEffect, useMemo, useRef, useSyncExternalStore } from 'react';\n\n/**\n * Type definition for the return type of the useBloc hook\n */\ntype HookTypes<B extends BlocConstructor<BlocBase<any>>> = [\n BlocState<InstanceType<B>>,\n InstanceType<B>,\n];\n\n/**\n * React hook for integrating with Blac state management\n */\nfunction useBloc<B extends BlocConstructor<BlocBase<any>>>(\n blocConstructor: B,\n options?: {\n props?: ConstructorParameters<B>[0];\n id?: string;\n dependencies?: (bloc: InstanceType<B>) => unknown[];\n onMount?: (bloc: InstanceType<B>) => void;\n onUnmount?: (bloc: InstanceType<B>) => void;\n },\n): HookTypes<B> {\n // Create a unique identifier for this hook instance\n const renderCount = useRef(0);\n renderCount.current++;\n\n const componentRef = useRef<object>({});\n\n // Track adapter creation\n const adapter = useMemo(() => {\n const newAdapter = new BlacAdapter<B>(\n {\n componentRef: componentRef,\n blocConstructor,\n },\n {\n id: options?.id,\n dependencies: options?.dependencies,\n props: options?.props,\n onMount: options?.onMount,\n onUnmount: options?.onUnmount,\n },\n );\n return newAdapter;\n }, []);\n\n // Reset tracking at the start of each render to ensure we only track\n // properties accessed during the current render\n adapter.resetConsumerTracking();\n\n // Track options changes and update props\n const optionsChangeCount = useRef(0);\n useEffect(() => {\n optionsChangeCount.current++;\n adapter.options = {\n id: options?.id,\n dependencies: options?.dependencies,\n props: options?.props,\n onMount: options?.onMount,\n onUnmount: options?.onUnmount,\n };\n\n if (options?.props !== undefined) {\n adapter.updateProps(options.props);\n }\n }, [options?.props, options?.id, options?.dependencies, options?.onMount, options?.onUnmount]);\n\n // Register as consumer and handle lifecycle\n const mountEffectCount = useRef(0);\n useEffect(() => {\n mountEffectCount.current++;\n adapter.mount();\n\n return () => {\n adapter.unmount();\n };\n }, [adapter.blocInstance]);\n\n // Subscribe to state changes using useSyncExternalStore\n const subscribeMemoCount = useRef(0);\n const subscribe = useMemo(() => {\n subscribeMemoCount.current++;\n let subscriptionCount = 0;\n\n return (onStoreChange: () => void) => {\n subscriptionCount++;\n const unsubscribe = adapter.createSubscription({\n onChange: () => {\n onStoreChange();\n },\n });\n\n return () => {\n unsubscribe();\n };\n };\n }, [adapter.blocInstance]);\n\n const snapshotCount = useRef(0);\n const serverSnapshotCount = useRef(0);\n\n const rawState: BlocState<InstanceType<B>> = useSyncExternalStore(\n subscribe,\n // Get snapshot\n () => {\n snapshotCount.current++;\n const bloc = adapter.blocInstance;\n const state = bloc.state;\n return state;\n },\n // Get server snapshot (same as client for now)\n () => {\n const bloc = adapter.blocInstance;\n serverSnapshotCount.current++;\n const state = bloc.state;\n return state;\n },\n );\n\n // Create proxies for fine-grained tracking (if enabled)\n const stateMemoCount = useRef(0);\n const finalState = useMemo(() => {\n stateMemoCount.current++;\n const proxyState = adapter.getProxyState(rawState);\n return proxyState;\n }, [rawState]);\n\n const blocMemoCount = useRef(0);\n const finalBloc = useMemo(() => {\n blocMemoCount.current++;\n const proxyBloc = adapter.getProxyBlocInstance();\n return proxyBloc;\n }, [adapter.blocInstance]);\n\n // Mark consumer as rendered after each render\n useEffect(() => {\n adapter.updateLastNotified(componentRef.current);\n });\n\n // Log final hook return\n return [finalState, finalBloc];\n}\n\nexport default useBloc;\n","import {\n Blac,\n BlocBase,\n BlocConstructor,\n BlocState,\n InferPropsFromGeneric,\n generateUUID,\n} from '@blac/core';\nimport { useCallback, useMemo, useRef } from 'react';\n\ninterface ExternalStoreOptions<B extends BlocBase<any>> {\n id?: string;\n props?: InferPropsFromGeneric<B>;\n selector?: (\n currentState: BlocState<B>,\n previousState: BlocState<B>,\n instance: B,\n ) => any[];\n}\n\ninterface ExternalStore<T> {\n getSnapshot: () => T | undefined;\n subscribe: (listener: () => void) => () => void;\n getServerSnapshot?: () => T | undefined;\n}\n\ninterface ExternalBlocStoreResult<B extends BlocBase<any>> {\n externalStore: ExternalStore<BlocState<B>>;\n instance: { current: B | null };\n usedKeys: { current: Set<string> };\n usedClassPropKeys: { current: Set<string> };\n rid: string;\n}\n\n/**\n * Hook for using an external bloc store\n * Provides low-level access to bloc instances for use with React's useSyncExternalStore\n */\nexport default function useExternalBlocStore<\n B extends BlocConstructor<BlocBase<any>>,\n>(\n blocConstructor: B,\n options: ExternalStoreOptions<InstanceType<B>> = {},\n): ExternalBlocStoreResult<InstanceType<B>> {\n const ridRef = useRef<string>(`external-${generateUUID()}`);\n const usedKeysRef = useRef<Set<string>>(new Set());\n const usedClassPropKeysRef = useRef<Set<string>>(new Set());\n const instanceRef = useRef<InstanceType<B> | null>(null);\n\n // Get or create bloc instance\n const bloc = useMemo(() => {\n const blac = Blac.getInstance();\n const base = blocConstructor as unknown as BlocBase<any>;\n\n // For isolated blocs, always create a new instance\n if (\n (base.constructor as any).isolated ||\n (blocConstructor as any).isolated\n ) {\n const newBloc = new blocConstructor(options?.props) as InstanceType<B>;\n const uniqueId =\n options?.id || `${blocConstructor.name}_${generateUUID()}`;\n newBloc._updateId(uniqueId);\n blac.activateBloc(newBloc);\n return newBloc;\n }\n\n // For shared blocs, use the existing getBloc logic\n return blac.getBloc(blocConstructor, {\n id: options?.id,\n props: options?.props,\n });\n }, [blocConstructor, options?.id]);\n\n // Create external store interface\n const externalStore = useMemo<ExternalStore<BlocState<InstanceType<B>>>>(\n () => ({\n getSnapshot: () => {\n const currentInstance = instanceRef.current;\n return currentInstance ? currentInstance.state : undefined;\n },\n subscribe: (listener: () => void) => {\n const currentInstance = instanceRef.current;\n if (!currentInstance) {\n // Return no-op unsubscribe if no instance\n return () => {};\n }\n\n // Wrap listener to handle errors gracefully\n const safeListener = (\n newState: BlocState<InstanceType<B>>,\n oldState: BlocState<InstanceType<B>>,\n ) => {\n try {\n // Reset tracking keys on each listener call\n usedKeysRef.current.clear();\n usedClassPropKeysRef.current.clear();\n\n // Call selector if provided\n if (options.selector && currentInstance) {\n options.selector(newState, oldState, currentInstance);\n }\n\n // Call listener with state if it expects it\n if (listener.length > 0) {\n (listener as any)(newState);\n } else {\n listener();\n }\n } catch (error) {\n console.error('Listener error in useExternalBlocStore:', error);\n // Don't rethrow to prevent breaking other listeners\n }\n };\n\n const unsubscribe = currentInstance._observer.subscribe({\n id: ridRef.current,\n fn: safeListener,\n });\n return unsubscribe;\n },\n getServerSnapshot: () => {\n const currentInstance = instanceRef.current;\n return currentInstance ? currentInstance.state : undefined;\n },\n }),\n [],\n );\n\n // Update instance ref\n instanceRef.current = bloc;\n\n return {\n externalStore,\n instance: instanceRef,\n usedKeys: usedKeysRef,\n usedClassPropKeys: usedClassPropKeysRef,\n rid: ridRef.current,\n };\n}\n"],"names":["useBloc","blocConstructor","options","renderCount","useRef","componentRef","adapter","useMemo","BlacAdapter","optionsChangeCount","useEffect","mountEffectCount","subscribeMemoCount","subscribe","onStoreChange","unsubscribe","snapshotCount","serverSnapshotCount","rawState","useSyncExternalStore","bloc","stateMemoCount","finalState","blocMemoCount","finalBloc","useExternalBlocStore","ridRef","generateUUID","usedKeysRef","usedClassPropKeysRef","instanceRef","blac","Blac","newBloc","uniqueId","externalStore","currentInstance","listener","safeListener","newState","oldState","error"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":["../src/useBloc.ts","../src/useExternalBlocStore.ts"],"sourcesContent":["import {\n BlacAdapter,\n BlocBase,\n BlocConstructor,\n BlocState,\n generateInstanceIdFromProps,\n Blac,\n} from '@blac/core';\nimport { useEffect, useMemo, useRef, useSyncExternalStore } from 'react';\n\n/**\n * Type definition for the return type of the useBloc hook\n */\ntype HookTypes<B extends BlocConstructor<BlocBase<any>>> = [\n BlocState<InstanceType<B>>,\n InstanceType<B>,\n];\n\n/**\n * React hook for integrating with Blac state management\n */\nfunction useBloc<B extends BlocConstructor<BlocBase<any>>>(\n blocConstructor: B,\n options?: {\n staticProps?: ConstructorParameters<B>[0];\n instanceId?: string;\n dependencies?: (bloc: InstanceType<B>) => unknown[];\n onMount?: (bloc: InstanceType<B>) => void;\n onUnmount?: (bloc: InstanceType<B>) => void;\n },\n): HookTypes<B> {\n // Create a unique identifier for this hook instance\n const renderCount = useRef(0);\n renderCount.current++;\n\n const componentRef = useRef<object>({});\n\n // Get component name for debugging\n const componentName = useRef<string>('');\n if (!componentName.current) {\n // Try to get component name from stack trace\n try {\n const error = new Error();\n const stack = error.stack || '';\n const lines = stack.split('\\n');\n\n // Look for React component in stack - try multiple patterns\n for (let i = 2; i < lines.length && i < 15; i++) {\n const line = lines[i];\n\n // Pattern 1: \"at ComponentName\" or \"at Object.ComponentName\"\n let match = line.match(/at\\s+(?:Object\\.)?([A-Z][a-zA-Z0-9_$]*)/);\n\n // Pattern 2: Look for component files like \"ComponentName.tsx\"\n if (!match) {\n match = line.match(/([A-Z][a-zA-Z0-9_$]*)\\.tsx/);\n }\n\n // Pattern 3: Look for render functions\n if (!match) {\n match = line.match(/render([A-Z][a-zA-Z0-9_$]*)/);\n if (match && match[1]) {\n match[1] = match[1]; // Use the part after \"render\"\n }\n }\n\n if (\n match &&\n match[1] !== 'Object' &&\n !match[1].startsWith('use') &&\n !match[1].startsWith('Use')\n ) {\n componentName.current = match[1];\n break;\n }\n }\n\n // If still no name, try to get it from the bloc constructor name\n if (!componentName.current) {\n const blocName = blocConstructor.name;\n if (blocName && blocName !== 'Object') {\n // Remove 'Cubit' or 'Bloc' suffix to guess component name\n componentName.current =\n blocName.replace(/(Cubit|Bloc)$/, '') || 'Component';\n } else {\n componentName.current = 'Component';\n }\n }\n } catch (e) {\n componentName.current = 'Component';\n }\n }\n\n // Pass through options\n const normalizedOptions = options;\n\n // Generate instance id from static props if needed\n const instanceKey = useMemo(() => {\n if (normalizedOptions?.instanceId) {\n return normalizedOptions.instanceId;\n }\n if (normalizedOptions?.staticProps) {\n return generateInstanceIdFromProps(normalizedOptions.staticProps) || null;\n }\n return null;\n }, [normalizedOptions?.instanceId, normalizedOptions?.staticProps]);\n\n // Track adapter creation - recreate when instanceId/staticProps change\n const adapter = useMemo(() => {\n const newAdapter = new BlacAdapter<B>(\n {\n componentRef: componentRef,\n blocConstructor,\n },\n {\n instanceId: normalizedOptions?.instanceId,\n dependencies: normalizedOptions?.dependencies,\n staticProps: normalizedOptions?.staticProps,\n onMount: normalizedOptions?.onMount,\n onUnmount: normalizedOptions?.onUnmount,\n },\n );\n // Set component name for rerender logging\n if (componentName.current) {\n newAdapter.setComponentName(componentName.current);\n }\n return newAdapter;\n }, [blocConstructor, instanceKey]); // Recreate adapter when instance key changes\n\n // Reset tracking at the start of each render to ensure we only track\n // properties accessed during the current render\n adapter.resetTracking();\n\n // Notify plugins about render\n adapter.notifyRender();\n\n // Update adapter options when they change (except instanceId/staticProps which recreate the adapter)\n const optionsChangeCount = useRef(0);\n useEffect(() => {\n optionsChangeCount.current++;\n adapter.options = {\n instanceId: normalizedOptions?.instanceId,\n dependencies: normalizedOptions?.dependencies,\n staticProps: normalizedOptions?.staticProps,\n onMount: normalizedOptions?.onMount,\n onUnmount: normalizedOptions?.onUnmount,\n };\n }, [\n adapter,\n normalizedOptions?.dependencies,\n normalizedOptions?.onMount,\n normalizedOptions?.onUnmount,\n ]);\n\n // Register as consumer and handle lifecycle\n const mountEffectCount = useRef(0);\n useEffect(() => {\n mountEffectCount.current++;\n adapter.mount();\n\n return () => {\n adapter.unmount();\n };\n }, [adapter]);\n\n // Subscribe to state changes using useSyncExternalStore\n const subscribeMemoCount = useRef(0);\n const subscribe = useMemo(() => {\n subscribeMemoCount.current++;\n let subscriptionCount = 0;\n\n return (onStoreChange: () => void) => {\n subscriptionCount++;\n const unsubscribe = adapter.createSubscription({\n onChange: () => {\n onStoreChange();\n },\n });\n\n return () => {\n unsubscribe();\n };\n };\n }, [adapter]);\n\n const snapshotCount = useRef(0);\n const serverSnapshotCount = useRef(0);\n\n const rawState: BlocState<InstanceType<B>> = useSyncExternalStore(\n subscribe,\n // Get snapshot\n () => {\n snapshotCount.current++;\n const bloc = adapter.blocInstance;\n const state = bloc.state;\n return state;\n },\n // Get server snapshot (same as client for now)\n () => {\n const bloc = adapter.blocInstance;\n serverSnapshotCount.current++;\n const state = bloc.state;\n return state;\n },\n );\n\n // Create proxies for fine-grained tracking (if enabled)\n const stateMemoCount = useRef(0);\n const finalState = useMemo(() => {\n stateMemoCount.current++;\n // Always return the proxy - it will handle whether to actually proxy or not\n const proxyState = adapter.getStateProxy();\n return proxyState;\n }, [rawState, adapter]);\n\n const blocMemoCount = useRef(0);\n const finalBloc = useMemo(() => {\n blocMemoCount.current++;\n const proxyBloc = adapter.getBlocProxy();\n return proxyBloc;\n }, [adapter]);\n\n // Log final hook return\n return [finalState, finalBloc];\n}\n\nexport default useBloc;\n","import {\n Blac,\n BlocBase,\n BlocConstructor,\n BlocState,\n generateUUID,\n} from '@blac/core';\nimport { useMemo, useRef } from 'react';\n\ninterface ExternalStoreOptions<B extends BlocBase<any>> {\n id?: string;\n staticProps?: ConstructorParameters<BlocConstructor<B>>[0];\n selector?: (\n currentState: BlocState<B>,\n previousState: BlocState<B>,\n instance: B,\n ) => any[];\n}\n\ninterface ExternalStore<T> {\n getSnapshot: () => T | undefined;\n subscribe: (listener: () => void) => () => void;\n getServerSnapshot?: () => T | undefined;\n}\n\ninterface ExternalBlocStoreResult<B extends BlocBase<any>> {\n externalStore: ExternalStore<BlocState<B>>;\n instance: { current: B | null };\n usedKeys: { current: Set<string> };\n usedClassPropKeys: { current: Set<string> };\n rid: string;\n}\n\n/**\n * Hook for using an external bloc store\n * Provides low-level access to bloc instances for use with React's useSyncExternalStore\n */\nexport default function useExternalBlocStore<\n B extends BlocConstructor<BlocBase<any>>,\n>(\n blocConstructor: B,\n options: ExternalStoreOptions<InstanceType<B>> = {},\n): ExternalBlocStoreResult<InstanceType<B>> {\n const ridRef = useRef<string>(`external-${generateUUID()}`);\n const usedKeysRef = useRef<Set<string>>(new Set());\n const usedClassPropKeysRef = useRef<Set<string>>(new Set());\n const instanceRef = useRef<InstanceType<B> | null>(null);\n\n // Get or create bloc instance\n const bloc = useMemo(() => {\n const blac = Blac.getInstance();\n const base = blocConstructor as unknown as BlocBase<any>;\n\n // For isolated blocs, always create a new instance\n if (\n (base.constructor as any).isolated ||\n (blocConstructor as any).isolated\n ) {\n const newBloc = new blocConstructor(\n options?.staticProps,\n ) as InstanceType<B>;\n const uniqueId =\n options?.id || `${blocConstructor.name}_${generateUUID()}`;\n newBloc._id = uniqueId;\n blac.activateBloc(newBloc);\n return newBloc;\n }\n\n // For shared blocs, use the existing getBloc logic\n return blac.getBloc(blocConstructor, {\n id: options?.id,\n constructorParams: options?.staticProps,\n });\n }, [blocConstructor, options?.id]);\n\n // Create external store interface\n const externalStore = useMemo<ExternalStore<BlocState<InstanceType<B>>>>(\n () => ({\n getSnapshot: () => {\n const currentInstance = instanceRef.current;\n return currentInstance ? currentInstance.state : undefined;\n },\n subscribe: (listener: () => void) => {\n const currentInstance = instanceRef.current;\n if (!currentInstance) {\n // Return no-op unsubscribe if no instance\n return () => {};\n }\n\n // Wrap listener to handle errors gracefully\n const safeListener = (\n newState: BlocState<InstanceType<B>>,\n oldState: BlocState<InstanceType<B>>,\n ) => {\n try {\n // Reset tracking keys on each listener call\n usedKeysRef.current.clear();\n usedClassPropKeysRef.current.clear();\n\n // Call selector if provided\n if (options.selector && currentInstance) {\n options.selector(newState, oldState, currentInstance);\n }\n\n // Call listener with state if it expects it\n if (listener.length > 0) {\n (listener as any)(newState);\n } else {\n listener();\n }\n } catch (error) {\n console.error('Listener error in useExternalBlocStore:', error);\n // Don't rethrow to prevent breaking other listeners\n }\n };\n\n // Store the previous state for the listener\n let previousState = currentInstance.state;\n\n const unsubscribe = currentInstance.subscribe((state) => {\n const oldState = previousState;\n previousState = state;\n safeListener(state, oldState);\n });\n return unsubscribe;\n },\n getServerSnapshot: () => {\n const currentInstance = instanceRef.current;\n return currentInstance ? currentInstance.state : undefined;\n },\n }),\n [],\n );\n\n // Update instance ref\n instanceRef.current = bloc;\n\n return {\n externalStore,\n instance: instanceRef,\n usedKeys: usedKeysRef,\n usedClassPropKeys: usedClassPropKeysRef,\n rid: ridRef.current,\n };\n}\n"],"names":["useBloc","blocConstructor","options","renderCount","useRef","componentRef","componentName","lines","i","line","match","blocName","normalizedOptions","instanceKey","useMemo","generateInstanceIdFromProps","adapter","newAdapter","BlacAdapter","optionsChangeCount","useEffect","mountEffectCount","subscribeMemoCount","subscribe","onStoreChange","unsubscribe","snapshotCount","serverSnapshotCount","rawState","useSyncExternalStore","bloc","stateMemoCount","finalState","blocMemoCount","finalBloc","useExternalBlocStore","ridRef","generateUUID","usedKeysRef","usedClassPropKeysRef","instanceRef","blac","Blac","newBloc","uniqueId","externalStore","currentInstance","listener","safeListener","newState","oldState","error","previousState","state"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { BlocBase } from '@blac/core';
|
|
2
2
|
import { BlocConstructor } from '@blac/core';
|
|
3
3
|
import { BlocState } from '@blac/core';
|
|
4
|
-
import { InferPropsFromGeneric } from '@blac/core';
|
|
5
4
|
|
|
6
5
|
declare interface ExternalBlocStoreResult<B extends BlocBase<any>> {
|
|
7
6
|
externalStore: ExternalStore<BlocState<B>>;
|
|
@@ -25,7 +24,7 @@ declare interface ExternalStore<T> {
|
|
|
25
24
|
|
|
26
25
|
declare interface ExternalStoreOptions<B extends BlocBase<any>> {
|
|
27
26
|
id?: string;
|
|
28
|
-
|
|
27
|
+
staticProps?: ConstructorParameters<BlocConstructor<B>>[0];
|
|
29
28
|
selector?: (currentState: BlocState<B>, previousState: BlocState<B>, instance: B) => any[];
|
|
30
29
|
}
|
|
31
30
|
|
|
@@ -41,8 +40,8 @@ InstanceType<B>
|
|
|
41
40
|
* React hook for integrating with Blac state management
|
|
42
41
|
*/
|
|
43
42
|
declare function useBloc<B extends BlocConstructor<BlocBase<any>>>(blocConstructor: B, options?: {
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
staticProps?: ConstructorParameters<B>[0];
|
|
44
|
+
instanceId?: string;
|
|
46
45
|
dependencies?: (bloc: InstanceType<B>) => unknown[];
|
|
47
46
|
onMount?: (bloc: InstanceType<B>) => void;
|
|
48
47
|
onUnmount?: (bloc: InstanceType<B>) => void;
|
package/dist/index.es.js
CHANGED
|
@@ -1,111 +1,140 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useRef as
|
|
3
|
-
function
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
import { generateInstanceIdFromProps as R, BlacAdapter as $, generateUUID as C, Blac as v } from "@blac/core";
|
|
2
|
+
import { useRef as r, useMemo as m, useEffect as M, useSyncExternalStore as E } from "react";
|
|
3
|
+
function K(a, u) {
|
|
4
|
+
const h = r(0);
|
|
5
|
+
h.current++;
|
|
6
|
+
const S = r({}), c = r("");
|
|
7
|
+
if (!c.current)
|
|
8
|
+
try {
|
|
9
|
+
const g = (new Error().stack || "").split(`
|
|
10
|
+
`);
|
|
11
|
+
for (let l = 2; l < g.length && l < 15; l++) {
|
|
12
|
+
const x = g[l];
|
|
13
|
+
let s = x.match(/at\s+(?:Object\.)?([A-Z][a-zA-Z0-9_$]*)/);
|
|
14
|
+
if (s || (s = x.match(/([A-Z][a-zA-Z0-9_$]*)\.tsx/)), s || (s = x.match(/render([A-Z][a-zA-Z0-9_$]*)/), s && s[1] && (s[1] = s[1])), s && s[1] !== "Object" && !s[1].startsWith("use") && !s[1].startsWith("Use")) {
|
|
15
|
+
c.current = s[1];
|
|
16
|
+
break;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (!c.current) {
|
|
20
|
+
const l = a.name;
|
|
21
|
+
l && l !== "Object" ? c.current = l.replace(/(Cubit|Bloc)$/, "") || "Component" : c.current = "Component";
|
|
22
|
+
}
|
|
23
|
+
} catch {
|
|
24
|
+
c.current = "Component";
|
|
17
25
|
}
|
|
18
|
-
), [])
|
|
19
|
-
|
|
20
|
-
|
|
26
|
+
const t = u, y = m(() => t?.instanceId ? t.instanceId : t?.staticProps && R(t.staticProps) || null, [t?.instanceId, t?.staticProps]), e = m(() => {
|
|
27
|
+
const o = new $(
|
|
28
|
+
{
|
|
29
|
+
componentRef: S,
|
|
30
|
+
blocConstructor: a
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
instanceId: t?.instanceId,
|
|
34
|
+
dependencies: t?.dependencies,
|
|
35
|
+
staticProps: t?.staticProps,
|
|
36
|
+
onMount: t?.onMount,
|
|
37
|
+
onUnmount: t?.onUnmount
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
return c.current && o.setComponentName(c.current), o;
|
|
41
|
+
}, [a, y]);
|
|
42
|
+
e.resetTracking(), e.notifyRender();
|
|
43
|
+
const n = r(0);
|
|
21
44
|
M(() => {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
dependencies:
|
|
25
|
-
|
|
26
|
-
onMount:
|
|
27
|
-
onUnmount:
|
|
28
|
-
}
|
|
29
|
-
}, [
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
45
|
+
n.current++, e.options = {
|
|
46
|
+
instanceId: t?.instanceId,
|
|
47
|
+
dependencies: t?.dependencies,
|
|
48
|
+
staticProps: t?.staticProps,
|
|
49
|
+
onMount: t?.onMount,
|
|
50
|
+
onUnmount: t?.onUnmount
|
|
51
|
+
};
|
|
52
|
+
}, [
|
|
53
|
+
e,
|
|
54
|
+
t?.dependencies,
|
|
55
|
+
t?.onMount,
|
|
56
|
+
t?.onUnmount
|
|
57
|
+
]);
|
|
58
|
+
const i = r(0);
|
|
59
|
+
M(() => (i.current++, e.mount(), () => {
|
|
60
|
+
e.unmount();
|
|
61
|
+
}), [e]);
|
|
62
|
+
const d = r(0), f = m(() => (d.current++, (o) => {
|
|
63
|
+
const I = e.createSubscription({
|
|
36
64
|
onChange: () => {
|
|
37
|
-
|
|
65
|
+
o();
|
|
38
66
|
}
|
|
39
67
|
});
|
|
40
68
|
return () => {
|
|
41
|
-
|
|
69
|
+
I();
|
|
42
70
|
};
|
|
43
|
-
}), [
|
|
44
|
-
|
|
71
|
+
}), [e]), B = r(0), p = r(0), b = E(
|
|
72
|
+
f,
|
|
45
73
|
// Get snapshot
|
|
46
|
-
() => (
|
|
74
|
+
() => (B.current++, e.blocInstance.state),
|
|
47
75
|
// Get server snapshot (same as client for now)
|
|
48
76
|
() => {
|
|
49
|
-
const
|
|
50
|
-
return
|
|
77
|
+
const o = e.blocInstance;
|
|
78
|
+
return p.current++, o.state;
|
|
51
79
|
}
|
|
52
|
-
),
|
|
53
|
-
return
|
|
54
|
-
r.updateLastNotified(f.current);
|
|
55
|
-
}), [I, C];
|
|
80
|
+
), P = r(0), A = m(() => (P.current++, e.getStateProxy()), [b, e]), U = r(0), w = m(() => (U.current++, e.getBlocProxy()), [e]);
|
|
81
|
+
return [A, w];
|
|
56
82
|
}
|
|
57
|
-
function
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
if (
|
|
61
|
-
const d = new
|
|
62
|
-
|
|
83
|
+
function _(a, u = {}) {
|
|
84
|
+
const h = r(`external-${C()}`), S = r(/* @__PURE__ */ new Set()), c = r(/* @__PURE__ */ new Set()), t = r(null), y = m(() => {
|
|
85
|
+
const n = v.getInstance();
|
|
86
|
+
if (a.constructor.isolated || a.isolated) {
|
|
87
|
+
const d = new a(
|
|
88
|
+
u?.staticProps
|
|
89
|
+
), f = u?.id || `${a.name}_${C()}`;
|
|
90
|
+
return d._id = f, n.activateBloc(d), d;
|
|
63
91
|
}
|
|
64
|
-
return
|
|
65
|
-
id:
|
|
66
|
-
|
|
92
|
+
return n.getBloc(a, {
|
|
93
|
+
id: u?.id,
|
|
94
|
+
constructorParams: u?.staticProps
|
|
67
95
|
});
|
|
68
|
-
}, [
|
|
96
|
+
}, [a, u?.id]), e = m(
|
|
69
97
|
() => ({
|
|
70
98
|
getSnapshot: () => {
|
|
71
|
-
const
|
|
72
|
-
return
|
|
99
|
+
const n = t.current;
|
|
100
|
+
return n ? n.state : void 0;
|
|
73
101
|
},
|
|
74
|
-
subscribe: (
|
|
75
|
-
const
|
|
76
|
-
if (!
|
|
102
|
+
subscribe: (n) => {
|
|
103
|
+
const i = t.current;
|
|
104
|
+
if (!i)
|
|
77
105
|
return () => {
|
|
78
106
|
};
|
|
79
|
-
const d = (
|
|
107
|
+
const d = (p, b) => {
|
|
80
108
|
try {
|
|
81
|
-
|
|
82
|
-
} catch (
|
|
83
|
-
console.error("Listener error in useExternalBlocStore:",
|
|
109
|
+
S.current.clear(), c.current.clear(), u.selector && i && u.selector(p, b, i), n.length > 0 ? n(p) : n();
|
|
110
|
+
} catch (P) {
|
|
111
|
+
console.error("Listener error in useExternalBlocStore:", P);
|
|
84
112
|
}
|
|
85
113
|
};
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
114
|
+
let f = i.state;
|
|
115
|
+
return i.subscribe((p) => {
|
|
116
|
+
const b = f;
|
|
117
|
+
f = p, d(p, b);
|
|
89
118
|
});
|
|
90
119
|
},
|
|
91
120
|
getServerSnapshot: () => {
|
|
92
|
-
const
|
|
93
|
-
return
|
|
121
|
+
const n = t.current;
|
|
122
|
+
return n ? n.state : void 0;
|
|
94
123
|
}
|
|
95
124
|
}),
|
|
96
125
|
[]
|
|
97
126
|
);
|
|
98
|
-
return t.current =
|
|
99
|
-
externalStore:
|
|
127
|
+
return t.current = y, {
|
|
128
|
+
externalStore: e,
|
|
100
129
|
instance: t,
|
|
101
|
-
usedKeys:
|
|
102
|
-
usedClassPropKeys:
|
|
103
|
-
rid:
|
|
130
|
+
usedKeys: S,
|
|
131
|
+
usedClassPropKeys: c,
|
|
132
|
+
rid: h.current
|
|
104
133
|
};
|
|
105
134
|
}
|
|
106
135
|
export {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
136
|
+
K as useBloc,
|
|
137
|
+
K as useBlocNext,
|
|
138
|
+
_ as useExternalBlocStore
|
|
110
139
|
};
|
|
111
140
|
//# sourceMappingURL=index.es.js.map
|
package/dist/index.es.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.es.js","sources":["../src/useBloc.ts","../src/useExternalBlocStore.ts"],"sourcesContent":["import {\n AdapterOptions,\n BlacAdapter,\n BlocBase,\n BlocConstructor,\n BlocState,\n} from '@blac/core';\nimport { useEffect, useMemo, useRef, useSyncExternalStore } from 'react';\n\n/**\n * Type definition for the return type of the useBloc hook\n */\ntype HookTypes<B extends BlocConstructor<BlocBase<any>>> = [\n BlocState<InstanceType<B>>,\n InstanceType<B>,\n];\n\n/**\n * React hook for integrating with Blac state management\n */\nfunction useBloc<B extends BlocConstructor<BlocBase<any>>>(\n blocConstructor: B,\n options?: {\n props?: ConstructorParameters<B>[0];\n id?: string;\n dependencies?: (bloc: InstanceType<B>) => unknown[];\n onMount?: (bloc: InstanceType<B>) => void;\n onUnmount?: (bloc: InstanceType<B>) => void;\n },\n): HookTypes<B> {\n // Create a unique identifier for this hook instance\n const renderCount = useRef(0);\n renderCount.current++;\n\n const componentRef = useRef<object>({});\n\n // Track adapter creation\n const adapter = useMemo(() => {\n const newAdapter = new BlacAdapter<B>(\n {\n componentRef: componentRef,\n blocConstructor,\n },\n {\n id: options?.id,\n dependencies: options?.dependencies,\n props: options?.props,\n onMount: options?.onMount,\n onUnmount: options?.onUnmount,\n },\n );\n return newAdapter;\n }, []);\n\n // Reset tracking at the start of each render to ensure we only track\n // properties accessed during the current render\n adapter.resetConsumerTracking();\n\n // Track options changes and update props\n const optionsChangeCount = useRef(0);\n useEffect(() => {\n optionsChangeCount.current++;\n adapter.options = {\n id: options?.id,\n dependencies: options?.dependencies,\n props: options?.props,\n onMount: options?.onMount,\n onUnmount: options?.onUnmount,\n };\n\n if (options?.props !== undefined) {\n adapter.updateProps(options.props);\n }\n }, [options?.props, options?.id, options?.dependencies, options?.onMount, options?.onUnmount]);\n\n // Register as consumer and handle lifecycle\n const mountEffectCount = useRef(0);\n useEffect(() => {\n mountEffectCount.current++;\n adapter.mount();\n\n return () => {\n adapter.unmount();\n };\n }, [adapter.blocInstance]);\n\n // Subscribe to state changes using useSyncExternalStore\n const subscribeMemoCount = useRef(0);\n const subscribe = useMemo(() => {\n subscribeMemoCount.current++;\n let subscriptionCount = 0;\n\n return (onStoreChange: () => void) => {\n subscriptionCount++;\n const unsubscribe = adapter.createSubscription({\n onChange: () => {\n onStoreChange();\n },\n });\n\n return () => {\n unsubscribe();\n };\n };\n }, [adapter.blocInstance]);\n\n const snapshotCount = useRef(0);\n const serverSnapshotCount = useRef(0);\n\n const rawState: BlocState<InstanceType<B>> = useSyncExternalStore(\n subscribe,\n // Get snapshot\n () => {\n snapshotCount.current++;\n const bloc = adapter.blocInstance;\n const state = bloc.state;\n return state;\n },\n // Get server snapshot (same as client for now)\n () => {\n const bloc = adapter.blocInstance;\n serverSnapshotCount.current++;\n const state = bloc.state;\n return state;\n },\n );\n\n // Create proxies for fine-grained tracking (if enabled)\n const stateMemoCount = useRef(0);\n const finalState = useMemo(() => {\n stateMemoCount.current++;\n const proxyState = adapter.getProxyState(rawState);\n return proxyState;\n }, [rawState]);\n\n const blocMemoCount = useRef(0);\n const finalBloc = useMemo(() => {\n blocMemoCount.current++;\n const proxyBloc = adapter.getProxyBlocInstance();\n return proxyBloc;\n }, [adapter.blocInstance]);\n\n // Mark consumer as rendered after each render\n useEffect(() => {\n adapter.updateLastNotified(componentRef.current);\n });\n\n // Log final hook return\n return [finalState, finalBloc];\n}\n\nexport default useBloc;\n","import {\n Blac,\n BlocBase,\n BlocConstructor,\n BlocState,\n InferPropsFromGeneric,\n generateUUID,\n} from '@blac/core';\nimport { useCallback, useMemo, useRef } from 'react';\n\ninterface ExternalStoreOptions<B extends BlocBase<any>> {\n id?: string;\n props?: InferPropsFromGeneric<B>;\n selector?: (\n currentState: BlocState<B>,\n previousState: BlocState<B>,\n instance: B,\n ) => any[];\n}\n\ninterface ExternalStore<T> {\n getSnapshot: () => T | undefined;\n subscribe: (listener: () => void) => () => void;\n getServerSnapshot?: () => T | undefined;\n}\n\ninterface ExternalBlocStoreResult<B extends BlocBase<any>> {\n externalStore: ExternalStore<BlocState<B>>;\n instance: { current: B | null };\n usedKeys: { current: Set<string> };\n usedClassPropKeys: { current: Set<string> };\n rid: string;\n}\n\n/**\n * Hook for using an external bloc store\n * Provides low-level access to bloc instances for use with React's useSyncExternalStore\n */\nexport default function useExternalBlocStore<\n B extends BlocConstructor<BlocBase<any>>,\n>(\n blocConstructor: B,\n options: ExternalStoreOptions<InstanceType<B>> = {},\n): ExternalBlocStoreResult<InstanceType<B>> {\n const ridRef = useRef<string>(`external-${generateUUID()}`);\n const usedKeysRef = useRef<Set<string>>(new Set());\n const usedClassPropKeysRef = useRef<Set<string>>(new Set());\n const instanceRef = useRef<InstanceType<B> | null>(null);\n\n // Get or create bloc instance\n const bloc = useMemo(() => {\n const blac = Blac.getInstance();\n const base = blocConstructor as unknown as BlocBase<any>;\n\n // For isolated blocs, always create a new instance\n if (\n (base.constructor as any).isolated ||\n (blocConstructor as any).isolated\n ) {\n const newBloc = new blocConstructor(options?.props) as InstanceType<B>;\n const uniqueId =\n options?.id || `${blocConstructor.name}_${generateUUID()}`;\n newBloc._updateId(uniqueId);\n blac.activateBloc(newBloc);\n return newBloc;\n }\n\n // For shared blocs, use the existing getBloc logic\n return blac.getBloc(blocConstructor, {\n id: options?.id,\n props: options?.props,\n });\n }, [blocConstructor, options?.id]);\n\n // Create external store interface\n const externalStore = useMemo<ExternalStore<BlocState<InstanceType<B>>>>(\n () => ({\n getSnapshot: () => {\n const currentInstance = instanceRef.current;\n return currentInstance ? currentInstance.state : undefined;\n },\n subscribe: (listener: () => void) => {\n const currentInstance = instanceRef.current;\n if (!currentInstance) {\n // Return no-op unsubscribe if no instance\n return () => {};\n }\n\n // Wrap listener to handle errors gracefully\n const safeListener = (\n newState: BlocState<InstanceType<B>>,\n oldState: BlocState<InstanceType<B>>,\n ) => {\n try {\n // Reset tracking keys on each listener call\n usedKeysRef.current.clear();\n usedClassPropKeysRef.current.clear();\n\n // Call selector if provided\n if (options.selector && currentInstance) {\n options.selector(newState, oldState, currentInstance);\n }\n\n // Call listener with state if it expects it\n if (listener.length > 0) {\n (listener as any)(newState);\n } else {\n listener();\n }\n } catch (error) {\n console.error('Listener error in useExternalBlocStore:', error);\n // Don't rethrow to prevent breaking other listeners\n }\n };\n\n const unsubscribe = currentInstance._observer.subscribe({\n id: ridRef.current,\n fn: safeListener,\n });\n return unsubscribe;\n },\n getServerSnapshot: () => {\n const currentInstance = instanceRef.current;\n return currentInstance ? currentInstance.state : undefined;\n },\n }),\n [],\n );\n\n // Update instance ref\n instanceRef.current = bloc;\n\n return {\n externalStore,\n instance: instanceRef,\n usedKeys: usedKeysRef,\n usedClassPropKeys: usedClassPropKeysRef,\n rid: ridRef.current,\n };\n}\n"],"names":["useBloc","blocConstructor","options","renderCount","useRef","componentRef","adapter","useMemo","BlacAdapter","optionsChangeCount","useEffect","mountEffectCount","subscribeMemoCount","subscribe","onStoreChange","unsubscribe","snapshotCount","serverSnapshotCount","rawState","useSyncExternalStore","bloc","stateMemoCount","finalState","blocMemoCount","finalBloc","useExternalBlocStore","ridRef","generateUUID","usedKeysRef","usedClassPropKeysRef","instanceRef","blac","Blac","newBloc","uniqueId","externalStore","currentInstance","listener","safeListener","newState","oldState","error"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.es.js","sources":["../src/useBloc.ts","../src/useExternalBlocStore.ts"],"sourcesContent":["import {\n BlacAdapter,\n BlocBase,\n BlocConstructor,\n BlocState,\n generateInstanceIdFromProps,\n Blac,\n} from '@blac/core';\nimport { useEffect, useMemo, useRef, useSyncExternalStore } from 'react';\n\n/**\n * Type definition for the return type of the useBloc hook\n */\ntype HookTypes<B extends BlocConstructor<BlocBase<any>>> = [\n BlocState<InstanceType<B>>,\n InstanceType<B>,\n];\n\n/**\n * React hook for integrating with Blac state management\n */\nfunction useBloc<B extends BlocConstructor<BlocBase<any>>>(\n blocConstructor: B,\n options?: {\n staticProps?: ConstructorParameters<B>[0];\n instanceId?: string;\n dependencies?: (bloc: InstanceType<B>) => unknown[];\n onMount?: (bloc: InstanceType<B>) => void;\n onUnmount?: (bloc: InstanceType<B>) => void;\n },\n): HookTypes<B> {\n // Create a unique identifier for this hook instance\n const renderCount = useRef(0);\n renderCount.current++;\n\n const componentRef = useRef<object>({});\n\n // Get component name for debugging\n const componentName = useRef<string>('');\n if (!componentName.current) {\n // Try to get component name from stack trace\n try {\n const error = new Error();\n const stack = error.stack || '';\n const lines = stack.split('\\n');\n\n // Look for React component in stack - try multiple patterns\n for (let i = 2; i < lines.length && i < 15; i++) {\n const line = lines[i];\n\n // Pattern 1: \"at ComponentName\" or \"at Object.ComponentName\"\n let match = line.match(/at\\s+(?:Object\\.)?([A-Z][a-zA-Z0-9_$]*)/);\n\n // Pattern 2: Look for component files like \"ComponentName.tsx\"\n if (!match) {\n match = line.match(/([A-Z][a-zA-Z0-9_$]*)\\.tsx/);\n }\n\n // Pattern 3: Look for render functions\n if (!match) {\n match = line.match(/render([A-Z][a-zA-Z0-9_$]*)/);\n if (match && match[1]) {\n match[1] = match[1]; // Use the part after \"render\"\n }\n }\n\n if (\n match &&\n match[1] !== 'Object' &&\n !match[1].startsWith('use') &&\n !match[1].startsWith('Use')\n ) {\n componentName.current = match[1];\n break;\n }\n }\n\n // If still no name, try to get it from the bloc constructor name\n if (!componentName.current) {\n const blocName = blocConstructor.name;\n if (blocName && blocName !== 'Object') {\n // Remove 'Cubit' or 'Bloc' suffix to guess component name\n componentName.current =\n blocName.replace(/(Cubit|Bloc)$/, '') || 'Component';\n } else {\n componentName.current = 'Component';\n }\n }\n } catch (e) {\n componentName.current = 'Component';\n }\n }\n\n // Pass through options\n const normalizedOptions = options;\n\n // Generate instance id from static props if needed\n const instanceKey = useMemo(() => {\n if (normalizedOptions?.instanceId) {\n return normalizedOptions.instanceId;\n }\n if (normalizedOptions?.staticProps) {\n return generateInstanceIdFromProps(normalizedOptions.staticProps) || null;\n }\n return null;\n }, [normalizedOptions?.instanceId, normalizedOptions?.staticProps]);\n\n // Track adapter creation - recreate when instanceId/staticProps change\n const adapter = useMemo(() => {\n const newAdapter = new BlacAdapter<B>(\n {\n componentRef: componentRef,\n blocConstructor,\n },\n {\n instanceId: normalizedOptions?.instanceId,\n dependencies: normalizedOptions?.dependencies,\n staticProps: normalizedOptions?.staticProps,\n onMount: normalizedOptions?.onMount,\n onUnmount: normalizedOptions?.onUnmount,\n },\n );\n // Set component name for rerender logging\n if (componentName.current) {\n newAdapter.setComponentName(componentName.current);\n }\n return newAdapter;\n }, [blocConstructor, instanceKey]); // Recreate adapter when instance key changes\n\n // Reset tracking at the start of each render to ensure we only track\n // properties accessed during the current render\n adapter.resetTracking();\n\n // Notify plugins about render\n adapter.notifyRender();\n\n // Update adapter options when they change (except instanceId/staticProps which recreate the adapter)\n const optionsChangeCount = useRef(0);\n useEffect(() => {\n optionsChangeCount.current++;\n adapter.options = {\n instanceId: normalizedOptions?.instanceId,\n dependencies: normalizedOptions?.dependencies,\n staticProps: normalizedOptions?.staticProps,\n onMount: normalizedOptions?.onMount,\n onUnmount: normalizedOptions?.onUnmount,\n };\n }, [\n adapter,\n normalizedOptions?.dependencies,\n normalizedOptions?.onMount,\n normalizedOptions?.onUnmount,\n ]);\n\n // Register as consumer and handle lifecycle\n const mountEffectCount = useRef(0);\n useEffect(() => {\n mountEffectCount.current++;\n adapter.mount();\n\n return () => {\n adapter.unmount();\n };\n }, [adapter]);\n\n // Subscribe to state changes using useSyncExternalStore\n const subscribeMemoCount = useRef(0);\n const subscribe = useMemo(() => {\n subscribeMemoCount.current++;\n let subscriptionCount = 0;\n\n return (onStoreChange: () => void) => {\n subscriptionCount++;\n const unsubscribe = adapter.createSubscription({\n onChange: () => {\n onStoreChange();\n },\n });\n\n return () => {\n unsubscribe();\n };\n };\n }, [adapter]);\n\n const snapshotCount = useRef(0);\n const serverSnapshotCount = useRef(0);\n\n const rawState: BlocState<InstanceType<B>> = useSyncExternalStore(\n subscribe,\n // Get snapshot\n () => {\n snapshotCount.current++;\n const bloc = adapter.blocInstance;\n const state = bloc.state;\n return state;\n },\n // Get server snapshot (same as client for now)\n () => {\n const bloc = adapter.blocInstance;\n serverSnapshotCount.current++;\n const state = bloc.state;\n return state;\n },\n );\n\n // Create proxies for fine-grained tracking (if enabled)\n const stateMemoCount = useRef(0);\n const finalState = useMemo(() => {\n stateMemoCount.current++;\n // Always return the proxy - it will handle whether to actually proxy or not\n const proxyState = adapter.getStateProxy();\n return proxyState;\n }, [rawState, adapter]);\n\n const blocMemoCount = useRef(0);\n const finalBloc = useMemo(() => {\n blocMemoCount.current++;\n const proxyBloc = adapter.getBlocProxy();\n return proxyBloc;\n }, [adapter]);\n\n // Log final hook return\n return [finalState, finalBloc];\n}\n\nexport default useBloc;\n","import {\n Blac,\n BlocBase,\n BlocConstructor,\n BlocState,\n generateUUID,\n} from '@blac/core';\nimport { useMemo, useRef } from 'react';\n\ninterface ExternalStoreOptions<B extends BlocBase<any>> {\n id?: string;\n staticProps?: ConstructorParameters<BlocConstructor<B>>[0];\n selector?: (\n currentState: BlocState<B>,\n previousState: BlocState<B>,\n instance: B,\n ) => any[];\n}\n\ninterface ExternalStore<T> {\n getSnapshot: () => T | undefined;\n subscribe: (listener: () => void) => () => void;\n getServerSnapshot?: () => T | undefined;\n}\n\ninterface ExternalBlocStoreResult<B extends BlocBase<any>> {\n externalStore: ExternalStore<BlocState<B>>;\n instance: { current: B | null };\n usedKeys: { current: Set<string> };\n usedClassPropKeys: { current: Set<string> };\n rid: string;\n}\n\n/**\n * Hook for using an external bloc store\n * Provides low-level access to bloc instances for use with React's useSyncExternalStore\n */\nexport default function useExternalBlocStore<\n B extends BlocConstructor<BlocBase<any>>,\n>(\n blocConstructor: B,\n options: ExternalStoreOptions<InstanceType<B>> = {},\n): ExternalBlocStoreResult<InstanceType<B>> {\n const ridRef = useRef<string>(`external-${generateUUID()}`);\n const usedKeysRef = useRef<Set<string>>(new Set());\n const usedClassPropKeysRef = useRef<Set<string>>(new Set());\n const instanceRef = useRef<InstanceType<B> | null>(null);\n\n // Get or create bloc instance\n const bloc = useMemo(() => {\n const blac = Blac.getInstance();\n const base = blocConstructor as unknown as BlocBase<any>;\n\n // For isolated blocs, always create a new instance\n if (\n (base.constructor as any).isolated ||\n (blocConstructor as any).isolated\n ) {\n const newBloc = new blocConstructor(\n options?.staticProps,\n ) as InstanceType<B>;\n const uniqueId =\n options?.id || `${blocConstructor.name}_${generateUUID()}`;\n newBloc._id = uniqueId;\n blac.activateBloc(newBloc);\n return newBloc;\n }\n\n // For shared blocs, use the existing getBloc logic\n return blac.getBloc(blocConstructor, {\n id: options?.id,\n constructorParams: options?.staticProps,\n });\n }, [blocConstructor, options?.id]);\n\n // Create external store interface\n const externalStore = useMemo<ExternalStore<BlocState<InstanceType<B>>>>(\n () => ({\n getSnapshot: () => {\n const currentInstance = instanceRef.current;\n return currentInstance ? currentInstance.state : undefined;\n },\n subscribe: (listener: () => void) => {\n const currentInstance = instanceRef.current;\n if (!currentInstance) {\n // Return no-op unsubscribe if no instance\n return () => {};\n }\n\n // Wrap listener to handle errors gracefully\n const safeListener = (\n newState: BlocState<InstanceType<B>>,\n oldState: BlocState<InstanceType<B>>,\n ) => {\n try {\n // Reset tracking keys on each listener call\n usedKeysRef.current.clear();\n usedClassPropKeysRef.current.clear();\n\n // Call selector if provided\n if (options.selector && currentInstance) {\n options.selector(newState, oldState, currentInstance);\n }\n\n // Call listener with state if it expects it\n if (listener.length > 0) {\n (listener as any)(newState);\n } else {\n listener();\n }\n } catch (error) {\n console.error('Listener error in useExternalBlocStore:', error);\n // Don't rethrow to prevent breaking other listeners\n }\n };\n\n // Store the previous state for the listener\n let previousState = currentInstance.state;\n\n const unsubscribe = currentInstance.subscribe((state) => {\n const oldState = previousState;\n previousState = state;\n safeListener(state, oldState);\n });\n return unsubscribe;\n },\n getServerSnapshot: () => {\n const currentInstance = instanceRef.current;\n return currentInstance ? currentInstance.state : undefined;\n },\n }),\n [],\n );\n\n // Update instance ref\n instanceRef.current = bloc;\n\n return {\n externalStore,\n instance: instanceRef,\n usedKeys: usedKeysRef,\n usedClassPropKeys: usedClassPropKeysRef,\n rid: ridRef.current,\n };\n}\n"],"names":["useBloc","blocConstructor","options","renderCount","useRef","componentRef","componentName","lines","i","line","match","blocName","normalizedOptions","instanceKey","useMemo","generateInstanceIdFromProps","adapter","newAdapter","BlacAdapter","optionsChangeCount","useEffect","mountEffectCount","subscribeMemoCount","subscribe","onStoreChange","unsubscribe","snapshotCount","serverSnapshotCount","rawState","useSyncExternalStore","bloc","stateMemoCount","finalState","blocMemoCount","finalBloc","useExternalBlocStore","ridRef","generateUUID","usedKeysRef","usedClassPropKeysRef","instanceRef","blac","Blac","newBloc","uniqueId","externalStore","currentInstance","listener","safeListener","newState","oldState","error","previousState","state"],"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-14",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Brendan Mullins <jsnanigans@gmail.com>",
|
|
6
6
|
"main": "dist/index.cjs.js",
|
|
@@ -42,26 +42,26 @@
|
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@testing-library/dom": "^10.4.1",
|
|
45
|
-
"@testing-library/jest-dom": "^6.6.
|
|
45
|
+
"@testing-library/jest-dom": "^6.6.4",
|
|
46
46
|
"@testing-library/react": "^16.3.0",
|
|
47
47
|
"@testing-library/user-event": "^14.6.1",
|
|
48
|
-
"@types/react": "^19.1.
|
|
48
|
+
"@types/react": "^19.1.9",
|
|
49
49
|
"@vitejs/plugin-react": "^4.7.0",
|
|
50
50
|
"@vitest/browser": "^3.2.4",
|
|
51
51
|
"@vitest/coverage-v8": "^3.2.4",
|
|
52
52
|
"happy-dom": "^18.0.1",
|
|
53
|
-
"jsdom": "^
|
|
53
|
+
"jsdom": "^26.1.0",
|
|
54
54
|
"prettier": "^3.6.2",
|
|
55
|
-
"react": "^19.1.
|
|
56
|
-
"react-dom": "^19.1.
|
|
57
|
-
"typescript": "^5.
|
|
58
|
-
"vite": "^
|
|
55
|
+
"react": "^19.1.1",
|
|
56
|
+
"react-dom": "^19.1.1",
|
|
57
|
+
"typescript": "^5.9.2",
|
|
58
|
+
"vite": "^7.0.6",
|
|
59
59
|
"vite-plugin-dts": "^4.5.4",
|
|
60
|
-
"vitest": "^
|
|
60
|
+
"vitest": "^3.2.4",
|
|
61
|
+
"@blac/plugin-render-logging": "2.0.0-rc-1"
|
|
61
62
|
},
|
|
62
63
|
"scripts": {
|
|
63
|
-
"
|
|
64
|
-
"format": "prettier --write \"src/**/*.{ts,tsx,js,jsx}\"",
|
|
64
|
+
"format": "prettier --write \".\"",
|
|
65
65
|
"test": "vitest run --config vitest.config.ts",
|
|
66
66
|
"test:watch": "vitest --watch --config vitest.config.ts",
|
|
67
67
|
"typecheck": "tsc --noEmit",
|