dxw_govuk_frontend_rails 3.3.1 → 3.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +1 -0
  3. data/Gemfile.lock +5 -4
  4. data/README.md +45 -42
  5. data/dxw_govuk_frontend_rails.gemspec +1 -1
  6. data/lib/dxw_govuk_frontend_rails/version.rb +1 -1
  7. data/package-lock.json +3 -3
  8. data/package.json +1 -1
  9. data/vendor/assets/javascripts/govuk_frontend_rails.js +194 -118
  10. data/vendor/assets/stylesheets/_base.scss +3 -0
  11. data/vendor/assets/stylesheets/all.scss +1 -3
  12. data/vendor/assets/stylesheets/components/_all.scss +31 -29
  13. data/vendor/assets/stylesheets/components/accordion/_accordion.scss +2 -208
  14. data/vendor/assets/stylesheets/components/accordion/_index.scss +207 -0
  15. data/vendor/assets/stylesheets/components/back-link/_back-link.scss +2 -62
  16. data/vendor/assets/stylesheets/components/back-link/_index.scss +112 -0
  17. data/vendor/assets/stylesheets/components/breadcrumbs/_breadcrumbs.scss +2 -118
  18. data/vendor/assets/stylesheets/components/breadcrumbs/_index.scss +138 -0
  19. data/vendor/assets/stylesheets/components/button/_button.scss +2 -290
  20. data/vendor/assets/stylesheets/components/button/_index.scss +280 -0
  21. data/vendor/assets/stylesheets/components/character-count/_character-count.scss +2 -31
  22. data/vendor/assets/stylesheets/components/character-count/_index.scss +28 -0
  23. data/vendor/assets/stylesheets/components/checkboxes/_checkboxes.scss +2 -308
  24. data/vendor/assets/stylesheets/components/checkboxes/_index.scss +305 -0
  25. data/vendor/assets/stylesheets/components/date-input/_date-input.scss +2 -30
  26. data/vendor/assets/stylesheets/components/date-input/_index.scss +26 -0
  27. data/vendor/assets/stylesheets/components/details/_details.scss +2 -88
  28. data/vendor/assets/stylesheets/components/details/_index.scss +84 -0
  29. data/vendor/assets/stylesheets/components/error-message/_error-message.scss +2 -15
  30. data/vendor/assets/stylesheets/components/error-message/_index.scss +11 -0
  31. data/vendor/assets/stylesheets/components/error-summary/_error-summary.scss +2 -59
  32. data/vendor/assets/stylesheets/components/error-summary/_index.scss +55 -0
  33. data/vendor/assets/stylesheets/components/fieldset/_fieldset.scss +2 -68
  34. data/vendor/assets/stylesheets/components/fieldset/_index.scss +64 -0
  35. data/vendor/assets/stylesheets/components/file-upload/_file-upload.scss +2 -81
  36. data/vendor/assets/stylesheets/components/file-upload/_index.scss +50 -0
  37. data/vendor/assets/stylesheets/components/footer/_footer.scss +2 -269
  38. data/vendor/assets/stylesheets/components/footer/_index.scss +235 -0
  39. data/vendor/assets/stylesheets/components/header/_header.scss +2 -318
  40. data/vendor/assets/stylesheets/components/header/_index.scss +312 -0
  41. data/vendor/assets/stylesheets/components/hint/_hint.scss +2 -50
  42. data/vendor/assets/stylesheets/components/hint/_index.scss +46 -0
  43. data/vendor/assets/stylesheets/components/input/_index.scss +96 -0
  44. data/vendor/assets/stylesheets/components/input/_input.scss +2 -103
  45. data/vendor/assets/stylesheets/components/inset-text/_index.scss +24 -0
  46. data/vendor/assets/stylesheets/components/inset-text/_inset-text.scss +2 -28
  47. data/vendor/assets/stylesheets/components/label/_index.scss +41 -0
  48. data/vendor/assets/stylesheets/components/label/_label.scss +2 -45
  49. data/vendor/assets/stylesheets/components/panel/_index.scss +40 -0
  50. data/vendor/assets/stylesheets/components/panel/_panel.scss +2 -44
  51. data/vendor/assets/stylesheets/components/phase-banner/_index.scss +27 -0
  52. data/vendor/assets/stylesheets/components/phase-banner/_phase-banner.scss +2 -31
  53. data/vendor/assets/stylesheets/components/radios/_index.scss +342 -0
  54. data/vendor/assets/stylesheets/components/radios/_radios.scss +2 -346
  55. data/vendor/assets/stylesheets/components/select/_index.scss +50 -0
  56. data/vendor/assets/stylesheets/components/select/_select.scss +2 -57
  57. data/vendor/assets/stylesheets/components/skip-link/_index.scss +34 -0
  58. data/vendor/assets/stylesheets/components/skip-link/_skip-link.scss +2 -31
  59. data/vendor/assets/stylesheets/components/summary-list/_index.scss +153 -0
  60. data/vendor/assets/stylesheets/components/summary-list/_summary-list.scss +2 -157
  61. data/vendor/assets/stylesheets/components/table/_index.scss +50 -0
  62. data/vendor/assets/stylesheets/components/table/_table.scss +2 -54
  63. data/vendor/assets/stylesheets/components/tabs/_index.scss +138 -0
  64. data/vendor/assets/stylesheets/components/tabs/_tabs.scss +2 -142
  65. data/vendor/assets/stylesheets/components/tag/_index.scss +87 -0
  66. data/vendor/assets/stylesheets/components/tag/_tag.scss +2 -44
  67. data/vendor/assets/stylesheets/components/textarea/_index.scss +48 -0
  68. data/vendor/assets/stylesheets/components/textarea/_textarea.scss +2 -55
  69. data/vendor/assets/stylesheets/components/warning-text/_index.scss +58 -0
  70. data/vendor/assets/stylesheets/components/warning-text/_warning-text.scss +2 -60
  71. data/vendor/assets/stylesheets/core/_global-styles.scss +5 -3
  72. data/vendor/assets/stylesheets/core/_links.scss +5 -3
  73. data/vendor/assets/stylesheets/core/_lists.scss +17 -3
  74. data/vendor/assets/stylesheets/core/_section-break.scss +5 -3
  75. data/vendor/assets/stylesheets/core/_template.scss +5 -3
  76. data/vendor/assets/stylesheets/core/_typography.scss +5 -3
  77. data/vendor/assets/stylesheets/helpers/_clearfix.scss +1 -1
  78. data/vendor/assets/stylesheets/helpers/_colour.scss +1 -1
  79. data/vendor/assets/stylesheets/helpers/_focused.scss +1 -1
  80. data/vendor/assets/stylesheets/helpers/_grid.scss +2 -1
  81. data/vendor/assets/stylesheets/helpers/_links.scss +1 -1
  82. data/vendor/assets/stylesheets/helpers/_media-queries.scss +1 -1
  83. data/vendor/assets/stylesheets/helpers/_shape-arrow.scss +1 -1
  84. data/vendor/assets/stylesheets/helpers/_spacing.scss +5 -5
  85. data/vendor/assets/stylesheets/helpers/_typography.scss +6 -6
  86. data/vendor/assets/stylesheets/helpers/_visually-hidden.scss +30 -30
  87. data/vendor/assets/stylesheets/objects/_form-group.scss +1 -3
  88. data/vendor/assets/stylesheets/objects/_grid.scss +1 -3
  89. data/vendor/assets/stylesheets/objects/_main-wrapper.scss +5 -3
  90. data/vendor/assets/stylesheets/objects/_width-container.scss +34 -13
  91. data/vendor/assets/stylesheets/overrides/_display.scss +11 -3
  92. data/vendor/assets/stylesheets/overrides/_spacing.scss +5 -3
  93. data/vendor/assets/stylesheets/overrides/_typography.scss +5 -3
  94. data/vendor/assets/stylesheets/overrides/_width.scss +5 -3
  95. data/vendor/assets/stylesheets/settings/_colours-applied.scss +2 -2
  96. data/vendor/assets/stylesheets/settings/_colours-palette.scss +1 -1
  97. data/vendor/assets/stylesheets/settings/_ie8.scss +1 -1
  98. data/vendor/assets/stylesheets/settings/_measurements.scss +4 -1
  99. data/vendor/assets/stylesheets/tools/_compatibility.scss +1 -1
  100. data/vendor/assets/stylesheets/tools/_font-url.scss +1 -1
  101. data/vendor/assets/stylesheets/tools/_ie8.scss +1 -1
  102. data/vendor/assets/stylesheets/tools/_iff.scss +3 -1
  103. data/vendor/assets/stylesheets/tools/_image-url.scss +1 -1
  104. data/vendor/assets/stylesheets/tools/_px-to-em.scss +1 -1
  105. data/vendor/assets/stylesheets/tools/_px-to-rem.scss +1 -1
  106. metadata +36 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 529c34dd67d14019a27162ddeb0eed821d312520591177bf5a78fac565418aa5
