govuk_publishing_components 32.0.0 → 33.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.
Files changed (206) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/component_guide/accessibility-test.js +0 -1
  3. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-core.js +175 -0
  4. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-ecommerce-tracker.js +1 -1
  5. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-event-tracker.js +5 -13
  6. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-link-tracker.js +80 -309
  7. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-page-views.js +2 -2
  8. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-specialist-link-tracker.js +140 -0
  9. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/init-ga4.js +3 -0
  10. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4.js +1 -0
  11. data/app/assets/javascripts/govuk_publishing_components/components/accordion.js +12 -1
  12. data/app/assets/javascripts/govuk_publishing_components/components/layout-super-navigation-header.js +13 -4
  13. data/app/assets/javascripts/govuk_publishing_components/components/single-page-notification-button.js +24 -8
  14. data/app/assets/javascripts/govuk_publishing_components/vendor/lux/lux-reporter.js +83 -86
  15. data/app/assets/stylesheets/govuk_publishing_components/components/_big-number.scss +2 -5
  16. data/app/assets/stylesheets/govuk_publishing_components/components/_image-card.scss +1 -5
  17. data/app/assets/stylesheets/govuk_publishing_components/components/_input.scss +3 -5
  18. data/app/assets/stylesheets/govuk_publishing_components/components/_layout-super-navigation-header.scss +10 -30
  19. data/app/assets/stylesheets/govuk_publishing_components/components/_search.scss +0 -7
  20. data/app/assets/stylesheets/govuk_publishing_components/components/_share-links.scss +0 -6
  21. data/app/views/govuk_publishing_components/components/_accordion.html.erb +14 -1
  22. data/app/views/govuk_publishing_components/components/_error_summary.html.erb +27 -26
  23. data/app/views/govuk_publishing_components/components/_layout_super_navigation_header.html.erb +2 -2
  24. data/app/views/govuk_publishing_components/components/_phase_banner.html.erb +1 -1
  25. data/app/views/govuk_publishing_components/components/_share_links.html.erb +18 -15
  26. data/app/views/govuk_publishing_components/components/_single_page_notification_button.html.erb +1 -1
  27. data/app/views/govuk_publishing_components/components/docs/accordion.yml +15 -3
  28. data/app/views/govuk_publishing_components/components/docs/button.yml +10 -0
  29. data/app/views/govuk_publishing_components/components/docs/share_links.yml +59 -30
  30. data/app/views/govuk_publishing_components/components/docs/single_page_notification_button.yml +10 -1
  31. data/app/views/govuk_publishing_components/components/feedback/_yes_no_banner.html.erb +3 -3
  32. data/config/locales/ar.yml +4 -1
  33. data/config/locales/az.yml +4 -1
  34. data/config/locales/be.yml +4 -1
  35. data/config/locales/bg.yml +4 -1
  36. data/config/locales/bn.yml +4 -1
  37. data/config/locales/cs.yml +4 -1
  38. data/config/locales/cy.yml +4 -1
  39. data/config/locales/da.yml +4 -1
  40. data/config/locales/de.yml +4 -1
  41. data/config/locales/dr.yml +4 -1
  42. data/config/locales/el.yml +4 -1
  43. data/config/locales/en.yml +20 -17
  44. data/config/locales/es-419.yml +4 -1
  45. data/config/locales/es.yml +4 -1
  46. data/config/locales/et.yml +4 -1
  47. data/config/locales/fa.yml +4 -1
  48. data/config/locales/fi.yml +4 -1
  49. data/config/locales/fr.yml +4 -1
  50. data/config/locales/gd.yml +4 -1
  51. data/config/locales/gu.yml +4 -1
  52. data/config/locales/he.yml +4 -1
  53. data/config/locales/hi.yml +4 -1
  54. data/config/locales/hr.yml +4 -1
  55. data/config/locales/hu.yml +4 -1
  56. data/config/locales/hy.yml +4 -1
  57. data/config/locales/id.yml +4 -1
  58. data/config/locales/is.yml +4 -1
  59. data/config/locales/it.yml +4 -1
  60. data/config/locales/ja.yml +4 -1
  61. data/config/locales/ka.yml +4 -1
  62. data/config/locales/kk.yml +4 -1
  63. data/config/locales/ko.yml +4 -1
  64. data/config/locales/lt.yml +4 -1
  65. data/config/locales/lv.yml +4 -1
  66. data/config/locales/ms.yml +4 -1
  67. data/config/locales/mt.yml +4 -1
  68. data/config/locales/nl.yml +4 -1
  69. data/config/locales/no.yml +4 -1
  70. data/config/locales/pa-pk.yml +4 -1
  71. data/config/locales/pa.yml +4 -1
  72. data/config/locales/pl.yml +4 -1
  73. data/config/locales/ps.yml +4 -1
  74. data/config/locales/pt.yml +4 -1
  75. data/config/locales/ro.yml +4 -1
  76. data/config/locales/ru.yml +4 -1
  77. data/config/locales/si.yml +4 -1
  78. data/config/locales/sk.yml +4 -1
  79. data/config/locales/sl.yml +4 -1
  80. data/config/locales/so.yml +4 -1
  81. data/config/locales/sq.yml +4 -1
  82. data/config/locales/sr.yml +4 -1
  83. data/config/locales/sv.yml +4 -1
  84. data/config/locales/sw.yml +4 -1
  85. data/config/locales/ta.yml +4 -1
  86. data/config/locales/th.yml +4 -1
  87. data/config/locales/tk.yml +4 -1
  88. data/config/locales/tr.yml +4 -1
  89. data/config/locales/uk.yml +4 -1
  90. data/config/locales/ur.yml +4 -1
  91. data/config/locales/uz.yml +4 -1
  92. data/config/locales/vi.yml +4 -1
  93. data/config/locales/zh-hk.yml +4 -1
  94. data/config/locales/zh-tw.yml +4 -1
  95. data/config/locales/zh.yml +4 -1
  96. data/lib/govuk_publishing_components/presenters/button_helper.rb +7 -1
  97. data/lib/govuk_publishing_components/presenters/single_page_notification_button_helper.rb +25 -1
  98. data/lib/govuk_publishing_components/version.rb +1 -1
  99. data/node_modules/axe-core/axe.js +4567 -4678
  100. data/node_modules/axe-core/axe.min.js +2 -2
  101. data/node_modules/axe-core/package.json +2 -2
  102. data/node_modules/axe-core/sri-history.json +8 -0
  103. data/node_modules/govuk-frontend/README.md +1 -2
  104. data/node_modules/govuk-frontend/govuk/all.js +1398 -273
  105. data/node_modules/govuk-frontend/govuk/common/closest-attribute-value.js +70 -0
  106. data/node_modules/govuk-frontend/govuk/common/index.js +172 -0
  107. data/node_modules/govuk-frontend/govuk/common/normalise-dataset.js +373 -0
  108. data/node_modules/govuk-frontend/govuk/common.js +138 -3
  109. data/node_modules/govuk-frontend/govuk/components/accordion/accordion.js +753 -25
  110. data/node_modules/govuk-frontend/govuk/components/accordion/fixtures.json +54 -22
  111. data/node_modules/govuk-frontend/govuk/components/accordion/macro-options.json +36 -0
  112. data/node_modules/govuk-frontend/govuk/components/accordion/template.njk +7 -1
  113. data/node_modules/govuk-frontend/govuk/components/back-link/fixtures.json +12 -12
  114. data/node_modules/govuk-frontend/govuk/components/breadcrumbs/fixtures.json +22 -22
  115. data/node_modules/govuk-frontend/govuk/components/button/_index.scss +23 -5
  116. data/node_modules/govuk-frontend/govuk/components/button/button.js +365 -107
  117. data/node_modules/govuk-frontend/govuk/components/button/fixtures.json +85 -66
  118. data/node_modules/govuk-frontend/govuk/components/button/template.njk +1 -1
  119. data/node_modules/govuk-frontend/govuk/components/character-count/_index.scss +9 -0
  120. data/node_modules/govuk-frontend/govuk/components/character-count/character-count.js +1033 -121
  121. data/node_modules/govuk-frontend/govuk/components/character-count/fixtures.json +112 -36
  122. data/node_modules/govuk-frontend/govuk/components/character-count/macro-options.json +42 -0
  123. data/node_modules/govuk-frontend/govuk/components/character-count/template.njk +27 -3
  124. data/node_modules/govuk-frontend/govuk/components/checkboxes/checkboxes.js +30 -2
  125. data/node_modules/govuk-frontend/govuk/components/checkboxes/fixtures.json +96 -93
  126. data/node_modules/govuk-frontend/govuk/components/cookie-banner/fixtures.json +46 -46
  127. data/node_modules/govuk-frontend/govuk/components/date-input/fixtures.json +50 -50
  128. data/node_modules/govuk-frontend/govuk/components/details/details.js +43 -13
  129. data/node_modules/govuk-frontend/govuk/components/details/fixtures.json +20 -20
  130. data/node_modules/govuk-frontend/govuk/components/error-message/fixtures.json +20 -20
  131. data/node_modules/govuk-frontend/govuk/components/error-summary/error-summary.js +268 -6
  132. data/node_modules/govuk-frontend/govuk/components/error-summary/fixtures.json +44 -35
  133. data/node_modules/govuk-frontend/govuk/components/error-summary/template.njk +25 -21
  134. data/node_modules/govuk-frontend/govuk/components/fieldset/fixtures.json +51 -39
  135. data/node_modules/govuk-frontend/govuk/components/file-upload/fixtures.json +26 -26
  136. data/node_modules/govuk-frontend/govuk/components/footer/_index.scss +1 -1
  137. data/node_modules/govuk-frontend/govuk/components/footer/fixtures.json +46 -46
  138. data/node_modules/govuk-frontend/govuk/components/footer/macro-options.json +2 -2
  139. data/node_modules/govuk-frontend/govuk/components/header/fixtures.json +93 -38
  140. data/node_modules/govuk-frontend/govuk/components/header/header.js +6 -0
  141. data/node_modules/govuk-frontend/govuk/components/header/macro-options.json +8 -2
  142. data/node_modules/govuk-frontend/govuk/components/header/template.njk +4 -2
  143. data/node_modules/govuk-frontend/govuk/components/hint/fixtures.json +12 -12
  144. data/node_modules/govuk-frontend/govuk/components/input/fixtures.json +80 -80
  145. data/node_modules/govuk-frontend/govuk/components/inset-text/fixtures.json +12 -12
  146. data/node_modules/govuk-frontend/govuk/components/label/fixtures.json +34 -34
  147. data/node_modules/govuk-frontend/govuk/components/notification-banner/fixtures.json +56 -46
  148. data/node_modules/govuk-frontend/govuk/components/notification-banner/notification-banner.js +252 -2
  149. data/node_modules/govuk-frontend/govuk/components/notification-banner/template.njk +1 -1
  150. data/node_modules/govuk-frontend/govuk/components/pagination/_index.scss +10 -7
  151. data/node_modules/govuk-frontend/govuk/components/pagination/fixtures.json +33 -26
  152. data/node_modules/govuk-frontend/govuk/components/panel/fixtures.json +18 -18
  153. data/node_modules/govuk-frontend/govuk/components/phase-banner/fixtures.json +14 -14
  154. data/node_modules/govuk-frontend/govuk/components/radios/fixtures.json +94 -91
  155. data/node_modules/govuk-frontend/govuk/components/radios/radios.js +30 -2
  156. data/node_modules/govuk-frontend/govuk/components/select/fixtures.json +32 -32
  157. data/node_modules/govuk-frontend/govuk/components/skip-link/fixtures.json +22 -20
  158. data/node_modules/govuk-frontend/govuk/components/skip-link/skip-link.js +10 -4
  159. data/node_modules/govuk-frontend/govuk/components/summary-list/fixtures.json +50 -50
  160. data/node_modules/govuk-frontend/govuk/components/table/_index.scss +1 -1
  161. data/node_modules/govuk-frontend/govuk/components/table/fixtures.json +40 -40
  162. data/node_modules/govuk-frontend/govuk/components/tabs/fixtures.json +29 -29
  163. data/node_modules/govuk-frontend/govuk/components/tabs/tabs.js +28 -0
  164. data/node_modules/govuk-frontend/govuk/components/tag/fixtures.json +28 -28
  165. data/node_modules/govuk-frontend/govuk/components/textarea/fixtures.json +34 -34
  166. data/node_modules/govuk-frontend/govuk/components/warning-text/fixtures.json +14 -14
  167. data/node_modules/govuk-frontend/govuk/core/_section-break.scss +1 -1
  168. data/node_modules/govuk-frontend/govuk/helpers/_colour.scss +2 -2
  169. data/node_modules/govuk-frontend/govuk/helpers/_links.scss +6 -6
  170. data/node_modules/govuk-frontend/govuk/i18n.js +390 -0
  171. data/node_modules/govuk-frontend/govuk/macros/i18n.njk +15 -0
  172. data/node_modules/govuk-frontend/govuk/settings/_all.scss +1 -0
  173. data/node_modules/govuk-frontend/govuk/settings/_colours-palette.scss +12 -0
  174. data/node_modules/govuk-frontend/govuk/settings/_compatibility.scss +26 -0
  175. data/node_modules/govuk-frontend/govuk/settings/_typography-font.scss +23 -0
  176. data/node_modules/govuk-frontend/govuk/settings/_typography-responsive.scss +12 -0
  177. data/node_modules/govuk-frontend/govuk/settings/_warnings.scss +53 -0
  178. data/node_modules/govuk-frontend/govuk/tools/_compatibility.scss +20 -6
  179. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Date/now.js +21 -0
  180. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/dataset.js +300 -0
  181. data/node_modules/govuk-frontend/govuk/vendor/polyfills/String/prototype/trim.js +21 -0
  182. data/node_modules/govuk-frontend/govuk-esm/all.mjs +50 -27
  183. data/node_modules/govuk-frontend/govuk-esm/common/closest-attribute-value.mjs +15 -0
  184. data/node_modules/govuk-frontend/govuk-esm/common/index.mjs +159 -0
  185. data/node_modules/govuk-frontend/govuk-esm/common/normalise-dataset.mjs +58 -0
  186. data/node_modules/govuk-frontend/govuk-esm/common.mjs +6 -28
  187. data/node_modules/govuk-frontend/govuk-esm/components/accordion/accordion.mjs +113 -43
  188. data/node_modules/govuk-frontend/govuk-esm/components/button/button.mjs +67 -30
  189. data/node_modules/govuk-frontend/govuk-esm/components/character-count/character-count.mjs +325 -123
  190. data/node_modules/govuk-frontend/govuk-esm/components/checkboxes/checkboxes.mjs +9 -3
  191. data/node_modules/govuk-frontend/govuk-esm/components/details/details.mjs +22 -8
  192. data/node_modules/govuk-frontend/govuk-esm/components/error-summary/error-summary.mjs +48 -6
  193. data/node_modules/govuk-frontend/govuk-esm/components/header/header.mjs +6 -0
  194. data/node_modules/govuk-frontend/govuk-esm/components/notification-banner/notification-banner.mjs +32 -2
  195. data/node_modules/govuk-frontend/govuk-esm/components/radios/radios.mjs +9 -3
  196. data/node_modules/govuk-frontend/govuk-esm/components/skip-link/skip-link.mjs +10 -4
  197. data/node_modules/govuk-frontend/govuk-esm/components/tabs/tabs.mjs +8 -2
  198. data/node_modules/govuk-frontend/govuk-esm/i18n.mjs +380 -0
  199. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Date/now.mjs +13 -0
  200. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element/prototype/dataset.mjs +68 -0
  201. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/String/prototype/trim.mjs +13 -0
  202. data/node_modules/govuk-frontend/govuk-prototype-kit/init.js +7 -0
  203. data/node_modules/govuk-frontend/govuk-prototype-kit/init.scss +12 -0
  204. data/node_modules/govuk-frontend/govuk-prototype-kit.config.json +138 -7
  205. data/node_modules/govuk-frontend/package.json +1 -1
  206. metadata +22 -3
