govuk_publishing_components 21.57.1 → 21.60.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +31 -6
  3. data/app/assets/javascripts/component_guide/accessibility-test.js +21 -21
  4. data/app/assets/javascripts/component_guide/filter-components.js +19 -19
  5. data/app/assets/javascripts/component_guide/visual-regression.js +38 -37
  6. data/app/assets/javascripts/govuk_publishing_components/components/checkboxes.js +2 -2
  7. data/app/assets/javascripts/govuk_publishing_components/components/details.js +6 -4
  8. data/app/assets/javascripts/govuk_publishing_components/components/step-by-step-nav.js +4 -4
  9. data/app/assets/javascripts/govuk_publishing_components/lib/auto-track-event.js +31 -0
  10. data/app/assets/javascripts/govuk_publishing_components/lib/cookie-functions.js +24 -24
  11. data/app/assets/javascripts/govuk_publishing_components/lib/govspeak/youtube-link-enhancement.js +17 -17
  12. data/app/assets/stylesheets/component_guide/application.scss +15 -15
  13. data/app/assets/stylesheets/govuk_publishing_components/components/_action-link.scss +0 -8
  14. data/app/assets/stylesheets/govuk_publishing_components/components/_breadcrumbs.scss +1 -1
  15. data/app/assets/stylesheets/govuk_publishing_components/components/_checkboxes.scss +4 -0
  16. data/app/assets/stylesheets/govuk_publishing_components/components/_cookie-banner.scss +1 -8
  17. data/app/assets/stylesheets/govuk_publishing_components/components/_feedback.scss +0 -1
  18. data/app/assets/stylesheets/govuk_publishing_components/components/_govspeak-html-publication.scss +4 -5
  19. data/app/assets/stylesheets/govuk_publishing_components/components/_heading.scss +3 -8
  20. data/app/assets/stylesheets/govuk_publishing_components/components/_image-card.scss +1 -1
  21. data/app/assets/stylesheets/govuk_publishing_components/components/_input.scss +1 -1
  22. data/app/assets/stylesheets/govuk_publishing_components/components/_inverse-header.scss +5 -8
  23. data/app/assets/stylesheets/govuk_publishing_components/components/_list.scss +1 -0
  24. data/app/assets/stylesheets/govuk_publishing_components/components/_radio.scss +4 -0
  25. data/app/assets/stylesheets/govuk_publishing_components/components/_related-navigation.scss +2 -2
  26. data/app/assets/stylesheets/govuk_publishing_components/components/_search.scss +7 -3
  27. data/app/assets/stylesheets/govuk_publishing_components/components/_step-by-step-nav-header.scss +0 -5
  28. data/app/assets/stylesheets/govuk_publishing_components/components/_step-by-step-nav-related.scss +1 -4
  29. data/app/assets/stylesheets/govuk_publishing_components/components/_step-by-step-nav.scss +8 -12
  30. data/app/assets/stylesheets/govuk_publishing_components/components/_table.scss +21 -24
  31. data/app/assets/stylesheets/govuk_publishing_components/components/_tabs.scss +4 -8
  32. data/app/assets/stylesheets/govuk_publishing_components/components/govspeak/_attachment.scss +2 -0
  33. data/app/assets/stylesheets/govuk_publishing_components/components/govspeak/_button.scss +1 -4
  34. data/app/assets/stylesheets/govuk_publishing_components/components/govspeak/_charts.scss +6 -6
  35. data/app/assets/stylesheets/govuk_publishing_components/components/govspeak/_contact.scss +2 -0
  36. data/app/assets/stylesheets/govuk_publishing_components/components/govspeak/_footnotes.scss +2 -0
  37. data/app/assets/stylesheets/govuk_publishing_components/components/govspeak/_highlight-answer.scss +2 -0
  38. data/app/assets/stylesheets/govuk_publishing_components/components/govspeak/_place.scss +1 -1
  39. data/app/assets/stylesheets/govuk_publishing_components/components/govspeak/_typography.scss +2 -0
  40. data/app/assets/stylesheets/govuk_publishing_components/components/helpers/_markdown-typography.scss +1 -1
  41. data/app/assets/stylesheets/govuk_publishing_components/components/print/_govspeak.scss +2 -0
  42. data/app/assets/stylesheets/govuk_publishing_components/components/print/_step-by-step-nav-header.scss +0 -4
  43. data/app/assets/stylesheets/govuk_publishing_components/components/print/_step-by-step-nav.scss +2 -10
  44. data/app/controllers/govuk_publishing_components/audit_controller.rb +52 -0
  45. data/app/controllers/govuk_publishing_components/component_guide_controller.rb +2 -1
  46. data/app/models/govuk_publishing_components/audit_applications.rb +117 -0
  47. data/app/models/govuk_publishing_components/audit_comparer.rb +198 -0
  48. data/app/models/govuk_publishing_components/audit_components.rb +158 -0
  49. data/app/views/govuk_publishing_components/audit/show.html.erb +258 -0
  50. data/app/views/govuk_publishing_components/component_guide/index.html.erb +9 -4
  51. data/app/views/govuk_publishing_components/components/_breadcrumbs.html.erb +1 -1
  52. data/app/views/govuk_publishing_components/components/_image_card.html.erb +1 -1
  53. data/app/views/govuk_publishing_components/components/_list.html.erb +26 -0
  54. data/app/views/govuk_publishing_components/components/_machine_readable_metadata.html.erb +1 -1
  55. data/app/views/govuk_publishing_components/components/_radio.html.erb +13 -5
  56. data/app/views/govuk_publishing_components/components/_step_by_step_nav_header.html.erb +2 -2
  57. data/app/views/govuk_publishing_components/components/docs/checkboxes.yml +4 -0
  58. data/app/views/govuk_publishing_components/components/docs/govspeak.yml +45 -0
  59. data/app/views/govuk_publishing_components/components/docs/heading.yml +6 -3
  60. data/app/views/govuk_publishing_components/components/docs/image_card.yml +13 -1
  61. data/app/views/govuk_publishing_components/components/docs/list.yml +64 -0
  62. data/app/views/govuk_publishing_components/components/docs/radio.yml +4 -0
  63. data/config/routes.rb +1 -0
  64. data/lib/govuk_publishing_components/presenters/checkboxes_helper.rb +15 -7
  65. data/lib/govuk_publishing_components/presenters/heading_helper.rb +21 -1
  66. data/lib/govuk_publishing_components/presenters/image_card_helper.rb +2 -1
  67. data/lib/govuk_publishing_components/version.rb +1 -1
  68. data/node_modules/axe-core/package.json +145 -220
  69. data/node_modules/govuk-frontend/govuk/all.js +160 -57
  70. data/node_modules/govuk-frontend/govuk/components/character-count/character-count.js +16 -8
  71. data/node_modules/govuk-frontend/govuk/components/character-count/macro-options.json +6 -0
  72. data/node_modules/govuk-frontend/govuk/components/character-count/template.njk +1 -0
  73. data/node_modules/govuk-frontend/govuk/components/checkboxes/_index.scss +6 -5
  74. data/node_modules/govuk-frontend/govuk/components/checkboxes/checkboxes.js +68 -21
  75. data/node_modules/govuk-frontend/govuk/components/date-input/macro-options.json +1 -1
  76. data/node_modules/govuk-frontend/govuk/components/file-upload/_index.scss +0 -27
  77. data/node_modules/govuk-frontend/govuk/components/footer/_index.scss +0 -3
  78. data/node_modules/govuk-frontend/govuk/components/footer/template.njk +1 -1
  79. data/node_modules/govuk-frontend/govuk/components/header/template.njk +1 -1
  80. data/node_modules/govuk-frontend/govuk/components/hint/_index.scss +2 -2
  81. data/node_modules/govuk-frontend/govuk/components/hint/template.njk +2 -2
  82. data/node_modules/govuk-frontend/govuk/components/input/_index.scss +0 -3
  83. data/node_modules/govuk-frontend/govuk/components/input/macro-options.json +6 -0
  84. data/node_modules/govuk-frontend/govuk/components/input/template.njk +1 -0
  85. data/node_modules/govuk-frontend/govuk/components/radios/radios.js +76 -28
  86. data/node_modules/govuk-frontend/govuk/components/select/_index.scss +0 -3
  87. data/node_modules/govuk-frontend/govuk/components/skip-link/_index.scss +1 -0
  88. data/node_modules/govuk-frontend/govuk/components/textarea/_index.scss +0 -3
  89. data/node_modules/govuk-frontend/govuk/components/textarea/macro-options.json +6 -0
  90. data/node_modules/govuk-frontend/govuk/components/textarea/template.njk +1 -0
  91. data/node_modules/govuk-frontend/govuk/components/warning-text/_index.scss +4 -2
  92. data/node_modules/govuk-frontend/govuk/helpers/_colour.scss +1 -1
  93. data/node_modules/govuk-frontend/govuk/settings/_colours-applied.scss +2 -2
  94. data/node_modules/govuk-frontend/govuk/settings/_colours-palette.scss +1 -1
  95. data/node_modules/govuk-frontend/govuk/settings/_measurements.scss +4 -1
  96. data/node_modules/govuk-frontend/package.json +14 -81
  97. data/node_modules/jquery/package.json +44 -116
  98. metadata +46 -66
  99. data/Rakefile +0 -32
