govuk_tech_docs 2.2.1 → 2.4.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of govuk_tech_docs might be problematic. Click here for more details.

Files changed (141) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.nvmrc +1 -1
  4. data/.travis.yml +2 -0
  5. data/CHANGELOG.md +25 -0
  6. data/govuk_tech_docs.gemspec +2 -1
  7. data/lib/assets/javascripts/_modules/collapsible-navigation.js +7 -7
  8. data/lib/assets/javascripts/_modules/in-page-navigation.js +2 -4
  9. data/lib/assets/stylesheets/_govuk_tech_docs.scss +9 -10
  10. data/lib/assets/stylesheets/modules/_search.scss +8 -25
  11. data/lib/assets/stylesheets/modules/_technical-documentation.scss +1 -1
  12. data/lib/assets/stylesheets/modules/_toc.scss +11 -11
  13. data/lib/govuk_tech_docs/table_of_contents/heading.rb +5 -1
  14. data/lib/govuk_tech_docs/table_of_contents/heading_tree_renderer.rb +2 -2
  15. data/lib/govuk_tech_docs/table_of_contents/helpers.rb +6 -3
  16. data/lib/govuk_tech_docs/tech_docs_html_renderer.rb +1 -1
  17. data/lib/govuk_tech_docs/version.rb +1 -1
  18. data/lib/source/layouts/_header.erb +1 -2
  19. data/lib/source/layouts/_search.erb +1 -1
  20. data/node_modules/govuk-frontend/govuk/_base.scss +3 -0
  21. data/node_modules/govuk-frontend/govuk/all.js +306 -94
  22. data/node_modules/govuk-frontend/govuk/all.scss +1 -3
  23. data/node_modules/govuk-frontend/govuk/components/_all.scss +33 -29
  24. data/node_modules/govuk-frontend/govuk/components/accordion/_accordion.scss +2 -208
  25. data/node_modules/govuk-frontend/govuk/components/accordion/_index.scss +197 -0
  26. data/node_modules/govuk-frontend/govuk/components/accordion/accordion.js +1 -1
  27. data/node_modules/govuk-frontend/govuk/components/back-link/_back-link.scss +2 -65
  28. data/node_modules/govuk-frontend/govuk/components/back-link/_index.scss +99 -0
  29. data/node_modules/govuk-frontend/govuk/components/breadcrumbs/_breadcrumbs.scss +2 -118
  30. data/node_modules/govuk-frontend/govuk/components/breadcrumbs/_index.scss +138 -0
  31. data/node_modules/govuk-frontend/govuk/components/button/_button.scss +2 -284
  32. data/node_modules/govuk-frontend/govuk/components/button/_index.scss +288 -0
  33. data/node_modules/govuk-frontend/govuk/components/character-count/_character-count.scss +2 -31
  34. data/node_modules/govuk-frontend/govuk/components/character-count/_index.scss +25 -0
  35. data/node_modules/govuk-frontend/govuk/components/character-count/character-count.js +17 -9
  36. data/node_modules/govuk-frontend/govuk/components/checkboxes/_checkboxes.scss +2 -308
  37. data/node_modules/govuk-frontend/govuk/components/checkboxes/_index.scss +320 -0
  38. data/node_modules/govuk-frontend/govuk/components/checkboxes/checkboxes.js +129 -24
  39. data/node_modules/govuk-frontend/govuk/components/cookie-banner/_cookie-banner.scss +2 -0
  40. data/node_modules/govuk-frontend/govuk/components/cookie-banner/_index.scss +51 -0
  41. data/node_modules/govuk-frontend/govuk/components/date-input/_date-input.scss +2 -30
  42. data/node_modules/govuk-frontend/govuk/components/date-input/_index.scss +26 -0
  43. data/node_modules/govuk-frontend/govuk/components/details/_details.scss +2 -88
  44. data/node_modules/govuk-frontend/govuk/components/details/_index.scss +87 -0
  45. data/node_modules/govuk-frontend/govuk/components/error-message/_error-message.scss +2 -15
  46. data/node_modules/govuk-frontend/govuk/components/error-message/_index.scss +11 -0
  47. data/node_modules/govuk-frontend/govuk/components/error-summary/_error-summary.scss +2 -59
  48. data/node_modules/govuk-frontend/govuk/components/error-summary/_index.scss +43 -0
  49. data/node_modules/govuk-frontend/govuk/components/fieldset/_fieldset.scss +2 -68
  50. data/node_modules/govuk-frontend/govuk/components/fieldset/_index.scss +64 -0
  51. data/node_modules/govuk-frontend/govuk/components/file-upload/_file-upload.scss +2 -81
  52. data/node_modules/govuk-frontend/govuk/components/file-upload/_index.scss +49 -0
  53. data/node_modules/govuk-frontend/govuk/components/footer/_footer.scss +2 -244
  54. data/node_modules/govuk-frontend/govuk/components/footer/_index.scss +241 -0
  55. data/node_modules/govuk-frontend/govuk/components/header/_header.scss +2 -318
  56. data/node_modules/govuk-frontend/govuk/components/header/_index.scss +331 -0
  57. data/node_modules/govuk-frontend/govuk/components/header/header.js +665 -316
  58. data/node_modules/govuk-frontend/govuk/components/hint/_hint.scss +2 -50
  59. data/node_modules/govuk-frontend/govuk/components/hint/_index.scss +44 -0
  60. data/node_modules/govuk-frontend/govuk/components/input/_index.scss +191 -0
  61. data/node_modules/govuk-frontend/govuk/components/input/_input.scss +2 -103
  62. data/node_modules/govuk-frontend/govuk/components/inset-text/_index.scss +24 -0
  63. data/node_modules/govuk-frontend/govuk/components/inset-text/_inset-text.scss +2 -28
  64. data/node_modules/govuk-frontend/govuk/components/label/_index.scss +41 -0
  65. data/node_modules/govuk-frontend/govuk/components/label/_label.scss +2 -45
  66. data/node_modules/govuk-frontend/govuk/components/notification-banner/_index.scss +89 -0
  67. data/node_modules/govuk-frontend/govuk/components/notification-banner/_notification-banner.scss +2 -0
  68. data/node_modules/govuk-frontend/govuk/components/notification-banner/notification-banner.js +61 -0
  69. data/node_modules/govuk-frontend/govuk/components/panel/_index.scss +44 -0
  70. data/node_modules/govuk-frontend/govuk/components/panel/_panel.scss +2 -44
  71. data/node_modules/govuk-frontend/govuk/components/phase-banner/_index.scss +27 -0
  72. data/node_modules/govuk-frontend/govuk/components/phase-banner/_phase-banner.scss +2 -31
  73. data/node_modules/govuk-frontend/govuk/components/radios/_index.scss +342 -0
  74. data/node_modules/govuk-frontend/govuk/components/radios/_radios.scss +2 -346
  75. data/node_modules/govuk-frontend/govuk/components/radios/radios.js +76 -28
  76. data/node_modules/govuk-frontend/govuk/components/select/_index.scss +49 -0
  77. data/node_modules/govuk-frontend/govuk/components/select/_select.scss +2 -57
  78. data/node_modules/govuk-frontend/govuk/components/skip-link/_index.scss +36 -0
  79. data/node_modules/govuk-frontend/govuk/components/skip-link/_skip-link.scss +2 -37
  80. data/node_modules/govuk-frontend/govuk/components/summary-list/_index.scss +145 -0
  81. data/node_modules/govuk-frontend/govuk/components/summary-list/_summary-list.scss +2 -157
  82. data/node_modules/govuk-frontend/govuk/components/table/_index.scss +71 -0
  83. data/node_modules/govuk-frontend/govuk/components/table/_table.scss +2 -54
  84. data/node_modules/govuk-frontend/govuk/components/tabs/_index.scss +130 -0
  85. data/node_modules/govuk-frontend/govuk/components/tabs/_tabs.scss +2 -142
  86. data/node_modules/govuk-frontend/govuk/components/tag/_index.scss +86 -0
  87. data/node_modules/govuk-frontend/govuk/components/tag/_tag.scss +2 -91
  88. data/node_modules/govuk-frontend/govuk/components/textarea/_index.scss +47 -0
  89. data/node_modules/govuk-frontend/govuk/components/textarea/_textarea.scss +2 -55
  90. data/node_modules/govuk-frontend/govuk/components/warning-text/_index.scss +66 -0
  91. data/node_modules/govuk-frontend/govuk/components/warning-text/_warning-text.scss +2 -60
  92. data/node_modules/govuk-frontend/govuk/core/_global-styles.scss +5 -3
  93. data/node_modules/govuk-frontend/govuk/core/_links.scss +13 -3
  94. data/node_modules/govuk-frontend/govuk/core/_lists.scss +17 -3
  95. data/node_modules/govuk-frontend/govuk/core/_section-break.scss +5 -3
  96. data/node_modules/govuk-frontend/govuk/core/_template.scss +5 -4
  97. data/node_modules/govuk-frontend/govuk/core/_typography.scss +5 -3
  98. data/node_modules/govuk-frontend/govuk/helpers/_clearfix.scss +1 -1
  99. data/node_modules/govuk-frontend/govuk/helpers/_colour.scss +1 -1
  100. data/node_modules/govuk-frontend/govuk/helpers/_device-pixels.scss +3 -3
  101. data/node_modules/govuk-frontend/govuk/helpers/_focused.scss +1 -1
  102. data/node_modules/govuk-frontend/govuk/helpers/_font-faces.scss +9 -11
  103. data/node_modules/govuk-frontend/govuk/helpers/_grid.scss +2 -1
  104. data/node_modules/govuk-frontend/govuk/helpers/_links.scss +246 -33
  105. data/node_modules/govuk-frontend/govuk/helpers/_media-queries.scss +2 -6
  106. data/node_modules/govuk-frontend/govuk/helpers/_shape-arrow.scss +1 -1
  107. data/node_modules/govuk-frontend/govuk/helpers/_spacing.scss +3 -2
  108. data/node_modules/govuk-frontend/govuk/helpers/_typography.scss +8 -7
  109. data/node_modules/govuk-frontend/govuk/helpers/_visually-hidden.scss +1 -1
  110. data/node_modules/govuk-frontend/govuk/objects/_all.scss +1 -0
  111. data/node_modules/govuk-frontend/govuk/objects/_button-group.scss +101 -0
  112. data/node_modules/govuk-frontend/govuk/objects/_form-group.scss +1 -4
  113. data/node_modules/govuk-frontend/govuk/objects/_grid.scss +3 -6
  114. data/node_modules/govuk-frontend/govuk/objects/_main-wrapper.scss +5 -4
  115. data/node_modules/govuk-frontend/govuk/objects/_width-container.scss +6 -4
  116. data/node_modules/govuk-frontend/govuk/overrides/_display.scss +6 -4
  117. data/node_modules/govuk-frontend/govuk/overrides/_spacing.scss +5 -3
  118. data/node_modules/govuk-frontend/govuk/overrides/_typography.scss +5 -3
  119. data/node_modules/govuk-frontend/govuk/overrides/_width.scss +6 -3
  120. data/node_modules/govuk-frontend/govuk/settings/_all.scss +2 -0
  121. data/node_modules/govuk-frontend/govuk/settings/_colours-applied.scss +11 -5
  122. data/node_modules/govuk-frontend/govuk/settings/_colours-organisations.scss +3 -0
  123. data/node_modules/govuk-frontend/govuk/settings/_colours-palette.scss +42 -35
  124. data/node_modules/govuk-frontend/govuk/settings/_compatibility.scss +0 -1
  125. data/node_modules/govuk-frontend/govuk/settings/_ie8.scss +1 -1
  126. data/node_modules/govuk-frontend/govuk/settings/_links.scss +62 -0
  127. data/node_modules/govuk-frontend/govuk/settings/_measurements.scss +4 -5
  128. data/node_modules/govuk-frontend/govuk/settings/_typography-font-families.scss +2 -2
  129. data/node_modules/govuk-frontend/govuk/settings/_typography-font.scss +14 -5
  130. data/node_modules/govuk-frontend/govuk/settings/_typography-responsive.scss +6 -2
  131. data/node_modules/govuk-frontend/govuk/tools/_compatibility.scss +1 -1
  132. data/node_modules/govuk-frontend/govuk/tools/_font-url.scss +1 -4
  133. data/node_modules/govuk-frontend/govuk/tools/_ie8.scss +1 -1
  134. data/node_modules/govuk-frontend/govuk/tools/_image-url.scss +1 -4
  135. data/node_modules/govuk-frontend/govuk/tools/_px-to-em.scss +1 -1
  136. data/node_modules/govuk-frontend/govuk/tools/_px-to-rem.scss +1 -1
  137. data/node_modules/govuk-frontend/govuk/utilities/_visually-hidden.scss +0 -1
  138. data/node_modules/govuk-frontend/govuk/vendor/_sass-mq.scss +0 -4
  139. data/package-lock.json +358 -288
  140. data/package.json +2 -2
  141. metadata +55 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 61743fbb0930f511a18a3e9640a85d0f7646c0e4ad8f3c0c1007db3ca27ed571
