govuk_tech_docs 3.0.1 → 3.1.0

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 (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
  })));