@@ -1062,13 +1062,22 @@ CharacterCount.prototype.init = function () {
1062
1062
  // Remove hard limit if set
1063
1063
  $module.removeAttribute('maxlength');
1064
1064
 
1065
- // Bind event changes to the textarea
1066
- var boundChangeEvents = this.bindChangeEvents.bind(this);
1067
- boundChangeEvents();
1065
+ // When the page is restored after navigating 'back' in some browsers the
1066
+ // state of the character count is not restored until *after* the DOMContentLoaded
1067
+ // event is fired, so we need to sync after the pageshow event in browsers
1068
+ // that support it.
1069
+ if ('onpageshow' in window) {
1070
+ window.addEventListener('pageshow', this.sync.bind(this));
1071
+ } else {
1072
+ window.addEventListener('DOMContentLoaded', this.sync.bind(this));
1073
+ }
1074
+
1075
+ this.sync();
1076
+ };
1068
1077
 
1069
- // Update count message
1070
- var boundUpdateCountMessage = this.updateCountMessage.bind(this);
1071
- boundUpdateCountMessage();
1078
+ CharacterCount.prototype.sync = function () {
1079
+ this.bindChangeEvents();
1080
+ this.updateCountMessage();
1072
1081
  };
1073
1082
 
1074
1083
  // Read data attributes
