govuk_publishing_components 35.10.0 → 35.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-core.js +2 -48
  3. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-form-tracker.js +5 -0
  4. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-link-tracker.js +2 -2
  5. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-schemas.js +51 -5
  6. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-scroll-tracker.js +225 -0
  7. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/init-ga4.js +0 -5
  8. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4.js +1 -0
  9. data/app/assets/stylesheets/govuk_publishing_components/components/_attachment.scss +7 -0
  10. data/app/assets/stylesheets/govuk_publishing_components/components/_feedback.scss +8 -0
  11. data/app/assets/stylesheets/govuk_publishing_components/components/govspeak/_tables.scss +41 -0
  12. data/app/views/govuk_publishing_components/components/_attachment.html.erb +3 -1
  13. data/app/views/govuk_publishing_components/components/_contents_list.html.erb +9 -9
  14. data/app/views/govuk_publishing_components/components/_metadata.html.erb +2 -1
  15. data/app/views/govuk_publishing_components/components/_tabs.html.erb +30 -14
  16. data/app/views/govuk_publishing_components/components/docs/govspeak.yml +22 -0
  17. data/app/views/govuk_publishing_components/components/docs/tabs.yml +25 -2
  18. data/lib/govuk_publishing_components/version.rb +1 -1
  19. data/node_modules/govuk-frontend/govuk/all.js +406 -1
  20. data/node_modules/govuk-frontend/govuk/all.js.map +1 -1
  21. data/node_modules/govuk-frontend/govuk/common/govuk-frontend-version.js +1 -1
  22. data/node_modules/govuk-frontend/govuk/components/_all.scss +2 -1
  23. data/node_modules/govuk-frontend/govuk/components/back-link/_index.scss +8 -0
  24. data/node_modules/govuk-frontend/govuk/components/back-link/fixtures.json +9 -0
  25. data/node_modules/govuk-frontend/govuk/components/breadcrumbs/_index.scss +12 -0
  26. data/node_modules/govuk-frontend/govuk/components/breadcrumbs/fixtures.json +21 -0
  27. data/node_modules/govuk-frontend/govuk/components/button/_index.scss +41 -3
  28. data/node_modules/govuk-frontend/govuk/components/button/fixtures.json +44 -0
  29. data/node_modules/govuk-frontend/govuk/components/checkboxes/macro-options.json +9 -8
  30. data/node_modules/govuk-frontend/govuk/components/exit-this-page/README.md +15 -0
  31. data/node_modules/govuk-frontend/govuk/components/exit-this-page/_exit-this-page.scss +2 -0
  32. data/node_modules/govuk-frontend/govuk/components/exit-this-page/_index.scss +97 -0
  33. data/node_modules/govuk-frontend/govuk/components/exit-this-page/exit-this-page.js +2120 -0
  34. data/node_modules/govuk-frontend/govuk/components/exit-this-page/exit-this-page.js.map +1 -0
  35. data/node_modules/govuk-frontend/govuk/components/exit-this-page/fixtures.json +50 -0
  36. data/node_modules/govuk-frontend/govuk/components/exit-this-page/macro-options.json +62 -0
  37. data/node_modules/govuk-frontend/govuk/components/exit-this-page/macro.njk +3 -0
  38. data/node_modules/govuk-frontend/govuk/components/exit-this-page/template.njk +16 -0
  39. data/node_modules/govuk-frontend/govuk/components/radios/macro-options.json +9 -8
  40. data/node_modules/govuk-frontend/govuk/core/_govuk-frontend-version.scss +1 -1
  41. data/node_modules/govuk-frontend/govuk/helpers/_visually-hidden.scss +12 -0
  42. data/node_modules/govuk-frontend/govuk/objects/_template.scss +20 -0
  43. data/node_modules/govuk-frontend/govuk-esm/all.mjs +8 -0
  44. data/node_modules/govuk-frontend/govuk-esm/all.mjs.map +1 -1
  45. data/node_modules/govuk-frontend/govuk-esm/common/govuk-frontend-version.mjs +1 -1
  46. data/node_modules/govuk-frontend/govuk-esm/components/exit-this-page/exit-this-page.mjs +406 -0
  47. data/node_modules/govuk-frontend/govuk-esm/components/exit-this-page/exit-this-page.mjs.map +1 -0
  48. data/node_modules/govuk-frontend/govuk-prototype-kit.config.json +4 -0
  49. data/node_modules/govuk-frontend/package.json +4 -2
  50. metadata +15 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8d14e21647dbbbe8c13a60c4ec7aaf3e1058cf97d9e52abb27e749eb63ec0725
