dxw_govuk_frontend_rails 3.6.0 → 3.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +1 -0
  3. data/Gemfile.lock +3 -2
  4. data/README.md +67 -43
  5. data/lib/dxw_govuk_frontend_rails/version.rb +1 -1
  6. data/package-lock.json +3 -3
  7. data/package.json +1 -1
  8. data/vendor/assets/javascripts/govuk_frontend_rails.js +245 -91
  9. data/vendor/assets/stylesheets/_base.scss +3 -0
  10. data/vendor/assets/stylesheets/all.scss +1 -3
  11. data/vendor/assets/stylesheets/components/_all.scss +33 -29
  12. data/vendor/assets/stylesheets/components/accordion/_accordion.scss +2 -208
  13. data/vendor/assets/stylesheets/components/accordion/_index.scss +206 -0
  14. data/vendor/assets/stylesheets/components/back-link/_back-link.scss +2 -65
  15. data/vendor/assets/stylesheets/components/back-link/_index.scss +110 -0
  16. data/vendor/assets/stylesheets/components/breadcrumbs/_breadcrumbs.scss +2 -118
  17. data/vendor/assets/stylesheets/components/breadcrumbs/_index.scss +137 -0
  18. data/vendor/assets/stylesheets/components/button/_button.scss +2 -284
  19. data/vendor/assets/stylesheets/components/button/_index.scss +282 -0
  20. data/vendor/assets/stylesheets/components/character-count/_character-count.scss +2 -31
  21. data/vendor/assets/stylesheets/components/character-count/_index.scss +25 -0
  22. data/vendor/assets/stylesheets/components/checkboxes/_checkboxes.scss +2 -308
  23. data/vendor/assets/stylesheets/components/checkboxes/_index.scss +305 -0
  24. data/vendor/assets/stylesheets/components/cookie-banner/_cookie-banner.scss +2 -0
  25. data/vendor/assets/stylesheets/components/cookie-banner/_index.scss +51 -0
  26. data/vendor/assets/stylesheets/components/date-input/_date-input.scss +2 -30
  27. data/vendor/assets/stylesheets/components/date-input/_index.scss +26 -0
  28. data/vendor/assets/stylesheets/components/details/_details.scss +2 -88
  29. data/vendor/assets/stylesheets/components/details/_index.scss +83 -0
  30. data/vendor/assets/stylesheets/components/error-message/_error-message.scss +2 -15
  31. data/vendor/assets/stylesheets/components/error-message/_index.scss +11 -0
  32. data/vendor/assets/stylesheets/components/error-summary/_error-summary.scss +2 -59
  33. data/vendor/assets/stylesheets/components/error-summary/_index.scss +43 -0
  34. data/vendor/assets/stylesheets/components/fieldset/_fieldset.scss +2 -68
  35. data/vendor/assets/stylesheets/components/fieldset/_index.scss +64 -0
  36. data/vendor/assets/stylesheets/components/file-upload/_file-upload.scss +2 -81
  37. data/vendor/assets/stylesheets/components/file-upload/_index.scss +50 -0
  38. data/vendor/assets/stylesheets/components/footer/_footer.scss +2 -244
  39. data/vendor/assets/stylesheets/components/footer/_index.scss +234 -0
  40. data/vendor/assets/stylesheets/components/header/_header.scss +2 -318
  41. data/vendor/assets/stylesheets/components/header/_index.scss +309 -0
  42. data/vendor/assets/stylesheets/components/hint/_hint.scss +2 -50
  43. data/vendor/assets/stylesheets/components/hint/_index.scss +44 -0
  44. data/vendor/assets/stylesheets/components/input/_index.scss +187 -0
  45. data/vendor/assets/stylesheets/components/input/_input.scss +2 -103
  46. data/vendor/assets/stylesheets/components/inset-text/_index.scss +24 -0
  47. data/vendor/assets/stylesheets/components/inset-text/_inset-text.scss +2 -28
  48. data/vendor/assets/stylesheets/components/label/_index.scss +41 -0
  49. data/vendor/assets/stylesheets/components/label/_label.scss +2 -45
  50. data/vendor/assets/stylesheets/components/notification-banner/_index.scss +89 -0
  51. data/vendor/assets/stylesheets/components/notification-banner/_notification-banner.scss +2 -0
  52. data/vendor/assets/stylesheets/components/panel/_index.scss +44 -0
  53. data/vendor/assets/stylesheets/components/panel/_panel.scss +2 -44
  54. data/vendor/assets/stylesheets/components/phase-banner/_index.scss +27 -0
  55. data/vendor/assets/stylesheets/components/phase-banner/_phase-banner.scss +2 -31
  56. data/vendor/assets/stylesheets/components/radios/_index.scss +342 -0
  57. data/vendor/assets/stylesheets/components/radios/_radios.scss +2 -346
  58. data/vendor/assets/stylesheets/components/select/_index.scss +49 -0
  59. data/vendor/assets/stylesheets/components/select/_select.scss +2 -57
  60. data/vendor/assets/stylesheets/components/skip-link/_index.scss +34 -0
  61. data/vendor/assets/stylesheets/components/skip-link/_skip-link.scss +2 -37
  62. data/vendor/assets/stylesheets/components/summary-list/_index.scss +145 -0
  63. data/vendor/assets/stylesheets/components/summary-list/_summary-list.scss +2 -157
  64. data/vendor/assets/stylesheets/components/table/_index.scss +71 -0
  65. data/vendor/assets/stylesheets/components/table/_table.scss +2 -54
  66. data/vendor/assets/stylesheets/components/tabs/_index.scss +135 -0
  67. data/vendor/assets/stylesheets/components/tabs/_tabs.scss +2 -142
  68. data/vendor/assets/stylesheets/components/tag/_index.scss +86 -0
  69. data/vendor/assets/stylesheets/components/tag/_tag.scss +2 -91
  70. data/vendor/assets/stylesheets/components/textarea/_index.scss +47 -0
  71. data/vendor/assets/stylesheets/components/textarea/_textarea.scss +2 -55
  72. data/vendor/assets/stylesheets/components/warning-text/_index.scss +57 -0
  73. data/vendor/assets/stylesheets/components/warning-text/_warning-text.scss +2 -60
  74. data/vendor/assets/stylesheets/core/_global-styles.scss +5 -3
  75. data/vendor/assets/stylesheets/core/_links.scss +5 -3
  76. data/vendor/assets/stylesheets/core/_lists.scss +17 -3
  77. data/vendor/assets/stylesheets/core/_section-break.scss +5 -3
  78. data/vendor/assets/stylesheets/core/_template.scss +5 -3
  79. data/vendor/assets/stylesheets/core/_typography.scss +5 -3
  80. data/vendor/assets/stylesheets/helpers/_clearfix.scss +1 -1
  81. data/vendor/assets/stylesheets/helpers/_colour.scss +1 -1
  82. data/vendor/assets/stylesheets/helpers/_device-pixels.scss +3 -3
  83. data/vendor/assets/stylesheets/helpers/_focused.scss +1 -1
  84. data/vendor/assets/stylesheets/helpers/_font-faces.scss +9 -11
  85. data/vendor/assets/stylesheets/helpers/_grid.scss +2 -1
  86. data/vendor/assets/stylesheets/helpers/_links.scss +96 -4
  87. data/vendor/assets/stylesheets/helpers/_media-queries.scss +2 -6
  88. data/vendor/assets/stylesheets/helpers/_shape-arrow.scss +1 -1
  89. data/vendor/assets/stylesheets/helpers/_spacing.scss +3 -2
  90. data/vendor/assets/stylesheets/helpers/_typography.scss +8 -7
  91. data/vendor/assets/stylesheets/helpers/_visually-hidden.scss +1 -1
  92. data/vendor/assets/stylesheets/objects/_all.scss +1 -0
  93. data/vendor/assets/stylesheets/objects/_button-group.scss +94 -0
  94. data/vendor/assets/stylesheets/objects/_form-group.scss +1 -4
  95. data/vendor/assets/stylesheets/objects/_grid.scss +3 -6
  96. data/vendor/assets/stylesheets/objects/_main-wrapper.scss +5 -4
  97. data/vendor/assets/stylesheets/objects/_width-container.scss +2 -4
  98. data/vendor/assets/stylesheets/overrides/_display.scss +6 -4
  99. data/vendor/assets/stylesheets/overrides/_spacing.scss +5 -3
  100. data/vendor/assets/stylesheets/overrides/_typography.scss +5 -3
  101. data/vendor/assets/stylesheets/overrides/_width.scss +6 -3
  102. data/vendor/assets/stylesheets/settings/_colours-applied.scss +11 -5
  103. data/vendor/assets/stylesheets/settings/_colours-organisations.scss +3 -0
  104. data/vendor/assets/stylesheets/settings/_colours-palette.scss +42 -35
  105. data/vendor/assets/stylesheets/settings/_compatibility.scss +0 -1
  106. data/vendor/assets/stylesheets/settings/_ie8.scss +1 -1
  107. data/vendor/assets/stylesheets/settings/_measurements.scss +4 -5
  108. data/vendor/assets/stylesheets/settings/_typography-font-families.scss +2 -2
  109. data/vendor/assets/stylesheets/settings/_typography-font.scss +14 -5
  110. data/vendor/assets/stylesheets/settings/_typography-responsive.scss +6 -2
  111. data/vendor/assets/stylesheets/tools/_compatibility.scss +1 -1
  112. data/vendor/assets/stylesheets/tools/_font-url.scss +1 -4
  113. data/vendor/assets/stylesheets/tools/_ie8.scss +1 -1
  114. data/vendor/assets/stylesheets/tools/_image-url.scss +1 -4
  115. data/vendor/assets/stylesheets/tools/_px-to-em.scss +1 -1
  116. data/vendor/assets/stylesheets/tools/_px-to-rem.scss +1 -1
  117. data/vendor/assets/stylesheets/utilities/_visually-hidden.scss +0 -1
  118. data/vendor/assets/stylesheets/vendor/_sass-mq.scss +0 -4
  119. metadata +39 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: de482bfdfa09a6ce5fa71d92b1e3aeba8067157a06e3da22bc86b34713b7c9b8
