@rakeyshgidwani/roger-ui-bank-theme-stan-design 0.1.4 → 0.1.5
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/CHANGELOG.md +1 -1
- package/dist/index.d.ts +131 -131
- package/dist/index.esm.js +148 -148
- package/dist/index.js +148 -148
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/components/ui/accessibility-demo.tsx +271 -0
- package/src/components/ui/advanced-component-architecture-demo.tsx +916 -0
- package/src/components/ui/advanced-transition-system-demo.tsx +670 -0
- package/src/components/ui/advanced-transition-system.tsx +395 -0
- package/src/components/ui/animation/animated-container.tsx +166 -0
- package/src/components/ui/animation/index.ts +19 -0
- package/src/components/ui/animation/staggered-container.tsx +68 -0
- package/src/components/ui/animation-demo.tsx +250 -0
- package/src/components/ui/badge.tsx +33 -0
- package/src/components/ui/battery-conscious-animation-demo.tsx +568 -0
- package/src/components/ui/border-radius-shadow-demo.tsx +187 -0
- package/src/components/ui/button.tsx +36 -0
- package/src/components/ui/card.tsx +207 -0
- package/src/components/ui/checkbox.tsx +30 -0
- package/src/components/ui/color-preview.tsx +411 -0
- package/src/components/ui/data-display/chart.tsx +653 -0
- package/src/components/ui/data-display/data-grid-simple.tsx +76 -0
- package/src/components/ui/data-display/data-grid.tsx +680 -0
- package/src/components/ui/data-display/list.tsx +456 -0
- package/src/components/ui/data-display/table.tsx +482 -0
- package/src/components/ui/data-display/timeline.tsx +441 -0
- package/src/components/ui/data-display/tree.tsx +602 -0
- package/src/components/ui/data-display/types.ts +536 -0
- package/src/components/ui/enterprise-mobile-experience-demo.tsx +749 -0
- package/src/components/ui/enterprise-mobile-experience.tsx +464 -0
- package/src/components/ui/feedback/alert.tsx +157 -0
- package/src/components/ui/feedback/progress.tsx +292 -0
- package/src/components/ui/feedback/skeleton.tsx +185 -0
- package/src/components/ui/feedback/toast.tsx +280 -0
- package/src/components/ui/feedback/types.ts +125 -0
- package/src/components/ui/font-preview.tsx +288 -0
- package/src/components/ui/form-demo.tsx +553 -0
- package/src/components/ui/hardware-acceleration-demo.tsx +547 -0
- package/src/components/ui/input.tsx +35 -0
- package/src/components/ui/label.tsx +16 -0
- package/src/components/ui/layout-demo.tsx +367 -0
- package/src/components/ui/layouts/adaptive-layout.tsx +139 -0
- package/src/components/ui/layouts/desktop-layout.tsx +224 -0
- package/src/components/ui/layouts/index.ts +10 -0
- package/src/components/ui/layouts/mobile-layout.tsx +162 -0
- package/src/components/ui/layouts/tablet-layout.tsx +197 -0
- package/src/components/ui/mobile-form-validation.tsx +451 -0
- package/src/components/ui/mobile-input-demo.tsx +201 -0
- package/src/components/ui/mobile-input.tsx +281 -0
- package/src/components/ui/mobile-skeleton-loading-demo.tsx +638 -0
- package/src/components/ui/navigation/breadcrumb.tsx +158 -0
- package/src/components/ui/navigation/index.ts +36 -0
- package/src/components/ui/navigation/menu.tsx +374 -0
- package/src/components/ui/navigation/navigation-demo.tsx +324 -0
- package/src/components/ui/navigation/pagination.tsx +272 -0
- package/src/components/ui/navigation/sidebar.tsx +383 -0
- package/src/components/ui/navigation/stepper.tsx +303 -0
- package/src/components/ui/navigation/tabs.tsx +205 -0
- package/src/components/ui/navigation/types.ts +299 -0
- package/src/components/ui/overlay/backdrop.tsx +81 -0
- package/src/components/ui/overlay/focus-manager.tsx +143 -0
- package/src/components/ui/overlay/index.ts +36 -0
- package/src/components/ui/overlay/modal.tsx +270 -0
- package/src/components/ui/overlay/overlay-manager.tsx +110 -0
- package/src/components/ui/overlay/popover.tsx +462 -0
- package/src/components/ui/overlay/portal.tsx +79 -0
- package/src/components/ui/overlay/tooltip.tsx +303 -0
- package/src/components/ui/overlay/types.ts +196 -0
- package/src/components/ui/performance-demo.tsx +596 -0
- package/src/components/ui/semantic-input-system-demo.tsx +502 -0
- package/src/components/ui/semantic-input-system-demo.tsx.disabled +873 -0
- package/src/components/ui/tablet-layout.tsx +192 -0
- package/src/components/ui/theme-customizer.tsx +386 -0
- package/src/components/ui/theme-preview.tsx +310 -0
- package/src/components/ui/theme-switcher.tsx +264 -0
- package/src/components/ui/theme-toggle.tsx +38 -0
- package/src/components/ui/token-demo.tsx +195 -0
- package/src/components/ui/touch-demo.tsx +462 -0
- package/src/components/ui/touch-friendly-interface-demo.tsx +519 -0
- package/src/components/ui/touch-friendly-interface.tsx +296 -0
- package/src/hooks/index.ts +190 -0
- package/src/hooks/use-accessibility-support.ts +518 -0
- package/src/hooks/use-adaptive-layout.ts +289 -0
- package/src/hooks/use-advanced-patterns.ts +294 -0
- package/src/hooks/use-advanced-transition-system.ts +393 -0
- package/src/hooks/use-animation-profile.ts +288 -0
- package/src/hooks/use-battery-animations.ts +384 -0
- package/src/hooks/use-battery-conscious-loading.ts +475 -0
- package/src/hooks/use-battery-optimization.ts +330 -0
- package/src/hooks/use-battery-status.ts +299 -0
- package/src/hooks/use-component-performance.ts +344 -0
- package/src/hooks/use-device-loading-states.ts +459 -0
- package/src/hooks/use-device.tsx +110 -0
- package/src/hooks/use-enterprise-mobile-experience.ts +488 -0
- package/src/hooks/use-form-feedback.ts +403 -0
- package/src/hooks/use-form-performance.ts +513 -0
- package/src/hooks/use-frame-rate.ts +251 -0
- package/src/hooks/use-gestures.ts +338 -0
- package/src/hooks/use-hardware-acceleration.ts +341 -0
- package/src/hooks/use-input-accessibility.ts +455 -0
- package/src/hooks/use-input-performance.ts +506 -0
- package/src/hooks/use-layout-performance.ts +319 -0
- package/src/hooks/use-loading-accessibility.ts +535 -0
- package/src/hooks/use-loading-performance.ts +473 -0
- package/src/hooks/use-memory-usage.ts +287 -0
- package/src/hooks/use-mobile-form-layout.ts +464 -0
- package/src/hooks/use-mobile-form-validation.ts +518 -0
- package/src/hooks/use-mobile-keyboard-optimization.ts +472 -0
- package/src/hooks/use-mobile-layout.ts +302 -0
- package/src/hooks/use-mobile-optimization.ts +406 -0
- package/src/hooks/use-mobile-skeleton.ts +402 -0
- package/src/hooks/use-mobile-touch.ts +414 -0
- package/src/hooks/use-performance-throttling.ts +348 -0
- package/src/hooks/use-performance.ts +316 -0
- package/src/hooks/use-reusable-architecture.ts +414 -0
- package/src/hooks/use-semantic-input-types.ts +357 -0
- package/src/hooks/use-semantic-input.ts +565 -0
- package/src/hooks/use-tablet-layout.ts +384 -0
- package/src/hooks/use-touch-friendly-input.ts +524 -0
- package/src/hooks/use-touch-friendly-interface.ts +331 -0
- package/src/hooks/use-touch-optimization.ts +375 -0
- package/src/index.ts +279 -279
- package/src/lib/utils.ts +6 -0
- package/src/themes/README.md +272 -0
- package/src/themes/ThemeContext.tsx +31 -0
- package/src/themes/ThemeProvider.tsx +232 -0
- package/src/themes/accessibility/index.ts +27 -0
- package/src/themes/accessibility.ts +259 -0
- package/src/themes/aria-patterns.ts +420 -0
- package/src/themes/base-themes.ts +55 -0
- package/src/themes/colorManager.ts +380 -0
- package/src/themes/examples/dark-theme.ts +154 -0
- package/src/themes/examples/minimal-theme.ts +108 -0
- package/src/themes/focus-management.ts +701 -0
- package/src/themes/fontLoader.ts +201 -0
- package/src/themes/high-contrast.ts +621 -0
- package/src/themes/index.ts +19 -0
- package/src/themes/inheritance.ts +227 -0
- package/src/themes/keyboard-navigation.ts +550 -0
- package/src/themes/motion-reduction.ts +662 -0
- package/src/themes/navigation.ts +238 -0
- package/src/themes/screen-reader.ts +645 -0
- package/src/themes/systemThemeDetector.ts +182 -0
- package/src/themes/themeCSSUpdater.ts +262 -0
- package/src/themes/themePersistence.ts +238 -0
- package/src/themes/themes/default.ts +586 -0
- package/src/themes/themes/harvey.ts +554 -0
- package/src/themes/themes/stan-design.ts +683 -0
- package/src/themes/types.ts +460 -0
- package/src/themes/useSystemTheme.ts +48 -0
- package/src/themes/useTheme.ts +87 -0
- package/src/themes/validation.ts +462 -0
- package/src/tokens/index.ts +34 -0
- package/src/tokens/tokenExporter.ts +397 -0
- package/src/tokens/tokenGenerator.ts +276 -0
- package/src/tokens/tokenManager.ts +248 -0
- package/src/tokens/tokenValidator.ts +543 -0
- package/src/tokens/types.ts +78 -0
- package/src/utils/bundle-analyzer.ts +260 -0
- package/src/utils/bundle-splitting.ts +483 -0
- package/src/utils/lazy-loading.ts +441 -0
- package/src/utils/performance-monitor.ts +513 -0
- package/src/utils/tree-shaking.ts +274 -0
|
@@ -0,0 +1,645 @@
|
|
|
1
|
+
// Screen reader optimization utilities
|
|
2
|
+
export class ScreenReaderOptimizer {
|
|
3
|
+
// Live region types for dynamic content
|
|
4
|
+
static readonly LIVE_REGIONS = {
|
|
5
|
+
POLITE: 'polite',
|
|
6
|
+
ASSERTIVE: 'assertive',
|
|
7
|
+
OFF: 'off'
|
|
8
|
+
} as const;
|
|
9
|
+
|
|
10
|
+
// Announcement priorities
|
|
11
|
+
static readonly PRIORITIES = {
|
|
12
|
+
LOW: 'low',
|
|
13
|
+
NORMAL: 'normal',
|
|
14
|
+
HIGH: 'high',
|
|
15
|
+
URGENT: 'urgent'
|
|
16
|
+
} as const;
|
|
17
|
+
|
|
18
|
+
private liveRegions: Map<string, HTMLElement>;
|
|
19
|
+
private announcementQueue: Array<{ message: string; priority: string; timestamp: number }>;
|
|
20
|
+
private isProcessingQueue: boolean;
|
|
21
|
+
|
|
22
|
+
constructor() {
|
|
23
|
+
this.liveRegions = new Map();
|
|
24
|
+
this.announcementQueue = [];
|
|
25
|
+
this.isProcessingQueue = false;
|
|
26
|
+
this.initializeLiveRegions();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Initialize live regions for different priorities
|
|
30
|
+
private initializeLiveRegions(): void {
|
|
31
|
+
const priorities = Object.values(ScreenReaderOptimizer.PRIORITIES);
|
|
32
|
+
|
|
33
|
+
priorities.forEach(priority => {
|
|
34
|
+
const region = document.createElement('div');
|
|
35
|
+
region.setAttribute('aria-live', this.getLiveRegionType(priority));
|
|
36
|
+
region.setAttribute('aria-atomic', 'true');
|
|
37
|
+
region.setAttribute('aria-relevant', 'additions text');
|
|
38
|
+
region.style.position = 'absolute';
|
|
39
|
+
region.style.left = '-10000px';
|
|
40
|
+
region.style.width = '1px';
|
|
41
|
+
region.style.height = '1px';
|
|
42
|
+
region.style.overflow = 'hidden';
|
|
43
|
+
region.setAttribute('data-screen-reader-priority', priority);
|
|
44
|
+
|
|
45
|
+
document.body.appendChild(region);
|
|
46
|
+
this.liveRegions.set(priority, region);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Get appropriate live region type for priority
|
|
51
|
+
private getLiveRegionType(priority: string): string {
|
|
52
|
+
switch (priority) {
|
|
53
|
+
case ScreenReaderOptimizer.PRIORITIES.URGENT:
|
|
54
|
+
return ScreenReaderOptimizer.LIVE_REGIONS.ASSERTIVE;
|
|
55
|
+
case ScreenReaderOptimizer.PRIORITIES.HIGH:
|
|
56
|
+
return ScreenReaderOptimizer.LIVE_REGIONS.ASSERTIVE;
|
|
57
|
+
case ScreenReaderOptimizer.PRIORITIES.NORMAL:
|
|
58
|
+
return ScreenReaderOptimizer.LIVE_REGIONS.POLITE;
|
|
59
|
+
case ScreenReaderOptimizer.PRIORITIES.LOW:
|
|
60
|
+
return ScreenReaderOptimizer.LIVE_REGIONS.POLITE;
|
|
61
|
+
default:
|
|
62
|
+
return ScreenReaderOptimizer.LIVE_REGIONS.POLITE;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Announce message to screen readers
|
|
67
|
+
announce(message: string, priority: string = ScreenReaderOptimizer.PRIORITIES.NORMAL): void {
|
|
68
|
+
if (!message || typeof message !== 'string') return;
|
|
69
|
+
|
|
70
|
+
const timestamp = Date.now();
|
|
71
|
+
this.announcementQueue.push({ message, priority, timestamp });
|
|
72
|
+
|
|
73
|
+
if (!this.isProcessingQueue) {
|
|
74
|
+
this.processAnnouncementQueue();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Process announcement queue
|
|
79
|
+
private async processAnnouncementQueue(): Promise<void> {
|
|
80
|
+
if (this.isProcessingQueue || this.announcementQueue.length === 0) return;
|
|
81
|
+
|
|
82
|
+
this.isProcessingQueue = true;
|
|
83
|
+
|
|
84
|
+
while (this.announcementQueue.length > 0) {
|
|
85
|
+
const announcement = this.announcementQueue.shift();
|
|
86
|
+
if (!announcement) continue;
|
|
87
|
+
|
|
88
|
+
await this.makeAnnouncement(announcement.message, announcement.priority);
|
|
89
|
+
|
|
90
|
+
// Small delay between announcements to prevent overlap
|
|
91
|
+
await this.delay(100);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
this.isProcessingQueue = false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Make individual announcement
|
|
98
|
+
private async makeAnnouncement(message: string, priority: string): Promise<void> {
|
|
99
|
+
const region = this.liveRegions.get(priority);
|
|
100
|
+
if (!region) return;
|
|
101
|
+
|
|
102
|
+
// Clear previous content
|
|
103
|
+
region.textContent = '';
|
|
104
|
+
|
|
105
|
+
// Add new message
|
|
106
|
+
region.textContent = message;
|
|
107
|
+
|
|
108
|
+
// Force screen reader to announce
|
|
109
|
+
region.setAttribute('aria-hidden', 'true');
|
|
110
|
+
await this.delay(10);
|
|
111
|
+
region.setAttribute('aria-hidden', 'false');
|
|
112
|
+
|
|
113
|
+
// Clear content after announcement
|
|
114
|
+
setTimeout(() => {
|
|
115
|
+
region.textContent = '';
|
|
116
|
+
}, 1000);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Utility delay function
|
|
120
|
+
private delay(ms: number): Promise<void> {
|
|
121
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Announce page title change
|
|
125
|
+
announcePageTitle(title: string): void {
|
|
126
|
+
this.announce(`Page title changed to: ${title}`, ScreenReaderOptimizer.PRIORITIES.HIGH);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Announce navigation change
|
|
130
|
+
announceNavigation(destination: string): void {
|
|
131
|
+
this.announce(`Navigated to: ${destination}`, ScreenReaderOptimizer.PRIORITIES.NORMAL);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Announce form submission
|
|
135
|
+
announceFormSubmission(formName: string, success: boolean): void {
|
|
136
|
+
const message = success
|
|
137
|
+
? `Form ${formName} submitted successfully`
|
|
138
|
+
: `Form ${formName} submission failed`;
|
|
139
|
+
this.announce(message, success ? ScreenReaderOptimizer.PRIORITIES.NORMAL : ScreenReaderOptimizer.PRIORITIES.HIGH);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Announce loading state
|
|
143
|
+
announceLoading(component: string, isLoading: boolean): void {
|
|
144
|
+
const message = isLoading ? `${component} is loading` : `${component} finished loading`;
|
|
145
|
+
this.announce(message, ScreenReaderOptimizer.PRIORITIES.LOW);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Announce error
|
|
149
|
+
announceError(error: string): void {
|
|
150
|
+
this.announce(`Error: ${error}`, ScreenReaderOptimizer.PRIORITIES.URGENT);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Announce success
|
|
154
|
+
announceSuccess(message: string): void {
|
|
155
|
+
this.announce(`Success: ${message}`, ScreenReaderOptimizer.PRIORITIES.NORMAL);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Announce warning
|
|
159
|
+
announceWarning(message: string): void {
|
|
160
|
+
this.announce(`Warning: ${message}`, ScreenReaderOptimizer.PRIORITIES.HIGH);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Announce information
|
|
164
|
+
announceInfo(message: string): void {
|
|
165
|
+
this.announce(`Information: ${message}`, ScreenReaderOptimizer.PRIORITIES.LOW);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Announce count changes
|
|
169
|
+
announceCountChange(itemType: string, count: number, total?: number): void {
|
|
170
|
+
let message = `${itemType} count: ${count}`;
|
|
171
|
+
if (total !== undefined) {
|
|
172
|
+
message += ` of ${total}`;
|
|
173
|
+
}
|
|
174
|
+
this.announce(message, ScreenReaderOptimizer.PRIORITIES.LOW);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Announce selection changes
|
|
178
|
+
announceSelectionChange(itemType: string, selected: number, total: number): void {
|
|
179
|
+
const message = `${selected} of ${total} ${itemType} selected`;
|
|
180
|
+
this.announce(message, ScreenReaderOptimizer.PRIORITIES.NORMAL);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Announce progress updates
|
|
184
|
+
announceProgress(current: number, total: number, percentage: number): void {
|
|
185
|
+
const message = `Progress: ${current} of ${total} (${percentage}%)`;
|
|
186
|
+
this.announce(message, ScreenReaderOptimizer.PRIORITIES.LOW);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Announce search results
|
|
190
|
+
announceSearchResults(query: string, count: number): void {
|
|
191
|
+
const message = `Found ${count} results for "${query}"`;
|
|
192
|
+
this.announce(message, ScreenReaderOptimizer.PRIORITIES.NORMAL);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Announce filter changes
|
|
196
|
+
announceFilterChange(filterName: string, value: string): void {
|
|
197
|
+
const message = `Filter ${filterName} changed to: ${value}`;
|
|
198
|
+
this.announce(message, ScreenReaderOptimizer.PRIORITIES.NORMAL);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Announce sort changes
|
|
202
|
+
announceSortChange(column: string, direction: 'ascending' | 'descending'): void {
|
|
203
|
+
const message = `Sorted by ${column} in ${direction} order`;
|
|
204
|
+
this.announce(message, ScreenReaderOptimizer.PRIORITIES.NORMAL);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Announce modal open/close
|
|
208
|
+
announceModalChange(modalName: string, isOpen: boolean): void {
|
|
209
|
+
const message = isOpen ? `${modalName} modal opened` : `${modalName} modal closed`;
|
|
210
|
+
this.announce(message, ScreenReaderOptimizer.PRIORITIES.HIGH);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Announce tab changes
|
|
214
|
+
announceTabChange(tabName: string): void {
|
|
215
|
+
this.announce(`Switched to ${tabName} tab`, ScreenReaderOptimizer.PRIORITIES.NORMAL);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Announce accordion changes
|
|
219
|
+
announceAccordionChange(sectionName: string, isExpanded: boolean): void {
|
|
220
|
+
const message = isExpanded ? `${sectionName} section expanded` : `${sectionName} section collapsed`;
|
|
221
|
+
this.announce(message, ScreenReaderOptimizer.PRIORITIES.NORMAL);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Announce tree changes
|
|
225
|
+
announceTreeChange(nodeName: string, action: 'expanded' | 'collapsed' | 'selected'): void {
|
|
226
|
+
const message = `${nodeName} node ${action}`;
|
|
227
|
+
this.announce(message, ScreenReaderOptimizer.PRIORITIES.NORMAL);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Announce list changes
|
|
231
|
+
announceListChange(listName: string, action: 'added' | 'removed' | 'updated', itemName: string): void {
|
|
232
|
+
const message = `${itemName} ${action} from ${listName} list`;
|
|
233
|
+
this.announce(message, ScreenReaderOptimizer.PRIORITIES.NORMAL);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Announce table changes
|
|
237
|
+
announceTableChange(tableName: string, action: 'row added' | 'row removed' | 'row updated' | 'sorted' | 'filtered'): void {
|
|
238
|
+
const message = `${tableName} table: ${action}`;
|
|
239
|
+
this.announce(message, ScreenReaderOptimizer.PRIORITIES.NORMAL);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Announce form field changes
|
|
243
|
+
announceFormFieldChange(fieldName: string, value: string, isValid: boolean): void {
|
|
244
|
+
let message = `${fieldName} field changed to: ${value}`;
|
|
245
|
+
if (!isValid) {
|
|
246
|
+
message += '. Field has validation errors';
|
|
247
|
+
}
|
|
248
|
+
this.announce(message, isValid ? ScreenReaderOptimizer.PRIORITIES.LOW : ScreenReaderOptimizer.PRIORITIES.HIGH);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Announce validation errors
|
|
252
|
+
announceValidationErrors(fieldName: string, errors: string[]): void {
|
|
253
|
+
const errorList = errors.join(', ');
|
|
254
|
+
const message = `${fieldName} validation errors: ${errorList}`;
|
|
255
|
+
this.announce(message, ScreenReaderOptimizer.PRIORITIES.HIGH);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Announce focus changes
|
|
259
|
+
announceFocusChange(elementName: string, context?: string): void {
|
|
260
|
+
let message = `Focused on: ${elementName}`;
|
|
261
|
+
if (context) {
|
|
262
|
+
message += ` in ${context}`;
|
|
263
|
+
}
|
|
264
|
+
this.announce(message, ScreenReaderOptimizer.PRIORITIES.LOW);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Announce keyboard shortcuts
|
|
268
|
+
announceKeyboardShortcut(action: string, shortcut: string): void {
|
|
269
|
+
const message = `${action}: ${shortcut}`;
|
|
270
|
+
this.announce(message, ScreenReaderOptimizer.PRIORITIES.LOW);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Announce time-sensitive information
|
|
274
|
+
announceTimeSensitive(message: string, urgency: 'low' | 'medium' | 'high'): void {
|
|
275
|
+
const priority = urgency === 'high' ? ScreenReaderOptimizer.PRIORITIES.URGENT :
|
|
276
|
+
urgency === 'medium' ? ScreenReaderOptimizer.PRIORITIES.HIGH :
|
|
277
|
+
ScreenReaderOptimizer.PRIORITIES.NORMAL;
|
|
278
|
+
this.announce(message, priority);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Announce status changes
|
|
282
|
+
announceStatusChange(component: string, oldStatus: string, newStatus: string): void {
|
|
283
|
+
const message = `${component} status changed from ${oldStatus} to ${newStatus}`;
|
|
284
|
+
this.announce(message, ScreenReaderOptimizer.PRIORITIES.NORMAL);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Announce data updates
|
|
288
|
+
announceDataUpdate(dataType: string, action: 'created' | 'updated' | 'deleted'): void {
|
|
289
|
+
const message = `${dataType} ${action}`;
|
|
290
|
+
this.announce(message, ScreenReaderOptimizer.PRIORITIES.NORMAL);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Announce connection status
|
|
294
|
+
announceConnectionStatus(isConnected: boolean): void {
|
|
295
|
+
const message = isConnected ? 'Connection restored' : 'Connection lost';
|
|
296
|
+
this.announce(message, isConnected ? ScreenReaderOptimizer.PRIORITIES.NORMAL : ScreenReaderOptimizer.PRIORITIES.URGENT);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Announce accessibility features
|
|
300
|
+
announceAccessibilityFeature(feature: string, enabled: boolean): void {
|
|
301
|
+
const message = `${feature} accessibility feature ${enabled ? 'enabled' : 'disabled'}`;
|
|
302
|
+
this.announce(message, ScreenReaderOptimizer.PRIORITIES.LOW);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Announce theme changes
|
|
306
|
+
announceThemeChange(themeName: string): void {
|
|
307
|
+
this.announce(`Theme changed to: ${themeName}`, ScreenReaderOptimizer.PRIORITIES.NORMAL);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Announce language changes
|
|
311
|
+
announceLanguageChange(language: string): void {
|
|
312
|
+
this.announce(`Language changed to: ${language}`, ScreenReaderOptimizer.PRIORITIES.HIGH);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Announce font size changes
|
|
316
|
+
announceFontSizeChange(size: string): void {
|
|
317
|
+
this.announce(`Font size changed to: ${size}`, ScreenReaderOptimizer.PRIORITIES.LOW);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Announce contrast changes
|
|
321
|
+
announceContrastChange(isHighContrast: boolean): void {
|
|
322
|
+
const message = isHighContrast ? 'High contrast mode enabled' : 'High contrast mode disabled';
|
|
323
|
+
this.announce(message, ScreenReaderOptimizer.PRIORITIES.NORMAL);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Announce motion changes
|
|
327
|
+
announceMotionChange(isReducedMotion: boolean): void {
|
|
328
|
+
const message = isReducedMotion ? 'Reduced motion enabled' : 'Reduced motion disabled';
|
|
329
|
+
this.announce(message, ScreenReaderOptimizer.PRIORITIES.LOW);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Get announcement queue status
|
|
333
|
+
getQueueStatus(): { length: number; isProcessing: boolean } {
|
|
334
|
+
return {
|
|
335
|
+
length: this.announcementQueue.length,
|
|
336
|
+
isProcessing: this.isProcessingQueue
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Clear announcement queue
|
|
341
|
+
clearQueue(): void {
|
|
342
|
+
this.announcementQueue = [];
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Destroy live regions
|
|
346
|
+
destroy(): void {
|
|
347
|
+
this.liveRegions.forEach(region => {
|
|
348
|
+
if (region.parentNode) {
|
|
349
|
+
region.parentNode.removeChild(region);
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
this.liveRegions.clear();
|
|
353
|
+
this.clearQueue();
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Screen reader navigation utilities
|
|
358
|
+
export class ScreenReaderNavigation {
|
|
359
|
+
// Create skip link for main content
|
|
360
|
+
static createSkipLink(targetId: string, text: string = 'Skip to main content'): HTMLAnchorElement {
|
|
361
|
+
const skipLink = document.createElement('a');
|
|
362
|
+
skipLink.href = `#${targetId}`;
|
|
363
|
+
skipLink.textContent = text;
|
|
364
|
+
skipLink.className = 'sr-only sr-only-focusable';
|
|
365
|
+
skipLink.style.cssText = `
|
|
366
|
+
position: absolute;
|
|
367
|
+
top: -40px;
|
|
368
|
+
left: 6px;
|
|
369
|
+
z-index: 1000;
|
|
370
|
+
padding: 8px 16px;
|
|
371
|
+
background: #000;
|
|
372
|
+
color: #fff;
|
|
373
|
+
text-decoration: none;
|
|
374
|
+
border-radius: 4px;
|
|
375
|
+
font-size: 14px;
|
|
376
|
+
line-height: 1.2;
|
|
377
|
+
`;
|
|
378
|
+
|
|
379
|
+
skipLink.addEventListener('focus', () => {
|
|
380
|
+
skipLink.style.top = '6px';
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
skipLink.addEventListener('blur', () => {
|
|
384
|
+
skipLink.style.top = '-40px';
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
return skipLink;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Create landmark navigation
|
|
391
|
+
static createLandmarkNavigation(): HTMLDivElement {
|
|
392
|
+
const nav = document.createElement('div');
|
|
393
|
+
nav.setAttribute('role', 'navigation');
|
|
394
|
+
nav.setAttribute('aria-label', 'Landmark navigation');
|
|
395
|
+
nav.className = 'sr-only';
|
|
396
|
+
|
|
397
|
+
const landmarks = [
|
|
398
|
+
{ role: 'banner', label: 'Header' },
|
|
399
|
+
{ role: 'main', label: 'Main content' },
|
|
400
|
+
{ role: 'complementary', label: 'Sidebar' },
|
|
401
|
+
{ role: 'contentinfo', label: 'Footer' }
|
|
402
|
+
];
|
|
403
|
+
|
|
404
|
+
landmarks.forEach(landmark => {
|
|
405
|
+
const link = document.createElement('a');
|
|
406
|
+
link.href = `[role="${landmark.role}"]`;
|
|
407
|
+
link.textContent = `Skip to ${landmark.label}`;
|
|
408
|
+
link.className = 'sr-only-focusable';
|
|
409
|
+
nav.appendChild(link);
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
return nav;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Create heading navigation
|
|
416
|
+
static createHeadingNavigation(): HTMLDivElement {
|
|
417
|
+
const nav = document.createElement('div');
|
|
418
|
+
nav.setAttribute('role', 'navigation');
|
|
419
|
+
nav.setAttribute('aria-label', 'Heading navigation');
|
|
420
|
+
nav.className = 'sr-only';
|
|
421
|
+
|
|
422
|
+
const headings = document.querySelectorAll('h1, h2, h3, h4, h5, h6');
|
|
423
|
+
headings.forEach((heading, index) => {
|
|
424
|
+
const link = document.createElement('a');
|
|
425
|
+
link.href = `#${heading.id || `heading-${index}`}`;
|
|
426
|
+
link.textContent = `${heading.tagName.toLowerCase()} ${heading.textContent}`;
|
|
427
|
+
link.className = 'sr-only-focusable';
|
|
428
|
+
nav.appendChild(link);
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
return nav;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Create form navigation
|
|
435
|
+
static createFormNavigation(): HTMLDivElement {
|
|
436
|
+
const nav = document.createElement('div');
|
|
437
|
+
nav.setAttribute('role', 'navigation');
|
|
438
|
+
nav.setAttribute('aria-label', 'Form navigation');
|
|
439
|
+
nav.className = 'sr-only';
|
|
440
|
+
|
|
441
|
+
const forms = document.querySelectorAll('form');
|
|
442
|
+
forms.forEach((form, index) => {
|
|
443
|
+
const link = document.createElement('a');
|
|
444
|
+
link.href = `#${form.id || `form-${index}`}`;
|
|
445
|
+
link.textContent = `Form ${index + 1}`;
|
|
446
|
+
link.className = 'sr-only-focusable';
|
|
447
|
+
nav.appendChild(link);
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
return nav;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Create table navigation
|
|
454
|
+
static createTableNavigation(): HTMLDivElement {
|
|
455
|
+
const nav = document.createElement('div');
|
|
456
|
+
nav.setAttribute('role', 'navigation');
|
|
457
|
+
nav.setAttribute('aria-label', 'Table navigation');
|
|
458
|
+
nav.className = 'sr-only';
|
|
459
|
+
|
|
460
|
+
const tables = document.querySelectorAll('table');
|
|
461
|
+
tables.forEach((table, index) => {
|
|
462
|
+
const link = document.createElement('a');
|
|
463
|
+
link.href = `#${table.id || `table-${index}`}`;
|
|
464
|
+
link.textContent = `Table ${index + 1}`;
|
|
465
|
+
link.className = 'sr-only-focusable';
|
|
466
|
+
nav.appendChild(link);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
return nav;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Create list navigation
|
|
473
|
+
static createListNavigation(): HTMLDivElement {
|
|
474
|
+
const nav = document.createElement('div');
|
|
475
|
+
nav.setAttribute('role', 'navigation');
|
|
476
|
+
nav.setAttribute('aria-label', 'List navigation');
|
|
477
|
+
nav.className = 'sr-only';
|
|
478
|
+
|
|
479
|
+
const lists = document.querySelectorAll('ul, ol');
|
|
480
|
+
lists.forEach((list, index) => {
|
|
481
|
+
const link = document.createElement('a');
|
|
482
|
+
link.href = `#${list.id || `list-${index}`}`;
|
|
483
|
+
link.textContent = `${list.tagName.toLowerCase()} with ${list.children.length} items`;
|
|
484
|
+
link.className = 'sr-only-focusable';
|
|
485
|
+
nav.appendChild(link);
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
return nav;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Create link navigation
|
|
492
|
+
static createLinkNavigation(): HTMLDivElement {
|
|
493
|
+
const nav = document.createElement('div');
|
|
494
|
+
nav.setAttribute('role', 'navigation');
|
|
495
|
+
nav.setAttribute('aria-label', 'Link navigation');
|
|
496
|
+
nav.className = 'sr-only';
|
|
497
|
+
|
|
498
|
+
const links = document.querySelectorAll('a[href]:not([href="#"])');
|
|
499
|
+
links.forEach((link, index) => {
|
|
500
|
+
const navLink = document.createElement('a');
|
|
501
|
+
navLink.href = (link as HTMLAnchorElement).href;
|
|
502
|
+
navLink.textContent = (link as HTMLAnchorElement).textContent || `Link ${index + 1}`;
|
|
503
|
+
navLink.className = 'sr-only-focusable';
|
|
504
|
+
nav.appendChild(navLink);
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
return nav;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Create button navigation
|
|
511
|
+
static createButtonNavigation(): HTMLDivElement {
|
|
512
|
+
const nav = document.createElement('div');
|
|
513
|
+
nav.setAttribute('role', 'navigation');
|
|
514
|
+
nav.setAttribute('aria-label', 'Button navigation');
|
|
515
|
+
nav.className = 'sr-only';
|
|
516
|
+
|
|
517
|
+
const buttons = document.querySelectorAll('button, [role="button"]');
|
|
518
|
+
buttons.forEach((button, index) => {
|
|
519
|
+
const link = document.createElement('a');
|
|
520
|
+
link.href = `#${button.id || `button-${index}`}`;
|
|
521
|
+
link.textContent = (button as HTMLElement).textContent || `Button ${index + 1}`;
|
|
522
|
+
link.className = 'sr-only-focusable';
|
|
523
|
+
nav.appendChild(link);
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
return nav;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Create input navigation
|
|
530
|
+
static createInputNavigation(): HTMLDivElement {
|
|
531
|
+
const nav = document.createElement('div');
|
|
532
|
+
nav.setAttribute('role', 'navigation');
|
|
533
|
+
nav.setAttribute('aria-label', 'Input navigation');
|
|
534
|
+
nav.className = 'sr-only';
|
|
535
|
+
|
|
536
|
+
const inputs = document.querySelectorAll('input, textarea, select');
|
|
537
|
+
inputs.forEach((input, index) => {
|
|
538
|
+
const link = document.createElement('a');
|
|
539
|
+
link.href = `#${input.id || `input-${index}`}`;
|
|
540
|
+
const label = (input as HTMLElement).getAttribute('aria-label') ||
|
|
541
|
+
(input as HTMLElement).getAttribute('placeholder') ||
|
|
542
|
+
(input as HTMLElement).getAttribute('name') ||
|
|
543
|
+
`Input ${index + 1}`;
|
|
544
|
+
link.textContent = label;
|
|
545
|
+
link.className = 'sr-only-focusable';
|
|
546
|
+
nav.appendChild(link);
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
return nav;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Create image navigation
|
|
553
|
+
static createImageNavigation(): HTMLDivElement {
|
|
554
|
+
const nav = document.createElement('div');
|
|
555
|
+
nav.setAttribute('role', 'navigation');
|
|
556
|
+
nav.setAttribute('aria-label', 'Image navigation');
|
|
557
|
+
nav.className = 'sr-only';
|
|
558
|
+
|
|
559
|
+
const images = document.querySelectorAll('img');
|
|
560
|
+
images.forEach((img, index) => {
|
|
561
|
+
const link = document.createElement('a');
|
|
562
|
+
link.href = `#${img.id || `image-${index}`}`;
|
|
563
|
+
const alt = (img as HTMLImageElement).alt || `Image ${index + 1}`;
|
|
564
|
+
link.textContent = alt;
|
|
565
|
+
link.className = 'sr-only-focusable';
|
|
566
|
+
nav.appendChild(link);
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
return nav;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Create media navigation
|
|
573
|
+
static createMediaNavigation(): HTMLDivElement {
|
|
574
|
+
const nav = document.createElement('div');
|
|
575
|
+
nav.setAttribute('role', 'navigation');
|
|
576
|
+
nav.setAttribute('aria-label', 'Media navigation');
|
|
577
|
+
nav.className = 'sr-only';
|
|
578
|
+
|
|
579
|
+
const media = document.querySelectorAll('video, audio');
|
|
580
|
+
media.forEach((element, index) => {
|
|
581
|
+
const link = document.createElement('a');
|
|
582
|
+
link.href = `#${element.id || `${element.tagName.toLowerCase()}-${index}`}`;
|
|
583
|
+
link.textContent = `${element.tagName} ${index + 1}`;
|
|
584
|
+
link.className = 'sr-only-focusable';
|
|
585
|
+
nav.appendChild(link);
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
return nav;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// Create section navigation
|
|
592
|
+
static createSectionNavigation(): HTMLDivElement {
|
|
593
|
+
const nav = document.createElement('div');
|
|
594
|
+
nav.setAttribute('role', 'navigation');
|
|
595
|
+
nav.setAttribute('aria-label', 'Section navigation');
|
|
596
|
+
nav.className = 'sr-only';
|
|
597
|
+
|
|
598
|
+
const sections = document.querySelectorAll('section, article, aside');
|
|
599
|
+
sections.forEach((section, index) => {
|
|
600
|
+
const link = document.createElement('a');
|
|
601
|
+
link.href = `#${section.id || `${section.tagName.toLowerCase()}-${index}`}`;
|
|
602
|
+
const title = (section as HTMLElement).getAttribute('aria-label') ||
|
|
603
|
+
(section as HTMLElement).querySelector('h1, h2, h3, h4, h5, h6')?.textContent ||
|
|
604
|
+
`${section.tagName} ${index + 1}`;
|
|
605
|
+
link.textContent = title;
|
|
606
|
+
link.className = 'sr-only-focusable';
|
|
607
|
+
nav.appendChild(link);
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
return nav;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Create comprehensive navigation
|
|
614
|
+
static createComprehensiveNavigation(): HTMLDivElement {
|
|
615
|
+
const nav = document.createElement('div');
|
|
616
|
+
nav.setAttribute('role', 'navigation');
|
|
617
|
+
nav.setAttribute('aria-label', 'Comprehensive navigation');
|
|
618
|
+
nav.className = 'sr-only';
|
|
619
|
+
|
|
620
|
+
// Add all navigation types
|
|
621
|
+
nav.appendChild(ScreenReaderNavigation.createLandmarkNavigation());
|
|
622
|
+
nav.appendChild(ScreenReaderNavigation.createHeadingNavigation());
|
|
623
|
+
nav.appendChild(ScreenReaderNavigation.createFormNavigation());
|
|
624
|
+
nav.appendChild(ScreenReaderNavigation.createTableNavigation());
|
|
625
|
+
nav.appendChild(ScreenReaderNavigation.createListNavigation());
|
|
626
|
+
nav.appendChild(ScreenReaderNavigation.createLinkNavigation());
|
|
627
|
+
nav.appendChild(ScreenReaderNavigation.createButtonNavigation());
|
|
628
|
+
nav.appendChild(ScreenReaderNavigation.createInputNavigation());
|
|
629
|
+
nav.appendChild(ScreenReaderNavigation.createImageNavigation());
|
|
630
|
+
nav.appendChild(ScreenReaderNavigation.createMediaNavigation());
|
|
631
|
+
nav.appendChild(ScreenReaderNavigation.createSectionNavigation());
|
|
632
|
+
|
|
633
|
+
return nav;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// Export default instances
|
|
638
|
+
export const screenReaderOptimizer = new ScreenReaderOptimizer();
|
|
639
|
+
|
|
640
|
+
// Export default
|
|
641
|
+
export default {
|
|
642
|
+
ScreenReaderOptimizer,
|
|
643
|
+
ScreenReaderNavigation,
|
|
644
|
+
screenReaderOptimizer
|
|
645
|
+
};
|