govuk_publishing_components 29.9.0 → 29.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (179) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +16 -5
  3. data/app/assets/javascripts/govuk_publishing_components/analytics/page-content.js +4 -4
  4. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/gtm-click-tracking.js +46 -24
  5. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/gtm-page-views.js +98 -0
  6. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4.js +3 -0
  7. data/app/assets/javascripts/govuk_publishing_components/components/accordion.js +28 -1
  8. data/app/assets/javascripts/govuk_publishing_components/vendor/lux/lux-measurer.js +2 -2
  9. data/app/assets/stylesheets/govuk_publishing_components/_all_components_print.scss +1 -0
  10. data/app/assets/stylesheets/govuk_publishing_components/components/_contextual-sidebar.scss +20 -0
  11. data/app/assets/stylesheets/govuk_publishing_components/components/_layout-super-navigation-header.scss +3 -8
  12. data/app/assets/stylesheets/govuk_publishing_components/components/govspeak/_attachment.scss +7 -1
  13. data/app/assets/stylesheets/govuk_publishing_components/components/print/_organisation-logo.scss +4 -0
  14. data/app/controllers/govuk_publishing_components/audit_controller.rb +3 -3
  15. data/app/controllers/govuk_publishing_components/component_guide_controller.rb +0 -9
  16. data/app/models/govuk_publishing_components/audit_comparer.rb +91 -34
  17. data/app/views/govuk_publishing_components/audit/_applications.html.erb +20 -9
  18. data/app/views/govuk_publishing_components/component_guide/index.html.erb +1 -19
  19. data/app/views/govuk_publishing_components/components/_accordion.html.erb +7 -5
  20. data/app/views/govuk_publishing_components/components/_attachment.html.erb +1 -3
  21. data/app/views/govuk_publishing_components/components/_character_count.html.erb +1 -1
  22. data/app/views/govuk_publishing_components/components/_date_input.html.erb +0 -1
  23. data/app/views/govuk_publishing_components/components/_error_alert.html.erb +6 -3
  24. data/app/views/govuk_publishing_components/components/_input.html.erb +0 -2
  25. data/app/views/govuk_publishing_components/components/_layout_footer.html.erb +23 -5
  26. data/app/views/govuk_publishing_components/components/_layout_super_navigation_header.html.erb +3 -8
  27. data/app/views/govuk_publishing_components/components/contextual_sidebar/_ukraine_cta.html.erb +18 -19
  28. data/app/views/govuk_publishing_components/components/docs/accordion.yml +22 -13
  29. data/app/views/govuk_publishing_components/components/docs/attachment.yml +0 -11
  30. data/app/views/govuk_publishing_components/components/docs/error_alert.yml +4 -0
  31. data/config/locales/ar.yml +1 -2
  32. data/config/locales/az.yml +1 -2
  33. data/config/locales/be.yml +1 -2
  34. data/config/locales/bg.yml +1 -2
  35. data/config/locales/bn.yml +1 -2
  36. data/config/locales/cs.yml +1 -2
  37. data/config/locales/cy.yml +1 -2
  38. data/config/locales/da.yml +1 -2
  39. data/config/locales/de.yml +1 -2
  40. data/config/locales/dr.yml +1 -2
  41. data/config/locales/el.yml +1 -2
  42. data/config/locales/en.yml +9 -2
  43. data/config/locales/es-419.yml +1 -2
  44. data/config/locales/es.yml +1 -2
  45. data/config/locales/et.yml +1 -2
  46. data/config/locales/fa.yml +1 -2
  47. data/config/locales/fi.yml +1 -2
  48. data/config/locales/fr.yml +1 -2
  49. data/config/locales/gd.yml +1 -2
  50. data/config/locales/gu.yml +1 -2
  51. data/config/locales/he.yml +1 -2
  52. data/config/locales/hi.yml +1 -2
  53. data/config/locales/hr.yml +1 -2
  54. data/config/locales/hu.yml +1 -2
  55. data/config/locales/hy.yml +1 -2
  56. data/config/locales/id.yml +1 -2
  57. data/config/locales/is.yml +1 -2
  58. data/config/locales/it.yml +1 -2
  59. data/config/locales/ja.yml +1 -2
  60. data/config/locales/ka.yml +1 -2
  61. data/config/locales/kk.yml +1 -2
  62. data/config/locales/ko.yml +1 -2
  63. data/config/locales/lt.yml +1 -2
  64. data/config/locales/lv.yml +1 -2
  65. data/config/locales/ms.yml +1 -2
  66. data/config/locales/mt.yml +1 -2
  67. data/config/locales/nl.yml +1 -2
  68. data/config/locales/no.yml +1 -2
  69. data/config/locales/pa-pk.yml +1 -2
  70. data/config/locales/pa.yml +1 -2
  71. data/config/locales/pl.yml +1 -2
  72. data/config/locales/ps.yml +1 -2
  73. data/config/locales/pt.yml +1 -2
  74. data/config/locales/ro.yml +1 -2
  75. data/config/locales/ru.yml +1 -2
  76. data/config/locales/si.yml +1 -2
  77. data/config/locales/sk.yml +1 -2
  78. data/config/locales/sl.yml +1 -2
  79. data/config/locales/so.yml +1 -2
  80. data/config/locales/sq.yml +1 -2
  81. data/config/locales/sr.yml +1 -2
  82. data/config/locales/sv.yml +1 -2
  83. data/config/locales/sw.yml +1 -2
  84. data/config/locales/ta.yml +1 -2
  85. data/config/locales/th.yml +1 -2
  86. data/config/locales/tk.yml +1 -2
  87. data/config/locales/tr.yml +1 -2
  88. data/config/locales/uk.yml +1 -2
  89. data/config/locales/ur.yml +1 -2
  90. data/config/locales/uz.yml +1 -2
  91. data/config/locales/vi.yml +1 -2
  92. data/config/locales/zh-hk.yml +1 -2
  93. data/config/locales/zh-tw.yml +1 -2
  94. data/config/locales/zh.yml +1 -2
  95. data/lib/govuk_publishing_components/presenters/attachment_helper.rb +0 -21
  96. data/lib/govuk_publishing_components/presenters/meta_tags.rb +6 -0
  97. data/lib/govuk_publishing_components/presenters/public_layout_helper.rb +35 -16
  98. data/lib/govuk_publishing_components/version.rb +1 -1
  99. data/node_modules/govuk-frontend/govuk/all.js +120 -49
  100. data/node_modules/govuk-frontend/govuk/components/back-link/macro-options.json +2 -2
  101. data/node_modules/govuk-frontend/govuk/components/breadcrumbs/_index.scss +0 -2
  102. data/node_modules/govuk-frontend/govuk/components/breadcrumbs/macro-options.json +2 -2
  103. data/node_modules/govuk-frontend/govuk/components/button/_index.scss +6 -16
  104. data/node_modules/govuk-frontend/govuk/components/button/macro-options.json +2 -2
  105. data/node_modules/govuk-frontend/govuk/components/character-count/character-count.js +120 -49
  106. data/node_modules/govuk-frontend/govuk/components/character-count/fixtures.json +33 -17
  107. data/node_modules/govuk-frontend/govuk/components/character-count/macro-options.json +2 -2
  108. data/node_modules/govuk-frontend/govuk/components/character-count/template.njk +1 -4
  109. data/node_modules/govuk-frontend/govuk/components/checkboxes/_index.scss +3 -2
  110. data/node_modules/govuk-frontend/govuk/components/checkboxes/fixtures.json +22 -10
  111. data/node_modules/govuk-frontend/govuk/components/checkboxes/macro-options.json +2 -2
  112. data/node_modules/govuk-frontend/govuk/components/date-input/fixtures.json +23 -23
  113. data/node_modules/govuk-frontend/govuk/components/date-input/template.njk +1 -1
  114. data/node_modules/govuk-frontend/govuk/components/details/macro-options.json +4 -4
  115. data/node_modules/govuk-frontend/govuk/components/error-message/macro-options.json +2 -2
  116. data/node_modules/govuk-frontend/govuk/components/error-summary/macro-options.json +3 -3
  117. data/node_modules/govuk-frontend/govuk/components/fieldset/macro-options.json +2 -2
  118. data/node_modules/govuk-frontend/govuk/components/footer/_index.scss +12 -22
  119. data/node_modules/govuk-frontend/govuk/components/header/_index.scss +13 -3
  120. data/node_modules/govuk-frontend/govuk/components/header/macro-options.json +2 -2
  121. data/node_modules/govuk-frontend/govuk/components/hint/macro-options.json +2 -2
  122. data/node_modules/govuk-frontend/govuk/components/input/_index.scss +4 -13
  123. data/node_modules/govuk-frontend/govuk/components/input/macro-options.json +5 -5
  124. data/node_modules/govuk-frontend/govuk/components/inset-text/macro-options.json +2 -2
  125. data/node_modules/govuk-frontend/govuk/components/label/macro-options.json +2 -2
  126. data/node_modules/govuk-frontend/govuk/components/panel/_index.scss +1 -1
  127. data/node_modules/govuk-frontend/govuk/components/panel/macro-options.json +4 -4
  128. data/node_modules/govuk-frontend/govuk/components/phase-banner/macro-options.json +2 -2
  129. data/node_modules/govuk-frontend/govuk/components/radios/_index.scss +5 -4
  130. data/node_modules/govuk-frontend/govuk/components/radios/fixtures.json +17 -12
  131. data/node_modules/govuk-frontend/govuk/components/radios/macro-options.json +2 -2
  132. data/node_modules/govuk-frontend/govuk/components/skip-link/_index.scss +1 -3
  133. data/node_modules/govuk-frontend/govuk/components/skip-link/macro-options.json +2 -2
  134. data/node_modules/govuk-frontend/govuk/components/summary-list/macro-options.json +5 -5
  135. data/node_modules/govuk-frontend/govuk/components/table/macro-options.json +4 -4
  136. data/node_modules/govuk-frontend/govuk/components/tabs/macro-options.json +2 -2
  137. data/node_modules/govuk-frontend/govuk/components/tag/macro-options.json +2 -2
  138. data/node_modules/govuk-frontend/govuk/components/warning-text/macro-options.json +2 -2
  139. data/node_modules/govuk-frontend/govuk/helpers/_colour.scss +3 -3
  140. data/node_modules/govuk-frontend/govuk/helpers/_links.scss +7 -5
  141. data/node_modules/govuk-frontend/govuk/helpers/_media-queries.scss +2 -2
  142. data/node_modules/govuk-frontend/govuk/helpers/_shape-arrow.scss +1 -1
  143. data/node_modules/govuk-frontend/govuk/helpers/_spacing.scss +3 -3
  144. data/node_modules/govuk-frontend/govuk/helpers/_typography.scss +2 -2
  145. data/node_modules/govuk-frontend/govuk/objects/_button-group.scss +10 -26
  146. data/node_modules/govuk-frontend/govuk/objects/_template.scss +1 -1
  147. data/node_modules/govuk-frontend/govuk/objects/_width-container.scss +0 -4
  148. data/node_modules/govuk-frontend/govuk/tools/_exports.scss +1 -1
  149. data/node_modules/govuk-frontend/govuk/tools/_font-url.scss +1 -1
  150. data/node_modules/govuk-frontend/govuk/tools/_image-url.scss +1 -1
  151. data/node_modules/govuk-frontend/govuk/tools/_px-to-em.scss +2 -2
  152. data/node_modules/govuk-frontend/govuk/tools/_px-to-rem.scss +1 -1
  153. data/node_modules/govuk-frontend/govuk-esm/all.mjs +88 -0
  154. data/node_modules/govuk-frontend/govuk-esm/common.mjs +28 -0
  155. data/node_modules/govuk-frontend/govuk-esm/components/accordion/accordion.mjs +374 -0
  156. data/node_modules/govuk-frontend/govuk-esm/components/button/button.mjs +64 -0
  157. data/node_modules/govuk-frontend/govuk-esm/components/character-count/character-count.mjs +251 -0
  158. data/node_modules/govuk-frontend/govuk-esm/components/checkboxes/checkboxes.mjs +164 -0
  159. data/node_modules/govuk-frontend/govuk-esm/components/details/details.mjs +147 -0
  160. data/node_modules/govuk-frontend/govuk-esm/components/error-summary/error-summary.mjs +168 -0
  161. data/node_modules/govuk-frontend/govuk-esm/components/header/header.mjs +52 -0
  162. data/node_modules/govuk-frontend/govuk-esm/components/notification-banner/notification-banner.mjs +55 -0
  163. data/node_modules/govuk-frontend/govuk-esm/components/radios/radios.mjs +122 -0
  164. data/node_modules/govuk-frontend/govuk-esm/components/skip-link/skip-link.mjs +94 -0
  165. data/node_modules/govuk-frontend/govuk-esm/components/tabs/tabs.mjs +282 -0
  166. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/DOMTokenList.js +264 -0
  167. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Document.js +26 -0
  168. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element/prototype/classList.js +93 -0
  169. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element/prototype/closest.js +24 -0
  170. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element/prototype/matches.js +23 -0
  171. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element/prototype/nextElementSibling.js +22 -0
  172. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element/prototype/previousElementSibling.js +22 -0
  173. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element.js +114 -0
  174. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Event.js +252 -0
  175. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Function/prototype/bind.js +159 -0
  176. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Object/defineProperty.js +86 -0
  177. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Window.js +20 -0
  178. data/node_modules/govuk-frontend/package.json +8 -1
  179. metadata +29 -3