@@ -1116,8 +1125,7 @@ CharacterCount.prototype.checkIfValueChanged = function () {
1116
1125
  if (!this.$textarea.oldValue) this.$textarea.oldValue = '';
1117
1126
  if (this.$textarea.value !== this.$textarea.oldValue) {
1118
1127
  this.$textarea.oldValue = this.$textarea.value;
1119
- var boundUpdateCountMessage = this.updateCountMessage.bind(this);
1120
- boundUpdateCountMessage();
1128
+ this.updateCountMessage();
1121
1129
  }
1122
1130
  };
1123
1131
 
@@ -88,6 +88,12 @@
88
88
  "required": false,
89
89
  "description": "HTML attributes (for example data attributes) to add to the textarea."
90
90
  },
91
+ {
92
+ "name": "spellcheck",
93
+ "type": "boolean",
94
+ "required": false,
95
+ "description": "Optional field to enable or disable the spellcheck attribute on the character count."
96
+ },
91
97
  {
92
98
  "name": "countMessage",
93
99
  "type": "object",
@@ -10,6 +10,7 @@
10
10
  name: params.name,
11
11
  describedBy: params.id + '-info',
12
12
  rows: params.rows,
13
+ spellcheck: params.spellcheck,
13
14
  value: params.value,
14
15
  formGroup: params.formGroup,
15
16
  classes: 'govuk-js-character-count' + (' govuk-textarea--error' if params.errorMessage) + (' ' + params.classes if params.classes),
@@ -92,12 +92,13 @@
92
92
  // rotated 45 degrees
93
93
  .govuk-checkboxes__label::after {
94
94
  content: "";
95
+ box-sizing: border-box;
95
96
 
96
97
  position: absolute;
97
98
  top: 11px;
98
99
  left: 9px;
99
- width: 18px;
100
- height: 7px;
100
+ width: 23px;
101
+ height: 12px;
101
102
 
102
103
  -webkit-transform: rotate(-45deg);
103
104
 
@@ -105,7 +106,7 @@
105
106
 
106
107
  transform: rotate(-45deg);
107
108
  border: solid;
108
- border-width: 0 0 $govuk-border-width $govuk-border-width;
109
+ border-width: 0 0 5px 5px;
109
110
  // Fix bug in IE11 caused by transform rotate (-45deg).
110
111
  // See: alphagov/govuk_elements/issues/518
111
112
  border-top-color: transparent;
@@ -238,8 +239,8 @@
238
239
  .govuk-checkboxes__label::after {
239
240
  top: 15px;
240
241
  left: 6px;
241
- width: 9px;
242
- height: 3.5px;
242
+ width: 12px;
243
+ height: 6.5px;
243
244
  border-width: 0 0 3px 3px;
244
245
  }
245
246
 
@@ -1033,44 +1033,91 @@ function Checkboxes ($module) {
1033
1033
  this.$inputs = $module.querySelectorAll('input[type="checkbox"]');
1034
1034
  }
1035
1035
 
1036
+ /**
1037
+ * Initialise Checkboxes
1038
+ *
1039
+ * Checkboxes can be associated with a 'conditionally revealed' content block –
1040
+ * for example, a checkbox for 'Phone' could reveal an additional form field for
1041
+ * the user to enter their phone number.
1042
+ *
1043
+ * These associations are made using a `data-aria-controls` attribute, which is
1044
+ * promoted to an aria-controls attribute during initialisation.
1045
+ *
1046
+ * We also need to restore the state of any conditional reveals on the page (for
1047
+ * example if the user has navigated back), and set up event handlers to keep
1048
+ * the reveal in sync with the checkbox state.
1049
+ */
1036
1050
  Checkboxes.prototype.init = function () {
1037
1051
  var $module = this.$module;
1038
1052
  var $inputs = this.$inputs;
1039
1053
 
1040
- /**
1041
- * Loop over all items with [data-controls]
1042
- * Check if they have a matching conditional reveal
1043
- * If they do, assign attributes.
1044
- **/
1045
1054
  nodeListForEach($inputs, function ($input) {
1046
- var controls = $input.getAttribute('data-aria-controls');
1055
+ var target = $input.getAttribute('data-aria-controls');
1047
1056
 
1048
- // Check if input controls anything
1049
- // Check if content exists, before setting attributes.
1050
- if (!controls || !$module.querySelector('#' + controls)) {
1057
+ // Skip checkboxes without data-aria-controls attributes, or where the
1058
+ // target element does not exist.
1059
+ if (!target || !$module.querySelector('#' + target)) {
1051
1060
  return
1052
1061
  }
1053
1062
 
1054
- // If we have content that is controlled, set attributes.
1055
- $input.setAttribute('aria-controls', controls);
1063
+ // Promote the data-aria-controls attribute to a aria-controls attribute
1064
+ // so that the relationship is exposed in the AOM
1065
+ $input.setAttribute('aria-controls', target);
1056
1066
  $input.removeAttribute('data-aria-controls');
1057
- this.setAttributes($input);
1058
- }.bind(this));
1067
+ });
1068
+
1069
+ // When the page is restored after navigating 'back' in some browsers the
1070
+ // state of form controls is not restored until *after* the DOMContentLoaded
1071
+ // event is fired, so we need to sync after the pageshow event in browsers
1072
+ // that support it.
1073
+ if ('onpageshow' in window) {
1074
+ window.addEventListener('pageshow', this.syncAllConditionalReveals.bind(this));
1075
+ } else {
1076
+ window.addEventListener('DOMContentLoaded', this.syncAllConditionalReveals.bind(this));
1077
+ }
1078
+
1079
+ // Although we've set up handlers to sync state on the pageshow or
1080
+ // DOMContentLoaded event, init could be called after those events have fired,
1081
+ // for example if they are added to the page dynamically, so sync now too.
1082
+ this.syncAllConditionalReveals();
1059
1083
 
1060
- // Handle events
1061
1084
  $module.addEventListener('click', this.handleClick.bind(this));
1062
1085
  };
1063
1086
 
1064
- Checkboxes.prototype.setAttributes = function ($input) {
1065
- var inputIsChecked = $input.checked;
1066
- $input.setAttribute('aria-expanded', inputIsChecked);
1087
+ /**
1088
+ * Sync the conditional reveal states for all inputs in this $module.
1089
+ */
1090
+ Checkboxes.prototype.syncAllConditionalReveals = function () {
1091
+ nodeListForEach(this.$inputs, this.syncConditionalRevealWithInputState.bind(this));
1092
+ };
1093
+
1094
+ /**
1095
+ * Sync conditional reveal with the input state
1096
+ *
1097
+ * Synchronise the visibility of the conditional reveal, and its accessible
1098
+ * state, with the input's checked state.
1099
+ *
1100
+ * @param {HTMLInputElement} $input Checkbox input
1101
+ */
1102
+ Checkboxes.prototype.syncConditionalRevealWithInputState = function ($input) {
1103
+ var $target = this.$module.querySelector('#' + $input.getAttribute('aria-controls'));
1067
1104
 
1068
- var $content = this.$module.querySelector('#' + $input.getAttribute('aria-controls'));
1069
- if ($content) {
1070
- $content.classList.toggle('govuk-checkboxes__conditional--hidden', !inputIsChecked);
1105
+ if ($target && $target.classList.contains('govuk-checkboxes__conditional')) {
1106
+ var inputIsChecked = $input.checked;
1107
+
1108
+ $input.setAttribute('aria-expanded', inputIsChecked);
1109
+ $target.classList.toggle('govuk-checkboxes__conditional--hidden', !inputIsChecked);
1071
1110
  }
1072
1111
  };
1073
1112
 
1113
+ /**
1114
+ * Click event handler
1115
+ *
1116
+ * Handle a click within the $module – if the click occurred on a checkbox, sync
1117
+ * the state of any associated conditional reveal with the checkbox state.
1118
+ *
1119
+ * @param {MouseEvent} event Click event
1120
+ */
1074
1121
  Checkboxes.prototype.handleClick = function (event) {
1075
1122
  var $target = event.target;
1076
1123
 
@@ -1078,7 +1125,7 @@ Checkboxes.prototype.handleClick = function (event) {
1078
1125
  var isCheckbox = $target.getAttribute('type') === 'checkbox';
1079
1126
  var hasAriaControls = $target.getAttribute('aria-controls');
1080
1127
  if (isCheckbox && hasAriaControls) {
1081
- this.setAttributes($target);
1128
+ this.syncConditionalRevealWithInputState($target);
1082
1129
  }
1083
1130
  };
1084
1131
 
@@ -45,7 +45,7 @@
45
45
  "name": "autocomplete",
46
46
  "type": "string",
47
47
  "required": false,
48
- "description": "Attribute to [identify input purpose](https://www.w3.org/WAI/WCAG21/Understanding/identify-input-purpose.html), for instance \"postal-code\" or \"username\". See [autofill](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofill) for full list of attributes that can be used."
48
+ "description": "Attribute to [identify input purpose](https://www.w3.org/WAI/WCAG21/Understanding/identify-input-purpose.html), for instance \"bday-day\". See [autofill](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofill) for full list of attributes that can be used."
49
49
  },
50
50
  {
51
51
  "name": "pattern",
@@ -47,31 +47,4 @@
47
47
  box-shadow: inset 0 0 0 4px $govuk-input-border-colour;
48
48
  }
49
49
  }
50
-
51
- .govuk-file-upload--error {
52
- // As `upload--error` has border, it needs to have the same padding as
53
- // the standard focused element.
54
- margin-right: -$component-padding;
55
- margin-left: -$component-padding;
56
- padding-right: $component-padding;
57
- padding-left: $component-padding;
58
- border: $govuk-border-width-form-element-error solid $govuk-error-colour;
59
-
60
- &:focus {
61
- border-color: $govuk-input-border-colour;
62
- // Remove `box-shadow` inherited from `:focus` as `file-upload--error`
63
- // already has the thicker border.
64
- box-shadow: none;
65
- }
66
-
67
- // Repeat `:focus` styles to prevent error styles from being applied when
68
- // input button is pressed as this moves the focus to "within".
69
- // This can't be set together with `:focus` as all versions of IE fail
70
- // to recognise `focus-within` and don't set any styles from the block
71
- // when it's a selector.
72
- &:focus-within {
73
- border-color: $govuk-input-border-colour;
74
- box-shadow: none;
75
- }
76
- }
77
50
  }
@@ -210,20 +210,17 @@
210
210
  padding: 0;
211
211
  list-style: none;
212
212
  -webkit-column-gap: $govuk-gutter;
213
- -moz-column-gap: $govuk-gutter;
214
213
  column-gap: $govuk-gutter; // Support: Columns
215
214
  }
