govuk_tech_docs 3.0.1 → 3.1.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.

Potentially problematic release.


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

Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/lib/govuk_tech_docs/version.rb +1 -1
  4. data/lib/source/layouts/_header.erb +3 -3
  5. data/lib/source/layouts/core.erb +1 -1
  6. data/node_modules/govuk-frontend/govuk/all.js +272 -75
  7. data/node_modules/govuk-frontend/govuk/components/accordion/_index.scss +275 -98
  8. data/node_modules/govuk-frontend/govuk/components/accordion/accordion.js +169 -65
  9. data/node_modules/govuk-frontend/govuk/components/button/_index.scss +3 -4
  10. data/node_modules/govuk-frontend/govuk/components/button/button.js +2 -2
  11. data/node_modules/govuk-frontend/govuk/components/character-count/character-count.js +3 -3
  12. data/node_modules/govuk-frontend/govuk/components/checkboxes/_index.scss +14 -0
  13. data/node_modules/govuk-frontend/govuk/components/checkboxes/checkboxes.js +8 -10
  14. data/node_modules/govuk-frontend/govuk/components/cookie-banner/_index.scss +0 -2
  15. data/node_modules/govuk-frontend/govuk/components/details/_index.scss +2 -1
  16. data/node_modules/govuk-frontend/govuk/components/details/details.js +2 -2
  17. data/node_modules/govuk-frontend/govuk/components/error-message/_index.scss +1 -0
  18. data/node_modules/govuk-frontend/govuk/components/error-summary/error-summary.js +2 -2
  19. data/node_modules/govuk-frontend/govuk/components/file-upload/_index.scss +1 -0
  20. data/node_modules/govuk-frontend/govuk/components/footer/_index.scss +6 -37
  21. data/node_modules/govuk-frontend/govuk/components/header/_index.scss +10 -4
  22. data/node_modules/govuk-frontend/govuk/components/header/header.js +4 -4
  23. data/node_modules/govuk-frontend/govuk/components/hint/_index.scss +1 -3
  24. data/node_modules/govuk-frontend/govuk/components/input/_index.scss +1 -1
  25. data/node_modules/govuk-frontend/govuk/components/notification-banner/notification-banner.js +499 -2
  26. data/node_modules/govuk-frontend/govuk/components/panel/_index.scss +13 -1
  27. data/node_modules/govuk-frontend/govuk/components/radios/_index.scss +14 -0
  28. data/node_modules/govuk-frontend/govuk/components/radios/radios.js +4 -4
  29. data/node_modules/govuk-frontend/govuk/components/select/_index.scss +1 -1
  30. data/node_modules/govuk-frontend/govuk/components/skip-link/_index.scss +13 -0
  31. data/node_modules/govuk-frontend/govuk/components/skip-link/skip-link.js +1108 -0
  32. data/node_modules/govuk-frontend/govuk/components/summary-list/_index.scss +15 -23
  33. data/node_modules/govuk-frontend/govuk/components/tabs/_index.scss +2 -2
  34. data/node_modules/govuk-frontend/govuk/components/tabs/tabs.js +2 -2
  35. data/node_modules/govuk-frontend/govuk/components/tag/_index.scss +0 -5
  36. data/node_modules/govuk-frontend/govuk/components/textarea/_index.scss +1 -1
  37. data/node_modules/govuk-frontend/govuk/core/_all.scss +0 -1
  38. data/node_modules/govuk-frontend/govuk/core/_global-styles.scss +0 -6
  39. data/node_modules/govuk-frontend/govuk/core/_links.scss +0 -6
  40. data/node_modules/govuk-frontend/govuk/core/_lists.scss +0 -6
  41. data/node_modules/govuk-frontend/govuk/core/_section-break.scss +0 -6
  42. data/node_modules/govuk-frontend/govuk/core/_typography.scss +0 -6
  43. data/node_modules/govuk-frontend/govuk/helpers/_colour.scss +2 -2
  44. data/node_modules/govuk-frontend/govuk/helpers/_spacing.scss +22 -4
  45. data/node_modules/govuk-frontend/govuk/objects/_all.scss +1 -0
  46. data/node_modules/govuk-frontend/govuk/objects/_main-wrapper.scss +15 -30
  47. data/node_modules/govuk-frontend/govuk/{core → objects}/_template.scss +1 -5
  48. data/node_modules/govuk-frontend/govuk/overrides/_all.scss +1 -0
  49. data/node_modules/govuk-frontend/govuk/overrides/_display.scss +0 -6
  50. data/node_modules/govuk-frontend/govuk/overrides/_spacing.scss +0 -6
  51. data/node_modules/govuk-frontend/govuk/overrides/_text-align.scss +14 -0
  52. data/node_modules/govuk-frontend/govuk/overrides/_typography.scss +0 -6
  53. data/node_modules/govuk-frontend/govuk/overrides/_width.scss +0 -6
  54. data/node_modules/govuk-frontend/govuk/settings/_colours-organisations.scss +3 -0
  55. data/node_modules/govuk-frontend/govuk/settings/_measurements.scss +0 -10
  56. data/node_modules/govuk-frontend/govuk/tools/_all.scss +0 -1
  57. data/package-lock.json +3 -3
  58. data/package.json +1 -1
  59. metadata +5 -4
  60. data/node_modules/govuk-frontend/govuk/tools/_iff.scss +0 -17
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 306970ce3c2eedf60e681b68a6a0539480d8a4626ff4bd3f57207fa32cb2a06c
4
- data.tar.gz: 2d8ccd150d9e394a9c54ee3a33bd0c424ecd71848022d00bbc7ce7f66c1cfc24
3
+ metadata.gz: b05aaaeb08b75633793f5ede8102ef35d54cedbe6bc419b9fe262a3596d6cfef
4
+ data.tar.gz: 30ab85794e056634591d692999364da7644908653e06d2707aa15eeff9b5bfea
5
5
  SHA512:
6
- metadata.gz: 2efb012ebfdf219f6f08fb91130189fb23368731a31371f806fb16727b627f7ba7c3f8fa0894afc0e10af879adb950eb1b2d7dfca49f98b0fa2fe6eabc129a74
7
- data.tar.gz: e34554e67b183255fa3f20fb2dcb95e4c3af042de317f7735c82e5c3a988524de7c4e0e12b58bac20f9bdf770a9bd2e556bea6febb0e62ecd560f04cdd885782
6
+ metadata.gz: 406187054e9b14fd631fb56a2602f8c607ab2abfe8be466673ffee99fba6451ebe684dca40e53270b66b5b1a6b12142336ad26124335b33b02205e13005290e8
7
+ data.tar.gz: fc0afcd3567241e487bb75fbfebb26561ee8b6c9dd0a50d5f2d8252ea2d8a95c71976ae2540ff1f58e2d89886234c25b6d6b726fb74e618a1fba05ae97b0303d
data/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 3.1.0
6
+
7
+ ### New features
8
+
9
+ There are some steps you should follow as the Technical Documentation Template (TDT) now uses GOV.UK Frontend 4.0.0.
10
+
11
+ 1. Update your documentation site to use the latest template version. You can [follow the TDT guidance on using the latest template version](https://tdt-documentation.london.cloudapps.digital/maintain_project/use_latest_template/).
12
+ 2. Check your documentation site displays correctly. If your site does not display correctly, you can refer to the [GOV.UK Frontend 4.0.0 release note](https://github.com/alphagov/govuk-frontend/releases/tag/v4.0.0) for more information.
13
+
5
14
  ## 3.0.1
6
15
 
7
16
  ### Fixes
@@ -1,3 +1,3 @@
1
1
  module GovukTechDocs
2
- VERSION = "3.0.1".freeze
2
+ VERSION = "3.1.0".freeze
3
3
  end
@@ -41,9 +41,9 @@
41
41
  </div>
42
42
  <% if config[:tech_docs][:header_links] %>
43
43
  <div class="govuk-header__content">
44
- <button type="button" class="govuk-header__menu-button govuk-js-header-toggle" aria-controls="navigation" aria-label="Show or hide Top Level Navigation">Menu</button>
45
- <nav>
46
- <ul id="navigation" class="govuk-header__navigation govuk-header__navigation--end" aria-label="Top Level Navigation">
44
+ <nav class="govuk-header__navigation govuk-header__navigation--end" aria-label="Menu">
45
+ <button type="button" class="govuk-header__menu-button govuk-js-header-toggle" aria-controls="navigation" aria-label="Show or hide menu">Menu</button>
46
+ <ul id="navigation" class="govuk-header__navigation-list">
47
47
  <% config[:tech_docs][:header_links].each do |title, path| %>
48
48
  <li class="govuk-header__navigation-item<% if active_page(path) %> govuk-header__navigation-item--active<% end %>">
49
49
  <a class="govuk-header__link" href="<%= url_for path %>"><%= title %></a>
@@ -27,7 +27,7 @@
27
27
 
28
28
  <div class="app-pane">
29
29
  <div class="app-pane__header toc-open-disabled">
30
- <a href="#content" class="govuk-skip-link">Skip to main content</a>
30
+ <a href="#content" class="govuk-skip-link" data-module="govuk-skip-link">Skip to main content</a>
31
31
 
32
32
  <%= partial 'layouts/header' %>
33
33
  </div>
@@ -777,19 +777,28 @@ function Accordion ($module) {
777
777
  this.$module = $module;
778
778
  this.moduleId = $module.getAttribute('id');
779
779
  this.$sections = $module.querySelectorAll('.govuk-accordion__section');
780
- this.$openAllButton = '';
780
+ this.$showAllButton = '';
781
781
  this.browserSupportsSessionStorage = helper.checkForSessionStorage();
782
782
 
783
783
  this.controlsClass = 'govuk-accordion__controls';
784
- this.openAllClass = 'govuk-accordion__open-all';
785
- this.iconClass = 'govuk-accordion__icon';
784
+ this.showAllClass = 'govuk-accordion__show-all';
785
+ this.showAllTextClass = 'govuk-accordion__show-all-text';
786
786
 
787
+ this.sectionExpandedClass = 'govuk-accordion__section--expanded';
788
+ this.sectionButtonClass = 'govuk-accordion__section-button';
787
789
  this.sectionHeaderClass = 'govuk-accordion__section-header';
788
- this.sectionHeaderFocusedClass = 'govuk-accordion__section-header--focused';
789
790
  this.sectionHeadingClass = 'govuk-accordion__section-heading';
791
+ this.sectionHeadingTextClass = 'govuk-accordion__section-heading-text';
792
+ this.sectionHeadingTextFocusClass = 'govuk-accordion__section-heading-text-focus';
793
+
794
+ this.sectionShowHideToggleClass = 'govuk-accordion__section-toggle';
795
+ this.sectionShowHideToggleFocusClass = 'govuk-accordion__section-toggle-focus';
796
+ this.sectionShowHideTextClass = 'govuk-accordion__section-toggle-text';
797
+ this.upChevronIconClass = 'govuk-accordion-nav__chevron';
798
+ this.downChevronIconClass = 'govuk-accordion-nav__chevron--down';
799
+
790
800
  this.sectionSummaryClass = 'govuk-accordion__section-summary';
791
- this.sectionButtonClass = 'govuk-accordion__section-button';
792
- this.sectionExpandedClass = 'govuk-accordion__section--expanded';
801
+ this.sectionSummaryFocusClass = 'govuk-accordion__section-summary-focus';
793
802
  }
794
803
 
795
804
  // Initialize component
@@ -800,32 +809,39 @@ Accordion.prototype.init = function () {
800
809
  }
801
810
 
802
811
  this.initControls();
803
-
804
812
  this.initSectionHeaders();
805
813
 
806
- // See if "Open all" button text should be updated
814
+ // See if "Show all sections" button text should be updated
807
815
  var areAllSectionsOpen = this.checkIfAllSectionsOpen();
808
- this.updateOpenAllButton(areAllSectionsOpen);
816
+ this.updateShowAllButton(areAllSectionsOpen);
809
817
  };
810
818
 
811
819
  // Initialise controls and set attributes
812
820
  Accordion.prototype.initControls = function () {
813
- // Create "Open all" button and set attributes
814
- this.$openAllButton = document.createElement('button');
815
- this.$openAllButton.setAttribute('type', 'button');
816
- this.$openAllButton.innerHTML = 'Open all <span class="govuk-visually-hidden">sections</span>';
817
- this.$openAllButton.setAttribute('class', this.openAllClass);
818
- this.$openAllButton.setAttribute('aria-expanded', 'false');
819
- this.$openAllButton.setAttribute('type', 'button');
821
+ // Create "Show all" button and set attributes
822
+ this.$showAllButton = document.createElement('button');
823
+ this.$showAllButton.setAttribute('type', 'button');
824
+ this.$showAllButton.setAttribute('class', this.showAllClass);
825
+ this.$showAllButton.setAttribute('aria-expanded', 'false');
826
+
827
+ // Create icon, add to element
828
+ var $icon = document.createElement('span');
829
+ $icon.classList.add(this.upChevronIconClass);
830
+ this.$showAllButton.appendChild($icon);
820
831
 
821
832
  // Create control wrapper and add controls to it
822
- var accordionControls = document.createElement('div');
823
- accordionControls.setAttribute('class', this.controlsClass);
824
- accordionControls.appendChild(this.$openAllButton);
825
- this.$module.insertBefore(accordionControls, this.$module.firstChild);
833
+ var $accordionControls = document.createElement('div');
834
+ $accordionControls.setAttribute('class', this.controlsClass);
835
+ $accordionControls.appendChild(this.$showAllButton);
836
+ this.$module.insertBefore($accordionControls, this.$module.firstChild);
837
+
838
+ // Build additional wrapper for Show all toggle text and place after icon
839
+ var $wrappershowAllText = document.createElement('span');
840
+ $wrappershowAllText.classList.add(this.showAllTextClass);
841
+ this.$showAllButton.appendChild($wrappershowAllText);
826
842
 
827
- // Handle events for the controls
828
- this.$openAllButton.addEventListener('click', this.onOpenOrCloseAllToggle.bind(this));
843
+ // Handle click events on the show/hide all button
844
+ this.$showAllButton.addEventListener('click', this.onShowOrHideAllToggle.bind(this));
829
845
  };
830
846
 
831
847
  // Initialise section headers
@@ -833,13 +849,12 @@ Accordion.prototype.initSectionHeaders = function () {
833
849
  // Loop through section headers
834
850
  nodeListForEach(this.$sections, function ($section, i) {
835
851
  // Set header attributes
836
- var header = $section.querySelector('.' + this.sectionHeaderClass);
837
- this.initHeaderAttributes(header, i);
838
-
852
+ var $header = $section.querySelector('.' + this.sectionHeaderClass);
853
+ this.constructHeaderMarkup($header, i);
839
854
  this.setExpanded(this.isExpanded($section), $section);
840
855
 
841
856
  // Handle events
842
- header.addEventListener('click', this.onSectionToggle.bind(this, $section));
857
+ $header.addEventListener('click', this.onSectionToggle.bind(this, $section));
843
858
 
844
859
  // See if there is any state stored in sessionStorage and set the sections to
845
860
  // open or closed.
@@ -847,51 +862,100 @@ Accordion.prototype.initSectionHeaders = function () {
847
862
  }.bind(this));
848
863
  };
849
864
 
850
- // Set individual header attributes
851
- Accordion.prototype.initHeaderAttributes = function ($headerWrapper, index) {
852
- var $module = this;
865
+ Accordion.prototype.constructHeaderMarkup = function ($headerWrapper, index) {
853
866
  var $span = $headerWrapper.querySelector('.' + this.sectionButtonClass);
854
867
  var $heading = $headerWrapper.querySelector('.' + this.sectionHeadingClass);
855
868
  var $summary = $headerWrapper.querySelector('.' + this.sectionSummaryClass);
856
869
 
857
- // Copy existing span element to an actual button element, for improved accessibility.
870
+ // Create a button element that will replace the '.govuk-accordion__section-button' span
858
871
  var $button = document.createElement('button');
859
872
  $button.setAttribute('type', 'button');
860
- $button.setAttribute('id', this.moduleId + '-heading-' + (index + 1));
861
873
  $button.setAttribute('aria-controls', this.moduleId + '-content-' + (index + 1));
862
874
 
863
875
  // Copy all attributes (https://developer.mozilla.org/en-US/docs/Web/API/Element/attributes) from $span to $button
864
876
  for (var i = 0; i < $span.attributes.length; i++) {
865
877
  var attr = $span.attributes.item(i);
866
- $button.setAttribute(attr.nodeName, attr.nodeValue);
878
+ // Add all attributes but not ID as this is being added to
879
+ // the section heading ($headingText)
880
+ if (attr.nodeName !== 'id') {
881
+ $button.setAttribute(attr.nodeName, attr.nodeValue);
882
+ }
867
883
  }
868
884
 
869
- $button.addEventListener('focusin', function (e) {
870
- if (!$headerWrapper.classList.contains($module.sectionHeaderFocusedClass)) {
871
- $headerWrapper.className += ' ' + $module.sectionHeaderFocusedClass;
885
+ // Create container for heading text so it can be styled
886
+ var $headingText = document.createElement('span');
887
+ $headingText.classList.add(this.sectionHeadingTextClass);
888
+ // Copy the span ID to the heading text to allow it to be referenced by `aria-labelledby` on the
889
+ // hidden content area without "Show this section"
890
+ $headingText.id = $span.id;
891
+
892
+ // Create an inner heading text container to limit the width of the focus state
893
+ var $headingTextFocus = document.createElement('span');
894
+ $headingTextFocus.classList.add(this.sectionHeadingTextFocusClass);
895
+ $headingText.appendChild($headingTextFocus);
896
+ // span could contain HTML elements (see https://www.w3.org/TR/2011/WD-html5-20110525/content-models.html#phrasing-content)
897
+ $headingTextFocus.innerHTML = $span.innerHTML;
898
+
899
+ // Create container for show / hide icons and text.
900
+ var $showToggle = document.createElement('span');
901
+ $showToggle.classList.add(this.sectionShowHideToggleClass);
902
+ // Tell Google not to index the 'show' text as part of the heading
903
+ // For the snippet to work with JavaScript, it must be added before adding the page element to the
904
+ // page's DOM. See https://developers.google.com/search/docs/advanced/robots/robots_meta_tag#data-nosnippet-attr
905
+ $showToggle.setAttribute('data-nosnippet', '');
906
+ // Create an inner container to limit the width of the focus state
907
+ var $showToggleFocus = document.createElement('span');
908
+ $showToggleFocus.classList.add(this.sectionShowHideToggleFocusClass);
909
+ $showToggle.appendChild($showToggleFocus);
910
+ // Create wrapper for the show / hide text. Append text after the show/hide icon
911
+ var $showToggleText = document.createElement('span');
912
+ var $icon = document.createElement('span');
913
+ $icon.classList.add(this.upChevronIconClass);
914
+ $showToggleFocus.appendChild($icon);
915
+ $showToggleText.classList.add(this.sectionShowHideTextClass);
916
+ $showToggleFocus.appendChild($showToggleText);
917
+
918
+ // Append elements to the button:
919
+ // 1. Heading text
920
+ // 2. Punctuation
921
+ // 3. (Optional: Summary line followed by punctuation)
922
+ // 4. Show / hide toggle
923
+ $button.appendChild($headingText);
924
+ $button.appendChild(this.getButtonPunctuationEl());
925
+
926
+ // If summary content exists add to DOM in correct order
927
+ if (typeof ($summary) !== 'undefined' && $summary !== null) {
928
+ // Create a new `span` element and copy the summary line content from the original `div` to the
929
+ // new `span`
930
+ // This is because the summary line text is now inside a button element, which can only contain
931
+ // phrasing content
932
+ var $summarySpan = document.createElement('span');
933
+ // Create an inner summary container to limit the width of the summary focus state
934
+ var $summarySpanFocus = document.createElement('span');
935
+ $summarySpanFocus.classList.add(this.sectionSummaryFocusClass);
936
+ $summarySpan.appendChild($summarySpanFocus);
937
+
938
+ // Get original attributes, and pass them to the replacement
939
+ for (var j = 0, l = $summary.attributes.length; j < l; ++j) {
940
+ var nodeName = $summary.attributes.item(j).nodeName;
941
+ var nodeValue = $summary.attributes.item(j).nodeValue;
942
+ $summarySpan.setAttribute(nodeName, nodeValue);
872
943
  }
873
- });
874
944
 
875
- $button.addEventListener('blur', function (e) {
876
- $headerWrapper.classList.remove($module.sectionHeaderFocusedClass);
877
- });
945
+ // Copy original contents of summary to the new summary span
946
+ $summarySpanFocus.innerHTML = $summary.innerHTML;
878
947
 
879
- if (typeof ($summary) !== 'undefined' && $summary !== null) {
880
- $button.setAttribute('aria-describedby', this.moduleId + '-summary-' + (index + 1));
948
+ // Replace the original summary `div` with the new summary `span`
949
+ $summary.parentNode.replaceChild($summarySpan, $summary);
950
+
951
+ $button.appendChild($summarySpan);
952
+ $button.appendChild(this.getButtonPunctuationEl());
881
953
  }
882
954
 
883
- // $span could contain HTML elements (see https://www.w3.org/TR/2011/WD-html5-20110525/content-models.html#phrasing-content)
884
- $button.innerHTML = $span.innerHTML;
955
+ $button.appendChild($showToggle);
885
956
 
886
957
  $heading.removeChild($span);
887
958
  $heading.appendChild($button);
888
-
889
- // Add "+/-" icon
890
- var icon = document.createElement('span');
891
- icon.className = this.iconClass;
892
- icon.setAttribute('aria-hidden', 'true');
893
-
894
- $button.appendChild(icon);
895
959
  };
896
960
 
897
961
  // When section toggled, set and store state
@@ -904,10 +968,9 @@ Accordion.prototype.onSectionToggle = function ($section) {
904
968
  };
905
969
 
906
970
  // When Open/Close All toggled, set and store state
907
- Accordion.prototype.onOpenOrCloseAllToggle = function () {
971
+ Accordion.prototype.onShowOrHideAllToggle = function () {
908
972
  var $module = this;
909
973
  var $sections = this.$sections;
910
-
911
974
  var nowExpanded = !this.checkIfAllSectionsOpen();
912
975
 
913
976
  nodeListForEach($sections, function ($section) {
@@ -916,23 +979,37 @@ Accordion.prototype.onOpenOrCloseAllToggle = function () {
916
979
  $module.storeState($section);
917
980
  });
918
981
 
919
- $module.updateOpenAllButton(nowExpanded);
982
+ $module.updateShowAllButton(nowExpanded);
920
983
  };
921
984
 
922
985
  // Set section attributes when opened/closed
923
986
  Accordion.prototype.setExpanded = function (expanded, $section) {
987
+ var $icon = $section.querySelector('.' + this.upChevronIconClass);
988
+ var $showHideText = $section.querySelector('.' + this.sectionShowHideTextClass);
924
989
  var $button = $section.querySelector('.' + this.sectionButtonClass);
990
+ var $newButtonText = expanded ? 'Hide' : 'Show';
991
+
992
+ // Build additional copy of "this section" for assistive technology and place inside toggle link
993
+ var $visuallyHiddenText = document.createElement('span');
994
+ $visuallyHiddenText.classList.add('govuk-visually-hidden');
995
+ $visuallyHiddenText.innerHTML = ' this section';
996
+
997
+ $showHideText.innerHTML = $newButtonText;
998
+ $showHideText.appendChild($visuallyHiddenText);
925
999
  $button.setAttribute('aria-expanded', expanded);
926
1000
 
1001
+ // Swap icon, change class
927
1002
  if (expanded) {
928
1003
  $section.classList.add(this.sectionExpandedClass);
1004
+ $icon.classList.remove(this.downChevronIconClass);
929
1005
  } else {
930
1006
  $section.classList.remove(this.sectionExpandedClass);
1007
+ $icon.classList.add(this.downChevronIconClass);
931
1008
  }
932
1009
 
933
- // See if "Open all" button text should be updated
1010
+ // See if "Show all sections" button text should be updated
934
1011
  var areAllSectionsOpen = this.checkIfAllSectionsOpen();
935
- this.updateOpenAllButton(areAllSectionsOpen);
1012
+ this.updateShowAllButton(areAllSectionsOpen);
936
1013
  };
937
1014
 
938
1015
  // Get state of section
@@ -951,12 +1028,20 @@ Accordion.prototype.checkIfAllSectionsOpen = function () {
951
1028
  return areAllSectionsOpen
952
1029
  };
953
1030
 
954
- // Update "Open all" button
955
- Accordion.prototype.updateOpenAllButton = function (expanded) {
956
- var newButtonText = expanded ? 'Close all' : 'Open all';
957
- newButtonText += '<span class="govuk-visually-hidden"> sections</span>';
958
- this.$openAllButton.setAttribute('aria-expanded', expanded);
959
- this.$openAllButton.innerHTML = newButtonText;
1031
+ // Update "Show all sections" button
1032
+ Accordion.prototype.updateShowAllButton = function (expanded) {
1033
+ var $showAllIcon = this.$showAllButton.querySelector('.' + this.upChevronIconClass);
1034
+ var $showAllText = this.$showAllButton.querySelector('.' + this.showAllTextClass);
1035
+ var newButtonText = expanded ? 'Hide all sections' : 'Show all sections';
1036
+ this.$showAllButton.setAttribute('aria-expanded', expanded);
1037
+ $showAllText.innerHTML = newButtonText;
1038
+
1039
+ // Swap icon, toggle class
1040
+ if (expanded) {
1041
+ $showAllIcon.classList.remove(this.downChevronIconClass);
1042
+ } else {
1043
+ $showAllIcon.classList.add(this.downChevronIconClass);
1044
+ }
960
1045
  };
961
1046
 
962
1047
  // Check for `window.sessionStorage`, and that it actually works.
@@ -980,7 +1065,7 @@ var helper = {
980
1065
  // Set the state of the accordions in sessionStorage
981
1066
  Accordion.prototype.storeState = function ($section) {
982
1067
  if (this.browserSupportsSessionStorage) {
983
- // We need a unique way of identifying each content in the accordion. Since
1068
+ // We need a unique way of identifying each content in the Accordion. Since
984
1069
  // an `#id` should be unique and an `id` is required for `aria-` attributes
985
1070
  // `id` can be safely used.
986
1071
  var $button = $section.querySelector('.' + this.sectionButtonClass);
@@ -1021,6 +1106,25 @@ Accordion.prototype.setInitialState = function ($section) {
1021
1106
  }
1022
1107
  };
1023
1108
 
1109
+ /**
1110
+ * Create an element to improve semantics of the section button with punctuation
1111
+ * @return {object} DOM element
1112
+ *
1113
+ * Used to add pause (with a comma) for assistive technology.
1114
+ * Example: [heading]Section A ,[pause] Show this section.
1115
+ * https://accessibility.blog.gov.uk/2017/12/18/what-working-on-gov-uk-navigation-taught-us-about-accessibility/
1116
+ *
1117
+ * Adding punctuation to the button can also improve its general semantics by dividing its contents
1118
+ * into thematic chunks.
1119
+ * See https://github.com/alphagov/govuk-frontend/issues/2327#issuecomment-922957442
1120
+ */
1121
+ Accordion.prototype.getButtonPunctuationEl = function () {
1122
+ var $punctuationEl = document.createElement('span');
1123
+ $punctuationEl.classList.add('govuk-visually-hidden', 'govuk-accordion__section-heading-divider');
1124
+ $punctuationEl.innerHTML = ', ';
1125
+ return $punctuationEl
1126
+ };
1127
+
1024
1128
  (function(undefined) {
1025
1129
 
1026
1130
  // Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Window/detect.js
@@ -1498,7 +1602,7 @@ function CharacterCount ($module) {
1498
1602
  this.$module = $module;
1499
1603
  this.$textarea = $module.querySelector('.govuk-js-character-count');
1500
1604
  if (this.$textarea) {
1501
- this.$countMessage = $module.querySelector('[id="' + this.$textarea.id + '-info"]');
1605
+ this.$countMessage = document.getElementById(this.$textarea.id + '-info');
1502
1606
  }
1503
1607
  }
1504
1608
 
@@ -1697,7 +1801,7 @@ Checkboxes.prototype.init = function () {
1697
1801
 
1698
1802
  // Skip checkboxes without data-aria-controls attributes, or where the
1699
1803
  // target element does not exist.
1700
- if (!target || !$module.querySelector('#' + target)) {
1804
+ if (!target || !document.getElementById(target)) {
1701
1805
  return
1702
1806
  }
1703
1807
 
@@ -1741,7 +1845,7 @@ Checkboxes.prototype.syncAllConditionalReveals = function () {
1741
1845
  * @param {HTMLInputElement} $input Checkbox input
1742
1846
  */
1743
1847
  Checkboxes.prototype.syncConditionalRevealWithInputState = function ($input) {
1744
- var $target = this.$module.querySelector('#' + $input.getAttribute('aria-controls'));
1848
+ var $target = document.getElementById($input.getAttribute('aria-controls'));
1745
1849
 
1746
1850
  if ($target && $target.classList.contains('govuk-checkboxes__conditional')) {
1747
1851
  var inputIsChecked = $input.checked;
@@ -1764,10 +1868,9 @@ Checkboxes.prototype.unCheckAllInputsExcept = function ($input) {
1764
1868
  var hasSameFormOwner = ($input.form === $inputWithSameName.form);
1765
1869
  if (hasSameFormOwner && $inputWithSameName !== $input) {
1766
1870
  $inputWithSameName.checked = false;
1871
+ this.syncConditionalRevealWithInputState($inputWithSameName);
1767
1872
  }
1768
- });
1769
-
1770
- this.syncAllConditionalReveals();
1873
+ }.bind(this));
1771
1874
  };
1772
1875
 
1773
1876
  /**
@@ -1786,10 +1889,9 @@ Checkboxes.prototype.unCheckExclusiveInputs = function ($input) {
1786
1889
  var hasSameFormOwner = ($input.form === $exclusiveInput.form);
1787
1890
  if (hasSameFormOwner) {
1788
1891
  $exclusiveInput.checked = false;
1892
+ this.syncConditionalRevealWithInputState($exclusiveInput);
1789
1893
  }
1790
- });
1791
-
1792
- this.syncAllConditionalReveals();
1894
+ }.bind(this));
1793
1895
  };
1794
1896
 
1795
1897
  /**
@@ -2088,7 +2190,7 @@ Header.prototype.init = function () {
2088
2190
  return
2089
2191
  }
2090
2192
 
2091
- this.syncState(this.$menu.classList.contains('govuk-header__navigation--open'));
2193
+ this.syncState(this.$menu.classList.contains('govuk-header__navigation-list--open'));
2092
2194
  this.$menuButton.addEventListener('click', this.handleMenuButtonClick.bind(this));
2093
2195
  };
2094
2196
 
@@ -2112,7 +2214,7 @@ Header.prototype.syncState = function (isVisible) {
2112
2214
  * sync the accessibility state and menu button state
2113
2215
  */
2114
2216
  Header.prototype.handleMenuButtonClick = function () {
2115
- var isVisible = this.$menu.classList.toggle('govuk-header__navigation--open');
2217
+ var isVisible = this.$menu.classList.toggle('govuk-header__navigation-list--open');
2116
2218
  this.syncState(isVisible);
2117
2219
  };
2118
2220
 
@@ -2144,7 +2246,7 @@ Radios.prototype.init = function () {
2144
2246
 
2145
2247
  // Skip radios without data-aria-controls attributes, or where the
2146
2248
  // target element does not exist.
2147
- if (!target || !$module.querySelector('#' + target)) {
2249
+ if (!target || !document.getElementById(target)) {
2148
2250
  return
2149
2251
  }
2150
2252
 
@@ -2189,7 +2291,7 @@ Radios.prototype.syncAllConditionalReveals = function () {
2189
2291
  * @param {HTMLInputElement} $input Radio input
2190
2292
  */
2191
2293
  Radios.prototype.syncConditionalRevealWithInputState = function ($input) {
2192
- var $target = document.querySelector('#' + $input.getAttribute('aria-controls'));
2294
+ var $target = document.getElementById($input.getAttribute('aria-controls'));
2193
2295
 
2194
2296
  if ($target && $target.classList.contains('govuk-radios__conditional')) {
2195
2297
  var inputIsChecked = $input.checked;
@@ -2231,6 +2333,95 @@ Radios.prototype.handleClick = function (event) {
2231
2333
  }.bind(this));
2232
2334
  };
2233
2335
 
2336
+ function SkipLink ($module) {
2337
+ this.$module = $module;
2338
+ this.$linkedElement = null;
2339
+ this.linkedElementListener = false;
2340
+ }
2341
+
2342
+ /**
2343
+ * Initialise the component
2344
+ */
2345
+ SkipLink.prototype.init = function () {
2346
+ // Check for module
2347
+ if (!this.$module) {
2348
+ return
2349
+ }
2350
+
2351
+ // Check for linked element
2352
+ this.$linkedElement = this.getLinkedElement();
2353
+ if (!this.$linkedElement) {
2354
+ return
2355
+ }
2356
+
2357
+ this.$module.addEventListener('click', this.focusLinkedElement.bind(this));
2358
+ };
2359
+
2360
+ /**
2361
+ * Get linked element
2362
+ *
2363
+ * @returns {HTMLElement} $linkedElement - DOM element linked to from the skip link
2364
+ */
2365
+ SkipLink.prototype.getLinkedElement = function () {
2366
+ var linkedElementId = this.getFragmentFromUrl();
2367
+
2368
+ if (!linkedElementId) {
2369
+ return false
2370
+ }
2371
+
2372
+ return document.getElementById(linkedElementId)
2373
+ };
2374
+
2375
+ /**
2376
+ * Focus the linked element
2377
+ *
2378
+ * Set tabindex and helper CSS class. Set listener to remove them on blur.
2379
+ */
2380
+ SkipLink.prototype.focusLinkedElement = function () {
2381
+ var $linkedElement = this.$linkedElement;
2382
+
2383
+ if (!$linkedElement.getAttribute('tabindex')) {
2384
+ // Set the element tabindex to -1 so it can be focused with JavaScript.
2385
+ $linkedElement.setAttribute('tabindex', '-1');
2386
+ $linkedElement.classList.add('govuk-skip-link-focused-element');
2387
+
2388
+ // Add listener for blur on the focused element (unless the listener has previously been added)
2389
+ if (!this.linkedElementListener) {
2390
+ this.$linkedElement.addEventListener('blur', this.removeFocusProperties.bind(this));
2391
+ this.linkedElementListener = true;
2392
+ }
2393
+ }
2394
+ $linkedElement.focus();
2395
+ };
2396
+
2397
+ /**
2398
+ * Remove the tabindex that makes the linked element focusable because the element only needs to be
2399
+ * focusable until it has received programmatic focus and a screen reader has announced it.
2400
+ *
2401
+ * Remove the CSS class that removes the native focus styles.
2402
+ */
2403
+ SkipLink.prototype.removeFocusProperties = function () {
2404
+ this.$linkedElement.removeAttribute('tabindex');
2405
+ this.$linkedElement.classList.remove('govuk-skip-link-focused-element');
2406
+ };
2407
+
2408
+ /**
2409
+ * Get fragment from URL
2410
+ *
2411
+ * Extract the fragment (everything after the hash symbol) from a URL, but not including
2412
+ * the symbol.
2413
+ *
2414
+ * @returns {string} Fragment from URL, without the hash symbol
2415
+ */
2416
+ SkipLink.prototype.getFragmentFromUrl = function () {
2417
+ // Bail if the anchor link doesn't have a hash
2418
+ if (!this.$module.hash) {
2419
+ return false
2420
+ }
2421
+
2422
+ return this.$module.hash.split('#').pop()
2423
+ };
2424
+
2234
2425
  (function(undefined) {
2235
2426
 
2236
2427
  // Detection from https://raw.githubusercontent.com/Financial-Times/polyfill-library/master/polyfills/Element/prototype/nextElementSibling/detect.js
@@ -2596,6 +2787,10 @@ function initAll (options) {
2596
2787
  new Radios($radio).init();
2597
2788
  });
2598
2789
 
2790
+ // Find first skip link module to enhance.
2791
+ var $skipLink = scope.querySelector('[data-module="govuk-skip-link"]');
2792
+ new SkipLink($skipLink).init();
2793
+
2599
2794
  var $tabs = scope.querySelectorAll('[data-module="govuk-tabs"]');
2600
2795
  nodeListForEach($tabs, function ($tabs) {
2601
2796
  new Tabs($tabs).init();
@@ -2610,7 +2805,9 @@ exports.CharacterCount = CharacterCount;
2610
2805
  exports.Checkboxes = Checkboxes;
2611
2806
  exports.ErrorSummary = ErrorSummary;
2612
2807
  exports.Header = Header;
2808
+ exports.NotificationBanner = NotificationBanner;
2613
2809
  exports.Radios = Radios;
2810
+ exports.SkipLink = SkipLink;
2614
2811
  exports.Tabs = Tabs;
2615
2812
 
2616
2813
  })));