@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,586 @@
|
|
1
|
+
/**
|
2
|
+
* Browser Polyfills for ProteusJS
|
3
|
+
* Comprehensive polyfills for cross-browser compatibility
|
4
|
+
*/
|
5
|
+
|
6
|
+
import { logger } from '../utils/Logger';
|
7
|
+
|
8
|
+
export interface PolyfillConfig {
|
9
|
+
resizeObserver: boolean;
|
10
|
+
intersectionObserver: boolean;
|
11
|
+
customProperties: boolean;
|
12
|
+
cssSupports: boolean;
|
13
|
+
requestAnimationFrame: boolean;
|
14
|
+
performance: boolean;
|
15
|
+
classList: boolean;
|
16
|
+
closest: boolean;
|
17
|
+
matchMedia: boolean;
|
18
|
+
mutationObserver: boolean;
|
19
|
+
}
|
20
|
+
|
21
|
+
export class BrowserPolyfills {
|
22
|
+
private static instance: BrowserPolyfills;
|
23
|
+
private polyfillsLoaded: Set<string> = new Set();
|
24
|
+
|
25
|
+
private constructor() {}
|
26
|
+
|
27
|
+
public static getInstance(): BrowserPolyfills {
|
28
|
+
if (!BrowserPolyfills.instance) {
|
29
|
+
BrowserPolyfills.instance = new BrowserPolyfills();
|
30
|
+
}
|
31
|
+
return BrowserPolyfills.instance;
|
32
|
+
}
|
33
|
+
|
34
|
+
/**
|
35
|
+
* Load all necessary polyfills
|
36
|
+
*/
|
37
|
+
public async loadPolyfills(config: Partial<PolyfillConfig> = {}): Promise<void> {
|
38
|
+
const defaultConfig: PolyfillConfig = {
|
39
|
+
resizeObserver: true,
|
40
|
+
intersectionObserver: true,
|
41
|
+
customProperties: true,
|
42
|
+
cssSupports: true,
|
43
|
+
requestAnimationFrame: true,
|
44
|
+
performance: true,
|
45
|
+
classList: true,
|
46
|
+
closest: true,
|
47
|
+
matchMedia: true,
|
48
|
+
mutationObserver: true
|
49
|
+
};
|
50
|
+
|
51
|
+
const finalConfig = { ...defaultConfig, ...config };
|
52
|
+
|
53
|
+
logger.info('Loading ProteusJS polyfills...');
|
54
|
+
|
55
|
+
// Load polyfills in order of dependency
|
56
|
+
if (finalConfig.performance) await this.loadPerformancePolyfill();
|
57
|
+
if (finalConfig.requestAnimationFrame) await this.loadRAFPolyfill();
|
58
|
+
if (finalConfig.classList) await this.loadClassListPolyfill();
|
59
|
+
if (finalConfig.closest) await this.loadClosestPolyfill();
|
60
|
+
if (finalConfig.cssSupports) await this.loadCSSSupportsPolyfill();
|
61
|
+
if (finalConfig.customProperties) await this.loadCustomPropertiesPolyfill();
|
62
|
+
if (finalConfig.matchMedia) await this.loadMatchMediaPolyfill();
|
63
|
+
if (finalConfig.mutationObserver) await this.loadMutationObserverPolyfill();
|
64
|
+
if (finalConfig.resizeObserver) await this.loadResizeObserverPolyfill();
|
65
|
+
if (finalConfig.intersectionObserver) await this.loadIntersectionObserverPolyfill();
|
66
|
+
|
67
|
+
logger.info(`Loaded ${this.polyfillsLoaded.size} polyfills`);
|
68
|
+
}
|
69
|
+
|
70
|
+
/**
|
71
|
+
* Check browser support for features
|
72
|
+
*/
|
73
|
+
public checkBrowserSupport(): {
|
74
|
+
supported: string[];
|
75
|
+
missing: string[];
|
76
|
+
warnings: string[];
|
77
|
+
} {
|
78
|
+
const supported: string[] = [];
|
79
|
+
const missing: string[] = [];
|
80
|
+
const warnings: string[] = [];
|
81
|
+
|
82
|
+
// Check ResizeObserver
|
83
|
+
if (typeof ResizeObserver !== 'undefined') {
|
84
|
+
supported.push('ResizeObserver');
|
85
|
+
} else {
|
86
|
+
missing.push('ResizeObserver');
|
87
|
+
}
|
88
|
+
|
89
|
+
// Check IntersectionObserver
|
90
|
+
if (typeof IntersectionObserver !== 'undefined') {
|
91
|
+
supported.push('IntersectionObserver');
|
92
|
+
} else {
|
93
|
+
missing.push('IntersectionObserver');
|
94
|
+
}
|
95
|
+
|
96
|
+
// Check CSS Custom Properties
|
97
|
+
if (this.supportsCSSCustomProperties()) {
|
98
|
+
supported.push('CSS Custom Properties');
|
99
|
+
} else {
|
100
|
+
missing.push('CSS Custom Properties');
|
101
|
+
}
|
102
|
+
|
103
|
+
// Check CSS.supports
|
104
|
+
if (typeof CSS !== 'undefined' && typeof CSS.supports === 'function') {
|
105
|
+
supported.push('CSS.supports');
|
106
|
+
} else {
|
107
|
+
missing.push('CSS.supports');
|
108
|
+
}
|
109
|
+
|
110
|
+
// Check performance API
|
111
|
+
if (typeof performance !== 'undefined' && typeof performance.now === 'function') {
|
112
|
+
supported.push('Performance API');
|
113
|
+
} else {
|
114
|
+
missing.push('Performance API');
|
115
|
+
}
|
116
|
+
|
117
|
+
// Check matchMedia
|
118
|
+
if (typeof window !== 'undefined' && typeof window.matchMedia === 'function') {
|
119
|
+
supported.push('matchMedia');
|
120
|
+
} else {
|
121
|
+
missing.push('matchMedia');
|
122
|
+
}
|
123
|
+
|
124
|
+
// Check MutationObserver
|
125
|
+
if (typeof window !== 'undefined' && typeof window.MutationObserver === 'function') {
|
126
|
+
supported.push('MutationObserver');
|
127
|
+
} else {
|
128
|
+
missing.push('MutationObserver');
|
129
|
+
}
|
130
|
+
|
131
|
+
// Browser-specific warnings
|
132
|
+
const userAgent = navigator.userAgent;
|
133
|
+
if (userAgent.includes('MSIE') || userAgent.includes('Trident')) {
|
134
|
+
warnings.push('Internet Explorer detected - limited support');
|
135
|
+
}
|
136
|
+
if (userAgent.includes('Safari') && !userAgent.includes('Chrome')) {
|
137
|
+
warnings.push('Safari detected - some features may need polyfills');
|
138
|
+
}
|
139
|
+
|
140
|
+
return { supported, missing, warnings };
|
141
|
+
}
|
142
|
+
|
143
|
+
/**
|
144
|
+
* ResizeObserver polyfill
|
145
|
+
*/
|
146
|
+
private async loadResizeObserverPolyfill(): Promise<void> {
|
147
|
+
if (typeof ResizeObserver !== 'undefined') {
|
148
|
+
return;
|
149
|
+
}
|
150
|
+
|
151
|
+
// Simple ResizeObserver polyfill
|
152
|
+
(window as any).ResizeObserver = class ResizeObserverPolyfill {
|
153
|
+
private callback: ResizeObserverCallback;
|
154
|
+
private elements: Set<Element> = new Set();
|
155
|
+
private rafId: number | null = null;
|
156
|
+
|
157
|
+
constructor(callback: ResizeObserverCallback) {
|
158
|
+
this.callback = callback;
|
159
|
+
}
|
160
|
+
|
161
|
+
observe(element: Element): void {
|
162
|
+
this.elements.add(element);
|
163
|
+
this.startPolling();
|
164
|
+
}
|
165
|
+
|
166
|
+
unobserve(element: Element): void {
|
167
|
+
this.elements.delete(element);
|
168
|
+
if (this.elements.size === 0) {
|
169
|
+
this.stopPolling();
|
170
|
+
}
|
171
|
+
}
|
172
|
+
|
173
|
+
disconnect(): void {
|
174
|
+
this.elements.clear();
|
175
|
+
this.stopPolling();
|
176
|
+
}
|
177
|
+
|
178
|
+
private startPolling(): void {
|
179
|
+
if (this.rafId) return;
|
180
|
+
|
181
|
+
const poll = () => {
|
182
|
+
const entries: ResizeObserverEntry[] = [];
|
183
|
+
|
184
|
+
this.elements.forEach(element => {
|
185
|
+
const rect = element.getBoundingClientRect();
|
186
|
+
entries.push({
|
187
|
+
target: element,
|
188
|
+
contentRect: rect,
|
189
|
+
borderBoxSize: [{ inlineSize: rect.width, blockSize: rect.height }],
|
190
|
+
contentBoxSize: [{ inlineSize: rect.width, blockSize: rect.height }],
|
191
|
+
devicePixelContentBoxSize: [{ inlineSize: rect.width, blockSize: rect.height }]
|
192
|
+
} as ResizeObserverEntry);
|
193
|
+
});
|
194
|
+
|
195
|
+
if (entries.length > 0) {
|
196
|
+
this.callback(entries, this);
|
197
|
+
}
|
198
|
+
|
199
|
+
this.rafId = requestAnimationFrame(poll);
|
200
|
+
};
|
201
|
+
|
202
|
+
this.rafId = requestAnimationFrame(poll);
|
203
|
+
}
|
204
|
+
|
205
|
+
private stopPolling(): void {
|
206
|
+
if (this.rafId) {
|
207
|
+
cancelAnimationFrame(this.rafId);
|
208
|
+
this.rafId = null;
|
209
|
+
}
|
210
|
+
}
|
211
|
+
};
|
212
|
+
|
213
|
+
this.polyfillsLoaded.add('ResizeObserver');
|
214
|
+
}
|
215
|
+
|
216
|
+
/**
|
217
|
+
* IntersectionObserver polyfill
|
218
|
+
*/
|
219
|
+
private async loadIntersectionObserverPolyfill(): Promise<void> {
|
220
|
+
if (typeof IntersectionObserver !== 'undefined') {
|
221
|
+
return;
|
222
|
+
}
|
223
|
+
|
224
|
+
// Simple IntersectionObserver polyfill
|
225
|
+
(window as any).IntersectionObserver = class IntersectionObserverPolyfill {
|
226
|
+
private callback: IntersectionObserverCallback;
|
227
|
+
private elements: Set<Element> = new Set();
|
228
|
+
private rafId: number | null = null;
|
229
|
+
|
230
|
+
constructor(callback: IntersectionObserverCallback, _options?: IntersectionObserverInit) {
|
231
|
+
this.callback = callback;
|
232
|
+
}
|
233
|
+
|
234
|
+
observe(element: Element): void {
|
235
|
+
this.elements.add(element);
|
236
|
+
this.startPolling();
|
237
|
+
}
|
238
|
+
|
239
|
+
unobserve(element: Element): void {
|
240
|
+
this.elements.delete(element);
|
241
|
+
if (this.elements.size === 0) {
|
242
|
+
this.stopPolling();
|
243
|
+
}
|
244
|
+
}
|
245
|
+
|
246
|
+
disconnect(): void {
|
247
|
+
this.elements.clear();
|
248
|
+
this.stopPolling();
|
249
|
+
}
|
250
|
+
|
251
|
+
private startPolling(): void {
|
252
|
+
if (this.rafId) return;
|
253
|
+
|
254
|
+
const poll = () => {
|
255
|
+
const entries: IntersectionObserverEntry[] = [];
|
256
|
+
|
257
|
+
this.elements.forEach(element => {
|
258
|
+
const rect = element.getBoundingClientRect();
|
259
|
+
const isIntersecting = rect.top < window.innerHeight && rect.bottom > 0;
|
260
|
+
|
261
|
+
entries.push({
|
262
|
+
target: element,
|
263
|
+
boundingClientRect: rect,
|
264
|
+
intersectionRatio: isIntersecting ? 1 : 0,
|
265
|
+
intersectionRect: isIntersecting ? rect : new DOMRect(),
|
266
|
+
isIntersecting,
|
267
|
+
rootBounds: new DOMRect(0, 0, window.innerWidth, window.innerHeight),
|
268
|
+
time: performance.now()
|
269
|
+
} as IntersectionObserverEntry);
|
270
|
+
});
|
271
|
+
|
272
|
+
if (entries.length > 0) {
|
273
|
+
this.callback(entries, this as any);
|
274
|
+
}
|
275
|
+
|
276
|
+
this.rafId = requestAnimationFrame(poll);
|
277
|
+
};
|
278
|
+
|
279
|
+
this.rafId = requestAnimationFrame(poll);
|
280
|
+
}
|
281
|
+
|
282
|
+
private stopPolling(): void {
|
283
|
+
if (this.rafId) {
|
284
|
+
cancelAnimationFrame(this.rafId);
|
285
|
+
this.rafId = null;
|
286
|
+
}
|
287
|
+
}
|
288
|
+
};
|
289
|
+
|
290
|
+
this.polyfillsLoaded.add('IntersectionObserver');
|
291
|
+
}
|
292
|
+
|
293
|
+
/**
|
294
|
+
* Performance API polyfill
|
295
|
+
*/
|
296
|
+
private async loadPerformancePolyfill(): Promise<void> {
|
297
|
+
if (typeof performance !== 'undefined' && typeof performance.now === 'function') {
|
298
|
+
return;
|
299
|
+
}
|
300
|
+
|
301
|
+
if (typeof performance === 'undefined') {
|
302
|
+
(window as any).performance = {};
|
303
|
+
}
|
304
|
+
|
305
|
+
if (typeof performance.now !== 'function') {
|
306
|
+
const startTime = Date.now();
|
307
|
+
performance.now = function() {
|
308
|
+
return Date.now() - startTime;
|
309
|
+
};
|
310
|
+
}
|
311
|
+
|
312
|
+
this.polyfillsLoaded.add('Performance');
|
313
|
+
}
|
314
|
+
|
315
|
+
/**
|
316
|
+
* RequestAnimationFrame polyfill
|
317
|
+
*/
|
318
|
+
private async loadRAFPolyfill(): Promise<void> {
|
319
|
+
if (typeof requestAnimationFrame === 'function') {
|
320
|
+
return;
|
321
|
+
}
|
322
|
+
|
323
|
+
let lastTime = 0;
|
324
|
+
|
325
|
+
(window as any).requestAnimationFrame = function(callback: FrameRequestCallback): number {
|
326
|
+
const currTime = new Date().getTime();
|
327
|
+
const timeToCall = Math.max(0, 16 - (currTime - lastTime));
|
328
|
+
const id = window.setTimeout(() => {
|
329
|
+
callback(currTime + timeToCall);
|
330
|
+
}, timeToCall);
|
331
|
+
lastTime = currTime + timeToCall;
|
332
|
+
return id;
|
333
|
+
};
|
334
|
+
|
335
|
+
(window as any).cancelAnimationFrame = function(id: number): void {
|
336
|
+
clearTimeout(id);
|
337
|
+
};
|
338
|
+
|
339
|
+
this.polyfillsLoaded.add('RequestAnimationFrame');
|
340
|
+
}
|
341
|
+
|
342
|
+
/**
|
343
|
+
* CSS.supports polyfill
|
344
|
+
*/
|
345
|
+
private async loadCSSSupportsPolyfill(): Promise<void> {
|
346
|
+
if (typeof CSS !== 'undefined' && typeof CSS.supports === 'function') {
|
347
|
+
return;
|
348
|
+
}
|
349
|
+
|
350
|
+
if (typeof CSS === 'undefined') {
|
351
|
+
(window as any).CSS = {};
|
352
|
+
}
|
353
|
+
|
354
|
+
CSS.supports = function(property: string, value?: string): boolean {
|
355
|
+
const testElement = document.createElement('div');
|
356
|
+
|
357
|
+
try {
|
358
|
+
if (value) {
|
359
|
+
testElement.style.setProperty(property, value);
|
360
|
+
return testElement.style.getPropertyValue(property) === value;
|
361
|
+
} else {
|
362
|
+
// Parse property: value format
|
363
|
+
const colonIndex = property.indexOf(':');
|
364
|
+
if (colonIndex === -1) return false;
|
365
|
+
|
366
|
+
const prop = property.substring(0, colonIndex).trim();
|
367
|
+
const val = property.substring(colonIndex + 1).trim();
|
368
|
+
|
369
|
+
testElement.style.setProperty(prop, val);
|
370
|
+
return testElement.style.getPropertyValue(prop) === val;
|
371
|
+
}
|
372
|
+
} catch {
|
373
|
+
return false;
|
374
|
+
}
|
375
|
+
};
|
376
|
+
|
377
|
+
this.polyfillsLoaded.add('CSS.supports');
|
378
|
+
}
|
379
|
+
|
380
|
+
/**
|
381
|
+
* CSS Custom Properties polyfill
|
382
|
+
*/
|
383
|
+
private async loadCustomPropertiesPolyfill(): Promise<void> {
|
384
|
+
if (this.supportsCSSCustomProperties()) {
|
385
|
+
return;
|
386
|
+
}
|
387
|
+
|
388
|
+
// Basic CSS custom properties support
|
389
|
+
const customProperties = new Map<string, string>();
|
390
|
+
|
391
|
+
// Override getComputedStyle to handle custom properties
|
392
|
+
const originalGetComputedStyle = window.getComputedStyle;
|
393
|
+
window.getComputedStyle = function(element: Element, pseudoElement?: string | null): CSSStyleDeclaration {
|
394
|
+
const styles = originalGetComputedStyle.call(this, element, pseudoElement);
|
395
|
+
|
396
|
+
// Add getPropertyValue method that handles custom properties
|
397
|
+
const originalGetPropertyValue = styles.getPropertyValue;
|
398
|
+
styles.getPropertyValue = function(property: string): string {
|
399
|
+
if (property.startsWith('--')) {
|
400
|
+
return customProperties.get(property) || '';
|
401
|
+
}
|
402
|
+
return originalGetPropertyValue.call(this, property);
|
403
|
+
};
|
404
|
+
|
405
|
+
return styles;
|
406
|
+
};
|
407
|
+
|
408
|
+
this.polyfillsLoaded.add('CSS Custom Properties');
|
409
|
+
}
|
410
|
+
|
411
|
+
/**
|
412
|
+
* matchMedia polyfill
|
413
|
+
*/
|
414
|
+
private async loadMatchMediaPolyfill(): Promise<void> {
|
415
|
+
if (typeof window !== 'undefined' && typeof window.matchMedia === 'function') {
|
416
|
+
return;
|
417
|
+
}
|
418
|
+
|
419
|
+
// Simple matchMedia polyfill
|
420
|
+
(window as any).matchMedia = function(query: string): MediaQueryList {
|
421
|
+
return {
|
422
|
+
matches: false,
|
423
|
+
media: query,
|
424
|
+
onchange: null,
|
425
|
+
addListener() {},
|
426
|
+
removeListener() {},
|
427
|
+
addEventListener() {},
|
428
|
+
removeEventListener() {},
|
429
|
+
dispatchEvent() { return true; }
|
430
|
+
} as MediaQueryList;
|
431
|
+
};
|
432
|
+
|
433
|
+
this.polyfillsLoaded.add('matchMedia');
|
434
|
+
}
|
435
|
+
|
436
|
+
/**
|
437
|
+
* MutationObserver polyfill
|
438
|
+
*/
|
439
|
+
private async loadMutationObserverPolyfill(): Promise<void> {
|
440
|
+
if (typeof window !== 'undefined' && typeof window.MutationObserver === 'function') {
|
441
|
+
return;
|
442
|
+
}
|
443
|
+
|
444
|
+
// Simple MutationObserver polyfill
|
445
|
+
class MutationObserverPolyfill {
|
446
|
+
private callback: MutationCallback;
|
447
|
+
private target: Node | null = null;
|
448
|
+
private config: MutationObserverInit = {};
|
449
|
+
private isObserving = false;
|
450
|
+
|
451
|
+
constructor(callback: MutationCallback) {
|
452
|
+
this.callback = callback;
|
453
|
+
}
|
454
|
+
|
455
|
+
observe(target: Node, config: MutationObserverInit = {}): void {
|
456
|
+
this.target = target;
|
457
|
+
this.config = config;
|
458
|
+
this.isObserving = true;
|
459
|
+
// In a real polyfill, we'd set up polling or event listeners
|
460
|
+
// For testing purposes, this basic implementation is sufficient
|
461
|
+
}
|
462
|
+
|
463
|
+
disconnect(): void {
|
464
|
+
this.isObserving = false;
|
465
|
+
this.target = null;
|
466
|
+
}
|
467
|
+
|
468
|
+
takeRecords(): MutationRecord[] {
|
469
|
+
return [];
|
470
|
+
}
|
471
|
+
}
|
472
|
+
|
473
|
+
(window as any).MutationObserver = MutationObserverPolyfill;
|
474
|
+
this.polyfillsLoaded.add('MutationObserver');
|
475
|
+
}
|
476
|
+
|
477
|
+
/**
|
478
|
+
* Element.classList polyfill
|
479
|
+
*/
|
480
|
+
private async loadClassListPolyfill(): Promise<void> {
|
481
|
+
if ('classList' in document.createElement('div')) {
|
482
|
+
return;
|
483
|
+
}
|
484
|
+
|
485
|
+
// Basic classList polyfill for older browsers
|
486
|
+
Object.defineProperty(Element.prototype, 'classList', {
|
487
|
+
get() {
|
488
|
+
const element = this;
|
489
|
+
return {
|
490
|
+
add(className: string) {
|
491
|
+
if (!element.className.includes(className)) {
|
492
|
+
element.className += (element.className ? ' ' : '') + className;
|
493
|
+
}
|
494
|
+
},
|
495
|
+
remove(className: string) {
|
496
|
+
element.className = element.className
|
497
|
+
.split(' ')
|
498
|
+
.filter((cls: string) => cls !== className)
|
499
|
+
.join(' ');
|
500
|
+
},
|
501
|
+
contains(className: string) {
|
502
|
+
return element.className.split(' ').includes(className);
|
503
|
+
},
|
504
|
+
toggle(className: string) {
|
505
|
+
if (this.contains(className)) {
|
506
|
+
this.remove(className);
|
507
|
+
} else {
|
508
|
+
this.add(className);
|
509
|
+
}
|
510
|
+
}
|
511
|
+
};
|
512
|
+
}
|
513
|
+
});
|
514
|
+
|
515
|
+
this.polyfillsLoaded.add('Element.classList');
|
516
|
+
}
|
517
|
+
|
518
|
+
/**
|
519
|
+
* Element.closest polyfill
|
520
|
+
*/
|
521
|
+
private async loadClosestPolyfill(): Promise<void> {
|
522
|
+
if (typeof Element.prototype.closest === 'function') {
|
523
|
+
return;
|
524
|
+
}
|
525
|
+
|
526
|
+
Element.prototype.closest = function(selector: string): Element | null {
|
527
|
+
let element: Element | null = this;
|
528
|
+
|
529
|
+
while (element && element.nodeType === 1) {
|
530
|
+
if (element.matches && element.matches(selector)) {
|
531
|
+
return element;
|
532
|
+
}
|
533
|
+
element = element.parentElement;
|
534
|
+
}
|
535
|
+
|
536
|
+
return null;
|
537
|
+
};
|
538
|
+
|
539
|
+
this.polyfillsLoaded.add('Element.closest');
|
540
|
+
}
|
541
|
+
|
542
|
+
/**
|
543
|
+
* Check if CSS Custom Properties are supported
|
544
|
+
*/
|
545
|
+
private supportsCSSCustomProperties(): boolean {
|
546
|
+
try {
|
547
|
+
const testElement = document.createElement('div');
|
548
|
+
testElement.style.setProperty('--test', 'test');
|
549
|
+
return testElement.style.getPropertyValue('--test') === 'test';
|
550
|
+
} catch {
|
551
|
+
return false;
|
552
|
+
}
|
553
|
+
}
|
554
|
+
|
555
|
+
/**
|
556
|
+
* Get loaded polyfills
|
557
|
+
*/
|
558
|
+
public getLoadedPolyfills(): string[] {
|
559
|
+
return Array.from(this.polyfillsLoaded);
|
560
|
+
}
|
561
|
+
|
562
|
+
/**
|
563
|
+
* Initialize polyfills automatically based on browser detection
|
564
|
+
*/
|
565
|
+
public static async autoInit(): Promise<void> {
|
566
|
+
const polyfills = BrowserPolyfills.getInstance();
|
567
|
+
const support = polyfills.checkBrowserSupport();
|
568
|
+
|
569
|
+
// Load polyfills for missing features
|
570
|
+
const config: Partial<PolyfillConfig> = {
|
571
|
+
resizeObserver: support.missing.includes('ResizeObserver'),
|
572
|
+
intersectionObserver: support.missing.includes('IntersectionObserver'),
|
573
|
+
cssSupports: support.missing.includes('CSS.supports'),
|
574
|
+
performance: support.missing.includes('Performance API'),
|
575
|
+
customProperties: support.missing.includes('CSS Custom Properties'),
|
576
|
+
matchMedia: support.missing.includes('matchMedia'),
|
577
|
+
mutationObserver: support.missing.includes('MutationObserver')
|
578
|
+
};
|
579
|
+
|
580
|
+
await polyfills.loadPolyfills(config);
|
581
|
+
|
582
|
+
if (support.warnings.length > 0) {
|
583
|
+
logger.warn('Browser Warnings:', support.warnings);
|
584
|
+
}
|
585
|
+
}
|
586
|
+
}
|