4
- data.tar.gz: 76f8f7c6c2b641eb874b59f5b73b4c43c21ee3b834c36bd35a2e561fb25993e8
3
+ metadata.gz: d923b79189269417363dd2c12781e9ee9644a03bf2c514b21e24d71efe0c07c7
4
+ data.tar.gz: ce1f69cdb8ef0eaf3fcf836e359311e8b38a0f401bb931a3e8bfc27c1b568f56
5
5
  SHA512:
6
- metadata.gz: 0e55ee7ba5da82893c875901e389f4017d8f0b3a30ed3da20d1f9eda6fca500447a0efe4d0cbc78b37c2c4a422e2a74131ece944c95fcbc8d55dbe143046b4db
7
- data.tar.gz: 77f9962cda4b334df7d09c40d5c57f17881de115b3feabc5081202ad2179a193d943303548881375502f7adeb484ddb12caca2b3280adb95bbee7264949dc05a
6
+ metadata.gz: bffc7c115facd8c323182b04f089ecae6b5b2a6991ebed42aea17fc748b1a95bcbf1093f75e238291819bb9d821152b7f4ef3f2b95f0a4b2b1b14021ccbb5a14
7
+ data.tar.gz: 03c818b7b4f9731f3075020ad5273256a30c3cdef0862f92892edea1dd6cd19e0e65ea781d8c9bb7fb2c87403c5478503442453c68ff80a2deea73ba820e3721
@@ -30,32 +30,7 @@ window.GOVUK.analyticsGa4 = window.GOVUK.analyticsGa4 || {};
30
30
  firstScript.parentNode.insertBefore(newScript, firstScript)
31
31
  },
32
32
 
33
- ensureIndexesArePopulated: function (data) {
34
- if (!data.event_data) {
35
- return data
36
- }
37
-
38
- if (!data.event_data.index) {
39
- return data
40
- }
41
-
42
- var indexKeys = ['index_link', 'index_section', 'index_section_count']
43
-
44
- for (var i = 0; i < indexKeys.length; i++) {
45
- var indexKey = indexKeys[i]
46
-
47
- // If the index key isn't in the object, populate it. However if it's set to 0, leave it as 0. 0 is falsy so we have to add this extra check.
48
- if (!data.event_data.index[indexKey] && data.event_data.index[indexKey] !== 0) {
49
- data.event_data.index[indexKey] = undefined
50
- }
51
- }
52
-
53
- return data
54
- },
55
-
56
33
  sendData: function (data) {
57
- data = this.ensureIndexesArePopulated(data)
58
-
59
34
  data.govuk_gem_version = this.getGemVersion()
60
35
  // set this in the console as a debugging aid
61
36
  if (window.GOVUK.analyticsGa4.showDebug) {
@@ -274,29 +249,8 @@ window.GOVUK.analyticsGa4 = window.GOVUK.analyticsGa4 || {};
274
249
  }
275
250
  },
276
251
 
277
- addAttributesToElements: function (selector, dataAttributes) {
278
- var targetElements = document.querySelectorAll(selector)
279
-
280
- for (var i = 0; i < targetElements.length; i++) {
281
- var element = targetElements[i]
282
-
283
- for (var j = 0; j < dataAttributes.length; j++) {
284
- var key = dataAttributes[j].key
285
- var value = dataAttributes[j].value
286
-
287
- // value must check for undefined only as it would return false on an empty string, the number 0, etc.
288
- if (key && value !== undefined) {
289
- var existingAttributeValue = element.getAttribute(key)
290
-
291
- if (key === 'data-module' && existingAttributeValue) {
292
- // Combines values to prevent replacing any existing data-module values.
293
- element.setAttribute(key, existingAttributeValue + ' ' + value)
294
- } else {
295
- element.setAttribute(key, value)
296
- }
297
- }
298
- }
299
- }
252
+ applyRedactionIfRequired: function (PIIRemover, element, data) {
253
+ return element.closest('[data-ga4-do-not-redact]') ? data : PIIRemover.stripPIIWithOverride(data, true, true)
300
254
  }
301
255
  },
302
256
 
@@ -45,6 +45,11 @@ window.GOVUK.Modules = window.GOVUK.Modules || {};
45
45
  var formData = this.getInputValues(formInputs)
46
46
  data.text = data.text || (this.combineGivenAnswers(formData) || 'No answer given')