4
- data.tar.gz: f3e866b3cb02ec7ef83d934beddb3ee3a84d17cf191228051301a5301d551f10
3
+ metadata.gz: 53ce04369f5585d9d7e25d250a3e346746bf5b8541387d23b9debfa85996d034
4
+ data.tar.gz: 50c7eedd8c1c126a6f55873bdb8e9955747de10048909861a086c81b2ae0bef4
5
5
  SHA512:
6
- metadata.gz: 9e1a93086b6f6ed875cb2d63452496cd20ba10c82d6b3f344ea3aa8bc82abf82ce07c5b76e473a864065066cf3cdd852425973c6105f47ec2991e71bc85f5e4e
7
- data.tar.gz: b45b9b5aa0bcc6f675d07438cfb9092dec30f836fe95f2ac594b74034c0ec85ff277dcf0478fb2f133398ccf30a0fc47670769e75ed997737984bdcb899a43a9
6
+ metadata.gz: 82eba76e269590ae83f405a02c03c76177d9d9bf35610e9f4a8b34a4e3550e5ab63f7d66c919744b5b404e08568af98924085f2c18fb17c1a38e8d33e96c958b
7
+ data.tar.gz: 68f8c51f435994ece6f8d8e219e3c369cfa4579e42cc2fb5f55d272cfd4504563c44f742c23595a7673d74ea6bdc0d05c34105d9d0a38054e31001468458f403
data/CONTRIBUTING.md ADDED
@@ -0,0 +1 @@
1
+ The [dxw standard contributing guide](https://github.com/dxw/.github/blob/main/CONTRIBUTING.md) applies for this repository.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- dxw_govuk_frontend_rails (3.6.0)
4
+ dxw_govuk_frontend_rails (3.11.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -10,6 +10,7 @@ GEM
10
10
 
11
11
  PLATFORMS
12
12
  ruby
13
+ x86_64-darwin-19
13
14
 
14
15
  DEPENDENCIES
15
16
  bundler (~> 2.0)
@@ -17,4 +18,4 @@ DEPENDENCIES
17
18
  rake (~> 13.0)
18
19
 
19
20
  BUNDLED WITH
20
- 2.0.2
21
+ 2.2.1
data/README.md CHANGED
@@ -1,26 +1,39 @@
1
- # GOVUK Frontend for Rails
1
+ # GOV.UK Frontend for Rails
2
2
 
3
- Adds the GOVUK Frontend for Rails using the asset pipeline.
3
+ ## Read this first
4
+
5
+ Although this gem makes it super easy to add the GOV.UK Frontend to your Rails
6
+ applications, we would recommend using it as a guide to add the GOV.UK Frontend
7
+ yourself.
8
+
9
+ ## About
10
+
11
+ Adds the GOV.UK Frontend for Rails using the asset pipeline.
4
12
 
5
13
  https://github.com/alphagov/govuk-frontend
6
14
 
7
15
  ## Breaking Changes in v3.0.0
8
16
 
9
- This release of the GOVUK Frontend has breaking changes! Do not update to this version until you have read and understood:
17
+ This release of the GOV.UK Frontend has breaking changes! Do not update to this
18
+ version until you have read and understood:
10
19
 
11
- You will not be required to updated any file paths with govuk as we take care of that for you, all others changes will need to be managed:
20
+ You will not be required to updated any file paths with `govuk` as we take care
21
+ of that for you, all others changes will need to be managed:
12
22
 
13
23
  https://github.com/alphagov/govuk-frontend/releases/tag/v3.0.0
14
24
 
15
25
  ## Limitations
16
26
 
17
- This gem does not include the ie8 version of GOVUK Frontend, as the service standard no longer requires it:
27
+ This gem does not include the `ie8` version of GOV.UK Frontend, as the service
28
+ standard no longer requires support for Internet Explorer versions less than 11:
18
29
 
19
30
  https://www.gov.uk/service-manual/technology/designing-for-different-browsers-and-devices
20
31
 
21
- This gem and its documentation cannot tell you how to use the GOVUK effectively, see the GOVUK Design System documentation for that:
32
+ This gem and its documentation cannot tell you how to use the GOV.UK patterns
33
+ effectively, see the GOV.UK Design System documentation for that:
22
34
 
23
35
  https://design-system.service.gov.uk/
36
+
24
37
  ## Installation
25
38
 
26
39
  Add this line to your application's Gemfile:
@@ -31,58 +44,69 @@ gem 'dxw_govuk_frontend_rails'
31
44
 
32
45
  And then execute:
33
46
 
34
- $ bundle
47
+ ```
48
+ $ bundle
49
+ ```
35
50
 
36
51
  ## Usage
37
- Your Rails app will need to have sass enabled.
52
+
53
+ Your Rails app will need to have SCSS enabled.
54
+
55
+ **IMPORTANT: You must not use this library without changes if the service you
56
+ are building is not on GOV.UK. Read and understand the guidance here:**
57
+
58
+ https://www.gov.uk/service-manual/design/making-your-service-look-like-govuk#if-your-service-isnt-on-govuk
38
59
 
39
60
  ### Basic usage
40
61
 
41
- If the service you are building does not need to modify or extend the GOVUK Frontend all you need to do is import the main sass and javascript files into your Rails app:
62
+ If the service you are building does not need to modify or extend the GOV.UK
63
+ Frontend, all you need to do is import the main SCSS and JavaScript files into
64
+ your Rails app:
65
+
66
+ - create a SCSS file in your application:
67
+
68
+ `app/assets/stylesheets/govuk_frontend_rails.scss`
42
69
 
43
- - create a sass file in your application:
44
- ```
45
- app/assets/stylesheets/govuk_frontend_rails.scss
46
- ```
47
70
  - import the styles into `govuk_frontend_rails.scss`:
48
- ```sass
49
- @import "govuk-frontend-rails";
50
- ```
51
- - require the `govuk-frontend-rails.scss` file in `application.css` or equivalent:
52
71
 
53
- ```
54
- *= require govuk_frontend_rails
55
- ```
72
+ `@import "govuk-frontend-rails";`
56
73
 
57
- - require the javascript into `app/assets/javascripts/application.js`:
74
+ - import the `govuk-frontend-rails.scss` file in the comment block in
75
+ `app/assets/stylesheets/application.css` or equivalent:
58
76
 
59
- ```
60
- //= require govuk_frontend_rails
61
- ```
62
- - initialise the GOVUK Frontend either in a .js file on in your application layout:
77
+ `*= require govuk_frontend_rails`
63
78
 
64
- ```javascript
65
- window.onload = function() {
66
- window.GOVUKFrontend.initAll()
67
- };
68
- ```
69
- - update your application markup to use the GOVUK Frontend class names
79
+ - import the JavaScript in the comment block of
80
+ `app/assets/javascripts/application.js`:
70
81
 
71
- IMPORTANT: You cannot use the GOVUK Frontend without changes if the service you are building is not on GOV.UK, read and understand the guidance here:
82
+ `//= require govuk_frontend_rails`
72
83
 
73
- https://www.gov.uk/service-manual/design/making-your-service-look-like-govuk#if-your-service-isnt-on-govuk
84
+ - initialise the GOV.UK Frontend either in a .js file or in your application
85
+ layout:
86
+
87
+ `window.onload = function() { window.GOVUKFrontend.initAll() };`
88
+
89
+ - update your application markup to use the GOV.UK Frontend class names
90
+
91
+ ## Tracking GOV.UK Frontend versions
74
92
 
75
- ## Tracking version
76
- We aim to tracking the version of GOVUK Frontend.
93
+ We aim to track the latest version of GOV.UK Frontend but we may fall behind.
94
+ Opening an issue if we are behind on a new release would be helpful, or open a
95
+ PR to update to the latest release!
77
96
 
78
- ## Updating to a new Version of GOVUK Frontend
97
+ ## Updating to a new version of GOV.UK Frontend
79
98
 
80
- When a new version of the GOVUK Frontend is released:
99
+ When a new version of the GOV.UK Frontend is released:
81
100
 
82
- - set the new version number in `package.json` and `lib/dxw_govuk_frontend_rails/version.rb`
83
- - run `npm update` to get the new release
84
- - run `bundle rake` to compile the new assets
101
+ - make a new branch with the GOV.UK Frontend version number
102
+ - set the new version number in `package.json` and
103
+ `lib/dxw_govuk_frontend_rails/version.rb`
104
+ - run `npm update` to get the new release of the GOV.UK Frontend
105
+ - run `bundle exec rake` to compile the new assets
85
106
  - commit the changes
86
- - tag with the same release number as GOVUK Frontend
87
- - push the changes
88
- - Github acitons will build the gem and push to Rubygems
107
+ - tag with the same release number as GOV.UK Frontend
108
+ - push the change
109
+ - push the tag
110
+ - merge your branch
111
+ - make a new release of the tag on GitHub
112
+ - GitHub actions will build the gem and push to RubyGems
@@ -1,3 +1,3 @@
1
1
  module DxwGovukFrontendRails
2
- VERSION = "3.6.0"
2
+ VERSION = "3.11.0"
3
3
  end
data/package-lock.json CHANGED
@@ -5,9 +5,9 @@
5
5
  "requires": true,
6
6
  "dependencies": {
7
7
  "govuk-frontend": {
8
- "version": "3.6.0",
9
- "resolved": "https://registry.npmjs.org/govuk-frontend/-/govuk-frontend-3.6.0.tgz",
10
- "integrity": "sha512-wTxufdY8vFvKJ2EmmQKmarrQ7n30kzg+vvqgGib2dawl7c5Wst8dffkEJQpy9Zs99TE/yEEFgj0VUO4GRUO22A=="
8
+ "version": "3.11.0",
9
+ "resolved": "https://registry.npmjs.org/govuk-frontend/-/govuk-frontend-3.11.0.tgz",
10
+ "integrity": "sha512-1hW/3etYBtKPM+PNdWVOijvWVI3mpYL8eb7WLTtlh/Qxf2mCp6LkCsZk9I034n4EJBYQ5jlUWsUlTOOIypftpg=="
11
11
  }
12
12
  }
13
13
  }
data/package.json CHANGED
@@ -21,6 +21,6 @@
21
21
  },
