govuk_publishing_components 28.5.0 → 28.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (138) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/app/assets/javascripts/govuk_publishing_components/analytics/page-content.js +3 -3
  4. data/app/assets/javascripts/govuk_publishing_components/analytics/track-select-change.js +4 -3
  5. data/app/assets/javascripts/govuk_publishing_components/components/accordion.js +21 -291
  6. data/app/assets/javascripts/govuk_publishing_components/components/button.js +1 -1
  7. data/app/assets/javascripts/govuk_publishing_components/components/character-count.js +1 -1
  8. data/app/assets/javascripts/govuk_publishing_components/components/checkboxes.js +1 -1
  9. data/app/assets/javascripts/govuk_publishing_components/components/details.js +1 -1
  10. data/app/assets/javascripts/govuk_publishing_components/components/error-summary.js +1 -1
  11. data/app/assets/javascripts/govuk_publishing_components/components/feedback.js +7 -6
  12. data/app/assets/javascripts/govuk_publishing_components/components/layout-header.js +1 -1
  13. data/app/assets/javascripts/govuk_publishing_components/components/radio.js +1 -1
  14. data/app/assets/javascripts/govuk_publishing_components/components/skip-link.js +5 -0
  15. data/app/assets/javascripts/govuk_publishing_components/components/step-by-step-nav.js +40 -37
  16. data/app/assets/javascripts/govuk_publishing_components/components/tabs.js +1 -1
  17. data/app/assets/javascripts/govuk_publishing_components/lib/govspeak/barchart-enhancement.js +2 -1
  18. data/app/assets/javascripts/govuk_publishing_components/lib/govspeak/magna-charta.js +8 -10
  19. data/app/assets/javascripts/govuk_publishing_components/lib/initial-focus.js +4 -3
  20. data/app/assets/javascripts/govuk_publishing_components/lib/toggle-input-class-on-focus.js +4 -3
  21. data/app/assets/javascripts/govuk_publishing_components/lib/toggle.js +4 -3
  22. data/app/assets/javascripts/govuk_publishing_components/vendor/lux/lux-reporter.js +1796 -911
  23. data/app/assets/stylesheets/govuk_publishing_components/components/_accordion.scss +1 -329
  24. data/app/assets/stylesheets/govuk_publishing_components/components/_share-links.scss +1 -1
  25. data/app/assets/stylesheets/govuk_publishing_components/components/_step-by-step-nav.scss +95 -56
  26. data/app/assets/stylesheets/govuk_publishing_components/components/print/_accordion.scss +6 -17
  27. data/app/assets/stylesheets/govuk_publishing_components/govuk_frontend_support.scss +0 -9
  28. data/app/models/govuk_publishing_components/component_docs.rb +1 -1
  29. data/app/views/govuk_publishing_components/components/_accordion.html.erb +31 -16
  30. data/app/views/govuk_publishing_components/components/_attachment.html.erb +3 -1
  31. data/app/views/govuk_publishing_components/components/_button.html.erb +4 -4
  32. data/app/views/govuk_publishing_components/components/_character_count.html.erb +2 -2
  33. data/app/views/govuk_publishing_components/components/_checkboxes.html.erb +2 -2
  34. data/app/views/govuk_publishing_components/components/_error_message.html.erb +1 -1
  35. data/app/views/govuk_publishing_components/components/_error_summary.html.erb +0 -1
  36. data/app/views/govuk_publishing_components/components/_input.html.erb +3 -3
  37. data/app/views/govuk_publishing_components/components/_inset_text.html.erb +12 -1
  38. data/app/views/govuk_publishing_components/components/_intervention.html.erb +2 -2
  39. data/app/views/govuk_publishing_components/components/_layout_footer.html.erb +1 -1
  40. data/app/views/govuk_publishing_components/components/_modal_dialogue.html.erb +1 -1
  41. data/app/views/govuk_publishing_components/components/_radio.html.erb +1 -1
  42. data/app/views/govuk_publishing_components/components/_search.html.erb +2 -4
  43. data/app/views/govuk_publishing_components/components/_select.html.erb +15 -43
  44. data/app/views/govuk_publishing_components/components/_skip_link.html.erb +1 -1
  45. data/app/views/govuk_publishing_components/components/_subscription_links.html.erb +7 -8
  46. data/app/views/govuk_publishing_components/components/docs/accordion.yml +2 -97
  47. data/app/views/govuk_publishing_components/components/docs/attachment.yml +11 -0
  48. data/app/views/govuk_publishing_components/components/docs/govspeak.yml +1 -1
  49. data/app/views/govuk_publishing_components/components/docs/inset_text.yml +6 -0
  50. data/app/views/govuk_publishing_components/components/docs/select.yml +1 -1
  51. data/app/views/govuk_publishing_components/components/docs/share_links.yml +1 -1
  52. data/app/views/govuk_publishing_components/components/docs/step_by_step_nav.yml +13 -13
  53. data/app/views/govuk_publishing_components/components/docs/tabs.yml +1 -1
  54. data/app/views/govuk_publishing_components/components/docs/textarea.yml +1 -1
  55. data/app/views/govuk_publishing_components/components/layout_header/_navigation_items.html.erb +11 -11
  56. data/config/initializers/assets.rb +1 -0
  57. data/lib/govuk_publishing_components/presenters/attachment.rb +23 -0
  58. data/lib/govuk_publishing_components/presenters/select_helper.rb +85 -0
  59. data/lib/govuk_publishing_components/version.rb +1 -1
  60. data/lib/govuk_publishing_components.rb +1 -1
  61. data/node_modules/govuk-frontend/govuk/all.js +294 -76
  62. data/node_modules/govuk-frontend/govuk/components/accordion/_index.scss +274 -99
  63. data/node_modules/govuk-frontend/govuk/components/accordion/accordion.js +169 -65
  64. data/node_modules/govuk-frontend/govuk/components/accordion/fixtures.json +37 -5
  65. data/node_modules/govuk-frontend/govuk/components/accordion/macro-options.json +4 -4
  66. data/node_modules/govuk-frontend/govuk/components/button/button.js +2 -2
  67. data/node_modules/govuk-frontend/govuk/components/character-count/character-count.js +3 -3
  68. data/node_modules/govuk-frontend/govuk/components/character-count/fixtures.json +15 -2
  69. data/node_modules/govuk-frontend/govuk/components/checkboxes/checkboxes.js +8 -10
  70. data/node_modules/govuk-frontend/govuk/components/checkboxes/fixtures.json +52 -15
  71. data/node_modules/govuk-frontend/govuk/components/cookie-banner/_index.scss +0 -2
  72. data/node_modules/govuk-frontend/govuk/components/cookie-banner/fixtures.json +31 -0
  73. data/node_modules/govuk-frontend/govuk/components/date-input/fixtures.json +25 -25
  74. data/node_modules/govuk-frontend/govuk/components/details/details.js +2 -2
  75. data/node_modules/govuk-frontend/govuk/components/error-message/_index.scss +1 -0
  76. data/node_modules/govuk-frontend/govuk/components/error-message/fixtures.json +8 -8
  77. data/node_modules/govuk-frontend/govuk/components/error-message/template.njk +2 -2
  78. data/node_modules/govuk-frontend/govuk/components/error-summary/error-summary.js +24 -3
  79. data/node_modules/govuk-frontend/govuk/components/error-summary/fixtures.json +25 -16
  80. data/node_modules/govuk-frontend/govuk/components/error-summary/macro-options.json +6 -0
  81. data/node_modules/govuk-frontend/govuk/components/error-summary/template.njk +2 -1
  82. data/node_modules/govuk-frontend/govuk/components/file-upload/fixtures.json +4 -4
  83. data/node_modules/govuk-frontend/govuk/components/footer/_index.scss +3 -37
  84. data/node_modules/govuk-frontend/govuk/components/footer/fixtures.json +119 -15
  85. data/node_modules/govuk-frontend/govuk/components/footer/macro-options.json +6 -0
  86. data/node_modules/govuk-frontend/govuk/components/footer/template.njk +1 -1
  87. data/node_modules/govuk-frontend/govuk/components/header/_index.scss +10 -4
  88. data/node_modules/govuk-frontend/govuk/components/header/fixtures.json +11 -11
  89. data/node_modules/govuk-frontend/govuk/components/header/header.js +4 -4
  90. data/node_modules/govuk-frontend/govuk/components/header/template.njk +4 -3
  91. data/node_modules/govuk-frontend/govuk/components/hint/_index.scss +1 -3
  92. data/node_modules/govuk-frontend/govuk/components/input/_index.scss +1 -1
  93. data/node_modules/govuk-frontend/govuk/components/input/fixtures.json +40 -40
  94. data/node_modules/govuk-frontend/govuk/components/input/template.njk +4 -3
  95. data/node_modules/govuk-frontend/govuk/components/notification-banner/notification-banner.js +499 -2
  96. data/node_modules/govuk-frontend/govuk/components/phase-banner/fixtures.json +2 -2
  97. data/node_modules/govuk-frontend/govuk/components/radios/_index.scss +0 -8
  98. data/node_modules/govuk-frontend/govuk/components/radios/fixtures.json +44 -45
  99. data/node_modules/govuk-frontend/govuk/components/radios/radios.js +4 -4
  100. data/node_modules/govuk-frontend/govuk/components/radios/template.njk +2 -9
  101. data/node_modules/govuk-frontend/govuk/components/select/_index.scss +6 -1
  102. data/node_modules/govuk-frontend/govuk/components/select/fixtures.json +3 -3
  103. data/node_modules/govuk-frontend/govuk/components/skip-link/_index.scss +13 -0
  104. data/node_modules/govuk-frontend/govuk/components/skip-link/fixtures.json +9 -9
  105. data/node_modules/govuk-frontend/govuk/components/skip-link/skip-link.js +1108 -0
  106. data/node_modules/govuk-frontend/govuk/components/skip-link/template.njk +1 -1
  107. data/node_modules/govuk-frontend/govuk/components/summary-list/_index.scss +20 -23
  108. data/node_modules/govuk-frontend/govuk/components/summary-list/fixtures.json +86 -1
  109. data/node_modules/govuk-frontend/govuk/components/summary-list/template.njk +1 -4
  110. data/node_modules/govuk-frontend/govuk/components/tabs/_index.scss +2 -2
  111. data/node_modules/govuk-frontend/govuk/components/tabs/tabs.js +2 -2
  112. data/node_modules/govuk-frontend/govuk/components/tag/_index.scss +0 -5
  113. data/node_modules/govuk-frontend/govuk/components/tag/fixtures.json +2 -2
  114. data/node_modules/govuk-frontend/govuk/components/textarea/_index.scss +1 -1
  115. data/node_modules/govuk-frontend/govuk/components/textarea/fixtures.json +4 -4
  116. data/node_modules/govuk-frontend/govuk/core/_all.scss +0 -1
  117. data/node_modules/govuk-frontend/govuk/core/_global-styles.scss +0 -6
  118. data/node_modules/govuk-frontend/govuk/core/_links.scss +0 -6
  119. data/node_modules/govuk-frontend/govuk/core/_lists.scss +0 -6
  120. data/node_modules/govuk-frontend/govuk/core/_section-break.scss +0 -6
  121. data/node_modules/govuk-frontend/govuk/core/_typography.scss +0 -6
  122. data/node_modules/govuk-frontend/govuk/objects/_all.scss +1 -0
  123. data/node_modules/govuk-frontend/govuk/objects/_main-wrapper.scss +15 -30
  124. data/node_modules/govuk-frontend/govuk/{core → objects}/_template.scss +1 -5
  125. data/node_modules/govuk-frontend/govuk/overrides/_display.scss +0 -6
  126. data/node_modules/govuk-frontend/govuk/overrides/_spacing.scss +0 -6
  127. data/node_modules/govuk-frontend/govuk/overrides/_text-align.scss +0 -6
  128. data/node_modules/govuk-frontend/govuk/overrides/_typography.scss +0 -6
  129. data/node_modules/govuk-frontend/govuk/overrides/_width.scss +0 -6
  130. data/node_modules/govuk-frontend/govuk/settings/_colours-organisations.scss +3 -0
  131. data/node_modules/govuk-frontend/govuk/settings/_measurements.scss +0 -10
  132. data/node_modules/govuk-frontend/govuk/tools/_all.scss +0 -1
  133. data/node_modules/govuk-frontend/package.json +1 -1
  134. metadata +22 -37
  135. data/app/assets/javascripts/govuk_publishing_components/lib/header-navigation.js +0 -100
  136. data/lib/govuk_publishing_components/presenters/select.rb +0 -43
  137. data/lib/tasks/govuk_publishing_components_tasks.rake +0 -49
  138. data/node_modules/govuk-frontend/govuk/tools/_iff.scss +0 -17