4
- data.tar.gz: f9f13780320df3439998c5886b6a95a93934031521b5ed7ba795186b48a95c3a
3
+ metadata.gz: 24366e1d10fcdffc7d4ce6ab81b1af6bc53a01971488e7dc4d1e3dc82b3edf3c
4
+ data.tar.gz: 9ef221edb271314433d1944067443cb2500536c98cf89e84e672ffd56c5ccc05
5
5
  SHA512:
6
- metadata.gz: 483a3f1dd7911a419df9379d07fed9cd4672847c239cfb111ab6d930e16ae792d54cc2d0bdfe4cea17d276f47d4cf47bc36000bd525685eb662ded08e436709b
7
- data.tar.gz: 5a7e13c09c472066e4f5dfa0c3bbab1258c6f9b05bbbb3ffe7b3a51bc9efdad26bdd95bdcb20a9c950cd43e63293380d38cd158eff17d290ea2f596db7fcfd21
6
+ metadata.gz: 5fb712c7e9a7b679f9727b49d57eefe2ca24c0e2b01b0216999ef20ff4ca5c476a63be78d803ff4651b330d5361a4bd13d342dc44118a667cd41b86c1fd44dee
7
+ data.tar.gz: 3c0f0d0f3d3dd1182d366a2a547345a63ea13ba90d44a7fa0e914201954c7547aafeff04119f64dcdb1db39c9002d922161df2758f5fc1f05a5290ce45312cc4
@@ -0,0 +1 @@
1
+ The [dxw standard contributing guide](https://github.com/dxw/.github/blob/main/CONTRIBUTING.md) applies for this repository.
@@ -1,20 +1,21 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- dxw_govuk_frontend_rails (3.3.0)
4
+ dxw_govuk_frontend_rails (3.8.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
- rake (10.5.0)
9
+ rake (13.0.1)
10
10
 
11
11
  PLATFORMS
12
12
  ruby
13
+ x86_64-darwin-19
13
14
 
14
15
  DEPENDENCIES
15
16
  bundler (~> 2.0)
16
17
  dxw_govuk_frontend_rails!
17
- rake (~> 10.0)
18
+ rake (~> 13.0)
18
19
 
19
20
  BUNDLED WITH
20
- 2.0.2
21
+ 2.2.1
data/README.md CHANGED
@@ -1,88 +1,91 @@
1
1
  # GOVUK Frontend for Rails
2
2
 
3
- Adds the GOVUK Frontend for Rails using the asset pipeline.
3
+ ## Read this first
4
+
5
+ Although this gem makes it super easy to add the GOVUK Frontend to your
6
+ Rails applications, we would recommend using it as a guide to add the
7
+ GOVUK Frontend yourself.
8
+
9
+ ## About Adds the GOVUK Frontend for Rails using the asset pipeline.
4
10
 
5
11
  https://github.com/alphagov/govuk-frontend
6
12
 
7
13
  ## Breaking Changes in v3.0.0
8
14
 
9
- This release of the GOVUK Frontend has breaking changes! Do not update to this version until you have read and understood:
15
+ This release of the GOVUK Frontend has breaking changes! Do not update to this
16
+ version until you have read and understood:
10
17
 
11
- You will not be required to updated any file paths with govuk as we take care of that for you, all others changes will need to be managed:
18
+ You will not be required to updated any file paths with govuk as we take care of
19
+ that for you, all others changes will need to be managed:
12
20
 
13
21
  https://github.com/alphagov/govuk-frontend/releases/tag/v3.0.0
14
22
 
15
23
  ## Limitations
16
24
 
17
- This gem does not include the ie8 version of GOVUK Frontend, as the service standard no longer requires it:
25
+ This gem does not include the ie8 version of GOVUK Frontend, as the service
26
+ standard no longer requires it:
18
27
 
19
28
  https://www.gov.uk/service-manual/technology/designing-for-different-browsers-and-devices
20
29
 
21
- This gem and its documentation cannot tell you how to use the GOVUK effectively, see the GOVUK Design System documentation for that:
30
+ This gem and its documentation cannot tell you how to use the GOVUK effectively,
31
+ see the GOVUK Design System documentation for that:
22
32
 
23
- https://design-system.service.gov.uk/
24
- ## Installation
33
+ https://design-system.service.gov.uk/ ## Installation
25
34
 
26
35
  Add this line to your application's Gemfile:
27
36
 
28
- ```ruby
29
- gem 'dxw_govuk_frontend_rails'
30
- ```
37
+ ```ruby gem 'dxw_govuk_frontend_rails' ```
31
38
 
32
39
  And then execute:
33
40
 
34
41
  $ bundle
35
42
 
36
- ## Usage
37
- Your Rails app will need to have sass enabled.
43
+ ## Usage Your Rails app will need to have sass enabled.
38
44
 
39
45
  ### Basic usage
40
46
 
41
- If the service you are building does not need to modify or extend the GOVUK Frontend all you need to do is import the main sass and javascript files into your Rails app:
47
+ If the service you are building does not need to modify or extend the GOVUK
48
+ Frontend all you need to do is import the main sass and javascript files into
49
+ your Rails app:
42
50
 
43
- - create a sass file in your application:
44
- ```
45
- app/assets/stylesheets/govuk_frontend_rails.scss
46
- ```
47
- - import the styles into `govuk_frontend_rails.scss`:
48
- ```sass
49
- @import "govuk-frontend-rails";
50
- ```
51
- - require the `govuk-frontend-rails.scss` file in `application.css` or equivalent:
51
+ - create a sass file in your application: ```
52
+ app/assets/stylesheets/govuk_frontend_rails.scss ```
53
+ - import the styles into `govuk_frontend_rails.scss`: ```sass @import
54
+ "govuk-frontend-rails"; ```
55
+ - require the `govuk-frontend-rails.scss` file in `application.css` or
56
+ equivalent:
52
57
 
53
- ```
54
- *= require govuk_frontend_rails
55
- ```
58
+ ``` *= require govuk_frontend_rails ```
56
59
 
57
60
  - require the javascript into `app/assets/javascripts/application.js`:
58
61
 
59
- ```
60
- //= require govuk_frontend_rails
61
- ```
62
- - initialise the GOVUK Frontend either in a .js file on in your application layout:
62
+ ``` //= require govuk_frontend_rails ```
63
+ - initialise the GOVUK Frontend either in a .js file on in your application
64
+ layout:
63
65
 
64
- ```javascript
65
- window.onload = function() {
66
- window.GOVUKFrontend.initAll()
67
- };
68
- ```
66
+ ```javascript window.onload = function() { window.GOVUKFrontend.initAll() }; ```
69
67
  - update your application markup to use the GOVUK Frontend class names
70
68
 
71
- IMPORTANT: You cannot use the GOVUK Frontend without changes if the service you are building is not on GOV.UK, read and understand the guidance here:
69
+ IMPORTANT: You cannot use the GOVUK Frontend without changes if the service you
70
+ are building is not on GOV.UK, read and understand the guidance here:
72
71
 
73
72
  https://www.gov.uk/service-manual/design/making-your-service-look-like-govuk#if-your-service-isnt-on-govuk
74
73
 
75
- ## Tracking version
76
- We aim to tracking the version of GOVUK Frontend.
74
+ ## Tracking version We aim to tracking the version of GOVUK Frontend.
77
75
 
78
76
  ## Updating to a new Version of GOVUK Frontend
79
77
 
80
78
  When a new version of the GOVUK Frontend is released:
81
79
 
82
- - set the new version number in `package.json` and `lib/dxw_govuk_frontend_rails/version.rb`
83
- - run `npm update` to get the new release
84
- - run `bundle rake` to compile the new assets
80
+ - make a new branch with the GOVUK Frontend version number
81
+ - set the new version number in `package.json` and
82
+ `lib/dxw_govuk_frontend_rails/version.rb`
83
+ - run `npm update` to get the new release of the GOVUK Frontend
84
+ - run `bundle exec rake` to compile the new assets
85
85
  - commit the changes
86
86
  - tag with the same release number as GOVUK Frontend
87
- - push the changes
88
- - Github acitons will build the gem and push to Rubygems
87
+ - push the change
88
+ - push the tag
89
+ - merge your branch
90
+ - make a new release of the tag on GitHub
91
+ - GitHub acitons will build the gem and push to Rubygems
@@ -23,5 +23,5 @@ Gem::Specification.new do |spec|
23
23
  spec.require_paths = ["lib"]
24
24
 
25
25
  spec.add_development_dependency "bundler", "~> 2.0"
26
- spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "rake", "~> 13.0"
27
27
  end
@@ -1,3 +1,3 @@
1
1
  module DxwGovukFrontendRails
2
- VERSION = "3.3.1"
2
+ VERSION = "3.8.0"
3
3
  end
@@ -5,9 +5,9 @@
5
5
  "requires": true,
6
6
  "dependencies": {
7
7
  "govuk-frontend": {
8
- "version": "3.3.0",
9
- "resolved": "https://registry.npmjs.org/govuk-frontend/-/govuk-frontend-3.3.0.tgz",
10
- "integrity": "sha512-ncOGTAV6mzz1CPBlr/UGETiG3IO6P3b0CvSI0wxBz7Uo0A/6jttLoxkvuYXju2oNA2yqRh2NjD1zJUOP3Q32CQ=="
8
+ "version": "3.8.0",
9
+ "resolved": "https://registry.npmjs.org/govuk-frontend/-/govuk-frontend-3.8.0.tgz",
10
+ "integrity": "sha512-+vgXzFsh7wpLRGjFSDvDcA2zNA2wOxT6gGs/KUpkTjF1Uop9BerW/1W/YB1BMpeTEJoPlmrkA19+DS1fqJtL9Q=="
11
11
  }
12
12
  }
13
13
  }
@@ -21,6 +21,6 @@
21
21
  },