@@ -2,8 +2,42 @@ import '../../vendor/polyfills/Function/prototype/bind.mjs'
2
2
  import '../../vendor/polyfills/Event.mjs' // addEventListener
3
3
  import '../../vendor/polyfills/Element/prototype/closest.mjs'
4
4
 
5
- function ErrorSummary ($module) {
5
+ import { mergeConfigs } from '../../common/index.mjs'
6
+ import { normaliseDataset } from '../../common/normalise-dataset.mjs'
7
+
8
+ /**
9
+ * JavaScript enhancements for the ErrorSummary
10
+ *
11
+ * Takes focus on initialisation for accessible announcement, unless disabled in configuration.
12
+ *
13
+ * @class
14
+ * @param {HTMLElement} $module - The element this component controls
15
+ * @param {ErrorSummaryConfig} config - Error summary config
16
+ */
17
+ function ErrorSummary ($module, config) {
18
+ // Some consuming code may not be passing a module,
19
+ // for example if they initialise the component
20
+ // on their own by directly passing the result
21
+ // of `document.querySelector`.
22
+ // To avoid breaking further JavaScript initialisation
23
+ // we need to safeguard against this so things keep
24
+ // working the same now we read the elements data attributes
25
+ if (!$module) {
26
+ // Little safety in case code gets ported as-is
27
+ // into and ES6 class constructor, where the return value matters
28
+ return this
29
+ }
30
+
6
31
  this.$module = $module
32
+
33
+ var defaultConfig = {
34
+ disableAutoFocus: false
35
+ }
36
+ this.config = mergeConfigs(
37
+ defaultConfig,
38
+ config || {},
39
+ normaliseDataset($module.dataset)
40
+ )
7
41
  }
8
42
 
9
43
  ErrorSummary.prototype.init = function () {
@@ -22,7 +56,7 @@ ErrorSummary.prototype.init = function () {
22
56
  ErrorSummary.prototype.setFocus = function () {
23
57
  var $module = this.$module
24
58
 
25
- if ($module.getAttribute('data-disable-auto-focus') === 'true') {
59
+ if (this.config.disableAutoFocus) {
26
60
  return
27
61
  }
28
62
 
@@ -38,10 +72,10 @@ ErrorSummary.prototype.setFocus = function () {
38
72
  }
39
73
 
40
74
  /**
41
- * Click event handler
42
- *
43
- * @param {MouseEvent} event - Click event
44
- */
75
+ * Click event handler
76
+ *
77
+ * @param {MouseEvent} event - Click event
78
+ */
45
79
  ErrorSummary.prototype.handleClick = function (event) {
46
80
  var target = event.target
47
81
  if (this.focusTarget(target)) {
@@ -166,3 +200,11 @@ ErrorSummary.prototype.getAssociatedLegendOrLabel = function ($input) {
166
200
  }
167
201
 
168
202
  export default ErrorSummary
203
+
204
+ /**
205
+ * Error summary config
206
+ *
207
+ * @typedef {object} ErrorSummaryConfig
208
+ * @property {boolean} [disableAutoFocus = false] -
209
+ * If set to `true` the error summary will not be focussed when the page loads.
210
+ */
@@ -2,6 +2,12 @@ import '../../vendor/polyfills/Event.mjs'
2
2
  import '../../vendor/polyfills/Element/prototype/classList.mjs'
3
3
  import '../../vendor/polyfills/Function/prototype/bind.mjs'
4
4
 
5
+ /**
6
+ * Header component
7
+ *
8
+ * @class
9
+ * @param {HTMLElement} $module - HTML element to use for header
10
+ */
5
11
  function Header ($module) {
6
12
  this.$module = $module
7
13
  this.$menuButton = $module && $module.querySelector('.govuk-js-header-toggle')
@@ -1,7 +1,26 @@
1
1
  import '../../vendor/polyfills/Event.mjs' // addEventListener
2
2
 
3
- function NotificationBanner ($module) {
3
+ import { mergeConfigs } from '../../common/index.mjs'
4
+ import { normaliseDataset } from '../../common/normalise-dataset.mjs'
5
+
6
+ /**
7
+ * Notification Banner component
8
+ *
9
+ * @class
10
+ * @param {HTMLElement} $module - HTML element to use for notification banner
11
+ * @param {NotificationBannerConfig} config - Notification banner config
12
+ */
13
+ function NotificationBanner ($module, config) {
4
14
  this.$module = $module
15
+
16
+ var defaultConfig = {
17
+ disableAutoFocus: false
18
+ }
19
+ this.config = mergeConfigs(
20
+ defaultConfig,
21
+ config || {},
22
+ normaliseDataset($module.dataset)
23
+ )
5
24
  }
6
25
 
7
26
  /**
@@ -30,7 +49,7 @@ NotificationBanner.prototype.init = function () {
30
49
  NotificationBanner.prototype.setFocus = function () {
31
50
  var $module = this.$module
32
51
 
33
- if ($module.getAttribute('data-disable-auto-focus') === 'true') {
52
+ if (this.config.disableAutoFocus) {
34
53
  return
35
54
  }
36
55
 
@@ -53,3 +72,14 @@ NotificationBanner.prototype.setFocus = function () {
53
72
  }
54
73
 
55
74
  export default NotificationBanner
75
+
76
+ /**
77
+ * Notification banner config
78
+ *
79
+ * @typedef {object} NotificationBannerConfig
80
+ * @property {boolean} [disableAutoFocus = false] -
81
+ * If set to `true` the notification banner will not be focussed when the page
82
+ * loads. This only applies if the component has a `role` of `alert` – in
83
+ * other cases the component will not be focused on page load, regardless of
84
+ * this option.
85
+ */
@@ -2,8 +2,14 @@ import '../../vendor/polyfills/Function/prototype/bind.mjs'
2
2
  // addEventListener, event.target normalization and DOMContentLoaded
3
3
  import '../../vendor/polyfills/Event.mjs'
4
4
  import '../../vendor/polyfills/Element/prototype/classList.mjs'
5
- import { nodeListForEach } from '../../common.mjs'
5
+ import { nodeListForEach } from '../../common/index.mjs'
6
6
 
7
+ /**
8
+ * Radios component
9
+ *
10
+ * @class
11
+ * @param {HTMLElement} $module - HTML element to use for radios
12
+ */
7
13
  function Radios ($module) {
8
14
  this.$module = $module
9
15
  this.$inputs = $module.querySelectorAll('input[type="radio"]')
@@ -74,7 +80,7 @@ Radios.prototype.syncAllConditionalReveals = function () {
74
80
  * Synchronise the visibility of the conditional reveal, and its accessible
75
81
  * state, with the input's checked state.
76
82
  *
77
- * @param {HTMLInputElement} $input Radio input
83
+ * @param {HTMLInputElement} $input - Radio input
78
84
  */
79
85
  Radios.prototype.syncConditionalRevealWithInputState = function ($input) {
80
86
  var $target = document.getElementById($input.getAttribute('aria-controls'))
@@ -95,7 +101,7 @@ Radios.prototype.syncConditionalRevealWithInputState = function ($input) {
95
101
  * with the same name (because checking one radio could have un-checked a radio
96
102
  * in another $module)
97
103
  *
98
- * @param {MouseEvent} event Click event
104
+ * @param {MouseEvent} event - Click event
99
105
  */
100
106
  Radios.prototype.handleClick = function (event) {
101
107
  var $clickedInput = event.target
@@ -2,6 +2,12 @@ import '../../vendor/polyfills/Function/prototype/bind.mjs'
2
2
  import '../../vendor/polyfills/Element/prototype/classList.mjs'
3
3
  import '../../vendor/polyfills/Event.mjs' // addEventListener and event.target normalization
4
4
 
5
+ /**
6
+ * Skip link component
7
+ *
8
+ * @class
9
+ * @param {HTMLElement} $module - HTML element to use for skip link
10
+ */
5
11
  function SkipLink ($module) {
6
12
  this.$module = $module
7
13
  this.$linkedElement = null
@@ -27,10 +33,10 @@ SkipLink.prototype.init = function () {
27
33
  }
28
34
 
29
35
  /**
30
- * Get linked element
31
- *
32
- * @returns {HTMLElement} $linkedElement - DOM element linked to from the skip link
33
- */
36
+ * Get linked element
37
+ *
38
+ * @returns {HTMLElement} $linkedElement - DOM element linked to from the skip link
39
+ */
34
40
  SkipLink.prototype.getLinkedElement = function () {
35
41
  var linkedElementId = this.getFragmentFromUrl()
36
42
 
@@ -3,8 +3,14 @@ import '../../vendor/polyfills/Element/prototype/classList.mjs'
3
3
  import '../../vendor/polyfills/Element/prototype/nextElementSibling.mjs'
4
4
  import '../../vendor/polyfills/Element/prototype/previousElementSibling.mjs'
5
5
  import '../../vendor/polyfills/Event.mjs' // addEventListener and event.target normaliziation
6
- import { nodeListForEach } from '../../common.mjs'
7
-
6
+ import { nodeListForEach } from '../../common/index.mjs'
7
+
8
+ /**
9
+ * Tabs component
10
+ *
11
+ * @class
12
+ * @param {HTMLElement} $module - HTML element to use for tabs
13
+ */
8
14
  function Tabs ($module) {
9
15
  this.$module = $module
10
16
  this.$tabs = $module.querySelectorAll('.govuk-tabs__tab')
@@ -0,0 +1,380 @@
1
+ /**
2
+ * Internal support for selecting messages to render, with placeholder
3
+ * interpolation and locale-aware number formatting and pluralisation
4
+ *
5
+ * @class
6
+ * @private
7
+ * @param {TranslationsFlattened} translations - Key-value pairs of the translation strings to use.
8
+ * @param {object} [config] - Configuration options for the function.
9
+ * @param {string} config.locale - An overriding locale for the PluralRules functionality.
10
+ */
11
+ export function I18n (translations, config) {
12
+ // Make list of translations available throughout function
13
+ this.translations = translations || {}
14
+
15
+ // The locale to use for PluralRules and NumberFormat
16
+ this.locale = (config && config.locale) || document.documentElement.lang || 'en'
17
+ }
18
+
19
+ /**
20
+ * The most used function - takes the key for a given piece of UI text and
21
+ * returns the appropriate string.
22
+ *
23
+ * @param {string} lookupKey - The lookup key of the string to use.
24
+ * @param {object} options - Any options passed with the translation string, e.g: for string interpolation.
25
+ * @returns {string} The appropriate translation string.
26
+ */
27
+ I18n.prototype.t = function (lookupKey, options) {
28
+ if (!lookupKey) {
29
+ // Print a console error if no lookup key has been provided
30
+ throw new Error('i18n: lookup key missing')
31
+ }
32
+
33
+ // If the `count` option is set, determine which plural suffix is needed and
34
+ // change the lookupKey to match. We check to see if it's undefined instead of
35
+ // falsy, as this could legitimately be 0.
36
+ if (options && typeof options.count !== 'undefined') {
37
+ // Get the plural suffix
38
+ lookupKey = lookupKey + '.' + this.getPluralSuffix(lookupKey, options.count)
39
+ }
40
+
41
+ if (lookupKey in this.translations) {
42
+ // Fetch the translation string for that lookup key
43
+ var translationString = this.translations[lookupKey]
44
+
45
+ // Check for ${} placeholders in the translation string
46
+ if (translationString.match(/%{(.\S+)}/)) {
47
+ if (!options) {
48
+ throw new Error('i18n: cannot replace placeholders in string if no option data provided')
49
+ }
50
+
51
+ return this.replacePlaceholders(translationString, options)
52
+ } else {
53
+ return translationString
54
+ }
55
+ } else {
56
+ // If the key wasn't found in our translations object,
57
+ // return the lookup key itself as the fallback
58
+ return lookupKey
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Takes a translation string with placeholders, and replaces the placeholders
64
+ * with the provided data
65
+ *
66
+ * @param {string} translationString - The translation string
67
+ * @param {object} options - Any options passed with the translation string, e.g: for string interpolation.
68
+ * @returns {string} The translation string to output, with ${} placeholders replaced
69
+ */
70
+ I18n.prototype.replacePlaceholders = function (translationString, options) {
71
+ var formatter
72
+
73
+ if (this.hasIntlNumberFormatSupport()) {
74
+ formatter = new Intl.NumberFormat(this.locale)
75
+ }
76
+
77
+ return translationString.replace(/%{(.\S+)}/g, function (placeholderWithBraces, placeholderKey) {
78
+ if (Object.prototype.hasOwnProperty.call(options, placeholderKey)) {
79
+ var placeholderValue = options[placeholderKey]
80
+
81
+ // If a user has passed `false` as the value for the placeholder
82
+ // treat it as though the value should not be displayed
83
+ if (placeholderValue === false) {
84
+ return ''
85
+ }
86
+
87
+ // If the placeholder's value is a number, localise the number formatting
88
+ if (typeof placeholderValue === 'number' && formatter) {
89
+ return formatter.format(placeholderValue)
90
+ }
91
+
92
+ return placeholderValue
93
+ } else {
94
+ throw new Error('i18n: no data found to replace ' + placeholderWithBraces + ' placeholder in string')
95
+ }
96
+ })
97
+ }
98
+
99
+ /**
100
+ * Check to see if the browser supports Intl and Intl.PluralRules.
101
+ *
102
+ * It requires all conditions to be met in order to be supported:
103
+ * - The browser supports the Intl class (true in IE11)
104
+ * - The implementation of Intl supports PluralRules (NOT true in IE11)
105
+ * - The browser/OS has plural rules for the current locale (browser dependent)
106
+ *
107
+ * @returns {boolean} Returns true if all conditions are met. Returns false otherwise.
108
+ */
109
+ I18n.prototype.hasIntlPluralRulesSupport = function () {
110
+ return Boolean(window.Intl && ('PluralRules' in window.Intl && Intl.PluralRules.supportedLocalesOf(this.locale).length))
111
+ }
112
+
113
+ /**
114
+ * Check to see if the browser supports Intl and Intl.NumberFormat.
115
+ *
116
+ * It requires all conditions to be met in order to be supported:
117
+ * - The browser supports the Intl class (true in IE11)
118
+ * - The implementation of Intl supports NumberFormat (also true in IE11)
119
+ * - The browser/OS has number formatting rules for the current locale (browser dependent)
120
+ *
121
+ * @returns {boolean} Returns true if all conditions are met. Returns false otherwise.
122
+ */
123
+ I18n.prototype.hasIntlNumberFormatSupport = function () {
124
+ return Boolean(window.Intl && ('NumberFormat' in window.Intl && Intl.NumberFormat.supportedLocalesOf(this.locale).length))
125
+ }
126
+
127
+ /**
128
+ * Get the appropriate suffix for the plural form.
129
+ *
130
+ * Uses Intl.PluralRules (or our own fallback implementation) to get the
131
+ * 'preferred' form to use for the given count.
132
+ *
133
+ * Checks that a translation has been provided for that plural form – if it
134
+ * hasn't, it'll fall back to the 'other' plural form (unless that doesn't exist
135
+ * either, in which case an error will be thrown)
136
+ *
137
+ * @param {string} lookupKey - The lookup key of the string to use.
138
+ * @param {number} count - Number used to determine which pluralisation to use.
139
+ * @returns {PluralRule} The suffix associated with the correct pluralisation for this locale.
140
+ */
141
+ I18n.prototype.getPluralSuffix = function (lookupKey, count) {
142
+ // Validate that the number is actually a number.
143
+ //
144
+ // Number(count) will turn anything that can't be converted to a Number type
145
+ // into 'NaN'. isFinite filters out NaN, as it isn't a finite number.
146
+ count = Number(count)
147
+ if (!isFinite(count)) { return 'other' }
148
+
149
+ var preferredForm
150
+
151
+ // Check to verify that all the requirements for Intl.PluralRules are met.
152
+ // If so, we can use that instead of our custom implementation. Otherwise,
153
+ // use the hardcoded fallback.
154
+ if (this.hasIntlPluralRulesSupport()) {
155
+ preferredForm = new Intl.PluralRules(this.locale).select(count)
156
+ } else {
157
+ preferredForm = this.selectPluralFormUsingFallbackRules(count)
158
+ }
159
+
160
+ // Use the correct plural form if provided
161
+ if (lookupKey + '.' + preferredForm in this.translations) {
162
+ return preferredForm
163
+ // Fall back to `other` if the plural form is missing, but log a warning
164
+ // to the console
165
+ } else if (lookupKey + '.other' in this.translations) {
166
+ if (console && 'warn' in console) {
167
+ console.warn('i18n: Missing plural form ".' + preferredForm + '" for "' +
168
+ this.locale + '" locale. Falling back to ".other".')
169
+ }
170
+
171
+ return 'other'
172
+ // If the required `other` plural form is missing, all we can do is error
173
+ } else {
174
+ throw new Error(
175
+ 'i18n: Plural form ".other" is required for "' + this.locale + '" locale'
176
+ )
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Get the plural form using our fallback implementation
182
+ *
183
+ * This is split out into a separate function to make it easier to test the
184
+ * fallback behaviour in an environment where Intl.PluralRules exists.
185
+ *
186
+ * @param {number} count - Number used to determine which pluralisation to use.
187
+ * @returns {PluralRule} The pluralisation form for count in this locale.
188
+ */
189
+ I18n.prototype.selectPluralFormUsingFallbackRules = function (count) {
190
+ // Currently our custom code can only handle positive integers, so let's
191
+ // make sure our number is one of those.
192
+ count = Math.abs(Math.floor(count))
193
+
194
+ var ruleset = this.getPluralRulesForLocale()
195
+
196
+ if (ruleset) {
197
+ return I18n.pluralRules[ruleset](count)
198
+ }
199
+
200
+ return 'other'
201
+ }
202
+
203
+ /**
204
+ * Work out which pluralisation rules to use for the current locale
205
+ *
206
+ * The locale may include a regional indicator (such as en-GB), but we don't
207
+ * usually care about this part, as pluralisation rules are usually the same
208
+ * regardless of region. There are exceptions, however, (e.g. Portuguese) so
209
+ * this searches by both the full and shortened locale codes, just to be sure.
210
+ *
211
+ * @returns {PluralRuleName | undefined} The name of the pluralisation rule to use (a key for one
212
+ * of the functions in this.pluralRules)
213
+ */
214
+ I18n.prototype.getPluralRulesForLocale = function () {
215
+ var locale = this.locale
216
+ var localeShort = locale.split('-')[0]
217
+
218
+ // Look through the plural rules map to find which `pluralRule` is
219
+ // appropriate for our current `locale`.
220
+ for (var pluralRule in I18n.pluralRulesMap) {
221
+ if (Object.prototype.hasOwnProperty.call(I18n.pluralRulesMap, pluralRule)) {
222
+ var languages = I18n.pluralRulesMap[pluralRule]
223
+ for (var i = 0; i < languages.length; i++) {
224
+ if (languages[i] === locale || languages[i] === localeShort) {
225
+ return pluralRule
226
+ }
227
+ }
228
+ }
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Map of plural rules to languages where those rules apply.
234
+ *
235
+ * Note: These groups are named for the most dominant or recognisable language
236
+ * that uses each system. The groupings do not imply that the languages are
237
+ * related to one another. Many languages have evolved the same systems
238
+ * independently of one another.
239
+ *
240
+ * Code to support more languages can be found in the i18n spike:
241
+ * {@link https://github.com/alphagov/govuk-frontend/blob/spike-i18n-support/src/govuk/i18n.mjs}
242
+ *
243
+ * Languages currently supported:
244
+ *
245
+ * Arabic: Arabic (ar)
246
+ * Chinese: Burmese (my), Chinese (zh), Indonesian (id), Japanese (ja),
247
+ * Javanese (jv), Korean (ko), Malay (ms), Thai (th), Vietnamese (vi)
248
+ * French: Armenian (hy), Bangla (bn), French (fr), Gujarati (gu), Hindi (hi),
249
+ * Persian Farsi (fa), Punjabi (pa), Zulu (zu)
250
+ * German: Afrikaans (af), Albanian (sq), Azerbaijani (az), Basque (eu),
251
+ * Bulgarian (bg), Catalan (ca), Danish (da), Dutch (nl), English (en),
252
+ * Estonian (et), Finnish (fi), Georgian (ka), German (de), Greek (el),
253
+ * Hungarian (hu), Luxembourgish (lb), Norwegian (no), Somali (so),
254
+ * Swahili (sw), Swedish (sv), Tamil (ta), Telugu (te), Turkish (tr),
255
+ * Urdu (ur)
256
+ * Irish: Irish Gaelic (ga)
257
+ * Russian: Russian (ru), Ukrainian (uk)
258
+ * Scottish: Scottish Gaelic (gd)
259
+ * Spanish: European Portuguese (pt-PT), Italian (it), Spanish (es)
260
+ * Welsh: Welsh (cy)
261
+ *
262
+ * @type {Object<PluralRuleName, string[]>}
263
+ */
264
+ I18n.pluralRulesMap = {
265
+ arabic: ['ar'],
266
+ chinese: ['my', 'zh', 'id', 'ja', 'jv', 'ko', 'ms', 'th', 'vi'],
267
+ french: ['hy', 'bn', 'fr', 'gu', 'hi', 'fa', 'pa', 'zu'],
268
+ german: [
269
+ 'af', 'sq', 'az', 'eu', 'bg', 'ca', 'da', 'nl', 'en', 'et', 'fi', 'ka',
270
+ 'de', 'el', 'hu', 'lb', 'no', 'so', 'sw', 'sv', 'ta', 'te', 'tr', 'ur'
271
+ ],
272
+ irish: ['ga'],
273
+ russian: ['ru', 'uk'],
274
+ scottish: ['gd'],
275
+ spanish: ['pt-PT', 'it', 'es'],
276
+ welsh: ['cy']
277
+ }
278
+
279
+ /**
280
+ * Different pluralisation rule sets
281
+ *
282
+ * Returns the appropriate suffix for the plural form associated with `n`.
283
+ * Possible suffixes: 'zero', 'one', 'two', 'few', 'many', 'other' (the actual
284
+ * meaning of each differs per locale). 'other' should always exist, even in
285
+ * languages without plurals, such as Chinese.
286
+ * {@link https://cldr.unicode.org/index/cldr-spec/plural-rules}
287
+ *
288
+ * The count must be a positive integer. Negative numbers and decimals aren't accounted for
289
+ *
290
+ * @type {Object<string, function(number): PluralRule>}
291
+ */
292
+ I18n.pluralRules = {
293
+ arabic: function (n) {
294
+ if (n === 0) { return 'zero' }
295
+ if (n === 1) { return 'one' }
296
+ if (n === 2) { return 'two' }
297
+ if (n % 100 >= 3 && n % 100 <= 10) { return 'few' }
298
+ if (n % 100 >= 11 && n % 100 <= 99) { return 'many' }
299
+ return 'other'
300
+ },
301
+ chinese: function () {
302
+ return 'other'
303
+ },
304
+ french: function (n) {
305
+ return n === 0 || n === 1 ? 'one' : 'other'
306
+ },
307
+ german: function (n) {
308
+ return n === 1 ? 'one' : 'other'
309
+ },
310
+ irish: function (n) {
311
+ if (n === 1) { return 'one' }
312
+ if (n === 2) { return 'two' }
313
+ if (n >= 3 && n <= 6) { return 'few' }
314
+ if (n >= 7 && n <= 10) { return 'many' }
315
+ return 'other'
316
+ },
317
+ russian: function (n) {
318
+ var lastTwo = n % 100
319
+ var last = lastTwo % 10
320
+ if (last === 1 && lastTwo !== 11) { return 'one' }
321
+ if (last >= 2 && last <= 4 && !(lastTwo >= 12 && lastTwo <= 14)) { return 'few' }
322
+ if (last === 0 || (last >= 5 && last <= 9) || (lastTwo >= 11 && lastTwo <= 14)) { return 'many' }
323
+ // Note: The 'other' suffix is only used by decimal numbers in Russian.
324
+ // We don't anticipate it being used, but it's here for consistency.
325
+ return 'other'
326
+ },
327
+ scottish: function (n) {
328
+ if (n === 1 || n === 11) { return 'one' }
329
+ if (n === 2 || n === 12) { return 'two' }
330
+ if ((n >= 3 && n <= 10) || (n >= 13 && n <= 19)) { return 'few' }
331
+ return 'other'
332
+ },
333
+ spanish: function (n) {
334
+ if (n === 1) { return 'one' }
335
+ if (n % 1000000 === 0 && n !== 0) { return 'many' }
336
+ return 'other'
337
+ },
338
+ welsh: function (n) {
339
+ if (n === 0) { return 'zero' }
340
+ if (n === 1) { return 'one' }
341
+ if (n === 2) { return 'two' }
342
+ if (n === 3) { return 'few' }
343
+ if (n === 6) { return 'many' }
344
+ return 'other'
345
+ }
346
+ }
347
+
348
+ /**
349
+ * Supported languages for plural rules
350
+ *
351
+ * @typedef {'arabic' | 'chinese' | 'french' | 'german' | 'irish' | 'russian' | 'scottish' | 'spanish' | 'welsh'} PluralRuleName
352
+ */
353
+
354
+ /**
355
+ * Plural rule category mnemonic tags
356
+ *
357
+ * @typedef {'zero' | 'one' | 'two' | 'few' | 'many' | 'other'} PluralRule
358
+ */
359
+
360
+ /**
361
+ * Translated message by plural rule they correspond to.
362
+ *
363
+ * Allows to group pluralised messages under a single key when passing
364
+ * translations to a component's constructor
365
+ *
366
+ * @typedef {object} TranslationPluralForms
367
+ * @property {string} [other] - General plural form
368
+ * @property {string} [zero] - Plural form used with 0
369
+ * @property {string} [one] - Plural form used with 1
370
+ * @property {string} [two] - Plural form used with 2
371
+ * @property {string} [few] - Plural form used for a few
372
+ * @property {string} [many] - Plural form used for many
373
+ */
374
+
375
+ /**
376
+ * Translated messages (flattened)
377
+ *
378
+ * @private
379
+ * @typedef {Object<string, string> | {}} TranslationsFlattened
380
+ */
@@ -0,0 +1,13 @@
1
+ (function(undefined) {
2
+
3
+ // Detection from https://github.com/Financial-Times/polyfill-library/blob/v3.111.0/polyfills/Date/now/detect.js
4
+ var detect = ('Date' in self && 'now' in self.Date && 'getTime' in self.Date.prototype)
5
+
6
+ if (detect) return
7
+
8
+ // Polyfill from https://polyfill.io/v3/polyfill.js?version=3.111.0&features=Date.now&flags=always
9
+ Date.now = function () {
10
+ return new Date().getTime();
11
+ };
12
+
13
+ }).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
@@ -0,0 +1,68 @@
1
+ import '../../Object/defineProperty.mjs'
2
+ import '../../Element.mjs'
3
+
4
+ (function(undefined) {
5
+
6
+ // Detection from https://raw.githubusercontent.com/Financial-Times/polyfill-library/13cf7c340974d128d557580b5e2dafcd1b1192d1/polyfills/Element/prototype/dataset/detect.js
7
+ var detect = (function(){
8
+ if (!document.documentElement.dataset) {
9
+ return false;
10
+ }
11
+ var el = document.createElement('div');
12
+ el.setAttribute("data-a-b", "c");
13
+ return el.dataset && el.dataset.aB == "c";
14
+ }())
15
+
16
+ if (detect) return
17
+
18
+ // Polyfill derived from https://raw.githubusercontent.com/Financial-Times/polyfill-library/13cf7c340974d128d557580b5e2dafcd1b1192d1/polyfills/Element/prototype/dataset/polyfill.js
19
+ Object.defineProperty(Element.prototype, 'dataset', {
20
+ get: function() {
21
+ var element = this;
22
+ var attributes = this.attributes;
23
+ var map = {};
24
+
25
+ for (var i = 0; i < attributes.length; i++) {
26
+ var attribute = attributes[i];
27
+
28
+ // This regex has been edited from the original polyfill, to add
29
+ // support for period (.) separators in data-* attribute names. These
30
+ // are allowed in the HTML spec, but were not covered by the original
31
+ // polyfill's regex. We use periods in our i18n implementation.
32
+ if (attribute && attribute.name && (/^data-\w[.\w-]*$/).test(attribute.name)) {
33
+ var name = attribute.name;
34
+ var value = attribute.value;
35
+
36
+ var propName = name.substr(5).replace(/-./g, function (prop) {
37
+ return prop.charAt(1).toUpperCase();
38
+ });
39
+
40
+ // If this browser supports __defineGetter__ and __defineSetter__,
41
+ // continue using defineProperty. If not (like IE 8 and below), we use
42
+ // a hacky fallback which at least gives an object in the right format
43
+ if ('__defineGetter__' in Object.prototype && '__defineSetter__' in Object.prototype) {
44
+ Object.defineProperty(map, propName, {
45
+ enumerable: true,
46
+ get: function() {
47
+ return this.value;
48
+ }.bind({value: value || ''}),
49
+ set: function setter(name, value) {
50
+ if (typeof value !== 'undefined') {
51
+ this.setAttribute(name, value);
52
+ } else {
53
+ this.removeAttribute(name);
54
+ }
55
+ }.bind(element, name)
56
+ });
57
+ } else {
58
+ map[propName] = value
59
+ }
60
+
61
+ }
62
+ }
63
+
64
+ return map;
65
+ }
66
+ });
67
+
68
+ }).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});