govuk_publishing_components 56.2.1 → 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: 68407f5db22229aa78134b622dc8559456e40553df87dd9cb01de60aaf3e462f
4
- data.tar.gz: 8b979b368aba80117804a47ae7d0c7595c2fce311578fc731bef81309456db5e
3
+ metadata.gz: 67d7750a4af75be256720a227401885c736d2af67113acfa16889fc5dc599a5d
4
+ data.tar.gz: a312eca2e3699de90c89bf5e256a09bca8eedffeb3e00657738ec21fb8fa3059
5
5
  SHA512:
6
- metadata.gz: c8d5952713a5fad747e52900b7e37dac29595e1cc63ebe02aa23b9751660011e7662432a5def3c7bd1ccbbc9328d434c2e40b5709da1290c3df91479cc6822ee
7
- data.tar.gz: f5c24bfbf5e24d745757b8add1af84cdc64fd261d41552ba7d9d988d63f04002982c73b927b3210ea636826c334cf02f61ba6074f0e46e8d9a5181f34914f9cf
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-width-or-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
 
@@ -137,7 +148,7 @@ $after-button-padding-left: govuk-spacing(4);
137
148
  padding: 0;
138
149
  }
139
150
 
140
- // Top level navigation links and search link.
151
+ // Top level navigation links and search link, used when JavaScript is not available
141
152
  .gem-c-layout-super-navigation-header__navigation-item-link,
