govuk_tech_docs 3.2.0 → 3.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.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/publish.yaml +1 -1
  3. data/CHANGELOG.md +26 -7
  4. data/README.md +2 -2
  5. data/example/source/code.html.md +3 -26
  6. data/lib/govuk_tech_docs/contribution_banner.rb +1 -1
  7. data/lib/govuk_tech_docs/tech_docs_html_renderer.rb +3 -3
  8. data/lib/govuk_tech_docs/version.rb +1 -1
  9. data/lib/source/layouts/core.erb +1 -1
  10. data/node_modules/govuk-frontend/govuk/all.js +1548 -311
  11. data/node_modules/govuk-frontend/govuk/common/closest-attribute-value.js +70 -0
  12. data/node_modules/govuk-frontend/govuk/common/index.js +172 -0
  13. data/node_modules/govuk-frontend/govuk/common/normalise-dataset.js +373 -0
  14. data/node_modules/govuk-frontend/govuk/common.js +138 -3
  15. data/node_modules/govuk-frontend/govuk/components/_all.scss +1 -0
  16. data/node_modules/govuk-frontend/govuk/components/accordion/_index.scss +5 -6
  17. data/node_modules/govuk-frontend/govuk/components/accordion/accordion.js +754 -36
  18. data/node_modules/govuk-frontend/govuk/components/breadcrumbs/_index.scss +0 -2
  19. data/node_modules/govuk-frontend/govuk/components/button/_index.scss +29 -21
  20. data/node_modules/govuk-frontend/govuk/components/button/button.js +365 -107
  21. data/node_modules/govuk-frontend/govuk/components/character-count/_index.scss +9 -0
  22. data/node_modules/govuk-frontend/govuk/components/character-count/character-count.js +1092 -109
  23. data/node_modules/govuk-frontend/govuk/components/checkboxes/_index.scss +3 -2
  24. data/node_modules/govuk-frontend/govuk/components/checkboxes/checkboxes.js +30 -2
  25. data/node_modules/govuk-frontend/govuk/components/details/details.js +51 -33
  26. data/node_modules/govuk-frontend/govuk/components/error-summary/error-summary.js +289 -6
  27. data/node_modules/govuk-frontend/govuk/components/footer/_index.scss +13 -23
  28. data/node_modules/govuk-frontend/govuk/components/header/_index.scss +30 -24
  29. data/node_modules/govuk-frontend/govuk/components/header/header.js +59 -11
  30. data/node_modules/govuk-frontend/govuk/components/input/_index.scss +13 -23
  31. data/node_modules/govuk-frontend/govuk/components/notification-banner/notification-banner.js +252 -2
  32. data/node_modules/govuk-frontend/govuk/components/pagination/_index.scss +247 -0
  33. data/node_modules/govuk-frontend/govuk/components/pagination/_pagination.scss +2 -0
  34. data/node_modules/govuk-frontend/govuk/components/panel/_index.scss +1 -1
  35. data/node_modules/govuk-frontend/govuk/components/radios/_index.scss +5 -12
  36. data/node_modules/govuk-frontend/govuk/components/radios/radios.js +30 -2
  37. data/node_modules/govuk-frontend/govuk/components/select/_index.scss +11 -0
  38. data/node_modules/govuk-frontend/govuk/components/skip-link/_index.scss +1 -3
  39. data/node_modules/govuk-frontend/govuk/components/skip-link/skip-link.js +10 -4
  40. data/node_modules/govuk-frontend/govuk/components/summary-list/_index.scss +45 -13
  41. data/node_modules/govuk-frontend/govuk/components/table/_index.scss +1 -1
  42. data/node_modules/govuk-frontend/govuk/components/tabs/tabs.js +28 -0
  43. data/node_modules/govuk-frontend/govuk/core/_section-break.scss +1 -1
  44. data/node_modules/govuk-frontend/govuk/helpers/_colour.scss +5 -5
  45. data/node_modules/govuk-frontend/govuk/helpers/_focused.scss +5 -0
  46. data/node_modules/govuk-frontend/govuk/helpers/_links.scss +13 -11
  47. data/node_modules/govuk-frontend/govuk/helpers/_media-queries.scss +2 -2
  48. data/node_modules/govuk-frontend/govuk/helpers/_shape-arrow.scss +1 -1
  49. data/node_modules/govuk-frontend/govuk/helpers/_spacing.scss +3 -3
  50. data/node_modules/govuk-frontend/govuk/helpers/_typography.scss +16 -9
  51. data/node_modules/govuk-frontend/govuk/i18n.js +390 -0
  52. data/node_modules/govuk-frontend/govuk/objects/_button-group.scss +10 -26
  53. data/node_modules/govuk-frontend/govuk/objects/_template.scss +1 -1
  54. data/node_modules/govuk-frontend/govuk/objects/_width-container.scss +0 -4
  55. data/node_modules/govuk-frontend/govuk/overrides/_spacing.scss +56 -12
  56. data/node_modules/govuk-frontend/govuk/settings/_all.scss +1 -0
  57. data/node_modules/govuk-frontend/govuk/settings/_colours-palette.scss +12 -0
  58. data/node_modules/govuk-frontend/govuk/settings/_compatibility.scss +26 -0
  59. data/node_modules/govuk-frontend/govuk/settings/_spacing.scss +4 -8
  60. data/node_modules/govuk-frontend/govuk/settings/_typography-font.scss +23 -0
  61. data/node_modules/govuk-frontend/govuk/settings/_typography-responsive.scss +12 -0
  62. data/node_modules/govuk-frontend/govuk/settings/_warnings.scss +53 -0
  63. data/node_modules/govuk-frontend/govuk/tools/_compatibility.scss +20 -6
  64. data/node_modules/govuk-frontend/govuk/tools/_exports.scss +1 -1
  65. data/node_modules/govuk-frontend/govuk/tools/_font-url.scss +1 -1
  66. data/node_modules/govuk-frontend/govuk/tools/_image-url.scss +1 -1
  67. data/node_modules/govuk-frontend/govuk/tools/_px-to-em.scss +2 -2
  68. data/node_modules/govuk-frontend/govuk/tools/_px-to-rem.scss +1 -1
  69. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Date/now.js +21 -0
  70. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/dataset.js +300 -0
  71. data/node_modules/govuk-frontend/govuk/vendor/polyfills/String/prototype/trim.js +21 -0
  72. data/node_modules/govuk-frontend/govuk-prototype-kit/init.js +7 -0
  73. data/node_modules/govuk-frontend/govuk-prototype-kit/init.scss +12 -0
  74. data/package-lock.json +12 -12
  75. data/package.json +1 -1
  76. metadata +17 -5
