@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.
Files changed (82) hide show
  1. package/API.md +438 -0
  2. package/FEATURES.md +286 -0
  3. package/LICENSE +21 -0
  4. package/README.md +645 -0
  5. package/dist/.tsbuildinfo +1 -0
  6. package/dist/proteus.cjs.js +16014 -0
  7. package/dist/proteus.cjs.js.map +1 -0
  8. package/dist/proteus.d.ts +3018 -0
  9. package/dist/proteus.esm.js +16005 -0
  10. package/dist/proteus.esm.js.map +1 -0
  11. package/dist/proteus.esm.min.js +8 -0
  12. package/dist/proteus.esm.min.js.map +1 -0
  13. package/dist/proteus.js +16020 -0
  14. package/dist/proteus.js.map +1 -0
  15. package/dist/proteus.min.js +8 -0
  16. package/dist/proteus.min.js.map +1 -0
  17. package/package.json +98 -0
  18. package/src/__tests__/mvp-integration.test.ts +518 -0
  19. package/src/accessibility/AccessibilityEngine.ts +2106 -0
  20. package/src/accessibility/ScreenReaderSupport.ts +444 -0
  21. package/src/accessibility/__tests__/ScreenReaderSupport.test.ts +435 -0
  22. package/src/animations/FLIPAnimationSystem.ts +491 -0
  23. package/src/compatibility/BrowserCompatibility.ts +1076 -0
  24. package/src/containers/BreakpointSystem.ts +347 -0
  25. package/src/containers/ContainerBreakpoints.ts +726 -0
  26. package/src/containers/ContainerManager.ts +370 -0
  27. package/src/containers/ContainerUnits.ts +336 -0
  28. package/src/containers/ContextIsolation.ts +394 -0
  29. package/src/containers/ElementQueries.ts +411 -0
  30. package/src/containers/SmartContainer.ts +536 -0
  31. package/src/containers/SmartContainers.ts +376 -0
  32. package/src/containers/__tests__/ContainerBreakpoints.test.ts +411 -0
  33. package/src/containers/__tests__/SmartContainers.test.ts +281 -0
  34. package/src/content/ResponsiveImages.ts +570 -0
  35. package/src/core/EventSystem.ts +147 -0
  36. package/src/core/MemoryManager.ts +321 -0
  37. package/src/core/PerformanceMonitor.ts +238 -0
  38. package/src/core/PluginSystem.ts +275 -0
  39. package/src/core/ProteusJS.test.ts +164 -0
  40. package/src/core/ProteusJS.ts +962 -0
  41. package/src/developer/PerformanceProfiler.ts +567 -0
  42. package/src/developer/VisualDebuggingTools.ts +656 -0
  43. package/src/developer/ZeroConfigSystem.ts +593 -0
  44. package/src/index.ts +35 -0
  45. package/src/integration.test.ts +227 -0
  46. package/src/layout/AdaptiveGrid.ts +429 -0
  47. package/src/layout/ContentReordering.ts +532 -0
  48. package/src/layout/FlexboxEnhancer.ts +406 -0
  49. package/src/layout/FlowLayout.ts +545 -0
  50. package/src/layout/SpacingSystem.ts +512 -0
  51. package/src/observers/IntersectionObserverPolyfill.ts +289 -0
  52. package/src/observers/ObserverManager.ts +299 -0
  53. package/src/observers/ResizeObserverPolyfill.ts +179 -0
  54. package/src/performance/BatchDOMOperations.ts +519 -0
  55. package/src/performance/CSSOptimizationEngine.ts +646 -0
  56. package/src/performance/CacheOptimizationSystem.ts +601 -0
  57. package/src/performance/EfficientEventHandler.ts +740 -0
  58. package/src/performance/LazyEvaluationSystem.ts +532 -0
  59. package/src/performance/MemoryManagementSystem.ts +497 -0
  60. package/src/performance/PerformanceMonitor.ts +931 -0
  61. package/src/performance/__tests__/BatchDOMOperations.test.ts +309 -0
  62. package/src/performance/__tests__/EfficientEventHandler.test.ts +268 -0
  63. package/src/performance/__tests__/PerformanceMonitor.test.ts +422 -0
  64. package/src/polyfills/BrowserPolyfills.ts +586 -0
  65. package/src/polyfills/__tests__/BrowserPolyfills.test.ts +328 -0
  66. package/src/test/setup.ts +115 -0
  67. package/src/theming/SmartThemeSystem.ts +591 -0
  68. package/src/types/index.ts +134 -0
  69. package/src/typography/ClampScaling.ts +356 -0
  70. package/src/typography/FluidTypography.ts +759 -0
  71. package/src/typography/LineHeightOptimization.ts +430 -0
  72. package/src/typography/LineHeightOptimizer.ts +326 -0
  73. package/src/typography/TextFitting.ts +355 -0
  74. package/src/typography/TypographicScale.ts +428 -0
  75. package/src/typography/VerticalRhythm.ts +369 -0
  76. package/src/typography/__tests__/FluidTypography.test.ts +432 -0
  77. package/src/typography/__tests__/LineHeightOptimization.test.ts +436 -0
  78. package/src/utils/Logger.ts +173 -0
  79. package/src/utils/debounce.ts +259 -0
  80. package/src/utils/performance.ts +371 -0
  81. package/src/utils/support.ts +106 -0
  82. package/src/utils/version.ts +24 -0