47
47
 
48
+ if (data.action === 'search') {
49
+ data.text = data.text.toLowerCase()
50
+ data.text = window.GOVUK.analyticsGa4.core.trackFunctions.removeLinesAndExtraSpaces(data.text)
51
+ }
52
+
48
53
  var schemas = new window.GOVUK.analyticsGa4.Schemas()
49
54
  var schema = schemas.mergeProperties(data, 'event_data')
50
55
  window.GOVUK.analyticsGa4.core.sendData(schema)
@@ -81,12 +81,12 @@ window.GOVUK.Modules = window.GOVUK.Modules || {};
81
81
 
82
82
  var text = data.text || event.target.textContent
83
83
  data.text = window.GOVUK.analyticsGa4.core.trackFunctions.removeLinesAndExtraSpaces(text)
84
- data.text = this.PIIRemover.stripPIIWithOverride(data.text, true, true)
84
+ data.text = window.GOVUK.analyticsGa4.core.trackFunctions.applyRedactionIfRequired(this.PIIRemover, element, data.text)
85
85
  if (!data.text && (element.querySelector('img') || element.querySelector('svg') || element.tagName === 'IMG' || element.closest('svg'))) {
86
86
  data.text = 'image'
87
87
  }
88
88
  var url = data.url || this.findLink(event.target).getAttribute('href')
89
- data.url = window.GOVUK.analyticsGa4.core.trackFunctions.removeCrossDomainParams(this.PIIRemover.stripPIIWithOverride(url, true, true))
89
+ data.url = window.GOVUK.analyticsGa4.core.trackFunctions.applyRedactionIfRequired(this.PIIRemover, element, window.GOVUK.analyticsGa4.core.trackFunctions.removeCrossDomainParams(url))
90
90
  data.link_domain = window.GOVUK.analyticsGa4.core.trackFunctions.populateLinkDomain(data.url)
91
91
  data.link_path_parts = window.GOVUK.analyticsGa4.core.trackFunctions.populateLinkPathParts(data.url)
92
92
  data.method = window.GOVUK.analyticsGa4.core.trackFunctions.getClickType(event)
@@ -16,7 +16,11 @@
16
16
  type: this.undefined,
17
17
  url: this.undefined,
18
18
  text: this.undefined,
19
- index: this.undefined,
19
+ index: {
20
+ index_link: this.undefined,
21
+ index_section: this.undefined,
22
+ index_section_count: this.undefined
23
+ },
20
24
  index_total: this.undefined,
21
25
  section: this.undefined,
22
26
  action: this.undefined,
@@ -24,7 +28,8 @@
24
28
  method: this.undefined,
25
29
  link_domain: this.undefined,
26
30
  link_path_parts: this.undefined,
27
- tool_name: this.undefined
31
+ tool_name: this.undefined,
32
+ percent_scrolled: this.undefined
28
33
  }
29
34
  }
30
35
  }
@@ -45,19 +50,60 @@
45
50
  }
46
51
  }
47
52
 
48
- // get attributes from the data attribute to send to GA
53
+ // merge data attributes data into the event schema
49
54
  // only allow it if it already exists in the schema
