govuk_publishing_components 29.9.0 → 29.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (160) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/govuk_publishing_components/analytics/page-content.js +4 -4
  3. data/app/assets/stylesheets/govuk_publishing_components/components/_contextual-sidebar.scss +20 -0
  4. data/app/assets/stylesheets/govuk_publishing_components/components/_layout-super-navigation-header.scss +3 -8
  5. data/app/assets/stylesheets/govuk_publishing_components/components/govspeak/_attachment.scss +7 -1
  6. data/app/controllers/govuk_publishing_components/audit_controller.rb +3 -2
  7. data/app/controllers/govuk_publishing_components/component_guide_controller.rb +0 -9
  8. data/app/models/govuk_publishing_components/audit_comparer.rb +92 -34
  9. data/app/views/govuk_publishing_components/audit/_applications.html.erb +20 -9
  10. data/app/views/govuk_publishing_components/component_guide/index.html.erb +1 -19
  11. data/app/views/govuk_publishing_components/components/_layout_footer.html.erb +20 -2
  12. data/app/views/govuk_publishing_components/components/contextual_sidebar/_ukraine_cta.html.erb +18 -19
  13. data/config/locales/ar.yml +1 -2
  14. data/config/locales/az.yml +1 -2
  15. data/config/locales/be.yml +1 -2
  16. data/config/locales/bg.yml +1 -2
  17. data/config/locales/bn.yml +1 -2
  18. data/config/locales/cs.yml +1 -2
  19. data/config/locales/cy.yml +1 -2
  20. data/config/locales/da.yml +1 -2
  21. data/config/locales/de.yml +1 -2
  22. data/config/locales/dr.yml +1 -2
  23. data/config/locales/el.yml +1 -2
  24. data/config/locales/en.yml +9 -2
  25. data/config/locales/es-419.yml +1 -2
  26. data/config/locales/es.yml +1 -2
  27. data/config/locales/et.yml +1 -2
  28. data/config/locales/fa.yml +1 -2
  29. data/config/locales/fi.yml +1 -2
  30. data/config/locales/fr.yml +1 -2
  31. data/config/locales/gd.yml +1 -2
  32. data/config/locales/gu.yml +1 -2
  33. data/config/locales/he.yml +1 -2
  34. data/config/locales/hi.yml +1 -2
  35. data/config/locales/hr.yml +1 -2
  36. data/config/locales/hu.yml +1 -2
  37. data/config/locales/hy.yml +1 -2
  38. data/config/locales/id.yml +1 -2
  39. data/config/locales/is.yml +1 -2
  40. data/config/locales/it.yml +1 -2
  41. data/config/locales/ja.yml +1 -2
  42. data/config/locales/ka.yml +1 -2
  43. data/config/locales/kk.yml +1 -2
  44. data/config/locales/ko.yml +1 -2
  45. data/config/locales/lt.yml +1 -2
  46. data/config/locales/lv.yml +1 -2
  47. data/config/locales/ms.yml +1 -2
  48. data/config/locales/mt.yml +1 -2
  49. data/config/locales/nl.yml +1 -2
  50. data/config/locales/no.yml +1 -2
  51. data/config/locales/pa-pk.yml +1 -2
  52. data/config/locales/pa.yml +1 -2
  53. data/config/locales/pl.yml +1 -2
  54. data/config/locales/ps.yml +1 -2
  55. data/config/locales/pt.yml +1 -2
  56. data/config/locales/ro.yml +1 -2
  57. data/config/locales/ru.yml +1 -2
  58. data/config/locales/si.yml +1 -2
  59. data/config/locales/sk.yml +1 -2
  60. data/config/locales/sl.yml +1 -2
  61. data/config/locales/so.yml +1 -2
  62. data/config/locales/sq.yml +1 -2
  63. data/config/locales/sr.yml +1 -2
  64. data/config/locales/sv.yml +1 -2
  65. data/config/locales/sw.yml +1 -2
  66. data/config/locales/ta.yml +1 -2
  67. data/config/locales/th.yml +1 -2
  68. data/config/locales/tk.yml +1 -2
  69. data/config/locales/tr.yml +1 -2
  70. data/config/locales/uk.yml +1 -2
  71. data/config/locales/ur.yml +1 -2
  72. data/config/locales/uz.yml +1 -2
  73. data/config/locales/vi.yml +1 -2
  74. data/config/locales/zh-hk.yml +1 -2
  75. data/config/locales/zh-tw.yml +1 -2
  76. data/config/locales/zh.yml +1 -2
  77. data/lib/govuk_publishing_components/presenters/attachment_helper.rb +1 -2
  78. data/lib/govuk_publishing_components/presenters/public_layout_helper.rb +35 -16
  79. data/lib/govuk_publishing_components/version.rb +1 -1
  80. data/node_modules/govuk-frontend/govuk/all.js +120 -49
  81. data/node_modules/govuk-frontend/govuk/components/back-link/macro-options.json +2 -2
  82. data/node_modules/govuk-frontend/govuk/components/breadcrumbs/_index.scss +0 -2
  83. data/node_modules/govuk-frontend/govuk/components/breadcrumbs/macro-options.json +2 -2
  84. data/node_modules/govuk-frontend/govuk/components/button/_index.scss +6 -16
  85. data/node_modules/govuk-frontend/govuk/components/button/macro-options.json +2 -2
  86. data/node_modules/govuk-frontend/govuk/components/character-count/character-count.js +120 -49
  87. data/node_modules/govuk-frontend/govuk/components/character-count/fixtures.json +33 -17
  88. data/node_modules/govuk-frontend/govuk/components/character-count/macro-options.json +2 -2
  89. data/node_modules/govuk-frontend/govuk/components/character-count/template.njk +1 -4
  90. data/node_modules/govuk-frontend/govuk/components/checkboxes/_index.scss +3 -2
  91. data/node_modules/govuk-frontend/govuk/components/checkboxes/fixtures.json +22 -10
  92. data/node_modules/govuk-frontend/govuk/components/checkboxes/macro-options.json +2 -2
  93. data/node_modules/govuk-frontend/govuk/components/date-input/fixtures.json +23 -23
  94. data/node_modules/govuk-frontend/govuk/components/date-input/template.njk +1 -1
  95. data/node_modules/govuk-frontend/govuk/components/details/macro-options.json +4 -4
  96. data/node_modules/govuk-frontend/govuk/components/error-message/macro-options.json +2 -2
  97. data/node_modules/govuk-frontend/govuk/components/error-summary/macro-options.json +3 -3
  98. data/node_modules/govuk-frontend/govuk/components/fieldset/macro-options.json +2 -2
  99. data/node_modules/govuk-frontend/govuk/components/footer/_index.scss +12 -22
  100. data/node_modules/govuk-frontend/govuk/components/header/_index.scss +13 -3
  101. data/node_modules/govuk-frontend/govuk/components/header/macro-options.json +2 -2
  102. data/node_modules/govuk-frontend/govuk/components/hint/macro-options.json +2 -2
  103. data/node_modules/govuk-frontend/govuk/components/input/_index.scss +4 -13
  104. data/node_modules/govuk-frontend/govuk/components/input/macro-options.json +5 -5
  105. data/node_modules/govuk-frontend/govuk/components/inset-text/macro-options.json +2 -2
  106. data/node_modules/govuk-frontend/govuk/components/label/macro-options.json +2 -2
  107. data/node_modules/govuk-frontend/govuk/components/panel/_index.scss +1 -1
  108. data/node_modules/govuk-frontend/govuk/components/panel/macro-options.json +4 -4
  109. data/node_modules/govuk-frontend/govuk/components/phase-banner/macro-options.json +2 -2
  110. data/node_modules/govuk-frontend/govuk/components/radios/_index.scss +5 -4
  111. data/node_modules/govuk-frontend/govuk/components/radios/fixtures.json +17 -12
  112. data/node_modules/govuk-frontend/govuk/components/radios/macro-options.json +2 -2
  113. data/node_modules/govuk-frontend/govuk/components/skip-link/_index.scss +1 -3
  114. data/node_modules/govuk-frontend/govuk/components/skip-link/macro-options.json +2 -2
  115. data/node_modules/govuk-frontend/govuk/components/summary-list/macro-options.json +5 -5
  116. data/node_modules/govuk-frontend/govuk/components/table/macro-options.json +4 -4
  117. data/node_modules/govuk-frontend/govuk/components/tabs/macro-options.json +2 -2
  118. data/node_modules/govuk-frontend/govuk/components/tag/macro-options.json +2 -2
  119. data/node_modules/govuk-frontend/govuk/components/warning-text/macro-options.json +2 -2
  120. data/node_modules/govuk-frontend/govuk/helpers/_colour.scss +3 -3
  121. data/node_modules/govuk-frontend/govuk/helpers/_links.scss +7 -5
  122. data/node_modules/govuk-frontend/govuk/helpers/_media-queries.scss +2 -2
  123. data/node_modules/govuk-frontend/govuk/helpers/_shape-arrow.scss +1 -1
  124. data/node_modules/govuk-frontend/govuk/helpers/_spacing.scss +3 -3
  125. data/node_modules/govuk-frontend/govuk/helpers/_typography.scss +2 -2
  126. data/node_modules/govuk-frontend/govuk/objects/_button-group.scss +10 -26
  127. data/node_modules/govuk-frontend/govuk/objects/_template.scss +1 -1
  128. data/node_modules/govuk-frontend/govuk/objects/_width-container.scss +0 -4
  129. data/node_modules/govuk-frontend/govuk/tools/_exports.scss +1 -1
  130. data/node_modules/govuk-frontend/govuk/tools/_font-url.scss +1 -1
  131. data/node_modules/govuk-frontend/govuk/tools/_image-url.scss +1 -1
  132. data/node_modules/govuk-frontend/govuk/tools/_px-to-em.scss +2 -2
  133. data/node_modules/govuk-frontend/govuk/tools/_px-to-rem.scss +1 -1
  134. data/node_modules/govuk-frontend/govuk-esm/all.mjs +88 -0
  135. data/node_modules/govuk-frontend/govuk-esm/common.mjs +28 -0
  136. data/node_modules/govuk-frontend/govuk-esm/components/accordion/accordion.mjs +374 -0
  137. data/node_modules/govuk-frontend/govuk-esm/components/button/button.mjs +64 -0
  138. data/node_modules/govuk-frontend/govuk-esm/components/character-count/character-count.mjs +251 -0
  139. data/node_modules/govuk-frontend/govuk-esm/components/checkboxes/checkboxes.mjs +164 -0
  140. data/node_modules/govuk-frontend/govuk-esm/components/details/details.mjs +147 -0
  141. data/node_modules/govuk-frontend/govuk-esm/components/error-summary/error-summary.mjs +168 -0
  142. data/node_modules/govuk-frontend/govuk-esm/components/header/header.mjs +52 -0
  143. data/node_modules/govuk-frontend/govuk-esm/components/notification-banner/notification-banner.mjs +55 -0
  144. data/node_modules/govuk-frontend/govuk-esm/components/radios/radios.mjs +122 -0
  145. data/node_modules/govuk-frontend/govuk-esm/components/skip-link/skip-link.mjs +94 -0
  146. data/node_modules/govuk-frontend/govuk-esm/components/tabs/tabs.mjs +282 -0
  147. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/DOMTokenList.js +264 -0
  148. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Document.js +26 -0
  149. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element/prototype/classList.js +93 -0
  150. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element/prototype/closest.js +24 -0
  151. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element/prototype/matches.js +23 -0
  152. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element/prototype/nextElementSibling.js +22 -0
  153. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element/prototype/previousElementSibling.js +22 -0
  154. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element.js +114 -0
  155. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Event.js +252 -0
  156. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Function/prototype/bind.js +159 -0
  157. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Object/defineProperty.js +86 -0
  158. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Window.js +20 -0
  159. data/node_modules/govuk-frontend/package.json +8 -1
  160. metadata +27 -2
