agentic-qe 2.4.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/.claude/agents/qe-a11y-ally.md +751 -0
  2. package/.claude/agents/qx-partner.md +120 -4
  3. package/.claude/skills/testability-scoring/SKILL.md +107 -6
  4. package/CHANGELOG.md +86 -0
  5. package/README.md +7 -6
  6. package/dist/agents/AccessibilityAllyAgent.d.ts +168 -0
  7. package/dist/agents/AccessibilityAllyAgent.d.ts.map +1 -0
  8. package/dist/agents/AccessibilityAllyAgent.js +462 -0
  9. package/dist/agents/AccessibilityAllyAgent.js.map +1 -0
  10. package/dist/agents/SONAIntegration.d.ts +109 -0
  11. package/dist/agents/SONAIntegration.d.ts.map +1 -0
  12. package/dist/agents/SONAIntegration.js +167 -0
  13. package/dist/agents/SONAIntegration.js.map +1 -0
  14. package/dist/agents/index.d.ts +3 -0
  15. package/dist/agents/index.d.ts.map +1 -1
  16. package/dist/agents/index.js +93 -2
  17. package/dist/agents/index.js.map +1 -1
  18. package/dist/cli/init/agents.js +1 -1
  19. package/dist/cli/init/claude-config.js +2 -2
  20. package/dist/cli/init/database-init.js +1 -1
  21. package/dist/core/cache/BinaryCacheImpl.d.ts +161 -0
  22. package/dist/core/cache/BinaryCacheImpl.d.ts.map +1 -0
  23. package/dist/core/cache/BinaryCacheImpl.js +685 -0
  24. package/dist/core/cache/BinaryCacheImpl.js.map +1 -0
  25. package/dist/core/cache/BinaryMetadataCache.d.ts +244 -0
  26. package/dist/core/cache/BinaryMetadataCache.d.ts.map +1 -1
  27. package/dist/core/cache/BinaryMetadataCache.js +63 -1
  28. package/dist/core/cache/BinaryMetadataCache.js.map +1 -1
  29. package/dist/core/cache/index.d.ts +1 -0
  30. package/dist/core/cache/index.d.ts.map +1 -1
  31. package/dist/core/cache/index.js +10 -1
  32. package/dist/core/cache/index.js.map +1 -1
  33. package/dist/core/memory/AgentDBService.d.ts +30 -4
  34. package/dist/core/memory/AgentDBService.d.ts.map +1 -1
  35. package/dist/core/memory/AgentDBService.js +122 -12
  36. package/dist/core/memory/AgentDBService.js.map +1 -1
  37. package/dist/core/memory/CachedHNSWVectorMemory.d.ts +153 -0
  38. package/dist/core/memory/CachedHNSWVectorMemory.d.ts.map +1 -0
  39. package/dist/core/memory/CachedHNSWVectorMemory.js +329 -0
  40. package/dist/core/memory/CachedHNSWVectorMemory.js.map +1 -0
  41. package/dist/core/memory/HNSWVectorMemory.js +1 -1
  42. package/dist/core/memory/RuVectorPatternStore.d.ts.map +1 -1
  43. package/dist/core/memory/RuVectorPatternStore.js +8 -2
  44. package/dist/core/memory/RuVectorPatternStore.js.map +1 -1
  45. package/dist/core/memory/UnifiedMemoryCoordinator.d.ts +50 -0
  46. package/dist/core/memory/UnifiedMemoryCoordinator.d.ts.map +1 -1
  47. package/dist/core/memory/UnifiedMemoryCoordinator.js +206 -0
  48. package/dist/core/memory/UnifiedMemoryCoordinator.js.map +1 -1
  49. package/dist/core/memory/index.d.ts +2 -0
  50. package/dist/core/memory/index.d.ts.map +1 -1
  51. package/dist/core/memory/index.js +8 -1
  52. package/dist/core/memory/index.js.map +1 -1
  53. package/dist/core/optimization/RecursiveOptimizer.d.ts +233 -0
  54. package/dist/core/optimization/RecursiveOptimizer.d.ts.map +1 -0
  55. package/dist/core/optimization/RecursiveOptimizer.js +509 -0
  56. package/dist/core/optimization/RecursiveOptimizer.js.map +1 -0
  57. package/dist/core/strategies/SONALearningStrategy.d.ts +115 -0
  58. package/dist/core/strategies/SONALearningStrategy.d.ts.map +1 -0
  59. package/dist/core/strategies/SONALearningStrategy.js +656 -0
  60. package/dist/core/strategies/SONALearningStrategy.js.map +1 -0
  61. package/dist/core/strategies/TRMLearningStrategy.d.ts +162 -0
  62. package/dist/core/strategies/TRMLearningStrategy.d.ts.map +1 -0
  63. package/dist/core/strategies/TRMLearningStrategy.js +670 -0
  64. package/dist/core/strategies/TRMLearningStrategy.js.map +1 -0
  65. package/dist/core/strategies/index.d.ts +10 -1
  66. package/dist/core/strategies/index.d.ts.map +1 -1
  67. package/dist/core/strategies/index.js +4 -1
  68. package/dist/core/strategies/index.js.map +1 -1
  69. package/dist/learning/SONAFeedbackLoop.d.ts +168 -0
  70. package/dist/learning/SONAFeedbackLoop.d.ts.map +1 -0
  71. package/dist/learning/SONAFeedbackLoop.js +344 -0
  72. package/dist/learning/SONAFeedbackLoop.js.map +1 -0
  73. package/dist/learning/baselines/BaselineCollector.d.ts +1 -1
  74. package/dist/learning/baselines/BaselineCollector.js +1 -1
  75. package/dist/learning/baselines/StandardTaskSuite.d.ts +1 -1
  76. package/dist/learning/baselines/StandardTaskSuite.js +1 -1
  77. package/dist/learning/index.d.ts +2 -0
  78. package/dist/learning/index.d.ts.map +1 -1
  79. package/dist/learning/index.js +6 -1
  80. package/dist/learning/index.js.map +1 -1
  81. package/dist/mcp/server-instructions.d.ts +1 -1
  82. package/dist/mcp/server-instructions.js +1 -1
  83. package/dist/mcp/server.d.ts.map +1 -1
  84. package/dist/mcp/server.js +23 -16
  85. package/dist/mcp/server.js.map +1 -1
  86. package/dist/mcp/services/AgentRegistry.d.ts.map +1 -1
  87. package/dist/mcp/services/AgentRegistry.js +6 -1
  88. package/dist/mcp/services/AgentRegistry.js.map +1 -1
  89. package/dist/mcp/tools/qe/accessibility/accname-computation.d.ts +114 -0
  90. package/dist/mcp/tools/qe/accessibility/accname-computation.d.ts.map +1 -0
  91. package/dist/mcp/tools/qe/accessibility/accname-computation.js +566 -0
  92. package/dist/mcp/tools/qe/accessibility/accname-computation.js.map +1 -0
  93. package/dist/mcp/tools/qe/accessibility/apg-patterns.d.ts +103 -0
  94. package/dist/mcp/tools/qe/accessibility/apg-patterns.d.ts.map +1 -0
  95. package/dist/mcp/tools/qe/accessibility/apg-patterns.js +1028 -0
  96. package/dist/mcp/tools/qe/accessibility/apg-patterns.js.map +1 -0
  97. package/dist/mcp/tools/qe/accessibility/en-301-549-mapping.d.ts +48 -0
  98. package/dist/mcp/tools/qe/accessibility/en-301-549-mapping.d.ts.map +1 -0
  99. package/dist/mcp/tools/qe/accessibility/en-301-549-mapping.js +565 -0
  100. package/dist/mcp/tools/qe/accessibility/en-301-549-mapping.js.map +1 -0
  101. package/dist/mcp/tools/qe/accessibility/eu-accessibility-act.d.ts +117 -0
  102. package/dist/mcp/tools/qe/accessibility/eu-accessibility-act.d.ts.map +1 -0
  103. package/dist/mcp/tools/qe/accessibility/eu-accessibility-act.js +571 -0
  104. package/dist/mcp/tools/qe/accessibility/eu-accessibility-act.js.map +1 -0
  105. package/dist/mcp/tools/qe/accessibility/html-report-generator.d.ts +23 -0
  106. package/dist/mcp/tools/qe/accessibility/html-report-generator.d.ts.map +1 -0
  107. package/dist/mcp/tools/qe/accessibility/html-report-generator.js +1152 -0
  108. package/dist/mcp/tools/qe/accessibility/html-report-generator.js.map +1 -0
  109. package/dist/mcp/tools/qe/accessibility/index.d.ts +22 -0
  110. package/dist/mcp/tools/qe/accessibility/index.d.ts.map +1 -0
  111. package/dist/mcp/tools/qe/accessibility/index.js +38 -0
  112. package/dist/mcp/tools/qe/accessibility/index.js.map +1 -0
  113. package/dist/mcp/tools/qe/accessibility/markdown-report-generator.d.ts +18 -0
  114. package/dist/mcp/tools/qe/accessibility/markdown-report-generator.d.ts.map +1 -0
  115. package/dist/mcp/tools/qe/accessibility/markdown-report-generator.js +549 -0
  116. package/dist/mcp/tools/qe/accessibility/markdown-report-generator.js.map +1 -0
  117. package/dist/mcp/tools/qe/accessibility/remediation-code-generator.d.ts +139 -0
  118. package/dist/mcp/tools/qe/accessibility/remediation-code-generator.d.ts.map +1 -0
  119. package/dist/mcp/tools/qe/accessibility/remediation-code-generator.js +1300 -0
  120. package/dist/mcp/tools/qe/accessibility/remediation-code-generator.js.map +1 -0
  121. package/dist/mcp/tools/qe/accessibility/scan-comprehensive.d.ts +138 -0
  122. package/dist/mcp/tools/qe/accessibility/scan-comprehensive.d.ts.map +1 -0
  123. package/dist/mcp/tools/qe/accessibility/scan-comprehensive.js +1326 -0
  124. package/dist/mcp/tools/qe/accessibility/scan-comprehensive.js.map +1 -0
  125. package/dist/mcp/tools/qe/accessibility/video-vision-analyzer.d.ts +50 -0
  126. package/dist/mcp/tools/qe/accessibility/video-vision-analyzer.d.ts.map +1 -0
  127. package/dist/mcp/tools/qe/accessibility/video-vision-analyzer.js +469 -0
  128. package/dist/mcp/tools/qe/accessibility/video-vision-analyzer.js.map +1 -0
  129. package/dist/mcp/tools/qe/accessibility/webvtt-generator.d.ts +193 -0
  130. package/dist/mcp/tools/qe/accessibility/webvtt-generator.d.ts.map +1 -0
  131. package/dist/mcp/tools/qe/accessibility/webvtt-generator.js +511 -0
  132. package/dist/mcp/tools/qe/accessibility/webvtt-generator.js.map +1 -0
  133. package/dist/mcp/tools.d.ts +1 -0
  134. package/dist/mcp/tools.d.ts.map +1 -1
  135. package/dist/mcp/tools.js +61 -0
  136. package/dist/mcp/tools.js.map +1 -1
  137. package/dist/providers/HybridRouter.d.ts +34 -3
  138. package/dist/providers/HybridRouter.d.ts.map +1 -1
  139. package/dist/providers/HybridRouter.js +69 -4
  140. package/dist/providers/HybridRouter.js.map +1 -1
  141. package/dist/providers/LLMProviderFactory.d.ts +68 -1
  142. package/dist/providers/LLMProviderFactory.d.ts.map +1 -1
  143. package/dist/providers/LLMProviderFactory.js +173 -6
  144. package/dist/providers/LLMProviderFactory.js.map +1 -1
  145. package/dist/providers/OpenRouterProvider.d.ts +150 -0
  146. package/dist/providers/OpenRouterProvider.d.ts.map +1 -0
  147. package/dist/providers/OpenRouterProvider.js +545 -0
  148. package/dist/providers/OpenRouterProvider.js.map +1 -0
  149. package/dist/providers/RuvllmProvider.d.ts +130 -16
  150. package/dist/providers/RuvllmProvider.d.ts.map +1 -1
  151. package/dist/providers/RuvllmProvider.js +399 -83
  152. package/dist/providers/RuvllmProvider.js.map +1 -1
  153. package/dist/providers/index.d.ts +33 -4
  154. package/dist/providers/index.d.ts.map +1 -1
  155. package/dist/providers/index.js +72 -21
  156. package/dist/providers/index.js.map +1 -1
  157. package/dist/telemetry/instrumentation/agent.d.ts +1 -1
  158. package/dist/telemetry/instrumentation/agent.js +1 -1
  159. package/dist/telemetry/instrumentation/index.d.ts +1 -1
  160. package/dist/telemetry/instrumentation/index.js +1 -1
  161. package/dist/types/index.d.ts +2 -1
  162. package/dist/types/index.d.ts.map +1 -1
  163. package/dist/types/index.js +2 -0
  164. package/dist/types/index.js.map +1 -1
  165. package/dist/types/ruvllm.d.ts +97 -0
  166. package/dist/types/ruvllm.d.ts.map +1 -0
  167. package/dist/types/ruvllm.js +46 -0
  168. package/dist/types/ruvllm.js.map +1 -0
  169. package/dist/utils/ruvllm-loader.d.ts +94 -0
  170. package/dist/utils/ruvllm-loader.d.ts.map +1 -0
  171. package/dist/utils/ruvllm-loader.js +87 -0
  172. package/dist/utils/ruvllm-loader.js.map +1 -0
  173. package/docs/reference/agents.md +36 -1
  174. package/package.json +4 -2
