govuk_publishing_components 38.1.1 → 38.2.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: e957626449176c29e36bde0d645dda9a4dd296b7f06969cee8fb86c5c9bedd25
4
- data.tar.gz: 7b8d2a6ea56fb37d014d01aa6cd5dc95fc0f62c99e36011ce8bea4183fd79c58
3
+ metadata.gz: '0386f2759bb8457478930df490b0744905dd8b044daffbdc9f4074e44c11f7a2'
4
+ data.tar.gz: b2892a04b8af279fc386deace44cde4adeea7119cd53d6276ca62b13bcdf01e4
5
5
  SHA512:
6
- metadata.gz: 5e35fa964443be079e31839687e19811493fb76faa63ec948a619ca57a42cf611868fb590d16e1b92b40c3b2ec534ff94b6433ba6dd2b5cd7e0565ef07cbdcff
7
- data.tar.gz: 6298895e8212651e27924b5dabe6e56f68ff8bab616051cb8c137e90e11f09b1ea5261a918de6b570729688ebc978cb40538235685525a9634e28425919d1ce7
6
+ metadata.gz: 790aa1333fddc398174f120f056b8b3860e1eed354478b09dcae609c67330f0eecc567d39e94b12cdb349575360ccd0b8e535c53e26c54436057715615febb8c
7
+ data.tar.gz: 3b3759b84cb3b936f169494694fd4cd47db0ab834e60168dabfb286b3dae8e9139ff4d8cbe81aa9449ada1857943e9022ffc9025044dda3ca1ac84b20f0c0917
@@ -371,11 +371,6 @@ window.GOVUK.analyticsGa4 = window.GOVUK.analyticsGa4 || {};
371
371
  }
372
372
  } else {
373
373
  for (var i = 0; i < items.length; i++) {
374
- // GA4 limits us to 200 items, so we should limit the array to this size.
375
- if (i === 200) {
376
- break
377
- }
378
-
379
374
  var item = items[i]
380
375
  var path = item.getAttribute('data-ga4-ecommerce-path')
381
376
 
@@ -385,16 +380,28 @@ window.GOVUK.analyticsGa4 = window.GOVUK.analyticsGa4 || {};
385
380
  item.setAttribute('data-ga4-ecommerce-index', i + 1)
386
381
  }
387
382
 
388
- ecommerceSchema.search_results.ecommerce.items.push({
383
+ var nextItem = {
389
384
  item_id: path,
390
385
  item_content_id: item.getAttribute('data-ga4-ecommerce-content-id') || undefined,
391
386
  item_list_name: listTitle,
392
387
  index: window.GOVUK.analyticsGa4.core.ecommerceHelperFunctions.getIndex(item, startPosition)
393
- })
388
+ }
389
+
390
+ // GA4 has a max payload, so ensure this array doesn't exceed 15,000 UTF-16 code units.
391
+ if ((this.getArraySize(ecommerceSchema.search_results.ecommerce.items) + this.getArraySize(nextItem)) >= 15000) {
392
+ break
393
+ }
394
+
395
+ ecommerceSchema.search_results.ecommerce.items.push(nextItem)
394
396
  }
395
397
  }
396
398
 
397
399
  return ecommerceSchema
400
+ },
401
+
402
+ getArraySize: function (array) {
403
+ // .length represents the number of UTF-16 code units in a string, so we can stringify the array then grab the length of the string to get an 'accurate enough' number representing the size.
404
+ return JSON.stringify(array).length
398
405
  }
399
406
  }
400
407
  }
@@ -192,5 +192,5 @@ try {
192
192
  }
193
193
  }
194
194
  } catch (e) {
195
- console.error('Error in LUX reporting the HTTP protocol (' + window.location + '):', e)
195
+ console.error('Error in LUX reporting the HTTP protocol (%s):', window.location, e)
196
196
  }
@@ -46,6 +46,7 @@
46
46
  auto: autoMode,
47
47
  beaconUrl: getProperty(obj, "beaconUrl", luxOrigin + "/lux/"),
48
48
  conversions: getProperty(obj, "conversions"),
49
+ cookieDomain: getProperty(obj, "cookieDomain"),
49
50
  customerid: getProperty(obj, "customerid"),
50
51
  errorBeaconUrl: getProperty(obj, "errorBeaconUrl", luxOrigin + "/error/"),
51
52
  jspagelabel: getProperty(obj, "jspagelabel"),