@@ -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
  /**
@@ -1884,11 +1986,32 @@ ErrorSummary.prototype.init = function () {
1884
1986
  if (!$module) {
1885
1987
  return
1886
1988
  }
1887
- $module.focus();
1888
1989
 
1990
+ this.setFocus();
1889
1991
  $module.addEventListener('click', this.handleClick.bind(this));
1890
1992
  };
1891
1993
 
1994
+ /**
1995
+ * Focus the error summary
1996
+ */
1997
+ ErrorSummary.prototype.setFocus = function () {
1998
+ var $module = this.$module;
1999
+
2000
+ if ($module.getAttribute('data-disable-auto-focus') === 'true') {
2001
+ return
2002
+ }
2003
+
2004
+ // Set tabindex to -1 to make the element programmatically focusable, but
2005
+ // remove it on blur as the error summary doesn't need to be focused again.
2006
+ $module.setAttribute('tabindex', '-1');
2007
+
2008
+ $module.addEventListener('blur', function () {
2009
+ $module.removeAttribute('tabindex');
2010
+ });
2011
+
2012
+ $module.focus();
2013
+ };
2014
+
1892
2015
  /**
1893
2016
  * Click event handler
1894
2017
  *
@@ -2088,7 +2211,7 @@ Header.prototype.init = function () {
2088
2211
  return
2089
2212
  }
2090
2213
 
2091
- this.syncState(this.$menu.classList.contains('govuk-header__navigation--open'));
2214
+ this.syncState(this.$menu.classList.contains('govuk-header__navigation-list--open'));
2092
2215
  this.$menuButton.addEventListener('click', this.handleMenuButtonClick.bind(this));
2093
2216
  };
2094
2217
 
@@ -2112,7 +2235,7 @@ Header.prototype.syncState = function (isVisible) {
2112
2235
  * sync the accessibility state and menu button state
2113
2236
  */
