@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.
Files changed (164) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/dist/index.d.ts +131 -131
  3. package/dist/index.esm.js +148 -148
  4. package/dist/index.js +148 -148
  5. package/dist/styles.css +1 -1
  6. package/package.json +1 -1
  7. package/src/components/ui/accessibility-demo.tsx +271 -0
  8. package/src/components/ui/advanced-component-architecture-demo.tsx +916 -0
  9. package/src/components/ui/advanced-transition-system-demo.tsx +670 -0
  10. package/src/components/ui/advanced-transition-system.tsx +395 -0
  11. package/src/components/ui/animation/animated-container.tsx +166 -0
  12. package/src/components/ui/animation/index.ts +19 -0
  13. package/src/components/ui/animation/staggered-container.tsx +68 -0
  14. package/src/components/ui/animation-demo.tsx +250 -0
  15. package/src/components/ui/badge.tsx +33 -0
  16. package/src/components/ui/battery-conscious-animation-demo.tsx +568 -0
  17. package/src/components/ui/border-radius-shadow-demo.tsx +187 -0
  18. package/src/components/ui/button.tsx +36 -0
  19. package/src/components/ui/card.tsx +207 -0
  20. package/src/components/ui/checkbox.tsx +30 -0
  21. package/src/components/ui/color-preview.tsx +411 -0
  22. package/src/components/ui/data-display/chart.tsx +653 -0
  23. package/src/components/ui/data-display/data-grid-simple.tsx +76 -0
  24. package/src/components/ui/data-display/data-grid.tsx +680 -0
  25. package/src/components/ui/data-display/list.tsx +456 -0
  26. package/src/components/ui/data-display/table.tsx +482 -0
  27. package/src/components/ui/data-display/timeline.tsx +441 -0
  28. package/src/components/ui/data-display/tree.tsx +602 -0
  29. package/src/components/ui/data-display/types.ts +536 -0
  30. package/src/components/ui/enterprise-mobile-experience-demo.tsx +749 -0
  31. package/src/components/ui/enterprise-mobile-experience.tsx +464 -0
  32. package/src/components/ui/feedback/alert.tsx +157 -0
  33. package/src/components/ui/feedback/progress.tsx +292 -0
  34. package/src/components/ui/feedback/skeleton.tsx +185 -0
  35. package/src/components/ui/feedback/toast.tsx +280 -0
  36. package/src/components/ui/feedback/types.ts +125 -0
  37. package/src/components/ui/font-preview.tsx +288 -0
  38. package/src/components/ui/form-demo.tsx +553 -0
  39. package/src/components/ui/hardware-acceleration-demo.tsx +547 -0
  40. package/src/components/ui/input.tsx +35 -0
  41. package/src/components/ui/label.tsx +16 -0
  42. package/src/components/ui/layout-demo.tsx +367 -0
  43. package/src/components/ui/layouts/adaptive-layout.tsx +139 -0
  44. package/src/components/ui/layouts/desktop-layout.tsx +224 -0
  45. package/src/components/ui/layouts/index.ts +10 -0
  46. package/src/components/ui/layouts/mobile-layout.tsx +162 -0
  47. package/src/components/ui/layouts/tablet-layout.tsx +197 -0
  48. package/src/components/ui/mobile-form-validation.tsx +451 -0
  49. package/src/components/ui/mobile-input-demo.tsx +201 -0
  50. package/src/components/ui/mobile-input.tsx +281 -0
  51. package/src/components/ui/mobile-skeleton-loading-demo.tsx +638 -0
  52. package/src/components/ui/navigation/breadcrumb.tsx +158 -0
  53. package/src/components/ui/navigation/index.ts +36 -0
  54. package/src/components/ui/navigation/menu.tsx +374 -0
  55. package/src/components/ui/navigation/navigation-demo.tsx +324 -0
  56. package/src/components/ui/navigation/pagination.tsx +272 -0
  57. package/src/components/ui/navigation/sidebar.tsx +383 -0
  58. package/src/components/ui/navigation/stepper.tsx +303 -0
  59. package/src/components/ui/navigation/tabs.tsx +205 -0
  60. package/src/components/ui/navigation/types.ts +299 -0
  61. package/src/components/ui/overlay/backdrop.tsx +81 -0
  62. package/src/components/ui/overlay/focus-manager.tsx +143 -0
  63. package/src/components/ui/overlay/index.ts +36 -0
  64. package/src/components/ui/overlay/modal.tsx +270 -0
  65. package/src/components/ui/overlay/overlay-manager.tsx +110 -0
  66. package/src/components/ui/overlay/popover.tsx +462 -0
  67. package/src/components/ui/overlay/portal.tsx +79 -0
  68. package/src/components/ui/overlay/tooltip.tsx +303 -0
  69. package/src/components/ui/overlay/types.ts +196 -0
  70. package/src/components/ui/performance-demo.tsx +596 -0
  71. package/src/components/ui/semantic-input-system-demo.tsx +502 -0
  72. package/src/components/ui/semantic-input-system-demo.tsx.disabled +873 -0
  73. package/src/components/ui/tablet-layout.tsx +192 -0
  74. package/src/components/ui/theme-customizer.tsx +386 -0
  75. package/src/components/ui/theme-preview.tsx +310 -0
  76. package/src/components/ui/theme-switcher.tsx +264 -0
  77. package/src/components/ui/theme-toggle.tsx +38 -0
  78. package/src/components/ui/token-demo.tsx +195 -0
  79. package/src/components/ui/touch-demo.tsx +462 -0
  80. package/src/components/ui/touch-friendly-interface-demo.tsx +519 -0
  81. package/src/components/ui/touch-friendly-interface.tsx +296 -0
  82. package/src/hooks/index.ts +190 -0
  83. package/src/hooks/use-accessibility-support.ts +518 -0
  84. package/src/hooks/use-adaptive-layout.ts +289 -0
  85. package/src/hooks/use-advanced-patterns.ts +294 -0
  86. package/src/hooks/use-advanced-transition-system.ts +393 -0
  87. package/src/hooks/use-animation-profile.ts +288 -0
  88. package/src/hooks/use-battery-animations.ts +384 -0
  89. package/src/hooks/use-battery-conscious-loading.ts +475 -0
  90. package/src/hooks/use-battery-optimization.ts +330 -0
  91. package/src/hooks/use-battery-status.ts +299 -0
  92. package/src/hooks/use-component-performance.ts +344 -0
  93. package/src/hooks/use-device-loading-states.ts +459 -0
  94. package/src/hooks/use-device.tsx +110 -0
  95. package/src/hooks/use-enterprise-mobile-experience.ts +488 -0
  96. package/src/hooks/use-form-feedback.ts +403 -0
  97. package/src/hooks/use-form-performance.ts +513 -0
  98. package/src/hooks/use-frame-rate.ts +251 -0
  99. package/src/hooks/use-gestures.ts +338 -0
  100. package/src/hooks/use-hardware-acceleration.ts +341 -0
  101. package/src/hooks/use-input-accessibility.ts +455 -0
  102. package/src/hooks/use-input-performance.ts +506 -0
  103. package/src/hooks/use-layout-performance.ts +319 -0
  104. package/src/hooks/use-loading-accessibility.ts +535 -0
  105. package/src/hooks/use-loading-performance.ts +473 -0
  106. package/src/hooks/use-memory-usage.ts +287 -0
  107. package/src/hooks/use-mobile-form-layout.ts +464 -0
  108. package/src/hooks/use-mobile-form-validation.ts +518 -0
  109. package/src/hooks/use-mobile-keyboard-optimization.ts +472 -0
  110. package/src/hooks/use-mobile-layout.ts +302 -0
  111. package/src/hooks/use-mobile-optimization.ts +406 -0
  112. package/src/hooks/use-mobile-skeleton.ts +402 -0
  113. package/src/hooks/use-mobile-touch.ts +414 -0
  114. package/src/hooks/use-performance-throttling.ts +348 -0
  115. package/src/hooks/use-performance.ts +316 -0
  116. package/src/hooks/use-reusable-architecture.ts +414 -0
  117. package/src/hooks/use-semantic-input-types.ts +357 -0
  118. package/src/hooks/use-semantic-input.ts +565 -0
  119. package/src/hooks/use-tablet-layout.ts +384 -0
  120. package/src/hooks/use-touch-friendly-input.ts +524 -0
  121. package/src/hooks/use-touch-friendly-interface.ts +331 -0
  122. package/src/hooks/use-touch-optimization.ts +375 -0
  123. package/src/index.ts +279 -279
  124. package/src/lib/utils.ts +6 -0
  125. package/src/themes/README.md +272 -0
  126. package/src/themes/ThemeContext.tsx +31 -0
  127. package/src/themes/ThemeProvider.tsx +232 -0
  128. package/src/themes/accessibility/index.ts +27 -0
  129. package/src/themes/accessibility.ts +259 -0
  130. package/src/themes/aria-patterns.ts +420 -0
  131. package/src/themes/base-themes.ts +55 -0
  132. package/src/themes/colorManager.ts +380 -0
  133. package/src/themes/examples/dark-theme.ts +154 -0
  134. package/src/themes/examples/minimal-theme.ts +108 -0
  135. package/src/themes/focus-management.ts +701 -0
  136. package/src/themes/fontLoader.ts +201 -0
  137. package/src/themes/high-contrast.ts +621 -0
  138. package/src/themes/index.ts +19 -0
  139. package/src/themes/inheritance.ts +227 -0
  140. package/src/themes/keyboard-navigation.ts +550 -0
  141. package/src/themes/motion-reduction.ts +662 -0
  142. package/src/themes/navigation.ts +238 -0
  143. package/src/themes/screen-reader.ts +645 -0
  144. package/src/themes/systemThemeDetector.ts +182 -0
  145. package/src/themes/themeCSSUpdater.ts +262 -0
  146. package/src/themes/themePersistence.ts +238 -0
  147. package/src/themes/themes/default.ts +586 -0
  148. package/src/themes/themes/harvey.ts +554 -0
  149. package/src/themes/themes/stan-design.ts +683 -0
  150. package/src/themes/types.ts +460 -0
  151. package/src/themes/useSystemTheme.ts +48 -0
  152. package/src/themes/useTheme.ts +87 -0
  153. package/src/themes/validation.ts +462 -0
  154. package/src/tokens/index.ts +34 -0
  155. package/src/tokens/tokenExporter.ts +397 -0
  156. package/src/tokens/tokenGenerator.ts +276 -0
  157. package/src/tokens/tokenManager.ts +248 -0
  158. package/src/tokens/tokenValidator.ts +543 -0
  159. package/src/tokens/types.ts +78 -0
  160. package/src/utils/bundle-analyzer.ts +260 -0
  161. package/src/utils/bundle-splitting.ts +483 -0
  162. package/src/utils/lazy-loading.ts +441 -0
  163. package/src/utils/performance-monitor.ts +513 -0
  164. package/src/utils/tree-shaking.ts +274 -0