4
- data.tar.gz: a5c775c15afb940c17f730f43b571d791aa208b84b8372219fb130b83aae6d4b
3
+ metadata.gz: 80dfd8a946033705eb3157cb70319a30860ca096e85ec4e8cf47a04b5c001d38
4
+ data.tar.gz: 553772f54e1b4afbd0db8c3eb40d60460df35104b570b8be13c527cecf71ff54
5
5
  SHA512:
6
- metadata.gz: 801d7d5ea6af33e7d8921e88c505649c37552da4e46dae3708d928515aa0dae7d8dfeb4785b602134580354c2acd0bd12c6b7338dbff592dc0559414d6fa1032
7
- data.tar.gz: 43c8d28ab4a4ebcfb176b164304c6c36310934c0a71bef2cdb3fd18007d8320cfd0d518ab75f12cba3579408420d0ff77d1827a5cb29fe310400159e40c30320
6
+ metadata.gz: d35acab5b3b497aa48fe880e838b817d584a6545b5881ef5f9ceeee40030366e15a4b183fa409b647b3ab4f64a81d478584787e68cf62d97e84be32a26c24802
7
+ data.tar.gz: 3f495169c4f98cf79f69ee8a33ff3440908a861fcd3ff2aa823f000dda28cfc2da0f5559c22851cbd4a456eb560b50f5af06598212b5c5a08be8ea72aa948022
data/.gitignore CHANGED
@@ -29,6 +29,7 @@
29
29
  .rvmrc
