govuk_publishing_components 43.4.1 → 43.5.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.
Files changed (21) hide show
  1. checksums.yaml +4 -4
  2. data/app/views/govuk_publishing_components/components/_radio.html.erb +2 -0
  3. data/app/views/govuk_publishing_components/components/docs/inverse_header.yml +2 -30
  4. data/app/views/govuk_publishing_components/components/docs/radio.yml +15 -0
  5. data/lib/govuk_publishing_components/version.rb +1 -1
  6. data/node_modules/accessible-autocomplete/CHANGELOG.md +8 -0
  7. data/node_modules/accessible-autocomplete/accessibility-criteria.md +2 -1
  8. data/node_modules/accessible-autocomplete/dist/accessible-autocomplete.min.js +1 -1
  9. data/node_modules/accessible-autocomplete/dist/accessible-autocomplete.min.js.map +1 -1
  10. data/node_modules/accessible-autocomplete/dist/lib/accessible-autocomplete.preact.min.js +1 -1
  11. data/node_modules/accessible-autocomplete/dist/lib/accessible-autocomplete.preact.min.js.map +1 -1
  12. data/node_modules/accessible-autocomplete/dist/lib/accessible-autocomplete.react.min.js +1 -1
  13. data/node_modules/accessible-autocomplete/dist/lib/accessible-autocomplete.react.min.js.map +1 -1
  14. data/node_modules/accessible-autocomplete/examples/ajax-source.html +300 -0
  15. data/node_modules/accessible-autocomplete/examples/form-single.html +4 -2
  16. data/node_modules/accessible-autocomplete/examples/index.html +7 -52
  17. data/node_modules/accessible-autocomplete/examples/suggestions.json +258 -0
  18. data/node_modules/accessible-autocomplete/package.json +15 -15
  19. data/node_modules/accessible-autocomplete/src/autocomplete.js +4 -2
  20. data/node_modules/accessible-autocomplete/test/functional/index.js +21 -5
  21. metadata +18 -2
