govuk_publishing_components 35.15.5 → 35.16.1

Sign up to get free protection for your applications and to get access to all the features.
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
  }