@react-native-ama/core 1.2.1 → 2.0.0-beta.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 (179) hide show
  1. package/dist/ReactNativeAma.types.d.ts +66 -0
  2. package/dist/ReactNativeAma.types.js +2 -0
  3. package/dist/ReactNativeAmaModule.d.ts +11 -0
  4. package/dist/ReactNativeAmaModule.js +5 -0
  5. package/dist/ReactNativeAmaModule.web.d.ts +9 -0
  6. package/dist/ReactNativeAmaModule.web.js +27 -0
  7. package/dist/ReactNativeAmaView.d.ts +3 -0
  8. package/dist/ReactNativeAmaView.js +32 -0
  9. package/dist/ReactNativeAmaView.web.d.ts +3 -0
  10. package/dist/ReactNativeAmaView.web.js +34 -0
  11. package/dist/{src/components → components}/AMAProvider.d.ts +4 -2
  12. package/dist/{src/components → components}/AMAProvider.js +19 -45
  13. package/dist/{src/components → components}/AutofocusContainer.d.ts +2 -1
  14. package/dist/{src/components → components}/HideChildrenFromAccessibilityTree.d.ts +1 -1
  15. package/dist/{src/components → components}/HideChildrenFromAccessibilityTree.js +7 -5
  16. package/dist/{src/hooks → hooks}/useFocus.js +10 -3
  17. package/dist/{src/index.d.ts → index.d.ts} +3 -2
  18. package/dist/index.js +38 -0
  19. package/dist/internals/checks/checkAriaLabel.d.ts +3 -0
  20. package/dist/internals/checks/checkAriaLabel.js +15 -0
  21. package/dist/internals/checks/checkAriaRole.d.ts +3 -0
  22. package/dist/internals/checks/checkAriaRole.js +48 -0
  23. package/dist/internals/checks/checkContrast.d.ts +3 -0
  24. package/dist/internals/checks/checkContrast.js +84 -0
  25. package/dist/internals/checks/checkImageAlt.d.ts +3 -0
  26. package/dist/internals/checks/checkImageAlt.js +20 -0
  27. package/dist/internals/checks/checkIsUppercase.d.ts +8 -0
  28. package/dist/internals/checks/checkIsUppercase.js +30 -0
  29. package/dist/internals/checks/checkLongNumber.d.ts +3 -0
  30. package/dist/internals/checks/checkLongNumber.js +31 -0
  31. package/dist/internals/checks/checkMinimumSize.d.ts +3 -0
  32. package/dist/internals/checks/checkMinimumSize.js +22 -0
  33. package/dist/internals/checks/checkTextInput.d.ts +3 -0
  34. package/dist/internals/checks/checkTextInput.js +51 -0
  35. package/dist/internals/checks/performChecks.d.ts +3 -0
  36. package/dist/internals/checks/performChecks.js +23 -0
  37. package/dist/internals/components/AMAErrorOverlay.d.ts +5 -0
  38. package/dist/internals/components/AMAErrorOverlay.js +334 -0
  39. package/dist/internals/components/AMARuleError.d.ts +7 -0
  40. package/dist/internals/components/AMARuleError.js +161 -0
  41. package/dist/internals/config.d.ts +23 -0
  42. package/dist/internals/config.js +33 -0
  43. package/dist/internals/types.d.ts +9 -0
  44. package/dist/internals/types.js +2 -0
  45. package/dist/internals/useAMADev.d.ts +8 -0
  46. package/dist/internals/useAMADev.dev.d.ts +2 -0
  47. package/dist/internals/useAMADev.dev.js +110 -0
  48. package/dist/internals/useAMADev.js +6 -0
  49. package/dist/internals/useAMADevNodeChecks.dev.d.ts +16 -0
  50. package/dist/internals/useAMADevNodeChecks.dev.js +115 -0
  51. package/dist/internals/useAMADevUiInteraction.dev.d.ts +7 -0
  52. package/dist/internals/useAMADevUiInteraction.dev.js +100 -0
  53. package/dist/internals/utils/amaClearHighlight.d.ts +2 -0
  54. package/dist/internals/utils/amaClearHighlight.js +12 -0
  55. package/dist/internals/utils/amaHighlightComponent.d.ts +3 -0
  56. package/dist/internals/utils/amaHighlightComponent.js +51 -0
  57. package/dist/internals/utils/constants.d.ts +3 -0
  58. package/dist/internals/utils/constants.js +32 -0
  59. package/dist/internals/utils/getErrorColor.d.ts +2 -0
  60. package/dist/internals/utils/getErrorColor.js +10 -0
  61. package/dist/internals/utils/getRuleAction.d.ts +2 -0
  62. package/dist/internals/utils/getRuleAction.js +40 -0
  63. package/dist/internals/utils/getRuleErrorInfo.d.ts +7 -0
  64. package/dist/internals/utils/getRuleErrorInfo.js +19 -0
  65. package/dist/internals/utils/ignoreContrastCheck.d.ts +1 -0
  66. package/dist/internals/utils/ignoreContrastCheck.js +15 -0
  67. package/dist/internals/utils/interpolateAnimationStates.d.ts +2 -0
  68. package/dist/internals/utils/interpolateAnimationStates.js +43 -0
  69. package/dist/internals/utils/isA11yLabelAllowed.d.ts +1 -0
  70. package/dist/internals/utils/isA11yLabelAllowed.js +14 -0
  71. package/dist/internals/utils/isRuleDisabled.d.ts +2 -0
  72. package/dist/internals/utils/isRuleDisabled.js +10 -0
  73. package/dist/internals/utils/logError.d.ts +2 -0
  74. package/dist/internals/utils/logError.js +36 -0
  75. package/dist/internals/utils/logFoundIssues.d.ts +2 -0
  76. package/dist/internals/utils/logFoundIssues.js +23 -0
  77. package/dist/internals/utils/logger.d.ts +6 -0
  78. package/dist/internals/utils/logger.js +15 -0
  79. package/dist/internals/utils/minimumTouchableSize.d.ts +1 -0
  80. package/dist/internals/utils/minimumTouchableSize.js +9 -0
  81. package/dist/internals/utils/platformHelpers.d.ts +2 -0
  82. package/dist/internals/utils/platformHelpers.js +6 -0
  83. package/dist/internals/utils/rules.d.ts +22 -0
  84. package/dist/internals/utils/rules.js +253 -0
  85. package/package.json +61 -18
  86. package/src/ReactNativeAma.types.ts +70 -0
  87. package/src/ReactNativeAmaModule.ts +18 -0
  88. package/src/ReactNativeAmaModule.web.ts +14 -0
  89. package/src/ReactNativeAmaView.tsx +9 -0
  90. package/src/ReactNativeAmaView.web.tsx +15 -0
  91. package/src/__mocks__/ReactNativeAmaModule.ts +9 -0
  92. package/src/components/AMAProvider.integration.test.tsx +3 -96
  93. package/src/components/AMAProvider.test.tsx +9 -4
  94. package/src/components/AMAProvider.tsx +40 -76
  95. package/src/components/AutofocusContainer.test.tsx +4 -5
  96. package/src/components/AutofocusContainer.tsx +20 -5
  97. package/src/components/HideChildrenFromAccessibilityTree.test.tsx +23 -39
  98. package/src/components/HideChildrenFromAccessibilityTree.tsx +12 -11
  99. package/src/hooks/useFocus.test.ts +20 -20
  100. package/src/hooks/useFocus.ts +14 -6
  101. package/src/hooks/useTimedAction.android.test.ts +0 -1
  102. package/src/hooks/useTimedAction.ios.test.ts +0 -1
  103. package/src/hooks/useTimedAction.ts +0 -1
  104. package/src/index.ts +4 -2
  105. package/src/internals/checks/checkAriaLabel.test.ts +165 -0
  106. package/src/internals/checks/checkAriaLabel.ts +16 -0
  107. package/src/internals/checks/checkAriaRole.test.ts +429 -0
  108. package/src/internals/checks/checkAriaRole.ts +60 -0
  109. package/src/internals/checks/checkContrast.test.ts +278 -0
  110. package/src/internals/checks/checkContrast.ts +109 -0
  111. package/src/internals/checks/checkImageAlt.test.ts +125 -0
  112. package/src/internals/checks/checkImageAlt.ts +23 -0
  113. package/src/internals/checks/checkIsUppercase.test.ts +399 -0
  114. package/src/internals/checks/checkIsUppercase.ts +46 -0
  115. package/src/internals/checks/checkLongNumber.test.ts +111 -0
  116. package/src/internals/checks/checkLongNumber.ts +34 -0
  117. package/src/internals/checks/checkMinimumSize.test.ts +67 -0
  118. package/src/internals/checks/checkMinimumSize.ts +25 -0
  119. package/src/internals/checks/checkTextInput.ts +65 -0
  120. package/src/internals/checks/performChecks.test.ts +171 -0
  121. package/src/internals/checks/performChecks.ts +24 -0
  122. package/src/internals/components/AMAErrorOverlay.test.tsx +139 -0
  123. package/src/internals/components/AMAErrorOverlay.tsx +451 -0
  124. package/src/internals/components/AMARuleError.test.tsx +93 -0
  125. package/src/internals/components/AMARuleError.tsx +225 -0
  126. package/src/internals/config.ts +58 -0
  127. package/src/internals/types.ts +11 -0
  128. package/src/internals/useAMADev.dev.ts +153 -0
  129. package/src/internals/useAMADev.test.ts +260 -0
  130. package/src/internals/useAMADev.ts +17 -0
  131. package/src/internals/useAMADevNodeChecks.dev.ts +178 -0
  132. package/src/internals/useAMADevUiInteraction.dev.ts +164 -0
  133. package/src/internals/utils/amaClearHighlight.test.ts +21 -0
  134. package/src/internals/utils/amaClearHighlight.ts +8 -0
  135. package/src/internals/utils/amaHighlightComponent.test.ts +74 -0
  136. package/src/internals/utils/amaHighlightComponent.ts +46 -0
  137. package/src/internals/utils/constants.ts +33 -0
  138. package/src/internals/utils/devMode.test.ts +99 -0
  139. package/src/internals/utils/getErrorColor.test.ts +18 -0
  140. package/src/internals/utils/getErrorColor.ts +8 -0
  141. package/src/internals/utils/getRuleAction.ts +13 -0
  142. package/src/internals/utils/getRuleErrorInfo.test.ts +59 -0
  143. package/src/internals/utils/getRuleErrorInfo.ts +26 -0
  144. package/src/internals/utils/ignoreContrastCheck.test.ts +34 -0
  145. package/src/internals/utils/ignoreContrastCheck.ts +11 -0
  146. package/src/internals/utils/interpolateAnimation.test.ts +176 -0
  147. package/src/internals/utils/interpolateAnimationStates.ts +55 -0
  148. package/src/internals/utils/isA11yLabelAllowed.test.ts +39 -0
  149. package/src/internals/utils/isA11yLabelAllowed.ts +10 -0
  150. package/src/internals/utils/isRuleDisabled.test.ts +35 -0
  151. package/src/internals/utils/isRuleDisabled.ts +10 -0
  152. package/src/internals/utils/logError.test.ts +148 -0
  153. package/src/internals/utils/logError.ts +41 -0
  154. package/src/internals/utils/logFoundIssues.test.ts +98 -0
  155. package/src/internals/utils/logFoundIssues.ts +22 -0
  156. package/src/internals/utils/logger.test.ts +26 -0
  157. package/src/internals/utils/logger.ts +15 -0
  158. package/src/internals/utils/loggerMethods.test.ts +38 -0
  159. package/src/internals/utils/minimumTouchableSize.test.ts +28 -0
  160. package/src/internals/utils/minimumTouchableSize.tsx +9 -0
  161. package/src/internals/utils/platformHelpers.test.ts +11 -0
  162. package/src/internals/utils/platformHelpers.ts +4 -0
  163. package/src/internals/utils/rules.ts +363 -0
  164. package/dist/scripts/create-config.d.ts +0 -1
  165. package/dist/scripts/create-config.js +0 -23
  166. package/dist/src/hooks/useButtonChecks.d.ts +0 -6
  167. package/dist/src/hooks/useButtonChecks.js +0 -51
  168. package/dist/src/hooks/useChecks.d.ts +0 -15
  169. package/dist/src/hooks/useChecks.js +0 -152
  170. package/dist/src/index.js +0 -20
  171. package/src/hooks/useButtonChecks.ts +0 -73
  172. package/src/hooks/useChecks.test.ts +0 -214
  173. package/src/hooks/useChecks.ts +0 -207
  174. /package/dist/{src/components → components}/AutofocusContainer.js +0 -0
  175. /package/dist/{src/hooks → hooks}/useFocus.d.ts +0 -0
  176. /package/dist/{src/hooks → hooks}/useTimedAction.d.ts +0 -0
  177. /package/dist/{src/hooks → hooks}/useTimedAction.js +0 -0
  178. /package/dist/{src/utils → utils}/numerify.d.ts +0 -0
  179. /package/dist/{src/utils → utils}/numerify.js +0 -0