@@ -160,9 +160,8 @@ th:
160
160
  link_text: ตรวจสอบสิ่งที่คุณต้องทำ
161
161
  title: Brexit
162
162
  ukraine:
163
- link_path:
164
- link_text:
165
163
  title:
164
+ links:
166
165
  world_locations: สถานที่ทั่วโลก
167
166
  search_box:
168
167
  input_title: ค้นหา
@@ -163,9 +163,8 @@ tk:
163
163
  link_text: Näme etmelidigiňizi belläň
164
164
  title: Brexit
165
165
  ukraine:
166
- link_path:
167
- link_text:
168
166
  title:
167
+ links:
169
168
  world_locations: Dünýä ýerleri
170
169
  search_box:
171
170
  input_title: Gözlemek
@@ -163,9 +163,8 @@ tr:
163
163
  link_text: Ne yapmaya ihtiyacınız olduğunu kontrol edin
164
164
  title: Brexit
165
165
  ukraine:
166
- link_path:
167
- link_text:
168
166
  title:
167
+ links:
169
168
  world_locations: Dünya geneli konumlar
170
169
  search_box:
171
170
  input_title: Ara
@@ -169,9 +169,8 @@ uk:
169
169
  link_text: Перевірте, що вам потрібно зробити
170
170
  title: Brexit