216
215
 
217
216
  @include govuk-media-query ($from: desktop) {
218
217
  .govuk-footer__list--columns-2 {
219
218
  -webkit-column-count: 2;
220
- -moz-column-count: 2;
221
219
  column-count: 2; // Support: Columns
222
220
  }
223
221
 
224
222
  .govuk-footer__list--columns-3 {
225
223
  -webkit-column-count: 3;
226
- -moz-column-count: 3;
227
224
  column-count: 3; // Support: Columns
228
225
  }
229
226
  }
@@ -58,7 +58,7 @@
58
58
  focusable="false"
59
59
  class="govuk-footer__licence-logo"
60
60
  xmlns="http://www.w3.org/2000/svg"
61
- viewbox="0 0 483.2 195.7"
61
+ viewBox="0 0 483.2 195.7"
62
62
  height="17"
63
63
  width="41"
64
64
  >
@@ -21,7 +21,7 @@
21
21
  focusable="false"
22
22
  class="govuk-header__logotype-crown"
23
23
  xmlns="http://www.w3.org/2000/svg"
24
- viewbox="0 0 132 97"
24
+ viewBox="0 0 132 97"
25
25
  height="30"
26
26
  width="36"
27
27
  >
@@ -16,7 +16,7 @@
16
16
  // is unlikely that the default or govuk-label--s class would be used in this
