@afixt/test-utils 1.0.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.
- package/.editorconfig +13 -0
- package/.eslintrc +78 -0
- package/.gitattributes +5 -0
- package/.nvmrc +1 -0
- package/CLAUDE.md +33 -0
- package/README.md +72 -0
- package/docs/arrayUtils.js.html +69 -0
- package/docs/data/search.json +1 -0
- package/docs/domUtils.js.html +182 -0
- package/docs/fonts/Inconsolata-Regular.ttf +0 -0
- package/docs/fonts/OpenSans-Regular.ttf +0 -0
- package/docs/fonts/WorkSans-Bold.ttf +0 -0
- package/docs/getAccessibleName.js.html +456 -0
- package/docs/getAccessibleText.js.html +65 -0
- package/docs/getAriaAttributesByElement.js.html +22 -0
- package/docs/getCSSGeneratedContent.js.html +62 -0
- package/docs/getComputedRole.js.html +172 -0
- package/docs/getFocusableElements.js.html +29 -0
- package/docs/getGeneratedContent.js.html +18 -0
- package/docs/getImageText.js.html +28 -0
- package/docs/getStyleObject.js.html +48 -0
- package/docs/global.html +3 -0
- package/docs/hasAccessibleName.js.html +30 -0
- package/docs/hasAttribute.js.html +18 -0
- package/docs/hasCSSGeneratedContent.js.html +23 -0
- package/docs/hasHiddenParent.js.html +32 -0
- package/docs/hasParent.js.html +57 -0
- package/docs/hasValidAriaAttributes.js.html +33 -0
- package/docs/hasValidAriaRole.js.html +32 -0
- package/docs/index.html +3 -0
- package/docs/index.js.html +66 -0
- package/docs/isAriaAttributesValid.js.html +76 -0
- package/docs/isComplexTable.js.html +112 -0
- package/docs/isDataTable.js.html +241 -0
- package/docs/isFocusable.js.html +37 -0
- package/docs/isHidden.js.html +20 -0
- package/docs/isOffScreen.js.html +19 -0
- package/docs/isValidUrl.js.html +16 -0
- package/docs/isVisible.js.html +65 -0
- package/docs/module-afixt-test-utils.html +3 -0
- package/docs/scripts/core.js +726 -0
- package/docs/scripts/core.min.js +23 -0
- package/docs/scripts/resize.js +90 -0
- package/docs/scripts/search.js +265 -0
- package/docs/scripts/search.min.js +6 -0
- package/docs/scripts/third-party/Apache-License-2.0.txt +202 -0
- package/docs/scripts/third-party/fuse.js +9 -0
- package/docs/scripts/third-party/hljs-line-num-original.js +369 -0
- package/docs/scripts/third-party/hljs-line-num.js +1 -0
- package/docs/scripts/third-party/hljs-original.js +5171 -0
- package/docs/scripts/third-party/hljs.js +1 -0
- package/docs/scripts/third-party/popper.js +5 -0
- package/docs/scripts/third-party/tippy.js +1 -0
- package/docs/scripts/third-party/tocbot.js +672 -0
- package/docs/scripts/third-party/tocbot.min.js +1 -0
- package/docs/styles/clean-jsdoc-theme-base.css +1159 -0
- package/docs/styles/clean-jsdoc-theme-dark.css +412 -0
- package/docs/styles/clean-jsdoc-theme-light.css +482 -0
- package/docs/styles/clean-jsdoc-theme-scrollbar.css +30 -0
- package/docs/styles/clean-jsdoc-theme-without-scrollbar.min.css +1 -0
- package/docs/styles/clean-jsdoc-theme.min.css +1 -0
- package/docs/testContrast.js.html +236 -0
- package/docs/testLang.js.html +578 -0
- package/docs/testOrder.js.html +93 -0
- package/jsdoc.json +67 -0
- package/package.json +32 -0
- package/src/arrayUtils.js +67 -0
- package/src/domUtils.js +179 -0
- package/src/getAccessibleName.js +454 -0
- package/src/getAccessibleText.js +63 -0
- package/src/getAriaAttributesByElement.js +19 -0
- package/src/getCSSGeneratedContent.js +60 -0
- package/src/getComputedRole.js +169 -0
- package/src/getFocusableElements.js +26 -0
- package/src/getGeneratedContent.js +15 -0
- package/src/getImageText.js +25 -0
- package/src/getStyleObject.js +45 -0
- package/src/hasAccessibleName.js +28 -0
- package/src/hasAttribute.js +15 -0
- package/src/hasCSSGeneratedContent.js +20 -0
- package/src/hasHiddenParent.js +29 -0
- package/src/hasParent.js +54 -0
- package/src/hasValidAriaAttributes.js +30 -0
- package/src/hasValidAriaRole.js +29 -0
- package/src/index.js +64 -0
- package/src/interactiveRoles.js +20 -0
- package/src/isAriaAttributesValid.js +74 -0
- package/src/isComplexTable.js +109 -0
- package/src/isDataTable.js +239 -0
- package/src/isFocusable.js +34 -0
- package/src/isHidden.js +17 -0
- package/src/isOffScreen.js +16 -0
- package/src/isValidUrl.js +13 -0
- package/src/isVisible.js +62 -0
- package/src/stringUtils.js +150 -0
- package/src/testContrast.js +233 -0
- package/src/testLang.js +575 -0
- package/src/testOrder.js +90 -0
- package/test/_template.test.js +21 -0
- package/test/arrayUtils.test.js +84 -0
- package/test/domUtils.test.js +147 -0
- package/test/generate-test-stubs.js +37 -0
- package/test/getAccessibleName.test.js +113 -0
- package/test/getAccessibleText.test.js +94 -0
- package/test/getAriaAttributesByElement.test.js +112 -0
- package/test/getCSSGeneratedContent.test.js +102 -0
- package/test/getComputedRole.test.js +180 -0
- package/test/getFocusableElements.test.js +134 -0
- package/test/getGeneratedContent.test.js +321 -0
- package/test/getImageText.test.js +21 -0
- package/test/getStyleObject.test.js +134 -0
- package/test/hasAccessibleName.test.js +59 -0
- package/test/hasAttribute.test.js +132 -0
- package/test/hasCSSGeneratedContent.test.js +143 -0
- package/test/hasHiddenParent.test.js +176 -0
- package/test/hasParent.test.js +266 -0
- package/test/hasValidAriaAttributes.test.js +79 -0
- package/test/hasValidAriaRole.test.js +98 -0
- package/test/isAriaAttributesValid.test.js +83 -0
- package/test/isComplexTable.test.js +363 -0
- package/test/isDataTable.test.js +948 -0
- package/test/isFocusable.test.js +182 -0
- package/test/isHidden.test.js +157 -0
- package/test/isOffScreen.test.js +249 -0
- package/test/isValidUrl.test.js +63 -0
- package/test/isVisible.test.js +104 -0
- package/test/setup.js +11 -0
- package/test/stringUtils.test.js +106 -0
- package/test/testContrast.test.js +77 -0
- package/test/testLang.test.js +21 -0
- package/test/testOrder.test.js +157 -0
- package/vitest.config.js +25 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* Checks if the given table element has multiple rows in the header.
|
|
4
|
+
*
|
|
5
|
+
* @param {HTMLElement} el - The table element to check.
|
|
6
|
+
* @returns {boolean} True if the table has more than one row in the header, otherwise false.
|
|
7
|
+
*/
|
|
8
|
+
export function checkMultiRowsInHeader(el) {
|
|
9
|
+
return el.querySelectorAll("thead tr").length > 1;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Checks if the given table element has multiple rows with at least
|
|
14
|
+
* one cell that has a colspan attribute.
|
|
15
|
+
*
|
|
16
|
+
* @param {HTMLElement} el - The table element to check.
|
|
17
|
+
* @returns {boolean} - Returns true if there are more than one row
|
|
18
|
+
* with at least one cell having a colspan attribute, otherwise false.
|
|
19
|
+
*/
|
|
20
|
+
export function checkMultiRowsWithColspan(el) {
|
|
21
|
+
let rowsWithColspanCount = 0;
|
|
22
|
+
el.querySelectorAll("tr").forEach((row) => {
|
|
23
|
+
if (row.querySelectorAll("td[colspan]").length >= 1) {
|
|
24
|
+
rowsWithColspanCount++;
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
return rowsWithColspanCount > 1;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Checks if a table element has inconsistent row or column spans.
|
|
32
|
+
*
|
|
33
|
+
* This function normalizes the table by removing `colspan` and `rowspan` attributes,
|
|
34
|
+
* and then checks if the number of columns is consistent across all rows.
|
|
35
|
+
*
|
|
36
|
+
* @param {HTMLElement} el - The table element to check for inconsistencies.
|
|
37
|
+
* @returns {boolean} - Returns `true` if the table has inconsistent
|
|
38
|
+
* row or column spans, otherwise `false`.
|
|
39
|
+
*/
|
|
40
|
+
export function checkInconsistent(el) {
|
|
41
|
+
let result = false;
|
|
42
|
+
let clone = el.cloneNode(true);
|
|
43
|
+
|
|
44
|
+
// Normalize the table by removing colspan and cloning cells with colspan
|
|
45
|
+
clone.querySelectorAll("th[colspan], td[colspan]").forEach((cell) => {
|
|
46
|
+
let count = parseInt(cell.getAttribute("colspan")) - 1;
|
|
47
|
+
cell.removeAttribute("colspan");
|
|
48
|
+
while (count > 0) {
|
|
49
|
+
let newCell = cell.cloneNode(true);
|
|
50
|
+
cell.parentNode.insertBefore(newCell, cell.nextSibling);
|
|
51
|
+
count--;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Normalize the table by removing rowspan and appending empty td
|
|
56
|
+
// elements in rows below
|
|
57
|
+
clone.querySelectorAll("th[rowspan], td[rowspan]").forEach((cell) => {
|
|
58
|
+
let row = cell.parentNode;
|
|
59
|
+
let count = parseInt(cell.getAttribute("rowspan")) - 1;
|
|
60
|
+
cell.removeAttribute("rowspan");
|
|
61
|
+
while (count > 0) {
|
|
62
|
+
if (!row.nextElementSibling) {
|
|
63
|
+
let newRow = document.createElement("tr");
|
|
64
|
+
row.parentNode.appendChild(newRow);
|
|
65
|
+
row = newRow;
|
|
66
|
+
} else {
|
|
67
|
+
row = row.nextElementSibling;
|
|
68
|
+
}
|
|
69
|
+
let emptyTd = document.createElement("td");
|
|
70
|
+
row.appendChild(emptyTd);
|
|
71
|
+
count--;
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// We just count columns. It is sufficient because inconsistent
|
|
76
|
+
// rowspan would cause unequal number of columns in the following rows
|
|
77
|
+
let prevNumberCols;
|
|
78
|
+
clone.querySelectorAll("tr").forEach((row) => {
|
|
79
|
+
let numCols = row.querySelectorAll("td, th").length;
|
|
80
|
+
if (prevNumberCols === undefined) {
|
|
81
|
+
prevNumberCols = numCols;
|
|
82
|
+
} else if (prevNumberCols !== numCols) {
|
|
83
|
+
result = true;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Determines if a given table element is complex.
|
|
91
|
+
* A table is considered complex if it has multiple rows in the header,
|
|
92
|
+
* rows with colspan attributes, or inconsistent structure.
|
|
93
|
+
*
|
|
94
|
+
* @param {HTMLElement} el - The table element to check.
|
|
95
|
+
* @returns {boolean} - Returns true if the table is complex, otherwise false.
|
|
96
|
+
*/
|
|
97
|
+
export function isComplexTable(el) {
|
|
98
|
+
if (!el) return true;
|
|
99
|
+
let clone = el.cloneNode(true);
|
|
100
|
+
|
|
101
|
+
// Remove nested tables
|
|
102
|
+
clone.querySelectorAll("table").forEach((table) => table.remove());
|
|
103
|
+
|
|
104
|
+
let multiRowsInHeader = checkMultiRowsInHeader(clone);
|
|
105
|
+
let multiRowsWithColspan = checkMultiRowsWithColspan(clone);
|
|
106
|
+
let inconsistent = checkInconsistent(clone);
|
|
107
|
+
|
|
108
|
+
return multiRowsInHeader || inconsistent || multiRowsWithColspan;
|
|
109
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
// Import using object destructuring for better compatibility with tests
|
|
2
|
+
import { arrayCount } from './arrayUtils.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns the number of rows in the given table element.
|
|
6
|
+
*
|
|
7
|
+
* @param {HTMLTableElement} table - The table element to count rows in.
|
|
8
|
+
* @returns {number} The number of rows in the table.
|
|
9
|
+
*/
|
|
10
|
+
function rowCount(table) {
|
|
11
|
+
return table.querySelectorAll('tr').length;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Counts the number of cells (both <td> and <th>) in a given table element.
|
|
16
|
+
*
|
|
17
|
+
* @param {HTMLTableElement} table - The table element to count cells in.
|
|
18
|
+
* @returns {number} The total number of cells in the table.
|
|
19
|
+
*/
|
|
20
|
+
function cellCount(table) {
|
|
21
|
+
return table.querySelectorAll('td, th').length;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Calculates the percentage of table cells that have a border.
|
|
26
|
+
*
|
|
27
|
+
* @param {HTMLTableElement} table - The table element to analyze.
|
|
28
|
+
* @returns {number} The percentage of table cells that have a border.
|
|
29
|
+
*/
|
|
30
|
+
function countBordersPct(table) {
|
|
31
|
+
const cells = table.querySelectorAll('td, th');
|
|
32
|
+
const totalCells = cells.length;
|
|
33
|
+
if (totalCells === 0) return 0;
|
|
34
|
+
|
|
35
|
+
let borderedCells = 0;
|
|
36
|
+
|
|
37
|
+
cells.forEach(cell => {
|
|
38
|
+
if (window.getComputedStyle(cell).borderWidth !== '0px') {
|
|
39
|
+
borderedCells++;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return (borderedCells / totalCells) * 100;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Calculates the average number of columns in a given HTML table.
|
|
48
|
+
*
|
|
49
|
+
* @param {HTMLTableElement} table - The table element to analyze.
|
|
50
|
+
* @returns {number} The average number of columns per row in the table.
|
|
51
|
+
*/
|
|
52
|
+
function colCount(table) {
|
|
53
|
+
const rows = table.querySelectorAll('tr');
|
|
54
|
+
const rowCnt = rows.length;
|
|
55
|
+
if (rowCnt === 0) return 0;
|
|
56
|
+
|
|
57
|
+
let totalColumns = 0;
|
|
58
|
+
|
|
59
|
+
rows.forEach(row => {
|
|
60
|
+
totalColumns += row.cells.length;
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return Math.round(totalColumns / rowCnt);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Checks if the color differences in the table cells exceed the specified limits.
|
|
68
|
+
*
|
|
69
|
+
* @param {HTMLTableElement} table - The table element to check.
|
|
70
|
+
* @param {number} maxColors - The maximum number of unique colors allowed.
|
|
71
|
+
* @param {number} maxDiffs - The maximum percentage difference allowed for any color.
|
|
72
|
+
* @returns {boolean} - Returns true if the color differences exceed the specified limits, otherwise false.
|
|
73
|
+
*/
|
|
74
|
+
function cellColorDiffs(table, maxColors, maxDiffs) {
|
|
75
|
+
const colors = [];
|
|
76
|
+
const cells = table.querySelectorAll('td, th');
|
|
77
|
+
|
|
78
|
+
if (cells.length === 0) return false;
|
|
79
|
+
|
|
80
|
+
cells.forEach(cell => {
|
|
81
|
+
colors.push(window.getComputedStyle(cell).backgroundColor);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const colorObj = arrayCount(colors);
|
|
85
|
+
// Check if colorObj is undefined or null before using Object.keys
|
|
86
|
+
if (!colorObj) return false;
|
|
87
|
+
|
|
88
|
+
const uniqueColors = Object.keys(colorObj).length;
|
|
89
|
+
|
|
90
|
+
if (uniqueColors > 1) {
|
|
91
|
+
if (uniqueColors > maxColors) {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
return Object.values(colorObj).some(value => {
|
|
95
|
+
return (value / uniqueColors) * 100 > maxDiffs;
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Export the utility functions
|
|
102
|
+
export { rowCount, cellCount, countBordersPct, colCount, cellColorDiffs };
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Determines if a given table element is a data table based on various heuristics and options.
|
|
106
|
+
*
|
|
107
|
+
* @param {HTMLTableElement} table - The table element to evaluate.
|
|
108
|
+
* @param {Object} [options={}] - Optional settings to override default heuristics.
|
|
109
|
+
* @param {number} [options.numDTColumns=5] - Minimum number of columns to consider as a data table.
|
|
110
|
+
* @param {number} [options.pctCellsWBorder=33] - Minimum percentage of cells with borders to consider as a data table.
|
|
111
|
+
* @param {number} [options.numDTRows=20] - Minimum number of rows to consider as a data table.
|
|
112
|
+
* @param {number} [options.maxWidthLT=95] - Maximum width percentage of the viewport to consider as a data table.
|
|
113
|
+
* @param {number} [options.minCellsLT=10] - Minimum number of cells to consider as a data table.
|
|
114
|
+
* @param {number} [options.maxCellColors=3] - Maximum number of different cell colors to consider as a data table.
|
|
115
|
+
* @param {number} [options.maxColorDiffs=30] - Maximum number of color differences to consider as a data table.
|
|
116
|
+
* @returns {boolean} - Returns true if the table is considered a data table, otherwise false.
|
|
117
|
+
*/
|
|
118
|
+
function isDataTable(table, options = {}) {
|
|
119
|
+
const defaults = {
|
|
120
|
+
numDTColumns: 5,
|
|
121
|
+
pctCellsWBorder: 33,
|
|
122
|
+
numDTRows: 20,
|
|
123
|
+
maxWidthLT: 95,
|
|
124
|
+
minCellsLT: 10,
|
|
125
|
+
maxCellColors: 3,
|
|
126
|
+
maxColorDiffs: 30
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const settings = { ...defaults, ...options };
|
|
130
|
+
|
|
131
|
+
if (!table || table.tagName.toLowerCase() !== 'table') {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
if (table.closest && table.closest("[contenteditable='true'], [contenteditable='inherit']")) {
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (table.getAttribute && table.getAttribute('role') === 'presentation') {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (table.getAttribute && ['grid', 'treegrid'].includes(table.getAttribute('role'))) {
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const roleChildren = table.querySelectorAll && table.querySelectorAll('th[role="gridcell"], td[role="rowheader"], td[role="columnheader"]');
|
|
149
|
+
if (roleChildren && roleChildren.length > 0) {
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (table.querySelector && table.querySelector('tr[role="row"]')) {
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (table.querySelector && table.querySelector('*[role="rowgroup"]')) {
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (table.hasAttribute && table.hasAttribute('datatable') && table.getAttribute('datatable') === '0') {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (table.hasAttribute && table.hasAttribute('datatable') && table.getAttribute('datatable') === '1') {
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Check for table elements that indicate it's a data table
|
|
170
|
+
if (table.hasAttribute && (table.hasAttribute('summary') || table.hasAttribute('rules'))) {
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Look for caption
|
|
175
|
+
if (table.querySelector && table.querySelector('caption')) {
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Look for other data table indicators
|
|
180
|
+
if (table.querySelector && table.querySelector('col, colgroup, tfoot, thead, th, td[headers], td[scope], td[abbr], td[axis]')) {
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (table.querySelector && table.querySelector('table')) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const rows = rowCount(table);
|
|
189
|
+
const cells = cellCount(table);
|
|
190
|
+
|
|
191
|
+
if (rows === 1 || cells === 1) {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (colCount(table) >= settings.numDTColumns) {
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (table.hasAttribute && table.hasAttribute('border') && table.getAttribute('border') !== '0') {
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (countBordersPct(table) > settings.pctCellsWBorder) {
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
if (cellColorDiffs(table, settings.maxCellColors, settings.maxColorDiffs)) {
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
} catch (e) {
|
|
212
|
+
// Safely ignore errors in color difference calculation
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (rows >= settings.numDTRows) {
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Check if the table is too wide compared to the viewport
|
|
220
|
+
if (table.offsetWidth && document.documentElement && document.documentElement.clientWidth) {
|
|
221
|
+
const viewportWidth = document.documentElement.clientWidth;
|
|
222
|
+
if ((table.offsetWidth / viewportWidth) * 100 > settings.maxWidthLT) {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (cells <= settings.minCellsLT) {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (table.querySelector && table.querySelector('embed, object, applet, iframe')) {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Export the main function
|
|
239
|
+
export { isDataTable };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Determines if an element is focusable.
|
|
3
|
+
* @param {Element} element - The HTML element to check.
|
|
4
|
+
* @returns {boolean} - Returns true if the element is focusable, otherwise false.
|
|
5
|
+
*/
|
|
6
|
+
export function isFocusable(element) {
|
|
7
|
+
if (!element) return false;
|
|
8
|
+
|
|
9
|
+
const nodeName = element.nodeName.toLowerCase();
|
|
10
|
+
const tabIndex = element.getAttribute("tabindex");
|
|
11
|
+
|
|
12
|
+
// The element and all of its ancestors must be visible
|
|
13
|
+
if (element.closest(":hidden")) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// If tabindex is defined, its value must be greater than or equal to 0
|
|
18
|
+
if (!isNaN(tabIndex) && tabIndex < 0) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// If the element is a standard form control, it must not be disabled
|
|
23
|
+
if (/input|select|textarea|button|object/.test(nodeName)) {
|
|
24
|
+
return !element.disabled;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// If the element is a link, href must be defined
|
|
28
|
+
if (nodeName === "a" || nodeName === "area") {
|
|
29
|
+
return element.hasAttribute("href");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// This is some other page element that is not normally focusable
|
|
33
|
+
return false;
|
|
34
|
+
}
|
package/src/isHidden.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* Checks if a given DOM element is hidden.
|
|
4
|
+
*
|
|
5
|
+
* An element is considered hidden if its `display` style is set to "none"
|
|
6
|
+
* or if it has the `hidden` attribute.
|
|
7
|
+
*
|
|
8
|
+
* @param {HTMLElement} element - The DOM element to check.
|
|
9
|
+
* @returns {boolean} - Returns `true` if the element is hidden, otherwise `false`.
|
|
10
|
+
*/
|
|
11
|
+
const isHidden = (element) => {
|
|
12
|
+
return (
|
|
13
|
+
element.style.display === "none" || element.hasAttribute("hidden")
|
|
14
|
+
);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export default isHidden;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks if the selected element is off-screen.
|
|
3
|
+
* @param {Element} element - The element to check.
|
|
4
|
+
* @returns {boolean} - True if the element is off-screen, otherwise false.
|
|
5
|
+
*/
|
|
6
|
+
export function isOffScreen(element) {
|
|
7
|
+
if (!element) return false;
|
|
8
|
+
|
|
9
|
+
const rect = element.getBoundingClientRect();
|
|
10
|
+
return (
|
|
11
|
+
rect.right < 0 ||
|
|
12
|
+
rect.bottom < 0 ||
|
|
13
|
+
rect.left > window.innerWidth ||
|
|
14
|
+
rect.top > window.innerHeight
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate if a string is a valid URL.
|
|
3
|
+
* @param {string} str - The string to validate.
|
|
4
|
+
* @returns {boolean} - Returns true if the string is a valid URL, otherwise false.
|
|
5
|
+
*/
|
|
6
|
+
export function isValidUrl(str) {
|
|
7
|
+
try {
|
|
8
|
+
new URL(str);
|
|
9
|
+
return true;
|
|
10
|
+
} catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
package/src/isVisible.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks if the selected item is visible to assistive technologies.
|
|
3
|
+
* @param {Element} element - The element to check.
|
|
4
|
+
* @param {boolean} strict - Option to be more strict about visibility of aria-hidden="true" in addition to CSS display: none.
|
|
5
|
+
* @returns {boolean}
|
|
6
|
+
*/
|
|
7
|
+
export function isVisible(element, strict = false) {
|
|
8
|
+
if (!element) return false;
|
|
9
|
+
|
|
10
|
+
const id = element.id;
|
|
11
|
+
let visible = true;
|
|
12
|
+
|
|
13
|
+
// These elements are inherently not visible
|
|
14
|
+
const nonVisibleSelectors = [
|
|
15
|
+
'base', 'head', 'meta', 'title', 'link', 'style', 'script', 'br', 'nobr', 'col', 'embed',
|
|
16
|
+
'input[type="hidden"]', 'keygen', 'source', 'track', 'wbr', 'datalist', 'area', 'param', 'noframes', 'ruby > rp'
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
if (nonVisibleSelectors.some(selector => element.matches(selector))) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const optionalAriaHidden = (el, strictCheck) => strictCheck && el.getAttribute('aria-hidden') === 'true';
|
|
24
|
+
|
|
25
|
+
const isElemDisplayed = el => window.getComputedStyle(el).display === 'none';
|
|
26
|
+
|
|
27
|
+
const isHidden = () => {
|
|
28
|
+
if (isElemDisplayed(element)) return true;
|
|
29
|
+
|
|
30
|
+
let parent = element.parentElement;
|
|
31
|
+
while (parent) {
|
|
32
|
+
if (isElemDisplayed(parent)) return true;
|
|
33
|
+
parent = parent.parentElement;
|
|
34
|
+
}
|
|
35
|
+
return optionalAriaHidden(element, strict);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
if (isHidden()) {
|
|
39
|
+
visible = false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Check if element is referenced by aria-labelledby or aria-describedby
|
|
43
|
+
document.querySelectorAll(`*[aria-labelledby~="${id}"], *[aria-describedby~="${id}"]`).forEach(referencingElement => {
|
|
44
|
+
if (window.getComputedStyle(referencingElement).display !== 'none') {
|
|
45
|
+
visible = true;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Check if any parent has aria-hidden="true" when strict mode is on
|
|
50
|
+
if (visible && strict) {
|
|
51
|
+
let parent = element.parentElement;
|
|
52
|
+
while (parent) {
|
|
53
|
+
if (optionalAriaHidden(parent, strict)) {
|
|
54
|
+
visible = false;
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
parent = parent.parentElement;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return visible;
|
|
62
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
const stringUtils = (function () {
|
|
2
|
+
/**
|
|
3
|
+
* Check if a string is empty or only contains whitespace.
|
|
4
|
+
* @param {string} str - The string to check.
|
|
5
|
+
* @returns {boolean} Whether the string is empty.
|
|
6
|
+
*/
|
|
7
|
+
function isEmpty(str) {
|
|
8
|
+
return !str || str.trim().length === 0;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Check if a value is a string.
|
|
13
|
+
* @param {*} str - The value to check.
|
|
14
|
+
* @returns {boolean} Whether the value is a string.
|
|
15
|
+
*/
|
|
16
|
+
function isString(str) {
|
|
17
|
+
return typeof str === "string" || str instanceof String;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get the length of a trimmed string, or return 0 if not a valid string.
|
|
22
|
+
* @param {string} str - The string to measure.
|
|
23
|
+
* @returns {number} The string length or 0.
|
|
24
|
+
*/
|
|
25
|
+
function strlen(str) {
|
|
26
|
+
return isString(str) && !isEmpty(str.trim()) ? str.trim().length : 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Utility function to check if the given string represents a positive integer
|
|
31
|
+
* @param {String} str to check
|
|
32
|
+
* @returns {boolean}
|
|
33
|
+
*/
|
|
34
|
+
function isNormalInteger(str) {
|
|
35
|
+
const n = ~~Number(str);
|
|
36
|
+
return String(n) === str && n >= 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Determines if a string of text is completely uppercase. Allows (ignores) whitespace, numerics, and punctuation
|
|
41
|
+
* @param {String} str to check
|
|
42
|
+
* @returns {boolean}
|
|
43
|
+
*/
|
|
44
|
+
function isUpperCase(str) {
|
|
45
|
+
if (str === undefined) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
let i = 0;
|
|
49
|
+
let char;
|
|
50
|
+
let isUpper = true;
|
|
51
|
+
|
|
52
|
+
str = str.replace(/[^\w\s]|_/g, "").replace(/\s+/g, " ");
|
|
53
|
+
|
|
54
|
+
while (i <= str.length) {
|
|
55
|
+
char = str.charAt(i);
|
|
56
|
+
if (
|
|
57
|
+
char.trim() !== "" &&
|
|
58
|
+
false === (!isNaN(parseFloat(char)) && isFinite(char))
|
|
59
|
+
) {
|
|
60
|
+
if (char === char.toLowerCase()) {
|
|
61
|
+
isUpper = false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
i++;
|
|
65
|
+
}
|
|
66
|
+
return isUpper;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Does the string contain only alphanumeric characters?
|
|
71
|
+
* @param {String} str to check
|
|
72
|
+
* @returns {boolean}
|
|
73
|
+
*/
|
|
74
|
+
function isAlphaNumeric(str) {
|
|
75
|
+
const pattern = "/^[a-zA-Z0-9]+$/";
|
|
76
|
+
return pattern.test(str);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get the path from a URL
|
|
81
|
+
* @param {String} url a URL
|
|
82
|
+
* @returns {String}
|
|
83
|
+
*/
|
|
84
|
+
function getPathFromUrl(url) {
|
|
85
|
+
const str = new URL(url);
|
|
86
|
+
return str.pathname;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Extracts and concatenates all text content from a given DOM element, including text from text nodes,
|
|
91
|
+
* elements with aria-label attributes, and alt attributes of img elements.
|
|
92
|
+
*
|
|
93
|
+
* @param {Element} el - The DOM element from which to extract text.
|
|
94
|
+
* @returns {string} A string containing all concatenated text content from the element.
|
|
95
|
+
*/
|
|
96
|
+
function getAllText(el) {
|
|
97
|
+
const walker = document.createTreeWalker(
|
|
98
|
+
el,
|
|
99
|
+
NodeFilter.SHOW_ALL,
|
|
100
|
+
null,
|
|
101
|
+
false
|
|
102
|
+
);
|
|
103
|
+
const textNodes = [];
|
|
104
|
+
let node, text;
|
|
105
|
+
|
|
106
|
+
while (walker.nextNode()) {
|
|
107
|
+
node = walker.currentNode;
|
|
108
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
109
|
+
text = node.nodeValue.trim();
|
|
110
|
+
if (text) {
|
|
111
|
+
textNodes.push(text);
|
|
112
|
+
} else {
|
|
113
|
+
textNodes.push(node.textContent.trim());
|
|
114
|
+
}
|
|
115
|
+
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
|
116
|
+
if (node.hasAttribute("aria-label")) {
|
|
117
|
+
textNodes.push(node.getAttribute("aria-label"));
|
|
118
|
+
} else if (node.tagName === "IMG" && node.hasAttribute("alt")) {
|
|
119
|
+
textNodes.push(node.getAttribute("alt"));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return textNodes.join(" ");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Checks if the given element contains any text.
|
|
129
|
+
*
|
|
130
|
+
* @param {HTMLElement} element - The element to check for text content.
|
|
131
|
+
* @returns {boolean} True if the element contains text, false otherwise.
|
|
132
|
+
*/
|
|
133
|
+
function hasText(element) {
|
|
134
|
+
return getAllText(element).trim() !== "";
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
isEmpty,
|
|
139
|
+
isString,
|
|
140
|
+
strlen,
|
|
141
|
+
isNormalInteger,
|
|
142
|
+
isUpperCase,
|
|
143
|
+
isAlphaNumeric,
|
|
144
|
+
getPathFromUrl,
|
|
145
|
+
getAllText,
|
|
146
|
+
hasText
|
|
147
|
+
};
|
|
148
|
+
})();
|
|
149
|
+
|
|
150
|
+
module.exports = stringUtils;
|