@sc4rfurryx/proteusjs 1.0.0
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/API.md +438 -0
- package/FEATURES.md +286 -0
- package/LICENSE +21 -0
- package/README.md +645 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/proteus.cjs.js +16014 -0
- package/dist/proteus.cjs.js.map +1 -0
- package/dist/proteus.d.ts +3018 -0
- package/dist/proteus.esm.js +16005 -0
- package/dist/proteus.esm.js.map +1 -0
- package/dist/proteus.esm.min.js +8 -0
- package/dist/proteus.esm.min.js.map +1 -0
- package/dist/proteus.js +16020 -0
- package/dist/proteus.js.map +1 -0
- package/dist/proteus.min.js +8 -0
- package/dist/proteus.min.js.map +1 -0
- package/package.json +98 -0
- package/src/__tests__/mvp-integration.test.ts +518 -0
- package/src/accessibility/AccessibilityEngine.ts +2106 -0
- package/src/accessibility/ScreenReaderSupport.ts +444 -0
- package/src/accessibility/__tests__/ScreenReaderSupport.test.ts +435 -0
- package/src/animations/FLIPAnimationSystem.ts +491 -0
- package/src/compatibility/BrowserCompatibility.ts +1076 -0
- package/src/containers/BreakpointSystem.ts +347 -0
- package/src/containers/ContainerBreakpoints.ts +726 -0
- package/src/containers/ContainerManager.ts +370 -0
- package/src/containers/ContainerUnits.ts +336 -0
- package/src/containers/ContextIsolation.ts +394 -0
- package/src/containers/ElementQueries.ts +411 -0
- package/src/containers/SmartContainer.ts +536 -0
- package/src/containers/SmartContainers.ts +376 -0
- package/src/containers/__tests__/ContainerBreakpoints.test.ts +411 -0
- package/src/containers/__tests__/SmartContainers.test.ts +281 -0
- package/src/content/ResponsiveImages.ts +570 -0
- package/src/core/EventSystem.ts +147 -0
- package/src/core/MemoryManager.ts +321 -0
- package/src/core/PerformanceMonitor.ts +238 -0
- package/src/core/PluginSystem.ts +275 -0
- package/src/core/ProteusJS.test.ts +164 -0
- package/src/core/ProteusJS.ts +962 -0
- package/src/developer/PerformanceProfiler.ts +567 -0
- package/src/developer/VisualDebuggingTools.ts +656 -0
- package/src/developer/ZeroConfigSystem.ts +593 -0
- package/src/index.ts +35 -0
- package/src/integration.test.ts +227 -0
- package/src/layout/AdaptiveGrid.ts +429 -0
- package/src/layout/ContentReordering.ts +532 -0
- package/src/layout/FlexboxEnhancer.ts +406 -0
- package/src/layout/FlowLayout.ts +545 -0
- package/src/layout/SpacingSystem.ts +512 -0
- package/src/observers/IntersectionObserverPolyfill.ts +289 -0
- package/src/observers/ObserverManager.ts +299 -0
- package/src/observers/ResizeObserverPolyfill.ts +179 -0
- package/src/performance/BatchDOMOperations.ts +519 -0
- package/src/performance/CSSOptimizationEngine.ts +646 -0
- package/src/performance/CacheOptimizationSystem.ts +601 -0
- package/src/performance/EfficientEventHandler.ts +740 -0
- package/src/performance/LazyEvaluationSystem.ts +532 -0
- package/src/performance/MemoryManagementSystem.ts +497 -0
- package/src/performance/PerformanceMonitor.ts +931 -0
- package/src/performance/__tests__/BatchDOMOperations.test.ts +309 -0
- package/src/performance/__tests__/EfficientEventHandler.test.ts +268 -0
- package/src/performance/__tests__/PerformanceMonitor.test.ts +422 -0
- package/src/polyfills/BrowserPolyfills.ts +586 -0
- package/src/polyfills/__tests__/BrowserPolyfills.test.ts +328 -0
- package/src/test/setup.ts +115 -0
- package/src/theming/SmartThemeSystem.ts +591 -0
- package/src/types/index.ts +134 -0
- package/src/typography/ClampScaling.ts +356 -0
- package/src/typography/FluidTypography.ts +759 -0
- package/src/typography/LineHeightOptimization.ts +430 -0
- package/src/typography/LineHeightOptimizer.ts +326 -0
- package/src/typography/TextFitting.ts +355 -0
- package/src/typography/TypographicScale.ts +428 -0
- package/src/typography/VerticalRhythm.ts +369 -0
- package/src/typography/__tests__/FluidTypography.test.ts +432 -0
- package/src/typography/__tests__/LineHeightOptimization.test.ts +436 -0
- package/src/utils/Logger.ts +173 -0
- package/src/utils/debounce.ts +259 -0
- package/src/utils/performance.ts +371 -0
- package/src/utils/support.ts +106 -0
- package/src/utils/version.ts +24 -0
@@ -0,0 +1,532 @@
|
|
1
|
+
/**
|
2
|
+
* Lazy Evaluation System for ProteusJS
|
3
|
+
* Viewport-based component activation with idle callbacks and progressive enhancement
|
4
|
+
*/
|
5
|
+
|
6
|
+
export interface LazyConfig {
|
7
|
+
rootMargin: string;
|
8
|
+
threshold: number[];
|
9
|
+
useIdleCallback: boolean;
|
10
|
+
idleTimeout: number;
|
11
|
+
progressiveEnhancement: boolean;
|
12
|
+
cacheResults: boolean;
|
13
|
+
priorityQueue: boolean;
|
14
|
+
maxConcurrent: number;
|
15
|
+
}
|
16
|
+
|
17
|
+
export interface LazyComponent {
|
18
|
+
id: string;
|
19
|
+
element: Element;
|
20
|
+
activator: () => Promise<void>;
|
21
|
+
priority: 'high' | 'normal' | 'low';
|
22
|
+
dependencies: string[];
|
23
|
+
activated: boolean;
|
24
|
+
cached: boolean;
|
25
|
+
timestamp: number;
|
26
|
+
}
|
27
|
+
|
28
|
+
export interface LazyMetrics {
|
29
|
+
totalComponents: number;
|
30
|
+
activatedComponents: number;
|
31
|
+
pendingComponents: number;
|
32
|
+
cacheHits: number;
|
33
|
+
cacheMisses: number;
|
34
|
+
averageActivationTime: number;
|
35
|
+
idleCallbacksUsed: number;
|
36
|
+
}
|
37
|
+
|
38
|
+
export class LazyEvaluationSystem {
|
39
|
+
private config: Required<LazyConfig>;
|
40
|
+
private components: Map<string, LazyComponent> = new Map();
|
41
|
+
private intersectionObserver: IntersectionObserver | null = null;
|
42
|
+
private activationQueue: LazyComponent[] = [];
|
43
|
+
private cache: Map<string, any> = new Map();
|
44
|
+
private metrics: LazyMetrics;
|
45
|
+
private isProcessing: boolean = false;
|
46
|
+
private activeActivations: Set<string> = new Set();
|
47
|
+
|
48
|
+
constructor(config: Partial<LazyConfig> = {}) {
|
49
|
+
this.config = {
|
50
|
+
rootMargin: '50px',
|
51
|
+
threshold: [0, 0.1, 0.5, 1.0],
|
52
|
+
useIdleCallback: true,
|
53
|
+
idleTimeout: 5000,
|
54
|
+
progressiveEnhancement: true,
|
55
|
+
cacheResults: true,
|
56
|
+
priorityQueue: true,
|
57
|
+
maxConcurrent: 3,
|
58
|
+
...config
|
59
|
+
};
|
60
|
+
|
61
|
+
this.metrics = this.createInitialMetrics();
|
62
|
+
this.setupIntersectionObserver();
|
63
|
+
}
|
64
|
+
|
65
|
+
/**
|
66
|
+
* Register a lazy component
|
67
|
+
*/
|
68
|
+
public register(
|
69
|
+
element: Element,
|
70
|
+
activator: () => Promise<void>,
|
71
|
+
options: {
|
72
|
+
id?: string;
|
73
|
+
priority?: 'high' | 'normal' | 'low';
|
74
|
+
dependencies?: string[];
|
75
|
+
immediate?: boolean;
|
76
|
+
} = {}
|
77
|
+
): string {
|
78
|
+
const id = options.id || this.generateId();
|
79
|
+
|
80
|
+
const component: LazyComponent = {
|
81
|
+
id,
|
82
|
+
element,
|
83
|
+
activator,
|
84
|
+
priority: options.priority || 'normal',
|
85
|
+
dependencies: options.dependencies || [],
|
86
|
+
activated: false,
|
87
|
+
cached: false,
|
88
|
+
timestamp: performance.now()
|
89
|
+
};
|
90
|
+
|
91
|
+
this.components.set(id, component);
|
92
|
+
this.metrics.totalComponents++;
|
93
|
+
|
94
|
+
if (options.immediate) {
|
95
|
+
this.activateComponent(component);
|
96
|
+
} else {
|
97
|
+
this.observeComponent(component);
|
98
|
+
}
|
99
|
+
|
100
|
+
return id;
|
101
|
+
}
|
102
|
+
|
103
|
+
/**
|
104
|
+
* Unregister a component
|
105
|
+
*/
|
106
|
+
public unregister(id: string): void {
|
107
|
+
const component = this.components.get(id);
|
108
|
+
if (component) {
|
109
|
+
this.intersectionObserver?.unobserve(component.element);
|
110
|
+
this.components.delete(id);
|
111
|
+
this.cache.delete(id);
|
112
|
+
this.metrics.totalComponents--;
|
113
|
+
}
|
114
|
+
}
|
115
|
+
|
116
|
+
/**
|
117
|
+
* Force activate a component
|
118
|
+
*/
|
119
|
+
public async activate(id: string): Promise<void> {
|
120
|
+
const component = this.components.get(id);
|
121
|
+
if (component && !component.activated) {
|
122
|
+
await this.activateComponent(component);
|
123
|
+
}
|
124
|
+
}
|
125
|
+
|
126
|
+
/**
|
127
|
+
* Preload components with high priority
|
128
|
+
*/
|
129
|
+
public preloadHighPriority(): void {
|
130
|
+
const highPriorityComponents = Array.from(this.components.values())
|
131
|
+
.filter(c => c.priority === 'high' && !c.activated);
|
132
|
+
|
133
|
+
highPriorityComponents.forEach(component => {
|
134
|
+
this.scheduleActivation(component);
|
135
|
+
});
|
136
|
+
}
|
137
|
+
|
138
|
+
/**
|
139
|
+
* Get cached result
|
140
|
+
*/
|
141
|
+
public getCached<T>(key: string): T | null {
|
142
|
+
if (!this.config.cacheResults) return null;
|
143
|
+
|
144
|
+
const cached = this.cache.get(key);
|
145
|
+
if (cached) {
|
146
|
+
this.metrics.cacheHits++;
|
147
|
+
return cached;
|
148
|
+
}
|
149
|
+
|
150
|
+
this.metrics.cacheMisses++;
|
151
|
+
return null;
|
152
|
+
}
|
153
|
+
|
154
|
+
/**
|
155
|
+
* Set cached result
|
156
|
+
*/
|
157
|
+
public setCached<T>(key: string, value: T): void {
|
158
|
+
if (this.config.cacheResults) {
|
159
|
+
this.cache.set(key, value);
|
160
|
+
}
|
161
|
+
}
|
162
|
+
|
163
|
+
/**
|
164
|
+
* Get current metrics
|
165
|
+
*/
|
166
|
+
public getMetrics(): LazyMetrics {
|
167
|
+
return { ...this.metrics };
|
168
|
+
}
|
169
|
+
|
170
|
+
/**
|
171
|
+
* Clear cache
|
172
|
+
*/
|
173
|
+
public clearCache(): void {
|
174
|
+
this.cache.clear();
|
175
|
+
}
|
176
|
+
|
177
|
+
/**
|
178
|
+
* Destroy the lazy evaluation system
|
179
|
+
*/
|
180
|
+
public destroy(): void {
|
181
|
+
this.intersectionObserver?.disconnect();
|
182
|
+
this.components.clear();
|
183
|
+
this.cache.clear();
|
184
|
+
this.activationQueue = [];
|
185
|
+
this.activeActivations.clear();
|
186
|
+
}
|
187
|
+
|
188
|
+
/**
|
189
|
+
* Setup intersection observer
|
190
|
+
*/
|
191
|
+
private setupIntersectionObserver(): void {
|
192
|
+
if (!window.IntersectionObserver) {
|
193
|
+
console.warn('IntersectionObserver not supported, falling back to immediate activation');
|
194
|
+
return;
|
195
|
+
}
|
196
|
+
|
197
|
+
this.intersectionObserver = new IntersectionObserver(
|
198
|
+
(entries) => {
|
199
|
+
entries.forEach(entry => {
|
200
|
+
if (entry.isIntersecting) {
|
201
|
+
const component = this.findComponentByElement(entry.target);
|
202
|
+
if (component && !component.activated) {
|
203
|
+
this.scheduleActivation(component);
|
204
|
+
}
|
205
|
+
}
|
206
|
+
});
|
207
|
+
},
|
208
|
+
{
|
209
|
+
rootMargin: this.config.rootMargin,
|
210
|
+
threshold: this.config.threshold
|
211
|
+
}
|
212
|
+
);
|
213
|
+
}
|
214
|
+
|
215
|
+
/**
|
216
|
+
* Observe component for intersection
|
217
|
+
*/
|
218
|
+
private observeComponent(component: LazyComponent): void {
|
219
|
+
if (this.intersectionObserver) {
|
220
|
+
this.intersectionObserver.observe(component.element);
|
221
|
+
} else {
|
222
|
+
// Fallback: activate immediately if no intersection observer
|
223
|
+
this.scheduleActivation(component);
|
224
|
+
}
|
225
|
+
}
|
226
|
+
|
227
|
+
/**
|
228
|
+
* Schedule component activation
|
229
|
+
*/
|
230
|
+
private scheduleActivation(component: LazyComponent): void {
|
231
|
+
if (this.config.priorityQueue) {
|
232
|
+
this.addToQueue(component);
|
233
|
+
this.processQueue();
|
234
|
+
} else {
|
235
|
+
this.activateComponent(component);
|
236
|
+
}
|
237
|
+
}
|
238
|
+
|
239
|
+
/**
|
240
|
+
* Add component to activation queue
|
241
|
+
*/
|
242
|
+
private addToQueue(component: LazyComponent): void {
|
243
|
+
// Check if already in queue
|
244
|
+
if (this.activationQueue.some(c => c.id === component.id)) {
|
245
|
+
return;
|
246
|
+
}
|
247
|
+
|
248
|
+
this.activationQueue.push(component);
|
249
|
+
|
250
|
+
// Sort by priority
|
251
|
+
this.activationQueue.sort((a, b) => {
|
252
|
+
const priorityOrder = { high: 0, normal: 1, low: 2 };
|
253
|
+
const priorityDiff = priorityOrder[a.priority] - priorityOrder[b.priority];
|
254
|
+
|
255
|
+
if (priorityDiff !== 0) {
|
256
|
+
return priorityDiff;
|
257
|
+
}
|
258
|
+
|
259
|
+
// Then by timestamp (older first)
|
260
|
+
return a.timestamp - b.timestamp;
|
261
|
+
});
|
262
|
+
}
|
263
|
+
|
264
|
+
/**
|
265
|
+
* Process activation queue
|
266
|
+
*/
|
267
|
+
private async processQueue(): Promise<void> {
|
268
|
+
if (this.isProcessing || this.activationQueue.length === 0) {
|
269
|
+
return;
|
270
|
+
}
|
271
|
+
|
272
|
+
this.isProcessing = true;
|
273
|
+
|
274
|
+
try {
|
275
|
+
while (this.activationQueue.length > 0 &&
|
276
|
+
this.activeActivations.size < this.config.maxConcurrent) {
|
277
|
+
|
278
|
+
const component = this.activationQueue.shift();
|
279
|
+
if (component && !component.activated && this.areDependenciesSatisfied(component)) {
|
280
|
+
this.activateComponent(component);
|
281
|
+
}
|
282
|
+
}
|
283
|
+
} finally {
|
284
|
+
this.isProcessing = false;
|
285
|
+
|
286
|
+
// Schedule next processing if queue is not empty
|
287
|
+
if (this.activationQueue.length > 0) {
|
288
|
+
this.scheduleNextProcessing();
|
289
|
+
}
|
290
|
+
}
|
291
|
+
}
|
292
|
+
|
293
|
+
/**
|
294
|
+
* Schedule next queue processing
|
295
|
+
*/
|
296
|
+
private scheduleNextProcessing(): void {
|
297
|
+
if (this.config.useIdleCallback && window.requestIdleCallback) {
|
298
|
+
window.requestIdleCallback(
|
299
|
+
() => this.processQueue(),
|
300
|
+
{ timeout: this.config.idleTimeout }
|
301
|
+
);
|
302
|
+
this.metrics.idleCallbacksUsed++;
|
303
|
+
} else {
|
304
|
+
setTimeout(() => this.processQueue(), 16); // Next frame
|
305
|
+
}
|
306
|
+
}
|
307
|
+
|
308
|
+
/**
|
309
|
+
* Activate a component
|
310
|
+
*/
|
311
|
+
private async activateComponent(component: LazyComponent): Promise<void> {
|
312
|
+
if (component.activated || this.activeActivations.has(component.id)) {
|
313
|
+
return;
|
314
|
+
}
|
315
|
+
|
316
|
+
this.activeActivations.add(component.id);
|
317
|
+
const startTime = performance.now();
|
318
|
+
|
319
|
+
try {
|
320
|
+
// Check cache first
|
321
|
+
const cacheKey = `component-${component.id}`;
|
322
|
+
let result = this.getCached(cacheKey);
|
323
|
+
|
324
|
+
if (!result) {
|
325
|
+
// Progressive enhancement check
|
326
|
+
if (this.config.progressiveEnhancement && !this.isEnhancementSupported(component)) {
|
327
|
+
console.warn(`Progressive enhancement not supported for component ${component.id}`);
|
328
|
+
return;
|
329
|
+
}
|
330
|
+
|
331
|
+
// Execute activator
|
332
|
+
result = await component.activator();
|
333
|
+
|
334
|
+
// Cache result if enabled
|
335
|
+
if (this.config.cacheResults) {
|
336
|
+
this.setCached(cacheKey, result);
|
337
|
+
component.cached = true;
|
338
|
+
}
|
339
|
+
}
|
340
|
+
|
341
|
+
component.activated = true;
|
342
|
+
this.metrics.activatedComponents++;
|
343
|
+
this.metrics.pendingComponents = this.metrics.totalComponents - this.metrics.activatedComponents;
|
344
|
+
|
345
|
+
// Update average activation time
|
346
|
+
const activationTime = performance.now() - startTime;
|
347
|
+
this.metrics.averageActivationTime =
|
348
|
+
(this.metrics.averageActivationTime + activationTime) / 2;
|
349
|
+
|
350
|
+
// Stop observing this component
|
351
|
+
this.intersectionObserver?.unobserve(component.element);
|
352
|
+
|
353
|
+
} catch (error) {
|
354
|
+
console.error(`Failed to activate component ${component.id}:`, error);
|
355
|
+
} finally {
|
356
|
+
this.activeActivations.delete(component.id);
|
357
|
+
|
358
|
+
// Continue processing queue
|
359
|
+
if (this.activationQueue.length > 0) {
|
360
|
+
this.scheduleNextProcessing();
|
361
|
+
}
|
362
|
+
}
|
363
|
+
}
|
364
|
+
|
365
|
+
/**
|
366
|
+
* Check if component dependencies are satisfied
|
367
|
+
*/
|
368
|
+
private areDependenciesSatisfied(component: LazyComponent): boolean {
|
369
|
+
return component.dependencies.every(depId => {
|
370
|
+
const dependency = this.components.get(depId);
|
371
|
+
return dependency?.activated || false;
|
372
|
+
});
|
373
|
+
}
|
374
|
+
|
375
|
+
/**
|
376
|
+
* Check if progressive enhancement is supported
|
377
|
+
*/
|
378
|
+
private isEnhancementSupported(component: LazyComponent): boolean {
|
379
|
+
// Basic feature detection
|
380
|
+
const requiredFeatures = [
|
381
|
+
'IntersectionObserver',
|
382
|
+
'ResizeObserver',
|
383
|
+
'requestAnimationFrame'
|
384
|
+
];
|
385
|
+
|
386
|
+
return requiredFeatures.every(feature => feature in window);
|
387
|
+
}
|
388
|
+
|
389
|
+
/**
|
390
|
+
* Find component by element
|
391
|
+
*/
|
392
|
+
private findComponentByElement(element: Element): LazyComponent | undefined {
|
393
|
+
return Array.from(this.components.values())
|
394
|
+
.find(component => component.element === element);
|
395
|
+
}
|
396
|
+
|
397
|
+
/**
|
398
|
+
* Generate unique component ID
|
399
|
+
*/
|
400
|
+
private generateId(): string {
|
401
|
+
return `lazy-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
402
|
+
}
|
403
|
+
|
404
|
+
/**
|
405
|
+
* Create initial metrics
|
406
|
+
*/
|
407
|
+
private createInitialMetrics(): LazyMetrics {
|
408
|
+
return {
|
409
|
+
totalComponents: 0,
|
410
|
+
activatedComponents: 0,
|
411
|
+
pendingComponents: 0,
|
412
|
+
cacheHits: 0,
|
413
|
+
cacheMisses: 0,
|
414
|
+
averageActivationTime: 0,
|
415
|
+
idleCallbacksUsed: 0
|
416
|
+
};
|
417
|
+
}
|
418
|
+
}
|
419
|
+
|
420
|
+
/**
|
421
|
+
* Lazy evaluation decorators and utilities
|
422
|
+
*/
|
423
|
+
export class LazyUtils {
|
424
|
+
/**
|
425
|
+
* Create a lazy-loaded image
|
426
|
+
*/
|
427
|
+
static lazyImage(
|
428
|
+
img: HTMLImageElement,
|
429
|
+
src: string,
|
430
|
+
options: { placeholder?: string; fadeIn?: boolean } = {}
|
431
|
+
): Promise<void> {
|
432
|
+
return new Promise((resolve, reject) => {
|
433
|
+
// Set placeholder
|
434
|
+
if (options.placeholder) {
|
435
|
+
img.src = options.placeholder;
|
436
|
+
}
|
437
|
+
|
438
|
+
// Create new image for loading
|
439
|
+
const loader = new Image();
|
440
|
+
|
441
|
+
loader.onload = () => {
|
442
|
+
img.src = src;
|
443
|
+
|
444
|
+
if (options.fadeIn) {
|
445
|
+
img.style.opacity = '0';
|
446
|
+
img.style.transition = 'opacity 0.3s ease';
|
447
|
+
|
448
|
+
requestAnimationFrame(() => {
|
449
|
+
img.style.opacity = '1';
|
450
|
+
});
|
451
|
+
}
|
452
|
+
|
453
|
+
resolve();
|
454
|
+
};
|
455
|
+
|
456
|
+
loader.onerror = reject;
|
457
|
+
loader.src = src;
|
458
|
+
});
|
459
|
+
}
|
460
|
+
|
461
|
+
/**
|
462
|
+
* Create a lazy-loaded script
|
463
|
+
*/
|
464
|
+
static lazyScript(src: string, options: { async?: boolean; defer?: boolean } = {}): Promise<void> {
|
465
|
+
return new Promise((resolve, reject) => {
|
466
|
+
const script = document.createElement('script');
|
467
|
+
script.src = src;
|
468
|
+
script.async = options.async !== false;
|
469
|
+
script.defer = options.defer || false;
|
470
|
+
|
471
|
+
script.onload = () => resolve();
|
472
|
+
script.onerror = reject;
|
473
|
+
|
474
|
+
document.head.appendChild(script);
|
475
|
+
});
|
476
|
+
}
|
477
|
+
|
478
|
+
/**
|
479
|
+
* Create a lazy-loaded CSS
|
480
|
+
*/
|
481
|
+
static lazyCSS(href: string, media: string = 'all'): Promise<void> {
|
482
|
+
return new Promise((resolve, reject) => {
|
483
|
+
const link = document.createElement('link');
|
484
|
+
link.rel = 'stylesheet';
|
485
|
+
link.href = href;
|
486
|
+
link.media = 'print'; // Load as print to avoid blocking
|
487
|
+
|
488
|
+
link.onload = () => {
|
489
|
+
link.media = media; // Switch to target media
|
490
|
+
resolve();
|
491
|
+
};
|
492
|
+
|
493
|
+
link.onerror = reject;
|
494
|
+
document.head.appendChild(link);
|
495
|
+
});
|
496
|
+
}
|
497
|
+
|
498
|
+
/**
|
499
|
+
* Lazy function execution with memoization
|
500
|
+
*/
|
501
|
+
static lazy<T>(fn: () => T): () => T {
|
502
|
+
let cached: T;
|
503
|
+
let executed = false;
|
504
|
+
|
505
|
+
return () => {
|
506
|
+
if (!executed) {
|
507
|
+
cached = fn();
|
508
|
+
executed = true;
|
509
|
+
}
|
510
|
+
return cached;
|
511
|
+
};
|
512
|
+
}
|
513
|
+
|
514
|
+
/**
|
515
|
+
* Debounced lazy execution
|
516
|
+
*/
|
517
|
+
static lazyDebounced<T extends (...args: any[]) => any>(
|
518
|
+
fn: T,
|
519
|
+
delay: number
|
520
|
+
): (...args: Parameters<T>) => Promise<ReturnType<T>> {
|
521
|
+
let timeoutId: number;
|
522
|
+
|
523
|
+
return (...args: Parameters<T>): Promise<ReturnType<T>> => {
|
524
|
+
return new Promise((resolve) => {
|
525
|
+
clearTimeout(timeoutId);
|
526
|
+
timeoutId = window.setTimeout(() => {
|
527
|
+
resolve(fn(...args));
|
528
|
+
}, delay);
|
529
|
+
});
|
530
|
+
};
|
531
|
+
}
|
532
|
+
}
|