50
55
  Schemas.prototype.mergeProperties = function (data, eventAttribute) {
51
56
  var schema = this.eventSchema()
52
57
  schema.event = eventAttribute
58
+ // exceptions should be inserted without checking for sub parameters
59
+ // exceptions should only come from code, not directly from data attributes
60
+ // e.g. link_path_parts is generated by JS and contains potentially non-unique
61
+ // sub parameter names i.e. '1', '2'
62
+ var exceptions = ['link_path_parts']
63
+
53
64
  for (var property in data) {
54
- if (property in schema.event_data) {
55
- schema.event_data[property] = data[property]
65
+ // some passed data might be undefined, don't want it to overwrite e.g. the index sub parameters
66
+ if (data[property] !== undefined) {
67
+ if (exceptions.indexOf(property) >= 0 || !this.isAnObject(data[property])) {
68
+ schema.event_data = this.addToObject(schema.event_data, property, data[property])
69
+ } else {
70
+ // we check for one level of nesting in the data attributes data
71
+ // this check can be removed once nesting is removed from all data attributes
72
+ for (var subproperty in data[property]) {
73
+ schema.event_data = this.addToObject(schema.event_data, subproperty, data[property][subproperty])
74
+ }
75
+ }
56
76
  }
57
77
  }
58
78
  return schema
59
79
  }
60
80
 
81
+ // might be easier to check if it's not a string or a number?
82
+ Schemas.prototype.isAnObject = function (item) {
83
+ if (typeof item === 'object' && !Array.isArray(item) && item !== null) {
84
+ return true
85
+ }
86
+ }
87
+
88
+ // given an object and a key, insert a value into that object for that key
89
+ // we check for one level of nesting in the object
90
+ Schemas.prototype.addToObject = function (obj, key, value) {
91
+ if (key in obj) {
92
+ obj[key] = value
93
+ return obj
94
+ } else {
95
+ for (var property in obj) {
96
+ if (this.isAnObject(obj[property])) {
97
+ if (key in obj[property]) {
98
+ obj[property][key] = value
99
+ return obj
100
+ }
101
+ }
102
+ }
103
+ }
104
+ return obj
105
+ }
106
+
61
107
  GOVUK.analyticsGa4 = GOVUK.analyticsGa4 || {}
62
108
  GOVUK.analyticsGa4.Schemas = Schemas
63
109
 
@@ -0,0 +1,225 @@
1
+ window.GOVUK = window.GOVUK || {}
2
+ window.GOVUK.Modules = window.GOVUK.Modules || {};
3
+
4
+ (function (Modules) {
5
+ function Ga4ScrollTracker ($module) {
6
+ this.$module = $module
7
+ this.pageHeight = document.querySelector('body').clientHeight
8
+ this.trackedNodes = []
9
+ this.config = {
10
+ allowHeadingsInside: ['main'],
11
+ percentages: [20, 40, 60, 80, 100],
12
+ scrollTimeoutDelay: 20,
13
+ resizeTimeoutDelay: 100,
14
+ pageHeightTimeoutDelay: 500,
15
+ markerAttribute: 'data-ga4-scroll-marker'
16
+ }
17
+ }
18
+
19
+ Ga4ScrollTracker.prototype.init = function () {
20
+ var consentCookie = window.GOVUK.getConsentCookie()
21
+
22
+ if (consentCookie && consentCookie.settings) {
23
+ this.startModule()
24
+ } else {
25
+ this.startModule = this.startModule.bind(this)
26
+ window.addEventListener('cookie-consent', this.startModule)
27
+ }
28
+ }
29
+
30
+ Ga4ScrollTracker.prototype.startModule = function () {
31
+ if (window.GOVUK.analyticsGa4.vars.scrollTrackerStarted) {
32
+ return
33
+ }
34
+
35
+ this.trackType = this.$module.getAttribute('data-ga4-track-type')
36
+ window.GOVUK.analyticsGa4.vars.scrollTrackerStarted = true
37
+
38
+ if (this.trackType === 'headings') {
39
+ this.track = new Ga4ScrollTracker.Heading(this.config)
40
+ } else if (this.trackType === 'markers') {
41
+ this.config.trackMarkers = true
42
+ this.track = new Ga4ScrollTracker.Heading(this.config)
43
+ } else {
44
+ this.track = new Ga4ScrollTracker.Percentage(this.config)
45
+ }
46
+
47
+ this.getWindowDetails()
48
+ // if the URL has a hash we want to prevent tracking on initial page load
49
+ // until the browser jumps down the page, at which point a scroll event
50
+ // will happen and tracking will continue normally
51
+ var windowHash = window.location.hash
52
+ var dontTrackOnLoad = windowHash && document.getElementById(windowHash.substring(1))
53
+ if (!dontTrackOnLoad) {
54
+ this.trackVisibleNodes()
55
+ }
56
+
57
+ if (this.trackedNodes.length) {
58
+ // store event listener functions as variables so they can be removed if needed
59
+ this.scrollEvent = this.onScroll.bind(this)
60
+ window.addEventListener('scroll', this.scrollEvent)
61
+ this.resizeEvent = this.onResize.bind(this)
62
+ window.addEventListener('resize', this.resizeEvent)
63
+
64
+ // check if the page height changes e.g. accordion opened
65
+ this.interval = window.setInterval(function () {
66
+ var pageHeight = document.querySelector('body').clientHeight
67
+ if (pageHeight !== this.pageHeight) {
68
+ this.pageHeight = pageHeight
69
+ this.getWindowDetails()
70
+ this.trackVisibleNodes()
71
+ }
72
+ }.bind(this), this.config.pageHeightTimeoutDelay)
73
+ }
74
+ }
75
+
76
+ Ga4ScrollTracker.prototype.onScroll = function () {
77
+ clearTimeout(this.scrollTimeout)
78
+ this.scrollTimeout = setTimeout(function () {
79
+ this.trackVisibleNodes()
80
+ }.bind(this), this.config.scrollTimeoutDelay)
81
+ }
82
+
83
+ Ga4ScrollTracker.prototype.onResize = function () {
84
+ clearTimeout(this.resizeTimeout)
85
+ this.resizeTimeout = setTimeout(function () {
86
+ this.getWindowDetails()
87
+ this.trackVisibleNodes()
88
+ }.bind(this), this.config.resizeTimeoutDelay)
89
+ }
90
+
91
+ Ga4ScrollTracker.prototype.getWindowDetails = function () {
92
+ this.pageHeight = document.querySelector('body').clientHeight
93
+ this.windowHeight = window.innerHeight
94
+ this.trackedNodes = this.track.getTrackingNodes(this.trackedNodes)
95
+ }
96
+
97
+ Ga4ScrollTracker.prototype.trackVisibleNodes = function () {
98
+ var data = {
99
+ event_name: 'scroll',
100
+ action: 'scroll',
101
+ type: this.config.type
102
+ }
103
+ for (var i = 0; i < this.trackedNodes.length; i++) {
104
+ var node = this.trackedNodes[i]
105
+ if (this.isVisible(node.top, node.bottom) && !node.alreadySeen) {
106
+ node.alreadySeen = true
107
+ // we store whether a heading has been tracked or not on the heading
108
+ // because if headings appear/disappear (e.g. inside an accordion)
109
+ // the order changes, so we can't refer to the previous trackedNodes
110
+ // as we do with percentages
111
+ if (node.element) {
112
+ node.element.setAttribute('data-ga4-scrolltracker-already-seen', true)
113
+ }
114
+
115
+ data.type = node.eventData.type
116
+ // following will be undefined if tracking percentages
117
+ data.text = node.eventData.text
118
+ data.index = node.eventData.index
119
+ // following will be undefined if tracking headings
120
+ data.percent_scrolled = node.eventData.percent_scrolled
121
+
122
+ var schemas = new window.GOVUK.analyticsGa4.Schemas()
123
+ var schema = schemas.mergeProperties(data, 'event_data')
124
+ window.GOVUK.analyticsGa4.core.sendData(schema)
125
+ }
126
+ }
127
+ }
128
+
129
+ Ga4ScrollTracker.prototype.isVisible = function (top, bottom) {
130
+ var scroll = window.scrollY || document.documentElement.scrollTop // IE fallback
131
+ return scroll <= top && (scroll + this.windowHeight) >= bottom
132
+ }
133
+
134
+ Ga4ScrollTracker.Heading = function (config) {
135
+ this.config = config
136
+ }
137
+
138
+ Ga4ScrollTracker.Heading.prototype.getTrackingNodes = function () {
139
+ var headingsDetails = []
140
+ var headingsFound = this.findAllowedHeadings()
141
+ var totalHeadings = headingsFound.length
142
+
143
+ for (var i = 0; i < totalHeadings; i++) {
144
+ var heading = headingsFound[i]
145
+ var type = this.config.trackMarkers ? 'marker' : 'heading'
146
+ // only track headings that are visible i.e. not inside display: none
147
+ if (this.visible(heading)) {
148
+ var pos = heading.getBoundingClientRect()
149
+ headingsDetails.push({
150
+ element: heading,
151
+ alreadySeen: heading.getAttribute('data-ga4-scrolltracker-already-seen'),
152
+ top: pos.top + document.documentElement.scrollTop,
153
+ bottom: pos.bottom + document.documentElement.scrollTop,
154
+ eventData: {
155
+ type: type,
156
+ text: heading.textContent.replace(/\s+/g, ' ').trim(),
157
+ index: {
158
+ index_section: i + 1,
159
+ index_section_count: totalHeadings
160
+ }
161
+ }
162
+ })
163
+ }
164
+ }
165
+ return headingsDetails
166
+ }
167
+
168
+ // check heading is inside allowed elements, generally ignores everything outside of page content
169
+ Ga4ScrollTracker.Heading.prototype.findAllowedHeadings = function () {
170
+ var headingsFound = []
171
+ var headings = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']
172
+ if (this.config.trackMarkers) {
173
+ headings = ['[' + this.config.markerAttribute + ']']
174
+ }
175
+
176
+ // this is a loop that only happens once as we currently only have one
177
+ // allowed element for headings to be in - 'main'
178
+ for (var h = 0; h < this.config.allowHeadingsInside.length; h++) {
179
+ var insideElements = document.querySelectorAll(this.config.allowHeadingsInside[h])
180
+ for (var e = 0; e < insideElements.length; e++) {
181
+ var found = insideElements[e].querySelectorAll(headings)
182
+ for (var f = 0; f < found.length; f++) {
183
+ headingsFound.push(found[f])
184
+ }
185
+ }
186
+ }
187
+ return headingsFound
188
+ }
189
+
190
+ // this is bit more verbose than checking offsetParent !== null but more reliable for IE10+
191
+ Ga4ScrollTracker.Heading.prototype.visible = function (el) {
192
+ return !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length)
193
+ }
194
+
195
+ Ga4ScrollTracker.Percentage = function (config) {
196
+ this.config = config
197
+ }
198
+
199
+ Ga4ScrollTracker.Percentage.prototype.getTrackingNodes = function (trackedNodes) {
200
+ var body = document.body
201
+ var html = document.documentElement
202
+ var pageHeight = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight)
203
+
204
+ var percentDetails = []
205
+
206
+ for (var i = 0; i < this.config.percentages.length; i++) {
207
+ var percent = this.config.percentages[i]
208
+ // subtract 1 pixel to solve a bug where 100% can't be reached in some cases
209
+ var pos = ((pageHeight / 100) * percent) - 1
210
+ var alreadySeen = false
211
+ if (trackedNodes.length) {
212
+ alreadySeen = trackedNodes[i].alreadySeen
213
+ }
214
+ percentDetails.push({
215
+ alreadySeen: alreadySeen,
216
+ top: pos,
217
+ bottom: pos,
218
+ eventData: { type: 'percent', percent_scrolled: String(percent) }
219
+ })
220
+ }
221
+ return percentDetails
222
+ }
223
+
224
+ Modules.Ga4ScrollTracker = Ga4ScrollTracker
225
+ })(window.GOVUK.Modules)
@@ -8,11 +8,6 @@ var initFunction = function () {
8
8
  window.GOVUK.analyticsGa4.vars.internalDomains = []
9
9
  window.GOVUK.analyticsGa4.vars.internalDomains.push(window.GOVUK.analyticsGa4.core.trackFunctions.getHostname())
10
10
  window.GOVUK.analyticsGa4.core.trackFunctions.appendDomainsWithoutWWW(window.GOVUK.analyticsGa4.vars.internalDomains)
11
- var attachmentLinkData = [
12
- { key: 'data-module', value: 'ga4-link-tracker' },
13
- { key: 'data-ga4-track-links-only', value: '' },
14
- { key: 'data-ga4-link', value: JSON.stringify({ event_name: 'navigation', type: 'attachment' }) }]
15
- window.GOVUK.analyticsGa4.core.trackFunctions.addAttributesToElements('[data-ga4-attachment-link]', attachmentLinkData)
16
11
  window.GOVUK.analyticsGa4.core.load()
17
12
 
18
13
  var analyticsModules = window.GOVUK.analyticsGa4.analyticsModules
@@ -10,4 +10,5 @@
10
10
  //= require ./analytics-ga4/ga4-form-tracker
11
11
  //= require ./analytics-ga4/ga4-auto-tracker
12
12
  //= require ./analytics-ga4/ga4-smart-answer-results-tracker
13
+ //= require ./analytics-ga4/ga4-scroll-tracker
13
14
  //= require ./analytics-ga4/init-ga4
@@ -67,6 +67,13 @@ $thumbnail-icon-border-colour: govuk-colour("mid-grey", $legacy: "grey-3");
67
67
  &:last-of-type {
68
68
  margin-bottom: 0;
69
69
  }
70
+
71
+ .gem-c-attachment__attribute {
72
+ // From the Design System
73
+ // Automatic wrapping for unbreakable text (e.g. URLs)
74
+ word-wrap: break-word; // Fallback for older browsers only
75
+ overflow-wrap: break-word;
76
+ }
70
77
  }