22
22
  "homepage": "https://github.com/mec/dxw_govuk_frontend_rails#readme",
23
23
  "dependencies": {
24
- "govuk-frontend": "3.6.0"
24
+ "govuk-frontend": "3.11.0"
25
25
  }
26
26
  }
@@ -891,7 +891,7 @@ Accordion.prototype.initHeaderAttributes = function ($headerWrapper, index) {
891
891
  icon.className = this.iconClass;
892
892
  icon.setAttribute('aria-hidden', 'true');
893
893
 
894
- $heading.appendChild(icon);
894
+ $button.appendChild(icon);
895
895
  };
896
896
 
897
897
  // When section toggled, set and store state
@@ -1498,7 +1498,7 @@ function CharacterCount ($module) {
1498
1498
  this.$module = $module;
1499
1499
  this.$textarea = $module.querySelector('.govuk-js-character-count');
1500
1500
  if (this.$textarea) {
1501
- this.$countMessage = $module.querySelector('[id=' + this.$textarea.id + '-info]');
1501
+ this.$countMessage = $module.querySelector('[id="' + this.$textarea.id + '-info"]');
1502
1502
  }
1503
1503
  }
1504
1504
 
@@ -1542,13 +1542,22 @@ CharacterCount.prototype.init = function () {
1542
1542
  // Remove hard limit if set
1543
1543
  $module.removeAttribute('maxlength');
1544
1544
 
1545
- // Bind event changes to the textarea
1546
- var boundChangeEvents = this.bindChangeEvents.bind(this);
1547
- boundChangeEvents();
1545
+ // When the page is restored after navigating 'back' in some browsers the
1546
+ // state of the character count is not restored until *after* the DOMContentLoaded
1547
+ // event is fired, so we need to sync after the pageshow event in browsers
1548
+ // that support it.
1549
+ if ('onpageshow' in window) {
1550
+ window.addEventListener('pageshow', this.sync.bind(this));
1551
+ } else {
1552
+ window.addEventListener('DOMContentLoaded', this.sync.bind(this));
1553
+ }
1554
+
1555
+ this.sync();
1556
+ };
1548
1557
 
