@qwickapps/react-framework 1.3.1 → 1.3.3

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 (43) hide show
  1. package/README.md +123 -1
  2. package/dist/components/AccessibilityProvider.d.ts +64 -0
  3. package/dist/components/AccessibilityProvider.d.ts.map +1 -0
  4. package/dist/components/Breadcrumbs.d.ts +39 -0
  5. package/dist/components/Breadcrumbs.d.ts.map +1 -0
  6. package/dist/components/ErrorBoundary.d.ts +39 -0
  7. package/dist/components/ErrorBoundary.d.ts.map +1 -0
  8. package/dist/components/QwickApp.d.ts.map +1 -1
  9. package/dist/components/index.d.ts +3 -0
  10. package/dist/components/index.d.ts.map +1 -1
  11. package/dist/index.bundled.css +12 -0
  12. package/dist/index.esm.js +910 -44
  13. package/dist/index.js +916 -47
  14. package/dist/templates/TemplateResolver.d.ts.map +1 -1
  15. package/dist/utils/htmlTransform.d.ts.map +1 -1
  16. package/dist/utils/logger.d.ts +15 -3
  17. package/dist/utils/logger.d.ts.map +1 -1
  18. package/package.json +4 -2
  19. package/src/components/AccessibilityProvider.tsx +466 -0
  20. package/src/components/Breadcrumbs.tsx +223 -0
  21. package/src/components/ErrorBoundary.tsx +216 -0
  22. package/src/components/QwickApp.tsx +17 -11
  23. package/src/components/__tests__/AccessibilityProvider.test.tsx +330 -0
  24. package/src/components/__tests__/Breadcrumbs.test.tsx +268 -0
  25. package/src/components/__tests__/ErrorBoundary.test.tsx +163 -0
  26. package/src/components/index.ts +3 -0
  27. package/src/stories/AccessibilityProvider.stories.tsx +284 -0
  28. package/src/stories/Breadcrumbs.stories.tsx +304 -0
  29. package/src/stories/ErrorBoundary.stories.tsx +159 -0
  30. package/src/stories/{form/FormComponents.stories.tsx → FormComponents.stories.tsx} +8 -8
  31. package/src/templates/TemplateResolver.ts +2 -6
  32. package/src/utils/__tests__/nested-dom-fix.test.tsx +53 -0
  33. package/src/utils/__tests__/optional-logging.test.ts +83 -0
  34. package/src/utils/htmlTransform.tsx +69 -3
  35. package/src/utils/logger.ts +60 -5
  36. package/dist/schemas/Builders.d.ts +0 -7
  37. package/dist/schemas/Builders.d.ts.map +0 -1
  38. package/dist/schemas/types.d.ts +0 -7
  39. package/dist/schemas/types.d.ts.map +0 -1
  40. package/dist/types/DataBinding.d.ts +0 -7
  41. package/dist/types/DataBinding.d.ts.map +0 -1
  42. package/dist/types/DataProvider.d.ts +0 -7
  43. package/dist/types/DataProvider.d.ts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"TemplateResolver.d.ts","sourceRoot":"","sources":["../../src/templates/TemplateResolver.ts"],"names":[],"mappings":"AAOA,OAAO,EAAsB,YAAY,EAAsD,KAAK,EAA4B,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACzK,OAAO,EAEL,iBAAiB,EAEjB,sBAAsB,EACvB,MAAM,UAAU,CAAC;AAElB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,qBAAa,gBAAiB,YAAW,iBAAiB;IACxD,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,aAAa,CAAC,CAAwB;IAC9C,OAAO,CAAC,aAAa,CAAU;IAC/B,OAAO,CAAC,GAAG,CAAS;gBAER,MAAM,EAAE,sBAAsB;IA4BpC,GAAG,CAAC,CAAC,SAAS,KAAK,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAI5D,MAAM,CAAC,CAAC,SAAS,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC;IAIlG;;OAEG;IACG,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAG,MAAM,CAAE;IAsB3D;;;OAGG;YACW,iBAAiB;CAsChC"}