@@ -0,0 +1,253 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AMA_COLORS = exports.RULES_HELP = exports.IGNORE_CONTRAST_FOR_DISABLED_ELEMENTS = exports.CONTRAST_CHECKER_MAX_DEPTH = exports.LOGGER_RULES = exports.NON_OVERRIDABLE_RULES = void 0;
4
+ const react_native_1 = require("react-native");
5
+ exports.NON_OVERRIDABLE_RULES = __DEV__
6
+ ? [
7
+ 'NO_ACCESSIBILITY_ROLE',
8
+ 'NO_ACCESSIBILITY_LABEL',
9
+ 'NO_KEYBOARD_TRAP',
10
+ 'NO_UNDEFINED',
11
+ 'INPUT_HAS_NO_VISIBLE_LABEL',
12
+ 'FLATLIST_NO_COUNT_IN_PLURAL_MESSAGE',
13
+ 'BOTTOM_SHEET_CLOSE_ACTION',
14
+ 'INCOMPATIBLE_ACCESSIBILITY_STATE',
15
+ 'INCOMPATIBLE_ACCESSIBILITY_ROLE',
16
+ 'NO_HEADER_FOUND',
17
+ ]
18
+ : undefined;
19
+ exports.LOGGER_RULES = __DEV__
20
+ ? {
21
+ CONTRAST_FAILED: 'MUST',
22
+ CONTRAST_FAILED_AAA: 'SHOULD',
23
+ FLATLIST_NO_COUNT_IN_SINGULAR_MESSAGE: 'SHOULD',
24
+ FLATLIST_NO_COUNT_IN_PLURAL_MESSAGE: 'MUST',
25
+ IMAGE_MISSING_ALT_TEXT: 'MUST',
26
+ MINIMUM_SIZE: 'MUST',
27
+ NO_ACCESSIBILITY_LABEL: 'MUST',
28
+ NO_ACCESSIBILITY_ROLE: 'MUST',
29
+ INPUT_HAS_NO_VISIBLE_LABEL: 'MUST',
30
+ NO_FORM_ERROR: 'MUST',
31
+ NO_KEYBOARD_TRAP: 'MUST_NOT',
32
+ NO_UNDEFINED: 'MUST_NOT',
33
+ UPPERCASE_TEXT_NO_ACCESSIBILITY_LABEL: 'MUST_NOT',
34
+ NO_UPPERCASE_TEXT: 'MUST_NOT',
35
+ BOTTOM_SHEET_CLOSE_ACTION: 'MUST',
36
+ INCOMPATIBLE_ACCESSIBILITY_STATE: 'MUST',
37
+ INPUT_HAS_NO_VISIBLE_LABEL_ENDING_WITH_ASTERISK: 'MUST_NOT',
38
+ INCOMPATIBLE_ACCESSIBILITY_ROLE: 'MUST_NOT',
39
+ NO_UPPERCASE_ACCESSIBILITY_LABEL: 'SHOULD_NOT',
40
+ NO_HEADER_FOUND: 'MUST',
41
+ NO_ACCESSIBILITY_STATE_SET: 'MUST',
42
+ INPUT_INVALID_RETURN_KEY: 'MUST',
43
+ INPUT_HAS_FOCUSABLE_LABEL: 'MUST',
44
+ LONG_NUMBER_NOT_FORMATTED: 'SHOULD_NOT',
45
+ }
46
+ : null;
47
+ exports.CONTRAST_CHECKER_MAX_DEPTH = 5;
48
+ exports.IGNORE_CONTRAST_FOR_DISABLED_ELEMENTS = false;
49
+ exports.RULES_HELP = __DEV__
50
+ ? {
51
+ CONTRAST_FAILED: {
52
+ url: '/guidelines/contrast',
53
+ issue: 'Insufficient color contrast',
54
+ severity: 'Serious',
55
+ message: 'Text does not have enough contrast against its background. Low contrast makes content hard or impossible to read for users with low vision or when viewing the screen in poor lighting.',
56
+ howToFix: 'Increase the contrast between text and background to meet WCAG AA requirements.',
57
+ },
58
+ CONTRAST_FAILED_AAA: {
59
+ url: '/guidelines/contrast',
60
+ issue: 'Insufficient color contrast (AAA)',
61
+ severity: 'Serious',
62
+ message: 'Text does not meet enhanced contrast requirements. This affects users with more severe visual impairments and reduces overall readability.',
63
+ howToFix: 'Increase the contrast between text and background to meet WCAG AAA requirements.',
64
+ },
65
+ MINIMUM_SIZE: {
66
+ url: '/guidelines/minimum-size',
67
+ issue: 'Touch target too small',
68
+ severity: 'Serious',
69
+ message: 'The element is too small to be tapped reliably. Small touch targets increase error rates, especially for users with motor impairments or larger fingers.',
70
+ howToFix: `Ensure the element is at least ${react_native_1.Platform.select({
71
+ ios: '44x44',
72
+ default: '48x48',
73
+ })} points, or increase the tappable area using hitSlop.`,
74
+ },
75
+ UPPERCASE_TEXT_NO_ACCESSIBILITY_LABEL: {
76
+ url: '/guidelines/text',
77
+ issue: 'Uppercase text without accessibility label',
78
+ severity: 'Serious',
79
+ message: 'All caps text can be read letter by letter by screen readers, making it hard to understand. It can also make voice commands less predictable.',
80
+ howToFix: 'Provide an accessibility label with normal capitalization.',
81
+ },
82
+ NO_UPPERCASE_TEXT: {
83
+ url: '/guidelines/text',
84
+ issue: 'Avoid uppercase text',
85
+ severity: 'Serious',
86
+ message: 'Uppercase text can reduce readability for both screen reader users and sighted users.',
87
+ howToFix: 'Use normal capitalization or provide a properly capitalized accessibility label.',
88
+ },
89
+ NO_UPPERCASE_ACCESSIBILITY_LABEL: {
90
+ url: '/guidelines/accessibility-label#avoid-all-caps-accessibility-label',
91
+ issue: 'Uppercase accessibility label',
92
+ severity: 'Warning',
93
+ message: 'Accessibility labels in all caps may be read incorrectly by screen readers.',
94
+ howToFix: 'Use normal capitalization in accessibility labels.',
95
+ },
96
+ NO_ACCESSIBILITY_LABEL: {
97
+ url: '/guidelines/accessibility-label',
98
+ issue: 'Missing accessibility label',
99
+ severity: 'Critical',
100
+ message: 'This element has no accessible name. Screen reader users will not know what this control does, and Voice Control users will not be able to target it with voice commands.',
101
+ howToFix: [
102
+ 'Add a descriptive accessibility label that explains the action or purpose, for example:',
103
+ ' • `aria-label="Add to cart"`',
104
+ ' • `aria-label="Go back"`',
105
+ ],
106
+ },
107
+ NO_ACCESSIBILITY_ROLE: {
108
+ url: '/guidelines/accessibility-role',
109
+ issue: 'Missing accessibility role',
110
+ severity: 'Critical',
111
+ message: 'Without a role, assistive technologies cannot determine how this element should be used. Voice Control may also misinterpret how to interact with it.',
112
+ howToFix: [
113
+ 'Assign the correct accessibility role, for example:',
114
+ ' • Button: `aria-role="button"`',
115
+ ' • Switch: `aria-role="switch"`',
116
+ ],
117
+ },
118
+ NO_KEYBOARD_TRAP: {
119
+ url: '/guidelines/forms',
120
+ issue: 'Keyboard trap detected',
121
+ severity: 'Critical',
122
+ message: 'Focus cannot escape this input. Users navigating with a keyboard or assistive technology may get stuck.',
123
+ howToFix: 'Ensure users can move focus away from the field even when validation fails.',
124
+ },
125
+ INPUT_HAS_NO_VISIBLE_LABEL: {
126
+ url: '/guidelines/forms#labels',
127
+ issue: 'Missing form label',
128
+ severity: 'Critical',
129
+ message: 'This input has no visible label. Without a persistent label, users can forget what the field is for once they start typing, increasing errors and frustration. Placeholders are not a replacement for labels, as they disappear once text is entered.',
130
+ howToFix: "Add a visible, persistent label that clearly explains the field's purpose.",
131
+ },
132
+ NO_FORM_ERROR: {
133
+ url: '/guidelines/forms',
134
+ issue: 'Missing form error handling',
135
+ severity: 'Serious',
136
+ message: 'After submission fails, users are not informed where the error occurred. Screen reader users receive no feedback.',
137
+ howToFix: 'Provide clear error messages and move focus to the first invalid field.',
138
+ },
139
+ INPUT_HAS_NO_VISIBLE_LABEL_ENDING_WITH_ASTERISK: {
140
+ url: '/guidelines/forms#labels',
141
+ issue: 'Asterisk used to indicate required field',
142
+ severity: 'Serious',
143
+ message: 'Asterisks are visual only. Screen readers do not reliably convey that the field is required.',
144
+ howToFix: 'Mark required fields programmatically instead of relying on an asterisk.',
145
+ },
146
+ FLATLIST_NO_COUNT_IN_SINGULAR_MESSAGE: {
147
+ url: '/guidelines/lists-grids#number-of-results',
148
+ issue: 'Missing item count in list announcement',
149
+ severity: 'Serious',
150
+ message: 'List announcements without item counts provide poor context for screen reader users.',
151
+ howToFix: 'Include the number of items in the announcement, even for a single result.',
152
+ },
153
+ FLATLIST_NO_COUNT_IN_PLURAL_MESSAGE: {
154
+ url: '/guidelines/lists-grids#number-of-results',
155
+ issue: 'Missing item count in list announcement',
156
+ severity: 'Serious',
157
+ message: 'List announcements without item counts provide poor context for screen reader users.',
158
+ howToFix: 'Include the total number of items in the announcement.',
159
+ },
160
+ BOTTOM_SHEET_CLOSE_ACTION: {
161
+ url: '/guidelines/bottomsheet',
162
+ issue: 'No accessible close action',
163
+ severity: 'Critical',
164
+ message: 'Users must have a clear and accessible way to dismiss a bottom sheet. Without it, Voice Control users may be unable to close the view.',
165
+ howToFix: 'Provide a close button or gesture that is accessible to screen readers.',
166
+ },
167
+ INCOMPATIBLE_ACCESSIBILITY_STATE: {
168
+ url: '/guidelines/accessibility-role',
169
+ issue: 'Incompatible accessibility state',
170
+ severity: 'Serious',
171
+ message: 'The accessibility state does not match the element’s behavior.',
172
+ howToFix: 'Use accessibility states that are valid for the given element type.',
173
+ },
174
+ INCOMPATIBLE_ACCESSIBILITY_ROLE: {
175
+ url: '/guidelines/accessibility-role',
176
+ issue: 'Incompatible accessibility role',
177
+ severity: 'Serious',
178
+ message: 'The assigned role does not match how this element behaves.',
179
+ howToFix: 'Update the role to reflect the element’s actual interaction pattern.',
180
+ },
181
+ NO_HEADER_FOUND: {
182
+ url: '/guidelines/headers',
183
+ issue: 'Missing header semantics',
184
+ severity: 'Serious',
185
+ message: 'Screen reader users rely on headers to navigate content efficiently. Headers also help Voice Control users jump to sections by name.',
186
+ howToFix: 'Mark visual headers with `aria-role="header"`.',
187
+ },
188
+ NO_ACCESSIBILITY_STATE_SET: {
189
+ url: '/guidelines/accessibility-states',
190
+ issue: 'Accessibility state not updated',
191
+ severity: 'Critical',
192
+ message: 'The UI state changed, but assistive technologies were not informed.',
193
+ howToFix: [
194
+ 'Update accessibilityState to reflect the new state, for example:',
195
+ ' • `aria-expanded={expanded}`',
196
+ ' • `aria-checked={checked}`',
197
+ ' • `aria-selected={selected}`',
198
+ ' • `aria-busy={busy}`',
199
+ ],
200
+ },
201
+ INPUT_INVALID_RETURN_KEY: {
202
+ url: '/guidelines/forms#focus-on-the-next-field',
203
+ issue: 'Invalid return key behavior',
204
+ severity: 'Serious',
205
+ message: 'By default, React Native shows the `Return` keyboard action that dismisses the keyboard. This interrupts form completion and forces users to manually navigate to the next field.',
206
+ howToFix: 'Explicitly set the appropriate `returnType` (for example `next`) and handle `onSubmitEditing` to move focus to the next input instead of dismissing the keyboard.',
207
+ },
208
+ INPUT_HAS_FOCUSABLE_LABEL: {
209
+ url: '/guidelines/forms#grouping',
210
+ issue: 'Label is focusable',
211
+ severity: 'Serious',
212
+ message: 'The label receives focus separately, resulting in duplicate announcements.',
213
+ howToFix: 'Make the label non-focusable and provide the label via `aria-label` on the input.',
214
+ },
215
+ IMAGE_MISSING_ALT_TEXT: {
216
+ url: '/guidelines/images',
217
+ issue: 'Image missing accessibility label',
218
+ severity: 'Critical',
219
+ message: 'This image has no accessible name. Screen reader users will not know what the image depicts.',
220
+ howToFix: [
221
+ 'Add a descriptive accessibility label that explains what the image shows, for example:',
222
+ ' • `aria-label="Product photo"`',
223
+ ' • `aria-label="Icon for settings"`',
224
+ 'For decorative images, mark them as non-accessible with `accessible={false}`.',
225
+ ],
226
+ },
227
+ NO_UNDEFINED: {
228
+ url: '/guidelines/accessibility-label',
229
+ issue: 'Undefined accessibility value',
230
+ severity: 'Critical',
231
+ message: 'An accessibility property has an undefined value. This can cause screen readers to announce unexpected content.',
232
+ howToFix: 'Ensure all accessibility props are explicitly set to a defined value or omitted entirely.',
233
+ },
234
+ LONG_NUMBER_NOT_FORMATTED: {
235
+ url: '/guidelines/text',
236
+ issue: 'Long number not formatted for screen readers',
237
+ severity: 'Warning',
238
+ message: 'A long run of unformatted digits (e.g. a card or account number) is read by screen readers as one large number instead of digit-by-digit.',
239
+ howToFix: 'Add spacing between digits via the accessibility label so the number is announced digit-by-digit.',
240
+ },
241
+ }
242
+ : null;
243
+ const canRuleBeOverridden = __DEV__
244
+ ? (rule) => {
245
+ return !exports.NON_OVERRIDABLE_RULES.includes(rule);
246
+ }
247
+ : null;
248
+ exports.AMA_COLORS = {
249
+ Critical: '#f00',
250
+ Serious: '#2563EB',
251
+ Warning: '#2563EB',
252
+ };
253
+ exports.default = canRuleBeOverridden;
package/package.json CHANGED
@@ -1,18 +1,46 @@
1
1
  {
2
2
  "name": "@react-native-ama/core",
3
- "version": "1.2.1",
3
+ "version": "2.0.0-beta.0",
4
4
  "description": "Accessible Mobile App Library for React Native",
5
- "react-native": "src/index",
6
- "types": "dist/src/index.d.ts",
7
- "main": "dist/src/index.js",
5
+ "sideEffects": false,
8
6
  "exports": {
9
- ".": [
10
- {
11
- "imports": "./dist/src/index.js",
12
- "types": "./dist/src/index.d.ts"
13
- },
14
- "./dist/src/index.js"
15
- ]
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "react-native": "./src/index.ts",
10
+ "default": "./dist/index.js"
11
+ },
12
+ "./AMAProvider": {
13
+ "types": "./dist/components/AMAProvider.d.ts",
14
+ "default": "./dist/components/AMAProvider.js"
15
+ },
16
+ "./AutofocusContainer": {
17
+ "types": "./dist/components/AutofocusContainer.d.ts",
18
+ "default": "./dist/components/AutofocusContainer.js"
19
+ },
20
+ "./HideChildrenFromAccessibilityTree": {
21
+ "types": "./dist/components/HideChildrenFromAccessibilityTree.d.ts",
22
+ "default": "./dist/components/HideChildrenFromAccessibilityTree.js"
23
+ },
24
+ "./useFocus": {
25
+ "types": "./dist/hooks/useFocus.d.ts",
26
+ "default": "./dist/hooks/useFocus.js"
27
+ },
28
+ "./useTimedAction": {
29
+ "types": "./dist/hooks/useTimedAction.d.ts",
30
+ "default": "./dist/hooks/useTimedAction.js"
31
+ },
32
+ "./ReactNativeAmaModule": {
33
+ "types": "./dist/ReactNativeAmaModule.d.ts",
34
+ "default": "./dist/ReactNativeAmaModule.js"
35
+ },
36
+ "./ReactNativeAmaView": {
37
+ "types": "./dist/ReactNativeAmaView.d.ts",
38
+ "default": "./dist/ReactNativeAmaView.js"
39
+ },
40
+ "./ReactNativeAma.types": {
41
+ "types": "./dist/ReactNativeAma.types.d.ts",
42
+ "default": "./dist/ReactNativeAma.types.js"
43
+ }
16
44
  },
