govuk_publishing_components 29.9.0 → 29.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (160) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/govuk_publishing_components/analytics/page-content.js +4 -4
  3. data/app/assets/stylesheets/govuk_publishing_components/components/_contextual-sidebar.scss +20 -0
  4. data/app/assets/stylesheets/govuk_publishing_components/components/_layout-super-navigation-header.scss +3 -8
  5. data/app/assets/stylesheets/govuk_publishing_components/components/govspeak/_attachment.scss +7 -1
  6. data/app/controllers/govuk_publishing_components/audit_controller.rb +3 -2
  7. data/app/controllers/govuk_publishing_components/component_guide_controller.rb +0 -9
  8. data/app/models/govuk_publishing_components/audit_comparer.rb +92 -34
  9. data/app/views/govuk_publishing_components/audit/_applications.html.erb +20 -9
  10. data/app/views/govuk_publishing_components/component_guide/index.html.erb +1 -19
  11. data/app/views/govuk_publishing_components/components/_layout_footer.html.erb +20 -2
  12. data/app/views/govuk_publishing_components/components/contextual_sidebar/_ukraine_cta.html.erb +18 -19
  13. data/config/locales/ar.yml +1 -2
  14. data/config/locales/az.yml +1 -2
  15. data/config/locales/be.yml +1 -2
  16. data/config/locales/bg.yml +1 -2
  17. data/config/locales/bn.yml +1 -2
  18. data/config/locales/cs.yml +1 -2
  19. data/config/locales/cy.yml +1 -2
  20. data/config/locales/da.yml +1 -2
  21. data/config/locales/de.yml +1 -2
  22. data/config/locales/dr.yml +1 -2
  23. data/config/locales/el.yml +1 -2
  24. data/config/locales/en.yml +9 -2
  25. data/config/locales/es-419.yml +1 -2
  26. data/config/locales/es.yml +1 -2
  27. data/config/locales/et.yml +1 -2
  28. data/config/locales/fa.yml +1 -2
  29. data/config/locales/fi.yml +1 -2
  30. data/config/locales/fr.yml +1 -2
  31. data/config/locales/gd.yml +1 -2
  32. data/config/locales/gu.yml +1 -2
  33. data/config/locales/he.yml +1 -2
  34. data/config/locales/hi.yml +1 -2
  35. data/config/locales/hr.yml +1 -2
  36. data/config/locales/hu.yml +1 -2
  37. data/config/locales/hy.yml +1 -2
  38. data/config/locales/id.yml +1 -2
  39. data/config/locales/is.yml +1 -2
  40. data/config/locales/it.yml +1 -2
  41. data/config/locales/ja.yml +1 -2
  42. data/config/locales/ka.yml +1 -2
  43. data/config/locales/kk.yml +1 -2
  44. data/config/locales/ko.yml +1 -2
  45. data/config/locales/lt.yml +1 -2
  46. data/config/locales/lv.yml +1 -2
  47. data/config/locales/ms.yml +1 -2
  48. data/config/locales/mt.yml +1 -2
  49. data/config/locales/nl.yml +1 -2
  50. data/config/locales/no.yml +1 -2
  51. data/config/locales/pa-pk.yml +1 -2
  52. data/config/locales/pa.yml +1 -2
  53. data/config/locales/pl.yml +1 -2
  54. data/config/locales/ps.yml +1 -2
  55. data/config/locales/pt.yml +1 -2
  56. data/config/locales/ro.yml +1 -2
  57. data/config/locales/ru.yml +1 -2
  58. data/config/locales/si.yml +1 -2
  59. data/config/locales/sk.yml +1 -2
  60. data/config/locales/sl.yml +1 -2
  61. data/config/locales/so.yml +1 -2
  62. data/config/locales/sq.yml +1 -2
  63. data/config/locales/sr.yml +1 -2
  64. data/config/locales/sv.yml +1 -2
  65. data/config/locales/sw.yml +1 -2
  66. data/config/locales/ta.yml +1 -2
  67. data/config/locales/th.yml +1 -2
  68. data/config/locales/tk.yml +1 -2
  69. data/config/locales/tr.yml +1 -2
  70. data/config/locales/uk.yml +1 -2
  71. data/config/locales/ur.yml +1 -2
  72. data/config/locales/uz.yml +1 -2
  73. data/config/locales/vi.yml +1 -2
  74. data/config/locales/zh-hk.yml +1 -2
  75. data/config/locales/zh-tw.yml +1 -2
  76. data/config/locales/zh.yml +1 -2
  77. data/lib/govuk_publishing_components/presenters/attachment_helper.rb +1 -2
  78. data/lib/govuk_publishing_components/presenters/public_layout_helper.rb +35 -16
  79. data/lib/govuk_publishing_components/version.rb +1 -1
  80. data/node_modules/govuk-frontend/govuk/all.js +120 -49
  81. data/node_modules/govuk-frontend/govuk/components/back-link/macro-options.json +2 -2
  82. data/node_modules/govuk-frontend/govuk/components/breadcrumbs/_index.scss +0 -2
  83. data/node_modules/govuk-frontend/govuk/components/breadcrumbs/macro-options.json +2 -2
  84. data/node_modules/govuk-frontend/govuk/components/button/_index.scss +6 -16
  85. data/node_modules/govuk-frontend/govuk/components/button/macro-options.json +2 -2
  86. data/node_modules/govuk-frontend/govuk/components/character-count/character-count.js +120 -49
  87. data/node_modules/govuk-frontend/govuk/components/character-count/fixtures.json +33 -17
  88. data/node_modules/govuk-frontend/govuk/components/character-count/macro-options.json +2 -2
  89. data/node_modules/govuk-frontend/govuk/components/character-count/template.njk +1 -4
  90. data/node_modules/govuk-frontend/govuk/components/checkboxes/_index.scss +3 -2
  91. data/node_modules/govuk-frontend/govuk/components/checkboxes/fixtures.json +22 -10
  92. data/node_modules/govuk-frontend/govuk/components/checkboxes/macro-options.json +2 -2
  93. data/node_modules/govuk-frontend/govuk/components/date-input/fixtures.json +23 -23
  94. data/node_modules/govuk-frontend/govuk/components/date-input/template.njk +1 -1
  95. data/node_modules/govuk-frontend/govuk/components/details/macro-options.json +4 -4
  96. data/node_modules/govuk-frontend/govuk/components/error-message/macro-options.json +2 -2
  97. data/node_modules/govuk-frontend/govuk/components/error-summary/macro-options.json +3 -3
  98. data/node_modules/govuk-frontend/govuk/components/fieldset/macro-options.json +2 -2
  99. data/node_modules/govuk-frontend/govuk/components/footer/_index.scss +12 -22
  100. data/node_modules/govuk-frontend/govuk/components/header/_index.scss +13 -3
  101. data/node_modules/govuk-frontend/govuk/components/header/macro-options.json +2 -2
  102. data/node_modules/govuk-frontend/govuk/components/hint/macro-options.json +2 -2
  103. data/node_modules/govuk-frontend/govuk/components/input/_index.scss +4 -13
  104. data/node_modules/govuk-frontend/govuk/components/input/macro-options.json +5 -5
  105. data/node_modules/govuk-frontend/govuk/components/inset-text/macro-options.json +2 -2
  106. data/node_modules/govuk-frontend/govuk/components/label/macro-options.json +2 -2
  107. data/node_modules/govuk-frontend/govuk/components/panel/_index.scss +1 -1
  108. data/node_modules/govuk-frontend/govuk/components/panel/macro-options.json +4 -4
  109. data/node_modules/govuk-frontend/govuk/components/phase-banner/macro-options.json +2 -2
  110. data/node_modules/govuk-frontend/govuk/components/radios/_index.scss +5 -4
  111. data/node_modules/govuk-frontend/govuk/components/radios/fixtures.json +17 -12
  112. data/node_modules/govuk-frontend/govuk/components/radios/macro-options.json +2 -2
  113. data/node_modules/govuk-frontend/govuk/components/skip-link/_index.scss +1 -3
  114. data/node_modules/govuk-frontend/govuk/components/skip-link/macro-options.json +2 -2
  115. data/node_modules/govuk-frontend/govuk/components/summary-list/macro-options.json +5 -5
  116. data/node_modules/govuk-frontend/govuk/components/table/macro-options.json +4 -4
  117. data/node_modules/govuk-frontend/govuk/components/tabs/macro-options.json +2 -2
  118. data/node_modules/govuk-frontend/govuk/components/tag/macro-options.json +2 -2
  119. data/node_modules/govuk-frontend/govuk/components/warning-text/macro-options.json +2 -2
  120. data/node_modules/govuk-frontend/govuk/helpers/_colour.scss +3 -3
  121. data/node_modules/govuk-frontend/govuk/helpers/_links.scss +7 -5
  122. data/node_modules/govuk-frontend/govuk/helpers/_media-queries.scss +2 -2
  123. data/node_modules/govuk-frontend/govuk/helpers/_shape-arrow.scss +1 -1
  124. data/node_modules/govuk-frontend/govuk/helpers/_spacing.scss +3 -3
  125. data/node_modules/govuk-frontend/govuk/helpers/_typography.scss +2 -2
  126. data/node_modules/govuk-frontend/govuk/objects/_button-group.scss +10 -26
  127. data/node_modules/govuk-frontend/govuk/objects/_template.scss +1 -1
  128. data/node_modules/govuk-frontend/govuk/objects/_width-container.scss +0 -4
  129. data/node_modules/govuk-frontend/govuk/tools/_exports.scss +1 -1
  130. data/node_modules/govuk-frontend/govuk/tools/_font-url.scss +1 -1
  131. data/node_modules/govuk-frontend/govuk/tools/_image-url.scss +1 -1
  132. data/node_modules/govuk-frontend/govuk/tools/_px-to-em.scss +2 -2
  133. data/node_modules/govuk-frontend/govuk/tools/_px-to-rem.scss +1 -1
  134. data/node_modules/govuk-frontend/govuk-esm/all.mjs +88 -0
  135. data/node_modules/govuk-frontend/govuk-esm/common.mjs +28 -0
  136. data/node_modules/govuk-frontend/govuk-esm/components/accordion/accordion.mjs +374 -0
  137. data/node_modules/govuk-frontend/govuk-esm/components/button/button.mjs +64 -0
  138. data/node_modules/govuk-frontend/govuk-esm/components/character-count/character-count.mjs +251 -0
  139. data/node_modules/govuk-frontend/govuk-esm/components/checkboxes/checkboxes.mjs +164 -0
  140. data/node_modules/govuk-frontend/govuk-esm/components/details/details.mjs +147 -0
  141. data/node_modules/govuk-frontend/govuk-esm/components/error-summary/error-summary.mjs +168 -0
  142. data/node_modules/govuk-frontend/govuk-esm/components/header/header.mjs +52 -0
  143. data/node_modules/govuk-frontend/govuk-esm/components/notification-banner/notification-banner.mjs +55 -0
  144. data/node_modules/govuk-frontend/govuk-esm/components/radios/radios.mjs +122 -0
  145. data/node_modules/govuk-frontend/govuk-esm/components/skip-link/skip-link.mjs +94 -0
  146. data/node_modules/govuk-frontend/govuk-esm/components/tabs/tabs.mjs +282 -0
  147. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/DOMTokenList.js +264 -0
  148. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Document.js +26 -0
  149. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element/prototype/classList.js +93 -0
  150. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element/prototype/closest.js +24 -0
  151. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element/prototype/matches.js +23 -0
  152. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element/prototype/nextElementSibling.js +22 -0
  153. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element/prototype/previousElementSibling.js +22 -0
  154. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element.js +114 -0
  155. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Event.js +252 -0
  156. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Function/prototype/bind.js +159 -0
  157. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Object/defineProperty.js +86 -0
  158. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Window.js +20 -0
  159. data/node_modules/govuk-frontend/package.json +8 -1
  160. metadata +27 -2