@@ -120,6 +121,7 @@
120
121
  return Math.floor(x);
121
122
  }
122
123
  var max = Math.max;
124
+ var round = Math.round;
123
125
  /**
124
126
  * Clamp a number so that it is never less than 0
125
127
  */
@@ -178,7 +180,7 @@
178
180
  startTime: 0,
179
181
  type: navType == 2 ? "back_forward" : navType === 1 ? "reload" : "navigate",
180
182
  };
181
- {
183
+ if (true) {
182
184
  for (var key in timing) {
183
185
  if (typeof timing[key] === "number" && key !== "navigationStart") {
184
186
  entry[key] = floor(timing[key] - timing.navigationStart);
@@ -240,6 +242,66 @@
240
242
  return getNavigationEntry().redirectCount > 0 || timing.redirectEnd > 0;
241
243
  }
242
244
 
245
+ function getClosestScTrackAttribute(el) {
246
+ var _a;
247
+ if (el.hasAttribute("data-sctrack")) {
248
+ var trackId = (_a = el.getAttribute("data-sctrack")) === null || _a === void 0 ? void 0 : _a.trim();
249
+ if (trackId) {
250
+ return trackId;
251
+ }
252
+ }
253
+ if (hasParentNode(el)) {
254
+ return getClosestScTrackAttribute(el.parentNode);
255
+ }
256
+ return null;
257
+ }
258
+
259
+ function hasParentNode(el) {
260
+ if (el.parentNode && el.parentNode.tagName) {
261
+ return true;
262
+ }
263
+ return false;
264
+ }
265
+ var MAX_SELECTOR_LENGTH = 100;
266
+ function getNodeSelector(node, selector) {
267
+ if (selector === void 0) {
268
+ selector = "";
269
+ }
270
+ try {
271
+ if (selector && (node.nodeType === 9 || selector.length > MAX_SELECTOR_LENGTH || !node.parentNode)) {
272
+ // Final selector.
273
+ return selector;
274
+ }
275
+ var el = node;
276
+ // Our first preference is to use the data-sctrack attribute from anywhere in the tree
277
+ var trackId = getClosestScTrackAttribute(el);
278
+ if (trackId) {
279
+ return trackId;
280
+ }
281
+ if (el.id) {
282
+ // Once we've found an element with ID we return the selector.
283
+ return "#" + el.id + (selector ? ">" + selector : "");
284
+ }
285
+ else {
286
+ // Otherwise attempt to get parent elements recursively
287
+ var name_1 = el.nodeType === 1 ? el.nodeName.toLowerCase() : el.nodeName.toUpperCase();
288
+ var classes = el.className ? "." + el.className.replace(/\s+/g, ".") : "";
289
+ var currentSelector = name_1 + classes + (selector ? ">" + selector : "");
290
+ if (el.parentNode) {
291
+ var selectorWithParent = getNodeSelector(el.parentNode, currentSelector);
292
+ if (selectorWithParent.length < MAX_SELECTOR_LENGTH) {
293
+ return selectorWithParent;
294
+ }
295
+ }
296
+ return currentSelector;
297
+ }
298
+ }
299
+ catch (error) {
300
+ // Do nothing.
301
+ }
302
+ return selector;
303
+ }
304
+
243
305
  var Flags = {
244
306
  InitCalled: 1 << 0,
245
307
  NavTimingNotSupported: 1 << 1,
@@ -258,56 +320,6 @@
258
320
  return flags | flag;
259
321
  }
260
322
 
261
- function hasParentNode(el) {
262
- if (el.parentNode && el.parentNode.tagName) {
263
- return true;
264
- }
265
- return false;
266
- }
267
-
268
- /**
269
- * Get the interaction attribution name for an element
270
- */
271
- function interactionAttributionForElement(el) {
272
- // Our first preference is to use the data-sctrack attribute from anywhere in the tree
273
- var trackId = getClosestScTrackAttribute(el);
274
- if (trackId) {
275
- return trackId;
276
- }
277
- // The second preference is to use the element's ID
278
- if (el.id) {
279
- return el.id;
280
- }
281
- // The third preference is to use the text content of a button or link
282
- var isSubmitInput = el.tagName === "INPUT" && el.type === "submit";
283
- var isButton = el.tagName === "BUTTON";
284
- var isLink = el.tagName === "A";
285
- if (isSubmitInput && el.value) {
286
- return el.value;
287
- }
288
- if ((isButton || isLink) && el.innerText) {
289
- return el.innerText;
290
- }
291
- if (hasParentNode(el)) {
292
- return interactionAttributionForElement(el.parentNode);
293
- }
294
- // No suitable attribute was found
295
- return "";
296
- }
297
- function getClosestScTrackAttribute(el) {
298
- var _a;
299
- if (el.hasAttribute("data-sctrack")) {
300
- var trackId = (_a = el.getAttribute("data-sctrack")) === null || _a === void 0 ? void 0 : _a.trim();
301
- if (trackId) {
302
- return trackId;
303
- }
304
- }
305
- if (hasParentNode(el)) {
306
- return getClosestScTrackAttribute(el.parentNode);
307
- }
308
- return null;
309
- }
310
-
311
323
  var LogEvent = {
312
324
  // Internal events
313
325
  EvaluationStart: 1,
@@ -358,7 +370,7 @@
358
370
  return this.events;
359
371
  };
360
372
  return Logger;
361
- }());
373
+ })();
362
374
 