71
78
 
72
79
  .gem-c-attachment__metadata--compact {
@@ -86,6 +86,10 @@
86
86
  padding-bottom: 0;
87
87
  margin-right: govuk-spacing(2);
88
88
  }
89
+
90
+ @include govuk-media-query($from: desktop) {
91
+ margin-right: govuk-spacing(4);
92
+ }
89
93
  }
90
94
 
91
95
  .gem-c-feedback__prompt-link {
@@ -138,6 +142,10 @@
138
142
  .gem-c-feedback__option-list-item {
139
143
  &:last-child {
140
144
  margin-left: govuk-spacing(2);
145
+
146
+ @include govuk-media-query($from: desktop) {
147
+ margin-left: govuk-spacing(4);
148
+ }
141
149
  }
142
150
  }
143
151
 
@@ -55,3 +55,44 @@
55
55
  }
56
56
  }
57
57
  }
58
+
59
+ // Add rtl table styling when `direction: "rtl"` is set
60
+ .govuk-govspeak.direction-rtl,
61
+ .gem-c-govspeak.direction-rtl {
62
+ table {
63
+ caption {
64
+ text-align: right;
65
+ }
66
+
67
+ th {
68
+ text-align: right;
69
+ }
70
+
71
+ th,
72
+ td {
73
+ padding: govuk-spacing(2) 0 govuk-spacing(2) govuk-spacing(4);
74
+ }
75
+ }
76
+ }
77
+
78
+ // Add rtl table styling when `.direction-rtl` is set on a parent element
79
+ // stylelint-disable max-nesting-depth
80
+ .direction-rtl {
81
+ .govspeak,
82
+ .gem-c-govspeak {
83
+ table {
84
+ caption {
85
+ text-align: right;
86
+ }
87
+
88
+ th {
89
+ text-align: right;
90
+ }
91
+
92
+ th,
93
+ td {
94
+ padding: govuk-spacing(2) 0 govuk-spacing(2) govuk-spacing(4);
95
+ }
96
+ }
97
+ }
98
+ }
@@ -43,13 +43,15 @@
43
43
  "HTML",
