@abstraks-dev/ui-library 1.0.1

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 (158) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +708 -0
  3. package/dist/__tests__/Anchor.test.js +145 -0
  4. package/dist/__tests__/ArrowRight.test.js +91 -0
  5. package/dist/__tests__/Avatar.test.js +123 -0
  6. package/dist/__tests__/Button.test.js +82 -0
  7. package/dist/__tests__/Card.test.js +198 -0
  8. package/dist/__tests__/CheckCircle.test.js +98 -0
  9. package/dist/__tests__/Checkbox.test.js +161 -0
  10. package/dist/__tests__/ChevronDown.test.js +73 -0
  11. package/dist/__tests__/Close.test.js +98 -0
  12. package/dist/__tests__/EditSquare.test.js +99 -0
  13. package/dist/__tests__/Error.test.js +74 -0
  14. package/dist/__tests__/Footer.test.js +66 -0
  15. package/dist/__tests__/Heading.test.js +227 -0
  16. package/dist/__tests__/Hero.test.js +74 -0
  17. package/dist/__tests__/Label.test.js +123 -0
  18. package/dist/__tests__/Loader.test.js +115 -0
  19. package/dist/__tests__/MenuHover.test.js +137 -0
  20. package/dist/__tests__/Paragraph.test.js +93 -0
  21. package/dist/__tests__/PlusCircle.test.js +99 -0
  22. package/dist/__tests__/Radio.test.js +153 -0
  23. package/dist/__tests__/Select.test.js +187 -0
  24. package/dist/__tests__/Tabs.test.js +162 -0
  25. package/dist/__tests__/TextArea.test.js +127 -0
  26. package/dist/__tests__/TextInput.test.js +181 -0
  27. package/dist/__tests__/Toggle.test.js +120 -0
  28. package/dist/__tests__/TrashX.test.js +99 -0
  29. package/dist/__tests__/useHeadingAccessibility.test.js +144 -0
  30. package/dist/components/Anchor.js +131 -0
  31. package/dist/components/Animation.js +129 -0
  32. package/dist/components/AnimationGroup.js +207 -0
  33. package/dist/components/AnimationToggle.js +216 -0
  34. package/dist/components/Avatar.js +153 -0
  35. package/dist/components/Button.js +218 -0
  36. package/dist/components/Card.js +222 -0
  37. package/dist/components/Checkbox.js +305 -0
  38. package/dist/components/Crud.js +564 -0
  39. package/dist/components/DragAndDrop.js +337 -0
  40. package/dist/components/Error.js +206 -0
  41. package/dist/components/Footer.js +99 -0
  42. package/dist/components/Form.js +412 -0
  43. package/dist/components/Header.js +372 -0
  44. package/dist/components/Heading.js +134 -0
  45. package/dist/components/Hero.js +181 -0
  46. package/dist/components/Label.js +256 -0
  47. package/dist/components/Loader.js +302 -0
  48. package/dist/components/MenuHover.js +114 -0
  49. package/dist/components/Paragraph.js +128 -0
  50. package/dist/components/Prompt.js +61 -0
  51. package/dist/components/Radio.js +254 -0
  52. package/dist/components/Select.js +422 -0
  53. package/dist/components/SideMenu.js +313 -0
  54. package/dist/components/Tabs.js +297 -0
  55. package/dist/components/TextArea.js +370 -0
  56. package/dist/components/TextInput.js +286 -0
  57. package/dist/components/Toggle.js +186 -0
  58. package/dist/components/crudFiles/CrudEditBase.js +150 -0
  59. package/dist/components/crudFiles/CrudViewBase.js +39 -0
  60. package/dist/components/crudFiles/crudDevelopment.js +118 -0
  61. package/dist/components/crudFiles/crudEditHandlers.js +50 -0
  62. package/dist/constants/animation.js +30 -0
  63. package/dist/icons/ArrowIcon.js +32 -0
  64. package/dist/icons/ArrowRight.js +33 -0
  65. package/dist/icons/CheckCircle.js +33 -0
  66. package/dist/icons/ChevronDown.js +28 -0
  67. package/dist/icons/Close.js +33 -0
  68. package/dist/icons/EditSquare.js +33 -0
  69. package/dist/icons/Ellipses.js +34 -0
  70. package/dist/icons/Hamburger.js +39 -0
  71. package/dist/icons/LoadingSpinner.js +42 -0
  72. package/dist/icons/PlusCircle.js +33 -0
  73. package/dist/icons/SaveIcon.js +32 -0
  74. package/dist/icons/TrashX.js +33 -0
  75. package/dist/icons/__tests__/CheckCircle.test.js +9 -0
  76. package/dist/icons/__tests__/ChevronDown.test.js +9 -0
  77. package/dist/icons/__tests__/Close.test.js +9 -0
  78. package/dist/icons/__tests__/EditSquare.test.js +9 -0
  79. package/dist/icons/__tests__/PlusCircle.test.js +9 -0
  80. package/dist/icons/__tests__/TrashX.test.js +9 -0
  81. package/dist/icons/index.js +89 -0
  82. package/dist/index.js +332 -0
  83. package/dist/setupTests.js +3 -0
  84. package/dist/styles/_variables.scss +286 -0
  85. package/dist/styles/anchor.scss +40 -0
  86. package/dist/styles/animation-accessibility.scss +96 -0
  87. package/dist/styles/animation-toggle.scss +233 -0
  88. package/dist/styles/animation.scss +3781 -0
  89. package/dist/styles/avatar.scss +285 -0
  90. package/dist/styles/button.scss +430 -0
  91. package/dist/styles/card.scss +210 -0
  92. package/dist/styles/checkbox.scss +160 -0
  93. package/dist/styles/crud.scss +474 -0
  94. package/dist/styles/dragAndDrop.scss +312 -0
  95. package/dist/styles/error.scss +232 -0
  96. package/dist/styles/footer.scss +58 -0
  97. package/dist/styles/form.scss +420 -0
  98. package/dist/styles/grid.scss +29 -0
  99. package/dist/styles/header.scss +276 -0
  100. package/dist/styles/heading.scss +118 -0
  101. package/dist/styles/hero.scss +185 -0
  102. package/dist/styles/htmlElements.scss +20 -0
  103. package/dist/styles/image.scss +9 -0
  104. package/dist/styles/label.scss +340 -0
  105. package/dist/styles/list-item.scss +5 -0
  106. package/dist/styles/loader.scss +354 -0
  107. package/dist/styles/logo.scss +19 -0
  108. package/dist/styles/main.css +9056 -0
  109. package/dist/styles/main.css.map +1 -0
  110. package/dist/styles/main.scss +0 -0
  111. package/dist/styles/menu-hover.scss +30 -0
  112. package/dist/styles/paragraph.scss +88 -0
  113. package/dist/styles/prompt.scss +51 -0
  114. package/dist/styles/radio.scss +202 -0
  115. package/dist/styles/select.scss +363 -0
  116. package/dist/styles/side-menu.scss +334 -0
  117. package/dist/styles/tabs.scss +540 -0
  118. package/dist/styles/text-area.scss +388 -0
  119. package/dist/styles/text-input.scss +171 -0
  120. package/dist/styles/toggle.scss +0 -0
  121. package/dist/styles/unordered-list.scss +8 -0
  122. package/dist/utils/ScrollHandler.js +30 -0
  123. package/dist/utils/accessibility.js +128 -0
  124. package/dist/utils/heroUtils.js +316 -0
  125. package/dist/utils/index.js +104 -0
  126. package/dist/utils/inputValidation.js +29 -0
  127. package/dist/utils/keyboardNavigation.js +536 -0
  128. package/dist/utils/labelUtils.js +708 -0
  129. package/dist/utils/loaderUtils.js +387 -0
  130. package/dist/utils/menuUtils.js +575 -0
  131. package/dist/utils/useHeadingAccessibility.js +298 -0
  132. package/dist/utils/useRadioGroup.js +260 -0
  133. package/dist/utils/useSelectAccessibility.js +426 -0
  134. package/dist/utils/useTabsAccessibility.js +278 -0
  135. package/dist/utils/useTextAreaAccessibility.js +255 -0
  136. package/dist/utils/useTextInputAccessibility.js +295 -0
  137. package/dist/utils/useTypographyAccessibility.js +168 -0
  138. package/dist/utils/useWindowSize.js +32 -0
  139. package/dist/utils/utils/ScrollHandler.js +26 -0
  140. package/dist/utils/utils/accessibility.js +133 -0
  141. package/dist/utils/utils/heroUtils.js +348 -0
  142. package/dist/utils/utils/index.js +9 -0
  143. package/dist/utils/utils/inputValidation.js +22 -0
  144. package/dist/utils/utils/keyboardNavigation.js +664 -0
  145. package/dist/utils/utils/labelUtils.js +772 -0
  146. package/dist/utils/utils/loaderUtils.js +436 -0
  147. package/dist/utils/utils/menuUtils.js +651 -0
  148. package/dist/utils/utils/useHeadingAccessibility.js +334 -0
  149. package/dist/utils/utils/useRadioGroup.js +311 -0
  150. package/dist/utils/utils/useSelectAccessibility.js +498 -0
  151. package/dist/utils/utils/useTabsAccessibility.js +316 -0
  152. package/dist/utils/utils/useTextAreaAccessibility.js +303 -0
  153. package/dist/utils/utils/useTextInputAccessibility.js +338 -0
  154. package/dist/utils/utils/useTypographyAccessibility.js +180 -0
  155. package/dist/utils/utils/useWindowSize.js +26 -0
  156. package/dist/utils/utils/validation.js +131 -0
  157. package/dist/utils/validation.js +139 -0
  158. package/package.json +90 -0
