govuk_publishing_components 32.0.0 → 33.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/component_guide/accessibility-test.js +0 -1
  3. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-core.js +175 -0
  4. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-ecommerce-tracker.js +1 -1
  5. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-event-tracker.js +5 -13
  6. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-link-tracker.js +80 -309
  7. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-page-views.js +2 -2
  8. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-specialist-link-tracker.js +140 -0
  9. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/init-ga4.js +3 -0
  10. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4.js +1 -0
  11. data/app/assets/javascripts/govuk_publishing_components/components/accordion.js +12 -1
  12. data/app/assets/javascripts/govuk_publishing_components/components/layout-super-navigation-header.js +13 -4
  13. data/app/assets/javascripts/govuk_publishing_components/components/single-page-notification-button.js +24 -8
  14. data/app/assets/javascripts/govuk_publishing_components/vendor/lux/lux-reporter.js +83 -86
  15. data/app/assets/stylesheets/govuk_publishing_components/components/_big-number.scss +2 -5
  16. data/app/assets/stylesheets/govuk_publishing_components/components/_image-card.scss +1 -5
  17. data/app/assets/stylesheets/govuk_publishing_components/components/_input.scss +3 -5
  18. data/app/assets/stylesheets/govuk_publishing_components/components/_layout-super-navigation-header.scss +10 -30
  19. data/app/assets/stylesheets/govuk_publishing_components/components/_search.scss +0 -7
  20. data/app/assets/stylesheets/govuk_publishing_components/components/_share-links.scss +0 -6
  21. data/app/views/govuk_publishing_components/components/_accordion.html.erb +14 -1
  22. data/app/views/govuk_publishing_components/components/_error_summary.html.erb +27 -26
  23. data/app/views/govuk_publishing_components/components/_layout_super_navigation_header.html.erb +2 -2
  24. data/app/views/govuk_publishing_components/components/_phase_banner.html.erb +1 -1
  25. data/app/views/govuk_publishing_components/components/_share_links.html.erb +18 -15
  26. data/app/views/govuk_publishing_components/components/_single_page_notification_button.html.erb +1 -1
  27. data/app/views/govuk_publishing_components/components/docs/accordion.yml +15 -3
  28. data/app/views/govuk_publishing_components/components/docs/button.yml +10 -0
  29. data/app/views/govuk_publishing_components/components/docs/share_links.yml +59 -30
  30. data/app/views/govuk_publishing_components/components/docs/single_page_notification_button.yml +10 -1
  31. data/app/views/govuk_publishing_components/components/feedback/_yes_no_banner.html.erb +3 -3
  32. data/config/locales/ar.yml +4 -1
  33. data/config/locales/az.yml +4 -1
  34. data/config/locales/be.yml +4 -1
  35. data/config/locales/bg.yml +4 -1
  36. data/config/locales/bn.yml +4 -1
  37. data/config/locales/cs.yml +4 -1
  38. data/config/locales/cy.yml +4 -1
  39. data/config/locales/da.yml +4 -1
  40. data/config/locales/de.yml +4 -1
  41. data/config/locales/dr.yml +4 -1
  42. data/config/locales/el.yml +4 -1
  43. data/config/locales/en.yml +20 -17
  44. data/config/locales/es-419.yml +4 -1
  45. data/config/locales/es.yml +4 -1
  46. data/config/locales/et.yml +4 -1
  47. data/config/locales/fa.yml +4 -1
  48. data/config/locales/fi.yml +4 -1
  49. data/config/locales/fr.yml +4 -1
  50. data/config/locales/gd.yml +4 -1
  51. data/config/locales/gu.yml +4 -1
  52. data/config/locales/he.yml +4 -1
  53. data/config/locales/hi.yml +4 -1
  54. data/config/locales/hr.yml +4 -1
  55. data/config/locales/hu.yml +4 -1
  56. data/config/locales/hy.yml +4 -1
  57. data/config/locales/id.yml +4 -1
  58. data/config/locales/is.yml +4 -1
  59. data/config/locales/it.yml +4 -1
  60. data/config/locales/ja.yml +4 -1
  61. data/config/locales/ka.yml +4 -1
  62. data/config/locales/kk.yml +4 -1
  63. data/config/locales/ko.yml +4 -1
  64. data/config/locales/lt.yml +4 -1
  65. data/config/locales/lv.yml +4 -1
  66. data/config/locales/ms.yml +4 -1
  67. data/config/locales/mt.yml +4 -1
  68. data/config/locales/nl.yml +4 -1
  69. data/config/locales/no.yml +4 -1
  70. data/config/locales/pa-pk.yml +4 -1
  71. data/config/locales/pa.yml +4 -1
  72. data/config/locales/pl.yml +4 -1
  73. data/config/locales/ps.yml +4 -1
  74. data/config/locales/pt.yml +4 -1
  75. data/config/locales/ro.yml +4 -1
  76. data/config/locales/ru.yml +4 -1
  77. data/config/locales/si.yml +4 -1
  78. data/config/locales/sk.yml +4 -1
  79. data/config/locales/sl.yml +4 -1
  80. data/config/locales/so.yml +4 -1
  81. data/config/locales/sq.yml +4 -1
  82. data/config/locales/sr.yml +4 -1
  83. data/config/locales/sv.yml +4 -1
  84. data/config/locales/sw.yml +4 -1
  85. data/config/locales/ta.yml +4 -1
  86. data/config/locales/th.yml +4 -1
  87. data/config/locales/tk.yml +4 -1
  88. data/config/locales/tr.yml +4 -1
  89. data/config/locales/uk.yml +4 -1
  90. data/config/locales/ur.yml +4 -1
  91. data/config/locales/uz.yml +4 -1
  92. data/config/locales/vi.yml +4 -1
  93. data/config/locales/zh-hk.yml +4 -1
  94. data/config/locales/zh-tw.yml +4 -1
  95. data/config/locales/zh.yml +4 -1
  96. data/lib/govuk_publishing_components/presenters/button_helper.rb +7 -1
  97. data/lib/govuk_publishing_components/presenters/single_page_notification_button_helper.rb +25 -1
  98. data/lib/govuk_publishing_components/version.rb +1 -1
  99. data/node_modules/axe-core/axe.js +4567 -4678
  100. data/node_modules/axe-core/axe.min.js +2 -2
  101. data/node_modules/axe-core/package.json +2 -2
  102. data/node_modules/axe-core/sri-history.json +8 -0
  103. data/node_modules/govuk-frontend/README.md +1 -2
  104. data/node_modules/govuk-frontend/govuk/all.js +1398 -273
  105. data/node_modules/govuk-frontend/govuk/common/closest-attribute-value.js +70 -0
  106. data/node_modules/govuk-frontend/govuk/common/index.js +172 -0
  107. data/node_modules/govuk-frontend/govuk/common/normalise-dataset.js +373 -0
  108. data/node_modules/govuk-frontend/govuk/common.js +138 -3
  109. data/node_modules/govuk-frontend/govuk/components/accordion/accordion.js +753 -25
  110. data/node_modules/govuk-frontend/govuk/components/accordion/fixtures.json +54 -22
  111. data/node_modules/govuk-frontend/govuk/components/accordion/macro-options.json +36 -0
  112. data/node_modules/govuk-frontend/govuk/components/accordion/template.njk +7 -1
  113. data/node_modules/govuk-frontend/govuk/components/back-link/fixtures.json +12 -12
  114. data/node_modules/govuk-frontend/govuk/components/breadcrumbs/fixtures.json +22 -22
  115. data/node_modules/govuk-frontend/govuk/components/button/_index.scss +23 -5
  116. data/node_modules/govuk-frontend/govuk/components/button/button.js +365 -107
  117. data/node_modules/govuk-frontend/govuk/components/button/fixtures.json +85 -66
  118. data/node_modules/govuk-frontend/govuk/components/button/template.njk +1 -1
  119. data/node_modules/govuk-frontend/govuk/components/character-count/_index.scss +9 -0
  120. data/node_modules/govuk-frontend/govuk/components/character-count/character-count.js +1033 -121
  121. data/node_modules/govuk-frontend/govuk/components/character-count/fixtures.json +112 -36
  122. data/node_modules/govuk-frontend/govuk/components/character-count/macro-options.json +42 -0
  123. data/node_modules/govuk-frontend/govuk/components/character-count/template.njk +27 -3
  124. data/node_modules/govuk-frontend/govuk/components/checkboxes/checkboxes.js +30 -2
  125. data/node_modules/govuk-frontend/govuk/components/checkboxes/fixtures.json +96 -93
  126. data/node_modules/govuk-frontend/govuk/components/cookie-banner/fixtures.json +46 -46
  127. data/node_modules/govuk-frontend/govuk/components/date-input/fixtures.json +50 -50
  128. data/node_modules/govuk-frontend/govuk/components/details/details.js +43 -13
  129. data/node_modules/govuk-frontend/govuk/components/details/fixtures.json +20 -20
  130. data/node_modules/govuk-frontend/govuk/components/error-message/fixtures.json +20 -20
  131. data/node_modules/govuk-frontend/govuk/components/error-summary/error-summary.js +268 -6
  132. data/node_modules/govuk-frontend/govuk/components/error-summary/fixtures.json +44 -35
  133. data/node_modules/govuk-frontend/govuk/components/error-summary/template.njk +25 -21
  134. data/node_modules/govuk-frontend/govuk/components/fieldset/fixtures.json +51 -39
  135. data/node_modules/govuk-frontend/govuk/components/file-upload/fixtures.json +26 -26
  136. data/node_modules/govuk-frontend/govuk/components/footer/_index.scss +1 -1
  137. data/node_modules/govuk-frontend/govuk/components/footer/fixtures.json +46 -46
  138. data/node_modules/govuk-frontend/govuk/components/footer/macro-options.json +2 -2
  139. data/node_modules/govuk-frontend/govuk/components/header/fixtures.json +93 -38
  140. data/node_modules/govuk-frontend/govuk/components/header/header.js +6 -0
  141. data/node_modules/govuk-frontend/govuk/components/header/macro-options.json +8 -2
  142. data/node_modules/govuk-frontend/govuk/components/header/template.njk +4 -2
  143. data/node_modules/govuk-frontend/govuk/components/hint/fixtures.json +12 -12
  144. data/node_modules/govuk-frontend/govuk/components/input/fixtures.json +80 -80
  145. data/node_modules/govuk-frontend/govuk/components/inset-text/fixtures.json +12 -12
  146. data/node_modules/govuk-frontend/govuk/components/label/fixtures.json +34 -34
  147. data/node_modules/govuk-frontend/govuk/components/notification-banner/fixtures.json +56 -46
  148. data/node_modules/govuk-frontend/govuk/components/notification-banner/notification-banner.js +252 -2
  149. data/node_modules/govuk-frontend/govuk/components/notification-banner/template.njk +1 -1
  150. data/node_modules/govuk-frontend/govuk/components/pagination/_index.scss +10 -7
  151. data/node_modules/govuk-frontend/govuk/components/pagination/fixtures.json +33 -26
  152. data/node_modules/govuk-frontend/govuk/components/panel/fixtures.json +18 -18
  153. data/node_modules/govuk-frontend/govuk/components/phase-banner/fixtures.json +14 -14
  154. data/node_modules/govuk-frontend/govuk/components/radios/fixtures.json +94 -91
  155. data/node_modules/govuk-frontend/govuk/components/radios/radios.js +30 -2
  156. data/node_modules/govuk-frontend/govuk/components/select/fixtures.json +32 -32
  157. data/node_modules/govuk-frontend/govuk/components/skip-link/fixtures.json +22 -20
  158. data/node_modules/govuk-frontend/govuk/components/skip-link/skip-link.js +10 -4
  159. data/node_modules/govuk-frontend/govuk/components/summary-list/fixtures.json +50 -50
  160. data/node_modules/govuk-frontend/govuk/components/table/_index.scss +1 -1
  161. data/node_modules/govuk-frontend/govuk/components/table/fixtures.json +40 -40
  162. data/node_modules/govuk-frontend/govuk/components/tabs/fixtures.json +29 -29
  163. data/node_modules/govuk-frontend/govuk/components/tabs/tabs.js +28 -0
  164. data/node_modules/govuk-frontend/govuk/components/tag/fixtures.json +28 -28
  165. data/node_modules/govuk-frontend/govuk/components/textarea/fixtures.json +34 -34
  166. data/node_modules/govuk-frontend/govuk/components/warning-text/fixtures.json +14 -14
  167. data/node_modules/govuk-frontend/govuk/core/_section-break.scss +1 -1
  168. data/node_modules/govuk-frontend/govuk/helpers/_colour.scss +2 -2
  169. data/node_modules/govuk-frontend/govuk/helpers/_links.scss +6 -6
  170. data/node_modules/govuk-frontend/govuk/i18n.js +390 -0
  171. data/node_modules/govuk-frontend/govuk/macros/i18n.njk +15 -0
  172. data/node_modules/govuk-frontend/govuk/settings/_all.scss +1 -0
  173. data/node_modules/govuk-frontend/govuk/settings/_colours-palette.scss +12 -0
  174. data/node_modules/govuk-frontend/govuk/settings/_compatibility.scss +26 -0
  175. data/node_modules/govuk-frontend/govuk/settings/_typography-font.scss +23 -0
  176. data/node_modules/govuk-frontend/govuk/settings/_typography-responsive.scss +12 -0
  177. data/node_modules/govuk-frontend/govuk/settings/_warnings.scss +53 -0
  178. data/node_modules/govuk-frontend/govuk/tools/_compatibility.scss +20 -6
  179. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Date/now.js +21 -0
  180. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/dataset.js +300 -0
  181. data/node_modules/govuk-frontend/govuk/vendor/polyfills/String/prototype/trim.js +21 -0
  182. data/node_modules/govuk-frontend/govuk-esm/all.mjs +50 -27
  183. data/node_modules/govuk-frontend/govuk-esm/common/closest-attribute-value.mjs +15 -0
  184. data/node_modules/govuk-frontend/govuk-esm/common/index.mjs +159 -0
  185. data/node_modules/govuk-frontend/govuk-esm/common/normalise-dataset.mjs +58 -0
  186. data/node_modules/govuk-frontend/govuk-esm/common.mjs +6 -28
  187. data/node_modules/govuk-frontend/govuk-esm/components/accordion/accordion.mjs +113 -43
  188. data/node_modules/govuk-frontend/govuk-esm/components/button/button.mjs +67 -30
  189. data/node_modules/govuk-frontend/govuk-esm/components/character-count/character-count.mjs +325 -123
  190. data/node_modules/govuk-frontend/govuk-esm/components/checkboxes/checkboxes.mjs +9 -3
  191. data/node_modules/govuk-frontend/govuk-esm/components/details/details.mjs +22 -8
  192. data/node_modules/govuk-frontend/govuk-esm/components/error-summary/error-summary.mjs +48 -6
  193. data/node_modules/govuk-frontend/govuk-esm/components/header/header.mjs +6 -0
  194. data/node_modules/govuk-frontend/govuk-esm/components/notification-banner/notification-banner.mjs +32 -2
  195. data/node_modules/govuk-frontend/govuk-esm/components/radios/radios.mjs +9 -3
  196. data/node_modules/govuk-frontend/govuk-esm/components/skip-link/skip-link.mjs +10 -4
  197. data/node_modules/govuk-frontend/govuk-esm/components/tabs/tabs.mjs +8 -2
  198. data/node_modules/govuk-frontend/govuk-esm/i18n.mjs +380 -0
  199. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Date/now.mjs +13 -0
  200. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element/prototype/dataset.mjs +68 -0
  201. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/String/prototype/trim.mjs +13 -0
  202. data/node_modules/govuk-frontend/govuk-prototype-kit/init.js +7 -0
  203. data/node_modules/govuk-frontend/govuk-prototype-kit/init.scss +12 -0
  204. data/node_modules/govuk-frontend/govuk-prototype-kit.config.json +138 -7
  205. data/node_modules/govuk-frontend/package.json +1 -1
  206. metadata +22 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 40c8f5cd0a641b265004d0db4aa4dad79cc1f251bc6510655d69bc7fa954614f
