govuk_publishing_components 34.7.0 → 34.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-event-tracker.js +3 -10
  3. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-form-tracker.js +113 -0
  4. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-link-tracker.js +2 -11
  5. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-schemas.js +15 -1
  6. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-specialist-link-tracker.js +20 -29
  7. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4.js +1 -0
  8. data/app/models/govuk_publishing_components/component_doc.rb +14 -0
  9. data/app/models/govuk_publishing_components/component_wrapper_helper_options.rb +14 -0
  10. data/app/views/govuk_publishing_components/component_guide/show.html.erb +21 -6
  11. data/app/views/govuk_publishing_components/components/_action_link.html.erb +5 -2
  12. data/app/views/govuk_publishing_components/components/_breadcrumbs.html.erb +6 -2
  13. data/app/views/govuk_publishing_components/components/_details.html.erb +3 -1
  14. data/app/views/govuk_publishing_components/components/docs/action_link.yml +1 -0
  15. data/app/views/govuk_publishing_components/components/docs/breadcrumbs.yml +1 -0
  16. data/app/views/govuk_publishing_components/components/docs/details.yml +17 -0
  17. data/lib/govuk_publishing_components/presenters/component_wrapper_helper.rb +78 -0
  18. data/lib/govuk_publishing_components/presenters/related_navigation_helper.rb +0 -7
  19. data/lib/govuk_publishing_components/version.rb +1 -1
  20. data/lib/govuk_publishing_components.rb +1 -0
  21. data/node_modules/govuk-frontend/govuk/all.js +4029 -3792
  22. data/node_modules/govuk-frontend/govuk/all.js.map +1 -0
  23. data/node_modules/govuk-frontend/govuk/common/closest-attribute-value.js +52 -51
  24. data/node_modules/govuk-frontend/govuk/common/closest-attribute-value.js.map +1 -0
  25. data/node_modules/govuk-frontend/govuk/common/index.js +153 -145
  26. data/node_modules/govuk-frontend/govuk/common/index.js.map +1 -0
  27. data/node_modules/govuk-frontend/govuk/common/normalise-dataset.js +324 -321
  28. data/node_modules/govuk-frontend/govuk/common/normalise-dataset.js.map +1 -0
  29. data/node_modules/govuk-frontend/govuk/common.js +154 -146
  30. data/node_modules/govuk-frontend/govuk/common.js.map +1 -0
  31. data/node_modules/govuk-frontend/govuk/components/_all.scss +1 -1
  32. data/node_modules/govuk-frontend/govuk/components/accordion/_index.scss +23 -4
  33. data/node_modules/govuk-frontend/govuk/components/accordion/accordion.js +2059 -1654
  34. data/node_modules/govuk-frontend/govuk/components/accordion/accordion.js.map +1 -0
  35. data/node_modules/govuk-frontend/govuk/components/accordion/fixtures.json +11 -11
  36. data/node_modules/govuk-frontend/govuk/components/accordion/template.njk +1 -1
  37. data/node_modules/govuk-frontend/govuk/components/back-link/_index.scss +19 -19
  38. data/node_modules/govuk-frontend/govuk/components/breadcrumbs/_index.scss +21 -10
  39. data/node_modules/govuk-frontend/govuk/components/button/button.js +927 -917
  40. data/node_modules/govuk-frontend/govuk/components/button/button.js.map +1 -0
  41. data/node_modules/govuk-frontend/govuk/components/character-count/character-count.js +2050 -2040
  42. data/node_modules/govuk-frontend/govuk/components/character-count/character-count.js.map +1 -0
  43. data/node_modules/govuk-frontend/govuk/components/checkboxes/checkboxes.js +1155 -1147
  44. data/node_modules/govuk-frontend/govuk/components/checkboxes/checkboxes.js.map +1 -0
  45. data/node_modules/govuk-frontend/govuk/components/cookie-banner/fixtures.json +23 -23
  46. data/node_modules/govuk-frontend/govuk/components/cookie-banner/template.njk +1 -1
  47. data/node_modules/govuk-frontend/govuk/components/details/details.js +800 -799
  48. data/node_modules/govuk-frontend/govuk/components/details/details.js.map +1 -0
  49. data/node_modules/govuk-frontend/govuk/components/error-summary/error-summary.js +1058 -1045
  50. data/node_modules/govuk-frontend/govuk/components/error-summary/error-summary.js.map +1 -0
  51. data/node_modules/govuk-frontend/govuk/components/header/_index.scss +6 -0
  52. data/node_modules/govuk-frontend/govuk/components/header/header.js +646 -998
  53. data/node_modules/govuk-frontend/govuk/components/header/header.js.map +1 -0
  54. data/node_modules/govuk-frontend/govuk/components/notification-banner/notification-banner.js +760 -752
  55. data/node_modules/govuk-frontend/govuk/components/notification-banner/notification-banner.js.map +1 -0
  56. data/node_modules/govuk-frontend/govuk/components/pagination/fixtures.json +61 -0
  57. data/node_modules/govuk-frontend/govuk/components/pagination/template.njk +1 -1
  58. data/node_modules/govuk-frontend/govuk/components/phase-banner/macro-options.json +1 -1
  59. data/node_modules/govuk-frontend/govuk/components/radios/radios.js +1110 -1107
  60. data/node_modules/govuk-frontend/govuk/components/radios/radios.js.map +1 -0
  61. data/node_modules/govuk-frontend/govuk/components/skip-link/skip-link.js +1017 -1014
  62. data/node_modules/govuk-frontend/govuk/components/skip-link/skip-link.js.map +1 -0
  63. data/node_modules/govuk-frontend/govuk/components/summary-list/_index.scss +107 -0
  64. data/node_modules/govuk-frontend/govuk/components/summary-list/fixtures.json +318 -23
  65. data/node_modules/govuk-frontend/govuk/components/summary-list/macro-options.json +110 -0
  66. data/node_modules/govuk-frontend/govuk/components/summary-list/template.njk +72 -28
  67. data/node_modules/govuk-frontend/govuk/components/tabs/tabs.js +1392 -1264
  68. data/node_modules/govuk-frontend/govuk/components/tabs/tabs.js.map +1 -0
  69. data/node_modules/govuk-frontend/govuk/i18n.js +363 -364
  70. data/node_modules/govuk-frontend/govuk/i18n.js.map +1 -0
  71. data/node_modules/govuk-frontend/govuk/settings/_measurements.scss +5 -5
  72. data/node_modules/govuk-frontend/govuk/vendor/polyfills/DOMTokenList.js +242 -241
  73. data/node_modules/govuk-frontend/govuk/vendor/polyfills/DOMTokenList.js.map +1 -0
  74. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Date/now.js +13 -12
  75. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Date/now.js.map +1 -0
  76. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Document.js +17 -16
  77. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Document.js.map +1 -0
  78. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/classList.js +547 -546
  79. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/classList.js.map +1 -0
  80. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/closest.js +37 -36
  81. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/closest.js.map +1 -0
  82. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/dataset.js +251 -250
  83. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/dataset.js.map +1 -0
  84. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/matches.js +21 -20
  85. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/matches.js.map +1 -0
  86. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/nextElementSibling.js +198 -197
  87. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/nextElementSibling.js.map +1 -0
  88. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/previousElementSibling.js +198 -197
  89. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/previousElementSibling.js.map +1 -0
  90. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element.js +106 -105
  91. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element.js.map +1 -0
  92. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Event.js +400 -399
  93. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Event.js.map +1 -0
  94. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Function/prototype/bind.js +239 -238
  95. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Function/prototype/bind.js.map +1 -0
  96. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Object/defineProperty.js +72 -71
  97. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Object/defineProperty.js.map +1 -0
  98. data/node_modules/govuk-frontend/govuk/vendor/polyfills/String/prototype/trim.js +14 -13
  99. data/node_modules/govuk-frontend/govuk/vendor/polyfills/String/prototype/trim.js.map +1 -0
  100. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Window.js +17 -16
  101. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Window.js.map +1 -0
  102. data/node_modules/govuk-frontend/govuk-esm/all.mjs +2 -2
  103. data/node_modules/govuk-frontend/govuk-esm/common/index.mjs +17 -10
  104. data/node_modules/govuk-frontend/govuk-esm/common/normalise-dataset.mjs +3 -1
  105. data/node_modules/govuk-frontend/govuk-esm/components/accordion/accordion.mjs +135 -52
  106. data/node_modules/govuk-frontend/govuk-esm/components/button/button.mjs +11 -9
  107. data/node_modules/govuk-frontend/govuk-esm/components/character-count/character-count.mjs +10 -7
  108. data/node_modules/govuk-frontend/govuk-esm/components/checkboxes/checkboxes.mjs +24 -18
  109. data/node_modules/govuk-frontend/govuk-esm/components/details/details.mjs +23 -16
  110. data/node_modules/govuk-frontend/govuk-esm/components/error-summary/error-summary.mjs +15 -11
  111. data/node_modules/govuk-frontend/govuk-esm/components/header/header.mjs +3 -2
  112. data/node_modules/govuk-frontend/govuk-esm/components/notification-banner/notification-banner.mjs +3 -4
  113. data/node_modules/govuk-frontend/govuk-esm/components/radios/radios.mjs +10 -9
  114. data/node_modules/govuk-frontend/govuk-esm/components/skip-link/skip-link.mjs +5 -3
  115. data/node_modules/govuk-frontend/govuk-esm/components/tabs/tabs.mjs +165 -38
  116. data/node_modules/govuk-frontend/govuk-esm/i18n.mjs +9 -11
  117. data/node_modules/govuk-frontend/package.json +1 -1
  118. metadata +37 -2