17
17
  // case.
18
18
 
19
- // This adjustment will not work in browsers that do not support :not().
19
+ // This adjustment will not work in browsers that do not support :not().
20
20
  // Users with these browsers will see the default size margin (5px larger).
21
21
 
22
22
  .govuk-label:not(.govuk-label--m):not(.govuk-label--l):not(.govuk-label--xl) + .govuk-hint {
@@ -30,7 +30,7 @@
30
30
  // it is unlikely that the default or govuk-fieldset__legend--s class would be
31
31
  // used in this case.
32
32
 
33
- // This adjustment will not work in browsers that do not support :not().
33
+ // This adjustment will not work in browsers that do not support :not().
34
34
  // Users with these browsers will see the default size margin (5px larger).
35
35
 
36
36
  .govuk-fieldset__legend:not(.govuk-fieldset__legend--m):not(.govuk-fieldset__legend--l):not(.govuk-fieldset__legend--xl) + .govuk-hint {
@@ -1,4 +1,4 @@
1
- <span {%- if params.id %} id="{{ params.id }}"{% endif %} class="govuk-hint {%- if params.classes %} {{ params.classes }}{% endif %}"
1
+ <div {%- if params.id %} id="{{ params.id }}"{% endif %} class="govuk-hint {%- if params.classes %} {{ params.classes }}{% endif %}"
2
2
  {%- for attribute, value in params.attributes %} {{attribute}}="{{value}}"{% endfor %}>
3
3
  {{ params.html | safe if params.html else params.text }}
4
- </span>
4
+ </div>
@@ -58,9 +58,6 @@
58
58
 
59
59
  &:focus {
60
60
  border-color: $govuk-input-border-colour;
61
- // Remove `box-shadow` inherited from `:focus` as `input--error`
62
- // already has the thicker border.
63
- box-shadow: none;
64
61
  }
65
62
  }
66
63
 
@@ -88,6 +88,12 @@
88
88
  "required": false,
89
89
  "description": "Attribute to [provide a regular expression pattern](https://www.w3.org/TR/html51/sec-forms.html#the-pattern-attribute), used to match allowed character combinations for the input value."
90
90
  },
