govuk_publishing_components 35.15.5 → 35.16.1

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 (108) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/config/govuk_publishing_components_manifest.js +1 -0
  3. data/app/assets/images/option-select/input-icon.svg +3 -0
  4. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-page-views.js +37 -15
  5. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-video-tracker.js +7 -1
  6. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/pii-remover.js +1 -1
  7. data/app/assets/javascripts/govuk_publishing_components/components/option-select.js +312 -0
  8. data/app/assets/stylesheets/component_guide/application.scss +6 -6
  9. data/app/assets/stylesheets/govuk_publishing_components/_all_components.scss +1 -0
  10. data/app/assets/stylesheets/govuk_publishing_components/components/_action-link.scss +15 -15
  11. data/app/assets/stylesheets/govuk_publishing_components/components/_big-number.scss +3 -2
  12. data/app/assets/stylesheets/govuk_publishing_components/components/_breadcrumbs.scss +3 -3
  13. data/app/assets/stylesheets/govuk_publishing_components/components/_button.scss +2 -2
  14. data/app/assets/stylesheets/govuk_publishing_components/components/_cards.scss +5 -5
  15. data/app/assets/stylesheets/govuk_publishing_components/components/_contents-list.scss +1 -1
  16. data/app/assets/stylesheets/govuk_publishing_components/components/_contextual-sidebar.scss +1 -1
  17. data/app/assets/stylesheets/govuk_publishing_components/components/_document-list.scss +2 -2
  18. data/app/assets/stylesheets/govuk_publishing_components/components/_image-card.scss +31 -5
  19. data/app/assets/stylesheets/govuk_publishing_components/components/_layout-super-navigation-header.scss +40 -35
  20. data/app/assets/stylesheets/govuk_publishing_components/components/_option-select.scss +172 -0
  21. data/app/assets/stylesheets/govuk_publishing_components/components/_step-by-step-nav.scss +23 -19
  22. data/app/assets/stylesheets/govuk_publishing_components/components/_table.scss +6 -6
  23. data/app/assets/stylesheets/govuk_publishing_components/components/_tabs.scss +2 -1
  24. data/app/assets/stylesheets/govuk_publishing_components/components/govspeak/_steps.scss +3 -1
  25. data/app/assets/stylesheets/govuk_publishing_components/components/govspeak/_typography.scss +4 -4
  26. data/app/assets/stylesheets/govuk_publishing_components/components/mixins/_media-down.scss +2 -2
  27. data/app/views/govuk_publishing_components/components/_attachment.html.erb +1 -1
  28. data/app/views/govuk_publishing_components/components/_contents_list.html.erb +20 -10
  29. data/app/views/govuk_publishing_components/components/_image_card.html.erb +8 -2
  30. data/app/views/govuk_publishing_components/components/_option_select.html.erb +71 -0
  31. data/app/views/govuk_publishing_components/components/_previous_and_next_navigation.html.erb +6 -6
  32. data/app/views/govuk_publishing_components/components/docs/contents_list.yml +1 -0
  33. data/app/views/govuk_publishing_components/components/docs/image_card.yml +13 -0
  34. data/app/views/govuk_publishing_components/components/docs/option_select.yml +343 -0
  35. data/config/locales/ar.yml +5 -0
  36. data/config/locales/az.yml +5 -0
  37. data/config/locales/be.yml +5 -0
  38. data/config/locales/bg.yml +5 -0
  39. data/config/locales/bn.yml +5 -0
  40. data/config/locales/cs.yml +5 -0
  41. data/config/locales/cy.yml +5 -0
  42. data/config/locales/da.yml +5 -0
  43. data/config/locales/de.yml +5 -0
  44. data/config/locales/dr.yml +5 -0
  45. data/config/locales/el.yml +5 -0
  46. data/config/locales/en.yml +6 -1
  47. data/config/locales/es-419.yml +5 -0
  48. data/config/locales/es.yml +5 -0
  49. data/config/locales/et.yml +5 -0
  50. data/config/locales/fa.yml +5 -0
  51. data/config/locales/fi.yml +5 -0
  52. data/config/locales/fr.yml +5 -0
  53. data/config/locales/gd.yml +5 -0
  54. data/config/locales/gu.yml +5 -0
  55. data/config/locales/he.yml +5 -0
  56. data/config/locales/hi.yml +5 -0
  57. data/config/locales/hr.yml +5 -0
  58. data/config/locales/hu.yml +5 -0
  59. data/config/locales/hy.yml +5 -0
  60. data/config/locales/id.yml +5 -0
  61. data/config/locales/is.yml +5 -0
  62. data/config/locales/it.yml +5 -0
  63. data/config/locales/ja.yml +5 -0
  64. data/config/locales/ka.yml +5 -0
  65. data/config/locales/kk.yml +5 -0
  66. data/config/locales/ko.yml +5 -0
  67. data/config/locales/lt.yml +5 -0
  68. data/config/locales/lv.yml +5 -0
  69. data/config/locales/ms.yml +5 -0
  70. data/config/locales/mt.yml +5 -0
  71. data/config/locales/nl.yml +5 -0
  72. data/config/locales/no.yml +5 -0
  73. data/config/locales/pa-pk.yml +5 -0
  74. data/config/locales/pa.yml +5 -0
  75. data/config/locales/pl.yml +5 -0
  76. data/config/locales/ps.yml +5 -0
  77. data/config/locales/pt.yml +5 -0
  78. data/config/locales/ro.yml +5 -0
  79. data/config/locales/ru.yml +5 -0
  80. data/config/locales/si.yml +5 -0
  81. data/config/locales/sk.yml +5 -0
  82. data/config/locales/sl.yml +5 -0
  83. data/config/locales/so.yml +5 -0
  84. data/config/locales/sq.yml +5 -0
  85. data/config/locales/sr.yml +5 -0
  86. data/config/locales/sv.yml +5 -0
  87. data/config/locales/sw.yml +5 -0
  88. data/config/locales/ta.yml +5 -0
  89. data/config/locales/th.yml +5 -0
  90. data/config/locales/tk.yml +5 -0
  91. data/config/locales/tr.yml +5 -0
  92. data/config/locales/uk.yml +5 -0
  93. data/config/locales/ur.yml +5 -0
  94. data/config/locales/uz.yml +5 -0
  95. data/config/locales/vi.yml +5 -0
  96. data/config/locales/zh-hk.yml +5 -0
  97. data/config/locales/zh-tw.yml +5 -0
  98. data/config/locales/zh.yml +5 -0
  99. data/lib/govuk_publishing_components/presenters/contents_list_helper.rb +8 -0
  100. data/lib/govuk_publishing_components/presenters/image_card_helper.rb +6 -2
  101. data/lib/govuk_publishing_components/presenters/meta_tags.rb +3 -0
  102. data/lib/govuk_publishing_components/version.rb +1 -1
  103. data/node_modules/axe-core/axe.js +6 -6
  104. data/node_modules/axe-core/axe.min.js +2 -2
  105. data/node_modules/axe-core/locales/_template.json +4 -4
  106. data/node_modules/axe-core/package.json +1 -1
  107. data/node_modules/axe-core/sri-history.json +4 -0
  108. metadata +7 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3117de6a0b4a66caea24d67e4c462c728ef09fdc3cd3503ca0e2429673726631