22
22
  "homepage": "https://github.com/mec/dxw_govuk_frontend_rails#readme",
23
23
  "dependencies": {
24
- "govuk-frontend": "3.3.0"
24
+ "govuk-frontend": "3.8.0"
25
25
  }
26
26
  }
@@ -891,7 +891,7 @@ Accordion.prototype.initHeaderAttributes = function ($headerWrapper, index) {
891
891
  icon.className = this.iconClass;
892
892
  icon.setAttribute('aria-hidden', 'true');
893
893
 
894
- $heading.appendChild(icon);
894
+ $button.appendChild(icon);
895
895
  };
896
896
 
897
897
  // When section toggled, set and store state
@@ -1497,6 +1497,9 @@ Details.prototype.polyfillHandleInputs = function (node, callback) {
1497
1497
  function CharacterCount ($module) {
1498
1498
  this.$module = $module;
1499
1499
  this.$textarea = $module.querySelector('.govuk-js-character-count');
1500
+ if (this.$textarea) {
1501
+ this.$countMessage = $module.querySelector('[id=' + this.$textarea.id + '-info]');
1502
+ }
1500
1503
  }
1501
1504
 
1502
1505
  CharacterCount.prototype.defaults = {
@@ -1509,10 +1512,16 @@ CharacterCount.prototype.init = function () {
1509
1512
  // Check for module
1510
1513
  var $module = this.$module;
1511
1514
  var $textarea = this.$textarea;
1512
- if (!$textarea) {
1515
+ var $countMessage = this.$countMessage;
1516
+
1517
+ if (!$textarea || !$countMessage) {
1513
1518
  return
1514
1519
  }
1515
1520
 
1521
+ // We move count message right after the field
1522
+ // Kept for backwards compatibility
1523
+ $textarea.insertAdjacentElement('afterend', $countMessage);
1524
+
1516
1525
  // Read options set using dataset ('data-' values)
1517
1526
  this.options = this.getDataset($module);
1518
1527
 
@@ -1530,23 +1539,25 @@ CharacterCount.prototype.init = function () {
1530
1539
  return
1531
1540
  }
1532
1541
 
1533
- // Generate and reference message
1534
- var boundCreateCountMessage = this.createCountMessage.bind(this);
1535
- this.countMessage = boundCreateCountMessage();
1542
+ // Remove hard limit if set
1543
+ $module.removeAttribute('maxlength');
1536
1544
 
1537
- // If there's a maximum length defined and the count message exists
1538
- if (this.countMessage) {
1539
- // Remove hard limit if set
1540
- $module.removeAttribute('maxlength');
1545
+ // When the page is restored after navigating 'back' in some browsers the
1546
+ // state of the character count is not restored until *after* the DOMContentLoaded
1547
+ // event is fired, so we need to sync after the pageshow event in browsers
1548
+ // that support it.
1549
+ if ('onpageshow' in window) {
1550
+ window.addEventListener('pageshow', this.sync.bind(this));
1551
+ } else {
1552
+ window.addEventListener('DOMContentLoaded', this.sync.bind(this));
1553
+ }
1541
1554
 
1542
- // Bind event changes to the textarea
1543
- var boundChangeEvents = this.bindChangeEvents.bind(this);
1544
- boundChangeEvents();
1555
+ this.sync();
1556
+ };
1545
1557
 
1546
- // Update count message
1547
- var boundUpdateCountMessage = this.updateCountMessage.bind(this);
1548
- boundUpdateCountMessage();
1549
- }
1558
+ CharacterCount.prototype.sync = function () {
1559
+ this.bindChangeEvents();
1560
+ this.updateCountMessage();
1550
1561
  };
1551
1562
 
1552
1563
  // Read data attributes
@@ -1577,27 +1588,6 @@ CharacterCount.prototype.count = function (text) {
1577
1588
  return length
1578
1589
  };
1579
1590
 
1580
- // Generate count message and bind it to the input
1581
- // returns reference to the generated element
1582
- CharacterCount.prototype.createCountMessage = function () {
1583
- var countElement = this.$textarea;
1584
- var elementId = countElement.id;
1585
- // Check for existing info count message
1586
- var countMessage = document.getElementById(elementId + '-info');
1587
- // If there is no existing info count message we add one right after the field
1588
- if (elementId && !countMessage) {
1589
- countElement.insertAdjacentHTML('afterend', '<span id="' + elementId + '-info" class="govuk-hint govuk-character-count__message" aria-live="polite"></span>');
1590
- this.describedBy = countElement.getAttribute('aria-describedby');
1591
- this.describedByInfo = this.describedBy + ' ' + elementId + '-info';
1592
- countElement.setAttribute('aria-describedby', this.describedByInfo);
1593
- countMessage = document.getElementById(elementId + '-info');
1594
- } else {
1595
- // If there is an existing info count message we move it right after the field
1596
- countElement.insertAdjacentElement('afterend', countMessage);
1597
- }
1598
- return countMessage
1599
- };
1600
-
1601
1591
  // Bind input propertychange to the elements and update based on the change
1602
1592
  CharacterCount.prototype.bindChangeEvents = function () {
1603
1593
  var $textarea = this.$textarea;
@@ -1615,8 +1605,7 @@ CharacterCount.prototype.checkIfValueChanged = function () {
1615
1605
  if (!this.$textarea.oldValue) this.$textarea.oldValue = '';
1616
1606
  if (this.$textarea.value !== this.$textarea.oldValue) {
1617
1607
  this.$textarea.oldValue = this.$textarea.value;
1618
- var boundUpdateCountMessage = this.updateCountMessage.bind(this);
1619
- boundUpdateCountMessage();
1608
+ this.updateCountMessage();
1620
1609
  }
1621
1610
  };
1622
1611
 
@@ -1624,7 +1613,7 @@ CharacterCount.prototype.checkIfValueChanged = function () {
1624
1613
  CharacterCount.prototype.updateCountMessage = function () {
1625
1614
  var countElement = this.$textarea;
1626
1615
  var options = this.options;
1627
- var countMessage = this.countMessage;
1616
+ var countMessage = this.$countMessage;
1628
1617
 
1629
1618
  // Determine the remaining number of characters/words
1630
1619
  var currentLength = this.count(countElement.value);
@@ -1685,44 +1674,91 @@ function Checkboxes ($module) {
1685
1674
  this.$inputs = $module.querySelectorAll('input[type="checkbox"]');
1686
1675
  }
1687
1676
 
1677
+ /**
1678
+ * Initialise Checkboxes
1679
+ *
1680
+ * Checkboxes can be associated with a 'conditionally revealed' content block –
1681
+ * for example, a checkbox for 'Phone' could reveal an additional form field for
1682
+ * the user to enter their phone number.
1683
+ *
1684
+ * These associations are made using a `data-aria-controls` attribute, which is
1685
+ * promoted to an aria-controls attribute during initialisation.
1686
+ *
1687
+ * We also need to restore the state of any conditional reveals on the page (for
1688
+ * example if the user has navigated back), and set up event handlers to keep
1689
+ * the reveal in sync with the checkbox state.
1690
+ */
1688
1691
  Checkboxes.prototype.init = function () {
1689
1692
  var $module = this.$module;
1690
1693
  var $inputs = this.$inputs;
1691
1694
 
1692
- /**
1693
- * Loop over all items with [data-controls]
1694
- * Check if they have a matching conditional reveal
1695
- * If they do, assign attributes.
1696
- **/
1697
1695
  nodeListForEach($inputs, function ($input) {
1698
- var controls = $input.getAttribute('data-aria-controls');
1696
+ var target = $input.getAttribute('data-aria-controls');
1699
1697
 
1700
- // Check if input controls anything
1701
- // Check if content exists, before setting attributes.
1702
- if (!controls || !$module.querySelector('#' + controls)) {
1698
+ // Skip checkboxes without data-aria-controls attributes, or where the
1699
+ // target element does not exist.
1700
+ if (!target || !$module.querySelector('#' + target)) {
1703
1701
  return
1704
1702
  }
1705
1703
 
1706
- // If we have content that is controlled, set attributes.
1707
- $input.setAttribute('aria-controls', controls);
1704
+ // Promote the data-aria-controls attribute to a aria-controls attribute
1705
+ // so that the relationship is exposed in the AOM
1706
+ $input.setAttribute('aria-controls', target);
1708
1707
  $input.removeAttribute('data-aria-controls');
1709
- this.setAttributes($input);
1710
- }.bind(this));
1708
+ });
1709
+
1710
+ // When the page is restored after navigating 'back' in some browsers the
1711
+ // state of form controls is not restored until *after* the DOMContentLoaded
1712
+ // event is fired, so we need to sync after the pageshow event in browsers
1713
+ // that support it.
1714
+ if ('onpageshow' in window) {
1715
+ window.addEventListener('pageshow', this.syncAllConditionalReveals.bind(this));
1716
+ } else {
1717
+ window.addEventListener('DOMContentLoaded', this.syncAllConditionalReveals.bind(this));
1718
+ }
1719
+
1720
+ // Although we've set up handlers to sync state on the pageshow or
1721
+ // DOMContentLoaded event, init could be called after those events have fired,
1722
+ // for example if they are added to the page dynamically, so sync now too.
1723
+ this.syncAllConditionalReveals();
1711
1724
 
1712
- // Handle events
1713
1725
  $module.addEventListener('click', this.handleClick.bind(this));
1714
1726
  };
1715
1727
 
1716
- Checkboxes.prototype.setAttributes = function ($input) {
1717
- var inputIsChecked = $input.checked;
1718
- $input.setAttribute('aria-expanded', inputIsChecked);
1728
+ /**
1729
+ * Sync the conditional reveal states for all inputs in this $module.
1730
+ */
1731
+ Checkboxes.prototype.syncAllConditionalReveals = function () {
1732
+ nodeListForEach(this.$inputs, this.syncConditionalRevealWithInputState.bind(this));
1733
+ };
1734
+
1735
+ /**
1736
+ * Sync conditional reveal with the input state
1737
+ *
1738
+ * Synchronise the visibility of the conditional reveal, and its accessible
1739
+ * state, with the input's checked state.
1740
+ *
1741
+ * @param {HTMLInputElement} $input Checkbox input
1742
+ */
1743
+ Checkboxes.prototype.syncConditionalRevealWithInputState = function ($input) {
1744
+ var $target = this.$module.querySelector('#' + $input.getAttribute('aria-controls'));
1745
+
1746
+ if ($target && $target.classList.contains('govuk-checkboxes__conditional')) {
1747
+ var inputIsChecked = $input.checked;
1719
1748
 
1720
- var $content = this.$module.querySelector('#' + $input.getAttribute('aria-controls'));
1721
- if ($content) {
1722
- $content.classList.toggle('govuk-checkboxes__conditional--hidden', !inputIsChecked);
1749
+ $input.setAttribute('aria-expanded', inputIsChecked);
1750
+ $target.classList.toggle('govuk-checkboxes__conditional--hidden', !inputIsChecked);
1723
1751
  }
1724
1752
  };
1725
1753
 
1754
+ /**
1755
+ * Click event handler
1756
+ *
1757
+ * Handle a click within the $module – if the click occurred on a checkbox, sync
1758
+ * the state of any associated conditional reveal with the checkbox state.
1759
+ *
1760
+ * @param {MouseEvent} event Click event
1761
+ */
1726
1762
  Checkboxes.prototype.handleClick = function (event) {
1727
1763
  var $target = event.target;
1728
1764
 
@@ -1730,7 +1766,7 @@ Checkboxes.prototype.handleClick = function (event) {
1730
1766
  var isCheckbox = $target.getAttribute('type') === 'checkbox';
1731
1767
  var hasAriaControls = $target.getAttribute('aria-controls');
1732
1768
  if (isCheckbox && hasAriaControls) {
1733
- this.setAttributes($target);
1769
+ this.syncConditionalRevealWithInputState($target);
1734
1770
  }
1735
1771
  };
1736
1772
 
@@ -1767,7 +1803,7 @@ Checkboxes.prototype.handleClick = function (event) {
1767
1803
 
1768
1804
  if (detect) return
1769
1805
 
1770
- // Polyfill from https://raw.githubusercontent.com/Financial-Times/polyfill-service/1f3c09b402f65bf6e393f933a15ba63f1b86ef1f/packages/polyfill-library/polyfills/Element/prototype/closest/polyfill.js
1806
+ // Polyfill from https://raw.githubusercontent.com/Financial-Times/polyfill-service/1f3c09b402f65bf6e393f933a15ba63f1b86ef1f/packages/polyfill-library/polyfills/Element/prototype/closest/polyfill.js
1771
1807
  Element.prototype.closest = function closest(selector) {
1772
1808
  var node = this;
1773
1809
 
@@ -1978,116 +2014,156 @@ Header.prototype.handleClick = function (event) {
1978
2014
 
1979
2015
  function Radios ($module) {
1980
2016
  this.$module = $module;
2017
+ this.$inputs = $module.querySelectorAll('input[type="radio"]');
1981
2018
  }
1982
2019
 
2020
+ /**
2021
+ * Initialise Radios
2022
+ *
2023
+ * Radios can be associated with a 'conditionally revealed' content block – for
2024
+ * example, a radio for 'Phone' could reveal an additional form field for the
2025
+ * user to enter their phone number.
2026
+ *
2027
+ * These associations are made using a `data-aria-controls` attribute, which is
2028
+ * promoted to an aria-controls attribute during initialisation.
2029
+ *
2030
+ * We also need to restore the state of any conditional reveals on the page (for
2031
+ * example if the user has navigated back), and set up event handlers to keep
2032
+ * the reveal in sync with the radio state.
2033
+ */
1983
2034
  Radios.prototype.init = function () {
1984
2035
  var $module = this.$module;
1985
- var $inputs = $module.querySelectorAll('input[type="radio"]');
2036
+ var $inputs = this.$inputs;
1986
2037
 
1987
- /**
1988
- * Loop over all items with [data-controls]
1989
- * Check if they have a matching conditional reveal
1990
- * If they do, assign attributes.
1991
- **/
1992
2038
  nodeListForEach($inputs, function ($input) {
1993
- var controls = $input.getAttribute('data-aria-controls');
2039
+ var target = $input.getAttribute('data-aria-controls');
1994
2040
 
1995
- // Check if input controls anything
1996
- // Check if content exists, before setting attributes.
1997
- if (!controls || !$module.querySelector('#' + controls)) {
2041
+ // Skip radios without data-aria-controls attributes, or where the
2042
+ // target element does not exist.
2043
+ if (!target || !$module.querySelector('#' + target)) {
1998
2044
  return
1999
2045
  }
2000
2046
 
2001
- // If we have content that is controlled, set attributes.
2002
- $input.setAttribute('aria-controls', controls);
2047
+ // Promote the data-aria-controls attribute to a aria-controls attribute
2048
+ // so that the relationship is exposed in the AOM
2049
+ $input.setAttribute('aria-controls', target);
2003
2050
  $input.removeAttribute('data-aria-controls');
2004
- this.setAttributes($input);
2005
- }.bind(this));
2051
+ });
2052
+
2053
+ // When the page is restored after navigating 'back' in some browsers the
2054
+ // state of form controls is not restored until *after* the DOMContentLoaded
2055
+ // event is fired, so we need to sync after the pageshow event in browsers
2056
+ // that support it.
2057
+ if ('onpageshow' in window) {
2058
+ window.addEventListener('pageshow', this.syncAllConditionalReveals.bind(this));
2059
+ } else {
2060
+ window.addEventListener('DOMContentLoaded', this.syncAllConditionalReveals.bind(this));
2061
+ }
2062
+
2063
+ // Although we've set up handlers to sync state on the pageshow or
2064
+ // DOMContentLoaded event, init could be called after those events have fired,
2065
+ // for example if they are added to the page dynamically, so sync now too.
2066
+ this.syncAllConditionalReveals();
2006
2067
 
2007
2068
  // Handle events
2008
2069
  $module.addEventListener('click', this.handleClick.bind(this));
2009
2070
  };
2010
2071
 
2011
- Radios.prototype.setAttributes = function ($input) {
2012
- var $content = document.querySelector('#' + $input.getAttribute('aria-controls'));
2072
+ /**
2073
+ * Sync the conditional reveal states for all inputs in this $module.
2074
+ */
2075
+ Radios.prototype.syncAllConditionalReveals = function () {
2076
+ nodeListForEach(this.$inputs, this.syncConditionalRevealWithInputState.bind(this));
2077
+ };
2078
+
2079
+ /**
2080
+ * Sync conditional reveal with the input state
2081
+ *
2082
+ * Synchronise the visibility of the conditional reveal, and its accessible
2083
+ * state, with the input's checked state.
2084
+ *
2085
+ * @param {HTMLInputElement} $input Radio input
2086
+ */
2087
+ Radios.prototype.syncConditionalRevealWithInputState = function ($input) {
2088
+ var $target = document.querySelector('#' + $input.getAttribute('aria-controls'));
2013
2089
 
2014
- if ($content && $content.classList.contains('govuk-radios__conditional')) {
2090
+ if ($target && $target.classList.contains('govuk-radios__conditional')) {
2015
2091
  var inputIsChecked = $input.checked;
2016
2092
 
2017
2093
  $input.setAttribute('aria-expanded', inputIsChecked);
2018
-
2019
- $content.classList.toggle('govuk-radios__conditional--hidden', !inputIsChecked);
2094
+ $target.classList.toggle('govuk-radios__conditional--hidden', !inputIsChecked);
2020
2095
  }
2021
2096
  };
2022
2097
 
2098
+ /**
2099
+ * Click event handler
2100
+ *
2101
+ * Handle a click within the $module – if the click occurred on a radio, sync
2102
+ * the state of the conditional reveal for all radio buttons in the same form
2103
+ * with the same name (because checking one radio could have un-checked a radio
2104
+ * in another $module)
2105
+ *
2106
+ * @param {MouseEvent} event Click event
2107
+ */
2023
2108
  Radios.prototype.handleClick = function (event) {
2024
2109
  var $clickedInput = event.target;
2025
- // We only want to handle clicks for radio inputs
2110
+
2111
+ // Ignore clicks on things that aren't radio buttons
2026
2112
  if ($clickedInput.type !== 'radio') {
2027
2113
  return
2028
2114
  }
2029
- // Because checking one radio can uncheck a radio in another $module,
2030
- // we need to call set attributes on all radios in the same form, or document if they're not in a form.
2031
- //
2032
- // We also only want radios which have aria-controls, as they support conditional reveals.
2115
+
2116
+ // We only need to consider radios with conditional reveals, which will have
2117
+ // aria-controls attributes.
2033
2118
  var $allInputs = document.querySelectorAll('input[type="radio"][aria-controls]');
2119
+
2034
2120
  nodeListForEach($allInputs, function ($input) {
2035
- // Only inputs with the same form owner should change.
2036
2121
  var hasSameFormOwner = ($input.form === $clickedInput.form);
2037
-
2038
- // In radios, only radios with the same name will affect each other.
2039
2122
  var hasSameName = ($input.name === $clickedInput.name);
2123
+
2040
2124
  if (hasSameName && hasSameFormOwner) {
2041
- this.setAttributes($input);
2125
+ this.syncConditionalRevealWithInputState($input);
2042
2126
  }
2043
2127
  }.bind(this));
2044
2128
  };
2045
2129
 
2046
2130
  (function(undefined) {
2047
2131
 
2048
- // Detection from https://github.com/Financial-Times/polyfill-service/pull/1062/files#diff-b09a5d2acf3314b46a6c8f8d0c31b85c
2132
+ // Detection from https://raw.githubusercontent.com/Financial-Times/polyfill-library/master/polyfills/Element/prototype/nextElementSibling/detect.js
2049
2133
  var detect = (
2050
- 'Element' in this && "nextElementSibling" in document.documentElement
2134
+ 'document' in this && "nextElementSibling" in document.documentElement
2051
2135
  );
2052
2136
 
2053
2137
  if (detect) return
2054
2138
 
2055
-
2056
- (function (global) {
2057
-
2058
- // Polyfill from https://github.com/Financial-Times/polyfill-service/pull/1062/files#diff-404b69b4750d18dea4174930a49170fd
2059
- Object.defineProperty(Element.prototype, "nextElementSibling", {
2060
- get: function(){
2061
- var el = this.nextSibling;
2062
- while (el && el.nodeType !== 1) { el = el.nextSibling; }
2063
- return (el.nodeType === 1) ? el : null;
2064
- }
2065
- });
2066
-
2067
- }(this));
2139
+ // Polyfill from https://raw.githubusercontent.com/Financial-Times/polyfill-library/master/polyfills/Element/prototype/nextElementSibling/polyfill.js
2140
+ Object.defineProperty(Element.prototype, "nextElementSibling", {
2141
+ get: function(){
2142
+ var el = this.nextSibling;
2143
+ while (el && el.nodeType !== 1) { el = el.nextSibling; }
2144
+ return el;
2145
+ }
2146
+ });
2068
2147
 
2069
2148
  }).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
2070
2149
 
2071
2150
  (function(undefined) {
2072
2151
 
2073
- // Detection from https://github.com/Financial-Times/polyfill-service/pull/1062/files#diff-a162235fbc9c0dd40d4032265f44942e
2152
+ // Detection from https://raw.githubusercontent.com/Financial-Times/polyfill-library/master/polyfills/Element/prototype/previousElementSibling/detect.js
2074
2153
  var detect = (
2075
- 'Element' in this && 'previousElementSibling' in document.documentElement
2154
+ 'document' in this && "previousElementSibling" in document.documentElement
2076
2155
  );
2077
2156
 
2078
2157
  if (detect) return
2079
2158
 
2080
- (function (global) {
2081
- // Polyfill from https://github.com/Financial-Times/polyfill-service/pull/1062/files#diff-b45a1197b842728cb76b624b6ba7d739
2082
- Object.defineProperty(Element.prototype, 'previousElementSibling', {
2083
- get: function(){
2084
- var el = this.previousSibling;
2085
- while (el && el.nodeType !== 1) { el = el.previousSibling; }
2086
- return (el.nodeType === 1) ? el : null;
2087
- }
2088
- });
2089
-
2090
- }(this));
2159
+ // Polyfill from https://raw.githubusercontent.com/Financial-Times/polyfill-library/master/polyfills/Element/prototype/previousElementSibling/polyfill.js
2160
+ Object.defineProperty(Element.prototype, 'previousElementSibling', {
2161
+ get: function(){
2162
+ var el = this.previousSibling;
2163
+ while (el && el.nodeType !== 1) { el = el.previousSibling; }
2164
+ return el;
2165
+ }
2166
+ });
2091
2167
 
2092
2168
  }).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
2093
2169