@blac/react 2.0.0-rc-7 → 2.0.0-rc-10
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/README.md +63 -14
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +27 -63
- package/dist/index.es.js +88 -157
- package/dist/index.es.js.map +1 -1
- package/package.json +10 -9
- package/src/__tests__/useBloc.tracking.test.tsx +198 -0
- package/src/index.ts +2 -4
- package/src/useBloc.ts +130 -0
- package/src/useExternalBlocStore.ts +116 -191
- package/src/useBloc.tsx +0 -193
package/README.md
CHANGED
|
@@ -10,7 +10,8 @@ A powerful React integration for the Blac state management library, providing se
|
|
|
10
10
|
- 🎨 TypeScript support with full type inference
|
|
11
11
|
- ⚡️ Efficient state management with minimal boilerplate
|
|
12
12
|
- 🔄 Support for isolated and shared bloc instances
|
|
13
|
-
- 🎯 Custom dependency selectors
|
|
13
|
+
- 🎯 Custom dependency selectors with access to state, previous state, and instance
|
|
14
|
+
- 🚀 Optimized re-rendering with intelligent snapshot comparison
|
|
14
15
|
|
|
15
16
|
## Important: Arrow Functions Required
|
|
16
17
|
|
|
@@ -83,13 +84,13 @@ const [state, bloc] = useBloc(YourBloc, {
|
|
|
83
84
|
id: 'custom-id', // Optional: Custom identifier for the bloc
|
|
84
85
|
props: { /* ... */ }, // Optional: Props to pass to the bloc
|
|
85
86
|
onMount: (bloc) => { /* ... */ }, // Optional: Callback when bloc is mounted (similar to useEffect(<>, []))
|
|
86
|
-
|
|
87
|
+
selector: (currentState, previousState, instance) => [/* ... */], // Optional: Custom dependency tracking
|
|
87
88
|
});
|
|
88
89
|
```
|
|
89
90
|
|
|
90
|
-
### Dependency Tracking
|
|
91
|
+
### Automatic Dependency Tracking
|
|
91
92
|
|
|
92
|
-
The hook automatically tracks which state properties are accessed in your component and only triggers re-renders when those specific
|
|
93
|
+
The hook automatically tracks which state properties and bloc instance properties are accessed in your component and only triggers re-renders when those specific values change:
|
|
93
94
|
|
|
94
95
|
```tsx
|
|
95
96
|
function UserProfile() {
|
|
@@ -100,30 +101,75 @@ function UserProfile() {
|
|
|
100
101
|
}
|
|
101
102
|
```
|
|
102
103
|
|
|
103
|
-
This also works for getters on the Bloc or Cubit class:
|
|
104
|
+
This also works for getters and computed properties on the Bloc or Cubit class:
|
|
104
105
|
|
|
105
106
|
```tsx
|
|
106
107
|
function UserProfile() {
|
|
107
|
-
const [, userBloc] = useBloc(UserBloc);
|
|
108
|
+
const [state, userBloc] = useBloc(UserBloc);
|
|
108
109
|
|
|
109
|
-
// Only re-renders when
|
|
110
|
-
|
|
110
|
+
// Only re-renders when:
|
|
111
|
+
// - state.firstName changes, OR
|
|
112
|
+
// - state.lastName changes (because the getter accesses these properties)
|
|
113
|
+
return <h1>{userBloc.fullName}</h1>; // Assuming fullName is a getter
|
|
111
114
|
}
|
|
112
115
|
```
|
|
113
116
|
|
|
117
|
+
#### How It Works
|
|
118
|
+
|
|
119
|
+
The dependency tracking system uses JavaScript Proxies to monitor property access during component renders:
|
|
120
|
+
|
|
121
|
+
1. **State Properties**: When you access `state.propertyName`, it's automatically tracked
|
|
122
|
+
2. **Instance Properties**: When you access `bloc.computedValue`, it's automatically tracked
|
|
123
|
+
3. **Intelligent Comparison**: The system separately tracks state dependencies and instance dependencies to handle edge cases where properties are dynamically added/removed
|
|
124
|
+
4. **Optimized Updates**: Components only re-render when tracked dependencies actually change their values
|
|
125
|
+
|
|
114
126
|
### Custom Dependency Selector
|
|
115
127
|
|
|
116
|
-
For more control over when your component re-renders, you can provide a custom dependency selector:
|
|
128
|
+
For more control over when your component re-renders, you can provide a custom dependency selector. The selector function receives the current state, previous state, and bloc instance, and should return an array of values to track:
|
|
117
129
|
|
|
118
130
|
```tsx
|
|
119
131
|
const [state, bloc] = useBloc(YourBloc, {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
132
|
+
selector: (currentState, previousState, instance) => [
|
|
133
|
+
currentState.specificField,
|
|
134
|
+
currentState.anotherField,
|
|
135
|
+
instance.computedValue // You can also track computed properties from the bloc instance
|
|
123
136
|
]
|
|
124
137
|
});
|
|
125
138
|
```
|
|
126
139
|
|
|
140
|
+
The component will only re-render when any of the values in the returned array change (using `Object.is` comparison, similar to React's `useEffect` dependency array).
|
|
141
|
+
|
|
142
|
+
#### Examples of Custom Selectors
|
|
143
|
+
|
|
144
|
+
**Track only specific state properties:**
|
|
145
|
+
```tsx
|
|
146
|
+
const [state, userBloc] = useBloc(UserBloc, {
|
|
147
|
+
selector: (currentState) => [
|
|
148
|
+
currentState.name,
|
|
149
|
+
currentState.email
|
|
150
|
+
] // Only re-render when name or email changes, ignore other properties
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**Track computed values:**
|
|
155
|
+
```tsx
|
|
156
|
+
const [state, shoppingCartBloc] = useBloc(ShoppingCartBloc, {
|
|
157
|
+
selector: (currentState, previousState, instance) => [
|
|
158
|
+
instance.totalPrice, // Computed getter
|
|
159
|
+
currentState.items.length // Number of items
|
|
160
|
+
] // Only re-render when total price or item count changes
|
|
161
|
+
});
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**Compare with previous state:**
|
|
165
|
+
```tsx
|
|
166
|
+
const [state, chatBloc] = useBloc(ChatBloc, {
|
|
167
|
+
selector: (currentState, previousState) => [
|
|
168
|
+
currentState.messages.length > (previousState?.messages.length || 0) ? 'new-message' : 'no-change'
|
|
169
|
+
] // Only re-render when new messages are added, not when existing messages change
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
127
173
|
## API Reference
|
|
128
174
|
|
|
129
175
|
### useBloc Hook
|
|
@@ -140,7 +186,7 @@ function useBloc<B extends BlocConstructor<BlocGeneric>>(
|
|
|
140
186
|
- `id?: string` - Custom identifier for the bloc instance
|
|
141
187
|
- `props?: InferPropsFromGeneric<B>` - Props to pass to the bloc
|
|
142
188
|
- `onMount?: (bloc: B) => void` - Callback function invoked when the react component (the consumer) is connected to the bloc instance
|
|
143
|
-
- `
|
|
189
|
+
- `selector?: (currentState: BlocState<InstanceType<B>>, previousState: BlocState<InstanceType<B>> | undefined, instance: InstanceType<B>) => unknown[]` - Function to select dependencies for re-renders
|
|
144
190
|
|
|
145
191
|
## Best Practices
|
|
146
192
|
|
|
@@ -182,7 +228,10 @@ function useBloc<B extends BlocConstructor<BlocGeneric>>(
|
|
|
182
228
|
}
|
|
183
229
|
```
|
|
184
230
|
|
|
185
|
-
3. **
|
|
231
|
+
3. **Choose the Right Dependency Strategy**:
|
|
232
|
+
- **Use automatic tracking** (default) for most cases - it's efficient and requires no setup
|
|
233
|
+
- **Use custom selectors** when you need complex logic, computed comparisons, or want to ignore certain property changes
|
|
234
|
+
- **Avoid custom selectors** for simple property access - automatic tracking is more efficient
|
|
186
235
|
|
|
187
236
|
4. **Type Safety**: Take advantage of TypeScript's type inference for better development experience and catch errors early.
|
|
188
237
|
|
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 S=require("@blac/core"),e=require("react");function I(c,t){const d=e.useRef(0);d.current++;const l=e.useRef({}),r=e.useMemo(()=>new S.BlacAdapter({componentRef:l,blocConstructor:c},t),[]);r.resetConsumerTracking();const s=e.useRef(0);e.useEffect(()=>{s.current++,r.options=t},[t]);const R=e.useRef(0);e.useEffect(()=>(R.current++,r.mount(),()=>{r.unmount()}),[r.blocInstance]);const m=e.useRef(0),n=e.useMemo(()=>(m.current++,o=>{const y=r.createSubscription({onChange:()=>{o()}});return()=>{y()}}),[r.blocInstance]),u=e.useRef(0),a=e.useRef(0),f=e.useSyncExternalStore(n,()=>(u.current++,r.blocInstance.state),()=>{const o=r.blocInstance;return a.current++,o.state}),b=e.useRef(0),i=e.useMemo(()=>(b.current++,r.getProxyState(f)),[f]),g=e.useRef(0),B=e.useMemo(()=>(g.current++,r.getProxyBlocInstance()),[r.blocInstance]);return e.useEffect(()=>{r.updateLastNotified(l.current)}),[i,B]}function x(c,t={}){const d=e.useRef(`external-${S.generateUUID()}`),l=e.useRef(new Set),r=e.useRef(new Set),s=e.useRef(null),R=e.useMemo(()=>{const n=S.Blac.getInstance();if(c.constructor.isolated||c.isolated){const a=new c(t==null?void 0:t.props),f=(t==null?void 0:t.id)||`${c.name}_${S.generateUUID()}`;return a._updateId(f),n.activateBloc(a),a}return n.getBloc(c,{id:t==null?void 0:t.id,props:t==null?void 0:t.props})},[c,t==null?void 0:t.id]),m=e.useMemo(()=>({getSnapshot:()=>{const n=s.current;return n?n.state:void 0},subscribe:n=>{const u=s.current;if(!u)return()=>{};const a=(b,i)=>{try{l.current.clear(),r.current.clear(),t.selector&&u&&t.selector(b,i,u),n.length>0?n(b):n()}catch(g){console.error("Listener error in useExternalBlocStore:",g)}};return u._observer.subscribe({id:d.current,fn:a})},getServerSnapshot:()=>{const n=s.current;return n?n.state:void 0}}),[]);return s.current=R,{externalStore:m,instance:s,usedKeys:l,usedClassPropKeys:r,rid:d.current}}exports.useBloc=I;exports.useExternalBlocStore=x;
|
|
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/useExternalBlocStore.ts","../src/useBloc.tsx"],"sourcesContent":["import { Blac, BlacObserver, BlocBase, BlocBaseAbstract, BlocConstructor, BlocHookDependencyArrayFn, BlocState, generateUUID } 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>> | undefined;\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>> | undefined;\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 generateUUID();\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, effectiveBlocId, props, rid]);\n\n const blocInstance = useRef<InstanceType<B>>(getBloc());\n\n // Update the instance when dependencies change\n useMemo(() => {\n blocInstance.current = getBloc();\n }, [getBloc]);\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: unknown[] = [];\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 // If no state properties have been accessed, return empty dependencies to prevent re-renders\n // Class properties can change independently of state\n if (usedKeys.current.size === 0) {\n return usedClassPropKeys.current.size > 0 ? [usedClassValues] : [[]];\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 const observer: BlacObserver<BlocState<InstanceType<B>>> = {\n fn: () => {\n try {\n // Reset dependency tracking before listener is called \n // This ensures we only track properties accessed during the current render\n usedKeys.current = new Set();\n usedClassPropKeys.current = new Set();\n\n // Only trigger listener if there are actual subscriptions\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>> | undefined => {\n const instance = blocInstance.current;\n if (!instance) {\n return {} as BlocState<InstanceType<B>>;\n }\n return instance.state;\n },\n // Server snapshot mirrors the client snapshot in this implementation\n getServerSnapshot: (): BlocState<InstanceType<B>> | undefined => {\n const instance = blocInstance.current;\n if (!instance) {\n return {} as BlocState<InstanceType<B>>;\n }\n return instance.state;\n },\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 * @returns {Array<Array<T>>} Dependency array containing the entire state\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 () => {\n const snapshot = externalStore.getSnapshot();\n if (snapshot === undefined) {\n throw new Error(`[useBloc] State snapshot is undefined for bloc ${bloc.name}. Bloc may not be properly initialized.`);\n }\n return snapshot;\n },\n externalStore.getServerSnapshot ? () => {\n const serverSnapshot = externalStore.getServerSnapshot!();\n if (serverSnapshot === undefined) {\n throw new Error(`[useBloc] Server state snapshot is undefined for bloc ${bloc.name}. Bloc may not be properly initialized.`);\n }\n return serverSnapshot;\n } : undefined,\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 // Use setTimeout to defer dependency tracking until after render\n setTimeout(() => {\n usedKeys.current.add(prop as string);\n }, 0);\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 throw new Error(`[useBloc] Bloc instance is null for ${bloc.name}. This should never happen - bloc instance must be defined.`);\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 throw new Error(`[useBloc] Bloc target is null for ${bloc.name}. This should never happen - bloc target must be defined.`);\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 // Create a stable reference object for this component\n const componentRef = useRef({});\n\n // Set up bloc lifecycle management\n useEffect(() => {\n const currentInstance = instance.current;\n if (!currentInstance) return;\n \n // Pass component reference for proper memory management\n currentInstance._addConsumer(rid, componentRef.current);\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 // Ensure state and instance are never undefined/null\n if (returnState === undefined) {\n throw new Error(`[useBloc] State is undefined for ${bloc.name}. This should never happen - state must be defined.`);\n }\n if (!returnClass) {\n throw new Error(`[useBloc] Instance is null for ${bloc.name}. This should never happen - instance must be defined.`);\n }\n\n // Safe return with proper typing\n return [returnState, returnClass] as [BlocState<InstanceType<B>>, InstanceType<B>];\n}\n"],"names":["useExternalBlocStore","bloc","options","blocId","props","selector","rid","useMemo","generateUUID","effectiveBlocId","usedKeys","useRef","usedClassPropKeys","getBloc","useCallback","Blac","blocInstance","dependencyArray","newState","instance","usedStateValues","key","usedClassValues","value","error","state","listener","currentInstance","observer","e","unSub","_a","useBloc","externalStore","useSyncExternalStore","snapshot","serverSnapshot","stateProxyCache","classProxyCache","returnState","proxy","target","prop","returnClass","componentRef","useEffect","_b"],"mappings":"iIA0CMA,EAAuB,CAG3BC,EACAC,IACyB,OACzB,KAAM,CAAE,GAAIC,EAAQ,MAAAC,EAAO,SAAAC,CAAS,EAAIH,GAAW,CAAC,EAE9CI,EAAMC,EAAAA,QAAQ,IACXC,eAAa,EACnB,EAAE,EAKCC,EAHOR,EAEW,SACaK,EAAMH,EAErCO,EAAWC,EAAAA,OAAwB,IAAA,GAAK,EACxCC,EAAoBD,EAAAA,OAAwB,IAAA,GAAK,EAEjDE,EAAUC,EAAAA,YAAY,IACnBC,EAAA,KAAK,QAAQd,EAAM,CACxB,GAAIQ,EACJ,MAAAL,EACA,YAAaE,CAAA,CACd,EACA,CAACL,EAAMQ,EAAiBL,EAAOE,CAAG,CAAC,EAEhCU,EAAeL,SAAwBE,GAAS,EAGtDN,EAAAA,QAAQ,IAAM,CACZS,EAAa,QAAUH,EAAQ,CAAA,EAC9B,CAACA,CAAO,CAAC,EAEZ,MAAMI,EACJV,EAAA,QACE,IACGW,GAAgF,CAC/E,MAAMC,EAAWH,EAAa,QAE9B,GAAI,CAACG,EACH,MAAO,CAAC,EAIV,GAAId,EACF,OAAOA,EAASa,CAAQ,EAI1B,GAAIC,EAAS,0BACJ,OAAAA,EAAS,0BAA0BD,CAAQ,EAIhD,GAAA,OAAOA,GAAa,SAEf,MAAA,CAAC,CAACA,CAAQ,CAAC,EAIpB,MAAME,EAA6B,CAAC,EACzB,UAAAC,KAAOX,EAAS,QACrBW,KAAOH,GACOE,EAAA,KAAKF,EAASG,CAA4B,CAAC,EAK/D,MAAMC,EAA6B,CAAC,EACzB,UAAAD,KAAOT,EAAkB,QAClC,GAAIS,KAAOF,EACL,GAAA,CACI,MAAAI,EAAQJ,EAASE,CAA4B,EACnD,OAAQ,OAAOE,EAAO,CACpB,IAAK,WACH,SACF,QACED,EAAgB,KAAKC,CAAK,EAC1B,QAAA,QAEGC,EAAO,CACTT,EAAAA,KAAA,SAAS,IAAI,gBAAiBS,CAAK,CAAA,CAO1C,OAAAd,EAAS,QAAQ,OAAS,EACrBE,EAAkB,QAAQ,KAAO,EAAI,CAACU,CAAe,EAAI,CAAC,EAAE,EAG9D,CAACF,EAAiBE,CAAe,CAC1C,EACF,CAAA,CACF,EAEIG,EAA0BlB,EAAAA,QAAQ,KAC/B,CACL,UAAYmB,GAA0D,CACpE,MAAMC,EAAkBX,EAAa,QACrC,GAAI,CAACW,EACH,MAAO,IAAM,CAAC,EAGhB,MAAMC,EAAqD,CACzD,GAAI,IAAM,CACJ,GAAA,CAGOlB,EAAA,YAAc,IACLE,EAAA,YAAc,IAGhCc,EAASC,EAAgB,KAAK,QACvBE,EAAG,CAGV,QAAQ,MAAM,CACZ,EAAAA,EACA,aAAcF,EACd,gBAAAV,CAAA,CACD,CAAA,CAEL,EAEA,gBAAAA,EAEA,GAAIX,CACN,EAEAS,EAAA,KAAK,aAAaY,CAAe,EAIjC,MAAMG,EAAQH,EAAgB,UAAU,UAAUC,CAAQ,EAG1D,MAAO,IAAM,CACLE,EAAA,CACR,CACF,EAEA,YAAa,IAA8C,CACzD,MAAMX,EAAWH,EAAa,QAC9B,OAAKG,EAGEA,EAAS,MAFP,CAAC,CAGZ,EAEA,kBAAmB,IAA8C,CAC/D,MAAMA,EAAWH,EAAa,QAC9B,OAAKG,EAGEA,EAAS,MAFP,CAAC,CAEM,CAEpB,GACC,EAACY,EAAAf,EAAa,UAAb,YAAAe,EAAsB,GAAG,CAAC,EAEvB,MAAA,CACL,SAAArB,EACA,kBAAAE,EACA,cAAea,EACf,SAAUT,EACV,IAAAV,CACF,CACF,ECvJwB,SAAA0B,EACtB/B,EACAC,EACc,SAKR,KAAA,CACJ,cAAA+B,EACA,SAAAvB,EACA,kBAAAE,EACA,SAAAO,EACA,IAAAb,CAAA,EACEN,EAAqBC,EAAMC,CAAO,EAGhCuB,EAAQS,EAAA,qBACZD,EAAc,UACd,IAAM,CACE,MAAAE,EAAWF,EAAc,YAAY,EAC3C,GAAIE,IAAa,OACf,MAAM,IAAI,MAAM,kDAAkDlC,EAAK,IAAI,yCAAyC,EAE/G,OAAAkC,CACT,EACAF,EAAc,kBAAoB,IAAM,CAChC,MAAAG,EAAiBH,EAAc,kBAAmB,EACxD,GAAIG,IAAmB,OACrB,MAAM,IAAI,MAAM,yDAAyDnC,EAAK,IAAI,yCAAyC,EAEtH,OAAAmC,CAAA,EACL,MACN,EAGMC,EAAkB1B,EAAAA,OAAoC,IAAA,OAAS,EAC/D2B,EAAkB3B,EAAAA,OAAoC,IAAA,OAAS,EAE/D4B,EAAchC,EAAAA,QAAQ,IAAM,CAChC,GAAI,OAAOkB,GAAU,UAAYA,IAAU,KAClC,OAAAA,EAIT,IAAIe,EAAQH,EAAgB,QAAQ,IAAIZ,CAAK,EAC7C,OAAKe,IACKA,EAAA,IAAI,MAAMf,EAAO,CACvB,IAAIgB,EAAQC,EAAM,CAEhB,kBAAW,IAAM,CACNhC,EAAA,QAAQ,IAAIgC,CAAc,GAClC,CAAC,EACUD,EAAOC,CAA2B,CAElD,EAEA,IAAID,EAAQC,EAAM,CAChB,OAAOA,KAAQD,CACjB,EACA,QAAQA,EAAQ,CACP,OAAA,QAAQ,QAAQA,CAAM,CAC/B,EACA,yBAAyBA,EAAQC,EAAM,CAC9B,OAAA,QAAQ,yBAAyBD,EAAQC,CAAI,CAAA,CACtD,CACD,EACeL,EAAA,QAAQ,IAAIZ,EAAiBe,CAAe,GAEvDA,CAAA,EACN,CAACf,CAAK,CAAC,EAEJkB,EAAcpC,EAAAA,QAAQ,IAAM,CAC5B,GAAA,CAACY,EAAS,QACZ,MAAM,IAAI,MAAM,uCAAuClB,EAAK,IAAI,6DAA6D,EAI/H,IAAIuC,EAAQF,EAAgB,QAAQ,IAAInB,EAAS,OAAO,EACxD,OAAKqB,IACKA,EAAA,IAAI,MAAMrB,EAAS,QAAS,CAClC,IAAIsB,EAAQC,EAAM,CAChB,GAAI,CAACD,EACH,MAAM,IAAI,MAAM,qCAAqCxC,EAAK,IAAI,2DAA2D,EAErH,MAAAsB,EAAQkB,EAAOC,CAA6B,EAC9C,OAAA,OAAOnB,GAAU,YACDX,EAAA,QAAQ,IAAI8B,CAAc,EAEvCnB,CAAA,CACT,CACD,EACDe,EAAgB,QAAQ,IAAInB,EAAS,QAASqB,CAAK,GAE9CA,CACN,EAAA,EAACT,EAAAZ,EAAS,UAAT,YAAAY,EAAkB,GAAG,CAAC,EAGpBa,EAAejC,EAAO,OAAA,EAAE,EAwB9B,GArBAkC,EAAAA,UAAU,IAAM,OACd,MAAMlB,EAAkBR,EAAS,QACjC,GAAKQ,EAGW,OAAAA,EAAA,aAAarB,EAAKsC,EAAa,OAAO,GAGtDb,EAAA7B,GAAA,YAAAA,EAAS,UAAT,MAAA6B,EAAA,KAAA7B,EAAmByB,GAGZ,IAAM,OACNA,KAGLI,EAAA7B,GAAA,YAAAA,EAAS,YAAT,MAAA6B,EAAA,KAAA7B,EAAqByB,GACrBA,EAAgB,gBAAgBrB,CAAG,EACrC,GACC,EAACwC,EAAA3B,EAAS,UAAT,YAAA2B,EAAkB,IAAKxC,CAAG,CAAC,EAG3BiC,IAAgB,OAClB,MAAM,IAAI,MAAM,oCAAoCtC,EAAK,IAAI,qDAAqD,EAEpH,GAAI,CAAC0C,EACH,MAAM,IAAI,MAAM,kCAAkC1C,EAAK,IAAI,wDAAwD,EAI9G,MAAA,CAACsC,EAAaI,CAAW,CAClC"}
|
|
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?: AdapterOptions<InstanceType<B>>,\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 options,\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\n const optionsChangeCount = useRef(0);\n useEffect(() => {\n optionsChangeCount.current++;\n adapter.options = options;\n }, [options]);\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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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,57 +1,37 @@
|
|
|
1
|
+
import { AdapterOptions } from '@blac/core';
|
|
1
2
|
import { BlocBase } from '@blac/core';
|
|
2
3
|
import { BlocConstructor } from '@blac/core';
|
|
3
|
-
import { BlocHookDependencyArrayFn } from '@blac/core';
|
|
4
4
|
import { BlocState } from '@blac/core';
|
|
5
5
|
import { InferPropsFromGeneric } from '@blac/core';
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
declare interface ExternalBlacStore<B extends BlocConstructor<BlocBase<any>>> {
|
|
19
|
-
usedKeys: React.RefObject<Set<string>>;
|
|
20
|
-
usedClassPropKeys: React.RefObject<Set<string>>;
|
|
21
|
-
externalStore: ExternalStore<B>;
|
|
22
|
-
instance: React.RefObject<InstanceType<B>>;
|
|
7
|
+
declare interface ExternalBlocStoreResult<B extends BlocBase<any>> {
|
|
8
|
+
externalStore: ExternalStore<BlocState<B>>;
|
|
9
|
+
instance: {
|
|
10
|
+
current: B | null;
|
|
11
|
+
};
|
|
12
|
+
usedKeys: {
|
|
13
|
+
current: Set<string>;
|
|
14
|
+
};
|
|
15
|
+
usedClassPropKeys: {
|
|
16
|
+
current: Set<string>;
|
|
17
|
+
};
|
|
23
18
|
rid: string;
|
|
24
19
|
}
|
|
25
20
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
21
|
+
declare interface ExternalStore<T> {
|
|
22
|
+
getSnapshot: () => T | undefined;
|
|
23
|
+
subscribe: (listener: () => void) => () => void;
|
|
24
|
+
getServerSnapshot?: () => T | undefined;
|
|
25
|
+
}
|
|
31
26
|
|
|
32
|
-
declare interface
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
* @returns A function that can be called to unsubscribe from store changes
|
|
37
|
-
*/
|
|
38
|
-
subscribe: (onStoreChange: (state: BlocState<InstanceType<B>>) => void) => () => void;
|
|
39
|
-
/**
|
|
40
|
-
* Gets the current snapshot of the store state.
|
|
41
|
-
* @returns The current state of the store
|
|
42
|
-
*/
|
|
43
|
-
getSnapshot: () => BlocState<InstanceType<B>> | undefined;
|
|
44
|
-
/**
|
|
45
|
-
* Gets the server snapshot of the store state.
|
|
46
|
-
* This is optional and defaults to the same value as getSnapshot.
|
|
47
|
-
* @returns The server state of the store
|
|
48
|
-
*/
|
|
49
|
-
getServerSnapshot?: () => BlocState<InstanceType<B>> | undefined;
|
|
27
|
+
declare interface ExternalStoreOptions<B extends BlocBase<any>> {
|
|
28
|
+
id?: string;
|
|
29
|
+
props?: InferPropsFromGeneric<B>;
|
|
30
|
+
selector?: (currentState: BlocState<B>, previousState: BlocState<B>, instance: B) => any[];
|
|
50
31
|
}
|
|
51
32
|
|
|
52
33
|
/**
|
|
53
34
|
* Type definition for the return type of the useBloc hook
|
|
54
|
-
* @template B - Bloc constructor type
|
|
55
35
|
*/
|
|
56
36
|
declare type HookTypes<B extends BlocConstructor<BlocBase<any>>> = [
|
|
57
37
|
BlocState<InstanceType<B>>,
|
|
@@ -59,30 +39,14 @@ InstanceType<B>
|
|
|
59
39
|
];
|
|
60
40
|
|
|
61
41
|
/**
|
|
62
|
-
*
|
|
63
|
-
* @template T - State type
|
|
64
|
-
* @returns {Array<Array<T>>} Dependency array containing the entire state
|
|
42
|
+
* React hook for integrating with Blac state management
|
|
65
43
|
*/
|
|
44
|
+
export declare function useBloc<B extends BlocConstructor<BlocBase<any>>>(blocConstructor: B, options?: AdapterOptions<InstanceType<B>>): HookTypes<B>;
|
|
45
|
+
|
|
66
46
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* This hook connects a React component to a Bloc state container, providing
|
|
70
|
-
* automatic re-rendering when relevant state changes, dependency tracking,
|
|
71
|
-
* and proper lifecycle management.
|
|
72
|
-
*
|
|
73
|
-
* @template B - Bloc constructor type
|
|
74
|
-
* @template O - BlocHookOptions type
|
|
75
|
-
* @param {B} bloc - Bloc constructor class
|
|
76
|
-
* @param {O} [options] - Configuration options for the hook
|
|
77
|
-
* @returns {HookTypes<B>} Tuple containing [state, bloc instance]
|
|
78
|
-
*
|
|
79
|
-
* @example
|
|
80
|
-
* const [state, counterBloc] = useBloc(CounterBloc);
|
|
81
|
-
* // Access state
|
|
82
|
-
* console.log(state.count);
|
|
83
|
-
* // Call bloc methods
|
|
84
|
-
* counterBloc.increment();
|
|
47
|
+
* Hook for using an external bloc store
|
|
48
|
+
* Provides low-level access to bloc instances for use with React's useSyncExternalStore
|
|
85
49
|
*/
|
|
86
|
-
export declare function
|
|
50
|
+
export declare function useExternalBlocStore<B extends BlocConstructor<BlocBase<any>>>(blocConstructor: B, options?: ExternalStoreOptions<InstanceType<B>>): ExternalBlocStoreResult<InstanceType<B>>;
|
|
87
51
|
|
|
88
52
|
export { }
|
package/dist/index.es.js
CHANGED
|
@@ -1,167 +1,98 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
1
|
+
import { BlacAdapter as C, generateUUID as g, Blac as w } from "@blac/core";
|
|
2
|
+
import { useRef as n, useMemo as l, useEffect as B, useSyncExternalStore as v } from "react";
|
|
3
|
+
function K(c, e) {
|
|
4
|
+
const f = n(0);
|
|
5
|
+
f.current++;
|
|
6
|
+
const d = n({}), t = l(() => new C(
|
|
7
|
+
{
|
|
8
|
+
componentRef: d,
|
|
9
|
+
blocConstructor: c
|
|
10
|
+
},
|
|
11
|
+
e
|
|
12
|
+
), []);
|
|
13
|
+
t.resetConsumerTracking();
|
|
14
|
+
const s = n(0);
|
|
15
|
+
B(() => {
|
|
16
|
+
s.current++, t.options = e;
|
|
17
|
+
}, [e]);
|
|
18
|
+
const m = n(0);
|
|
19
|
+
B(() => (m.current++, t.mount(), () => {
|
|
20
|
+
t.unmount();
|
|
21
|
+
}), [t.blocInstance]);
|
|
22
|
+
const I = n(0), r = l(() => (I.current++, (o) => {
|
|
23
|
+
const y = t.createSubscription({
|
|
24
|
+
onChange: () => {
|
|
25
|
+
o();
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
return () => {
|
|
29
|
+
y();
|
|
30
|
+
};
|
|
31
|
+
}), [t.blocInstance]), u = n(0), a = n(0), b = v(
|
|
32
|
+
r,
|
|
33
|
+
// Get snapshot
|
|
34
|
+
() => (u.current++, t.blocInstance.state),
|
|
35
|
+
// Get server snapshot (same as client for now)
|
|
36
|
+
() => {
|
|
37
|
+
const o = t.blocInstance;
|
|
38
|
+
return a.current++, o.state;
|
|
39
|
+
}
|
|
40
|
+
), S = n(0), i = l(() => (S.current++, t.getProxyState(b)), [b]), x = n(0), h = l(() => (x.current++, t.getProxyBlocInstance()), [t.blocInstance]);
|
|
41
|
+
return B(() => {
|
|
42
|
+
t.updateLastNotified(d.current);
|
|
43
|
+
}), [i, h];
|
|
44
|
+
}
|
|
45
|
+
function M(c, e = {}) {
|
|
46
|
+
const f = n(`external-${g()}`), d = n(/* @__PURE__ */ new Set()), t = n(/* @__PURE__ */ new Set()), s = n(null), m = l(() => {
|
|
47
|
+
const r = w.getInstance();
|
|
48
|
+
if (c.constructor.isolated || c.isolated) {
|
|
49
|
+
const a = new c(e == null ? void 0 : e.props), b = (e == null ? void 0 : e.id) || `${c.name}_${g()}`;
|
|
50
|
+
return a._updateId(b), r.activateBloc(a), a;
|
|
51
|
+
}
|
|
52
|
+
return r.getBloc(c, {
|
|
53
|
+
id: e == null ? void 0 : e.id,
|
|
54
|
+
props: e == null ? void 0 : e.props
|
|
55
|
+
});
|
|
56
|
+
}, [c, e == null ? void 0 : e.id]), I = l(
|
|
57
|
+
() => ({
|
|
58
|
+
getSnapshot: () => {
|
|
59
|
+
const r = s.current;
|
|
60
|
+
return r ? r.state : void 0;
|
|
61
|
+
},
|
|
62
|
+
subscribe: (r) => {
|
|
63
|
+
const u = s.current;
|
|
64
|
+
if (!u)
|
|
65
|
+
return () => {
|
|
66
|
+
};
|
|
67
|
+
const a = (S, i) => {
|
|
30
68
|
try {
|
|
31
|
-
|
|
32
|
-
switch (typeof x) {
|
|
33
|
-
case "function":
|
|
34
|
-
continue;
|
|
35
|
-
default:
|
|
36
|
-
S.push(x);
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
69
|
+
d.current.clear(), t.current.clear(), e.selector && u && e.selector(S, i, u), r.length > 0 ? r(S) : r();
|
|
39
70
|
} catch (x) {
|
|
40
|
-
|
|
71
|
+
console.error("Listener error in useExternalBlocStore:", x);
|
|
41
72
|
}
|
|
42
|
-
return i.current.size === 0 ? l.current.size > 0 ? [S] : [[]] : [a, S];
|
|
43
|
-
},
|
|
44
|
-
[]
|
|
45
|
-
), g = y(() => ({
|
|
46
|
-
subscribe: (e) => {
|
|
47
|
-
const t = u.current;
|
|
48
|
-
if (!t)
|
|
49
|
-
return () => {
|
|
50
73
|
};
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
} catch (f) {
|
|
56
|
-
console.error({
|
|
57
|
-
e: f,
|
|
58
|
-
blocInstance: t,
|
|
59
|
-
dependencyArray: v
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
// Pass the dependency array to control when the subscription is updated
|
|
64
|
-
dependencyArray: v,
|
|
65
|
-
// Use the provided id to identify this subscription
|
|
66
|
-
id: s
|
|
67
|
-
};
|
|
68
|
-
E.activateBloc(t);
|
|
69
|
-
const S = t._observer.subscribe(a);
|
|
70
|
-
return () => {
|
|
71
|
-
S();
|
|
72
|
-
};
|
|
73
|
-
},
|
|
74
|
-
// Return an immutable snapshot of the current bloc state
|
|
75
|
-
getSnapshot: () => {
|
|
76
|
-
const e = u.current;
|
|
77
|
-
return e ? e.state : {};
|
|
78
|
-
},
|
|
79
|
-
// Server snapshot mirrors the client snapshot in this implementation
|
|
80
|
-
getServerSnapshot: () => {
|
|
81
|
-
const e = u.current;
|
|
82
|
-
return e ? e.state : {};
|
|
83
|
-
}
|
|
84
|
-
}), [(r = u.current) == null ? void 0 : r.uid]);
|
|
85
|
-
return {
|
|
86
|
-
usedKeys: i,
|
|
87
|
-
usedClassPropKeys: l,
|
|
88
|
-
externalStore: g,
|
|
89
|
-
instance: u,
|
|
90
|
-
rid: s
|
|
91
|
-
};
|
|
92
|
-
};
|
|
93
|
-
function R(c, n) {
|
|
94
|
-
var v, g;
|
|
95
|
-
const {
|
|
96
|
-
externalStore: d,
|
|
97
|
-
usedKeys: w,
|
|
98
|
-
usedClassPropKeys: b,
|
|
99
|
-
instance: s,
|
|
100
|
-
rid: B
|
|
101
|
-
} = $(c, n), o = C(
|
|
102
|
-
d.subscribe,
|
|
103
|
-
() => {
|
|
104
|
-
const r = d.getSnapshot();
|
|
105
|
-
if (r === void 0)
|
|
106
|
-
throw new Error(`[useBloc] State snapshot is undefined for bloc ${c.name}. Bloc may not be properly initialized.`);
|
|
107
|
-
return r;
|
|
108
|
-
},
|
|
109
|
-
d.getServerSnapshot ? () => {
|
|
110
|
-
const r = d.getServerSnapshot();
|
|
111
|
-
if (r === void 0)
|
|
112
|
-
throw new Error(`[useBloc] Server state snapshot is undefined for bloc ${c.name}. Bloc may not be properly initialized.`);
|
|
113
|
-
return r;
|
|
114
|
-
} : void 0
|
|
115
|
-
), m = p(/* @__PURE__ */ new WeakMap()), i = p(/* @__PURE__ */ new WeakMap()), l = y(() => {
|
|
116
|
-
if (typeof o != "object" || o === null)
|
|
117
|
-
return o;
|
|
118
|
-
let r = m.current.get(o);
|
|
119
|
-
return r || (r = new Proxy(o, {
|
|
120
|
-
get(e, t) {
|
|
121
|
-
return setTimeout(() => {
|
|
122
|
-
w.current.add(t);
|
|
123
|
-
}, 0), e[t];
|
|
74
|
+
return u._observer.subscribe({
|
|
75
|
+
id: f.current,
|
|
76
|
+
fn: a
|
|
77
|
+
});
|
|
124
78
|
},
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
return
|
|
128
|
-
},
|
|
129
|
-
ownKeys(e) {
|
|
130
|
-
return Reflect.ownKeys(e);
|
|
131
|
-
},
|
|
132
|
-
getOwnPropertyDescriptor(e, t) {
|
|
133
|
-
return Reflect.getOwnPropertyDescriptor(e, t);
|
|
134
|
-
}
|
|
135
|
-
}), m.current.set(o, r)), r;
|
|
136
|
-
}, [o]), h = y(() => {
|
|
137
|
-
if (!s.current)
|
|
138
|
-
throw new Error(`[useBloc] Bloc instance is null for ${c.name}. This should never happen - bloc instance must be defined.`);
|
|
139
|
-
let r = i.current.get(s.current);
|
|
140
|
-
return r || (r = new Proxy(s.current, {
|
|
141
|
-
get(e, t) {
|
|
142
|
-
if (!e)
|
|
143
|
-
throw new Error(`[useBloc] Bloc target is null for ${c.name}. This should never happen - bloc target must be defined.`);
|
|
144
|
-
const a = e[t];
|
|
145
|
-
return typeof a != "function" && b.current.add(t), a;
|
|
79
|
+
getServerSnapshot: () => {
|
|
80
|
+
const r = s.current;
|
|
81
|
+
return r ? r.state : void 0;
|
|
146
82
|
}
|
|
147
|
-
}),
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}, [(g = s.current) == null ? void 0 : g.uid, B]), l === void 0)
|
|
158
|
-
throw new Error(`[useBloc] State is undefined for ${c.name}. This should never happen - state must be defined.`);
|
|
159
|
-
if (!h)
|
|
160
|
-
throw new Error(`[useBloc] Instance is null for ${c.name}. This should never happen - instance must be defined.`);
|
|
161
|
-
return [l, h];
|
|
83
|
+
}),
|
|
84
|
+
[]
|
|
85
|
+
);
|
|
86
|
+
return s.current = m, {
|
|
87
|
+
externalStore: I,
|
|
88
|
+
instance: s,
|
|
89
|
+
usedKeys: d,
|
|
90
|
+
usedClassPropKeys: t,
|
|
91
|
+
rid: f.current
|
|
92
|
+
};
|
|
162
93
|
}
|
|
163
94
|
export {
|
|
164
|
-
|
|
165
|
-
|
|
95
|
+
K as useBloc,
|
|
96
|
+
M as useExternalBlocStore
|
|
166
97
|
};
|
|
167
98
|
//# sourceMappingURL=index.es.js.map
|