@@ -58,7 +58,7 @@
58
58
  position: relative;
59
59
  top: -1px;
60
60
  margin-right: 1px;
61
- fill: currentColor;
61
+ fill: currentcolor;
62
62
  vertical-align: top;
63
63
  }
64
64
 
@@ -91,7 +91,7 @@
91
91
  text-decoration: underline;
92
92
  text-decoration-thickness: $govuk-header-link-underline-thickness;
93
93
 
94
- @if ($govuk-link-underline-offset) {
94
+ @if $govuk-link-underline-offset {
95
95
  text-underline-offset: $govuk-link-underline-offset;
96
96
  }
97
97
  }
@@ -111,6 +111,16 @@
111
111
  font-size: 30px; // We don't have a mixin that produces 30px font size
112
112
  line-height: 1;
113
113
 
114
+ @include govuk-media-query($from: tablet) {
115
+ display: inline;
116
+
117
+ &:focus {
118
+ // Replicate the focus box shadow but without the -2px y-offset of the first yellow shadow
119
+ // This is to stop the logo getting cut off by the box shadow when focused on above a product name
120
+ box-shadow: 0 0 $govuk-focus-colour;
121
+ }
122
+ }
123
+
114
124
  &:link,
115
125
  &:visited {
116
126
  text-decoration: none;
@@ -132,6 +142,9 @@
132
142
  }
133
143
  }
134
144
 