4
- data.tar.gz: 164a43d2a5ccc732f37fbdf6eb862fc500311f473b9cf1aca998515d6c978c14
3
+ metadata.gz: f215a358b5348fe905ec3b11ba2d3301dd263bbe5fe6961cdab3586de5f11ead
4
+ data.tar.gz: fee32e2c6b0b5a587b36577db5f51a731b8c3df67b5d7b30db4089650edf7efd
5
5
  SHA512:
6
- metadata.gz: f9e56a63dc6f33e2df012aa4993cdd9404d07d8c96ecfe39ffe253676aa443deabadc47062b4a9443ee8e2f0fa89943629dd145b8e673f9c1ddef6e56fb5624d
7
- data.tar.gz: ca4f944dbcbf4d77171f797bc1f3b16caad24b513dbb2c49d0749edba6570f9acb701922cd0fa4d33259ec2b80ebbabd5ad8e9390bfa2fb0e54aa58e5eab14d5
6
+ metadata.gz: f687a3d509cc89bd128d4c696f8f504575188b3ade7b6e536587c09be27ce7d0a3733203c52acfb48252ba6655a99b020d64d8ae5d2afbb23994e0f16a6bc2ad
7
+ data.tar.gz: 82488d3f778c9c3dc22f27eab581965780b695a43ff0585fd191771dbbf6aa05c165616c61d89873342755e9c90b13e638766f116fef25a72baf8c08a265362f
@@ -60,6 +60,7 @@
60
60
  //= link govuk_publishing_components/components/_metadata.css
61
61
  //= link govuk_publishing_components/components/_modal-dialogue.css
62
62
  //= link govuk_publishing_components/components/_notice.css
63
+ //= link govuk_publishing_components/components/_option-select.css
63
64
  //= link govuk_publishing_components/components/_organisation-logo.css
64
65
  //= link govuk_publishing_components/components/_panel.css
65
66
  //= link govuk_publishing_components/components/_phase-banner.css
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" width="40" height="40">
2
+ <path d="M25.7 24.8L21.9 21c.7-1 1.1-2.2 1.1-3.5 0-3.6-2.9-6.5-6.5-6.5S10 13.9 10 17.5s2.9 6.5 6.5 6.5c1.6 0 3-.6 4.1-1.5l3.7 3.7 1.4-1.4zM12 17.5c0-2.5 2-4.5 4.5-4.5s4.5 2 4.5 4.5-2 4.5-4.5 4.5-4.5-2-4.5-4.5z" fill="currentColor" />
3
+ </svg>
@@ -28,7 +28,7 @@ window.GOVUK.analyticsGa4.analyticsModules = window.GOVUK.analyticsGa4.analytics
28
28
  schema_name: this.getMetaContent('schema-name'),
