@blac/core 2.0.0-rc-13 → 2.0.0-rc-14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs.js +2 -2
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +522 -297
- package/dist/index.es.js +1833 -1550
- package/dist/index.es.js.map +1 -1
- package/package.json +6 -7
- package/src/Blac.ts +38 -71
- package/src/Bloc.ts +8 -9
- package/src/BlocBase.ts +246 -540
- package/src/Cubit.ts +1 -16
- package/src/__tests__/Blac.config.test.ts +10 -17
- package/src/__tests__/Bloc.event.test.ts +27 -52
- package/src/__tests__/BlocBase.subscription.test.ts +349 -0
- package/src/__tests__/Cubit.test.ts +39 -50
- package/src/__tests__/memory-leaks.test.ts +383 -0
- package/src/__tests__/plugins.test.ts +107 -97
- package/src/adapter/BlacAdapter.ts +347 -207
- package/src/adapter/ProxyFactory.ts +196 -255
- package/src/adapter/__tests__/BlacAdapter.test.ts +71 -77
- package/src/adapter/__tests__/ProxyFactory.test.ts +1 -1
- package/src/adapter/index.ts +0 -1
- package/src/errors/BlacError.ts +69 -0
- package/src/errors/ErrorManager.ts +101 -0
- package/src/errors/handleError.ts +114 -0
- package/src/errors/index.ts +13 -0
- package/src/events.ts +7 -1
- package/src/index.ts +7 -1
- package/src/lifecycle/BlocLifecycle.ts +156 -0
- package/src/lifecycle/index.ts +1 -0
- package/src/plugins/BlocPluginRegistry.ts +71 -51
- package/src/plugins/SystemPluginRegistry.ts +63 -13
- package/src/plugins/__tests__/BlocPluginRegistry.test.ts +450 -0
- package/src/plugins/__tests__/SystemPluginRegistry.test.ts +404 -0
- package/src/plugins/types.ts +32 -3
- package/src/subscription/README.md +132 -0
- package/src/subscription/SubscriptionManager.ts +395 -0
- package/src/subscription/__tests__/SubscriptionManager.test.ts +355 -0
- package/src/subscription/index.ts +2 -0
- package/src/subscription/types.ts +90 -0
- package/src/testing.ts +31 -37
- package/src/types.ts +4 -21
- package/src/utils/BatchingManager.ts +59 -0
- package/src/utils/RerenderLogger.ts +265 -0
- package/src/utils/__tests__/generateInstanceId.test.ts +104 -0
- package/src/utils/__tests__/shallowEqual.test.ts +171 -0
- package/src/utils/__tests__/uuid.test.ts +144 -0
- package/src/utils/generateInstanceId.ts +54 -0
- package/src/utils/shallowEqual.ts +1 -1
- package/README.md +0 -329
- package/src/BlacObserver.ts +0 -189
- package/src/__tests__/BlocBase.lifecycle.test.ts +0 -441
- package/src/__tests__/props.test.ts +0 -256
- package/src/adapter/ConsumerTracker.ts +0 -241
- package/src/adapter/__tests__/ConsumerTracker.test.ts +0 -562
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import { Blac } from '../Blac';
|
|
2
|
-
import { BlocBase
|
|
3
|
-
import { BlocConstructor, BlocState
|
|
4
|
-
import { PropsUpdated } from '../events';
|
|
2
|
+
import { BlocBase } from '../BlocBase';
|
|
3
|
+
import { BlocConstructor, BlocState } from '../types';
|
|
5
4
|
import { generateUUID } from '../utils/uuid';
|
|
6
|
-
import {
|
|
7
|
-
import { ConsumerTracker, DependencyArray } from './ConsumerTracker';
|
|
5
|
+
import { generateInstanceIdFromProps } from '../utils/generateInstanceId';
|
|
8
6
|
import { ProxyFactory } from './ProxyFactory';
|
|
9
7
|
|
|
10
8
|
export interface AdapterOptions<B extends BlocBase<any>> {
|
|
11
|
-
|
|
9
|
+
instanceId?: string;
|
|
12
10
|
dependencies?: (bloc: B) => unknown[];
|
|
13
|
-
|
|
11
|
+
staticProps?: any;
|
|
14
12
|
onMount?: (bloc: B) => void;
|
|
15
13
|
onUnmount?: (bloc: B) => void;
|
|
16
14
|
}
|
|
@@ -20,29 +18,40 @@ export interface AdapterOptions<B extends BlocBase<any>> {
|
|
|
20
18
|
* It manages dependency tracking, lifecycle hooks, and proxy creation.
|
|
21
19
|
*/
|
|
22
20
|
export class BlacAdapter<B extends BlocConstructor<BlocBase<any>>> {
|
|
23
|
-
public readonly id = `
|
|
21
|
+
public readonly id = `adapter-${generateUUID()}`;
|
|
24
22
|
public readonly blocConstructor: B;
|
|
25
23
|
public readonly componentRef: { current: object } = { current: {} };
|
|
26
|
-
public blocInstance
|
|
27
|
-
|
|
28
|
-
// Core components
|
|
29
|
-
private consumerTracker: ConsumerTracker;
|
|
30
|
-
|
|
31
|
-
// Props ownership tracking
|
|
32
|
-
private static propsOwners = new WeakMap<BlocBase<any>, string>();
|
|
33
|
-
private lastProps?: any;
|
|
24
|
+
public blocInstance!: InstanceType<B>;
|
|
34
25
|
|
|
35
26
|
unmountTime: number = 0;
|
|
36
27
|
mountTime: number = 0;
|
|
37
28
|
|
|
29
|
+
// Subscription management
|
|
30
|
+
private subscriptionId?: string;
|
|
31
|
+
private unsubscribe?: () => void;
|
|
32
|
+
|
|
38
33
|
// Dependency tracking
|
|
39
34
|
private dependencyValues?: unknown[];
|
|
40
35
|
private isUsingDependencies: boolean = false;
|
|
36
|
+
private trackedPaths = new Set<string>();
|
|
37
|
+
private pendingTrackedPaths = new Set<string>(); // Paths tracked before subscription exists
|
|
38
|
+
|
|
39
|
+
// Proxy caching
|
|
40
|
+
private cachedStateProxy?: BlocState<InstanceType<B>>;
|
|
41
|
+
private cachedBlocProxy?: InstanceType<B>;
|
|
42
|
+
private lastProxiedState?: BlocState<InstanceType<B>>;
|
|
41
43
|
|
|
42
44
|
// Lifecycle state
|
|
43
45
|
private hasMounted = false;
|
|
44
46
|
private mountCount = 0;
|
|
45
47
|
|
|
48
|
+
// Rerender tracking
|
|
49
|
+
private lastState?: BlocState<InstanceType<B>>;
|
|
50
|
+
private lastDependencyValues?: unknown[];
|
|
51
|
+
private componentName?: string;
|
|
52
|
+
private renderCount = 0;
|
|
53
|
+
private lastDependenciesFn?: (bloc: InstanceType<B>) => unknown[];
|
|
54
|
+
|
|
46
55
|
options?: AdapterOptions<InstanceType<B>>;
|
|
47
56
|
|
|
48
57
|
constructor(
|
|
@@ -54,22 +63,17 @@ export class BlacAdapter<B extends BlocConstructor<BlocBase<any>>> {
|
|
|
54
63
|
this.componentRef = instanceProps.componentRef;
|
|
55
64
|
this.isUsingDependencies = !!options?.dependencies;
|
|
56
65
|
|
|
57
|
-
// Initialize
|
|
58
|
-
this.
|
|
59
|
-
|
|
60
|
-
// Initialize bloc instance and register consumer
|
|
61
|
-
this.blocInstance = this.updateBlocInstance();
|
|
62
|
-
this.consumerTracker.register(instanceProps.componentRef.current, this.id);
|
|
66
|
+
// Initialize bloc instance
|
|
67
|
+
this.updateBlocInstance();
|
|
63
68
|
|
|
64
69
|
// Initialize dependency values if using dependencies
|
|
65
70
|
if (this.isUsingDependencies && options?.dependencies) {
|
|
66
71
|
this.dependencyValues = options.dependencies(this.blocInstance);
|
|
67
72
|
}
|
|
68
73
|
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
74
|
+
// Notify plugins
|
|
75
|
+
const metadata = this.getAdapterMetadata();
|
|
76
|
+
Blac.getInstance().plugins.notifyAdapterCreated(this, metadata);
|
|
73
77
|
}
|
|
74
78
|
|
|
75
79
|
trackAccess(
|
|
@@ -78,39 +82,139 @@ export class BlacAdapter<B extends BlocConstructor<BlocBase<any>>> {
|
|
|
78
82
|
path: string,
|
|
79
83
|
value?: any,
|
|
80
84
|
): void {
|
|
81
|
-
|
|
85
|
+
const fullPath = type === 'class' ? `_class.${path}` : path;
|
|
86
|
+
this.trackedPaths.add(fullPath);
|
|
87
|
+
|
|
88
|
+
if (this.subscriptionId) {
|
|
89
|
+
this.blocInstance.trackAccess(this.subscriptionId, fullPath, value);
|
|
90
|
+
} else {
|
|
91
|
+
// No subscription ID yet - store for later
|
|
92
|
+
this.pendingTrackedPaths.add(fullPath);
|
|
93
|
+
if ((Blac.config as any).logLevel === 'debug') {
|
|
94
|
+
Blac.log(`[BlacAdapter] trackAccess storing pending path: ${path}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
82
97
|
}
|
|
83
98
|
|
|
84
|
-
|
|
85
|
-
|
|
99
|
+
updateBlocInstance(): InstanceType<B> {
|
|
100
|
+
// Determine the instance ID
|
|
101
|
+
let instanceId = this.options?.instanceId;
|
|
102
|
+
|
|
103
|
+
// If no explicit instanceId provided but staticProps exist, generate from them
|
|
104
|
+
if (!instanceId && this.options?.staticProps) {
|
|
105
|
+
const generatedId = generateInstanceIdFromProps(this.options.staticProps);
|
|
106
|
+
if (generatedId) {
|
|
107
|
+
instanceId = generatedId;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
this.blocInstance = Blac.instance.getBloc<B>(this.blocConstructor, {
|
|
112
|
+
constructorParams: this.options?.staticProps,
|
|
113
|
+
id: instanceId,
|
|
114
|
+
instanceRef: this.id,
|
|
115
|
+
});
|
|
116
|
+
return this.blocInstance;
|
|
86
117
|
}
|
|
87
118
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
119
|
+
createSubscription = (options: { onChange: () => void }) => {
|
|
120
|
+
// If using manual dependencies, create a selector-based subscription
|
|
121
|
+
if (this.isUsingDependencies && this.options?.dependencies) {
|
|
122
|
+
this.unsubscribe = this.blocInstance.subscribeWithSelector(
|
|
123
|
+
(_state) => this.options!.dependencies!(this.blocInstance),
|
|
124
|
+
(newValues) => {
|
|
125
|
+
const hasChanged = this.hasDependencyValuesChanged(
|
|
126
|
+
this.dependencyValues,
|
|
127
|
+
newValues as unknown[],
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
if (hasChanged) {
|
|
131
|
+
this.dependencyValues = newValues as unknown[];
|
|
132
|
+
options.onChange();
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
);
|
|
136
|
+
} else {
|
|
137
|
+
// Create a component subscription with weak reference
|
|
138
|
+
const weakRef = new WeakRef(this.componentRef.current);
|
|
139
|
+
this.unsubscribe = this.blocInstance.subscribeComponent(
|
|
140
|
+
weakRef,
|
|
141
|
+
options.onChange,
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
// Get the subscription ID for tracking
|
|
145
|
+
const subscriptions = (this.blocInstance._subscriptionManager as any)
|
|
146
|
+
.subscriptions as Map<string, any>;
|
|
147
|
+
this.subscriptionId = Array.from(subscriptions.keys()).pop();
|
|
148
|
+
|
|
149
|
+
// Apply any pending tracked paths
|
|
150
|
+
if (this.subscriptionId && this.pendingTrackedPaths.size > 0) {
|
|
151
|
+
for (const path of this.pendingTrackedPaths) {
|
|
152
|
+
this.blocInstance.trackAccess(this.subscriptionId, path);
|
|
153
|
+
}
|
|
154
|
+
this.pendingTrackedPaths.clear();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Don't call onChange initially - let React handle the initial render
|
|
159
|
+
|
|
160
|
+
return () => {
|
|
161
|
+
if (this.unsubscribe) {
|
|
162
|
+
this.unsubscribe();
|
|
163
|
+
this.unsubscribe = undefined;
|
|
164
|
+
this.subscriptionId = undefined;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
hasDependencyValuesChanged = (
|
|
170
|
+
oldValues: unknown[] | undefined,
|
|
171
|
+
newValues: unknown[],
|
|
172
|
+
): boolean => {
|
|
173
|
+
if (!oldValues) return true;
|
|
174
|
+
if (oldValues.length !== newValues.length) return true;
|
|
175
|
+
|
|
176
|
+
for (let i = 0; i < oldValues.length; i++) {
|
|
177
|
+
if (!Object.is(oldValues[i], newValues[i])) {
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
95
180
|
}
|
|
96
181
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
182
|
+
return false;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
getStateProxy = (): BlocState<InstanceType<B>> => {
|
|
186
|
+
// If using manual dependencies or proxy tracking is disabled, return raw state
|
|
187
|
+
if (this.isUsingDependencies || !Blac.config.proxyDependencyTracking) {
|
|
188
|
+
return this.blocInstance.state;
|
|
100
189
|
}
|
|
101
190
|
|
|
102
|
-
//
|
|
103
|
-
|
|
104
|
-
|
|
191
|
+
// Return cached proxy if state hasn't changed
|
|
192
|
+
const currentState = this.blocInstance.state;
|
|
193
|
+
if (this.cachedStateProxy && this.lastProxiedState === currentState) {
|
|
194
|
+
return this.cachedStateProxy;
|
|
195
|
+
}
|
|
105
196
|
|
|
106
|
-
|
|
107
|
-
this.
|
|
108
|
-
this.
|
|
109
|
-
|
|
197
|
+
// Create new proxy for new state
|
|
198
|
+
this.lastProxiedState = currentState;
|
|
199
|
+
this.cachedStateProxy = this.createStateProxy({ target: currentState });
|
|
200
|
+
return this.cachedStateProxy!;
|
|
201
|
+
};
|
|
110
202
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
203
|
+
getBlocProxy = (): InstanceType<B> => {
|
|
204
|
+
// If using manual dependencies or proxy tracking is disabled, return raw bloc
|
|
205
|
+
if (this.isUsingDependencies || !Blac.config.proxyDependencyTracking) {
|
|
206
|
+
return this.blocInstance;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Return cached proxy if bloc instance hasn't changed
|
|
210
|
+
if (this.cachedBlocProxy) {
|
|
211
|
+
return this.cachedBlocProxy;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Create and cache proxy
|
|
215
|
+
this.cachedBlocProxy = this.createClassProxy({ target: this.blocInstance });
|
|
216
|
+
return this.cachedBlocProxy;
|
|
217
|
+
};
|
|
114
218
|
|
|
115
219
|
createStateProxy = <T extends object>(props: { target: T }): T => {
|
|
116
220
|
return ProxyFactory.createStateProxy({
|
|
@@ -128,200 +232,236 @@ export class BlacAdapter<B extends BlocConstructor<BlocBase<any>>> {
|
|
|
128
232
|
});
|
|
129
233
|
};
|
|
130
234
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
});
|
|
137
|
-
return this.blocInstance;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
createSubscription = (options: { onChange: () => void }) => {
|
|
141
|
-
const unsubscribe = this.blocInstance._observer.subscribe({
|
|
142
|
-
id: this.id,
|
|
143
|
-
fn: (
|
|
144
|
-
newState: BlocState<InstanceType<B>>,
|
|
145
|
-
oldState: BlocState<InstanceType<B>>,
|
|
146
|
-
) => {
|
|
147
|
-
// Case 1: Manual dependencies provided
|
|
148
|
-
if (this.isUsingDependencies && this.options?.dependencies) {
|
|
149
|
-
const newValues = this.options.dependencies(this.blocInstance);
|
|
150
|
-
const hasChanged = this.hasDependencyValuesChanged(
|
|
151
|
-
this.dependencyValues,
|
|
152
|
-
newValues,
|
|
153
|
-
);
|
|
154
|
-
|
|
155
|
-
if (!hasChanged) {
|
|
156
|
-
return; // Don't trigger re-render
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
this.dependencyValues = newValues;
|
|
160
|
-
}
|
|
161
|
-
// Case 2: Proxy tracking disabled globally (and no manual dependencies)
|
|
162
|
-
else if (!Blac.config.proxyDependencyTracking) {
|
|
163
|
-
// Always trigger re-render when proxy tracking is disabled
|
|
164
|
-
options.onChange();
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
// Case 3: Proxy tracking enabled (default behavior)
|
|
168
|
-
else {
|
|
169
|
-
// Check if any tracked values have changed (proxy-based tracking)
|
|
170
|
-
const consumerInfo = this.consumerTracker.getConsumerInfo(
|
|
171
|
-
this.componentRef.current,
|
|
172
|
-
);
|
|
173
|
-
if (consumerInfo && consumerInfo.hasRendered) {
|
|
174
|
-
// Only check dependencies if component has rendered at least once
|
|
175
|
-
const hasChanged = this.consumerTracker.hasValuesChanged(
|
|
176
|
-
this.componentRef.current,
|
|
177
|
-
newState,
|
|
178
|
-
this.blocInstance,
|
|
179
|
-
);
|
|
180
|
-
|
|
181
|
-
if (!hasChanged) {
|
|
182
|
-
return; // Don't trigger re-render
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
options.onChange();
|
|
188
|
-
},
|
|
189
|
-
});
|
|
235
|
+
// Lifecycle methods
|
|
236
|
+
mount = () => {
|
|
237
|
+
this.hasMounted = true;
|
|
238
|
+
this.mountCount++;
|
|
239
|
+
this.mountTime = Date.now();
|
|
190
240
|
|
|
191
|
-
|
|
192
|
-
|
|
241
|
+
// Call onMount hook
|
|
242
|
+
if (this.options?.onMount) {
|
|
243
|
+
this.options.onMount(this.blocInstance);
|
|
244
|
+
}
|
|
193
245
|
|
|
194
|
-
|
|
195
|
-
// Re-run dependencies on mount to ensure fresh values
|
|
246
|
+
// Refresh dependencies if using manual tracking
|
|
196
247
|
if (this.isUsingDependencies && this.options?.dependencies) {
|
|
197
248
|
this.dependencyValues = this.options.dependencies(this.blocInstance);
|
|
198
249
|
}
|
|
199
250
|
|
|
200
|
-
//
|
|
201
|
-
this.
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
// Call onMount callback if provided and not already called
|
|
205
|
-
if (!this.hasMounted) {
|
|
206
|
-
this.hasMounted = true;
|
|
207
|
-
this.mountTime = Date.now();
|
|
208
|
-
|
|
209
|
-
if (this.options?.onMount) {
|
|
210
|
-
try {
|
|
211
|
-
this.options.onMount(this.blocInstance);
|
|
212
|
-
} catch (error) {
|
|
213
|
-
throw error;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
251
|
+
// Notify plugins
|
|
252
|
+
const metadata = this.getAdapterMetadata();
|
|
253
|
+
Blac.getInstance().plugins.notifyAdapterMount(this, metadata);
|
|
217
254
|
};
|
|
218
255
|
|
|
219
|
-
unmount = ()
|
|
256
|
+
unmount = () => {
|
|
220
257
|
this.unmountTime = Date.now();
|
|
221
|
-
this.consumerTracker.unregister(this.componentRef.current);
|
|
222
|
-
this.blocInstance._removeConsumer(this.id);
|
|
223
258
|
|
|
224
|
-
//
|
|
225
|
-
if (
|
|
226
|
-
|
|
259
|
+
// Cancel subscription
|
|
260
|
+
if (this.unsubscribe) {
|
|
261
|
+
this.unsubscribe();
|
|
262
|
+
this.unsubscribe = undefined;
|
|
263
|
+
this.subscriptionId = undefined;
|
|
227
264
|
}
|
|
228
265
|
|
|
229
|
-
// Call onUnmount
|
|
266
|
+
// Call onUnmount hook
|
|
230
267
|
if (this.options?.onUnmount) {
|
|
231
268
|
try {
|
|
232
269
|
this.options.onUnmount(this.blocInstance);
|
|
233
270
|
} catch (error) {
|
|
234
|
-
|
|
271
|
+
console.error('Error in onUnmount hook:', error);
|
|
235
272
|
}
|
|
236
273
|
}
|
|
274
|
+
|
|
275
|
+
// Notify plugins
|
|
276
|
+
const metadata = this.getAdapterMetadata();
|
|
277
|
+
Blac.getInstance().plugins.notifyAdapterUnmount(this, metadata);
|
|
237
278
|
};
|
|
238
279
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
280
|
+
// Reset tracking for next render
|
|
281
|
+
resetTracking(): void {
|
|
282
|
+
// Clear tracked paths from previous render
|
|
283
|
+
this.trackedPaths.clear();
|
|
284
|
+
this.pendingTrackedPaths.clear();
|
|
285
|
+
|
|
286
|
+
// Clear subscription dependencies to track only current render
|
|
287
|
+
if (this.subscriptionId) {
|
|
288
|
+
const subscription = (
|
|
289
|
+
this.blocInstance._subscriptionManager as any
|
|
290
|
+
).subscriptions.get(this.subscriptionId);
|
|
291
|
+
if (subscription && subscription.dependencies) {
|
|
292
|
+
// Clear old dependencies - we'll track new ones in this render
|
|
293
|
+
subscription.dependencies.clear();
|
|
294
|
+
}
|
|
245
295
|
}
|
|
296
|
+
}
|
|
246
297
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
});
|
|
252
|
-
};
|
|
298
|
+
// Set component name for rerender logging
|
|
299
|
+
setComponentName(name: string): void {
|
|
300
|
+
this.componentName = name;
|
|
301
|
+
}
|
|
253
302
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
303
|
+
// Notify plugins about render
|
|
304
|
+
notifyRender(): void {
|
|
305
|
+
this.renderCount++;
|
|
306
|
+
|
|
307
|
+
// Update dependency values if using manual tracking
|
|
308
|
+
if (this.isUsingDependencies && this.options?.dependencies) {
|
|
309
|
+
this.lastDependencyValues = this.dependencyValues;
|
|
310
|
+
this.dependencyValues = this.options.dependencies(this.blocInstance);
|
|
258
311
|
}
|
|
259
312
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
consumerTracker: this as any,
|
|
264
|
-
});
|
|
265
|
-
};
|
|
313
|
+
const metadata = this.getAdapterMetadata();
|
|
314
|
+
Blac.getInstance().plugins.notifyAdapterRender(this, metadata);
|
|
315
|
+
}
|
|
266
316
|
|
|
267
|
-
//
|
|
268
|
-
|
|
269
|
-
return
|
|
317
|
+
// Get adapter metadata for plugins
|
|
318
|
+
private getAdapterMetadata(): any {
|
|
319
|
+
return {
|
|
320
|
+
componentName: this.componentName,
|
|
321
|
+
blocInstance: this.blocInstance,
|
|
322
|
+
renderCount: this.renderCount,
|
|
323
|
+
trackedPaths: Array.from(this.trackedPaths),
|
|
324
|
+
isUsingDependencies: this.isUsingDependencies,
|
|
325
|
+
lastState: this.lastState,
|
|
326
|
+
lastDependencyValues: this.lastDependencyValues,
|
|
327
|
+
currentDependencyValues: this.dependencyValues,
|
|
328
|
+
};
|
|
270
329
|
}
|
|
271
330
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
if (BlacAdapter.propsOwners.get(bloc) !== this.id) {
|
|
282
|
-
Blac.warn(
|
|
283
|
-
`[BlacAdapter] Attempted to set props on ${bloc.constructor.name} from non-owner adapter`
|
|
284
|
-
);
|
|
285
|
-
return;
|
|
331
|
+
// Get value at a dot-notated path
|
|
332
|
+
private getValueAtPath(obj: any, path: string): any {
|
|
333
|
+
const parts = path.split('.');
|
|
334
|
+
let current = obj;
|
|
335
|
+
|
|
336
|
+
for (const part of parts) {
|
|
337
|
+
if (current == null) return undefined;
|
|
338
|
+
current = current[part];
|
|
286
339
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
340
|
+
|
|
341
|
+
return current;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Get paths that changed between states
|
|
345
|
+
private getStateChangedPaths(oldState: any, newState: any): string[] {
|
|
346
|
+
const changedPaths: string[] = [];
|
|
347
|
+
|
|
348
|
+
// Only check tracked paths if we have any
|
|
349
|
+
if (this.trackedPaths.size > 0) {
|
|
350
|
+
for (const path of this.trackedPaths) {
|
|
351
|
+
// Skip class property paths
|
|
352
|
+
if (path.startsWith('_class.')) continue;
|
|
353
|
+
|
|
354
|
+
const oldValue = this.getValueAtPath(oldState, path);
|
|
355
|
+
const newValue = this.getValueAtPath(newState, path);
|
|
356
|
+
|
|
357
|
+
if (!Object.is(oldValue, newValue)) {
|
|
358
|
+
changedPaths.push(path);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
} else {
|
|
362
|
+
// No tracked paths - do deep comparison to find what changed
|
|
363
|
+
if (oldState !== newState) {
|
|
364
|
+
const deepChanges = this.getDeepChangedPaths(oldState, newState);
|
|
365
|
+
if (deepChanges.length > 0) {
|
|
366
|
+
return deepChanges;
|
|
367
|
+
}
|
|
368
|
+
changedPaths.push('(entire state)');
|
|
369
|
+
}
|
|
292
370
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
371
|
+
|
|
372
|
+
return changedPaths;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Deep comparison to find all changed paths
|
|
376
|
+
private getDeepChangedPaths(
|
|
377
|
+
oldObj: any,
|
|
378
|
+
newObj: any,
|
|
379
|
+
basePath: string = '',
|
|
380
|
+
): string[] {
|
|
381
|
+
const changes: string[] = [];
|
|
382
|
+
|
|
383
|
+
// Handle null/undefined
|
|
384
|
+
if (oldObj === newObj) return changes;
|
|
385
|
+
if (oldObj == null || newObj == null) {
|
|
386
|
+
changes.push(basePath || '(root)');
|
|
387
|
+
return changes;
|
|
297
388
|
}
|
|
298
|
-
|
|
299
|
-
//
|
|
300
|
-
if ('
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
(bloc as any)._updateProps(props);
|
|
389
|
+
|
|
390
|
+
// Handle primitives
|
|
391
|
+
if (typeof oldObj !== 'object' || typeof newObj !== 'object') {
|
|
392
|
+
if (oldObj !== newObj) {
|
|
393
|
+
changes.push(basePath || '(root)');
|
|
394
|
+
}
|
|
395
|
+
return changes;
|
|
306
396
|
}
|
|
307
|
-
|
|
308
|
-
this.lastProps = props;
|
|
309
|
-
}
|
|
310
397
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
398
|
+
// Handle arrays
|
|
399
|
+
if (Array.isArray(oldObj) || Array.isArray(newObj)) {
|
|
400
|
+
if (
|
|
401
|
+
!Array.isArray(oldObj) ||
|
|
402
|
+
!Array.isArray(newObj) ||
|
|
403
|
+
oldObj.length !== newObj.length
|
|
404
|
+
) {
|
|
405
|
+
changes.push(basePath || '(root)');
|
|
406
|
+
return changes;
|
|
407
|
+
}
|
|
408
|
+
// Compare array elements
|
|
409
|
+
for (let i = 0; i < oldObj.length; i++) {
|
|
410
|
+
const elementPath = basePath ? `${basePath}[${i}]` : `[${i}]`;
|
|
411
|
+
changes.push(
|
|
412
|
+
...this.getDeepChangedPaths(oldObj[i], newObj[i], elementPath),
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
return changes;
|
|
416
|
+
}
|
|
317
417
|
|
|
318
|
-
//
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
418
|
+
// Handle objects
|
|
419
|
+
const allKeys = new Set([...Object.keys(oldObj), ...Object.keys(newObj)]);
|
|
420
|
+
for (const key of allKeys) {
|
|
421
|
+
const keyPath = basePath ? `${basePath}.${key}` : key;
|
|
422
|
+
|
|
423
|
+
if (!(key in oldObj)) {
|
|
424
|
+
changes.push(`${keyPath} (added)`);
|
|
425
|
+
} else if (!(key in newObj)) {
|
|
426
|
+
changes.push(`${keyPath} (removed)`);
|
|
427
|
+
} else {
|
|
428
|
+
const oldVal = oldObj[key];
|
|
429
|
+
const newVal = newObj[key];
|
|
430
|
+
|
|
431
|
+
// Use Object.is for comparison
|
|
432
|
+
if (!Object.is(oldVal, newVal)) {
|
|
433
|
+
// For primitives and different object references, record the change
|
|
434
|
+
if (
|
|
435
|
+
typeof oldVal !== 'object' ||
|
|
436
|
+
typeof newVal !== 'object' ||
|
|
437
|
+
oldVal === null ||
|
|
438
|
+
newVal === null
|
|
439
|
+
) {
|
|
440
|
+
changes.push(keyPath);
|
|
441
|
+
} else {
|
|
442
|
+
// For objects/arrays, recurse deeper
|
|
443
|
+
const deeperChanges = this.getDeepChangedPaths(
|
|
444
|
+
oldVal,
|
|
445
|
+
newVal,
|
|
446
|
+
keyPath,
|
|
447
|
+
);
|
|
448
|
+
if (deeperChanges.length > 0) {
|
|
449
|
+
changes.push(...deeperChanges);
|
|
450
|
+
} else {
|
|
451
|
+
// Objects are different references but have same content
|
|
452
|
+
changes.push(keyPath);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
322
456
|
}
|
|
323
457
|
}
|
|
324
458
|
|
|
325
|
-
return
|
|
459
|
+
return changes;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Get last rerender reason
|
|
463
|
+
getLastRerenderReason(): any {
|
|
464
|
+
// This method is deprecated - render reason is now handled by the RenderLoggingPlugin
|
|
465
|
+
return undefined;
|
|
326
466
|
}
|
|
327
467
|
}
|