@@ -1,9 +1,14 @@
1
+ /* eslint-disable es-x/no-function-prototype-bind -- Polyfill imported */
2
+ /* eslint-disable es-x/no-string-prototype-trim -- Polyfill imported */
3
+
1
4
  import { nodeListForEach, mergeConfigs, extractConfigByNamespace } from '../../common/index.mjs'
5
+ import { normaliseDataset } from '../../common/normalise-dataset.mjs'
2
6
  import { I18n } from '../../i18n.mjs'
3
- import '../../vendor/polyfills/Function/prototype/bind.mjs'
4
7
  import '../../vendor/polyfills/Element/prototype/classList.mjs'
8
+ import '../../vendor/polyfills/Element/prototype/closest.mjs'
9
+ import '../../vendor/polyfills/Event.mjs' // addEventListener, event.target normalization and DOMContentLoaded
10
+ import '../../vendor/polyfills/Function/prototype/bind.mjs'
5
11
  import '../../vendor/polyfills/String/prototype/trim.mjs'
6
- import { normaliseDataset } from '../../common/normalise-dataset.mjs'
7
12
 
8
13
  /**
9
14
  * @constant
@@ -38,27 +43,29 @@ var ACCORDION_TRANSLATIONS = {
38
43
  */
39
44
  function Accordion ($module, config) {
40
45
  this.$module = $module
41
- this.$sections = $module.querySelectorAll('.govuk-accordion__section')
42
- this.browserSupportsSessionStorage = helper.checkForSessionStorage()
43
46
 
44
47
  var defaultConfig = {
45
48
  i18n: ACCORDION_TRANSLATIONS
46
49
  }
50
+
47
51
  this.config = mergeConfigs(
48
52
  defaultConfig,
49
53
  config || {},
50
54
  normaliseDataset($module.dataset)
51
55
  )
56
+
52
57
  this.i18n = new I18n(extractConfigByNamespace(this.config, 'i18n'))
53
58
 
54
59
  this.controlsClass = 'govuk-accordion__controls'
55
60
  this.showAllClass = 'govuk-accordion__show-all'
56
61
  this.showAllTextClass = 'govuk-accordion__show-all-text'
57
62
 
63
+ this.sectionClass = 'govuk-accordion__section'
58
64
  this.sectionExpandedClass = 'govuk-accordion__section--expanded'
59
65
  this.sectionButtonClass = 'govuk-accordion__section-button'
60
66
  this.sectionHeaderClass = 'govuk-accordion__section-header'
61
67
  this.sectionHeadingClass = 'govuk-accordion__section-heading'
68
+ this.sectionHeadingDividerClass = 'govuk-accordion__section-heading-divider'
62
69
  this.sectionHeadingTextClass = 'govuk-accordion__section-heading-text'
63
70
  this.sectionHeadingTextFocusClass = 'govuk-accordion__section-heading-text-focus'
64
71
 
@@ -70,9 +77,15 @@ function Accordion ($module, config) {
70
77
 
71
78
  this.sectionSummaryClass = 'govuk-accordion__section-summary'
72
79
  this.sectionSummaryFocusClass = 'govuk-accordion__section-summary-focus'
80
+ this.sectionContentClass = 'govuk-accordion__section-content'
81
+
82
+ this.$sections = this.$module.querySelectorAll('.' + this.sectionClass)
83
+ this.browserSupportsSessionStorage = helper.checkForSessionStorage()
73
84
  }
74
85
 
75
- // Initialize component
86
+ /**
87
+ * Initialise component
88
+ */
76
89
  Accordion.prototype.init = function () {
77
90
  // Check for module
78
91
  if (!this.$module) {
@@ -87,7 +100,9 @@ Accordion.prototype.init = function () {
87
100
  this.updateShowAllButton(areAllSectionsOpen)
88
101
  }
89
102
 
90
- // Initialise controls and set attributes
103
+ /**
104
+ * Initialise controls and set attributes
105
+ */
91
106
  Accordion.prototype.initControls = function () {
92
107
  // Create "Show all" button and set attributes
93
108
  this.$showAllButton = document.createElement('button')
@@ -96,9 +111,9 @@ Accordion.prototype.initControls = function () {
96
111
  this.$showAllButton.setAttribute('aria-expanded', 'false')
97
112
 
98
113
  // Create icon, add to element
99
- var $icon = document.createElement('span')
100
- $icon.classList.add(this.upChevronIconClass)
101
- this.$showAllButton.appendChild($icon)
114
+ this.$showAllIcon = document.createElement('span')
115
+ this.$showAllIcon.classList.add(this.upChevronIconClass)
116
+ this.$showAllButton.appendChild(this.$showAllIcon)
102
117
 
103
118
  // Create control wrapper and add controls to it
104
119
  var $accordionControls = document.createElement('div')
@@ -107,15 +122,22 @@ Accordion.prototype.initControls = function () {
107
122
  this.$module.insertBefore($accordionControls, this.$module.firstChild)
108
123
 
109
124
  // Build additional wrapper for Show all toggle text and place after icon
110
- var $wrappershowAllText = document.createElement('span')
111
- $wrappershowAllText.classList.add(this.showAllTextClass)
112
- this.$showAllButton.appendChild($wrappershowAllText)
125
+ this.$showAllText = document.createElement('span')
126
+ this.$showAllText.classList.add(this.showAllTextClass)
127
+ this.$showAllButton.appendChild(this.$showAllText)
113
128
 
114
129
  // Handle click events on the show/hide all button
115
130
  this.$showAllButton.addEventListener('click', this.onShowOrHideAllToggle.bind(this))
131
+
132
+ // Handle 'beforematch' events, if the user agent supports them
133
+ if ('onbeforematch' in document) {
134
+ document.addEventListener('beforematch', this.onBeforeMatch.bind(this))
135
+ }
116
136
  }
117
137
 
118
- // Initialise section headers
138
+ /**
139
+ * Initialise section headers
140
+ */
119
141
  Accordion.prototype.initSectionHeaders = function () {
120
142
  // Loop through section headers
121
143
  nodeListForEach(this.$sections, function ($section, i) {
@@ -133,10 +155,16 @@ Accordion.prototype.initSectionHeaders = function () {
133
155
  }.bind(this))
134
156
  }
135
157
 
136
- Accordion.prototype.constructHeaderMarkup = function ($headerWrapper, index) {
137
- var $span = $headerWrapper.querySelector('.' + this.sectionButtonClass)
138
- var $heading = $headerWrapper.querySelector('.' + this.sectionHeadingClass)
139
- var $summary = $headerWrapper.querySelector('.' + this.sectionSummaryClass)
158
+ /**
159
+ * Construct section header
160
+ *
161
+ * @param {HTMLDivElement} $header - Section header
162
+ * @param {number} index - Section index
163
+ */
164
+ Accordion.prototype.constructHeaderMarkup = function ($header, index) {
165
+ var $span = $header.querySelector('.' + this.sectionButtonClass)
166
+ var $heading = $header.querySelector('.' + this.sectionHeadingClass)
167
+ var $summary = $header.querySelector('.' + this.sectionSummaryClass)
140
168
 
141
169
  // Create a button element that will replace the '.govuk-accordion__section-button' span
142
170
  var $button = document.createElement('button')
@@ -168,23 +196,23 @@ Accordion.prototype.constructHeaderMarkup = function ($headerWrapper, index) {
168
196
  $headingTextFocus.innerHTML = $span.innerHTML
169
197
 
170
198
  // Create container for show / hide icons and text.
171
- var $showToggle = document.createElement('span')
172
- $showToggle.classList.add(this.sectionShowHideToggleClass)
199
+ var $showHideToggle = document.createElement('span')
200
+ $showHideToggle.classList.add(this.sectionShowHideToggleClass)
173
201
  // Tell Google not to index the 'show' text as part of the heading
174
202
  // For the snippet to work with JavaScript, it must be added before adding the page element to the
175
203
  // page's DOM. See https://developers.google.com/search/docs/advanced/robots/robots_meta_tag#data-nosnippet-attr
176
- $showToggle.setAttribute('data-nosnippet', '')
204
+ $showHideToggle.setAttribute('data-nosnippet', '')
177
205
  // Create an inner container to limit the width of the focus state
178
- var $showToggleFocus = document.createElement('span')
179
- $showToggleFocus.classList.add(this.sectionShowHideToggleFocusClass)
180
- $showToggle.appendChild($showToggleFocus)
206
+ var $showHideToggleFocus = document.createElement('span')
207
+ $showHideToggleFocus.classList.add(this.sectionShowHideToggleFocusClass)
208
+ $showHideToggle.appendChild($showHideToggleFocus)
181
209
  // Create wrapper for the show / hide text. Append text after the show/hide icon
182
- var $showToggleText = document.createElement('span')
183
- var $icon = document.createElement('span')
184
- $icon.classList.add(this.upChevronIconClass)
185
- $showToggleFocus.appendChild($icon)
186
- $showToggleText.classList.add(this.sectionShowHideTextClass)
187
- $showToggleFocus.appendChild($showToggleText)
210
+ var $showHideText = document.createElement('span')
211
+ var $showHideIcon = document.createElement('span')
212
+ $showHideIcon.classList.add(this.upChevronIconClass)
213
+ $showHideToggleFocus.appendChild($showHideIcon)
214
+ $showHideText.classList.add(this.sectionShowHideTextClass)
215
+ $showHideToggleFocus.appendChild($showHideText)
188
216
 
189
217
  // Append elements to the button:
190
218
  // 1. Heading text
@@ -223,13 +251,29 @@ Accordion.prototype.constructHeaderMarkup = function ($headerWrapper, index) {
223
251
  $button.appendChild(this.getButtonPunctuationEl())
224
252
  }
225
253
 
226
- $button.appendChild($showToggle)
254
+ $button.appendChild($showHideToggle)
227
255
 
228
256
  $heading.removeChild($span)
229
257
  $heading.appendChild($button)
230
258
  }
231
259
 
232
- // When section toggled, set and store state
260
+ /**
261
+ * When a section is opened by the user agent via the 'beforematch' event
262
+ *
263
+ * @param {Event} event - Generic event
264
+ */
265
+ Accordion.prototype.onBeforeMatch = function (event) {
266
+ var $section = event.target.closest('.' + this.sectionClass)
267
+ if ($section) {
268
+ this.setExpanded(true, $section)
269
+ }
270
+ }
271
+
272
+ /**
273
+ * When section toggled, set and store state
274
+ *
275
+ * @param {HTMLElement} $section - Section element
276
+ */
233
277
  Accordion.prototype.onSectionToggle = function ($section) {
234
278
  var expanded = this.isExpanded($section)
235
279
  this.setExpanded(!expanded, $section)
@@ -238,7 +282,9 @@ Accordion.prototype.onSectionToggle = function ($section) {
238
282
  this.storeState($section)
239
283
  }
240
284
 
241
- // When Open/Close All toggled, set and store state
285
+ /**
286
+ * When Open/Close All toggled, set and store state
287
+ */
242
288
  Accordion.prototype.onShowOrHideAllToggle = function () {
243
289
  var $module = this
244
290
  var $sections = this.$sections
@@ -253,11 +299,18 @@ Accordion.prototype.onShowOrHideAllToggle = function () {
253
299
  $module.updateShowAllButton(nowExpanded)
254
300
  }
255
301
 
256
- // Set section attributes when opened/closed
302
+ /**
303
+ * Set section attributes when opened/closed
304
+ *
305
+ * @param {boolean} expanded - Section expanded
306
+ * @param {HTMLElement} $section - Section element
307
+ */
257
308
  Accordion.prototype.setExpanded = function (expanded, $section) {
258
- var $icon = $section.querySelector('.' + this.upChevronIconClass)
309
+ var $showHideIcon = $section.querySelector('.' + this.upChevronIconClass)
259
310
  var $showHideText = $section.querySelector('.' + this.sectionShowHideTextClass)
260
311
  var $button = $section.querySelector('.' + this.sectionButtonClass)
312
+ var $content = $section.querySelector('.' + this.sectionContentClass)
313
+
261
314
  var newButtonText = expanded
262
315
  ? this.i18n.t('hideSection')
263
316
  : this.i18n.t('showSection')
@@ -266,8 +319,12 @@ Accordion.prototype.setExpanded = function (expanded, $section) {
266
319
  $button.setAttribute('aria-expanded', expanded)
267
320
 
268
321
  // Update aria-label combining
269
- var $header = $section.querySelector('.' + this.sectionHeadingTextClass)
270
- var ariaLabelParts = [$header.innerText.trim()]
322
+ var ariaLabelParts = []
323
+
324
+ var $headingText = $section.querySelector('.' + this.sectionHeadingTextClass)
325
+ if ($headingText) {
326
+ ariaLabelParts.push($headingText.innerText.trim())
327
+ }
271
328
 
272
329
  var $summary = $section.querySelector('.' + this.sectionSummaryClass)
273
330
  if ($summary) {
@@ -288,11 +345,13 @@ Accordion.prototype.setExpanded = function (expanded, $section) {
288
345
 
289
346
  // Swap icon, change class
290
347
  if (expanded) {
348
+ $content.removeAttribute('hidden')
291
349
  $section.classList.add(this.sectionExpandedClass)
292
- $icon.classList.remove(this.downChevronIconClass)
350
+ $showHideIcon.classList.remove(this.downChevronIconClass)
293
351
  } else {
352
+ $content.setAttribute('hidden', 'until-found')
294
353
  $section.classList.remove(this.sectionExpandedClass)
295
- $icon.classList.add(this.downChevronIconClass)
354
+ $showHideIcon.classList.add(this.downChevronIconClass)
296
355
  }
297
356
 
298
357
  // See if "Show all sections" button text should be updated
@@ -300,12 +359,21 @@ Accordion.prototype.setExpanded = function (expanded, $section) {
300
359
  this.updateShowAllButton(areAllSectionsOpen)
301
360
  }
302
361
 
303
- // Get state of section
362
+ /**
363
+ * Get state of section
364
+ *
365
+ * @param {HTMLElement} $section - Section element
366
+ * @returns {boolean} True if expanded
367
+ */
304
368
  Accordion.prototype.isExpanded = function ($section) {
305
369
  return $section.classList.contains(this.sectionExpandedClass)
306
370
  }
307
371
 
308
- // Check if all sections are open
372
+ /**
373
+ * Check if all sections are open
374
+ *
375
+ * @returns {boolean} True if all sections are open
376
+ */
309
377
  Accordion.prototype.checkIfAllSectionsOpen = function () {
310
378
  // Get a count of all the Accordion sections
311
379
  var sectionsCount = this.$sections.length
@@ -316,26 +384,33 @@ Accordion.prototype.checkIfAllSectionsOpen = function () {
316
384
  return areAllSectionsOpen
317
385
  }
318
386
 
319
- // Update "Show all sections" button
387
+ /**
388
+ * Update "Show all sections" button
389
+ *
390
+ * @param {boolean} expanded - Section expanded
391
+ */
320
392
  Accordion.prototype.updateShowAllButton = function (expanded) {
321
- var $showAllIcon = this.$showAllButton.querySelector('.' + this.upChevronIconClass)
322
- var $showAllText = this.$showAllButton.querySelector('.' + this.showAllTextClass)
323
393
  var newButtonText = expanded
324
394
  ? this.i18n.t('hideAllSections')
325
395
  : this.i18n.t('showAllSections')
396
+
326
397
  this.$showAllButton.setAttribute('aria-expanded', expanded)
327
- $showAllText.innerText = newButtonText
398
+ this.$showAllText.innerText = newButtonText
328
399
 
329
400
  // Swap icon, toggle class
330
401
  if (expanded) {
331
- $showAllIcon.classList.remove(this.downChevronIconClass)
402
+ this.$showAllIcon.classList.remove(this.downChevronIconClass)
332
403
  } else {
333
- $showAllIcon.classList.add(this.downChevronIconClass)
404
+ this.$showAllIcon.classList.add(this.downChevronIconClass)
334
405
  }
335
406
  }
336
407
 
337
- // Check for `window.sessionStorage`, and that it actually works.
338
408
  var helper = {
409
+ /**
410
+ * Check for `window.sessionStorage`, and that it actually works.
411
+ *
412
+ * @returns {boolean} True if session storage is available
413
+ */
339
414
  checkForSessionStorage: function () {
340
415
  var testString = 'this is the test string'
341
416
  var result
@@ -350,7 +425,11 @@ var helper = {
350
425
  }
351
426
  }
352
427
 
353
- // Set the state of the accordions in sessionStorage
428
+ /**
429
+ * Set the state of the accordions in sessionStorage
430
+ *
431
+ * @param {HTMLElement} $section - Section element
432
+ */
354
433
  Accordion.prototype.storeState = function ($section) {
355
434
  if (this.browserSupportsSessionStorage) {
356
435
  // We need a unique way of identifying each content in the Accordion. Since
@@ -370,7 +449,11 @@ Accordion.prototype.storeState = function ($section) {
370
449
  }
371
450
  }
372
451
 
373
- // Read the state of the accordions from sessionStorage
452
+ /**
453
+ * Read the state of the accordions from sessionStorage
454
+ *
455
+ * @param {HTMLElement} $section - Section element
456
+ */
374
457
  Accordion.prototype.setInitialState = function ($section) {
375
458
  if (this.browserSupportsSessionStorage) {
376
459
  var $button = $section.querySelector('.' + this.sectionButtonClass)
@@ -389,15 +472,15 @@ Accordion.prototype.setInitialState = function ($section) {
389
472
  /**
390
473
  * Create an element to improve semantics of the section button with punctuation
391
474
  *
392
- * @returns {HTMLSpanElement} DOM element
393
- *
394
475
  * Adding punctuation to the button can also improve its general semantics by dividing its contents
395
476
  * into thematic chunks.
396
477
  * See https://github.com/alphagov/govuk-frontend/issues/2327#issuecomment-922957442
478
+ *
479
+ * @returns {HTMLElement} DOM element
397
480
  */
398
481
  Accordion.prototype.getButtonPunctuationEl = function () {
399
482
  var $punctuationEl = document.createElement('span')
400
- $punctuationEl.classList.add('govuk-visually-hidden', 'govuk-accordion__section-heading-divider')
483
+ $punctuationEl.classList.add('govuk-visually-hidden', this.sectionHeadingDividerClass)
401
484
  $punctuationEl.innerHTML = ', '
402
485
  return $punctuationEl
403
486
  }
@@ -1,6 +1,8 @@
1
+ /* eslint-disable es-x/no-function-prototype-bind -- Polyfill imported */
2
+
1
3
  import { mergeConfigs } from '../../common/index.mjs'
2
4
  import { normaliseDataset } from '../../common/normalise-dataset.mjs'
3
- import '../../vendor/polyfills/Event.mjs' // addEventListener and event.target normaliziation
5
+ import '../../vendor/polyfills/Event.mjs' // addEventListener, event.target normalization and DOMContentLoaded
4
6
  import '../../vendor/polyfills/Function/prototype/bind.mjs'
5
7
 
6
8
  var KEY_SPACE = 32
@@ -10,8 +12,8 @@ var DEBOUNCE_TIMEOUT_IN_SECONDS = 1
10
12
  * JavaScript enhancements for the Button component
11
13
  *
12
14
  * @class
13
- * @param {HTMLElement} $module - The element this component controls
14
- * @param {ButtonConfig} config - Button config
15
+ * @param {HTMLElement} $module - HTML element to use for button
16
+ * @param {ButtonConfig} [config] - Button config
15
17
  */
16
18
  function Button ($module, config) {
17
19
  if (!$module) {
@@ -51,14 +53,14 @@ Button.prototype.init = function () {
51
53
  *
52
54
  * See https://github.com/alphagov/govuk_elements/pull/272#issuecomment-233028270
53
55
  *
54
- * @param {KeyboardEvent} event
56
+ * @param {KeyboardEvent} event - Keydown event
55
57
  */
56
58
  Button.prototype.handleKeyDown = function (event) {
57
- var target = event.target
59
+ var $target = event.target
58
60
 
59
- if (target.getAttribute('role') === 'button' && event.keyCode === KEY_SPACE) {
61
+ if ($target.getAttribute('role') === 'button' && event.keyCode === KEY_SPACE) {
60
62
  event.preventDefault() // prevent the page from scrolling
61
- target.click()
63
+ $target.click()
62
64
  }
63
65
  }
64
66
 
@@ -69,8 +71,8 @@ Button.prototype.handleKeyDown = function (event) {
69
71
  * stops people accidentally causing multiple form submissions by double
70
72
  * clicking buttons.
71
73
  *
72
- * @param {MouseEvent} event
73
- * @returns {undefined | false} - Returns undefined, or false when debounced
74
+ * @param {MouseEvent} event - Mouse click event
75
+ * @returns {undefined | false} Returns undefined, or false when debounced
74
76
  */
75
77
  Button.prototype.debounce = function (event) {
76
78
  // Check the button that was clicked has preventDoubleClick enabled
@@ -1,11 +1,14 @@
1
- import '../../vendor/polyfills/Date/now.mjs'
2
- import '../../vendor/polyfills/Function/prototype/bind.mjs'
3
- import '../../vendor/polyfills/Event.mjs' // addEventListener and event.target normalisation
4
- import '../../vendor/polyfills/Element/prototype/classList.mjs'
1
+ /* eslint-disable es-x/no-date-now -- Polyfill imported */
2
+ /* eslint-disable es-x/no-function-prototype-bind -- Polyfill imported */
3
+
4
+ import { closestAttributeValue } from '../../common/closest-attribute-value.mjs'
5
5
  import { extractConfigByNamespace, mergeConfigs } from '../../common/index.mjs'
6
- import { I18n } from '../../i18n.mjs'
7
6
  import { normaliseDataset } from '../../common/normalise-dataset.mjs'
8
- import { closestAttributeValue } from '../../common/closest-attribute-value.mjs'
7
+ import { I18n } from '../../i18n.mjs'
8
+ import '../../vendor/polyfills/Date/now.mjs'
9
+ import '../../vendor/polyfills/Element/prototype/classList.mjs'
10
+ import '../../vendor/polyfills/Event.mjs' // addEventListener, event.target normalization and DOMContentLoaded
11
+ import '../../vendor/polyfills/Function/prototype/bind.mjs'
9
12
 
10
13
  /**
11
14
  * @constant
@@ -50,7 +53,7 @@ var CHARACTER_COUNT_TRANSLATIONS = {
50
53
  * of the available characters/words has been entered.
51
54
  *
52
55
  * @class
53
- * @param {HTMLElement} $module - The element this component controls
56
+ * @param {HTMLElement} $module - HTML element to use for character count
54
57
  * @param {CharacterCountConfig} [config] - Character count config
55
58
  */
56
59
  function CharacterCount ($module, config) {
@@ -1,8 +1,9 @@
1
- import '../../vendor/polyfills/Function/prototype/bind.mjs'
2
- // addEventListener, event.target normalization and DOMContentLoaded
3
- import '../../vendor/polyfills/Event.mjs'
4
- import '../../vendor/polyfills/Element/prototype/classList.mjs'
1
+ /* eslint-disable es-x/no-function-prototype-bind -- Polyfill imported */
2
+
5
3
  import { nodeListForEach } from '../../common/index.mjs'
4
+ import '../../vendor/polyfills/Element/prototype/classList.mjs'
5
+ import '../../vendor/polyfills/Event.mjs' // addEventListener, event.target normalization and DOMContentLoaded
6
+ import '../../vendor/polyfills/Function/prototype/bind.mjs'
6
7
 
7
8
  /**
8
9
  * Checkboxes component
@@ -16,7 +17,7 @@ function Checkboxes ($module) {
16
17
  }
17
18
 
18
19
  /**
19
- * Initialise Checkboxes
20
+ * Initialise component
20
21
  *
21
22
  * Checkboxes can be associated with a 'conditionally revealed' content block –
22
23
  * for example, a checkbox for 'Phone' could reveal an additional form field for
@@ -34,17 +35,17 @@ Checkboxes.prototype.init = function () {
34
35
  var $inputs = this.$inputs
35
36
 
36
37
  nodeListForEach($inputs, function ($input) {
37
- var target = $input.getAttribute('data-aria-controls')
38
+ var targetId = $input.getAttribute('data-aria-controls')
38
39
 
39
40
  // Skip checkboxes without data-aria-controls attributes, or where the
40
41
  // target element does not exist.
41
- if (!target || !document.getElementById(target)) {
42
+ if (!targetId || !document.getElementById(targetId)) {
42
43
  return
43
44
  }
44
45
 
45
46
  // Promote the data-aria-controls attribute to a aria-controls attribute
46
47
  // so that the relationship is exposed in the AOM
47
- $input.setAttribute('aria-controls', target)
48
+ $input.setAttribute('aria-controls', targetId)
48
49
  $input.removeAttribute('data-aria-controls')
49
50
  })
50
51
 
@@ -63,11 +64,12 @@ Checkboxes.prototype.init = function () {
63
64
  // for example if they are added to the page dynamically, so sync now too.
64
65
  this.syncAllConditionalReveals()
65
66
 
67
+ // Handle events
66
68
  $module.addEventListener('click', this.handleClick.bind(this))
67
69
  }
68
70
 
69
71
  /**
70
- * Sync the conditional reveal states for all inputs in this $module.
72
+ * Sync the conditional reveal states for all checkboxes in this $module.
71
73
  */
72
74
  Checkboxes.prototype.syncAllConditionalReveals = function () {
73
75
  nodeListForEach(this.$inputs, this.syncConditionalRevealWithInputState.bind(this))
@@ -97,6 +99,8 @@ Checkboxes.prototype.syncConditionalRevealWithInputState = function ($input) {
97
99
  *
98
100
  * Find any other checkbox inputs with the same name value, and uncheck them.
99
101
  * This is useful for when a “None of these" checkbox is checked.
102
+ *
103
+ * @param {HTMLElement} $input - Checkbox input
100
104
  */
101
105
  Checkboxes.prototype.unCheckAllInputsExcept = function ($input) {
102
106
  var allInputsWithSameName = document.querySelectorAll('input[type="checkbox"][name="' + $input.name + '"]')
@@ -111,11 +115,13 @@ Checkboxes.prototype.unCheckAllInputsExcept = function ($input) {
111
115
  }
112
116
 
113
117
  /**
114
- * Uncheck exclusive inputs
118
+ * Uncheck exclusive checkboxes
115
119
  *
116
120
  * Find any checkbox inputs with the same name value and the 'exclusive' behaviour,
117
121
  * and uncheck them. This helps prevent someone checking both a regular checkbox and a
118
122
  * "None of these" checkbox in the same fieldset.
123
+ *
124
+ * @param {HTMLInputElement} $input - Checkbox input
119
125
  */
120
126
  Checkboxes.prototype.unCheckExclusiveInputs = function ($input) {
121
127
  var allInputsWithSameNameAndExclusiveBehaviour = document.querySelectorAll(
@@ -140,30 +146,30 @@ Checkboxes.prototype.unCheckExclusiveInputs = function ($input) {
140
146
  * @param {MouseEvent} event - Click event
141
147
  */
142
148
  Checkboxes.prototype.handleClick = function (event) {
143
- var $target = event.target
149
+ var $clickedInput = event.target
144
150
 
145
151
  // Ignore clicks on things that aren't checkbox inputs
146
- if ($target.type !== 'checkbox') {
152
+ if ($clickedInput.type !== 'checkbox') {
147
153
  return
148
154
  }
149
155
 
150
156
  // If the checkbox conditionally-reveals some content, sync the state
151
- var hasAriaControls = $target.getAttribute('aria-controls')
157
+ var hasAriaControls = $clickedInput.getAttribute('aria-controls')
152
158
  if (hasAriaControls) {
153
- this.syncConditionalRevealWithInputState($target)
159
+ this.syncConditionalRevealWithInputState($clickedInput)
154
160
  }
155
161
 
156
162
  // No further behaviour needed for unchecking
157
- if (!$target.checked) {
163
+ if (!$clickedInput.checked) {
158
164
  return
159
165
  }
160
166
 
161
167
  // Handle 'exclusive' checkbox behaviour (ie "None of these")
162
- var hasBehaviourExclusive = ($target.getAttribute('data-behaviour') === 'exclusive')
168
+ var hasBehaviourExclusive = ($clickedInput.getAttribute('data-behaviour') === 'exclusive')
163
169
  if (hasBehaviourExclusive) {
164
- this.unCheckAllInputsExcept($target)
170
+ this.unCheckAllInputsExcept($clickedInput)
165
171
  } else {
166
- this.unCheckExclusiveInputs($target)
172
+ this.unCheckExclusiveInputs($clickedInput)
167
173
  }
168
174
  }
169
175
 
@@ -1,12 +1,14 @@
1
+ /* eslint-disable es-x/no-function-prototype-bind -- Polyfill imported */
2
+
1
3
  /**
2
4
  * JavaScript 'polyfill' for HTML5's <details> and <summary> elements
3
5
  * and 'shim' to add accessiblity enhancements for all browsers
4
6
  *
5
7
  * http://caniuse.com/#feat=details
6
8
  */
7
- import '../../vendor/polyfills/Function/prototype/bind.mjs'
8
- import '../../vendor/polyfills/Event.mjs' // addEventListener and event.target normaliziation
9
9
  import { generateUniqueID } from '../../common/index.mjs'
10
+ import '../../vendor/polyfills/Event.mjs' // addEventListener, event.target normalization and DOMContentLoaded
11
+ import '../../vendor/polyfills/Function/prototype/bind.mjs'
10
12
 
11
13
  var KEY_ENTER = 13
12
14
  var KEY_SPACE = 32
@@ -21,6 +23,9 @@ function Details ($module) {
21
23
  this.$module = $module
22
24
  }
23
25
 
26
+ /**
27
+ * Initialise component
28
+ */
24
29
  Details.prototype.init = function () {
25
30
  if (!this.$module) {
26
31
  return
@@ -36,6 +41,9 @@ Details.prototype.init = function () {
36
41
  this.polyfillDetails()
37
42
  }
38
43
 
44
+ /**
45
+ * Polyfill component in older browsers
46
+ */
39
47
  Details.prototype.polyfillDetails = function () {
40
48
  var $module = this.$module
41
49
 
@@ -79,7 +87,7 @@ Details.prototype.polyfillDetails = function () {
79
87
  }
80
88
 
81
89
  // Bind an event to handle summary elements
82
- this.polyfillHandleInputs($summary, this.polyfillSetAttributes.bind(this))
90
+ this.polyfillHandleInputs(this.polyfillSetAttributes.bind(this))
83
91
  }
84
92
 
85
93
  /**
@@ -104,21 +112,20 @@ Details.prototype.polyfillSetAttributes = function () {
104
112
  /**
105
113
  * Handle cross-modal click events
106
114
  *
107
- * @param {object} node - element
108
115
  * @param {polyfillHandleInputsCallback} callback - function
109
116
  */
110
- Details.prototype.polyfillHandleInputs = function (node, callback) {
111
- node.addEventListener('keypress', function (event) {
112
- var target = event.target
117
+ Details.prototype.polyfillHandleInputs = function (callback) {
118
+ this.$summary.addEventListener('keypress', function (event) {
119
+ var $target = event.target
113
120
  // When the key gets pressed - check if it is enter or space
114
121
  if (event.keyCode === KEY_ENTER || event.keyCode === KEY_SPACE) {
115
- if (target.nodeName.toLowerCase() === 'summary') {
122
+ if ($target.nodeName.toLowerCase() === 'summary') {
116
123
  // Prevent space from scrolling the page
117
124
  // and enter from submitting a form
118
125
  event.preventDefault()
119
126
  // Click to let the click event do all the necessary action
120
- if (target.click) {
121
- target.click()
127
+ if ($target.click) {
128
+ $target.click()
122
129
  } else {
123
130
  // except Safari 5.1 and under don't support .click() here
124
131
  callback(event)
@@ -128,22 +135,22 @@ Details.prototype.polyfillHandleInputs = function (node, callback) {
128
135
  })
129
136
 
130
137
  // Prevent keyup to prevent clicking twice in Firefox when using space key
131
- node.addEventListener('keyup', function (event) {
132
- var target = event.target
138
+ this.$summary.addEventListener('keyup', function (event) {
139
+ var $target = event.target
133
140
  if (event.keyCode === KEY_SPACE) {
134
- if (target.nodeName.toLowerCase() === 'summary') {
141
+ if ($target.nodeName.toLowerCase() === 'summary') {
135
142
  event.preventDefault()
136
143
  }
137
144
  }
138
145
  })
139
146
 
140
- node.addEventListener('click', callback)
147
+ this.$summary.addEventListener('click', callback)
141
148
  }
142
149
 
143
150
  export default Details
144
151
 
145
152
  /**
146
153
  * @callback polyfillHandleInputsCallback
147
- * @param {KeyboardEvent} event - Keyboard event
148
- * @returns {undefined}
154
+ * @param {UIEvent} event - Keyboard or mouse event
155
+ * @returns {void}
149
156
  */