govuk_publishing_components 37.6.0 → 37.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b5059f8b24e5d099520ca6f53061525153ddf6e7e31a5d4d1401516cb3856fc4
4
- data.tar.gz: 6565eeaa1b13939dbb5295327aa5d9862bd568b8af022d803419d22bc6890f5b
3
+ metadata.gz: 53bea50d5b775c3e29b495eb9098ceb4d4708f081a7e0d3f6c18069de3f4e82b
4
+ data.tar.gz: dff2018e3a0320a9bb5f0822ec6145c656d9c4b8afee95a07173fc76f48c337e
5
5
  SHA512:
6
- metadata.gz: 828d20c4426cad1bce861a0f4eef8f9aa46947fa0f381ca54f8408ef9675d828b3d9c7be18d513bf5c9f4b9c34197cb30cefa030f33134cac24ae1cdead608ab
7
- data.tar.gz: 53515ff838f233ab6b5bf5d3db5fe4d78e4c360faf4368cdfff7d9ef1ebbcebb8959637728348c74acd2e7003b94c4fa127c9dfe83c0a0b608263bcfdb17ce1f
6
+ metadata.gz: 810afe26d85390892172d9424293c9b2937d6c39531470b680428929852ba512d9e2eb738286fdce53fbf1e55b84c7eab0a1119e3f41661771d429366cf83a49
7
+ data.tar.gz: aabf6aa8ee19cbbe2c86ecc666a86be58218a0bf5c61e5834cbd93741ad83ceb78458c1b3dd6f4dfccb74179743f7bcda67f1e75613c6807641cbe6654fab7a8
@@ -40,11 +40,33 @@ window.GOVUK.analyticsGa4.analyticsModules = window.GOVUK.analyticsGa4.analytics
40
40
  return
41
41
  }
42
42
 
43
- // don't track this link if it's already being tracked by the another tracker (e.g. the link tracker or ecommerce tracker)
44
- if (element.closest('[data-ga4-link]') || element.closest('[data-ga4-ecommerce-path]')) {
43
+ // Don't track this link if it's already being tracked by the ecommerce tracker
44
+ if (element.closest('[data-ga4-ecommerce-path]')) {
45
45
  return
46
46
  }
47
47
 
48
+ // Code below ensures the tracker plays nicely with the other link tracker
49
+ var otherLinkTracker = element.closest('[data-ga4-link]')
50
+ if (otherLinkTracker) {
51
+ var limitToElementClass = otherLinkTracker.getAttribute('data-ga4-limit-to-element-class')
52
+
53
+ if (!limitToElementClass) {
54
+ // If this link is inside the other link tracker, and the other link tracker IS NOT limiting itself to specific classes,
55
+ // then stop this tracker from firing, as the other tracker is responsible for this link.
56
+ return
57
+ } else {
58
+ // If this link is inside the other link tracker, but the other link tracker IS limiting itself to specific classes,
59
+ // then track the link here only if it is not within the specified classes that the other tracker is looking for.
60
+ var classes = limitToElementClass.split(',')
61
+
62
+ for (var i = 0; i < classes.length; i++) {
63
+ if (element.closest('.' + classes[i].trim())) {
64
+ return
65
+ }
66
+ }
67
+ }
68
+ }
69
+
48
70
  var href = element.getAttribute('href')
49
71
 
50
72
  if (!href) {
@@ -93,6 +93,8 @@ window.GOVUK.Modules = window.GOVUK.Modules || {};
93
93
  this.$module = $module
94
94
  this.$searchToggle = this.$module.querySelector('#super-search-menu-toggle')
95
95
  this.$searchMenu = this.$module.querySelector('#super-search-menu')
96
+ this.$navToggle = this.$module.querySelector('#super-navigation-menu-toggle')
97
+ this.$navMenu = this.$module.querySelector('#super-navigation-menu')
96
98
 
97
99
  // The menu toggler buttons need three attributes for this to work:
98
100
  // - `aria-controls` contains the id of the menu to be toggled
@@ -126,7 +128,68 @@ window.GOVUK.Modules = window.GOVUK.Modules || {};
126
128
  toggle($target, $targetMenu)
127
129
  }
128
130
 
131
+ SuperNavigationMegaMenu.prototype.handleKeyDown = function (event) {
132
+ var KEY_TAB = 9
133
+ var KEY_ESC = 27
134
+ var $navMenuLinks = this.$navMenu.querySelectorAll('li a')
135
+ var $firstNavLink = $navMenuLinks[0]
136
+ var $lastNavLink = $navMenuLinks[$navMenuLinks.length - 1]
137
+ var $searchMenuLinks = this.$searchMenu.querySelectorAll('li a')
138
+ var $lastSearchLink = $searchMenuLinks[$searchMenuLinks.length - 1]
139
+
140
+ if (event.keyCode === KEY_TAB) {
141
+ if (!this.$navMenu.hasAttribute('hidden')) {
142
+ switch (document.activeElement) {
143
+ case this.$navToggle:
144
+ if (!event.shiftKey) {
145
+ event.preventDefault()
146
+ $firstNavLink.focus()
147
+ }
148
+ break
149
+ case $lastNavLink:
150
+ if (!event.shiftKey) {
151
+ event.preventDefault()
152
+ this.$searchToggle.focus()
153
+ hide(this.$navToggle, this.$navMenu)
154
+ }
155
+ break
156
+ case $firstNavLink:
157
+ if (event.shiftKey) {
158
+ event.preventDefault()
159
+ this.$navToggle.focus()
160
+ }
161
+ break
162
+ case this.$searchToggle:
163
+ if (event.shiftKey) {
164
+ event.preventDefault()
165
+ $lastNavLink.focus()
166
+ }
167
+ break
168
+ default:
169
+ break
170
+ }
171
+ } else if (!this.$searchMenu.hasAttribute('hidden')) {
172
+ if (document.activeElement === $lastSearchLink) {
173
+ if (!event.shiftKey) {
174
+ hide(this.$searchToggle, this.$searchMenu)
175
+ }
176
+ }
177
+ }
178
+ } else if (event.keyCode === KEY_ESC) {
179
+ if (!this.$navMenu.hasAttribute('hidden')) {
180
+ hide(this.$navToggle, this.$navMenu)
181
+ this.$navToggle.focus()
182
+ } else if (!this.$searchMenu.hasAttribute('hidden')) {
183
+ hide(this.$searchToggle, this.$searchMenu)
184
+ this.$searchToggle.focus()
185
+ }
186
+ }
187
+ }
188
+
129
189
  SuperNavigationMegaMenu.prototype.init = function () {
190
+ // Handle key events for tab and escape keys
191
+ this.$module.addEventListener('keydown', this.handleKeyDown.bind(this))
192
+
130
193
  for (var j = 0; j < this.$buttons.length; j++) {
131
194
  var $button = this.$buttons[j]
132
195
  $button.addEventListener('click', this.buttonHandler.bind(this), true)
@@ -27,6 +27,10 @@
27
27
  event.keyCode = keyCode
28
28
  }
29
29
 
30
+ if (params.shiftKey) {
31
+ event.shiftKey = true
32
+ }
33
+
30
34
  element.dispatchEvent(event)
31
35
  }
32
36
  }(window))
