govuk_publishing_components 21.16.3 → 21.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (230) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/component_guide/accessibility-test.js +0 -6
  3. data/app/views/govuk_publishing_components/components/docs/machine_readable_metadata.yml +7 -0
  4. data/lib/govuk_publishing_components/presenters/machine_readable/dataset_schema.rb +34 -0
  5. data/lib/govuk_publishing_components/presenters/schema_org.rb +3 -0
  6. data/lib/govuk_publishing_components/version.rb +1 -1
  7. data/node_modules/axe-core/CHANGELOG.md +166 -2
  8. data/node_modules/axe-core/CONTRIBUTING.md +5 -5
  9. data/node_modules/axe-core/README.md +4 -4
  10. data/node_modules/axe-core/axe.d.ts +27 -11
  11. data/node_modules/axe-core/axe.js +9597 -2431
  12. data/node_modules/axe-core/axe.min.js +2 -2
  13. data/node_modules/axe-core/bower.json +1 -1
  14. data/node_modules/axe-core/doc/API.md +211 -128
  15. data/node_modules/axe-core/doc/accessibility-supported.md +1 -1
  16. data/node_modules/axe-core/doc/aria-supported.md +4 -13
  17. data/node_modules/axe-core/doc/backwards-compatibility-doc.md +93 -0
  18. data/node_modules/axe-core/doc/code-submission-guidelines.md +4 -4
  19. data/node_modules/axe-core/doc/developer-guide.md +27 -13
  20. data/node_modules/axe-core/doc/examples/chrome-debugging-protocol/package.json +5 -2
  21. data/node_modules/axe-core/doc/examples/jasmine/README.md +3 -5
  22. data/node_modules/axe-core/doc/examples/jasmine/karma.conf.js +29 -0
  23. data/node_modules/axe-core/doc/examples/jasmine/package.json +6 -5
  24. data/node_modules/axe-core/doc/examples/jest_react/README.md +1 -1
  25. data/node_modules/axe-core/doc/examples/jest_react/link.test.js +3 -3
  26. data/node_modules/axe-core/doc/examples/jest_react/package.json +9 -9
  27. data/node_modules/axe-core/doc/examples/jsdom/package.json +15 -0
  28. data/node_modules/axe-core/doc/examples/mocha/README.md +5 -7
  29. data/node_modules/axe-core/doc/examples/mocha/karma.conf.js +29 -0
  30. data/node_modules/axe-core/doc/examples/mocha/package.json +7 -6
  31. data/node_modules/axe-core/doc/examples/phantomjs/README.md +3 -3
  32. data/node_modules/axe-core/doc/examples/phantomjs/axe-phantom.js +4 -2
  33. data/node_modules/axe-core/doc/examples/phantomjs/package.json +3 -3
  34. data/node_modules/axe-core/doc/examples/puppeteer/package.json +5 -2
  35. data/node_modules/axe-core/doc/examples/qunit/README.md +2 -2
  36. data/node_modules/axe-core/doc/examples/qunit/package.json +2 -2
  37. data/node_modules/axe-core/doc/examples/test-examples.js +32 -0
  38. data/node_modules/axe-core/doc/plugins.md +10 -10
  39. data/node_modules/axe-core/doc/projects.md +12 -8
  40. data/node_modules/axe-core/doc/rule-descriptions.md +87 -79
  41. data/node_modules/axe-core/doc/rule-development.md +30 -2
  42. data/node_modules/axe-core/lib/checks/aria/allowed-attr.js +1 -1
  43. data/node_modules/axe-core/lib/checks/aria/aria-roledescription.js +14 -0
  44. data/node_modules/axe-core/lib/checks/aria/aria-roledescription.json +23 -0
  45. data/node_modules/axe-core/lib/checks/aria/no-implicit-explicit-label.js +21 -0
  46. data/node_modules/axe-core/lib/checks/aria/no-implicit-explicit-label.json +11 -0
  47. data/node_modules/axe-core/lib/checks/aria/required-attr.js +32 -7
  48. data/node_modules/axe-core/lib/checks/aria/required-children.js +40 -14
  49. data/node_modules/axe-core/lib/checks/aria/required-children.json +12 -1
  50. data/node_modules/axe-core/lib/checks/aria/required-parent.js +1 -1
  51. data/node_modules/axe-core/lib/checks/aria/unsupportedattr.js +1 -1
  52. data/node_modules/axe-core/lib/checks/aria/valid-attr-value.js +50 -17
  53. data/node_modules/axe-core/lib/checks/aria/valid-attr-value.json +2 -1
  54. data/node_modules/axe-core/lib/checks/aria/valid-attr.js +1 -1
  55. data/node_modules/axe-core/lib/checks/color/color-contrast.js +2 -2
  56. data/node_modules/axe-core/lib/checks/forms/autocomplete-appropriate.js +4 -4
  57. data/node_modules/axe-core/lib/checks/forms/autocomplete-valid.js +1 -1
  58. data/node_modules/axe-core/lib/checks/forms/fieldset.json +1 -0
  59. data/node_modules/axe-core/lib/checks/forms/group-labelledby.json +1 -0
  60. data/node_modules/axe-core/lib/checks/keyboard/focusable-content.js +16 -0
  61. data/node_modules/axe-core/lib/checks/keyboard/focusable-content.json +11 -0
  62. data/node_modules/axe-core/lib/checks/keyboard/focusable-element.js +12 -0
  63. data/node_modules/axe-core/lib/checks/keyboard/focusable-element.json +11 -0
  64. data/node_modules/axe-core/lib/checks/keyboard/tabindex.js +6 -1
  65. data/node_modules/axe-core/lib/checks/label/alt-space-value.js +3 -2
  66. data/node_modules/axe-core/lib/checks/label/duplicate-img-label.js +18 -15
  67. data/node_modules/axe-core/lib/checks/label/label-content-name-mismatch.js +13 -3
  68. data/node_modules/axe-core/lib/checks/label/label-content-name-mismatch.json +4 -0
  69. data/node_modules/axe-core/lib/checks/label/multiple-label.js +22 -12
  70. data/node_modules/axe-core/lib/checks/label/multiple-label.json +1 -1
  71. data/node_modules/axe-core/lib/checks/landmarks/landmark-is-unique-after.js +23 -0
  72. data/node_modules/axe-core/lib/checks/landmarks/landmark-is-unique.js +7 -0
  73. data/node_modules/axe-core/lib/checks/landmarks/landmark-is-unique.json +12 -0
  74. data/node_modules/axe-core/lib/checks/lists/listitem.js +1 -0
  75. data/node_modules/axe-core/lib/checks/lists/listitem.json +1 -1
  76. data/node_modules/axe-core/lib/checks/lists/only-listitems.js +0 -4
  77. data/node_modules/axe-core/lib/checks/mobile/css-orientation-lock.js +8 -6
  78. data/node_modules/axe-core/lib/checks/navigation/region.js +2 -19
  79. data/node_modules/axe-core/lib/checks/shared/avoid-inline-spacing.js +18 -0
  80. data/node_modules/axe-core/lib/checks/shared/avoid-inline-spacing.json +11 -0
  81. data/node_modules/axe-core/lib/checks/shared/exists.js +1 -1
  82. data/node_modules/axe-core/lib/checks/shared/exists.json +1 -1
  83. data/node_modules/axe-core/lib/checks/shared/has-alt.js +6 -4
  84. data/node_modules/axe-core/lib/checks/shared/non-empty-alt.js +1 -1
  85. data/node_modules/axe-core/lib/checks/tables/caption-faked.json +1 -1
  86. data/node_modules/axe-core/lib/checks/tables/td-has-header.js +5 -4
  87. data/node_modules/axe-core/lib/checks/tables/th-has-data-cells.js +19 -29
  88. data/node_modules/axe-core/lib/commons/aria/get-owned-virtual.js +1 -1
  89. data/node_modules/axe-core/lib/commons/aria/index.js +50 -46
  90. data/node_modules/axe-core/lib/commons/aria/is-accessible-ref.js +41 -37
  91. data/node_modules/axe-core/lib/commons/aria/label-virtual.js +2 -2
  92. data/node_modules/axe-core/lib/commons/aria/roles.js +1 -1
  93. data/node_modules/axe-core/lib/commons/aria/validate-attr-value.js +0 -1
  94. data/node_modules/axe-core/lib/commons/color/center-point-of-rect.js +30 -0
  95. data/node_modules/axe-core/lib/commons/color/contrast.js +7 -1
  96. data/node_modules/axe-core/lib/commons/color/element-has-image.js +36 -0
  97. data/node_modules/axe-core/lib/commons/color/get-background-color.js +332 -306
  98. data/node_modules/axe-core/lib/commons/color/get-foreground-color.js +35 -6
  99. data/node_modules/axe-core/lib/commons/color/get-own-background-color.js +22 -0
  100. data/node_modules/axe-core/lib/commons/dom/find-up.js +5 -5
  101. data/node_modules/axe-core/lib/commons/dom/get-scroll-offset.js +0 -1
  102. data/node_modules/axe-core/lib/commons/dom/get-tabbable-elements.js +1 -1
  103. data/node_modules/axe-core/lib/commons/dom/has-content-virtual.js +7 -5
  104. data/node_modules/axe-core/lib/commons/dom/is-focusable.js +8 -5
  105. data/node_modules/axe-core/lib/commons/dom/is-hidden-with-css.js +15 -2
  106. data/node_modules/axe-core/lib/commons/dom/is-in-text-block.js +1 -2
  107. data/node_modules/axe-core/lib/commons/dom/is-skip-link.js +45 -0
  108. data/node_modules/axe-core/lib/commons/dom/is-visible.js +43 -17
  109. data/node_modules/axe-core/lib/commons/dom/is-visual-content.js +0 -1
  110. data/node_modules/axe-core/lib/commons/dom/visually-contains.js +0 -1
  111. data/node_modules/axe-core/lib/commons/dom/visually-overlaps.js +0 -1
  112. data/node_modules/axe-core/lib/commons/forms/index.js +8 -0
  113. data/node_modules/axe-core/lib/commons/forms/is-aria-combobox.js +13 -0
  114. data/node_modules/axe-core/lib/commons/forms/is-aria-listbox.js +13 -0
  115. data/node_modules/axe-core/lib/commons/forms/is-aria-range.js +14 -0
  116. data/node_modules/axe-core/lib/commons/forms/is-aria-textbox.js +13 -0
  117. data/node_modules/axe-core/lib/commons/forms/is-native-select.js +13 -0
  118. data/node_modules/axe-core/lib/commons/forms/is-native-textbox.js +28 -0
  119. data/node_modules/axe-core/lib/commons/table/get-cell-position.js +2 -2
  120. data/node_modules/axe-core/lib/commons/table/get-headers.js +55 -11
  121. data/node_modules/axe-core/lib/commons/table/get-scope.js +1 -1
  122. data/node_modules/axe-core/lib/commons/table/is-data-table.js +0 -1
  123. data/node_modules/axe-core/lib/commons/table/to-grid.js +2 -2
  124. data/node_modules/axe-core/lib/commons/text/accessible-text-virtual.js +5 -5
  125. data/node_modules/axe-core/lib/commons/text/form-control-value.js +18 -30
  126. data/node_modules/axe-core/lib/commons/text/is-icon-ligature.js +210 -0
  127. data/node_modules/axe-core/lib/commons/text/is-valid-autocomplete.js +0 -1
  128. data/node_modules/axe-core/lib/commons/text/label-text.js +1 -1
  129. data/node_modules/axe-core/lib/commons/text/label-virtual.js +1 -1
  130. data/node_modules/axe-core/lib/commons/text/native-text-methods.js +1 -1
  131. data/node_modules/axe-core/lib/commons/text/subtree-text.js +3 -3
  132. data/node_modules/axe-core/lib/commons/text/unicode.js +15 -0
  133. data/node_modules/axe-core/lib/commons/text/visible-text-nodes.js +25 -0
  134. data/node_modules/axe-core/lib/commons/text/visible-virtual.js +1 -1
  135. data/node_modules/axe-core/lib/core/base/audit.js +90 -15
  136. data/node_modules/axe-core/lib/core/base/cache.js +33 -0
  137. data/node_modules/axe-core/lib/core/base/check.js +48 -1
  138. data/node_modules/axe-core/lib/core/base/context.js +15 -14
  139. data/node_modules/axe-core/lib/core/base/rule.js +223 -46
  140. data/node_modules/axe-core/lib/core/base/virtual-node/abstract-virtual-node.js +40 -0
  141. data/node_modules/axe-core/lib/core/base/virtual-node/serial-virtual-node.js +86 -0
  142. data/node_modules/axe-core/lib/core/base/virtual-node/virtual-node.js +85 -0
  143. data/node_modules/axe-core/lib/core/constants.js +10 -2
  144. data/node_modules/axe-core/lib/core/imports/index.js +28 -3
  145. data/node_modules/axe-core/lib/core/index.js +2 -4
  146. data/node_modules/axe-core/lib/core/public/configure.js +28 -1
  147. data/node_modules/axe-core/lib/core/public/run-rules.js +2 -0
  148. data/node_modules/axe-core/lib/core/public/run-virtual-rule.js +50 -0
  149. data/node_modules/axe-core/lib/core/public/run.js +13 -2
  150. data/node_modules/axe-core/lib/core/reporters/helpers/process-aggregate.js +1 -1
  151. data/node_modules/axe-core/lib/core/reporters/na.js +4 -0
  152. data/node_modules/axe-core/lib/core/reporters/raw-env.js +12 -0
  153. data/node_modules/axe-core/lib/core/reporters/raw.js +25 -1
  154. data/node_modules/axe-core/lib/core/utils/are-styles-set.js +4 -7
  155. data/node_modules/axe-core/lib/core/utils/assert.js +12 -0
  156. data/node_modules/axe-core/lib/core/utils/collect-results-from-frames.js +2 -2
  157. data/node_modules/axe-core/lib/core/utils/contains.js +27 -13
  158. data/node_modules/axe-core/lib/core/utils/css-parser.js +2 -0
  159. data/node_modules/axe-core/lib/core/utils/element-matches.js +5 -1
  160. data/node_modules/axe-core/lib/core/utils/escape-selector.js +1 -2
  161. data/node_modules/axe-core/lib/core/utils/flattened-tree.js +47 -61
  162. data/node_modules/axe-core/lib/core/utils/get-check-option.js +0 -1
  163. data/node_modules/axe-core/lib/core/utils/get-friendly-uri-end.js +1 -1
  164. data/node_modules/axe-core/lib/core/utils/get-node-attributes.js +21 -0
  165. data/node_modules/axe-core/lib/core/utils/get-scroll.js +39 -0
  166. data/node_modules/axe-core/lib/core/utils/get-selector.js +9 -6
  167. data/node_modules/axe-core/lib/core/utils/get-stylesheet-factory.js +51 -0
  168. data/node_modules/axe-core/lib/core/utils/get-xpath.js +0 -1
  169. data/node_modules/axe-core/lib/core/utils/is-hidden.js +16 -4
  170. data/node_modules/axe-core/lib/core/utils/is-html-element.js +5 -5
  171. data/node_modules/axe-core/lib/core/utils/is-shadow-root.js +3 -3
  172. data/node_modules/axe-core/lib/core/utils/memoize.js +17 -0
  173. data/node_modules/axe-core/lib/core/utils/parse-crossorigin-stylesheet.js +53 -0
  174. data/node_modules/axe-core/lib/core/utils/parse-sameorigin-stylesheet.js +96 -0
  175. data/node_modules/axe-core/lib/core/utils/parse-stylesheet.js +70 -0
  176. data/node_modules/axe-core/lib/core/utils/performance-timer.js +7 -2
  177. data/node_modules/axe-core/lib/core/utils/preload-cssom.js +77 -281
  178. data/node_modules/axe-core/lib/core/utils/preload.js +49 -23
  179. data/node_modules/axe-core/lib/core/utils/qsa.js +39 -50
  180. data/node_modules/axe-core/lib/core/utils/respondable.js +20 -3
  181. data/node_modules/axe-core/lib/core/utils/rule-should-run.js +0 -1
  182. data/node_modules/axe-core/lib/core/utils/scroll-state.js +12 -25
  183. data/node_modules/axe-core/lib/core/utils/select.js +12 -23
  184. data/node_modules/axe-core/lib/core/utils/uuid.js +1 -2
  185. data/node_modules/axe-core/lib/intro.stub +1 -1
  186. data/node_modules/axe-core/lib/misc/incomplete-fallback.json +1 -1
  187. data/node_modules/axe-core/lib/rules/aria-allowed-attr-matches.js +1 -1
  188. data/node_modules/axe-core/lib/rules/aria-form-field-name-matches.js +50 -0
  189. data/node_modules/axe-core/lib/rules/aria-has-attr-matches.js +1 -1
  190. data/node_modules/axe-core/lib/rules/aria-hidden-focus.json +1 -1
  191. data/node_modules/axe-core/lib/rules/aria-input-field-name.json +13 -0
  192. data/node_modules/axe-core/lib/rules/aria-roledescription.json +12 -0
  193. data/node_modules/axe-core/lib/rules/aria-toggle-field-name.json +18 -0
  194. data/node_modules/axe-core/lib/rules/autocomplete-matches.js +14 -10
  195. data/node_modules/axe-core/lib/rules/avoid-inline-spacing.json +12 -0
  196. data/node_modules/axe-core/lib/rules/button-name.json +3 -5
  197. data/node_modules/axe-core/lib/rules/checkboxgroup.json +2 -1
  198. data/node_modules/axe-core/lib/rules/color-contrast-matches.js +37 -22
  199. data/node_modules/axe-core/lib/rules/duplicate-id-active-matches.js +1 -1
  200. data/node_modules/axe-core/lib/rules/duplicate-id-misc-matches.js +2 -2
  201. data/node_modules/axe-core/lib/rules/form-field-multiple-labels.json +1 -1
  202. data/node_modules/axe-core/lib/rules/html-has-lang.json +1 -0
  203. data/node_modules/axe-core/lib/rules/image-alt.json +1 -1
  204. data/node_modules/axe-core/lib/rules/img-redundant-alt.json +3 -3
  205. data/node_modules/axe-core/lib/rules/input-button-name.json +26 -0
  206. data/node_modules/axe-core/lib/rules/landmark-unique-matches.js +41 -0
  207. data/node_modules/axe-core/lib/rules/landmark-unique.json +13 -0
  208. data/node_modules/axe-core/lib/rules/link-name.json +5 -4
  209. data/node_modules/axe-core/lib/rules/meta-refresh.json +8 -1
  210. data/node_modules/axe-core/lib/rules/radiogroup.json +2 -1
  211. data/node_modules/axe-core/lib/rules/role-img-alt.json +18 -0
  212. data/node_modules/axe-core/lib/rules/scrollable-region-focusable-matches.js +30 -0
  213. data/node_modules/axe-core/lib/rules/scrollable-region-focusable.json +12 -0
  214. data/node_modules/axe-core/lib/rules/skip-link-matches.js +1 -1
  215. data/node_modules/axe-core/lib/rules/skip-link.json +1 -1
  216. data/node_modules/axe-core/lib/rules/video-description.json +3 -1
  217. data/node_modules/axe-core/locales/de.json +1 -5
  218. data/node_modules/axe-core/locales/es.json +773 -0
  219. data/node_modules/axe-core/locales/fr.json +15 -19
  220. data/node_modules/axe-core/locales/ja.json +65 -11
  221. data/node_modules/axe-core/locales/ko.json +777 -0
  222. data/node_modules/axe-core/locales/nl.json +35 -35
  223. data/node_modules/axe-core/locales/pt_BR.json +773 -0
  224. data/node_modules/axe-core/package.json +56 -52
  225. data/node_modules/axe-core/sri-history.json +20 -0
  226. data/node_modules/axe-core/typings/axe-core/axe-core-tests.ts +5 -15
  227. data/node_modules/govuk-frontend/package.json +10 -10
  228. metadata +62 -4
  229. data/node_modules/axe-core/doc/axelogo2018.png +0 -0
  230. data/node_modules/axe-core/lib/rules/role-not-button-matches.js +0 -1