@@ -29,19 +29,12 @@
29
29
  // margins, but unfortunately the support isn't there (yet) and @supports
30
30
  // doesn't play nicely with it
31
31
  // (https://github.com/w3c/csswg-drafts/issues/3559)
32
- display: -webkit-box;
33
- display: -webkit-flex;
34
32
  display: -ms-flexbox;
35
33
  display: flex;
36
- -webkit-box-orient: vertical;
37
- -webkit-box-direction: normal;
38
- -webkit-flex-direction: column;
39
- -ms-flex-direction: column;
40
- flex-direction: column;
41
- -webkit-box-align: center;
42
- -webkit-align-items: center;
43
- -ms-flex-align: center;
44
- align-items: center;
34
+ -ms-flex-direction: column;
35
+ flex-direction: column;
36
+ -ms-flex-align: center;
37
+ align-items: center;
45
38
 
46
39
  // Give links within the button group the same font-size and line-height
47
40
  // as buttons.
@@ -71,22 +64,13 @@
71
64
  // Cancel out the column gap for the last item in each row
72
65
  margin-right: ($horizontal-gap * -1);
73
66
 
74
- -webkit-box-orient: horizontal;
67
+ -ms-flex-direction: row;
75
68
 
76
- -webkit-box-direction: normal;
77
-
78
- -webkit-flex-direction: row;
79
-
80
- -ms-flex-direction: row;
81
-
82
- flex-direction: row;
83
- -webkit-flex-wrap: wrap;
84
- -ms-flex-wrap: wrap;
85
- flex-wrap: wrap;
86
- -webkit-box-align: baseline;
87
- -webkit-align-items: baseline;
88
- -ms-flex-align: baseline;
89
- align-items: baseline;
69
+ flex-direction: row;
70
+ -ms-flex-wrap: wrap;
71
+ flex-wrap: wrap;
72
+ -ms-flex-align: baseline;
73
+ align-items: baseline;
90
74
 