17
45
  "files": [
18
46
  "src",
@@ -20,14 +48,11 @@
20
48
  "dist/scripts"
21
49
  ],
22
50
  "scripts": {
23
- "build": "rm -rf dist && tsc -p ./tsconfig.build.json",
24
- "typecheck": "tsc --noEmit",
51
+ "build": "rm -rf dist && ../../node_modules/.bin/tsc -p ./tsconfig.build.json",
52
+ "typecheck": "../../node_modules/.bin/tsc --noEmit",
25
53
  "test": "jest",
26
54
  "postinstall": "node ./src/postinstall.js"
27
55
  },
28
- "dependencies": {
29
- "@react-native-ama/internal": "~1.2.1"
30
- },
31
56
  "peerDependencies": {
32
57
  "react": "*",
33
58
  "react-native": "*"
@@ -60,8 +85,17 @@
60
85
  "<rootDir>/node_modules",
61
86
  "<rootDir>/dist/"
62
87
  ],
88
+ "setupFilesAfterEnv": [
89
+ "<rootDir>/jest.setup.js"
90
+ ],
63
91
  "collectCoverageFrom": [
64
- "src/**/*.{ts,tsx}"
92
+ "src/**/*.{ts,tsx}",
93
+ "!src/index.ts",
94
+ "!src/ReactNativeAmaModule.ts",
95
+ "!src/ReactNativeAmaModule.web.ts",
96
+ "!src/ReactNativeAmaView.tsx",
97
+ "!src/ReactNativeAmaView.web.tsx",
98
+ "!src/ReactNativeAma.types.ts"
65
99
  ],
