@ministryofjustice/hmpps-connect-dps-shared-items 5.1.0 → 5.2.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 (26) hide show
  1. package/dist/assets/dps/components/address-autosuggest/_address-autosuggest.scss +7 -0
  2. package/dist/assets/dps/components/address-autosuggest/abortable-fetch.d.ts +26 -0
  3. package/dist/assets/dps/components/address-autosuggest/abortable-fetch.js +20 -8
  4. package/dist/assets/dps/components/address-autosuggest/abortable-fetch.ts +76 -0
  5. package/dist/assets/dps/components/address-autosuggest/autosuggest-address.d.ts +18 -0
  6. package/dist/assets/dps/components/address-autosuggest/autosuggest-address.js +27 -13
  7. package/dist/assets/dps/components/address-autosuggest/autosuggest-address.ts +108 -0
  8. package/dist/assets/dps/components/address-autosuggest/autosuggest-ui.d.ts +82 -0
  9. package/dist/assets/dps/components/address-autosuggest/autosuggest-ui.js +83 -53
  10. package/dist/assets/dps/components/address-autosuggest/autosuggest-ui.ts +459 -0
  11. package/dist/assets/dps/components/address-autosuggest/index.d.ts +1 -0
  12. package/dist/assets/dps/components/address-autosuggest/index.js +4 -4
  13. package/dist/assets/dps/components/address-autosuggest/index.ts +9 -0
  14. package/dist/assets/dps/components/alert-flag/_alert-flag.scss +2 -1
  15. package/dist/assets/dps/components/modal/index.d.ts +34 -0
  16. package/dist/assets/dps/components/modal/macro.njk +2 -1
  17. package/dist/assets/dps/components/webcam-capture/_webcam-capture.scss +2 -2
  18. package/dist/assets/dps/components/webcam-capture/index.d.ts +1 -0
  19. package/dist/assets/dps/components/webcam-capture/index.js +14 -8
  20. package/dist/assets/dps/components/webcam-capture/index.ts +165 -0
  21. package/dist/assets/js/all.d.ts +1 -0
  22. package/dist/assets/js/all.ts +10 -0
  23. package/dist/assets/scss/all.css +66 -16
  24. package/dist/index.d.ts +10 -1
  25. package/dist/types/public/addressAutosuggest/addressAutosuggestResponse.d.ts +9 -0
  26. package/package.json +4 -4
@@ -1,6 +1,7 @@
1
1
  import { abortError, abortTimeout } from './abortable-fetch.js';
2
2
 
3
3
  // Based on ONSDigital's https://github.com/ONSdigital/design-system/blob/main/src/components/autosuggest/autosuggest.ui.js
4
+ /* eslint-disable no-param-reassign, @stylistic/lines-between-class-members */
4
5
  const jsBaseClass = 'hmpps-js-autosuggest';
5
6
  const classSuffixInitialised = '--initialised';
6
7
  const classSuffixOption = '__option';
@@ -13,6 +14,38 @@ const NoResults = {
13
14
  server_error: 'SERVER_ERROR',
14
15
  };