145
+ // The govuk-header__link--service-name class is deprecated - use
146
+ // govuk-header__service-name instead.
147
+ .govuk-header__service-name,
135
148
  .govuk-header__link--service-name {
136
149
  display: inline-block;
137
150
  margin-bottom: govuk-spacing(2);
@@ -180,7 +193,7 @@
180
193
  -webkit-text-decoration: solid underline $govuk-header-link-underline-thickness;
181
194
  text-decoration: solid underline $govuk-header-link-underline-thickness;
182
195
 
183
- @if ($govuk-link-underline-offset) {
196
+ @if $govuk-link-underline-offset {
184
197
  text-underline-offset: $govuk-link-underline-offset;
185
198
  }
186
199
  }
@@ -195,14 +208,21 @@
195
208
  margin-left: govuk-spacing(1);
196
209
  }
197
210
 
211
+ &[aria-expanded="true"]:after {
212
+ @include govuk-shape-arrow($direction: up, $base: 10px, $display: inline-block);
213
+ }
214
+
198
215
  @include govuk-media-query ($from: tablet) {
199
216
  top: govuk-spacing(3);
200
217
  }
201
- }
202
218
 
203
- .govuk-header__menu-button--open {
204
- &:after {
205
- @include govuk-shape-arrow($direction: up, $base: 10px, $display: inline-block);
219
+ .js-enabled & {
220
+ display: block;
221
+ }
222
+
223
+ &[hidden],
224
+ .js-enabled &[hidden] {
225
+ display: none;
206
226
  }
207
227
  }
208
228
 
@@ -217,25 +237,9 @@
217
237
  margin: 0;
218
238
  padding: 0;
219
239
  list-style: none;
220
- }
221
240
 
222
- .js-enabled {
223
- .govuk-header__menu-button {
224
- display: block;
225
- @include govuk-media-query ($from: desktop) {
226
- display: none;
227
- }
228
- }
229
-
230
- .govuk-header__navigation-list {
241
+ &[hidden] {
231
242
  display: none;
232
- @include govuk-media-query ($from: desktop) {
233
- display: block;
234
- }
235
- }
236
-
237
- .govuk-header__navigation-list--open {
238
- display: block;
239
243
  }
240
244
  }
241
245
 
@@ -247,6 +251,8 @@
247
251
  }
248
252
  }
249
253
 
254
+ // The govuk-header__navigation--no-service-name class is deprecated and will
255
+ // be removed in the next major release.
250
256
  .govuk-header__navigation--no-service-name {
251
257
  padding-top: govuk-spacing(7);
252
258
  }
@@ -1014,12 +1014,29 @@ if (detect) return
1014
1014
  })
1015
1015
  .call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
1016
1016
 
1017
+ /**
1018
+ * Header component
1019
+ *
1020
+ * @class
1021
+ * @param {HTMLElement} $module - HTML element to use for header
1022
+ */
1017
1023
  function Header ($module) {
1018
1024
  this.$module = $module;
1019
1025
  this.$menuButton = $module && $module.querySelector('.govuk-js-header-toggle');
1020
1026
  this.$menu = this.$menuButton && $module.querySelector(
1021
1027
  '#' + this.$menuButton.getAttribute('aria-controls')
1022
1028
  );
1029
+
1030
+ // Save the opened/closed state for the nav in memory so that we can
1031
+ // accurately maintain state when the screen is changed from small to
1032
+ // big and back to small
1033
+ this.menuIsOpen = false;
1034
+
1035
+ // A global const for storing a matchMedia instance which we'll use to
1036
+ // detect when a screen size change happens. We set this later during the
1037
+ // init function and rely on it being null if the feature isn't available
1038
+ // to initially apply hidden attributes
1039
+ this.mql = null;
1023
1040
  }
1024
1041
 
1025
1042
  /**
@@ -1027,27 +1044,58 @@ function Header ($module) {
1027
1044
  *
1028
1045
  * Check for the presence of the header, menu and menu button – if any are
1029
1046
  * missing then there's nothing to do so return early.
1047
+ * Feature sniff for and apply a matchMedia for desktop which will
1048
+ * trigger a state sync if the browser viewport moves between states. If
1049
+ * matchMedia isn't available, hide the menu button and present the "no js"
1050
+ * version of the menu to the user.
1030
1051
  */