66
100
  "coverageThreshold": {
67
101
  "global": {
@@ -72,8 +106,17 @@
72
106
  }
73
107
  },
74
108
  "transformIgnorePatterns": [
75
- "node_modules/(?!(@react-native|react-native|react-native-reanimated/))"
109
+ "node_modules/(?!(react-native-reanimated|expo|@expo)/)"
110
+ ],
111
+ "moduleDirectories": [
112
+ "node_modules",
113
+ "../../playground/node_modules"
76
114
  ],
115
+ "moduleNameMapper": {
116
+ "@react-native/js-polyfills/error-guard": "<rootDir>/../../jest-mocks/error-guard.js",
117
+ "^(\\.{1,2}/)*ama\\.config\\.json$": "<rootDir>/../../jest-mocks/ama-config.js",
118
+ "^(\\.{1,2}/)*ReactNativeAmaModule$": "<rootDir>/../../jest-mocks/ReactNativeAmaModule.js"
119
+ },
77
120
  "verbose": true
78
121
  }
79
122
  }
@@ -0,0 +1,70 @@
1
+ import type { StyleProp, ViewStyle } from 'react-native';
2
+
3
+ export type OnLoadEventPayload = {
4
+ url: string;
5
+ };
6
+
7
+ export type AmaNode = {
8
+ type: 'Pressable' | 'Text' | 'TextInput' | 'Image';
9
+ viewId: number;
10
+ bounds: [number, number, number, number];
11
+ hitSlop?: { top: number; left: number; bottom: number; right: number };
12
+
13
+ ariaLabel?: string;
14
+ ariaRole?: string;
15
+ traits?: string[];
16
+ content?: string;
17
+
18
+ fg?: string;
19
+ bg?: string;
20
+ fontSize?: number; // pt
21
+ isBold?: boolean;
22
+ isEnabled?: boolean;
23
+ returnType?: number;
24
+ hasOnSubmitEditing: boolean;
25
+ isAccessible?: boolean; // For Text components: false if accessibilityElementsHidden (iOS) or importantForAccessibility="no" (Android)
26
+ };
27
+
28
+ export type AmaNodes = Record<number, AmaNode>;
29
+
30
+ export type AmaUiSnapshot = {
31
+ fgColor?: string;
32
+ bgColor?: string;
33
+ x: number;
34
+ y: number;
35
+ width: number;
36
+ height: number;
37
+ parentId: number;
38
+ isPressable: boolean;
39
+ isChecked: boolean;
40
+ isBusy: boolean;
41
+ isSelected: boolean;
42
+ isDisabled: boolean;
43
+ isExpanded: boolean;
44
+ };
45
+
46
+ export type AmaUiSnapshotsData = {
47
+ rootTag: number;
48
+ before: Record<number, AmaUiSnapshot>;
49
+ after: Record<number, AmaUiSnapshot>;
50
+ afterSettled?: Record<number, AmaUiSnapshot>;
51
+ beforeModalVisible: boolean;
52
+ afterModalVisible: boolean;
53
+ };
54
+
55
+ export type AmaUiSnapshotKeys = keyof AmaUiSnapshot;
56
+
57
+ export type ReactNativeAmaModuleEvents = {
58
+ onAmaNodes: (nodes: AmaNodes) => void;
59
+ onUIInteraction: (nodes: AmaUiSnapshotsData) => void;
60
+ };
61
+
62
+ export type ChangeEventPayload = {
63
+ value: string;
64
+ };
65
+
66
+ export type ReactNativeAmaViewProps = {
67
+ url: string;
68
+ onLoad: (event: { nativeEvent: OnLoadEventPayload }) => void;
69
+ style?: StyleProp<ViewStyle>;
70
+ };
@@ -0,0 +1,18 @@
1
+ import { NativeModule, requireNativeModule } from 'expo';
2
+ import { ReactNativeAmaModuleEvents } from './ReactNativeAma.types';
3
+ import { Position } from './internals/types';
4
+
5
+ declare class ReactNativeAmaModule extends NativeModule<ReactNativeAmaModuleEvents> {
6
+ start(config?: any): void;
7
+ stop(): void;
8
+ highlight(
9
+ viewId: number,
10
+ mode: 'background' | 'border' | 'both',
11
+ color: string,
12
+ issueCount: number,
13
+ ): Promise<Position>;
14
+ clearHighlight(viewId: number): void;
15
+ }
16
+
17
+ // This call loads the native module object from the JSI.
18
+ export default requireNativeModule<ReactNativeAmaModule>('ReactNativeAma');
@@ -0,0 +1,14 @@
1
+ import { NativeModule, registerWebModule } from 'expo';
2
+ import { AmaNodes, ReactNativeAmaModuleEvents } from './ReactNativeAma.types';
3
+
4
+ class ReactNativeAmaModule extends NativeModule<ReactNativeAmaModuleEvents> {
5
+ PI = Math.PI;
6
+ async setValueAsync(nodes: AmaNodes): Promise<void> {
7
+ this.emit('onAmaNodes', nodes);
8
+ }
9
+ hello() {
10
+ return 'Hello world! 👋';
11
+ }
12
+ }
13
+
14
+ export default registerWebModule(ReactNativeAmaModule, 'ReactNativeAmaModule');
@@ -0,0 +1,9 @@
1
+ import { requireNativeView } from 'expo';
2
+ import * as React from 'react';
3
+ import { ReactNativeAmaViewProps } from './ReactNativeAma.types';
4
+
5
+ const NativeView: React.ComponentType<ReactNativeAmaViewProps> = requireNativeView('ReactNativeAma');
6
+
7
+ export default function ReactNativeAmaView(props: ReactNativeAmaViewProps) {
8
+ return <NativeView {...props} />;
9
+ }
@@ -0,0 +1,15 @@
1
+ import * as React from 'react';
2
+ import { ReactNativeAmaViewProps } from './ReactNativeAma.types';
3
+
4
+ export default function ReactNativeAmaView(props: ReactNativeAmaViewProps) {
5
+ return (
6
+ <div>
7
+ <iframe
8
+ // eslint-disable-next-line react-native/no-inline-styles
9
+ style={{ flex: 1 }}
10
+ src={props.url}
11
+ onLoad={() => props.onLoad({ nativeEvent: { url: props.url } })}
12
+ />
13
+ </div>
14
+ );
15
+ }
@@ -0,0 +1,9 @@
1
+ const ReactNativeAmaModule = {
2
+ start: jest.fn(),
3
+ stop: jest.fn(),
4
+ highlight: jest.fn(),
5
+ clearHighlight: jest.fn(),
6
+ addListener: jest.fn(() => ({ remove: jest.fn() })),
7
+ };
8
+
9
+ export default ReactNativeAmaModule;
@@ -1,7 +1,6 @@
1
1
  import { act, render } from '@testing-library/react-native';