@@ -0,0 +1,14 @@
1
+ /* global forms */
2
+ const rangeRoles = ['progressbar', 'scrollbar', 'slider', 'spinbutton'];
3
+
4
+ /**
5
+ * Determines if an element is an aria range element
6
+ * @method isAriaRange
7
+ * @memberof axe.commons.forms
8
+ * @param {Element} node Node to determine if aria range
9
+ * @returns {Bool}
10
+ */
11
+ forms.isAriaRange = function(node) {
12
+ const role = axe.commons.aria.getRole(node, { noImplicit: true });
13
+ return rangeRoles.includes(role);
14
+ };
@@ -0,0 +1,13 @@
1
+ /* global forms */
2
+
3
+ /**
4
+ * Determines if an element is an aria textbox element
5
+ * @method isAriaTextbox
6
+ * @memberof axe.commons.forms
7
+ * @param {Element} node Node to determine if aria textbox
8
+ * @returns {Bool}
9
+ */
10
+ forms.isAriaTextbox = function(node) {
11
+ const role = axe.commons.aria.getRole(node, { noImplicit: true });
12
+ return role === 'textbox';
13
+ };
@@ -0,0 +1,13 @@
1
+ /* global forms */
2
+
3
+ /**
4
+ * Determines if an element is a native select element
5
+ * @method isNativeSelect
6
+ * @memberof axe.commons.forms
7
+ * @param {Element} node Node to determine if select
8
+ * @returns {Bool}
9
+ */
10
+ forms.isNativeSelect = function(node) {
11
+ const nodeName = node.nodeName.toUpperCase();
12
+ return nodeName === 'SELECT';
13
+ };
@@ -0,0 +1,28 @@
1
+ /* global forms */
2
+ const nonTextInputTypes = [
3
+ 'button',
4
+ 'checkbox',
5
+ 'color',
6
+ 'file',
7
+ 'hidden',
8
+ 'image',
9
+ 'password',
10
+ 'radio',
11
+ 'reset',
12
+ 'submit'
13
+ ];
14
+
15
+ /**
16
+ * Determines if an element is a native textbox element
17
+ * @method isNativeTextbox
18
+ * @memberof axe.commons.forms
19
+ * @param {Element} node Node to determine if textbox
20
+ * @returns {Bool}
21
+ */
22
+ forms.isNativeTextbox = function(node) {
23
+ const nodeName = node.nodeName.toUpperCase();
24
+ return (
25
+ nodeName === 'TEXTAREA' ||
26
+ (nodeName === 'INPUT' && !nonTextInputTypes.includes(node.type))
27
+ );
28
+ };
@@ -8,7 +8,7 @@
8
8
  * @param {HTMLTableCellElement} cell The table cell of which to get the position