171
171
  ukraine:
172
- link_path:
173
- link_text:
174
172
  title:
173
+ links:
175
174
  world_locations: Світові локації
176
175
  search_box:
177
176
  input_title: Пошук
@@ -159,9 +159,8 @@ ur:
159
159
  link_text: پڑتال کریں کہ آپ کو کیا کرنے کی ضرورت ہے
160
160
  title: Brexit
161
161
  ukraine:
162
- link_path:
163
- link_text:
164
162
  title:
163
+ links:
165
164
  world_locations: عالمی مقامات
166
165
  search_box:
167
166
  input_title: تلاش
@@ -164,9 +164,8 @@ uz:
164
164
  link_text: Нима қилиш зарурлигини текшириш
165
165
  title: Брексит
166
166
  ukraine:
167
- link_path:
168
- link_text:
169
167
  title:
168
+ links:
170
169
  world_locations: Жаҳондаги жойлашиш жойи
171
170
  search_box:
172
171
  input_title: Излаш
@@ -161,9 +161,8 @@ vi:
161
161
  link_text: Kiểm tra những gì bạn cần làm
162
162
  title: Brexit
163
163
  ukraine:
164
- link_path:
165
- link_text:
166
164
  title:
165
+ links:
167
166
  world_locations: Các vị trí trên thế giới
