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 +4 -4
- data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-specialist-link-tracker.js +24 -2
- data/app/assets/javascripts/govuk_publishing_components/components/layout-super-navigation-header.js +63 -0
- data/app/assets/javascripts/govuk_publishing_components/lib/trigger-event.js +4 -0
- data/app/assets/stylesheets/govuk_publishing_components/components/_contents-list.scss +1 -1
- data/app/views/govuk_publishing_components/components/_contents_list.html.erb +2 -0
- data/app/views/govuk_publishing_components/components/_govspeak.html.erb +11 -0
- data/app/views/govuk_publishing_components/components/_layout_super_navigation_header.html.erb +3 -4
- data/app/views/govuk_publishing_components/components/docs/contents_list.yml +1 -0
- data/app/views/govuk_publishing_components/components/docs/govspeak.yml +9 -0
- data/app/views/govuk_publishing_components/components/docs/layout_super_navigation_header.yml +2 -0
- data/lib/govuk_publishing_components/version.rb +1 -1
- data/node_modules/govuk-single-consent/README.md +157 -0
- data/node_modules/govuk-single-consent/dist/singleconsent.cjs.js +419 -0
- data/node_modules/govuk-single-consent/dist/singleconsent.esm.js +417 -0
- data/node_modules/govuk-single-consent/dist/singleconsent.iife.js +431 -0
- data/node_modules/govuk-single-consent/dist/singleconsent.iife.min.js +1 -0
- data/node_modules/govuk-single-consent/package.json +56 -0
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 53bea50d5b775c3e29b495eb9098ceb4d4708f081a7e0d3f6c18069de3f4e82b
|
4
|
+
data.tar.gz: dff2018e3a0320a9bb5f0822ec6145c656d9c4b8afee95a07173fc76f48c337e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 810afe26d85390892172d9424293c9b2937d6c39531470b680428929852ba512d9e2eb738286fdce53fbf1e55b84c7eab0a1119e3f41661771d429366cf83a49
|
7
|
+
data.tar.gz: aabf6aa8ee19cbbe2c86ecc666a86be58218a0bf5c61e5834cbd93741ad83ceb78458c1b3dd6f4dfccb74179743f7bcda67f1e75613c6807641cbe6654fab7a8
|
data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-specialist-link-tracker.js
CHANGED
@@ -40,11 +40,33 @@ window.GOVUK.analyticsGa4.analyticsModules = window.GOVUK.analyticsGa4.analytics
|
|
40
40
|
return
|
41
41
|
}
|
42
42
|
|
43
|
-
//
|
44
|
-
if (element.closest('[data-ga4-
|
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) {
|
data/app/assets/javascripts/govuk_publishing_components/components/layout-super-navigation-header.js
CHANGED
@@ -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)
|
@@ -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 %>
|
data/app/views/govuk_publishing_components/components/_layout_super_navigation_header.html.erb
CHANGED
@@ -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
|
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
|
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
|
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
|
data/app/views/govuk_publishing_components/components/docs/layout_super_navigation_header.yml
CHANGED
@@ -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
|
|
@@ -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).
|