@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,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
|
+
}
|