2
2
  import { flushMicroTasks } from '@testing-library/react-native/build/flush-micro-tasks';
3
3
  import * as React from 'react';
4
-
5
4
  import { AMAContextValue, AMAProvider, useAMAContext } from './AMAProvider';
6
5
 
7
6
  beforeEach(() => {
@@ -9,95 +8,6 @@ beforeEach(() => {
9
8
  });
10
9
 
11
10
  describe('AMAProvider', () => {
12
- describe('When __DEV__ is true', () => {
13
- beforeEach(() => {
14
- // @ts-ignore
15
- global.__DEV__ = true;
16
- });
17
-
18
- describe('trackError', () => {
19
- it('exports trackError', async () => {
20
- await renderAMAProvider();
21
-
22
- expect(amaContextValuesRef.trackError).toBeDefined();
23
- });
24
-
25
- it('shows the AMA error banner when called', async () => {
26
- const renderAPI = await renderAMAProvider();
27
-
28
- expect(renderAPI.queryByTestId('amaError')).toBeNull();
29
-
30
- act(() => {
31
- amaContextValuesRef.trackError('123');
32
- });
33
-
34
- expect(renderAPI.getByTestId('amaError')).toBeDefined();
35
- expect(
36
- renderAPI.getByTestId('amaError').props.accessibilityLabel,
37
- ).toEqual("1 component(s) didn't pass the accessibility check(s)");
38
-
39
- act(() => {
40
- amaContextValuesRef.trackError('123');
41
- });
42
-
43
- expect(
44
- renderAPI.getByTestId('amaError').props.accessibilityLabel,
45
- ).toEqual("1 component(s) didn't pass the accessibility check(s)");
46
-
47
- expect(renderAPI.getByTestId('amaError')).toBeDefined();
48
-
49
- act(() => {
50
- amaContextValuesRef.trackError('another');
51
- });
52
-
53
- expect(
54
- renderAPI.getByTestId('amaError').props.accessibilityLabel,
55
- ).toEqual("2 component(s) didn't pass the accessibility check(s)");
56
-
57
- expect(renderAPI.getByTestId('amaError')).toBeDefined();
58
- });
59
- });
60
-
61
- describe('removeError', () => {
62
- it('exports removeError', async () => {
63
- await renderAMAProvider();
64
-
65
- expect(amaContextValuesRef.removeError).toBeDefined();
66
- });
67
-
68
- it('shows the AMA error banner when called', async () => {
69
- const renderAPI = await renderAMAProvider();
70
-
71
- expect(renderAPI.queryByTestId('amaError')).toBeNull();
72
-
73
- act(() => {
74
- amaContextValuesRef.trackError('123');
75
- });
76
-
77
- expect(
78
- renderAPI.getByTestId('amaError').props.accessibilityLabel,
79
- ).toEqual("1 component(s) didn't pass the accessibility check(s)");
80
- expect(renderAPI.getByTestId('amaError')).toBeDefined();
81
-
82
- act(() => {
83
- amaContextValuesRef.removeError('invalidID');
84
- });
85
-
86
- expect(
87
- renderAPI.getByTestId('amaError').props.accessibilityLabel,
88
- ).toEqual("1 component(s) didn't pass the accessibility check(s)");
89
-
90
- expect(renderAPI.getByTestId('amaError')).toBeDefined();
91
-
92
- act(() => {
93
- amaContextValuesRef.removeError('123');
94
- });
95
-
96
- expect(renderAPI.queryByTestId('amaError')).toBeNull();
97
- });
98
- });
99
- });
100
-
101
11
  describe('When __DEV__ is false', () => {
102
12
  beforeEach(() => {
103
13
  // @ts-ignore
@@ -107,13 +17,13 @@ describe('AMAProvider', () => {
107
17
  it('does not export trackError', async () => {
108
18
  await renderAMAProvider();
109
19
 
110
- expect(amaContextValuesRef.trackError).toBeUndefined();
20
+ expect((amaContextValuesRef as any).trackError).toBeUndefined();
111
21
  });
112
22
 
113
23
  it('does not export removeError', async () => {
114
24
  await renderAMAProvider();
115
25
 
116
- expect(amaContextValuesRef.removeError).toBeUndefined();
26
+ expect((amaContextValuesRef as any).removeError).toBeUndefined();
117
27
  });
118
28
  });
119
29
  });
@@ -132,10 +42,7 @@ const renderAMAProvider = async () => {
132
42
  return result;
133
43
  };
134
44
 
135
- let amaContextValuesRef: Extract<
136
- AMAContextValue,
137
- { trackError: Function; removeError: Function }
138
- >;
45
+ let amaContextValuesRef: AMAContextValue;
139
46
 
140
47
  const DummyComponent = () => {
141
48
  // @ts-ignore