govuk_publishing_components 21.16.3 → 21.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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
+ };