govuk_publishing_components 56.2.2 → 56.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 973a94df7d794d748953338a89e1eb7d53a6645d8b162de2f1abd542cc775223
4
- data.tar.gz: 21a1fb1a11c4953eb0d92502ec839ade24ad4edfee9a6873878c7fbdb3edbb4a
3
+ metadata.gz: 67d7750a4af75be256720a227401885c736d2af67113acfa16889fc5dc599a5d
4
+ data.tar.gz: a312eca2e3699de90c89bf5e256a09bca8eedffeb3e00657738ec21fb8fa3059
5
5
  SHA512:
6
- metadata.gz: b3dad62280a3c2b0b2184d3aba708121c9e7c483b86c428d3490ae3682ae8ebb8e3ce60305e036dc884bb94d2f7830c2674c5d69e2d03f2bff2ca01e44e82af0
7
- data.tar.gz: e3274d4870271eb6ae390ba7ab7daed3f1171bdcd3c8b9e03c90020a093eadaa41eeebac43397ad2f20cf82ead618275593eddd4c233c63a79bce9487cbefd94
6
+ metadata.gz: 1ae3113f4320e8ce93b70ec08a9b9581101d59c3eddff0444ec1ec42b551cf14f593e085fb919c7a70d634e47aeb49ec7f463c50195c40e56fe9a42636c864db
7
+ data.tar.gz: ebc820fe837ad54b2539fbe6c9baed82438c4ad254b9680d1c7716b2d34c6ffcbcfac49f516b5da883f74084116d72c70621b689af5a8adff314d4a9f63f2e04
@@ -364,7 +364,7 @@ window.GOVUK.analyticsGa4 = window.GOVUK.analyticsGa4 || {};
364
364
  ecommerceSchema.search_results.ecommerce.items.push({
365
365
  item_id: target.getAttribute('data-ga4-ecommerce-path'),
366
366
  item_content_id: target.getAttribute('data-ga4-ecommerce-content-id') || undefined,
367
- item_name: target.textContent,
367
+ item_name: target.getAttribute('data-ga4-ecommerce-item-name') || target.textContent,
368
368
  item_list_name: listTitle,
369
369
  index: window.GOVUK.analyticsGa4.core.ecommerceHelperFunctions.getIndex(target, startPosition)
370
370
  })
@@ -0,0 +1,163 @@
1
+ ;(function (global) {
2
+ 'use strict'
3
+
4
+ const GOVUK = global.GOVUK || {}
5
+ GOVUK.analyticsGa4 = GOVUK.analyticsGa4 || {}
6
+
7
+ GOVUK.analyticsGa4.Ga4FinderTracker = {
8
+ // Called when the search results updates. Takes the event target, and a string containing the type of change and element type. Creates the GTM schema and pushes it.
9
+ // changeEventMetadata is a string referring to the type of form change and the element type that triggered it. For example 'update-filter checkbox'.
10
+ trackChangeEvent: function (event) {
11
+ const eventTarget = event.target
12
+ let changeEventMetadata = eventTarget.closest('[data-ga4-change-category]')
13
+
14
+ if (!changeEventMetadata) {
15
+ return
16
+ }
17
+
18
+ changeEventMetadata = changeEventMetadata.dataset.ga4ChangeCategory
19
+ changeEventMetadata = changeEventMetadata.split(' ')
20
+
21
+ const filterParent = eventTarget.closest('[data-ga4-filter-parent]')
22
+ const section = eventTarget.closest('[data-ga4-section]')
23
+ const changeType = changeEventMetadata[0]
24
+ const elementType = changeEventMetadata[1]
25
+
26
+ if ((!changeType || !elementType) && changeType !== 'clear-all-filters') {
27
+ console.warn('GA4 Finder tracker incorrectly configured for element: ' + eventTarget)
28
+ return
29
+ }
30
+
31
+ let data = {}
32
+
33
+ const filterType = eventTarget.closest('[data-ga4-filter-type]')
34
+
35
+ data.type = filterType ? filterType.dataset.ga4FilterType : 'finder'
36
+ data.event_name = 'select_content'
37
+
38
+ const elementInfo = this.getElementInfo(event, elementType)
39
+ if (!elementInfo) {
40
+ return
41
+ }
42
+ const elementValue = elementInfo.elementValue
43
+ data.text = elementValue
44
+ const wasFilterRemoved = elementInfo.wasFilterRemoved
45
+ data = this.setSchemaBasedOnChangeType(data, elementValue, elementType, wasFilterRemoved, changeType, section, filterParent)
46
+
47
+ window.GOVUK.analyticsGa4.core.applySchemaAndSendData(data, 'event_data')
48
+ },
49
+
50
+ // Grabs the value from the eventTarget. Checks if the filter was removed if the eventTarget is unchecked, set back to default, or has its user input removed. Returns the results as an object.
51
+ getElementInfo: function (event, elementType) {
52
+ const supportedElements = Object.assign(Object.assign({}, this.defaultSupportedElements), this.extraSupportedElements || {})
53
+
54
+ return supportedElements[elementType] ? supportedElements[elementType](event.target, event) : { elementValue: '', wasFilterRemoved: false }
55
+ },
56
+
57
+ // Takes the GTM schema, the event target value, the event target HTML type, whether the filter was removed, the type of filter change it was, and the parent section heading. Populates the GTM object based on these values.
58
+ setSchemaBasedOnChangeType: function (schema, elementValue, elementType, wasFilterRemoved, changeType, section, filterParent) {
59
+ switch (changeType) {
60
+ case 'update-filter': {
61
+ if (section) {
62
+ schema.section = section.getAttribute('data-ga4-section')
63
+ }
64
+
65
+ const index = this.getSectionIndex(filterParent)
66
+ if (wasFilterRemoved) {
67
+ schema.action = 'remove'
68
+ schema.text = elementType === 'text' ? undefined : elementValue
69
+ } else {
70
+ schema.action = elementType === 'text' ? 'search' : 'select'
71
+ }
72
+ schema.index_link = index.index_link || undefined
73
+ schema.index_section = index.index_section || undefined
74
+ schema.index_section_count = index.index_section_count || undefined
75
+ break
76
+ }
77
+ case 'update-keyword':
78
+ schema.event_name = 'search'
79
+ schema.url = window.location.pathname
80
+ schema.section = 'Search'
81
+ schema.action = 'search'
82
+ schema.text = GOVUK.analyticsGa4.core.trackFunctions.standardiseSearchTerm(schema.text)
83
+ break
84
+
85
+ case 'clear-all-filters':
86
+ schema.action = 'remove'
87
+ schema.text = 'Clear all filters'
88
+ break
89
+
90
+ case 'update-sort':
91
+ schema.action = 'sort'
92
+ schema.section = 'Sort by'
93
+ break
94
+ }
95
+ return schema
96
+ },
97
+
98
+ // Takes a filter section's div, and grabs the index value from it.
99
+ getSectionIndex: function (sectionElement) {
100
+ try {
101
+ let index = sectionElement.getAttribute('data-ga4-index')
102
+ index = JSON.parse(index)
103
+ return index
104
+ } catch (e) {
105
+ console.error('GA4 configuration error: ' + e.message, window.location)
106
+ }
107
+ },
108
+
109
+ defaultSupportedElements: {
110
+ checkbox: (eventTarget) => {
111
+ const checkboxId = eventTarget.id
112
+
113
+ return {
114
+ elementValue: document.querySelector("label[for='" + checkboxId + "']").textContent,
115
+ wasFilterRemoved: !eventTarget.checked
116
+ }
117
+ },
118
+ radio: (eventTarget) => {
119
+ const radioId = eventTarget.id
120
+
121
+ // The "value" we need for a radio is the label text that the user sees beside the checkbox.
122
+ const elementValue = document.querySelector("label[for='" + radioId + "']").textContent
123
+ const defaultValue = eventTarget.closest('[data-ga4-section]').querySelector('input[type=radio]:first-of-type')
124
+
125
+ return {
126
+ elementValue: elementValue,
127
+ wasFilterRemoved: eventTarget.id === defaultValue.id
128
+ }
129
+ },
130
+ select: (eventTarget) => {
131
+ // The value of a <select> is the value attribute of the selected <option>, which is a hyphenated key. We need to grab the human readable label instead for tracking.
132
+ const elementValue = eventTarget.querySelector("option[value='" + eventTarget.value + "']").textContent
133
+ const defaultValue = eventTarget.querySelector('option:first-of-type').textContent
134
+
135
+ return {
136
+ elementValue: elementValue,
137
+ wasFilterRemoved: elementValue === defaultValue
138
+ }
139
+ },
140
+ text: (eventTarget) => ({
141
+ elementValue: eventTarget.value,
142
+ wasFilterRemoved: eventTarget.value === ''
143
+ }),
144
+ date: (eventTarget) => {
145
+ // The GOV.UK Design System date input consists of three grouped but separate fields (day,
146
+ // month, year). We want to fire a single event when all three fields are filled in to
147
+ // avoid firing excessive events.
148
+ const inputs = [...eventTarget.closest('.govuk-date-input').querySelectorAll('input')]
149
+ const allInputsSet = inputs.every(input => input.value)
150
+ const noInputsSet = inputs.every(input => !input.value)
151
+
152
+ if (!allInputsSet && !noInputsSet) return
153
+
154
+ return {
155
+ elementValue: allInputsSet ? inputs.map(input => input.value).join('/') : '',
156
+ wasFilterRemoved: noInputsSet
157
+ }
158
+ }
159
+ }
160
+ }
161
+
162
+ global.GOVUK = GOVUK
163
+ })(window)
@@ -9,6 +9,7 @@
9
9
  //= require ./analytics-ga4/ga4-link-tracker
