@coherent.js/core 1.0.0-beta.2
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/LICENSE +21 -0
- package/README.md +130 -0
- package/dist/coherent.d.ts +472 -0
- package/dist/coherent.d.ts.map +1 -0
- package/dist/coherent.js +590 -0
- package/dist/coherent.js.map +1 -0
- package/dist/components/component-system.d.ts +1138 -0
- package/dist/components/component-system.d.ts.map +1 -0
- package/dist/components/component-system.js +2220 -0
- package/dist/components/component-system.js.map +1 -0
- package/dist/components/lifecycle.d.ts +212 -0
- package/dist/components/lifecycle.d.ts.map +1 -0
- package/dist/components/lifecycle.js +525 -0
- package/dist/components/lifecycle.js.map +1 -0
- package/dist/core/html-utils.d.ts +14 -0
- package/dist/core/html-utils.d.ts.map +1 -0
- package/dist/core/html-utils.js +166 -0
- package/dist/core/html-utils.js.map +1 -0
- package/dist/core/object-factory.d.ts +38 -0
- package/dist/core/object-factory.d.ts.map +1 -0
- package/dist/core/object-factory.js +63 -0
- package/dist/core/object-factory.js.map +1 -0
- package/dist/core/object-utils.d.ts +77 -0
- package/dist/core/object-utils.d.ts.map +1 -0
- package/dist/core/object-utils.js +502 -0
- package/dist/core/object-utils.js.map +1 -0
- package/dist/dev/dev-tools.d.ts +194 -0
- package/dist/dev/dev-tools.d.ts.map +1 -0
- package/dist/dev/dev-tools.js +846 -0
- package/dist/dev/dev-tools.js.map +1 -0
- package/dist/forms/validation.d.ts +271 -0
- package/dist/forms/validation.d.ts.map +1 -0
- package/dist/forms/validation.js +573 -0
- package/dist/forms/validation.js.map +1 -0
- package/dist/index.cjs +5281 -0
- package/dist/index.cjs.map +7 -0
- package/dist/index.js +5204 -0
- package/dist/index.js.map +7 -0
- package/dist/performance/bundle-optimizer.d.ts +95 -0
- package/dist/performance/bundle-optimizer.d.ts.map +1 -0
- package/dist/performance/bundle-optimizer.js +192 -0
- package/dist/performance/bundle-optimizer.js.map +1 -0
- package/dist/performance/cache-manager.d.ts +107 -0
- package/dist/performance/cache-manager.d.ts.map +1 -0
- package/dist/performance/cache-manager.js +314 -0
- package/dist/performance/cache-manager.js.map +1 -0
- package/dist/performance/component-cache.d.ts +120 -0
- package/dist/performance/component-cache.d.ts.map +1 -0
- package/dist/performance/component-cache.js +364 -0
- package/dist/performance/component-cache.js.map +1 -0
- package/dist/performance/monitor.d.ts +189 -0
- package/dist/performance/monitor.d.ts.map +1 -0
- package/dist/performance/monitor.js +347 -0
- package/dist/performance/monitor.js.map +1 -0
- package/dist/rendering/base-renderer.d.ts +140 -0
- package/dist/rendering/base-renderer.d.ts.map +1 -0
- package/dist/rendering/base-renderer.js +409 -0
- package/dist/rendering/base-renderer.js.map +1 -0
- package/dist/rendering/css-manager.d.ts +73 -0
- package/dist/rendering/css-manager.d.ts.map +1 -0
- package/dist/rendering/css-manager.js +176 -0
- package/dist/rendering/css-manager.js.map +1 -0
- package/dist/rendering/dom-renderer.d.ts +62 -0
- package/dist/rendering/dom-renderer.d.ts.map +1 -0
- package/dist/rendering/dom-renderer.js +252 -0
- package/dist/rendering/dom-renderer.js.map +1 -0
- package/dist/rendering/html-renderer.d.ts +67 -0
- package/dist/rendering/html-renderer.d.ts.map +1 -0
- package/dist/rendering/html-renderer.js +444 -0
- package/dist/rendering/html-renderer.js.map +1 -0
- package/dist/rendering/renderer-config.d.ts +282 -0
- package/dist/rendering/renderer-config.d.ts.map +1 -0
- package/dist/rendering/renderer-config.js +144 -0
- package/dist/rendering/renderer-config.js.map +1 -0
- package/dist/rendering/streaming-renderer.d.ts +117 -0
- package/dist/rendering/streaming-renderer.d.ts.map +1 -0
- package/dist/rendering/streaming-renderer.js +326 -0
- package/dist/rendering/streaming-renderer.js.map +1 -0
- package/dist/rendering/vdom-diff.d.ts +47 -0
- package/dist/rendering/vdom-diff.d.ts.map +1 -0
- package/dist/rendering/vdom-diff.js +416 -0
- package/dist/rendering/vdom-diff.js.map +1 -0
- package/dist/routing/router.d.ts +241 -0
- package/dist/routing/router.d.ts.map +1 -0
- package/dist/routing/router.js +648 -0
- package/dist/routing/router.js.map +1 -0
- package/dist/state/reactive-state.d.ts +166 -0
- package/dist/state/reactive-state.d.ts.map +1 -0
- package/dist/state/reactive-state.js +546 -0
- package/dist/state/reactive-state.js.map +1 -0
- package/dist/state/state-manager.d.ts +45 -0
- package/dist/state/state-manager.d.ts.map +1 -0
- package/dist/state/state-manager.js +151 -0
- package/dist/state/state-manager.js.map +1 -0
- package/dist/types/constants.d.ts +8 -0
- package/dist/types/constants.d.ts.map +1 -0
- package/dist/types/constants.js +36 -0
- package/dist/types/constants.js.map +1 -0
- package/dist/utils/dependency-utils.d.ts +43 -0
- package/dist/utils/dependency-utils.d.ts.map +1 -0
- package/dist/utils/dependency-utils.js +105 -0
- package/dist/utils/dependency-utils.js.map +1 -0
- package/dist/utils/error-handler.d.ts +148 -0
- package/dist/utils/error-handler.d.ts.map +1 -0
- package/dist/utils/error-handler.js +468 -0
- package/dist/utils/error-handler.js.map +1 -0
- package/dist/utils/normalization.d.ts +3 -0
- package/dist/utils/normalization.d.ts.map +1 -0
- package/dist/utils/normalization.js +24 -0
- package/dist/utils/normalization.js.map +1 -0
- package/dist/utils/validation.d.ts +10 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +32 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +44 -0
- package/types/index.d.ts +734 -0
|
@@ -0,0 +1,2220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component System for Coherent.js
|
|
3
|
+
* Provides component definition, lifecycle, composition, and state management
|
|
4
|
+
*/
|
|
5
|
+
import { deepClone, validateComponent } from '../core/object-utils.js';
|
|
6
|
+
import { performanceMonitor } from '../performance/monitor.js';
|
|
7
|
+
/**
|
|
8
|
+
* Component registry for global component management
|
|
9
|
+
*/
|
|
10
|
+
const COMPONENT_REGISTRY = new Map();
|
|
11
|
+
// eslint-disable-next-line no-unused-vars -- kept for future devtools/features
|
|
12
|
+
const COMPONENT_INSTANCES = new WeakMap();
|
|
13
|
+
const COMPONENT_METADATA = new WeakMap();
|
|
14
|
+
/**
|
|
15
|
+
* Component lifecycle hooks
|
|
16
|
+
*/
|
|
17
|
+
// eslint-disable-next-line no-unused-vars -- lifecycle map kept for planned hooks
|
|
18
|
+
const LIFECYCLE_HOOKS = {
|
|
19
|
+
beforeCreate: 'beforeCreate',
|
|
20
|
+
created: 'created',
|
|
21
|
+
beforeMount: 'beforeMount',
|
|
22
|
+
mounted: 'mounted',
|
|
23
|
+
beforeUpdate: 'beforeUpdate',
|
|
24
|
+
updated: 'updated',
|
|
25
|
+
beforeDestroy: 'beforeDestroy',
|
|
26
|
+
destroyed: 'destroyed',
|
|
27
|
+
errorCaptured: 'errorCaptured'
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Component state management class
|
|
31
|
+
*
|
|
32
|
+
* @class ComponentState
|
|
33
|
+
* @description Manages component state with reactive updates and listener notifications.
|
|
34
|
+
* Provides immutable state updates with change detection.
|
|
35
|
+
*
|
|
36
|
+
* @param {Object} [initialState={}] - Initial state object
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* const state = new ComponentState({ count: 0 });
|
|
40
|
+
* state.set({ count: 1 });
|
|
41
|
+
* const count = state.get('count'); // 1
|
|
42
|
+
*/
|
|
43
|
+
class ComponentState {
|
|
44
|
+
constructor(initialState = {}) {
|
|
45
|
+
this.state = { ...initialState };
|
|
46
|
+
this.listeners = new Set();
|
|
47
|
+
this.isUpdating = false;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Get state value by key or entire state
|
|
51
|
+
*
|
|
52
|
+
* @param {string} [key] - State key to retrieve
|
|
53
|
+
* @returns {*} State value or entire state object
|
|
54
|
+
*/
|
|
55
|
+
get(key) {
|
|
56
|
+
return key ? this.state[key] : { ...this.state };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Update state with new values
|
|
60
|
+
*
|
|
61
|
+
* @param {Object} updates - State updates to apply
|
|
62
|
+
* @returns {ComponentState} This instance for chaining
|
|
63
|
+
*/
|
|
64
|
+
set(updates) {
|
|
65
|
+
if (this.isUpdating)
|
|
66
|
+
return this;
|
|
67
|
+
const oldState = { ...this.state };
|
|
68
|
+
if (typeof updates === 'function') {
|
|
69
|
+
updates = updates(oldState);
|
|
70
|
+
}
|
|
71
|
+
this.state = { ...this.state, ...updates };
|
|
72
|
+
// Notify listeners
|
|
73
|
+
this.notifyListeners(oldState, this.state);
|
|
74
|
+
return this;
|
|
75
|
+
}
|
|
76
|
+
subscribe(listener) {
|
|
77
|
+
this.listeners.add(listener);
|
|
78
|
+
return () => this.listeners.delete(listener);
|
|
79
|
+
}
|
|
80
|
+
notifyListeners(oldState, newState) {
|
|
81
|
+
if (this.listeners.size === 0)
|
|
82
|
+
return;
|
|
83
|
+
this.isUpdating = true;
|
|
84
|
+
this.listeners.forEach(listener => {
|
|
85
|
+
try {
|
|
86
|
+
listener(newState, oldState);
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
console.error('State listener error:', error);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
this.isUpdating = false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Base Component class
|
|
97
|
+
*/
|
|
98
|
+
export class Component {
|
|
99
|
+
constructor(definition = {}) {
|
|
100
|
+
this.definition = definition;
|
|
101
|
+
this.name = definition.name || 'AnonymousComponent';
|
|
102
|
+
this.props = {};
|
|
103
|
+
this.state = new ComponentState(definition.state || {});
|
|
104
|
+
this.children = [];
|
|
105
|
+
this.parent = null;
|
|
106
|
+
this.rendered = null;
|
|
107
|
+
this.isMounted = false;
|
|
108
|
+
this.isDestroyed = false;
|
|
109
|
+
// Lifecycle hooks
|
|
110
|
+
this.hooks = {
|
|
111
|
+
beforeCreate: definition.beforeCreate || (() => {
|
|
112
|
+
}),
|
|
113
|
+
created: definition.created || (() => {
|
|
114
|
+
}),
|
|
115
|
+
beforeMount: definition.beforeMount || (() => {
|
|
116
|
+
}),
|
|
117
|
+
mounted: definition.mounted || (() => {
|
|
118
|
+
}),
|
|
119
|
+
beforeUpdate: definition.beforeUpdate || (() => {
|
|
120
|
+
}),
|
|
121
|
+
updated: definition.updated || (() => {
|
|
122
|
+
}),
|
|
123
|
+
beforeDestroy: definition.beforeDestroy || (() => {
|
|
124
|
+
}),
|
|
125
|
+
destroyed: definition.destroyed || (() => {
|
|
126
|
+
}),
|
|
127
|
+
errorCaptured: definition.errorCaptured || (() => {
|
|
128
|
+
})
|
|
129
|
+
};
|
|
130
|
+
// Methods
|
|
131
|
+
this.methods = definition.methods || {};
|
|
132
|
+
Object.keys(this.methods).forEach(methodName => {
|
|
133
|
+
if (typeof this.methods[methodName] === 'function') {
|
|
134
|
+
this[methodName] = this.methods[methodName].bind(this);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
// Computed properties
|
|
138
|
+
this.computed = definition.computed || {};
|
|
139
|
+
this.computedCache = new Map();
|
|
140
|
+
// Watch properties
|
|
141
|
+
this.watchers = definition.watch || {};
|
|
142
|
+
this.setupWatchers();
|
|
143
|
+
// Store metadata
|
|
144
|
+
COMPONENT_METADATA.set(this, {
|
|
145
|
+
createdAt: Date.now(),
|
|
146
|
+
updateCount: 0,
|
|
147
|
+
renderCount: 0
|
|
148
|
+
});
|
|
149
|
+
// Initialize lifecycle
|
|
150
|
+
this.callHook('beforeCreate');
|
|
151
|
+
this.initialize();
|
|
152
|
+
this.callHook('created');
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Initialize component
|
|
156
|
+
*/
|
|
157
|
+
initialize() {
|
|
158
|
+
// Set up state subscription for re-rendering
|
|
159
|
+
this.unsubscribeState = this.state.subscribe((newState, oldState) => {
|
|
160
|
+
this.onStateChange(newState, oldState);
|
|
161
|
+
});
|
|
162
|
+
// Initialize computed properties
|
|
163
|
+
this.initializeComputed();
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Set up watchers for reactive data
|
|
167
|
+
*/
|
|
168
|
+
setupWatchers() {
|
|
169
|
+
Object.keys(this.watchers).forEach(key => {
|
|
170
|
+
const handler = this.watchers[key];
|
|
171
|
+
// Watch state changes
|
|
172
|
+
this.state.subscribe((newState, oldState) => {
|
|
173
|
+
if (newState[key] !== oldState[key]) {
|
|
174
|
+
handler.call(this, newState[key], oldState[key]);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Initialize computed properties
|
|
181
|
+
*/
|
|
182
|
+
initializeComputed() {
|
|
183
|
+
Object.keys(this.computed).forEach(key => {
|
|
184
|
+
Object.defineProperty(this, key, {
|
|
185
|
+
get: () => {
|
|
186
|
+
if (!this.computedCache.has(key)) {
|
|
187
|
+
const value = this.computed[key].call(this);
|
|
188
|
+
this.computedCache.set(key, value);
|
|
189
|
+
}
|
|
190
|
+
return this.computedCache.get(key);
|
|
191
|
+
},
|
|
192
|
+
enumerable: true
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Handle state changes
|
|
198
|
+
*/
|
|
199
|
+
onStateChange() {
|
|
200
|
+
if (this.isDestroyed)
|
|
201
|
+
return;
|
|
202
|
+
// Clear computed cache
|
|
203
|
+
this.computedCache.clear();
|
|
204
|
+
// Trigger update if mounted
|
|
205
|
+
if (this.isMounted) {
|
|
206
|
+
this.update();
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Call lifecycle hook
|
|
211
|
+
*/
|
|
212
|
+
callHook(hookName, ...args) {
|
|
213
|
+
try {
|
|
214
|
+
if (this.hooks[hookName]) {
|
|
215
|
+
return this.hooks[hookName].call(this, ...args);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
this.handleError(error, `${hookName} hook`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Handle component errors
|
|
224
|
+
*/
|
|
225
|
+
handleError(error) {
|
|
226
|
+
console.error(`Component Error in ${this.name}:`, error);
|
|
227
|
+
// Call error hook
|
|
228
|
+
this.callHook('errorCaptured', error);
|
|
229
|
+
// Propagate to parent
|
|
230
|
+
if (this.parent && this.parent.handleError) {
|
|
231
|
+
this.parent.handleError(error, `${this.name} -> ${context}`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Render the component
|
|
236
|
+
*/
|
|
237
|
+
render(props = {}) {
|
|
238
|
+
if (this.isDestroyed) {
|
|
239
|
+
console.warn(`Attempting to render destroyed component: ${this.name}`);
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
try {
|
|
243
|
+
// Update metadata
|
|
244
|
+
const metadata = COMPONENT_METADATA.get(this);
|
|
245
|
+
if (metadata) {
|
|
246
|
+
metadata.renderCount++;
|
|
247
|
+
}
|
|
248
|
+
// Set props
|
|
249
|
+
this.props = { ...props };
|
|
250
|
+
// Call render method
|
|
251
|
+
if (typeof this.definition.render === 'function') {
|
|
252
|
+
this.rendered = this.definition.render.call(this, this.props, this.state.get());
|
|
253
|
+
}
|
|
254
|
+
else if (typeof this.definition.template !== 'undefined') {
|
|
255
|
+
this.rendered = this.processTemplate(this.definition.template, this.props, this.state.get());
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
throw new Error(`Component ${this.name} must have either render method or template`);
|
|
259
|
+
}
|
|
260
|
+
// Validate rendered output
|
|
261
|
+
if (this.rendered !== null) {
|
|
262
|
+
validateComponent(this.rendered, this.name);
|
|
263
|
+
}
|
|
264
|
+
return this.rendered;
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
this.handleError(error);
|
|
268
|
+
return { div: { className: 'component-error', text: `Error in ${this.name}` } };
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Process template with data
|
|
273
|
+
*/
|
|
274
|
+
processTemplate(template, props, state) {
|
|
275
|
+
if (typeof template === 'function') {
|
|
276
|
+
return template.call(this, props, state);
|
|
277
|
+
}
|
|
278
|
+
if (typeof template === 'string') {
|
|
279
|
+
// Simple string interpolation
|
|
280
|
+
return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
|
|
281
|
+
return props[key] || state[key] || '';
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
// Clone template object and process
|
|
285
|
+
const processed = deepClone(template);
|
|
286
|
+
this.interpolateObject(processed, { ...props, ...state });
|
|
287
|
+
return processed;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Interpolate object with data
|
|
291
|
+
*/
|
|
292
|
+
interpolateObject(obj, data) {
|
|
293
|
+
if (typeof obj === 'string') {
|
|
294
|
+
return obj.replace(/\{\{(\w+)\}\}/g, (match, key) => data[key] || '');
|
|
295
|
+
}
|
|
296
|
+
if (Array.isArray(obj)) {
|
|
297
|
+
return obj.map(item => this.interpolateObject(item, data));
|
|
298
|
+
}
|
|
299
|
+
if (obj && typeof obj === 'object') {
|
|
300
|
+
Object.keys(obj).forEach(key => {
|
|
301
|
+
obj[key] = this.interpolateObject(obj[key], data);
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
return obj;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Mount the component
|
|
308
|
+
*/
|
|
309
|
+
mount() {
|
|
310
|
+
if (this.isMounted || this.isDestroyed)
|
|
311
|
+
return this;
|
|
312
|
+
this.callHook('beforeMount');
|
|
313
|
+
this.isMounted = true;
|
|
314
|
+
this.callHook('mounted');
|
|
315
|
+
return this;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Update the component
|
|
319
|
+
*/
|
|
320
|
+
update() {
|
|
321
|
+
if (!this.isMounted || this.isDestroyed)
|
|
322
|
+
return this;
|
|
323
|
+
const metadata = COMPONENT_METADATA.get(this);
|
|
324
|
+
if (metadata) {
|
|
325
|
+
metadata.updateCount++;
|
|
326
|
+
}
|
|
327
|
+
this.callHook('beforeUpdate');
|
|
328
|
+
// Re-render will happen automatically via state subscription
|
|
329
|
+
this.callHook('updated');
|
|
330
|
+
return this;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Destroy the component
|
|
334
|
+
*/
|
|
335
|
+
destroy() {
|
|
336
|
+
if (this.isDestroyed)
|
|
337
|
+
return this;
|
|
338
|
+
this.callHook('beforeDestroy');
|
|
339
|
+
// Cleanup
|
|
340
|
+
if (this.unsubscribeState) {
|
|
341
|
+
this.unsubscribeState();
|
|
342
|
+
}
|
|
343
|
+
// Destroy children
|
|
344
|
+
this.children.forEach(child => {
|
|
345
|
+
if (child.destroy) {
|
|
346
|
+
child.destroy();
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
this.isMounted = false;
|
|
350
|
+
this.isDestroyed = true;
|
|
351
|
+
this.children = [];
|
|
352
|
+
this.parent = null;
|
|
353
|
+
this.callHook('destroyed');
|
|
354
|
+
return this;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Get component metadata
|
|
358
|
+
*/
|
|
359
|
+
getMetadata() {
|
|
360
|
+
return COMPONENT_METADATA.get(this) || {};
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Clone component with new props/state
|
|
364
|
+
*/
|
|
365
|
+
clone(overrides = {}) {
|
|
366
|
+
const newDefinition = { ...this.definition, ...overrides };
|
|
367
|
+
return new Component(newDefinition);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Create a functional component
|
|
372
|
+
*/
|
|
373
|
+
export function createComponent(definition) {
|
|
374
|
+
if (typeof definition === 'function') {
|
|
375
|
+
// Convert function to component definition
|
|
376
|
+
definition = {
|
|
377
|
+
name: definition.name || 'FunctionalComponent',
|
|
378
|
+
render: definition
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
return new Component(definition);
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Create a component factory
|
|
385
|
+
*/
|
|
386
|
+
export function defineComponent(definition) {
|
|
387
|
+
const componentFactory = (_props) => {
|
|
388
|
+
const component = new Component(definition);
|
|
389
|
+
return component.render(_props);
|
|
390
|
+
};
|
|
391
|
+
// Add static properties
|
|
392
|
+
componentFactory.componentName = definition.name || 'Component';
|
|
393
|
+
componentFactory.definition = definition;
|
|
394
|
+
return componentFactory;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Register a global component
|
|
398
|
+
*/
|
|
399
|
+
export function registerComponent(name, definition) {
|
|
400
|
+
if (COMPONENT_REGISTRY.has(name)) {
|
|
401
|
+
console.warn(`Component ${name} is already registered. Overriding.`);
|
|
402
|
+
}
|
|
403
|
+
const component = typeof definition === 'function' ?
|
|
404
|
+
defineComponent({ name, render: definition }) :
|
|
405
|
+
defineComponent(definition);
|
|
406
|
+
COMPONENT_REGISTRY.set(name, component);
|
|
407
|
+
return component;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Get a registered component
|
|
411
|
+
*/
|
|
412
|
+
export function getComponent(name) {
|
|
413
|
+
return COMPONENT_REGISTRY.get(name);
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Get all registered components
|
|
417
|
+
*/
|
|
418
|
+
export function getRegisteredComponents() {
|
|
419
|
+
return new Map(COMPONENT_REGISTRY);
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Create a higher-order component
|
|
423
|
+
*/
|
|
424
|
+
export function createHOC(enhancer) {
|
|
425
|
+
return (WrappedComponent) => {
|
|
426
|
+
return defineComponent({
|
|
427
|
+
name: `HOC(${WrappedComponent.componentName || 'Component'})`,
|
|
428
|
+
render(_props) {
|
|
429
|
+
return enhancer(WrappedComponent, _props);
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Mixin functionality
|
|
436
|
+
*/
|
|
437
|
+
export function createMixin(mixin) {
|
|
438
|
+
return (componentDefinition) => {
|
|
439
|
+
return {
|
|
440
|
+
...mixin,
|
|
441
|
+
...componentDefinition,
|
|
442
|
+
// Merge methods
|
|
443
|
+
methods: {
|
|
444
|
+
...mixin.methods,
|
|
445
|
+
...componentDefinition.methods
|
|
446
|
+
},
|
|
447
|
+
// Merge computed
|
|
448
|
+
computed: {
|
|
449
|
+
...mixin.computed,
|
|
450
|
+
...componentDefinition.computed
|
|
451
|
+
},
|
|
452
|
+
// Merge watchers
|
|
453
|
+
watch: {
|
|
454
|
+
...mixin.watch,
|
|
455
|
+
...componentDefinition.watch
|
|
456
|
+
},
|
|
457
|
+
// Merge state
|
|
458
|
+
state: {
|
|
459
|
+
...mixin.state,
|
|
460
|
+
...componentDefinition.state
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Component composition utilities
|
|
467
|
+
*/
|
|
468
|
+
export const compose = {
|
|
469
|
+
/**
|
|
470
|
+
* Combine multiple components
|
|
471
|
+
*/
|
|
472
|
+
combine: (...components) => {
|
|
473
|
+
return defineComponent({
|
|
474
|
+
name: 'ComposedComponent',
|
|
475
|
+
render(_props) {
|
|
476
|
+
return components.map(comp => typeof comp === 'function' ? comp(_props) : comp);
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
},
|
|
480
|
+
/**
|
|
481
|
+
* Conditionally render components
|
|
482
|
+
*/
|
|
483
|
+
conditional: (condition, trueComponent, falseComponent = null) => {
|
|
484
|
+
return defineComponent({
|
|
485
|
+
name: 'ConditionalComponent',
|
|
486
|
+
render(_props) {
|
|
487
|
+
const shouldRender = typeof condition === 'function' ?
|
|
488
|
+
condition(_props) : condition;
|
|
489
|
+
if (shouldRender) {
|
|
490
|
+
return typeof trueComponent === 'function' ?
|
|
491
|
+
trueComponent(_props) : trueComponent;
|
|
492
|
+
}
|
|
493
|
+
else if (falseComponent) {
|
|
494
|
+
return typeof falseComponent === 'function' ?
|
|
495
|
+
falseComponent(_props) : falseComponent;
|
|
496
|
+
}
|
|
497
|
+
return null;
|
|
498
|
+
}
|
|
499
|
+
});
|
|
500
|
+
},
|
|
501
|
+
/**
|
|
502
|
+
* Loop over data to render components
|
|
503
|
+
*/
|
|
504
|
+
loop: (data, itemComponent, keyFn = (item, index) => index) => {
|
|
505
|
+
return defineComponent({
|
|
506
|
+
name: 'LoopComponent',
|
|
507
|
+
render(_props) {
|
|
508
|
+
const items = typeof data === 'function' ? data(_props) : data;
|
|
509
|
+
if (!Array.isArray(items)) {
|
|
510
|
+
console.warn('Loop component expects array data');
|
|
511
|
+
return null;
|
|
512
|
+
}
|
|
513
|
+
return items.map((item, index) => {
|
|
514
|
+
const key = keyFn(item, index);
|
|
515
|
+
const itemProps = { ...props, item, index, key };
|
|
516
|
+
return typeof itemComponent === 'function' ?
|
|
517
|
+
itemComponent(itemProps) : itemComponent;
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
/**
|
|
524
|
+
* Component utilities
|
|
525
|
+
*/
|
|
526
|
+
export const componentUtils = {
|
|
527
|
+
/**
|
|
528
|
+
* Get component tree information
|
|
529
|
+
*/
|
|
530
|
+
getComponentTree: (component) => {
|
|
531
|
+
const tree = {
|
|
532
|
+
name: component.name || 'Unknown',
|
|
533
|
+
props: component.props || {},
|
|
534
|
+
state: component.state ? component.state.get() : {},
|
|
535
|
+
children: [],
|
|
536
|
+
metadata: component.getMetadata ? component.getMetadata() : {}
|
|
537
|
+
};
|
|
538
|
+
if (component.children && component.children.length > 0) {
|
|
539
|
+
tree.children = component.children.map(child => componentUtils.getComponentTree(child));
|
|
540
|
+
}
|
|
541
|
+
return tree;
|
|
542
|
+
},
|
|
543
|
+
/**
|
|
544
|
+
* Find component by name in tree
|
|
545
|
+
*/
|
|
546
|
+
findComponent: (component, name) => {
|
|
547
|
+
if (component.name === name) {
|
|
548
|
+
return component;
|
|
549
|
+
}
|
|
550
|
+
if (component.children) {
|
|
551
|
+
for (const child of component.children) {
|
|
552
|
+
const found = componentUtils.findComponent(child, name);
|
|
553
|
+
if (found)
|
|
554
|
+
return found;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return null;
|
|
558
|
+
},
|
|
559
|
+
/**
|
|
560
|
+
* Get component performance metrics
|
|
561
|
+
*/
|
|
562
|
+
getPerformanceMetrics: (component) => {
|
|
563
|
+
const metadata = component.getMetadata ? component.getMetadata() : {};
|
|
564
|
+
return {
|
|
565
|
+
renderCount: metadata.renderCount || 0,
|
|
566
|
+
updateCount: metadata.updateCount || 0,
|
|
567
|
+
createdAt: metadata.createdAt || Date.now(),
|
|
568
|
+
age: Date.now() - (metadata.createdAt || Date.now())
|
|
569
|
+
};
|
|
570
|
+
},
|
|
571
|
+
/**
|
|
572
|
+
* Validate component definition
|
|
573
|
+
*/
|
|
574
|
+
validateDefinition: (definition) => {
|
|
575
|
+
const errors = [];
|
|
576
|
+
if (!definition || typeof definition !== 'object') {
|
|
577
|
+
errors.push('Definition must be an object');
|
|
578
|
+
return errors;
|
|
579
|
+
}
|
|
580
|
+
if (!definition.render && !definition.template) {
|
|
581
|
+
errors.push('Component must have either render method or template');
|
|
582
|
+
}
|
|
583
|
+
if (definition.render && typeof definition.render !== 'function') {
|
|
584
|
+
errors.push('render must be a function');
|
|
585
|
+
}
|
|
586
|
+
if (definition.methods && typeof definition.methods !== 'object') {
|
|
587
|
+
errors.push('methods must be an object');
|
|
588
|
+
}
|
|
589
|
+
if (definition.computed && typeof definition.computed !== 'object') {
|
|
590
|
+
errors.push('computed must be an object');
|
|
591
|
+
}
|
|
592
|
+
if (definition.watch && typeof definition.watch !== 'object') {
|
|
593
|
+
errors.push('watch must be an object');
|
|
594
|
+
}
|
|
595
|
+
return errors;
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
/**
|
|
599
|
+
* Performance monitoring for components
|
|
600
|
+
*/
|
|
601
|
+
if (performanceMonitor) {
|
|
602
|
+
const originalRender = Component.prototype.render;
|
|
603
|
+
Component.prototype.render = function (...args) {
|
|
604
|
+
const start = performance.now();
|
|
605
|
+
const result = originalRender.apply(this, args);
|
|
606
|
+
const duration = performance.now() - start;
|
|
607
|
+
performanceMonitor.recordRender('component', duration, {
|
|
608
|
+
name: this.name,
|
|
609
|
+
propsSize: JSON.stringify(this.props || {}).length,
|
|
610
|
+
hasState: Object.keys(this.state?.get() || {}).length > 0
|
|
611
|
+
});
|
|
612
|
+
return result;
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Development helpers
|
|
617
|
+
*/
|
|
618
|
+
export const dev = {
|
|
619
|
+
/**
|
|
620
|
+
* Get all component instances
|
|
621
|
+
*/
|
|
622
|
+
getAllComponents: () => {
|
|
623
|
+
return Array.from(COMPONENT_REGISTRY.entries()).map(([name, factory]) => ({
|
|
624
|
+
name,
|
|
625
|
+
factory,
|
|
626
|
+
definition: factory.definition
|
|
627
|
+
}));
|
|
628
|
+
},
|
|
629
|
+
/**
|
|
630
|
+
* Clear component registry
|
|
631
|
+
*/
|
|
632
|
+
clearRegistry: () => {
|
|
633
|
+
COMPONENT_REGISTRY.clear();
|
|
634
|
+
},
|
|
635
|
+
/**
|
|
636
|
+
* Get component registry stats
|
|
637
|
+
*/
|
|
638
|
+
getRegistryStats: () => ({
|
|
639
|
+
totalComponents: COMPONENT_REGISTRY.size,
|
|
640
|
+
components: Array.from(COMPONENT_REGISTRY.keys())
|
|
641
|
+
})
|
|
642
|
+
};
|
|
643
|
+
/**
|
|
644
|
+
* Create lazy-evaluated properties and components
|
|
645
|
+
* Defers computation until actually needed, with optional caching
|
|
646
|
+
*/
|
|
647
|
+
export function lazy(factory, options = {}) {
|
|
648
|
+
const { cache = true, // Cache the result after first evaluation
|
|
649
|
+
timeout = null, // Optional timeout for evaluation
|
|
650
|
+
fallback = null, // Fallback value if evaluation fails
|
|
651
|
+
onError = null, // Error handler
|
|
652
|
+
dependencies = [] // Dependencies that invalidate cache
|
|
653
|
+
} = options;
|
|
654
|
+
let cached = false;
|
|
655
|
+
let cachedValue = null;
|
|
656
|
+
let isEvaluating = false;
|
|
657
|
+
let lastDependencyHash = null;
|
|
658
|
+
const lazyWrapper = {
|
|
659
|
+
// Mark as lazy for identification
|
|
660
|
+
__isLazy: true,
|
|
661
|
+
__factory: factory,
|
|
662
|
+
__options: options,
|
|
663
|
+
// Evaluation method
|
|
664
|
+
evaluate(...args) {
|
|
665
|
+
// Prevent recursive evaluation
|
|
666
|
+
if (isEvaluating) {
|
|
667
|
+
console.warn('Lazy evaluation cycle detected, returning fallback');
|
|
668
|
+
return fallback;
|
|
669
|
+
}
|
|
670
|
+
// Check dependency changes
|
|
671
|
+
if (cache && dependencies.length > 0) {
|
|
672
|
+
const currentHash = hashDependencies(dependencies);
|
|
673
|
+
if (lastDependencyHash !== null && lastDependencyHash !== currentHash) {
|
|
674
|
+
cached = false;
|
|
675
|
+
cachedValue = null;
|
|
676
|
+
}
|
|
677
|
+
lastDependencyHash = currentHash;
|
|
678
|
+
}
|
|
679
|
+
// Return cached value if available
|
|
680
|
+
if (cache && cached) {
|
|
681
|
+
return cachedValue;
|
|
682
|
+
}
|
|
683
|
+
isEvaluating = true;
|
|
684
|
+
try {
|
|
685
|
+
let result;
|
|
686
|
+
// Handle timeout
|
|
687
|
+
if (timeout) {
|
|
688
|
+
result = evaluateWithTimeout(factory, timeout, args, fallback);
|
|
689
|
+
}
|
|
690
|
+
else {
|
|
691
|
+
result = typeof factory === 'function' ? factory(...args) : factory;
|
|
692
|
+
}
|
|
693
|
+
// Handle promises
|
|
694
|
+
if (result && typeof result.then === 'function') {
|
|
695
|
+
return result.catch(error => {
|
|
696
|
+
if (onError)
|
|
697
|
+
onError(error);
|
|
698
|
+
return fallback;
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
// Cache successful result
|
|
702
|
+
if (cache) {
|
|
703
|
+
cached = true;
|
|
704
|
+
cachedValue = result;
|
|
705
|
+
}
|
|
706
|
+
return result;
|
|
707
|
+
}
|
|
708
|
+
catch (error) {
|
|
709
|
+
if (onError) {
|
|
710
|
+
onError(error);
|
|
711
|
+
}
|
|
712
|
+
else {
|
|
713
|
+
console.error('Lazy evaluation error:', error);
|
|
714
|
+
}
|
|
715
|
+
return fallback;
|
|
716
|
+
}
|
|
717
|
+
finally {
|
|
718
|
+
isEvaluating = false;
|
|
719
|
+
}
|
|
720
|
+
},
|
|
721
|
+
// Force re-evaluation
|
|
722
|
+
invalidate() {
|
|
723
|
+
cached = false;
|
|
724
|
+
cachedValue = null;
|
|
725
|
+
lastDependencyHash = null;
|
|
726
|
+
return this;
|
|
727
|
+
},
|
|
728
|
+
// Check if evaluated
|
|
729
|
+
isEvaluated() {
|
|
730
|
+
return cached;
|
|
731
|
+
},
|
|
732
|
+
// Get cached value without evaluation
|
|
733
|
+
getCachedValue() {
|
|
734
|
+
return cachedValue;
|
|
735
|
+
},
|
|
736
|
+
// Transform the lazy value
|
|
737
|
+
map(transform) {
|
|
738
|
+
return lazy((...args) => {
|
|
739
|
+
const value = this.evaluate(...args);
|
|
740
|
+
return transform(value);
|
|
741
|
+
}, { ...options, cache: false }); // Don't double-cache
|
|
742
|
+
},
|
|
743
|
+
// Chain lazy evaluations
|
|
744
|
+
flatMap(transform) {
|
|
745
|
+
return lazy((...args) => {
|
|
746
|
+
const value = this.evaluate(...args);
|
|
747
|
+
const transformed = transform(value);
|
|
748
|
+
if (isLazy(transformed)) {
|
|
749
|
+
return transformed.evaluate(...args);
|
|
750
|
+
}
|
|
751
|
+
return transformed;
|
|
752
|
+
}, { ...options, cache: false });
|
|
753
|
+
},
|
|
754
|
+
// Convert to string for debugging
|
|
755
|
+
toString() {
|
|
756
|
+
return `[Lazy${cached ? ' (cached)' : ''}]`;
|
|
757
|
+
},
|
|
758
|
+
// JSON serialization
|
|
759
|
+
toJSON() {
|
|
760
|
+
return this.evaluate();
|
|
761
|
+
}
|
|
762
|
+
};
|
|
763
|
+
return lazyWrapper;
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Check if value is lazy
|
|
767
|
+
*/
|
|
768
|
+
export function isLazy(value) {
|
|
769
|
+
return value && typeof value === 'object' && value.__isLazy === true;
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Evaluate lazy values recursively in an object
|
|
773
|
+
*/
|
|
774
|
+
export function evaluateLazy(obj, ...args) {
|
|
775
|
+
if (isLazy(obj)) {
|
|
776
|
+
return obj.evaluate(...args);
|
|
777
|
+
}
|
|
778
|
+
if (Array.isArray(obj)) {
|
|
779
|
+
return obj.map(item => evaluateLazy(item, ...args));
|
|
780
|
+
}
|
|
781
|
+
if (obj && typeof obj === 'object') {
|
|
782
|
+
const result = {};
|
|
783
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
784
|
+
result[key] = evaluateLazy(value, ...args);
|
|
785
|
+
}
|
|
786
|
+
return result;
|
|
787
|
+
}
|
|
788
|
+
return obj;
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* Create lazy component
|
|
792
|
+
*/
|
|
793
|
+
export function lazyComponent(componentFactory, options = {}) {
|
|
794
|
+
return lazy(componentFactory, {
|
|
795
|
+
cache: true,
|
|
796
|
+
fallback: { div: { text: 'Loading component...' } },
|
|
797
|
+
...options
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Create lazy import for dynamic imports
|
|
802
|
+
*/
|
|
803
|
+
export function lazyImport(importPromise, options = {}) {
|
|
804
|
+
return lazy(async () => {
|
|
805
|
+
const module = await importPromise;
|
|
806
|
+
return module.default || module;
|
|
807
|
+
}, {
|
|
808
|
+
cache: true,
|
|
809
|
+
fallback: { div: { text: 'Loading...' } },
|
|
810
|
+
...options
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* Create lazy computed property
|
|
815
|
+
*/
|
|
816
|
+
export function lazyComputed(computeFn, dependencies = [], options = {}) {
|
|
817
|
+
return lazy(computeFn, {
|
|
818
|
+
cache: true,
|
|
819
|
+
dependencies,
|
|
820
|
+
...options
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* Batch evaluate multiple lazy values
|
|
825
|
+
*/
|
|
826
|
+
export function batchEvaluate(lazyValues, ...args) {
|
|
827
|
+
const results = {};
|
|
828
|
+
const promises = [];
|
|
829
|
+
Object.entries(lazyValues).forEach(([key, lazyValue]) => {
|
|
830
|
+
if (isLazy(lazyValue)) {
|
|
831
|
+
const result = lazyValue.evaluate(...args);
|
|
832
|
+
if (result && typeof result.then === 'function') {
|
|
833
|
+
promises.push(result.then(value => ({ key, value }))
|
|
834
|
+
.catch(error => ({ key, error })));
|
|
835
|
+
}
|
|
836
|
+
else {
|
|
837
|
+
results[key] = result;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
else {
|
|
841
|
+
results[key] = lazyValue;
|
|
842
|
+
}
|
|
843
|
+
});
|
|
844
|
+
if (promises.length === 0) {
|
|
845
|
+
return results;
|
|
846
|
+
}
|
|
847
|
+
return Promise.all(promises).then(asyncResults => {
|
|
848
|
+
asyncResults.forEach(({ key, value, error }) => {
|
|
849
|
+
if (error) {
|
|
850
|
+
console.error(`Batch evaluation error for ${key}:`, error);
|
|
851
|
+
results[key] = null;
|
|
852
|
+
}
|
|
853
|
+
else {
|
|
854
|
+
results[key] = value;
|
|
855
|
+
}
|
|
856
|
+
});
|
|
857
|
+
return results;
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* Helper function to hash dependencies
|
|
862
|
+
*/
|
|
863
|
+
function hashDependencies(dependencies) {
|
|
864
|
+
return dependencies.map(dep => {
|
|
865
|
+
if (typeof dep === 'function') {
|
|
866
|
+
return dep.toString();
|
|
867
|
+
}
|
|
868
|
+
return JSON.stringify(dep);
|
|
869
|
+
}).join('|');
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* Helper function to evaluate with timeout
|
|
873
|
+
*/
|
|
874
|
+
function evaluateWithTimeout(factory, timeout, args, fallback) {
|
|
875
|
+
return new Promise((resolve, reject) => {
|
|
876
|
+
const timer = setTimeout(() => {
|
|
877
|
+
reject(new Error(`Lazy evaluation timeout after ${timeout}ms`));
|
|
878
|
+
}, timeout);
|
|
879
|
+
try {
|
|
880
|
+
const result = factory(...args);
|
|
881
|
+
if (result && typeof result.then === 'function') {
|
|
882
|
+
result
|
|
883
|
+
.then(value => {
|
|
884
|
+
clearTimeout(timer);
|
|
885
|
+
resolve(value);
|
|
886
|
+
})
|
|
887
|
+
.catch(error => {
|
|
888
|
+
clearTimeout(timer);
|
|
889
|
+
reject(error);
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
else {
|
|
893
|
+
clearTimeout(timer);
|
|
894
|
+
resolve(result);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
catch (error) {
|
|
898
|
+
clearTimeout(timer);
|
|
899
|
+
reject(error);
|
|
900
|
+
}
|
|
901
|
+
}).catch(() => fallback);
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* Lazy evaluation utilities
|
|
905
|
+
*/
|
|
906
|
+
export const lazyUtils = {
|
|
907
|
+
/**
|
|
908
|
+
* Create a lazy chain of transformations
|
|
909
|
+
*/
|
|
910
|
+
chain: (...transformations) => {
|
|
911
|
+
return lazy((initialValue) => {
|
|
912
|
+
return transformations.reduce((value, transform) => {
|
|
913
|
+
if (isLazy(value)) {
|
|
914
|
+
value = value.evaluate();
|
|
915
|
+
}
|
|
916
|
+
return transform(value);
|
|
917
|
+
}, initialValue);
|
|
918
|
+
});
|
|
919
|
+
},
|
|
920
|
+
/**
|
|
921
|
+
* Create conditional lazy evaluation
|
|
922
|
+
*/
|
|
923
|
+
conditional: (condition, trueFactory, falseFactory) => {
|
|
924
|
+
return lazy((...args) => {
|
|
925
|
+
const shouldEvaluateTrue = typeof condition === 'function' ?
|
|
926
|
+
condition(...args) : condition;
|
|
927
|
+
const factory = shouldEvaluateTrue ? trueFactory : falseFactory;
|
|
928
|
+
return typeof factory === 'function' ? factory(...args) : factory;
|
|
929
|
+
});
|
|
930
|
+
},
|
|
931
|
+
/**
|
|
932
|
+
* Memoize a function with lazy evaluation
|
|
933
|
+
*/
|
|
934
|
+
memoize: (fn, keyFn = (...args) => JSON.stringify(args)) => {
|
|
935
|
+
const cache = new Map();
|
|
936
|
+
return lazy((...args) => {
|
|
937
|
+
const key = keyFn(...args);
|
|
938
|
+
if (cache.has(key)) {
|
|
939
|
+
return cache.get(key);
|
|
940
|
+
}
|
|
941
|
+
const result = fn(...args);
|
|
942
|
+
cache.set(key, result);
|
|
943
|
+
return result;
|
|
944
|
+
}, { cache: false }); // Handle caching internally
|
|
945
|
+
},
|
|
946
|
+
/**
|
|
947
|
+
* Create lazy value from async function
|
|
948
|
+
*/
|
|
949
|
+
async: (asyncFn, options = {}) => {
|
|
950
|
+
return lazy(asyncFn, {
|
|
951
|
+
cache: true,
|
|
952
|
+
fallback: options.loading || { div: { text: 'Loading...' } },
|
|
953
|
+
onError: options.onError,
|
|
954
|
+
...options
|
|
955
|
+
});
|
|
956
|
+
},
|
|
957
|
+
/**
|
|
958
|
+
* Create lazy array with lazy items
|
|
959
|
+
*/
|
|
960
|
+
array: (items = []) => {
|
|
961
|
+
return lazy(() => items.map(item => isLazy(item) ? item.evaluate() : item));
|
|
962
|
+
},
|
|
963
|
+
/**
|
|
964
|
+
* Create lazy object with lazy properties
|
|
965
|
+
*/
|
|
966
|
+
object: (obj = {}) => {
|
|
967
|
+
return lazy(() => {
|
|
968
|
+
const result = {};
|
|
969
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
970
|
+
result[key] = isLazy(value) ? value.evaluate() : value;
|
|
971
|
+
}
|
|
972
|
+
return result;
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
};
|
|
976
|
+
/**
|
|
977
|
+
* Memoization utilities for caching function results and component renders
|
|
978
|
+
*/
|
|
979
|
+
/**
|
|
980
|
+
* Enhanced memoization with multiple caching strategies
|
|
981
|
+
*/
|
|
982
|
+
export function memo(fn, options = {}) {
|
|
983
|
+
const {
|
|
984
|
+
// Caching strategy
|
|
985
|
+
strategy = 'lru', // 'lru', 'ttl', 'weak', 'simple'
|
|
986
|
+
maxSize = 100, // Maximum cache entries
|
|
987
|
+
ttl = null, // Time to live in milliseconds
|
|
988
|
+
// Key generation
|
|
989
|
+
keyFn = null, // Custom key function
|
|
990
|
+
keySerializer = JSON.stringify, // Default serialization
|
|
991
|
+
// Comparison
|
|
992
|
+
// eslint-disable-next-line no-unused-vars
|
|
993
|
+
compareFn = null, // Custom equality comparison
|
|
994
|
+
// eslint-disable-next-line no-unused-vars
|
|
995
|
+
shallow = false, // Shallow comparison for objects
|
|
996
|
+
// Lifecycle hooks
|
|
997
|
+
onHit = null, // Called on cache hit
|
|
998
|
+
onMiss = null, // Called on cache miss
|
|
999
|
+
onEvict = null, // Called when item evicted
|
|
1000
|
+
// Performance
|
|
1001
|
+
stats = false, // Track hit/miss statistics
|
|
1002
|
+
// Development
|
|
1003
|
+
debug = false // Debug logging
|
|
1004
|
+
} = options;
|
|
1005
|
+
// Choose cache implementation based on strategy
|
|
1006
|
+
let cache;
|
|
1007
|
+
const stats_data = stats ? { hits: 0, misses: 0, evictions: 0 } : null;
|
|
1008
|
+
switch (strategy) {
|
|
1009
|
+
case 'lru':
|
|
1010
|
+
cache = new LRUCache(maxSize, { onEvict: onEvict });
|
|
1011
|
+
break;
|
|
1012
|
+
case 'ttl':
|
|
1013
|
+
cache = new TTLCache(ttl, { onEvict: onEvict });
|
|
1014
|
+
break;
|
|
1015
|
+
case 'weak':
|
|
1016
|
+
cache = new WeakMap();
|
|
1017
|
+
break;
|
|
1018
|
+
default:
|
|
1019
|
+
cache = new Map();
|
|
1020
|
+
}
|
|
1021
|
+
// Generate cache key
|
|
1022
|
+
const generateKey = keyFn || ((...args) => {
|
|
1023
|
+
if (args.length === 0)
|
|
1024
|
+
return '__empty__';
|
|
1025
|
+
if (args.length === 1)
|
|
1026
|
+
return keySerializer(args[0]);
|
|
1027
|
+
return keySerializer(args);
|
|
1028
|
+
});
|
|
1029
|
+
// Compare values for equality (inline via compareFn or defaults where used)
|
|
1030
|
+
const memoizedFn = (...args) => {
|
|
1031
|
+
const key = generateKey(...args);
|
|
1032
|
+
// Check cache hit
|
|
1033
|
+
if (cache.has(key)) {
|
|
1034
|
+
const cached = cache.get(key);
|
|
1035
|
+
// For TTL cache or custom validation
|
|
1036
|
+
if (cached && (!cached.expires || Date.now() < cached.expires)) {
|
|
1037
|
+
if (debug)
|
|
1038
|
+
console.log(`Memo cache hit for key: ${key}`);
|
|
1039
|
+
if (onHit)
|
|
1040
|
+
onHit(key, cached.value, args);
|
|
1041
|
+
if (stats_data)
|
|
1042
|
+
stats_data.hits++;
|
|
1043
|
+
return cached.value || cached;
|
|
1044
|
+
}
|
|
1045
|
+
else {
|
|
1046
|
+
// Expired entry
|
|
1047
|
+
cache.delete(key);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
// Cache miss - compute result
|
|
1051
|
+
if (debug)
|
|
1052
|
+
console.log(`Memo cache miss for key: ${key}`);
|
|
1053
|
+
if (onMiss)
|
|
1054
|
+
onMiss(key, args);
|
|
1055
|
+
if (stats_data)
|
|
1056
|
+
stats_data.misses++;
|
|
1057
|
+
const result = fn(...args);
|
|
1058
|
+
// Store in cache
|
|
1059
|
+
const cacheEntry = ttl ?
|
|
1060
|
+
{ value: result, expires: Date.now() + ttl } :
|
|
1061
|
+
result;
|
|
1062
|
+
cache.set(key, cacheEntry);
|
|
1063
|
+
return result;
|
|
1064
|
+
};
|
|
1065
|
+
// Attach utility methods
|
|
1066
|
+
memoizedFn.cache = cache;
|
|
1067
|
+
memoizedFn.clear = () => cache.clear();
|
|
1068
|
+
memoizedFn.delete = (key) => cache.delete(key);
|
|
1069
|
+
memoizedFn.has = (key) => cache.has(key);
|
|
1070
|
+
memoizedFn.size = () => cache.size;
|
|
1071
|
+
if (stats_data) {
|
|
1072
|
+
memoizedFn.stats = () => ({ ...stats_data });
|
|
1073
|
+
memoizedFn.resetStats = () => {
|
|
1074
|
+
stats_data.hits = 0;
|
|
1075
|
+
stats_data.misses = 0;
|
|
1076
|
+
stats_data.evictions = 0;
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
// Force recomputation for specific args
|
|
1080
|
+
memoizedFn.refresh = (...args) => {
|
|
1081
|
+
const key = generateKey(...args);
|
|
1082
|
+
cache.delete(key);
|
|
1083
|
+
return memoizedFn(...args);
|
|
1084
|
+
};
|
|
1085
|
+
return memoizedFn;
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Component-specific memoization
|
|
1089
|
+
*/
|
|
1090
|
+
export function memoComponent(component, options = {}) {
|
|
1091
|
+
const { propsEqual = shallowEqual, stateEqual = shallowEqual, name = component.name || 'AnonymousComponent' } = options;
|
|
1092
|
+
return memo((props = {}, state = {}, context = {}) => {
|
|
1093
|
+
return typeof component === 'function' ?
|
|
1094
|
+
component(props, state, _context) :
|
|
1095
|
+
component;
|
|
1096
|
+
}, {
|
|
1097
|
+
keyFn: (props, state) => {
|
|
1098
|
+
// Create key based on props and state
|
|
1099
|
+
return `${name}:${JSON.stringify(_props)}:${JSON.stringify(state)}`;
|
|
1100
|
+
},
|
|
1101
|
+
compareFn: (a, b) => {
|
|
1102
|
+
// Custom comparison for component args
|
|
1103
|
+
return propsEqual(a.props, b._props) &&
|
|
1104
|
+
stateEqual(a.state, b.state);
|
|
1105
|
+
},
|
|
1106
|
+
...options
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
/**
|
|
1110
|
+
* Async memoization with promise caching
|
|
1111
|
+
*/
|
|
1112
|
+
export function memoAsync(asyncFn, options = {}) {
|
|
1113
|
+
const promiseCache = new Map();
|
|
1114
|
+
const memoized = memo((...args) => {
|
|
1115
|
+
const key = options.keyFn ?
|
|
1116
|
+
options.keyFn(...args) :
|
|
1117
|
+
JSON.stringify(args);
|
|
1118
|
+
// Check if promise is already running
|
|
1119
|
+
if (promiseCache.has(key)) {
|
|
1120
|
+
return promiseCache.get(key);
|
|
1121
|
+
}
|
|
1122
|
+
// Start new async operation
|
|
1123
|
+
const promise = asyncFn(...args).catch(error => {
|
|
1124
|
+
// Remove failed promise from cache
|
|
1125
|
+
promiseCache.delete(key);
|
|
1126
|
+
throw error;
|
|
1127
|
+
});
|
|
1128
|
+
promiseCache.set(key, promise);
|
|
1129
|
+
// Clean up resolved promise
|
|
1130
|
+
promise.finally(() => {
|
|
1131
|
+
setTimeout(() => promiseCache.delete(key), 0);
|
|
1132
|
+
});
|
|
1133
|
+
return promise;
|
|
1134
|
+
}, options);
|
|
1135
|
+
// Clear both caches
|
|
1136
|
+
const originalClear = memoized.clear;
|
|
1137
|
+
memoized.clear = () => {
|
|
1138
|
+
originalClear();
|
|
1139
|
+
promiseCache.clear();
|
|
1140
|
+
};
|
|
1141
|
+
return memoized;
|
|
1142
|
+
}
|
|
1143
|
+
/**
|
|
1144
|
+
* LRU Cache implementation
|
|
1145
|
+
*/
|
|
1146
|
+
class LRUCache {
|
|
1147
|
+
constructor(maxSize = 100, options = {}) {
|
|
1148
|
+
this.maxSize = maxSize;
|
|
1149
|
+
this.cache = new Map();
|
|
1150
|
+
this.onEvict = options.onEvict;
|
|
1151
|
+
}
|
|
1152
|
+
get(key) {
|
|
1153
|
+
if (this.cache.has(key)) {
|
|
1154
|
+
// Move to end (most recently used)
|
|
1155
|
+
const value = this.cache.get(key);
|
|
1156
|
+
this.cache.delete(key);
|
|
1157
|
+
this.cache.set(key, value);
|
|
1158
|
+
return value;
|
|
1159
|
+
}
|
|
1160
|
+
return undefined;
|
|
1161
|
+
}
|
|
1162
|
+
set(key, value) {
|
|
1163
|
+
if (this.cache.has(key)) {
|
|
1164
|
+
// Update existing
|
|
1165
|
+
this.cache.delete(key);
|
|
1166
|
+
}
|
|
1167
|
+
else if (this.cache.size >= this.maxSize) {
|
|
1168
|
+
// Evict least recently used (first entry)
|
|
1169
|
+
const firstKey = this.cache.keys().next().value;
|
|
1170
|
+
const evicted = this.cache.get(firstKey);
|
|
1171
|
+
this.cache.delete(firstKey);
|
|
1172
|
+
if (this.onEvict) {
|
|
1173
|
+
this.onEvict(firstKey, evicted);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
this.cache.set(key, value);
|
|
1177
|
+
}
|
|
1178
|
+
has(key) {
|
|
1179
|
+
return this.cache.has(key);
|
|
1180
|
+
}
|
|
1181
|
+
delete(key) {
|
|
1182
|
+
return this.cache.delete(key);
|
|
1183
|
+
}
|
|
1184
|
+
clear() {
|
|
1185
|
+
this.cache.clear();
|
|
1186
|
+
}
|
|
1187
|
+
get size() {
|
|
1188
|
+
return this.cache.size;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
/**
|
|
1192
|
+
* TTL Cache implementation
|
|
1193
|
+
*/
|
|
1194
|
+
class TTLCache {
|
|
1195
|
+
constructor(ttl, options = {}) {
|
|
1196
|
+
this.ttl = ttl;
|
|
1197
|
+
this.cache = new Map();
|
|
1198
|
+
this.timers = new Map();
|
|
1199
|
+
this.onEvict = options.onEvict;
|
|
1200
|
+
}
|
|
1201
|
+
get(key) {
|
|
1202
|
+
if (this.cache.has(key)) {
|
|
1203
|
+
const _entry = this.cache.get(key);
|
|
1204
|
+
if (Date.now() < entry.expires) {
|
|
1205
|
+
return entry.value;
|
|
1206
|
+
}
|
|
1207
|
+
else {
|
|
1208
|
+
// Expired
|
|
1209
|
+
this.delete(key);
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
return undefined;
|
|
1213
|
+
}
|
|
1214
|
+
set(key, value) {
|
|
1215
|
+
// Clear existing timer
|
|
1216
|
+
if (this.timers.has(key)) {
|
|
1217
|
+
clearTimeout(this.timers.get(key));
|
|
1218
|
+
}
|
|
1219
|
+
const expires = Date.now() + this.ttl;
|
|
1220
|
+
this.cache.set(key, { value, expires });
|
|
1221
|
+
// Set expiration timer
|
|
1222
|
+
const timer = setTimeout(() => {
|
|
1223
|
+
this.delete(key);
|
|
1224
|
+
}, this.ttl);
|
|
1225
|
+
this.timers.set(key, timer);
|
|
1226
|
+
}
|
|
1227
|
+
has(key) {
|
|
1228
|
+
if (this.cache.has(key)) {
|
|
1229
|
+
const _entry = this.cache.get(key);
|
|
1230
|
+
return Date.now() < entry.expires;
|
|
1231
|
+
}
|
|
1232
|
+
return false;
|
|
1233
|
+
}
|
|
1234
|
+
delete(key) {
|
|
1235
|
+
const had = this.cache.has(key);
|
|
1236
|
+
if (had) {
|
|
1237
|
+
const _entry = this.cache.get(key);
|
|
1238
|
+
this.cache.delete(key);
|
|
1239
|
+
if (this.timers.has(key)) {
|
|
1240
|
+
clearTimeout(this.timers.get(key));
|
|
1241
|
+
this.timers.delete(key);
|
|
1242
|
+
}
|
|
1243
|
+
if (this.onEvict) {
|
|
1244
|
+
this.onEvict(key, entry.value);
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
return had;
|
|
1248
|
+
}
|
|
1249
|
+
clear() {
|
|
1250
|
+
// Clear all timers
|
|
1251
|
+
this.timers.forEach(timer => clearTimeout(timer));
|
|
1252
|
+
this.timers.clear();
|
|
1253
|
+
this.cache.clear();
|
|
1254
|
+
}
|
|
1255
|
+
get size() {
|
|
1256
|
+
return this.cache.size;
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
/**
|
|
1260
|
+
* Shallow equality check for objects
|
|
1261
|
+
*/
|
|
1262
|
+
function shallowEqual(a, b) {
|
|
1263
|
+
if (a === b)
|
|
1264
|
+
return true;
|
|
1265
|
+
if (!a || !b)
|
|
1266
|
+
return false;
|
|
1267
|
+
if (typeof a !== typeof b)
|
|
1268
|
+
return false;
|
|
1269
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
1270
|
+
if (a.length !== b.length)
|
|
1271
|
+
return false;
|
|
1272
|
+
return a.every((item, index) => item === b[index]);
|
|
1273
|
+
}
|
|
1274
|
+
if (typeof a === 'object') {
|
|
1275
|
+
const keysA = Object.keys(a);
|
|
1276
|
+
const keysB = Object.keys(b);
|
|
1277
|
+
if (keysA.length !== keysB.length)
|
|
1278
|
+
return false;
|
|
1279
|
+
return keysA.every(key => a[key] === b[key]);
|
|
1280
|
+
}
|
|
1281
|
+
return false;
|
|
1282
|
+
}
|
|
1283
|
+
/**
|
|
1284
|
+
* Memoization utilities
|
|
1285
|
+
*/
|
|
1286
|
+
export const memoUtils = {
|
|
1287
|
+
/**
|
|
1288
|
+
* Create a memo with automatic dependency tracking
|
|
1289
|
+
*/
|
|
1290
|
+
auto: (fn, dependencies = []) => {
|
|
1291
|
+
let lastDeps = null;
|
|
1292
|
+
let cached = null;
|
|
1293
|
+
let hasCached = false;
|
|
1294
|
+
return (...args) => {
|
|
1295
|
+
const currentDeps = dependencies.map(dep => typeof dep === 'function' ? dep() : dep);
|
|
1296
|
+
if (!hasCached || !shallowEqual(lastDeps, currentDeps)) {
|
|
1297
|
+
cached = fn(...args);
|
|
1298
|
+
lastDeps = currentDeps;
|
|
1299
|
+
hasCached = true;
|
|
1300
|
+
}
|
|
1301
|
+
return cached;
|
|
1302
|
+
};
|
|
1303
|
+
},
|
|
1304
|
+
/**
|
|
1305
|
+
* Memo with size-based eviction
|
|
1306
|
+
*/
|
|
1307
|
+
sized: (fn, maxSize = 50) => memo(fn, { strategy: 'lru', maxSize }),
|
|
1308
|
+
/**
|
|
1309
|
+
* Memo with time-based eviction
|
|
1310
|
+
*/
|
|
1311
|
+
timed: (fn, ttl = 5000) => memo(fn, { strategy: 'ttl', ttl }),
|
|
1312
|
+
/**
|
|
1313
|
+
* Weak memo (garbage collected with keys)
|
|
1314
|
+
*/
|
|
1315
|
+
weak: (fn) => memo(fn, { strategy: 'weak' }),
|
|
1316
|
+
/**
|
|
1317
|
+
* Create multiple memoized variants
|
|
1318
|
+
*/
|
|
1319
|
+
variants: (fn, configs) => {
|
|
1320
|
+
const variants = {};
|
|
1321
|
+
Object.entries(configs).forEach(([name, config]) => {
|
|
1322
|
+
variants[name] = memo(fn, config);
|
|
1323
|
+
});
|
|
1324
|
+
return variants;
|
|
1325
|
+
},
|
|
1326
|
+
/**
|
|
1327
|
+
* Conditional memoization
|
|
1328
|
+
*/
|
|
1329
|
+
conditional: (fn, shouldMemo = () => true) => {
|
|
1330
|
+
const memoized = memo(fn);
|
|
1331
|
+
return (...args) => {
|
|
1332
|
+
if (shouldMemo(...args)) {
|
|
1333
|
+
return memoized(...args);
|
|
1334
|
+
}
|
|
1335
|
+
return fn(...args);
|
|
1336
|
+
};
|
|
1337
|
+
},
|
|
1338
|
+
/**
|
|
1339
|
+
* Memo with custom storage
|
|
1340
|
+
*/
|
|
1341
|
+
custom: (fn, storage) => {
|
|
1342
|
+
return (...args) => {
|
|
1343
|
+
const key = JSON.stringify(args);
|
|
1344
|
+
if (storage.has(key)) {
|
|
1345
|
+
return storage.get(key);
|
|
1346
|
+
}
|
|
1347
|
+
const result = fn(...args);
|
|
1348
|
+
storage.set(key, result);
|
|
1349
|
+
return result;
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
};
|
|
1353
|
+
/**
|
|
1354
|
+
* Higher-order function utilities for component composition and prop manipulation
|
|
1355
|
+
*/
|
|
1356
|
+
/**
|
|
1357
|
+
* Enhanced withProps utility for component prop transformation and injection
|
|
1358
|
+
*/
|
|
1359
|
+
export function withProps(propsTransform, options = {}) {
|
|
1360
|
+
const {
|
|
1361
|
+
// Transformation options
|
|
1362
|
+
merge = true, // Merge with existing props vs replace
|
|
1363
|
+
override = false, // Allow overriding existing props
|
|
1364
|
+
validate = null, // Validation function for props
|
|
1365
|
+
// Caching and performance
|
|
1366
|
+
memoize = false, // Memoize the transformation
|
|
1367
|
+
memoOptions = {}, // Memoization options
|
|
1368
|
+
// Error handling
|
|
1369
|
+
onError = null, // Error handler for transformation
|
|
1370
|
+
fallbackProps = {}, // Fallback props on error
|
|
1371
|
+
// Development
|
|
1372
|
+
displayName = null, // Component name for debugging
|
|
1373
|
+
debug = false, // Debug logging
|
|
1374
|
+
// Lifecycle
|
|
1375
|
+
onPropsChange = null, // Called when props change
|
|
1376
|
+
shouldUpdate = null // Custom update logic
|
|
1377
|
+
} = options;
|
|
1378
|
+
return function withPropsHOC(WrappedComponent) {
|
|
1379
|
+
// Create the enhanced component
|
|
1380
|
+
function WithPropsComponent(originalProps = {}, state = {}, context = {}) {
|
|
1381
|
+
try {
|
|
1382
|
+
// Transform props
|
|
1383
|
+
let transformedProps;
|
|
1384
|
+
if (typeof propsTransform === 'function') {
|
|
1385
|
+
transformedProps = propsTransform(originalProps, state, _context);
|
|
1386
|
+
}
|
|
1387
|
+
else if (typeof propsTransform === 'object') {
|
|
1388
|
+
transformedProps = propsTransform;
|
|
1389
|
+
}
|
|
1390
|
+
else {
|
|
1391
|
+
transformedProps = {};
|
|
1392
|
+
}
|
|
1393
|
+
// Handle async transformations
|
|
1394
|
+
if (transformedProps && typeof transformedProps.then === 'function') {
|
|
1395
|
+
return transformedProps.then(resolved => {
|
|
1396
|
+
return processProps(resolved, originalProps, WrappedComponent, state, _context);
|
|
1397
|
+
}).catch(error => {
|
|
1398
|
+
if (onError)
|
|
1399
|
+
onError(error, originalProps);
|
|
1400
|
+
return processProps(fallbackProps, originalProps, WrappedComponent, state, _context);
|
|
1401
|
+
});
|
|
1402
|
+
}
|
|
1403
|
+
return processProps(transformedProps, originalProps, WrappedComponent, state, _context);
|
|
1404
|
+
}
|
|
1405
|
+
catch (error) {
|
|
1406
|
+
if (debug)
|
|
1407
|
+
console.error('withProps error:', error);
|
|
1408
|
+
if (onError)
|
|
1409
|
+
onError(error, originalProps);
|
|
1410
|
+
// Use fallback props
|
|
1411
|
+
return processProps(fallbackProps, originalProps, WrappedComponent, state, _context);
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
// Process and merge props
|
|
1415
|
+
function processProps(transformed, original, component, state, _context) {
|
|
1416
|
+
let finalProps;
|
|
1417
|
+
if (merge) {
|
|
1418
|
+
if (override) {
|
|
1419
|
+
finalProps = { ...original, ...transformed };
|
|
1420
|
+
}
|
|
1421
|
+
else {
|
|
1422
|
+
// Don't override existing props
|
|
1423
|
+
finalProps = { ...transformed, ...original };
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
else {
|
|
1427
|
+
finalProps = transformed;
|
|
1428
|
+
}
|
|
1429
|
+
// Validate final props
|
|
1430
|
+
if (validate && !validate(finalProps)) {
|
|
1431
|
+
if (debug)
|
|
1432
|
+
console.warn('Props validation failed:', finalProps);
|
|
1433
|
+
finalProps = { ...finalProps, ...fallbackProps };
|
|
1434
|
+
}
|
|
1435
|
+
// Check if should update
|
|
1436
|
+
if (shouldUpdate && !shouldUpdate(finalProps, original, state)) {
|
|
1437
|
+
return null; // Skip update
|
|
1438
|
+
}
|
|
1439
|
+
// Notify props change
|
|
1440
|
+
if (onPropsChange) {
|
|
1441
|
+
onPropsChange(finalProps, original, transformed);
|
|
1442
|
+
}
|
|
1443
|
+
if (debug) {
|
|
1444
|
+
console.log('withProps transformation:', {
|
|
1445
|
+
original,
|
|
1446
|
+
transformed,
|
|
1447
|
+
final: finalProps
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
// Render wrapped component
|
|
1451
|
+
return typeof component === 'function' ?
|
|
1452
|
+
component(finalProps, state, _context) :
|
|
1453
|
+
component;
|
|
1454
|
+
}
|
|
1455
|
+
// Set display name for debugging
|
|
1456
|
+
WithPropsComponent.displayName = displayName ||
|
|
1457
|
+
`withProps(${WrappedComponent.displayName || WrappedComponent.name || 'Component'})`;
|
|
1458
|
+
// Add metadata
|
|
1459
|
+
WithPropsComponent.__isHOC = true;
|
|
1460
|
+
WithPropsComponent.__wrappedComponent = WrappedComponent;
|
|
1461
|
+
WithPropsComponent.__transform = propsTransform;
|
|
1462
|
+
// Apply memoization if requested
|
|
1463
|
+
if (memoize) {
|
|
1464
|
+
return memo(WithPropsComponent, {
|
|
1465
|
+
keyFn: (props, state) => JSON.stringify({ props, state }),
|
|
1466
|
+
...memoOptions
|
|
1467
|
+
});
|
|
1468
|
+
}
|
|
1469
|
+
return WithPropsComponent;
|
|
1470
|
+
};
|
|
1471
|
+
}
|
|
1472
|
+
/**
|
|
1473
|
+
* Specialized withProps variants
|
|
1474
|
+
*/
|
|
1475
|
+
export const withPropsUtils = {
|
|
1476
|
+
/**
|
|
1477
|
+
* Add static props to component
|
|
1478
|
+
*/
|
|
1479
|
+
static: (staticProps) => withProps(() => staticProps),
|
|
1480
|
+
/**
|
|
1481
|
+
* Transform props based on conditions
|
|
1482
|
+
*/
|
|
1483
|
+
conditional: (condition, trueProps, falseProps = {}) => withProps((props, state, _context) => {
|
|
1484
|
+
const shouldApply = typeof condition === 'function' ?
|
|
1485
|
+
condition(props, state, _context) :
|
|
1486
|
+
condition;
|
|
1487
|
+
return shouldApply ? trueProps : falseProps;
|
|
1488
|
+
}),
|
|
1489
|
+
/**
|
|
1490
|
+
* Map specific props to new names/values
|
|
1491
|
+
*/
|
|
1492
|
+
map: (mapping) => withProps((_props) => {
|
|
1493
|
+
const result = {};
|
|
1494
|
+
Object.entries(mapping).forEach(([newKey, mapper]) => {
|
|
1495
|
+
if (typeof mapper === 'string') {
|
|
1496
|
+
// Simple property rename
|
|
1497
|
+
result[newKey] = props[mapper];
|
|
1498
|
+
}
|
|
1499
|
+
else if (typeof mapper === 'function') {
|
|
1500
|
+
// Transform function
|
|
1501
|
+
result[newKey] = mapper(_props);
|
|
1502
|
+
}
|
|
1503
|
+
else {
|
|
1504
|
+
// Static value
|
|
1505
|
+
result[newKey] = mapper;
|
|
1506
|
+
}
|
|
1507
|
+
});
|
|
1508
|
+
return result;
|
|
1509
|
+
}),
|
|
1510
|
+
/**
|
|
1511
|
+
* Pick only specific props
|
|
1512
|
+
*/
|
|
1513
|
+
pick: (keys) => withProps((_props) => {
|
|
1514
|
+
const result = {};
|
|
1515
|
+
keys.forEach(key => {
|
|
1516
|
+
if (props.hasOwnProperty(key)) {
|
|
1517
|
+
result[key] = props[key];
|
|
1518
|
+
}
|
|
1519
|
+
});
|
|
1520
|
+
return result;
|
|
1521
|
+
}),
|
|
1522
|
+
/**
|
|
1523
|
+
* Omit specific props
|
|
1524
|
+
*/
|
|
1525
|
+
omit: (keys) => withProps((_props) => {
|
|
1526
|
+
const result = { ...props };
|
|
1527
|
+
keys.forEach(key => delete result[key]);
|
|
1528
|
+
return result;
|
|
1529
|
+
}),
|
|
1530
|
+
/**
|
|
1531
|
+
* Default values for missing props
|
|
1532
|
+
*/
|
|
1533
|
+
defaults: (defaultProps) => withProps((_props) => ({
|
|
1534
|
+
...defaultProps,
|
|
1535
|
+
...props
|
|
1536
|
+
}), { merge: false }),
|
|
1537
|
+
/**
|
|
1538
|
+
* Transform props with validation
|
|
1539
|
+
*/
|
|
1540
|
+
validated: (transform, validator) => withProps(transform, {
|
|
1541
|
+
validate: validator,
|
|
1542
|
+
onError: (error) => console.warn('Prop validation failed:', error)
|
|
1543
|
+
}),
|
|
1544
|
+
/**
|
|
1545
|
+
* Async prop transformation
|
|
1546
|
+
*/
|
|
1547
|
+
async: (asyncTransform, loadingProps = {}) => withProps(async (props, state, _context) => {
|
|
1548
|
+
try {
|
|
1549
|
+
return await asyncTransform(props, state, _context);
|
|
1550
|
+
}
|
|
1551
|
+
catch (error) {
|
|
1552
|
+
console.error('Async prop transform failed:', error);
|
|
1553
|
+
return loadingProps;
|
|
1554
|
+
}
|
|
1555
|
+
}, {
|
|
1556
|
+
fallbackProps: loadingProps
|
|
1557
|
+
}),
|
|
1558
|
+
/**
|
|
1559
|
+
* Computed props based on other props
|
|
1560
|
+
*/
|
|
1561
|
+
computed: (computedProps) => withProps((_props) => {
|
|
1562
|
+
const computed = {};
|
|
1563
|
+
Object.entries(computedProps).forEach(([key, compute]) => {
|
|
1564
|
+
computed[key] = typeof compute === 'function' ?
|
|
1565
|
+
compute(_props) :
|
|
1566
|
+
compute;
|
|
1567
|
+
});
|
|
1568
|
+
return computed;
|
|
1569
|
+
}),
|
|
1570
|
+
/**
|
|
1571
|
+
* Props with context injection
|
|
1572
|
+
*/
|
|
1573
|
+
withContext: (contextKeys) => withProps((props, state, _context) => {
|
|
1574
|
+
const contextProps = {};
|
|
1575
|
+
contextKeys.forEach(key => {
|
|
1576
|
+
if (context && context[key] !== undefined) {
|
|
1577
|
+
contextProps[key] = context[key];
|
|
1578
|
+
}
|
|
1579
|
+
});
|
|
1580
|
+
return contextProps;
|
|
1581
|
+
}),
|
|
1582
|
+
/**
|
|
1583
|
+
* Props with state injection
|
|
1584
|
+
*/
|
|
1585
|
+
withState: (stateMapping) => withProps((props, state) => {
|
|
1586
|
+
if (typeof stateMapping === 'function') {
|
|
1587
|
+
return stateMapping(state, _props);
|
|
1588
|
+
}
|
|
1589
|
+
const stateProps = {};
|
|
1590
|
+
Object.entries(stateMapping).forEach(([propKey, stateKey]) => {
|
|
1591
|
+
stateProps[propKey] = state[stateKey];
|
|
1592
|
+
});
|
|
1593
|
+
return stateProps;
|
|
1594
|
+
}),
|
|
1595
|
+
/**
|
|
1596
|
+
* Memoized prop transformation
|
|
1597
|
+
*/
|
|
1598
|
+
memoized: (transform, memoOptions = {}) => withProps(transform, {
|
|
1599
|
+
memoize: true,
|
|
1600
|
+
memoOptions: {
|
|
1601
|
+
maxSize: 50,
|
|
1602
|
+
...memoOptions
|
|
1603
|
+
}
|
|
1604
|
+
}),
|
|
1605
|
+
/**
|
|
1606
|
+
* Props with performance measurement
|
|
1607
|
+
*/
|
|
1608
|
+
timed: (transform, name = 'PropTransform') => withProps((props, state, _context) => {
|
|
1609
|
+
const start = performance.now();
|
|
1610
|
+
const result = transform(props, state, _context);
|
|
1611
|
+
const end = performance.now();
|
|
1612
|
+
if (end - start > 1) { // Log if > 1ms
|
|
1613
|
+
console.log(`${name} took ${(end - start).toFixed(2)}ms`);
|
|
1614
|
+
}
|
|
1615
|
+
return result;
|
|
1616
|
+
}),
|
|
1617
|
+
/**
|
|
1618
|
+
* Chain multiple prop transformations
|
|
1619
|
+
*/
|
|
1620
|
+
chain: (...transforms) => withProps((props, state, _context) => {
|
|
1621
|
+
return transforms.reduce((acc, transform) => {
|
|
1622
|
+
if (typeof transform === 'function') {
|
|
1623
|
+
return { ...acc, ...transform(acc, state, _context) };
|
|
1624
|
+
}
|
|
1625
|
+
return { ...acc, ...transform };
|
|
1626
|
+
}, _props);
|
|
1627
|
+
})
|
|
1628
|
+
};
|
|
1629
|
+
/**
|
|
1630
|
+
* Create a reusable prop transformer
|
|
1631
|
+
*/
|
|
1632
|
+
export function createPropTransformer(config) {
|
|
1633
|
+
const { transforms = [], validators = [], defaults = {}, options = {} } = config;
|
|
1634
|
+
return withProps((props, state, _context) => {
|
|
1635
|
+
let result = { ...defaults, ...props };
|
|
1636
|
+
// Apply transforms in sequence
|
|
1637
|
+
for (const transform of transforms) {
|
|
1638
|
+
if (typeof transform === 'function') {
|
|
1639
|
+
result = { ...result, ...transform(result, state, _context) };
|
|
1640
|
+
}
|
|
1641
|
+
else {
|
|
1642
|
+
result = { ...result, ...transform };
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
// Run validators
|
|
1646
|
+
for (const validator of validators) {
|
|
1647
|
+
if (!validator(result)) {
|
|
1648
|
+
throw new Error('Prop validation failed');
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
return result;
|
|
1652
|
+
}, options);
|
|
1653
|
+
}
|
|
1654
|
+
/**
|
|
1655
|
+
* Props debugging utility
|
|
1656
|
+
*/
|
|
1657
|
+
export function withPropsDebug(component, debugOptions = {}) {
|
|
1658
|
+
const { logProps = true, logChanges = true, breakOnError = false } = debugOptions;
|
|
1659
|
+
return withProps((props, state, _context) => {
|
|
1660
|
+
if (logProps) {
|
|
1661
|
+
console.group(`Props Debug: ${component.name || 'Component'}`);
|
|
1662
|
+
console.log('Props:', _props);
|
|
1663
|
+
console.log('State:', state);
|
|
1664
|
+
console.log('Context:', _context);
|
|
1665
|
+
console.groupEnd();
|
|
1666
|
+
}
|
|
1667
|
+
return props;
|
|
1668
|
+
}, {
|
|
1669
|
+
debug: true,
|
|
1670
|
+
onError: breakOnError ? () => {
|
|
1671
|
+
// eslint-disable-next-line no-debugger
|
|
1672
|
+
debugger;
|
|
1673
|
+
} : undefined,
|
|
1674
|
+
onPropsChange: logChanges ? (finalProps, original) => {
|
|
1675
|
+
console.log('Props changed:', { from: original, to: finalProps });
|
|
1676
|
+
} : undefined
|
|
1677
|
+
})(component);
|
|
1678
|
+
}
|
|
1679
|
+
/**
|
|
1680
|
+
* State management utilities for component state injection and management
|
|
1681
|
+
*/
|
|
1682
|
+
/**
|
|
1683
|
+
* Enhanced withState utility for component state management and injection
|
|
1684
|
+
*/
|
|
1685
|
+
export function withState(initialState = {}, options = {}) {
|
|
1686
|
+
const {
|
|
1687
|
+
// State options
|
|
1688
|
+
persistent = false, // Persist state across component unmounts
|
|
1689
|
+
storageKey = null, // Key for persistent storage
|
|
1690
|
+
storage = typeof localStorage !== 'undefined' ? localStorage : {
|
|
1691
|
+
// Fallback storage for Node.js environments
|
|
1692
|
+
_data: new Map(),
|
|
1693
|
+
setItem(key, value) { this._data.set(key, value); },
|
|
1694
|
+
getItem(key) { return this._data.get(key) || null; },
|
|
1695
|
+
removeItem(key) { this._data.delete(key); },
|
|
1696
|
+
clear() { this._data.clear(); }
|
|
1697
|
+
}, // Storage mechanism
|
|
1698
|
+
// State transformation
|
|
1699
|
+
stateTransform = null, // Transform state before injection
|
|
1700
|
+
propName = 'state', // Prop name for state injection
|
|
1701
|
+
actionsName = 'actions', // Prop name for action injection
|
|
1702
|
+
// Reducers and actions
|
|
1703
|
+
reducer = null, // State reducer function
|
|
1704
|
+
actions = {}, // Action creators
|
|
1705
|
+
middleware = [], // State middleware
|
|
1706
|
+
// Performance
|
|
1707
|
+
memoizeState = false, // Memoize state transformations
|
|
1708
|
+
// eslint-disable-next-line no-unused-vars
|
|
1709
|
+
shallow = false, // Shallow state comparison
|
|
1710
|
+
// Development
|
|
1711
|
+
// eslint-disable-next-line no-unused-vars
|
|
1712
|
+
devTools = false, // Connect to dev tools
|
|
1713
|
+
debug = false, // Debug logging
|
|
1714
|
+
displayName = null, // Component name for debugging
|
|
1715
|
+
// Lifecycle hooks
|
|
1716
|
+
onStateChange = null, // Called when state changes
|
|
1717
|
+
onMount = null, // Called when component mounts
|
|
1718
|
+
onUnmount = null, // Called when component unmounts
|
|
1719
|
+
// Validation
|
|
1720
|
+
validator = null, // State validator function
|
|
1721
|
+
// Async state
|
|
1722
|
+
supportAsync = false // Support async state updates
|
|
1723
|
+
} = options;
|
|
1724
|
+
return function withStateHOC(WrappedComponent) {
|
|
1725
|
+
// Create state container
|
|
1726
|
+
const stateContainer = createStateContainer(initialState, {
|
|
1727
|
+
persistent,
|
|
1728
|
+
storageKey: storageKey || `${getComponentName(WrappedComponent)}_state`,
|
|
1729
|
+
storage,
|
|
1730
|
+
reducer,
|
|
1731
|
+
middleware,
|
|
1732
|
+
validator,
|
|
1733
|
+
onStateChange,
|
|
1734
|
+
debug
|
|
1735
|
+
});
|
|
1736
|
+
function WithStateComponent(props = {}, globalState = {}, context = {}) {
|
|
1737
|
+
// Initialize component state if not exists
|
|
1738
|
+
if (!stateContainer.initialized) {
|
|
1739
|
+
stateContainer.initialize();
|
|
1740
|
+
if (onMount) {
|
|
1741
|
+
onMount(stateContainer.getState(), props, _context);
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
// Get current state
|
|
1745
|
+
const currentState = stateContainer.getState();
|
|
1746
|
+
// Transform state if needed
|
|
1747
|
+
let transformedState = currentState;
|
|
1748
|
+
if (stateTransform) {
|
|
1749
|
+
transformedState = stateTransform(currentState, props, _context);
|
|
1750
|
+
}
|
|
1751
|
+
// Create actions bound to this state container
|
|
1752
|
+
const boundActions = createBoundActions(actions, stateContainer, {
|
|
1753
|
+
props,
|
|
1754
|
+
context,
|
|
1755
|
+
supportAsync,
|
|
1756
|
+
debug
|
|
1757
|
+
});
|
|
1758
|
+
// Create state management utilities
|
|
1759
|
+
const stateUtils = {
|
|
1760
|
+
// Basic state operations
|
|
1761
|
+
setState: stateContainer.setState.bind(stateContainer),
|
|
1762
|
+
getState: stateContainer.getState.bind(stateContainer),
|
|
1763
|
+
resetState: () => stateContainer.setState(initialState),
|
|
1764
|
+
// Advanced operations
|
|
1765
|
+
updateState: (updater) => {
|
|
1766
|
+
const current = stateContainer.getState();
|
|
1767
|
+
const next = typeof updater === 'function' ? updater(current) : updater;
|
|
1768
|
+
stateContainer.setState(next);
|
|
1769
|
+
},
|
|
1770
|
+
// Batch updates
|
|
1771
|
+
batchUpdate: (updates) => {
|
|
1772
|
+
stateContainer.batch(() => {
|
|
1773
|
+
updates.forEach(update => {
|
|
1774
|
+
if (typeof update === 'function') {
|
|
1775
|
+
update(stateContainer);
|
|
1776
|
+
}
|
|
1777
|
+
else {
|
|
1778
|
+
stateContainer.setState(update);
|
|
1779
|
+
}
|
|
1780
|
+
});
|
|
1781
|
+
});
|
|
1782
|
+
},
|
|
1783
|
+
// Computed state
|
|
1784
|
+
computed: (computeFn) => {
|
|
1785
|
+
return memoizeState ?
|
|
1786
|
+
memo(computeFn)(transformedState) :
|
|
1787
|
+
computeFn(transformedState);
|
|
1788
|
+
},
|
|
1789
|
+
// State subscription
|
|
1790
|
+
subscribe: stateContainer.subscribe.bind(stateContainer),
|
|
1791
|
+
unsubscribe: stateContainer.unsubscribe.bind(stateContainer),
|
|
1792
|
+
// Async state operations
|
|
1793
|
+
...(supportAsync && {
|
|
1794
|
+
setStateAsync: async (stateOrPromise) => {
|
|
1795
|
+
const resolved = await Promise.resolve(stateOrPromise);
|
|
1796
|
+
stateContainer.setState(resolved);
|
|
1797
|
+
},
|
|
1798
|
+
updateStateAsync: async (asyncUpdater) => {
|
|
1799
|
+
const current = stateContainer.getState();
|
|
1800
|
+
const next = await Promise.resolve(asyncUpdater(current));
|
|
1801
|
+
stateContainer.setState(next);
|
|
1802
|
+
}
|
|
1803
|
+
})
|
|
1804
|
+
};
|
|
1805
|
+
// Prepare enhanced props
|
|
1806
|
+
const enhancedProps = {
|
|
1807
|
+
...props,
|
|
1808
|
+
[propName]: transformedState,
|
|
1809
|
+
[actionsName]: boundActions,
|
|
1810
|
+
stateUtils
|
|
1811
|
+
};
|
|
1812
|
+
if (debug) {
|
|
1813
|
+
console.log('withState render:', {
|
|
1814
|
+
component: getComponentName(WrappedComponent),
|
|
1815
|
+
state: transformedState,
|
|
1816
|
+
props: enhancedProps
|
|
1817
|
+
});
|
|
1818
|
+
}
|
|
1819
|
+
// Render wrapped component
|
|
1820
|
+
return typeof WrappedComponent === 'function' ?
|
|
1821
|
+
WrappedComponent(enhancedProps, globalState, _context) :
|
|
1822
|
+
WrappedComponent;
|
|
1823
|
+
}
|
|
1824
|
+
// Set display name
|
|
1825
|
+
WithStateComponent.displayName = displayName ||
|
|
1826
|
+
`withState(${getComponentName(WrappedComponent)})`;
|
|
1827
|
+
// Add metadata
|
|
1828
|
+
WithStateComponent.__isHOC = true;
|
|
1829
|
+
WithStateComponent.__hasState = true;
|
|
1830
|
+
WithStateComponent.__stateContainer = stateContainer;
|
|
1831
|
+
WithStateComponent.__wrappedComponent = WrappedComponent;
|
|
1832
|
+
// Cleanup on unmount
|
|
1833
|
+
WithStateComponent.cleanup = () => {
|
|
1834
|
+
if (onUnmount) {
|
|
1835
|
+
onUnmount(stateContainer.getState());
|
|
1836
|
+
}
|
|
1837
|
+
if (!persistent) {
|
|
1838
|
+
stateContainer.destroy();
|
|
1839
|
+
}
|
|
1840
|
+
};
|
|
1841
|
+
return WithStateComponent;
|
|
1842
|
+
};
|
|
1843
|
+
}
|
|
1844
|
+
/**
|
|
1845
|
+
* Create a centralized state container
|
|
1846
|
+
*/
|
|
1847
|
+
function createStateContainer(initialState, options) {
|
|
1848
|
+
const { persistent, storageKey, storage, reducer, middleware, validator, onStateChange, debug } = options;
|
|
1849
|
+
let state = deepClone(initialState);
|
|
1850
|
+
let listeners = new Set();
|
|
1851
|
+
const middlewareStack = [...middleware];
|
|
1852
|
+
const container = {
|
|
1853
|
+
initialized: false,
|
|
1854
|
+
initialize() {
|
|
1855
|
+
// Load persisted state
|
|
1856
|
+
if (persistent && storageKey) {
|
|
1857
|
+
try {
|
|
1858
|
+
const saved = storage.getItem(storageKey);
|
|
1859
|
+
if (saved) {
|
|
1860
|
+
const parsed = JSON.parse(saved);
|
|
1861
|
+
state = { ...state, ...parsed };
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
catch (error) {
|
|
1865
|
+
if (debug)
|
|
1866
|
+
console.warn('Failed to load persisted state:', error);
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
container.initialized = true;
|
|
1870
|
+
},
|
|
1871
|
+
getState() {
|
|
1872
|
+
return deepClone(state);
|
|
1873
|
+
},
|
|
1874
|
+
setState(newState) {
|
|
1875
|
+
const prevState = state;
|
|
1876
|
+
// Apply reducer if provided
|
|
1877
|
+
if (reducer) {
|
|
1878
|
+
state = reducer(state, { type: 'SET_STATE', payload: newState });
|
|
1879
|
+
}
|
|
1880
|
+
else {
|
|
1881
|
+
state = typeof newState === 'function' ?
|
|
1882
|
+
newState(state) :
|
|
1883
|
+
{ ...state, ...newState };
|
|
1884
|
+
}
|
|
1885
|
+
// Validate state
|
|
1886
|
+
if (validator && !validator(state)) {
|
|
1887
|
+
if (debug)
|
|
1888
|
+
console.warn('State validation failed, reverting:', state);
|
|
1889
|
+
state = prevState;
|
|
1890
|
+
return false;
|
|
1891
|
+
}
|
|
1892
|
+
// Apply middleware
|
|
1893
|
+
state = middlewareStack.reduce((acc, middleware) => middleware(acc, prevState) || acc, state);
|
|
1894
|
+
// Persist state
|
|
1895
|
+
if (persistent && storageKey) {
|
|
1896
|
+
try {
|
|
1897
|
+
storage.setItem(storageKey, JSON.stringify(state));
|
|
1898
|
+
}
|
|
1899
|
+
catch (error) {
|
|
1900
|
+
if (debug)
|
|
1901
|
+
console.warn('Failed to persist state:', error);
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
// Notify listeners
|
|
1905
|
+
if (state !== prevState) {
|
|
1906
|
+
listeners.forEach(listener => {
|
|
1907
|
+
try {
|
|
1908
|
+
listener(state, prevState);
|
|
1909
|
+
}
|
|
1910
|
+
catch (error) {
|
|
1911
|
+
if (debug)
|
|
1912
|
+
console.error('State listener error:', error);
|
|
1913
|
+
}
|
|
1914
|
+
});
|
|
1915
|
+
if (onStateChange) {
|
|
1916
|
+
onStateChange(state, prevState);
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
return true;
|
|
1920
|
+
},
|
|
1921
|
+
subscribe(listener) {
|
|
1922
|
+
listeners.add(listener);
|
|
1923
|
+
return () => listeners.delete(listener);
|
|
1924
|
+
},
|
|
1925
|
+
unsubscribe(listener) {
|
|
1926
|
+
return listeners.delete(listener);
|
|
1927
|
+
},
|
|
1928
|
+
batch(batchFn) {
|
|
1929
|
+
const originalListeners = listeners;
|
|
1930
|
+
listeners = new Set(); // Temporarily disable listeners
|
|
1931
|
+
try {
|
|
1932
|
+
batchFn();
|
|
1933
|
+
}
|
|
1934
|
+
finally {
|
|
1935
|
+
listeners = originalListeners;
|
|
1936
|
+
// Notify once after batch
|
|
1937
|
+
listeners.forEach(listener => listener(state));
|
|
1938
|
+
}
|
|
1939
|
+
},
|
|
1940
|
+
destroy() {
|
|
1941
|
+
listeners.clear();
|
|
1942
|
+
if (persistent && storageKey) {
|
|
1943
|
+
try {
|
|
1944
|
+
storage.removeItem(storageKey);
|
|
1945
|
+
}
|
|
1946
|
+
catch (error) {
|
|
1947
|
+
if (debug)
|
|
1948
|
+
console.warn('Failed to remove persisted state:', error);
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
};
|
|
1953
|
+
return container;
|
|
1954
|
+
}
|
|
1955
|
+
/**
|
|
1956
|
+
* Create bound action creators
|
|
1957
|
+
*/
|
|
1958
|
+
function createBoundActions(actions, stateContainer, options) {
|
|
1959
|
+
const { props, context, supportAsync, debug } = options;
|
|
1960
|
+
const boundActions = {};
|
|
1961
|
+
Object.entries(actions).forEach(([actionName, actionCreator]) => {
|
|
1962
|
+
boundActions[actionName] = (...args) => {
|
|
1963
|
+
try {
|
|
1964
|
+
const result = actionCreator(stateContainer.getState(), stateContainer.setState.bind(stateContainer), { props, context, args });
|
|
1965
|
+
// Handle async actions
|
|
1966
|
+
if (supportAsync && result && typeof result.then === 'function') {
|
|
1967
|
+
return result.catch(error => {
|
|
1968
|
+
if (debug)
|
|
1969
|
+
console.error(`Async action ${actionName} failed:`, error);
|
|
1970
|
+
throw error;
|
|
1971
|
+
});
|
|
1972
|
+
}
|
|
1973
|
+
return result;
|
|
1974
|
+
}
|
|
1975
|
+
catch (error) {
|
|
1976
|
+
if (debug)
|
|
1977
|
+
console.error(`Action ${actionName} failed:`, error);
|
|
1978
|
+
throw error;
|
|
1979
|
+
}
|
|
1980
|
+
};
|
|
1981
|
+
});
|
|
1982
|
+
return boundActions;
|
|
1983
|
+
}
|
|
1984
|
+
/**
|
|
1985
|
+
* Specialized withState variants
|
|
1986
|
+
*/
|
|
1987
|
+
export const withStateUtils = {
|
|
1988
|
+
/**
|
|
1989
|
+
* Simple local state
|
|
1990
|
+
*/
|
|
1991
|
+
local: (initialState) => withState(initialState),
|
|
1992
|
+
/**
|
|
1993
|
+
* Persistent state with localStorage
|
|
1994
|
+
*/
|
|
1995
|
+
persistent: (initialState, key) => withState(initialState, {
|
|
1996
|
+
persistent: true,
|
|
1997
|
+
storageKey: key
|
|
1998
|
+
}),
|
|
1999
|
+
/**
|
|
2000
|
+
* State with reducer pattern
|
|
2001
|
+
*/
|
|
2002
|
+
reducer: (initialState, reducer, actions = {}) => withState(initialState, {
|
|
2003
|
+
reducer,
|
|
2004
|
+
actions
|
|
2005
|
+
}),
|
|
2006
|
+
/**
|
|
2007
|
+
* Async state management
|
|
2008
|
+
*/
|
|
2009
|
+
async: (initialState, asyncActions = {}) => withState(initialState, {
|
|
2010
|
+
supportAsync: true,
|
|
2011
|
+
actions: asyncActions
|
|
2012
|
+
}),
|
|
2013
|
+
/**
|
|
2014
|
+
* State with validation
|
|
2015
|
+
*/
|
|
2016
|
+
validated: (initialState, validator) => withState(initialState, {
|
|
2017
|
+
validator,
|
|
2018
|
+
debug: true
|
|
2019
|
+
}),
|
|
2020
|
+
/**
|
|
2021
|
+
* Shared state across components
|
|
2022
|
+
*/
|
|
2023
|
+
shared: (initialState, sharedKey) => {
|
|
2024
|
+
const sharedStates = withStateUtils._shared || (withStateUtils._shared = new Map());
|
|
2025
|
+
if (!sharedStates.has(sharedKey)) {
|
|
2026
|
+
sharedStates.set(sharedKey, createStateContainer(initialState, {}));
|
|
2027
|
+
}
|
|
2028
|
+
return (WrappedComponent) => {
|
|
2029
|
+
const sharedContainer = sharedStates.get(sharedKey);
|
|
2030
|
+
function SharedStateComponent(props, globalState, _context) {
|
|
2031
|
+
const currentState = sharedContainer.getState();
|
|
2032
|
+
const enhancedProps = {
|
|
2033
|
+
...props,
|
|
2034
|
+
state: currentState,
|
|
2035
|
+
setState: sharedContainer.setState.bind(sharedContainer),
|
|
2036
|
+
subscribe: sharedContainer.subscribe.bind(sharedContainer)
|
|
2037
|
+
};
|
|
2038
|
+
return typeof WrappedComponent === 'function' ?
|
|
2039
|
+
WrappedComponent(enhancedProps, globalState, _context) :
|
|
2040
|
+
WrappedComponent;
|
|
2041
|
+
}
|
|
2042
|
+
SharedStateComponent.displayName = `withSharedState(${getComponentName(WrappedComponent)})`;
|
|
2043
|
+
return SharedStateComponent;
|
|
2044
|
+
};
|
|
2045
|
+
},
|
|
2046
|
+
/**
|
|
2047
|
+
* State with form utilities
|
|
2048
|
+
*/
|
|
2049
|
+
form: (initialFormState) => withState(initialFormState, {
|
|
2050
|
+
actions: {
|
|
2051
|
+
updateField: (state, setState, { args: [field, value] }) => {
|
|
2052
|
+
setState({ [field]: value });
|
|
2053
|
+
},
|
|
2054
|
+
updateMultiple: (state, setState, { args: [updates] }) => {
|
|
2055
|
+
setState(updates);
|
|
2056
|
+
},
|
|
2057
|
+
resetForm: (state, setState) => {
|
|
2058
|
+
setState(initialFormState);
|
|
2059
|
+
},
|
|
2060
|
+
validateForm: (state, setState, { args: [validator] }) => {
|
|
2061
|
+
const errors = validator(state);
|
|
2062
|
+
setState({ _errors: errors });
|
|
2063
|
+
return Object.keys(errors).length === 0;
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
}),
|
|
2067
|
+
/**
|
|
2068
|
+
* State with loading/error handling
|
|
2069
|
+
*/
|
|
2070
|
+
withLoading: async (initialState) => withState({
|
|
2071
|
+
...initialState,
|
|
2072
|
+
_loading: false,
|
|
2073
|
+
_error: null
|
|
2074
|
+
}, {
|
|
2075
|
+
supportAsync: true,
|
|
2076
|
+
actions: {
|
|
2077
|
+
setLoading: (state, setState, { args: [loading] }) => {
|
|
2078
|
+
setState({ _loading: loading });
|
|
2079
|
+
},
|
|
2080
|
+
setError: (state, setState, { args: [error] }) => {
|
|
2081
|
+
setState({ _error: error, _loading: false });
|
|
2082
|
+
},
|
|
2083
|
+
clearError: (state, setState) => {
|
|
2084
|
+
setState({ _error: null });
|
|
2085
|
+
},
|
|
2086
|
+
asyncAction: async (state, setState, { args: [asyncFn] }) => {
|
|
2087
|
+
setState({ _loading: true, _error: null });
|
|
2088
|
+
try {
|
|
2089
|
+
const result = await asyncFn(state);
|
|
2090
|
+
setState({ _loading: false });
|
|
2091
|
+
return result;
|
|
2092
|
+
}
|
|
2093
|
+
catch (error) {
|
|
2094
|
+
setState({ _loading: false, _error: error });
|
|
2095
|
+
throw error;
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
}),
|
|
2100
|
+
/**
|
|
2101
|
+
* State with undo/redo functionality
|
|
2102
|
+
*/
|
|
2103
|
+
withHistory: (initialState, maxHistory = 10) => {
|
|
2104
|
+
const historyState = {
|
|
2105
|
+
present: initialState,
|
|
2106
|
+
past: [],
|
|
2107
|
+
future: []
|
|
2108
|
+
};
|
|
2109
|
+
return withState(historyState, {
|
|
2110
|
+
actions: {
|
|
2111
|
+
undo: (state, setState) => {
|
|
2112
|
+
if (state.past.length === 0)
|
|
2113
|
+
return;
|
|
2114
|
+
const previous = state.past[state.past.length - 1];
|
|
2115
|
+
const newPast = state.past.slice(0, state.past.length - 1);
|
|
2116
|
+
setState({
|
|
2117
|
+
past: newPast,
|
|
2118
|
+
present: previous,
|
|
2119
|
+
future: [state.present, ...state.future]
|
|
2120
|
+
});
|
|
2121
|
+
},
|
|
2122
|
+
redo: (state, setState) => {
|
|
2123
|
+
if (state.future.length === 0)
|
|
2124
|
+
return;
|
|
2125
|
+
const next = state.future[0];
|
|
2126
|
+
const newFuture = state.future.slice(1);
|
|
2127
|
+
setState({
|
|
2128
|
+
past: [...state.past, state.present],
|
|
2129
|
+
present: next,
|
|
2130
|
+
future: newFuture
|
|
2131
|
+
});
|
|
2132
|
+
},
|
|
2133
|
+
updatePresent: (state, setState, { args: [newPresent] }) => {
|
|
2134
|
+
setState({
|
|
2135
|
+
past: [...state.past, state.present].slice(-maxHistory),
|
|
2136
|
+
present: newPresent,
|
|
2137
|
+
future: []
|
|
2138
|
+
});
|
|
2139
|
+
},
|
|
2140
|
+
canUndo: (state) => state.past.length > 0,
|
|
2141
|
+
canRedo: (state) => state.future.length > 0
|
|
2142
|
+
}
|
|
2143
|
+
});
|
|
2144
|
+
},
|
|
2145
|
+
/**
|
|
2146
|
+
* Computed state properties
|
|
2147
|
+
*/
|
|
2148
|
+
computed: (initialState, computedProps) => withState(initialState, {
|
|
2149
|
+
stateTransform: (state) => {
|
|
2150
|
+
const computed = {};
|
|
2151
|
+
Object.entries(computedProps).forEach(([key, computeFn]) => {
|
|
2152
|
+
computed[key] = computeFn(state);
|
|
2153
|
+
});
|
|
2154
|
+
return { ...state, ...computed };
|
|
2155
|
+
},
|
|
2156
|
+
memoizeState: true
|
|
2157
|
+
})
|
|
2158
|
+
};
|
|
2159
|
+
/**
|
|
2160
|
+
* Create a compound state manager
|
|
2161
|
+
*/
|
|
2162
|
+
export function createStateManager(config) {
|
|
2163
|
+
const { initialState = {}, reducers = {}, actions = {}, middleware = [], plugins = [] } = config;
|
|
2164
|
+
// Combine reducers
|
|
2165
|
+
const rootReducer = (state, action) => {
|
|
2166
|
+
let nextState = state;
|
|
2167
|
+
Object.entries(reducers).forEach(([key, reducer]) => {
|
|
2168
|
+
nextState = {
|
|
2169
|
+
...nextState,
|
|
2170
|
+
[key]: reducer(nextState[key], action)
|
|
2171
|
+
};
|
|
2172
|
+
});
|
|
2173
|
+
return nextState;
|
|
2174
|
+
};
|
|
2175
|
+
// Apply plugins
|
|
2176
|
+
const enhancedConfig = plugins.reduce((acc, plugin) => plugin(acc), { initialState, reducer: rootReducer, actions, middleware });
|
|
2177
|
+
return withState(enhancedConfig.initialState, {
|
|
2178
|
+
reducer: enhancedConfig.reducer,
|
|
2179
|
+
actions: enhancedConfig.actions,
|
|
2180
|
+
middleware: enhancedConfig.middleware
|
|
2181
|
+
});
|
|
2182
|
+
}
|
|
2183
|
+
// Utility to get component name
|
|
2184
|
+
function getComponentName(component) {
|
|
2185
|
+
if (!component)
|
|
2186
|
+
return 'Component';
|
|
2187
|
+
return component.displayName ||
|
|
2188
|
+
component.name ||
|
|
2189
|
+
component.constructor?.name ||
|
|
2190
|
+
'Component';
|
|
2191
|
+
}
|
|
2192
|
+
export default {
|
|
2193
|
+
Component,
|
|
2194
|
+
createComponent,
|
|
2195
|
+
defineComponent,
|
|
2196
|
+
registerComponent,
|
|
2197
|
+
getComponent,
|
|
2198
|
+
getRegisteredComponents,
|
|
2199
|
+
createHOC,
|
|
2200
|
+
createMixin,
|
|
2201
|
+
compose,
|
|
2202
|
+
componentUtils,
|
|
2203
|
+
dev,
|
|
2204
|
+
lazy,
|
|
2205
|
+
lazyUtils,
|
|
2206
|
+
evaluateLazy,
|
|
2207
|
+
evaluateWithTimeout,
|
|
2208
|
+
batchEvaluate,
|
|
2209
|
+
hashDependencies,
|
|
2210
|
+
memo,
|
|
2211
|
+
memoUtils,
|
|
2212
|
+
withProps,
|
|
2213
|
+
withPropsUtils,
|
|
2214
|
+
withPropsDebug,
|
|
2215
|
+
withState,
|
|
2216
|
+
createStateManager,
|
|
2217
|
+
getComponentName,
|
|
2218
|
+
withStateUtils
|
|
2219
|
+
};
|
|
2220
|
+
//# sourceMappingURL=component-system.js.map
|