363
375
  var sessionValue = 0;
364
376
  var sessionEntries = [];
@@ -406,14 +418,33 @@
406
418
  }
407
419
  function addEntry$1(entry) {
408
420
  if (entry.interactionId || (entry.entryType === "first-input" && !entryExists(entry))) {
409
- var duration = entry.duration, startTime = entry.startTime, interactionId = entry.interactionId;
421
+ var duration = entry.duration,
422
+ startTime = entry.startTime,
423
+ interactionId = entry.interactionId,
424
+ processingStart = entry.processingStart,
425
+ processingEnd = entry.processingEnd,
426
+ target = entry.target;
410
427
  var existingEntry = slowestEntriesMap[interactionId];
428
+ var selector = target ? getNodeSelector(target) : null;
411
429
  if (existingEntry) {
412
- existingEntry.duration = max(duration, existingEntry.duration);
430
+ if (existingEntry.duration < duration) {
431
+ existingEntry.duration = duration;
432
+ existingEntry.startTime = startTime;
433
+ existingEntry.processingStart = processingStart;
434
+ existingEntry.processingEnd = processingEnd;
435
+ existingEntry.selector = selector;
436
+ }
413
437
  }
414
438
  else {
415
439
  interactionCountEstimate++;
416
- slowestEntriesMap[interactionId] = { duration: duration, interactionId: interactionId, startTime: startTime };
440
+ slowestEntriesMap[interactionId] = {
441
+ duration: duration,
442
+ interactionId: interactionId,
443
+ startTime: startTime,
444
+ processingStart: processingStart,
445
+ processingEnd: processingEnd,
446
+ selector: selector,
447
+ };
417
448
  slowestEntries.push(slowestEntriesMap[interactionId]);
418
449
  }
419
450
  // Only store the longest <MAX_INTERACTIONS> interactions
@@ -430,10 +461,9 @@
430
461
  * Returns an estimated high percentile INP value based on the total number of interactions on the
431
462
  * current page.
432
463
  */
433
- function getHighPercentileINP() {
434
- var _a;
464
+ function getHighPercentileInteraction() {
435
465
  var index = Math.min(slowestEntries.length - 1, Math.floor(getInteractionCount() / 50));
436
- return (_a = slowestEntries[index]) === null || _a === void 0 ? void 0 : _a.duration;
466
+ return slowestEntries[index];
437
467
  }
438
468
  function getInteractionCount() {
439
469
  if ("interactionCount" in performance) {
@@ -547,7 +577,7 @@
547
577
  // -------------------------------------------------------------------------
548
578
  /// End
549
579
  // -------------------------------------------------------------------------
550
- var SCRIPT_VERSION = "313";
580
+ var SCRIPT_VERSION = "314";
551
581
  var logger = new Logger();
552
582
  var globalConfig = fromObject(LUX);
553
583
  logger.logEvent(LogEvent.EvaluationStart, [SCRIPT_VERSION, globalConfig]);
@@ -661,7 +691,7 @@
661
691
  */
662
692
  var getZeroTime = function () {
663
693
  var _a;
664
- return Math.max(pageRestoreTime || 0, getNavigationEntry().activationStart, ((_a = _getMark(START_MARK)) === null || _a === void 0 ? void 0 : _a.startTime) || 0);
694
+ return max(pageRestoreTime || 0, getNavigationEntry().activationStart, ((_a = _getMark(START_MARK)) === null || _a === void 0 ? void 0 : _a.startTime) || 0);
665
695
  };
666
696
  /**
667
697
  * Most time-based metrics that LUX reports should be relative to the "zero" marker, rounded down
@@ -792,7 +822,7 @@
792
822
  return performance.mark.apply(performance, args);
793
823
  }
794
824
  // ...Otherwise provide a polyfill
795
- {
825
+ if (true) {
796
826
  var name_1 = args[0];
797
827
  var detail = ((_a = args[1]) === null || _a === void 0 ? void 0 : _a.detail) || null;
798
828
  var startTime = ((_b = args[1]) === null || _b === void 0 ? void 0 : _b.startTime) || _now();
@@ -855,7 +885,7 @@
855
885
  return performance.measure.apply(performance, args);
856
886
  }
857
887
  // ...Otherwise provide a polyfill
858
- {
888
+ if (true) {
859
889
  var navEntry = getNavigationEntry();
860
890
  var startTime = typeof startMarkName === "number" ? startMarkName : 0;
861
891
  var endTime = typeof endMarkName === "number" ? endMarkName : _now();
@@ -1123,7 +1153,7 @@
1123
1153
  }
1124
1154
  else {
1125
1155
  // Return the average of the two middle values.
1126
- return Math.round((aValues[half - 1] + aValues[half]) / 2.0);
1156
+ return round((aValues[half - 1] + aValues[half]) / 2.0);
1127
1157
  }
1128
1158
  }
1129
1159
  // Track how long it took lux.js to load via Resource Timing.
@@ -1176,7 +1206,7 @@
1176
1206
  function ixValues() {
1177
1207
  var aIx = [];
1178
1208
  for (var key in ghIx) {
1179
- aIx.push(key + "|" + ghIx[key]);
1209
+ aIx.push(key + "|" + encodeURIComponent(ghIx[key]));
1180
1210
  }
1181
1211
  return aIx.join(",");
1182
1212
  }
@@ -1288,7 +1318,7 @@
1288
1318
  e.onloadcssdefined ||
1289
1319
  "print" === e.media ||
1290
1320
  "style" === e.as ||
1291
- (typeof e.onload === "function" && e.media === "all")) ;
1321
+ (typeof e.onload === "function" && e.media === "all"));
1292
1322
  else {
1293
1323
  nBlocking++;
1294
1324
  }
@@ -1492,11 +1522,31 @@
1492
1522
  logger.logEvent(LogEvent.PaintTimingNotSupported);
1493
1523
  return undefined;
1494
1524
  }
1495
- function getINP() {
1525
+ function getINPDetails() {
1496
1526
  if (!("PerformanceEventTiming" in self)) {
1497
1527
  return undefined;
1498
1528
  }
1499
- return getHighPercentileINP();
1529
+ return getHighPercentileInteraction();
1530
+ }
1531
+ /**
1532
+ * Build the query string for the INP parameters:
1533
+ *
1534
+ * - INP: The duration of the P98 interaction
1535
+ * - INPs: The selector of the P98 interaction element
1536
+ * - INPt: The timestamp of the P98 interaction start time
1537
+ * - INPi: The input delay subpart of the P98 interaction
1538
+ * - INPp: The processing time subpart of the P98 interaction
1539
+ * - INPd: The presentation delay subpart of the P98 interaction
1540
+ */
1541
+ function getINPString(details) {
1542
+ return [
1543
+ "&INP=" + details.duration,
1544
+ details.selector ? "&INPs=" + encodeURIComponent(details.selector) : "",
1545
+ "&INPt=" + clamp(floor(details.startTime)),
1546
+ "&INPi=" + clamp(floor(details.processingStart - details.startTime)),
1547
+ "&INPp=" + clamp(floor(details.processingEnd - details.processingStart)),
1548
+ "&INPd=" + clamp(floor(details.startTime + details.duration - details.processingEnd)),
1549
+ ].join("");
1500
1550
  }
1501
1551
  function getCustomerId() {
1502
1552
  return LUX.customerid || "";
@@ -1508,7 +1558,7 @@
1508
1558
  while (i--) {
1509
1559
  totalParents += numParents(aElems[i]);
1510
1560
  }
1511
- var average = Math.round(totalParents / aElems.length);
1561
+ var average = round(totalParents / aElems.length);
1512
1562
  return average;
1513
1563
  }
1514
1564
  function numParents(elem) {
@@ -1607,7 +1657,7 @@
1607
1657
  var vw = document.documentElement.clientWidth;
1608
1658
  // Return true if the top-left corner is in the viewport and it has width & height.
1609
1659
  var lt = findPos(e);
1610
- return (lt[0] >= 0 && lt[1] >= 0 && lt[0] < vw && lt[1] < vh && e.offsetWidth > 0 && e.offsetHeight > 0);
1660
+ return lt[0] >= 0 && lt[1] >= 0 && lt[0] < vw && lt[1] < vh && e.offsetWidth > 0 && e.offsetHeight > 0;
1611
1661
  }
1612
1662
  // Return an array containing the top & left coordinates of the element.
1613
1663
  // from http://www.quirksmode.org/js/findpos.html
@@ -1689,14 +1739,14 @@
1689
1739
  _markLoadTime();
1690
1740
  }
1691
1741
  var sIx = "";
1692
- var INP = getINP();
1693
- // It's possible that the interaction beacon has been sent before the main beacon. We don't want
1694
- // to send the interaction metrics twice, so we only include them here if the interaction beacon
1695
- // has not been sent.
1742
+ var INP = getINPDetails();
1743
+ // If we haven't already sent an interaction beacon, check for interaction metrics and include
1744
+ // them in the main beacon.
1696
1745
  if (!gbIxSent) {
1697
1746
  sIx = ixValues();
1698
1747
  if (sIx === "") {
1699
- // If there are no interaction metrics, we
1748
+ // If there are no interaction metrics, we wait to send INP with the IX beacon to increase
1749
+ // the chance that we capture a valid INP.
1700
1750
  INP = undefined;
1701
1751
  }
1702
1752
  }
@@ -1766,13 +1816,14 @@
1766
1816
  nErrors +
1767
1817
  "nt" +
1768
1818
  navigationType() +
1769
- (navigator.deviceMemory ? "dm" + Math.round(navigator.deviceMemory) : "") + // device memory (GB)
1819
+ (navigator.deviceMemory ? "dm" + round(navigator.deviceMemory) : "") + // device memory (GB)
1770
1820
  (sIx ? "&IX=" + sIx : "") +
1771
1821
  (typeof gFirstInputDelay !== "undefined" ? "&FID=" + gFirstInputDelay : "") +
1772
1822
  (sCPU ? "&CPU=" + sCPU : "") +
1773
1823
  (sET ? "&ET=" + sET : "") + // element timing
1774
1824
  (typeof CLS !== "undefined" ? "&CLS=" + CLS : "") +
1775
- (typeof INP !== "undefined" ? "&INP=" + INP : "");
1825
+ // INP and sub-parts
1826
+ (typeof INP !== "undefined" ? getINPString(INP) : "");
1776
1827
  // We add the user timing entries last so that we can split them to reduce the URL size if necessary.
1777
1828
  var utValues = userTimingValues();
1778
1829
  var _b = fitUserTimingEntries(utValues, globalConfig, baseUrl + metricsQueryString), beaconUtValues = _b[0], remainingUtValues = _b[1];
@@ -1788,7 +1839,7 @@
1788
1839
  gbIxSent = sIx ? 1 : 0;
1789
1840
  // Send other beacons for JUST User Timing.
1790
1841
  while (remainingUtValues.length) {
1791
- _a = fitUserTimingEntries(remainingUtValues, globalConfig, baseUrl), beaconUtValues = _a[0], remainingUtValues = _a[1];
1842
+ (_a = fitUserTimingEntries(remainingUtValues, globalConfig, baseUrl)), (beaconUtValues = _a[0]), (remainingUtValues = _a[1]);
1792
1843
  var utBeaconUrl = baseUrl + "&UT=" + beaconUtValues.join(",");
1793
1844
  logger.logEvent(LogEvent.UserTimingBeaconSent, [utBeaconUrl]);
1794
1845
  _sendBeacon(utBeaconUrl);
@@ -1811,13 +1862,13 @@
1811
1862
  return;
1812
1863
  }
1813
1864
  var sIx = ixValues(); // Interaction Metrics
1814
- var INP = getINP();
1865
+ var INP = getINPDetails();
1815
1866
  if (sIx) {
1816
1867
  var beaconUrl = _getBeaconUrl(getUpdatedCustomData()) +
1817
1868
  "&IX=" +
1818
1869
  sIx +
1819
1870
  (typeof gFirstInputDelay !== "undefined" ? "&FID=" + gFirstInputDelay : "") +
1820
- (typeof INP !== "undefined" ? "&INP=" + INP : "");
1871
+ (typeof INP !== "undefined" ? getINPString(INP) : "");
1821
1872
  logger.logEvent(LogEvent.InteractionBeaconSent, [beaconUrl]);
1822
1873
  _sendBeacon(beaconUrl);
1823
1874
  gbIxSent = 1;
@@ -1872,22 +1923,27 @@
1872
1923
  if (keyCode === 16 || keyCode === 17 || keyCode === 18 || keyCode === 20 || keyCode === 224) {
1873
1924
  return;
1874
1925
  }
1875
- _removeIxHandlers();
1876
1926
  if (typeof ghIx["k"] === "undefined") {
1877
1927
  ghIx["k"] = _now();
1878
1928
  if (e && e.target instanceof Element) {
1879
- var trackId = interactionAttributionForElement(e.target);
1929
+ var trackId = getNodeSelector(e.target);
1880
1930
  if (trackId) {
1881
1931
  ghIx["ki"] = trackId;
1882
1932
  }
1883
1933
  }
1934
+ // Only one interaction type is recorded. Scrolls are considered less important, so delete
1935
+ // any scroll times if they exist.
1936
+ delete ghIx["s"];
1884
1937
  _sendIxAfterDelay();
1885
1938
  }
1939
+ _removeIxHandlers();
1886
1940
  }
1887
1941
  function _clickHandler(e) {
1888
- _removeIxHandlers();
1889
1942
  if (typeof ghIx["c"] === "undefined") {
1890
1943
  ghIx["c"] = _now();
1944
+ // Only one interaction type is recorded. Scrolls are considered less important, so delete
1945
+ // any scroll times if they exist.
1946
+ delete ghIx["s"];
1891
1947
  var target = void 0;
1892
1948
  try {
1893
1949
  // Seeing "Permission denied" errors, so do a simple try-catch.
@@ -1904,13 +1960,14 @@
1904
1960
  ghIx["cx"] = e.clientX;
1905
1961
  ghIx["cy"] = e.clientY;
1906
1962
  }
1907
- var trackId = interactionAttributionForElement(target);
1963
+ var trackId = getNodeSelector(target);
1908
1964
  if (trackId) {
1909
1965
  ghIx["ci"] = trackId;
1910
1966
  }
1911
1967
  }
1912
1968
  _sendIxAfterDelay();
1913
1969
  }
1970
+ _removeIxHandlers();
1914
1971
  }
1915
1972
  // Wrapper to support older browsers (<= IE8)
1916
1973
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -1976,7 +2033,7 @@
1976
2033
  // "00" matches all sample rates
1977
2034
  return Number(new Date()) + "00000";
1978
2035
  }
1979
- return Number(new Date()) + _padLeft(String(Math.round(100000 * Math.random())), "00000");
2036
+ return Number(new Date()) + _padLeft(String(round(100000 * Math.random())), "00000");
1980
2037
  }
1981
2038
  // Unique ID (also known as Session ID)
1982
2039
  // We use this to track all the page views in a single user session.
@@ -2063,6 +2120,8 @@
2063
2120
  "=" +
2064
2121
  escape(value) +
2065
2122
  (seconds ? "; max-age=" + seconds : "") +
2123
+ (globalConfig.cookieDomain ? "; domain=" +
2124
+ globalConfig.cookieDomain : "") +
2066
2125
  "; path=/; SameSite=Lax";
2067
2126
  }