1
+ {"version":3,"file":"TemplateResolver.d.ts","sourceRoot":"","sources":["../../src/templates/TemplateResolver.ts"],"names":[],"mappings":"AAOA,OAAO,EAAsB,YAAY,EAAsD,KAAK,EAA4B,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACzK,OAAO,EAEL,iBAAiB,EAEjB,sBAAsB,EACvB,MAAM,UAAU,CAAC;AAElB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,qBAAa,gBAAiB,YAAW,iBAAiB;IACxD,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,aAAa,CAAC,CAAwB;IAC9C,OAAO,CAAC,aAAa,CAAU;IAC/B,OAAO,CAAC,GAAG,CAAS;gBAER,MAAM,EAAE,sBAAsB;IAwBpC,GAAG,CAAC,CAAC,SAAS,KAAK,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAI5D,MAAM,CAAC,CAAC,SAAS,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC;IAIlG;;OAEG;IACG,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAG,MAAM,CAAE;IAsB3D;;;OAGG;YACW,iBAAiB;CAsChC"}
@@ -1 +1 @@
1
- {"version":3,"file":"htmlTransform.d.ts","sourceRoot":"","sources":["../../src/utils/htmlTransform.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,MAAM,OAAO,CAAC;AAM1B,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;CAC/D;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,eAAe,CAAC,EAAE,GAAG,CAAC;IACtB,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;CACxE;AAED;;GAEG;AACH,eAAO,MAAM,mBAAmB,EAAE,aAAa,EA0G9C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,oBAAoB,EAAE,aAAa,EAqC/C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,SAAS,OAAO,EAAE,KAAK,MAAM,KAAG,KAAK,CAAC,SA+BrE,CAAC;AAkBF;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,MAAM,EACX,MAAM,GAAE,eAAgD,GACvD,KAAK,CAAC,SAAS,CAqCjB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,MAAM,GAAE,eAAgD,GACvD,KAAK,CAAC,SAAS,EAAE,CASnB;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAuB3D"}
1
+ {"version":3,"file":"htmlTransform.d.ts","sourceRoot":"","sources":["../../src/utils/htmlTransform.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,MAAM,OAAO,CAAC;AAM1B,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;CAC/D;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,eAAe,CAAC,EAAE,GAAG,CAAC;IACtB,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;CACxE;AAED;;GAEG;AACH,eAAO,MAAM,mBAAmB,EAAE,aAAa,EA0G9C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,oBAAoB,EAAE,aAAa,EAqC/C,CAAC;AAsBF;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,SAAS,OAAO,EAAE,KAAK,MAAM,KAAG,KAAK,CAAC,SAoCrE,CAAC;AAkBF;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,MAAM,EACX,MAAM,GAAE,eAAgD,GACvD,KAAK,CAAC,SAAS,CA0CjB;AAmCD;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,MAAM,GAAE,eAAgD,GACvD,KAAK,CAAC,SAAS,EAAE,CAYnB;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAuB3D"}
@@ -1,12 +1,24 @@
1
1
  /**
2
2
  * QwickApps React Framework - Logger Utility
3
3
  *
4
- * Re-exports from @qwickapps/logging with framework-specific loggers
4
+ * Optional logging with fallback to console when @qwickapps/logging is not available
5
5
  *
6
6
  * Copyright (c) 2025 QwickApps.com. All rights reserved.
7
7
  */
8
- export * from '@qwickapps/logging';
9
- import { Logger } from '@qwickapps/logging';
8
+ export interface Logger {
9
+ debug(message: string, ...args: any[]): void;
10
+ info(message: string, ...args: any[]): void;
11
+ warn(message: string, ...args: any[]): void;
12
+ error(message: string, ...args: any[]): void;
13
+ }
14
+ /**
15
+ * Create logger with optional @qwickapps/logging dependency
16
+ */
17
+ declare function createLogger(name: string): Logger;
18
+ /**
19
+ * Re-export Logger type and createLogger function for compatibility
20
+ */
21
+ export { createLogger };
10
22
  /**
11
23
  * Framework-specific loggers
12
24
  */
@@ -1 +1 @@
1
- {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,cAAc,oBAAoB,CAAC;AAGnC,OAAO,EAAgB,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE1D;;GAEG;AACH,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAU1C,CAAC"}
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,MAAM;IACrB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAC7C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAC5C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAC5C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;CAC9C;AAqCD;;GAEG;AACH,iBAAS,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAS1C;AAED;;GAEG;AACH,OAAO,EAAE,YAAY,EAAE,CAAC;AAExB;;GAEG;AACH,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAU1C,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qwickapps/react-framework",
3
- "version": "1.3.1",
3
+ "version": "1.3.3",
4
4
  "type": "module",
5
5
  "description": "Complete React framework with responsive navigation, flexible layouts, theming system, and reusable components for building modern applications.",
6
6
  "main": "dist/index.js",
@@ -48,12 +48,14 @@
48
48
  "author": "QwickApps",
49
49
  "license": "PolyForm-Shield-1.0.0",
50
50
  "dependencies": {
51
- "@qwickapps/logging": "^1.0.0",
52
51
  "@qwickapps/schema": "^1.3.1",
53
52
  "marked": "^4.3.0",
54
53
  "md5": "^2.3.0",
55
54
  "sanitize-html": "^2.17.0"
56
55
  },
56
+ "optionalDependencies": {
57
+ "@qwickapps/logging": "^1.0.0"
58
+ },
57
59
  "peerDependencies": {
58
60
  "@emotion/react": ">=11.0.0",
59
61
  "@emotion/styled": ">=11.0.0",
@@ -0,0 +1,466 @@
1
+ import React, { createContext, useContext, useEffect, useReducer, ReactNode } from 'react';
2
+
3
+ // Accessibility State
4
+ export interface AccessibilityState {
5
+ highContrast: boolean;
6
+ reducedMotion: boolean;
7
+ largeText: boolean;
8
+ focusVisible: boolean;
9
+ isKeyboardUser: boolean;
10
+ issues: AccessibilityIssue[];
11
+ lastAnnouncement: Announcement | null;
12
+ preferences: Record<string, any>;
13
+ }
14
+
15
+ export interface AccessibilityIssue {
16
+ type: string;
17
+ message: string;
18
+ level: 'error' | 'warning' | 'info';
19
+ element?: Element;
20
+ }
21
+
22
+ export interface Announcement {
23
+ message: string;
24
+ level: 'polite' | 'assertive';
25
+ timestamp: number;
26
+ }
27
+
28
+ // Actions
29
+ type AccessibilityAction =
30
+ | { type: 'SET_HIGH_CONTRAST'; payload: boolean }
31
+ | { type: 'SET_REDUCED_MOTION'; payload: boolean }
32
+ | { type: 'SET_LARGE_TEXT'; payload: boolean }
33
+ | { type: 'SET_FOCUS_VISIBLE'; payload: boolean }
34
+ | { type: 'SET_KEYBOARD_USER'; payload: boolean }
35
+ | { type: 'ADD_ISSUE'; payload: AccessibilityIssue }
36
+ | { type: 'CLEAR_ISSUES' }
37
+ | { type: 'SET_ANNOUNCEMENT'; payload: Announcement };
38
+
39
+ // Context
40
+ export interface AccessibilityContextValue extends AccessibilityState {
41
+ setHighContrast: (enabled: boolean) => void;
42
+ setReducedMotion: (enabled: boolean) => void;
43
+ setLargeText: (enabled: boolean) => void;
44
+ setFocusVisible: (enabled: boolean) => void;
45
+ announce: (message: string, level?: 'polite' | 'assertive') => void;
46
+ announcePolite: (message: string) => void;
47
+ announceAssertive: (message: string) => void;
48
+ addIssue: (issue: AccessibilityIssue) => void;
49
+ clearIssues: () => void;
50
+ runAudit: () => void;
51
+ }
52
+
53
+ const AccessibilityContext = createContext<AccessibilityContextValue | null>(null);
54
+
55
+ // Reducer
56
+ const accessibilityReducer = (state: AccessibilityState, action: AccessibilityAction): AccessibilityState => {
57
+ switch (action.type) {
58
+ case 'SET_HIGH_CONTRAST':
59
+ return { ...state, highContrast: action.payload };
60
+ case 'SET_REDUCED_MOTION':
61
+ return { ...state, reducedMotion: action.payload };
62
+ case 'SET_LARGE_TEXT':
63
+ return { ...state, largeText: action.payload };
64
+ case 'SET_FOCUS_VISIBLE':
65
+ return { ...state, focusVisible: action.payload };
66
+ case 'SET_KEYBOARD_USER':
67
+ return { ...state, isKeyboardUser: action.payload };
68
+ case 'ADD_ISSUE':
69
+ return { ...state, issues: [...state.issues, action.payload] };
70
+ case 'CLEAR_ISSUES':
71
+ return { ...state, issues: [] };
72
+ case 'SET_ANNOUNCEMENT':
73
+ return { ...state, lastAnnouncement: action.payload };
74
+ default:
75
+ return state;
76
+ }
77
+ };
78
+
79
+ // Initial state
80
+ const initialState: AccessibilityState = {
81
+ highContrast: false,
82
+ reducedMotion: false,
83
+ largeText: false,
84
+ focusVisible: true,
85
+ isKeyboardUser: false,
86
+ issues: [],
87
+ lastAnnouncement: null,
88
+ preferences: {}
89
+ };
90
+
91
+ // ARIA Live Manager
92
+ class AriaLiveManager {
93
+ private politeRegion: HTMLElement | null = null;
94
+ private assertiveRegion: HTMLElement | null = null;
95
+
96
+ constructor() {
97
+ this.createLiveRegions();
98
+ }
99
+
100
+ private createLiveRegions() {
101
+ if (typeof document === 'undefined') return;
102
+
103
+ // Polite announcements
104
+ this.politeRegion = document.createElement('div');
105
+ this.politeRegion.setAttribute('aria-live', 'polite');
106
+ this.politeRegion.setAttribute('aria-atomic', 'true');
107
+ this.politeRegion.setAttribute('id', 'qwickapps-aria-live-polite');
108
+ this.politeRegion.style.cssText = `
109
+ position: absolute !important;
110
+ left: -10000px !important;
111
+ width: 1px !important;
112
+ height: 1px !important;
113
+ overflow: hidden !important;
114
+ `;
115
+ document.body.appendChild(this.politeRegion);
116
+
117
+ // Assertive announcements
118
+ this.assertiveRegion = document.createElement('div');
119
+ this.assertiveRegion.setAttribute('aria-live', 'assertive');
120
+ this.assertiveRegion.setAttribute('aria-atomic', 'true');
121
+ this.assertiveRegion.setAttribute('id', 'qwickapps-aria-live-assertive');
122
+ this.assertiveRegion.style.cssText = `
123
+ position: absolute !important;
124
+ left: -10000px !important;
125
+ width: 1px !important;
126
+ height: 1px !important;
127
+ overflow: hidden !important;
128
+ `;
129
+ document.body.appendChild(this.assertiveRegion);
130
+ }
131
+
132
+ announce(message: string, level: 'polite' | 'assertive' = 'polite') {
133
+ if (level === 'assertive') {
134
+ this.announceAssertive(message);
135
+ } else {
136
+ this.announcePolite(message);
137
+ }
138
+ }
139
+
140
+ announcePolite(message: string) {
141
+ if (!this.politeRegion) return;
142
+
143
+ this.politeRegion.textContent = '';
144
+ // Small delay ensures screen readers detect the change
145
+ setTimeout(() => {
146
+ if (this.politeRegion) {
147
+ this.politeRegion.textContent = message;
148
+ }
149
+ }, 100);
150
+ }
151
+
152
+ announceAssertive(message: string) {
153
+ if (!this.assertiveRegion) return;
154
+
155
+ this.assertiveRegion.textContent = '';
156
+ // Small delay ensures screen readers detect the change
157
+ setTimeout(() => {
158
+ if (this.assertiveRegion) {
159
+ this.assertiveRegion.textContent = message;
160
+ }
161
+ }, 100);
162
+ }
163
+ }
164
+
165
+ const ariaLiveManager = new AriaLiveManager();
166
+
167
+ // Props
168
+ export interface AccessibilityProviderProps {
169
+ children: ReactNode;
170
+ enableAudit?: boolean;
171
+ }
172
+
173
+ /**
174
+ * Accessibility Provider Component
175
+ * Provides comprehensive accessibility context and utilities
176
+ *
177
+ * Features:
178
+ * - System preference detection (high contrast, reduced motion)
179
+ * - Keyboard navigation detection
180
+ * - ARIA live announcements
181
+ * - Focus management
182
+ * - Accessibility auditing
183
+ * - Settings persistence
184
+ */
185
+ export const AccessibilityProvider: React.FC<AccessibilityProviderProps> = ({
186
+ children,
187
+ enableAudit = process.env.NODE_ENV === 'development'
188
+ }) => {
189
+ const [state, dispatch] = useReducer(accessibilityReducer, initialState);
190
+
191
+ useEffect(() => {
192
+ // Detect user preferences from system
193
+ detectUserPreferences();
194
+
195
+ // Set up keyboard detection
196
+ const keyboardCleanup = setupKeyboardDetection();
197
+
198
+ // Initialize focus management
199
+ initializeFocusManagement();
200
+
201
+ // Run initial accessibility audit
202
+ if (enableAudit) {
203
+ runAccessibilityAudit();
204
+ }
205
+
206
+ // Cleanup
207
+ return () => {
208
+ if (keyboardCleanup) keyboardCleanup();
209
+ };
210
+ }, [enableAudit]);
211
+
212
+ const detectUserPreferences = () => {
213
+ if (typeof window === 'undefined') return;
214
+
215
+ // High contrast mode
216
+ if (window.matchMedia && window.matchMedia('(prefers-contrast: high)').matches) {
217
+ dispatch({ type: 'SET_HIGH_CONTRAST', payload: true });
218
+ }
219
+
220
+ // Reduced motion
221
+ if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
222
+ dispatch({ type: 'SET_REDUCED_MOTION', payload: true });
223
+ }
224
+
225
+ // Listen for changes
226
+ if (window.matchMedia) {
227
+ const contrastMedia = window.matchMedia('(prefers-contrast: high)');
228
+ const motionMedia = window.matchMedia('(prefers-reduced-motion: reduce)');
229
+
230
+ const contrastHandler = (e: MediaQueryListEvent) => {
231
+ dispatch({ type: 'SET_HIGH_CONTRAST', payload: e.matches });
232
+ };
233
+
234
+ const motionHandler = (e: MediaQueryListEvent) => {
235
+ dispatch({ type: 'SET_REDUCED_MOTION', payload: e.matches });
236
+ };
237
+
238
+ contrastMedia.addEventListener('change', contrastHandler);
239
+ motionMedia.addEventListener('change', motionHandler);
240
+
241
+ // Return cleanup function
242
+ return () => {
243
+ contrastMedia.removeEventListener('change', contrastHandler);
244
+ motionMedia.removeEventListener('change', motionHandler);
245
+ };
246
+ }
247
+ };
248
+
249
+ const setupKeyboardDetection = () => {
250
+ if (typeof document === 'undefined') return;
251
+
252
+ let keyboardUser = false;
253
+
254
+ const handleKeyDown = (e: KeyboardEvent) => {
255
+ if (e.key === 'Tab') {
256
+ keyboardUser = true;
257
+ dispatch({ type: 'SET_KEYBOARD_USER', payload: true });
258
+ document.body.classList.add('keyboard-user');
259
+ }
260
+ };
261
+
262
+ const handleMouseDown = () => {
263
+ if (keyboardUser) {
264
+ keyboardUser = false;
265
+ dispatch({ type: 'SET_KEYBOARD_USER', payload: false });
266
+ document.body.classList.remove('keyboard-user');
267
+ }
268
+ };
269
+
270
+ document.addEventListener('keydown', handleKeyDown);
271
+ document.addEventListener('mousedown', handleMouseDown);
272
+
273
+ return () => {
274
+ document.removeEventListener('keydown', handleKeyDown);
275
+ document.removeEventListener('mousedown', handleMouseDown);
276
+ };
277
+ };
278
+
279
+ const initializeFocusManagement = () => {
280
+ if (typeof document === 'undefined') return;
281
+
282
+ // Enhanced focus indicators for keyboard users
283
+ const style = document.createElement('style');
284
+ style.textContent = `
285
+ .keyboard-user *:focus {
286
+ outline: 3px solid #005cee !important;
287
+ outline-offset: 2px !important;
288
+ }
289
+
290
+ .high-contrast *:focus {
291
+ outline: 3px solid #ffffff !important;
292
+ outline-offset: 2px !important;
293
+ box-shadow: 0 0 0 1px #000000 !important;
294
+ }
295
+
296
+ .reduced-motion * {
297
+ animation-duration: 0.01ms !important;
298
+ animation-iteration-count: 1 !important;
299
+ transition-duration: 0.01ms !important;
300
+ }
301
+
302
+ .large-text {
303
+ font-size: 1.2em !important;
304
+ }
305
+ `;
306
+ document.head.appendChild(style);
307
+ };
308
+
309
+ const runAccessibilityAudit = () => {
310
+ if (typeof document === 'undefined') return;
311
+
312
+ setTimeout(() => {
313
+ const issues: AccessibilityIssue[] = [];
314
+
315
+ // Check for images without alt text
316
+ const images = document.querySelectorAll('img:not([alt])');
317
+ images.forEach(img => {
318
+ issues.push({
319
+ type: 'missing-alt-text',
320
+ message: 'Image missing alt attribute',
321
+ level: 'error',
322
+ element: img
323
+ });
324
+ });
325
+
326
+ // Check for buttons without accessible names
327
+ const buttons = document.querySelectorAll('button:not([aria-label]):not([title])');
328
+ buttons.forEach(button => {
329
+ if (!button.textContent?.trim()) {
330
+ issues.push({
331
+ type: 'unnamed-button',
332
+ message: 'Button missing accessible name',
333
+ level: 'error',
334
+ element: button
335
+ });
336
+ }
337
+ });
338
+
339
+ // Check for form inputs without labels
340
+ const inputs = document.querySelectorAll('input:not([aria-label]):not([title])');
341
+ inputs.forEach(input => {
342
+ const id = input.getAttribute('id');
343
+ if (id) {
344
+ const label = document.querySelector(`label[for="${id}"]`);
345
+ if (!label) {
346
+ issues.push({
347
+ type: 'unlabeled-input',
348
+ message: 'Form input missing label',
349
+ level: 'error',
350
+ element: input
351
+ });
352
+ }
353
+ } else {
354
+ issues.push({
355
+ type: 'unlabeled-input',
356
+ message: 'Form input missing label',
357
+ level: 'error',
358
+ element: input
359
+ });
360
+ }
361
+ });
362
+
363
+ dispatch({ type: 'CLEAR_ISSUES' });
364
+
365
+ issues.forEach(issue => {
366
+ dispatch({ type: 'ADD_ISSUE', payload: issue });
367
+ });
368
+
369
+ if (issues.length > 0) {
370
+ console.group('🔍 Accessibility Issues Found');
371
+ issues.forEach(issue => {
372
+ const logMethod = issue.level === 'error' ? console.error : console.warn;
373
+ logMethod(`${issue.type}: ${issue.message}`);
374
+ });
375
+ console.groupEnd();
376
+ }
377
+ }, 1000);
378
+ };
379
+
380
+ // Context value
381
+ const contextValue: AccessibilityContextValue = {
382
+ ...state,
383
+
384
+ // Actions
385
+ setHighContrast: (enabled: boolean) => dispatch({ type: 'SET_HIGH_CONTRAST', payload: enabled }),
386
+ setReducedMotion: (enabled: boolean) => dispatch({ type: 'SET_REDUCED_MOTION', payload: enabled }),
387
+ setLargeText: (enabled: boolean) => dispatch({ type: 'SET_LARGE_TEXT', payload: enabled }),
388
+ setFocusVisible: (enabled: boolean) => dispatch({ type: 'SET_FOCUS_VISIBLE', payload: enabled }),
389
+
390
+ // Utilities
391
+ announce: (message: string, level: 'polite' | 'assertive' = 'polite') => {
392
+ ariaLiveManager.announce(message, level);
393
+ dispatch({ type: 'SET_ANNOUNCEMENT', payload: { message, level, timestamp: Date.now() } });
394
+ },
395
+
396
+ announcePolite: (message: string) => {
397
+ ariaLiveManager.announcePolite(message);
398
+ dispatch({ type: 'SET_ANNOUNCEMENT', payload: { message, level: 'polite', timestamp: Date.now() } });
399
+ },
400
+
401
+ announceAssertive: (message: string) => {
402
+ ariaLiveManager.announceAssertive(message);
403
+ dispatch({ type: 'SET_ANNOUNCEMENT', payload: { message, level: 'assertive', timestamp: Date.now() } });
404
+ },
405
+
406
+ addIssue: (issue: AccessibilityIssue) => dispatch({ type: 'ADD_ISSUE', payload: issue }),
407
+ clearIssues: () => dispatch({ type: 'CLEAR_ISSUES' }),
408
+
409
+ // Audit function
410
+ runAudit: runAccessibilityAudit
411
+ };
412
+
413
+ // Apply CSS classes based on preferences
414
+ useEffect(() => {
415
+ if (typeof document === 'undefined') return;
416
+
417
+ const { highContrast, reducedMotion, largeText } = state;
418
+
419
+ document.body.classList.toggle('high-contrast', highContrast);
420
+ document.body.classList.toggle('reduced-motion', reducedMotion);
421
+ document.body.classList.toggle('large-text', largeText);
422
+ }, [state.highContrast, state.reducedMotion, state.largeText]);
423
+
424
+ return (
425
+ <AccessibilityContext.Provider value={contextValue}>
426
+ {children}
427
+ </AccessibilityContext.Provider>
428
+ );
429
+ };
430
+
431
+ /**
432
+ * Hook to access accessibility context
433
+ */
434
+ export const useAccessibility = (): AccessibilityContextValue => {
435
+ const context = useContext(AccessibilityContext);
436
+
437
+ if (!context) {
438
+ throw new Error('useAccessibility must be used within an AccessibilityProvider');
439
+ }
440
+
441
+ return context;
442
+ };
443
+
444
+ /**
445
+ * Higher-Order Component for accessibility enhancements
446
+ */
447
+ export const withAccessibility = <P extends object>(
448
+ WrappedComponent: React.ComponentType<P>
449
+ ) => {
450
+ const AccessibilityEnhancedComponent = (props: P) => {
451
+ const accessibility = useAccessibility();
452
+
453
+ return (
454
+ <WrappedComponent
455
+ {...props}
456
+ accessibility={accessibility}
457
+ />
458
+ );
459
+ };
460
+
461
+ AccessibilityEnhancedComponent.displayName = `withAccessibility(${WrappedComponent.displayName || WrappedComponent.name || 'Component'})`;
462
+
463
+ return AccessibilityEnhancedComponent;
464
+ };
465
+
466
+ export default AccessibilityProvider;