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.
- checksums.yaml +4 -4
- data/app/assets/javascripts/govuk_publishing_components/analytics/page-content.js +4 -4
- data/app/assets/stylesheets/govuk_publishing_components/components/_contextual-sidebar.scss +20 -0
- data/app/assets/stylesheets/govuk_publishing_components/components/_layout-super-navigation-header.scss +3 -8
- data/app/assets/stylesheets/govuk_publishing_components/components/govspeak/_attachment.scss +7 -1
- data/app/controllers/govuk_publishing_components/audit_controller.rb +3 -2
- data/app/controllers/govuk_publishing_components/component_guide_controller.rb +0 -9
- data/app/models/govuk_publishing_components/audit_comparer.rb +92 -34
- data/app/views/govuk_publishing_components/audit/_applications.html.erb +20 -9
- data/app/views/govuk_publishing_components/component_guide/index.html.erb +1 -19
- data/app/views/govuk_publishing_components/components/_layout_footer.html.erb +20 -2
- data/app/views/govuk_publishing_components/components/contextual_sidebar/_ukraine_cta.html.erb +18 -19
- data/config/locales/ar.yml +1 -2
- data/config/locales/az.yml +1 -2
- data/config/locales/be.yml +1 -2
- data/config/locales/bg.yml +1 -2
- data/config/locales/bn.yml +1 -2
- data/config/locales/cs.yml +1 -2
- data/config/locales/cy.yml +1 -2
- data/config/locales/da.yml +1 -2
- data/config/locales/de.yml +1 -2
- data/config/locales/dr.yml +1 -2
- data/config/locales/el.yml +1 -2
- data/config/locales/en.yml +9 -2
- data/config/locales/es-419.yml +1 -2
- data/config/locales/es.yml +1 -2
- data/config/locales/et.yml +1 -2
- data/config/locales/fa.yml +1 -2
- data/config/locales/fi.yml +1 -2
- data/config/locales/fr.yml +1 -2
- data/config/locales/gd.yml +1 -2
- data/config/locales/gu.yml +1 -2
- data/config/locales/he.yml +1 -2
- data/config/locales/hi.yml +1 -2
- data/config/locales/hr.yml +1 -2
- data/config/locales/hu.yml +1 -2
- data/config/locales/hy.yml +1 -2
- data/config/locales/id.yml +1 -2
- data/config/locales/is.yml +1 -2
- data/config/locales/it.yml +1 -2
- data/config/locales/ja.yml +1 -2
- data/config/locales/ka.yml +1 -2
- data/config/locales/kk.yml +1 -2
- data/config/locales/ko.yml +1 -2
- data/config/locales/lt.yml +1 -2
- data/config/locales/lv.yml +1 -2
- data/config/locales/ms.yml +1 -2
- data/config/locales/mt.yml +1 -2
- data/config/locales/nl.yml +1 -2
- data/config/locales/no.yml +1 -2
- data/config/locales/pa-pk.yml +1 -2
- data/config/locales/pa.yml +1 -2
- data/config/locales/pl.yml +1 -2
- data/config/locales/ps.yml +1 -2
- data/config/locales/pt.yml +1 -2
- data/config/locales/ro.yml +1 -2
- data/config/locales/ru.yml +1 -2
- data/config/locales/si.yml +1 -2
- data/config/locales/sk.yml +1 -2
- data/config/locales/sl.yml +1 -2
- data/config/locales/so.yml +1 -2
- data/config/locales/sq.yml +1 -2
- data/config/locales/sr.yml +1 -2
- data/config/locales/sv.yml +1 -2
- data/config/locales/sw.yml +1 -2
- data/config/locales/ta.yml +1 -2
- data/config/locales/th.yml +1 -2
- data/config/locales/tk.yml +1 -2
- data/config/locales/tr.yml +1 -2
- data/config/locales/uk.yml +1 -2
- data/config/locales/ur.yml +1 -2
- data/config/locales/uz.yml +1 -2
- data/config/locales/vi.yml +1 -2
- data/config/locales/zh-hk.yml +1 -2
- data/config/locales/zh-tw.yml +1 -2
- data/config/locales/zh.yml +1 -2
- data/lib/govuk_publishing_components/presenters/attachment_helper.rb +1 -2
- data/lib/govuk_publishing_components/presenters/public_layout_helper.rb +35 -16
- data/lib/govuk_publishing_components/version.rb +1 -1
- data/node_modules/govuk-frontend/govuk/all.js +120 -49
- data/node_modules/govuk-frontend/govuk/components/back-link/macro-options.json +2 -2
- data/node_modules/govuk-frontend/govuk/components/breadcrumbs/_index.scss +0 -2
- data/node_modules/govuk-frontend/govuk/components/breadcrumbs/macro-options.json +2 -2
- data/node_modules/govuk-frontend/govuk/components/button/_index.scss +6 -16
- data/node_modules/govuk-frontend/govuk/components/button/macro-options.json +2 -2
- data/node_modules/govuk-frontend/govuk/components/character-count/character-count.js +120 -49
- data/node_modules/govuk-frontend/govuk/components/character-count/fixtures.json +33 -17
- data/node_modules/govuk-frontend/govuk/components/character-count/macro-options.json +2 -2
- data/node_modules/govuk-frontend/govuk/components/character-count/template.njk +1 -4
- data/node_modules/govuk-frontend/govuk/components/checkboxes/_index.scss +3 -2
- data/node_modules/govuk-frontend/govuk/components/checkboxes/fixtures.json +22 -10
- data/node_modules/govuk-frontend/govuk/components/checkboxes/macro-options.json +2 -2
- data/node_modules/govuk-frontend/govuk/components/date-input/fixtures.json +23 -23
- data/node_modules/govuk-frontend/govuk/components/date-input/template.njk +1 -1
- data/node_modules/govuk-frontend/govuk/components/details/macro-options.json +4 -4
- data/node_modules/govuk-frontend/govuk/components/error-message/macro-options.json +2 -2
- data/node_modules/govuk-frontend/govuk/components/error-summary/macro-options.json +3 -3
- data/node_modules/govuk-frontend/govuk/components/fieldset/macro-options.json +2 -2
- data/node_modules/govuk-frontend/govuk/components/footer/_index.scss +12 -22
- data/node_modules/govuk-frontend/govuk/components/header/_index.scss +13 -3
- data/node_modules/govuk-frontend/govuk/components/header/macro-options.json +2 -2
- data/node_modules/govuk-frontend/govuk/components/hint/macro-options.json +2 -2
- data/node_modules/govuk-frontend/govuk/components/input/_index.scss +4 -13
- data/node_modules/govuk-frontend/govuk/components/input/macro-options.json +5 -5
- data/node_modules/govuk-frontend/govuk/components/inset-text/macro-options.json +2 -2
- data/node_modules/govuk-frontend/govuk/components/label/macro-options.json +2 -2
- data/node_modules/govuk-frontend/govuk/components/panel/_index.scss +1 -1
- data/node_modules/govuk-frontend/govuk/components/panel/macro-options.json +4 -4
- data/node_modules/govuk-frontend/govuk/components/phase-banner/macro-options.json +2 -2
- data/node_modules/govuk-frontend/govuk/components/radios/_index.scss +5 -4
- data/node_modules/govuk-frontend/govuk/components/radios/fixtures.json +17 -12
- data/node_modules/govuk-frontend/govuk/components/radios/macro-options.json +2 -2
- data/node_modules/govuk-frontend/govuk/components/skip-link/_index.scss +1 -3
- data/node_modules/govuk-frontend/govuk/components/skip-link/macro-options.json +2 -2
- data/node_modules/govuk-frontend/govuk/components/summary-list/macro-options.json +5 -5
- data/node_modules/govuk-frontend/govuk/components/table/macro-options.json +4 -4
- data/node_modules/govuk-frontend/govuk/components/tabs/macro-options.json +2 -2
- data/node_modules/govuk-frontend/govuk/components/tag/macro-options.json +2 -2
- data/node_modules/govuk-frontend/govuk/components/warning-text/macro-options.json +2 -2
- data/node_modules/govuk-frontend/govuk/helpers/_colour.scss +3 -3
- data/node_modules/govuk-frontend/govuk/helpers/_links.scss +7 -5
- data/node_modules/govuk-frontend/govuk/helpers/_media-queries.scss +2 -2
- data/node_modules/govuk-frontend/govuk/helpers/_shape-arrow.scss +1 -1
- data/node_modules/govuk-frontend/govuk/helpers/_spacing.scss +3 -3
- data/node_modules/govuk-frontend/govuk/helpers/_typography.scss +2 -2
- data/node_modules/govuk-frontend/govuk/objects/_button-group.scss +10 -26
- data/node_modules/govuk-frontend/govuk/objects/_template.scss +1 -1
- data/node_modules/govuk-frontend/govuk/objects/_width-container.scss +0 -4
- data/node_modules/govuk-frontend/govuk/tools/_exports.scss +1 -1
- data/node_modules/govuk-frontend/govuk/tools/_font-url.scss +1 -1
- data/node_modules/govuk-frontend/govuk/tools/_image-url.scss +1 -1
- data/node_modules/govuk-frontend/govuk/tools/_px-to-em.scss +2 -2
- data/node_modules/govuk-frontend/govuk/tools/_px-to-rem.scss +1 -1
- data/node_modules/govuk-frontend/govuk-esm/all.mjs +88 -0
- data/node_modules/govuk-frontend/govuk-esm/common.mjs +28 -0
- data/node_modules/govuk-frontend/govuk-esm/components/accordion/accordion.mjs +374 -0
- data/node_modules/govuk-frontend/govuk-esm/components/button/button.mjs +64 -0
- data/node_modules/govuk-frontend/govuk-esm/components/character-count/character-count.mjs +251 -0
- data/node_modules/govuk-frontend/govuk-esm/components/checkboxes/checkboxes.mjs +164 -0
- data/node_modules/govuk-frontend/govuk-esm/components/details/details.mjs +147 -0
- data/node_modules/govuk-frontend/govuk-esm/components/error-summary/error-summary.mjs +168 -0
- data/node_modules/govuk-frontend/govuk-esm/components/header/header.mjs +52 -0
- data/node_modules/govuk-frontend/govuk-esm/components/notification-banner/notification-banner.mjs +55 -0
- data/node_modules/govuk-frontend/govuk-esm/components/radios/radios.mjs +122 -0
- data/node_modules/govuk-frontend/govuk-esm/components/skip-link/skip-link.mjs +94 -0
- data/node_modules/govuk-frontend/govuk-esm/components/tabs/tabs.mjs +282 -0
- data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/DOMTokenList.js +264 -0
- data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Document.js +26 -0
- data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element/prototype/classList.js +93 -0
- data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element/prototype/closest.js +24 -0
- data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element/prototype/matches.js +23 -0
- data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element/prototype/nextElementSibling.js +22 -0
- data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element/prototype/previousElementSibling.js +22 -0
- data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element.js +114 -0
- data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Event.js +252 -0
- data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Function/prototype/bind.js +159 -0
- data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Object/defineProperty.js +86 -0
- data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Window.js +20 -0
- data/node_modules/govuk-frontend/package.json +8 -1
- 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
|
-
-
|
37
|
-
|
38
|
-
-
|
39
|
-
-
|
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
|
-
-
|
67
|
+
-ms-flex-direction: row;
|
75
68
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
-
|
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
|
-
|
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
|
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
|
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
|
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
|
13
|
+
@if unitless($value) {
|
14
14
|
$value: $value * 1px;
|
15
15
|
}
|
16
|
-
@if
|
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;
|
@@ -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
|