@@ -0,0 +1,64 @@
1
+ import '../../vendor/polyfills/Event' // addEventListener and event.target normaliziation
2
+ import '../../vendor/polyfills/Function/prototype/bind'
3
+
4
+ var KEY_SPACE = 32
5
+ var DEBOUNCE_TIMEOUT_IN_SECONDS = 1
6
+
7
+ function Button ($module) {
8
+ this.$module = $module
9
+ this.debounceFormSubmitTimer = null
10
+ }
11
+
12
+ /**
13
+ * JavaScript 'shim' to trigger the click event of element(s) when the space key is pressed.
14
+ *
15
+ * Created since some Assistive Technologies (for example some Screenreaders)
16
+ * will tell a user to press space on a 'button', so this functionality needs to be shimmed
17
+ * See https://github.com/alphagov/govuk_elements/pull/272#issuecomment-233028270
18
+ *
19
+ * @param {object} event event
20
+ */
21
+ Button.prototype.handleKeyDown = function (event) {
22
+ // get the target element
23
+ var target = event.target
24
+ // if the element has a role='button' and the pressed key is a space, we'll simulate a click
25
+ if (target.getAttribute('role') === 'button' && event.keyCode === KEY_SPACE) {
26
+ event.preventDefault()
27
+ // trigger the target's click event
28
+ target.click()
29
+ }
30
+ }
31
+
32
+ /**
33
+ * If the click quickly succeeds a previous click then nothing will happen.
34
+ * This stops people accidentally causing multiple form submissions by
35
+ * double clicking buttons.
36
+ */
37
+ Button.prototype.debounce = function (event) {
38
+ var target = event.target
39
+ // Check the button that is clicked on has the preventDoubleClick feature enabled
40
+ if (target.getAttribute('data-prevent-double-click') !== 'true') {
41
+ return
42
+ }
43
+
44
+ // If the timer is still running then we want to prevent the click from submitting the form
45
+ if (this.debounceFormSubmitTimer) {
46
+ event.preventDefault()
47
+ return false
48
+ }
49
+
50
+ this.debounceFormSubmitTimer = setTimeout(function () {
51
+ this.debounceFormSubmitTimer = null
52
+ }.bind(this), DEBOUNCE_TIMEOUT_IN_SECONDS * 1000)
53
+ }
54
+
55
+ /**
56
+ * Initialise an event listener for keydown at document level
57
+ * this will help listening for later inserted elements with a role="button"
58
+ */
59
+ Button.prototype.init = function () {
60
+ this.$module.addEventListener('keydown', this.handleKeyDown)
61
+ this.$module.addEventListener('click', this.debounce)
62
+ }
63
+
64
+ export default Button
@@ -0,0 +1,251 @@
1
+ import '../../vendor/polyfills/Function/prototype/bind'
2
+ import '../../vendor/polyfills/Event' // addEventListener and event.target normaliziation
3
+ import '../../vendor/polyfills/Element/prototype/classList'
4
+
5
+ function CharacterCount ($module) {
6
+ this.$module = $module
7
+ this.$textarea = $module.querySelector('.govuk-js-character-count')
8
+ this.$visibleCountMessage = null
9
+ this.$screenReaderCountMessage = null
10
+ this.lastInputTimestamp = null
11
+ }
12
+
13
+ CharacterCount.prototype.defaults = {
14
+ characterCountAttribute: 'data-maxlength',
15
+ wordCountAttribute: 'data-maxwords'
16
+ }
17
+
18
+ // Initialize component
19
+ CharacterCount.prototype.init = function () {
20
+ // Check that required elements are present
21
+ if (!this.$textarea) {
22
+ return
23
+ }
24
+
25
+ // Check for module
26
+ var $module = this.$module
27
+ var $textarea = this.$textarea
28
+ var $fallbackLimitMessage = document.getElementById($textarea.id + '-info')
29
+
30
+ // Move the fallback count message to be immediately after the textarea
31
+ // Kept for backwards compatibility
32
+ $textarea.insertAdjacentElement('afterend', $fallbackLimitMessage)
33
+
34
+ // Create the *screen reader* specific live-updating counter
35
+ // This doesn't need any styling classes, as it is never visible
36
+ var $screenReaderCountMessage = document.createElement('div')
37
+ $screenReaderCountMessage.className = 'govuk-character-count__sr-status govuk-visually-hidden'
38
+ $screenReaderCountMessage.setAttribute('aria-live', 'polite')
39
+ this.$screenReaderCountMessage = $screenReaderCountMessage
40
+ $fallbackLimitMessage.insertAdjacentElement('afterend', $screenReaderCountMessage)
41
+
42
+ // Create our live-updating counter element, copying the classes from the
43
+ // fallback element for backwards compatibility as these may have been configured
44
+ var $visibleCountMessage = document.createElement('div')
45
+ $visibleCountMessage.className = $fallbackLimitMessage.className
46
+ $visibleCountMessage.classList.add('govuk-character-count__status')
47
+ $visibleCountMessage.setAttribute('aria-hidden', 'true')
48
+ this.$visibleCountMessage = $visibleCountMessage
49
+ $fallbackLimitMessage.insertAdjacentElement('afterend', $visibleCountMessage)
50
+
51
+ // Hide the fallback limit message
52
+ $fallbackLimitMessage.classList.add('govuk-visually-hidden')
53
+
54
+ // Read options set using dataset ('data-' values)
55
+ this.options = this.getDataset($module)
56
+
57
+ // Determine the limit attribute (characters or words)
58
+ var countAttribute = this.defaults.characterCountAttribute
59
+ if (this.options.maxwords) {
60
+ countAttribute = this.defaults.wordCountAttribute
61
+ }
62
+
63
+ // Save the element limit
64
+ this.maxLength = $module.getAttribute(countAttribute)
65
+
66
+ // Check for limit
67
+ if (!this.maxLength) {
68
+ return
69
+ }
70
+
71
+ // Remove hard limit if set
72
+ $textarea.removeAttribute('maxlength')
73
+
74
+ this.bindChangeEvents()
75
+
76
+ // When the page is restored after navigating 'back' in some browsers the
77
+ // state of the character count is not restored until *after* the DOMContentLoaded
78
+ // event is fired, so we need to manually update it after the pageshow event
79
+ // in browsers that support it.
80
+ if ('onpageshow' in window) {
81
+ window.addEventListener('pageshow', this.updateCountMessage.bind(this))
82
+ } else {
83
+ window.addEventListener('DOMContentLoaded', this.updateCountMessage.bind(this))
84
+ }
85
+ this.updateCountMessage()
86
+ }
87
+
88
+ // Read data attributes
89
+ CharacterCount.prototype.getDataset = function (element) {
90
+ var dataset = {}
91
+ var attributes = element.attributes
92
+ if (attributes) {
93
+ for (var i = 0; i < attributes.length; i++) {
94
+ var attribute = attributes[i]
95
+ var match = attribute.name.match(/^data-(.+)/)
96
+ if (match) {
97
+ dataset[match[1]] = attribute.value
98
+ }
99
+ }
100
+ }
101
+ return dataset
102
+ }
103
+
104
+ // Counts characters or words in text
105
+ CharacterCount.prototype.count = function (text) {
106
+ var length
107
+ if (this.options.maxwords) {
108
+ var tokens = text.match(/\S+/g) || [] // Matches consecutive non-whitespace chars
109
+ length = tokens.length
110
+ } else {
111
+ length = text.length
112
+ }
113
+ return length
114
+ }
115
+
116
+ // Bind input propertychange to the elements and update based on the change
117
+ CharacterCount.prototype.bindChangeEvents = function () {
118
+ var $textarea = this.$textarea
119
+ $textarea.addEventListener('keyup', this.handleKeyUp.bind(this))
120
+
121
+ // Bind focus/blur events to start/stop polling
122
+ $textarea.addEventListener('focus', this.handleFocus.bind(this))
123
+ $textarea.addEventListener('blur', this.handleBlur.bind(this))
124
+ }
125
+
126
+ // Speech recognition software such as Dragon NaturallySpeaking will modify the
127
+ // fields by directly changing its `value`. These changes don't trigger events
128
+ // in JavaScript, so we need to poll to handle when and if they occur.
129
+ CharacterCount.prototype.checkIfValueChanged = function () {
130
+ if (!this.$textarea.oldValue) this.$textarea.oldValue = ''
131
+ if (this.$textarea.value !== this.$textarea.oldValue) {
132
+ this.$textarea.oldValue = this.$textarea.value
133
+ this.updateCountMessage()
134
+ }
135
+ }
136
+
137
+ // Helper function to update both the visible and screen reader-specific
138
+ // counters simultaneously (e.g. on init)
139
+ CharacterCount.prototype.updateCountMessage = function () {
140
+ this.updateVisibleCountMessage()
141
+ this.updateScreenReaderCountMessage()
142
+ }
143
+
144
+ // Update visible counter
145
+ CharacterCount.prototype.updateVisibleCountMessage = function () {
146
+ var $textarea = this.$textarea
147
+ var $visibleCountMessage = this.$visibleCountMessage
148
+ var remainingNumber = this.maxLength - this.count($textarea.value)
149
+
150
+ // If input is over the threshold, remove the disabled class which renders the
151
+ // counter invisible.
152
+ if (this.isOverThreshold()) {
153
+ $visibleCountMessage.classList.remove('govuk-character-count__message--disabled')
154
+ } else {
155
+ $visibleCountMessage.classList.add('govuk-character-count__message--disabled')
156
+ }
157
+
158
+ // Update styles
159
+ if (remainingNumber < 0) {
160
+ $textarea.classList.add('govuk-textarea--error')
161
+ $visibleCountMessage.classList.remove('govuk-hint')
162
+ $visibleCountMessage.classList.add('govuk-error-message')
163
+ } else {
164
+ $textarea.classList.remove('govuk-textarea--error')
165
+ $visibleCountMessage.classList.remove('govuk-error-message')
166
+ $visibleCountMessage.classList.add('govuk-hint')
167
+ }
168
+
169
+ // Update message
170
+ $visibleCountMessage.innerHTML = this.formattedUpdateMessage()
171
+ }
172
+
173
+ // Update screen reader-specific counter
174
+ CharacterCount.prototype.updateScreenReaderCountMessage = function () {
175
+ var $screenReaderCountMessage = this.$screenReaderCountMessage
176
+
177
+ // If over the threshold, remove the aria-hidden attribute, allowing screen
178
+ // readers to announce the content of the element.
179
+ if (this.isOverThreshold()) {
180
+ $screenReaderCountMessage.removeAttribute('aria-hidden')
181
+ } else {
182
+ $screenReaderCountMessage.setAttribute('aria-hidden', true)
183
+ }
184
+
185
+ // Update message
186
+ $screenReaderCountMessage.innerHTML = this.formattedUpdateMessage()
187
+ }
188
+
189
+ // Format update message
190
+ CharacterCount.prototype.formattedUpdateMessage = function () {
191
+ var $textarea = this.$textarea
192
+ var options = this.options
193
+ var remainingNumber = this.maxLength - this.count($textarea.value)
194
+
195
+ var charVerb = 'remaining'
196
+ var charNoun = 'character'
197
+ var displayNumber = remainingNumber
198
+ if (options.maxwords) {
199
+ charNoun = 'word'
200
+ }
201
+ charNoun = charNoun + ((remainingNumber === -1 || remainingNumber === 1) ? '' : 's')
202
+
203
+ charVerb = (remainingNumber < 0) ? 'too many' : 'remaining'
204
+ displayNumber = Math.abs(remainingNumber)
205
+
206
+ return 'You have ' + displayNumber + ' ' + charNoun + ' ' + charVerb
207
+ }
208
+
209
+ // Checks whether the value is over the configured threshold for the input.
210
+ // If there is no configured threshold, it is set to 0 and this function will
211
+ // always return true.
212
+ CharacterCount.prototype.isOverThreshold = function () {
213
+ var $textarea = this.$textarea
214
+ var options = this.options
215
+
216
+ // Determine the remaining number of characters/words
217
+ var currentLength = this.count($textarea.value)
218
+ var maxLength = this.maxLength
219
+
220
+ // Set threshold if presented in options
221
+ var thresholdPercent = options.threshold ? options.threshold : 0
222
+ var thresholdValue = maxLength * thresholdPercent / 100
223
+
224
+ return (thresholdValue <= currentLength)
225
+ }
226
+
227
+ // Update the visible character counter and keep track of when the last update
228
+ // happened for each keypress
229
+ CharacterCount.prototype.handleKeyUp = function () {
230
+ this.updateVisibleCountMessage()
231
+ this.lastInputTimestamp = Date.now()
232
+ }
233
+
234
+ CharacterCount.prototype.handleFocus = function () {
235
+ // If the field is focused, and a keyup event hasn't been detected for at
236
+ // least 1000 ms (1 second), then run the manual change check.
237
+ // This is so that the update triggered by the manual comparison doesn't
238
+ // conflict with debounced KeyboardEvent updates.
239
+ this.valueChecker = setInterval(function () {
240
+ if (!this.lastInputTimestamp || (Date.now() - 500) >= this.lastInputTimestamp) {
241
+ this.checkIfValueChanged()
242
+ }
243
+ }.bind(this), 1000)
244
+ }
245
+
246
+ CharacterCount.prototype.handleBlur = function () {
247
+ // Cancel value checking on blur
248
+ clearInterval(this.valueChecker)
249
+ }
250
+
251
+ export default CharacterCount
@@ -0,0 +1,164 @@
1
+ import '../../vendor/polyfills/Function/prototype/bind'
2
+ // addEventListener, event.target normalization and DOMContentLoaded
3
+ import '../../vendor/polyfills/Event'
4
+ import '../../vendor/polyfills/Element/prototype/classList'
5
+ import { nodeListForEach } from '../../common'
6
+
7
+ function Checkboxes ($module) {
8
+ this.$module = $module
9
+ this.$inputs = $module.querySelectorAll('input[type="checkbox"]')
10
+ }
11
+
12
+ /**
13
+ * Initialise Checkboxes
14
+ *
15
+ * Checkboxes can be associated with a 'conditionally revealed' content block –
16
+ * for example, a checkbox for 'Phone' could reveal an additional form field for
17
+ * the user to enter their phone number.
18
+ *
19
+ * These associations are made using a `data-aria-controls` attribute, which is
20
+ * promoted to an aria-controls attribute during initialisation.
21
+ *
22
+ * We also need to restore the state of any conditional reveals on the page (for
23
+ * example if the user has navigated back), and set up event handlers to keep
24
+ * the reveal in sync with the checkbox state.
25
+ */
26
+ Checkboxes.prototype.init = function () {
27
+ var $module = this.$module
28
+ var $inputs = this.$inputs
29
+
30
+ nodeListForEach($inputs, function ($input) {
31
+ var target = $input.getAttribute('data-aria-controls')
32
+
33
+ // Skip checkboxes without data-aria-controls attributes, or where the
34
+ // target element does not exist.
35
+ if (!target || !document.getElementById(target)) {
36
+ return
37
+ }
38
+
39
+ // Promote the data-aria-controls attribute to a aria-controls attribute
40
+ // so that the relationship is exposed in the AOM
41
+ $input.setAttribute('aria-controls', target)
42
+ $input.removeAttribute('data-aria-controls')
43
+ })
44
+
45
+ // When the page is restored after navigating 'back' in some browsers the
46
+ // state of form controls is not restored until *after* the DOMContentLoaded
47
+ // event is fired, so we need to sync after the pageshow event in browsers
48
+ // that support it.
49
+ if ('onpageshow' in window) {
50
+ window.addEventListener('pageshow', this.syncAllConditionalReveals.bind(this))
51
+ } else {
52
+ window.addEventListener('DOMContentLoaded', this.syncAllConditionalReveals.bind(this))
53
+ }
54
+
55
+ // Although we've set up handlers to sync state on the pageshow or
56
+ // DOMContentLoaded event, init could be called after those events have fired,
57
+ // for example if they are added to the page dynamically, so sync now too.
58
+ this.syncAllConditionalReveals()
59
+
60
+ $module.addEventListener('click', this.handleClick.bind(this))
61
+ }
62
+
63
+ /**
64
+ * Sync the conditional reveal states for all inputs in this $module.
65
+ */
66
+ Checkboxes.prototype.syncAllConditionalReveals = function () {
67
+ nodeListForEach(this.$inputs, this.syncConditionalRevealWithInputState.bind(this))
68
+ }
69
+
70
+ /**
71
+ * Sync conditional reveal with the input state
72
+ *
73
+ * Synchronise the visibility of the conditional reveal, and its accessible
74
+ * state, with the input's checked state.
75
+ *
76
+ * @param {HTMLInputElement} $input Checkbox input
77
+ */
78
+ Checkboxes.prototype.syncConditionalRevealWithInputState = function ($input) {
79
+ var $target = document.getElementById($input.getAttribute('aria-controls'))
80
+
81
+ if ($target && $target.classList.contains('govuk-checkboxes__conditional')) {
82
+ var inputIsChecked = $input.checked
83
+
84
+ $input.setAttribute('aria-expanded', inputIsChecked)
85
+ $target.classList.toggle('govuk-checkboxes__conditional--hidden', !inputIsChecked)
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Uncheck other checkboxes
91
+ *
92
+ * Find any other checkbox inputs with the same name value, and uncheck them.
93
+ * This is useful for when a “None of these" checkbox is checked.
94
+ */
95
+ Checkboxes.prototype.unCheckAllInputsExcept = function ($input) {
96
+ var allInputsWithSameName = document.querySelectorAll('input[type="checkbox"][name="' + $input.name + '"]')
97
+
98
+ nodeListForEach(allInputsWithSameName, function ($inputWithSameName) {
99
+ var hasSameFormOwner = ($input.form === $inputWithSameName.form)
100
+ if (hasSameFormOwner && $inputWithSameName !== $input) {
101
+ $inputWithSameName.checked = false
102
+ this.syncConditionalRevealWithInputState($inputWithSameName)
103
+ }
104
+ }.bind(this))
105
+ }
106
+
107
+ /**
108
+ * Uncheck exclusive inputs
109
+ *
110
+ * Find any checkbox inputs with the same name value and the 'exclusive' behaviour,
111
+ * and uncheck them. This helps prevent someone checking both a regular checkbox and a
112
+ * "None of these" checkbox in the same fieldset.
113
+ */
114
+ Checkboxes.prototype.unCheckExclusiveInputs = function ($input) {
115
+ var allInputsWithSameNameAndExclusiveBehaviour = document.querySelectorAll(
116
+ 'input[data-behaviour="exclusive"][type="checkbox"][name="' + $input.name + '"]'
117
+ )
118
+
119
+ nodeListForEach(allInputsWithSameNameAndExclusiveBehaviour, function ($exclusiveInput) {
120
+ var hasSameFormOwner = ($input.form === $exclusiveInput.form)
121
+ if (hasSameFormOwner) {
122
+ $exclusiveInput.checked = false
123
+ this.syncConditionalRevealWithInputState($exclusiveInput)
124
+ }
125
+ }.bind(this))
126
+ }
127
+
128
+ /**
129
+ * Click event handler
130
+ *
131
+ * Handle a click within the $module – if the click occurred on a checkbox, sync
132
+ * the state of any associated conditional reveal with the checkbox state.
133
+ *
134
+ * @param {MouseEvent} event Click event
135
+ */
136
+ Checkboxes.prototype.handleClick = function (event) {
137
+ var $target = event.target
138
+
139
+ // Ignore clicks on things that aren't checkbox inputs
140
+ if ($target.type !== 'checkbox') {
141
+ return
142
+ }
143
+
144
+ // If the checkbox conditionally-reveals some content, sync the state
145
+ var hasAriaControls = $target.getAttribute('aria-controls')
146
+ if (hasAriaControls) {
147
+ this.syncConditionalRevealWithInputState($target)
148
+ }
149
+
150
+ // No further behaviour needed for unchecking
151
+ if (!$target.checked) {
152
+ return
153
+ }
154
+
155
+ // Handle 'exclusive' checkbox behaviour (ie "None of these")
156
+ var hasBehaviourExclusive = ($target.getAttribute('data-behaviour') === 'exclusive')
157
+ if (hasBehaviourExclusive) {
158
+ this.unCheckAllInputsExcept($target)
159
+ } else {
160
+ this.unCheckExclusiveInputs($target)
161
+ }
162
+ }
163
+
164
+ export default Checkboxes
@@ -0,0 +1,147 @@
1
+ /**
2
+ * JavaScript 'polyfill' for HTML5's <details> and <summary> elements
3
+ * and 'shim' to add accessiblity enhancements for all browsers
4
+ *
5
+ * http://caniuse.com/#feat=details
6
+ */
7
+ import '../../vendor/polyfills/Function/prototype/bind'
8
+ import '../../vendor/polyfills/Event' // addEventListener and event.target normaliziation
9
+ import { generateUniqueID } from '../../common'
10
+
11
+ var KEY_ENTER = 13
12
+ var KEY_SPACE = 32
13
+
14
+ function Details ($module) {
15
+ this.$module = $module
16
+ }
17
+
18
+ Details.prototype.init = function () {
19
+ if (!this.$module) {
20
+ return
21
+ }
22
+
23
+ // If there is native details support, we want to avoid running code to polyfill native behaviour.
24
+ var hasNativeDetails = typeof this.$module.open === 'boolean'
25
+
26
+ if (hasNativeDetails) {
27
+ return
28
+ }
29
+
30
+ this.polyfillDetails()
31
+ }
32
+
33
+ Details.prototype.polyfillDetails = function () {
34
+ var $module = this.$module
35
+
36
+ // Save shortcuts to the inner summary and content elements
37
+ var $summary = this.$summary = $module.getElementsByTagName('summary').item(0)
38
+ var $content = this.$content = $module.getElementsByTagName('div').item(0)
39
+
40
+ // If <details> doesn't have a <summary> and a <div> representing the content
41
+ // it means the required HTML structure is not met so the script will stop
42
+ if (!$summary || !$content) {
43
+ return
44
+ }
45
+
46
+ // If the content doesn't have an ID, assign it one now
47
+ // which we'll need for the summary's aria-controls assignment
48
+ if (!$content.id) {
49
+ $content.id = 'details-content-' + generateUniqueID()
50
+ }
51
+
52
+ // Add ARIA role="group" to details
53
+ $module.setAttribute('role', 'group')
54
+
55
+ // Add role=button to summary
56
+ $summary.setAttribute('role', 'button')
57
+
58
+ // Add aria-controls
59
+ $summary.setAttribute('aria-controls', $content.id)
60
+
61
+ // Set tabIndex so the summary is keyboard accessible for non-native elements
62
+ //
63
+ // We have to use the camelcase `tabIndex` property as there is a bug in IE6/IE7 when we set the correct attribute lowercase:
64
+ // See http://web.archive.org/web/20170120194036/http://www.saliences.com/browserBugs/tabIndex.html for more information.
65
+ $summary.tabIndex = 0
66
+
67
+ // Detect initial open state
68
+ var openAttr = $module.getAttribute('open') !== null
69
+ if (openAttr === true) {
70
+ $summary.setAttribute('aria-expanded', 'true')
71
+ $content.setAttribute('aria-hidden', 'false')
72
+ } else {
73
+ $summary.setAttribute('aria-expanded', 'false')
74
+ $content.setAttribute('aria-hidden', 'true')
75
+ $content.style.display = 'none'
76
+ }
77
+
78
+ // Bind an event to handle summary elements
79
+ this.polyfillHandleInputs($summary, this.polyfillSetAttributes.bind(this))
80
+ }
81
+
82
+ /**
83
+ * Define a statechange function that updates aria-expanded and style.display
84
+ * @param {object} summary element
85
+ */
86
+ Details.prototype.polyfillSetAttributes = function () {
87
+ var $module = this.$module
88
+ var $summary = this.$summary
89
+ var $content = this.$content
90
+
91
+ var expanded = $summary.getAttribute('aria-expanded') === 'true'
92
+ var hidden = $content.getAttribute('aria-hidden') === 'true'
93
+
94
+ $summary.setAttribute('aria-expanded', (expanded ? 'false' : 'true'))
95
+ $content.setAttribute('aria-hidden', (hidden ? 'false' : 'true'))
96
+
97
+ $content.style.display = (expanded ? 'none' : '')
98
+
99
+ var hasOpenAttr = $module.getAttribute('open') !== null
100
+ if (!hasOpenAttr) {
101
+ $module.setAttribute('open', 'open')
102
+ } else {
103
+ $module.removeAttribute('open')
104
+ }
105
+
106
+ return true
107
+ }
108
+
109
+ /**
110
+ * Handle cross-modal click events
111
+ * @param {object} node element
112
+ * @param {function} callback function
113
+ */
114
+ Details.prototype.polyfillHandleInputs = function (node, callback) {
115
+ node.addEventListener('keypress', function (event) {
116
+ var target = event.target
117
+ // When the key gets pressed - check if it is enter or space
118
+ if (event.keyCode === KEY_ENTER || event.keyCode === KEY_SPACE) {
119
+ if (target.nodeName.toLowerCase() === 'summary') {
120
+ // Prevent space from scrolling the page
121
+ // and enter from submitting a form
122
+ event.preventDefault()
123
+ // Click to let the click event do all the necessary action
124
+ if (target.click) {
125
+ target.click()
126
+ } else {
127
+ // except Safari 5.1 and under don't support .click() here
128
+ callback(event)
129
+ }
130
+ }
131
+ }
132
+ })
133
+
134
+ // Prevent keyup to prevent clicking twice in Firefox when using space key
135
+ node.addEventListener('keyup', function (event) {
136
+ var target = event.target
137
+ if (event.keyCode === KEY_SPACE) {
138
+ if (target.nodeName.toLowerCase() === 'summary') {
139
+ event.preventDefault()
140
+ }
141
+ }
142
+ })
143
+
144
+ node.addEventListener('click', callback)
145
+ }
146
+
147
+ export default Details