168
167
  search_box:
169
168
  input_title: Tìm kiếm
@@ -160,9 +160,8 @@ zh-hk:
160
160
  link_text: 查看您需要做的事
161
161
  title: 英國脫歐
162
162
  ukraine:
163
- link_path:
164
- link_text:
165
163
  title:
164
+ links:
166
165
  world_locations: 全世界之地點
167
166
  search_box:
168
167
  input_title: 搜尋
@@ -160,9 +160,8 @@ zh-tw:
160
160
  link_text: 確認你需要:
161
161
  title: 脫歐
162
162
  ukraine:
163
- link_path:
164
- link_text:
165
163
  title:
164
+ links:
166
165
  world_locations: 世界地點
167
166
  search_box:
168
167
  input_title: 搜尋
@@ -160,9 +160,8 @@ zh:
160
160
  link_text: 检查您需要做的事情
161
161
  title: 英国脱欧
162
162
  ukraine:
163
- link_path:
164
- link_text:
165
163
  title:
164
+ links:
166
165
  world_locations: 世界地点
167
166
  search_box:
168
167
  input_title: 搜索
@@ -1,14 +1,6 @@
1
1
  module GovukPublishingComponents
2
2
  module Presenters
3
3
  class AttachmentHelper
4
- # Various departments are taking part in a pilot to use a form
5
- # rather than direct email for users to request accessible formats. When the pilot
6
- # scheme is rolled out further this can be removed.
7
- # Currently DfE, DWP and DVSA are participating in the pilot.
8
- EMAILS_IN_ACCESSIBLE_FORMAT_REQUEST_PILOT = %w[govuk_publishing_components@example.com
9
- alternative.formats@education.gov.uk
10
- gov.uk.publishing@dvsa.gov.uk].freeze
11
-
12
4
  delegate :opendocument?, :document?, :spreadsheet?, to: :content_type
13
5
 
14
6
  attr_reader :attachment_data
@@ -16,7 +8,6 @@ module GovukPublishingComponents
16
8
  # Expects a hash of attachment data
17
9
  # * title and url are required
18
10
  # * content_type, filename, file_size, number of pages, alternative_format_contact_email can be provided
19
- # * attachment_id and owning_document_content_id are required to use the accessible format request form
20
11
  def initialize(attachment_data)
21
12
  @attachment_data = attachment_data.with_indifferent_access
22
13
  end
@@ -56,14 +47,6 @@ module GovukPublishingComponents
56
47
  attachment_data[:alternative_format_contact_email]
57
48
  end
58
49
 
59
- def attachment_id
60
- attachment_data[:attachment_id]
61
- end
62
-
63
- def owning_document_content_id
64
- attachment_data[:owning_document_content_id]
65
- end
66
-
67
50
  def reference
68
51
  reference = []
69
52
  reference << "ISBN #{attachment_data[:isbn]}" if attachment_data[:isbn].present?
@@ -88,10 +71,6 @@ module GovukPublishingComponents
88
71
  attachment_data[:command_paper_number].present? || attachment_data[:hoc_paper_number].present? || attachment_data[:unnumbered_command_paper].eql?(true) || attachment_data[:unnumbered_hoc_paper].eql?(true)
89
72
  end
90
73
 
91
- def display_accessible_format_request_form_link?
92
- EMAILS_IN_ACCESSIBLE_FORMAT_REQUEST_PILOT.include?(alternative_format_contact_email) && owning_document_content_id.present? && attachment_id.present?
93
- end
94
-
95
74
  class SupportedContentType
96
75
  attr_reader :content_type_data
97
76
 
@@ -39,6 +39,12 @@ module GovukPublishingComponents
39
39
  meta_tags["govuk:content-has-history"] = "true" if has_content_history?
40
40
  meta_tags["govuk:static-analytics:strip-dates"] = "true" if should_strip_dates_pii?(content_item, local_assigns)
41
41
  meta_tags["govuk:static-analytics:strip-postcodes"] = "true" if should_strip_postcode_pii?(content_item, local_assigns)