@@ -0,0 +1,740 @@
1
+ /**
2
+ * Efficient Resizing & Event Handling for ProteusJS
3
+ * Debounced resize events with RAF timing, batch DOM operations, and 60fps performance
4
+ */
5
+
6
+ export interface EventHandlerConfig {
7
+ debounceDelay: number;
8
+ throttleDelay: number;
9
+ useRAF: boolean;
10
+ batchOperations: boolean;
11
+ passiveListeners: boolean;
12
+ intersectionBased: boolean;
13
+ performanceTarget: number; // Target frame time in ms (16.67ms for 60fps)
14
+ maxBatchSize: number;
15
+ priorityLevels: ('high' | 'normal' | 'low')[];
16
+ }
17
+
18
+ export interface EventOperation {
19
+ id: string;
20
+ element: Element;
21
+ callback: () => void;
22
+ priority: 'high' | 'normal' | 'low';
23
+ timestamp: number;
24
+ cost: number; // Estimated execution cost in ms
25
+ }
26
+
27
+ export interface PerformanceMetrics {
28
+ frameTime: number;
29
+ fps: number;
30
+ operationsPerFrame: number;
31
+ queueSize: number;
32
+ droppedFrames: number;
33
+ averageLatency: number;
34
+ }
35
+
36
+ export class EfficientEventHandler {
37
+ private config: Required<EventHandlerConfig>;
38
+ private resizeObserver: ResizeObserver | null = null;
39
+ private intersectionObserver: IntersectionObserver | null = null;
40
+ private operationQueue: Map<string, EventOperation> = new Map();
41
+ private batchQueue: EventOperation[] = [];
42
+ private rafId: number | null = null;
43
+ private lastFrameTime: number = 0;
44
+ private frameCount: number = 0;
45
+ private metrics: PerformanceMetrics;
46
+ private isProcessing: boolean = false;
47
+ private visibleElements: Set<Element> = new Set();
48
+
49
+ // Debounce and throttle timers
50
+ private debounceTimers: Map<string, number> = new Map();
51
+ private throttleTimers: Map<string, number> = new Map();
52
+ private lastThrottleTime: Map<string, number> = new Map();
53
+
54
+ constructor(config: Partial<EventHandlerConfig> = {}) {
55
+ this.config = {
56
+ debounceDelay: 16, // ~1 frame at 60fps
57
+ throttleDelay: 16,
58
+ useRAF: true,
59
+ batchOperations: true,
60
+ passiveListeners: true,
61
+ intersectionBased: true,
62
+ performanceTarget: 16.67, // 60fps target
63
+ maxBatchSize: 10,
64
+ priorityLevels: ['high', 'normal', 'low'],
65
+ ...config
66
+ };
67
+
68
+ this.metrics = this.createInitialMetrics();
69
+ this.setupPerformanceMonitoring();
70
+ }
71
+
72
+ /**
73
+ * Initialize the efficient event handling system
74
+ */
75
+ public initialize(): void {
76
+ this.setupResizeObserver();
77
+ this.setupIntersectionObserver();
78
+ this.startProcessingLoop();
79
+ }
80
+
81
+ /**
82
+ * Clean up and destroy the event handler
83
+ */
84
+ public destroy(): void {
85
+ this.stopProcessingLoop();
86
+ this.cleanupObservers();
87
+ this.clearQueues();
88
+ }
89
+
90
+ /**
91
+ * Register element for efficient resize handling
92
+ */
93
+ public observeResize(
94
+ element: Element,
95
+ callback: (entry: ResizeObserverEntry) => void,
96
+ priority: 'high' | 'normal' | 'low' = 'normal'
97
+ ): string {
98
+ const operationId = this.generateOperationId('resize', element);
99
+
100
+ // Wrap callback for batching
101
+ const wrappedCallback = () => {
102
+ if (this.resizeObserver) {
103
+ // Get the latest resize entry for this element
104
+ const rect = element.getBoundingClientRect();
105
+ const entry = {
106
+ target: element,
107
+ contentRect: rect,
108
+ borderBoxSize: [{ inlineSize: rect.width, blockSize: rect.height }],
109
+ contentBoxSize: [{ inlineSize: rect.width, blockSize: rect.height }],
110
+ devicePixelContentBoxSize: [{ inlineSize: rect.width, blockSize: rect.height }]
111
+ } as ResizeObserverEntry;
112
+
113
+ callback(entry);
114
+ }
115
+ };
116
+
117
+ // Add to operation queue
118
+ this.addOperation({
119
+ id: operationId,
120
+ element,
121
+ callback: wrappedCallback,
122
+ priority,
123
+ timestamp: performance.now(),
124
+ cost: this.estimateOperationCost('resize')
125
+ });
126
+
127
+ // Start observing with ResizeObserver
128
+ if (this.resizeObserver) {
129
+ this.resizeObserver.observe(element);
130
+ }
131
+
132
+ return operationId;
133
+ }
134
+
135
+ /**
136
+ * Register element for intersection-based activation
137
+ */
138
+ public observeIntersection(
139
+ element: Element,
140
+ callback: (entry: IntersectionObserverEntry) => void,
141
+ _options: IntersectionObserverInit = {}
142
+ ): string {
143
+ const operationId = this.generateOperationId('intersection', element);
144
+
145
+ // Wrap callback for batching
146
+ const wrappedCallback = () => {
147
+ const rect = element.getBoundingClientRect();
148
+ const rootBounds = document.documentElement.getBoundingClientRect();
149
+
150
+ const entry = {
151
+ target: element,
152
+ boundingClientRect: rect,
153
+ rootBounds,
154
+ intersectionRect: rect,
155
+ intersectionRatio: this.calculateIntersectionRatio(rect, rootBounds),
156
+ isIntersecting: this.isElementIntersecting(rect, rootBounds),
157
+ time: performance.now()
158
+ } as IntersectionObserverEntry;
159
+
160
+ callback(entry);
161
+ };
162
+
163
+ this.addOperation({
164
+ id: operationId,
165
+ element,
166
+ callback: wrappedCallback,
167
+ priority: 'normal',
168
+ timestamp: performance.now(),
169
+ cost: this.estimateOperationCost('intersection')
170
+ });
171
+
172
+ // Start observing with IntersectionObserver
173
+ if (this.intersectionObserver) {
174
+ this.intersectionObserver.observe(element);
175
+ }
176
+
177
+ return operationId;
178
+ }
179
+
180
+ /**
181
+ * Add debounced event listener
182
+ */
183
+ public addDebouncedListener(
184
+ element: Element,
185
+ event: string,
186
+ callback: (event: Event) => void,
187
+ delay?: number
188
+ ): string {
189
+ const operationId = this.generateOperationId(event, element);
190
+ const debounceDelay = delay || this.config.debounceDelay;
191
+
192
+ const debouncedCallback = (event: Event) => {
193
+ this.debounce(operationId, () => {
194
+ this.addOperation({
195
+ id: `${operationId}-${Date.now()}`,
196
+ element,
197
+ callback: () => callback(event),
198
+ priority: 'normal',
199
+ timestamp: performance.now(),
200
+ cost: this.estimateOperationCost(event.type)
201
+ });
202
+ }, debounceDelay);
203
+ };
204
+
205
+ const options = this.config.passiveListeners ? { passive: true } : false;
206
+ element.addEventListener(event, debouncedCallback, options);
207
+
208
+ return operationId;
209
+ }
210
+
211
+ /**
212
+ * Add throttled event listener
213
+ */
214
+ public addThrottledListener(
215
+ element: Element,
216
+ event: string,
217
+ callback: (event: Event) => void,
218
+ delay?: number
219
+ ): string {
220
+ const operationId = this.generateOperationId(event, element);
221
+ const throttleDelay = delay || this.config.throttleDelay;
222
+
223
+ const throttledCallback = (event: Event) => {
224
+ this.throttle(operationId, () => {
225
+ this.addOperation({
226
+ id: `${operationId}-${Date.now()}`,
227
+ element,
228
+ callback: () => callback(event),
229
+ priority: 'normal',
230
+ timestamp: performance.now(),
231
+ cost: this.estimateOperationCost(event.type)
232
+ });
233
+ }, throttleDelay);
234
+ };
235
+
236
+ const options = this.config.passiveListeners ? { passive: true } : false;
237
+ element.addEventListener(event, throttledCallback, options);
238
+
239
+ return operationId;
240
+ }
241
+
242
+ /**
243
+ * Add passive event listener
244
+ */
245
+ public addPassiveListener(
246
+ element: Element,
247
+ event: string,
248
+ callback: (event: Event) => void
249
+ ): string {
250
+ const operationId = this.generateOperationId(event, element);
251
+
252
+ const passiveCallback = (event: Event) => {
253
+ this.addOperation({
254
+ id: `${operationId}-${Date.now()}`,
255
+ element,
256
+ callback: () => callback(event),
257
+ priority: 'normal',
258
+ timestamp: performance.now(),
259
+ cost: this.estimateOperationCost(event.type)
260
+ });
261
+ };
262
+
263
+ element.addEventListener(event, passiveCallback, { passive: true });
264
+
265
+ return operationId;
266
+ }
267
+
268
+ /**
269
+ * Batch DOM operations for better performance
270
+ */
271
+ public batchDOMOperations(operations: () => void): void {
272
+ // Use requestAnimationFrame to batch DOM operations
273
+ requestAnimationFrame(() => {
274
+ operations();
275
+ });
276
+ }
277
+
278
+ /**
279
+ * Remove passive event listener
280
+ */
281
+ public removePassiveListener(_element: Element, _event: string, operationId: string): void {
282
+ this.removeOperation(operationId);
283
+ // Note: In a real implementation, we'd need to store the callback reference
284
+ // to properly remove it. For now, we just remove from our operation queue.
285
+ }
286
+
287
+ /**
288
+ * Add delegated event listener
289
+ */
290
+ public addDelegatedListener(
291
+ container: Element,
292
+ selector: string,
293
+ event: string,
294
+ callback: (event: Event, target: Element) => void
295
+ ): string {
296
+ const operationId = this.generateOperationId(`delegated-${event}`, container);
297
+
298
+ const delegatedCallback = (event: Event) => {
299
+ const target = event.target as Element;
300
+ if (target && target.matches && target.matches(selector)) {
301
+ this.addOperation({
302
+ id: `${operationId}-${Date.now()}`,
303
+ element: container,
304
+ callback: () => callback(event, target),
305
+ priority: 'normal',
306
+ timestamp: performance.now(),
307
+ cost: this.estimateOperationCost(event.type)
308
+ });
309
+ }
310
+ };
311
+
312
+ const options = this.config.passiveListeners ? { passive: true } : false;
313
+ container.addEventListener(event, delegatedCallback, options);
314
+
315
+ return operationId;
316
+ }
317
+
318
+ /**
319
+ * Remove operation from queue
320
+ */
321
+ public removeOperation(operationId: string): void {
322
+ this.operationQueue.delete(operationId);
323
+ this.batchQueue = this.batchQueue.filter(op => op.id !== operationId);
324
+ }
325
+
326
+ /**
327
+ * Get current performance metrics
328
+ */
329
+ public getMetrics(): PerformanceMetrics {
330
+ return { ...this.metrics };
331
+ }
332
+
333
+ /**
334
+ * Force process all queued operations
335
+ */
336
+ public flush(): void {
337
+ this.processOperations(true);
338
+ }
339
+
340
+ /**
341
+ * Setup ResizeObserver with batching
342
+ */
343
+ private setupResizeObserver(): void {
344
+ if (!window.ResizeObserver) {
345
+ console.warn('ResizeObserver not supported, falling back to window resize');
346
+ this.setupFallbackResize();
347
+ return;
348
+ }
349
+
350
+ this.resizeObserver = new ResizeObserver((entries) => {
351
+ if (this.config.batchOperations) {
352
+ // Batch resize operations
353
+ entries.forEach(entry => {
354
+ const operationId = this.generateOperationId('resize', entry.target);
355
+ const operation = this.operationQueue.get(operationId);
356
+
357
+ if (operation) {
358
+ operation.timestamp = performance.now();
359
+ this.batchQueue.push(operation);
360
+ }
361
+ });
362
+ } else {
363
+ // Process immediately
364
+ entries.forEach(entry => {
365
+ const operationId = this.generateOperationId('resize', entry.target);
366
+ const operation = this.operationQueue.get(operationId);
367
+
368
+ if (operation) {
369
+ operation.callback();
370
+ }
371
+ });
372
+ }
373
+ });
374
+ }
375
+
376
+ /**
377
+ * Setup IntersectionObserver for visibility-based activation
378
+ */
379
+ private setupIntersectionObserver(): void {
380
+ if (!window.IntersectionObserver) {
381
+ console.warn('IntersectionObserver not supported');
382
+ return;
383
+ }
384
+
385
+ this.intersectionObserver = new IntersectionObserver((entries) => {
386
+ entries.forEach(entry => {
387
+ if (entry.isIntersecting) {
388
+ this.visibleElements.add(entry.target);
389
+ } else {
390
+ this.visibleElements.delete(entry.target);
391
+ }
392
+
393
+ const operationId = this.generateOperationId('intersection', entry.target);
394
+ const operation = this.operationQueue.get(operationId);
395
+
396
+ if (operation) {
397
+ if (this.config.batchOperations) {
398
+ operation.timestamp = performance.now();
399
+ this.batchQueue.push(operation);
400
+ } else {
401
+ operation.callback();
402
+ }
403
+ }
404
+ });
405
+ }, {
406
+ rootMargin: '50px', // Start processing slightly before element is visible
407
+ threshold: [0, 0.1, 0.5, 1.0]
408
+ });
409
+ }
410
+
411
+ /**
412
+ * Setup fallback resize handling for older browsers
413
+ */
414
+ private setupFallbackResize(): void {
415
+ let resizeTimeout: number;
416
+
417
+ const handleResize = () => {
418
+ clearTimeout(resizeTimeout);
419
+ resizeTimeout = window.setTimeout(() => {
420
+ // Process all resize operations
421
+ this.operationQueue.forEach(operation => {
422
+ if (operation.id.includes('resize')) {
423
+ if (this.config.batchOperations) {
424
+ this.batchQueue.push(operation);
425
+ } else {
426
+ operation.callback();
427
+ }
428
+ }
429
+ });
430
+ }, this.config.debounceDelay);
431
+ };
432
+
433
+ const options = this.config.passiveListeners ? { passive: true } : false;
434
+ window.addEventListener('resize', handleResize, options);
435
+ }
436
+
437
+ /**
438
+ * Start the main processing loop
439
+ */
440
+ private startProcessingLoop(): void {
441
+ if (this.config.useRAF) {
442
+ this.processWithRAF();
443
+ } else {
444
+ this.processWithTimer();
445
+ }
446
+ }
447
+
448
+ /**
449
+ * Process operations using requestAnimationFrame
450
+ */
451
+ private processWithRAF(): void {
452
+ const process = (timestamp: number) => {
453
+ this.updateMetrics(timestamp);
454
+ this.processOperations();
455
+
456
+ if (!this.isProcessing) {
457
+ this.rafId = requestAnimationFrame(process);
458
+ }
459
+ };
460
+
461
+ this.rafId = requestAnimationFrame(process);
462
+ }
463
+
464
+ /**
465
+ * Process operations using timer (fallback)
466
+ */
467
+ private processWithTimer(): void {
468
+ const interval = Math.max(this.config.performanceTarget, 16);
469
+
470
+ const process = () => {
471
+ if (!this.isProcessing) {
472
+ this.processOperations();
473
+ setTimeout(process, interval);
474
+ }
475
+ };
476
+
477
+ setTimeout(process, interval);
478
+ }
479
+
480
+ /**
481
+ * Process queued operations with performance budgeting
482
+ */
483
+ private processOperations(forceFlush: boolean = false): void {
484
+ if (this.batchQueue.length === 0 && this.operationQueue.size === 0) {
485
+ return;
486
+ }
487
+
488
+ const startTime = performance.now();
489
+ const budget = forceFlush ? Infinity : this.config.performanceTarget;
490
+ let processedCount = 0;
491
+
492
+ // Sort operations by priority and timestamp
493
+ const sortedOperations = this.getSortedOperations();
494
+
495
+ for (const operation of sortedOperations) {
496
+ const elapsed = performance.now() - startTime;
497
+
498
+ // Check if we have budget remaining
499
+ if (!forceFlush && elapsed + operation.cost > budget) {
500
+ break;
501
+ }
502
+
503
+ // Only process operations for visible elements (if intersection-based)
504
+ if (this.config.intersectionBased &&
505
+ !this.visibleElements.has(operation.element) &&
506
+ operation.priority !== 'high') {
507
+ continue;
508
+ }
509
+
510
+ try {
511
+ operation.callback();
512
+ processedCount++;
513
+
514
+ // Remove from queues
515
+ this.operationQueue.delete(operation.id);
516
+ this.batchQueue = this.batchQueue.filter(op => op.id !== operation.id);
517
+
518
+ // Respect batch size limit
519
+ if (!forceFlush && processedCount >= this.config.maxBatchSize) {
520
+ break;
521
+ }
522
+ } catch (error) {
523
+ console.error('Error processing operation:', error);
524
+ this.operationQueue.delete(operation.id);
525
+ }
526
+ }
527
+
528
+ // Update metrics
529
+ this.metrics.operationsPerFrame = processedCount;
530
+ this.metrics.queueSize = this.operationQueue.size + this.batchQueue.length;
531
+ }
532
+
533
+ /**
534
+ * Get operations sorted by priority and age
535
+ */
536
+ private getSortedOperations(): EventOperation[] {
537
+ const allOperations = [
538
+ ...Array.from(this.operationQueue.values()),
539
+ ...this.batchQueue
540
+ ];
541
+
542
+ return allOperations.sort((a, b) => {
543
+ // Priority first
544
+ const priorityOrder = { high: 0, normal: 1, low: 2 };
545
+ const priorityDiff = priorityOrder[a.priority] - priorityOrder[b.priority];
546
+
547
+ if (priorityDiff !== 0) {
548
+ return priorityDiff;
549
+ }
550
+
551
+ // Then by timestamp (older first)
552
+ return a.timestamp - b.timestamp;
553
+ });
554
+ }
555
+
556
+ /**
557
+ * Add operation to queue
558
+ */
559
+ private addOperation(operation: EventOperation): void {
560
+ this.operationQueue.set(operation.id, operation);
561
+ }
562
+
563
+ /**
564
+ * Debounce function execution
565
+ */
566
+ private debounce(key: string, func: () => void, delay: number): void {
567
+ const existingTimer = this.debounceTimers.get(key);
568
+ if (existingTimer) {
569
+ clearTimeout(existingTimer);
570
+ }
571
+
572
+ const timer = window.setTimeout(() => {
573
+ func();
574
+ this.debounceTimers.delete(key);
575
+ }, delay);
576
+
577
+ this.debounceTimers.set(key, timer);
578
+ }
579
+
580
+ /**
581
+ * Throttle function execution
582
+ */
583
+ private throttle(key: string, func: () => void, delay: number): void {
584
+ const lastTime = this.lastThrottleTime.get(key) || 0;
585
+ const now = performance.now();
586
+
587
+ if (now - lastTime >= delay) {
588
+ func();
589
+ this.lastThrottleTime.set(key, now);
590
+ } else {
591
+ // Schedule for later if not already scheduled
592
+ if (!this.throttleTimers.has(key)) {
593
+ const timer = window.setTimeout(() => {
594
+ func();
595
+ this.lastThrottleTime.set(key, performance.now());
596
+ this.throttleTimers.delete(key);
597
+ }, delay - (now - lastTime));
598
+
599
+ this.throttleTimers.set(key, timer);
600
+ }
601
+ }
602
+ }
603
+
604
+ /**
605
+ * Generate unique operation ID
606
+ */
607
+ private generateOperationId(type: string, element: Element): string {
608
+ const elementId = element.id || element.tagName + Math.random().toString(36).substring(2, 11);
609
+ return `${type}-${elementId}`;
610
+ }
611
+
612
+ /**
613
+ * Estimate operation execution cost
614
+ */
615
+ private estimateOperationCost(operationType: string): number {
616
+ const baseCosts = {
617
+ resize: 2,
618
+ intersection: 1,
619
+ scroll: 1,
620
+ click: 0.5,
621
+ mousemove: 0.5,
622
+ default: 1
623
+ };
624
+
625
+ return baseCosts[operationType as keyof typeof baseCosts] || baseCosts.default;
626
+ }
627
+
628
+ /**
629
+ * Calculate intersection ratio
630
+ */
631
+ private calculateIntersectionRatio(rect: DOMRect, rootBounds: DOMRect): number {
632
+ const intersectionArea = Math.max(0,
633
+ Math.min(rect.right, rootBounds.right) - Math.max(rect.left, rootBounds.left)
634
+ ) * Math.max(0,
635
+ Math.min(rect.bottom, rootBounds.bottom) - Math.max(rect.top, rootBounds.top)
636
+ );
637
+
638
+ const elementArea = rect.width * rect.height;
639
+ return elementArea > 0 ? intersectionArea / elementArea : 0;
640
+ }
641
+
642
+ /**
643
+ * Check if element is intersecting viewport
644
+ */
645
+ private isElementIntersecting(rect: DOMRect, rootBounds: DOMRect): boolean {
646
+ return rect.left < rootBounds.right &&
647
+ rect.right > rootBounds.left &&
648
+ rect.top < rootBounds.bottom &&
649
+ rect.bottom > rootBounds.top;
650
+ }
651
+
652
+ /**
653
+ * Setup performance monitoring
654
+ */
655
+ private setupPerformanceMonitoring(): void {
656
+ this.lastFrameTime = performance.now();
657
+ }
658
+
659
+ /**
660
+ * Update performance metrics
661
+ */
662
+ private updateMetrics(timestamp: number): void {
663
+ const deltaTime = timestamp - this.lastFrameTime;
664
+ this.frameCount++;
665
+
666
+ // Update frame time and FPS
667
+ this.metrics.frameTime = deltaTime;
668
+ this.metrics.fps = 1000 / deltaTime;
669
+
670
+ // Track dropped frames (frames taking longer than target)
671
+ if (deltaTime > this.config.performanceTarget * 1.5) {
672
+ this.metrics.droppedFrames++;
673
+ }
674
+
675
+ // Calculate average latency (simplified)
676
+ const currentLatency = this.batchQueue.length > 0
677
+ ? timestamp - Math.min(...this.batchQueue.map(op => op.timestamp))
678
+ : 0;
679
+
680
+ this.metrics.averageLatency = (this.metrics.averageLatency + currentLatency) / 2;
681
+
682
+ this.lastFrameTime = timestamp;
683
+ }
684
+
685
+ /**
686
+ * Stop processing loop
687
+ */
688
+ private stopProcessingLoop(): void {
689
+ this.isProcessing = true;
690
+
691
+ if (this.rafId) {
692
+ cancelAnimationFrame(this.rafId);
693
+ this.rafId = null;
694
+ }
695
+ }
696
+
697
+ /**
698
+ * Clean up observers
699
+ */
700
+ private cleanupObservers(): void {
701
+ if (this.resizeObserver) {
702
+ this.resizeObserver.disconnect();
703
+ this.resizeObserver = null;
704
+ }
705
+
706
+ if (this.intersectionObserver) {
707
+ this.intersectionObserver.disconnect();
708
+ this.intersectionObserver = null;
709
+ }
710
+ }
711
+
712
+ /**
713
+ * Clear all queues
714
+ */
715
+ private clearQueues(): void {
716
+ this.operationQueue.clear();
717
+ this.batchQueue = [];
718
+
719
+ // Clear timers
720
+ this.debounceTimers.forEach(timer => clearTimeout(timer));
721
+ this.throttleTimers.forEach(timer => clearTimeout(timer));
722
+ this.debounceTimers.clear();
723
+ this.throttleTimers.clear();
724
+ this.lastThrottleTime.clear();
725
+ }
726
+
727
+ /**
728
+ * Create initial metrics
729
+ */
730
+ private createInitialMetrics(): PerformanceMetrics {
731
+ return {
732
+ frameTime: 0,
733
+ fps: 60,
734
+ operationsPerFrame: 0,
735
+ queueSize: 0,
736
+ droppedFrames: 0,
737
+ averageLatency: 0
738
+ };
739
+ }
740
+ }