29
29
  content_id: this.getMetaContent('content-id'),
30
30
 
31
- browse_topic: this.getMetaContent('section'),
31
+ browse_topic: this.getMetaContent('ga4-browse-topic'),
32
32
  navigation_page_type: this.getMetaContent('navigation-page-type'),
33
33
  navigation_list_type: this.getMetaContent('navigation-list-type'),
34
34
  step_navs: this.getMetaContent('stepnavs'),
@@ -57,9 +57,11 @@ window.GOVUK.analyticsGa4.analyticsModules = window.GOVUK.analyticsGa4.analytics
57
57
  emergency_banner: document.querySelector('[data-ga4-emergency-banner]') ? 'true' : undefined,
58
58
  phase_banner: this.getElementAttribute('data-ga4-phase-banner') || undefined,
59
59
  devolved_nations_banner: this.getElementAttribute('data-ga4-devolved-nations-banner') || undefined,
60
- cookie_banner: document.querySelector('[data-ga4-cookie-banner]') ? 'true' : undefined,
61
- intervention: this.getInterventionPresence(),
62
- query_string: this.getQueryString()
60
+ cookie_banner: this.getBannerPresence('[data-ga4-cookie-banner]'),
61
+ intervention: this.getBannerPresence('[data-ga4-intervention-banner]'),
62
+ query_string: this.getQueryString(),
63
+ search_term: this.getSearchTerm(),
64
+ spelling_suggestion: this.getMetaContent('spelling-suggestion')
63
65
  }
64
66
  }
65
67
  window.GOVUK.analyticsGa4.core.sendData(data)
@@ -67,18 +69,35 @@ window.GOVUK.analyticsGa4.analyticsModules = window.GOVUK.analyticsGa4.analytics
67
69
  },
68
70
 
69
71
  getLocation: function () {
70
- return this.PIIRemover.stripPII(this.stripGaParam(document.location.href))
72
+ return this.PIIRemover.stripPIIWithOverride(this.stripGaParam(document.location.href), true, true)
71
73
  },
72
74
 
73
75
  getSearch: function () {
74
76
  return window.location.search
75
77
  },
76
78
 
79
+ getSearchTerm: function () {
80
+ var queryString = this.getSearch()
81
+
82
+ if (!queryString) {
83
+ return undefined
84
+ }
85
+
86
+ var searchTerm = queryString.match(/keywords=([^&]*)/)
87
+ if (!searchTerm) {
88
+ return undefined
89
+ }
90
+
91
+ searchTerm = searchTerm[0].replace('keywords=', '')
92
+ searchTerm = this.PIIRemover.stripPIIWithOverride(searchTerm, true, true)
93
+ return searchTerm
94
+ },
95
+
77
96
  getQueryString: function () {
78
97
  var queryString = this.getSearch()
79
98
  if (queryString) {
80
- queryString = this.PIIRemover.stripPIIWithOverride(queryString, true, true)
81
99
  queryString = this.stripGaParam(queryString)
100
+ queryString = this.PIIRemover.stripPIIWithOverride(queryString, true, true)
82
101
  queryString = queryString.substring(1) // removes the '?' character from the start.
83
102
  return queryString
84
103
  }
@@ -97,7 +116,7 @@ window.GOVUK.analyticsGa4.analyticsModules = window.GOVUK.analyticsGa4.analytics
97
116
  },
98
117
 
99
118
  getTitle: function () {
100
- return this.PIIRemover.stripPII(document.title)
119
+ return this.PIIRemover.stripPIIWithOverride(document.title, true, true)
101
120
  },
102
121
 
103
122
  // window.httpStatusCode is set in the source of the error page in static
@@ -130,7 +149,10 @@ window.GOVUK.analyticsGa4.analyticsModules = window.GOVUK.analyticsGa4.analytics
130
149
  var content = document.getElementById('content')
131
150
  var html = document.querySelector('html')
132
151
  if (content) {
133
- return content.getAttribute('lang') || this.nullValue
152
+ var contentLanguage = content.getAttribute('lang')
153
+ if (contentLanguage) {
154
+ return contentLanguage
155
+ }
134
156
  }
135
157
  // html.getAttribute('lang') is untested - Jasmine would not allow lang to be set on <html>.
136
158
  return html.getAttribute('lang') || this.nullValue
@@ -146,18 +168,18 @@ window.GOVUK.analyticsGa4.analyticsModules = window.GOVUK.analyticsGa4.analytics
146
168
  return (withdrawn === 'withdrawn') ? 'true' : 'false'
147
169
  },
148
170
 
