@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.
- package/dist/assets/dps/components/address-autosuggest/_address-autosuggest.scss +7 -0
- package/dist/assets/dps/components/address-autosuggest/abortable-fetch.d.ts +26 -0
- package/dist/assets/dps/components/address-autosuggest/abortable-fetch.js +20 -8
- package/dist/assets/dps/components/address-autosuggest/abortable-fetch.ts +76 -0
- package/dist/assets/dps/components/address-autosuggest/autosuggest-address.d.ts +18 -0
- package/dist/assets/dps/components/address-autosuggest/autosuggest-address.js +27 -13
- package/dist/assets/dps/components/address-autosuggest/autosuggest-address.ts +108 -0
- package/dist/assets/dps/components/address-autosuggest/autosuggest-ui.d.ts +82 -0
- package/dist/assets/dps/components/address-autosuggest/autosuggest-ui.js +83 -53
- package/dist/assets/dps/components/address-autosuggest/autosuggest-ui.ts +459 -0
- package/dist/assets/dps/components/address-autosuggest/index.d.ts +1 -0
- package/dist/assets/dps/components/address-autosuggest/index.js +4 -4
- package/dist/assets/dps/components/address-autosuggest/index.ts +9 -0
- package/dist/assets/dps/components/alert-flag/_alert-flag.scss +2 -1
- package/dist/assets/dps/components/modal/index.d.ts +34 -0
- package/dist/assets/dps/components/modal/macro.njk +2 -1
- package/dist/assets/dps/components/webcam-capture/_webcam-capture.scss +2 -2
- package/dist/assets/dps/components/webcam-capture/index.d.ts +1 -0
- package/dist/assets/dps/components/webcam-capture/index.js +14 -8
- package/dist/assets/dps/components/webcam-capture/index.ts +165 -0
- package/dist/assets/js/all.d.ts +1 -0
- package/dist/assets/js/all.ts +10 -0
- package/dist/assets/scss/all.css +66 -16
- package/dist/index.d.ts +10 -1
- package/dist/types/public/addressAutosuggest/addressAutosuggestResponse.d.ts +9 -0
- 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.
|
|
30
|
-
this.ariaMinChars = context.
|
|
31
|
-
this.ariaOneResult = context.
|
|
32
|
-
this.ariaNResults = context.
|
|
33
|
-
this.noResults = context.
|
|
34
|
-
this.typeMore = context.
|
|
35
|
-
this.
|
|
36
|
-
this.
|
|
37
|
-
this.
|
|
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 =
|
|
82
|
+
this.blurTimeout = 0;
|
|
51
83
|
this.scrolling = false;
|
|
52
|
-
this.scrollTimeout =
|
|
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.
|
|
58
|
-
this.input.setAttribute('aria-describedby', this.instructions.
|
|
59
|
-
this.input.setAttribute('aria-haspopup', true);
|
|
60
|
-
this.input.setAttribute('aria-owns', this.listbox.
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
|
83
|
-
// Down
|
|
115
|
+
case 'ArrowDown': {
|
|
84
116
|
event.preventDefault();
|
|
85
117
|
this.navigateResults(1);
|
|
86
118
|
break;
|
|
87
119
|
}
|
|
88
|
-
case
|
|
89
|
-
// Escape
|
|
120
|
+
case 'Escape': {
|
|
90
121
|
this.clearListbox();
|
|
91
122
|
break;
|
|
92
123
|
}
|
|
93
|
-
case
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
case
|
|
106
|
-
case
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
220
|
-
this.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 (
|
|
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.
|
|
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
|
|
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
|
-
|
|
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 {
|
|
393
|
+
export { NoResults, AutosuggestUi as default };
|