44
44
  class: "gem-c-attachment__attribute",
45
45
  )
46
+ data_attributes[:module] ? data_attributes[:module] << " ga4-link-tracker" : data_attributes[:module] = "ga4-link-tracker"
47
+ data_attributes[:ga4_link] = { "event_name": "navigation", "type": "attachment" }.to_json
46
48
  when "external"
47
49
  attributes << tag.span(
48
50
  attachment.url,
49
51
  class: "gem-c-attachment__attribute",
50
52
  )
51
53
  end
52
-
54
+
53
55
  %>
54
56
  <%= tag.section class: class_names(container_class_names) do %>
55
57
  <%= tag.div class: "gem-c-attachment__thumbnail" do %>
@@ -13,20 +13,16 @@
13
13
  link_classes << "govuk-link--no-underline" unless underline_links
14
14
 
15
15
  ga4_tracking ||= false
16
- ga4_data = nil
17
- if ga4_tracking
18
- ga4_data = {
16
+ ga4_data = {
19
17
  event_name: "navigation",
20
18
  type: "content",
21
19
  section: t("components.contents_list.contents", locale: :en) || ""
22
- }.to_json
23
- end
20
+ } if ga4_tracking
24
21
  local_assigns[:aria] ||= {}
25
22
  component_helper = GovukPublishingComponents::Presenters::ComponentWrapperHelper.new(local_assigns)