@@ -0,0 +1,255 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.validateTextAreaAccessibility = exports.useTextAreaAccessibility = exports.getCharacterCountInfo = void 0;
7
+ var _react = require("react");
8
+ /**
9
+ * Custom hook for managing textarea state with accessibility features
10
+ * Provides controlled state management for textarea with ARIA support
11
+ *
12
+ * @param {Object} options - Configuration options
13
+ * @param {string} options.defaultValue - Default textarea value
14
+ * @param {Function} options.onChange - Change callback function
15
+ * @param {boolean} options.required - Whether the field is required
16
+ * @param {number} options.maxLength - Maximum character count
17
+ * @param {number} options.minLength - Minimum character count
18
+ * @returns {Object} TextArea state and handlers
19
+ */
20
+ const useTextAreaAccessibility = (options = {}) => {
21
+ const {
22
+ defaultValue = '',
23
+ onChange,
24
+ required = false,
25
+ maxLength,
26
+ minLength
27
+ } = options;
28
+ const [value, setValue] = (0, _react.useState)(defaultValue);
29
+ const [charCount, setCharCount] = (0, _react.useState)(defaultValue.length);
30
+ const [isValid, setIsValid] = (0, _react.useState)(!required || defaultValue.length > 0);
31
+ const [hasBeenTouched, setHasBeenTouched] = (0, _react.useState)(false);
32
+ const textareaRef = (0, _react.useRef)(null);
33
+ const handleChange = (0, _react.useCallback)(event => {
34
+ const newValue = event.target.value;
35
+ setValue(newValue);
36
+ setCharCount(newValue.length);
37
+ setHasBeenTouched(true);
38
+
39
+ // Validate the field
40
+ let valid = true;
41
+ if (required && newValue.trim().length === 0) {
42
+ valid = false;
43
+ }
44
+ if (minLength && newValue.length < minLength) {
45
+ valid = false;
46
+ }
47
+ if (maxLength && newValue.length > maxLength) {
48
+ valid = false;
49
+ }
50
+ setIsValid(valid);
51
+
52
+ // Call external onChange handler
53
+ onChange?.(event, {
54
+ value: newValue,
55
+ isValid: valid,
56
+ charCount: newValue.length
57
+ });
58
+ }, [onChange, required, minLength, maxLength]);
59
+ const handleFocus = (0, _react.useCallback)(event => {
60
+ // Focus handling for accessibility
61
+ setHasBeenTouched(true);
62
+ }, []);
63
+ const handleBlur = (0, _react.useCallback)(event => {
64
+ // Blur handling for validation
65
+ const newValue = event.target.value;
66
+ let valid = true;
67
+ if (required && newValue.trim().length === 0) {
68
+ valid = false;
69
+ }
70
+ if (minLength && newValue.length < minLength) {
71
+ valid = false;
72
+ }
73
+ if (maxLength && newValue.length > maxLength) {
74
+ valid = false;
75
+ }
76
+ setIsValid(valid);
77
+ }, [required, minLength, maxLength]);
78
+ const reset = (0, _react.useCallback)(() => {
79
+ setValue(defaultValue);
80
+ setCharCount(defaultValue.length);
81
+ setIsValid(!required || defaultValue.length > 0);
82
+ setHasBeenTouched(false);
83
+ }, [defaultValue, required]);
84
+ const focus = (0, _react.useCallback)(() => {
85
+ textareaRef.current?.focus();
86
+ }, []);
87
+ return {
88
+ // State
89
+ value,
90
+ charCount,
91
+ isValid,
92
+ hasBeenTouched,
93
+ showError: hasBeenTouched && !isValid,
94
+ // Handlers
95
+ handleChange,
96
+ handleFocus,
97
+ handleBlur,
98
+ reset,
99
+ focus,
100
+ // Ref
101
+ textareaRef,
102
+ // Computed properties
103
+ remainingChars: maxLength ? maxLength - charCount : null,
104
+ isAtMaxLength: maxLength ? charCount >= maxLength : false,
105
+ isAtMinLength: minLength ? charCount >= minLength : true
106
+ };
107
+ };
108
+
109
+ /**
110
+ * Validates textarea accessibility compliance
111
+ * Checks for proper ARIA attributes, labels, and keyboard support
112
+ *
113
+ * @param {HTMLElement} textareaElement - The textarea element to validate
114
+ * @returns {Object} Validation results with errors and warnings
115
+ */
116
+ exports.useTextAreaAccessibility = useTextAreaAccessibility;
117
+ const validateTextAreaAccessibility = textareaElement => {
118
+ const results = {
119
+ passed: true,
120
+ errors: [],
121
+ warnings: [],
122
+ info: []
123
+ };
124
+ if (!textareaElement) {
125
+ results.passed = false;
126
+ results.errors.push('No textarea element provided for validation');
127
+ return results;
128
+ }
129
+
130
+ // Check if it's actually a textarea element
131
+ if (textareaElement.tagName.toLowerCase() !== 'textarea') {
132
+ // Look for textarea within the element
133
+ const textarea = textareaElement.querySelector('textarea');
134
+ if (!textarea) {
135
+ results.passed = false;
136
+ results.errors.push('Element is not a textarea and contains no textarea element');
137
+ return results;
138
+ }
139
+ // Use the found textarea for validation
140
+ textareaElement = textarea;
141
+ }
142
+
143
+ // 1. Check for accessible name (label, aria-label, or aria-labelledby)
144
+ const hasLabel = textareaElement.hasAttribute('aria-label') || textareaElement.hasAttribute('aria-labelledby') || textareaElement.labels?.length > 0;
145
+ if (!hasLabel) {
146
+ results.passed = false;
147
+ results.errors.push('Textarea must have an accessible name (label, aria-label, or aria-labelledby)');
148
+ }
149
+
150
+ // 2. Check for proper ID if labelledby is used
151
+ if (textareaElement.hasAttribute('aria-labelledby')) {
152
+ const labelId = textareaElement.getAttribute('aria-labelledby');
153
+ const labelElement = document.getElementById(labelId);
154
+ if (!labelElement) {
155
+ results.passed = false;
156
+ results.errors.push(`Referenced label element with ID "${labelId}" not found`);
157
+ }
158
+ }
159
+
160
+ // 3. Check for required field indication
161
+ if (textareaElement.hasAttribute('required') || textareaElement.hasAttribute('aria-required')) {
162
+ if (!textareaElement.hasAttribute('aria-required')) {
163
+ results.warnings.push('Consider adding aria-required="true" for screen reader users');
164
+ }
165
+ }
166
+
167
+ // 4. Check for error state accessibility
168
+ if (textareaElement.hasAttribute('aria-invalid')) {
169
+ const isInvalid = textareaElement.getAttribute('aria-invalid') === 'true';
170
+ if (isInvalid && !textareaElement.hasAttribute('aria-describedby')) {
171
+ results.warnings.push('Invalid textarea should have aria-describedby pointing to error message');
172
+ }
173
+ }
174
+
175
+ // 5. Check for character count accessibility
176
+ const hasMaxLength = textareaElement.hasAttribute('maxlength');
177
+ if (hasMaxLength && !textareaElement.hasAttribute('aria-describedby')) {
178
+ results.warnings.push('Textarea with maxlength should have aria-describedby for character count info');
179
+ }
180
+
181
+ // 6. Check for placeholder accessibility
182
+ if (textareaElement.hasAttribute('placeholder')) {
183
+ results.warnings.push('Avoid using placeholder as the only label - ensure proper labeling exists');
184
+ }
185
+
186
+ // 7. Check for disabled state
187
+ if (textareaElement.disabled) {
188
+ results.info.push('Textarea is disabled');
189
+ }
190
+
191
+ // 8. Check for keyboard accessibility
192
+ const tabIndex = textareaElement.getAttribute('tabindex');
193
+ if (tabIndex && parseInt(tabIndex) < 0) {
194
+ results.warnings.push('Textarea has negative tabindex - may not be keyboard accessible');
195
+ }
196
+
197
+ // 9. Check for resize capability
198
+ const computedStyle = window.getComputedStyle(textareaElement);
199
+ if (computedStyle.resize === 'none') {
200
+ results.info.push('Textarea resize is disabled - consider allowing users to resize for better usability');
201
+ }
202
+
203
+ // 10. Check for minimum contrast (if possible)
204
+ const bgColor = computedStyle.backgroundColor;
205
+ const textColor = computedStyle.color;
206
+ if (bgColor && textColor) {
207
+ results.info.push('Manual contrast check recommended for accessibility compliance');
208
+ }
209
+ if (results.errors.length === 0) {
210
+ results.info.push('Basic accessibility validation passed');
211
+ }
212
+ return results;
213
+ };
214
+
215
+ /**
216
+ * Character count utility for textarea accessibility
217
+ * Provides screen reader announcements for character limits
218
+ *
219
+ * @param {number} currentCount - Current character count
220
+ * @param {number} maxLength - Maximum allowed characters
221
+ * @param {number} warningThreshold - Threshold to start warnings (default: 0.8)
222
+ * @returns {Object} Character count info and announcements
223
+ */
224
+ exports.validateTextAreaAccessibility = validateTextAreaAccessibility;
225
+ const getCharacterCountInfo = (currentCount, maxLength, warningThreshold = 0.8) => {
226
+ if (!maxLength) return null;
227
+ const remaining = maxLength - currentCount;
228
+ const percentage = currentCount / maxLength;
229
+ const isWarning = percentage >= warningThreshold;
230
+ const isError = currentCount > maxLength;
231
+ let announcement = '';
232
+ let status = 'info';
233
+ if (isError) {
234
+ announcement = `Character limit exceeded. ${Math.abs(remaining)} characters over limit.`;
235
+ status = 'error';
236
+ } else if (isWarning) {
237
+ announcement = `${remaining} characters remaining.`;
238
+ status = 'warning';
239
+ } else {
240
+ announcement = `${currentCount} of ${maxLength} characters used.`;
241
+ status = 'info';
242
+ }
243
+ return {
244
+ currentCount,
245
+ maxLength,
246
+ remaining,
247
+ percentage,
248
+ isWarning,
249
+ isError,
250
+ announcement,
251
+ status,
252
+ ariaLabel: `Character count: ${announcement}`
253
+ };
254
+ };
255
+ exports.getCharacterCountInfo = getCharacterCountInfo;
@@ -0,0 +1,295 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.validateTextInputAccessibility = exports.useTextInputAccessibility = exports.getInputValidationInfo = void 0;
7
+ var _react = require("react");
8
+ var _inputValidation = require("./inputValidation");
9
+ /**
10
+ * Custom hook for managing text input state with accessibility features
11
+ * Provides controlled state management for text input with ARIA support
12
+ *
13
+ * @param {Object} options - Configuration options
14
+ * @param {string} options.defaultValue - Default input value
15
+ * @param {Function} options.onChange - Change callback function
16
+ * @param {boolean} options.required - Whether the field is required
17
+ * @param {number} options.maxLength - Maximum character count
18
+ * @param {number} options.minLength - Minimum character count
19
+ * @param {string} options.type - Input type for validation
20
+ * @returns {Object} TextInput state and handlers
21
+ */
22
+ const useTextInputAccessibility = (options = {}) => {
23
+ const {
24
+ defaultValue = '',
25
+ onChange,
26
+ required = false,
27
+ maxLength,
28
+ minLength,
29
+ type = 'text'
30
+ } = options;
31
+ const [value, setValue] = (0, _react.useState)(defaultValue);
32
+ const [isValid, setIsValid] = (0, _react.useState)(!required || defaultValue.length > 0);
33
+ const [hasBeenTouched, setHasBeenTouched] = (0, _react.useState)(false);
34
+ const inputRef = (0, _react.useRef)(null);
35
+ const validateInput = (0, _react.useCallback)((inputValue, inputType) => {
36
+ let valid = true;
37
+ if (required && !(0, _inputValidation.validateRequired)(inputValue)) valid = false;
38
+ if (!(0, _inputValidation.validateLength)(inputValue, minLength, maxLength)) valid = false;
39
+ switch (inputType) {
40
+ case 'email':
41
+ if (inputValue && !(0, _inputValidation.validateEmail)(inputValue)) valid = false;
42
+ break;
43
+ case 'tel':
44
+ if (inputValue && !(0, _inputValidation.validatePhone)(inputValue)) valid = false;
45
+ break;
46
+ case 'url':
47
+ try {
48
+ if (inputValue && !(inputValue.startsWith('http://') || inputValue.startsWith('https://'))) {
49
+ valid = false;
50
+ }
51
+ } catch (e) {
52
+ valid = false;
53
+ }
54
+ break;
55
+ default:
56
+ break;
57
+ }
58
+ return valid;
59
+ }, [type]);
60
+ const handleFocus = (0, _react.useCallback)(event => {
61
+ setHasBeenTouched(true);
62
+ }, []);
63
+ const handleBlur = (0, _react.useCallback)(event => {
64
+ const newValue = event.target.value;
65
+ const valid = validateInput(newValue, type);
66
+ setIsValid(valid);
67
+ }, [type, validateInput]);
68
+ const reset = (0, _react.useCallback)(() => {
69
+ setValue(defaultValue);
70
+ setIsValid(!required || defaultValue.length > 0);
71
+ setHasBeenTouched(false);
72
+ }, [defaultValue, required]);
73
+ const focus = (0, _react.useCallback)(() => {
74
+ inputRef.current?.focus();
75
+ }, []);
76
+
77
+ // Unified onChange handler for input/textarea
78
+ const handleChange = (0, _react.useCallback)(event => {
79
+ const newValue = event.target.value;
80
+ setValue(newValue);
81
+ if (onChange) onChange(newValue);
82
+ }, [onChange]);
83
+ return {
84
+ // State
85
+ value,
86
+ isValid,
87
+ hasBeenTouched,
88
+ showError: hasBeenTouched && !isValid,
89
+ // Handlers
90
+ handleFocus,
91
+ handleBlur,
92
+ handleChange,
93
+ reset,
94
+ focus,
95
+ // Ref
96
+ inputRef,
97
+ // Computed properties
98
+ charCount: value.length,
99
+ remainingChars: maxLength ? maxLength - value.length : null,
100
+ isAtMaxLength: maxLength ? value.length >= maxLength : false,
101
+ isAtMinLength: minLength ? value.length >= minLength : true
102
+ };
103
+ };
104
+
105
+ /**
106
+ * Validates text input accessibility compliance
107
+ * Checks for proper ARIA attributes, labels, and keyboard support
108
+ *
109
+ * @param {HTMLElement} inputElement - The input element to validate
110
+ * @returns {Object} Validation results with errors and warnings
111
+ */
112
+ exports.useTextInputAccessibility = useTextInputAccessibility;
113
+ const validateTextInputAccessibility = inputElement => {
114
+ const results = {
115
+ passed: true,
116
+ errors: [],
117
+ warnings: [],
118
+ info: []
119
+ };
120
+ if (!inputElement) {
121
+ results.passed = false;
122
+ results.errors.push('No input element provided for validation');
123
+ return results;
124
+ }
125
+
126
+ // Check if it's actually an input element
127
+ if (inputElement.tagName.toLowerCase() !== 'input') {
128
+ const input = inputElement.querySelector('input');
129
+ if (!input) {
130
+ results.passed = false;
131
+ results.errors.push('Element is not an input and contains no input element');
132
+ return results;
133
+ }
134
+ inputElement = input;
135
+ }
136
+
137
+ // 1. Check for accessible name
138
+ const hasLabel = inputElement.hasAttribute('aria-label') || inputElement.hasAttribute('aria-labelledby') || inputElement.labels?.length > 0;
139
+ if (!hasLabel) {
140
+ results.passed = false;
141
+ results.errors.push('Input must have an accessible name (label, aria-label, or aria-labelledby)');
142
+ }
143
+
144
+ // 2. Check for proper ID if labelledby is used
145
+ if (inputElement.hasAttribute('aria-labelledby')) {
146
+ const labelId = inputElement.getAttribute('aria-labelledby');
147
+ const labelElement = document.getElementById(labelId);
148
+ if (!labelElement) {
149
+ results.passed = false;
150
+ results.errors.push(`Referenced label element with ID "${labelId}" not found`);
151
+ }
152
+ }
153
+
154
+ // 3. Check for required field indication
155
+ if (inputElement.hasAttribute('required') || inputElement.hasAttribute('aria-required')) {
156
+ if (!inputElement.hasAttribute('aria-required')) {
157
+ results.warnings.push('Consider adding aria-required="true" for screen reader users');
158
+ }
159
+ }
160
+
161
+ // 4. Check for error state accessibility
162
+ if (inputElement.hasAttribute('aria-invalid')) {
163
+ const isInvalid = inputElement.getAttribute('aria-invalid') === 'true';
164
+ if (isInvalid && !inputElement.hasAttribute('aria-describedby')) {
165
+ results.warnings.push('Invalid input should have aria-describedby pointing to error message');
166
+ }
167
+ }
168
+
169
+ // 5. Check input type appropriateness
170
+ const inputType = inputElement.getAttribute('type') || 'text';
171
+ if (inputType === 'text' && inputElement.getAttribute('name')) {
172
+ const name = inputElement.getAttribute('name').toLowerCase();
173
+ if (name.includes('email')) {
174
+ results.warnings.push('Consider using type="email" for email inputs');
175
+ } else if (name.includes('phone') || name.includes('tel')) {
176
+ results.warnings.push('Consider using type="tel" for phone inputs');
177
+ } else if (name.includes('url') || name.includes('website')) {
178
+ results.warnings.push('Consider using type="url" for URL inputs');
179
+ }
180
+ }
181
+
182
+ // 6. Check for placeholder accessibility
183
+ if (inputElement.hasAttribute('placeholder')) {
184
+ results.warnings.push('Avoid using placeholder as the only label - ensure proper labeling exists');
185
+ }
186
+
187
+ // 7. Check for disabled state
188
+ if (inputElement.disabled) {
189
+ results.info.push('Input is disabled');
190
+ }
191
+
192
+ // 8. Check for keyboard accessibility
193
+ const tabIndex = inputElement.getAttribute('tabindex');
194
+ if (tabIndex && parseInt(tabIndex) < 0) {
195
+ results.warnings.push('Input has negative tabindex - may not be keyboard accessible');
196
+ }
197
+
198
+ // 9. Check autocomplete attributes
199
+ const autocomplete = inputElement.getAttribute('autocomplete');
200
+ if (!autocomplete && ['email', 'tel', 'url'].includes(inputType)) {
201
+ results.info.push(`Consider adding autocomplete attribute for ${inputType} inputs`);
202
+ }
203
+ if (results.errors.length === 0) {
204
+ results.info.push('Basic accessibility validation passed');
205
+ }
206
+ return results;
207
+ };
208
+
209
+ /**
210
+ * Input validation utility for common input types
211
+ * Provides validation messages and patterns
212
+ *
213
+ * @param {string} value - Current input value
214
+ * @param {string} type - Input type
215
+ * @param {Object} options - Validation options
216
+ * @returns {Object} Validation result with message and validity
217
+ */
218
+ exports.validateTextInputAccessibility = validateTextInputAccessibility;
219
+ const getInputValidationInfo = (value, type, options = {}) => {
220
+ const {
221
+ required = false,
222
+ minLength,
223
+ maxLength
224
+ } = options;
225
+ let isValid = true;
226
+ let message = '';
227
+ let status = 'valid';
228
+
229
+ // Required validation
230
+ if (required && !value.trim()) {
231
+ isValid = false;
232
+ message = 'This field is required';
233
+ status = 'error';
234
+ return {
235
+ isValid,
236
+ message,
237
+ status
238
+ };
239
+ }
240
+
241
+ // Length validation
242
+ if (minLength && value.length < minLength) {
243
+ isValid = false;
244
+ message = `Minimum ${minLength} characters required`;
245
+ status = 'error';
246
+ } else if (maxLength && value.length > maxLength) {
247
+ isValid = false;
248
+ message = `Maximum ${maxLength} characters allowed`;
249
+ status = 'error';
250
+ }
251
+
252
+ // Type-specific validation
253
+ if (value && isValid) {
254
+ switch (type) {
255
+ case 'email':
256
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
257
+ if (!emailRegex.test(value)) {
258
+ isValid = false;
259
+ message = 'Please enter a valid email address';
260
+ status = 'error';
261
+ }
262
+ break;
263
+ case 'tel':
264
+ const phoneRegex = /^[\+]?[\s\-\(\)]?[\d\s\-\(\)]{10,}$/;
265
+ if (!phoneRegex.test(value)) {
266
+ isValid = false;
267
+ message = 'Please enter a valid phone number';
268
+ status = 'error';
269
+ }
270
+ break;
271
+ case 'url':
272
+ if (!value.startsWith('http://') && !value.startsWith('https://')) {
273
+ isValid = false;
274
+ message = 'Please enter a valid URL (starting with http:// or https://)';
275
+ status = 'error';
276
+ }
277
+ break;
278
+ case 'number':
279
+ if (isNaN(Number(value))) {
280
+ isValid = false;
281
+ message = 'Please enter a valid number';
282
+ status = 'error';
283
+ }
284
+ break;
285
+ }
286
+ }
287
+ return {
288
+ isValid,
289
+ message,
290
+ status,
291
+ charCount: value.length,
292
+ remainingChars: maxLength ? maxLength - value.length : null
293
+ };
294
+ };
295
+ exports.getInputValidationInfo = getInputValidationInfo;