@@ -0,0 +1,300 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Accessible Autocomplete AJAX source example</title>
7
+ <style>
8
+ /* Example page specific styling. */
9
+ html {
10
+ color: #111;
11
+ background: #FFF;
12
+ font-family: -apple-system, BlinkMacSystemFont, 'avenir next', avenir, 'helvetica neue', helvetica, ubuntu, roboto, noto, 'segoe ui', arial, sans-serif;
13
+ font-size: 16px;
14
+ line-height: 1.5;
15
+ }
16
+
17
+ body {
18
+ padding-left: 1rem;
19
+ padding-right: 1rem;
20
+ }
21
+
22
+ h1, h2, h3, h4, h5, h6 {
23
+ line-height: normal;
24
+ }
25
+
26
+ label {
27
+ display: block;
28
+ margin-bottom: .5rem;
29
+ }
30
+
31
+ code {
32
+ padding-left: .5em;
33
+ padding-right: .5em;
34
+ background: #EFEFEF;
35
+ font-weight: normal;
36
+ font-family: monospace;
37
+ }
38
+
39
+ main {
40
+ max-width: 40em;
41
+ margin-left: auto;
42
+ margin-right: auto;
43
+ }
44
+
45
+ .autocomplete-wrapper {
46
+ max-width: 20em;
47
+ margin-bottom: 4rem;
48
+ }
49
+
50
+ .submitted--hidden {
51
+ display: none;
52
+ }
53
+ </style>
54
+ <link rel="stylesheet" href="../dist/accessible-autocomplete.min.css">
55
+ </head>
56
+ <body>
57
+ <main>
58
+ <h1>Accessible Autocomplete AJAX source example</h1>
59
+
60
+ <div class="submitted submitted--hidden">
61
+ <p>You submitted:</p>
62
+ <ul>
63
+ <li><code>"last-location": <span class="submitted__last-location"></span></code></li>
64
+ </ul>
65
+ <hr />
66
+ </div>
67
+
68
+ <form action="form-single.html" method="get">
69
+ <label for="last-location">What was the last location you visited?</label>
70
+ <div class="autocomplete-wrapper">
71
+ </div>
72
+
73
+ <button type="submit">Submit your answer</button>
74
+ </form>
75
+ </main>
76
+
77
+ <script type="text/javascript" src="../dist/accessible-autocomplete.min.js"></script>
78
+ <script type="text/javascript">
79
+ // Sending requests to a server means that when the autocomplete has no
80
+ // result it may not be because there are no results, but because these
81
+ // results are being fetched, or because an error happened. We can use the
82
+ // function for internationalising the 'No results found' message to
83
+ // provide a little more context to users.
84
+ //
85
+ // It'll rely on a `status` variable updated by the wrappers of the
86
+ // function making the request (see thereafter)
87
+ let status;
88
+ function tNoResults() {
89
+ if (status === 'loading') {
90
+ return 'Loading suggestions...'
91
+ } else if (status === 'error') {
92
+ return 'Sorry, an error occurred'
93
+ }else {
94
+ return 'No results found'
95
+ }
96
+ }
97
+
98
+ // The aim being to load suggestions from a server, we'll need a function
99
+ // that does just that. This one uses `fetch`, but you could also use
100
+ // XMLHttpRequest or whichever library is the most suitable to your
101
+ // project
102
+ //
103
+ // For lack of a actual server able of doing computation our endpoint will
104
+ // return the whole list of countries and we'll simulate the work of the
105
+ // server client-side
106
+ function requestSuggestions(query, fetchArgs = {}) {
107
+ return fetch('./suggestions.json', fetchArgs)
108
+ .then((response) => response.json())
109
+ }
110
+
111
+ // We'll wrap that function multiple times, each enhancing the previous
112
+ // wrapping to handle the the different behaviours necessary to
113
+ // appropriately coordinate requests to the server and display feedback to
114
+ // users
115
+ const makeRequest =
116
+ // Wrapping everything is the error handling to make sure it catches
117
+ // errors from any of the other wrappers
118
+ trackErrors(
119
+ // Next up is tracking whether we're loading new results
120
+ trackLoading(
121
+ // To avoid overloading the server with potentially costly requests
122
+ // as well as avoid wasting bandwidth while users are typing we'll
123
+ // only send requests a little bit after they stop typing
124
+ debounce(
125
+ // Finally we want to cancel requests that are already sent, so
126
+ // only the results of the last one update the UI This is the role
127
+ // of the next two wrappers
128
+ abortExisting(
129
+ // That last one is for demo only, to simulate server behaviours
130
+ // (latency, errors, filtering) on the client
131
+ simulateServer(
132
+ requestSuggestions
133
+ )
134
+ ),
135
+ 250
136
+ )
137
+ )
138
+ );
139
+
140
+ // We can then use the function we built and adapt it to the autocomplete
141
+ // API encapsulating the adjustments specific to rendering the 'No result
142
+ // found' message
143
+ function source(query, populateResults) {
144
+ // Start by clearing the results to ensure a loading message
145
+ // shows when a the query gets updated after results have loaded
146
+ populateResults([])
147
+
148
+ makeRequest(query)
149
+ // Only update the results if an actual array of options get returned
150
+ // allowing for `makeRequest` to avoid making updates to results being
151
+ // already displayed by resolving to `undefined`, like when we're
152
+ // aborting requests
153
+ .then(options => options && populateResults(options))
154
+ // In case of errors, we need to clear the results so the accessible
155
+ // autocomplate show its 'No result found'
156
+ .catch(error => populateResults([]))
157
+ }
158
+
159
+ // And finally we can set up our accessible autocomplete
160
+ const element = document.querySelector('.autocomplete-wrapper')
161
+ const id = 'autocomplete-default'
162
+ accessibleAutocomplete({
163
+ element: element,
164
+ id: id,
165
+ source: source,
166
+ tNoResults: tNoResults,
167
+ menuAttributes: {
168
+ "aria-labelledby": id
169
+ },
170
+ inputClasses: "govuk-input"
171
+ })
172
+
173
+ ////
174
+ // INTERNAL DETAILS
175
+ ////
176
+
177
+ // Technically, it'd be the server doing the filtering but for lack of
178
+ // server, we're requesting the whole list and filter client-side.
179
+ // Similarly, we'll use a specific query to trigger error for demo
180
+ // purpose, which will be easier than going in the devtools and making the
181
+ // request error We'll also simulate that the server takes a little time
182
+ // to respond to make things more visible in the UI
183
+ const SERVER_LATENCY = 2500;
184
+ function simulateServer(fn) {
185
+ return function(query, ...args) {
186
+ return new Promise(resolve => {
187
+ setTimeout(() => {
188
+ const suggestions = fn(query, ...args)
189
+ .then((response) => {
190
+ if (query === 'trigger-error') {
191
+ throw new Error('Custom error')
192
+ }
193
+ return response;
194
+ })
195
+ .then(countries => {
196
+ return countries.filter(country => country.toLowerCase().indexOf(query.toLowerCase()) !== -1)
197
+ })
198
+
199
+ resolve(suggestions)
200
+ }, SERVER_LATENCY)
201
+ })
202
+ }
203
+ }
204
+
205
+ // Debouncing limits the number of requests being sent
206
+ // but does not guarantee the order in which the responses come in
207
+ // Due to network and server latency, a response to an earlier request
208
+ // may come back after a response to a later request
209
+ // This keeps track of the AbortController of the last request sent
210
+ // so it can be cancelled before sending a new one
211
+ //
212
+ // NOTE: If you're using `XMLHttpRequest`s or a custom library,
213
+ // they'll have a different mechanism for aborting. You can either:
214
+ // - adapt this function to store whatever object lets you abort in-flight requests
215
+ // - or adapt your version of `requestSuggestion` to listen to the `signal`
216
+ // that this function passes to the wrapped function and trigger
217
+ // whichever API for aborting you have available
218
+ // See: https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal#events
219
+ let abortController;
220
+ function abortExisting(fn) {
221
+ return function(...args) {
222
+ if (abortController) {
223
+ abortController.abort();
224
+ }
225
+
226
+ abortController = new AbortController();
227
+
228
+ return fn(...args, {signal: abortController.signal})
229
+ .then(result => {
230
+ abortController = null;
231
+ return result;
232
+ }, error => {
233
+ // Aborting requests will lead to `fetch` rejecting with an
234
+ // `AbortError` In that situation, that's something we expect, so
235
+ // we don't want to show a message to users
236
+ if (error.name !== 'AbortError') {
237
+ abortController = null;
238
+ throw error;
239
+ }
240
+ })
241
+ }
242
+ }
243
+
244
+ // Debounces the given function so it only gets executed after a specific delay
245
+ function debounce(fn, wait) {
246
+ let timeout
247
+ return function (...args) {
248
+ return new Promise(resolve => {
249
+ clearTimeout(timeout)
250
+
251
+ const later = function () {
252
+ timeout = null
253
+ resolve(fn(...args))
254
+ }
255
+ timeout = setTimeout(later, wait)
256
+ })
257
+ }
258
+ }
259
+
260
+
261
+ // Tracks the loading state so we can adapt the message being displayed to the user
262
+ function trackLoading(fn) {
263
+ return function(...args) {
264
+ status = 'loading';
265
+ return fn(...args)
266
+ .then(result => {
267
+ status = null;
268
+ return result
269
+ }, error => {
270
+ status = null;
271
+ throw error
272
+ })
273
+ }
274
+ }
275
+
276
+ // In a similar fashion, we can track errors happening, which will adjust the message
277
+ function trackErrors(fn) {
278
+ return function(...args) {
279
+ return fn(...args)
280
+ .catch(error => {
281
+ status = 'error'
282
+ throw error
283
+ })
284
+ }
285
+ }
286
+ </script>
287
+
288
+ <script>
289
+ var queryStringParameters = window.location.search
290
+ var previouslySubmitted = queryStringParameters.length > 0
291
+ if (previouslySubmitted) {
292
+ var submittedEl = document.querySelector('.submitted')
293
+ submittedEl.classList.remove('submitted--hidden')
294
+ var params = new URLSearchParams(document.location.search.split('?')[1])
295
+ document.querySelector('.submitted__last-location').innerHTML = params.get('last-location')
296
+ document.querySelector('.submitted__passport-location').innerHTML = params.get('passport-location')
297
+ }
298
+ </script>
299
+ </body>
300
+ </html>
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <title>Accessible Autocomplete form example</title>
6
+ <title>Accessible Autocomplete single field form example</title>
7
7
  <style>