@@ -50,7 +50,7 @@
50
50
  padding-left: $contents-spacing;
51
51
  padding-right: $contents-spacing;
52
52
 
53
- &::before {
53
+ & span::before {
54
54
  content: "—";
55
55
  position: absolute;
56
56
  left: 0;
@@ -41,6 +41,7 @@
41
41
  <% index_link = 1 unless disable_ga4 %>
42
42
  <% contents.each.with_index(1) do |contents_item, position| %>
43
43
  <li class="<%= cl_helper.list_item_classes(contents_item, false) %>" <%= "aria-current=true" if contents_item[:active] %>>
44
+ <span aria-hidden="true"></span>
44
45
  <% link_text = format_numbers ? cl_helper.wrap_numbers_with_spans(contents_item[:text]) : contents_item[:text]
45
46
  unless disable_ga4
46
47
  ga4_data[:event_name] = cl_helper.get_ga4_event_name(contents_item[:href]) if contents_item[:href]
@@ -64,6 +65,7 @@
64
65
  <ol class="gem-c-contents-list__nested-list">
65
66
  <% contents_item[:items].each.with_index(1) do |nested_contents_item, nested_position| %>
66
67
  <li class="<%= cl_helper.list_item_classes(nested_contents_item, true) %>" <%= "aria-current=true" if nested_contents_item[:active] %>>
68
+ <span aria-hidden="true"></span>
67
69
  <%
68
70
  unless disable_ga4
69
71
  ga4_data[:event_name] = cl_helper.get_ga4_event_name(nested_contents_item[:href]) if nested_contents_item[:href]
@@ -10,9 +10,20 @@
10
10
  classes << "disable-youtube" if disable_youtube_expansions
11
11
  classes << "gem-c-govspeak--inverse" if inverse
12
12
 
13
+ disable_ga4 ||= false
14
+
13
15
  data_modules = "govspeak"
16
+ data_modules << " ga4-link-tracker" unless disable_ga4
14
17
  data_attributes = { module: data_modules }
15
18
 
19
+ unless disable_ga4
20
+ data_attributes.merge!({
21
+ ga4_track_links_only: "",
22
+ ga4_limit_to_element_class: "call-to-action, info-notice, help-notice, advisory",
23
+ ga4_link: { "event_name": "navigation", "type": "callout" }.to_json,
24
+ })
25
+ end
26
+
16
27
  %>
17
28
 
18
29
  <%= tag.div(class: "gem-c-govspeak govuk-govspeak " + classes.join(" "), data: data_attributes) do %>
@@ -162,7 +162,6 @@
162
162
 
163
163
  <%
164
164
  link = t("components.layout_super_navigation_header.navigation_link")
165
- unique_id = SecureRandom.hex(4)
166
165
  show_menu_text = show_navigation_menu_text
167
166
  hide_menu_text = hide_navigation_menu_text
168
167
  tracking_label = link[:label].downcase.gsub(/\s+/, "")
@@ -192,7 +191,7 @@
192
191
 
193
192
  <%= content_tag(:button, {
194
193
  aria: {
195
- controls: "super-navigation-menu-#{unique_id}",
194
+ controls: "super-navigation-menu",
196
195
  expanded: false,
197
196
  label: show_menu_text,
198
197
  },
@@ -213,7 +212,7 @@
213
212
  }
214
213
  },
215
214
  hidden: true,
216
- id: "super-navigation-menu-#{unique_id}-toggle",
215
+ id: "super-navigation-menu-toggle",
217
216
  type: "button",
218
217
  }) do %>
