govuk_publishing_components 38.1.1 → 38.2.0

Sign up to get free protection for your applications and to get access to all the features.
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