2068
2127
  catch (e) {
@@ -104,6 +104,10 @@
104
104
  @include tall-crest;
105
105
  }
106
106
 
107
+ .gem-c-organisation-logo__crest--no10 {
108
+ @include crest("no10_crest", $xpos: 8px);
109
+ }
110
+
107
111
  .gem-c-organisation-logo__crest--single-identity,
108
112
  .gem-c-organisation-logo__crest--eo,
109
113
  .gem-c-organisation-logo__crest--org {
@@ -17,6 +17,7 @@
17
17
  omit_footer_navigation ||= false
18
18
  omit_footer_border ||= false
19
19
  omit_header ||= false
20
+ custom_layout ||= false
20
21
  product_name ||= nil
21
22
  show_explore_header ||= false
22
23
  show_cross_service_header ||= false
@@ -120,6 +121,8 @@
120
121
  service_navigation_items: service_navigation_items,
121
122
  product_name: product_name,
122
123
  } %>
124
+ <% elsif content_for?(:custom_header) %>
125
+ <%= yield :custom_header %>
123
126
  <% else %>
124
127
  <%= render "govuk_publishing_components/components/layout_header", {
125
128
  search: show_search,
@@ -159,6 +162,8 @@
159
162
  <%= yield :before_content %>
160
163
  <%= yield %>
161
164
  <% end %>
165
+ <% elsif custom_layout %>
166
+ <%= yield %>
162
167
  <% else %>
163
168
  <div id="wrapper" class="<%= "govuk-width-container" unless full_width %>">
164
169
  <%= yield :before_content %>
@@ -123,3 +123,19 @@ examples:
123
123
  cookie_preferences:
124
124
  text: How GOV.UK accounts use cookies
125
125
  href: https://www.gov.uk/government/publications/govuk-accounts-trial-full-privacy-notice-and-accessibility-statement
126
+ with_custom_layout:
127
+ description: Yields a custom layout for the content.
128
+ data:
129
+ custom_layout: true
130
+ block: |
131
+ <main id="custom-layout">
132
+ <h1>This is a custom layout</h1>
133
+ </main>
134
+ with_custom_header:
135
+ description: Allows the header to be replaced with HTML injected by the calling application in a `content_for` tag named `:custom_header`.
136
+ embed: |
137
+ <% content_for(:custom_header) do %>
138
+ <header id="custom-header">I'm a custom header</header>
139
+ <% end %>
140
+ <%= render "govuk_publishing_components/components/layout_for_public", {
141
+ } %>
@@ -51,8 +51,8 @@ examples:
51
51
  executive_office:
52
52
  data:
53
53
  organisation:
54
- name: Prime Minister's Office, 10 Downing Street
55
- url: '/government/organisations/prime-ministers-office-10-downing-street'
54
+ name: Example
55
+ url: '/government/organisations/'
56
56
  brand: 'executive-office'
57
57
  crest: 'eo'
58
58
  home_office:
@@ -69,6 +69,13 @@ examples:
69
69
  url: '/government/organisations/ministry-of-defence'
70
70
  brand: 'ministry-of-defence'
71
71
  crest: 'mod'
72
+ prime_ministers_office_10_downing_street:
73
+ data:
74
+ organisation:
75
+ name: "Prime Minister's Office,<br>10 Downing Street"
76
+ url: '/government/organisations/prime-ministers-office-10-downing-street'
77
+ brand: 'prime-ministers-office-10-downing-street'
78
+ crest: 'no10'
72
79
  office_of_the_advocate_general_for_scotland:
73
80
  data:
74
81
  organisation:
@@ -1,3 +1,3 @@
1
1
  module GovukPublishingComponents
2
- VERSION = "38.1.1".freeze
2
+ VERSION = "38.2.0".freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: govuk_publishing_components
3
3
  version: !ruby/object:Gem::Version
4
- version: 38.1.1
4
+ version: 38.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GOV.UK Dev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-04-30 00:00:00.000000000 Z
11
+ date: 2024-05-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: govuk_app_config
@@ -373,6 +373,7 @@ files:
373
373
  - app/assets/images/govuk_publishing_components/crests/hmrc_crest_18px_x2.png
374
374
  - app/assets/images/govuk_publishing_components/crests/ho_crest_18px_x2.png
375
375
  - app/assets/images/govuk_publishing_components/crests/mod_crest_18px_x2.png
376
+ - app/assets/images/govuk_publishing_components/crests/no10_crest_18px_x2.png
376
377
  - app/assets/images/govuk_publishing_components/crests/org_crest_18px_x2.png
377
378
  - app/assets/images/govuk_publishing_components/crests/portcullis_18px_x2.png
378
379
  - app/assets/images/govuk_publishing_components/crests/so_crest_18px_x2.png
@@ -1599,7 +1600,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
1599
1600
  - !ruby/object:Gem::Version
1600
1601
  version: '0'
1601
1602
  requirements: []
1602
- rubygems_version: 3.5.9
1603
+ rubygems_version: 3.5.10
1603
1604
  signing_key:
1604
1605
  specification_version: 4
1605
1606
  summary: A gem to document components in GOV.UK frontend applications