1549
- // Update count message
1550
- var boundUpdateCountMessage = this.updateCountMessage.bind(this);
1551
- boundUpdateCountMessage();
1558
+ CharacterCount.prototype.sync = function () {
1559
+ this.bindChangeEvents();
1560
+ this.updateCountMessage();
1552
1561
  };
1553
1562
 
1554
1563
  // Read data attributes
@@ -1596,8 +1605,7 @@ CharacterCount.prototype.checkIfValueChanged = function () {
1596
1605
  if (!this.$textarea.oldValue) this.$textarea.oldValue = '';
1597
1606
  if (this.$textarea.value !== this.$textarea.oldValue) {
1598
1607
  this.$textarea.oldValue = this.$textarea.value;
1599
- var boundUpdateCountMessage = this.updateCountMessage.bind(this);
1600
- boundUpdateCountMessage();
1608
+ this.updateCountMessage();
1601
1609
  }
1602
1610
  };
1603
1611
 
@@ -1666,44 +1674,91 @@ function Checkboxes ($module) {
1666
1674
  this.$inputs = $module.querySelectorAll('input[type="checkbox"]');
1667
1675
  }
1668
1676
 
1677
+ /**
1678
+ * Initialise Checkboxes
1679
+ *
1680
+ * Checkboxes can be associated with a 'conditionally revealed' content block –
1681
+ * for example, a checkbox for 'Phone' could reveal an additional form field for
1682
+ * the user to enter their phone number.
1683
+ *
1684
+ * These associations are made using a `data-aria-controls` attribute, which is
1685
+ * promoted to an aria-controls attribute during initialisation.
1686
+ *
1687
+ * We also need to restore the state of any conditional reveals on the page (for
1688
+ * example if the user has navigated back), and set up event handlers to keep
1689
+ * the reveal in sync with the checkbox state.
1690
+ */
1669
1691
  Checkboxes.prototype.init = function () {
1670
1692
  var $module = this.$module;
1671
1693
  var $inputs = this.$inputs;
1672
1694
 
1673
- /**
1674
- * Loop over all items with [data-controls]
1675
- * Check if they have a matching conditional reveal
1676
- * If they do, assign attributes.
1677
- **/
1678
1695
  nodeListForEach($inputs, function ($input) {
1679
- var controls = $input.getAttribute('data-aria-controls');
1696
+ var target = $input.getAttribute('data-aria-controls');
1680
1697
 
1681
- // Check if input controls anything
1682
- // Check if content exists, before setting attributes.
1683
- if (!controls || !$module.querySelector('#' + controls)) {
1698
+ // Skip checkboxes without data-aria-controls attributes, or where the
1699
+ // target element does not exist.
1700
+ if (!target || !$module.querySelector('#' + target)) {
1684
1701
  return
1685
1702
  }
1686
1703
 
1687
- // If we have content that is controlled, set attributes.
1688
- $input.setAttribute('aria-controls', controls);
1704
+ // Promote the data-aria-controls attribute to a aria-controls attribute
1705
+ // so that the relationship is exposed in the AOM
1706
+ $input.setAttribute('aria-controls', target);
1689
1707
  $input.removeAttribute('data-aria-controls');
1690
- this.setAttributes($input);
1691
- }.bind(this));
1708
+ });
1709
+
1710
+ // When the page is restored after navigating 'back' in some browsers the
1711
+ // state of form controls is not restored until *after* the DOMContentLoaded
1712
+ // event is fired, so we need to sync after the pageshow event in browsers
1713
+ // that support it.
1714
+ if ('onpageshow' in window) {
1715
+ window.addEventListener('pageshow', this.syncAllConditionalReveals.bind(this));
1716
+ } else {
1717
+ window.addEventListener('DOMContentLoaded', this.syncAllConditionalReveals.bind(this));
1718
+ }
1719
+
1720
+ // Although we've set up handlers to sync state on the pageshow or
1721
+ // DOMContentLoaded event, init could be called after those events have fired,
1722
+ // for example if they are added to the page dynamically, so sync now too.
1723
+ this.syncAllConditionalReveals();
1692
1724
 