30
30
 
31
31
  /example/build
32
+ /example/.bundle
32
33
  /example/.sass-cache
33
34
 
34
35
  Gemfile.lock
data/.nvmrc CHANGED
@@ -1 +1 @@
1
- 10.16.0
1
+ 10
data/.travis.yml CHANGED
@@ -3,6 +3,8 @@ cache:
3
3
  bundler: true
4
4
  npm: true
5
5
  before_install:
6
+ - nvm install
7
+ - nvm use
6
8
  - gem update --system
7
9
  - gem install bundler
8
10
  before_script:
data/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 2.4.1
6
+
7
+ - [#248: Remove IE8 fallback PNG with broken image reference](https://github.com/alphagov/tech-docs-gem/pull/248)
8
+
9
+ ## 2.4.0
10
+
11
+ - [Bump redcarpet to 3.5.1 to fix CVE-2020-26298](https://github.com/alphagov/tech-docs-gem/pull/226)
12
+ - [#238: Move the aria-expanded attribute to the correct element](https://github.com/alphagov/tech-docs-gem/pull/238)
13
+ - [#240: Update menu html structure so it's one single hierarchical list](https://github.com/alphagov/tech-docs-gem/pull/240)
14
+ - [#244: Don't change the focus of the page on initial load](https://github.com/alphagov/tech-docs-gem/pull/244)
15
+ - [#243: Fix focus state for links containing inline code](https://github.com/alphagov/tech-docs-gem/pull/243)
16
+ - [#245: Fix focus state for search results ‘Close’ button](https://github.com/alphagov/tech-docs-gem/pull/245)
17
+ - [#246: Update GOV.UK Frontend to v3.13.0](https://github.com/alphagov/tech-docs-gem/pull/246)
18
+
19
+ ## 2.3.0
20
+
21
+ - [#232: Update GOV.UK Frontend and use new link styles](https://github.com/alphagov/tech-docs-gem/pull/232)
22
+
23
+ ## 2.2.2
24
+
25
+ - [#223: Remove unnecessary CSS class on the search submit button](https://github.com/alphagov/tech-docs-gem/pull/223)
26
+ - [#224: Accessibility fix: Hide the 'table of contents close' button when search results are open](https://github.com/alphagov/tech-docs-gem/pull/224)
27
+
3
28
  ## 2.2.1
4
29
 
5
30
  - [#218: Remove unnecessary explicit dependencies: sprockets, activesupport, sass and pry](https://github.com/alphagov/tech-docs-gem/pull/218)
@@ -32,6 +32,7 @@ Gem::Specification.new do |spec|
32
32
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
33
33
  spec.require_paths = ["lib"]
34
34
 
35
+ spec.add_dependency "autoprefixer-rails", "~> 10.2"
35
36
  spec.add_dependency "chronic", "~> 0.10.2"
36
37
  spec.add_dependency "middleman", "~> 4.0"
37
38
  spec.add_dependency "middleman-autoprefixer", "~> 2.10.0"
@@ -42,7 +43,7 @@ Gem::Specification.new do |spec|
42
43
  spec.add_dependency "middleman-syntax", "~> 3.2.0"
43
44
  spec.add_dependency "nokogiri"
44
45
  spec.add_dependency "openapi3_parser", "~> 0.9.0"
45
- spec.add_dependency "redcarpet", "~> 3.5.0"
46
+ spec.add_dependency "redcarpet", "~> 3.5.1"
46
47
 
47
48
  spec.add_development_dependency "byebug"
48
49
  spec.add_development_dependency "capybara", "~> 3.32"
@@ -34,10 +34,9 @@
34
34
  arrayOfIds.push(uniqueId)
35
35
  $(this).addClass('collapsible__body')
36
36
  .attr('id', uniqueId)
37
- .attr('aria-expanded', 'false')
38
37
  })
39
38
  $heading.addClass('collapsible__heading')
40
- .after('<button class="collapsible__toggle" aria-controls="' + arrayOfIds.join(' ') + '"><span class="collapsible__toggle-label">Expand ' + $heading.text() + '</span><span class="collapsible__toggle-icon" aria-hidden="true"></button>')
39
+ .after('<button class="collapsible__toggle" aria-expanded="false" aria-controls="' + arrayOfIds.join(' ') + '"><span class="collapsible__toggle-label">Expand ' + $heading.text() + '</span><span class="collapsible__toggle-icon" aria-hidden="true"></button>')
41
40
  $topLevelItem.on('click', '.collapsible__toggle', function (e) {
42
41
  e.preventDefault()
43
42
  var $parent = $(this).parent()
@@ -47,14 +46,15 @@
47
46
  }
48
47
 
49
48
  function toggleHeading ($topLevelItem) {
50
- var isOpen = $topLevelItem.hasClass('is-open')
49
+ var setOpen = !$topLevelItem.hasClass('is-open')
50
+
51
51
  var $heading = $topLevelItem.find('> a')
52
- var $body = $topLevelItem.find('.collapsible__body')
52
+ var $button = $topLevelItem.find('.collapsible__toggle')
53
53
  var $toggleLabel = $topLevelItem.find('.collapsible__toggle-label')
54
54
 
55
- $topLevelItem.toggleClass('is-open', !isOpen)
56
- $body.attr('aria-expanded', isOpen ? 'false' : 'true')
57
- $toggleLabel.text(isOpen ? 'Expand ' + $heading.text() : 'Collapse ' + $heading.text())
55
+ $topLevelItem.toggleClass('is-open', setOpen)
56
+ $button.attr('aria-expanded', setOpen ? 'true' : 'false')
57
+ $toggleLabel.text(setOpen ? 'Collapse ' + $heading.text() : 'Expand ' + $heading.text())
58
58
  }
59
59
 
60
60
  function openActiveHeading () {
@@ -47,14 +47,12 @@
47
47
  fragment = fragmentForFirstElementInView()
48
48
  }
49
49
 
50
- handleChangeInActiveItem(fragment)
50
+ highlightActiveItemInToc(fragment)
51
51
  }
52
52
 
53
53
  function handleScrollEvent () {
54
- handleChangeInActiveItem(fragmentForFirstElementInView())
55
- }
54
+ var fragment = fragmentForFirstElementInView()
56
55
 
57
- function handleChangeInActiveItem (fragment) {
58
56
  storeCurrentPositionInHistoryApi(fragment)
59
57
  highlightActiveItemInToc(fragment)
60
58
  }
@@ -1,20 +1,19 @@
1
1
  $govuk-assets-path: "/assets/govuk/assets/";
2
2
 
3
3
  // Include only the bits of GOV.UK Frontend we need
4
- @import "govuk/settings/all";
5
- @import "govuk/tools/all";
6
- @import "govuk/helpers/all";
4
+ $govuk-new-link-styles: true;
5
+ @import "govuk/base";
7
6
 
8
7
  @import "govuk/core/all";
9
8
  @import "govuk/objects/all";
10
9
 
11
- @import "govuk/components/footer/footer";
12
- @import "govuk/components/header/header";
13
- @import "govuk/components/inset-text/inset-text";
14
- @import "govuk/components/input/input";
15
- @import "govuk/components/tag/tag";
16
- @import "govuk/components/skip-link/skip-link";
17
- @import "govuk/components/warning-text/warning-text";
10
+ @import "govuk/components/footer/index";
11
+ @import "govuk/components/header/index";
12
+ @import "govuk/components/inset-text/index";
13
+ @import "govuk/components/input/index";
14
+ @import "govuk/components/tag/index";
15
+ @import "govuk/components/skip-link/index";
16
+ @import "govuk/components/warning-text/index";
18
17
 
19
18
  @import "govuk/utilities/all";
20
19
  @import "govuk/overrides/all";
@@ -57,6 +57,10 @@ html.has-search-results-open {
57
57
  .app-pane__content {
58
58
  overflow: hidden;
59
59
  }
60
+
61
+ .toc__close{
62
+ display: none;
63
+ }
60
64
  }
61
65
  .search-results {
62
66
  display: none;
@@ -83,21 +87,9 @@ html.has-search-results-open {
83
87
  min-height: auto;
84
88
  }
85
89
 
86
- a:link, a:visited {
87
- text-decoration: none;
88
- color: $govuk-link-colour;
89
- }
90
-
91
- a:hover {
92
- text-decoration: underline;
93
-
94
- &:focus {
95
- text-decoration: none;
96
- }
97
- }
98
- a:focus {
99
- outline: none;
100
- @include govuk-focused-text;
90
+ a {
91
+ @include govuk-link-common;
92
+ @include govuk-link-style-no-visited-state;
101
93
  }
102
94
  }
103
95
  .search-results__inner {
@@ -138,16 +130,7 @@ html.has-search-results-open {
138
130
  cursor: pointer;
139
131
 
140
132
  &:focus {
141
- background-color: $govuk-focus-colour;
142
- outline: $govuk-focus-width solid transparent;
143
- box-shadow: inset 0 0 0 1px $govuk-focus-colour;
144
- }
145
-
146
- &:focus:not(:active):not(:hover) {
147
- border-color: $govuk-focus-colour;
148
- color: $govuk-focus-text-colour;
149
- background-color: $govuk-focus-colour;
150
- box-shadow: 0 2px 0 $govuk-focus-text-colour;
133
+ @include govuk-focused-text;
151
134
  }
152
135
 
153
136
  &::after {
@@ -185,7 +185,7 @@
185
185
  }
186
186
 
187
187
  a:focus code {
188
- background: inherit;
188
+ background: transparent;
189
189
  color: inherit;
190
190
  }
191
191
 
@@ -15,14 +15,18 @@
15
15
 
16
16
  &__list {
17
17
  li {
18
+ a {
19
+ @include govuk-link-decoration;
20
+ @include govuk-link-style-no-visited-state;
21
+ @include govuk-link-style-no-underline;
22
+ }
23
+
18
24
  a:link, a:visited {
19
25
  display: block;
20
26
  position: relative;
21
27
  padding: 8px govuk-spacing(6) 8px govuk-spacing(2);
22
28
  margin: 0 -1 * govuk-spacing(3);
23
29
  border-left: 5px solid transparent;
24
- text-decoration: none;
25
- color: $govuk-link-colour;
26
30
 
27
31
  @include govuk-media-query(tablet) {
28
32
  &.toc-link--in-view {
@@ -33,21 +37,17 @@
33
37
  }
34
38
  }
35
39
 
40
+ a:hover {
41
+ @include govuk-link-hover-decoration;
42
+ }
43
+
36
44
  a:focus {
37
- outline: none;
45
+ text-decoration: none;
38
46
 
39
47
  span {
40
48
  @include govuk-focused-text;
41
49
  }
42
50
  }
43
-
44
- a:hover {
45
- text-decoration: underline;
46
-
47
- &:focus {
48
- text-decoration: none;
49
- }
50
- }
51
51
  }
52
52
 
53
53
  // Top level
@@ -13,7 +13,11 @@ module GovukTechDocs
13
13
  end
14
14
 
15
15
  def href
16
- @page_url + "#" + @attributes["id"]
16
+ if @page_url != "" && size == 1
17
+ @page_url
18
+ else
19
+ @page_url + "#" + @attributes["id"]
20
+ end
17
21
  end
18
22
 
19
23
  def title
@@ -24,7 +24,7 @@ module GovukTechDocs
24
24
  end
25
25
 
26
26
  if tree.children.any? && level < @max_level
27
- output += indentation + "<ul>\n"
27
+ output += indentation + "<ul>\n" unless level.zero?
28
28
 
29
29
  tree.children.each do |child|
30
30
  output += indentation + INDENTATION_INCREMENT + "<li>\n"
@@ -36,7 +36,7 @@ module GovukTechDocs
36
36
  output += indentation + INDENTATION_INCREMENT + "</li>\n"
37
37
  end
38
38
 
39
- output += indentation + "</ul>\n"
39
+ output += indentation + "</ul>\n" unless level.zero?
40
40
  end
41
41
 
42
42
  output
@@ -32,7 +32,8 @@ module GovukTechDocs
32
32
  # Sort by weight frontmatter
33
33
  resources = resources
34
34
  .sort_by { |r| [r.data.weight ? 0 : 1, r.data.weight || 0] }
35
- output = "";
35
+
36
+ output = "<ul>\n"
36
37
  resources.each do |resource|
37
38
  # Skip from page tree if hide_in_navigation:true frontmatter
38
39
  next if resource.data.hide_in_navigation
@@ -63,9 +64,9 @@ module GovukTechDocs
63
64
  end
64
65
 
65
66
  if resource.children.any? && resource.url != home_url
66
- output += %{<ul><li><a href="#{resource.url}"><span>#{resource.data.title}</span></a>\n}
67
+ output += %{<li><a href="#{resource.url}"><span>#{resource.data.title}</span></a>\n}
67
68
  output += render_page_tree(resource.children, current_page, config, current_page_html)
68
- output += "</li></ul>"
69
+ output += "</li>\n"
69
70
  else
70
71
  output +=
71
72
  single_page_table_of_contents(
@@ -75,6 +76,8 @@ module GovukTechDocs
75
76
  )
76
77
  end
77
78
  end
79
+ output += "</ul>\n"
80
+
78
81
  output
79
82
  end
80
83
  end
@@ -16,7 +16,7 @@ module GovukTechDocs
16
16
 
17
17
  def header(text, level)
18
18
  anchor = UniqueIdentifierGenerator.instance.create(text, level)
19
- %(<h#{level} id="#{anchor}">#{text}</h#{level}>)
19
+ %(<h#{level} id="#{anchor}">#{text}</h#{level}>\n)
20
20
  end
21
21
 
22
22
  def image(link, *args)
@@ -1,3 +1,3 @@
1
1
  module GovukTechDocs
2
- VERSION = "2.2.1".freeze
2
+ VERSION = "2.4.1".freeze
3
3
  end
@@ -13,7 +13,7 @@
13
13
  focusable="false"
14
14
  class="govuk-header__logotype-crown"
15
15
  xmlns="http://www.w3.org/2000/svg"
16
- viewbox="0 0 132 97"
16
+ viewBox="0 0 132 97"
17
17
  height="30"
18
18
  width="36"
19
19
  >
@@ -21,7 +21,6 @@
21
21
  fill="currentColor" fill-rule="evenodd"
22
22
  d="M25 30.2c3.5 1.5 7.7-.2 9.1-3.7 1.5-3.6-.2-7.8-3.9-9.2-3.6-1.4-7.6.3-9.1 3.9-1.4 3.5.3 7.5 3.9 9zM9 39.5c3.6 1.5 7.8-.2 9.2-3.7 1.5-3.6-.2-7.8-3.9-9.1-3.6-1.5-7.6.2-9.1 3.8-1.4 3.5.3 7.5 3.8 9zM4.4 57.2c3.5 1.5 7.7-.2 9.1-3.8 1.5-3.6-.2-7.7-3.9-9.1-3.5-1.5-7.6.3-9.1 3.8-1.4 3.5.3 7.6 3.9 9.1zm38.3-21.4c3.5 1.5 7.7-.2 9.1-3.8 1.5-3.6-.2-7.7-3.9-9.1-3.6-1.5-7.6.3-9.1 3.8-1.3 3.6.4 7.7 3.9 9.1zm64.4-5.6c-3.6 1.5-7.8-.2-9.1-3.7-1.5-3.6.2-7.8 3.8-9.2 3.6-1.4 7.7.3 9.2 3.9 1.3 3.5-.4 7.5-3.9 9zm15.9 9.3c-3.6 1.5-7.7-.2-9.1-3.7-1.5-3.6.2-7.8 3.7-9.1 3.6-1.5 7.7.2 9.2 3.8 1.5 3.5-.3 7.5-3.8 9zm4.7 17.7c-3.6 1.5-7.8-.2-9.2-3.8-1.5-3.6.2-7.7 3.9-9.1 3.6-1.5 7.7.3 9.2 3.8 1.3 3.5-.4 7.6-3.9 9.1zM89.3 35.8c-3.6 1.5-7.8-.2-9.2-3.8-1.4-3.6.2-7.7 3.9-9.1 3.6-1.5 7.7.3 9.2 3.8 1.4 3.6-.3 7.7-3.9 9.1zM69.7 17.7l8.9 4.7V9.3l-8.9 2.8c-.2-.3-.5-.6-.9-.9L72.4 0H59.6l3.5 11.2c-.3.3-.6.5-.9.9l-8.8-2.8v13.1l8.8-4.7c.3.3.6.7.9.9l-5 15.4v.1c-.2.8-.4 1.6-.4 2.4 0 4.1 3.1 7.5 7 8.1h.2c.3 0 .7.1 1 .1.4 0 .7 0 1-.1h.2c4-.6 7.1-4.1 7.1-8.1 0-.8-.1-1.7-.4-2.4V34l-5.1-15.4c.4-.2.7-.6 1-.9zM66 92.8c16.9 0 32.8 1.1 47.1 3.2 4-16.9 8.9-26.7 14-33.5l-9.6-3.4c1 4.9 1.1 7.2 0 10.2-1.5-1.4-3-4.3-4.2-8.7L108.6 76c2.8-2 5-3.2 7.5-3.3-4.4 9.4-10 11.9-13.6 11.2-4.3-.8-6.3-4.6-5.6-7.9 1-4.7 5.7-5.9 8-.5 4.3-8.7-3-11.4-7.6-8.8 7.1-7.2 7.9-13.5 2.1-21.1-8 6.1-8.1 12.3-4.5 20.8-4.7-5.4-12.1-2.5-9.5 6.2 3.4-5.2 7.9-2 7.2 3.1-.6 4.3-6.4 7.8-13.5 7.2-10.3-.9-10.9-8-11.2-13.8 2.5-.5 7.1 1.8 11 7.3L80.2 60c-4.1 4.4-8 5.3-12.3 5.4 1.4-4.4 8-11.6 8-11.6H55.5s6.4 7.2 7.9 11.6c-4.2-.1-8-1-12.3-5.4l1.4 16.4c3.9-5.5 8.5-7.7 10.9-7.3-.3 5.8-.9 12.8-11.1 13.8-7.2.6-12.9-2.9-13.5-7.2-.7-5 3.8-8.3 7.1-3.1 2.7-8.7-4.6-11.6-9.4-6.2 3.7-8.5 3.6-14.7-4.6-20.8-5.8 7.6-5 13.9 2.2 21.1-4.7-2.6-11.9.1-7.7 8.8 2.3-5.5 7.1-4.2 8.1.5.7 3.3-1.3 7.1-5.7 7.9-3.5.7-9-1.8-13.5-11.2 2.5.1 4.7 1.3 7.5 3.3l-4.7-15.4c-1.2 4.4-2.7 7.2-4.3 8.7-1.1-3-.9-5.3 0-10.2l-9.5 3.4c5 6.9 9.9 16.7 14 33.5 14.8-2.1 30.8-3.2 47.7-3.2z"
23
23
  ></path>
24
- <image src="/assets/images/govuk-logotype-crown.png" xlink:href="" class="govuk-header__logotype-crown-fallback-image" width="36" height="32"></image>
25
24
  </svg>
26
25
  <span class="govuk-header__logotype-text">
27
26
  GOV.UK
@@ -11,7 +11,7 @@
11
11
  class="govuk-input govuk-!-margin-bottom-0 search__input"
12
12
  aria-controls="search-results"
13
13
  placeholder="Search">
14
- <button type="submit" class="govuk-button search__button">Search</button>
14
+ <button type="submit" class="search__button">Search</button>
15
15
  </form>
16
16
  <div id="search-results" class="search-results" aria-hidden="true" role="dialog" aria-labelledby="search-results-title">
17
17
  <div class="search-results__inner">
@@ -0,0 +1,3 @@
1
+ @import "settings/all";
2
+ @import "tools/all";
3
+ @import "helpers/all";
@@ -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
+ }
1548
1554
 
1549
- // Update count message
1550
- var boundUpdateCountMessage = this.updateCountMessage.bind(this);
1551
- boundUpdateCountMessage();
1555
+ this.sync();
1556
+ };
1557
+
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,52 +1674,157 @@ 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
+ };
1700
1734
 
1701
- var $content = this.$module.querySelector('#' + $input.getAttribute('aria-controls'));
1702
- if ($content) {
1703
- $content.classList.toggle('govuk-checkboxes__conditional--hidden', !inputIsChecked);
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;
1748
+
1749
+ $input.setAttribute('aria-expanded', inputIsChecked);
1750
+ $target.classList.toggle('govuk-checkboxes__conditional--hidden', !inputIsChecked);
1704
1751
  }
1705
1752
  };
1706
1753
 
1754
+ /**
1755
+ * Uncheck other checkboxes
1756
+ *
1757
+ * Find any other checkbox inputs with the same name value, and uncheck them.
1758
+ * This is useful for when a “None of these" checkbox is checked.
1759
+ */
1760
+ Checkboxes.prototype.unCheckAllInputsExcept = function ($input) {
1761
+ var allInputsWithSameName = document.querySelectorAll('input[type="checkbox"][name="' + $input.name + '"]');
1762
+
1763
+ nodeListForEach(allInputsWithSameName, function ($inputWithSameName) {
1764
+ var hasSameFormOwner = ($input.form === $inputWithSameName.form);
1765
+ if (hasSameFormOwner && $inputWithSameName !== $input) {
1766
+ $inputWithSameName.checked = false;
1767
+ }
1768
+ });
1769
+
1770
+ this.syncAllConditionalReveals();
1771
+ };
1772
+
1773
+ /**
1774
+ * Uncheck exclusive inputs
1775
+ *
1776
+ * Find any checkbox inputs with the same name value and the 'exclusive' behaviour,
1777
+ * and uncheck them. This helps prevent someone checking both a regular checkbox and a
1778
+ * "None of these" checkbox in the same fieldset.
1779
+ */
1780
+ Checkboxes.prototype.unCheckExclusiveInputs = function ($input) {
1781
+ var allInputsWithSameNameAndExclusiveBehaviour = document.querySelectorAll(
1782
+ 'input[data-behaviour="exclusive"][type="checkbox"][name="' + $input.name + '"]'
1783
+ );
1784
+
1785
+ nodeListForEach(allInputsWithSameNameAndExclusiveBehaviour, function ($exclusiveInput) {
1786
+ var hasSameFormOwner = ($input.form === $exclusiveInput.form);
1787
+ if (hasSameFormOwner) {
1788
+ $exclusiveInput.checked = false;
1789
+ }
1790
+ });
1791
+
1792
+ this.syncAllConditionalReveals();
1793
+ };
1794
+
1795
+ /**
1796
+ * Click event handler
1797
+ *
1798
+ * Handle a click within the $module – if the click occurred on a checkbox, sync
1799
+ * the state of any associated conditional reveal with the checkbox state.
1800
+ *
1801
+ * @param {MouseEvent} event Click event
1802
+ */
1707
1803
  Checkboxes.prototype.handleClick = function (event) {
1708
1804
  var $target = event.target;
1709
1805
 
1710
- // If a checkbox with aria-controls, handle click
1711
- var isCheckbox = $target.getAttribute('type') === 'checkbox';
1806
+ // Ignore clicks on things that aren't checkbox inputs
1807
+ if ($target.type !== 'checkbox') {
1808
+ return
1809
+ }
1810
+
1811
+ // If the checkbox conditionally-reveals some content, sync the state
1712
1812
  var hasAriaControls = $target.getAttribute('aria-controls');
1713
- if (isCheckbox && hasAriaControls) {
1714
- this.setAttributes($target);
1813
+ if (hasAriaControls) {
1814
+ this.syncConditionalRevealWithInputState($target);
1815
+ }
1816
+
1817
+ // No further behaviour needed for unchecking
1818
+ if (!$target.checked) {
1819
+ return
1820
+ }
1821
+
1822
+ // Handle 'exclusive' checkbox behaviour (ie "None of these")
1823
+ var hasBehaviourExclusive = ($target.getAttribute('data-behaviour') === 'exclusive');
1824
+ if (hasBehaviourExclusive) {
1825
+ this.unCheckAllInputsExcept($target);
1826
+ } else {
1827
+ this.unCheckExclusiveInputs($target);
1715
1828
  }
1716
1829
  };
1717
1830
 
@@ -1904,122 +2017,216 @@ ErrorSummary.prototype.getAssociatedLegendOrLabel = function ($input) {
1904
2017
  $input.closest('label')
1905
2018
  };
1906
2019
 
1907
- function Header ($module) {
2020
+ function NotificationBanner ($module) {
1908
2021
  this.$module = $module;
1909
2022
  }
1910
2023
 
1911
- Header.prototype.init = function () {
1912
- // Check for module
2024
+ /**
2025
+ * Initialise the component
2026
+ */
2027
+ NotificationBanner.prototype.init = function () {
1913
2028
  var $module = this.$module;
2029
+ // Check for module
1914
2030
  if (!$module) {
1915
2031
  return
1916
2032
  }
1917
2033
 
1918
- // Check for button
1919
- var $toggleButton = $module.querySelector('.govuk-js-header-toggle');
1920
- if (!$toggleButton) {
2034
+ this.setFocus();
2035
+ };
2036
+
2037
+ /**
2038
+ * Focus the element
2039
+ *
2040
+ * If `role="alert"` is set, focus the element to help some assistive technologies
2041
+ * prioritise announcing it.
2042
+ *
2043
+ * You can turn off the auto-focus functionality by setting `data-disable-auto-focus="true"` in the
2044
+ * component HTML. You might wish to do this based on user research findings, or to avoid a clash
2045
+ * with another element which should be focused when the page loads.
2046
+ */
2047
+ NotificationBanner.prototype.setFocus = function () {
2048
+ var $module = this.$module;
2049
+
2050
+ if ($module.getAttribute('data-disable-auto-focus') === 'true') {
1921
2051
  return
1922
2052
  }
1923
2053
 
1924
- // Handle $toggleButton click events
1925
- $toggleButton.addEventListener('click', this.handleClick.bind(this));
2054
+ if ($module.getAttribute('role') !== 'alert') {
2055
+ return
2056
+ }
2057
+
2058
+ // Set tabindex to -1 to make the element focusable with JavaScript.
2059
+ // Remove the tabindex on blur as the component doesn't need to be focusable after the page has
2060
+ // loaded.
2061
+ if (!$module.getAttribute('tabindex')) {
2062
+ $module.setAttribute('tabindex', '-1');
2063
+
2064
+ $module.addEventListener('blur', function () {
2065
+ $module.removeAttribute('tabindex');
2066
+ });
2067
+ }
2068
+
2069
+ $module.focus();
1926
2070
  };
1927
2071
 
2072
+ function Header ($module) {
2073
+ this.$module = $module;
2074
+ this.$menuButton = $module && $module.querySelector('.govuk-js-header-toggle');
2075
+ this.$menu = this.$menuButton && $module.querySelector(
2076
+ '#' + this.$menuButton.getAttribute('aria-controls')
2077
+ );
2078
+ }
2079
+
1928
2080
  /**
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;
2081
+ * Initialise header
2082
+ *
2083
+ * Check for the presence of the header, menu and menu button – if any are
2084
+ * missing then there's nothing to do so return early.
2085
+ */
2086
+ Header.prototype.init = function () {
2087
+ if (!this.$module || !this.$menuButton || !this.$menu) {
2088
+ return
1938
2089
  }
2090
+
2091
+ this.syncState(this.$menu.classList.contains('govuk-header__navigation--open'));
2092
+ this.$menuButton.addEventListener('click', this.handleMenuButtonClick.bind(this));
1939
2093
  };
1940
2094
 
1941
2095
  /**
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');
2096
+ * Sync menu state
2097
+ *
2098
+ * Sync the menu button class and the accessible state of the menu and the menu
2099
+ * button with the visible state of the menu
2100
+ *
2101
+ * @param {boolean} isVisible Whether the menu is currently visible
2102
+ */
2103
+ Header.prototype.syncState = function (isVisible) {
2104
+ this.$menuButton.classList.toggle('govuk-header__menu-button--open', isVisible);
2105
+ this.$menuButton.setAttribute('aria-expanded', isVisible);
2106
+ };
1954
2107
 
1955
- $toggleButton.setAttribute('aria-expanded', $toggleButton.getAttribute('aria-expanded') !== 'true');
1956
- $target.setAttribute('aria-hidden', $target.getAttribute('aria-hidden') === 'false');
1957
- }
2108
+ /**
2109
+ * Handle menu button click
2110
+ *
2111
+ * When the menu button is clicked, change the visibility of the menu and then
2112
+ * sync the accessibility state and menu button state
2113
+ */
2114
+ Header.prototype.handleMenuButtonClick = function () {
2115
+ var isVisible = this.$menu.classList.toggle('govuk-header__navigation--open');
2116
+ this.syncState(isVisible);
1958
2117
  };
1959
2118
 
1960
2119
  function Radios ($module) {
1961
2120
  this.$module = $module;
2121
+ this.$inputs = $module.querySelectorAll('input[type="radio"]');
1962
2122
  }
1963
2123
 
2124
+ /**
2125
+ * Initialise Radios
2126
+ *
2127
+ * Radios can be associated with a 'conditionally revealed' content block – for
2128
+ * example, a radio for 'Phone' could reveal an additional form field for the
2129
+ * user to enter their phone number.
2130
+ *
2131
+ * These associations are made using a `data-aria-controls` attribute, which is
2132
+ * promoted to an aria-controls attribute during initialisation.
2133
+ *
2134
+ * We also need to restore the state of any conditional reveals on the page (for
2135
+ * example if the user has navigated back), and set up event handlers to keep
2136
+ * the reveal in sync with the radio state.
2137
+ */
1964
2138
  Radios.prototype.init = function () {
1965
2139
  var $module = this.$module;
1966
- var $inputs = $module.querySelectorAll('input[type="radio"]');
2140
+ var $inputs = this.$inputs;
1967
2141
 
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
2142
  nodeListForEach($inputs, function ($input) {
1974
- var controls = $input.getAttribute('data-aria-controls');
2143
+ var target = $input.getAttribute('data-aria-controls');
1975
2144
 
1976
- // Check if input controls anything
1977
- // Check if content exists, before setting attributes.
1978
- if (!controls || !$module.querySelector('#' + controls)) {
2145
+ // Skip radios without data-aria-controls attributes, or where the
2146
+ // target element does not exist.
2147
+ if (!target || !$module.querySelector('#' + target)) {
1979
2148
  return
1980
2149
  }
1981
2150
 
1982
- // If we have content that is controlled, set attributes.
1983
- $input.setAttribute('aria-controls', controls);
2151
+ // Promote the data-aria-controls attribute to a aria-controls attribute
2152
+ // so that the relationship is exposed in the AOM
2153
+ $input.setAttribute('aria-controls', target);
1984
2154
  $input.removeAttribute('data-aria-controls');
1985
- this.setAttributes($input);
1986
- }.bind(this));
2155
+ });
2156
+
2157
+ // When the page is restored after navigating 'back' in some browsers the
2158
+ // state of form controls is not restored until *after* the DOMContentLoaded
2159
+ // event is fired, so we need to sync after the pageshow event in browsers
2160
+ // that support it.
2161
+ if ('onpageshow' in window) {
2162
+ window.addEventListener('pageshow', this.syncAllConditionalReveals.bind(this));
2163
+ } else {
2164
+ window.addEventListener('DOMContentLoaded', this.syncAllConditionalReveals.bind(this));
2165
+ }
2166
+
2167
+ // Although we've set up handlers to sync state on the pageshow or
2168
+ // DOMContentLoaded event, init could be called after those events have fired,
2169
+ // for example if they are added to the page dynamically, so sync now too.
2170
+ this.syncAllConditionalReveals();
1987
2171
 
1988
2172
  // Handle events
1989
2173
  $module.addEventListener('click', this.handleClick.bind(this));
1990
2174
  };
1991
2175
 
1992
- Radios.prototype.setAttributes = function ($input) {
1993
- var $content = document.querySelector('#' + $input.getAttribute('aria-controls'));
2176
+ /**
2177
+ * Sync the conditional reveal states for all inputs in this $module.
2178
+ */
2179
+ Radios.prototype.syncAllConditionalReveals = function () {
2180
+ nodeListForEach(this.$inputs, this.syncConditionalRevealWithInputState.bind(this));
2181
+ };
2182
+
2183
+ /**
2184
+ * Sync conditional reveal with the input state
2185
+ *
2186
+ * Synchronise the visibility of the conditional reveal, and its accessible
2187
+ * state, with the input's checked state.
2188
+ *
2189
+ * @param {HTMLInputElement} $input Radio input
2190
+ */
2191
+ Radios.prototype.syncConditionalRevealWithInputState = function ($input) {
2192
+ var $target = document.querySelector('#' + $input.getAttribute('aria-controls'));
1994
2193
 
1995
- if ($content && $content.classList.contains('govuk-radios__conditional')) {
2194
+ if ($target && $target.classList.contains('govuk-radios__conditional')) {
1996
2195
  var inputIsChecked = $input.checked;
1997
2196
 
1998
2197
  $input.setAttribute('aria-expanded', inputIsChecked);
1999
-
2000
- $content.classList.toggle('govuk-radios__conditional--hidden', !inputIsChecked);
2198
+ $target.classList.toggle('govuk-radios__conditional--hidden', !inputIsChecked);
2001
2199
  }
2002
2200
  };
2003
2201
 
2202
+ /**
2203
+ * Click event handler
2204
+ *
2205
+ * Handle a click within the $module – if the click occurred on a radio, sync
2206
+ * the state of the conditional reveal for all radio buttons in the same form
2207
+ * with the same name (because checking one radio could have un-checked a radio
2208
+ * in another $module)
2209
+ *
2210
+ * @param {MouseEvent} event Click event
2211
+ */
2004
2212
  Radios.prototype.handleClick = function (event) {
2005
2213
  var $clickedInput = event.target;
2006
- // We only want to handle clicks for radio inputs
2214
+
2215
+ // Ignore clicks on things that aren't radio buttons
2007
2216
  if ($clickedInput.type !== 'radio') {
2008
2217
  return
2009
2218
  }
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.
2219
+
2220
+ // We only need to consider radios with conditional reveals, which will have
2221
+ // aria-controls attributes.
2014
2222
  var $allInputs = document.querySelectorAll('input[type="radio"][aria-controls]');
2223
+
2015
2224
  nodeListForEach($allInputs, function ($input) {
2016
- // Only inputs with the same form owner should change.
2017
2225
  var hasSameFormOwner = ($input.form === $clickedInput.form);
2018
-
2019
- // In radios, only radios with the same name will affect each other.
2020
2226
  var hasSameName = ($input.name === $clickedInput.name);
2227
+
2021
2228
  if (hasSameName && hasSameFormOwner) {
2022
- this.setAttributes($input);
2229
+ this.syncConditionalRevealWithInputState($input);
2023
2230
  }
2024
2231
  }.bind(this));
2025
2232
  };
@@ -2379,6 +2586,11 @@ function initAll (options) {
2379
2586
  var $toggleButton = scope.querySelector('[data-module="govuk-header"]');
2380
2587
  new Header($toggleButton).init();
2381
2588
 
2589
+ var $notificationBanners = scope.querySelectorAll('[data-module="govuk-notification-banner"]');
2590
+ nodeListForEach($notificationBanners, function ($notificationBanner) {
2591
+ new NotificationBanner($notificationBanner).init();
2592
+ });
2593
+
2382
2594
  var $radios = scope.querySelectorAll('[data-module="govuk-radios"]');
2383
2595
  nodeListForEach($radios, function ($radio) {
2384
2596
  new Radios($radio).init();