10
10
  //= require ./analytics-ga4/ga4-event-tracker
11
11
  //= require ./analytics-ga4/ga4-ecommerce-tracker
12
+ //= require ./analytics-ga4/ga4-finder-tracker
12
13
  //= require ./analytics-ga4/ga4-form-tracker
13
14
  //= require ./analytics-ga4/ga4-auto-tracker
14
15
  //= require ./analytics-ga4/ga4-smart-answer-results-tracker
@@ -22,10 +22,8 @@
22
22
  (function () {
23
23
  'use strict';
24
24
 
25
- function floor(x) {
26
- return Math.floor(x);
27
- }
28
25
  var max = Math.max;
26
+ var floor = Math.floor;
29
27
  var round = Math.round;
30
28
  /**
31
29
  * Clamp a number so that it is never less than 0
@@ -263,6 +261,7 @@
263
261
  // PostBeaconDisabled: 88, // Not used
264
262
  PostBeaconSendFailed: 89,
265
263
  PostBeaconCSPViolation: 90,
264
+ PostBeaconCollector: 91,
266
265
  };
267
266
  var Logger = /** @class */ (function () {
268
267
  function Logger() {
@@ -337,7 +336,7 @@
337
336
  return str;
338
337
  }
339
338
 
340
- var VERSION = "4.0.32";
339
+ var VERSION = "4.1.1";
341
340
  /**
342
341
  * Returns the version of the script as a float to be stored in legacy systems that do not support
343
342
  * string versions.
@@ -390,6 +389,7 @@
390
389
  this.sendRetries = 0;
391
390
  this.maxMeasureTimeout = 0;
392
391
  this.flags = 0;
392
+ this.metricCollectors = {};
393
393
  this.onBeforeSendCbs = [];
394
394
  this.startTime = opts.startTime || getZeroTime();
395
395
  this.config = opts.config;
@@ -397,7 +397,6 @@
397
397
  this.customerId = opts.customerId;
398
398
  this.sessionId = opts.sessionId;
399
399
  this.pageId = opts.pageId;
400
- this.metricData = {};
401
400
  this.maxMeasureTimeout = window.setTimeout(function () {
402
401
  _this.logger.logEvent(LogEvent.PostBeaconTimeoutReached);
403
402
  _this.stopRecording();
@@ -440,19 +439,12 @@
440
439
  this.isRecording = false;
441
440
  this.logger.logEvent(LogEvent.PostBeaconStopRecording);
442
441
  };
443
- Beacon.prototype.setMetricData = function (metric, data) {
444
- if (!this.isRecording) {
445
- this.logger.logEvent(LogEvent.PostBeaconMetricRejected, [metric]);
446
- return;
447
- }
448
- this.metricData[metric] = data;
442
+ Beacon.prototype.addCollector = function (metric, collector) {
443
+ this.metricCollectors[metric] = collector;
449
444
  };
450
445
  Beacon.prototype.addFlag = function (flag) {
451
446
  this.flags = addFlag(this.flags, flag);
452
447
  };
453
- Beacon.prototype.hasMetricData = function () {
454
- return Object.keys(this.metricData).length > 0;
455
- };
456
448
  Beacon.prototype.beaconUrl = function () {
457
449
  return this.config.beaconUrlV2;
458
450
  };
@@ -468,7 +460,16 @@
468
460
  if (!this.isBeingSampled()) {
469
461
  return;
470
462
  }
471
- if (!this.hasMetricData() && !this.config.allowEmptyPostBeacon) {
463
+ var collectionStart = now();
464
+ var metricData = {};
465
+ for (var metric in this.metricCollectors) {
466
+ var data = this.metricCollectors[metric]();
467
+ this.logger.logEvent(LogEvent.PostBeaconCollector, [metric, !!data]);
468
+ if (data) {
469
+ metricData[metric] = data;
470
+ }
471
+ }
472
+ if (!Object.keys(metricData).length && !this.config.allowEmptyPostBeacon) {
472
473
  // TODO: This is only required while the new beacon is supplementary. Once it's the primary
473
474
  // beacon, we should send it regardless of how much metric data it has.
474
475
  this.logger.logEvent(LogEvent.PostBeaconCancelled);
@@ -485,11 +486,12 @@
485
486
  customerId: this.customerId,
486
487
  flags: this.flags,
487
488
  measureDuration: msSincePageInit(),
489
+ collectionDuration: now() - collectionStart,
488
490
  pageId: this.pageId,
489
491
  scriptVersion: VERSION,
490
492
  sessionId: this.sessionId,
491
493
  startTime: this.startTime,
492
- }, this.metricData);
494
+ }, metricData);
493
495
  try {
494
496
  if (sendBeacon(beaconUrl, JSON.stringify(payload))) {
495
497
  this.isSent = true;
@@ -505,6 +507,14 @@
505
507
  };
506
508
  return Beacon;
507
509
  }());
510
+ var BeaconMetricKey;
511
+ (function (BeaconMetricKey) {
512
+ BeaconMetricKey["CLS"] = "cls";
513
+ BeaconMetricKey["INP"] = "inp";
514
+ BeaconMetricKey["LCP"] = "lcp";
515
+ BeaconMetricKey["LoAF"] = "loaf";
516
+ BeaconMetricKey["NavigationTiming"] = "navigationTiming";
517
+ })(BeaconMetricKey || (BeaconMetricKey = {}));
508
518
 
509
519
  function onPageLoad(callback) {
510
520
  if (document.readyState === "complete") {
@@ -631,36 +641,36 @@
631
641
  try {
632
642
  if (selector &&
633
643
  (node.nodeType === 9 || selector.length > MAX_SELECTOR_LENGTH || !node.parentNode)) {
634
- // Final selector.
635
- return selector;
636
- }
637
- var el = node;
638
- // Our first preference is to use the data-sctrack attribute from anywhere in the tree
639
- var trackId = getClosestScTrackAttribute(el);
640
- if (trackId) {
641
- return trackId;
642
- }
643
- if (el.id) {
644
- // Once we've found an element with ID we return the selector.
645
- return "#" + el.id + (selector ? ">" + selector : "");
646
- }
647
- else if (el) {
648
- // Otherwise attempt to get parent elements recursively
649
- var name_1 = el.nodeType === 1 ? el.nodeName.toLowerCase() : el.nodeName.toUpperCase();
650
- var classes = el.className ? "." + el.className.replace(/\s+/g, ".") : "";
651
- // Remove classes until the selector is short enough
652
- while ((name_1 + classes).length > MAX_SELECTOR_LENGTH) {
653
- classes = classes.split(".").slice(0, -1).join(".");
654
- }
655
- var currentSelector = name_1 + classes + (selector ? ">" + selector : "");
656
- if (el.parentNode) {
657
- var selectorWithParent = getNodeSelector(el.parentNode, currentSelector);
658
- if (selectorWithParent.length < MAX_SELECTOR_LENGTH) {
659
- return selectorWithParent;
660
- }
644
+ // Final selector.
645
+ return selector;
646
+ }
647
+ var el = node;
648
+ // Our first preference is to use the data-sctrack attribute from anywhere in the tree
649
+ var trackId = getClosestScTrackAttribute(el);
650
+ if (trackId) {
651
+ return trackId;
652
+ }
653
+ if (el.id) {
654
+ // Once we've found an element with ID we return the selector.
655
+ return "#" + el.id + (selector ? ">" + selector : "");
656
+ }
657
+ else if (el) {
658
+ // Otherwise attempt to get parent elements recursively
659
+ var name_1 = el.nodeType === 1 ? el.nodeName.toLowerCase() : el.nodeName.toUpperCase();
660
+ var classes = el.className ? "." + el.className.replace(/\s+/g, ".") : "";
661
+ // Remove classes until the selector is short enough
662
+ while ((name_1 + classes).length > MAX_SELECTOR_LENGTH) {
663
+ classes = classes.split(".").slice(0, -1).join(".");
664
+ }
665
+ var currentSelector = name_1 + classes + (selector ? ">" + selector : "");
666
+ if (el.parentNode) {
667
+ var selectorWithParent = getNodeSelector(el.parentNode, currentSelector);
668
+ if (selectorWithParent.length < MAX_SELECTOR_LENGTH) {
669
+ return selectorWithParent;
661
670
  }
662
- return currentSelector;
663
671
  }
672
+ return currentSelector;
673
+ }
664
674
  }
665
675
  catch (error) {
666
676
  // Do nothing.
@@ -680,13 +690,9 @@
680
690
  */
681
691
  function getTrackingParams() {
682
692
  var trackingParams = {};
683
- if (location.search && URLSearchParams) {
693
+ if (location.search && typeof URLSearchParams === "function") {
684
694
  var p = new URLSearchParams(location.search);
685
- for (
686
- var _i = 0, KNOWN_TRACKING_PARAMS_1 = KNOWN_TRACKING_PARAMS;
687
- _i < KNOWN_TRACKING_PARAMS_1.length;
688
- _i++
689
- ) {
695
+ for (var _i = 0, KNOWN_TRACKING_PARAMS_1 = KNOWN_TRACKING_PARAMS; _i < KNOWN_TRACKING_PARAMS_1.length; _i++) {
690
696
  var key = KNOWN_TRACKING_PARAMS_1[_i];
691
697
  var value = p.get(key);
692
698
  if (value) {
@@ -703,7 +709,7 @@
703
709
  var largestEntry;
704
710
  var maximumSessionValue = 0;
705
711
  var MAX_CLS_SOURCES = 50;
706
- function processEntry$2(entry) {
712
+ function processEntry$3(entry) {
707
713
  if (!entry.hadRecentInput) {
708
714
  var firstEntry = sessionEntries[0];
709
715
  var latestEntry = sessionEntries[sessionEntries.length - 1];
@@ -736,13 +742,13 @@
736
742
  maximumSessionValue = max(maximumSessionValue, sessionValue);
737
743
  }
738
744
  }
739
- function reset$2() {
745
+ function reset$3() {
740
746
  sessionValue = 0;
741
747
  sessionEntries = [];
742
748
  maximumSessionValue = 0;
743
749
  largestEntry = undefined;
744
750
  }
745
- function getData$2() {
751
+ function getData$3() {
746
752
  return {
747
753
  value: maximumSessionValue,
748
754
  startTime: sessionEntries[0] ? processTimeMetric(sessionEntries[0].startTime) : null,
@@ -756,12 +762,121 @@
756
762
  };
757
763
  }
758
764
 
765
+ /******************************************************************************
766
+ Copyright (c) Microsoft Corporation.
767
+
768
+ Permission to use, copy, modify, and/or distribute this software for any
769
+ purpose with or without fee is hereby granted.
770
+
771
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
772
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
773
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
774
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
775
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
776
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
777
+ PERFORMANCE OF THIS SOFTWARE.
778
+ ***************************************************************************** */
779
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
780
+
781
+
782
+ var __assign = function() {
783
+ __assign = Object.assign || function __assign(t) {
784
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
785
+ s = arguments[i];
786
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
787
+ }
788
+ return t;
789
+ };
790
+ return __assign.apply(this, arguments);
791
+ };
792
+
793
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
794
+ var e = new Error(message);
795
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
796
+ };
797
+
798
+ var MAX_LOAF_ENTRIES = 50;
799
+ var MAX_LOAF_SCRIPTS = 50;
800
+ var entries = [];
801
+ function processEntry$2(entry) {
802
+ entries.push(entry);
803
+ }
804
+ function reset$2() {
805
+ entries = [];
806
+ }
807
+ function getEntries$1() {
808
+ return entries;
809
+ }
810
+ function getData$2() {
811
+ var summarizedEntries = [];
812
+ var totalDuration = 0;
813
+ var totalBlockingDuration = 0;
814
+ var totalStyleAndLayoutDuration = 0;
815
+ var totalWorkDuration = 0;
816
+ entries.forEach(function (entry) {
817
+ var startTime = entry.startTime, blockingDuration = entry.blockingDuration, duration = entry.duration, renderStart = entry.renderStart, styleAndLayoutStart = entry.styleAndLayoutStart;
818
+ totalDuration += duration;
819
+ totalBlockingDuration += blockingDuration;
820
+ totalStyleAndLayoutDuration += styleAndLayoutStart
821
+ ? clamp(startTime + duration - styleAndLayoutStart)
822
+ : 0;
823
+ totalWorkDuration += renderStart ? renderStart - startTime : duration;
824
+ summarizedEntries.push({
825
+ startTime: floor(startTime),
826
+ duration: floor(duration),
827
+ renderStart: floor(renderStart),
828
+ styleAndLayoutStart: floor(styleAndLayoutStart),
829
+ blockingDuration: floor(blockingDuration),
830
+ });
831
+ });
832
+ return {
833
+ totalBlockingDuration: floor(totalBlockingDuration),
834
+ totalDuration: floor(totalDuration),
835
+ totalEntries: entries.length,
836
+ totalStyleAndLayoutDuration: floor(totalStyleAndLayoutDuration),
837
+ totalWorkDuration: floor(totalWorkDuration),
838
+ entries: summarizedEntries.slice(0, MAX_LOAF_ENTRIES),
839
+ scripts: summarizeLoAFScripts(entries.flatMap(function (entry) { return entry.scripts; })).slice(0, MAX_LOAF_SCRIPTS),
840
+ };
841
+ }
842
+ function summarizeLoAFScripts(scripts) {
843
+ var summary = {};
844
+ scripts.forEach(function (script) {
845
+ var key = script.invoker + ":" + script.sourceURL + ":" + script.sourceFunctionName;
846
+ if (!summary[key]) {
847
+ summary[key] = {
848
+ sourceUrl: script.sourceURL,
849
+ sourceFunctionName: script.sourceFunctionName,
850
+ timings: [],
851
+ totalEntries: 0,
852
+ totalDuration: 0,
853
+ totalPauseDuration: 0,
854
+ totalForcedStyleAndLayoutDuration: 0,
855
+ invoker: script.invoker,
856
+ inpPhase: script.inpPhase,
857
+ };
858
+ }
859
+ summary[key].totalEntries++;
860
+ summary[key].totalDuration += script.duration;
861
+ summary[key].totalPauseDuration += script.pauseDuration;
862
+ summary[key].totalForcedStyleAndLayoutDuration += script.forcedStyleAndLayoutDuration;
863
+ summary[key].timings.push([floor(script.startTime), floor(script.duration)]);
864
+ });
865
+ return Object.values(summary).map(function (script) { return (__assign(__assign({}, script), { totalDuration: floor(script.totalDuration), totalPauseDuration: floor(script.totalPauseDuration), totalForcedStyleAndLayoutDuration: floor(script.totalForcedStyleAndLayoutDuration) })); });
866
+ }
867
+
759
868
  /**
760
869
  * This implementation is based on the web-vitals implementation, however it is stripped back to the
761
870
  * bare minimum required to measure just the INP value and does not store the actual event entries.
762
871
  */
763
872
  // The maximum number of interactions to store
764
873
  var MAX_INTERACTIONS = 10;
874
+ var INPPhase;
875
+ (function (INPPhase) {
876
+ INPPhase["InputDelay"] = "ID";
877
+ INPPhase["ProcessingTime"] = "PT";
878
+ INPPhase["PresentationDelay"] = "PD";
879
+ })(INPPhase || (INPPhase = {}));
765
880
  // A list of the slowest interactions
766
881
  var slowestEntries = [];
767
882
  // A map of the slowest interactions by ID
@@ -838,21 +953,51 @@
838
953
  if (!interaction) {
839
954
  return undefined;
840
955
  }
956
+ var duration = interaction.duration, startTime = interaction.startTime, processingStart = interaction.processingStart;
957
+ var inpScripts = getEntries$1()
958
+ .flatMap(function (entry) { return entry.scripts; })
959
+ // Only include scripts that started during the interaction
960
+ .filter(function (script) {
961
+ return script.startTime + script.duration >= startTime && script.startTime <= startTime + duration;
962
+ })
963
+ .map(function (_script) {
964
+ var script = JSON.parse(JSON.stringify(_script));
965
+ // Clamp the script duration to the time of the interaction
966
+ script.duration = script.startTime + script.duration - max(startTime, script.startTime);
967
+ script.inpPhase = getINPPhase(script, interaction);
968
+ return script;
969
+ });
970
+ var loafScripts = summarizeLoAFScripts(inpScripts);
841
971
  return {
842
972
  value: interaction.duration,
843
- startTime: processTimeMetric(interaction.startTime),
973
+ startTime: processTimeMetric(startTime),
974
+ duration: interaction.duration,
844
975
  subParts: {
845
- inputDelay: clamp(floor(interaction.processingStart - interaction.startTime)),
976
+ inputDelay: clamp(floor(processingStart - startTime)),
977
+ processingStart: processTimeMetric(processingStart),
978
+ processingEnd: processTimeMetric(interaction.processingEnd),
846
979
  processingTime: clamp(floor(interaction.processingTime)),
847
- presentationDelay: clamp(floor(interaction.startTime + interaction.duration - interaction.processingEnd)),
980
+ presentationDelay: clamp(floor(startTime + interaction.duration - interaction.processingEnd)),
848
981
  },
849
982
  attribution: {
850
983
  eventType: interaction.name,
851
984
  elementSelector: interaction.selector || null,
852
985
  elementType: ((_a = interaction.target) === null || _a === void 0 ? void 0 : _a.nodeName) || null,
986
+ loafScripts: loafScripts,
853
987
  },
854
988
  };
855
989
  }
990
+ function getINPPhase(script, interaction) {
991
+ var processingStart = interaction.processingStart, processingTime = interaction.processingTime, startTime = interaction.startTime;
992
+ var inputDelay = processingStart - startTime;
993
+ if (script.startTime < startTime + inputDelay) {
994
+ return INPPhase.InputDelay;
995
+ }
996
+ else if (script.startTime >= startTime + inputDelay + processingTime) {
997
+ return INPPhase.PresentationDelay;
998
+ }
999
+ return INPPhase.ProcessingTime;
1000
+ }
856
1001
  function getInteractionCount() {
857
1002
  if ("interactionCount" in performance) {
858
1003
  return performance.interactionCount;
@@ -1103,24 +1248,29 @@
1103
1248
  observe("longtask", processAndLogEntry);
1104
1249
  observe("element", processAndLogEntry);
1105
1250
  observe("paint", processAndLogEntry);
1106
- observe("largest-contentful-paint", function (entry) {
1251
+ if (observe("largest-contentful-paint", function (entry) {
1107
1252
  // Process the LCP entry for the legacy beacon
1108
1253
  processAndLogEntry(entry);
1109
1254
  // Process the LCP entry for the new beacon
1110
1255
  processEntry(entry);
1111
- beacon.setMetricData("lcp", getData());
1112
- });
1113
- observe("layout-shift", function (entry) {
1256
+ })) {
1257
+ beacon.addCollector(BeaconMetricKey.LCP, getData);
1258
+ }
1259
+ if (observe("layout-shift", function (entry) {
1260
+ processEntry$3(entry);
1261
+ logEntry(entry);
1262
+ })) {
1263
+ beacon.addCollector(BeaconMetricKey.CLS, getData$3);
1264
+ }
1265
+ if (observe("long-animation-frame", function (entry) {
1114
1266
  processEntry$2(entry);
1115
- beacon.setMetricData("cls", getData$2());
1116
1267
  logEntry(entry);
1117
- });
1268
+ })) {
1269
+ beacon.addCollector(BeaconMetricKey.LoAF, getData$2);
1270
+ }
1118
1271
  var handleINPEntry_1 = function (entry) {
1119
1272
  processEntry$1(entry);
1120
- var data = getData$1();
1121
- if (data) {
1122
- beacon.setMetricData("inp", data);
1123
- }
1273
+ logEntry(entry);
1124
1274
  };
1125
1275
  observe("first-input", function (entry) {
1126
1276
  logEntry(entry);
@@ -1134,7 +1284,7 @@
1134
1284
  // TODO: Set durationThreshold to 40 once performance.interactionCount is widely supported.
1135
1285
  // Right now we have to count every event to get the total interaction count so that we can
1136
1286
  // estimate a high percentile value for INP.
1137
- observe("event", function (entry) {
1287
+ if (observe("event", function (entry) {
1138
1288
  handleINPEntry_1(entry);
1139
1289
  // It's useful to log the interactionId, but it is not serialised by default. Annoyingly, we
1140
1290
  // need to manually serialize our own object with the keys we want.
@@ -1147,7 +1297,9 @@
1147
1297
  processingStart: entry.processingStart,
1148
1298
  processingEnd: entry.processingEnd,
1149
1299
  });
1150
- }, { durationThreshold: 0 });
1300
+ }, { durationThreshold: 0 })) {
1301
+ beacon.addCollector(BeaconMetricKey.INP, getData$1);
1302
+ }
1151
1303
  }
1152
1304
  catch (e) {
1153
1305
  logger.logEvent(LogEvent.PerformanceObserverError, [e]);
@@ -1571,7 +1723,7 @@
1571
1723
  if (!("LayoutShift" in self)) {
1572
1724
  return undefined;
1573
1725
  }
1574
- var clsData = getData$2();
1726
+ var clsData = getData$3();
1575
1727
  return clsData.value.toFixed(6);
1576
1728
  }
1577
1729
  // Return the median value from an array of integers.
@@ -1714,8 +1866,9 @@
1714
1866
  gSyncId = createSyncId();
1715
1867
  gUid = refreshUniqueId(gSyncId);
1716
1868
  reset();
1717
- reset$2();
1869
+ reset$3();
1718
1870
  reset$1();
1871
+ reset$2();
1719
1872
  nErrors = 0;
1720
1873
  gFirstInputDelay = undefined;
1721
1874
  beacon = initPostBeacon();
@@ -2121,7 +2274,8 @@
2121
2274
  curleft += el.offsetLeft;
2122
2275
  curtop += el.offsetTop;
2123
2276
  el = el.offsetParent;
2124
- } catch (e) {
2277
+ }
2278
+ catch (e) {
2125
2279
  // If we get an exception, just return the current values.
2126
2280
  return [curleft, curtop];
2127
2281
  }
@@ -2199,10 +2353,7 @@
2199
2353
  // Store any tracking parameters as custom data
2200
2354
  var trackingParams = getTrackingParams();
2201
2355
  for (var key in trackingParams) {
2202
- logger.logEvent(LogEvent.TrackingParamAdded, [
2203
- key,
2204
- trackingParams[key],
2205
- ]);
2356
+ logger.logEvent(LogEvent.TrackingParamAdded, [key, trackingParams[key]]);
2206
2357
  addCustomDataValue("_" + key, trackingParams[key]);
2207
2358
  }
2208
2359
  var sIx = "";
@@ -3,15 +3,18 @@
3
3
  @import "mixins/prefixed-transform";
4
4
  @import "mixins/grid-helper";
5
5
 
6
+ $pale-blue-colour: #d2e2f1;
7
+
8
+ $chevron-breakpoint: 360px;
6
9
  $chevron-indent-spacing: 7px;
7
- $black-bar-height: 50px;
8
- $search-toggle-button-height: $black-bar-height;
10
+
9
11
  $pseudo-underline-height: 3px;
10
- $button-pipe-colour: darken(govuk-colour("mid-grey"), 20%);
11
12
 
13
+ $navbar-height: 50px;
12
14
  $large-navbar-height: 72px;
13
15
 
14
- $pale-blue-colour: #d2e2f1;
16
+ $button-pipe-colour: darken(govuk-colour("mid-grey"), 20%);
17
+ $button-pipe-colour-blue-background: $pale-blue-colour;
15
18
 
16
19
  $after-link-padding: govuk-spacing(4);
17
20
  $after-button-padding-right: govuk-spacing(4);
@@ -58,6 +61,9 @@ $after-button-padding-left: govuk-spacing(4);
58
61
  }
59
62
  }
60
63
 
64
+ // Using `:focus-visible` means that in supporting browsers the focus state won't
65
+ // be visible when a user clicks on the element, but the focus state will still be
66
+ // useful for those who use the keyboard to navigate around the page.
61
67
  @mixin focus-and-focus-visible {
62
68
  &:focus {
63
69
  @content;
@@ -68,6 +74,11 @@ $after-button-padding-left: govuk-spacing(4);
68
74
  }
69
75
  }
70
76
 
77
+ // For browsers that don't support `:focus-visible`, this defaults to using
78
+ // `:focus` with a CSS-only fallback strategy.
79
+ //
80
+ // Undoes the :focus styles *only* for browsers that support :focus-visible.
81
+ // See https://www.tpgi.com/focus-visible-and-backwards-compatibility/
71
82
  @mixin focus-not-focus-visible {
72
83
  & {
73
84
  @content;
@@ -104,7 +115,7 @@ $after-button-padding-left: govuk-spacing(4);
104
115
  }
105
116
 
106
117
  .gem-c-layout-super-navigation-header__button-container {
107
- top: -$black-bar-height;
118
+ top: -$navbar-height;
108
119
  position: absolute;
109
120
  right: 0;
110
121
 
@@ -316,7 +327,7 @@ $after-button-padding-left: govuk-spacing(4);
316
327
  }
317
328
 
318
329
  .gem-c-layout-super-navigation-header__navigation-item-link-inner--blue-background {
319
- border-right: 1px solid $pale-blue-colour;
330
+ border-right: 1px solid $button-pipe-colour-blue-background;
320
331
  }
321
332
 
322
333
  // Search link and dropdown.
@@ -389,7 +400,7 @@ $after-button-padding-left: govuk-spacing(4);
389
400
  box-sizing: border-box;
390
401
  color: govuk-colour("white");
391
402
  cursor: pointer;
392
- height: $black-bar-height;
403
+ height: $navbar-height;
393
404
  padding: 0;
394
405
  position: relative;
395
406
  margin: 0;
@@ -400,16 +411,18 @@ $after-button-padding-left: govuk-spacing(4);
400
411
  @include pseudo-underline($left: $after-button-padding-left, $right: $after-button-padding-right);
401
412
  }
402
413
 
403
- &:hover {
404
- color: govuk-colour("mid-grey");
414
+ @media (hover: hover) and (pointer: fine) {
415
+ &:hover {
416
+ color: govuk-colour("mid-grey");
405
417
 
406
- &::after {
407
- background: govuk-colour("mid-grey");
408
- }
418
+ &::after {
419
+ background: govuk-colour("mid-grey");
420
+ }
409
421
 
410
- .gem-c-layout-super-navigation-header__navigation-top-toggle-button-inner {
411
- &::before {
412
- border-color: govuk-colour("mid-grey");
422
+ .gem-c-layout-super-navigation-header__navigation-top-toggle-button-inner {
423
+ &::before {
424
+ border-color: govuk-colour("mid-grey");
425
+ }
413
426
  }
414
427
  }
415
428
  }
@@ -445,9 +458,11 @@ $after-button-padding-left: govuk-spacing(4);
445
458
 
446
459
  box-shadow: none;
447
460
 
448
- &:hover {
449
- &::after {
450
- background-color: govuk-colour("black");
461
+ @media (hover: hover) and (pointer: fine) {
462
+ &:hover {
463
+ &::after {
464
+ background-color: govuk-colour("black");
465
+ }
451
466
  }
452
467
  }
453
468
 
@@ -465,25 +480,25 @@ $after-button-padding-left: govuk-spacing(4);
465
480
  // stylelint-enable max-nesting-depth
466
481
  }
467
482
 
468
- // Undoes the :focus styles *only* for browsers that support :focus-visible.
469
- // See https://www.tpgi.com/focus-visible-and-backwards-compatibility/
470
483
  @include focus-not-focus-visible {
471
484
  background: none;
472
485
  box-shadow: none;
473
486
  color: govuk-colour("white");
474
487
 
475
488
  // stylelint-disable max-nesting-depth
476
- &:hover {
477
- .gem-c-layout-super-navigation-header__navigation-top-toggle-button-inner {
478
- color: govuk-colour("mid-grey");
489
+ @media (hover: hover) and (pointer: fine) {
490
+ &:hover {
491
+ .gem-c-layout-super-navigation-header__navigation-top-toggle-button-inner {
492
+ color: govuk-colour("mid-grey");
479
493
 
480
- &::before {
481
- @include chevron(govuk-colour("mid-grey"), true);
494
+ &::before {
495
+ @include chevron(govuk-colour("mid-grey"), true);
496
+ }
482
497
  }
483
- }
484
498
 
485
- &::after {
486
- background: govuk-colour("mid-grey");
499
+ &::after {
500
+ background: govuk-colour("mid-grey");
501
+ }
487
502
  }
488
503
  }
489
504
 
@@ -491,10 +506,10 @@ $after-button-padding-left: govuk-spacing(4);
491
506
  border-color: $button-pipe-colour;
492
507
 
493
508
  &.gem-c-layout-super-navigation-header__navigation-top-toggle-button-inner--blue-background {
494
- border-color: $pale-blue-colour;
509
+ border-color: $button-pipe-colour-blue-background;
495
510
  }
496
511
 
497
- @include govuk-media-query($from: 360px) {
512
+ @include govuk-media-query($from: $chevron-breakpoint) {
498
513
  &::before {
499
514
  @include chevron(govuk-colour("white"), true);
500
515
  }
@@ -505,6 +520,8 @@ $after-button-padding-left: govuk-spacing(4);
505
520
  }
506
521
  }
507
522
 
523
+ // JS available - targets the "Menu" toggle button
524
+ // Styles the "Menu" open state
508
525
  .gem-c-layout-super-navigation-header__navigation-top-toggle-button.gem-c-layout-super-navigation-header__navigation-top-toggle-button--blue-background,
509
526
  .gem-c-layout-super-navigation-header__navigation-top-toggle-button {
510
527
  // Open button modifier
@@ -522,7 +539,7 @@ $after-button-padding-left: govuk-spacing(4);
522
539
  color: govuk-colour("black");
523
540
  border-color: $govuk-focus-colour;
524
541
 
525
- @include govuk-media-query($from: 360px) {
542
+ @include govuk-media-query($from: $chevron-breakpoint) {
526
543
  &::before {
527
544
  @include chevron(govuk-colour("black"), true);
528
545
  @include prefixed-transform($rotate: 225deg, $translateY: 1px);
@@ -542,7 +559,7 @@ $after-button-padding-left: govuk-spacing(4);
542
559
  color: $govuk-link-colour;
543
560
  border-color: govuk-colour("light-grey");
544
561
 
545
- @include govuk-media-query($from: 360px) {
562
+ @include govuk-media-query($from: $chevron-breakpoint) {
546
563
  &::before {
547
564
  @include chevron($govuk-link-colour);
548
565
  @include prefixed-transform($rotate: 225deg, $translateY: 1px);
@@ -554,135 +571,142 @@ $after-button-padding-left: govuk-spacing(4);
554
571
  }
555
572
  }
556
573
 
574
+ // JS available - targets the "Menu" toggle button used on a blue background
557
575
  .gem-c-layout-super-navigation-header__navigation-top-toggle-button--blue-background {
558
576
  @include focus-not-focus-visible {
559
- &:hover {
560
- &::after {
561
- background: govuk-colour("white");
562
- }
577
+ @media (hover: hover) and (pointer: fine) {
578
+ &:hover {
579
+ &::after {
580
+ background: govuk-colour("white");
581
+ }
563
582
 
564
- .gem-c-layout-super-navigation-header__navigation-top-toggle-button-inner {
565
- color: govuk-colour("white");
583
+ .gem-c-layout-super-navigation-header__navigation-top-toggle-button-inner {
584
+ color: govuk-colour("white");
566
585
 
567
- &::before {
568
- border-color: govuk-colour("white");
586
+ &::before {
587
+ border-color: govuk-colour("white");
588
+ }
569
589
  }
570
590
  }
571
591
  }
572
592
  }
573
593
  }
574
594
 
595
+ // JS available - targets the "Menu" toggle button inner text
575
596
  .gem-c-layout-super-navigation-header__navigation-top-toggle-button-inner {
576
597
  display: inline-block;
577
598
  border-right: 1px solid govuk-colour("white");
578
599
  margin: 0;
579
600
  padding: govuk-spacing(1) govuk-spacing(4);
580
601
 
581
- @include govuk-media-query($from: 360px) {
602
+ @include govuk-media-query($from: $chevron-breakpoint) {
582
603
  &::before {
583
604
  @include chevron(govuk-colour("white"));
584
605
  }
585
606
  }
586
607
  }
587
608
 
588
- // Styles for search toggle button.
609
+ // JS available - targets the search toggle button
589
610
  .gem-c-layout-super-navigation-header__search-toggle-button {
590
611
  background: none;
591
612
  border: 0;
592
613
  color: govuk-colour("white");
593
614
  cursor: pointer;
594
- height: $search-toggle-button-height;
615
+ height: $navbar-height;
595
616
  padding: govuk-spacing(3);
596
617
  position: relative;
597
618
  width: 51px;
619
+ @include govuk-font($size: 19, $weight: "bold", $line-height: 20px);
598
620
 
599
- &.gem-c-layout-super-navigation-header__search-toggle-button--blue-background {
600
- background: $govuk-brand-colour;
621
+ &::after {
622
+ @include pseudo-underline($left: 0, $right: 0);
601
623
  }
602
624
 
603
- @include govuk-font($size: 19, $weight: "bold", $line-height: 20px);
625
+ @media (hover: hover) and (pointer: fine) {
626
+ &:hover {
627
+ color: govuk-colour("mid-grey");
628
+
629
+ &::after {
630
+ background: govuk-colour("mid-grey");
631
+ }
632
+ }
633
+ }
604
634
 
605
635
  @include focus-and-focus-visible {
606
636
  @include govuk-focused-text;
607
637
  border-color: $govuk-focus-colour;
608
638
  box-shadow: none;
609
639
  z-index: 11;
640
+
641
+ @media (hover: hover) and (pointer: fine) {
642
+ &:hover {
643
+ &::after {
644
+ background: none;
645
+ }
646
+ }
647
+ }
610
648
  }
611
649
 
612
- &:focus:not(:focus-visible) {
650
+ @include focus-not-focus-visible {
613
651
  background: none;
614
- border-color: govuk-colour("white");
615
652
  box-shadow: none;
616
653
  color: govuk-colour("white");
617
654
  }
618
655
 
619
- &:focus-visible {
620
- &.gem-c-layout-super-navigation-header__search-toggle-button--blue-background {
621
- border-bottom: $pseudo-underline-height solid govuk-colour("black");
622
- }
623
- }
624
-
625
- @include govuk-media-query($from: "desktop") {
626
- border: 0;
627
- margin: 0;
628
- right: 0;
629
-
630
- @include focus-not-focus-visible {
631
- border-bottom: 1px solid transparent;
632
- border-left: none;
633
- position: relative;
634
- }
635
-
636
- &:hover {
637
- border-bottom: $pseudo-underline-height solid govuk-colour("mid-grey");
638
- color: govuk-colour("mid-grey");
639
- }
640
-
641
- &.gem-c-layout-super-navigation-header__search-toggle-button--blue-background {
656
+ &.gem-c-layout-super-navigation-header__search-toggle-button--blue-background {
657
+ @media (hover: hover) and (pointer: fine) {
642
658
  &:hover {
643
- border-bottom: $pseudo-underline-height solid govuk-colour("white");
644
659
  color: govuk-colour("white");
645
- }
646
660
 
647
- // stylelint-disable max-nesting-depth
648
- @include focus-not-focus-visible {
649
- border-bottom: $pseudo-underline-height solid $govuk-brand-colour;
650
-
651
- &:hover {
652
- border-bottom: $pseudo-underline-height solid govuk-colour("white");
661
+ &::after {
662
+ background: govuk-colour("white");
653
663
  }
654
664
  }
655
- // stylelint-enable max-nesting-depth
656
665
  }
657
666
 
658
667
  &:focus-visible {
659
- &.gem-c-layout-super-navigation-header__search-toggle-button--blue-background {
660
- color: govuk-colour("black");
668
+ color: govuk-colour("black");
669
+
670
+ &::after {
671
+ background: govuk-colour("black");
672
+ }
661
673
 
674
+ @media (hover: hover) and (pointer: fine) {
662
675
  &:hover {
663
- border-bottom: $pseudo-underline-height solid govuk-colour("black");
664
676
  color: govuk-colour("black");
677
+
678
+ &::after {
679
+ background: govuk-colour("black");
680
+ }
665
681
  }
666
682
  }
667
683
  }
668
684
 
669
- @include focus-and-focus-visible {
670
- @include govuk-focused-text;
671
- border-bottom-color: $govuk-focus-colour;
672
- box-shadow: none;
685
+ @include focus-not-focus-visible {
686
+ &::after {
687
+ background: none;
688
+ }
689
+
690
+ @media (hover: hover) and (pointer: fine) {
691
+ &:hover {
692
+ &::after {
693
+ background: govuk-colour("white");
694
+ }
695
+ }
696
+ }
673
697
  }
674
698
  }
675
699
 
676
700
  // Open button modifier
677
701
  &.gem-c-layout-super-navigation-header__open-button {
678
702
  &.gem-c-layout-super-navigation-header__search-toggle-button--blue-background {
679
- &:hover {
680
- border-bottom: $pseudo-underline-height solid govuk-colour("light-grey");
681
- color: govuk-colour("white");
703
+ @media (hover: hover) and (pointer: fine) {
704
+ &:hover {
705
+ color: govuk-colour("white");
706
+ }
682
707
  }
683
708
 
684
709
  &:focus-visible {
685
- border-bottom: $pseudo-underline-height solid govuk-colour("black");
686
710
  color: govuk-colour("black");
687
711
  }
688
712
  }
@@ -696,17 +720,23 @@ $after-button-padding-left: govuk-spacing(4);
696
720
 
697
721
  @include focus-not-focus-visible {
698
722
  background: govuk-colour("light-grey");
699
- border-bottom-color: govuk-colour("light-grey");
700
723
  color: govuk-colour("light-grey");
701
724
  outline: 1px solid govuk-colour("light-grey"); // overlap the border of the nav menu so it won't appear when menu open
702
725
 
703
- &:hover {
704
- border-bottom: $pseudo-underline-height solid govuk-colour("light-grey");
726
+ // stylelint-disable max-nesting-depth
727
+ @media (hover: hover) and (pointer: fine) {
728
+ &:hover {
729
+ &::after {
730
+ background: none;
731
+ }
732
+ }
705
733
  }
734
+ // stylelint-enable max-nesting-depth
706
735
  }
707
736
  }
708
737
  }
709
738
 
739
+ // JS available - styles the close icon, used when the search menu is in the open state
710
740
  .gem-c-layout-super-navigation-header__navigation-top-toggle-close-icon {
711
741
  color: $govuk-text-colour;
712
742
  display: none;
@@ -728,7 +758,7 @@ $after-button-padding-left: govuk-spacing(4);
728
758
  padding-bottom: govuk-spacing(7);
729
759
  }
730
760
 
731
- // Dropdown menu.
761
+ // JS available - dropdown menu
732
762
  .gem-c-layout-super-navigation-header__navigation-dropdown-menu {
733
763
  background: govuk-colour("light-grey");
734
764
  border-bottom: 1px govuk-colour("mid-grey") solid;
@@ -743,6 +773,7 @@ $after-button-padding-left: govuk-spacing(4);
743
773
  }
744
774
  }
745
775
 
776
+ // JS available - adds a custom margin to the wrapper for the search items in the search dropdown
746
777
  .gem-c-layout-super-navigation-header__navigation-dropdown-menu--large-navbar {
747
778
  @include govuk-media-query($until: "desktop") {
748
779
  .gem-c-layout-super-navigation-header__search-items {
@@ -751,6 +782,7 @@ $after-button-padding-left: govuk-spacing(4);
751
782
  }
752
783
  }
753
784
 
785
+ // JS available - adds custom padding to the services and information and government activity sections in the menu dropdown
754
786
  @include govuk-media-query($until: "tablet") {
755
787
  // padding to make it the same as the padding for the redesign of the homepage
756
788
  .gem-c-layout-super-navigation-header__navigation-dropdown-menu--large-navbar .gem-c-layout-super-navigation-header__column--services-and-information,
@@ -759,7 +791,7 @@ $after-button-padding-left: govuk-spacing(4);
759
791
  }
760
792
  }
761
793
 
762
- // Dropdown menu items.
794
+ // JS available - styles the links in the dropdown menu
763
795
  .gem-c-layout-super-navigation-header__dropdown-list-item {
764
796
  box-sizing: border-box;
765
797
  padding: 0 0 govuk-spacing(3) 0;
@@ -771,7 +803,7 @@ $after-button-padding-left: govuk-spacing(4);
771
803
  }
772
804
  }
773
805
 
774
- // Navigation menu items.
806
+ // JS available - wraps the `dropdown-list-item` navigation menu items
775
807
  .gem-c-layout-super-navigation-header__navigation-second-items {
776
808
  list-style: none;
777
809
  margin: 0;
@@ -783,6 +815,7 @@ $after-button-padding-left: govuk-spacing(4);
783
815
  }
784
816
  }
785
817
 
818
+ // JS available - styling for the "government-activity" group of links
786
819
  .gem-c-layout-super-navigation-header__column--government-activity {
787
820
  position: relative;
788
821
 
@@ -791,6 +824,7 @@ $after-button-padding-left: govuk-spacing(4);
791
824
  }
792
825
  }
793
826
 
827
+ // JS available - styling for the "services-and-information" group of links
794
828
  .gem-c-layout-super-navigation-header__navigation-second-items--services-and-information {
795
829
  @include govuk-media-query($until: "desktop") {
796
830
  border-bottom: 1px solid govuk-colour("mid-grey");
@@ -31,7 +31,8 @@
31
31
  format: item[:format],
32
32
  href: item[:href],
33
33
  data_attributes: item[:data_attributes],
34
- sort_direction: item[:sort_direction]
34
+ sort_direction: item[:sort_direction],
35
+ width: item[:width],
35
36
  } %>
36
37
  <% end %>
37
38
  <% end %>
@@ -200,3 +200,26 @@ examples:
200
200
  format: numeric
201
201
  - text: £125
202
202
  format: numeric
203
+ with_custom_width:
204
+ description: >
205
+ You can use `width` on a header cell to set the width of a table column if you do not want the
206
+ width to be inferred by the browser based on the content of its cells.
207
+ data:
208
+ head:
209
+ - text: Phrase
210
+ - text: Rating
211
+ width: one-half
212
+ rows:
213
+ -
214
+ - text: Lorem ipsum
215
+ - text: good
216
+ -
217
+ - text: dolor sit amet, consectetur adipiscing elit
218
+ - text: okay
219
+ -
220
+ - text: >
221
+ sed do eiusmod tempor incididunt ut labore et dolore magna aliqua duis aute irure dolor
222
+ in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur
223
+ molestiae non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam
224
+ quaerat voluptatem!!!
225
+ - text: bad
@@ -20,6 +20,14 @@ module GovukPublishingComponents
20
20
  end
21
21
 
22
22
  class TableBuilder
23
+ ALLOWABLE_COLUMN_WIDTHS = %w[
24
+ three-quarters
25
+ two-thirds
26
+ one-half
27
+ one-third
28
+ one-quarter
29
+ ].freeze
30
+
23
31
  include ActionView::Helpers::UrlHelper
24
32
  include ActionView::Helpers::TagHelper
25
33
 
@@ -53,6 +61,7 @@ module GovukPublishingComponents
53
61
  classes = %w[govuk-table__header]
54
62
  classes << "govuk-table__header--#{opt[:format]}" if opt[:format]
55
63
  classes << "govuk-table__header--active" if opt[:sort_direction]
64
+ classes << "govuk-!-width-#{opt[:width]}" if ALLOWABLE_COLUMN_WIDTHS.include?(opt[:width])
56
65
  link_classes = %w[app-table__sort-link]
57
66
  link_classes << "app-table__sort-link--#{opt[:sort_direction]}" if opt[:sort_direction]
58
67
  str = link_to str, opt[:href], class: link_classes, data: opt[:data_attributes] if opt[:href]
@@ -1,3 +1,3 @@
1
1
  module GovukPublishingComponents
2
- VERSION = "56.2.2".freeze
2
+ VERSION = "56.3.0".freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: govuk_publishing_components
3
3
  version: !ruby/object:Gem::Version
4
- version: 56.2.2
4
+ version: 56.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GOV.UK Dev
@@ -456,6 +456,7 @@ files:
456
456
  - app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-core.js
457
457
  - app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-ecommerce-tracker.js
458
458
  - app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-event-tracker.js
459
+ - app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-finder-tracker.js
459
460
  - app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-focus-loss-tracker.js
460
461
  - app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-form-tracker.js
461
462
  - app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-link-tracker.js