@@ -0,0 +1,701 @@
1
+ import { screenReaderOptimizer } from './screen-reader';
2
+
3
+ // Focus management utilities
4
+ export class FocusManager {
5
+ private focusableSelectors: string[];
6
+ private focusableElements: HTMLElement[];
7
+ private lastFocusedElement: HTMLElement | null;
8
+ private focusTraps: Map<string, FocusTrap>;
9
+ private focusHistory: HTMLElement[];
10
+ private maxHistorySize: number;
11
+
12
+ constructor() {
13
+ this.focusableSelectors = [
14
+ 'a[href]',
15
+ 'button:not([disabled])',
16
+ 'input:not([disabled])',
17
+ 'select:not([disabled])',
18
+ 'textarea:not([disabled])',
19
+ '[tabindex]:not([tabindex="-1"])',
20
+ '[contenteditable="true"]',
21
+ 'audio[controls]',
22
+ 'video[controls]',
23
+ 'iframe'
24
+ ];
25
+
26
+ this.focusableElements = [];
27
+ this.lastFocusedElement = null;
28
+ this.focusTraps = new Map();
29
+ this.focusHistory = [];
30
+ this.maxHistorySize = 10;
31
+
32
+ this.initialize();
33
+ }
34
+
35
+ // Initialize focus management
36
+ private initialize(): void {
37
+ this.updateFocusableElements();
38
+ this.setupEventListeners();
39
+ }
40
+
41
+ // Update list of focusable elements
42
+ private updateFocusableElements(): void {
43
+ this.focusableElements = Array.from(
44
+ document.querySelectorAll(this.focusableSelectors.join(','))
45
+ ) as HTMLElement[];
46
+ }
47
+
48
+ // Setup event listeners
49
+ private setupEventListeners(): void {
50
+ // Track focus changes
51
+ document.addEventListener('focusin', this.handleFocusIn.bind(this));
52
+ document.addEventListener('focusout', this.handleFocusOut.bind(this));
53
+
54
+ // Handle tab key navigation
55
+ document.addEventListener('keydown', this.handleKeyDown.bind(this));
56
+
57
+ // Update focusable elements when DOM changes
58
+ const observer = new MutationObserver(() => {
59
+ this.updateFocusableElements();
60
+ });
61
+
62
+ observer.observe(document.body, {
63
+ childList: true,
64
+ subtree: true
65
+ });
66
+ }
67
+
68
+ // Handle focus in events
69
+ private handleFocusIn(event: FocusEvent): void {
70
+ const target = event.target as HTMLElement;
71
+
72
+ if (target && target !== this.lastFocusedElement) {
73
+ // Add to focus history
74
+ this.addToFocusHistory(target);
75
+
76
+ // Update last focused element
77
+ this.lastFocusedElement = target;
78
+
79
+ // Announce focus change for screen readers
80
+ if (screenReaderOptimizer) {
81
+ screenReaderOptimizer.announceFocusChange(
82
+ this.getElementDescription(target),
83
+ this.getElementContext(target)
84
+ );
85
+ }
86
+ }
87
+ }
88
+
89
+ // Handle focus out events
90
+ private handleFocusOut(_event: FocusEvent): void {
91
+ // Focus out handling if needed
92
+ }
93
+
94
+ // Handle key down events
95
+ private handleKeyDown(event: KeyboardEvent): void {
96
+ if (event.key === 'Tab') {
97
+ this.handleTabNavigation(event);
98
+ }
99
+ }
100
+
101
+ // Handle tab navigation
102
+ private handleTabNavigation(event: KeyboardEvent): void {
103
+ const isShiftTab = event.shiftKey;
104
+ const currentElement = event.target as HTMLElement;
105
+
106
+ // Check if we're in a focus trap
107
+ const activeTrap = this.getActiveFocusTrap(currentElement);
108
+ if (activeTrap) {
109
+ event.preventDefault();
110
+ this.navigateInFocusTrap(activeTrap, isShiftTab);
111
+ return;
112
+ }
113
+
114
+ // Normal tab navigation
115
+ this.navigateFocusableElements(isShiftTab);
116
+ }
117
+
118
+ // Get active focus trap for element
119
+ private getActiveFocusTrap(element: HTMLElement): FocusTrap | null {
120
+ for (const trap of this.focusTraps.values()) {
121
+ if (trap.isActive && trap.container.contains(element)) {
122
+ return trap;
123
+ }
124
+ }
125
+ return null;
126
+ }
127
+
128
+ // Navigate within a focus trap
129
+ private navigateInFocusTrap(trap: FocusTrap, isShiftTab: boolean): void {
130
+ const focusableElements = trap.getFocusableElements();
131
+ if (focusableElements.length === 0) return;
132
+
133
+ const currentIndex = focusableElements.indexOf(document.activeElement as HTMLElement);
134
+ let nextIndex: number;
135
+
136
+ if (isShiftTab) {
137
+ nextIndex = currentIndex > 0 ? currentIndex - 1 : focusableElements.length - 1;
138
+ } else {
139
+ nextIndex = currentIndex < focusableElements.length - 1 ? currentIndex + 1 : 0;
140
+ }
141
+
142
+ focusableElements[nextIndex]?.focus();
143
+ }
144
+
145
+ // Navigate focusable elements
146
+ private navigateFocusableElements(isShiftTab: boolean): void {
147
+ const currentIndex = this.focusableElements.indexOf(document.activeElement as HTMLElement);
148
+ let nextIndex: number;
149
+
150
+ if (isShiftTab) {
151
+ nextIndex = currentIndex > 0 ? currentIndex - 1 : this.focusableElements.length - 1;
152
+ } else {
153
+ nextIndex = currentIndex < this.focusableElements.length - 1 ? currentIndex + 1 : 0;
154
+ }
155
+
156
+ this.focusableElements[nextIndex]?.focus();
157
+ }
158
+
159
+ // Add element to focus history
160
+ private addToFocusHistory(element: HTMLElement): void {
161
+ // Remove if already exists
162
+ this.focusHistory = this.focusHistory.filter(el => el !== element);
163
+
164
+ // Add to beginning
165
+ this.focusHistory.unshift(element);
166
+
167
+ // Maintain history size
168
+ if (this.focusHistory.length > this.maxHistorySize) {
169
+ this.focusHistory.pop();
170
+ }
171
+ }
172
+
173
+ // Get element description for screen readers
174
+ private getElementDescription(element: HTMLElement): string {
175
+ const ariaLabel = element.getAttribute('aria-label');
176
+ if (ariaLabel) return ariaLabel;
177
+
178
+ const ariaLabelledBy = element.getAttribute('aria-labelledby');
179
+ if (ariaLabelledBy) {
180
+ const labelElement = document.getElementById(ariaLabelledBy);
181
+ if (labelElement) return labelElement.textContent || '';
182
+ }
183
+
184
+ const placeholder = element.getAttribute('placeholder');
185
+ if (placeholder) return placeholder;
186
+
187
+ const title = element.getAttribute('title');
188
+ if (title) return title;
189
+
190
+ return element.textContent || element.tagName.toLowerCase();
191
+ }
192
+
193
+ // Get element context for screen readers
194
+ private getElementContext(element: HTMLElement): string | undefined {
195
+ // Find nearest landmark or section
196
+ const landmark = element.closest('[role], section, article, aside, nav, header, footer, main');
197
+ if (landmark) {
198
+ const role = landmark.getAttribute('role');
199
+ const ariaLabel = landmark.getAttribute('aria-label');
200
+ return ariaLabel || role || landmark.tagName.toLowerCase();
201
+ }
202
+ return undefined;
203
+ }
204
+
205
+ // Create focus trap
206
+ createFocusTrap(container: HTMLElement, options: FocusTrapOptions = {}): FocusTrap {
207
+ const trap = new FocusTrap(container, options);
208
+ this.focusTraps.set(trap.id, trap);
209
+ return trap;
210
+ }
211
+
212
+ // Remove focus trap
213
+ removeFocusTrap(trapId: string): void {
214
+ const trap = this.focusTraps.get(trapId);
215
+ if (trap) {
216
+ trap.deactivate();
217
+ this.focusTraps.delete(trapId);
218
+ }
219
+ }
220
+
221
+ // Focus first focusable element
222
+ focusFirst(): void {
223
+ const firstElement = this.focusableElements[0];
224
+ if (firstElement) {
225
+ firstElement.focus();
226
+ }
227
+ }
228
+
229
+ // Focus last focusable element
230
+ focusLast(): void {
231
+ const lastElement = this.focusableElements[this.focusableElements.length - 1];
232
+ if (lastElement) {
233
+ lastElement.focus();
234
+ }
235
+ }
236
+
237
+ // Focus next element
238
+ focusNext(): void {
239
+ const currentIndex = this.focusableElements.indexOf(document.activeElement as HTMLElement);
240
+ const nextIndex = currentIndex < this.focusableElements.length - 1 ? currentIndex + 1 : 0;
241
+ this.focusableElements[nextIndex]?.focus();
242
+ }
243
+
244
+ // Focus previous element
245
+ focusPrevious(): void {
246
+ const currentIndex = this.focusableElements.indexOf(document.activeElement as HTMLElement);
247
+ const prevIndex = currentIndex > 0 ? currentIndex - 1 : this.focusableElements.length - 1;
248
+ this.focusableElements[prevIndex]?.focus();
249
+ }
250
+
251
+ // Focus element by index
252
+ focusByIndex(index: number): void {
253
+ if (index >= 0 && index < this.focusableElements.length) {
254
+ this.focusableElements[index].focus();
255
+ }
256
+ }
257
+
258
+ // Focus element by ID
259
+ focusById(id: string): void {
260
+ const element = document.getElementById(id);
261
+ if (element && this.focusableElements.includes(element)) {
262
+ element.focus();
263
+ }
264
+ }
265
+
266
+ // Focus element by data attribute
267
+ focusByDataAttribute(attribute: string, value: string): void {
268
+ const element = document.querySelector(`[data-${attribute}="${value}"]`) as HTMLElement;
269
+ if (element && this.focusableElements.includes(element)) {
270
+ element.focus();
271
+ }
272
+ }
273
+
274
+ // Restore previous focus
275
+ restoreFocus(): void {
276
+ if (this.lastFocusedElement && this.lastFocusedElement.offsetParent !== null) {
277
+ this.lastFocusedElement.focus();
278
+ }
279
+ }
280
+
281
+ // Get focus history
282
+ getFocusHistory(): HTMLElement[] {
283
+ return [...this.focusHistory];
284
+ }
285
+
286
+ // Clear focus history
287
+ clearFocusHistory(): void {
288
+ this.focusHistory = [];
289
+ }
290
+
291
+ // Get current focus information
292
+ getCurrentFocusInfo(): {
293
+ element: HTMLElement | null;
294
+ index: number;
295
+ total: number;
296
+ context: string | undefined;
297
+ } {
298
+ const currentElement = document.activeElement as HTMLElement;
299
+ const index = this.focusableElements.indexOf(currentElement);
300
+
301
+ return {
302
+ element: currentElement,
303
+ index: index >= 0 ? index : -1,
304
+ total: this.focusableElements.length,
305
+ context: currentElement ? this.getElementContext(currentElement) : undefined
306
+ };
307
+ }
308
+
309
+ // Check if element is focusable
310
+ isFocusable(element: HTMLElement): boolean {
311
+ return this.focusableElements.includes(element);
312
+ }
313
+
314
+ // Get all focusable elements
315
+ getAllFocusableElements(): HTMLElement[] {
316
+ return [...this.focusableElements];
317
+ }
318
+
319
+ // Get focusable elements within container
320
+ getFocusableElementsInContainer(container: HTMLElement): HTMLElement[] {
321
+ return this.focusableElements.filter(element =>
322
+ container.contains(element)
323
+ );
324
+ }
325
+
326
+ // Update focusable elements manually
327
+ refreshFocusableElements(): void {
328
+ this.updateFocusableElements();
329
+ }
330
+
331
+ // Destroy focus manager
332
+ destroy(): void {
333
+ // Remove all focus traps
334
+ this.focusTraps.forEach(trap => trap.deactivate());
335
+ this.focusTraps.clear();
336
+
337
+ // Clear focus history
338
+ this.focusHistory = [];
339
+
340
+ // Remove event listeners
341
+ document.removeEventListener('focusin', this.handleFocusIn.bind(this));
342
+ document.removeEventListener('focusout', this.handleFocusOut.bind(this));
343
+ document.removeEventListener('keydown', this.handleKeyDown.bind(this));
344
+ }
345
+ }
346
+
347
+ // Focus trap options
348
+ export interface FocusTrapOptions {
349
+ autoFocus?: boolean;
350
+ returnFocus?: boolean;
351
+ escapeDeactivates?: boolean;
352
+ clickOutsideDeactivates?: boolean;
353
+ allowOutsideClick?: boolean;
354
+ fallbackFocus?: HTMLElement;
355
+ }
356
+
357
+ // Focus trap class
358
+ export class FocusTrap {
359
+ public readonly id: string;
360
+ public readonly container: HTMLElement;
361
+ public readonly options: FocusTrapOptions;
362
+ public isActive: boolean = false;
363
+
364
+ private returnFocusElement: HTMLElement | null = null;
365
+ private focusableElements: HTMLElement[] = [];
366
+ private eventListeners: Array<{ event: string; handler: (event: any) => void }> = [];
367
+
368
+ constructor(container: HTMLElement, options: FocusTrapOptions = {}) {
369
+ this.id = `focus-trap-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
370
+ this.container = container;
371
+ this.options = {
372
+ autoFocus: true,
373
+ returnFocus: true,
374
+ escapeDeactivates: true,
375
+ clickOutsideDeactivates: false,
376
+ allowOutsideClick: false,
377
+ fallbackFocus: container,
378
+ ...options
379
+ };
380
+ }
381
+
382
+ // Activate focus trap
383
+ activate(): void {
384
+ if (this.isActive) return;
385
+
386
+ // Store current focus for return
387
+ if (this.options.returnFocus) {
388
+ this.returnFocusElement = document.activeElement as HTMLElement;
389
+ }
390
+
391
+ // Get focusable elements within container
392
+ this.focusableElements = this.getFocusableElements();
393
+
394
+ // Auto-focus first element or fallback
395
+ if (this.options.autoFocus && this.focusableElements.length > 0) {
396
+ this.focusableElements[0].focus();
397
+ } else if (this.options.fallbackFocus) {
398
+ this.options.fallbackFocus.focus();
399
+ }
400
+
401
+ // Setup event listeners
402
+ this.setupEventListeners();
403
+
404
+ this.isActive = true;
405
+ }
406
+
407
+ // Deactivate focus trap
408
+ deactivate(): void {
409
+ if (!this.isActive) return;
410
+
411
+ // Remove event listeners
412
+ this.removeEventListeners();
413
+
414
+ // Return focus if requested
415
+ if (this.options.returnFocus && this.returnFocusElement) {
416
+ this.returnFocusElement.focus();
417
+ }
418
+
419
+ this.isActive = false;
420
+ }
421
+
422
+ // Get focusable elements within container
423
+ getFocusableElements(): HTMLElement[] {
424
+ const focusableSelectors = [
425
+ 'a[href]',
426
+ 'button:not([disabled])',
427
+ 'input:not([disabled])',
428
+ 'select:not([disabled])',
429
+ 'textarea:not([disabled])',
430
+ '[tabindex]:not([tabindex="-1"])',
431
+ '[contenteditable="true"]'
432
+ ];
433
+
434
+ return Array.from(
435
+ this.container.querySelectorAll(focusableSelectors.join(','))
436
+ ) as HTMLElement[];
437
+ }
438
+
439
+ // Setup event listeners
440
+ private setupEventListeners(): void {
441
+ // Handle escape key
442
+ if (this.options.escapeDeactivates) {
443
+ const escapeHandler = (event: KeyboardEvent) => {
444
+ if (event.key === 'Escape') {
445
+ this.deactivate();
446
+ }
447
+ };
448
+
449
+ document.addEventListener('keydown', escapeHandler);
450
+ this.eventListeners.push({ event: 'keydown', handler: escapeHandler });
451
+ }
452
+
453
+ // Handle click outside
454
+ if (this.options.clickOutsideDeactivates) {
455
+ const clickHandler = (event: MouseEvent) => {
456
+ if (!this.container.contains(event.target as Node)) {
457
+ this.deactivate();
458
+ }
459
+ };
460
+
461
+ document.addEventListener('click', clickHandler);
462
+ this.eventListeners.push({ event: 'click', handler: clickHandler });
463
+ }
464
+
465
+ // Handle tab key within trap
466
+ const tabHandler = (event: KeyboardEvent) => {
467
+ if (event.key === 'Tab') {
468
+ event.preventDefault();
469
+ this.handleTabNavigation(event.shiftKey);
470
+ }
471
+ };
472
+
473
+ this.container.addEventListener('keydown', tabHandler);
474
+ this.eventListeners.push({ event: 'keydown', handler: tabHandler });
475
+ }
476
+
477
+ // Remove event listeners
478
+ private removeEventListeners(): void {
479
+ this.eventListeners.forEach(({ event, handler }) => {
480
+ if (event === 'keydown' && handler.toString().includes('tabHandler')) {
481
+ this.container.removeEventListener(event, handler);
482
+ } else {
483
+ document.removeEventListener(event, handler);
484
+ }
485
+ });
486
+
487
+ this.eventListeners = [];
488
+ }
489
+
490
+ // Handle tab navigation within trap
491
+ private handleTabNavigation(isShiftTab: boolean): void {
492
+ if (this.focusableElements.length === 0) return;
493
+
494
+ const currentIndex = this.focusableElements.indexOf(document.activeElement as HTMLElement);
495
+ let nextIndex: number;
496
+
497
+ if (isShiftTab) {
498
+ nextIndex = currentIndex > 0 ? currentIndex - 1 : this.focusableElements.length - 1;
499
+ } else {
500
+ nextIndex = currentIndex < this.focusableElements.length - 1 ? currentIndex + 1 : 0;
501
+ }
502
+
503
+ this.focusableElements[nextIndex]?.focus();
504
+ }
505
+
506
+ // Focus first element in trap
507
+ focusFirst(): void {
508
+ if (this.focusableElements.length > 0) {
509
+ this.focusableElements[0].focus();
510
+ }
511
+ }
512
+
513
+ // Focus last element in trap
514
+ focusLast(): void {
515
+ if (this.focusableElements.length > 0) {
516
+ this.focusableElements[this.focusableElements.length - 1].focus();
517
+ }
518
+ }
519
+
520
+ // Focus next element in trap
521
+ focusNext(): void {
522
+ if (this.focusableElements.length === 0) return;
523
+
524
+ const currentIndex = this.focusableElements.indexOf(document.activeElement as HTMLElement);
525
+ const nextIndex = currentIndex < this.focusableElements.length - 1 ? currentIndex + 1 : 0;
526
+ this.focusableElements[nextIndex]?.focus();
527
+ }
528
+
529
+ // Focus previous element in trap
530
+ focusPrevious(): void {
531
+ if (this.focusableElements.length === 0) return;
532
+
533
+ const currentIndex = this.focusableElements.indexOf(document.activeElement as HTMLElement);
534
+ const prevIndex = currentIndex > 0 ? currentIndex - 1 : this.focusableElements.length - 1;
535
+ this.focusableElements[prevIndex]?.focus();
536
+ }
537
+
538
+ // Get trap information
539
+ getInfo(): {
540
+ id: string;
541
+ isActive: boolean;
542
+ focusableElementsCount: number;
543
+ currentFocusIndex: number;
544
+ container: HTMLElement;
545
+ } {
546
+ const currentIndex = this.focusableElements.indexOf(document.activeElement as HTMLElement);
547
+
548
+ return {
549
+ id: this.id,
550
+ isActive: this.isActive,
551
+ focusableElementsCount: this.focusableElements.length,
552
+ currentFocusIndex: currentIndex >= 0 ? currentIndex : -1,
553
+ container: this.container
554
+ };
555
+ }
556
+ }
557
+
558
+ // Focus restoration utilities
559
+ export class FocusRestoration {
560
+ private focusStack: HTMLElement[] = [];
561
+ private maxStackSize: number = 20;
562
+
563
+ // Push current focus to stack
564
+ pushFocus(): void {
565
+ const currentElement = document.activeElement as HTMLElement;
566
+ if (currentElement && currentElement !== document.body) {
567
+ this.focusStack.push(currentElement);
568
+
569
+ // Maintain stack size
570
+ if (this.focusStack.length > this.maxStackSize) {
571
+ this.focusStack.shift();
572
+ }
573
+ }
574
+ }
575
+
576
+ // Pop and restore focus from stack
577
+ popFocus(): boolean {
578
+ const element = this.focusStack.pop();
579
+ if (element && element.offsetParent !== null) {
580
+ element.focus();
581
+ return true;
582
+ }
583
+ return false;
584
+ }
585
+
586
+ // Peek at top of focus stack
587
+ peekFocus(): HTMLElement | undefined {
588
+ return this.focusStack[this.focusStack.length - 1];
589
+ }
590
+
591
+ // Clear focus stack
592
+ clear(): void {
593
+ this.focusStack = [];
594
+ }
595
+
596
+ // Get stack size
597
+ getSize(): number {
598
+ return this.focusStack.length;
599
+ }
600
+
601
+ // Check if stack is empty
602
+ isEmpty(): boolean {
603
+ return this.focusStack.length === 0;
604
+ }
605
+ }
606
+
607
+ // Focus navigation utilities
608
+ export class FocusNavigation {
609
+ // Navigate to next heading
610
+ static focusNextHeading(): void {
611
+ const headings = Array.from(document.querySelectorAll('h1, h2, h3, h4, h5, h6'));
612
+ const currentIndex = headings.indexOf(document.activeElement as HTMLElement);
613
+ const nextIndex = currentIndex < headings.length - 1 ? currentIndex + 1 : 0;
614
+
615
+ if (headings[nextIndex]) {
616
+ (headings[nextIndex] as HTMLElement).focus();
617
+ }
618
+ }
619
+
620
+ // Navigate to previous heading
621
+ static focusPreviousHeading(): void {
622
+ const headings = Array.from(document.querySelectorAll('h1, h2, h3, h4, h5, h6'));
623
+ const currentIndex = headings.indexOf(document.activeElement as HTMLElement);
624
+ const prevIndex = currentIndex > 0 ? currentIndex - 1 : headings.length - 1;
625
+
626
+ if (headings[prevIndex]) {
627
+ (headings[prevIndex] as HTMLElement).focus();
628
+ }
629
+ }
630
+
631
+ // Navigate to next landmark
632
+ static focusNextLandmark(): void {
633
+ const landmarks = Array.from(document.querySelectorAll('[role="banner"], [role="main"], [role="navigation"], [role="complementary"], [role="contentinfo"]'));
634
+ const currentIndex = landmarks.indexOf(document.activeElement as HTMLElement);
635
+ const nextIndex = currentIndex < landmarks.length - 1 ? currentIndex + 1 : 0;
636
+
637
+ if (landmarks[nextIndex]) {
638
+ (landmarks[nextIndex] as HTMLElement).focus();
639
+ }
640
+ }
641
+
642
+ // Navigate to previous landmark
643
+ static focusPreviousLandmark(): void {
644
+ const landmarks = Array.from(document.querySelectorAll('[role="banner"], [role="main"], [role="navigation"], [role="complementary"], [role="contentinfo"]'));
645
+ const currentIndex = landmarks.indexOf(document.activeElement as HTMLElement);
646
+ const prevIndex = currentIndex > 0 ? currentIndex - 1 : landmarks.length - 1;
647
+
648
+ if (landmarks[prevIndex]) {
649
+ (landmarks[prevIndex] as HTMLElement).focus();
650
+ }
651
+ }
652
+
653
+ // Focus first focusable element in container
654
+ static focusFirstInContainer(container: HTMLElement): void {
655
+ const focusableSelectors = [
656
+ 'a[href]',
657
+ 'button:not([disabled])',
658
+ 'input:not([disabled])',
659
+ 'select:not([disabled])',
660
+ 'textarea:not([disabled])',
661
+ '[tabindex]:not([tabindex="-1"])'
662
+ ];
663
+
664
+ const firstElement = container.querySelector(focusableSelectors.join(','));
665
+ if (firstElement) {
666
+ (firstElement as HTMLElement).focus();
667
+ }
668
+ }
669
+
670
+ // Focus last focusable element in container
671
+ static focusLastInContainer(container: HTMLElement): void {
672
+ const focusableSelectors = [
673
+ 'a[href]',
674
+ 'button:not([disabled])',
675
+ 'input:not([disabled])',
676
+ 'select:not([disabled])',
677
+ 'textarea:not([disabled])',
678
+ '[tabindex]:not([tabindex="-1"])'
679
+ ];
680
+
681
+ const elements = Array.from(container.querySelectorAll(focusableSelectors.join(',')));
682
+ const lastElement = elements[elements.length - 1];
683
+ if (lastElement) {
684
+ (lastElement as HTMLElement).focus();
685
+ }
686
+ }
687
+ }
688
+
689
+ // Export default instances
690
+ export const focusManager = new FocusManager();
691
+ export const focusRestoration = new FocusRestoration();
692
+
693
+ // Export default
694
+ export default {
695
+ FocusManager,
696
+ FocusTrap,
697
+ FocusRestoration,
698
+ FocusNavigation,
699
+ focusManager,
700
+ focusRestoration
701
+ };