26
23
  component_helper.add_class("gem-c-contents-list #{brand_helper.brand_class}")
27
24
  component_helper.add_data_attribute({ module: "gem-track-click" })
28
25
  component_helper.add_data_attribute({ module: "ga4-link-tracker" }) if ga4_tracking
29
- component_helper.add_data_attribute({ ga4_link: ga4_data, ga4_track_links_only: "" }) if ga4_tracking
30
26
  component_helper.add_aria_attribute({ label: t("components.contents_list.contents") }) unless local_assigns[:aria][:label]
31
27
  component_helper.add_role("navigation")
32
28
  -%>
@@ -44,8 +40,12 @@
44
40
  <% contents.each.with_index(1) do |contents_item, position| %>
45
41
  <li class="<%= cl_helper.list_item_classes(contents_item, false) %>" <%= "aria-current=true" if contents_item[:active] %>>
46
42
  <% link_text = format_numbers ? cl_helper.wrap_numbers_with_spans(contents_item[:text]) : contents_item[:text]
47
- ga4_link_data = nil
48
- ga4_link_data = { "index": { "index_link": position }, "index_total": contents.length }.to_json if ga4_tracking
43
+ if ga4_tracking
44
+ ga4_data[:index] = {
45
+ "index_link": position,
46
+ }
47
+ ga4_data[:index_total] = contents.length
48
+ end
49
49
  %>
50
50
  <%= link_to_if !contents_item[:active], link_text, contents_item[:href],