219
218
  <%= tag.span link[:label], class: top_toggle_button_inner_classes %>
@@ -282,7 +281,7 @@
282
281
  </div>
283
282
 
284
283
  <%= content_tag(:div, {
285
- id: "super-navigation-menu-#{unique_id}",
284
+ id: "super-navigation-menu",
286
285
  hidden: "",
287
286
  class: dropdown_menu_classes,
288
287
  }) do %>
@@ -18,6 +18,7 @@ accessibility_criteria: |
18
18
  - convey the content structure
19
19
  - indicate the current page when contents span different pages, and not link to itself
20
20
  - include an aria-label to contextualise the list
21
+ - ensure dashes before each list item are hidden from screen readers
21
22
 
22
23
  Links with formatted numbers must separate the number and text with a space for correct screen reader pronunciation. This changes pronunciation from "1 dot Item" to "1 Item".
23
24
  shared_accessibility_criteria:
@@ -914,3 +914,12 @@ examples:
914
914
  <p>Deforested area. Credit: Blue Ventures-Garth Cripps</p>
915
915
  </figcaption>
916
916
  </figure>
917
+ without_ga4_tracking:
918
+ description: |
919
+ Disables GA4 tracking on the component. Tracking is enabled by default. This adds a data module and data-attributes with JSON data. See the [ga4-link-tracker documentation](https://github.com/alphagov/govuk_publishing_components/blob/main/docs/analytics-ga4/ga4-link-tracker.md) for more information.
920
+ data:
921
+ block: |
922
+ <p>
923
+ <a href='https://www.gov.uk'>Hello World</a>
924
+ </p>
925
+ disable_ga4: true
@@ -7,6 +7,8 @@ accessibility_criteria: |
7
7
  The component must:
8
8
 
9
9
  * have a text contrast ratio higher than 4.5:1 against the background colour to meet WCAG AA
10
+ * follow the expected tabbing border
11
+ * allow menus to be closed when the escape key is pressed
10
12
 
11
13
  Images in the super navigation header must:
12
14
 
@@ -1,3 +1,3 @@
1
1
  module GovukPublishingComponents
2
- VERSION = "37.6.0".freeze
2
+ VERSION = "37.7.0".freeze
3
3
  end
@@ -0,0 +1,157 @@
1
+ # Single Consent client
2
+
3
+ The Single Consent client is a small Javascript which can be embedded in a
4
+ website to enable easily sharing a user's consent or rejection of cookies across
5
+ different websites.
6
+
7
+ See the [Single Consent service README](../README.md).
8
+
9
+ ## Quick start
10
+
11
+ See the [CommonJS example](https://github.com/alphagov/consent-api/blob/main/client/examples/commonJS-usage/index.js).
12
+
13
+ Contact data-tools-team@digital.cabinet-office.gov.uk to get the URL of the API endpoint.
14
+
15
+ The library provides the following static methods for finding out which types of
16
+ cookies a user has consented to.
17
+
18
+ - `hasConsentedToEssential`
19
+ - `hasConsentedToUsage`
20
+ - `hasConsentedToCampaigns`
21
+ - `hasConsentedToSetting`
22
+
23
+ The method `GovSingleConsent.getConsents()` provides the current state of the
24
+ user's consents to all types of cookies.
25
+
26
+ ### 1. Install with npm
27
+
28
+ In order to set first-party cookies, the client Javascript must be served with
29
+ your application.
30
+
31
+ We recommend installing the Single Consent client using
32
+ [node package manager (npm)](https://www.npmjs.com/).
33
+
34
+ ```sh
35
+ npm i govuk-single-consent
36
+ ```
37
+
38
+ https://www.npmjs.com/package/govuk-single-consent
39
+
40
+ ### 2. Including the Javascript client
41
+
42
+ The javascript client can be included in different ways. Choose one below.
43
+
44
+ #### CommonJS
45
+
46
+ See the [example](https://github.com/alphagov/consent-api/blob/main/client/examples/commonJS-usage/index.js).
47
+
48
+ #### Typescript
49
+
50
+ See the [example](https://github.com/alphagov/consent-api/blob/main/client/examples/typescript-usage/index.ts).
51
+
52
+ #### HTML script tag (IIFE)
53
+
54
+ The javascript client makes available the object `window.GovSingleConsent` for
55
+ interacting with the API. It needs to be loaded on any page that could be an
56
+ entry point to your web application, that allows modifying cookie consent, or
57
+ provides a link to another domain with which you want to share cookie consent
58
+ status. It is probably easiest to add the script to a base template used for all
59
+ pages.
60
+
61
+ On the same pages, you need to load your javascript for interacting with the
62
+ `window.GovSingleConsent` object.
63
+
64
+ See the following examples.
65
+
66
+ - [Cookie banner script](https://github.com/alphagov/consent-api/blob/main/client/example/cookie-banner.js)
67
+ - [Cooke page script](https://github.com/alphagov/consent-api/blob/main/client/example/cookies-page.js)
68
+
69
+ It is common practice to add Javascript tags just before the end `</body>` tag,
70
+ eg:
71
+
72
+ ```html
73
+ ...
74
+
75
+ <script src="{path_to_client_js}/singleconsent.js"></script>
76
+ <script src="{path_to_cookie_banner_script}.js"></script>
77
+ <script src="{path_to_cookies_page_script}.js"></script>
78
+ </body>
79
+ </html>
80
+ ```
81
+
82
+ ### 3. Passing the base URL to the constructor
83
+
84
+ You can either pass an environment string, or a custom base URL.
85
+
86
+ If using an environment string, its value should be either `staging` or `production`.
87
+
88
+ e.g
89
+
90
+ ```javascript
91
+ const dummyCallback = () => {}
92
+
93
+ // With an environemnt string to the staging environment
94
+ new GovSingleConsent(dummyCallback, 'staging')
95
+
96
+ // With an environemnt string to the production environment
97
+ new GovSingleConsent(dummyCallback, 'production')
98
+
99
+ // With a custom base URL
100
+ new GovSingleConsent(dummyCallback, 'http://some-development-url.com')
101
+ ```
102
+
103
+ ### 4. Share the user's consent to cookies via the API
104
+
105
+ When the user interacts with your cookie banner or cookie settings page to
106
+ consent to or reject cookies you can update the central database by invoking the
107
+ following function:
108
+
109
+ ```typescript
110
+ exampleCookieConsentStatusObject: Consents = {
111
+ essential: true,
112
+ settings: false,
113
+ usage: true,
114
+ campaigns: false,
115
+ }
116
+
117
+ singleConsentObject.setConsents(exampleCookieConsentStatusObject)
118
+ ```
119
+
120
+ ## Content Security Policy
121
+
122
+ If your website is served with a
123
+ [`Content-Security-Policy` HTTP header or `<meta>` element](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP), you
124
+ may need to modify it to allow the client to access the Single Consent service.
125
+ The value of the header or meta element should contain the following:
126
+
127
+ ```
128
+ connect-src 'self' https://consent-api-nw.a.run.app/api/v1/consent [... other site URLs separated by spaces];
129
+ ```
130
+
131
+ ## What the library does
132
+
133
+ The library manages all read/write operations with a cookie that stores the
134
+ state of a user's consent.
135
+
136
+ ### Callback
137
+
138
+ Websites using the Consent API must provide a callback function. This will be
139
+ invoked each time the consent has been updated. It will be called with three
140
+ parameters:
141
+
142
+ - `consents` An object describing the new consent state.
143
+ - `consentsPreferencesSet` Boolean, the cookie banner must be displayed if this
144
+ value is `false`.
145
+ - `error` An object describing any error, otherwise `null`. If there is an
146
+ error, then the `consents` object will say that the consents have been
147
+ revoked.
148
+
149
+ The structure of the consent data object is currently based on the
150
+ [GOV.UK `cookies_policy` cookie](https://www.gov.uk/help/cookies). If your
151
+ website cookies do not fall into any of the four categories listed, please
152
+ contact us.
153
+
154
+ ## Getting updates
155
+
156
+ To be notified when there's a new release, you can watch the
157
+ [consent-api Github repository](https://github.com/alphagov/consent-api).