4
- data.tar.gz: 80b737085d4e49d8a2d5558472f918ed7632db5932d32c9945ad82d936c3a2d0
3
+ metadata.gz: 72c594630cfc9f9359ae29eaff51094d512f4060cff0ad23e96319f264b0bae6
4
+ data.tar.gz: d074f64d7b59bd3657e0e84c2a44f9de23a8a5db99e71e832b2eab98a617fb8e
5
5
  SHA512:
6
- metadata.gz: c18a6aee8070978fd4f72f29eb7d916d8899a261c3bac0fac075703a18d5c7e7b1854fc56dc3d9ee4bfae10e1c5725aa4693f0488edac5429bb5f9b6ceed3e33
7
- data.tar.gz: 2903283d75f095a49ebd6940cb003bc6653bf3e2e96cd990f311b3b113b1925bf042b9bc689a0f01d1f951973aee8f58fd00945e771507477e1ec69fa29eca1b
6
+ metadata.gz: 77cde1c322a721bc97a30a348e53b84d369c029afa32cd81fc7def30577aea67324f4c60ecc5c6f03e1c77dc3697d6a2ab5339a177c3946276ddcaa26bb72303
7
+ data.tar.gz: 7134f8030b4d16a4b4e38dc5ba6aa8d9c08aa0bc2ef319e7710b407685a8cbace2ce9c32c5330883cd93412690687387f5960246667a63a8808cc20949c02de1
@@ -24,7 +24,6 @@
24
24
  })