9
9
  * @return {Object} Object with `x` and `y` properties of the coordinates
10
10
  */
11
- table.getCellPosition = function(cell, tableGrid) {
11
+ table.getCellPosition = axe.utils.memoize(function(cell, tableGrid) {
12
12
  var rowIndex, index;
13
13
  if (!tableGrid) {
14
14
  tableGrid = table.toGrid(dom.findUp(cell, 'table'));
@@ -25,4 +25,4 @@ table.getCellPosition = function(cell, tableGrid) {
25
25
  }
26
26
  }
27
27
  }
28
- };
28
+ });
@@ -1,29 +1,73 @@
1
1
  /* global table */
2
2
 
3
+ /**
4
+ * Loop through the table grid looking for headers and caching the result.
5
+ * @param {String} headerType The type of header to look for ("row" or "col")
6
+ * @param {Object} position The position of the cell to start looking
7
+ * @param {Array} tablegrid A matrix of the table obtained using axe.commons.table.toGrid
8
+ * @return {Array<HTMLTableCellElement>} Array of HTMLTableCellElements that are headers
9
+ */
10
+ function traverseForHeaders(headerType, position, tableGrid) {
11
+ const property = headerType === 'row' ? '_rowHeaders' : '_colHeaders';
12
+ const predicate =
13
+ headerType === 'row' ? table.isRowHeader : table.isColumnHeader;
14
+ const rowEnd = headerType === 'row' ? position.y : 0;
15
+ const colEnd = headerType === 'row' ? 0 : position.x;
16
+
17
+ let headers;
18
+ const cells = [];
19
+ for (let row = position.y; row >= rowEnd && !headers; row--) {
20
+ for (let col = position.x; col >= colEnd; col--) {
21
+ const cell = tableGrid[row] ? tableGrid[row][col] : undefined;
22
+
23
+ if (!cell) {
24
+ continue;
25
+ }
26
+
27
+ // stop traversing once we've found a cache
28
+ const vNode = axe.utils.getNodeFromTree(cell);
29
+ if (vNode[property]) {
30
+ headers = vNode[property];
31
+ break;
32
+ }
33
+
34
+ cells.push(cell);
35
+ }
36
+ }
37
+
38
+ // need to check that the cells we've traversed are headers
39
+ headers = (headers || []).concat(cells.filter(predicate));
40
+
41
+ // cache results
42
+ cells.forEach(tableCell => {
43
+ const vNode = axe.utils.getNodeFromTree(tableCell);
44
+ vNode[property] = headers;
45
+ });
46
+
47
+ return headers;
48
+ }
49
+
3
50
  /**
4
51
  * Get any associated table headers for a `HTMLTableCellElement`
5
52
  * @method getHeaders
6
53
  * @memberof axe.commons.table
7
54
  * @instance
8
55
  * @param {HTMLTableCellElement} cell The cell of which to get headers
56
+ * @param {Array} [tablegrid] A matrix of the table obtained using axe.commons.table.toGrid
9
57
  * @return {Array<HTMLTableCellElement>} Array of headers associated to the table cell
10
58
  */
