@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,289 @@
1
+ /**
2
+ * IntersectionObserver Polyfill for ProteusJS
3
+ * Provides IntersectionObserver functionality for browsers that don't support it
4
+ */
5
+
6
+ export interface IntersectionObserverEntry {
7
+ target: Element;
8
+ boundingClientRect: DOMRectReadOnly;
9
+ intersectionRect: DOMRectReadOnly;
10
+ rootBounds: DOMRectReadOnly | null;
11
+ intersectionRatio: number;
12
+ isIntersecting: boolean;
13
+ time: number;
14
+ }
15
+
16
+ export type IntersectionObserverCallback = (entries: IntersectionObserverEntry[]) => void;
17
+
18
+ export class IntersectionObserverPolyfill {
19
+ public root: Element | null;
20
+ public rootMargin: string;
21
+ public thresholds: number[];
22
+
23
+ private callback: IntersectionObserverCallback;
24
+ private observedElements: Map<Element, { lastRatio: number; wasIntersecting: boolean }> = new Map();
25
+ private rafId: number | null = null;
26
+ private isObserving: boolean = false;
27
+ private parsedRootMargin: { top: number; right: number; bottom: number; left: number };
28
+
29
+ constructor(callback: IntersectionObserverCallback, options?: IntersectionObserverInit) {
30
+ this.callback = callback;
31
+ this.root = (options?.root instanceof Element ? options.root : null);
32
+ this.rootMargin = options?.rootMargin || '0px';
33
+ this.thresholds = this.normalizeThresholds(options?.threshold);
34
+ this.parsedRootMargin = this.parseRootMargin(this.rootMargin);
35
+ }
36
+
37
+ /**
38
+ * Start observing an element
39
+ */
40
+ public observe(element: Element): void {
41
+ if (this.observedElements.has(element)) return;
42
+
43
+ this.observedElements.set(element, {
44
+ lastRatio: 0,
45
+ wasIntersecting: false
46
+ });
47
+
48
+ if (!this.isObserving) {
49
+ this.startObserving();
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Stop observing an element
55
+ */
56
+ public unobserve(element: Element): void {
57
+ this.observedElements.delete(element);
58
+
59
+ if (this.observedElements.size === 0) {
60
+ this.stopObserving();
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Disconnect all observations
66
+ */
67
+ public disconnect(): void {
68
+ this.observedElements.clear();
69
+ this.stopObserving();
70
+ }
71
+
72
+ /**
73
+ * Start the polling mechanism
74
+ */
75
+ private startObserving(): void {
76
+ if (this.isObserving) return;
77
+
78
+ this.isObserving = true;
79
+ this.checkForIntersections();
80
+ }
81
+
82
+ /**
83
+ * Stop the polling mechanism
84
+ */
85
+ private stopObserving(): void {
86
+ if (!this.isObserving) return;
87
+
88
+ this.isObserving = false;
89
+ if (this.rafId) {
90
+ cancelAnimationFrame(this.rafId);
91
+ this.rafId = null;
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Check for intersection changes
97
+ */
98
+ private checkForIntersections(): void {
99
+ if (!this.isObserving) return;
100
+
101
+ const changedEntries: IntersectionObserverEntry[] = [];
102
+ const rootBounds = this.getRootBounds();
103
+
104
+ this.observedElements.forEach((lastState, element) => {
105
+ // Check if element is still in DOM
106
+ if (!document.contains(element)) {
107
+ this.observedElements.delete(element);
108
+ return;
109
+ }
110
+
111
+ const targetRect = element.getBoundingClientRect();
112
+ const intersectionRect = this.calculateIntersection(targetRect, rootBounds);
113
+ const intersectionRatio = this.calculateIntersectionRatio(targetRect, intersectionRect);
114
+ const isIntersecting = intersectionRatio > 0;
115
+
116
+ // Check if we should trigger callback
117
+ const shouldTrigger = this.shouldTriggerCallback(
118
+ intersectionRatio,
119
+ lastState.lastRatio,
120
+ isIntersecting,
121
+ lastState.wasIntersecting
122
+ );
123
+
124
+ if (shouldTrigger) {
125
+ // Update stored state
126
+ this.observedElements.set(element, {
127
+ lastRatio: intersectionRatio,
128
+ wasIntersecting: isIntersecting
129
+ });
130
+
131
+ // Create entry
132
+ const entry: IntersectionObserverEntry = {
133
+ target: element,
134
+ boundingClientRect: this.createDOMRectReadOnly(targetRect),
135
+ intersectionRect: this.createDOMRectReadOnly(intersectionRect),
136
+ rootBounds: rootBounds ? this.createDOMRectReadOnly(rootBounds) : null,
137
+ intersectionRatio,
138
+ isIntersecting,
139
+ time: performance.now()
140
+ };
141
+
142
+ changedEntries.push(entry);
143
+ }
144
+ });
145
+
146
+ // Call callback if there are changes
147
+ if (changedEntries.length > 0) {
148
+ try {
149
+ this.callback(changedEntries);
150
+ } catch (error) {
151
+ console.error('IntersectionObserver callback error:', error);
152
+ }
153
+ }
154
+
155
+ // Schedule next check
156
+ if (this.isObserving) {
157
+ this.rafId = requestAnimationFrame(() => this.checkForIntersections());
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Get root bounds with margin applied
163
+ */
164
+ private getRootBounds(): DOMRect {
165
+ const rootElement = this.root || document.documentElement;
166
+ const rect = rootElement.getBoundingClientRect();
167
+
168
+ return new DOMRect(
169
+ rect.left - this.parsedRootMargin.left,
170
+ rect.top - this.parsedRootMargin.top,
171
+ rect.width + this.parsedRootMargin.left + this.parsedRootMargin.right,
172
+ rect.height + this.parsedRootMargin.top + this.parsedRootMargin.bottom
173
+ );
174
+ }
175
+
176
+ /**
177
+ * Calculate intersection rectangle
178
+ */
179
+ private calculateIntersection(targetRect: DOMRect, rootBounds: DOMRect): DOMRect {
180
+ const left = Math.max(targetRect.left, rootBounds.left);
181
+ const top = Math.max(targetRect.top, rootBounds.top);
182
+ const right = Math.min(targetRect.right, rootBounds.right);
183
+ const bottom = Math.min(targetRect.bottom, rootBounds.bottom);
184
+
185
+ const width = Math.max(0, right - left);
186
+ const height = Math.max(0, bottom - top);
187
+
188
+ return new DOMRect(left, top, width, height);
189
+ }
190
+
191
+ /**
192
+ * Calculate intersection ratio
193
+ */
194
+ private calculateIntersectionRatio(targetRect: DOMRect, intersectionRect: DOMRect): number {
195
+ const targetArea = targetRect.width * targetRect.height;
196
+ if (targetArea === 0) return 0;
197
+
198
+ const intersectionArea = intersectionRect.width * intersectionRect.height;
199
+ return intersectionArea / targetArea;
200
+ }
201
+
202
+ /**
203
+ * Check if callback should be triggered based on thresholds
204
+ */
205
+ private shouldTriggerCallback(
206
+ currentRatio: number,
207
+ lastRatio: number,
208
+ isIntersecting: boolean,
209
+ wasIntersecting: boolean
210
+ ): boolean {
211
+ // Always trigger on first observation
212
+ if (lastRatio === 0 && !wasIntersecting) {
213
+ return true;
214
+ }
215
+
216
+ // Check if intersection state changed
217
+ if (isIntersecting !== wasIntersecting) {
218
+ return true;
219
+ }
220
+
221
+ // Check if any threshold was crossed
222
+ for (const threshold of this.thresholds) {
223
+ if ((lastRatio < threshold && currentRatio >= threshold) ||
224
+ (lastRatio > threshold && currentRatio <= threshold)) {
225
+ return true;
226
+ }
227
+ }
228
+
229
+ return false;
230
+ }
231
+
232
+ /**
233
+ * Normalize threshold values
234
+ */
235
+ private normalizeThresholds(threshold?: number | number[]): number[] {
236
+ if (threshold === undefined) return [0];
237
+ if (typeof threshold === 'number') return [threshold];
238
+ return threshold.slice().sort((a, b) => a - b);
239
+ }
240
+
241
+ /**
242
+ * Parse root margin string
243
+ */
244
+ private parseRootMargin(margin: string): { top: number; right: number; bottom: number; left: number } {
245
+ const values = margin.split(/\s+/).map(value => {
246
+ const num = parseFloat(value);
247
+ return value.endsWith('%') ? (num / 100) * window.innerHeight : num;
248
+ });
249
+
250
+ switch (values.length) {
251
+ case 1: return { top: values[0]!, right: values[0]!, bottom: values[0]!, left: values[0]! };
252
+ case 2: return { top: values[0]!, right: values[1]!, bottom: values[0]!, left: values[1]! };
253
+ case 3: return { top: values[0]!, right: values[1]!, bottom: values[2]!, left: values[1]! };
254
+ case 4: return { top: values[0]!, right: values[1]!, bottom: values[2]!, left: values[3]! };
255
+ default: return { top: 0, right: 0, bottom: 0, left: 0 };
256
+ }
257
+ }
258
+
259
+ /**
260
+ * Create a DOMRectReadOnly-like object
261
+ */
262
+ private createDOMRectReadOnly(rect: DOMRect): DOMRectReadOnly {
263
+ return {
264
+ x: rect.x,
265
+ y: rect.y,
266
+ width: rect.width,
267
+ height: rect.height,
268
+ top: rect.top,
269
+ right: rect.right,
270
+ bottom: rect.bottom,
271
+ left: rect.left,
272
+ toJSON: () => ({
273
+ x: rect.x,
274
+ y: rect.y,
275
+ width: rect.width,
276
+ height: rect.height,
277
+ top: rect.top,
278
+ right: rect.right,
279
+ bottom: rect.bottom,
280
+ left: rect.left
281
+ })
282
+ };
283
+ }
284
+ }
285
+
286
+ // Add static method for feature detection
287
+ (IntersectionObserverPolyfill as any).isSupported = (): boolean => {
288
+ return typeof IntersectionObserver !== 'undefined';
289
+ };
@@ -0,0 +1,299 @@
1
+ /**
2
+ * Observer Manager for ProteusJS
3
+ * Manages ResizeObserver and IntersectionObserver instances efficiently
4
+ */
5
+
6
+ import type { ResizeCallback, IntersectionCallback } from '../types';
7
+ import { ResizeObserverPolyfill } from './ResizeObserverPolyfill';
8
+ import { IntersectionObserverPolyfill } from './IntersectionObserverPolyfill';
9
+
10
+ export interface ObserverEntry {
11
+ element: Element;
12
+ callback: ResizeCallback | IntersectionCallback;
13
+ options?: ResizeObserverOptions | IntersectionObserverInit;
14
+ }
15
+
16
+ export class ObserverManager {
17
+ private resizeObservers: Map<string, ResizeObserver> = new Map();
18
+ private intersectionObservers: Map<string, IntersectionObserver> = new Map();
19
+ private resizeEntries: Map<Element, ObserverEntry> = new Map();
20
+ private intersectionEntries: Map<Element, ObserverEntry> = new Map();
21
+ private isPolyfillMode: boolean = false;
22
+
23
+ constructor() {
24
+ this.checkPolyfillNeeds();
25
+ }
26
+
27
+ /**
28
+ * Observe element for resize changes
29
+ */
30
+ public observeResize(
31
+ element: Element,
32
+ callback: ResizeCallback,
33
+ options?: ResizeObserverOptions
34
+ ): () => void {
35
+ const observerKey = this.getResizeObserverKey(options);
36
+ let observer = this.resizeObservers.get(observerKey);
37
+
38
+ if (!observer) {
39
+ observer = this.createResizeObserver(options);
40
+ this.resizeObservers.set(observerKey, observer);
41
+ }
42
+
43
+ // Store entry for cleanup
44
+ const entry: ObserverEntry = {
45
+ element,
46
+ callback: callback as any,
47
+ ...(options && { options })
48
+ };
49
+ this.resizeEntries.set(element, entry);
50
+
51
+ // Start observing
52
+ observer.observe(element, options);
53
+
54
+ // Return unobserve function
55
+ return () => this.unobserveResize(element);
56
+ }
57
+
58
+ /**
59
+ * Observe element for intersection changes
60
+ */
61
+ public observeIntersection(
62
+ element: Element,
63
+ callback: IntersectionCallback,
64
+ options?: IntersectionObserverInit
65
+ ): () => void {
66
+ const observerKey = this.getIntersectionObserverKey(options);
67
+ let observer = this.intersectionObservers.get(observerKey);
68
+
69
+ if (!observer) {
70
+ observer = this.createIntersectionObserver(callback, options);
71
+ this.intersectionObservers.set(observerKey, observer);
72
+ }
73
+
74
+ // Store entry for cleanup
75
+ const entry: ObserverEntry = {
76
+ element,
77
+ callback: callback as any,
78
+ ...(options && { options })
79
+ };
80
+ this.intersectionEntries.set(element, entry);
81
+
82
+ // Start observing
83
+ observer.observe(element);
84
+
85
+ // Return unobserve function
86
+ return () => this.unobserveIntersection(element);
87
+ }
88
+
89
+ /**
90
+ * Stop observing element for resize changes
91
+ */
92
+ public unobserveResize(element: Element): void {
93
+ const entry = this.resizeEntries.get(element);
94
+ if (!entry) return;
95
+
96
+ const observerKey = this.getResizeObserverKey(entry.options as ResizeObserverOptions);
97
+ const observer = this.resizeObservers.get(observerKey);
98
+
99
+ if (observer) {
100
+ observer.unobserve(element);
101
+ }
102
+
103
+ this.resizeEntries.delete(element);
104
+ this.cleanupResizeObserver(observerKey);
105
+ }
106
+
107
+ /**
108
+ * Stop observing element for intersection changes
109
+ */
110
+ public unobserveIntersection(element: Element): void {
111
+ const entry = this.intersectionEntries.get(element);
112
+ if (!entry) return;
113
+
114
+ const observerKey = this.getIntersectionObserverKey(entry.options as IntersectionObserverInit);
115
+ const observer = this.intersectionObservers.get(observerKey);
116
+
117
+ if (observer) {
118
+ observer.unobserve(element);
119
+ }
120
+
121
+ this.intersectionEntries.delete(element);
122
+ this.cleanupIntersectionObserver(observerKey);
123
+ }
124
+
125
+ /**
126
+ * Get total number of observed elements
127
+ */
128
+ public getObservedElementCount(): number {
129
+ return this.resizeEntries.size + this.intersectionEntries.size;
130
+ }
131
+
132
+ /**
133
+ * Get number of active observers
134
+ */
135
+ public getObserverCount(): number {
136
+ return this.resizeObservers.size + this.intersectionObservers.size;
137
+ }
138
+
139
+ /**
140
+ * Check if element is being observed for resize
141
+ */
142
+ public isObservingResize(element: Element): boolean {
143
+ return this.resizeEntries.has(element);
144
+ }
145
+
146
+ /**
147
+ * Check if element is being observed for intersection
148
+ */
149
+ public isObservingIntersection(element: Element): boolean {
150
+ return this.intersectionEntries.has(element);
151
+ }
152
+
153
+ /**
154
+ * Disconnect all observers and clean up
155
+ */
156
+ public destroy(): void {
157
+ // Disconnect all resize observers
158
+ this.resizeObservers.forEach(observer => observer.disconnect());
159
+ this.resizeObservers.clear();
160
+ this.resizeEntries.clear();
161
+
162
+ // Disconnect all intersection observers
163
+ this.intersectionObservers.forEach(observer => observer.disconnect());
164
+ this.intersectionObservers.clear();
165
+ this.intersectionEntries.clear();
166
+ }
167
+
168
+ /**
169
+ * Get debug information
170
+ */
171
+ public getDebugInfo(): object {
172
+ return {
173
+ isPolyfillMode: this.isPolyfillMode,
174
+ resizeObservers: this.resizeObservers.size,
175
+ intersectionObservers: this.intersectionObservers.size,
176
+ resizeEntries: this.resizeEntries.size,
177
+ intersectionEntries: this.intersectionEntries.size,
178
+ totalObservedElements: this.getObservedElementCount(),
179
+ totalObservers: this.getObserverCount()
180
+ };
181
+ }
182
+
183
+ /**
184
+ * Check if polyfills are needed and set up accordingly
185
+ */
186
+ private checkPolyfillNeeds(): void {
187
+ if (typeof ResizeObserver === 'undefined') {
188
+ this.setupResizeObserverPolyfill();
189
+ this.isPolyfillMode = true;
190
+ }
191
+
192
+ if (typeof IntersectionObserver === 'undefined') {
193
+ this.setupIntersectionObserverPolyfill();
194
+ this.isPolyfillMode = true;
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Set up ResizeObserver polyfill
200
+ */
201
+ private setupResizeObserverPolyfill(): void {
202
+ (globalThis as any).ResizeObserver = ResizeObserverPolyfill;
203
+ }
204
+
205
+ /**
206
+ * Set up IntersectionObserver polyfill
207
+ */
208
+ private setupIntersectionObserverPolyfill(): void {
209
+ (globalThis as any).IntersectionObserver = IntersectionObserverPolyfill;
210
+ }
211
+
212
+ /**
213
+ * Create a new ResizeObserver instance
214
+ */
215
+ private createResizeObserver(_options?: ResizeObserverOptions): ResizeObserver {
216
+ return new ResizeObserver((entries) => {
217
+ entries.forEach(entry => {
218
+ const storedEntry = this.resizeEntries.get(entry.target);
219
+ if (storedEntry) {
220
+ (storedEntry.callback as ResizeCallback)(entry);
221
+ }
222
+ });
223
+ });
224
+ }
225
+
226
+ /**
227
+ * Create a new IntersectionObserver instance
228
+ */
229
+ private createIntersectionObserver(
230
+ _callback: IntersectionCallback,
231
+ options?: IntersectionObserverInit
232
+ ): IntersectionObserver {
233
+ return new IntersectionObserver((entries) => {
234
+ entries.forEach(entry => {
235
+ const storedEntry = this.intersectionEntries.get(entry.target);
236
+ if (storedEntry) {
237
+ (storedEntry.callback as IntersectionCallback)(entry);
238
+ }
239
+ });
240
+ }, options);
241
+ }
242
+
243
+ /**
244
+ * Generate key for ResizeObserver based on options
245
+ */
246
+ private getResizeObserverKey(options?: ResizeObserverOptions): string {
247
+ if (!options) return 'default';
248
+ return `box:${options.box || 'content-box'}`;
249
+ }
250
+
251
+ /**
252
+ * Generate key for IntersectionObserver based on options
253
+ */
254
+ private getIntersectionObserverKey(options?: IntersectionObserverInit): string {
255
+ if (!options) return 'default';
256
+
257
+ const root = options.root ? 'custom' : 'viewport';
258
+ const rootMargin = options.rootMargin || '0px';
259
+ const threshold = Array.isArray(options.threshold)
260
+ ? options.threshold.join(',')
261
+ : (options.threshold || 0).toString();
262
+
263
+ return `${root}:${rootMargin}:${threshold}`;
264
+ }
265
+
266
+ /**
267
+ * Clean up ResizeObserver if no longer needed
268
+ */
269
+ private cleanupResizeObserver(observerKey: string): void {
270
+ const hasElements = Array.from(this.resizeEntries.values()).some(
271
+ entry => this.getResizeObserverKey(entry.options as ResizeObserverOptions) === observerKey
272
+ );
273
+
274
+ if (!hasElements) {
275
+ const observer = this.resizeObservers.get(observerKey);
276
+ if (observer) {
277
+ observer.disconnect();
278
+ this.resizeObservers.delete(observerKey);
279
+ }
280
+ }
281
+ }
282
+
283
+ /**
284
+ * Clean up IntersectionObserver if no longer needed
285
+ */
286
+ private cleanupIntersectionObserver(observerKey: string): void {
287
+ const hasElements = Array.from(this.intersectionEntries.values()).some(
288
+ entry => this.getIntersectionObserverKey(entry.options as IntersectionObserverInit) === observerKey
289
+ );
290
+
291
+ if (!hasElements) {
292
+ const observer = this.intersectionObservers.get(observerKey);
293
+ if (observer) {
294
+ observer.disconnect();
295
+ this.intersectionObservers.delete(observerKey);
296
+ }
297
+ }
298
+ }
299
+ }