8
8
  /* Example page specific styling. */
9
9
  html {
@@ -55,7 +55,9 @@
55
55
  </head>
56
56
  <body>
57
57
  <main>
58
- <h1>Accessible Autocomplete form example</h1>
58
+ <h1>Accessible Autocomplete single field form example</h1>
59
+
60
+ <p><a href="ajax-source.html">Another HTML form example, simulating an AJAX request</a></p>
59
61
 
60
62
  <div class="submitted submitted--hidden">
61
63
  <p>You submitted:</p>
@@ -470,10 +470,7 @@
470
470
  accessibleAutocomplete({
471
471
  element: element,
472
472
  id: id,
473
- source: countries,
474
- menuAttributes: {
475
- "aria-labelledby": id
476
- }
473
+ source: countries
477
474
  })
478
475
  </script>
479
476
 
@@ -484,10 +481,7 @@
484
481
  element: element,
485
482
  id: id,
486
483
  minLength: 2,
487
- source: countries,
488
- menuAttributes: {
489
- "aria-labelledby": id
490
- }
484
+ source: countries
491
485
  })
492
486
  </script>
493
487
 
@@ -498,10 +492,7 @@
498
492
  autoselect: true,
499
493
  element: element,
500
494
  id: id,
501
- source: countries,
502
- menuAttributes: {
503
- "aria-labelledby": id
504
- }
495
+ source: countries
505
496
  })