11
- table.getHeaders = function(cell) {
59
+ table.getHeaders = function(cell, tableGrid) {
12
60
  if (cell.hasAttribute('headers')) {
13
61
  return commons.dom.idrefs(cell, 'headers');
14
62
  }
15
-
16
- var tableGrid = commons.table.toGrid(commons.dom.findUp(cell, 'table'));
17
- var position = commons.table.getCellPosition(cell, tableGrid);
63
+ if (!tableGrid) {
64
+ tableGrid = commons.table.toGrid(commons.dom.findUp(cell, 'table'));
65
+ }
66
+ const position = commons.table.getCellPosition(cell, tableGrid);
18
67
 
19
68
  // TODO: RTL text
20
- var rowHeaders = table
21
- .traverse('left', position, tableGrid)
22
- .filter(cell => table.isRowHeader(cell));
23
-
24
- var colHeaders = table
25
- .traverse('up', position, tableGrid)
26
- .filter(cell => table.isColumnHeader(cell));
69
+ const rowHeaders = traverseForHeaders('row', position, tableGrid);
70
+ const colHeaders = traverseForHeaders('col', position, tableGrid);
27
71
 
28
72
  return [].concat(rowHeaders, colHeaders).reverse();
29
73
  };
@@ -29,7 +29,7 @@ table.getScope = function(cell) {
29
29
  return false;
30
30
  }
31
31
  var tableGrid = table.toGrid(dom.findUp(cell, 'table'));
32
- var pos = table.getCellPosition(cell);
32
+ var pos = table.getCellPosition(cell, tableGrid);
33
33
 
34
34
  // The element is in a row with all th elements, that makes it a column header
35
35
  var headerRow = tableGrid[pos.y].reduce((headerRow, cell) => {
@@ -1,5 +1,4 @@
1
1
  /* global table, dom */
2
- /* eslint max-statements: ["error",70], complexity: ["error",47] */
3
2
 
4
3
  /**
5
4
  * Determines whether a table is a data table
@@ -8,7 +8,7 @@
8
8
  * @param {HTMLTableElement} node The table to convert
9
9
  * @return {Array<HTMLTableCellElement>} Array of HTMLTableCellElements
10
10
  */
11
- table.toGrid = function(node) {
11
+ table.toGrid = axe.utils.memoize(function(node) {
12
12
  var table = [];
13
13
  var rows = node.rows;
14
14
  for (var i = 0, rowLength = rows.length; i < rowLength; i++) {
@@ -32,7 +32,7 @@ table.toGrid = function(node) {
32
32
  }
33
33
 
34
34
  return table;
35
- };
35
+ });
36
36
 
37
37
  // This was the old name
38
38
  table.toArray = table.toGrid;
@@ -11,7 +11,7 @@
11
11
  * @return {string}
12
12
  */
13
13
  text.accessibleText = function accessibleText(element, context) {
14
- let virtualNode = axe.utils.getNodeFromTree(axe._tree[0], element); // throws an exception on purpose if axe._tree not correct
14
+ const virtualNode = axe.utils.getNodeFromTree(element); // throws an exception on purpose if axe._tree not correct
15
15
  return text.accessibleTextVirtual(virtualNode, context);
16
16
  };
17
17
 
@@ -49,6 +49,10 @@ text.accessibleTextVirtual = function accessibleTextVirtual(
49
49
 
50
50
  // Find the first step that returns a non-empty string
51
51
  let accName = computationSteps.reduce((accName, step) => {
52
+ if (context.startNode === virtualNode) {
53
+ accName = text.sanitize(accName);
54
+ }
55
+
52
56
  if (accName !== '') {
53
57
  // yes, whitespace only a11y names halt the algorithm
54
58
  return accName;
@@ -56,10 +60,6 @@ text.accessibleTextVirtual = function accessibleTextVirtual(
56
60
  return step(virtualNode, context);
57
61
  }, '');
58
62
 
59
- if (context.startNode === virtualNode) {
60
- accName = text.sanitize(accName);
61
- }
62
-
63
63
  if (context.debug) {
64
64
  axe.log(accName || '{empty-value}', actualNode, context);
65
65
  }
@@ -1,7 +1,13 @@
1
1
  /* global text, aria, dom */
2
- const selectRoles = ['combobox', 'listbox'];
3
- const rangeRoles = ['progressbar', 'scrollbar', 'slider', 'spinbutton'];
4
- const controlValueRoles = ['textbox', ...selectRoles, ...rangeRoles];
2
+ const controlValueRoles = [
3
+ 'textbox',
4
+ 'progressbar',
5
+ 'scrollbar',
6
+ 'slider',
7
+ 'spinbutton',
8
+ 'combobox',
9
+ 'listbox'
10
+ ];
5
11
 
6
12
  text.formControlValueMethods = {
7
13
  nativeTextboxValue,
@@ -62,24 +68,7 @@ text.formControlValue = function formControlValue(virtualNode, context = {}) {
62
68
  */
63
69
  function nativeTextboxValue(node) {
64
70
  node = node.actualNode || node;
65
- const nonTextInputTypes = [
66
- 'button',
67
- 'checkbox',
68
- 'file',
69
- 'hidden',
70
- 'image',
71
- 'password',
72
- 'radio',
73
- 'reset',
74
- 'submit',
75
- 'color'
76
- ];
77
- const nodeName = node.nodeName.toUpperCase();
78
-
79
- if (
80
- nodeName === 'TEXTAREA' ||
81
- (nodeName === 'INPUT' && !nonTextInputTypes.includes(node.type))
82
- ) {
71
+ if (axe.commons.forms.isNativeTextbox(node)) {
83
72
  return node.value || '';
84
73
  }
85
74
  return '';
@@ -93,7 +82,7 @@ function nativeTextboxValue(node) {
93
82
  */
94
83
  function nativeSelectValue(node) {
95
84
  node = node.actualNode || node;
96
- if (node.nodeName.toUpperCase() !== 'SELECT') {
85
+ if (!axe.commons.forms.isNativeSelect(node)) {
97
86
  return '';
98
87
  }
99
88
  return (
@@ -112,8 +101,7 @@ function nativeSelectValue(node) {
112
101
  */
113
102
  function ariaTextboxValue(virtualNode) {
114
103
  const { actualNode } = virtualNode;
115
- const role = aria.getRole(actualNode);
116
- if (role !== 'textbox') {
104
+ if (!axe.commons.forms.isAriaTextbox(actualNode)) {
117
105
  return '';
118
106
  }
119
107
  if (!dom.isHiddenWithCSS(actualNode)) {
@@ -135,8 +123,7 @@ function ariaTextboxValue(virtualNode) {
135
123
  */
136
124
  function ariaListboxValue(virtualNode, context) {
137
125
  const { actualNode } = virtualNode;
138
- const role = aria.getRole(actualNode);
139
- if (role !== 'listbox') {
126
+ if (!axe.commons.forms.isAriaListbox(actualNode)) {
140
127
  return '';
141
128
  }
142
129
 
@@ -169,11 +156,10 @@ function ariaListboxValue(virtualNode, context) {
169
156
  */
170
157
  function ariaComboboxValue(virtualNode, context) {
171
158
  const { actualNode } = virtualNode;
172
- const role = aria.getRole(actualNode, { noImplicit: true });
173
159
  let listbox;
174
160
 
175
161
  // For combobox, find the first owned listbox:
176
- if (!role === 'combobox') {
162
+ if (!axe.commons.forms.isAriaCombobox(actualNode)) {
177
163
  return '';
178
164
  }
179
165
  listbox = aria
@@ -193,8 +179,10 @@ function ariaComboboxValue(virtualNode, context) {
193
179
  */
194
180
  function ariaRangeValue(node) {
195
181
  node = node.actualNode || node;
196
- const role = aria.getRole(node);
197
- if (!rangeRoles.includes(role) || !node.hasAttribute('aria-valuenow')) {
182
+ if (
183
+ !axe.commons.forms.isAriaRange(node) ||
184
+ !node.hasAttribute('aria-valuenow')
185
+ ) {
198
186
  return '';
199
187
  }
200
188
  // Validate the number, if not, return 0.
@@ -0,0 +1,210 @@
1
+ /* global text */
2
+
3
+ /**
4
+ * Determines if a given text node is an icon ligature
5
+ *
6
+ * @method isIconLigature
7
+ * @memberof axe.commons.text
8
+ * @instance
9
+ * @param {VirtualNode} textVNode The virtual text node
10
+ * @param {Number} occuranceThreshold Number of times the font is encountered before auto-assigning the font as a ligature or not
11
+ * @param {Number} differenceThreshold Percent of differences in pixel data or pixel width needed to determine if a font is a ligature font
12
+ * @return {Boolean}
13
+ */
14
+ text.isIconLigature = function(
15
+ textVNode,
16
+ differenceThreshold = 0.15,
17
+ occuranceThreshold = 3
18
+ ) {
19
+ /**
20
+ * Determine if the visible text is a ligature by comparing the
21
+ * first letters image data to the entire strings image data.
22
+ * If the two images are significantly different (typical set to 5%
23
+ * statistical significance, but we'll be using a slightly higher value
24
+ * of 15% to help keep the size of the canvas down) then we know the text
25
+ * has been replaced by a ligature.
26
+ *
27
+ * Example:
28
+ * If a text node was the string "File", looking at just the first
29
+ * letter "F" would produce the following image:
30
+ *
31
+ * ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
32
+ * │ │ │█│█│█│█│█│█│█│█│█│█│█│ │ │
33
+ * ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
34
+ * │ │ │█│█│█│█│█│█│█│█│█│█│█│ │ │
35
+ * ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
36
+ * │ │ │█│█│ │ │ │ │ │ │ │ │ │ │ │
37
+ * ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
38
+ * │ │ │█│█│ │ │ │ │ │ │ │ │ │ │ │
39
+ * ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
40
+ * │ │ │█│█│█│█│█│█│█│ │ │ │ │ │ │
41
+ * ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
42
+ * │ │ │█│█│█│█│█│█│█│ │ │ │ │ │ │
43
+ * ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
44
+ * │ │ │█│█│ │ │ │ │ │ │ │ │ │ │ │
45
+ * ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
46
+ * │ │ │█│█│ │ │ │ │ │ │ │ │ │ │ │
47
+ * ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
48
+ * │ │ │█│█│ │ │ │ │ │ │ │ │ │ │ │
49
+ * └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘
50
+ *
51
+ * But if the entire string "File" produced an image which had at least
52
+ * a 15% difference in pixels, we would know that the string was replaced
53
+ * by a ligature:
54
+ *
55
+ * ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
56
+ * │ │█│█│█│█│█│█│█│█│█│█│ │ │ │ │
57
+ * ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
58
+ * │ │█│ │ │ │ │ │ │ │ │█│█│ │ │ │
59
+ * ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
60
+ * │ │█│ │█│█│█│█│█│█│ │█│ │█│ │ │
61
+ * ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
62
+ * │ │█│ │ │ │ │ │ │ │ │█│█│█│█│ │
63
+ * ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
64
+ * │ │█│ │█│█│█│█│█│█│ │ │ │ │█│ │
65
+ * ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
66
+ * │ │█│ │ │ │ │ │ │ │ │ │ │ │█│ │
67
+ * ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
68
+ * │ │█│ │█│█│█│█│█│█│█│█│█│ │█│ │
69
+ * ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
70
+ * │ │█│ │ │ │ │ │ │ │ │ │ │ │█│ │
71
+ * ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
72
+ * │ │█│█│█│█│█│█│█│█│█│█│█│█│█│ │
73
+ * └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘
74
+ */
75
+ const nodeValue = textVNode.actualNode.nodeValue.trim();
76
+
77
+ // text with unicode or non-bmp letters cannot be ligature icons
78
+ if (
79
+ !text.sanitize(nodeValue) ||
80
+ text.hasUnicode(nodeValue, { emoji: true, nonBmp: true })
81
+ ) {
82
+ return false;
83
+ }
84
+
85
+ if (!axe._cache.get('canvasContext')) {
86
+ axe._cache.set(
87
+ 'canvasContext',
88
+ document.createElement('canvas').getContext('2d')
89
+ );
90
+ }
91
+ const canvasContext = axe._cache.get('canvasContext');
92
+ const canvas = canvasContext.canvas;
93
+
94
+ // keep track of each font encountered and the number of times it shows up
95
+ // as a ligature.
96
+ if (!axe._cache.get('fonts')) {
97
+ axe._cache.set('fonts', {});
98
+ }
99
+ const fonts = axe._cache.get('fonts');
100
+
101
+ const style = window.getComputedStyle(textVNode.parent.actualNode);
102
+ const fontFamily = style.getPropertyValue('font-family');
103
+
104
+ if (!fonts[fontFamily]) {
105
+ fonts[fontFamily] = {
106
+ occurances: 0,
107
+ numLigatures: 0
108
+ };
109
+ }
110
+ const font = fonts[fontFamily];
111
+
112
+ // improve the performance by only comparing the image data of a font
113
+ // a certain number of times
114
+ if (font.occurances >= occuranceThreshold) {
115
+ // if the font has always been a ligature assume it's a ligature font
116
+ if (font.numLigatures / font.occurances === 1) {
117
+ return true;
118
+ }
119
+ // inversely, if it's never been a ligature assume it's not a ligature font
120
+ else if (font.numLigatures === 0) {
121
+ return false;
122
+ }
123
+
124
+ // we could theoretically get into an odd middle ground scenario in which
125
+ // the font family is being used as normal text sometimes and as icons
126
+ // other times. in these cases we would need to always check the text
127
+ // to know if it's an icon or not
128
+ }
129
+ font.occurances++;
130
+
131
+ // 30px was chosen to account for common ligatures in normal fonts
132
+ // such as fi, ff, ffi. If a ligature would add a single column of
133
+ // pixels to a 30x30 grid, it would not meet the statistical significance
134
+ // threshold of 15% (30x30 = 900; 30/900 = 3.333%). this also allows for
135
+ // more than 1 column differences (60/900 = 6.666%) and things like
136
+ // extending the top of the f in the fi ligature.
137
+ let fontSize = 30;
138
+ let fontStyle = `${fontSize}px ${fontFamily}`;
139
+
140
+ // set the size of the canvas to the width of the first letter
141
+ canvasContext.font = fontStyle;
142
+ const firstChar = nodeValue.charAt(0);
143
+ let width = canvasContext.measureText(firstChar).width;
144
+
145
+ // ensure font meets the 30px width requirement (30px font-size doesn't
146
+ // necessarily mean its 30px wide when drawn)
147
+ if (width < 30) {
148
+ const diff = 30 / width;
149
+ width *= diff;
150
+ fontSize *= diff;
151
+ fontStyle = `${fontSize}px ${fontFamily}`;
152
+ }
153
+ canvas.width = width;
154
+ canvas.height = fontSize;
155
+
156
+ // changing the dimensions of a canvas resets all properties (include font)
157
+ // and clears it
158
+ canvasContext.font = fontStyle;
159
+ canvasContext.textAlign = 'left';
160
+ canvasContext.textBaseline = 'top';
161
+ canvasContext.fillText(firstChar, 0, 0);
162
+ const compareData = new Uint32Array(
163
+ canvasContext.getImageData(0, 0, width, fontSize).data.buffer
164
+ );
165
+
166
+ // if the font doesn't even have character data for a single char then
167
+ // it has to be an icon ligature (e.g. Material Icon)
168
+ if (!compareData.some(pixel => pixel)) {
169
+ font.numLigatures++;
170
+ return true;
171
+ }
172
+
173
+ canvasContext.clearRect(0, 0, width, fontSize);
174
+ canvasContext.fillText(nodeValue, 0, 0);
175
+ const compareWith = new Uint32Array(
176
+ canvasContext.getImageData(0, 0, width, fontSize).data.buffer
177
+ );
178
+
179
+ // calculate the number of differences between the first letter and the
180
+ // entire string, ignoring color differences
181
+ const differences = compareData.reduce((diff, pixel, i) => {
182
+ if (pixel === 0 && compareWith[i] === 0) {
183
+ return diff;
184
+ }
185
+ if (pixel !== 0 && compareWith[i] !== 0) {
186
+ return diff;
187
+ }
188
+ return ++diff;
189
+ }, 0);
190
+
191
+ // calculate the difference between the width of each character and the
192
+ // combined with of all characters
193
+ const expectedWidth = nodeValue.split('').reduce((width, char) => {
194
+ return width + canvasContext.measureText(char).width;
195
+ }, 0);
196
+ const actualWidth = canvasContext.measureText(nodeValue).width;
197
+
198
+ const pixelDifference = differences / compareData.length;
199
+ const sizeDifference = 1 - actualWidth / expectedWidth;
200
+
201
+ if (
202
+ pixelDifference >= differenceThreshold &&
203
+ sizeDifference >= differenceThreshold
204
+ ) {
205
+ font.numLigatures++;
206
+ return true;
207
+ }
208
+
209
+ return false;
210
+ };