2114
2237
  Header.prototype.handleMenuButtonClick = function () {
2115
- var isVisible = this.$menu.classList.toggle('govuk-header__navigation--open');
2238
+ var isVisible = this.$menu.classList.toggle('govuk-header__navigation-list--open');
2116
2239
  this.syncState(isVisible);
2117
2240
  };
2118
2241
 
@@ -2144,7 +2267,7 @@ Radios.prototype.init = function () {
2144
2267
 
2145
2268
  // Skip radios without data-aria-controls attributes, or where the
2146
2269
  // target element does not exist.
2147
- if (!target || !$module.querySelector('#' + target)) {
2270
+ if (!target || !document.getElementById(target)) {
2148
2271
  return
2149
2272
  }
2150
2273
 
@@ -2189,7 +2312,7 @@ Radios.prototype.syncAllConditionalReveals = function () {
2189
2312
  * @param {HTMLInputElement} $input Radio input
2190
2313
  */
2191
2314
  Radios.prototype.syncConditionalRevealWithInputState = function ($input) {
2192
- var $target = document.querySelector('#' + $input.getAttribute('aria-controls'));
2315
+ var $target = document.getElementById($input.getAttribute('aria-controls'));
2193
2316
 
2194
2317
  if ($target && $target.classList.contains('govuk-radios__conditional')) {
2195
2318
  var inputIsChecked = $input.checked;
@@ -2231,6 +2354,95 @@ Radios.prototype.handleClick = function (event) {
2231
2354
  }.bind(this));
2232
2355
  };
2233
2356
 
2357
+ function SkipLink ($module) {
2358
+ this.$module = $module;
2359
+ this.$linkedElement = null;
2360
+ this.linkedElementListener = false;
2361
+ }
2362
+
2363
+ /**
2364
+ * Initialise the component
2365
+ */
2366
+ SkipLink.prototype.init = function () {
2367
+ // Check for module
2368
+ if (!this.$module) {
2369
+ return
2370
+ }
2371
+
2372
+ // Check for linked element
2373
+ this.$linkedElement = this.getLinkedElement();
2374
+ if (!this.$linkedElement) {
2375
+ return
2376
+ }
2377
+
2378
+ this.$module.addEventListener('click', this.focusLinkedElement.bind(this));
2379
+ };
2380
+
2381
+ /**
2382
+ * Get linked element
2383
+ *
2384
+ * @returns {HTMLElement} $linkedElement - DOM element linked to from the skip link
2385
+ */
2386
+ SkipLink.prototype.getLinkedElement = function () {
2387
+ var linkedElementId = this.getFragmentFromUrl();
2388
+
2389
+ if (!linkedElementId) {
2390
+ return false
2391
+ }
2392
+
2393
+ return document.getElementById(linkedElementId)
2394
+ };
2395
+
2396
+ /**
2397
+ * Focus the linked element
2398
+ *
2399
+ * Set tabindex and helper CSS class. Set listener to remove them on blur.
2400
+ */
2401
+ SkipLink.prototype.focusLinkedElement = function () {
2402
+ var $linkedElement = this.$linkedElement;
2403
+
2404
+ if (!$linkedElement.getAttribute('tabindex')) {
2405
+ // Set the element tabindex to -1 so it can be focused with JavaScript.
2406
+ $linkedElement.setAttribute('tabindex', '-1');
2407
+ $linkedElement.classList.add('govuk-skip-link-focused-element');
2408
+
2409
+ // Add listener for blur on the focused element (unless the listener has previously been added)
2410
+ if (!this.linkedElementListener) {
2411
+ this.$linkedElement.addEventListener('blur', this.removeFocusProperties.bind(this));
2412
+ this.linkedElementListener = true;
2413
+ }
2414
+ }
2415
+ $linkedElement.focus();
2416
+ };
2417
+
2418
+ /**
2419
+ * Remove the tabindex that makes the linked element focusable because the element only needs to be
2420
+ * focusable until it has received programmatic focus and a screen reader has announced it.
2421
+ *
2422
+ * Remove the CSS class that removes the native focus styles.
2423
+ */
2424
+ SkipLink.prototype.removeFocusProperties = function () {
2425
+ this.$linkedElement.removeAttribute('tabindex');
2426
+ this.$linkedElement.classList.remove('govuk-skip-link-focused-element');
2427
+ };
2428
+
2429
+ /**
2430
+ * Get fragment from URL
2431
+ *
2432
+ * Extract the fragment (everything after the hash symbol) from a URL, but not including
2433
+ * the symbol.
2434
+ *
2435
+ * @returns {string} Fragment from URL, without the hash symbol
2436
+ */
2437
+ SkipLink.prototype.getFragmentFromUrl = function () {
2438
+ // Bail if the anchor link doesn't have a hash
2439
+ if (!this.$module.hash) {
2440
+ return false
2441
+ }
2442
+
2443
+ return this.$module.hash.split('#').pop()
2444
+ };
2445
+
2234
2446
  (function(undefined) {
2235
2447
 
2236
2448
  // Detection from https://raw.githubusercontent.com/Financial-Times/polyfill-library/master/polyfills/Element/prototype/nextElementSibling/detect.js
@@ -2596,6 +2808,10 @@ function initAll (options) {
2596
2808
  new Radios($radio).init();
2597
2809
  });
2598
2810
 
2811
+ // Find first skip link module to enhance.
2812
+ var $skipLink = scope.querySelector('[data-module="govuk-skip-link"]');
2813
+ new SkipLink($skipLink).init();
2814
+
2599
2815
  var $tabs = scope.querySelectorAll('[data-module="govuk-tabs"]');
2600
2816
  nodeListForEach($tabs, function ($tabs) {
2601
2817
  new Tabs($tabs).init();
@@ -2610,7 +2826,9 @@ exports.CharacterCount = CharacterCount;
2610
2826
  exports.Checkboxes = Checkboxes;
2611
2827
  exports.ErrorSummary = ErrorSummary;
2612
2828
  exports.Header = Header;
2829
+ exports.NotificationBanner = NotificationBanner;
2613
2830
  exports.Radios = Radios;
2831
+ exports.SkipLink = SkipLink;
2614
2832
  exports.Tabs = Tabs;
2615
2833
 
2616
2834
  })));