91
+ {
92
+ "name": "spellcheck",
93
+ "type": "boolean",
94
+ "required": false,
95
+ "description": "Optional field to enable or disable the spellcheck attribute on the input."
96
+ },
91
97
  {
92
98
  "name": "attributes",
93
99
  "type": "object",
@@ -38,6 +38,7 @@
38
38
  }) | indent(2) | trim }}
39
39
  {% endif %}
40
40
  <input class="govuk-input {%- if params.classes %} {{ params.classes }}{% endif %} {%- if params.errorMessage %} govuk-input--error{% endif %}" id="{{ params.id }}" name="{{ params.name }}" type="{{ params.type | default('text') }}"
41
+ {%- if (params.spellcheck === false) or (params.spellcheck === true) %} spellcheck="{{ params.spellcheck }}"{% endif %}
41
42
  {%- if params.value %} value="{{ params.value}}"{% endif %}
42
43
  {%- if describedBy %} aria-describedby="{{ describedBy }}"{% endif %}
43
44
  {%- if params.autocomplete %} autocomplete="{{ params.autocomplete}}"{% endif %}
@@ -1030,67 +1030,115 @@ function nodeListForEach (nodes, callback) {
1030
1030
 
1031
1031
  function Radios ($module) {
1032
1032
  this.$module = $module;
1033
+ this.$inputs = $module.querySelectorAll('input[type="radio"]');
1033
1034
  }
1034
1035
 
1036
+ /**
1037
+ * Initialise Radios
1038
+ *
1039
+ * Radios can be associated with a 'conditionally revealed' content block – for
1040
+ * example, a radio for 'Phone' could reveal an additional form field for the
1041
+ * user to enter their phone number.
1042
+ *
1043
+ * These associations are made using a `data-aria-controls` attribute, which is
1044
+ * promoted to an aria-controls attribute during initialisation.
1045
+ *
1046
+ * We also need to restore the state of any conditional reveals on the page (for
1047
+ * example if the user has navigated back), and set up event handlers to keep
1048
+ * the reveal in sync with the radio state.
1049
+ */
1035
1050
  Radios.prototype.init = function () {
1036
1051
  var $module = this.$module;
1037
- var $inputs = $module.querySelectorAll('input[type="radio"]');
1052
+ var $inputs = this.$inputs;
1038
1053
 
1039
- /**
1040
- * Loop over all items with [data-controls]
1041
- * Check if they have a matching conditional reveal
1042
- * If they do, assign attributes.
1043
- **/
1044
1054
  nodeListForEach($inputs, function ($input) {
1045
- var controls = $input.getAttribute('data-aria-controls');
1055
+ var target = $input.getAttribute('data-aria-controls');
1046
1056
 
1047
- // Check if input controls anything
1048
- // Check if content exists, before setting attributes.
1049
- if (!controls || !$module.querySelector('#' + controls)) {
1057
+ // Skip radios without data-aria-controls attributes, or where the
1058
+ // target element does not exist.
1059
+ if (!target || !$module.querySelector('#' + target)) {
1050
1060
  return
1051
1061
  }
1052
1062
 
1053
- // If we have content that is controlled, set attributes.
1054
- $input.setAttribute('aria-controls', controls);
1063
+ // Promote the data-aria-controls attribute to a aria-controls attribute
1064
+ // so that the relationship is exposed in the AOM
1065
+ $input.setAttribute('aria-controls', target);
1055
1066
  $input.removeAttribute('data-aria-controls');
1056
- this.setAttributes($input);
1057
- }.bind(this));
1067
+ });
1068
+
1069
+ // When the page is restored after navigating 'back' in some browsers the
1070
+ // state of form controls is not restored until *after* the DOMContentLoaded
1071
+ // event is fired, so we need to sync after the pageshow event in browsers
1072
+ // that support it.
1073
+ if ('onpageshow' in window) {
1074
+ window.addEventListener('pageshow', this.syncAllConditionalReveals.bind(this));
1075
+ } else {
1076
+ window.addEventListener('DOMContentLoaded', this.syncAllConditionalReveals.bind(this));
1077
+ }
1078
+
1079
+ // Although we've set up handlers to sync state on the pageshow or
1080
+ // DOMContentLoaded event, init could be called after those events have fired,
1081
+ // for example if they are added to the page dynamically, so sync now too.
1082
+ this.syncAllConditionalReveals();
1058
1083
 
1059
1084
  // Handle events
1060
1085
  $module.addEventListener('click', this.handleClick.bind(this));
1061
1086
  };