@@ -0,0 +1,1028 @@
1
+ "use strict";
2
+ /**
3
+ * ARIA Authoring Practices Guide (APG) Pattern Library
4
+ *
5
+ * Comprehensive database of accessible component patterns based on W3C APG
6
+ * Version: Based on APG 1.2 (2024)
7
+ *
8
+ * Purpose: Provide context-aware ARIA recommendations for accessible components
9
+ * Reference: https://www.w3.org/WAI/ARIA/apg/
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.APG_PATTERNS = void 0;
13
+ exports.getAPGPattern = getAPGPattern;
14
+ exports.getPatternsByWCAG = getPatternsByWCAG;
15
+ exports.getPatternsByRole = getPatternsByRole;
16
+ exports.getPatternsByCategory = getPatternsByCategory;
17
+ exports.suggestAPGPattern = suggestAPGPattern;
18
+ exports.generatePatternCodeExample = generatePatternCodeExample;
19
+ exports.getKeyboardReference = getKeyboardReference;
20
+ exports.validateAgainstPattern = validateAgainstPattern;
21
+ exports.APG_PATTERNS = {
22
+ 'accordion': {
23
+ name: 'Accordion',
24
+ description: 'Vertically stacked set of interactive headings that each reveal a section of content',
25
+ category: 'widget',
26
+ wcagCriteria: ['2.1.1', '4.1.2', '1.3.1'],
27
+ roles: {
28
+ primary: 'button',
29
+ related: ['region', 'heading']
30
+ },
31
+ ariaAttributes: [
32
+ {
33
+ name: 'aria-expanded',
34
+ required: true,
35
+ description: 'Indicates whether the controlled region is expanded or collapsed',
36
+ possibleValues: ['true', 'false']
37
+ },
38
+ {
39
+ name: 'aria-controls',
40
+ required: true,
41
+ description: 'Identifies the element whose contents or presence is controlled',
42
+ },
43
+ {
44
+ name: 'aria-disabled',
45
+ required: false,
46
+ description: 'Indicates the element is disabled',
47
+ possibleValues: ['true', 'false']
48
+ }
49
+ ],
50
+ keyboardInteractions: [
51
+ { key: 'Enter or Space', action: 'Toggle expanded/collapsed state', required: true },
52
+ { key: 'Tab', action: 'Move focus to next focusable element', required: true },
53
+ { key: 'Shift + Tab', action: 'Move focus to previous focusable element', required: true }
54
+ ],
55
+ htmlExample: `<!-- Accordion Example -->
56
+ <div class="accordion">
57
+ <h3>
58
+ <button type="button"
59
+ aria-expanded="false"
60
+ aria-controls="accordion-panel-1"
61
+ id="accordion-header-1">
62
+ Personal Information
63
+ </button>
64
+ </h3>
65
+ <div id="accordion-panel-1"
66
+ role="region"
67
+ aria-labelledby="accordion-header-1"
68
+ hidden>
69
+ <p>Panel content goes here...</p>
70
+ </div>
71
+ </div>`,
72
+ javascriptExample: `// Accordion toggle functionality
73
+ button.addEventListener('click', () => {
74
+ const expanded = button.getAttribute('aria-expanded') === 'true';
75
+ button.setAttribute('aria-expanded', !expanded);
76
+ panel.hidden = expanded;
77
+ });`,
78
+ commonMistakes: [
79
+ 'Using <div> instead of <button> for accordion header',
80
+ 'Missing aria-controls connection',
81
+ 'Not toggling aria-expanded state',
82
+ 'Using display:none instead of hidden attribute',
83
+ 'Missing heading structure for accordion headers'
84
+ ],
85
+ testingGuidelines: [
86
+ 'Verify keyboard navigation with Enter/Space',
87
+ 'Check aria-expanded state changes',
88
+ 'Test with screen reader announcement',
89
+ 'Validate heading hierarchy',
90
+ 'Ensure panel is correctly hidden/shown'
91
+ ],
92
+ apgUrl: 'https://www.w3.org/WAI/ARIA/apg/patterns/accordion/'
93
+ },
94
+ 'alert-dialog': {
95
+ name: 'Alert Dialog (Modal)',
96
+ description: 'Interrupts user workflow to communicate important information and acquire a response',
97
+ category: 'widget',
98
+ wcagCriteria: ['2.1.2', '4.1.2', '2.4.3'],
99
+ roles: {
100
+ primary: 'alertdialog',
101
+ related: ['dialog']
102
+ },
103
+ ariaAttributes: [
104
+ {
105
+ name: 'aria-labelledby',
106
+ required: true,
107
+ description: 'References the dialog title element'
108
+ },
109
+ {
110
+ name: 'aria-describedby',
111
+ required: false,
112
+ description: 'References the element containing dialog description'
113
+ },
114
+ {
115
+ name: 'aria-modal',
116
+ required: true,
117
+ description: 'Indicates the dialog is modal',
118
+ possibleValues: ['true']
119
+ }
120
+ ],
121
+ keyboardInteractions: [
122
+ { key: 'Tab', action: 'Move focus to next focusable element within dialog', required: true },
123
+ { key: 'Shift + Tab', action: 'Move focus to previous focusable element within dialog', required: true },
124
+ { key: 'Escape', action: 'Close dialog', required: true }
125
+ ],
126
+ htmlExample: `<!-- Alert Dialog Example -->
127
+ <div role="alertdialog"
128
+ aria-labelledby="dialog-title"
129
+ aria-describedby="dialog-desc"
130
+ aria-modal="true"
131
+ class="modal">
132
+ <h2 id="dialog-title">Confirm Delete</h2>
133
+ <p id="dialog-desc">
134
+ Are you sure you want to delete this item? This action cannot be undone.
135
+ </p>
136
+ <div class="dialog-actions">
137
+ <button type="button" onclick="confirmDelete()">Delete</button>
138
+ <button type="button" onclick="closeDialog()">Cancel</button>
139
+ </div>
140
+ </div>`,
141
+ javascriptExample: `// Alert dialog focus management
142
+ function openAlertDialog() {
143
+ dialog.removeAttribute('hidden');
144
+ previousFocus = document.activeElement;
145
+ dialog.querySelector('button').focus();
146
+ trapFocus(dialog);
147
+ }
148
+
149
+ function closeDialog() {
150
+ dialog.setAttribute('hidden', '');
151
+ previousFocus.focus();
152
+ }
153
+
154
+ // Trap focus within dialog
155
+ function trapFocus(element) {
156
+ const focusableElements = element.querySelectorAll(
157
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
158
+ );
159
+ const firstFocusable = focusableElements[0];
160
+ const lastFocusable = focusableElements[focusableElements.length - 1];
161
+
162
+ element.addEventListener('keydown', (e) => {
163
+ if (e.key === 'Tab') {
164
+ if (e.shiftKey && document.activeElement === firstFocusable) {
165
+ e.preventDefault();
166
+ lastFocusable.focus();
167
+ } else if (!e.shiftKey && document.activeElement === lastFocusable) {
168
+ e.preventDefault();
169
+ firstFocusable.focus();
170
+ }
171
+ }
172
+ if (e.key === 'Escape') {
173
+ closeDialog();
174
+ }
175
+ });
176
+ }`,
177
+ commonMistakes: [
178
+ 'No focus trap - focus escapes to background',
179
+ 'Missing Escape key handler',
180
+ 'Not returning focus to trigger element after close',
181
+ 'Background content still interactive',
182
+ 'Missing aria-modal="true"',
183
+ 'Alert announced but focus not moved to dialog'
184
+ ],
185
+ testingGuidelines: [
186
+ 'Verify focus moves to dialog on open',
187
+ 'Test Tab wraps within dialog (focus trap)',
188
+ 'Verify Escape closes dialog',
189
+ 'Check focus returns to trigger after close',
190
+ 'Test screen reader announces alert content',
191
+ 'Ensure background is inert (not interactive)'
192
+ ],
193
+ apgUrl: 'https://www.w3.org/WAI/ARIA/apg/patterns/alertdialog/'
194
+ },
195
+ 'button': {
196
+ name: 'Button',
197
+ description: 'Triggers an action or event when activated',
198
+ category: 'widget',
199
+ wcagCriteria: ['2.1.1', '4.1.2'],
200
+ roles: {
201
+ primary: 'button',
202
+ related: []
203
+ },
204
+ ariaAttributes: [
205
+ {
206
+ name: 'aria-pressed',
207
+ required: false,
208
+ description: 'For toggle buttons, indicates pressed state',
209
+ possibleValues: ['true', 'false', 'mixed']
210
+ },
211
+ {
212
+ name: 'aria-expanded',
213
+ required: false,
214
+ description: 'For buttons controlling expandable regions',
215
+ possibleValues: ['true', 'false']
216
+ },
217
+ {
218
+ name: 'aria-label',
219
+ required: false,
220
+ description: 'Accessible name when button has no visible text'
221
+ },
222
+ {
223
+ name: 'aria-labelledby',
224
+ required: false,
225
+ description: 'References visible label elements'
226
+ }
227
+ ],
228
+ keyboardInteractions: [
229
+ { key: 'Enter', action: 'Activate button', required: true },
230
+ { key: 'Space', action: 'Activate button', required: true }
231
+ ],
232
+ htmlExample: `<!-- Standard Button -->
233
+ <button type="button">Click Me</button>
234
+
235
+ <!-- Icon Button -->
236
+ <button type="button" aria-label="Close navigation menu">
237
+ <svg aria-hidden="true">
238
+ <use xlink:href="#icon-close"/>
239
+ </svg>
240
+ </button>
241
+
242
+ <!-- Toggle Button -->
243
+ <button type="button"
244
+ aria-pressed="false"
245
+ onclick="toggleMute()">
246
+ Mute
247
+ </button>
248
+
249
+ <!-- Expand/Collapse Button -->
250
+ <button type="button"
251
+ aria-expanded="false"
252
+ aria-controls="section-1">
253
+ Show Details
254
+ </button>`,
255
+ cssExample: `/* Ensure buttons have visible focus indicator */
256
+ button:focus {
257
+ outline: 2px solid #005fcc;
258
+ outline-offset: 2px;
259
+ }
260
+
261
+ /* Show pressed state visually */
262
+ button[aria-pressed="true"] {
263
+ background-color: #0056b3;
264
+ color: white;
265
+ }`,
266
+ commonMistakes: [
267
+ 'Using <div> or <a> instead of <button>',
268
+ 'Icon buttons without aria-label',
269
+ 'Missing keyboard support (click-only)',
270
+ 'No visible focus indicator',
271
+ 'Toggle buttons without aria-pressed',
272
+ 'Disabled buttons with opacity but still focusable'
273
+ ],
274
+ testingGuidelines: [
275
+ 'Verify activation with Enter and Space',
276
+ 'Check visible focus indicator',
277
+ 'Test screen reader announces role and name',
278
+ 'For toggle buttons, verify aria-pressed changes',
279
+ 'Ensure disabled state is properly conveyed',
280
+ 'Test touch target size (min 44x44 px)'
281
+ ],
282
+ apgUrl: 'https://www.w3.org/WAI/ARIA/apg/patterns/button/'
283
+ },
284
+ 'breadcrumb': {
285
+ name: 'Breadcrumb',
286
+ description: 'Provides navigation showing the user\'s location in a hierarchical structure',
287
+ category: 'landmark',
288
+ wcagCriteria: ['2.4.8', '1.3.1'],
289
+ roles: {
290
+ primary: 'navigation',
291
+ related: []
292
+ },
293
+ ariaAttributes: [
294
+ {
295
+ name: 'aria-label',
296
+ required: true,
297
+ description: 'Labels the navigation as breadcrumb',
298
+ defaultValue: 'Breadcrumb'
299
+ },
300
+ {
301
+ name: 'aria-current',
302
+ required: true,
303
+ description: 'Indicates current page in breadcrumb',
304
+ possibleValues: ['page']
305
+ }
306
+ ],
307
+ keyboardInteractions: [
308
+ { key: 'Tab', action: 'Navigate between breadcrumb links', required: true }
309
+ ],
310
+ htmlExample: `<!-- Breadcrumb Navigation -->
311
+ <nav aria-label="Breadcrumb">
312
+ <ol>
313
+ <li><a href="/">Home</a></li>
314
+ <li><a href="/products">Products</a></li>
315
+ <li><a href="/products/electronics">Electronics</a></li>
316
+ <li>
317
+ <a href="/products/electronics/laptops" aria-current="page">
318
+ Laptops
319
+ </a>
320
+ </li>
321
+ </ol>
322
+ </nav>`,
323
+ cssExample: `/* Breadcrumb styling */
324
+ nav[aria-label="Breadcrumb"] ol {
325
+ list-style: none;
326
+ display: flex;
327
+ gap: 0.5rem;
328
+ }
329
+
330
+ nav[aria-label="Breadcrumb"] li:not(:last-child)::after {
331
+ content: "/";
332
+ margin-left: 0.5rem;
333
+ color: #666;
334
+ }
335
+
336
+ nav[aria-label="Breadcrumb"] [aria-current="page"] {
337
+ color: #333;
338
+ font-weight: 600;
339
+ text-decoration: none;
340
+ }`,
341
+ commonMistakes: [
342
+ 'Using <div> instead of <nav>',
343
+ 'Missing aria-label="Breadcrumb"',
344
+ 'No aria-current on current page',
345
+ 'Not using <ol> for proper hierarchy',
346
+ 'Making current page not focusable',
347
+ 'Visual separators not hidden from screen readers'
348
+ ],
349
+ testingGuidelines: [
350
+ 'Verify screen reader announces "Breadcrumb navigation"',
351
+ 'Check aria-current="page" on current item',
352
+ 'Test list structure is conveyed',
353
+ 'Ensure separators are decorative only',
354
+ 'Verify keyboard navigation works'
355
+ ],
356
+ apgUrl: 'https://www.w3.org/WAI/ARIA/apg/patterns/breadcrumb/'
357
+ },
358
+ 'combobox': {
359
+ name: 'Combobox (Autocomplete)',
360
+ description: 'Input with popup list of suggested values',
361
+ category: 'composite',
362
+ wcagCriteria: ['2.1.1', '4.1.2', '3.2.2'],
363
+ roles: {
364
+ primary: 'combobox',
365
+ related: ['listbox', 'option']
366
+ },
367
+ ariaAttributes: [
368
+ {
369
+ name: 'aria-expanded',
370
+ required: true,
371
+ description: 'Indicates if popup is displayed',
372
+ possibleValues: ['true', 'false']
373
+ },
374
+ {
375
+ name: 'aria-controls',
376
+ required: true,
377
+ description: 'Identifies the popup element'
378
+ },
379
+ {
380
+ name: 'aria-autocomplete',
381
+ required: true,
382
+ description: 'Type of autocomplete',
383
+ possibleValues: ['list', 'both', 'inline', 'none']
384
+ },
385
+ {
386
+ name: 'aria-activedescendant',
387
+ required: false,
388
+ description: 'ID of the currently highlighted option'
389
+ }
390
+ ],
391
+ keyboardInteractions: [
392
+ { key: 'Down Arrow', action: 'Open popup or move to next option', required: true },
393
+ { key: 'Up Arrow', action: 'Move to previous option', required: true },
394
+ { key: 'Enter', action: 'Select highlighted option', required: true },
395
+ { key: 'Escape', action: 'Close popup without selection', required: true },
396
+ { key: 'Alt + Down Arrow', action: 'Open popup', required: false }
397
+ ],
398
+ htmlExample: `<!-- Combobox with Autocomplete -->
399
+ <label for="country-input">Country</label>
400
+ <input type="text"
401
+ id="country-input"
402
+ role="combobox"
403
+ aria-expanded="false"
404
+ aria-autocomplete="list"
405
+ aria-controls="country-listbox"
406
+ aria-activedescendant="">
407
+
408
+ <ul id="country-listbox"
409
+ role="listbox"
410
+ hidden>
411
+ <li role="option" id="option-1">United States</li>
412
+ <li role="option" id="option-2">United Kingdom</li>
413
+ <li role="option" id="option-3">Canada</li>
414
+ </ul>`,
415
+ javascriptExample: `// Combobox functionality
416
+ const input = document.getElementById('country-input');
417
+ const listbox = document.getElementById('country-listbox');
418
+ let currentOption = -1;
419
+
420
+ input.addEventListener('input', () => {
421
+ const value = input.value.toLowerCase();
422
+ const options = filterOptions(value);
423
+
424
+ if (options.length > 0) {
425
+ input.setAttribute('aria-expanded', 'true');
426
+ listbox.hidden = false;
427
+ renderOptions(options);
428
+ } else {
429
+ input.setAttribute('aria-expanded', 'false');
430
+ listbox.hidden = true;
431
+ }
432
+ });
433
+
434
+ input.addEventListener('keydown', (e) => {
435
+ const options = listbox.querySelectorAll('[role="option"]');
436
+
437
+ if (e.key === 'ArrowDown') {
438
+ e.preventDefault();
439
+ currentOption = Math.min(currentOption + 1, options.length - 1);
440
+ highlightOption(options[currentOption]);
441
+ } else if (e.key === 'ArrowUp') {
442
+ e.preventDefault();
443
+ currentOption = Math.max(currentOption - 1, 0);
444
+ highlightOption(options[currentOption]);
445
+ } else if (e.key === 'Enter' && currentOption >= 0) {
446
+ e.preventDefault();
447
+ selectOption(options[currentOption]);
448
+ } else if (e.key === 'Escape') {
449
+ closeListbox();
450
+ }
451
+ });
452
+
453
+ function highlightOption(option) {
454
+ input.setAttribute('aria-activedescendant', option.id);
455
+ option.scrollIntoView({ block: 'nearest' });
456
+ }`,
457
+ commonMistakes: [
458
+ 'Missing aria-expanded state management',
459
+ 'Not using aria-activedescendant for highlighted option',
460
+ 'Popup not dismissed on Escape',
461
+ 'No keyboard navigation for options',
462
+ 'Selected value not announced to screen readers',
463
+ 'Focus trapped in popup'
464
+ ],
465
+ testingGuidelines: [
466
+ 'Verify arrow keys navigate options',
467
+ 'Check Enter selects highlighted option',
468
+ 'Test Escape closes popup',
469
+ 'Verify screen reader announces options',
470
+ 'Test aria-activedescendant updates',
471
+ 'Check popup opens/closes correctly'
472
+ ],
473
+ apgUrl: 'https://www.w3.org/WAI/ARIA/apg/patterns/combobox/'
474
+ },
475
+ 'tabs': {
476
+ name: 'Tabs',
477
+ description: 'Set of layered sections of content (tab panels) displayed one at a time',
478
+ category: 'composite',
479
+ wcagCriteria: ['2.1.1', '4.1.2', '1.3.1'],
480
+ roles: {
481
+ primary: 'tablist',
482
+ related: ['tab', 'tabpanel']
483
+ },
484
+ ariaAttributes: [
485
+ {
486
+ name: 'aria-selected',
487
+ required: true,
488
+ description: 'Indicates selected tab',
489
+ possibleValues: ['true', 'false']
490
+ },
491
+ {
492
+ name: 'aria-controls',
493
+ required: true,
494
+ description: 'Identifies the associated tabpanel'
495
+ },
496
+ {
497
+ name: 'aria-labelledby',
498
+ required: true,
499
+ description: 'On tabpanel, references the controlling tab'
500
+ }
501
+ ],
502
+ keyboardInteractions: [
503
+ { key: 'Tab', action: 'Move focus into/out of tab list', required: true },
504
+ { key: 'Left Arrow', action: 'Move to previous tab', required: true },
505
+ { key: 'Right Arrow', action: 'Move to next tab', required: true },
506
+ { key: 'Home', action: 'Move to first tab', required: false },
507
+ { key: 'End', action: 'Move to last tab', required: false }
508
+ ],
509
+ htmlExample: `<!-- Tabs Example -->
510
+ <div class="tabs">
511
+ <div role="tablist" aria-label="Account Settings">
512
+ <button role="tab"
513
+ aria-selected="true"
514
+ aria-controls="profile-panel"
515
+ id="profile-tab">
516
+ Profile
517
+ </button>
518
+ <button role="tab"
519
+ aria-selected="false"
520
+ aria-controls="security-panel"
521
+ id="security-tab"
522
+ tabindex="-1">
523
+ Security
524
+ </button>
525
+ <button role="tab"
526
+ aria-selected="false"
527
+ aria-controls="billing-panel"
528
+ id="billing-tab"
529
+ tabindex="-1">
530
+ Billing
531
+ </button>
532
+ </div>
533
+
534
+ <div role="tabpanel"
535
+ id="profile-panel"
536
+ aria-labelledby="profile-tab">
537
+ <h2>Profile Settings</h2>
538
+ <p>Manage your profile information...</p>
539
+ </div>
540
+
541
+ <div role="tabpanel"
542
+ id="security-panel"
543
+ aria-labelledby="security-tab"
544
+ hidden>
545
+ <h2>Security Settings</h2>
546
+ <p>Manage your security preferences...</p>
547
+ </div>
548
+
549
+ <div role="tabpanel"
550
+ id="billing-panel"
551
+ aria-labelledby="billing-tab"
552
+ hidden>
553
+ <h2>Billing Settings</h2>
554
+ <p>Manage your billing information...</p>
555
+ </div>
556
+ </div>`,
557
+ javascriptExample: `// Tabs keyboard navigation
558
+ const tablist = document.querySelector('[role="tablist"]');
559
+ const tabs = tablist.querySelectorAll('[role="tab"]');
560
+
561
+ tabs.forEach((tab, index) => {
562
+ tab.addEventListener('click', () => {
563
+ activateTab(tab);
564
+ });
565
+
566
+ tab.addEventListener('keydown', (e) => {
567
+ let newIndex;
568
+
569
+ if (e.key === 'ArrowRight') {
570
+ newIndex = (index + 1) % tabs.length;
571
+ } else if (e.key === 'ArrowLeft') {
572
+ newIndex = (index - 1 + tabs.length) % tabs.length;
573
+ } else if (e.key === 'Home') {
574
+ newIndex = 0;
575
+ } else if (e.key === 'End') {
576
+ newIndex = tabs.length - 1;
577
+ }
578
+
579
+ if (newIndex !== undefined) {
580
+ e.preventDefault();
581
+ tabs[newIndex].focus();
582
+ activateTab(tabs[newIndex]);
583
+ }
584
+ });
585
+ });
586
+
587
+ function activateTab(newTab) {
588
+ tabs.forEach(tab => {
589
+ tab.setAttribute('aria-selected', 'false');
590
+ tab.setAttribute('tabindex', '-1');
591
+ const panel = document.getElementById(tab.getAttribute('aria-controls'));
592
+ panel.hidden = true;
593
+ });
594
+
595
+ newTab.setAttribute('aria-selected', 'true');
596
+ newTab.removeAttribute('tabindex');
597
+ const panel = document.getElementById(newTab.getAttribute('aria-controls'));
598
+ panel.hidden = false;
599
+ }`,
600
+ commonMistakes: [
601
+ 'Using links (<a>) instead of buttons for tabs',
602
+ 'Missing aria-selected on tabs',
603
+ 'No arrow key navigation',
604
+ 'All tabs focusable (should use tabindex=-1 for inactive tabs)',
605
+ 'Missing aria-controls/aria-labelledby relationship',
606
+ 'Not hiding inactive panels properly'
607
+ ],
608
+ testingGuidelines: [
609
+ 'Verify arrow keys switch between tabs',
610
+ 'Check only active tab is in tab order',
611
+ 'Test Home/End keys work',
612
+ 'Verify aria-selected updates',
613
+ 'Check screen reader announces tab selection',
614
+ 'Ensure inactive panels are hidden from AT'
615
+ ],
616
+ apgUrl: 'https://www.w3.org/WAI/ARIA/apg/patterns/tabpanel/'
617
+ },
618
+ 'menu': {
619
+ name: 'Menu / Menubar',
620
+ description: 'List of actions or functions presented as clickable items',
621
+ category: 'composite',
622
+ wcagCriteria: ['2.1.1', '4.1.2'],
623
+ roles: {
624
+ primary: 'menu',
625
+ related: ['menubar', 'menuitem', 'menuitemcheckbox', 'menuitemradio']
626
+ },
627
+ ariaAttributes: [
628
+ {
629
+ name: 'aria-haspopup',
630
+ required: false,
631
+ description: 'Indicates menuitem has submenu',
632
+ possibleValues: ['menu', 'true']
633
+ },
634
+ {
635
+ name: 'aria-expanded',
636
+ required: false,
637
+ description: 'For items with submenus, indicates if open',
638
+ possibleValues: ['true', 'false']
639
+ },
640
+ {
641
+ name: 'aria-checked',
642
+ required: false,
643
+ description: 'For menuitemcheckbox/menuitemradio',
644
+ possibleValues: ['true', 'false', 'mixed']
645
+ }
646
+ ],
647
+ keyboardInteractions: [
648
+ { key: 'Enter or Space', action: 'Activate menuitem', required: true },
649
+ { key: 'Down Arrow', action: 'Move to next item', required: true },
650
+ { key: 'Up Arrow', action: 'Move to previous item', required: true },
651
+ { key: 'Home', action: 'Move to first item', required: false },
652
+ { key: 'End', action: 'Move to last item', required: false },
653
+ { key: 'Escape', action: 'Close menu', required: true },
654
+ { key: 'Right Arrow', action: 'Open submenu (horizontal menubar)', required: false },
655
+ { key: 'Left Arrow', action: 'Close submenu (horizontal menubar)', required: false }
656
+ ],
657
+ htmlExample: `<!-- Dropdown Menu Example -->
658
+ <nav>
659
+ <button type="button"
660
+ aria-haspopup="menu"
661
+ aria-expanded="false"
662
+ aria-controls="file-menu">
663
+ File
664
+ </button>
665
+
666
+ <ul id="file-menu"
667
+ role="menu"
668
+ hidden>
669
+ <li role="none">
670
+ <button role="menuitem" onclick="newFile()">
671
+ New
672
+ </button>
673
+ </li>
674
+ <li role="none">
675
+ <button role="menuitem" onclick="openFile()">
676
+ Open
677
+ </button>
678
+ </li>
679
+ <li role="separator"></li>
680
+ <li role="none">
681
+ <button role="menuitem" onclick="saveFile()">
682
+ Save
683
+ </button>
684
+ </li>
685
+ </ul>
686
+ </nav>`,
687
+ commonMistakes: [
688
+ 'Using <a> links for menu items (should be buttons)',
689
+ 'Missing keyboard navigation',
690
+ 'Menu doesn\'t close on Escape',
691
+ 'Focus not managed when opening/closing',
692
+ 'Submenus not properly nested',
693
+ 'Using <select> for navigation menus'
694
+ ],
695
+ testingGuidelines: [
696
+ 'Verify arrow keys navigate items',
697
+ 'Check Enter/Space activates items',
698
+ 'Test Escape closes menu',
699
+ 'Verify focus management',
700
+ 'Test submenu keyboard interaction',
701
+ 'Check screen reader announcements'
702
+ ],
703
+ apgUrl: 'https://www.w3.org/WAI/ARIA/apg/patterns/menubar/'
704
+ },
705
+ 'dialog': {
706
+ name: 'Dialog (Modal)',
707
+ description: 'Window overlaid on primary window, blocking interaction with underlying content',
708
+ category: 'widget',
709
+ wcagCriteria: ['2.1.2', '4.1.2', '2.4.3'],
710
+ roles: {
711
+ primary: 'dialog',
712
+ related: ['alertdialog']
713
+ },
714
+ ariaAttributes: [
715
+ {
716
+ name: 'aria-labelledby',
717
+ required: true,
718
+ description: 'References dialog title'
719
+ },
720
+ {
721
+ name: 'aria-describedby',
722
+ required: false,
723
+ description: 'References dialog description'
724
+ },
725
+ {
726
+ name: 'aria-modal',
727
+ required: true,
728
+ description: 'Indicates dialog is modal',
729
+ possibleValues: ['true']
730
+ }
731
+ ],
732
+ keyboardInteractions: [
733
+ { key: 'Tab', action: 'Move focus within dialog (trapped)', required: true },
734
+ { key: 'Escape', action: 'Close dialog', required: true }
735
+ ],
736
+ htmlExample: `<!-- Modal Dialog -->
737
+ <div role="dialog"
738
+ aria-labelledby="dialog-title"
739
+ aria-modal="true">
740
+ <h2 id="dialog-title">Settings</h2>
741
+ <form>
742
+ <label for="username">Username:</label>
743
+ <input type="text" id="username">
744
+
745
+ <button type="submit">Save</button>
746
+ <button type="button" onclick="closeDialog()">Cancel</button>
747
+ </form>
748
+ </div>`,
749
+ commonMistakes: [
750
+ 'No focus trap',
751
+ 'Missing Escape handler',
752
+ 'Focus not returned after close',
753
+ 'Background still interactive',
754
+ 'Missing aria-modal="true"'
755
+ ],
756
+ testingGuidelines: [
757
+ 'Verify focus moves to dialog',
758
+ 'Test Tab wraps within dialog',
759
+ 'Check Escape closes dialog',
760
+ 'Test focus returns correctly',
761
+ 'Verify background is inert'
762
+ ],
763
+ apgUrl: 'https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/'
764
+ },
765
+ 'slider': {
766
+ name: 'Slider',
767
+ description: 'Input where user selects value from within a range',
768
+ category: 'widget',
769
+ wcagCriteria: ['2.1.1', '4.1.2'],
770
+ roles: {
771
+ primary: 'slider',
772
+ related: []
773
+ },
774
+ ariaAttributes: [
775
+ {
776
+ name: 'aria-valuemin',
777
+ required: true,
778
+ description: 'Minimum value'
779
+ },
780
+ {
781
+ name: 'aria-valuemax',
782
+ required: true,
783
+ description: 'Maximum value'
784
+ },
785
+ {
786
+ name: 'aria-valuenow',
787
+ required: true,
788
+ description: 'Current value'
789
+ },
790
+ {
791
+ name: 'aria-valuetext',
792
+ required: false,
793
+ description: 'Human-readable value (e.g., "Low", "Medium", "High")'
794
+ },
795
+ {
796
+ name: 'aria-label',
797
+ required: true,
798
+ description: 'Accessible name for slider'
799
+ }
800
+ ],
801
+ keyboardInteractions: [
802
+ { key: 'Right Arrow', action: 'Increase value', required: true },
803
+ { key: 'Up Arrow', action: 'Increase value', required: true },
804
+ { key: 'Left Arrow', action: 'Decrease value', required: true },
805
+ { key: 'Down Arrow', action: 'Decrease value', required: true },
806
+ { key: 'Home', action: 'Set to minimum', required: false },
807
+ { key: 'End', action: 'Set to maximum', required: false },
808
+ { key: 'Page Up', action: 'Increase by larger step', required: false },
809
+ { key: 'Page Down', action: 'Decrease by larger step', required: false }
810
+ ],
811
+ htmlExample: `<!-- Slider Example -->
812
+ <label id="volume-label">Volume</label>
813
+ <div role="slider"
814
+ aria-labelledby="volume-label"
815
+ aria-valuemin="0"
816
+ aria-valuemax="100"
817
+ aria-valuenow="50"
818
+ aria-valuetext="50 percent"
819
+ tabindex="0">
820
+ <div class="slider-track">
821
+ <div class="slider-thumb" style="left: 50%;"></div>
822
+ </div>
823
+ </div>`,
824
+ javascriptExample: `// Slider keyboard interaction
825
+ const slider = document.querySelector('[role="slider"]');
826
+ let value = 50;
827
+
828
+ slider.addEventListener('keydown', (e) => {
829
+ let newValue = value;
830
+
831
+ if (e.key === 'ArrowRight' || e.key === 'ArrowUp') {
832
+ newValue = Math.min(value + 1, 100);
833
+ } else if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') {
834
+ newValue = Math.max(value - 1, 0);
835
+ } else if (e.key === 'Home') {
836
+ newValue = 0;
837
+ } else if (e.key === 'End') {
838
+ newValue = 100;
839
+ } else if (e.key === 'PageUp') {
840
+ newValue = Math.min(value + 10, 100);
841
+ } else if (e.key === 'PageDown') {
842
+ newValue = Math.max(value - 10, 0);
843
+ }
844
+
845
+ if (newValue !== value) {
846
+ e.preventDefault();
847
+ updateSlider(newValue);
848
+ }
849
+ });
850
+
851
+ function updateSlider(newValue) {
852
+ value = newValue;
853
+ slider.setAttribute('aria-valuenow', value);
854
+ slider.setAttribute('aria-valuetext', value + ' percent');
855
+ // Update visual position
856
+ const thumb = slider.querySelector('.slider-thumb');
857
+ thumb.style.left = value + '%';
858
+ }`,
859
+ commonMistakes: [
860
+ 'Missing aria-valuemin/max/now',
861
+ 'No keyboard support',
862
+ 'aria-valuenow not updated',
863
+ 'Slider not focusable (missing tabindex)',
864
+ 'No visual feedback for value changes',
865
+ 'Using <input type="range"> without proper labeling'
866
+ ],
867
+ testingGuidelines: [
868
+ 'Verify arrow keys change value',
869
+ 'Check Home/End keys work',
870
+ 'Test screen reader announces values',
871
+ 'Verify aria-valuenow updates',
872
+ 'Check visual thumb position matches value',
873
+ 'Test with assistive technologies'
874
+ ],
875
+ apgUrl: 'https://www.w3.org/WAI/ARIA/apg/patterns/slider/'
876
+ }
877
+ };
878
+ /**
879
+ * Helper Functions
880
+ */
881
+ /**
882
+ * Get APG pattern by name
883
+ */
884
+ function getAPGPattern(patternName) {
885
+ return exports.APG_PATTERNS[patternName.toLowerCase()];
886
+ }
887
+ /**
888
+ * Find patterns by WCAG criterion
889
+ */
890
+ function getPatternsByWCAG(wcagCriterion) {
891
+ return Object.values(exports.APG_PATTERNS).filter(pattern => pattern.wcagCriteria.includes(wcagCriterion));
892
+ }
893
+ /**
894
+ * Find patterns by role
895
+ */
896
+ function getPatternsByRole(role) {
897
+ return Object.values(exports.APG_PATTERNS).filter(pattern => pattern.roles.primary === role ||
898
+ (pattern.roles.related && pattern.roles.related.includes(role)));
899
+ }
900
+ /**
901
+ * Get all patterns by category
902
+ */
903
+ function getPatternsByCategory(category) {
904
+ return Object.values(exports.APG_PATTERNS).filter(pattern => pattern.category === category);
905
+ }
906
+ /**
907
+ * Suggest APG pattern based on element analysis
908
+ *
909
+ * @param elementContext - Context about the element (role, attributes, etc.)
910
+ * @returns Recommended APG pattern with confidence score
911
+ */
912
+ function suggestAPGPattern(elementContext) {
913
+ // Direct role match
914
+ if (elementContext.role) {
915
+ const patterns = getPatternsByRole(elementContext.role);
916
+ if (patterns.length > 0) {
917
+ return {
918
+ pattern: patterns[0],
919
+ confidence: 0.95,
920
+ reason: `Element has role="${elementContext.role}" matching APG pattern`
921
+ };
922
+ }
923
+ }
924
+ // Heuristic matching based on attributes and context
925
+ const hasAriaExpanded = elementContext.attributes?.['aria-expanded'];
926
+ const hasAriaControls = elementContext.attributes?.['aria-controls'];
927
+ const hasAriaPressed = elementContext.attributes?.['aria-pressed'];
928
+ const context = (elementContext.context || '').toLowerCase();
929
+ // Accordion pattern detection
930
+ if (hasAriaExpanded && hasAriaControls && context.includes('header')) {
931
+ return {
932
+ pattern: exports.APG_PATTERNS.accordion,
933
+ confidence: 0.85,
934
+ reason: 'Element has aria-expanded and aria-controls within heading context'
935
+ };
936
+ }
937
+ // Dialog pattern detection
938
+ if (context.includes('modal') || context.includes('dialog')) {
939
+ return {
940
+ pattern: exports.APG_PATTERNS.dialog,
941
+ confidence: 0.80,
942
+ reason: 'Element context suggests dialog/modal pattern'
943
+ };
944
+ }
945
+ // Toggle button pattern
946
+ if (hasAriaPressed && elementContext.tagName === 'button') {
947
+ return {
948
+ pattern: exports.APG_PATTERNS.button,
949
+ confidence: 0.90,
950
+ reason: 'Button element with aria-pressed indicates toggle button pattern'
951
+ };
952
+ }
953
+ // Tabs pattern detection
954
+ if (context.includes('tab') && hasAriaControls) {
955
+ return {
956
+ pattern: exports.APG_PATTERNS.tabs,
957
+ confidence: 0.85,
958
+ reason: 'Element has tab-related context with aria-controls'
959
+ };
960
+ }
961
+ return null;
962
+ }
963
+ /**
964
+ * Generate code example for a specific pattern
965
+ */
966
+ function generatePatternCodeExample(patternName, options = {}) {
967
+ const pattern = getAPGPattern(patternName);
968
+ if (!pattern) {
969
+ return '';
970
+ }
971
+ let example = `<!-- ${pattern.name} Pattern -->\n`;
972
+ example += `<!-- Reference: ${pattern.apgUrl} -->\n\n`;
973
+ example += pattern.htmlExample;
974
+ if (options.includeJavaScript && pattern.javascriptExample) {
975
+ example += '\n\n<script>\n' + pattern.javascriptExample + '\n</script>';
976
+ }
977
+ if (options.includeCSS && pattern.cssExample) {
978
+ example += '\n\n<style>\n' + pattern.cssExample + '\n</style>';
979
+ }
980
+ return example;
981
+ }
982
+ /**
983
+ * Get keyboard shortcuts reference for a pattern
984
+ */
985
+ function getKeyboardReference(patternName) {
986
+ const pattern = getAPGPattern(patternName);
987
+ if (!pattern) {
988
+ return '';
989
+ }
990
+ let reference = `Keyboard Shortcuts for ${pattern.name}:\n\n`;
991
+ pattern.keyboardInteractions.forEach(interaction => {
992
+ const badge = interaction.required ? '[REQUIRED]' : '[OPTIONAL]';
993
+ reference += `${badge} ${interaction.key}: ${interaction.action}\n`;
994
+ });
995
+ return reference;
996
+ }
997
+ /**
998
+ * Validate element against APG pattern requirements
999
+ */
1000
+ function validateAgainstPattern(patternName, element) {
1001
+ const pattern = getAPGPattern(patternName);
1002
+ if (!pattern) {
1003
+ return { valid: false, missingAttributes: [], incorrectValues: [] };
1004
+ }
1005
+ const missingAttributes = [];
1006
+ const incorrectValues = [];
1007
+ // Check required ARIA attributes
1008
+ pattern.ariaAttributes
1009
+ .filter(attr => attr.required)
1010
+ .forEach(attr => {
1011
+ if (!element.attributes[attr.name]) {
1012
+ missingAttributes.push(attr.name);
1013
+ }
1014
+ else if (attr.possibleValues) {
1015
+ const actualValue = element.attributes[attr.name];
1016
+ if (!attr.possibleValues.includes(actualValue)) {
1017
+ incorrectValues.push({
1018
+ attribute: attr.name,
1019
+ expected: attr.possibleValues.join(' | '),
1020
+ actual: actualValue
1021
+ });
1022
+ }
1023
+ }
1024
+ });
1025
+ const valid = missingAttributes.length === 0 && incorrectValues.length === 0;
1026
+ return { valid, missingAttributes, incorrectValues };
1027
+ }
1028
+ //# sourceMappingURL=apg-patterns.js.map