142
153
  .gem-c-layout-super-navigation-header__search-item-link {
143
154
  display: inline-block;
@@ -305,10 +316,18 @@ $after-button-padding-left: govuk-spacing(4);
305
316
 
306
317
  .gem-c-layout-super-navigation-header__navigation-item-link-inner {
307
318
  padding: govuk-spacing(1) $after-link-padding;
319
+ border-right: 1px solid $button-pipe-colour;
320
+ }
321
+
322
+ .gem-c-layout-super-navigation-header__navigation-item-link--large-navbar {
323
+ & .gem-c-layout-super-navigation-header__navigation-item-link-inner {
324
+ padding-left: 26px;
325
+ padding-right: 26px;
326
+ }
308
327
  }
309
328
 
310
329
  .gem-c-layout-super-navigation-header__navigation-item-link-inner--blue-background {
311
- border-right: 1px solid $pale-blue-colour;
330
+ border-right: 1px solid $button-pipe-colour-blue-background;
312
331
  }
313
332
 
314
333
  // Search link and dropdown.
@@ -321,8 +340,6 @@ $after-button-padding-left: govuk-spacing(4);
321
340
 
322
341
  &:link,
323
342
  &:visited {
324
- background: $govuk-brand-colour;
325
-
326
343
  &:hover {
327
344
  &::before {
328
345
  left: 0;
@@ -344,10 +361,6 @@ $after-button-padding-left: govuk-spacing(4);
344
361
  width: 100%;
345
362
  }
346
363
 
347
- @include focus-not-focus-visible {
348
- background: $govuk-link-colour;
349
- }
350
-
351
364
  @include focus-and-focus-visible {
352
365
  &:hover {
353
366
  background: $govuk-focus-colour;
@@ -361,6 +374,11 @@ $after-button-padding-left: govuk-spacing(4);
361
374
  }
362
375
  }
363
376
 
377
+ .gem-c-layout-super-navigation-header__search-item-link--large-navbar {
378
+ padding-left: 24px;
379
+ padding-right: 24px;
380
+ }
381
+
364
382
  .gem-c-layout-super-navigation-header__search-item-link-icon,
365
383
  .gem-c-layout-super-navigation-header__search-toggle-button-link-icon {
366
384
  height: 20px;
@@ -382,7 +400,7 @@ $after-button-padding-left: govuk-spacing(4);
382
400
  box-sizing: border-box;
383
401
  color: govuk-colour("white");
384
402
  cursor: pointer;
385
- height: $black-bar-height;
403
+ height: $navbar-height;
386
404
  padding: 0;
387
405
  position: relative;
388
406
  margin: 0;
@@ -393,16 +411,18 @@ $after-button-padding-left: govuk-spacing(4);
393
411
  @include pseudo-underline($left: $after-button-padding-left, $right: $after-button-padding-right);
394
412
  }
395
413
 
396
- &:hover {
397
- color: govuk-colour("mid-grey");
414
+ @media (hover: hover) and (pointer: fine) {
415
+ &:hover {
416
+ color: govuk-colour("mid-grey");
398
417
 
399
- &::after {
400
- background: govuk-colour("mid-grey");
401
- }
418
+ &::after {
419
+ background: govuk-colour("mid-grey");
420
+ }
402
421
 
403
- .gem-c-layout-super-navigation-header__navigation-top-toggle-button-inner {
404
- &::before {
405
- 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
+ }
406
426
  }
407
427
  }
408
428
  }
@@ -438,9 +458,11 @@ $after-button-padding-left: govuk-spacing(4);
438
458
 
439
459
  box-shadow: none;
440
460
 
441
- &:hover {
442
- &::after {
443
- background-color: govuk-colour("black");
461
+ @media (hover: hover) and (pointer: fine) {
462
+ &:hover {
463
+ &::after {
464
+ background-color: govuk-colour("black");
465
+ }
444
466
  }
445
467
  }
446
468
 
@@ -458,25 +480,25 @@ $after-button-padding-left: govuk-spacing(4);
458
480
  // stylelint-enable max-nesting-depth
459
481
  }
460
482
 
461
- // Undoes the :focus styles *only* for browsers that support :focus-visible.
462
- // See https://www.tpgi.com/focus-visible-and-backwards-compatibility/
463
483
  @include focus-not-focus-visible {
464
484
  background: none;
465
485
  box-shadow: none;
466
486
  color: govuk-colour("white");
467
487
 
468
488
  // stylelint-disable max-nesting-depth
469
- &:hover {
470
- .gem-c-layout-super-navigation-header__navigation-top-toggle-button-inner {
471
- 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");
472
493
 
473
- &::before {
474
- @include chevron(govuk-colour("mid-grey"), true);
494
+ &::before {
495
+ @include chevron(govuk-colour("mid-grey"), true);
496
+ }
475
497
  }
476
- }
477
498
 
478
- &::after {
479
- background: govuk-colour("mid-grey");
499
+ &::after {
500
+ background: govuk-colour("mid-grey");
501
+ }
480
502
  }
481
503
  }
482
504
 
@@ -484,10 +506,10 @@ $after-button-padding-left: govuk-spacing(4);
484
506
  border-color: $button-pipe-colour;
485
507
 
486
508
  &.gem-c-layout-super-navigation-header__navigation-top-toggle-button-inner--blue-background {
487
- border-color: $pale-blue-colour;
509
+ border-color: $button-pipe-colour-blue-background;
488
510
  }
489
511
 
490
- @include govuk-media-query($from: 360px) {
512
+ @include govuk-media-query($from: $chevron-breakpoint) {
491
513
  &::before {
492
514
  @include chevron(govuk-colour("white"), true);
493
515
  }
@@ -498,6 +520,8 @@ $after-button-padding-left: govuk-spacing(4);
498
520
  }
499
521
  }
500
522
 
523
+ // JS available - targets the "Menu" toggle button
524
+ // Styles the "Menu" open state
501
525
  .gem-c-layout-super-navigation-header__navigation-top-toggle-button.gem-c-layout-super-navigation-header__navigation-top-toggle-button--blue-background,
502
526
  .gem-c-layout-super-navigation-header__navigation-top-toggle-button {
503
527
  // Open button modifier
@@ -515,7 +539,7 @@ $after-button-padding-left: govuk-spacing(4);
515
539
  color: govuk-colour("black");
516
540
  border-color: $govuk-focus-colour;
517
541
 
518
- @include govuk-media-query($from: 360px) {
542
+ @include govuk-media-query($from: $chevron-breakpoint) {
519
543
  &::before {
520
544
  @include chevron(govuk-colour("black"), true);
521
545
  @include prefixed-transform($rotate: 225deg, $translateY: 1px);
@@ -535,7 +559,7 @@ $after-button-padding-left: govuk-spacing(4);
535
559
  color: $govuk-link-colour;
536
560
  border-color: govuk-colour("light-grey");
537
561
 
538
- @include govuk-media-query($from: 360px) {
562
+ @include govuk-media-query($from: $chevron-breakpoint) {
539
563
  &::before {
540
564
  @include chevron($govuk-link-colour);
541
565
  @include prefixed-transform($rotate: 225deg, $translateY: 1px);
@@ -547,135 +571,142 @@ $after-button-padding-left: govuk-spacing(4);
547
571
  }
548
572
  }
549
573
 
574
+ // JS available - targets the "Menu" toggle button used on a blue background
550
575
  .gem-c-layout-super-navigation-header__navigation-top-toggle-button--blue-background {
551
576
  @include focus-not-focus-visible {
552
- &:hover {
553
- &::after {
554
- background: govuk-colour("white");
555
- }
577
+ @media (hover: hover) and (pointer: fine) {
578
+ &:hover {
579
+ &::after {
580
+ background: govuk-colour("white");
581
+ }
556
582
 
557
- .gem-c-layout-super-navigation-header__navigation-top-toggle-button-inner {
558
- color: govuk-colour("white");
583
+ .gem-c-layout-super-navigation-header__navigation-top-toggle-button-inner {
584
+ color: govuk-colour("white");
559
585
 
560
- &::before {
561
- border-color: govuk-colour("white");
586
+ &::before {
587
+ border-color: govuk-colour("white");
588
+ }
562
589
  }
563
590
  }
564
591
  }
565
592
  }
566
593
  }
567
594
 
595
+ // JS available - targets the "Menu" toggle button inner text
568
596
  .gem-c-layout-super-navigation-header__navigation-top-toggle-button-inner {
569
597
  display: inline-block;
570
598
  border-right: 1px solid govuk-colour("white");
571
599
  margin: 0;
572
600
  padding: govuk-spacing(1) govuk-spacing(4);
573
601
 
574
- @include govuk-media-query($from: 360px) {
602
+ @include govuk-media-query($from: $chevron-breakpoint) {
575
603
  &::before {
576
604
  @include chevron(govuk-colour("white"));
577
605
  }
578
606
  }
579
607
  }
580
608
 
581
- // Styles for search toggle button.
609
+ // JS available - targets the search toggle button
582
610
  .gem-c-layout-super-navigation-header__search-toggle-button {
583
611
  background: none;
584
612
  border: 0;
585
613
  color: govuk-colour("white");
586
614
  cursor: pointer;
587
- height: $search-width-or-height;
615
+ height: $navbar-height;
588
616
  padding: govuk-spacing(3);
589
617
  position: relative;
590
- width: $search-width-or-height;
618
+ width: 51px;
619
+ @include govuk-font($size: 19, $weight: "bold", $line-height: 20px);
591
620
 
592
- &.gem-c-layout-super-navigation-header__search-toggle-button--blue-background {
593
- background: $govuk-brand-colour;
621
+ &::after {
622
+ @include pseudo-underline($left: 0, $right: 0);
594
623
  }
595
624
 
596
- @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
+ }
597
634
 
598
635
  @include focus-and-focus-visible {
599
636
  @include govuk-focused-text;
600
637
  border-color: $govuk-focus-colour;
601
638
  box-shadow: none;
602
639
  z-index: 11;
640
+
641
+ @media (hover: hover) and (pointer: fine) {
642
+ &:hover {
643
+ &::after {
644
+ background: none;
645
+ }
646
+ }
647
+ }
603
648
  }
604
649
 
605
- &:focus:not(:focus-visible) {
650
+ @include focus-not-focus-visible {
606
651
  background: none;
607
- border-color: govuk-colour("white");
608
652
  box-shadow: none;
609
653
  color: govuk-colour("white");
610
654
  }
611
655
 
612
- &:focus-visible {
613
- &.gem-c-layout-super-navigation-header__search-toggle-button--blue-background {
614
- border-bottom: $pseudo-underline-height solid govuk-colour("black");
615
- }
616
- }
617
-
618
- @include govuk-media-query($from: "desktop") {
619
- border: 0;
620
- margin: 0;
621
- right: 0;
622
-
623
- @include focus-not-focus-visible {
624
- border-bottom: 1px solid transparent;
625
- border-left: none;
626
- position: relative;
627
- }
628
-
629
- &:hover {
630
- border-bottom: $pseudo-underline-height solid govuk-colour("mid-grey");
631
- color: govuk-colour("mid-grey");
632
- }
633
-
634
- &.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) {
635
658
  &:hover {
636
- border-bottom: $pseudo-underline-height solid govuk-colour("white");
637
659
  color: govuk-colour("white");
638
- }
639
660
 
640
- // stylelint-disable max-nesting-depth
641
- @include focus-not-focus-visible {
642
- border-bottom: $pseudo-underline-height solid $govuk-brand-colour;
643
-
644
- &:hover {
645
- border-bottom: $pseudo-underline-height solid govuk-colour("white");
661
+ &::after {
662
+ background: govuk-colour("white");
646
663
  }
647
664
  }
648
- // stylelint-enable max-nesting-depth
649
665
  }
650
666
 
651
667
  &:focus-visible {
652
- &.gem-c-layout-super-navigation-header__search-toggle-button--blue-background {
653
- color: govuk-colour("black");
668
+ color: govuk-colour("black");
654
669
 
670
+ &::after {
671
+ background: govuk-colour("black");
672
+ }
673
+
674
+ @media (hover: hover) and (pointer: fine) {
655
675
  &:hover {
656
- border-bottom: $pseudo-underline-height solid govuk-colour("black");
657
676
  color: govuk-colour("black");
677
+
678
+ &::after {
679
+ background: govuk-colour("black");
680
+ }
658
681
  }
659
682
  }
660
683
  }
661
684
 
662
- @include focus-and-focus-visible {
663
- @include govuk-focused-text;
664
- border-bottom-color: $govuk-focus-colour;
665
- 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
+ }
666
697
  }
667
698
  }
668
699
 
669
700
  // Open button modifier
670
701
  &.gem-c-layout-super-navigation-header__open-button {
671
702
  &.gem-c-layout-super-navigation-header__search-toggle-button--blue-background {
672
- &:hover {
673
- border-bottom: $pseudo-underline-height solid govuk-colour("light-grey");
674
- color: govuk-colour("white");
703
+ @media (hover: hover) and (pointer: fine) {
704
+ &:hover {
705
+ color: govuk-colour("white");
706
+ }
675
707
  }
676
708
 
677
709
  &:focus-visible {
678
- border-bottom: $pseudo-underline-height solid govuk-colour("black");
679
710
  color: govuk-colour("black");
680
711
  }
681
712
  }
@@ -689,17 +720,23 @@ $after-button-padding-left: govuk-spacing(4);
689
720
 
690
721
  @include focus-not-focus-visible {
691
722
  background: govuk-colour("light-grey");
692
- border-bottom-color: govuk-colour("light-grey");
693
723
  color: govuk-colour("light-grey");
694
724
  outline: 1px solid govuk-colour("light-grey"); // overlap the border of the nav menu so it won't appear when menu open
695
725
 
696
- &:hover {
697
- 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
+ }
698
733
  }
734
+ // stylelint-enable max-nesting-depth
699
735
  }
700
736
  }
701
737
  }
702
738
 
739
+ // JS available - styles the close icon, used when the search menu is in the open state
703
740
  .gem-c-layout-super-navigation-header__navigation-top-toggle-close-icon {
704
741
  color: $govuk-text-colour;
705
742
  display: none;
@@ -721,7 +758,7 @@ $after-button-padding-left: govuk-spacing(4);
721
758
  padding-bottom: govuk-spacing(7);
722
759
  }
723
760
 
724
- // Dropdown menu.
761
+ // JS available - dropdown menu
725
762
  .gem-c-layout-super-navigation-header__navigation-dropdown-menu {
726
763
  background: govuk-colour("light-grey");
727
764
  border-bottom: 1px govuk-colour("mid-grey") solid;
@@ -736,6 +773,7 @@ $after-button-padding-left: govuk-spacing(4);
736
773
  }
737
774
  }
738
775
 
776
+ // JS available - adds a custom margin to the wrapper for the search items in the search dropdown
739
777
  .gem-c-layout-super-navigation-header__navigation-dropdown-menu--large-navbar {
740
778
  @include govuk-media-query($until: "desktop") {
741
779
  .gem-c-layout-super-navigation-header__search-items {
@@ -744,6 +782,7 @@ $after-button-padding-left: govuk-spacing(4);
744
782
  }
745
783
  }
746
784
 
785
+ // JS available - adds custom padding to the services and information and government activity sections in the menu dropdown
747
786
  @include govuk-media-query($until: "tablet") {
748
787
  // padding to make it the same as the padding for the redesign of the homepage
749
788
  .gem-c-layout-super-navigation-header__navigation-dropdown-menu--large-navbar .gem-c-layout-super-navigation-header__column--services-and-information,
@@ -752,7 +791,7 @@ $after-button-padding-left: govuk-spacing(4);
752
791
  }
753
792
  }
754
793
 
755
- // Dropdown menu items.
794
+ // JS available - styles the links in the dropdown menu
756
795
  .gem-c-layout-super-navigation-header__dropdown-list-item {
757
796
  box-sizing: border-box;
758
797
  padding: 0 0 govuk-spacing(3) 0;
@@ -764,7 +803,7 @@ $after-button-padding-left: govuk-spacing(4);
764
803
  }
765
804
  }
766
805
 
767
- // Navigation menu items.
806
+ // JS available - wraps the `dropdown-list-item` navigation menu items
768
807
  .gem-c-layout-super-navigation-header__navigation-second-items {
769
808
  list-style: none;
770
809
  margin: 0;
@@ -776,6 +815,7 @@ $after-button-padding-left: govuk-spacing(4);
776
815
  }
777
816
  }
778
817
 
818
+ // JS available - styling for the "government-activity" group of links
779
819
  .gem-c-layout-super-navigation-header__column--government-activity {
780
820
  position: relative;
781
821
 
@@ -784,6 +824,7 @@ $after-button-padding-left: govuk-spacing(4);
784
824
  }
785
825
  }
786
826
 
827
+ // JS available - styling for the "services-and-information" group of links
787
828
  .gem-c-layout-super-navigation-header__navigation-second-items--services-and-information {
788
829
  @include govuk-media-query($until: "desktop") {
789
830
  border-bottom: 1px solid govuk-colour("mid-grey");
@@ -833,7 +874,7 @@ $after-button-padding-left: govuk-spacing(4);
833
874
  .gem-c-layout-super-navigation-header__search-toggle-button--large-navbar {
834
875
  padding-left: 24px;
835
876
  padding-right: 24px;
836
- width: 68px;
877
+ width: 69px;
837
878
  }
838
879
 
839
880
  .gem-c-layout-super-navigation-header__navigation-second-item-description {
@@ -875,8 +916,8 @@ $after-button-padding-left: govuk-spacing(4);
875
916
  // isn't a multiple of 5 :(
876
917
  .gem-c-layout-super-navigation-header__navigation-item-link--large-navbar,
877
918
  .gem-c-layout-super-navigation-header__search-item-link--large-navbar {
878
- padding-top: 33px;
879
- padding-bottom: 33px;
919
+ padding-top: 26px;
920
+ padding-bottom: 26px;
880
921
  }
881
922
 
882
923
  .gem-c-layout-super-navigation-header__logotype-crown--large-navbar {
@@ -904,7 +945,8 @@ $after-button-padding-left: govuk-spacing(4);
904
945
  .gem-c-layout-super-navigation-header__search-toggle-button--large-navbar {
905
946
  height: $large-navbar-height;
906
947
  // to stop the search icon moving on hover
907
- padding-bottom: 12px;
948
+ padding-top: 26px;
949
+ padding-bottom: 26px;
908
950
  }
909
951
 
910
952
  .gem-c-layout-super-navigation-header__button-container--large-navbar {
@@ -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.1".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.1
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
@@ -2038,7 +2039,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
2038
2039
  - !ruby/object:Gem::Version
2039
2040
  version: '0'
2040
2041
  requirements: []
2041
- rubygems_version: 3.6.7
2042
+ rubygems_version: 3.6.8
2042
2043
  specification_version: 4
2043
2044
  summary: A gem to document components in GOV.UK frontend applications
2044
2045
  test_files: []