1031
1052
  Header.prototype.init = function () {
1032
1053
  if (!this.$module || !this.$menuButton || !this.$menu) {
1033
1054
  return
1034
1055
  }
1035
1056
 
1036
- this.syncState(this.$menu.classList.contains('govuk-header__navigation-list--open'));
1037
- this.$menuButton.addEventListener('click', this.handleMenuButtonClick.bind(this));
1057
+ if ('matchMedia' in window) {
1058
+ // Set the matchMedia to the govuk-frontend desktop breakpoint
1059
+ this.mql = window.matchMedia('(min-width: 48.0625em)');
1060
+
1061
+ if ('addEventListener' in this.mql) {
1062
+ this.mql.addEventListener('change', this.syncState.bind(this));
1063
+ } else {
1064
+ // addListener is a deprecated function, however addEventListener
1065
+ // isn't supported by IE or Safari. We therefore add this in as
1066
+ // a fallback for those browsers
1067
+ this.mql.addListener(this.syncState.bind(this));
1068
+ }
1069
+
1070
+ this.syncState();
1071
+ this.$menuButton.addEventListener('click', this.handleMenuButtonClick.bind(this));
1072
+ } else {
1073
+ this.$menuButton.setAttribute('hidden', '');
1074
+ }
1038
1075
  };
1039
1076
 
1040
1077
  /**
1041
1078
  * Sync menu state
1042
1079
  *
1043
- * Sync the menu button class and the accessible state of the menu and the menu
1044
- * button with the visible state of the menu
1045
- *
1046
- * @param {boolean} isVisible Whether the menu is currently visible
1080
+ * Uses the global variable menuIsOpen to correctly set the accessible and
1081
+ * visual states of the menu and the menu button.
1082
+ * Additionally will force the menu to be visible and the menu button to be
1083
+ * hidden if the matchMedia is triggered to desktop.
1047
1084
  */
1048
- Header.prototype.syncState = function (isVisible) {
1049
- this.$menuButton.classList.toggle('govuk-header__menu-button--open', isVisible);
1050
- this.$menuButton.setAttribute('aria-expanded', isVisible);
1085
+ Header.prototype.syncState = function () {
1086
+ if (this.mql.matches) {
1087
+ this.$menu.removeAttribute('hidden');
1088
+ this.$menuButton.setAttribute('hidden', '');
1089
+ } else {
1090
+ this.$menuButton.removeAttribute('hidden');
1091
+ this.$menuButton.setAttribute('aria-expanded', this.menuIsOpen);
1092
+
1093
+ if (this.menuIsOpen) {
1094
+ this.$menu.removeAttribute('hidden');
1095
+ } else {
1096
+ this.$menu.setAttribute('hidden', '');
1097
+ }
1098
+ }
1051
1099
  };
1052
1100
 
1053
1101
  /**
@@ -1057,8 +1105,8 @@ Header.prototype.syncState = function (isVisible) {
1057
1105
  * sync the accessibility state and menu button state
1058
1106
  */
1059
1107
  Header.prototype.handleMenuButtonClick = function () {
1060
- var isVisible = this.$menu.classList.toggle('govuk-header__navigation-list--open');
1061
- this.syncState(isVisible);
1108
+ this.menuIsOpen = !this.menuIsOpen;
1109
+ this.syncState();
1062
1110
  };
1063
1111
 
1064
1112
  return Header;
@@ -22,7 +22,6 @@
22
22
 
23
23
  // Disable inner shadow and remove rounded corners
24
24
  -webkit-appearance: none;
25
- -moz-appearance: none;
26
25
  appearance: none;
27
26
 
28
27
  &:focus {
@@ -61,49 +60,44 @@
61
60
  }
62
61
  }
63
62
 
64
- // The ex measurements are based on the number of W's that can fit inside the input
65
- // Extra space is left on the right hand side to allow for the Safari prefill icon
66
- // Linear regression estimation based on visual tests: y = 1.76 + 1.81x
63
+ // em measurements are based on the point size of the typeface
64
+ // Extra space is added on the right hand side to allow for the Safari prefill icon
67
65
 
68
66
  .govuk-input--width-30 {
69
- max-width: 56ex + 3ex;
67
+ max-width: 29.5em;
70
68
  }