149
- getInterventionPresence: function () {
171
+ getBannerPresence: function (bannerSelector) {
150
172
  /* If the user hides the banner using JS, a cookie is set to hide it on future page loads.
151
- * Therefore we need to start the intervention banner early so that it hides if this cookie exists.
173
+ * Therefore we need to start the banner module early so that it hides if this cookie exists.
152
174
  * Without this, our pageview object will track the banner as visible before it gets hidden. */
153
175
 
154
- var intervention = document.querySelector('[data-ga4-intervention-banner]')
176
+ var banner = document.querySelector(bannerSelector)
155
177
 
156
- if (intervention) {
157
- window.GOVUK.modules.start(intervention)
158
- var interventionHidden = intervention.getAttribute('hidden') === '' || intervention.getAttribute('hidden')
178
+ if (banner) {
179
+ window.GOVUK.modules.start(banner)
180
+ var bannerHidden = banner.getAttribute('hidden') === '' || banner.getAttribute('hidden')
159
181
 
160
- if (interventionHidden) {
182
+ if (bannerHidden) {
161
183
  return undefined
162
184
  }
163
185
  return 'true'
@@ -70,7 +70,7 @@ window.GOVUK.analyticsGa4.analyticsModules = window.GOVUK.analyticsGa4.analytics
70
70
  var data = {}
71
71
  data.event_name = 'video_' + event
72
72
  data.type = 'video'
73
- data.url = player.getVideoUrl()
73
+ data.url = this.cleanVideoUrl(player.getVideoUrl())
74
74
  data.text = player.videoTitle
75
75
  data.action = event
76
76
  data.video_current_time = Math.round(player.getCurrentTime())
@@ -81,6 +81,12 @@ window.GOVUK.analyticsGa4.analyticsModules = window.GOVUK.analyticsGa4.analytics
81
81
  var schema = schemas.mergeProperties(data, 'event_data')
82
82
 
83
83
  window.GOVUK.analyticsGa4.core.sendData(schema)
84
+ },
85
+
86
+ cleanVideoUrl: function (url) {
87
+ url = url.replace(/[?]{1}t=[0-9]+[&]{1}/, '?') // replace ?t=123& with ?
88
+ url = url.replace(/[&]{1}t=[0-9]+/, '') // replace &t=123 with ''
89
+ return url
84
90
  }
85
91
  }
86
92
 
@@ -2,7 +2,7 @@
2
2
  'use strict'
3
3
 
4
4
  var GOVUK = global.GOVUK || {}
5
- var EMAIL_PATTERN = /[^\s=/?&#]+(?:@|%40)[^\s=/?&]+/g
5
+ var EMAIL_PATTERN = /[^\s=/?&#+]+(?:@|%40)[^\s=/?&+]+/g
6
6
  var POSTCODE_PATTERN = /\b[A-PR-UWYZ][A-HJ-Z]?[0-9][0-9A-HJKMNPR-Y]?(?:[\s+]|%20)*[0-9](?!refund)[ABD-HJLNPQ-Z]{2,3}\b/gi
7
7
  var DATE_PATTERN_NUMERIC = /\d{4}(-?)\d{2}(-?)\d{2}/g
8
8
  var DATE_PATTERN_STRING = /\d{1,2}\s(January|February|March|April|May|June|July|August|September|October|November|December)\s\d{4}/g
@@ -0,0 +1,312 @@
1
+ window.GOVUK = window.GOVUK || {}
2
+ window.GOVUK.Modules = window.GOVUK.Modules || {};
3
+
4
+ (function (Modules) {
5
+ /* This JavaScript provides two functional enhancements to option-select components:
6
+ 1) A count that shows how many results have been checked in the option-container
7
+ 2) Open/closing of the list of checkboxes
8
+ */
9
+ function OptionSelect ($module) {
10
+ this.$optionSelect = $module
11
+ this.$options = this.$optionSelect.querySelectorAll("input[type='checkbox']")
12
+ this.$optionsContainer = this.$optionSelect.querySelector('.js-options-container')
13
+ this.$optionList = this.$optionsContainer.querySelector('.js-auto-height-inner')
14
+ this.$allCheckboxes = this.$optionsContainer.querySelectorAll('.govuk-checkboxes__item')
15
+ this.hasFilter = this.$optionSelect.getAttribute('data-filter-element') || ''
16
+
17
+ this.checkedCheckboxes = []
18
+
19
+ this.mq = window.matchMedia('(min-width: 641px)')
20
+ this.isClosedOnLoad = this.$optionSelect.getAttribute('data-closed-on-load')
21
+ this.isClosedOnLoadMobile = this.$optionSelect.getAttribute('data-closed-on-load-mobile')
22
+ }
23
+
24
+ OptionSelect.prototype.init = function () {
25
+ if (this.hasFilter.length) {
26
+ var filterEl = document.createElement('div')
27
+ filterEl.innerHTML = this.hasFilter
28
+
29
+ var optionSelectFilter = document.createElement('div')
30
+ optionSelectFilter.classList.add('gem-c-option-select__filter')
31
+ optionSelectFilter.innerHTML = filterEl.childNodes[0].nodeValue
32
+
33
+ this.$optionsContainer.parentNode.insertBefore(optionSelectFilter, this.$optionsContainer)
34
+
35
+ this.$filter = this.$optionSelect.querySelector('input[name="option-select-filter"]')
36
+ this.$filterCount = document.getElementById(this.$filter.getAttribute('aria-describedby'))
37
+ this.filterTextSingle = ' ' + this.$filterCount.getAttribute('data-single')
38
+ this.filterTextMultiple = ' ' + this.$filterCount.getAttribute('data-multiple')
39
+ this.filterTextSelected = ' ' + this.$filterCount.getAttribute('data-selected')
40
+ this.checkboxLabels = []
41
+ this.filterTimeout = 0
42
+
43
+ this.getAllCheckedCheckboxes()
44
+ for (var i = 0; i < this.$allCheckboxes.length; i++) {
45
+ this.checkboxLabels.push(this.cleanString(this.$allCheckboxes[i].textContent))
46
+ }
47
+
48
+ this.$filter.addEventListener('keyup', this.typeFilterText.bind(this))
49
+ }
50
+
51
+ // Attach listener to update checked count
52
+ this.$optionsContainer.querySelector('.gem-c-checkboxes__list').addEventListener('change', this.updateCheckedCount.bind(this))
53
+
54
+ // Replace div.container-head with a button
55
+ this.replaceHeadingSpanWithButton()
56
+
57
+ // Add js-collapsible class to parent for CSS
58
+ this.$optionSelect.classList.add('js-collapsible')
59
+
60
+ // Add open/close listeners
61
+ var button = this.$optionSelect.querySelector('.js-container-button')
62
+ button.addEventListener('click', this.toggleOptionSelect.bind(this))
63
+
64
+ // Toggle option visibility depending on screen size (min-width: 641px) and
65
+ // presence of any 'closed' properties (`data-closed-on-load`, `data-closed-on-load-mobile`).
66
+ // See https://github.com/alphagov/govuk-frontend/blob/main/packages/govuk-frontend/src/govuk/settings/_media-queries.scss#L10-L14
67
+ if (this.mq.matches) {
68
+ this.toggleVisibility(true)
69
+ } else {
70
+ this.toggleVisibility(false)
71
+ }
72
+
73
+ var checkedString = this.checkedString()
74
+ if (checkedString) {
75
+ this.attachCheckedCounter(checkedString)
76
+ }
77
+ }
78
+
79
+ OptionSelect.prototype.toggleVisibility = function (isTabletOrLarger) {
80
+ if (isTabletOrLarger) {
81
+ if (this.isClosedOnLoad === 'true') {
82
+ this.close()
83
+ } else {
84
+ this.setupHeight()
85
+ }
86
+ } else {
87
+ if (this.isClosedOnLoadMobile === 'true' || this.isClosedOnLoad === 'true') {
88
+ this.close()
89
+ } else {
90
+ this.setContainerHeight(201)
91
+ }
92
+ }
93
+ }
94
+
95
+ OptionSelect.prototype.typeFilterText = function (event) {
96
+ event.stopPropagation()
97
+ var ENTER_KEY = 13
98
+
99
+ if (event.keyCode !== ENTER_KEY) {
100
+ clearTimeout(this.filterTimeout)
101
+ this.filterTimeout = setTimeout(
102
+ function () { this.doFilter(this) }.bind(this),
103
+ 300
104
+ )
105
+ } else {
106
+ event.preventDefault() // prevents finder forms from being submitted when user presses ENTER
107
+ }
108
+ }
109
+
110
+ OptionSelect.prototype.cleanString = function cleanString (text) {
111
+ text = text.replace(/&/g, 'and')
112
+ text = text.replace(/[’',:–-]/g, '') // remove punctuation characters
113
+ text = text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // escape special characters
114
+ return text.trim().replace(/\s\s+/g, ' ').toLowerCase() // replace multiple spaces with one
115
+ }
116
+
117
+ OptionSelect.prototype.getAllCheckedCheckboxes = function getAllCheckedCheckboxes () {
118
+ this.checkedCheckboxes = []
119
+
120
+ for (var i = 0; i < this.$options.length; i++) {
121
+ if (this.$options[i].checked) {
122
+ this.checkedCheckboxes.push(i)
123
+ }
124
+ }
125
+ }
126
+
127
+ OptionSelect.prototype.doFilter = function doFilter (obj) {
128
+ var filterBy = obj.cleanString(obj.$filter.value)
129
+ var showCheckboxes = obj.checkedCheckboxes.slice()
130
+ var i = 0
131
+
132
+ for (i = 0; i < obj.$allCheckboxes.length; i++) {
133
+ if (showCheckboxes.indexOf(i) === -1 && obj.checkboxLabels[i].search(filterBy) !== -1) {
134
+ showCheckboxes.push(i)
135
+ }
136
+ }
137
+
138
+ for (i = 0; i < obj.$allCheckboxes.length; i++) {
139
+ obj.$allCheckboxes[i].style.display = 'none'
140
+ }
141
+
142
+ for (i = 0; i < showCheckboxes.length; i++) {
143
+ obj.$allCheckboxes[showCheckboxes[i]].style.display = 'block'
144
+ }
145
+
146
+ var lenChecked = obj.$optionsContainer.querySelectorAll('.govuk-checkboxes__input:checked').length
147
+ var len = showCheckboxes.length + lenChecked
148
+ var html = len + (len === 1 ? obj.filterTextSingle : obj.filterTextMultiple) + ', ' + lenChecked + obj.filterTextSelected
149
+ obj.$filterCount.innerHTML = html
150
+ }
151
+
152
+ OptionSelect.prototype.replaceHeadingSpanWithButton = function replaceHeadingSpanWithButton () {
153
+ /* Replace the span within the heading with a button element. This is based on feedback from Léonie Watson.
154
+ * The button has all of the accessibility hooks that are used by screen readers and etc.
155
+ * We do this in the JavaScript because if the JavaScript is not active then the button shouldn't
156
+ * be there as there is no JS to handle the click event.
157
+ */
158
+ var containerHead = this.$optionSelect.querySelector('.js-container-button')
159
+ var jsContainerHeadHTML = containerHead.innerHTML
160
+
161
+ // Create button and replace the preexisting html with the button.
162
+ var button = document.createElement('button')
163
+ button.setAttribute('class', 'js-container-button gem-c-option-select__title gem-c-option-select__button')
164
+ // Add type button to override default type submit when this component is used within a form
165
+ button.setAttribute('type', 'button')
166
+ button.setAttribute('aria-expanded', true)
167
+ button.setAttribute('id', containerHead.getAttribute('id'))
168
+ button.setAttribute('aria-controls', this.$optionsContainer.getAttribute('id'))
169
+ button.innerHTML = jsContainerHeadHTML
170
+ containerHead.parentNode.replaceChild(button, containerHead)
171
+
172
+ // GA4 Accordion tracking. Relies on the ga4-finder-tracker setting the index first, so we wrap this in a custom event.
173
+ window.addEventListener('ga4-filter-indexes-added', function () {
174
+ if (window.GOVUK.analyticsGa4) {
175
+ if (window.GOVUK.analyticsGa4.Ga4FinderTracker) {
176
+ window.GOVUK.analyticsGa4.Ga4FinderTracker.addFilterButtonTracking(button, button.innerHTML)
177
+ }
178
+ }
179
+ })
180
+ }
181
+
182
+ OptionSelect.prototype.attachCheckedCounter = function attachCheckedCounter (checkedString) {
183
+ var element = document.createElement('div')
184
+ element.setAttribute('class', 'gem-c-option-select__selected-counter js-selected-counter')
185
+ element.innerHTML = checkedString
186
+ this.$optionSelect.querySelector('.js-container-button').insertAdjacentElement('afterend', element)
187
+ }
188
+
189
+ OptionSelect.prototype.updateCheckedCount = function updateCheckedCount () {
190
+ var checkedString = this.checkedString()
191
+ var checkedStringElement = this.$optionSelect.querySelector('.js-selected-counter')
192
+
193
+ if (checkedString) {
194
+ if (checkedStringElement === null) {
195
+ this.attachCheckedCounter(checkedString)
196
+ } else {
197
+ checkedStringElement.textContent = checkedString
198
+ }
199
+ } else if (checkedStringElement) {
200
+ checkedStringElement.parentNode.removeChild(checkedStringElement)
201
+ }
202
+ }
203
+
204
+ OptionSelect.prototype.checkedString = function checkedString () {
205
+ this.getAllCheckedCheckboxes()
206
+ var count = this.checkedCheckboxes.length
207
+ var checkedString = false
208
+ if (count > 0) {
209
+ checkedString = count + ' selected'
210
+ }
211
+
212
+ return checkedString
213
+ }
214
+
215
+ OptionSelect.prototype.toggleOptionSelect = function toggleOptionSelect (e) {
216
+ if (this.isClosed()) {
217
+ this.open()
218
+ } else {
219
+ this.close()
220
+ }
221
+ e.preventDefault()
222
+ }
223
+
224
+ OptionSelect.prototype.open = function open () {
225
+ if (this.isClosed()) {
226
+ this.$optionSelect.querySelector('.js-container-button').setAttribute('aria-expanded', true)
227
+ this.$optionSelect.classList.remove('js-closed')
228
+ this.$optionSelect.classList.add('js-opened')
229
+ if (!this.$optionsContainer.style.height) {
230
+ this.setupHeight()
231
+ }
232
+ }
233
+ }
234
+
235
+ OptionSelect.prototype.close = function close () {
236
+ this.$optionSelect.classList.remove('js-opened')
237
+ this.$optionSelect.classList.add('js-closed')
238
+ this.$optionSelect.querySelector('.js-container-button').setAttribute('aria-expanded', false)
239
+ }
240
+
241
+ OptionSelect.prototype.isClosed = function isClosed () {
242
+ return this.$optionSelect.classList.contains('js-closed')
243
+ }
244
+
245
+ OptionSelect.prototype.setContainerHeight = function setContainerHeight (height) {
246
+ this.$optionsContainer.style.height = height + 'px'
247
+ }
248
+
249
+ OptionSelect.prototype.isCheckboxVisible = function isCheckboxVisible (option) {
250
+ var initialOptionContainerHeight = this.$optionsContainer.clientHeight
251
+ var optionListOffsetTop = this.$optionList.getBoundingClientRect().top
252
+ var distanceFromTopOfContainer = option.getBoundingClientRect().top - optionListOffsetTop
253
+ return distanceFromTopOfContainer < initialOptionContainerHeight
254
+ }
255
+
256
+ OptionSelect.prototype.getVisibleCheckboxes = function getVisibleCheckboxes () {
257
+ var visibleCheckboxes = []
258
+ for (var i = 0; i < this.$options.length; i++) {
259
+ if (this.isCheckboxVisible(this.$options[i])) {
260
+ visibleCheckboxes.push(this.$options[i])
261
+ }
262
+ }
263
+
264
+ // add an extra checkbox, if the label of the first is too long it collapses onto itself
265
+ if (this.$options[visibleCheckboxes.length]) {
266
+ visibleCheckboxes.push(this.$options[visibleCheckboxes.length])
267
+ }
268
+ return visibleCheckboxes
269
+ }
270
+
271
+ OptionSelect.prototype.isComponentParentHidden = function isComponentParentHidden () {
272
+ var parentContent = this.$optionSelect.parentElement
273
+ var isparentContentHidden = false
274
+ // check whether this is hidden by progressive disclosure,
275
+ // because height calculations won't work
276
+ // would use offsetParent === null but for IE10+
277
+ if (parentContent) {
278
+ isparentContentHidden = !(parentContent.offsetWidth || parentContent.offsetHeight || parentContent.getClientRects().length)
279
+ }
280
+
281
+ return isparentContentHidden
282
+ }
283
+
284
+ OptionSelect.prototype.setupHeight = function setupHeight () {
285
+ var initialOptionContainerHeight = this.$optionsContainer.clientHeight
286
+ var height = this.$optionList.offsetHeight
287
+
288
+ var isComponentParentHidden = this.isComponentParentHidden()
289
+
290
+ if (isComponentParentHidden) {
291
+ initialOptionContainerHeight = 200
292
+ height = 200
293
+ }
294
+
295
+ // Resize if the list is only slightly bigger than its container
296
+ // If isComponentParentHidden is true, then 200 < 250
297
+ // And the container height is always set to 201px
298
+ if (height < initialOptionContainerHeight + 50) {
299
+ this.setContainerHeight(height + 1)
300
+ return
301
+ }
302
+
303
+ // Resize to cut last item cleanly in half
304
+ var visibleCheckboxes = this.getVisibleCheckboxes()
305
+
306
+ var lastVisibleCheckbox = visibleCheckboxes[visibleCheckboxes.length - 1]
307
+ var position = lastVisibleCheckbox.parentNode.offsetTop // parent element is relative
308
+ this.setContainerHeight(position + (lastVisibleCheckbox.clientHeight / 1.5))
309
+ }
310
+
311
+ Modules.OptionSelect = OptionSelect
312
+ })(window.GOVUK.Modules)
@@ -145,7 +145,7 @@ $gem-guide-border-width: 1px;
145
145
  border: 0;
146
146
  padding: govuk-spacing(2);
147
147
 
148
- &:before {
148
+ &::before {
149
149
  display: none;
150
150
  }
151
151
  }
@@ -187,7 +187,7 @@ $gem-guide-border-width: 1px;
187
187
  font-style: italic;
188
188
  }
189
189
 
190
- &:before {
190
+ &::before {
191
191
  @include govuk-font($size: 14);
192
192
  content: attr(data-content);
193
193
  position: absolute;
@@ -201,7 +201,7 @@ $gem-guide-border-width: 1px;
201
201
  .component-guide-preview--warning {
202
202
  border-color: govuk-colour("yellow");
203
203
 
204
- &:before {
204
+ &::before {
205
205
  background-color: govuk-colour("yellow");
206
206
  }
207
207
  }
@@ -209,7 +209,7 @@ $gem-guide-border-width: 1px;
209
209
  .component-guide-preview--violation {
210
210
  border-color: govuk-colour("red");
211
211
 
212
- &:before {
212
+ &::before {
213
213
  background-color: govuk-colour("red");
214
214
  color: govuk-colour("white");
215
215
  }
@@ -516,11 +516,11 @@ $code-delete-bg: #fadddd;
516
516
  }
517
517
 
518
518
  .component__count {
519
- &:before {
519
+ &::before {
520
520
  content: "(";
521
521
  }
522
522
 
523
- &:after {
523
+ &::after {
524
524
  content: ")";
525
525
  }
526
526
  }
@@ -57,6 +57,7 @@ $govuk-new-link-styles: true;
57
57
  @import "components/metadata";
58
58
  @import "components/modal-dialogue";
59
59
  @import "components/notice";
60
+ @import "components/option-select";
60
61
  @import "components/organisation-logo";
61
62
  @import "components/panel";
62
63
  @import "components/phase-banner";
@@ -3,7 +3,7 @@
3
3
  .gem-c-action-link {
4
4
  display: table;
5
5
 
6
- &:before {
6
+ &::before {
7
7
  content: "";
8
8
  display: table-cell;
9
9
  width: 60px;
@@ -66,7 +66,7 @@
66
66
  .gem-c-action-link__subtext {
67
67
  padding: 0;
68
68
 
69
- &:before {
69
+ &::before {
70
70
  display: none;
71
71
  }
72
72
  }
@@ -82,7 +82,7 @@
82
82
  position: relative;
83
83
  padding-left: govuk-spacing(4);
84
84
 
85
- &:before {
85
+ &::before {
86
86
  content: "";
87
87
  position: absolute;
88
88
  top: 10%;
@@ -95,14 +95,14 @@
95
95
  }
96
96
 
97
97
  .gem-c-action-link--white-arrow {
98
- &:before {
98
+ &::before {
99
99
  background-image: image-url("govuk_publishing_components/action-link-arrow--white.png");
100
100
  background-image: image-url("govuk_publishing_components/action-link-arrow--white.svg"), linear-gradient(transparent, transparent);
101
101
  }
102
102
  }
103
103
 
104
104
  .gem-c-action-link--blue-arrow {
105
- &:before {
105
+ &::before {
106
106
  width: 36px;
107
107
  height: 28px;
108
108
  background: image-url("govuk_publishing_components/action-link-arrow--blue.png");
@@ -120,7 +120,7 @@
120
120
  }
121
121
 
122
122
  .gem-c-action-link--simple {
123
- &:before {
123
+ &::before {
124
124
  width: 30px;
125
125
  height: 30px;
126
126
  background: image-url("govuk_publishing_components/action-link-arrow--simple.png");
@@ -132,7 +132,7 @@
132
132
  }
133
133
 
134
134
  .gem-c-action-link--simple-light {
135
- &:before {
135
+ &::before {
136
136
  width: 30px;
137
137
  height: 30px;
138
138
  background: image-url("govuk_publishing_components/action-link-arrow--simple-light.png");
@@ -144,14 +144,14 @@
144
144
  }
145
145
 
146
146
  .gem-c-action-link--dark-icon {
147
- &:before {
147
+ &::before {
148
148
  background: image-url("govuk_publishing_components/action-link-arrow--dark.png");
149
149
  background: image-url("govuk_publishing_components/action-link-arrow--dark.svg"), linear-gradient(transparent, transparent);
150
150
  }
151
151
  }
152
152
 
153
153
  .gem-c-action-link--dark-large-icon {
154
- &:before {
154
+ &::before {
155
155
  background: image-url("govuk_publishing_components/action-link-arrow--dark.png");
156
156
  background: image-url("govuk_publishing_components/action-link-arrow--dark.svg"), linear-gradient(transparent, transparent);
157
157
  height: 34px;
@@ -174,7 +174,7 @@
174
174
  margin-bottom: govuk-spacing(2);
175
175
  }
176
176
 
177
- &:before {
177
+ &::before {
178
178
  height: 30px;
179
179
  width: 35px;
180
180
  background-repeat: no-repeat;
@@ -184,7 +184,7 @@
184
184
  }
185
185
 
186
186
  .gem-c-action-link--transparent-icon {
187
- &:before {
187
+ &::before {
188
188
  background-image: image-url("govuk_publishing_components/action-link-arrow--transparent.svg");
189
189
  }
190
190
  }
@@ -192,7 +192,7 @@
192
192
  .gem-c-action-link--brexit {
193
193
  max-width: none;
194
194
 
195
- &:before {
195
+ &::before {
196
196
  height: 30px;
197
197
  width: 30px;
198
198
  background-image: image-url("govuk_publishing_components/action-link-arrow--brexit.svg");
@@ -208,7 +208,7 @@
208
208
  @include govuk-media-query($from: tablet) {
209
209
  margin-bottom: govuk-spacing(2);
210
210
 
211
- &:before {
211
+ &::before {
212
212
  width: 35px;
213
213
  background-position: 0 4px;
214
214
  background-size: 25px auto;
@@ -217,7 +217,7 @@
217
217
  }
218
218
 
219
219
  .gem-c-action-link--nhs {
220
- &:before {
220
+ &::before {
221
221
  width: 80px;
222
222
  height: 70px;
223
223
  background: image-url("govuk_publishing_components/action-link--nhs.png");
@@ -232,7 +232,7 @@
232
232
  color: govuk-colour("white");
233
233
 
234
234
  .gem-c-action-link__subtext {
235
- &:before {
235
+ &::before {
236
236
  border-color: govuk-colour("white");
237
237
  }
238
238
  }
@@ -16,8 +16,9 @@
16
16
  @include govuk-font($size: 19, $weight: bold);
17
17
 
18
18
  // This pseudo element is to bypass an issue with NVDA where block level elements are dictated separately.
19
- // What's happening here is that the label and the number technically have an inline relationship but appear to have a block relationship thanks to this element
20
- &:before {
19
+ // What's happening here is that the label and the number technically have an inline relationship
20
+ // but appear to have a block relationship thanks to this element
21
+ &::before {
21
22
  content: "";
22
23
  display: block;
23
24
  }