25
25
 
26
26
  var axeOptions = {
27
- restoreScroll: true,
28
27
  include: [selector],
29
28
  rules: axeRules
30
29
  }
@@ -1,3 +1,4 @@
1
+ //= require ../vendor/polyfills/closest.js
1
2
  window.GOVUK = window.GOVUK || {}
2
3
  window.GOVUK.analyticsGa4 = window.GOVUK.analyticsGa4 || {};
3
4
 
@@ -47,6 +48,180 @@ window.GOVUK.analyticsGa4 = window.GOVUK.analyticsGa4 || {};
47
48
 
48
49
  getGemVersion: function () {
49
50
  return window.GOVUK.analyticsGa4.vars.gem_version || 'not found'
51
+ },
52
+
53
+ trackFunctions: {
54
+ findTrackingAttributes: function (clicked, trackingTrigger) {
55
+ if (clicked.hasAttribute('[' + trackingTrigger + ']')) {
56
+ return clicked
57
+ } else {
58
+ return clicked.closest('[' + trackingTrigger + ']')
59
+ }
60
+ },
61
+
62
+ // create an object to split up long URLs and get around the 100 character limit on GTM data
63
+ // this gets reassembled in GA4
64
+ populateLinkPathParts: function (href) {
65
+ var path = ''
66
+ if (this.hrefIsRelative(href) || this.isMailToLink(href)) {
67
+ path = href
68
+ } else {
69
+ // This regex matches a protocol and domain name at the start of a string such as https://www.gov.uk, http://gov.uk, //gov.uk
70
+ path = href.replace(/^(http:||https:)?(\/\/)([^\/]*)/, '') // eslint-disable-line no-useless-escape
71
+ }
72
+
73
+ if (path === '/' || path.length === 0) {
74
+ return
75
+ }
76
+
77
+ /*
78
+ This will create an object with 5 keys that are indexes ("1", "2", etc.)
79
+ The values will be each part of the link path split every 100 characters, or undefined.
80
+ For example: {"1": "/hello/world/etc...", "2": "/more/path/text...", "3": undefined, "4": undefined, "5": undefined}
81
+ Undefined values are needed to override the persistent object in GTM so that any values from old pushes are overwritten.
82
+ */
83
+ var parts = path.match(/.{1,100}/g)
84
+ var obj = {}
85
+ for (var i = 0; i < 5; i++) {
86
+ obj[(i + 1).toString()] = parts[i]
87
+ }
88
+ return obj
89
+ },
90
+
91
+ hrefIsRelative: function (href) {
92
+ // Checks that a link is relative, but is not a protocol relative url
93
+ return href[0] === '/' && href[1] !== '/'
94
+ },
95
+
96
+ hrefIsAnchor: function (href) {
97
+ return href[0] === '#'
98
+ },
99
+
100
+ isMailToLink: function (href) {
101
+ return href.substring(0, 7) === 'mailto:'
102
+ },
103
+
104
+ getClickType: function (event) {
105
+ switch (event.type) {
106
+ case 'click':
107
+ if (event.ctrlKey) {
108
+ return 'ctrl click'
109
+ } else if (event.metaKey) {
110
+ return 'command/win click'
111
+ } else if (event.shiftKey) {
112
+ return 'shift click'
113
+ } else {
114
+ return 'primary click'
115
+ }
116
+ case 'mousedown':
117
+ return 'middle click'
118
+ case 'contextmenu':
119
+ return 'secondary click'
120
+ }
121
+ },
122
+
123
+ isInternalLink: function (href) {
124
+ var internalDomains = window.GOVUK.analyticsGa4.vars.internalDomains
125
+ if (this.hrefIsRelative(href) || this.hrefIsAnchor(href)) {
126
+ return true
127
+ }
128
+ var result = false
129
+ for (var i = 0; i < internalDomains.length; i++) {
130
+ var internalDomain = internalDomains[i]
131
+ if (this.hrefPointsToDomain(href, internalDomain)) {
132
+ result = true
133
+ }
134
+ }
135
+ return result
136
+ },
137
+
138
+ isExternalLink: function (href) {
139
+ return !this.isInternalLink(href)
140
+ },
141
+
142
+ hrefPointsToDomain: function (href, domain) {
143
+ /* Add a trailing slash to prevent an edge case such
144
+ as the href www.gov.uk.domain.co.uk being detected as an internal link,
145
+ if we were checking for 'www.gov.uk' instead of 'www.gov.uk/' */
146
+ if (domain.substring(domain.length) !== '/') {
147
+ domain = domain + '/'
148
+ }
149
+
150
+ /* If the href doesn't end in a slash, we add one.
151
+ This fixes an edge case where the <a href> is exactly `https://www.gov.uk`
152
+ but these checks would only look for `https://www.gov.uk/` */
153
+ if (href.substring(href.length) !== '/') {
154
+ href = href + '/'
155
+ }
156
+ // matches the domain preceded by https:// http:// or //
157
+ var regex = new RegExp('^((http)*(s)*(:)*//)(' + domain + ')', 'g')
158
+ return regex.test(href)
159
+ },
160
+
161
+ removeLinesAndExtraSpaces: function (text) {
162
+ text = text.trim()
163
+ text = text.replace(/(\r\n|\n|\r)/gm, ' ') // Replace line breaks with 1 space
164
+ text = text.replace(/\s+/g, ' ') // Replace instances of 2+ spaces with 1 space
165
+ return text
166
+ },
167
+
168
+ removeCrossDomainParams: function (href) {
169
+ if (href.indexOf('_ga') !== -1 || href.indexOf('_gl') !== -1) {
170
+ // _ga & _gl are values needed for cross domain tracking, but we don't want them included in our click tracking.
171
+ href = href.replaceAll(/_g[al]=([^&]*)/g, '')
172
+
173
+ // The following code cleans up inconsistencies such as gov.uk/&&, gov.uk/?&hello=world, gov.uk/?, and gov.uk/&.
174
+ href = href.replaceAll(/(&&)+/g, '&')
175
+ href = href.replace('?&', '?')
176
+ if (this.stringEndsWith(href, '?') || this.stringEndsWith(href, '&')) {
177
+ href = href.substring(0, href.length - 1)
178
+ }
179
+ }
180
+ return href
181
+ },
182
+
183
+ stringStartsWith: function (string, stringToFind) {
184
+ return string.substring(0, stringToFind.length) === stringToFind
185
+ },
186
+
187
+ stringEndsWith: function (string, stringToFind) {
188
+ return string.substring(string.length - stringToFind.length, string.length) === stringToFind
189
+ },
190
+
191
+ populateLinkDomain: function (href) {
192
+ // We always want mailto links to have an undefined link_domain
193
+ if (this.isMailToLink(href)) {
194
+ return undefined
195
+ }
196
+
197
+ if (this.hrefIsRelative(href) || this.hrefIsAnchor(href)) {
198
+ return this.getProtocol() + '//' + this.getHostname()
199
+ } else {
200
+ // This regex matches a protocol and domain name at the start of a string such as https://www.gov.uk, http://gov.uk, //gov.uk
201
+ var domainRegex = /^(http:||https:)?(\/\/)([^\/]*)/ // eslint-disable-line no-useless-escape
202
+ var domain = domainRegex.exec(href)[0]
203
+ return domain
204
+ }
205
+ },
206
+
207
+ getProtocol: function () {
208
+ return window.location.protocol
209
+ },
210
+
211
+ getHostname: function () {
212
+ return window.location.hostname
213
+ },
214
+
215
+ appendDomainsWithoutWWW: function (domainsArrays) {
216
+ // Add domains with www. removed, in case site hrefs are marked up without www. included.
217
+ for (var i = 0; i < domainsArrays.length; i++) {
218
+ var domain = domainsArrays[i]
219
+ if (this.stringStartsWith(domain, 'www.')) {
220
+ var domainWithoutWww = domain.replace('www.', '')
221
+ domainsArrays.push(domainWithoutWww)
222
+ }
223
+ }
224
+ }
50
225
  }