506
497
  </script>
507
498
 
@@ -512,10 +503,7 @@
512
503
  displayMenu: 'overlay',
513
504
  element: element,
514
505
  id: id,
515
- source: countries,
516
- menuAttributes: {
517
- "aria-labelledby": id
518
- }
506
+ source: countries
519
507
  })
520
508
  </script>
521
509
 
@@ -526,10 +514,7 @@
526
514
  defaultValue: 'Germany',
527
515
  element: element,
528
516
  id: id,
529
- source: countries,
530
- menuAttributes: {
531
- "aria-labelledby": id
532
- }
517
+ source: countries
533
518
  })
534
519
  </script>
535
520
 
@@ -540,9 +525,6 @@
540
525
  element: element,
541
526
  id: id,
542
527
  source: countries,
543
- menuAttributes: {
544
- "aria-labelledby": id
545
- },
546
528
  autoselect: true,
547
529
  inputClasses: 'app-input'
548
530
  })
@@ -555,9 +537,6 @@
555
537
  element: element,
556
538
  id: id,
557
539
  source: countries,
558
- menuAttributes: {
559
- "aria-labelledby": id
560
- },
561
540
  autoselect: true,
562
541
  hintClasses: 'app-hint'
563
542
  })
@@ -570,9 +549,6 @@
570
549
  element: element,
571
550
  id: id,
572
551
  source: countries,
573
- menuAttributes: {
574
- "aria-labelledby": id
575
- },
576
552
  autoselect: true,
577
553
  menuClasses: 'custom-menu-class'
578
554
  })
@@ -617,9 +593,6 @@
617
593
  element: element,
618
594
  id: id,
619
595
  source: customSuggest,
620
- menuAttributes: {
621
- "aria-labelledby": id
622
- },
623
596
  templates: {
624
597
  inputValue: inputValueTemplate,
625
598
  suggestion: suggestionTemplate
@@ -635,9 +608,6 @@
635
608
  id: id,
636
609
  confirmOnBlur: false,
637
610
  source: countries,
638
- menuAttributes: {
639
- "aria-labelledby": id
640
- }
641
611
  })
642
612
  </script>
643
613
 
@@ -649,9 +619,6 @@
649
619
  id: id,
650
620
  placeholder: 'Search for a country',
651
621
  source: countries,
652
- menuAttributes: {
653
- "aria-labelledby": id
654
- }
655
622
  })
656
623
  </script>
657
624
 
@@ -662,10 +629,7 @@
662
629
  element: element,
663
630
  id: id,
664
631
  showNoOptionsFound: false,
665
- source: countries,
666
- menuAttributes: {
667
- "aria-labelledby": id
668
- }
632
+ source: countries
669
633
  })
670
634
  </script>
671
635
 
@@ -676,10 +640,7 @@
676
640
  element: element,
677
641
  id: id,
678
642
  showAllValues: true,
679
- source: countries,
680
- menuAttributes: {
681
- "aria-labelledby": id
682
- }
643
+ source: countries
683
644
  })
684
645
  </script>
685
646
 
@@ -690,9 +651,6 @@
690
651
  element: element,
691
652
  id: id,
692
653
  showAllValues: false,
693
- menuAttributes: {
694
- "aria-labelledby": id
695
- },
696
654
  source: countries,
697
655
  tStatusQueryTooShort: function (minQueryLength) {
698
656
  return 'Tippe mindestens ' + minQueryLength + ' Zeichen ein für Resultate.'
@@ -725,9 +683,6 @@
725
683
  id: id,
726
684
  showAllValues: true,
727
685
  source: countries,
728
- menuAttributes: {
729
- "aria-labelledby": id
730
- },
731
686
  dropdownArrow: function (config) {
732
687
  return '<svg class="' + config.className + '" style="top: 8px;" viewBox="0 0 512 512" ><path d="M256,298.3L256,298.3L256,298.3l174.2-167.2c4.3-4.2,11.4-4.1,15.8,0.2l30.6,29.9c4.4,4.3,4.5,11.3,0.2,15.5L264.1,380.9 c-2.2,2.2-5.2,3.2-8.1,3c-3,0.1-5.9-0.9-8.1-3L35.2,176.7c-4.3-4.2-4.2-11.2,0.2-15.5L66,131.3c4.4-4.3,11.5-4.4,15.8-0.2L256,298.3 z"/></svg>'
733
688
  }