42
+ meta_tags["govuk:first-published-at"] = content_item[:first_published_at] if content_item[:first_published_at]
43
+ meta_tags["govuk:updated-at"] = content_item[:updated_at] if content_item[:updated_at]
44
+ meta_tags["govuk:public-updated-at"] = content_item[:public_updated_at] if content_item[:public_updated_at]
45
+ primary_publisher = content_item.dig(:links, :primary_publishing_organisation)
46
+ primary_publisher = primary_publisher.first[:title] if primary_publisher
47
+ meta_tags["govuk:primary-publishing-organisation"] = primary_publisher if primary_publisher
42
48
 
43
49
  meta_tags
44
50
  end
@@ -2,7 +2,7 @@ module GovukPublishingComponents
2
2
  module Presenters
3
3
  class PublicLayoutHelper
4
4
  FOOTER_NAVIGATION_COLUMNS = [2, 1].freeze
5
-
5
+ FOOTER_TRACK_ACTIONS = %w[topicsLink governmentactivityLink].freeze
6
6
  FOOTER_META = {
7
7
  items: [
8
8
  {
@@ -46,25 +46,44 @@ module GovukPublishingComponents
46
46
  attr_reader :footer_navigation, :footer_meta, :cookie_banner_data
47
47
 
48
48
  def initialize(local_assigns)
49
- @footer_navigation = local_assigns[:footer_navigation] || I18n.t("components.layout_footer.navigation_links").each_with_index.map do |menu, i|
49
+ @footer_navigation = local_assigns[:footer_navigation] || navigation_link_generation_from_locale(I18n.t("components.layout_footer.navigation_links"))
50
+ @footer_meta = local_assigns[:footer_meta] || { items: add_data_attributes_to_links(FOOTER_META[:items], "supportLink") }
51
+ @cookie_banner_data = local_assigns[:cookie_banner_data] || {}
52
+ end
53
+
54
+ def navigation_link_generation_from_locale(links)
55
+ links.each_with_index.map do |menu, i|
50
56
  {
51
57
  title: menu[:title],
52
- columns: FOOTER_NAVIGATION_COLUMNS[i],
53
- items: menu[:menu_contents].map do |item|
54
- item.merge({
55
- attributes: {
56
- data: {
57
- track_category: "footerClicked",
58
- track_action: "footerLinks",
59
- track_label: item[:text],
60
- },
61
- },
62
- })
63
- end,
58
+ columns: footer_navigation_columns[i],
59
+ items: add_data_attributes_to_links(menu[:menu_contents], footer_track_actions[i]),
64
60
  }
65
61
  end
66
- @footer_meta = local_assigns[:footer_meta] || FOOTER_META
67
- @cookie_banner_data = local_assigns[:cookie_banner_data] || {}
62
+ end
63
+
64
+ def footer_navigation_columns
65
+ FOOTER_NAVIGATION_COLUMNS
66
+ end
67
+
68
+ def footer_track_actions
69
+ FOOTER_TRACK_ACTIONS
70
+ end
71
+
72
+ def generate_data_attribute(link, track_action)
73
+ {
74
+ track_category: "footerClicked",
75
+ track_action: track_action,
76
+ track_label: link[:href],
77
+ track_options: {
78
+ dimension29: link[:text],
79
+ },
80
+ }
81
+ end
82
+
83
+ def add_data_attributes_to_links(items, track_action)
84
+ items.map do |item|
85
+ item.deep_merge({ attributes: { data: generate_data_attribute(item, track_action) } })
86
+ end
68
87
  end
69
88
  end
70
89
  end
@@ -1,3 +1,3 @@
1
1
  module GovukPublishingComponents
2
- VERSION = "29.9.0".freeze
2
+ VERSION = "29.12.0".freeze
3
3
  end
@@ -1601,9 +1601,9 @@ Details.prototype.polyfillHandleInputs = function (node, callback) {
1601
1601
  function CharacterCount ($module) {
1602
1602
  this.$module = $module;
1603
1603
  this.$textarea = $module.querySelector('.govuk-js-character-count');
1604
- if (this.$textarea) {
1605
- this.$countMessage = document.getElementById(this.$textarea.id + '-info');
1606
- }
1604
+ this.$visibleCountMessage = null;
1605
+ this.$screenReaderCountMessage = null;
1606
+ this.lastInputTimestamp = null;
1607
1607
  }
1608
1608
 
1609
1609
  CharacterCount.prototype.defaults = {
@@ -1613,18 +1613,39 @@ CharacterCount.prototype.defaults = {
1613
1613
 
1614
1614
  // Initialize component
1615
1615
  CharacterCount.prototype.init = function () {
1616
+ // Check that required elements are present
1617
+ if (!this.$textarea) {
1618
+ return
1619
+ }
1620
+
1616
1621
  // Check for module
1617
1622
  var $module = this.$module;
1618
1623
  var $textarea = this.$textarea;
1619
- var $countMessage = this.$countMessage;
1620
-
1621
- if (!$textarea || !$countMessage) {
1622
- return
1623
- }
1624
+ var $fallbackLimitMessage = document.getElementById($textarea.id + '-info');
1624
1625
 
1625
- // We move count message right after the field
1626
+ // Move the fallback count message to be immediately after the textarea
1626
1627
  // Kept for backwards compatibility
1627
- $textarea.insertAdjacentElement('afterend', $countMessage);
1628
+ $textarea.insertAdjacentElement('afterend', $fallbackLimitMessage);
1629
+
1630
+ // Create the *screen reader* specific live-updating counter
1631
+ // This doesn't need any styling classes, as it is never visible
1632
+ var $screenReaderCountMessage = document.createElement('div');
1633
+ $screenReaderCountMessage.className = 'govuk-character-count__sr-status govuk-visually-hidden';
1634
+ $screenReaderCountMessage.setAttribute('aria-live', 'polite');
1635
+ this.$screenReaderCountMessage = $screenReaderCountMessage;
1636
+ $fallbackLimitMessage.insertAdjacentElement('afterend', $screenReaderCountMessage);
1637
+
1638
+ // Create our live-updating counter element, copying the classes from the
1639
+ // fallback element for backwards compatibility as these may have been configured
1640
+ var $visibleCountMessage = document.createElement('div');
1641
+ $visibleCountMessage.className = $fallbackLimitMessage.className;
1642
+ $visibleCountMessage.classList.add('govuk-character-count__status');
1643
+ $visibleCountMessage.setAttribute('aria-hidden', 'true');
1644
+ this.$visibleCountMessage = $visibleCountMessage;
1645
+ $fallbackLimitMessage.insertAdjacentElement('afterend', $visibleCountMessage);
1646
+
1647
+ // Hide the fallback limit message
1648
+ $fallbackLimitMessage.classList.add('govuk-visually-hidden');
1628
1649
 
1629
1650
  // Read options set using dataset ('data-' values)
1630
1651
  this.options = this.getDataset($module);
@@ -1644,23 +1665,19 @@ CharacterCount.prototype.init = function () {
1644
1665
  }
1645
1666
 
1646
1667
  // Remove hard limit if set
1647
- $module.removeAttribute('maxlength');
1668
+ $textarea.removeAttribute('maxlength');
1669
+
1670
+ this.bindChangeEvents();
1648
1671
 
1649
1672
  // When the page is restored after navigating 'back' in some browsers the
1650
1673
  // state of the character count is not restored until *after* the DOMContentLoaded
1651
- // event is fired, so we need to sync after the pageshow event in browsers
1652
- // that support it.
1674
+ // event is fired, so we need to manually update it after the pageshow event
1675
+ // in browsers that support it.
1653
1676
  if ('onpageshow' in window) {
1654
- window.addEventListener('pageshow', this.sync.bind(this));
1677
+ window.addEventListener('pageshow', this.updateCountMessage.bind(this));
1655
1678
  } else {
1656
- window.addEventListener('DOMContentLoaded', this.sync.bind(this));
1679
+ window.addEventListener('DOMContentLoaded', this.updateCountMessage.bind(this));
1657
1680
  }
1658
-
1659
- this.sync();
1660
- };
1661
-
1662
- CharacterCount.prototype.sync = function () {
1663
- this.bindChangeEvents();
1664
1681
  this.updateCountMessage();
1665
1682
  };
1666
1683
 
@@ -1695,7 +1712,7 @@ CharacterCount.prototype.count = function (text) {
1695
1712
  // Bind input propertychange to the elements and update based on the change
1696
1713
  CharacterCount.prototype.bindChangeEvents = function () {
1697
1714
  var $textarea = this.$textarea;
1698
- $textarea.addEventListener('keyup', this.checkIfValueChanged.bind(this));
1715
+ $textarea.addEventListener('keyup', this.handleKeyUp.bind(this));
1699
1716
 
1700
1717
  // Bind focus/blur events to start/stop polling
1701
1718
  $textarea.addEventListener('focus', this.handleFocus.bind(this));
@@ -1713,42 +1730,64 @@ CharacterCount.prototype.checkIfValueChanged = function () {
1713
1730
  }
1714
1731
  };
1715
1732
 
1716
- // Update message box
1733
+ // Helper function to update both the visible and screen reader-specific
1734
+ // counters simultaneously (e.g. on init)
1717
1735
  CharacterCount.prototype.updateCountMessage = function () {
1718
- var countElement = this.$textarea;
1719
- var options = this.options;
1720
- var countMessage = this.$countMessage;
1736
+ this.updateVisibleCountMessage();
1737
+ this.updateScreenReaderCountMessage();
1738
+ };
1721
1739
 
1722
- // Determine the remaining number of characters/words
1723
- var currentLength = this.count(countElement.value);
1724
- var maxLength = this.maxLength;
1725
- var remainingNumber = maxLength - currentLength;
1740
+ // Update visible counter
1741
+ CharacterCount.prototype.updateVisibleCountMessage = function () {
1742
+ var $textarea = this.$textarea;
1743
+ var $visibleCountMessage = this.$visibleCountMessage;
1744
+ var remainingNumber = this.maxLength - this.count($textarea.value);
1726
1745
 
1727
- // Set threshold if presented in options
1728
- var thresholdPercent = options.threshold ? options.threshold : 0;
1729
- var thresholdValue = maxLength * thresholdPercent / 100;
1730
- if (thresholdValue > currentLength) {
1731
- countMessage.classList.add('govuk-character-count__message--disabled');
1732
- // Ensure threshold is hidden for users of assistive technologies
1733
- countMessage.setAttribute('aria-hidden', true);
1746
+ // If input is over the threshold, remove the disabled class which renders the
1747
+ // counter invisible.
1748
+ if (this.isOverThreshold()) {
1749
+ $visibleCountMessage.classList.remove('govuk-character-count__message--disabled');
1734
1750
  } else {
1735
- countMessage.classList.remove('govuk-character-count__message--disabled');
1736
- // Ensure threshold is visible for users of assistive technologies
1737
- countMessage.removeAttribute('aria-hidden');
1751
+ $visibleCountMessage.classList.add('govuk-character-count__message--disabled');
1738
1752
  }
1739
1753
 
1740
1754
  // Update styles
1741
1755
  if (remainingNumber < 0) {
1742
- countElement.classList.add('govuk-textarea--error');
1743
- countMessage.classList.remove('govuk-hint');
1744
- countMessage.classList.add('govuk-error-message');
1756
+ $textarea.classList.add('govuk-textarea--error');
1757
+ $visibleCountMessage.classList.remove('govuk-hint');
1758
+ $visibleCountMessage.classList.add('govuk-error-message');
1759
+ } else {
1760
+ $textarea.classList.remove('govuk-textarea--error');
1761
+ $visibleCountMessage.classList.remove('govuk-error-message');
1762
+ $visibleCountMessage.classList.add('govuk-hint');
1763
+ }
1764
+
1765
+ // Update message
1766
+ $visibleCountMessage.innerHTML = this.formattedUpdateMessage();
1767
+ };
1768
+
1769
+ // Update screen reader-specific counter
1770
+ CharacterCount.prototype.updateScreenReaderCountMessage = function () {
1771
+ var $screenReaderCountMessage = this.$screenReaderCountMessage;
1772
+
1773
+ // If over the threshold, remove the aria-hidden attribute, allowing screen
1774
+ // readers to announce the content of the element.
1775
+ if (this.isOverThreshold()) {
1776
+ $screenReaderCountMessage.removeAttribute('aria-hidden');
1745
1777
  } else {
1746
- countElement.classList.remove('govuk-textarea--error');
1747
- countMessage.classList.remove('govuk-error-message');
1748
- countMessage.classList.add('govuk-hint');
1778
+ $screenReaderCountMessage.setAttribute('aria-hidden', true);
1749
1779
  }
1750
1780
 
1751
1781
  // Update message
1782
+ $screenReaderCountMessage.innerHTML = this.formattedUpdateMessage();
1783
+ };
1784
+
1785
+ // Format update message
1786
+ CharacterCount.prototype.formattedUpdateMessage = function () {
1787
+ var $textarea = this.$textarea;
1788
+ var options = this.options;
1789
+ var remainingNumber = this.maxLength - this.count($textarea.value);
1790
+
1752
1791
  var charVerb = 'remaining';
1753
1792
  var charNoun = 'character';
1754
1793
  var displayNumber = remainingNumber;
@@ -1760,12 +1799,44 @@ CharacterCount.prototype.updateCountMessage = function () {
1760
1799
  charVerb = (remainingNumber < 0) ? 'too many' : 'remaining';
1761
1800
  displayNumber = Math.abs(remainingNumber);
1762
1801
 
1763
- countMessage.innerHTML = 'You have ' + displayNumber + ' ' + charNoun + ' ' + charVerb;
1802
+ return 'You have ' + displayNumber + ' ' + charNoun + ' ' + charVerb
1803
+ };
1804
+
1805
+ // Checks whether the value is over the configured threshold for the input.
1806
+ // If there is no configured threshold, it is set to 0 and this function will
1807
+ // always return true.
1808
+ CharacterCount.prototype.isOverThreshold = function () {
1809
+ var $textarea = this.$textarea;
1810
+ var options = this.options;
1811
+
1812
+ // Determine the remaining number of characters/words
1813
+ var currentLength = this.count($textarea.value);
1814
+ var maxLength = this.maxLength;
1815
+
1816
+ // Set threshold if presented in options
1817
+ var thresholdPercent = options.threshold ? options.threshold : 0;
1818
+ var thresholdValue = maxLength * thresholdPercent / 100;
1819
+
1820
+ return (thresholdValue <= currentLength)
1821
+ };
1822
+
1823
+ // Update the visible character counter and keep track of when the last update
1824
+ // happened for each keypress
1825
+ CharacterCount.prototype.handleKeyUp = function () {
1826
+ this.updateVisibleCountMessage();
1827
+ this.lastInputTimestamp = Date.now();
1764
1828
  };
1765
1829
 
1766
1830
  CharacterCount.prototype.handleFocus = function () {
1767
- // Check if value changed on focus
1768
- this.valueChecker = setInterval(this.checkIfValueChanged.bind(this), 1000);
1831
+ // If the field is focused, and a keyup event hasn't been detected for at
1832
+ // least 1000 ms (1 second), then run the manual change check.
1833
+ // This is so that the update triggered by the manual comparison doesn't
1834
+ // conflict with debounced KeyboardEvent updates.
1835
+ this.valueChecker = setInterval(function () {
1836
+ if (!this.lastInputTimestamp || (Date.now() - 500) >= this.lastInputTimestamp) {
1837
+ this.checkIfValueChanged();
1838
+ }
1839
+ }.bind(this), 1000);
1769
1840
  };
1770
1841
 
1771
1842
  CharacterCount.prototype.handleBlur = function () {
@@ -3,13 +3,13 @@
3
3
  "name": "text",
4
4
  "type": "string",
5
5
  "required": false,
6
- "description": "Text to use within the back link component. If `html` is provided, the `text` argument will be ignored. Defaults to 'Back'."
6
+ "description": "Text to use within the back link component. If `html` is provided, the `text` option will be ignored. Defaults to 'Back'."
7
7
  },
8
8
  {
9
9
  "name": "html",
10
10
  "type": "string",
11
11
  "required": false,
12
- "description": "HTML to use within the back link component. If `html` is provided, the `text` argument will be ignored. Defaults to 'Back'."
12
+ "description": "HTML to use within the back link component. If `html` is provided, the `text` option will be ignored. Defaults to 'Back'."
13
13
  },
14
14
  {
15
15
  "name": "href",
@@ -128,8 +128,6 @@
128
128
  }
129
129
 
130
130
  .govuk-breadcrumbs__list {
131
- display: -webkit-box;
132
- display: -webkit-flex;
133
131
  display: -ms-flexbox;
134
132
  display: flex;
135
133
  }
@@ -9,13 +9,13 @@
9
9
  "name": "text",
10
10
  "type": "string",
11
11
  "required": true,
12
- "description": "If `html` is set, this is not required. Text to use within the breadcrumbs item. If `html` is provided, the `text` argument will be ignored."
12
+ "description": "If `html` is set, this is not required. Text to use within the breadcrumbs item. If `html` is provided, the `text` option will be ignored."
13
13
  },
14
14
  {
15
15
  "name": "html",
16
16
  "type": "string",
17
17
  "required": true,
18
- "description": "If `text` is set, this is not required. HTML to use within the breadcrumbs item. If `html` is provided, the `text` argument will be ignored."
18
+ "description": "If `text` is set, this is not required. HTML to use within the breadcrumbs item. If `html` is provided, the `text` option will be ignored."
19
19
  },
20
20
  {
21
21
  "name": "href",
@@ -235,22 +235,14 @@
235
235
  @include govuk-typography-weight-bold;
236
236
  @include govuk-typography-responsive($size: 24, $override-line-height: 1);
237
237
 
238
- display: -webkit-inline-box;
239
-
240
- display: -webkit-inline-flex;
241
-
242
238
  display: -ms-inline-flexbox;
243
239
 
244
240
  display: inline-flex;
245
241
  min-height: auto;
246
242
 
247
- -webkit-box-pack: center;
248
-
249
- -webkit-justify-content: center;
250
-
251
- -ms-flex-pack: center;
243
+ -ms-flex-pack: center;
252
244
 
253
- justify-content: center;
245
+ justify-content: center;
254
246
  }
255
247
 
256
248
  .govuk-button__start-icon {
@@ -260,12 +252,10 @@
260
252
  margin-left: govuk-spacing(2);
261
253
  }
262
254
  vertical-align: middle;
263
- -webkit-flex-shrink: 0;
264
- -ms-flex-negative: 0;
265
- flex-shrink: 0;
266
- -webkit-align-self: center;
267
- -ms-flex-item-align: center;
268
- align-self: center;
255
+ -ms-flex-negative: 0;
256
+ flex-shrink: 0;
257
+ -ms-flex-item-align: center;
258
+ align-self: center;
269
259
  // Work around SVGs not inheriting color from parent in forced color mode
270
260
  // (https://github.com/w3c/csswg-drafts/issues/6310)
271
261
  forced-color-adjust: auto;
@@ -9,13 +9,13 @@
9
9
  "name": "text",
10
10
  "type": "string",
11
11
  "required": true,
12
- "description": "If `html` is set, this is not required. Text for the button or link. If `html` is provided, the `text` argument will be ignored and `element` will be automatically set to `button` unless `href` is also set, or it has already been defined. This argument has no effect if `element` is set to `input`."
12
+ "description": "If `html` is set, this is not required. Text for the button or link. If `html` is provided, the `text` option will be ignored and `element` will be automatically set to `button` unless `href` is also set, or it has already been defined. This option has no effect if `element` is set to `input`."
13
13
  },
14
14
  {
15
15
  "name": "html",
16
16
  "type": "string",
17
17
  "required": true,
18
- "description": "If `text` is set, this is not required. HTML for the button or link. If `html` is provided, the `text` argument will be ignored and `element` will be automatically set to `button` unless `href` is also set, or it has already been defined. This argument has no effect if `element` is set to `input`."
18
+ "description": "If `text` is set, this is not required. HTML for the button or link. If `html` is provided, the `text` option will be ignored and `element` will be automatically set to `button` unless `href` is also set, or it has already been defined. This option has no effect if `element` is set to `input`."
19
19
  },
20
20
  {
21
21
  "name": "name",