51
226
  }
52
227
 
@@ -97,7 +97,7 @@
97
97
  })
98
98
 
99
99
  ecommerceObject.event_data = {
100
- external: GOVUK.analyticsGa4.analyticsModules.Ga4LinkTracker.isExternalLink(searchResult.getAttribute('data-ecommerce-path')) ? 'true' : 'false'
100
+ external: GOVUK.analyticsGa4.core.trackFunctions.isExternalLink(searchResult.getAttribute('data-ecommerce-path')) ? 'true' : 'false'
101
101
  }
102
102
  } else {
103
103
  for (var i = 0; i < ecommerceRows.length; i++) {
@@ -1,4 +1,4 @@
1
- // = require govuk/vendor/polyfills/Element/prototype/closest.js
1
+ //= require ../vendor/polyfills/closest.js
2
2
  window.GOVUK = window.GOVUK || {}
3
3
  window.GOVUK.Modules = window.GOVUK.Modules || {};
4
4
 
@@ -7,7 +7,7 @@ window.GOVUK.Modules = window.GOVUK.Modules || {};
7
7
 
8
8
  function Ga4EventTracker (module) {
9
9
  this.module = module
10
- this.trackingTrigger = 'data-ga4' // elements with this attribute get tracked
10
+ this.trackingTrigger = 'data-ga4-event' // elements with this attribute get tracked
11
11
  }
12
12
 
13
13
  Ga4EventTracker.prototype.init = function () {
@@ -29,7 +29,7 @@ window.GOVUK.Modules = window.GOVUK.Modules || {};
29
29
  }
30
30
 
31
31
  Ga4EventTracker.prototype.trackClick = function (event) {
32
- var target = this.findTrackingAttributes(event.target)
32
+ var target = window.GOVUK.analyticsGa4.core.trackFunctions.findTrackingAttributes(event.target, this.trackingTrigger)
33
33
  if (target) {
34
34
  var schema = new window.GOVUK.analyticsGa4.Schemas().eventSchema()
35
35
 
@@ -51,8 +51,8 @@ window.GOVUK.Modules = window.GOVUK.Modules || {};
51
51
  }
52
52
  }
53
53
 
54
- /* Ensure it only tracks aria-expanded in an accordion or element with data-ga4-expandable on it. */
55
- if (target.closest('.gem-c-accordion') || target.closest('[data-ga4-expandable]')) {
54
+ /* Ensure it only tracks aria-expanded in an element with data-ga4-expandable on it. */
55
+ if (target.closest('[data-ga4-expandable]')) {
56
56
  var ariaExpanded = this.getClosestAttribute(target, 'aria-expanded')
57
57
  }
58
58
 
@@ -88,14 +88,6 @@ window.GOVUK.Modules = window.GOVUK.Modules || {};
88
88
  }
89
89
  }
90
90
 
91
- Ga4EventTracker.prototype.findTrackingAttributes = function (clicked) {
92
- if (clicked.hasAttribute('[' + this.trackingTrigger + ']')) {
93
- return clicked
94
- } else {
95
- return clicked.closest('[' + this.trackingTrigger + ']')
96
- }
97
- }
98
-
99
91
  // check if an attribute exists or contains the attribute
100
92
  Ga4EventTracker.prototype.getClosestAttribute = function (clicked, attribute) {
101
93
  var isAttributeOnElement = clicked.getAttribute(attribute)
@@ -1,331 +1,102 @@
1
- // = require govuk/vendor/polyfills/Element/prototype/closest.js
1
+ //= require ../vendor/polyfills/closest.js
2
2
  window.GOVUK = window.GOVUK || {}
3
- window.GOVUK.analyticsGa4 = window.GOVUK.analyticsGa4 || {}
4
- window.GOVUK.analyticsGa4.analyticsModules = window.GOVUK.analyticsGa4.analyticsModules || {};
3
+ window.GOVUK.Modules = window.GOVUK.Modules || {};
5
4
 
6
- (function (analyticsModules) {
5
+ (function (Modules) {
7
6
  'use strict'
8
7
 
9
- var Ga4LinkTracker = {
10
- init: function (config) {
11
- if (window.dataLayer) {
12
- config = config || {}
13
- this.internalDomains = config.internalDomains || []
14
- this.internalDomains.push(this.getHostname())
15
- this.appendDomainsWithoutWWW(this.internalDomains)
16
- this.internalDownloadPaths = config.internalDownloadPaths || ['/government/uploads/']
17
- this.dedicatedDownloadDomains = config.dedicatedDownloadDomains || ['assets.publishing.service.gov.uk']
18
- this.appendDomainsWithoutWWW(this.dedicatedDownloadDomains)
19
- this.handleClick = this.handleClick.bind(this)
20
- this.handleMousedown = this.handleMousedown.bind(this)
21
-
22
- if (!config.disableListeners) {
23
- document.querySelector('body').addEventListener('click', this.handleClick)
24
- document.querySelector('body').addEventListener('contextmenu', this.handleClick)
25
- document.querySelector('body').addEventListener('mousedown', this.handleMousedown)
26
- }
27
- }
28
- },
29
-
30
- stopTracking: function () {
31
- document.querySelector('body').removeEventListener('click', this.handleClick)
32
- document.querySelector('body').removeEventListener('contextmenu', this.handleClick)
33
- document.querySelector('body').removeEventListener('mousedown', this.handleMousedown)
34
- },
35
-
36
- handleClick: function (event) {
37
- var element = event.target
38
-
39
- if (element.tagName !== 'A') {
40
- element = element.closest('a')
41
- }
42
-
43
- if (!element) {
44
- return
45
- }
46
-
47
- var href = element.getAttribute('href')
48
-
49
- if (!href) {
50
- return
51
- }
52
- var clickData = {}
53
- var linkAttributes = element.getAttribute('data-ga4-link')
54
- if (linkAttributes) {
55
- clickData = JSON.parse(linkAttributes)
56
-
57
- /* Since external links can't be determined in the template, we use populated-via-js as a signal
58
- for our JavaScript to determine this value. */
59
- if (clickData.external === 'populated-via-js' && clickData.url) {
60
- clickData.external = this.isExternalLink(clickData.url) ? 'true' : 'false'
61
- }
62
-
63
- if (clickData.method === 'populated-via-js') {
64
- clickData.method = this.getClickType(event)
65
- }
66
-
67
- if (clickData.index) {
68
- clickData.index = parseInt(clickData.index)
69
- }
8
+ function Ga4LinkTracker (module) {
9
+ this.module = module
10
+ this.trackingTrigger = 'data-ga4-link' // elements with this attribute get tracked
11
+ this.trackLinksOnly = this.module.hasAttribute('data-ga4-track-links-only')
12
+ this.limitToElementClass = this.module.getAttribute('data-ga4-limit-to-element-class')
13
+ }
70
14
 
71
- if (clickData.index_total) {
72
- clickData.index_total = parseInt(clickData.index_total)
73
- }
74
- } else if (this.isMailToLink(href)) {
75
- clickData.event_name = 'navigation'
76
- clickData.type = 'email'
77
- clickData.external = 'true'
78
- clickData.url = href
79
- clickData.text = this.removeLinesAndExtraSpaces(element.textContent)
80
- clickData.method = this.getClickType(event)
81
- } else if (this.isDownloadLink(href)) {
82
- clickData.event_name = 'file_download'
83
- clickData.type = this.isPreviewLink(href) ? 'preview' : 'generic download'
84
- clickData.external = this.isExternalLink(href) ? 'true' : 'false'
85
- clickData.url = href
86
- clickData.text = this.removeLinesAndExtraSpaces(element.textContent)
87
- clickData.method = this.getClickType(event)
88
- } else if (this.isExternalLink(href)) {
89
- clickData.event_name = 'navigation'
90
- clickData.type = 'generic link'
91
- clickData.external = 'true'
92
- clickData.url = href
93
- clickData.text = this.removeLinesAndExtraSpaces(element.textContent)
94
- clickData.method = this.getClickType(event)
95
- }
15
+ Ga4LinkTracker.prototype.init = function () {
16
+ var consentCookie = window.GOVUK.getConsentCookie()
96
17
 
97
- if (Object.keys(clickData).length > 0) {
98
- if (clickData.url) {
99
- clickData.url = this.removeCrossDomainParams(clickData.url)
100
- clickData.link_domain = this.populateLinkDomain(clickData.url)
101
- clickData.link_path_parts = this.populateLinkPathParts(clickData.url)
102
- }
18
+ if (consentCookie && consentCookie.settings) {
19
+ this.startModule()
20
+ } else {
21
+ this.startModule = this.startModule.bind(this)
22
+ window.addEventListener('cookie-consent', this.startModule)
23
+ }
24
+ }
103
25
 
104
- var schema = new window.GOVUK.analyticsGa4.Schemas().eventSchema()
105
- schema.event = 'event_data'
26
+ // triggered by cookie-consent event, which happens when users consent to cookies
27
+ Ga4LinkTracker.prototype.startModule = function () {
28
+ if (window.dataLayer) {
29
+ this.handleClick = this.handleClick.bind(this)
30
+ this.handleMousedown = this.handleMousedown.bind(this)
106
31
 
107
- // get attributes from the clickData object to send to GA
108
- // only allow it if it already exists in the schema
109
- for (var property in clickData) {
110
- if (property in schema.event_data) {
111
- schema.event_data[property] = clickData[property]
112
- }
113
- }
32
+ this.module.addEventListener('click', this.handleClick)
33
+ this.module.addEventListener('contextmenu', this.handleClick)
34
+ this.module.addEventListener('mousedown', this.handleMousedown)
35
+ }
36
+ }
114
37
 
115
- window.GOVUK.analyticsGa4.core.sendData(schema)
38
+ Ga4LinkTracker.prototype.handleClick = function (event) {
39
+ var target = event.target
40
+ if (!this.trackLinksOnly) {
41
+ this.trackClick(event)
42
+ } else if (this.trackLinksOnly && target.closest('a')) {
43
+ if (!this.limitToElementClass) {
44
+ this.trackClick(event)
45
+ } else if (target.closest('.' + this.limitToElementClass)) {
46
+ this.trackClick(event)
116
47
  }
117
- },
48
+ }
49
+ }
118
50
 
119
- populateLinkPathParts: function (href) {
120
- var path = ''
121
- if (this.hrefIsRelative(href) || this.isMailToLink(href)) {
122
- path = href
123
- } else {
124
- // This regex matches a protocol and domain name at the start of a string such as https://www.gov.uk, http://gov.uk, //gov.uk
125
- path = href.replace(/^(http:||https:)?(\/\/)([^\/]*)/, '') // eslint-disable-line no-useless-escape
126
- }
51
+ Ga4LinkTracker.prototype.handleMousedown = function (event) {
52
+ // 1 = middle mouse button
53
+ if (event.button === 1) {
54
+ this.handleClick(event)
55
+ }
56
+ }
127
57
 
128
- if (path === '/' || path.length === 0) {
58
+ Ga4LinkTracker.prototype.trackClick = function (event) {
59
+ var target = window.GOVUK.analyticsGa4.core.trackFunctions.findTrackingAttributes(event.target, this.trackingTrigger)
60
+ if (target) {
61
+ var schema = new window.GOVUK.analyticsGa4.Schemas().eventSchema()
62
+
63
+ try {
64
+ var data = target.getAttribute(this.trackingTrigger)
65
+ data = JSON.parse(data)
66
+ } catch (e) {
67
+ // if there's a problem with the config, don't start the tracker
68
+ console.error('GA4 configuration error: ' + e.message, window.location)
129
69
  return
130
70
  }
131
71
 
132
- /*
133
- This will create an object with 5 keys that are indexes ("1", "2", etc.)
134
- The values will be each part of the link path split every 100 characters, or undefined.
135
- For example: {"1": "/hello/world/etc...", "2": "/more/path/text...", "3": undefined, "4": undefined, "5": undefined}
136
- Undefined values are needed to override the persistent object in GTM so that any values from old pushes are overwritten.
137
- */
138
- var parts = path.match(/.{1,100}/g)
139
- var obj = {}
140
- for (var i = 0; i < 5; i++) {
141
- obj[(i + 1).toString()] = parts[i]
142
- }
143
- return obj
144
- },
145
-
146
- populateLinkDomain: function (href) {
147
- // We always want mailto links to have an undefined link_domain
148
- if (this.isMailToLink(href)) {
149
- return undefined
150
- }
151
-
152
- if (this.hrefIsRelative(href)) {
153
- return this.getProtocol() + '//' + this.getHostname()
154
- } else {
155
- // This regex matches a protocol and domain name at the start of a string such as https://www.gov.uk, http://gov.uk, //gov.uk
156
- var domainRegex = /^(http:||https:)?(\/\/)([^\/]*)/ // eslint-disable-line no-useless-escape
157
- var domain = domainRegex.exec(href)[0]
158
- return domain
159
- }
160
- },
161
-
162
- appendDomainsWithoutWWW: function (domainsArrays) {
163
- // Add domains with www. removed, in case site hrefs are marked up without www. included.
164
- for (var i = 0; i < domainsArrays.length; i++) {
165
- var domain = domainsArrays[i]
166
- if (this.stringStartsWith(domain, 'www.')) {
167
- var domainWithoutWww = domain.replace('www.', '')
168
- domainsArrays.push(domainWithoutWww)
72
+ schema.event = 'event_data'
73
+ var text = data.text || event.target.textContent
74
+ data.text = window.GOVUK.analyticsGa4.core.trackFunctions.removeLinesAndExtraSpaces(text)
75
+ data.url = window.GOVUK.analyticsGa4.core.trackFunctions.removeCrossDomainParams(this.findLink(event.target).getAttribute('href'))
76
+ data.link_domain = window.GOVUK.analyticsGa4.core.trackFunctions.populateLinkDomain(data.url)
77
+ data.link_path_parts = window.GOVUK.analyticsGa4.core.trackFunctions.populateLinkPathParts(data.url)
78
+ data.method = window.GOVUK.analyticsGa4.core.trackFunctions.getClickType(event)
79
+ data.external = window.GOVUK.analyticsGa4.core.trackFunctions.isExternalLink(data.url) ? 'true' : 'false'
80
+
81
+ // get attributes from the data attribute to send to GA
82
+ // only allow it if it already exists in the schema
83
+ for (var property in data) {
84
+ if (property in schema.event_data) {
85
+ schema.event_data[property] = data[property]
169
86
  }
170
87
  }
171
- },
172
-
173
- removeLinesAndExtraSpaces: function (text) {
174
- text = text.trim()
175
- text = text.replace(/(\r\n|\n|\r)/gm, ' ') // Replace line breaks with 1 space
176
- text = text.replace(/\s+/g, ' ') // Replace instances of 2+ spaces with 1 space
177
- return text
178
- },
179
-
180
- getClickType: function (event) {
181
- switch (event.type) {
182
- case 'click':
183
- if (event.ctrlKey) {
184
- return 'ctrl click'
185
- } else if (event.metaKey) {
186
- return 'command/win click'
187
- } else if (event.shiftKey) {
188
- return 'shift click'
189
- } else {
190
- return 'primary click'
191
- }
192
- case 'mousedown':
193
- return 'middle click'
194
- case 'contextmenu':
195
- return 'secondary click'
196
- }
197
- },
198
-
199
- handleMousedown: function (event) {
200
- // 1 = middle mouse button
201
- if (event.button === 1) {
202
- this.handleClick(event)
203
- }
204
- },
205
-
206
- isMailToLink: function (href) {
207
- return href.substring(0, 7) === 'mailto:'
208
- },
209
-
210
- isDownloadLink: function (href) {
211
- if (this.isInternalLink(href) && this.hrefPointsToDownloadPath(href)) {
212
- return true
213
- }
214
-
215
- var result = false
216
- for (var i = 0; i < this.dedicatedDownloadDomains.length; i++) {
217
- var downloadDomain = this.dedicatedDownloadDomains[i]
218
- if (this.hrefPointsToDomain(href, downloadDomain)) {
219
- result = true
220
- }
221
- }
222
- return result
223
- },
224
-
225
- isInternalLink: function (href) {
226
- if (this.hrefIsRelative(href) || this.hrefIsAnchor(href)) {
227
- return true
228
- }
229
- var result = false
230
- for (var i = 0; i < this.internalDomains.length; i++) {
231
- var internalDomain = this.internalDomains[i]
232
- if (this.hrefPointsToDomain(href, internalDomain)) {
233
- result = true
234
- }
235
- }
236
- return result
237
- },
238
-
239
- isExternalLink: function (href) {
240
- return !this.isInternalLink(href)
241
- },
242
88
 
243
- isPreviewLink: function (href) {
244
- /* Regex looks for:
245
- 1. The file extension period (the character '.')
246
- 2. any alphanumeric characters (so we can match any file type such as jpg, pdf, mp4.)
247
- 3. the presence of '/preview'.
248
- For example, .csv/preview or .mp4/preview will be matched.
249
- Regex is used over JS string methods as this should work with anchor links, query string parameters and files that may have 'preview' in their name.
250
- */
251
- var previewRegex = /\.\w+\/preview/i
252
- return previewRegex.test(href)
253
- },
254
-
255
- hrefPointsToDomain: function (href, domain) {
256
- /* Add a trailing slash to prevent an edge case such
257
- as the href www.gov.uk.domain.co.uk being detected as an internal link,
258
- if we were checking for 'www.gov.uk' instead of 'www.gov.uk/' */
259
- if (domain.substring(domain.length) !== '/') {
260
- domain = domain + '/'
261
- }
262
-
263
- /* If the href doesn't end in a slash, we add one.
264
- This fixes an edge case where the <a href> is exactly `https://www.gov.uk`
265
- but these checks would only look for `https://www.gov.uk/` */
266
- if (href.substring(href.length) !== '/') {
267
- href = href + '/'
268
- }
269
- var httpDomain = 'http://' + domain
270
- var httpsDomain = 'https://' + domain
271
- var schemaRelativeDomain = '//' + domain
272
- return this.stringStartsWith(href, domain) ||
273
- this.stringStartsWith(href, httpDomain) ||
274
- this.stringStartsWith(href, httpsDomain) ||
275
- this.stringStartsWith(href, schemaRelativeDomain)
276
- },
277
-
278
- hrefPointsToDownloadPath: function (href) {
279
- var result = false
280
- for (var i = 0; i < this.internalDownloadPaths.length; i++) {
281
- var internalDownloadPath = this.internalDownloadPaths[i]
282
- if (href.indexOf(internalDownloadPath) !== -1) {
283
- result = true
284
- }
285
- }
286
- return result
287
- },
288
-
289
- stringStartsWith: function (string, stringToFind) {
290
- return string.substring(0, stringToFind.length) === stringToFind
291
- },
292
-
293
- stringEndsWith: function (string, stringToFind) {
294
- return string.substring(string.length - stringToFind.length, string.length) === stringToFind
295
- },
296
-
297
- hrefIsRelative: function (href) {
298
- // Checks that a link is relative, but is not a protocol relative url
299
- return href[0] === '/' && href[1] !== '/'
300
- },
301
-
302
- hrefIsAnchor: function (href) {
303
- return href[0] === '#'
304
- },
305
-
306
- getHostname: function () {
307
- return window.location.hostname
308
- },
309
-
310
- getProtocol: function () {
311
- return window.location.protocol
312
- },
313
-
314
- removeCrossDomainParams: function (href) {
315
- if (href.indexOf('_ga') !== -1 || href.indexOf('_gl') !== -1) {
316
- // _ga & _gl are values needed for cross domain tracking, but we don't want them included in our click tracking.
317
- href = href.replaceAll(/_g[al]=([^&]*)/g, '')
89
+ window.GOVUK.analyticsGa4.core.sendData(schema)
90
+ }
91
+ }
318
92
 
319
- // The following code cleans up inconsistencies such as gov.uk/&&, gov.uk/?&hello=world, gov.uk/?, and gov.uk/&.
320
- href = href.replaceAll(/(&&)+/g, '&')
321
- href = href.replace('?&', '?')
322
- if (this.stringEndsWith(href, '?') || this.stringEndsWith(href, '&')) {
323
- href = href.substring(0, href.length - 1)
324
- }
325
- }
326
- return href
93
+ Ga4LinkTracker.prototype.findLink = function (target) {
94
+ if (target.tagName === 'A') {
95
+ return target
96
+ } else {
97
+ return target.closest('a')
327
98
  }
328
99
  }
329
100
 
330
- analyticsModules.Ga4LinkTracker = Ga4LinkTracker
331
- })(window.GOVUK.analyticsGa4.analyticsModules)
101
+ Modules.Ga4LinkTracker = Ga4LinkTracker
102
+ })(window.GOVUK.Modules)