1693
- // Handle events
1694
1725
  $module.addEventListener('click', this.handleClick.bind(this));
1695
1726
  };
1696
1727
 
1697
- Checkboxes.prototype.setAttributes = function ($input) {
1698
- var inputIsChecked = $input.checked;
1699
- $input.setAttribute('aria-expanded', inputIsChecked);
1728
+ /**
1729
+ * Sync the conditional reveal states for all inputs in this $module.
1730
+ */
1731
+ Checkboxes.prototype.syncAllConditionalReveals = function () {
1732
+ nodeListForEach(this.$inputs, this.syncConditionalRevealWithInputState.bind(this));
1733
+ };
1734
+
1735
+ /**
1736
+ * Sync conditional reveal with the input state
1737
+ *
1738
+ * Synchronise the visibility of the conditional reveal, and its accessible
1739
+ * state, with the input's checked state.
1740
+ *
1741
+ * @param {HTMLInputElement} $input Checkbox input
1742
+ */
1743
+ Checkboxes.prototype.syncConditionalRevealWithInputState = function ($input) {
1744
+ var $target = this.$module.querySelector('#' + $input.getAttribute('aria-controls'));
1745
+
1746
+ if ($target && $target.classList.contains('govuk-checkboxes__conditional')) {
1747
+ var inputIsChecked = $input.checked;
1700
1748
 
1701
- var $content = this.$module.querySelector('#' + $input.getAttribute('aria-controls'));
1702
- if ($content) {
1703
- $content.classList.toggle('govuk-checkboxes__conditional--hidden', !inputIsChecked);
1749
+ $input.setAttribute('aria-expanded', inputIsChecked);
1750
+ $target.classList.toggle('govuk-checkboxes__conditional--hidden', !inputIsChecked);
1704
1751
  }
1705
1752
  };
1706
1753
 
1754
+ /**
1755
+ * Click event handler
1756
+ *
1757
+ * Handle a click within the $module – if the click occurred on a checkbox, sync
1758
+ * the state of any associated conditional reveal with the checkbox state.
1759
+ *
1760
+ * @param {MouseEvent} event Click event
1761
+ */
1707
1762
  Checkboxes.prototype.handleClick = function (event) {
1708
1763
  var $target = event.target;
1709
1764
 
@@ -1711,7 +1766,7 @@ Checkboxes.prototype.handleClick = function (event) {
1711
1766
  var isCheckbox = $target.getAttribute('type') === 'checkbox';
1712
1767
  var hasAriaControls = $target.getAttribute('aria-controls');
1713
1768
  if (isCheckbox && hasAriaControls) {
1714
- this.setAttributes($target);
1769
+ this.syncConditionalRevealWithInputState($target);
1715
1770
  }
1716
1771
  };
1717
1772
 
@@ -1904,122 +1959,216 @@ ErrorSummary.prototype.getAssociatedLegendOrLabel = function ($input) {
1904
1959
  $input.closest('label')
1905
1960
  };
1906
1961
 
1907
- function Header ($module) {
1962
+ function NotificationBanner ($module) {
1908
1963
  this.$module = $module;
1909
1964
  }
1910
1965
 
1911
- Header.prototype.init = function () {
1912
- // Check for module
1966
+ /**
1967
+ * Initialise the component
1968
+ */
1969
+ NotificationBanner.prototype.init = function () {
1913
1970
  var $module = this.$module;
1971
+ // Check for module
1914
1972
  if (!$module) {
1915
1973
  return
1916
1974
  }
1917
1975
 
1918
- // Check for button
1919
- var $toggleButton = $module.querySelector('.govuk-js-header-toggle');
1920
- if (!$toggleButton) {
1976
+ this.setFocus();
1977
+ };
1978
+
1979
+ /**
1980
+ * Focus the element
1981
+ *
1982
+ * If `role="alert"` is set, focus the element to help some assistive technologies
1983
+ * prioritise announcing it.
1984
+ *
1985
+ * You can turn off the auto-focus functionality by setting `data-disable-auto-focus="true"` in the
1986
+ * component HTML. You might wish to do this based on user research findings, or to avoid a clash
1987
+ * with another element which should be focused when the page loads.
1988
+ */
1989
+ NotificationBanner.prototype.setFocus = function () {
1990
+ var $module = this.$module;
1991
+
1992
+ if ($module.getAttribute('data-disable-auto-focus') === 'true') {
1993
+ return
1994
+ }
1995
+
1996
+ if ($module.getAttribute('role') !== 'alert') {
1921
1997
  return
1922
1998
  }
1923
1999
 
1924
- // Handle $toggleButton click events
1925
- $toggleButton.addEventListener('click', this.handleClick.bind(this));
2000
+ // Set tabindex to -1 to make the element focusable with JavaScript.
2001
+ // Remove the tabindex on blur as the component doesn't need to be focusable after the page has
2002
+ // loaded.
2003
+ if (!$module.getAttribute('tabindex')) {
2004
+ $module.setAttribute('tabindex', '-1');
2005
+
2006
+ $module.addEventListener('blur', function () {
2007
+ $module.removeAttribute('tabindex');
2008
+ });
2009
+ }
2010
+
2011
+ $module.focus();
1926
2012
  };
1927
2013
 
2014
+ function Header ($module) {
2015
+ this.$module = $module;
2016
+ this.$menuButton = $module && $module.querySelector('.govuk-js-header-toggle');
2017
+ this.$menu = this.$menuButton && $module.querySelector(
2018
+ '#' + this.$menuButton.getAttribute('aria-controls')
2019
+ );
2020
+ }
2021
+
1928
2022
  /**
1929
- * Toggle class
1930
- * @param {object} node element
1931
- * @param {string} className to toggle
1932
- */
1933
- Header.prototype.toggleClass = function (node, className) {
1934
- if (node.className.indexOf(className) > 0) {
1935
- node.className = node.className.replace(' ' + className, '');
1936
- } else {
1937
- node.className += ' ' + className;
2023
+ * Initialise header
2024
+ *
2025
+ * Check for the presence of the header, menu and menu button – if any are
2026
+ * missing then there's nothing to do so return early.
2027
+ */
2028
+ Header.prototype.init = function () {
2029
+ if (!this.$module || !this.$menuButton || !this.$menu) {
2030
+ return
1938
2031
  }
2032
+
2033
+ this.syncState(this.$menu.classList.contains('govuk-header__navigation--open'));
2034
+ this.$menuButton.addEventListener('click', this.handleMenuButtonClick.bind(this));
1939
2035
  };
1940
2036
 
1941
2037
  /**
1942
- * An event handler for click event on $toggleButton
1943
- * @param {object} event event
1944
- */
1945
- Header.prototype.handleClick = function (event) {
1946
- var $module = this.$module;
1947
- var $toggleButton = event.target || event.srcElement;
1948
- var $target = $module.querySelector('#' + $toggleButton.getAttribute('aria-controls'));
1949
-
1950
- // If a button with aria-controls, handle click
1951
- if ($toggleButton && $target) {
1952
- this.toggleClass($target, 'govuk-header__navigation--open');
1953
- this.toggleClass($toggleButton, 'govuk-header__menu-button--open');
2038
+ * Sync menu state
2039
+ *
2040
+ * Sync the menu button class and the accessible state of the menu and the menu
2041
+ * button with the visible state of the menu
2042
+ *
2043
+ * @param {boolean} isVisible Whether the menu is currently visible
2044
+ */
2045
+ Header.prototype.syncState = function (isVisible) {
2046
+ this.$menuButton.classList.toggle('govuk-header__menu-button--open', isVisible);
2047
+ this.$menuButton.setAttribute('aria-expanded', isVisible);
2048
+ };
1954
2049
 
1955
- $toggleButton.setAttribute('aria-expanded', $toggleButton.getAttribute('aria-expanded') !== 'true');
1956
- $target.setAttribute('aria-hidden', $target.getAttribute('aria-hidden') === 'false');
1957
- }
2050
+ /**
2051
+ * Handle menu button click
2052
+ *
2053
+ * When the menu button is clicked, change the visibility of the menu and then
2054
+ * sync the accessibility state and menu button state
2055
+ */
2056
+ Header.prototype.handleMenuButtonClick = function () {
2057
+ var isVisible = this.$menu.classList.toggle('govuk-header__navigation--open');
2058
+ this.syncState(isVisible);
1958
2059
  };
1959
2060
 
1960
2061
  function Radios ($module) {
1961
2062
  this.$module = $module;
2063
+ this.$inputs = $module.querySelectorAll('input[type="radio"]');
1962
2064
  }
1963
2065
 
2066
+ /**
2067
+ * Initialise Radios
2068
+ *
2069
+ * Radios can be associated with a 'conditionally revealed' content block – for
2070
+ * example, a radio for 'Phone' could reveal an additional form field for the
2071
+ * user to enter their phone number.
2072
+ *
2073
+ * These associations are made using a `data-aria-controls` attribute, which is
2074
+ * promoted to an aria-controls attribute during initialisation.
2075
+ *
2076
+ * We also need to restore the state of any conditional reveals on the page (for
2077
+ * example if the user has navigated back), and set up event handlers to keep
2078
+ * the reveal in sync with the radio state.
2079
+ */
1964
2080
  Radios.prototype.init = function () {
1965
2081
  var $module = this.$module;
1966
- var $inputs = $module.querySelectorAll('input[type="radio"]');
2082
+ var $inputs = this.$inputs;
1967
2083
 
1968
- /**
1969
- * Loop over all items with [data-controls]
1970
- * Check if they have a matching conditional reveal
1971
- * If they do, assign attributes.
1972
- **/
1973
2084
  nodeListForEach($inputs, function ($input) {
1974
- var controls = $input.getAttribute('data-aria-controls');
2085
+ var target = $input.getAttribute('data-aria-controls');
1975
2086
 
1976
- // Check if input controls anything
1977
- // Check if content exists, before setting attributes.
1978
- if (!controls || !$module.querySelector('#' + controls)) {
2087
+ // Skip radios without data-aria-controls attributes, or where the
2088
+ // target element does not exist.
2089
+ if (!target || !$module.querySelector('#' + target)) {
1979
2090
  return
1980
2091
  }
1981
2092
 
1982
- // If we have content that is controlled, set attributes.
1983
- $input.setAttribute('aria-controls', controls);
2093
+ // Promote the data-aria-controls attribute to a aria-controls attribute
2094
+ // so that the relationship is exposed in the AOM
2095
+ $input.setAttribute('aria-controls', target);
1984
2096
  $input.removeAttribute('data-aria-controls');
1985
- this.setAttributes($input);
1986
- }.bind(this));
2097
+ });
2098
+
2099
+ // When the page is restored after navigating 'back' in some browsers the
2100
+ // state of form controls is not restored until *after* the DOMContentLoaded
2101
+ // event is fired, so we need to sync after the pageshow event in browsers
2102
+ // that support it.
2103
+ if ('onpageshow' in window) {
2104
+ window.addEventListener('pageshow', this.syncAllConditionalReveals.bind(this));
2105
+ } else {
2106
+ window.addEventListener('DOMContentLoaded', this.syncAllConditionalReveals.bind(this));
2107
+ }
2108
+
2109
+ // Although we've set up handlers to sync state on the pageshow or
2110
+ // DOMContentLoaded event, init could be called after those events have fired,
2111
+ // for example if they are added to the page dynamically, so sync now too.
2112
+ this.syncAllConditionalReveals();
1987
2113
 
1988
2114
  // Handle events
1989
2115
  $module.addEventListener('click', this.handleClick.bind(this));
1990
2116
  };
1991
2117
 
1992
- Radios.prototype.setAttributes = function ($input) {
1993
- var $content = document.querySelector('#' + $input.getAttribute('aria-controls'));
2118
+ /**
2119
+ * Sync the conditional reveal states for all inputs in this $module.
2120
+ */
2121
+ Radios.prototype.syncAllConditionalReveals = function () {
2122
+ nodeListForEach(this.$inputs, this.syncConditionalRevealWithInputState.bind(this));
2123
+ };
2124
+
2125
+ /**
2126
+ * Sync conditional reveal with the input state
2127
+ *
2128
+ * Synchronise the visibility of the conditional reveal, and its accessible
2129
+ * state, with the input's checked state.
2130
+ *
2131
+ * @param {HTMLInputElement} $input Radio input
2132
+ */
2133
+ Radios.prototype.syncConditionalRevealWithInputState = function ($input) {
2134
+ var $target = document.querySelector('#' + $input.getAttribute('aria-controls'));
1994
2135
 
1995
- if ($content && $content.classList.contains('govuk-radios__conditional')) {
2136
+ if ($target && $target.classList.contains('govuk-radios__conditional')) {
1996
2137
  var inputIsChecked = $input.checked;
1997
2138
 
1998
2139
  $input.setAttribute('aria-expanded', inputIsChecked);
1999
-
2000
- $content.classList.toggle('govuk-radios__conditional--hidden', !inputIsChecked);
2140
+ $target.classList.toggle('govuk-radios__conditional--hidden', !inputIsChecked);
2001
2141
  }
2002
2142
  };
2003
2143
 
2144
+ /**
2145
+ * Click event handler
2146
+ *
2147
+ * Handle a click within the $module – if the click occurred on a radio, sync
2148
+ * the state of the conditional reveal for all radio buttons in the same form
2149
+ * with the same name (because checking one radio could have un-checked a radio
2150
+ * in another $module)
2151
+ *
2152
+ * @param {MouseEvent} event Click event
2153
+ */
2004
2154
  Radios.prototype.handleClick = function (event) {
2005
2155
  var $clickedInput = event.target;
2006
- // We only want to handle clicks for radio inputs
2156
+
2157
+ // Ignore clicks on things that aren't radio buttons
2007
2158
  if ($clickedInput.type !== 'radio') {
2008
2159
  return
2009
2160
  }
2010
- // Because checking one radio can uncheck a radio in another $module,
2011
- // we need to call set attributes on all radios in the same form, or document if they're not in a form.
2012
- //
2013
- // We also only want radios which have aria-controls, as they support conditional reveals.
2161
+
2162
+ // We only need to consider radios with conditional reveals, which will have
2163
+ // aria-controls attributes.
2014
2164
  var $allInputs = document.querySelectorAll('input[type="radio"][aria-controls]');
2165
+
2015
2166
  nodeListForEach($allInputs, function ($input) {
2016
- // Only inputs with the same form owner should change.
2017
2167
  var hasSameFormOwner = ($input.form === $clickedInput.form);
2018
-
2019
- // In radios, only radios with the same name will affect each other.
2020
2168
  var hasSameName = ($input.name === $clickedInput.name);
2169
+
2021
2170
  if (hasSameName && hasSameFormOwner) {
2022
- this.setAttributes($input);
2171
+ this.syncConditionalRevealWithInputState($input);
2023
2172
  }
2024
2173
  }.bind(this));
2025
2174
  };
@@ -2379,6 +2528,11 @@ function initAll (options) {
2379
2528
  var $toggleButton = scope.querySelector('[data-module="govuk-header"]');
2380
2529
  new Header($toggleButton).init();
2381
2530
 
2531
+ var $notificationBanners = scope.querySelectorAll('[data-module="govuk-notification-banner"]');
2532
+ nodeListForEach($notificationBanners, function ($notificationBanner) {
2533
+ new NotificationBanner($notificationBanner).init();
2534
+ });
2535
+
2382
2536
  var $radios = scope.querySelectorAll('[data-module="govuk-radios"]');
2383
2537
  nodeListForEach($radios, function ($radio) {
2384
2538
  new Radios($radio).init();