@blac/react 2.0.1 → 2.0.4
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 +7 -9
- package/dist/index.cjs +24 -118
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -77
- package/dist/index.d.ts +8 -77
- package/dist/index.js +18 -110
- package/dist/index.js.map +1 -1
- package/package.json +6 -4
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# @blac/react
|
|
2
2
|
|
|
3
|
+
> ⚠️ **Warning:** This project is currently under active development. The API may change in future releases. Use with caution in production environments.
|
|
4
|
+
|
|
3
5
|
React integration for the BlaC state management library with automatic re-render optimization.
|
|
4
6
|
|
|
5
7
|
## Installation
|
|
@@ -102,7 +104,6 @@ useBloc(MyBloc, {
|
|
|
102
104
|
|
|
103
105
|
// Instance management
|
|
104
106
|
instanceId: 'unique-id', // Custom instance identifier
|
|
105
|
-
props: { userId: 123 }, // Props passed to bloc
|
|
106
107
|
|
|
107
108
|
// Lifecycle callbacks
|
|
108
109
|
onMount: (bloc) => console.log('Mounted', bloc),
|
|
@@ -143,7 +144,6 @@ function IncrementButton() {
|
|
|
143
144
|
```tsx
|
|
144
145
|
useBlocActions(MyBloc, {
|
|
145
146
|
instanceId: 'unique-id', // Custom instance identifier
|
|
146
|
-
props: { config: value }, // Props passed to bloc
|
|
147
147
|
onMount: (bloc) => bloc.initialize(),
|
|
148
148
|
onUnmount: (bloc) => bloc.cleanup(),
|
|
149
149
|
});
|
|
@@ -249,18 +249,16 @@ function useBlocActions<T extends StateContainerConstructor>(
|
|
|
249
249
|
| `autoTrack` | `boolean` | Enable auto-tracking (default: true) |
|
|
250
250
|
| `dependencies` | `(state) => any[]` | Manual dependency selector |
|
|
251
251
|
| `instanceId` | `string \| number` | Custom instance identifier |
|
|
252
|
-
| `props` | `P` | Props passed to bloc constructor |
|
|
253
252
|
| `onMount` | `(bloc) => void` | Called when component mounts |
|
|
254
253
|
| `onUnmount` | `(bloc) => void` | Called when component unmounts |
|
|
255
254
|
|
|
256
255
|
### UseBlocActionsOptions
|
|
257
256
|
|
|
258
|
-
| Option | Type | Description
|
|
259
|
-
| ------------ | ------------------ |
|
|
260
|
-
| `instanceId` | `string \| number` | Custom instance identifier
|
|
261
|
-
| `
|
|
262
|
-
| `
|
|
263
|
-
| `onUnmount` | `(bloc) => void` | Called when component unmounts |
|
|
257
|
+
| Option | Type | Description |
|
|
258
|
+
| ------------ | ------------------ | ------------------------------ |
|
|
259
|
+
| `instanceId` | `string \| number` | Custom instance identifier |
|
|
260
|
+
| `onMount` | `(bloc) => void` | Called when component mounts |
|
|
261
|
+
| `onUnmount` | `(bloc) => void` | Called when component unmounts |
|
|
264
262
|
|
|
265
263
|
## Compatibility
|
|
266
264
|
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
let react = require("react");
|
|
2
|
-
let
|
|
2
|
+
let _blac_adapter = require("@blac/adapter");
|
|
3
3
|
|
|
4
4
|
//#region src/utils/instance-keys.ts
|
|
5
5
|
/**
|
|
@@ -21,7 +21,7 @@ let _blac_core = require("@blac/core");
|
|
|
21
21
|
function generateInstanceKey(componentRef, isIsolated, providedId) {
|
|
22
22
|
if (providedId !== void 0) return typeof providedId === "number" ? String(providedId) : providedId;
|
|
23
23
|
if (isIsolated) {
|
|
24
|
-
if (!componentRef.__blocInstanceId) componentRef.__blocInstanceId = (0,
|
|
24
|
+
if (!componentRef.__blocInstanceId) componentRef.__blocInstanceId = (0, _blac_adapter.generateIsolatedKey)();
|
|
25
25
|
return componentRef.__blocInstanceId;
|
|
26
26
|
}
|
|
27
27
|
}
|
|
@@ -58,19 +58,12 @@ function configureBlacReact(config) {
|
|
|
58
58
|
function getBlacReactConfig() {
|
|
59
59
|
return globalConfig;
|
|
60
60
|
}
|
|
61
|
-
/**
|
|
62
|
-
* Reset configuration to defaults (useful for testing).
|
|
63
|
-
* @internal
|
|
64
|
-
*/
|
|
65
|
-
function resetBlacReactConfig() {
|
|
66
|
-
globalConfig = { ...defaultConfig };
|
|
67
|
-
}
|
|
68
61
|
|
|
69
62
|
//#endregion
|
|
70
63
|
//#region src/useBloc.ts
|
|
71
64
|
function determineTrackingMode(options) {
|
|
72
|
-
const globalConfig
|
|
73
|
-
const autoTrackEnabled = options?.autoTrack !== void 0 ? options.autoTrack : globalConfig
|
|
65
|
+
const globalConfig = getBlacReactConfig();
|
|
66
|
+
const autoTrackEnabled = options?.autoTrack !== void 0 ? options.autoTrack : globalConfig.autoTrack;
|
|
74
67
|
return {
|
|
75
68
|
useManualDeps: options?.dependencies !== void 0,
|
|
76
69
|
autoTrackEnabled
|
|
@@ -84,11 +77,8 @@ function determineTrackingMode(options) {
|
|
|
84
77
|
* - **Manual dependencies**: Explicit dependency array like useEffect
|
|
85
78
|
* - **No tracking**: Returns full state without optimization
|
|
86
79
|
*
|
|
87
|
-
* **Note:** This hook does NOT support stateless containers (StatelessCubit, StatelessVertex).
|
|
88
|
-
* Use `useBlocActions()` instead for stateless containers.
|
|
89
|
-
*
|
|
90
80
|
* @template T - The state container constructor type (inferred from BlocClass)
|
|
91
|
-
* @param BlocClass - The state container class to connect to
|
|
81
|
+
* @param BlocClass - The state container class to connect to
|
|
92
82
|
* @param options - Configuration options for tracking mode and instance management
|
|
93
83
|
* @returns Tuple with [state, bloc instance, ref]
|
|
94
84
|
*
|
|
@@ -113,79 +103,53 @@ function determineTrackingMode(options) {
|
|
|
113
103
|
*/
|
|
114
104
|
function useBloc(BlocClass, options) {
|
|
115
105
|
const componentRef = (0, react.useRef)({});
|
|
116
|
-
const
|
|
117
|
-
const isStateless = (0, _blac_core.isStatelessClass)(BlocClass);
|
|
118
|
-
const isIsolated = (0, _blac_core.isIsolatedClass)(BlocClass);
|
|
106
|
+
const isIsolated = (0, _blac_adapter.isIsolatedClass)(BlocClass);
|
|
119
107
|
const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] = (0, react.useMemo)(() => {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const instance$1 = (0, _blac_core.acquire)(BlocClass, "default");
|
|
123
|
-
return [
|
|
124
|
-
instance$1,
|
|
125
|
-
() => () => {},
|
|
126
|
-
() => ({}),
|
|
127
|
-
void 0,
|
|
128
|
-
null,
|
|
129
|
-
instance$1
|
|
130
|
-
];
|
|
131
|
-
}
|
|
132
|
-
const instanceKey$1 = generateInstanceKey(componentRef.current, isIsolated, options?.instanceId);
|
|
133
|
-
const instance = (0, _blac_core.acquire)(BlocClass, instanceKey$1, { props: initialPropsRef.current });
|
|
134
|
-
if (initialPropsRef.current !== void 0) instance.updateProps(initialPropsRef.current);
|
|
108
|
+
const instanceKey = generateInstanceKey(componentRef.current, isIsolated, options?.instanceId);
|
|
109
|
+
const instance = (0, _blac_adapter.acquire)(BlocClass, instanceKey);
|
|
135
110
|
const { useManualDeps, autoTrackEnabled } = determineTrackingMode(options);
|
|
136
111
|
let subscribeFn;
|
|
137
112
|
let getSnapshotFn;
|
|
138
|
-
let adapterState
|
|
113
|
+
let adapterState;
|
|
139
114
|
if (useManualDeps && options?.dependencies) {
|
|
140
|
-
adapterState
|
|
141
|
-
subscribeFn = (0,
|
|
142
|
-
getSnapshotFn = (0,
|
|
115
|
+
adapterState = (0, _blac_adapter.manualDepsInit)(instance);
|
|
116
|
+
subscribeFn = (0, _blac_adapter.manualDepsSubscribe)(instance, adapterState, { dependencies: options.dependencies });
|
|
117
|
+
getSnapshotFn = (0, _blac_adapter.manualDepsSnapshot)(instance, adapterState, { dependencies: options.dependencies });
|
|
143
118
|
} else if (!autoTrackEnabled) {
|
|
144
|
-
adapterState
|
|
145
|
-
subscribeFn = (0,
|
|
146
|
-
getSnapshotFn = (0,
|
|
119
|
+
adapterState = (0, _blac_adapter.noTrackInit)(instance);
|
|
120
|
+
subscribeFn = (0, _blac_adapter.noTrackSubscribe)(instance);
|
|
121
|
+
getSnapshotFn = (0, _blac_adapter.noTrackSnapshot)(instance);
|
|
147
122
|
} else {
|
|
148
|
-
adapterState
|
|
149
|
-
subscribeFn = (0,
|
|
150
|
-
getSnapshotFn = (0,
|
|
123
|
+
adapterState = (0, _blac_adapter.autoTrackInit)(instance);
|
|
124
|
+
subscribeFn = (0, _blac_adapter.autoTrackSubscribe)(instance, adapterState);
|
|
125
|
+
getSnapshotFn = (0, _blac_adapter.autoTrackSnapshot)(instance, adapterState);
|
|
151
126
|
}
|
|
152
127
|
return [
|
|
153
|
-
adapterState
|
|
128
|
+
adapterState.proxiedBloc,
|
|
154
129
|
subscribeFn,
|
|
155
130
|
getSnapshotFn,
|
|
156
|
-
instanceKey
|
|
157
|
-
adapterState
|
|
131
|
+
instanceKey,
|
|
132
|
+
adapterState,
|
|
158
133
|
instance
|
|
159
134
|
];
|
|
160
135
|
}, [
|
|
161
136
|
BlocClass,
|
|
162
|
-
isStateless,
|
|
163
137
|
isIsolated,
|
|
164
138
|
options?.instanceId
|
|
165
139
|
]);
|
|
166
140
|
const state = (0, react.useSyncExternalStore)(subscribe, getSnapshot);
|
|
167
141
|
const [, forceUpdate] = (0, react.useReducer)((x) => x + 1, 0);
|
|
168
|
-
const externalDepsManager = (0, react.useRef)(new
|
|
142
|
+
const externalDepsManager = (0, react.useRef)(new _blac_adapter.ExternalDepsManager());
|
|
169
143
|
(0, react.useEffect)(() => {
|
|
170
|
-
|
|
171
|
-
if (options?.props !== initialPropsRef.current) rawInstance.updateProps(options?.props);
|
|
172
|
-
}, [
|
|
173
|
-
options?.props,
|
|
174
|
-
rawInstance,
|
|
175
|
-
isStateless
|
|
176
|
-
]);
|
|
177
|
-
(0, react.useEffect)(() => {
|
|
178
|
-
if (isStateless || !adapterState) return;
|
|
179
|
-
(0, _blac_core.disableGetterTracking)(adapterState, rawInstance);
|
|
144
|
+
(0, _blac_adapter.disableGetterTracking)(adapterState, rawInstance);
|
|
180
145
|
externalDepsManager.current.updateSubscriptions(adapterState.getterState, rawInstance, forceUpdate);
|
|
181
146
|
});
|
|
182
147
|
(0, react.useEffect)(() => {
|
|
183
|
-
if (isStateless) return;
|
|
184
148
|
if (options?.onMount) options.onMount(bloc);
|
|
185
149
|
return () => {
|
|
186
150
|
externalDepsManager.current.cleanup();
|
|
187
151
|
if (options?.onUnmount) options.onUnmount(bloc);
|
|
188
|
-
(0,
|
|
152
|
+
(0, _blac_adapter.release)(BlocClass, instanceKey);
|
|
189
153
|
if (isIsolated && !rawInstance.isDisposed) rawInstance.dispose();
|
|
190
154
|
};
|
|
191
155
|
}, []);
|
|
@@ -196,65 +160,7 @@ function useBloc(BlocClass, options) {
|
|
|
196
160
|
];
|
|
197
161
|
}
|
|
198
162
|
|
|
199
|
-
//#endregion
|
|
200
|
-
//#region src/useBlocActions.ts
|
|
201
|
-
/**
|
|
202
|
-
* React hook that connects to a state container instance without triggering re-renders.
|
|
203
|
-
* Use this when you only need to call actions on the bloc without subscribing to state changes.
|
|
204
|
-
*
|
|
205
|
-
* **This is the recommended hook for stateless containers** (StatelessCubit, StatelessVertex).
|
|
206
|
-
* It also works with regular Cubit/Vertex when you don't need state subscriptions.
|
|
207
|
-
*
|
|
208
|
-
* @template T - The state container constructor type (inferred from BlocClass)
|
|
209
|
-
* @param BlocClass - The state container class to connect to (supports both stateful and stateless)
|
|
210
|
-
* @param options - Configuration options for instance management and lifecycle
|
|
211
|
-
* @returns The state container instance for calling actions
|
|
212
|
-
*
|
|
213
|
-
* @example Basic usage with stateless container
|
|
214
|
-
* ```ts
|
|
215
|
-
* class AnalyticsService extends StatelessCubit {
|
|
216
|
-
* trackEvent(name: string) { ... }
|
|
217
|
-
* }
|
|
218
|
-
*
|
|
219
|
-
* const analytics = useBlocActions(AnalyticsService);
|
|
220
|
-
* analytics.trackEvent('page_view');
|
|
221
|
-
* ```
|
|
222
|
-
*
|
|
223
|
-
* @example With stateful container (no re-renders)
|
|
224
|
-
* ```ts
|
|
225
|
-
* const myBloc = useBlocActions(MyBloc);
|
|
226
|
-
* myBloc.someMethod(); // Won't cause re-renders
|
|
227
|
-
* ```
|
|
228
|
-
*
|
|
229
|
-
* @example With isolated instance
|
|
230
|
-
* ```ts
|
|
231
|
-
* const myBloc = useBlocActions(MyBloc, {
|
|
232
|
-
* instanceId: 'unique-id'
|
|
233
|
-
* });
|
|
234
|
-
* ```
|
|
235
|
-
*/
|
|
236
|
-
function useBlocActions(BlocClass, options) {
|
|
237
|
-
const componentRef = (0, react.useRef)({});
|
|
238
|
-
const initialPropsRef = (0, react.useRef)(options?.props);
|
|
239
|
-
const [bloc, instanceKey] = (0, react.useMemo)(() => {
|
|
240
|
-
const isIsolated = (0, _blac_core.isIsolatedClass)(BlocClass);
|
|
241
|
-
const instanceKey$1 = generateInstanceKey(componentRef.current, isIsolated, options?.instanceId);
|
|
242
|
-
return [(0, _blac_core.acquire)(BlocClass, instanceKey$1, { props: initialPropsRef.current }), instanceKey$1];
|
|
243
|
-
}, [BlocClass]);
|
|
244
|
-
(0, react.useEffect)(() => {
|
|
245
|
-
if (options?.onMount) options.onMount(bloc);
|
|
246
|
-
return () => {
|
|
247
|
-
if (options?.onUnmount) options.onUnmount(bloc);
|
|
248
|
-
(0, _blac_core.release)(BlocClass, instanceKey);
|
|
249
|
-
if ((0, _blac_core.isIsolatedClass)(BlocClass) && !bloc.isDisposed) bloc.dispose();
|
|
250
|
-
};
|
|
251
|
-
}, []);
|
|
252
|
-
return bloc;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
163
|
//#endregion
|
|
256
164
|
exports.configureBlacReact = configureBlacReact;
|
|
257
|
-
exports.resetBlacReactConfig = resetBlacReactConfig;
|
|
258
165
|
exports.useBloc = useBloc;
|
|
259
|
-
exports.useBlocActions = useBlocActions;
|
|
260
166
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["globalConfig","instance","instanceKey","adapterState","ExternalDepsManager","instanceKey"],"sources":["../src/utils/instance-keys.ts","../src/config.ts","../src/useBloc.ts","../src/useBlocActions.ts"],"sourcesContent":["/**\n * Instance key generation utilities for React integration\n */\n\nimport { generateIsolatedKey } from '@blac/core';\nimport type { ComponentRef } from '../types';\n\n/**\n * Generate an instance key for a bloc\n *\n * Logic:\n * - If user provides instanceId, use it (convert number to string)\n * - If isolated, generate or reuse a unique key for this component\n * - Otherwise, return undefined (use default key)\n *\n * @param componentRef - React component reference (persists across remounts)\n * @param isIsolated - Whether the bloc is isolated\n * @param providedId - User-provided instance ID (from options)\n * @returns Instance key string or undefined for default\n */\nexport function generateInstanceKey(\n componentRef: ComponentRef,\n isIsolated: boolean,\n providedId?: string | number,\n): string | undefined {\n // User explicitly provided an ID - use it\n if (providedId !== undefined) {\n return typeof providedId === 'number' ? String(providedId) : providedId;\n }\n\n // Isolated bloc - generate unique key per component\n if (isIsolated) {\n if (!componentRef.__blocInstanceId) {\n componentRef.__blocInstanceId = generateIsolatedKey();\n }\n return componentRef.__blocInstanceId;\n }\n\n // Shared bloc - use default key (undefined)\n return undefined;\n}\n","/**\n * Global configuration for @blac/react\n */\n\nexport interface BlacReactConfig {\n /** Enable automatic property tracking via Proxy (default: true) */\n autoTrack: boolean;\n}\n\nconst defaultConfig: BlacReactConfig = {\n autoTrack: true,\n};\n\nlet globalConfig: BlacReactConfig = { ...defaultConfig };\n\n/**\n * Configure global defaults for @blac/react hooks.\n *\n * @example\n * ```ts\n * import { configureBlacReact } from '@blac/react';\n *\n * // Disable auto-tracking globally\n * configureBlacReact({\n * autoTrack: false\n * });\n * ```\n *\n * @param config - Partial configuration to merge with defaults\n */\nexport function configureBlacReact(config: Partial<BlacReactConfig>): void {\n globalConfig = { ...globalConfig, ...config };\n}\n\n/**\n * Get the current global configuration.\n * @internal\n */\nexport function getBlacReactConfig(): BlacReactConfig {\n return globalConfig;\n}\n\n/**\n * Reset configuration to defaults (useful for testing).\n * @internal\n */\nexport function resetBlacReactConfig(): void {\n globalConfig = { ...defaultConfig };\n}\n","import {\n useMemo,\n useSyncExternalStore,\n useEffect,\n useRef,\n useReducer,\n} from 'react';\nimport {\n type ExtractState,\n type AdapterState,\n type IsStatelessContainer,\n ExternalDepsManager,\n autoTrackSubscribe,\n manualDepsSubscribe,\n noTrackSubscribe,\n autoTrackSnapshot,\n manualDepsSnapshot,\n noTrackSnapshot,\n autoTrackInit,\n manualDepsInit,\n noTrackInit,\n disableGetterTracking,\n isIsolatedClass,\n isStatelessClass,\n StateContainerConstructor,\n InstanceState,\n acquire,\n release,\n} from '@blac/core';\nimport type { UseBlocOptions, UseBlocReturn, ComponentRef } from './types';\nimport { generateInstanceKey } from './utils/instance-keys';\nimport { getBlacReactConfig } from './config';\n\ninterface TrackingMode {\n useManualDeps: boolean;\n autoTrackEnabled: boolean;\n}\n\nfunction determineTrackingMode<TBloc extends StateContainerConstructor>(\n options?: UseBlocOptions<TBloc>,\n): TrackingMode {\n const globalConfig = getBlacReactConfig();\n const autoTrackEnabled =\n options?.autoTrack !== undefined\n ? options.autoTrack\n : globalConfig.autoTrack;\n\n return {\n useManualDeps: options?.dependencies !== undefined,\n autoTrackEnabled,\n };\n}\n\n/**\n * Type that produces a TypeScript error when a stateless container is used.\n * Evaluates to `never` for stateless containers, causing a type mismatch.\n */\ntype StatefulContainer<T extends StateContainerConstructor> =\n IsStatelessContainer<T> extends true ? never : T;\n\n/**\n * React hook that connects a component to a state container with automatic re-render on state changes.\n *\n * Supports three tracking modes:\n * - **Auto-tracking** (default): Automatically detects accessed state properties via Proxy\n * - **Manual dependencies**: Explicit dependency array like useEffect\n * - **No tracking**: Returns full state without optimization\n *\n * **Note:** This hook does NOT support stateless containers (StatelessCubit, StatelessVertex).\n * Use `useBlocActions()` instead for stateless containers.\n *\n * @template T - The state container constructor type (inferred from BlocClass)\n * @param BlocClass - The state container class to connect to (must not be stateless)\n * @param options - Configuration options for tracking mode and instance management\n * @returns Tuple with [state, bloc instance, ref]\n *\n * @example Basic usage\n * ```ts\n * const [state, myBloc, ref] = useBloc(MyBloc);\n * ```\n *\n * @example With manual dependencies\n * ```ts\n * const [state, myBloc] = useBloc(MyBloc, {\n * dependencies: (state) => [state.count]\n * });\n * ```\n *\n * @example With isolated instance\n * ```ts\n * const [state, myBloc] = useBloc(MyBloc, {\n * instanceId: 'unique-id'\n * });\n * ```\n */\nexport function useBloc<\n T extends StateContainerConstructor = StateContainerConstructor,\n>(\n BlocClass: StatefulContainer<T>,\n options?: UseBlocOptions<T>,\n): UseBlocReturn<T, ExtractState<T>> {\n type TBloc = InstanceState<T>;\n\n const componentRef = useRef<ComponentRef>({});\n const initialPropsRef = useRef(options?.props);\n const isStateless = isStatelessClass(BlocClass);\n const isIsolated = isIsolatedClass(BlocClass);\n\n const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] =\n useMemo<\n readonly [\n TBloc,\n (callback: () => void) => () => void,\n () => ExtractState<T>,\n string | undefined,\n AdapterState<T> | null,\n TBloc,\n ]\n >(() => {\n // Runtime check for stateless containers - log error but don't crash\n if (isStateless) {\n console.error(\n `[BlaC] useBloc() does not support stateless containers. ` +\n `\"${BlocClass.name}\" is a StatelessCubit or StatelessVertex. ` +\n `Use useBlocActions() instead for stateless containers.`,\n );\n const instance = acquire(BlocClass, 'default') as TBloc;\n // Return minimal working functions that won't subscribe to anything\n return [\n instance,\n () => () => {},\n () => ({}) as ExtractState<T>,\n undefined,\n null,\n instance,\n ];\n }\n\n const instanceKey = generateInstanceKey(\n componentRef.current,\n isIsolated,\n options?.instanceId,\n );\n\n const instance = acquire(BlocClass, instanceKey, {\n props: initialPropsRef.current,\n }) as TBloc;\n\n if (initialPropsRef.current !== undefined) {\n instance.updateProps(initialPropsRef.current);\n }\n\n const { useManualDeps, autoTrackEnabled } =\n determineTrackingMode(options);\n\n let subscribeFn: (callback: () => void) => () => void;\n let getSnapshotFn: () => ExtractState<T>;\n let adapterState: AdapterState<T>;\n\n if (useManualDeps && options?.dependencies) {\n adapterState = manualDepsInit(instance);\n subscribeFn = manualDepsSubscribe(instance, adapterState, {\n dependencies: options.dependencies,\n });\n getSnapshotFn = manualDepsSnapshot(instance, adapterState, {\n dependencies: options.dependencies,\n });\n } else if (!autoTrackEnabled) {\n adapterState = noTrackInit(instance);\n subscribeFn = noTrackSubscribe(instance);\n getSnapshotFn = noTrackSnapshot(instance);\n } else {\n adapterState = autoTrackInit(instance);\n subscribeFn = autoTrackSubscribe(instance, adapterState);\n getSnapshotFn = autoTrackSnapshot(instance, adapterState);\n }\n\n return [\n adapterState.proxiedBloc as TBloc,\n subscribeFn,\n getSnapshotFn,\n instanceKey,\n adapterState,\n instance,\n ];\n }, [BlocClass, isStateless, isIsolated, options?.instanceId]);\n\n const state = useSyncExternalStore(subscribe, getSnapshot);\n\n const [, forceUpdate] = useReducer((x: number) => x + 1, 0);\n\n const externalDepsManager = useRef(new ExternalDepsManager());\n\n useEffect(() => {\n if (isStateless) return;\n if (options?.props !== initialPropsRef.current) {\n rawInstance.updateProps(options?.props);\n }\n }, [options?.props, rawInstance, isStateless]);\n\n useEffect(() => {\n if (isStateless || !adapterState) return;\n disableGetterTracking(adapterState, rawInstance);\n externalDepsManager.current.updateSubscriptions(\n adapterState.getterState,\n rawInstance,\n forceUpdate,\n );\n });\n\n useEffect(() => {\n if (isStateless) return;\n\n if (options?.onMount) {\n options.onMount(bloc as InstanceType<T>);\n }\n\n return () => {\n externalDepsManager.current.cleanup();\n\n if (options?.onUnmount) {\n options.onUnmount(bloc as InstanceType<T>);\n }\n\n release(BlocClass, instanceKey);\n\n if (isIsolated && !rawInstance.isDisposed) {\n rawInstance.dispose();\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return [state, bloc, componentRef] as UseBlocReturn<T, ExtractState<T>>;\n}\n","import { useMemo, useEffect, useRef } from 'react';\nimport {\n StateContainerConstructor,\n isIsolatedClass,\n acquire,\n release,\n} from '@blac/core';\nimport type { ComponentRef } from './types';\nimport { generateInstanceKey } from './utils/instance-keys';\n\n/**\n * Configuration options for useBlocActions hook\n * @template TBloc - The state container type\n * @template TProps - Props type passed to the container\n */\nexport interface UseBlocActionsOptions<TBloc, TProps = any> {\n /** Props passed to bloc constructor or updateProps */\n props?: TProps;\n /** Custom instance identifier for shared or isolated instances */\n instanceId?: string | number;\n /** Callback invoked when bloc instance mounts */\n onMount?: (bloc: TBloc) => void;\n /** Callback invoked when bloc instance unmounts */\n onUnmount?: (bloc: TBloc) => void;\n}\n\n/**\n * React hook that connects to a state container instance without triggering re-renders.\n * Use this when you only need to call actions on the bloc without subscribing to state changes.\n *\n * **This is the recommended hook for stateless containers** (StatelessCubit, StatelessVertex).\n * It also works with regular Cubit/Vertex when you don't need state subscriptions.\n *\n * @template T - The state container constructor type (inferred from BlocClass)\n * @param BlocClass - The state container class to connect to (supports both stateful and stateless)\n * @param options - Configuration options for instance management and lifecycle\n * @returns The state container instance for calling actions\n *\n * @example Basic usage with stateless container\n * ```ts\n * class AnalyticsService extends StatelessCubit {\n * trackEvent(name: string) { ... }\n * }\n *\n * const analytics = useBlocActions(AnalyticsService);\n * analytics.trackEvent('page_view');\n * ```\n *\n * @example With stateful container (no re-renders)\n * ```ts\n * const myBloc = useBlocActions(MyBloc);\n * myBloc.someMethod(); // Won't cause re-renders\n * ```\n *\n * @example With isolated instance\n * ```ts\n * const myBloc = useBlocActions(MyBloc, {\n * instanceId: 'unique-id'\n * });\n * ```\n */\nexport function useBlocActions<\n T extends StateContainerConstructor = StateContainerConstructor,\n>(\n BlocClass: T,\n options?: UseBlocActionsOptions<InstanceType<T>>,\n): InstanceType<T> {\n const componentRef = useRef<ComponentRef>({});\n const initialPropsRef = useRef(options?.props);\n\n const [bloc, instanceKey] = useMemo(() => {\n const isIsolated = isIsolatedClass(BlocClass);\n\n const instanceKey = generateInstanceKey(\n componentRef.current,\n isIsolated,\n options?.instanceId,\n );\n\n const instance = acquire(BlocClass, instanceKey, {\n props: initialPropsRef.current,\n });\n\n return [instance, instanceKey] as const;\n }, [BlocClass]);\n\n useEffect(() => {\n if (options?.onMount) {\n options.onMount(bloc);\n }\n\n return () => {\n if (options?.onUnmount) {\n options.onUnmount(bloc);\n }\n\n release(BlocClass, instanceKey);\n\n if (isIsolatedClass(BlocClass) && !bloc.isDisposed) {\n bloc.dispose();\n }\n };\n }, []);\n\n return bloc as InstanceType<T>;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,oBACd,cACA,YACA,YACoB;AAEpB,KAAI,eAAe,OACjB,QAAO,OAAO,eAAe,WAAW,OAAO,WAAW,GAAG;AAI/D,KAAI,YAAY;AACd,MAAI,CAAC,aAAa,iBAChB,cAAa,wDAAwC;AAEvD,SAAO,aAAa;;;;;;AC1BxB,MAAM,gBAAiC,EACrC,WAAW,MACZ;AAED,IAAI,eAAgC,EAAE,GAAG,eAAe;;;;;;;;;;;;;;;;AAiBxD,SAAgB,mBAAmB,QAAwC;AACzE,gBAAe;EAAE,GAAG;EAAc,GAAG;EAAQ;;;;;;AAO/C,SAAgB,qBAAsC;AACpD,QAAO;;;;;;AAOT,SAAgB,uBAA6B;AAC3C,gBAAe,EAAE,GAAG,eAAe;;;;;ACTrC,SAAS,sBACP,SACc;CACd,MAAMA,iBAAe,oBAAoB;CACzC,MAAM,mBACJ,SAAS,cAAc,SACnB,QAAQ,YACRA,eAAa;AAEnB,QAAO;EACL,eAAe,SAAS,iBAAiB;EACzC;EACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CH,SAAgB,QAGd,WACA,SACmC;CAGnC,MAAM,iCAAoC,EAAE,CAAC;CAC7C,MAAM,oCAAyB,SAAS,MAAM;CAC9C,MAAM,+CAA+B,UAAU;CAC/C,MAAM,6CAA6B,UAAU;CAE7C,MAAM,CAAC,MAAM,WAAW,aAAa,aAAa,cAAc,wCAUtD;AAEN,MAAI,aAAa;AACf,WAAQ,MACN,4DACM,UAAU,KAAK,kGAEtB;GACD,MAAMC,qCAAmB,WAAW,UAAU;AAE9C,UAAO;IACLA;gBACY;WACL,EAAE;IACT;IACA;IACAA;IACD;;EAGH,MAAMC,gBAAc,oBAClB,aAAa,SACb,YACA,SAAS,WACV;EAED,MAAM,mCAAmB,WAAWA,eAAa,EAC/C,OAAO,gBAAgB,SACxB,CAAC;AAEF,MAAI,gBAAgB,YAAY,OAC9B,UAAS,YAAY,gBAAgB,QAAQ;EAG/C,MAAM,EAAE,eAAe,qBACrB,sBAAsB,QAAQ;EAEhC,IAAI;EACJ,IAAI;EACJ,IAAIC;AAEJ,MAAI,iBAAiB,SAAS,cAAc;AAC1C,mDAA8B,SAAS;AACvC,qDAAkC,UAAUA,gBAAc,EACxD,cAAc,QAAQ,cACvB,CAAC;AACF,sDAAmC,UAAUA,gBAAc,EACzD,cAAc,QAAQ,cACvB,CAAC;aACO,CAAC,kBAAkB;AAC5B,gDAA2B,SAAS;AACpC,kDAA+B,SAAS;AACxC,mDAAgC,SAAS;SACpC;AACL,kDAA6B,SAAS;AACtC,oDAAiC,UAAUA,eAAa;AACxD,qDAAkC,UAAUA,eAAa;;AAG3D,SAAO;GACLA,eAAa;GACb;GACA;GACAD;GACAC;GACA;GACD;IACA;EAAC;EAAW;EAAa;EAAY,SAAS;EAAW,CAAC;CAE/D,MAAM,wCAA6B,WAAW,YAAY;CAE1D,MAAM,GAAG,sCAA2B,MAAc,IAAI,GAAG,EAAE;CAE3D,MAAM,wCAA6B,IAAIC,gCAAqB,CAAC;AAE7D,4BAAgB;AACd,MAAI,YAAa;AACjB,MAAI,SAAS,UAAU,gBAAgB,QACrC,aAAY,YAAY,SAAS,MAAM;IAExC;EAAC,SAAS;EAAO;EAAa;EAAY,CAAC;AAE9C,4BAAgB;AACd,MAAI,eAAe,CAAC,aAAc;AAClC,wCAAsB,cAAc,YAAY;AAChD,sBAAoB,QAAQ,oBAC1B,aAAa,aACb,aACA,YACD;GACD;AAEF,4BAAgB;AACd,MAAI,YAAa;AAEjB,MAAI,SAAS,QACX,SAAQ,QAAQ,KAAwB;AAG1C,eAAa;AACX,uBAAoB,QAAQ,SAAS;AAErC,OAAI,SAAS,UACX,SAAQ,UAAU,KAAwB;AAG5C,2BAAQ,WAAW,YAAY;AAE/B,OAAI,cAAc,CAAC,YAAY,WAC7B,aAAY,SAAS;;IAIxB,EAAE,CAAC;AAEN,QAAO;EAAC;EAAO;EAAM;EAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC5KpC,SAAgB,eAGd,WACA,SACiB;CACjB,MAAM,iCAAoC,EAAE,CAAC;CAC7C,MAAM,oCAAyB,SAAS,MAAM;CAE9C,MAAM,CAAC,MAAM,wCAA6B;EACxC,MAAM,6CAA6B,UAAU;EAE7C,MAAMC,gBAAc,oBAClB,aAAa,SACb,YACA,SAAS,WACV;AAMD,SAAO,yBAJkB,WAAWA,eAAa,EAC/C,OAAO,gBAAgB,SACxB,CAAC,EAEgBA,cAAY;IAC7B,CAAC,UAAU,CAAC;AAEf,4BAAgB;AACd,MAAI,SAAS,QACX,SAAQ,QAAQ,KAAK;AAGvB,eAAa;AACX,OAAI,SAAS,UACX,SAAQ,UAAU,KAAK;AAGzB,2BAAQ,WAAW,YAAY;AAE/B,uCAAoB,UAAU,IAAI,CAAC,KAAK,WACtC,MAAK,SAAS;;IAGjB,EAAE,CAAC;AAEN,QAAO"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["ExternalDepsManager"],"sources":["../src/utils/instance-keys.ts","../src/config.ts","../src/useBloc.ts"],"sourcesContent":["/**\n * Instance key generation utilities for React integration\n */\n\nimport { generateIsolatedKey } from '@blac/adapter';\nimport type { ComponentRef } from '../types';\n\n/**\n * Generate an instance key for a bloc\n *\n * Logic:\n * - If user provides instanceId, use it (convert number to string)\n * - If isolated, generate or reuse a unique key for this component\n * - Otherwise, return undefined (use default key)\n *\n * @param componentRef - React component reference (persists across remounts)\n * @param isIsolated - Whether the bloc is isolated\n * @param providedId - User-provided instance ID (from options)\n * @returns Instance key string or undefined for default\n */\nexport function generateInstanceKey(\n componentRef: ComponentRef,\n isIsolated: boolean,\n providedId?: string | number,\n): string | undefined {\n // User explicitly provided an ID - use it\n if (providedId !== undefined) {\n return typeof providedId === 'number' ? String(providedId) : providedId;\n }\n\n // Isolated bloc - generate unique key per component\n if (isIsolated) {\n if (!componentRef.__blocInstanceId) {\n componentRef.__blocInstanceId = generateIsolatedKey();\n }\n return componentRef.__blocInstanceId;\n }\n\n // Shared bloc - use default key (undefined)\n return undefined;\n}\n","/**\n * Global configuration for @blac/react\n */\n\nexport interface BlacReactConfig {\n /** Enable automatic property tracking via Proxy (default: true) */\n autoTrack: boolean;\n}\n\nconst defaultConfig: BlacReactConfig = {\n autoTrack: true,\n};\n\nlet globalConfig: BlacReactConfig = { ...defaultConfig };\n\n/**\n * Configure global defaults for @blac/react hooks.\n *\n * @example\n * ```ts\n * import { configureBlacReact } from '@blac/react';\n *\n * // Disable auto-tracking globally\n * configureBlacReact({\n * autoTrack: false\n * });\n * ```\n *\n * @param config - Partial configuration to merge with defaults\n */\nexport function configureBlacReact(config: Partial<BlacReactConfig>): void {\n globalConfig = { ...globalConfig, ...config };\n}\n\n/**\n * Get the current global configuration.\n * @internal\n */\nexport function getBlacReactConfig(): BlacReactConfig {\n return globalConfig;\n}\n\n/**\n * Reset configuration to defaults (useful for testing).\n * @internal\n */\nexport function resetBlacReactConfig(): void {\n globalConfig = { ...defaultConfig };\n}\n","import {\n useMemo,\n useSyncExternalStore,\n useEffect,\n useRef,\n useReducer,\n} from 'react';\nimport {\n type ExtractState,\n type AdapterState,\n ExternalDepsManager,\n autoTrackSubscribe,\n manualDepsSubscribe,\n noTrackSubscribe,\n autoTrackSnapshot,\n manualDepsSnapshot,\n noTrackSnapshot,\n autoTrackInit,\n manualDepsInit,\n noTrackInit,\n disableGetterTracking,\n isIsolatedClass,\n type StateContainerConstructor,\n type InstanceState,\n acquire,\n release,\n} from '@blac/adapter';\nimport type { UseBlocOptions, UseBlocReturn, ComponentRef } from './types';\nimport { generateInstanceKey } from './utils/instance-keys';\nimport { getBlacReactConfig } from './config';\n\ninterface TrackingMode {\n useManualDeps: boolean;\n autoTrackEnabled: boolean;\n}\n\nfunction determineTrackingMode<TBloc extends StateContainerConstructor>(\n options?: UseBlocOptions<TBloc>,\n): TrackingMode {\n const globalConfig = getBlacReactConfig();\n const autoTrackEnabled =\n options?.autoTrack !== undefined\n ? options.autoTrack\n : globalConfig.autoTrack;\n\n return {\n useManualDeps: options?.dependencies !== undefined,\n autoTrackEnabled,\n };\n}\n\n/**\n * React hook that connects a component to a state container with automatic re-render on state changes.\n *\n * Supports three tracking modes:\n * - **Auto-tracking** (default): Automatically detects accessed state properties via Proxy\n * - **Manual dependencies**: Explicit dependency array like useEffect\n * - **No tracking**: Returns full state without optimization\n *\n * @template T - The state container constructor type (inferred from BlocClass)\n * @param BlocClass - The state container class to connect to\n * @param options - Configuration options for tracking mode and instance management\n * @returns Tuple with [state, bloc instance, ref]\n *\n * @example Basic usage\n * ```ts\n * const [state, myBloc, ref] = useBloc(MyBloc);\n * ```\n *\n * @example With manual dependencies\n * ```ts\n * const [state, myBloc] = useBloc(MyBloc, {\n * dependencies: (state) => [state.count]\n * });\n * ```\n *\n * @example With isolated instance\n * ```ts\n * const [state, myBloc] = useBloc(MyBloc, {\n * instanceId: 'unique-id'\n * });\n * ```\n */\nexport function useBloc<\n T extends StateContainerConstructor = StateContainerConstructor,\n>(\n BlocClass: T,\n options?: UseBlocOptions<T>,\n): UseBlocReturn<T, ExtractState<T>> {\n type TBloc = InstanceState<T>;\n\n const componentRef = useRef<ComponentRef>({});\n const isIsolated = isIsolatedClass(BlocClass);\n\n const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] =\n useMemo<\n readonly [\n TBloc,\n (callback: () => void) => () => void,\n () => ExtractState<T>,\n string | undefined,\n AdapterState<T>,\n TBloc,\n ]\n >(() => {\n const instanceKey = generateInstanceKey(\n componentRef.current,\n isIsolated,\n options?.instanceId,\n );\n\n const instance = acquire(BlocClass, instanceKey) as TBloc;\n\n const { useManualDeps, autoTrackEnabled } =\n determineTrackingMode(options);\n\n let subscribeFn: (callback: () => void) => () => void;\n let getSnapshotFn: () => ExtractState<T>;\n let adapterState: AdapterState<T>;\n\n if (useManualDeps && options?.dependencies) {\n adapterState = manualDepsInit(instance);\n subscribeFn = manualDepsSubscribe(instance, adapterState, {\n dependencies: options.dependencies,\n });\n getSnapshotFn = manualDepsSnapshot(instance, adapterState, {\n dependencies: options.dependencies,\n });\n } else if (!autoTrackEnabled) {\n adapterState = noTrackInit(instance);\n subscribeFn = noTrackSubscribe(instance);\n getSnapshotFn = noTrackSnapshot(instance);\n } else {\n adapterState = autoTrackInit(instance);\n subscribeFn = autoTrackSubscribe(instance, adapterState);\n getSnapshotFn = autoTrackSnapshot(instance, adapterState);\n }\n\n return [\n adapterState.proxiedBloc as TBloc,\n subscribeFn,\n getSnapshotFn,\n instanceKey,\n adapterState,\n instance,\n ];\n }, [BlocClass, isIsolated, options?.instanceId]);\n\n const state = useSyncExternalStore(subscribe, getSnapshot);\n\n const [, forceUpdate] = useReducer((x: number) => x + 1, 0);\n\n const externalDepsManager = useRef(new ExternalDepsManager());\n\n useEffect(() => {\n disableGetterTracking(adapterState, rawInstance);\n externalDepsManager.current.updateSubscriptions(\n adapterState.getterState,\n rawInstance,\n forceUpdate,\n );\n });\n\n useEffect(() => {\n if (options?.onMount) {\n options.onMount(bloc as InstanceType<T>);\n }\n\n return () => {\n externalDepsManager.current.cleanup();\n\n if (options?.onUnmount) {\n options.onUnmount(bloc as InstanceType<T>);\n }\n\n release(BlocClass, instanceKey);\n\n if (isIsolated && !rawInstance.isDisposed) {\n rawInstance.dispose();\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return [state, bloc, componentRef] as UseBlocReturn<T, ExtractState<T>>;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,oBACd,cACA,YACA,YACoB;AAEpB,KAAI,eAAe,OACjB,QAAO,OAAO,eAAe,WAAW,OAAO,WAAW,GAAG;AAI/D,KAAI,YAAY;AACd,MAAI,CAAC,aAAa,iBAChB,cAAa,2DAAwC;AAEvD,SAAO,aAAa;;;;;;AC1BxB,MAAM,gBAAiC,EACrC,WAAW,MACZ;AAED,IAAI,eAAgC,EAAE,GAAG,eAAe;;;;;;;;;;;;;;;;AAiBxD,SAAgB,mBAAmB,QAAwC;AACzE,gBAAe;EAAE,GAAG;EAAc,GAAG;EAAQ;;;;;;AAO/C,SAAgB,qBAAsC;AACpD,QAAO;;;;;ACHT,SAAS,sBACP,SACc;CACd,MAAM,eAAe,oBAAoB;CACzC,MAAM,mBACJ,SAAS,cAAc,SACnB,QAAQ,YACR,aAAa;AAEnB,QAAO;EACL,eAAe,SAAS,iBAAiB;EACzC;EACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCH,SAAgB,QAGd,WACA,SACmC;CAGnC,MAAM,iCAAoC,EAAE,CAAC;CAC7C,MAAM,gDAA6B,UAAU;CAE7C,MAAM,CAAC,MAAM,WAAW,aAAa,aAAa,cAAc,wCAUtD;EACN,MAAM,cAAc,oBAClB,aAAa,SACb,YACA,SAAS,WACV;EAED,MAAM,sCAAmB,WAAW,YAAY;EAEhD,MAAM,EAAE,eAAe,qBACrB,sBAAsB,QAAQ;EAEhC,IAAI;EACJ,IAAI;EACJ,IAAI;AAEJ,MAAI,iBAAiB,SAAS,cAAc;AAC1C,oDAA8B,SAAS;AACvC,wDAAkC,UAAU,cAAc,EACxD,cAAc,QAAQ,cACvB,CAAC;AACF,yDAAmC,UAAU,cAAc,EACzD,cAAc,QAAQ,cACvB,CAAC;aACO,CAAC,kBAAkB;AAC5B,iDAA2B,SAAS;AACpC,qDAA+B,SAAS;AACxC,sDAAgC,SAAS;SACpC;AACL,mDAA6B,SAAS;AACtC,uDAAiC,UAAU,aAAa;AACxD,wDAAkC,UAAU,aAAa;;AAG3D,SAAO;GACL,aAAa;GACb;GACA;GACA;GACA;GACA;GACD;IACA;EAAC;EAAW;EAAY,SAAS;EAAW,CAAC;CAElD,MAAM,wCAA6B,WAAW,YAAY;CAE1D,MAAM,GAAG,sCAA2B,MAAc,IAAI,GAAG,EAAE;CAE3D,MAAM,wCAA6B,IAAIA,mCAAqB,CAAC;AAE7D,4BAAgB;AACd,2CAAsB,cAAc,YAAY;AAChD,sBAAoB,QAAQ,oBAC1B,aAAa,aACb,aACA,YACD;GACD;AAEF,4BAAgB;AACd,MAAI,SAAS,QACX,SAAQ,QAAQ,KAAwB;AAG1C,eAAa;AACX,uBAAoB,QAAQ,SAAS;AAErC,OAAI,SAAS,UACX,SAAQ,UAAU,KAAwB;AAG5C,8BAAQ,WAAW,YAAY;AAE/B,OAAI,cAAc,CAAC,YAAY,WAC7B,aAAY,SAAS;;IAIxB,EAAE,CAAC;AAEN,QAAO;EAAC;EAAO;EAAM;EAAa"}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import { ExtractState } from '@blac/
|
|
1
|
+
import type { ExtractState } from '@blac/core';
|
|
2
|
+
import { ExtractState as ExtractState_2 } from '@blac/adapter';
|
|
3
3
|
import { InstanceReadonlyState } from '@blac/core';
|
|
4
|
-
import { IsStatelessContainer } from '@blac/core';
|
|
5
4
|
import type { RefObject } from 'react';
|
|
6
|
-
import { StateContainerConstructor } from '@blac/
|
|
5
|
+
import { StateContainerConstructor } from '@blac/adapter';
|
|
6
|
+
import type { StateContainerConstructor as StateContainerConstructor_2 } from '@blac/core';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Global configuration for @blac/react
|
|
@@ -32,14 +32,6 @@ export declare interface BlacReactConfig {
|
|
|
32
32
|
*/
|
|
33
33
|
export declare function configureBlacReact(config: Partial<BlacReactConfig>): void;
|
|
34
34
|
|
|
35
|
-
/* Excluded from this release type: resetBlacReactConfig */
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Type that produces a TypeScript error when a stateless container is used.
|
|
39
|
-
* Evaluates to `never` for stateless containers, causing a type mismatch.
|
|
40
|
-
*/
|
|
41
|
-
declare type StatefulContainer<T extends StateContainerConstructor> = IsStatelessContainer<T> extends true ? never : T;
|
|
42
|
-
|
|
43
35
|
/**
|
|
44
36
|
* React hook that connects a component to a state container with automatic re-render on state changes.
|
|
45
37
|
*
|
|
@@ -48,11 +40,8 @@ declare type StatefulContainer<T extends StateContainerConstructor> = IsStateles
|
|
|
48
40
|
* - **Manual dependencies**: Explicit dependency array like useEffect
|
|
49
41
|
* - **No tracking**: Returns full state without optimization
|
|
50
42
|
*
|
|
51
|
-
* **Note:** This hook does NOT support stateless containers (StatelessCubit, StatelessVertex).
|
|
52
|
-
* Use `useBlocActions()` instead for stateless containers.
|
|
53
|
-
*
|
|
54
43
|
* @template T - The state container constructor type (inferred from BlocClass)
|
|
55
|
-
* @param BlocClass - The state container class to connect to
|
|
44
|
+
* @param BlocClass - The state container class to connect to
|
|
56
45
|
* @param options - Configuration options for tracking mode and instance management
|
|
57
46
|
* @returns Tuple with [state, bloc instance, ref]
|
|
58
47
|
*
|
|
@@ -75,77 +64,19 @@ declare type StatefulContainer<T extends StateContainerConstructor> = IsStateles
|
|
|
75
64
|
* });
|
|
76
65
|
* ```
|
|
77
66
|
*/
|
|
78
|
-
export declare function useBloc<T extends StateContainerConstructor = StateContainerConstructor>(BlocClass:
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* React hook that connects to a state container instance without triggering re-renders.
|
|
82
|
-
* Use this when you only need to call actions on the bloc without subscribing to state changes.
|
|
83
|
-
*
|
|
84
|
-
* **This is the recommended hook for stateless containers** (StatelessCubit, StatelessVertex).
|
|
85
|
-
* It also works with regular Cubit/Vertex when you don't need state subscriptions.
|
|
86
|
-
*
|
|
87
|
-
* @template T - The state container constructor type (inferred from BlocClass)
|
|
88
|
-
* @param BlocClass - The state container class to connect to (supports both stateful and stateless)
|
|
89
|
-
* @param options - Configuration options for instance management and lifecycle
|
|
90
|
-
* @returns The state container instance for calling actions
|
|
91
|
-
*
|
|
92
|
-
* @example Basic usage with stateless container
|
|
93
|
-
* ```ts
|
|
94
|
-
* class AnalyticsService extends StatelessCubit {
|
|
95
|
-
* trackEvent(name: string) { ... }
|
|
96
|
-
* }
|
|
97
|
-
*
|
|
98
|
-
* const analytics = useBlocActions(AnalyticsService);
|
|
99
|
-
* analytics.trackEvent('page_view');
|
|
100
|
-
* ```
|
|
101
|
-
*
|
|
102
|
-
* @example With stateful container (no re-renders)
|
|
103
|
-
* ```ts
|
|
104
|
-
* const myBloc = useBlocActions(MyBloc);
|
|
105
|
-
* myBloc.someMethod(); // Won't cause re-renders
|
|
106
|
-
* ```
|
|
107
|
-
*
|
|
108
|
-
* @example With isolated instance
|
|
109
|
-
* ```ts
|
|
110
|
-
* const myBloc = useBlocActions(MyBloc, {
|
|
111
|
-
* instanceId: 'unique-id'
|
|
112
|
-
* });
|
|
113
|
-
* ```
|
|
114
|
-
*/
|
|
115
|
-
export declare function useBlocActions<T extends StateContainerConstructor = StateContainerConstructor>(BlocClass: T, options?: UseBlocActionsOptions<InstanceType<T>>): InstanceType<T>;
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Configuration options for useBlocActions hook
|
|
119
|
-
* @template TBloc - The state container type
|
|
120
|
-
* @template TProps - Props type passed to the container
|
|
121
|
-
*/
|
|
122
|
-
export declare interface UseBlocActionsOptions<TBloc, TProps = any> {
|
|
123
|
-
/** Props passed to bloc constructor or updateProps */
|
|
124
|
-
props?: TProps;
|
|
125
|
-
/** Custom instance identifier for shared or isolated instances */
|
|
126
|
-
instanceId?: string | number;
|
|
127
|
-
/** Callback invoked when bloc instance mounts */
|
|
128
|
-
onMount?: (bloc: TBloc) => void;
|
|
129
|
-
/** Callback invoked when bloc instance unmounts */
|
|
130
|
-
onUnmount?: (bloc: TBloc) => void;
|
|
131
|
-
}
|
|
67
|
+
export declare function useBloc<T extends StateContainerConstructor = StateContainerConstructor>(BlocClass: T, options?: UseBlocOptions<T>): UseBlocReturn<T, ExtractState_2<T>>;
|
|
132
68
|
|
|
133
69
|
/**
|
|
134
70
|
* Configuration options for useBloc hook
|
|
135
71
|
* @template TBloc - The state container type
|
|
136
|
-
* @template TProps - Props type passed to the container
|
|
137
72
|
*/
|
|
138
|
-
export declare interface UseBlocOptions<TBloc extends
|
|
139
|
-
/** Props passed to bloc constructor or updateProps */
|
|
140
|
-
props?: TProps;
|
|
73
|
+
export declare interface UseBlocOptions<TBloc extends StateContainerConstructor_2> {
|
|
141
74
|
/** Custom instance identifier for shared or isolated instances */
|
|
142
75
|
instanceId?: string | number;
|
|
143
76
|
/** Manual dependency array like useEffect (disables autoTrack) */
|
|
144
77
|
dependencies?: (state: ExtractState<TBloc>, bloc: InstanceReadonlyState<TBloc>) => unknown[];
|
|
145
78
|
/** Enable automatic property tracking via Proxy (default: true) */
|
|
146
79
|
autoTrack?: boolean;
|
|
147
|
-
/** Disable caching for getter tracking */
|
|
148
|
-
disableGetterCache?: boolean;
|
|
149
80
|
/** Callback invoked when bloc instance mounts */
|
|
150
81
|
onMount?: (bloc: InstanceType<TBloc>) => void;
|
|
151
82
|
/** Callback invoked when bloc instance unmounts */
|
|
@@ -160,6 +91,6 @@ export declare interface UseBlocOptions<TBloc extends StateContainerConstructor,
|
|
|
160
91
|
*
|
|
161
92
|
* @template TBloc - The state container type
|
|
162
93
|
*/
|
|
163
|
-
export declare type UseBlocReturn<TBloc extends
|
|
94
|
+
export declare type UseBlocReturn<TBloc extends StateContainerConstructor_2, S = ExtractState<TBloc>> = [S, InstanceReadonlyState<TBloc>, RefObject<ComponentRef>];
|
|
164
95
|
|
|
165
96
|
export { }
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import { ExtractState } from '@blac/
|
|
1
|
+
import type { ExtractState } from '@blac/core';
|
|
2
|
+
import { ExtractState as ExtractState_2 } from '@blac/adapter';
|
|
3
3
|
import { InstanceReadonlyState } from '@blac/core';
|
|
4
|
-
import { IsStatelessContainer } from '@blac/core';
|
|
5
4
|
import type { RefObject } from 'react';
|
|
6
|
-
import { StateContainerConstructor } from '@blac/
|
|
5
|
+
import { StateContainerConstructor } from '@blac/adapter';
|
|
6
|
+
import type { StateContainerConstructor as StateContainerConstructor_2 } from '@blac/core';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Global configuration for @blac/react
|
|
@@ -32,14 +32,6 @@ export declare interface BlacReactConfig {
|
|
|
32
32
|
*/
|
|
33
33
|
export declare function configureBlacReact(config: Partial<BlacReactConfig>): void;
|
|
34
34
|
|
|
35
|
-
/* Excluded from this release type: resetBlacReactConfig */
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Type that produces a TypeScript error when a stateless container is used.
|
|
39
|
-
* Evaluates to `never` for stateless containers, causing a type mismatch.
|
|
40
|
-
*/
|
|
41
|
-
declare type StatefulContainer<T extends StateContainerConstructor> = IsStatelessContainer<T> extends true ? never : T;
|
|
42
|
-
|
|
43
35
|
/**
|
|
44
36
|
* React hook that connects a component to a state container with automatic re-render on state changes.
|
|
45
37
|
*
|
|
@@ -48,11 +40,8 @@ declare type StatefulContainer<T extends StateContainerConstructor> = IsStateles
|
|
|
48
40
|
* - **Manual dependencies**: Explicit dependency array like useEffect
|
|
49
41
|
* - **No tracking**: Returns full state without optimization
|
|
50
42
|
*
|
|
51
|
-
* **Note:** This hook does NOT support stateless containers (StatelessCubit, StatelessVertex).
|
|
52
|
-
* Use `useBlocActions()` instead for stateless containers.
|
|
53
|
-
*
|
|
54
43
|
* @template T - The state container constructor type (inferred from BlocClass)
|
|
55
|
-
* @param BlocClass - The state container class to connect to
|
|
44
|
+
* @param BlocClass - The state container class to connect to
|
|
56
45
|
* @param options - Configuration options for tracking mode and instance management
|
|
57
46
|
* @returns Tuple with [state, bloc instance, ref]
|
|
58
47
|
*
|
|
@@ -75,77 +64,19 @@ declare type StatefulContainer<T extends StateContainerConstructor> = IsStateles
|
|
|
75
64
|
* });
|
|
76
65
|
* ```
|
|
77
66
|
*/
|
|
78
|
-
export declare function useBloc<T extends StateContainerConstructor = StateContainerConstructor>(BlocClass:
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* React hook that connects to a state container instance without triggering re-renders.
|
|
82
|
-
* Use this when you only need to call actions on the bloc without subscribing to state changes.
|
|
83
|
-
*
|
|
84
|
-
* **This is the recommended hook for stateless containers** (StatelessCubit, StatelessVertex).
|
|
85
|
-
* It also works with regular Cubit/Vertex when you don't need state subscriptions.
|
|
86
|
-
*
|
|
87
|
-
* @template T - The state container constructor type (inferred from BlocClass)
|
|
88
|
-
* @param BlocClass - The state container class to connect to (supports both stateful and stateless)
|
|
89
|
-
* @param options - Configuration options for instance management and lifecycle
|
|
90
|
-
* @returns The state container instance for calling actions
|
|
91
|
-
*
|
|
92
|
-
* @example Basic usage with stateless container
|
|
93
|
-
* ```ts
|
|
94
|
-
* class AnalyticsService extends StatelessCubit {
|
|
95
|
-
* trackEvent(name: string) { ... }
|
|
96
|
-
* }
|
|
97
|
-
*
|
|
98
|
-
* const analytics = useBlocActions(AnalyticsService);
|
|
99
|
-
* analytics.trackEvent('page_view');
|
|
100
|
-
* ```
|
|
101
|
-
*
|
|
102
|
-
* @example With stateful container (no re-renders)
|
|
103
|
-
* ```ts
|
|
104
|
-
* const myBloc = useBlocActions(MyBloc);
|
|
105
|
-
* myBloc.someMethod(); // Won't cause re-renders
|
|
106
|
-
* ```
|
|
107
|
-
*
|
|
108
|
-
* @example With isolated instance
|
|
109
|
-
* ```ts
|
|
110
|
-
* const myBloc = useBlocActions(MyBloc, {
|
|
111
|
-
* instanceId: 'unique-id'
|
|
112
|
-
* });
|
|
113
|
-
* ```
|
|
114
|
-
*/
|
|
115
|
-
export declare function useBlocActions<T extends StateContainerConstructor = StateContainerConstructor>(BlocClass: T, options?: UseBlocActionsOptions<InstanceType<T>>): InstanceType<T>;
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Configuration options for useBlocActions hook
|
|
119
|
-
* @template TBloc - The state container type
|
|
120
|
-
* @template TProps - Props type passed to the container
|
|
121
|
-
*/
|
|
122
|
-
export declare interface UseBlocActionsOptions<TBloc, TProps = any> {
|
|
123
|
-
/** Props passed to bloc constructor or updateProps */
|
|
124
|
-
props?: TProps;
|
|
125
|
-
/** Custom instance identifier for shared or isolated instances */
|
|
126
|
-
instanceId?: string | number;
|
|
127
|
-
/** Callback invoked when bloc instance mounts */
|
|
128
|
-
onMount?: (bloc: TBloc) => void;
|
|
129
|
-
/** Callback invoked when bloc instance unmounts */
|
|
130
|
-
onUnmount?: (bloc: TBloc) => void;
|
|
131
|
-
}
|
|
67
|
+
export declare function useBloc<T extends StateContainerConstructor = StateContainerConstructor>(BlocClass: T, options?: UseBlocOptions<T>): UseBlocReturn<T, ExtractState_2<T>>;
|
|
132
68
|
|
|
133
69
|
/**
|
|
134
70
|
* Configuration options for useBloc hook
|
|
135
71
|
* @template TBloc - The state container type
|
|
136
|
-
* @template TProps - Props type passed to the container
|
|
137
72
|
*/
|
|
138
|
-
export declare interface UseBlocOptions<TBloc extends
|
|
139
|
-
/** Props passed to bloc constructor or updateProps */
|
|
140
|
-
props?: TProps;
|
|
73
|
+
export declare interface UseBlocOptions<TBloc extends StateContainerConstructor_2> {
|
|
141
74
|
/** Custom instance identifier for shared or isolated instances */
|
|
142
75
|
instanceId?: string | number;
|
|
143
76
|
/** Manual dependency array like useEffect (disables autoTrack) */
|
|
144
77
|
dependencies?: (state: ExtractState<TBloc>, bloc: InstanceReadonlyState<TBloc>) => unknown[];
|
|
145
78
|
/** Enable automatic property tracking via Proxy (default: true) */
|
|
146
79
|
autoTrack?: boolean;
|
|
147
|
-
/** Disable caching for getter tracking */
|
|
148
|
-
disableGetterCache?: boolean;
|
|
149
80
|
/** Callback invoked when bloc instance mounts */
|
|
150
81
|
onMount?: (bloc: InstanceType<TBloc>) => void;
|
|
151
82
|
/** Callback invoked when bloc instance unmounts */
|
|
@@ -160,6 +91,6 @@ export declare interface UseBlocOptions<TBloc extends StateContainerConstructor,
|
|
|
160
91
|
*
|
|
161
92
|
* @template TBloc - The state container type
|
|
162
93
|
*/
|
|
163
|
-
export declare type UseBlocReturn<TBloc extends
|
|
94
|
+
export declare type UseBlocReturn<TBloc extends StateContainerConstructor_2, S = ExtractState<TBloc>> = [S, InstanceReadonlyState<TBloc>, RefObject<ComponentRef>];
|
|
164
95
|
|
|
165
96
|
export { }
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useEffect, useMemo, useReducer, useRef, useSyncExternalStore } from "react";
|
|
2
|
-
import { ExternalDepsManager, acquire, autoTrackInit, autoTrackSnapshot, autoTrackSubscribe, disableGetterTracking, generateIsolatedKey, isIsolatedClass,
|
|
2
|
+
import { ExternalDepsManager, acquire, autoTrackInit, autoTrackSnapshot, autoTrackSubscribe, disableGetterTracking, generateIsolatedKey, isIsolatedClass, manualDepsInit, manualDepsSnapshot, manualDepsSubscribe, noTrackInit, noTrackSnapshot, noTrackSubscribe, release } from "@blac/adapter";
|
|
3
3
|
|
|
4
4
|
//#region src/utils/instance-keys.ts
|
|
5
5
|
/**
|
|
@@ -58,19 +58,12 @@ function configureBlacReact(config) {
|
|
|
58
58
|
function getBlacReactConfig() {
|
|
59
59
|
return globalConfig;
|
|
60
60
|
}
|
|
61
|
-
/**
|
|
62
|
-
* Reset configuration to defaults (useful for testing).
|
|
63
|
-
* @internal
|
|
64
|
-
*/
|
|
65
|
-
function resetBlacReactConfig() {
|
|
66
|
-
globalConfig = { ...defaultConfig };
|
|
67
|
-
}
|
|
68
61
|
|
|
69
62
|
//#endregion
|
|
70
63
|
//#region src/useBloc.ts
|
|
71
64
|
function determineTrackingMode(options) {
|
|
72
|
-
const globalConfig
|
|
73
|
-
const autoTrackEnabled = options?.autoTrack !== void 0 ? options.autoTrack : globalConfig
|
|
65
|
+
const globalConfig = getBlacReactConfig();
|
|
66
|
+
const autoTrackEnabled = options?.autoTrack !== void 0 ? options.autoTrack : globalConfig.autoTrack;
|
|
74
67
|
return {
|
|
75
68
|
useManualDeps: options?.dependencies !== void 0,
|
|
76
69
|
autoTrackEnabled
|
|
@@ -84,11 +77,8 @@ function determineTrackingMode(options) {
|
|
|
84
77
|
* - **Manual dependencies**: Explicit dependency array like useEffect
|
|
85
78
|
* - **No tracking**: Returns full state without optimization
|
|
86
79
|
*
|
|
87
|
-
* **Note:** This hook does NOT support stateless containers (StatelessCubit, StatelessVertex).
|
|
88
|
-
* Use `useBlocActions()` instead for stateless containers.
|
|
89
|
-
*
|
|
90
80
|
* @template T - The state container constructor type (inferred from BlocClass)
|
|
91
|
-
* @param BlocClass - The state container class to connect to
|
|
81
|
+
* @param BlocClass - The state container class to connect to
|
|
92
82
|
* @param options - Configuration options for tracking mode and instance management
|
|
93
83
|
* @returns Tuple with [state, bloc instance, ref]
|
|
94
84
|
*
|
|
@@ -113,53 +103,37 @@ function determineTrackingMode(options) {
|
|
|
113
103
|
*/
|
|
114
104
|
function useBloc(BlocClass, options) {
|
|
115
105
|
const componentRef = useRef({});
|
|
116
|
-
const initialPropsRef = useRef(options?.props);
|
|
117
|
-
const isStateless = isStatelessClass(BlocClass);
|
|
118
106
|
const isIsolated = isIsolatedClass(BlocClass);
|
|
119
107
|
const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] = useMemo(() => {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const instance$1 = acquire(BlocClass, "default");
|
|
123
|
-
return [
|
|
124
|
-
instance$1,
|
|
125
|
-
() => () => {},
|
|
126
|
-
() => ({}),
|
|
127
|
-
void 0,
|
|
128
|
-
null,
|
|
129
|
-
instance$1
|
|
130
|
-
];
|
|
131
|
-
}
|
|
132
|
-
const instanceKey$1 = generateInstanceKey(componentRef.current, isIsolated, options?.instanceId);
|
|
133
|
-
const instance = acquire(BlocClass, instanceKey$1, { props: initialPropsRef.current });
|
|
134
|
-
if (initialPropsRef.current !== void 0) instance.updateProps(initialPropsRef.current);
|
|
108
|
+
const instanceKey = generateInstanceKey(componentRef.current, isIsolated, options?.instanceId);
|
|
109
|
+
const instance = acquire(BlocClass, instanceKey);
|
|
135
110
|
const { useManualDeps, autoTrackEnabled } = determineTrackingMode(options);
|
|
136
111
|
let subscribeFn;
|
|
137
112
|
let getSnapshotFn;
|
|
138
|
-
let adapterState
|
|
113
|
+
let adapterState;
|
|
139
114
|
if (useManualDeps && options?.dependencies) {
|
|
140
|
-
adapterState
|
|
141
|
-
subscribeFn = manualDepsSubscribe(instance, adapterState
|
|
142
|
-
getSnapshotFn = manualDepsSnapshot(instance, adapterState
|
|
115
|
+
adapterState = manualDepsInit(instance);
|
|
116
|
+
subscribeFn = manualDepsSubscribe(instance, adapterState, { dependencies: options.dependencies });
|
|
117
|
+
getSnapshotFn = manualDepsSnapshot(instance, adapterState, { dependencies: options.dependencies });
|
|
143
118
|
} else if (!autoTrackEnabled) {
|
|
144
|
-
adapterState
|
|
119
|
+
adapterState = noTrackInit(instance);
|
|
145
120
|
subscribeFn = noTrackSubscribe(instance);
|
|
146
121
|
getSnapshotFn = noTrackSnapshot(instance);
|
|
147
122
|
} else {
|
|
148
|
-
adapterState
|
|
149
|
-
subscribeFn = autoTrackSubscribe(instance, adapterState
|
|
150
|
-
getSnapshotFn = autoTrackSnapshot(instance, adapterState
|
|
123
|
+
adapterState = autoTrackInit(instance);
|
|
124
|
+
subscribeFn = autoTrackSubscribe(instance, adapterState);
|
|
125
|
+
getSnapshotFn = autoTrackSnapshot(instance, adapterState);
|
|
151
126
|
}
|
|
152
127
|
return [
|
|
153
|
-
adapterState
|
|
128
|
+
adapterState.proxiedBloc,
|
|
154
129
|
subscribeFn,
|
|
155
130
|
getSnapshotFn,
|
|
156
|
-
instanceKey
|
|
157
|
-
adapterState
|
|
131
|
+
instanceKey,
|
|
132
|
+
adapterState,
|
|
158
133
|
instance
|
|
159
134
|
];
|
|
160
135
|
}, [
|
|
161
136
|
BlocClass,
|
|
162
|
-
isStateless,
|
|
163
137
|
isIsolated,
|
|
164
138
|
options?.instanceId
|
|
165
139
|
]);
|
|
@@ -167,20 +141,10 @@ function useBloc(BlocClass, options) {
|
|
|
167
141
|
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
|
168
142
|
const externalDepsManager = useRef(new ExternalDepsManager());
|
|
169
143
|
useEffect(() => {
|
|
170
|
-
if (isStateless) return;
|
|
171
|
-
if (options?.props !== initialPropsRef.current) rawInstance.updateProps(options?.props);
|
|
172
|
-
}, [
|
|
173
|
-
options?.props,
|
|
174
|
-
rawInstance,
|
|
175
|
-
isStateless
|
|
176
|
-
]);
|
|
177
|
-
useEffect(() => {
|
|
178
|
-
if (isStateless || !adapterState) return;
|
|
179
144
|
disableGetterTracking(adapterState, rawInstance);
|
|
180
145
|
externalDepsManager.current.updateSubscriptions(adapterState.getterState, rawInstance, forceUpdate);
|
|
181
146
|
});
|
|
182
147
|
useEffect(() => {
|
|
183
|
-
if (isStateless) return;
|
|
184
148
|
if (options?.onMount) options.onMount(bloc);
|
|
185
149
|
return () => {
|
|
186
150
|
externalDepsManager.current.cleanup();
|
|
@@ -197,61 +161,5 @@ function useBloc(BlocClass, options) {
|
|
|
197
161
|
}
|
|
198
162
|
|
|
199
163
|
//#endregion
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* React hook that connects to a state container instance without triggering re-renders.
|
|
203
|
-
* Use this when you only need to call actions on the bloc without subscribing to state changes.
|
|
204
|
-
*
|
|
205
|
-
* **This is the recommended hook for stateless containers** (StatelessCubit, StatelessVertex).
|
|
206
|
-
* It also works with regular Cubit/Vertex when you don't need state subscriptions.
|
|
207
|
-
*
|
|
208
|
-
* @template T - The state container constructor type (inferred from BlocClass)
|
|
209
|
-
* @param BlocClass - The state container class to connect to (supports both stateful and stateless)
|
|
210
|
-
* @param options - Configuration options for instance management and lifecycle
|
|
211
|
-
* @returns The state container instance for calling actions
|
|
212
|
-
*
|
|
213
|
-
* @example Basic usage with stateless container
|
|
214
|
-
* ```ts
|
|
215
|
-
* class AnalyticsService extends StatelessCubit {
|
|
216
|
-
* trackEvent(name: string) { ... }
|
|
217
|
-
* }
|
|
218
|
-
*
|
|
219
|
-
* const analytics = useBlocActions(AnalyticsService);
|
|
220
|
-
* analytics.trackEvent('page_view');
|
|
221
|
-
* ```
|
|
222
|
-
*
|
|
223
|
-
* @example With stateful container (no re-renders)
|
|
224
|
-
* ```ts
|
|
225
|
-
* const myBloc = useBlocActions(MyBloc);
|
|
226
|
-
* myBloc.someMethod(); // Won't cause re-renders
|
|
227
|
-
* ```
|
|
228
|
-
*
|
|
229
|
-
* @example With isolated instance
|
|
230
|
-
* ```ts
|
|
231
|
-
* const myBloc = useBlocActions(MyBloc, {
|
|
232
|
-
* instanceId: 'unique-id'
|
|
233
|
-
* });
|
|
234
|
-
* ```
|
|
235
|
-
*/
|
|
236
|
-
function useBlocActions(BlocClass, options) {
|
|
237
|
-
const componentRef = useRef({});
|
|
238
|
-
const initialPropsRef = useRef(options?.props);
|
|
239
|
-
const [bloc, instanceKey] = useMemo(() => {
|
|
240
|
-
const isIsolated = isIsolatedClass(BlocClass);
|
|
241
|
-
const instanceKey$1 = generateInstanceKey(componentRef.current, isIsolated, options?.instanceId);
|
|
242
|
-
return [acquire(BlocClass, instanceKey$1, { props: initialPropsRef.current }), instanceKey$1];
|
|
243
|
-
}, [BlocClass]);
|
|
244
|
-
useEffect(() => {
|
|
245
|
-
if (options?.onMount) options.onMount(bloc);
|
|
246
|
-
return () => {
|
|
247
|
-
if (options?.onUnmount) options.onUnmount(bloc);
|
|
248
|
-
release(BlocClass, instanceKey);
|
|
249
|
-
if (isIsolatedClass(BlocClass) && !bloc.isDisposed) bloc.dispose();
|
|
250
|
-
};
|
|
251
|
-
}, []);
|
|
252
|
-
return bloc;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
//#endregion
|
|
256
|
-
export { configureBlacReact, resetBlacReactConfig, useBloc, useBlocActions };
|
|
164
|
+
export { configureBlacReact, useBloc };
|
|
257
165
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["globalConfig","instance","instanceKey","adapterState","instanceKey"],"sources":["../src/utils/instance-keys.ts","../src/config.ts","../src/useBloc.ts","../src/useBlocActions.ts"],"sourcesContent":["/**\n * Instance key generation utilities for React integration\n */\n\nimport { generateIsolatedKey } from '@blac/core';\nimport type { ComponentRef } from '../types';\n\n/**\n * Generate an instance key for a bloc\n *\n * Logic:\n * - If user provides instanceId, use it (convert number to string)\n * - If isolated, generate or reuse a unique key for this component\n * - Otherwise, return undefined (use default key)\n *\n * @param componentRef - React component reference (persists across remounts)\n * @param isIsolated - Whether the bloc is isolated\n * @param providedId - User-provided instance ID (from options)\n * @returns Instance key string or undefined for default\n */\nexport function generateInstanceKey(\n componentRef: ComponentRef,\n isIsolated: boolean,\n providedId?: string | number,\n): string | undefined {\n // User explicitly provided an ID - use it\n if (providedId !== undefined) {\n return typeof providedId === 'number' ? String(providedId) : providedId;\n }\n\n // Isolated bloc - generate unique key per component\n if (isIsolated) {\n if (!componentRef.__blocInstanceId) {\n componentRef.__blocInstanceId = generateIsolatedKey();\n }\n return componentRef.__blocInstanceId;\n }\n\n // Shared bloc - use default key (undefined)\n return undefined;\n}\n","/**\n * Global configuration for @blac/react\n */\n\nexport interface BlacReactConfig {\n /** Enable automatic property tracking via Proxy (default: true) */\n autoTrack: boolean;\n}\n\nconst defaultConfig: BlacReactConfig = {\n autoTrack: true,\n};\n\nlet globalConfig: BlacReactConfig = { ...defaultConfig };\n\n/**\n * Configure global defaults for @blac/react hooks.\n *\n * @example\n * ```ts\n * import { configureBlacReact } from '@blac/react';\n *\n * // Disable auto-tracking globally\n * configureBlacReact({\n * autoTrack: false\n * });\n * ```\n *\n * @param config - Partial configuration to merge with defaults\n */\nexport function configureBlacReact(config: Partial<BlacReactConfig>): void {\n globalConfig = { ...globalConfig, ...config };\n}\n\n/**\n * Get the current global configuration.\n * @internal\n */\nexport function getBlacReactConfig(): BlacReactConfig {\n return globalConfig;\n}\n\n/**\n * Reset configuration to defaults (useful for testing).\n * @internal\n */\nexport function resetBlacReactConfig(): void {\n globalConfig = { ...defaultConfig };\n}\n","import {\n useMemo,\n useSyncExternalStore,\n useEffect,\n useRef,\n useReducer,\n} from 'react';\nimport {\n type ExtractState,\n type AdapterState,\n type IsStatelessContainer,\n ExternalDepsManager,\n autoTrackSubscribe,\n manualDepsSubscribe,\n noTrackSubscribe,\n autoTrackSnapshot,\n manualDepsSnapshot,\n noTrackSnapshot,\n autoTrackInit,\n manualDepsInit,\n noTrackInit,\n disableGetterTracking,\n isIsolatedClass,\n isStatelessClass,\n StateContainerConstructor,\n InstanceState,\n acquire,\n release,\n} from '@blac/core';\nimport type { UseBlocOptions, UseBlocReturn, ComponentRef } from './types';\nimport { generateInstanceKey } from './utils/instance-keys';\nimport { getBlacReactConfig } from './config';\n\ninterface TrackingMode {\n useManualDeps: boolean;\n autoTrackEnabled: boolean;\n}\n\nfunction determineTrackingMode<TBloc extends StateContainerConstructor>(\n options?: UseBlocOptions<TBloc>,\n): TrackingMode {\n const globalConfig = getBlacReactConfig();\n const autoTrackEnabled =\n options?.autoTrack !== undefined\n ? options.autoTrack\n : globalConfig.autoTrack;\n\n return {\n useManualDeps: options?.dependencies !== undefined,\n autoTrackEnabled,\n };\n}\n\n/**\n * Type that produces a TypeScript error when a stateless container is used.\n * Evaluates to `never` for stateless containers, causing a type mismatch.\n */\ntype StatefulContainer<T extends StateContainerConstructor> =\n IsStatelessContainer<T> extends true ? never : T;\n\n/**\n * React hook that connects a component to a state container with automatic re-render on state changes.\n *\n * Supports three tracking modes:\n * - **Auto-tracking** (default): Automatically detects accessed state properties via Proxy\n * - **Manual dependencies**: Explicit dependency array like useEffect\n * - **No tracking**: Returns full state without optimization\n *\n * **Note:** This hook does NOT support stateless containers (StatelessCubit, StatelessVertex).\n * Use `useBlocActions()` instead for stateless containers.\n *\n * @template T - The state container constructor type (inferred from BlocClass)\n * @param BlocClass - The state container class to connect to (must not be stateless)\n * @param options - Configuration options for tracking mode and instance management\n * @returns Tuple with [state, bloc instance, ref]\n *\n * @example Basic usage\n * ```ts\n * const [state, myBloc, ref] = useBloc(MyBloc);\n * ```\n *\n * @example With manual dependencies\n * ```ts\n * const [state, myBloc] = useBloc(MyBloc, {\n * dependencies: (state) => [state.count]\n * });\n * ```\n *\n * @example With isolated instance\n * ```ts\n * const [state, myBloc] = useBloc(MyBloc, {\n * instanceId: 'unique-id'\n * });\n * ```\n */\nexport function useBloc<\n T extends StateContainerConstructor = StateContainerConstructor,\n>(\n BlocClass: StatefulContainer<T>,\n options?: UseBlocOptions<T>,\n): UseBlocReturn<T, ExtractState<T>> {\n type TBloc = InstanceState<T>;\n\n const componentRef = useRef<ComponentRef>({});\n const initialPropsRef = useRef(options?.props);\n const isStateless = isStatelessClass(BlocClass);\n const isIsolated = isIsolatedClass(BlocClass);\n\n const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] =\n useMemo<\n readonly [\n TBloc,\n (callback: () => void) => () => void,\n () => ExtractState<T>,\n string | undefined,\n AdapterState<T> | null,\n TBloc,\n ]\n >(() => {\n // Runtime check for stateless containers - log error but don't crash\n if (isStateless) {\n console.error(\n `[BlaC] useBloc() does not support stateless containers. ` +\n `\"${BlocClass.name}\" is a StatelessCubit or StatelessVertex. ` +\n `Use useBlocActions() instead for stateless containers.`,\n );\n const instance = acquire(BlocClass, 'default') as TBloc;\n // Return minimal working functions that won't subscribe to anything\n return [\n instance,\n () => () => {},\n () => ({}) as ExtractState<T>,\n undefined,\n null,\n instance,\n ];\n }\n\n const instanceKey = generateInstanceKey(\n componentRef.current,\n isIsolated,\n options?.instanceId,\n );\n\n const instance = acquire(BlocClass, instanceKey, {\n props: initialPropsRef.current,\n }) as TBloc;\n\n if (initialPropsRef.current !== undefined) {\n instance.updateProps(initialPropsRef.current);\n }\n\n const { useManualDeps, autoTrackEnabled } =\n determineTrackingMode(options);\n\n let subscribeFn: (callback: () => void) => () => void;\n let getSnapshotFn: () => ExtractState<T>;\n let adapterState: AdapterState<T>;\n\n if (useManualDeps && options?.dependencies) {\n adapterState = manualDepsInit(instance);\n subscribeFn = manualDepsSubscribe(instance, adapterState, {\n dependencies: options.dependencies,\n });\n getSnapshotFn = manualDepsSnapshot(instance, adapterState, {\n dependencies: options.dependencies,\n });\n } else if (!autoTrackEnabled) {\n adapterState = noTrackInit(instance);\n subscribeFn = noTrackSubscribe(instance);\n getSnapshotFn = noTrackSnapshot(instance);\n } else {\n adapterState = autoTrackInit(instance);\n subscribeFn = autoTrackSubscribe(instance, adapterState);\n getSnapshotFn = autoTrackSnapshot(instance, adapterState);\n }\n\n return [\n adapterState.proxiedBloc as TBloc,\n subscribeFn,\n getSnapshotFn,\n instanceKey,\n adapterState,\n instance,\n ];\n }, [BlocClass, isStateless, isIsolated, options?.instanceId]);\n\n const state = useSyncExternalStore(subscribe, getSnapshot);\n\n const [, forceUpdate] = useReducer((x: number) => x + 1, 0);\n\n const externalDepsManager = useRef(new ExternalDepsManager());\n\n useEffect(() => {\n if (isStateless) return;\n if (options?.props !== initialPropsRef.current) {\n rawInstance.updateProps(options?.props);\n }\n }, [options?.props, rawInstance, isStateless]);\n\n useEffect(() => {\n if (isStateless || !adapterState) return;\n disableGetterTracking(adapterState, rawInstance);\n externalDepsManager.current.updateSubscriptions(\n adapterState.getterState,\n rawInstance,\n forceUpdate,\n );\n });\n\n useEffect(() => {\n if (isStateless) return;\n\n if (options?.onMount) {\n options.onMount(bloc as InstanceType<T>);\n }\n\n return () => {\n externalDepsManager.current.cleanup();\n\n if (options?.onUnmount) {\n options.onUnmount(bloc as InstanceType<T>);\n }\n\n release(BlocClass, instanceKey);\n\n if (isIsolated && !rawInstance.isDisposed) {\n rawInstance.dispose();\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return [state, bloc, componentRef] as UseBlocReturn<T, ExtractState<T>>;\n}\n","import { useMemo, useEffect, useRef } from 'react';\nimport {\n StateContainerConstructor,\n isIsolatedClass,\n acquire,\n release,\n} from '@blac/core';\nimport type { ComponentRef } from './types';\nimport { generateInstanceKey } from './utils/instance-keys';\n\n/**\n * Configuration options for useBlocActions hook\n * @template TBloc - The state container type\n * @template TProps - Props type passed to the container\n */\nexport interface UseBlocActionsOptions<TBloc, TProps = any> {\n /** Props passed to bloc constructor or updateProps */\n props?: TProps;\n /** Custom instance identifier for shared or isolated instances */\n instanceId?: string | number;\n /** Callback invoked when bloc instance mounts */\n onMount?: (bloc: TBloc) => void;\n /** Callback invoked when bloc instance unmounts */\n onUnmount?: (bloc: TBloc) => void;\n}\n\n/**\n * React hook that connects to a state container instance without triggering re-renders.\n * Use this when you only need to call actions on the bloc without subscribing to state changes.\n *\n * **This is the recommended hook for stateless containers** (StatelessCubit, StatelessVertex).\n * It also works with regular Cubit/Vertex when you don't need state subscriptions.\n *\n * @template T - The state container constructor type (inferred from BlocClass)\n * @param BlocClass - The state container class to connect to (supports both stateful and stateless)\n * @param options - Configuration options for instance management and lifecycle\n * @returns The state container instance for calling actions\n *\n * @example Basic usage with stateless container\n * ```ts\n * class AnalyticsService extends StatelessCubit {\n * trackEvent(name: string) { ... }\n * }\n *\n * const analytics = useBlocActions(AnalyticsService);\n * analytics.trackEvent('page_view');\n * ```\n *\n * @example With stateful container (no re-renders)\n * ```ts\n * const myBloc = useBlocActions(MyBloc);\n * myBloc.someMethod(); // Won't cause re-renders\n * ```\n *\n * @example With isolated instance\n * ```ts\n * const myBloc = useBlocActions(MyBloc, {\n * instanceId: 'unique-id'\n * });\n * ```\n */\nexport function useBlocActions<\n T extends StateContainerConstructor = StateContainerConstructor,\n>(\n BlocClass: T,\n options?: UseBlocActionsOptions<InstanceType<T>>,\n): InstanceType<T> {\n const componentRef = useRef<ComponentRef>({});\n const initialPropsRef = useRef(options?.props);\n\n const [bloc, instanceKey] = useMemo(() => {\n const isIsolated = isIsolatedClass(BlocClass);\n\n const instanceKey = generateInstanceKey(\n componentRef.current,\n isIsolated,\n options?.instanceId,\n );\n\n const instance = acquire(BlocClass, instanceKey, {\n props: initialPropsRef.current,\n });\n\n return [instance, instanceKey] as const;\n }, [BlocClass]);\n\n useEffect(() => {\n if (options?.onMount) {\n options.onMount(bloc);\n }\n\n return () => {\n if (options?.onUnmount) {\n options.onUnmount(bloc);\n }\n\n release(BlocClass, instanceKey);\n\n if (isIsolatedClass(BlocClass) && !bloc.isDisposed) {\n bloc.dispose();\n }\n };\n }, []);\n\n return bloc as InstanceType<T>;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/utils/instance-keys.ts","../src/config.ts","../src/useBloc.ts"],"sourcesContent":["/**\n * Instance key generation utilities for React integration\n */\n\nimport { generateIsolatedKey } from '@blac/adapter';\nimport type { ComponentRef } from '../types';\n\n/**\n * Generate an instance key for a bloc\n *\n * Logic:\n * - If user provides instanceId, use it (convert number to string)\n * - If isolated, generate or reuse a unique key for this component\n * - Otherwise, return undefined (use default key)\n *\n * @param componentRef - React component reference (persists across remounts)\n * @param isIsolated - Whether the bloc is isolated\n * @param providedId - User-provided instance ID (from options)\n * @returns Instance key string or undefined for default\n */\nexport function generateInstanceKey(\n componentRef: ComponentRef,\n isIsolated: boolean,\n providedId?: string | number,\n): string | undefined {\n // User explicitly provided an ID - use it\n if (providedId !== undefined) {\n return typeof providedId === 'number' ? String(providedId) : providedId;\n }\n\n // Isolated bloc - generate unique key per component\n if (isIsolated) {\n if (!componentRef.__blocInstanceId) {\n componentRef.__blocInstanceId = generateIsolatedKey();\n }\n return componentRef.__blocInstanceId;\n }\n\n // Shared bloc - use default key (undefined)\n return undefined;\n}\n","/**\n * Global configuration for @blac/react\n */\n\nexport interface BlacReactConfig {\n /** Enable automatic property tracking via Proxy (default: true) */\n autoTrack: boolean;\n}\n\nconst defaultConfig: BlacReactConfig = {\n autoTrack: true,\n};\n\nlet globalConfig: BlacReactConfig = { ...defaultConfig };\n\n/**\n * Configure global defaults for @blac/react hooks.\n *\n * @example\n * ```ts\n * import { configureBlacReact } from '@blac/react';\n *\n * // Disable auto-tracking globally\n * configureBlacReact({\n * autoTrack: false\n * });\n * ```\n *\n * @param config - Partial configuration to merge with defaults\n */\nexport function configureBlacReact(config: Partial<BlacReactConfig>): void {\n globalConfig = { ...globalConfig, ...config };\n}\n\n/**\n * Get the current global configuration.\n * @internal\n */\nexport function getBlacReactConfig(): BlacReactConfig {\n return globalConfig;\n}\n\n/**\n * Reset configuration to defaults (useful for testing).\n * @internal\n */\nexport function resetBlacReactConfig(): void {\n globalConfig = { ...defaultConfig };\n}\n","import {\n useMemo,\n useSyncExternalStore,\n useEffect,\n useRef,\n useReducer,\n} from 'react';\nimport {\n type ExtractState,\n type AdapterState,\n ExternalDepsManager,\n autoTrackSubscribe,\n manualDepsSubscribe,\n noTrackSubscribe,\n autoTrackSnapshot,\n manualDepsSnapshot,\n noTrackSnapshot,\n autoTrackInit,\n manualDepsInit,\n noTrackInit,\n disableGetterTracking,\n isIsolatedClass,\n type StateContainerConstructor,\n type InstanceState,\n acquire,\n release,\n} from '@blac/adapter';\nimport type { UseBlocOptions, UseBlocReturn, ComponentRef } from './types';\nimport { generateInstanceKey } from './utils/instance-keys';\nimport { getBlacReactConfig } from './config';\n\ninterface TrackingMode {\n useManualDeps: boolean;\n autoTrackEnabled: boolean;\n}\n\nfunction determineTrackingMode<TBloc extends StateContainerConstructor>(\n options?: UseBlocOptions<TBloc>,\n): TrackingMode {\n const globalConfig = getBlacReactConfig();\n const autoTrackEnabled =\n options?.autoTrack !== undefined\n ? options.autoTrack\n : globalConfig.autoTrack;\n\n return {\n useManualDeps: options?.dependencies !== undefined,\n autoTrackEnabled,\n };\n}\n\n/**\n * React hook that connects a component to a state container with automatic re-render on state changes.\n *\n * Supports three tracking modes:\n * - **Auto-tracking** (default): Automatically detects accessed state properties via Proxy\n * - **Manual dependencies**: Explicit dependency array like useEffect\n * - **No tracking**: Returns full state without optimization\n *\n * @template T - The state container constructor type (inferred from BlocClass)\n * @param BlocClass - The state container class to connect to\n * @param options - Configuration options for tracking mode and instance management\n * @returns Tuple with [state, bloc instance, ref]\n *\n * @example Basic usage\n * ```ts\n * const [state, myBloc, ref] = useBloc(MyBloc);\n * ```\n *\n * @example With manual dependencies\n * ```ts\n * const [state, myBloc] = useBloc(MyBloc, {\n * dependencies: (state) => [state.count]\n * });\n * ```\n *\n * @example With isolated instance\n * ```ts\n * const [state, myBloc] = useBloc(MyBloc, {\n * instanceId: 'unique-id'\n * });\n * ```\n */\nexport function useBloc<\n T extends StateContainerConstructor = StateContainerConstructor,\n>(\n BlocClass: T,\n options?: UseBlocOptions<T>,\n): UseBlocReturn<T, ExtractState<T>> {\n type TBloc = InstanceState<T>;\n\n const componentRef = useRef<ComponentRef>({});\n const isIsolated = isIsolatedClass(BlocClass);\n\n const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] =\n useMemo<\n readonly [\n TBloc,\n (callback: () => void) => () => void,\n () => ExtractState<T>,\n string | undefined,\n AdapterState<T>,\n TBloc,\n ]\n >(() => {\n const instanceKey = generateInstanceKey(\n componentRef.current,\n isIsolated,\n options?.instanceId,\n );\n\n const instance = acquire(BlocClass, instanceKey) as TBloc;\n\n const { useManualDeps, autoTrackEnabled } =\n determineTrackingMode(options);\n\n let subscribeFn: (callback: () => void) => () => void;\n let getSnapshotFn: () => ExtractState<T>;\n let adapterState: AdapterState<T>;\n\n if (useManualDeps && options?.dependencies) {\n adapterState = manualDepsInit(instance);\n subscribeFn = manualDepsSubscribe(instance, adapterState, {\n dependencies: options.dependencies,\n });\n getSnapshotFn = manualDepsSnapshot(instance, adapterState, {\n dependencies: options.dependencies,\n });\n } else if (!autoTrackEnabled) {\n adapterState = noTrackInit(instance);\n subscribeFn = noTrackSubscribe(instance);\n getSnapshotFn = noTrackSnapshot(instance);\n } else {\n adapterState = autoTrackInit(instance);\n subscribeFn = autoTrackSubscribe(instance, adapterState);\n getSnapshotFn = autoTrackSnapshot(instance, adapterState);\n }\n\n return [\n adapterState.proxiedBloc as TBloc,\n subscribeFn,\n getSnapshotFn,\n instanceKey,\n adapterState,\n instance,\n ];\n }, [BlocClass, isIsolated, options?.instanceId]);\n\n const state = useSyncExternalStore(subscribe, getSnapshot);\n\n const [, forceUpdate] = useReducer((x: number) => x + 1, 0);\n\n const externalDepsManager = useRef(new ExternalDepsManager());\n\n useEffect(() => {\n disableGetterTracking(adapterState, rawInstance);\n externalDepsManager.current.updateSubscriptions(\n adapterState.getterState,\n rawInstance,\n forceUpdate,\n );\n });\n\n useEffect(() => {\n if (options?.onMount) {\n options.onMount(bloc as InstanceType<T>);\n }\n\n return () => {\n externalDepsManager.current.cleanup();\n\n if (options?.onUnmount) {\n options.onUnmount(bloc as InstanceType<T>);\n }\n\n release(BlocClass, instanceKey);\n\n if (isIsolated && !rawInstance.isDisposed) {\n rawInstance.dispose();\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return [state, bloc, componentRef] as UseBlocReturn<T, ExtractState<T>>;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,oBACd,cACA,YACA,YACoB;AAEpB,KAAI,eAAe,OACjB,QAAO,OAAO,eAAe,WAAW,OAAO,WAAW,GAAG;AAI/D,KAAI,YAAY;AACd,MAAI,CAAC,aAAa,iBAChB,cAAa,mBAAmB,qBAAqB;AAEvD,SAAO,aAAa;;;;;;AC1BxB,MAAM,gBAAiC,EACrC,WAAW,MACZ;AAED,IAAI,eAAgC,EAAE,GAAG,eAAe;;;;;;;;;;;;;;;;AAiBxD,SAAgB,mBAAmB,QAAwC;AACzE,gBAAe;EAAE,GAAG;EAAc,GAAG;EAAQ;;;;;;AAO/C,SAAgB,qBAAsC;AACpD,QAAO;;;;;ACHT,SAAS,sBACP,SACc;CACd,MAAM,eAAe,oBAAoB;CACzC,MAAM,mBACJ,SAAS,cAAc,SACnB,QAAQ,YACR,aAAa;AAEnB,QAAO;EACL,eAAe,SAAS,iBAAiB;EACzC;EACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCH,SAAgB,QAGd,WACA,SACmC;CAGnC,MAAM,eAAe,OAAqB,EAAE,CAAC;CAC7C,MAAM,aAAa,gBAAgB,UAAU;CAE7C,MAAM,CAAC,MAAM,WAAW,aAAa,aAAa,cAAc,eAC9D,cASQ;EACN,MAAM,cAAc,oBAClB,aAAa,SACb,YACA,SAAS,WACV;EAED,MAAM,WAAW,QAAQ,WAAW,YAAY;EAEhD,MAAM,EAAE,eAAe,qBACrB,sBAAsB,QAAQ;EAEhC,IAAI;EACJ,IAAI;EACJ,IAAI;AAEJ,MAAI,iBAAiB,SAAS,cAAc;AAC1C,kBAAe,eAAe,SAAS;AACvC,iBAAc,oBAAoB,UAAU,cAAc,EACxD,cAAc,QAAQ,cACvB,CAAC;AACF,mBAAgB,mBAAmB,UAAU,cAAc,EACzD,cAAc,QAAQ,cACvB,CAAC;aACO,CAAC,kBAAkB;AAC5B,kBAAe,YAAY,SAAS;AACpC,iBAAc,iBAAiB,SAAS;AACxC,mBAAgB,gBAAgB,SAAS;SACpC;AACL,kBAAe,cAAc,SAAS;AACtC,iBAAc,mBAAmB,UAAU,aAAa;AACxD,mBAAgB,kBAAkB,UAAU,aAAa;;AAG3D,SAAO;GACL,aAAa;GACb;GACA;GACA;GACA;GACA;GACD;IACA;EAAC;EAAW;EAAY,SAAS;EAAW,CAAC;CAElD,MAAM,QAAQ,qBAAqB,WAAW,YAAY;CAE1D,MAAM,GAAG,eAAe,YAAY,MAAc,IAAI,GAAG,EAAE;CAE3D,MAAM,sBAAsB,OAAO,IAAI,qBAAqB,CAAC;AAE7D,iBAAgB;AACd,wBAAsB,cAAc,YAAY;AAChD,sBAAoB,QAAQ,oBAC1B,aAAa,aACb,aACA,YACD;GACD;AAEF,iBAAgB;AACd,MAAI,SAAS,QACX,SAAQ,QAAQ,KAAwB;AAG1C,eAAa;AACX,uBAAoB,QAAQ,SAAS;AAErC,OAAI,SAAS,UACX,SAAQ,UAAU,KAAwB;AAG5C,WAAQ,WAAW,YAAY;AAE/B,OAAI,cAAc,CAAC,YAAY,WAC7B,aAAY,SAAS;;IAIxB,EAAE,CAAC;AAEN,QAAO;EAAC;EAAO;EAAM;EAAa"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blac/react",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.4",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Brendan Mullins <jsnanigans@gmail.com>",
|
|
6
6
|
"repository": {
|
|
@@ -46,10 +46,12 @@
|
|
|
46
46
|
"bloc",
|
|
47
47
|
"bloc-pattern"
|
|
48
48
|
],
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@blac/adapter": "2.0.4"
|
|
51
|
+
},
|
|
49
52
|
"peerDependencies": {
|
|
50
53
|
"@types/react": "^18.0.0 || ^19.0.0",
|
|
51
|
-
"react": "^18.0.0 || ^19.0.0"
|
|
52
|
-
"@blac/core": "2.0.1"
|
|
54
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
53
55
|
},
|
|
54
56
|
"peerDependenciesMeta": {
|
|
55
57
|
"@types/react": {
|
|
@@ -78,7 +80,7 @@
|
|
|
78
80
|
"tsdown": "^0.15.7",
|
|
79
81
|
"typescript": "^5.9.2",
|
|
80
82
|
"vitest": "3.2.4",
|
|
81
|
-
"@blac/core": "2.0.
|
|
83
|
+
"@blac/core": "2.0.4"
|
|
82
84
|
},
|
|
83
85
|
"scripts": {
|
|
84
86
|
"dev": "tsdown --watch",
|