@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,444 @@
1
+ /**
2
+ * ScreenReaderSupport - Enhanced screen reader compatibility
3
+ * Provides intelligent announcements, live regions, and ARIA management
4
+ */
5
+
6
+ export interface AnnouncementConfig {
7
+ priority: 'polite' | 'assertive' | 'off';
8
+ delay?: number;
9
+ clear?: boolean;
10
+ interrupt?: boolean;
11
+ }
12
+
13
+ export interface LiveRegionConfig {
14
+ type: 'status' | 'alert' | 'log' | 'marquee' | 'timer';
15
+ atomic: boolean;
16
+ relevant: 'additions' | 'removals' | 'text' | 'all';
17
+ busy: boolean;
18
+ }
19
+
20
+ export interface AriaLabelConfig {
21
+ label?: string;
22
+ labelledBy?: string;
23
+ describedBy?: string;
24
+ role?: string;
25
+ expanded?: boolean;
26
+ selected?: boolean;
27
+ checked?: boolean;
28
+ disabled?: boolean;
29
+ hidden?: boolean;
30
+ live?: 'polite' | 'assertive' | 'off';
31
+ }
32
+
33
+ export class ScreenReaderSupport {
34
+ private liveRegions: Map<string, HTMLElement> = new Map();
35
+ private announcementQueue: Array<{ message: string; config: AnnouncementConfig }> = [];
36
+ private isProcessingQueue: boolean = false;
37
+ private screenReaderDetected: boolean = false;
38
+ private lastAnnouncement: string = '';
39
+ private lastAnnouncementTime: number = 0;
40
+
41
+ constructor() {
42
+ this.detectScreenReader();
43
+ this.setupDefaultLiveRegions();
44
+ this.setupKeyboardDetection();
45
+ }
46
+
47
+ /**
48
+ * Announce message to screen readers
49
+ */
50
+ public announce(message: string, config: AnnouncementConfig = { priority: 'polite' }): void {
51
+ if (!message.trim()) return;
52
+
53
+ // Avoid duplicate announcements within 1 second
54
+ const now = Date.now();
55
+ if (this.lastAnnouncement === message && now - this.lastAnnouncementTime < 1000) {
56
+ return;
57
+ }
58
+
59
+ this.lastAnnouncement = message;
60
+ this.lastAnnouncementTime = now;
61
+
62
+ if (config.interrupt) {
63
+ this.clearQueue();
64
+ }
65
+
66
+ this.announcementQueue.push({ message, config });
67
+ this.processQueue();
68
+ }
69
+
70
+ /**
71
+ * Create or update live region
72
+ */
73
+ public createLiveRegion(id: string, config: LiveRegionConfig): HTMLElement {
74
+ let region = this.liveRegions.get(id);
75
+
76
+ if (!region) {
77
+ region = document.createElement('div');
78
+ region.id = `proteus-live-${id}`;
79
+ region.className = 'proteus-sr-only';
80
+ this.applyScreenReaderOnlyStyles(region);
81
+ document.body.appendChild(region);
82
+ this.liveRegions.set(id, region);
83
+ }
84
+
85
+ // Update ARIA attributes
86
+ region.setAttribute('aria-live', this.mapLiveRegionType(config.type));
87
+ region.setAttribute('aria-atomic', config.atomic.toString());
88
+ region.setAttribute('aria-relevant', config.relevant);
89
+ region.setAttribute('aria-busy', config.busy.toString());
90
+ region.setAttribute('role', config.type === 'alert' ? 'alert' : 'status');
91
+
92
+ return region;
93
+ }
94
+
95
+ /**
96
+ * Update live region content
97
+ */
98
+ public updateLiveRegion(id: string, content: string): void {
99
+ const region = this.liveRegions.get(id);
100
+ if (region) {
101
+ region.textContent = content;
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Apply comprehensive ARIA labels to element
107
+ */
108
+ public applyAriaLabels(element: Element, config: AriaLabelConfig): void {
109
+ const htmlElement = element as HTMLElement;
110
+
111
+ // Label attributes
112
+ if (config.label) {
113
+ htmlElement.setAttribute('aria-label', config.label);
114
+ }
115
+ if (config.labelledBy) {
116
+ htmlElement.setAttribute('aria-labelledby', config.labelledBy);
117
+ }
118
+ if (config.describedBy) {
119
+ htmlElement.setAttribute('aria-describedby', config.describedBy);
120
+ }
121
+
122
+ // Role
123
+ if (config.role) {
124
+ htmlElement.setAttribute('role', config.role);
125
+ }
126
+
127
+ // State attributes
128
+ if (config.expanded !== undefined) {
129
+ htmlElement.setAttribute('aria-expanded', config.expanded.toString());
130
+ }
131
+ if (config.selected !== undefined) {
132
+ htmlElement.setAttribute('aria-selected', config.selected.toString());
133
+ }
134
+ if (config.checked !== undefined) {
135
+ htmlElement.setAttribute('aria-checked', config.checked.toString());
136
+ }
137
+ if (config.disabled !== undefined) {
138
+ htmlElement.setAttribute('aria-disabled', config.disabled.toString());
139
+ }
140
+ if (config.hidden !== undefined) {
141
+ htmlElement.setAttribute('aria-hidden', config.hidden.toString());
142
+ }
143
+ if (config.live) {
144
+ htmlElement.setAttribute('aria-live', config.live);
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Announce container changes for responsive layouts
150
+ */
151
+ public announceContainerChange(element: Element, breakpoint: string, width: number): void {
152
+ const elementName = this.getElementDescription(element);
153
+ const message = `${elementName} layout changed to ${breakpoint} breakpoint at ${width} pixels wide`;
154
+
155
+ this.announce(message, { priority: 'polite', delay: 500 });
156
+ }
157
+
158
+ /**
159
+ * Announce typography changes
160
+ */
161
+ public announceTypographyChange(element: Element, fontSize: string): void {
162
+ const elementName = this.getElementDescription(element);
163
+ const message = `${elementName} text size adjusted to ${fontSize}`;
164
+
165
+ this.announce(message, { priority: 'polite', delay: 300 });
166
+ }
167
+
168
+ /**
169
+ * Create skip links for navigation
170
+ */
171
+ public createSkipLinks(targets: Array<{ id: string; label: string }>): HTMLElement {
172
+ const skipContainer = document.createElement('div');
173
+ skipContainer.className = 'proteus-skip-links';
174
+ skipContainer.setAttribute('role', 'navigation');
175
+ skipContainer.setAttribute('aria-label', 'Skip links');
176
+
177
+ targets.forEach(target => {
178
+ const link = document.createElement('a');
179
+ link.href = `#${target.id}`;
180
+ link.textContent = target.label;
181
+ link.className = 'proteus-skip-link';
182
+
183
+ // Style skip link
184
+ this.applySkipLinkStyles(link);
185
+
186
+ skipContainer.appendChild(link);
187
+ });
188
+
189
+ // Insert at beginning of body
190
+ document.body.insertBefore(skipContainer, document.body.firstChild);
191
+
192
+ return skipContainer;
193
+ }
194
+
195
+ /**
196
+ * Manage focus for dynamic content
197
+ */
198
+ public manageFocus(element: Element, reason: string): void {
199
+ const htmlElement = element as HTMLElement;
200
+
201
+ // Ensure element is focusable
202
+ if (!htmlElement.hasAttribute('tabindex')) {
203
+ htmlElement.setAttribute('tabindex', '-1');
204
+ }
205
+
206
+ // Focus the element
207
+ htmlElement.focus();
208
+
209
+ // Announce focus change
210
+ const elementName = this.getElementDescription(element);
211
+ this.announce(`Focus moved to ${elementName}. ${reason}`, { priority: 'assertive' });
212
+ }
213
+
214
+ /**
215
+ * Check if screen reader is active
216
+ */
217
+ public isScreenReaderActive(): boolean {
218
+ return this.screenReaderDetected;
219
+ }
220
+
221
+ /**
222
+ * Clean up resources
223
+ */
224
+ public destroy(): void {
225
+ // Remove live regions
226
+ this.liveRegions.forEach(region => {
227
+ if (region.parentNode) {
228
+ region.parentNode.removeChild(region);
229
+ }
230
+ });
231
+ this.liveRegions.clear();
232
+
233
+ // Clear queue
234
+ this.clearQueue();
235
+ }
236
+
237
+ /**
238
+ * Detect screen reader presence
239
+ */
240
+ private detectScreenReader(): void {
241
+ // Check for common screen reader indicators
242
+ const indicators = [
243
+ () => navigator.userAgent.includes('NVDA'),
244
+ () => navigator.userAgent.includes('JAWS'),
245
+ () => navigator.userAgent.includes('VoiceOver'),
246
+ () => navigator.userAgent.includes('Orca'),
247
+ () => window.speechSynthesis && window.speechSynthesis.getVoices().length > 0,
248
+ () => 'speechSynthesis' in window,
249
+ () => document.querySelector('[aria-live]') !== null
250
+ ];
251
+
252
+ this.screenReaderDetected = indicators.some(check => {
253
+ try {
254
+ return check();
255
+ } catch {
256
+ return false;
257
+ }
258
+ });
259
+
260
+ // Also check for keyboard-only navigation
261
+ this.setupKeyboardDetection();
262
+ }
263
+
264
+ /**
265
+ * Setup keyboard navigation detection
266
+ */
267
+ private setupKeyboardDetection(): void {
268
+ let keyboardUser = false;
269
+
270
+ document.addEventListener('keydown', (e) => {
271
+ if (e.key === 'Tab') {
272
+ keyboardUser = true;
273
+ document.body.classList.add('proteus-keyboard-user');
274
+ }
275
+ });
276
+
277
+ document.addEventListener('mousedown', () => {
278
+ if (keyboardUser) {
279
+ keyboardUser = false;
280
+ document.body.classList.remove('proteus-keyboard-user');
281
+ }
282
+ });
283
+ }
284
+
285
+ /**
286
+ * Setup default live regions
287
+ */
288
+ private setupDefaultLiveRegions(): void {
289
+ // Status region for general announcements
290
+ this.createLiveRegion('status', {
291
+ type: 'status',
292
+ atomic: false,
293
+ relevant: 'additions',
294
+ busy: false
295
+ });
296
+
297
+ // Alert region for important announcements
298
+ this.createLiveRegion('alert', {
299
+ type: 'alert',
300
+ atomic: true,
301
+ relevant: 'all',
302
+ busy: false
303
+ });
304
+ }
305
+
306
+ /**
307
+ * Process announcement queue
308
+ */
309
+ private async processQueue(): Promise<void> {
310
+ if (this.isProcessingQueue || this.announcementQueue.length === 0) {
311
+ return;
312
+ }
313
+
314
+ this.isProcessingQueue = true;
315
+
316
+ while (this.announcementQueue.length > 0) {
317
+ const { message, config } = this.announcementQueue.shift()!;
318
+
319
+ if (config.clear) {
320
+ this.clearLiveRegions();
321
+ }
322
+
323
+ const regionId = config.priority === 'assertive' ? 'alert' : 'status';
324
+ this.updateLiveRegion(regionId, message);
325
+
326
+ if (config.delay) {
327
+ await this.delay(config.delay);
328
+ }
329
+ }
330
+
331
+ this.isProcessingQueue = false;
332
+ }
333
+
334
+ /**
335
+ * Clear announcement queue
336
+ */
337
+ private clearQueue(): void {
338
+ this.announcementQueue = [];
339
+ this.isProcessingQueue = false;
340
+ }
341
+
342
+ /**
343
+ * Clear all live regions
344
+ */
345
+ private clearLiveRegions(): void {
346
+ this.liveRegions.forEach(region => {
347
+ region.textContent = '';
348
+ });
349
+ }
350
+
351
+ /**
352
+ * Map live region type to aria-live value
353
+ */
354
+ private mapLiveRegionType(type: LiveRegionConfig['type']): 'polite' | 'assertive' | 'off' {
355
+ switch (type) {
356
+ case 'alert':
357
+ return 'assertive';
358
+ case 'status':
359
+ case 'log':
360
+ case 'marquee':
361
+ case 'timer':
362
+ return 'polite';
363
+ default:
364
+ return 'polite';
365
+ }
366
+ }
367
+
368
+ /**
369
+ * Get descriptive name for element
370
+ */
371
+ private getElementDescription(element: Element): string {
372
+ // Try aria-label first
373
+ const ariaLabel = element.getAttribute('aria-label');
374
+ if (ariaLabel) return ariaLabel;
375
+
376
+ // Try aria-labelledby
377
+ const labelledBy = element.getAttribute('aria-labelledby');
378
+ if (labelledBy) {
379
+ const labelElement = document.getElementById(labelledBy);
380
+ if (labelElement) return labelElement.textContent || 'element';
381
+ }
382
+
383
+ // Try text content
384
+ const textContent = element.textContent?.trim();
385
+ if (textContent && textContent.length < 50) return textContent;
386
+
387
+ // Try tag name and class
388
+ const tagName = element.tagName.toLowerCase();
389
+ const className = element.className ? ` with class ${element.className}` : '';
390
+
391
+ return `${tagName}${className}`;
392
+ }
393
+
394
+ /**
395
+ * Apply screen reader only styles
396
+ */
397
+ private applyScreenReaderOnlyStyles(element: HTMLElement): void {
398
+ element.style.cssText = `
399
+ position: absolute !important;
400
+ width: 1px !important;
401
+ height: 1px !important;
402
+ padding: 0 !important;
403
+ margin: -1px !important;
404
+ overflow: hidden !important;
405
+ clip: rect(0, 0, 0, 0) !important;
406
+ white-space: nowrap !important;
407
+ border: 0 !important;
408
+ `;
409
+ }
410
+
411
+ /**
412
+ * Apply skip link styles
413
+ */
414
+ private applySkipLinkStyles(element: HTMLElement): void {
415
+ element.style.cssText = `
416
+ position: absolute;
417
+ top: -40px;
418
+ left: 6px;
419
+ background: #000;
420
+ color: #fff;
421
+ padding: 8px;
422
+ text-decoration: none;
423
+ border-radius: 4px;
424
+ z-index: 10000;
425
+ transition: top 0.3s;
426
+ `;
427
+
428
+ // Show on focus
429
+ element.addEventListener('focus', () => {
430
+ element.style.top = '6px';
431
+ });
432
+
433
+ element.addEventListener('blur', () => {
434
+ element.style.top = '-40px';
435
+ });
436
+ }
437
+
438
+ /**
439
+ * Utility delay function
440
+ */
441
+ private delay(ms: number): Promise<void> {
442
+ return new Promise(resolve => setTimeout(resolve, ms));
443
+ }
444
+ }