71
69
 
72
70
  .govuk-input--width-20 {
73
- max-width: 38ex + 3ex;
71
+ max-width: 20.5em;
74
72
  }
75
73
 
76
74
  .govuk-input--width-10 {
77
- max-width: 20ex + 3ex;
75
+ max-width: 11.5em;
78
76
  }
79
77
 
80
78
  .govuk-input--width-5 {
81
- max-width: 10.8ex;
79
+ max-width: 5.5em;
82
80
  }
83
81
 
84
82
  .govuk-input--width-4 {
85
- max-width: 9ex;
83
+ max-width: 4.5em;
86
84
  }
87
85
 
88
86
  .govuk-input--width-3 {
89
- max-width: 7.2ex;
87
+ max-width: 3.75em;
90
88
  }
91
89
 
92
90
  .govuk-input--width-2 {
93
- max-width: 5.4ex;
91
+ max-width: 2.75em;
94
92
  }
95
93
 
96
94
  .govuk-input__wrapper {
97
- display: -webkit-box;
98
- display: -webkit-flex;
99
95
  display: -ms-flexbox;
100
96
  display: flex;
101
97
 
102
98
  .govuk-input {
103
- -webkit-box-flex: 0;
104
- -webkit-flex: 0 1 auto;
105
- -ms-flex: 0 1 auto;
106
- flex: 0 1 auto;
99
+ -ms-flex: 0 1 auto;
100
+ flex: 0 1 auto;
107
101
  }
108
102
 
109
103
  .govuk-input:focus {
@@ -150,13 +144,9 @@
150
144
  // Emphasise non-editable status of prefixes and suffixes
151
145
  cursor: default;
152
146
 
153
- -webkit-box-flex: 0;
147
+ -ms-flex: 0 0 auto;
154
148
 
155
- -webkit-flex: 0 0 auto;
156
-
157
- -ms-flex: 0 0 auto;
158
-
159
- flex: 0 0 auto;
149
+ flex: 0 0 auto;
160
150
 
161
151
  // Split prefix/suffix onto separate lines on narrow screens
162
152
  @include govuk-media-query($until: mobile) {
@@ -501,8 +501,247 @@ if (detect) return
501
501
  })
502
502
  .call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
503
503
 
504
- function NotificationBanner ($module) {
504
+ /**
505
+ * Common helpers which do not require polyfill.
506
+ *
507
+ * IMPORTANT: If a helper require a polyfill, please isolate it in its own module
508
+ * so that the polyfill can be properly tree-shaken and does not burden
509
+ * the components that do not need that helper
510
+ *
511
+ * @module common/index
512
+ */
513
+
514
+ /**
515
+ * Config flattening function
516
+ *
517
+ * Takes any number of objects, flattens them into namespaced key-value pairs,
518
+ * (e.g. {'i18n.showSection': 'Show section'}) and combines them together, with
519
+ * greatest priority on the LAST item passed in.
520
+ *
521
+ * @returns {object} A flattened object of key-value pairs.
522
+ */
523
+ function mergeConfigs (/* configObject1, configObject2, ...configObjects */) {
524
+ /**
525
+ * Function to take nested objects and flatten them to a dot-separated keyed
526
+ * object. Doing this means we don't need to do any deep/recursive merging of
527
+ * each of our objects, nor transform our dataset from a flat list into a
528
+ * nested object.
529
+ *
530
+ * @param {object} configObject - Deeply nested object
531
+ * @returns {object} Flattened object with dot-separated keys
532
+ */
533
+ var flattenObject = function (configObject) {
534
+ // Prepare an empty return object
535
+ var flattenedObject = {};
536
+
537
+ // Our flattening function, this is called recursively for each level of
538
+ // depth in the object. At each level we prepend the previous level names to
539
+ // the key using `prefix`.
540
+ var flattenLoop = function (obj, prefix) {
541
+ // Loop through keys...
542
+ for (var key in obj) {
543
+ // Check to see if this is a prototypical key/value,
544
+ // if it is, skip it.
545
+ if (!Object.prototype.hasOwnProperty.call(obj, key)) {
546
+ continue
547
+ }
548
+ var value = obj[key];
549
+ var prefixedKey = prefix ? prefix + '.' + key : key;
550
+ if (typeof value === 'object') {
551
+ // If the value is a nested object, recurse over that too
552
+ flattenLoop(value, prefixedKey);
553
+ } else {
554
+ // Otherwise, add this value to our return object
555
+ flattenedObject[prefixedKey] = value;
556
+ }
557
+ }
558
+ };
559
+
560
+ // Kick off the recursive loop
561
+ flattenLoop(configObject);
562
+ return flattenedObject
563
+ };
564
+
565
+ // Start with an empty object as our base
566
+ var formattedConfigObject = {};
567
+
568
+ // Loop through each of the remaining passed objects and push their keys
569
+ // one-by-one into configObject. Any duplicate keys will override the existing
570
+ // key with the new value.
571
+ for (var i = 0; i < arguments.length; i++) {
572
+ var obj = flattenObject(arguments[i]);
573
+ for (var key in obj) {
574
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
575
+ formattedConfigObject[key] = obj[key];
576
+ }
577
+ }
578
+ }
579
+
580
+ return formattedConfigObject
581
+ }
582
+
583
+ /**
584
+ * @callback nodeListIterator
585
+ * @param {Element} value - The current node being iterated on
586
+ * @param {number} index - The current index in the iteration
587
+ * @param {NodeListOf<Element>} nodes - NodeList from querySelectorAll()
588
+ * @returns {undefined}
589
+ */
590
+
591
+ (function(undefined) {
592
+
593
+ // Detection from https://raw.githubusercontent.com/Financial-Times/polyfill-library/13cf7c340974d128d557580b5e2dafcd1b1192d1/polyfills/Element/prototype/dataset/detect.js
594
+ var detect = (function(){
595
+ if (!document.documentElement.dataset) {
596
+ return false;
597
+ }
598
+ var el = document.createElement('div');
599
+ el.setAttribute("data-a-b", "c");
600
+ return el.dataset && el.dataset.aB == "c";
601
+ }());
602
+
603
+ if (detect) return
604
+
605
+ // Polyfill derived from https://raw.githubusercontent.com/Financial-Times/polyfill-library/13cf7c340974d128d557580b5e2dafcd1b1192d1/polyfills/Element/prototype/dataset/polyfill.js
606
+ Object.defineProperty(Element.prototype, 'dataset', {
607
+ get: function() {
608
+ var element = this;
609
+ var attributes = this.attributes;
610
+ var map = {};
611
+
612
+ for (var i = 0; i < attributes.length; i++) {
613
+ var attribute = attributes[i];
614
+
615
+ // This regex has been edited from the original polyfill, to add
616
+ // support for period (.) separators in data-* attribute names. These
617
+ // are allowed in the HTML spec, but were not covered by the original
618
+ // polyfill's regex. We use periods in our i18n implementation.
619
+ if (attribute && attribute.name && (/^data-\w[.\w-]*$/).test(attribute.name)) {
620
+ var name = attribute.name;
621
+ var value = attribute.value;
622
+
623
+ var propName = name.substr(5).replace(/-./g, function (prop) {
624
+ return prop.charAt(1).toUpperCase();
625
+ });
626
+
627
+ // If this browser supports __defineGetter__ and __defineSetter__,
628
+ // continue using defineProperty. If not (like IE 8 and below), we use
629
+ // a hacky fallback which at least gives an object in the right format
630
+ if ('__defineGetter__' in Object.prototype && '__defineSetter__' in Object.prototype) {
631
+ Object.defineProperty(map, propName, {
632
+ enumerable: true,
633
+ get: function() {
634
+ return this.value;
635
+ }.bind({value: value || ''}),
636
+ set: function setter(name, value) {
637
+ if (typeof value !== 'undefined') {
638
+ this.setAttribute(name, value);
639
+ } else {
640
+ this.removeAttribute(name);
641
+ }
642
+ }.bind(element, name)
643
+ });
644
+ } else {
645
+ map[propName] = value;
646
+ }
647
+
648
+ }
649
+ }
650
+
651
+ return map;
652
+ }
653
+ });
654
+
655
+ }).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
656
+
657
+ (function(undefined) {
658
+
659
+ // Detection from https://github.com/mdn/content/blob/cf607d68522cd35ee7670782d3ee3a361eaef2e4/files/en-us/web/javascript/reference/global_objects/string/trim/index.md#polyfill
660
+ var detect = ('trim' in String.prototype);
661
+
662
+ if (detect) return
663
+
664
+ // Polyfill from https://github.com/mdn/content/blob/cf607d68522cd35ee7670782d3ee3a361eaef2e4/files/en-us/web/javascript/reference/global_objects/string/trim/index.md#polyfill
665
+ String.prototype.trim = function () {
666
+ return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
667
+ };
668
+
669
+ }).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
670
+
671
+ /**
672
+ * Normalise string
673
+ *
674
+ * 'If it looks like a duck, and it quacks like a duck…' 🦆
675
+ *
676
+ * If the passed value looks like a boolean or a number, convert it to a boolean
677
+ * or number.
678
+ *
679
+ * Designed to be used to convert config passed via data attributes (which are
680
+ * always strings) into something sensible.
681
+ *
682
+ * @param {string} value - The value to normalise
683
+ * @returns {string | boolean | number | undefined} Normalised data
684
+ */
685
+ function normaliseString (value) {
686
+ if (typeof value !== 'string') {
687
+ return value
688
+ }
689
+
690
+ var trimmedValue = value.trim();
691
+
692
+ if (trimmedValue === 'true') {
693
+ return true
694
+ }
695
+
696
+ if (trimmedValue === 'false') {
697
+ return false
698
+ }
699
+
700
+ // Empty / whitespace-only strings are considered finite so we need to check
701
+ // the length of the trimmed string as well
702
+ if (trimmedValue.length > 0 && isFinite(trimmedValue)) {
703
+ return Number(trimmedValue)
704
+ }
705
+
706
+ return value
707
+ }
708
+
709
+ /**
710
+ * Normalise dataset
711
+ *
712
+ * Loop over an object and normalise each value using normaliseData function
713
+ *
714
+ * @param {DOMStringMap} dataset - HTML element dataset
715
+ * @returns {Object<string, string | boolean | number | undefined>} Normalised dataset
716
+ */
717
+ function normaliseDataset (dataset) {
718
+ var out = {};
719
+
720
+ for (var key in dataset) {
721
+ out[key] = normaliseString(dataset[key]);
722
+ }
723
+
724
+ return out
725
+ }
726
+
727
+ /**
728
+ * Notification Banner component
729
+ *
730
+ * @class
731
+ * @param {HTMLElement} $module - HTML element to use for notification banner
732
+ * @param {NotificationBannerConfig} config - Notification banner config
733
+ */
734
+ function NotificationBanner ($module, config) {
505
735
  this.$module = $module;
736
+
737
+ var defaultConfig = {
738
+ disableAutoFocus: false
739
+ };
740
+ this.config = mergeConfigs(
741
+ defaultConfig,
742
+ config || {},
743
+ normaliseDataset($module.dataset)
744
+ );
506
745
  }
507
746
 
508
747
  /**
@@ -531,7 +770,7 @@ NotificationBanner.prototype.init = function () {
531
770
  NotificationBanner.prototype.setFocus = function () {
532
771
  var $module = this.$module;
533
772
 
534
- if ($module.getAttribute('data-disable-auto-focus') === 'true') {
773
+ if (this.config.disableAutoFocus) {
535
774
  return
536
775
  }
537
776
 
@@ -553,6 +792,17 @@ NotificationBanner.prototype.setFocus = function () {
553
792
  $module.focus();
554
793
  };
555
794
 
795
+ /**
796
+ * Notification banner config
797
+ *
798
+ * @typedef {object} NotificationBannerConfig
799
+ * @property {boolean} [disableAutoFocus = false] -
800
+ * If set to `true` the notification banner will not be focussed when the page
801
+ * loads. This only applies if the component has a `role` of `alert` – in
802
+ * other cases the component will not be focused on page load, regardless of
803
+ * this option.
804
+ */
805
+
556
806
  return NotificationBanner;
557
807
 
558
808
  })));