govuk_publishing_components 37.6.0 → 37.7.0

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.
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).