1062
1087
 
1063
- Radios.prototype.setAttributes = function ($input) {
1064
- var $content = document.querySelector('#' + $input.getAttribute('aria-controls'));
1088
+ /**
1089
+ * Sync the conditional reveal states for all inputs in this $module.
1090
+ */
1091
+ Radios.prototype.syncAllConditionalReveals = function () {
1092
+ nodeListForEach(this.$inputs, this.syncConditionalRevealWithInputState.bind(this));
1093
+ };
1065
1094
 
1066
- if ($content && $content.classList.contains('govuk-radios__conditional')) {
1095
+ /**
1096
+ * Sync conditional reveal with the input state
1097
+ *
1098
+ * Synchronise the visibility of the conditional reveal, and its accessible
1099
+ * state, with the input's checked state.
1100
+ *
1101
+ * @param {HTMLInputElement} $input Radio input
1102
+ */
1103
+ Radios.prototype.syncConditionalRevealWithInputState = function ($input) {
1104
+ var $target = document.querySelector('#' + $input.getAttribute('aria-controls'));
1105
+
1106
+ if ($target && $target.classList.contains('govuk-radios__conditional')) {
1067
1107
  var inputIsChecked = $input.checked;
1068
1108
 
1069
1109
  $input.setAttribute('aria-expanded', inputIsChecked);
1070
-
1071
- $content.classList.toggle('govuk-radios__conditional--hidden', !inputIsChecked);
1110
+ $target.classList.toggle('govuk-radios__conditional--hidden', !inputIsChecked);
1072
1111
  }
1073
1112
  };
1074
1113
 
1114
+ /**
1115
+ * Click event handler
1116
+ *
1117
+ * Handle a click within the $module – if the click occurred on a radio, sync
1118
+ * the state of the conditional reveal for all radio buttons in the same form
1119
+ * with the same name (because checking one radio could have un-checked a radio
1120
+ * in another $module)
1121
+ *
1122
+ * @param {MouseEvent} event Click event
1123
+ */
1075
1124
  Radios.prototype.handleClick = function (event) {
1076
1125
  var $clickedInput = event.target;
1077
- // We only want to handle clicks for radio inputs
1126
+
1127
+ // Ignore clicks on things that aren't radio buttons
1078
1128
  if ($clickedInput.type !== 'radio') {
1079
1129
  return
1080
1130
  }
1081
- // Because checking one radio can uncheck a radio in another $module,
1082
- // we need to call set attributes on all radios in the same form, or document if they're not in a form.
1083
- //
1084
- // We also only want radios which have aria-controls, as they support conditional reveals.
1131
+
1132
+ // We only need to consider radios with conditional reveals, which will have
1133
+ // aria-controls attributes.
1085
1134
  var $allInputs = document.querySelectorAll('input[type="radio"][aria-controls]');
1135
+
1086
1136
  nodeListForEach($allInputs, function ($input) {
1087
- // Only inputs with the same form owner should change.
1088
1137
  var hasSameFormOwner = ($input.form === $clickedInput.form);
1089
-
1090
- // In radios, only radios with the same name will affect each other.
1091
1138
  var hasSameName = ($input.name === $clickedInput.name);
1139
+
1092
1140
  if (hasSameName && hasSameFormOwner) {
1093
- this.setAttributes($input);
1141
+ this.syncConditionalRevealWithInputState($input);
1094
1142
  }
1095
1143
  }.bind(this));
1096
1144
  };