51
51
  class: link_classes,
@@ -56,7 +56,7 @@
56
56
  track_options: {
57
57
  dimension29: contents_item[:text]
58
58
  },
59
- ga4_link: ga4_link_data
59
+ ga4_link: (ga4_tracking ? ga4_data.to_json : nil)
60
60
  }
61
61
  %>
62
62
 
@@ -23,7 +23,8 @@
23
23
  ga4_object = {
24
24
  event_name: "navigation",
25
25
  type: "content",
26
- section: "Top"
26
+ section: "Top",
27
+ action: "opened"
27
28
  }.to_json
28
29
  %>
29
30
  <%= content_tag :div, class: classes, data: { module: "gem-toggle metadata" } do %>
@@ -7,22 +7,30 @@
7
7
  panel_css_classes << "gem-c-tabs__panel--no-border" if panel_border == false
8
8
  panel_css_classes = panel_css_classes.join(" ")
9
9
 
10
+ as_links ||= false
10
11
  ga4_tracking ||= false
11
- data_module = "govuk-tabs"
12
- data_module << " ga4-event-tracker" if ga4_tracking
12
+
13
+ component_helper = GovukPublishingComponents::Presenters::ComponentWrapperHelper.new(local_assigns)
14
+ component_helper.add_class("govuk-tabs gem-c-tabs")
15
+ component_helper.add_data_attribute({ module: "govuk-tabs" }) unless as_links
16
+
17
+ if ga4_tracking
18
+ component_helper.add_data_attribute({ module: "ga4-event-tracker" }) unless as_links
19
+ component_helper.add_data_attribute({ module: "ga4-link-tracker" }) if as_links
20
+ end
13
21
  %>
14
22
  <% if tabs.count > 1 %>
15
- <div class="govuk-tabs gem-c-tabs" data-module="<%= data_module %>">
23
+ <%= tag.div(**component_helper.all_attributes) do %>
16
24
  <h2 class="govuk-tabs__title">
17
25
  <%= t("components.tabs.contents") %>
18
26
  </h2>
19
27
  <ul class="govuk-tabs__list">
20
28
  <% tabs.each_with_index do |tab, index| %>
21
- <li class="govuk-tabs__list-item">
29
+ <li class="govuk-tabs__list-item <%= "govuk-tabs__list-item--selected" if tab[:active] %>">
22
30
  <%
23
31
  tab[:tab_data_attributes] ||= {}
24
32
  if ga4_tracking
25
- tab[:tab_data_attributes][:ga4_event] = {
33
+ ga4_attributes = {
26
34
  event_name: "select_content",
27
35
  type: "tabs",
28
36
  text: tab[:label],
@@ -31,24 +39,32 @@
31
39
  index_section_count: tabs.length,
32
40
  },
33
41
  }
42
+ ga4_attributes[:event_name] = "navigation" if as_links
43
+ tab[:tab_data_attributes][:ga4_link] = ga4_attributes if as_links
44
+ tab[:tab_data_attributes][:ga4_event] = ga4_attributes unless as_links
34
45
  end
46
+
47
+ tab_link = "##{tab[:id]}"
48
+ tab_link = tab[:href] if as_links
35
49
  %>
36
50
  <%= link_to(tab[:label],
37
- "##{tab[:id]}",
51
+ tab_link,
38
52
  class: "govuk-tabs__tab",
39
53
  data: tab[:tab_data_attributes]) %>
40
54
  </li>
41
55
  <% end %>
42
56
  </ul>
43
- <% tabs.each do |tab| %>
44
- <section class="<%= panel_css_classes %>" id="<%= tab[:id] %>">
45
- <% if tab[:title] %>
46
- <h2 class="govuk-heading-l"><%= tab[:title] %></h2>
47
- <% end %>
48
- <%= tab[:content] %>
49
- </section>
57
+ <% unless as_links %>
58
+ <% tabs.each do |tab| %>
59
+ <section class="<%= panel_css_classes %>" id="<%= tab[:id] %>">
60
+ <% if tab[:title] %>
61
+ <h2 class="govuk-heading-l"><%= tab[:title] %></h2>
62
+ <% end %>
63
+ <%= tab[:content] %>
64
+ </section>
65
+ <% end %>
50
66
  <% end %>
51
- </div>
67
+ <% end %>
52
68
  <% end %>
53
69
  <% if tabs.count == 1 %>
54
70
  <section id="<%= tabs[0][:id] %>">