@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,726 @@
|
|
1
|
+
/**
|
2
|
+
* ContainerBreakpoints - Breakpoint management system for container queries
|
3
|
+
* Handles breakpoint registration, monitoring, and callback execution
|
4
|
+
*/
|
5
|
+
|
6
|
+
import { logger } from '../utils/Logger';
|
7
|
+
import { PerformanceMonitor } from '../performance/PerformanceMonitor';
|
8
|
+
|
9
|
+
export interface BreakpointMap {
|
10
|
+
[key: string]: string;
|
11
|
+
}
|
12
|
+
|
13
|
+
export interface BreakpointConfig {
|
14
|
+
element: Element;
|
15
|
+
breakpoints: BreakpointMap;
|
16
|
+
callback?: BreakpointCallback;
|
17
|
+
currentBreakpoint?: string;
|
18
|
+
parsedBreakpoints: ParsedBreakpoint[];
|
19
|
+
}
|
20
|
+
|
21
|
+
export interface ParsedBreakpoint {
|
22
|
+
name: string;
|
23
|
+
value: number;
|
24
|
+
unit: string;
|
25
|
+
type: 'min' | 'max';
|
26
|
+
originalValue: string;
|
27
|
+
}
|
28
|
+
|
29
|
+
export interface BreakpointCallbackData {
|
30
|
+
width: number;
|
31
|
+
height: number;
|
32
|
+
breakpoint: string;
|
33
|
+
previousBreakpoint?: string;
|
34
|
+
element: Element;
|
35
|
+
}
|
36
|
+
|
37
|
+
export type BreakpointCallback = (breakpoint: string, data: BreakpointCallbackData) => void;
|
38
|
+
|
39
|
+
export class ContainerBreakpoints {
|
40
|
+
private breakpoints: Map<string, BreakpointConfig> = new Map();
|
41
|
+
private observer: ResizeObserver | null = null;
|
42
|
+
private idCounter: number = 0;
|
43
|
+
private cssRules: Map<string, CSSStyleSheet> = new Map();
|
44
|
+
private performanceMonitor?: PerformanceMonitor;
|
45
|
+
private performanceMetrics: {
|
46
|
+
evaluationTimes: number[];
|
47
|
+
averageTime: number;
|
48
|
+
totalEvaluations: number;
|
49
|
+
} = {
|
50
|
+
evaluationTimes: [],
|
51
|
+
averageTime: 0,
|
52
|
+
totalEvaluations: 0
|
53
|
+
};
|
54
|
+
|
55
|
+
constructor() {
|
56
|
+
this.setupResizeObserver();
|
57
|
+
this.setupPerformanceMonitoring();
|
58
|
+
this.injectBaseCSS();
|
59
|
+
}
|
60
|
+
|
61
|
+
/**
|
62
|
+
* Set performance monitor for integration
|
63
|
+
*/
|
64
|
+
public setPerformanceMonitor(monitor: PerformanceMonitor): void {
|
65
|
+
this.performanceMonitor = monitor;
|
66
|
+
}
|
67
|
+
|
68
|
+
/**
|
69
|
+
* Register breakpoints for a container element (alias for register)
|
70
|
+
*/
|
71
|
+
public registerContainer(
|
72
|
+
element: Element,
|
73
|
+
breakpoints: BreakpointMap,
|
74
|
+
callback?: BreakpointCallback
|
75
|
+
): string {
|
76
|
+
return this.register(element, breakpoints, callback);
|
77
|
+
}
|
78
|
+
|
79
|
+
/**
|
80
|
+
* Register breakpoints for a container element
|
81
|
+
*/
|
82
|
+
public register(
|
83
|
+
element: Element,
|
84
|
+
breakpoints: BreakpointMap,
|
85
|
+
callback?: BreakpointCallback
|
86
|
+
): string {
|
87
|
+
const id = this.generateId();
|
88
|
+
|
89
|
+
try {
|
90
|
+
const parsedBreakpoints = this.parseBreakpoints(breakpoints);
|
91
|
+
const config: BreakpointConfig = {
|
92
|
+
element,
|
93
|
+
breakpoints,
|
94
|
+
...(callback && { callback }),
|
95
|
+
parsedBreakpoints
|
96
|
+
};
|
97
|
+
|
98
|
+
this.breakpoints.set(id, config);
|
99
|
+
|
100
|
+
// Start observing the element
|
101
|
+
if (this.observer) {
|
102
|
+
this.observer.observe(element);
|
103
|
+
}
|
104
|
+
|
105
|
+
// Set initial breakpoint
|
106
|
+
this.updateBreakpoint(id);
|
107
|
+
|
108
|
+
return id;
|
109
|
+
} catch (error) {
|
110
|
+
logger.error('Failed to register breakpoints', error);
|
111
|
+
return '';
|
112
|
+
}
|
113
|
+
}
|
114
|
+
|
115
|
+
/**
|
116
|
+
* Update container breakpoints (alias for updateElement)
|
117
|
+
*/
|
118
|
+
public updateContainer(element: Element): void {
|
119
|
+
this.updateElement(element);
|
120
|
+
}
|
121
|
+
|
122
|
+
/**
|
123
|
+
* Unregister breakpoints for an element
|
124
|
+
*/
|
125
|
+
public unregister(id: string): void {
|
126
|
+
const config = this.breakpoints.get(id);
|
127
|
+
if (!config) return;
|
128
|
+
|
129
|
+
// Stop observing the element if no other configs use it
|
130
|
+
const elementStillUsed = Array.from(this.breakpoints.values())
|
131
|
+
.some(c => c !== config && c.element === config.element);
|
132
|
+
|
133
|
+
if (!elementStillUsed && this.observer) {
|
134
|
+
this.observer.unobserve(config.element);
|
135
|
+
}
|
136
|
+
|
137
|
+
// Remove CSS classes
|
138
|
+
this.removeBreakpointClasses(config.element, config.parsedBreakpoints);
|
139
|
+
|
140
|
+
this.breakpoints.delete(id);
|
141
|
+
}
|
142
|
+
|
143
|
+
/**
|
144
|
+
* Get current breakpoint for a registered element
|
145
|
+
*/
|
146
|
+
public getCurrentBreakpoint(id: string): string | null {
|
147
|
+
const config = this.breakpoints.get(id);
|
148
|
+
return config?.currentBreakpoint || null;
|
149
|
+
}
|
150
|
+
|
151
|
+
/**
|
152
|
+
* Update breakpoints for a specific element
|
153
|
+
*/
|
154
|
+
public updateElement(element: Element): void {
|
155
|
+
for (const [id, config] of this.breakpoints) {
|
156
|
+
if (config.element === element) {
|
157
|
+
this.updateBreakpoint(id);
|
158
|
+
}
|
159
|
+
}
|
160
|
+
}
|
161
|
+
|
162
|
+
/**
|
163
|
+
* Get all registered breakpoint configurations
|
164
|
+
*/
|
165
|
+
public getAllConfigs(): Map<string, BreakpointConfig> {
|
166
|
+
return new Map(this.breakpoints);
|
167
|
+
}
|
168
|
+
|
169
|
+
// destroy method moved to end of class with enhanced functionality
|
170
|
+
|
171
|
+
/**
|
172
|
+
* Setup ResizeObserver to monitor element size changes
|
173
|
+
*/
|
174
|
+
private setupResizeObserver(): void {
|
175
|
+
if (typeof ResizeObserver === 'undefined') {
|
176
|
+
logger.warn('ResizeObserver not supported. Container breakpoints may not work correctly.');
|
177
|
+
return;
|
178
|
+
}
|
179
|
+
|
180
|
+
this.observer = new ResizeObserver((entries) => {
|
181
|
+
for (const entry of entries) {
|
182
|
+
this.updateElement(entry.target);
|
183
|
+
}
|
184
|
+
});
|
185
|
+
}
|
186
|
+
|
187
|
+
/**
|
188
|
+
* Generate unique ID for breakpoint registration
|
189
|
+
*/
|
190
|
+
private generateId(): string {
|
191
|
+
return `proteus-breakpoint-${++this.idCounter}-${Date.now()}`;
|
192
|
+
}
|
193
|
+
|
194
|
+
/**
|
195
|
+
* Parse breakpoint values into standardized format
|
196
|
+
*/
|
197
|
+
private parseBreakpoints(breakpoints: BreakpointMap): ParsedBreakpoint[] {
|
198
|
+
const parsed: ParsedBreakpoint[] = [];
|
199
|
+
|
200
|
+
for (const [name, value] of Object.entries(breakpoints)) {
|
201
|
+
try {
|
202
|
+
const parsedValue = this.parseBreakpointValue(value);
|
203
|
+
parsed.push({
|
204
|
+
name,
|
205
|
+
value: parsedValue.value,
|
206
|
+
unit: parsedValue.unit,
|
207
|
+
type: parsedValue.type,
|
208
|
+
originalValue: value
|
209
|
+
});
|
210
|
+
} catch (error) {
|
211
|
+
logger.warn(`Invalid breakpoint value "${value}" for "${name}"`, error);
|
212
|
+
}
|
213
|
+
}
|
214
|
+
|
215
|
+
// Sort by value (ascending)
|
216
|
+
parsed.sort((a, b) => a.value - b.value);
|
217
|
+
|
218
|
+
return parsed;
|
219
|
+
}
|
220
|
+
|
221
|
+
/**
|
222
|
+
* Parse individual breakpoint value
|
223
|
+
*/
|
224
|
+
private parseBreakpointValue(value: string): { value: number; unit: string; type: 'min' | 'max' } {
|
225
|
+
// Handle media query syntax like "(max-width: 200px)" or "(min-width: 400px)"
|
226
|
+
const mediaQueryMatch = value.match(/\((min|max)-width:\s*(\d+(?:\.\d+)?)(px|em|rem|%|vw|vh|ch|cw|cmin|cmax)?\)/);
|
227
|
+
|
228
|
+
if (mediaQueryMatch) {
|
229
|
+
const type = mediaQueryMatch[1] as 'min' | 'max';
|
230
|
+
const numericValue = parseFloat(mediaQueryMatch[2]!);
|
231
|
+
const unit = mediaQueryMatch[3] || 'px';
|
232
|
+
|
233
|
+
return { value: numericValue, unit, type };
|
234
|
+
}
|
235
|
+
|
236
|
+
// Handle simple numeric values like "200px"
|
237
|
+
const simpleMatch = value.match(/^(\d+(?:\.\d+)?)(px|em|rem|%|vw|vh|ch|cw|cmin|cmax)?$/);
|
238
|
+
|
239
|
+
if (!simpleMatch) {
|
240
|
+
throw new Error(`Invalid breakpoint value: ${value}`);
|
241
|
+
}
|
242
|
+
|
243
|
+
const numericValue = parseFloat(simpleMatch[1]!);
|
244
|
+
const unit = simpleMatch[2] || 'px';
|
245
|
+
|
246
|
+
// Convert relative units to pixels for comparison
|
247
|
+
let pixelValue = numericValue;
|
248
|
+
|
249
|
+
switch (unit) {
|
250
|
+
case 'em':
|
251
|
+
case 'rem':
|
252
|
+
pixelValue = numericValue * 16; // Assume 16px base font size
|
253
|
+
break;
|
254
|
+
case 'vw':
|
255
|
+
pixelValue = (numericValue / 100) * window.innerWidth;
|
256
|
+
break;
|
257
|
+
case 'vh':
|
258
|
+
pixelValue = (numericValue / 100) * window.innerHeight;
|
259
|
+
break;
|
260
|
+
case '%':
|
261
|
+
// For percentage, we'll need the parent element context
|
262
|
+
// For now, treat as pixels
|
263
|
+
pixelValue = numericValue;
|
264
|
+
break;
|
265
|
+
case 'px':
|
266
|
+
default:
|
267
|
+
pixelValue = numericValue;
|
268
|
+
break;
|
269
|
+
}
|
270
|
+
|
271
|
+
return { value: pixelValue, unit, type: 'min' }; // Default to min-width for simple values
|
272
|
+
}
|
273
|
+
|
274
|
+
/**
|
275
|
+
* Update breakpoint for a specific configuration
|
276
|
+
*/
|
277
|
+
private updateBreakpoint(id: string): void {
|
278
|
+
const config = this.breakpoints.get(id);
|
279
|
+
if (!config) return;
|
280
|
+
|
281
|
+
const rect = config.element.getBoundingClientRect();
|
282
|
+
const width = rect.width;
|
283
|
+
const height = rect.height;
|
284
|
+
|
285
|
+
// Determine current breakpoint
|
286
|
+
const currentBreakpoint = this.determineBreakpoint(width, config.parsedBreakpoints);
|
287
|
+
const previousBreakpoint = config.currentBreakpoint;
|
288
|
+
|
289
|
+
// Record performance metrics
|
290
|
+
if (this.performanceMonitor) {
|
291
|
+
this.performanceMonitor.recordOperation();
|
292
|
+
}
|
293
|
+
|
294
|
+
// Only update if breakpoint changed
|
295
|
+
if (currentBreakpoint !== previousBreakpoint) {
|
296
|
+
config.currentBreakpoint = currentBreakpoint;
|
297
|
+
|
298
|
+
// Update CSS classes
|
299
|
+
this.updateBreakpointClasses(config.element, currentBreakpoint, config.parsedBreakpoints);
|
300
|
+
|
301
|
+
// Call callback if provided
|
302
|
+
if (config.callback) {
|
303
|
+
try {
|
304
|
+
// Create callback data object with core properties
|
305
|
+
const callbackData: any = {
|
306
|
+
width,
|
307
|
+
height,
|
308
|
+
breakpoint: currentBreakpoint
|
309
|
+
};
|
310
|
+
|
311
|
+
// Add optional properties only if they exist
|
312
|
+
if (previousBreakpoint) {
|
313
|
+
callbackData.previousBreakpoint = previousBreakpoint;
|
314
|
+
}
|
315
|
+
|
316
|
+
// Add element reference for advanced use cases
|
317
|
+
callbackData.element = config.element;
|
318
|
+
|
319
|
+
config.callback(currentBreakpoint, callbackData);
|
320
|
+
} catch (error) {
|
321
|
+
logger.error('Error in breakpoint callback', error);
|
322
|
+
}
|
323
|
+
}
|
324
|
+
|
325
|
+
// Dispatch custom event
|
326
|
+
this.dispatchBreakpointEvent(config.element, currentBreakpoint, {
|
327
|
+
width,
|
328
|
+
height,
|
329
|
+
breakpoint: currentBreakpoint,
|
330
|
+
...(previousBreakpoint && { previousBreakpoint }),
|
331
|
+
element: config.element
|
332
|
+
});
|
333
|
+
}
|
334
|
+
}
|
335
|
+
|
336
|
+
/**
|
337
|
+
* Determine which breakpoint applies to the current width
|
338
|
+
*/
|
339
|
+
private determineBreakpoint(width: number, breakpoints: ParsedBreakpoint[]): string {
|
340
|
+
let activeBreakpoint = 'default';
|
341
|
+
|
342
|
+
// Separate min-width and max-width breakpoints
|
343
|
+
const minWidthBreakpoints = breakpoints.filter(bp => bp.type === 'min');
|
344
|
+
const maxWidthBreakpoints = breakpoints.filter(bp => bp.type === 'max');
|
345
|
+
|
346
|
+
// For min-width breakpoints, find the largest one that matches
|
347
|
+
// (iterate in reverse order since they're sorted ascending)
|
348
|
+
for (let i = minWidthBreakpoints.length - 1; i >= 0; i--) {
|
349
|
+
const breakpoint = minWidthBreakpoints[i];
|
350
|
+
if (breakpoint && width >= breakpoint.value) {
|
351
|
+
activeBreakpoint = breakpoint.name;
|
352
|
+
break;
|
353
|
+
}
|
354
|
+
}
|
355
|
+
|
356
|
+
// For max-width breakpoints, find the first one that matches
|
357
|
+
// (iterate in normal order since they're sorted ascending)
|
358
|
+
for (const breakpoint of maxWidthBreakpoints) {
|
359
|
+
if (width <= breakpoint.value) {
|
360
|
+
return breakpoint.name; // Max-width takes precedence
|
361
|
+
}
|
362
|
+
}
|
363
|
+
|
364
|
+
return activeBreakpoint;
|
365
|
+
}
|
366
|
+
|
367
|
+
/**
|
368
|
+
* Update CSS classes on element based on current breakpoint
|
369
|
+
*/
|
370
|
+
private updateBreakpointClasses(
|
371
|
+
element: Element,
|
372
|
+
currentBreakpoint: string,
|
373
|
+
breakpoints: ParsedBreakpoint[]
|
374
|
+
): void {
|
375
|
+
// Remove all existing breakpoint classes
|
376
|
+
this.removeBreakpointClasses(element, breakpoints);
|
377
|
+
|
378
|
+
// Add current breakpoint class
|
379
|
+
element.classList.add(`proteus-${currentBreakpoint}`);
|
380
|
+
element.setAttribute('data-proteus-breakpoint', currentBreakpoint);
|
381
|
+
element.setAttribute('data-container', currentBreakpoint); // For test compatibility
|
382
|
+
}
|
383
|
+
|
384
|
+
/**
|
385
|
+
* Remove all breakpoint classes from element
|
386
|
+
*/
|
387
|
+
private removeBreakpointClasses(element: Element, breakpoints: ParsedBreakpoint[]): void {
|
388
|
+
for (const breakpoint of breakpoints) {
|
389
|
+
element.classList.remove(`proteus-${breakpoint.name}`);
|
390
|
+
}
|
391
|
+
element.removeAttribute('data-proteus-breakpoint');
|
392
|
+
element.removeAttribute('data-container'); // Remove test compatibility attribute
|
393
|
+
}
|
394
|
+
|
395
|
+
/**
|
396
|
+
* Dispatch custom breakpoint change event
|
397
|
+
*/
|
398
|
+
private dispatchBreakpointEvent(
|
399
|
+
element: Element,
|
400
|
+
_breakpoint: string,
|
401
|
+
data: BreakpointCallbackData
|
402
|
+
): void {
|
403
|
+
const event = new CustomEvent('proteus:breakpoint-change', {
|
404
|
+
detail: data,
|
405
|
+
bubbles: true
|
406
|
+
});
|
407
|
+
|
408
|
+
element.dispatchEvent(event);
|
409
|
+
}
|
410
|
+
|
411
|
+
/**
|
412
|
+
* Get performance metrics
|
413
|
+
*/
|
414
|
+
public getMetrics(): {
|
415
|
+
totalRegistrations: number;
|
416
|
+
activeElements: number;
|
417
|
+
averageBreakpoints: number;
|
418
|
+
breakpointDistribution: Record<string, number>;
|
419
|
+
} {
|
420
|
+
const configs = Array.from(this.breakpoints.values());
|
421
|
+
const breakpointDistribution: Record<string, number> = {};
|
422
|
+
|
423
|
+
configs.forEach(config => {
|
424
|
+
config.parsedBreakpoints.forEach(bp => {
|
425
|
+
breakpointDistribution[bp.name] = (breakpointDistribution[bp.name] || 0) + 1;
|
426
|
+
});
|
427
|
+
});
|
428
|
+
|
429
|
+
const averageBreakpoints = configs.length > 0
|
430
|
+
? configs.reduce((sum, c) => sum + c.parsedBreakpoints.length, 0) / configs.length
|
431
|
+
: 0;
|
432
|
+
|
433
|
+
return {
|
434
|
+
totalRegistrations: configs.length,
|
435
|
+
activeElements: new Set(configs.map(c => c.element)).size,
|
436
|
+
averageBreakpoints,
|
437
|
+
breakpointDistribution
|
438
|
+
};
|
439
|
+
}
|
440
|
+
|
441
|
+
/**
|
442
|
+
* Register multiple breakpoint sets at once
|
443
|
+
*/
|
444
|
+
public registerMultiple(
|
445
|
+
registrations: Array<{
|
446
|
+
element: Element;
|
447
|
+
breakpoints: BreakpointMap;
|
448
|
+
callback?: BreakpointCallback;
|
449
|
+
}>
|
450
|
+
): string[] {
|
451
|
+
return registrations.map(reg =>
|
452
|
+
this.register(reg.element, reg.breakpoints, reg.callback)
|
453
|
+
);
|
454
|
+
}
|
455
|
+
|
456
|
+
/**
|
457
|
+
* Unregister all breakpoints for an element
|
458
|
+
*/
|
459
|
+
public unregisterElement(element: Element): void {
|
460
|
+
const idsToRemove: string[] = [];
|
461
|
+
|
462
|
+
for (const [id, config] of this.breakpoints) {
|
463
|
+
if (config.element === element) {
|
464
|
+
idsToRemove.push(id);
|
465
|
+
}
|
466
|
+
}
|
467
|
+
|
468
|
+
idsToRemove.forEach(id => this.unregister(id));
|
469
|
+
}
|
470
|
+
|
471
|
+
/**
|
472
|
+
* Get all active breakpoints across all elements
|
473
|
+
*/
|
474
|
+
public getAllActiveBreakpoints(): Array<{
|
475
|
+
id: string;
|
476
|
+
element: Element;
|
477
|
+
breakpoint: string;
|
478
|
+
width: number;
|
479
|
+
height: number;
|
480
|
+
}> {
|
481
|
+
const active: Array<{
|
482
|
+
id: string;
|
483
|
+
element: Element;
|
484
|
+
breakpoint: string;
|
485
|
+
width: number;
|
486
|
+
height: number;
|
487
|
+
}> = [];
|
488
|
+
|
489
|
+
for (const [id, config] of this.breakpoints) {
|
490
|
+
if (config.currentBreakpoint) {
|
491
|
+
const rect = config.element.getBoundingClientRect();
|
492
|
+
active.push({
|
493
|
+
id,
|
494
|
+
element: config.element,
|
495
|
+
breakpoint: config.currentBreakpoint,
|
496
|
+
width: rect.width,
|
497
|
+
height: rect.height
|
498
|
+
});
|
499
|
+
}
|
500
|
+
}
|
501
|
+
|
502
|
+
return active;
|
503
|
+
}
|
504
|
+
|
505
|
+
/**
|
506
|
+
* Force update all breakpoints
|
507
|
+
*/
|
508
|
+
public updateAll(): void {
|
509
|
+
for (const id of this.breakpoints.keys()) {
|
510
|
+
this.updateBreakpoint(id);
|
511
|
+
}
|
512
|
+
}
|
513
|
+
|
514
|
+
/**
|
515
|
+
* Setup performance monitoring for container queries
|
516
|
+
*/
|
517
|
+
private setupPerformanceMonitoring(): void {
|
518
|
+
// Monitor performance metrics
|
519
|
+
setInterval(() => {
|
520
|
+
if (this.performanceMetrics.evaluationTimes.length > 0) {
|
521
|
+
this.performanceMetrics.averageTime =
|
522
|
+
this.performanceMetrics.evaluationTimes.reduce((a, b) => a + b, 0) /
|
523
|
+
this.performanceMetrics.evaluationTimes.length;
|
524
|
+
|
525
|
+
// Log performance warnings
|
526
|
+
if (this.performanceMetrics.averageTime > 5) {
|
527
|
+
logger.warn(`Container query evaluation taking ${this.performanceMetrics.averageTime.toFixed(2)}ms on average`);
|
528
|
+
}
|
529
|
+
|
530
|
+
// Reset metrics periodically
|
531
|
+
if (this.performanceMetrics.evaluationTimes.length > 100) {
|
532
|
+
this.performanceMetrics.evaluationTimes = this.performanceMetrics.evaluationTimes.slice(-50);
|
533
|
+
}
|
534
|
+
}
|
535
|
+
}, 10000); // Check every 10 seconds
|
536
|
+
}
|
537
|
+
|
538
|
+
/**
|
539
|
+
* Inject base CSS for container query functionality
|
540
|
+
*/
|
541
|
+
private injectBaseCSS(): void {
|
542
|
+
const style = document.createElement('style');
|
543
|
+
style.id = 'proteus-container-queries';
|
544
|
+
style.textContent = `
|
545
|
+
/* Base container query styles */
|
546
|
+
[data-proteus-container] {
|
547
|
+
container-type: inline-size;
|
548
|
+
position: relative;
|
549
|
+
}
|
550
|
+
|
551
|
+
/* Performance optimizations */
|
552
|
+
[data-proteus-container] * {
|
553
|
+
contain: layout style;
|
554
|
+
}
|
555
|
+
|
556
|
+
/* Responsive utility classes */
|
557
|
+
.proteus-container-small { /* Applied when container is small */ }
|
558
|
+
.proteus-container-medium { /* Applied when container is medium */ }
|
559
|
+
.proteus-container-large { /* Applied when container is large */ }
|
560
|
+
|
561
|
+
/* Transition support */
|
562
|
+
[data-proteus-container] {
|
563
|
+
transition: all 0.2s ease-out;
|
564
|
+
}
|
565
|
+
|
566
|
+
/* Debug mode styles */
|
567
|
+
[data-proteus-debug="true"] {
|
568
|
+
outline: 2px dashed rgba(255, 0, 0, 0.3);
|
569
|
+
position: relative;
|
570
|
+
}
|
571
|
+
|
572
|
+
[data-proteus-debug="true"]::before {
|
573
|
+
content: attr(data-proteus-breakpoint);
|
574
|
+
position: absolute;
|
575
|
+
top: 0;
|
576
|
+
left: 0;
|
577
|
+
background: rgba(255, 0, 0, 0.8);
|
578
|
+
color: white;
|
579
|
+
padding: 2px 6px;
|
580
|
+
font-size: 10px;
|
581
|
+
font-family: monospace;
|
582
|
+
z-index: 9999;
|
583
|
+
pointer-events: none;
|
584
|
+
}
|
585
|
+
`;
|
586
|
+
|
587
|
+
// Only inject if not already present
|
588
|
+
if (!document.getElementById('proteus-container-queries')) {
|
589
|
+
document.head.appendChild(style);
|
590
|
+
}
|
591
|
+
}
|
592
|
+
|
593
|
+
/**
|
594
|
+
* Generate responsive CSS classes for a container
|
595
|
+
*/
|
596
|
+
public generateResponsiveCSS(elementId: string, breakpoints: BreakpointMap): void {
|
597
|
+
const cssRules: string[] = [];
|
598
|
+
|
599
|
+
Object.entries(breakpoints).forEach(([name, query]) => {
|
600
|
+
// Generate container query CSS
|
601
|
+
const containerQuery = this.parseContainerQuery(query);
|
602
|
+
if (containerQuery) {
|
603
|
+
cssRules.push(`
|
604
|
+
@container (${containerQuery}) {
|
605
|
+
[data-proteus-id="${elementId}"] {
|
606
|
+
--proteus-breakpoint: "${name}";
|
607
|
+
}
|
608
|
+
|
609
|
+
[data-proteus-id="${elementId}"] .proteus-${name} {
|
610
|
+
display: block;
|
611
|
+
}
|
612
|
+
|
613
|
+
[data-proteus-id="${elementId}"] .proteus-not-${name} {
|
614
|
+
display: none;
|
615
|
+
}
|
616
|
+
}
|
617
|
+
`);
|
618
|
+
}
|
619
|
+
});
|
620
|
+
|
621
|
+
// Inject CSS if we have rules
|
622
|
+
if (cssRules.length > 0) {
|
623
|
+
this.injectContainerCSS(elementId, cssRules.join('\n'));
|
624
|
+
}
|
625
|
+
}
|
626
|
+
|
627
|
+
/**
|
628
|
+
* Parse container query string
|
629
|
+
*/
|
630
|
+
private parseContainerQuery(query: string): string | null {
|
631
|
+
// Convert media query syntax to container query syntax
|
632
|
+
const containerQuery = query
|
633
|
+
.replace(/max-width/g, 'max-inline-size')
|
634
|
+
.replace(/min-width/g, 'min-inline-size')
|
635
|
+
.replace(/width/g, 'inline-size')
|
636
|
+
.replace(/max-height/g, 'max-block-size')
|
637
|
+
.replace(/min-height/g, 'min-block-size')
|
638
|
+
.replace(/height/g, 'block-size');
|
639
|
+
|
640
|
+
return containerQuery;
|
641
|
+
}
|
642
|
+
|
643
|
+
/**
|
644
|
+
* Inject container-specific CSS
|
645
|
+
*/
|
646
|
+
private injectContainerCSS(elementId: string, css: string): void {
|
647
|
+
const styleId = `proteus-container-${elementId}`;
|
648
|
+
let style = document.getElementById(styleId) as HTMLStyleElement;
|
649
|
+
|
650
|
+
if (!style) {
|
651
|
+
style = document.createElement('style');
|
652
|
+
style.id = styleId;
|
653
|
+
document.head.appendChild(style);
|
654
|
+
}
|
655
|
+
|
656
|
+
style.textContent = css;
|
657
|
+
|
658
|
+
// Store reference for cleanup
|
659
|
+
if (style.sheet) {
|
660
|
+
this.cssRules.set(elementId, style.sheet);
|
661
|
+
}
|
662
|
+
}
|
663
|
+
|
664
|
+
/**
|
665
|
+
* Record performance metrics
|
666
|
+
*/
|
667
|
+
private recordPerformanceMetric(evaluationTime: number): void {
|
668
|
+
this.performanceMetrics.evaluationTimes.push(evaluationTime);
|
669
|
+
this.performanceMetrics.totalEvaluations++;
|
670
|
+
|
671
|
+
// Keep only recent measurements
|
672
|
+
if (this.performanceMetrics.evaluationTimes.length > 100) {
|
673
|
+
this.performanceMetrics.evaluationTimes.shift();
|
674
|
+
}
|
675
|
+
}
|
676
|
+
|
677
|
+
/**
|
678
|
+
* Get performance metrics
|
679
|
+
*/
|
680
|
+
public getPerformanceMetrics(): typeof this.performanceMetrics {
|
681
|
+
return { ...this.performanceMetrics };
|
682
|
+
}
|
683
|
+
|
684
|
+
/**
|
685
|
+
* Enable debug mode for container queries
|
686
|
+
*/
|
687
|
+
public enableDebugMode(enable: boolean = true): void {
|
688
|
+
this.breakpoints.forEach((config) => {
|
689
|
+
const element = config.element as HTMLElement;
|
690
|
+
if (enable) {
|
691
|
+
element.setAttribute('data-proteus-debug', 'true');
|
692
|
+
} else {
|
693
|
+
element.removeAttribute('data-proteus-debug');
|
694
|
+
}
|
695
|
+
});
|
696
|
+
}
|
697
|
+
|
698
|
+
/**
|
699
|
+
* Cleanup resources when destroying
|
700
|
+
*/
|
701
|
+
public destroy(): void {
|
702
|
+
// Disconnect observer
|
703
|
+
if (this.observer) {
|
704
|
+
this.observer.disconnect();
|
705
|
+
this.observer = null;
|
706
|
+
}
|
707
|
+
|
708
|
+
// Clean up CSS rules
|
709
|
+
this.cssRules.forEach((_, elementId) => {
|
710
|
+
const styleElement = document.getElementById(`proteus-container-${elementId}`);
|
711
|
+
if (styleElement) {
|
712
|
+
styleElement.remove();
|
713
|
+
}
|
714
|
+
});
|
715
|
+
|
716
|
+
// Clear maps
|
717
|
+
this.breakpoints.clear();
|
718
|
+
this.cssRules.clear();
|
719
|
+
|
720
|
+
// Remove base CSS
|
721
|
+
const baseStyle = document.getElementById('proteus-container-queries');
|
722
|
+
if (baseStyle) {
|
723
|
+
baseStyle.remove();
|
724
|
+
}
|
725
|
+
}
|
726
|
+
}
|