15
16
  class AutosuggestUi {
17
+ context;
18
+ input;
19
+ resultsContainer;
20
+ resultsTitleContainer;
21
+ listbox;
22
+ instructions;
23
+ errorContainer;
24
+ errorTextContainer;
25
+ ariaStatus;
26
+ minChars;
27
+ ariaYouHaveSelected;
28
+ ariaMinChars;
29
+ ariaOneResult;
30
+ ariaNResults;
31
+ noResults;
32
+ typeMore;
33
+ listboxId;
34
+ errorMessage;
35
+ stylingBaseClass;
36
+ fetchSuggestions;
37
+ onSelect;
38
+ query;
39
+ sanitisedQuery;
40
+ results;
41
+ resultOptions;
42
+ numberOfResults;
43
+ blurring;
44
+ blurTimeout;
45
+ scrolling;
46
+ scrollTimeout;
47
+ inputTimeout;
48
+ highlightedResultIndex;
16
49
  constructor({ context, onSelect, suggestionFunction }) {
17
50
  // DOM Elements
18
51
  this.context = context;
@@ -26,15 +59,15 @@ class AutosuggestUi {
26
59
  this.ariaStatus = context.querySelector(`.${jsBaseClass}-aria-status`);
27
60
  // Settings
28
61
  this.minChars = 3;
29
- this.ariaYouHaveSelected = context.getAttribute('data-aria-you-have-selected');
30
- this.ariaMinChars = context.getAttribute('data-aria-min-chars');
31
- this.ariaOneResult = context.getAttribute('data-aria-one-result');
32
- this.ariaNResults = context.getAttribute('data-aria-n-results');
33
- this.noResults = context.getAttribute('data-no-results');
34
- this.typeMore = context.getAttribute('data-type-more');
35
- this.listboxId = this.listbox.getAttribute('id');
36
- this.errorMessage = context.getAttribute('data-error-api');
37
- this.stylingBaseClass = context.getAttribute('data-styling-base-class');
62
+ this.ariaYouHaveSelected = context.dataset.ariaYouHaveSelected;
63
+ this.ariaMinChars = context.dataset.ariaMinChars;
64
+ this.ariaOneResult = context.dataset.ariaOneResult;
65
+ this.ariaNResults = context.dataset.ariaNResults;
66
+ this.noResults = context.dataset.noResults;
67
+ this.typeMore = context.dataset.typeMore;
68
+ this.errorMessage = context.dataset.errorApi;
69
+ this.stylingBaseClass = context.dataset.stylingBaseClass;
70
+ this.listboxId = this.listbox.id;
38
71
  // Callbacks
39
72
  this.onSelect = onSelect;
40
73
  this.fetchSuggestions = suggestionFunction;
@@ -43,22 +76,22 @@ class AutosuggestUi {
43
76
  this.sanitisedQuery = '';
44
77
  this.results = [];
45
78
  this.resultOptions = [];
46
- this.data = [];
47
79
  this.numberOfResults = 0;
48
80
  this.highlightedResultIndex = null;
49
81
  this.blurring = false;
50
- this.blurTimeout = null;
82
+ this.blurTimeout = 0;
51
83
  this.scrolling = false;
52
- this.scrollTimeout = null;
84
+ this.scrollTimeout = 0;
85
+ this.inputTimeout = 0;
53
86
  this.initialiseUI();
54
87
  }
55
88
  initialiseUI() {
56
89
  this.input.setAttribute('aria-autocomplete', 'list');
57
- this.input.setAttribute('aria-controls', this.listbox.getAttribute('id'));
58
- this.input.setAttribute('aria-describedby', this.instructions.getAttribute('id'));
59
- this.input.setAttribute('aria-haspopup', true);
60
- this.input.setAttribute('aria-owns', this.listbox.getAttribute('id'));
61
- this.input.setAttribute('aria-expanded', false);
90
+ this.input.setAttribute('aria-controls', this.listbox.id);
91
+ this.input.setAttribute('aria-describedby', this.instructions.id);
92
+ this.input.setAttribute('aria-haspopup', 'true');
93
+ this.input.setAttribute('aria-owns', this.listbox.id);
94
+ this.input.setAttribute('aria-expanded', 'false');
62
95
  this.input.setAttribute('autocomplete', 'off');
63
96
  this.input.setAttribute('role', 'combobox');
64
97
  this.context.classList.add(`${this.stylingBaseClass}${classSuffixInitialised}`);
@@ -72,26 +105,23 @@ class AutosuggestUi {
72
105
  this.input.addEventListener('focus', this.handleFocus.bind(this));
73
106
  }
74
107
  handleKeydown(event) {
75
- switch (event.keyCode) {
76
- case 38: {
77
- // Up
108
+ // eslint-disable-next-line default-case
109
+ switch (event.key) {
110
+ case 'ArrowUp': {
78
111
  event.preventDefault();
79
112
  this.navigateResults(-1);
80
113
  break;
81
114
  }
82
- case 40: {
83
- // Down
115
+ case 'ArrowDown': {
84
116
  event.preventDefault();
85
117
  this.navigateResults(1);
86
118
  break;
87
119
  }
88
- case 27: {
89
- // Escape
120
+ case 'Escape': {
90
121
  this.clearListbox();
91
122
  break;
92
123
  }
93
- case 13: {
94
- // Enter
124
+ case 'Enter': {
95
125
  if (this.highlightedResultIndex !== null) {
96
126
  event.preventDefault();
97
127
  }
@@ -100,15 +130,14 @@ class AutosuggestUi {
100
130
  }
101
131
  }
102
132
  handleKeyup(event) {
103
- switch (event.keyCode) {
104
- // Up and down
105
- case 40:
106
- case 38: {
133
+ // eslint-disable-next-line default-case
134
+ switch (event.key) {
135
+ case 'ArrowUp':
136
+ case 'ArrowDown': {
107
137
  event.preventDefault();
108
138
  break;
109
139
  }
110
- case 13: {
111
- // Enter
140
+ case 'Enter': {
112
141
  if (this.highlightedResultIndex !== null) {
113
142
  this.selectResult();
114
143
  }
@@ -116,7 +145,7 @@ class AutosuggestUi {
116
145
  }
117
146
  }
118
147
  }
119
- handleChange() {
148
+ handleChange(_event) {
120
149
  if (!this.blurring && this.input.value.trim()) {
121
150
  this.getSuggestions();
122
151
  }
@@ -124,21 +153,21 @@ class AutosuggestUi {
124
153
  this.clearListbox();
125
154
  }
126
155
  }
127
- handleBlur() {
156
+ handleBlur(_event) {
128
157
  clearTimeout(this.blurTimeout);
129
158
  this.blurring = true;
130
159
  // Timeout required to allow user to click an option before clearing the listbox:
131
160
  this.blurTimeout = setTimeout(() => {
132
161
  this.blurring = false;
133
162
  this.context.classList.remove(`${this.stylingBaseClass}${classSuffixHasResults}`);
134
- this.input.setAttribute('aria-expanded', false);
163
+ this.input.setAttribute('aria-expanded', 'false');
135
164
  this.setAriaStatus();
136
165
  }, 300);
137
166
  }
138
- handleFocus() {
167
+ handleFocus(_event) {
139
168
  if (this.listbox.innerHTML) {
140
169
  this.context.classList.add(`${this.stylingBaseClass}${classSuffixHasResults}`);
141
- this.input.setAttribute('aria-expanded', true);
170
+ this.input.setAttribute('aria-expanded', 'true');
142
171
  this.setAriaStatus();
143
172
  }
144
173
  }
@@ -178,7 +207,7 @@ class AutosuggestUi {
178
207
  sanitisedString = sanitisedString.trim();
179
208
  sanitisedString = sanitisedString.replace(/,/g, '');
180
209
  sanitisedString = sanitisedString.replace(/\s\s+/g, ' ');
181
- sanitisedString = sanitisedString.replace(/[&]/g, '%26');
210
+ sanitisedString = sanitisedString.replace(/&/g, '%26');
182
211
  sanitisedString = sanitisedString.replace(/\d(?=[a-z]{3,})/gi, '$& ');
183
212
  return sanitisedString;
184
213
  }
@@ -190,10 +219,11 @@ class AutosuggestUi {
190
219
  this.unsetResults();
191
220
  this.checkCharCount();
192
221
  if (this.sanitisedQuery.length >= this.minChars) {
193
- this.fetchSuggestions(this.sanitisedQuery, this.data)
222
+ this.fetchSuggestions(this.sanitisedQuery)
194
223
  .then(this.handleResults.bind(this))
195
224
  .catch(error => {
196
225
  if (error.name !== abortError && error.reason !== abortTimeout) {
226
+ // eslint-disable-next-line no-console
197
227
  console.log('error:', error);
198
228
  this.handleNoResults(NoResults.server_error);
199
229
  }
@@ -212,12 +242,12 @@ class AutosuggestUi {
212
242
  this.listbox.innerHTML = '';
213
243
  this.context.classList.remove(`${this.stylingBaseClass}${classSuffixHasResults}`);
214
244
  this.input.removeAttribute('aria-activedescendant');
215
- this.input.setAttribute('aria-expanded', false);
245
+ this.input.setAttribute('aria-expanded', 'false');
216
246
  this.setHighlightedResult(null);
217
247
  this.setAriaStatus();
218
248
  }
219
- handleResults(result) {
220
- this.results = result.results;
249
+ handleResults(response) {
250
+ this.results = response?.results;
221
251
  this.numberOfResults = this.results?.length || 0;
222
252
  this.setAriaStatus();
223
253
  this.listbox.innerHTML = '';
@@ -242,7 +272,7 @@ class AutosuggestUi {
242
272
  }
243
273
  }
244
274
  createListElement(result, index) {
245
- const innerHTML = this.emboldenMatch(result.displayText, this.query);
275
+ const innerHTML = this.emboldenMatch(result.displayText ?? '', this.query);
246
276
  const listElement = document.createElement('li');
247
277
  listElement.className = `${this.stylingBaseClass}${classSuffixOption}`;
248
278
  listElement.setAttribute('id', `${this.listboxId}__option--${index}`);
@@ -255,10 +285,10 @@ class AutosuggestUi {
255
285
  return listElement;
256
286
  }
257
287
  handleNoResults(reason) {
258
- if ([NoResults.type_more, NoResults.no_match].includes(reason)) {
288
+ if (reason === NoResults.type_more || reason === NoResults.no_match) {
259
289
  this.context.classList.add(`${this.stylingBaseClass}${classSuffixHasResults}`);
260
290
  this.resultsTitleContainer?.classList?.add('hmpps-display-none');
261
- this.input.setAttribute('aria-expanded', true);
291
+ this.input.setAttribute('aria-expanded', 'true');
262
292
  const message = reason === NoResults.type_more ? this.typeMore : this.noResults;
263
293
  this.setAriaStatus(message);
264
294
  this.listbox.innerHTML = '';
@@ -273,7 +303,7 @@ class AutosuggestUi {
273
303
  }
274
304
  displayAPIError() {
275
305
  this.input.value = '';
276
- this.input.setAttribute('disabled', true);
306
+ this.input.setAttribute('disabled', 'true');
277
307
  this.clearListbox();
278
308
  this.errorContainer.classList.remove('hmpps-display-none');
279
309
  this.errorTextContainer.textContent = this.errorMessage;
@@ -295,8 +325,8 @@ class AutosuggestUi {
295
325
  this.handleScroll(option, false);
296
326
  }
297
327
  option.classList.add(`${this.stylingBaseClass}${classSuffixOptionFocused}`);
298
- option.setAttribute('aria-selected', true);
299
- this.input.setAttribute('aria-activedescendant', option.getAttribute('id'));
328
+ option.setAttribute('aria-selected', 'true');
329
+ this.input.setAttribute('aria-activedescendant', option.id);
300
330
  const optionText = option.innerHTML.replace('<b>', '').replace('</b>', '');
301
331
  this.setAriaStatus(optionText);
302
332
  }
@@ -308,12 +338,12 @@ class AutosuggestUi {
308
338
  }
309
339
  }
310
340
  selectResult(index) {
311
- if (!this.results.length)
341
+ if (!this.results?.length)
312
342
  return;
313
343
  const selectedIndex = typeof index === 'number' ? index : this.highlightedResultIndex;
314
344
  if (selectedIndex === null || selectedIndex === undefined)
315
345
  return;
316
- const result = this.results[selectedIndex];
346
+ const result = this.results?.[selectedIndex];
317
347
  if (!result)
318
348
  return;
319
349
  this.onSelect(result);
@@ -334,7 +364,7 @@ class AutosuggestUi {
334
364
  content = this.ariaOneResult;
335
365
  }
336
366
  else {
337
- content = this.ariaNResults.replace('{n}', this.numberOfResults);
367
+ content = this.ariaNResults.replace('{n}', `${this.numberOfResults}`);
338
368
  }
339
369
  }
340
370
  this.ariaStatus.textContent = content;
@@ -345,7 +375,7 @@ class AutosuggestUi {
345
375
  .replace(/\s\s+/g, ' ')
346
376
  .split(' ')
347
377
  .reduce((accumulator, currentValue) => {
348
- let reg = new RegExp(`(?<!<)${currentValue}(?![\\w\\s]*>)`, 'gi');
378
+ const reg = new RegExp(`(?<!<)${currentValue}(?![\\w\\s]*>)`, 'gi');
349
379
  return accumulator.replace(reg, '<b>$&</b>');
350
380
  }, string);
351
381
  }
@@ -360,4 +390,4 @@ class AutosuggestUi {
360
390
  }
361
391
  }
362
392
 
363
- export { AutosuggestUi, NoResults };
393
+ export { NoResults, AutosuggestUi as default };