91
75
  .govuk-button,
92
76
  .govuk-link {
@@ -11,7 +11,7 @@
11
11
  // Prevent automatic text sizing, as we already cater for small devices and
12
12
  // would like the browser to stay on 100% text zoom by default.
13
13
  -webkit-text-size-adjust: 100%;
14
- -ms-text-size-adjust: 100%;
14
+ -moz-text-size-adjust: 100%;
15
15
  text-size-adjust: 100%;
16
16
 
17
17
  // Force the scrollbar to always display in IE, to prevent horizontal page
@@ -28,9 +28,7 @@
28
28
 
29
29
  // Respect 'display cutout' safe area (avoids notches and rounded corners)
30
30
  @supports (margin: unquote("max(calc(0px))")) {
31
- $gutter-safe-area-right: -webkit-calc(#{$govuk-gutter-half} + env(safe-area-inset-right));
32
31
  $gutter-safe-area-right: calc(#{$govuk-gutter-half} + env(safe-area-inset-right));
33
- $gutter-safe-area-left: -webkit-calc(#{$govuk-gutter-half} + env(safe-area-inset-left));
34
32
  $gutter-safe-area-left: calc(#{$govuk-gutter-half} + env(safe-area-inset-left));
35
33
 
36
34
  // Use max() to pick largest margin, default or with safe area
@@ -46,9 +44,7 @@
46
44
 
47
45
  // Respect 'display cutout' safe area (avoids notches and rounded corners)
48
46
  @supports (margin: unquote("max(calc(0px))")) {
49
- $gutter-safe-area-right: -webkit-calc(#{$govuk-gutter-half} + env(safe-area-inset-right));
50
47
  $gutter-safe-area-right: calc(#{$govuk-gutter-half} + env(safe-area-inset-right));
51
- $gutter-safe-area-left: -webkit-calc(#{$govuk-gutter-half} + env(safe-area-inset-left));
52
48
  $gutter-safe-area-left: calc(#{$govuk-gutter-half} + env(safe-area-inset-left));
53
49
 
54
50
  // Use max() to pick largest margin, default or with safe area
@@ -22,7 +22,7 @@ $_govuk-imported-modules: () !default;
22
22
 
23
23
  @mixin govuk-exports($name) {
24
24
  // If the mixin is not in the list of modules already exported...
25
- @if (index($_govuk-imported-modules, $name) == null) {
25
+ @if not index($_govuk-imported-modules, $name) {
26
26
  // ... then add it to the list
27
27
  $_govuk-imported-modules: append($_govuk-imported-modules, $name) !global;
28
28
  // ... and output the CSS for that module
@@ -17,7 +17,7 @@
17
17
  and $govuk-font-url-function
18
18
  and function-exists($govuk-font-url-function);
19
19
 
20
- @if ($use-custom-function) {
20
+ @if $use-custom-function {
21
21
  @return call(get-function($govuk-font-url-function), $filename);
22
22
  } @else {
23
23
  @return url($govuk-fonts-path + $filename);
@@ -17,7 +17,7 @@
17
17
  and $govuk-image-url-function
18
18
  and function-exists($govuk-image-url-function);
19
19
 
20
- @if ($use-custom-function) {
20
+ @if $use-custom-function {
21
21
  @return call(get-function($govuk-image-url-function), $filename);
22
22
  } @else {
23
23
  @return url($govuk-images-path + $filename);
@@ -10,10 +10,10 @@
10
10
  /// @access public
11
11
 
12
12
  @function govuk-em($value, $context-font-size) {
13
- @if (unitless($value)) {
13
+ @if unitless($value) {
14
14
  $value: $value * 1px;
15
15
  }
16
- @if (unitless($context-font-size)) {
16
+ @if unitless($context-font-size) {
17
17
  $context-font-size: $context-font-size * 1px;
18
18
  }
19
19
  @return $value / $context-font-size * 1em;
@@ -12,7 +12,7 @@
12
12
  /// @access public
13
13
 
14
14
  @function govuk-px-to-rem($value) {
15
- @if (unitless($value)) {
15
+ @if unitless($value) {
16
16
  $value: $value * 1px;
17
17
  }
18
18
 
@@ -0,0 +1,88 @@
1
+ import { nodeListForEach } from './common'
2
+ import Accordion from './components/accordion/accordion'
3
+ import Button from './components/button/button'
4
+ import Details from './components/details/details'
5
+ import CharacterCount from './components/character-count/character-count'
6
+ import Checkboxes from './components/checkboxes/checkboxes'
7
+ import ErrorSummary from './components/error-summary/error-summary'
8
+ import NotificationBanner from './components/notification-banner/notification-banner'
9
+ import Header from './components/header/header'
10
+ import Radios from './components/radios/radios'
11
+ import SkipLink from './components/skip-link/skip-link'
12
+ import Tabs from './components/tabs/tabs'
13
+
14
+ function initAll (options) {
15
+ // Set the options to an empty object by default if no options are passed.
16
+ options = typeof options !== 'undefined' ? options : {}
17
+
18
+ // Allow the user to initialise GOV.UK Frontend in only certain sections of the page
19
+ // Defaults to the entire document if nothing is set.
20
+ var scope = typeof options.scope !== 'undefined' ? options.scope : document
21
+
22
+ var $buttons = scope.querySelectorAll('[data-module="govuk-button"]')
23
+ nodeListForEach($buttons, function ($button) {
24
+ new Button($button).init()
25
+ })
26
+
27
+ var $accordions = scope.querySelectorAll('[data-module="govuk-accordion"]')
28
+ nodeListForEach($accordions, function ($accordion) {
29
+ new Accordion($accordion).init()
30
+ })
31
+
32
+ var $details = scope.querySelectorAll('[data-module="govuk-details"]')
33
+ nodeListForEach($details, function ($detail) {
34
+ new Details($detail).init()
35
+ })
36
+
37
+ var $characterCounts = scope.querySelectorAll('[data-module="govuk-character-count"]')
38
+ nodeListForEach($characterCounts, function ($characterCount) {
39
+ new CharacterCount($characterCount).init()
40
+ })
41
+
42
+ var $checkboxes = scope.querySelectorAll('[data-module="govuk-checkboxes"]')
43
+ nodeListForEach($checkboxes, function ($checkbox) {
44
+ new Checkboxes($checkbox).init()
45
+ })
46
+
47
+ // Find first error summary module to enhance.
48
+ var $errorSummary = scope.querySelector('[data-module="govuk-error-summary"]')
49
+ new ErrorSummary($errorSummary).init()
50
+
51
+ // Find first header module to enhance.
52
+ var $toggleButton = scope.querySelector('[data-module="govuk-header"]')
53
+ new Header($toggleButton).init()
54
+
55
+ var $notificationBanners = scope.querySelectorAll('[data-module="govuk-notification-banner"]')
56
+ nodeListForEach($notificationBanners, function ($notificationBanner) {
57
+ new NotificationBanner($notificationBanner).init()
58
+ })
59
+
60
+ var $radios = scope.querySelectorAll('[data-module="govuk-radios"]')
61
+ nodeListForEach($radios, function ($radio) {
62
+ new Radios($radio).init()
63
+ })
64
+
65
+ // Find first skip link module to enhance.
66
+ var $skipLink = scope.querySelector('[data-module="govuk-skip-link"]')
67
+ new SkipLink($skipLink).init()
68
+
69
+ var $tabs = scope.querySelectorAll('[data-module="govuk-tabs"]')
70
+ nodeListForEach($tabs, function ($tabs) {
71
+ new Tabs($tabs).init()
72
+ })
73
+ }
74
+
75
+ export {
76
+ initAll,
77
+ Accordion,
78
+ Button,
79
+ Details,
80
+ CharacterCount,
81
+ Checkboxes,
82
+ ErrorSummary,
83
+ Header,
84
+ NotificationBanner,
85
+ Radios,
86
+ SkipLink,
87
+ Tabs
88
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * TODO: Ideally this would be a NodeList.prototype.forEach polyfill
3
+ * This seems to fail in IE8, requires more investigation.
4
+ * See: https://github.com/imagitama/nodelist-foreach-polyfill
5
+ */
6
+ export function nodeListForEach (nodes, callback) {
7
+ if (window.NodeList.prototype.forEach) {
8
+ return nodes.forEach(callback)
9
+ }
10
+ for (var i = 0; i < nodes.length; i++) {
11
+ callback.call(window, nodes[i], i, nodes)
12
+ }
13
+ }
14
+
15
+ // Used to generate a unique string, allows multiple instances of the component without
16
+ // Them conflicting with each other.
17
+ // https://stackoverflow.com/a/8809472
18
+ export function generateUniqueID () {
19
+ var d = new Date().getTime()
20
+ if (typeof window.performance !== 'undefined' && typeof window.performance.now === 'function') {
21
+ d += window.performance.now() // use high-precision timer if available
22
+ }
23
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
24
+ var r = (d + Math.random() * 16) % 16 | 0
25
+ d = Math.floor(d / 16)
26
+ return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16)
27
+ })
28
+ }
@@ -0,0 +1,374 @@
1
+
2
+ /*
3
+ Accordion
4
+
5
+ This allows a collection of sections to be collapsed by default,
6
+ showing only their headers. Sections can be expanded or collapsed
7
+ individually by clicking their headers. An "Show all sections" button is
8
+ also added to the top of the accordion, which switches to "Hide all sections"
9
+ when all the sections are expanded.
10
+
11
+ The state of each section is saved to the DOM via the `aria-expanded`
12
+ attribute, which also provides accessibility.
13
+
14
+ A Chevron icon has been added for extra affordance that this is an interactive element.
15
+
16
+ */
17
+
18
+ import { nodeListForEach } from '../../common'
19
+ import '../../vendor/polyfills/Function/prototype/bind'
20
+ import '../../vendor/polyfills/Element/prototype/classList'
21
+
22
+ function Accordion ($module) {
23
+ this.$module = $module
24
+ this.moduleId = $module.getAttribute('id')
25
+ this.$sections = $module.querySelectorAll('.govuk-accordion__section')
26
+ this.$showAllButton = ''
27
+ this.browserSupportsSessionStorage = helper.checkForSessionStorage()
28
+
29
+ this.controlsClass = 'govuk-accordion__controls'
30
+ this.showAllClass = 'govuk-accordion__show-all'
31
+ this.showAllTextClass = 'govuk-accordion__show-all-text'
32
+
33
+ this.sectionExpandedClass = 'govuk-accordion__section--expanded'
34
+ this.sectionButtonClass = 'govuk-accordion__section-button'
35
+ this.sectionHeaderClass = 'govuk-accordion__section-header'
36
+ this.sectionHeadingClass = 'govuk-accordion__section-heading'
37
+ this.sectionHeadingTextClass = 'govuk-accordion__section-heading-text'
38
+ this.sectionHeadingTextFocusClass = 'govuk-accordion__section-heading-text-focus'
39
+
40
+ this.sectionShowHideToggleClass = 'govuk-accordion__section-toggle'
41
+ this.sectionShowHideToggleFocusClass = 'govuk-accordion__section-toggle-focus'
42
+ this.sectionShowHideTextClass = 'govuk-accordion__section-toggle-text'
43
+ this.upChevronIconClass = 'govuk-accordion-nav__chevron'
44
+ this.downChevronIconClass = 'govuk-accordion-nav__chevron--down'
45
+
46
+ this.sectionSummaryClass = 'govuk-accordion__section-summary'
47
+ this.sectionSummaryFocusClass = 'govuk-accordion__section-summary-focus'
48
+ }
49
+
50
+ // Initialize component
51
+ Accordion.prototype.init = function () {
52
+ // Check for module
53
+ if (!this.$module) {
54
+ return
55
+ }
56
+
57
+ this.initControls()
58
+ this.initSectionHeaders()
59
+
60
+ // See if "Show all sections" button text should be updated
61
+ var areAllSectionsOpen = this.checkIfAllSectionsOpen()
62
+ this.updateShowAllButton(areAllSectionsOpen)
63
+ }
64
+
65
+ // Initialise controls and set attributes
66
+ Accordion.prototype.initControls = function () {
67
+ // Create "Show all" button and set attributes
68
+ this.$showAllButton = document.createElement('button')
69
+ this.$showAllButton.setAttribute('type', 'button')
70
+ this.$showAllButton.setAttribute('class', this.showAllClass)
71
+ this.$showAllButton.setAttribute('aria-expanded', 'false')
72
+
73
+ // Create icon, add to element
74
+ var $icon = document.createElement('span')
75
+ $icon.classList.add(this.upChevronIconClass)
76
+ this.$showAllButton.appendChild($icon)
77
+
78
+ // Create control wrapper and add controls to it
79
+ var $accordionControls = document.createElement('div')
80
+ $accordionControls.setAttribute('class', this.controlsClass)
81
+ $accordionControls.appendChild(this.$showAllButton)
82
+ this.$module.insertBefore($accordionControls, this.$module.firstChild)
83
+
84
+ // Build additional wrapper for Show all toggle text and place after icon
85
+ var $wrappershowAllText = document.createElement('span')
86
+ $wrappershowAllText.classList.add(this.showAllTextClass)
87
+ this.$showAllButton.appendChild($wrappershowAllText)
88
+
89
+ // Handle click events on the show/hide all button
90
+ this.$showAllButton.addEventListener('click', this.onShowOrHideAllToggle.bind(this))
91
+ }
92
+
93
+ // Initialise section headers
94
+ Accordion.prototype.initSectionHeaders = function () {
95
+ // Loop through section headers
96
+ nodeListForEach(this.$sections, function ($section, i) {
97
+ // Set header attributes
98
+ var $header = $section.querySelector('.' + this.sectionHeaderClass)
99
+ this.constructHeaderMarkup($header, i)
100
+ this.setExpanded(this.isExpanded($section), $section)
101
+
102
+ // Handle events
103
+ $header.addEventListener('click', this.onSectionToggle.bind(this, $section))
104
+
105
+ // See if there is any state stored in sessionStorage and set the sections to
106
+ // open or closed.
107
+ this.setInitialState($section)
108
+ }.bind(this))
109
+ }
110
+
111
+ Accordion.prototype.constructHeaderMarkup = function ($headerWrapper, index) {
112
+ var $span = $headerWrapper.querySelector('.' + this.sectionButtonClass)
113
+ var $heading = $headerWrapper.querySelector('.' + this.sectionHeadingClass)
114
+ var $summary = $headerWrapper.querySelector('.' + this.sectionSummaryClass)
115
+
116
+ // Create a button element that will replace the '.govuk-accordion__section-button' span
117
+ var $button = document.createElement('button')
118
+ $button.setAttribute('type', 'button')
119
+ $button.setAttribute('aria-controls', this.moduleId + '-content-' + (index + 1))
120
+
121
+ // Copy all attributes (https://developer.mozilla.org/en-US/docs/Web/API/Element/attributes) from $span to $button
122
+ for (var i = 0; i < $span.attributes.length; i++) {
123
+ var attr = $span.attributes.item(i)
124
+ // Add all attributes but not ID as this is being added to
125
+ // the section heading ($headingText)
126
+ if (attr.nodeName !== 'id') {
127
+ $button.setAttribute(attr.nodeName, attr.nodeValue)
128
+ }
129
+ }
130
+
131
+ // Create container for heading text so it can be styled
132
+ var $headingText = document.createElement('span')
133
+ $headingText.classList.add(this.sectionHeadingTextClass)
134
+ // Copy the span ID to the heading text to allow it to be referenced by `aria-labelledby` on the
135
+ // hidden content area without "Show this section"
136
+ $headingText.id = $span.id
137
+
138
+ // Create an inner heading text container to limit the width of the focus state
139
+ var $headingTextFocus = document.createElement('span')
140
+ $headingTextFocus.classList.add(this.sectionHeadingTextFocusClass)
141
+ $headingText.appendChild($headingTextFocus)
142
+ // span could contain HTML elements (see https://www.w3.org/TR/2011/WD-html5-20110525/content-models.html#phrasing-content)
143
+ $headingTextFocus.innerHTML = $span.innerHTML
144
+
145
+ // Create container for show / hide icons and text.
146
+ var $showToggle = document.createElement('span')
147
+ $showToggle.classList.add(this.sectionShowHideToggleClass)
148
+ // Tell Google not to index the 'show' text as part of the heading
149
+ // For the snippet to work with JavaScript, it must be added before adding the page element to the
150
+ // page's DOM. See https://developers.google.com/search/docs/advanced/robots/robots_meta_tag#data-nosnippet-attr
151
+ $showToggle.setAttribute('data-nosnippet', '')
152
+ // Create an inner container to limit the width of the focus state
153
+ var $showToggleFocus = document.createElement('span')
154
+ $showToggleFocus.classList.add(this.sectionShowHideToggleFocusClass)
155
+ $showToggle.appendChild($showToggleFocus)
156
+ // Create wrapper for the show / hide text. Append text after the show/hide icon
157
+ var $showToggleText = document.createElement('span')
158
+ var $icon = document.createElement('span')
159
+ $icon.classList.add(this.upChevronIconClass)
160
+ $showToggleFocus.appendChild($icon)
161
+ $showToggleText.classList.add(this.sectionShowHideTextClass)
162
+ $showToggleFocus.appendChild($showToggleText)
163
+
164
+ // Append elements to the button:
165
+ // 1. Heading text
166
+ // 2. Punctuation
167
+ // 3. (Optional: Summary line followed by punctuation)
168
+ // 4. Show / hide toggle
169
+ $button.appendChild($headingText)
170
+ $button.appendChild(this.getButtonPunctuationEl())
171
+
172
+ // If summary content exists add to DOM in correct order
173
+ if (typeof ($summary) !== 'undefined' && $summary !== null) {
174
+ // Create a new `span` element and copy the summary line content from the original `div` to the
175
+ // new `span`
176
+ // This is because the summary line text is now inside a button element, which can only contain
177
+ // phrasing content
178
+ var $summarySpan = document.createElement('span')
179
+ // Create an inner summary container to limit the width of the summary focus state
180
+ var $summarySpanFocus = document.createElement('span')
181
+ $summarySpanFocus.classList.add(this.sectionSummaryFocusClass)
182
+ $summarySpan.appendChild($summarySpanFocus)
183
+
184
+ // Get original attributes, and pass them to the replacement
185
+ for (var j = 0, l = $summary.attributes.length; j < l; ++j) {
186
+ var nodeName = $summary.attributes.item(j).nodeName
187
+ var nodeValue = $summary.attributes.item(j).nodeValue
188
+ $summarySpan.setAttribute(nodeName, nodeValue)
189
+ }
190
+
191
+ // Copy original contents of summary to the new summary span
192
+ $summarySpanFocus.innerHTML = $summary.innerHTML
193
+
194
+ // Replace the original summary `div` with the new summary `span`
195
+ $summary.parentNode.replaceChild($summarySpan, $summary)
196
+
197
+ $button.appendChild($summarySpan)
198
+ $button.appendChild(this.getButtonPunctuationEl())
199
+ }
200
+
201
+ $button.appendChild($showToggle)
202
+
203
+ $heading.removeChild($span)
204
+ $heading.appendChild($button)
205
+ }
206
+
207
+ // When section toggled, set and store state
208
+ Accordion.prototype.onSectionToggle = function ($section) {
209
+ var expanded = this.isExpanded($section)
210
+ this.setExpanded(!expanded, $section)
211
+
212
+ // Store the state in sessionStorage when a change is triggered
213
+ this.storeState($section)
214
+ }
215
+
216
+ // When Open/Close All toggled, set and store state
217
+ Accordion.prototype.onShowOrHideAllToggle = function () {
218
+ var $module = this
219
+ var $sections = this.$sections
220
+ var nowExpanded = !this.checkIfAllSectionsOpen()
221
+
222
+ nodeListForEach($sections, function ($section) {
223
+ $module.setExpanded(nowExpanded, $section)
224
+ // Store the state in sessionStorage when a change is triggered
225
+ $module.storeState($section)
226
+ })
227
+
228
+ $module.updateShowAllButton(nowExpanded)
229
+ }
230
+
231
+ // Set section attributes when opened/closed
232
+ Accordion.prototype.setExpanded = function (expanded, $section) {
233
+ var $icon = $section.querySelector('.' + this.upChevronIconClass)
234
+ var $showHideText = $section.querySelector('.' + this.sectionShowHideTextClass)
235
+ var $button = $section.querySelector('.' + this.sectionButtonClass)
236
+ var $newButtonText = expanded ? 'Hide' : 'Show'
237
+
238
+ // Build additional copy of "this section" for assistive technology and place inside toggle link
239
+ var $visuallyHiddenText = document.createElement('span')
240
+ $visuallyHiddenText.classList.add('govuk-visually-hidden')
241
+ $visuallyHiddenText.innerHTML = ' this section'
242
+
243
+ $showHideText.innerHTML = $newButtonText
244
+ $showHideText.appendChild($visuallyHiddenText)
245
+ $button.setAttribute('aria-expanded', expanded)
246
+
247
+ // Swap icon, change class
248
+ if (expanded) {
249
+ $section.classList.add(this.sectionExpandedClass)
250
+ $icon.classList.remove(this.downChevronIconClass)
251
+ } else {
252
+ $section.classList.remove(this.sectionExpandedClass)
253
+ $icon.classList.add(this.downChevronIconClass)
254
+ }
255
+
256
+ // See if "Show all sections" button text should be updated
257
+ var areAllSectionsOpen = this.checkIfAllSectionsOpen()
258
+ this.updateShowAllButton(areAllSectionsOpen)
259
+ }
260
+
261
+ // Get state of section
262
+ Accordion.prototype.isExpanded = function ($section) {
263
+ return $section.classList.contains(this.sectionExpandedClass)
264
+ }
265
+
266
+ // Check if all sections are open
267
+ Accordion.prototype.checkIfAllSectionsOpen = function () {
268
+ // Get a count of all the Accordion sections
269
+ var sectionsCount = this.$sections.length
270
+ // Get a count of all Accordion sections that are expanded
271
+ var expandedSectionCount = this.$module.querySelectorAll('.' + this.sectionExpandedClass).length
272
+ var areAllSectionsOpen = sectionsCount === expandedSectionCount
273
+
274
+ return areAllSectionsOpen
275
+ }
276
+
277
+ // Update "Show all sections" button
278
+ Accordion.prototype.updateShowAllButton = function (expanded) {
279
+ var $showAllIcon = this.$showAllButton.querySelector('.' + this.upChevronIconClass)
280
+ var $showAllText = this.$showAllButton.querySelector('.' + this.showAllTextClass)
281
+ var newButtonText = expanded ? 'Hide all sections' : 'Show all sections'
282
+ this.$showAllButton.setAttribute('aria-expanded', expanded)
283
+ $showAllText.innerHTML = newButtonText
284
+
285
+ // Swap icon, toggle class
286
+ if (expanded) {
287
+ $showAllIcon.classList.remove(this.downChevronIconClass)
288
+ } else {
289
+ $showAllIcon.classList.add(this.downChevronIconClass)
290
+ }
291
+ }
292
+
293
+ // Check for `window.sessionStorage`, and that it actually works.
294
+ var helper = {
295
+ checkForSessionStorage: function () {
296
+ var testString = 'this is the test string'
297
+ var result
298
+ try {
299
+ window.sessionStorage.setItem(testString, testString)
300
+ result = window.sessionStorage.getItem(testString) === testString.toString()
301
+ window.sessionStorage.removeItem(testString)
302
+ return result
303
+ } catch (exception) {
304
+ if ((typeof console === 'undefined' || typeof console.log === 'undefined')) {
305
+ console.log('Notice: sessionStorage not available.')
306
+ }
307
+ }
308
+ }
309
+ }
310
+
311
+ // Set the state of the accordions in sessionStorage
312
+ Accordion.prototype.storeState = function ($section) {
313
+ if (this.browserSupportsSessionStorage) {
314
+ // We need a unique way of identifying each content in the Accordion. Since
315
+ // an `#id` should be unique and an `id` is required for `aria-` attributes
316
+ // `id` can be safely used.
317
+ var $button = $section.querySelector('.' + this.sectionButtonClass)
318
+
319
+ if ($button) {
320
+ var contentId = $button.getAttribute('aria-controls')
321
+ var contentState = $button.getAttribute('aria-expanded')
322
+
323
+ if (typeof contentId === 'undefined' && (typeof console === 'undefined' || typeof console.log === 'undefined')) {
324
+ console.error(new Error('No aria controls present in accordion section heading.'))
325
+ }
326
+
327
+ if (typeof contentState === 'undefined' && (typeof console === 'undefined' || typeof console.log === 'undefined')) {
328
+ console.error(new Error('No aria expanded present in accordion section heading.'))
329
+ }
330
+
331
+ // Only set the state when both `contentId` and `contentState` are taken from the DOM.
332
+ if (contentId && contentState) {
333
+ window.sessionStorage.setItem(contentId, contentState)
334
+ }
335
+ }
336
+ }
337
+ }
338
+
339
+ // Read the state of the accordions from sessionStorage
340
+ Accordion.prototype.setInitialState = function ($section) {
341
+ if (this.browserSupportsSessionStorage) {
342
+ var $button = $section.querySelector('.' + this.sectionButtonClass)
343
+
344
+ if ($button) {
345
+ var contentId = $button.getAttribute('aria-controls')
346
+ var contentState = contentId ? window.sessionStorage.getItem(contentId) : null
347
+
348
+ if (contentState !== null) {
349
+ this.setExpanded(contentState === 'true', $section)
350
+ }
351
+ }
352
+ }
353
+ }
354
+
355
+ /**
356
+ * Create an element to improve semantics of the section button with punctuation
357
+ * @return {object} DOM element
358
+ *
359
+ * Used to add pause (with a comma) for assistive technology.
360
+ * Example: [heading]Section A ,[pause] Show this section.
361
+ * https://accessibility.blog.gov.uk/2017/12/18/what-working-on-gov-uk-navigation-taught-us-about-accessibility/
362
+ *
363
+ * Adding punctuation to the button can also improve its general semantics by dividing its contents
364
+ * into thematic chunks.
365
+ * See https://github.com/alphagov/govuk-frontend/issues/2327#issuecomment-922957442
366
+ */
367
+ Accordion.prototype.getButtonPunctuationEl = function () {
368
+ var $punctuationEl = document.createElement('span')
369
+ $punctuationEl.classList.add('govuk-visually-hidden', 'govuk-accordion__section-heading-divider')
370
+ $punctuationEl.innerHTML = ', '
371
+ return $punctuationEl
372
+ }
373
+
374
+ export default Accordion