@ministryofjustice/hmpps-connect-dps-shared-items 2.0.0 → 2.1.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 (27) hide show
  1. package/README.md +34 -11
  2. package/dist/addressAutosuggest/osPlacesAddressService.d.ts +14 -0
  3. package/dist/addressAutosuggest/osPlacesAddressService.test.d.ts +1 -0
  4. package/dist/addressAutosuggest/osPlacesApiClient.d.ts +13 -0
  5. package/dist/addressAutosuggest/osPlacesApiClient.test.d.ts +1 -0
  6. package/dist/addressAutosuggest/testMocks/osPlacesAddressQueryResponse.d.ts +12 -0
  7. package/dist/addressAutosuggest/testMocks/osPlacesApiClientMock.d.ts +2 -0
  8. package/dist/assets/dps/components/address-autosuggest/_address-autosuggest.scss +98 -0
  9. package/dist/assets/dps/components/address-autosuggest/abortable-fetch.js +43 -0
  10. package/dist/assets/dps/components/address-autosuggest/autosuggest-address.js +90 -0
  11. package/dist/assets/dps/components/address-autosuggest/autosuggest-ui.js +369 -0
  12. package/dist/assets/dps/components/address-autosuggest/index.js +10 -0
  13. package/dist/assets/dps/components/address-autosuggest/macro.njk +61 -0
  14. package/dist/assets/js/all.js +5 -0
  15. package/dist/assets/scss/all.scss +2 -1
  16. package/dist/index.cjs +128 -0
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.ts +169 -2
  19. package/dist/index.esm.js +127 -1
  20. package/dist/index.esm.js.map +1 -1
  21. package/dist/types/public/ConnectDpsComponentLogger.d.ts +2 -0
  22. package/dist/types/public/addressAutosuggest/index.d.ts +2 -0
  23. package/dist/types/public/addressAutosuggest/osAddress.d.ts +13 -0
  24. package/dist/types/public/addressAutosuggest/osPlaces.d.ts +124 -0
  25. package/dist/utils/utils.d.ts +8 -0
  26. package/package.json +10 -2
  27. package/readme/publishing.md +3 -3
@@ -0,0 +1,369 @@
1
+ // Based on ONSDigital's https://github.com/ONSdigital/design-system/blob/main/src/components/autosuggest/autosuggest.ui.js
2
+ import { abortError, abortTimeout } from './abortable-fetch'
3
+
4
+ const jsBaseClass = 'hmpps-js-autosuggest'
5
+
6
+ const classSuffixInitialised = '--initialised'
7
+ const classSuffixOption = '__option'
8
+ const classSuffixOptionFocused = `${classSuffixOption}--focused`
9
+ const classSuffixOptionNoResults = `${classSuffixOption}--no-results`
10
+ const classSuffixHasResults = '--has-results'
11
+
12
+ export const NoResults = {
13
+ type_more: 'TYPE_MORE',
14
+ no_match: 'NO_MATCH',
15
+ server_error: 'SERVER_ERROR',
16
+ }
17
+
18
+ export class AutosuggestUi {
19
+ constructor({ context, onSelect, suggestionFunction }) {
20
+ // DOM Elements
21
+ this.context = context
22
+ this.input = context.querySelector(`.${jsBaseClass}-input`)
23
+ this.resultsContainer = context.querySelector(`.${jsBaseClass}-results`)
24
+ this.resultsTitleContainer = this.resultsContainer.querySelector(`.${jsBaseClass}-results-title`)
25
+ this.listbox = this.resultsContainer.querySelector(`.${jsBaseClass}-listbox`)
26
+ this.instructions = context.querySelector(`.${jsBaseClass}-instructions`)
27
+ this.errorContainer = context.querySelector(`.${jsBaseClass}-error`)
28
+ this.errorTextContainer = this.errorContainer.querySelector('.moj-alert__content')
29
+ this.ariaStatus = context.querySelector(`.${jsBaseClass}-aria-status`)
30
+
31
+ // Settings
32
+ this.minChars = 3
33
+ this.ariaYouHaveSelected = context.getAttribute('data-aria-you-have-selected')
34
+ this.ariaMinChars = context.getAttribute('data-aria-min-chars')
35
+ this.ariaOneResult = context.getAttribute('data-aria-one-result')
36
+ this.ariaNResults = context.getAttribute('data-aria-n-results')
37
+ this.noResults = context.getAttribute('data-no-results')
38
+ this.typeMore = context.getAttribute('data-type-more')
39
+ this.listboxId = this.listbox.getAttribute('id')
40
+ this.errorMessage = context.getAttribute('data-error-api')
41
+ this.stylingBaseClass = context.getAttribute('data-styling-base-class')
42
+
43
+ // Callbacks
44
+ this.onSelect = onSelect
45
+ this.fetchSuggestions = suggestionFunction
46
+
47
+ // State
48
+ this.query = ''
49
+ this.sanitisedQuery = ''
50
+ this.results = []
51
+ this.resultOptions = []
52
+ this.data = []
53
+ this.numberOfResults = 0
54
+ this.highlightedResultIndex = 0
55
+ this.blurring = false
56
+ this.blurTimeout = null
57
+ this.scrolling = false
58
+ this.scrollTimeout = null
59
+
60
+ this.initialiseUI()
61
+ }
62
+
63
+ initialiseUI() {
64
+ this.input.setAttribute('aria-autocomplete', 'list')
65
+ this.input.setAttribute('aria-controls', this.listbox.getAttribute('id'))
66
+ this.input.setAttribute('aria-describedby', this.instructions.getAttribute('id'))
67
+ this.input.setAttribute('aria-haspopup', true)
68
+ this.input.setAttribute('aria-owns', this.listbox.getAttribute('id'))
69
+ this.input.setAttribute('aria-expanded', false)
70
+ this.input.setAttribute('autocomplete', 'off')
71
+ this.input.setAttribute('role', 'combobox')
72
+
73
+ this.context.classList.add(`${this.stylingBaseClass}${classSuffixInitialised}`)
74
+
75
+ this.bindEventListeners()
76
+ }
77
+
78
+ bindEventListeners() {
79
+ this.input.addEventListener('keydown', this.handleKeydown.bind(this))
80
+ this.input.addEventListener('keyup', this.handleKeyup.bind(this))
81
+ this.input.addEventListener('input', this.handleChange.bind(this))
82
+ this.input.addEventListener('blur', this.handleBlur.bind(this))
83
+ }
84
+
85
+ handleKeydown(event) {
86
+ switch (event.keyCode) {
87
+ case 38: {
88
+ // Up
89
+ event.preventDefault()
90
+ this.navigateResults(-1)
91
+ break
92
+ }
93
+ case 40: {
94
+ // Down
95
+ event.preventDefault()
96
+ this.navigateResults(1)
97
+ break
98
+ }
99
+ case 13: {
100
+ // Enter
101
+ if (this.highlightedResultIndex !== null) {
102
+ event.preventDefault()
103
+ }
104
+ break
105
+ }
106
+ }
107
+ }
108
+
109
+ handleKeyup(event) {
110
+ switch (event.keyCode) {
111
+ // Up and down
112
+ case 40:
113
+ case 38: {
114
+ event.preventDefault()
115
+ break
116
+ }
117
+ case 13: {
118
+ // Enter
119
+ if (this.highlightedResultIndex !== null) {
120
+ this.selectResult()
121
+ }
122
+ break
123
+ }
124
+ }
125
+ }
126
+
127
+ handleChange() {
128
+ if (!this.blurring && this.input.value.trim()) {
129
+ this.getSuggestions()
130
+ } else {
131
+ this.clearListbox()
132
+ }
133
+ }
134
+
135
+ handleBlur() {
136
+ clearTimeout(this.blurTimeout)
137
+ this.blurring = true
138
+
139
+ // Timeout required to allow user to click an option before clearing the listbox:
140
+ this.blurTimeout = setTimeout(() => {
141
+ this.blurring = false
142
+ this.clearListbox()
143
+ }, 300)
144
+ }
145
+
146
+ handleScroll(option, scrollUp) {
147
+ clearTimeout(this.scrollTimeout)
148
+ this.scrolling = true
149
+ option.scrollIntoView(scrollUp)
150
+
151
+ // Timeout required to allow user to click an option before clearing the listbox:
152
+ this.scrollTimeout = setTimeout(() => {
153
+ this.scrolling = false
154
+ }, 300)
155
+ }
156
+
157
+ checkCharCount() {
158
+ if (this.input.value.length > 1 && this.input.value.length < this.minChars) {
159
+ this.inputTimeout = setTimeout(() => {
160
+ this.handleNoResults(NoResults.type_more)
161
+ }, 2000)
162
+ } else {
163
+ clearTimeout(this.inputTimeout)
164
+ }
165
+ }
166
+
167
+ navigateResults(direction) {
168
+ let index = 0
169
+ if (this.highlightedResultIndex !== null) {
170
+ index = this.highlightedResultIndex + direction
171
+ }
172
+
173
+ if (index < this.numberOfResults) {
174
+ if (index < 0) {
175
+ index = null
176
+ }
177
+
178
+ this.setHighlightedResult(index)
179
+ }
180
+ }
181
+
182
+ sanitiseAutosuggestText(string) {
183
+ let sanitisedString = string.toLowerCase()
184
+
185
+ sanitisedString = sanitisedString.trim()
186
+ sanitisedString = sanitisedString.replace(/,/g, '')
187
+ sanitisedString = sanitisedString.replace(/\s\s+/g, ' ')
188
+ sanitisedString = sanitisedString.replace(/[&]/g, '%26')
189
+ sanitisedString = sanitisedString.replace(/\d(?=[a-z]{3,})/gi, '$& ')
190
+
191
+ return sanitisedString
192
+ }
193
+
194
+ getSuggestions() {
195
+ this.query = this.input.value
196
+
197
+ const sanitisedQuery = this.sanitiseAutosuggestText(this.query)
198
+
199
+ if (sanitisedQuery !== this.sanitisedQuery) {
200
+ this.sanitisedQuery = sanitisedQuery
201
+ this.unsetResults()
202
+ this.checkCharCount()
203
+
204
+ if (this.sanitisedQuery.length >= this.minChars) {
205
+ this.fetchSuggestions(this.sanitisedQuery, this.data)
206
+ .then(this.handleResults.bind(this))
207
+ .catch(error => {
208
+ if (error.name !== abortError && error.reason !== abortTimeout) {
209
+ console.log('error:', error)
210
+ this.handleNoResults(NoResults.server_error)
211
+ }
212
+ })
213
+ } else {
214
+ this.clearListbox()
215
+ }
216
+ }
217
+ }
218
+
219
+ unsetResults() {
220
+ this.results = []
221
+ this.resultOptions = []
222
+ }
223
+
224
+ clearListbox() {
225
+ this.listbox.innerHTML = ''
226
+ this.context.classList.remove(`${this.stylingBaseClass}${classSuffixHasResults}`)
227
+ this.input.removeAttribute('aria-activedescendant')
228
+ this.input.setAttribute('aria-expanded', false)
229
+ this.setAriaStatus()
230
+ }
231
+
232
+ handleResults(result) {
233
+ this.results = result.results
234
+ this.numberOfResults = this.results?.length || 0
235
+ this.setAriaStatus()
236
+ this.listbox.innerHTML = ''
237
+
238
+ this.resultOptions =
239
+ this.results?.map((result, index) => {
240
+ this.resultsTitleContainer?.classList?.remove('hmpps-display-none')
241
+ const listElement = this.createListElement(result, index)
242
+ this.listbox.appendChild(listElement)
243
+ return listElement
244
+ }) ?? []
245
+
246
+ this.setHighlightedResult(0)
247
+ this.input.setAttribute('aria-expanded', this.numberOfResults ? 'true' : 'false')
248
+
249
+ if (!!this.numberOfResults && this.sanitisedQuery.length >= this.minChars) {
250
+ this.context.classList.add(`${this.stylingBaseClass}${classSuffixHasResults}`)
251
+ } else {
252
+ this.context.classList.remove(`${this.stylingBaseClass}${classSuffixHasResults}`)
253
+ this.clearListbox()
254
+ }
255
+
256
+ if (this.numberOfResults === 0 && this.noResults) {
257
+ this.handleNoResults(NoResults.no_match)
258
+ }
259
+ }
260
+
261
+ createListElement(result, index) {
262
+ const innerHTML = this.emboldenMatch(result.displayText, this.query)
263
+ const listElement = document.createElement('li')
264
+ listElement.className = `${this.stylingBaseClass}${classSuffixOption}`
265
+ listElement.setAttribute('id', `${this.listboxId}__option--${index}`)
266
+ listElement.setAttribute('role', 'option')
267
+ listElement.innerHTML = innerHTML
268
+ listElement.addEventListener('click', () => {
269
+ this.selectResult(index)
270
+ })
271
+ listElement.addEventListener('mouseenter', () => !this.scrolling && this.setHighlightedResult(index))
272
+ return listElement
273
+ }
274
+
275
+ handleNoResults(reason) {
276
+ if ([NoResults.type_more, NoResults.no_match].includes(reason)) {
277
+ this.context.classList.add(`${this.stylingBaseClass}${classSuffixHasResults}`)
278
+ this.resultsTitleContainer?.classList?.add('hmpps-display-none')
279
+ this.input.setAttribute('aria-expanded', true)
280
+
281
+ const message = reason === NoResults.type_more ? this.typeMore : this.noResults
282
+ this.setAriaStatus(message)
283
+ this.listbox.innerHTML = `<li class="${this.stylingBaseClass}${classSuffixOption} ${this.stylingBaseClass}${classSuffixOptionNoResults}">${message}</li>`
284
+ } else {
285
+ this.displayAPIError()
286
+ }
287
+ }
288
+
289
+ displayAPIError() {
290
+ this.input.value = ''
291
+ this.input.setAttribute('disabled', true)
292
+ this.clearListbox()
293
+ this.errorContainer.classList.remove('hmpps-display-none')
294
+ this.errorTextContainer.innerHTML = this.errorMessage
295
+ this.ariaStatus.setAttribute('aria-hidden', 'true')
296
+ this.setAriaStatus(this.errorMessage)
297
+ }
298
+
299
+ setHighlightedResult(index) {
300
+ this.highlightedResultIndex = index
301
+
302
+ if (this.highlightedResultIndex === null) {
303
+ this.input.removeAttribute('aria-activedescendant')
304
+ } else if (this.numberOfResults) {
305
+ this.resultOptions.forEach((option, optionIndex) => {
306
+ if (optionIndex === index) {
307
+ if (this.isAboveViewport(option)) {
308
+ this.handleScroll(option, true)
309
+ }
310
+ if (this.isBelowViewport(option)) {
311
+ this.handleScroll(option, false)
312
+ }
313
+ option.classList.add(`${this.stylingBaseClass}${classSuffixOptionFocused}`)
314
+ option.setAttribute('aria-selected', true)
315
+ this.input.setAttribute('aria-activedescendant', option.getAttribute('id'))
316
+ const optionText = option.innerHTML.replace('<strong>', '').replace('</strong>', '')
317
+ this.setAriaStatus(optionText)
318
+ } else {
319
+ option.classList.remove(`${this.stylingBaseClass}${classSuffixOptionFocused}`)
320
+ option.removeAttribute('aria-selected')
321
+ }
322
+ })
323
+ }
324
+ }
325
+
326
+ selectResult(index) {
327
+ if (!this.results.length) return
328
+
329
+ const result = this.results[index || this.highlightedResultIndex || 0]
330
+ this.onSelect(result)
331
+ this.clearListbox()
332
+ this.setAriaStatus(`${this.ariaYouHaveSelected}: ${result.displayText}.`)
333
+ }
334
+
335
+ setAriaStatus(content) {
336
+ if (!content) {
337
+ const queryTooShort = this.sanitisedQuery.length < this.minChars
338
+ const noResults = this.numberOfResults === 0
339
+ if (queryTooShort) {
340
+ content = this.ariaMinChars
341
+ } else if (noResults) {
342
+ content = `${this.noResults}: "${this.query}"`
343
+ } else if (this.numberOfResults === 1) {
344
+ content = this.ariaOneResult
345
+ } else {
346
+ content = this.ariaNResults.replace('{n}', this.numberOfResults)
347
+ }
348
+ }
349
+ this.ariaStatus.innerHTML = content
350
+ }
351
+
352
+ emboldenMatch(string, query) {
353
+ let reg = new RegExp(this.escapeRegExp(query).split(' ').join('[\\s,]*'), 'gi')
354
+
355
+ return string.replace(reg, '<strong>$&</strong>')
356
+ }
357
+
358
+ escapeRegExp(string) {
359
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
360
+ }
361
+
362
+ isAboveViewport(element) {
363
+ return element.getBoundingClientRect().top < 0
364
+ }
365
+
366
+ isBelowViewport(element) {
367
+ return element.getBoundingClientRect().bottom > (window.innerHeight || document.documentElement.clientHeight)
368
+ }
369
+ }
@@ -0,0 +1,10 @@
1
+ import { AutosuggestAddress } from './autosuggest-address'
2
+
3
+ export function init() {
4
+ // Setup all address autosuggest instances:
5
+ const addressAutosuggests = [...document.querySelectorAll('.hmpps-js-address-autosuggest')]
6
+
7
+ if (addressAutosuggests.length) {
8
+ addressAutosuggests.forEach(addressAutosuggest => new AutosuggestAddress(addressAutosuggest))
9
+ }
10
+ }
@@ -0,0 +1,61 @@
1
+ {% from "govuk/components/input/macro.njk" import govukInput %}
2
+ {% from "moj/components/alert/macro.njk" import mojAlert %}
3
+
4
+ {% macro addressAutosuggest(options) %}
5
+
6
+ <div class="hmpps-js-address-autosuggest">
7
+ <input type="hidden" id="uprn" class="hmpps-js-uprn hmpps-display-none" name="uprn"/>
8
+ <div
9
+ class="hmpps-address-autosuggest{% if options.classes %} {{ options.classes }}{% endif %}"
10
+ data-aria-you-have-selected="You have selected"
11
+ data-aria-min-chars="Enter 3 or more characters for suggestions."
12
+ data-aria-one-result="There is one suggestion available."
13
+ data-aria-n-results="There are {n} suggestions available."
14
+ data-no-results="No results found. Try entering a different part of the address"
15
+ data-type-more="Enter more of the address to get results"
16
+ data-error-api="The address search is not working right now. Enter the address manually to continue."
17
+ data-styling-base-class="hmpps-address-autosuggest"
18
+ data-find-url="{{ options.findUrl or "/api/addresses/find" }}"
19
+ >
20
+ {{ govukInput({
21
+ id: 'address-autosuggest-input',
22
+ name: 'address-autosuggest-input',
23
+ classes: 'hmpps-js-autosuggest-input',
24
+ value: options.inputValue,
25
+ attributes: {
26
+ 'autocomplete': 'off',
27
+ 'aria-describedby': 'address-hint'
28
+ },
29
+ errorMessage: options.errorMessage
30
+ }) }}
31
+ <div class="hmpps-address-autosuggest__results hmpps-js-autosuggest-results">
32
+ <div id="address-results" class="hmpps-address-autosuggest__results-title hmpps-js-autosuggest-results-title">
33
+ Select an address
34
+ </div>
35
+ <ul
36
+ class="hmpps-address-autosuggest__listbox hmpps-js-autosuggest-listbox"
37
+ title="Select an address"
38
+ role="listbox"
39
+ id="address-autosuggest-listbox"
40
+ tabindex="-1"></ul>
41
+ </div>
42
+ <div
43
+ class="hmpps-address-autosuggest__instructions hmpps-js-autosuggest-instructions govuk-visually-hidden"
44
+ id="address-autosuggest-instructions"
45
+ tabindex="-1">
46
+ Use up and down keys to navigate suggestions once you have typed more than two characters. Use the enter key to select a
47
+ suggestion. Touch device users, explore by touch or with swipe gestures.
48
+ </div>
49
+ <div
50
+ class="hmpps-address-autosuggest__status hmpps-js-autosuggest-aria-status govuk-visually-hidden "
51
+ aria-live="assertive"
52
+ aria-atomic="true"
53
+ role="status"
54
+ tabindex="-1"></div>
55
+ {{ mojAlert({
56
+ variant: "warning",
57
+ classes: 'hmpps-address-autosuggest__error hmpps-js-autosuggest-error hmpps-display-none'
58
+ }) }}
59
+ </div>
60
+ </div>
61
+ {% endmacro %}
@@ -0,0 +1,5 @@
1
+ import * as addressAutosuggest from '../dps/components/address-autosuggest'
2
+
3
+ export function initAll() {
4
+ addressAutosuggest.init()
5
+ }
@@ -1 +1,2 @@
1
- @import '../dps/components/alert-flag/alert-flag';
1
+ @import '../dps/components/alert-flag/alert-flag';
2
+ @import '../dps/components/address-autosuggest/address-autosuggest';
package/dist/index.cjs CHANGED
@@ -1,5 +1,131 @@
1
1
  'use strict';
2
2
 
3
+ var Fuse = require('fuse.js');
4
+ var superagent = require('superagent');
5
+
6
+ const isBlank = (str) => !str || /^\s*$/.test(str);
7
+ const properCase = (word) => word.length >= 1 ? word[0].toUpperCase() + word.toLowerCase().slice(1) : word;
8
+ /**
9
+ * Converts a name (first name, last name, middle name, etc.) to proper case equivalent, handling double-barreled names
10
+ * correctly (i.e. each part in a double-barreled is converted to proper case).
11
+ * @param name name to be converted.
12
+ * @returns name converted to proper case.
13
+ */
14
+ const properCaseName = (name) => (isBlank(name) ? '' : name.split('-').map(properCase).join('-'));
15
+ const convertToTitleCase = (sentence) => isBlank(sentence) ? '' : sentence.split(' ').map(properCaseName).join(' ');
16
+
17
+ const simplePostCodeRegex = /^[A-Z]{1,2}\d[A-Z\d]? ?\d[A-Z]{2}$/i;
18
+ const stringContainingPostCodeRegex = /^(.*?)([A-Z]{1,2}\d[A-Z\d]? ?)(\d[A-Z]{2})(.*)$/i;
19
+ class OsPlacesAddressService {
20
+ logger;
21
+ osPlacesApiClient;
22
+ constructor(logger, osPlacesApiClient) {
23
+ this.logger = logger;
24
+ this.osPlacesApiClient = osPlacesApiClient;
25
+ }
26
+ async getAddressesMatchingQuery(searchQuery, sanitisePostcode = true) {
27
+ const response = await this.osPlacesApiClient.getAddressesByFreeTextQuery(sanitisePostcode ? this.sanitiseUkPostcode(searchQuery) : searchQuery);
28
+ const rawResults = this.handleResponse(response);
29
+ const bestMatchResults = new Fuse(rawResults, {
30
+ shouldSort: true,
31
+ threshold: 0.2,
32
+ ignoreLocation: true,
33
+ keys: [{ name: 'addressString' }],
34
+ })
35
+ .search(searchQuery)
36
+ .map(result => result.item);
37
+ const queryIsAPostCode = simplePostCodeRegex.test(searchQuery.trim());
38
+ const buildingNumberSort = (a, b) => a?.addressString && b?.addressString && queryIsAPostCode
39
+ ? a.addressString.localeCompare(b.addressString, undefined, { numeric: true, sensitivity: 'base' })
40
+ : 1;
41
+ // maximum number of properties in a postcode is 100
42
+ return (bestMatchResults.length ? bestMatchResults : rawResults).sort(buildingNumberSort).slice(0, 100);
43
+ }
44
+ async getAddressByUprn(uprn) {
45
+ const response = await this.osPlacesApiClient.getAddressesByUprn(uprn);
46
+ const result = this.handleResponse(response);
47
+ if (result.length === 0)
48
+ return null;
49
+ if (result.length > 1)
50
+ this.logger.info(`Multiple results returned for UPRN`);
51
+ return result[result.length - 1];
52
+ }
53
+ sanitiseUkPostcode(stringContainingPostcode) {
54
+ const postCodeQuery = stringContainingPostCodeRegex.exec(stringContainingPostcode?.replace(/[^A-Z0-9 ]/gi, ''));
55
+ if (!postCodeQuery)
56
+ return stringContainingPostcode;
57
+ return `${postCodeQuery[1]}${postCodeQuery[2].toUpperCase().trim()} ${postCodeQuery[3].toUpperCase().trim()}${postCodeQuery[4]}`;
58
+ }
59
+ handleResponse(response) {
60
+ if (response.header && response.header.totalresults === 0)
61
+ return [];
62
+ return response.results.filter(result => result.DPA).map(result => this.toOsAddress(result.DPA));
63
+ }
64
+ toOsAddress(addressResult) {
65
+ const { UPRN, DEPENDENT_LOCALITY, SUB_BUILDING_NAME, BUILDING_NAME, BUILDING_NUMBER, THOROUGHFARE_NAME, POST_TOWN, POSTCODE, COUNTRY_CODE, LOCAL_CUSTODIAN_CODE_DESCRIPTION, } = addressResult;
66
+ return {
67
+ addressString: this.formatAddressString(addressResult),
68
+ buildingNumber: BUILDING_NUMBER,
69
+ buildingName: convertToTitleCase(BUILDING_NAME),
70
+ subBuildingName: convertToTitleCase(SUB_BUILDING_NAME),
71
+ thoroughfareName: convertToTitleCase(THOROUGHFARE_NAME),
72
+ dependentLocality: convertToTitleCase(DEPENDENT_LOCALITY),
73
+ postTown: convertToTitleCase(POST_TOWN),
74
+ county: convertToTitleCase(LOCAL_CUSTODIAN_CODE_DESCRIPTION),
75
+ postcode: POSTCODE,
76
+ country: COUNTRY_CODE,
77
+ uprn: UPRN,
78
+ };
79
+ }
80
+ formatAddressString(addressResult) {
81
+ const { ADDRESS, BUILDING_NUMBER, THOROUGHFARE_NAME, POSTCODE } = addressResult;
82
+ const withoutPostcode = ADDRESS.replace(`, ${POSTCODE}`, '').replace(`${BUILDING_NUMBER}, ${THOROUGHFARE_NAME}`, `${BUILDING_NUMBER} ${THOROUGHFARE_NAME}`);
83
+ return `${convertToTitleCase(withoutPostcode)}, ${POSTCODE}`;
84
+ }
85
+ }
86
+
87
+ class OsPlacesApiClient {
88
+ logger;
89
+ config;
90
+ constructor(logger, config) {
91
+ this.logger = logger;
92
+ this.config = config;
93
+ }
94
+ async getAddressesByFreeTextQuery(freeTextQuery) {
95
+ const queryParams = {
96
+ query: freeTextQuery,
97
+ lr: 'EN',
98
+ key: this.config.apiKey,
99
+ };
100
+ return this.get('/find', queryParams);
101
+ }
102
+ async getAddressesByUprn(uprn) {
103
+ const queryParams = {
104
+ uprn,
105
+ key: this.config.apiKey,
106
+ };
107
+ return this.get('/uprn', queryParams);
108
+ }
109
+ async get(path, query) {
110
+ const endpoint = `${this.config.url}${path}`;
111
+ try {
112
+ const result = await superagent
113
+ .get(endpoint)
114
+ .retry(2, (err, res) => {
115
+ if (err)
116
+ this.logger.info(`Retry handler found API error with ${err.code} ${err.message}`);
117
+ return undefined; // retry handler only for logging retries, not to influence retry logic
118
+ })
119
+ .query(query)
120
+ .timeout(this.config.timeout);
121
+ return result.body;
122
+ }
123
+ catch (error) {
124
+ throw Error(`Error calling OS Places API: ${error.message}`);
125
+ }
126
+ }
127
+ }
128
+
3
129
  const alertFlagLabels = [
4
130
  { alertCodes: ['HA'], classes: 'dps-alert-status dps-alert-status--self-harm', label: 'ACCT open' },
5
131
  {
@@ -145,5 +271,7 @@ function getAlertFlagLabelsForAlerts(prisonerAlerts) {
145
271
  }, []);
146
272
  }
147
273
 
274
+ exports.OsPlacesAddressService = OsPlacesAddressService;
275
+ exports.OsPlacesApiClient = OsPlacesApiClient;
148
276
  exports.getAlertFlagLabelsForAlerts = getAlertFlagLabelsForAlerts;
149
277
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../src/alertFlags/getAlertFlagLabelsForAlerts.ts"],"sourcesContent":["import { Alert, AlertsServiceAlert } from '../types/public/alertFlags/Alert'\nimport { AlertFlagLabel } from '../types/public/alertFlags/AlertFlagLabel'\n\nexport const alertFlagLabels = [\n { alertCodes: ['HA'], classes: 'dps-alert-status dps-alert-status--self-harm', label: 'ACCT open' },\n {\n alertCodes: ['HA1'],\n classes: 'dps-alert-status dps-alert-status--self-harm',\n label: 'ACCT post closure',\n },\n {\n alertCodes: ['XSA', 'SA'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Staff assaulter',\n },\n {\n alertCodes: ['XA'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Arsonist',\n },\n {\n alertCodes: ['PEEP'],\n classes: 'dps-alert-status dps-alert-status--medical',\n label: 'PEEP',\n },\n {\n alertCodes: ['HID'],\n classes: 'dps-alert-status dps-alert-status--medical',\n label: 'Hidden disability',\n },\n { alertCodes: ['XEL'], classes: 'dps-alert-status dps-alert-status--security', label: 'E-list' },\n { alertCodes: ['XELH'], classes: 'dps-alert-status dps-alert-status--security', label: 'E-list heightened' },\n { alertCodes: ['XER'], classes: 'dps-alert-status dps-alert-status--security', label: 'Escape risk' },\n {\n alertCodes: ['XRF'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Risk to females',\n },\n { alertCodes: ['XTACT'], classes: 'dps-alert-status dps-alert-status--security', label: 'TACT' },\n {\n alertCodes: ['XCO'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Corruptor',\n },\n {\n alertCodes: ['XCA'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Chemical attacker',\n },\n {\n alertCodes: ['XCI'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Concerted indiscipline',\n },\n { alertCodes: ['XR'], classes: 'dps-alert-status dps-alert-status--security', label: 'Racist' },\n {\n alertCodes: ['RTP', 'RLG'],\n classes: 'dps-alert-status dps-alert-status--risk',\n label: 'Risk to LGBT',\n },\n {\n alertCodes: ['XHT'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Hostage taker',\n },\n {\n alertCodes: ['XCU'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Controlled unlock',\n },\n {\n alertCodes: ['XGANG'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Gang member',\n },\n { alertCodes: ['CSIP'], classes: 'dps-alert-status dps-alert-status--other', label: 'CSIP' },\n { alertCodes: ['F1'], classes: 'dps-alert-status dps-alert-status--ex-armed-forces', label: 'Veteran' },\n {\n alertCodes: ['LCE'],\n classes: 'dps-alert-status dps-alert-status--care-leaver',\n label: 'Care experienced',\n },\n {\n alertCodes: ['RNO121'],\n classes: 'dps-alert-status dps-alert-status--risk',\n label: 'No one-to-one',\n },\n { alertCodes: ['RCON'], classes: 'dps-alert-status dps-alert-status--risk', label: 'Conflict' },\n {\n alertCodes: ['RCDR'],\n classes: 'dps-alert-status dps-alert-status--quarantined',\n label: 'Quarantined',\n },\n {\n alertCodes: ['URCU'],\n classes: 'dps-alert-status dps-alert-status--reverse-cohorting-unit',\n label: 'Reverse Cohorting Unit',\n },\n {\n alertCodes: ['UPIU'],\n classes: 'dps-alert-status dps-alert-status--protective-isolation-unit',\n label: 'Protective Isolation Unit',\n },\n { alertCodes: ['USU'], classes: 'dps-alert-status dps-alert-status--shielding-unit', label: 'Shielding Unit' },\n {\n alertCodes: ['URS'],\n classes: 'dps-alert-status dps-alert-status--refusing-to-shield',\n label: 'Refusing to shield',\n },\n {\n alertCodes: ['RKS'],\n classes: 'dps-alert-status dps-alert-status--risk-to-known-adults',\n label: 'Risk to known adults',\n },\n { alertCodes: ['VIP'], classes: 'dps-alert-status dps-alert-status--isolated-prisoner', label: 'Isolated' },\n {\n alertCodes: ['PVN'],\n classes: 'dps-alert-status dps-alert-status--multicase dps-alert-status--visor',\n label: 'ViSOR',\n },\n {\n alertCodes: ['XCDO'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Involved in 2024 civil disorder',\n },\n {\n alertCodes: ['XVL'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Violent',\n },\n].sort((a, b) => a.label.localeCompare(b.label))\n\nfunction isAlertsServiceAlert(alert: Alert): alert is AlertsServiceAlert {\n return typeof alert.alertCode !== 'string'\n}\n\nexport default function getAlertFlagLabelsForAlerts(prisonerAlerts: Alert[]): AlertFlagLabel[] {\n return alertFlagLabels.reduce(\n (acc: AlertFlagLabel[], flag: { alertCodes: string[]; classes: string; label: string }) => {\n const alertIds = prisonerAlerts\n .filter(alert => {\n const alertsServiceAlert = isAlertsServiceAlert(alert)\n const alertIsActive = alertsServiceAlert ? alert.isActive : alert.active && !alert.expired\n const prisonerAlertCode = alertsServiceAlert ? alert.alertCode.code : alert.alertCode\n\n return alertIsActive && flag.alertCodes.includes(prisonerAlertCode)\n })\n .map(alert => (isAlertsServiceAlert(alert) ? (alert.alertUuid as string) : alert.alertCode))\n\n return alertIds.length ? [...acc, { ...flag, alertIds }] : acc\n },\n [] as AlertFlagLabel[],\n )\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","sources":["../src/utils/utils.ts","../src/addressAutosuggest/osPlacesAddressService.ts","../src/addressAutosuggest/osPlacesApiClient.ts","../src/alertFlags/getAlertFlagLabelsForAlerts.ts"],"sourcesContent":["const isBlank = (str: string): boolean => !str || /^\\s*$/.test(str)\n\nconst properCase = (word: string): string =>\n word.length >= 1 ? word[0].toUpperCase() + word.toLowerCase().slice(1) : word\n\n/**\n * Converts a name (first name, last name, middle name, etc.) to proper case equivalent, handling double-barreled names\n * correctly (i.e. each part in a double-barreled is converted to proper case).\n * @param name name to be converted.\n * @returns name converted to proper case.\n */\nexport const properCaseName = (name: string): string => (isBlank(name) ? '' : name.split('-').map(properCase).join('-'))\n\nexport const convertToTitleCase = (sentence: string): string =>\n isBlank(sentence) ? '' : sentence.split(' ').map(properCaseName).join(' ')\n","import Fuse from 'fuse.js'\nimport { OsAddress } from '../types/public/addressAutosuggest/osAddress'\nimport { convertToTitleCase } from '../utils/utils'\nimport OsPlacesApiClient from './osPlacesApiClient'\nimport { ConnectDpsComponentLogger } from '../types/public/ConnectDpsComponentLogger'\nimport { OsPlacesDeliveryPointAddress, OsPlacesQueryResponse } from '../types/public/addressAutosuggest/osPlaces'\n\nconst simplePostCodeRegex = /^[A-Z]{1,2}\\d[A-Z\\d]? ?\\d[A-Z]{2}$/i\nconst stringContainingPostCodeRegex = /^(.*?)([A-Z]{1,2}\\d[A-Z\\d]? ?)(\\d[A-Z]{2})(.*)$/i\n\nexport default class OsPlacesAddressService {\n constructor(\n private readonly logger: ConnectDpsComponentLogger,\n private readonly osPlacesApiClient: OsPlacesApiClient,\n ) {}\n\n public async getAddressesMatchingQuery(searchQuery: string, sanitisePostcode: boolean = true): Promise<OsAddress[]> {\n const response = await this.osPlacesApiClient.getAddressesByFreeTextQuery(\n sanitisePostcode ? this.sanitiseUkPostcode(searchQuery) : searchQuery,\n )\n\n const rawResults = this.handleResponse(response)\n\n const bestMatchResults = new Fuse(rawResults, {\n shouldSort: true,\n threshold: 0.2,\n ignoreLocation: true,\n keys: [{ name: 'addressString' }],\n })\n .search(searchQuery)\n .map(result => result.item)\n\n const queryIsAPostCode = simplePostCodeRegex.test(searchQuery.trim())\n const buildingNumberSort = (a: OsAddress, b: OsAddress) =>\n a?.addressString && b?.addressString && queryIsAPostCode\n ? a.addressString.localeCompare(b.addressString, undefined, { numeric: true, sensitivity: 'base' })\n : 1\n\n // maximum number of properties in a postcode is 100\n return (bestMatchResults.length ? bestMatchResults : rawResults).sort(buildingNumberSort).slice(0, 100)\n }\n\n public async getAddressByUprn(uprn: string): Promise<OsAddress | null> {\n const response = await this.osPlacesApiClient.getAddressesByUprn(uprn)\n const result = this.handleResponse(response)\n\n if (result.length === 0) return null\n if (result.length > 1) this.logger.info(`Multiple results returned for UPRN`)\n\n return result[result.length - 1]\n }\n\n public sanitiseUkPostcode(stringContainingPostcode: string) {\n const postCodeQuery = stringContainingPostCodeRegex.exec(stringContainingPostcode?.replace(/[^A-Z0-9 ]/gi, ''))\n if (!postCodeQuery) return stringContainingPostcode\n\n return `${postCodeQuery[1]}${postCodeQuery[2].toUpperCase().trim()} ${postCodeQuery[3].toUpperCase().trim()}${postCodeQuery[4]}`\n }\n\n private handleResponse(response: OsPlacesQueryResponse): OsAddress[] {\n if (response.header && response.header.totalresults === 0) return []\n\n return response.results.filter(result => result.DPA).map(result => this.toOsAddress(result.DPA!))\n }\n\n private toOsAddress(addressResult: OsPlacesDeliveryPointAddress): OsAddress {\n const {\n UPRN,\n DEPENDENT_LOCALITY,\n SUB_BUILDING_NAME,\n BUILDING_NAME,\n BUILDING_NUMBER,\n THOROUGHFARE_NAME,\n POST_TOWN,\n POSTCODE,\n COUNTRY_CODE,\n LOCAL_CUSTODIAN_CODE_DESCRIPTION,\n } = addressResult\n\n return {\n addressString: this.formatAddressString(addressResult),\n buildingNumber: BUILDING_NUMBER,\n buildingName: convertToTitleCase(BUILDING_NAME),\n subBuildingName: convertToTitleCase(SUB_BUILDING_NAME),\n thoroughfareName: convertToTitleCase(THOROUGHFARE_NAME),\n dependentLocality: convertToTitleCase(DEPENDENT_LOCALITY),\n postTown: convertToTitleCase(POST_TOWN),\n county: convertToTitleCase(LOCAL_CUSTODIAN_CODE_DESCRIPTION),\n postcode: POSTCODE,\n country: COUNTRY_CODE,\n uprn: UPRN,\n }\n }\n\n private formatAddressString(addressResult: OsPlacesDeliveryPointAddress) {\n const { ADDRESS, BUILDING_NUMBER, THOROUGHFARE_NAME, POSTCODE } = addressResult\n const withoutPostcode = ADDRESS.replace(`, ${POSTCODE}`, '').replace(\n `${BUILDING_NUMBER}, ${THOROUGHFARE_NAME}`,\n `${BUILDING_NUMBER} ${THOROUGHFARE_NAME}`,\n )\n return `${convertToTitleCase(withoutPostcode)}, ${POSTCODE}`\n }\n}\n","import superagent from 'superagent'\nimport { ApiConfig } from '@ministryofjustice/hmpps-rest-client'\nimport { OsPlacesQueryResponse } from '../types/public/addressAutosuggest/osPlaces'\nimport { ConnectDpsComponentLogger } from '../types/public/ConnectDpsComponentLogger'\n\nexport default class OsPlacesApiClient {\n constructor(\n private readonly logger: ConnectDpsComponentLogger,\n private readonly config: ApiConfig & { apiKey: string },\n ) {}\n\n async getAddressesByFreeTextQuery(freeTextQuery: string): Promise<OsPlacesQueryResponse> {\n const queryParams: Record<string, string> = {\n query: freeTextQuery,\n lr: 'EN',\n key: this.config.apiKey,\n }\n\n return this.get<Promise<OsPlacesQueryResponse>>('/find', queryParams)\n }\n\n async getAddressesByUprn(uprn: string): Promise<OsPlacesQueryResponse> {\n const queryParams: Record<string, string> = {\n uprn,\n key: this.config.apiKey,\n }\n\n return this.get<Promise<OsPlacesQueryResponse>>('/uprn', queryParams)\n }\n\n async get<T>(path: string, query: Record<string, string>): Promise<T> {\n const endpoint = `${this.config.url}${path}`\n try {\n const result = await superagent\n .get(endpoint)\n .retry(2, (err, res) => {\n if (err) this.logger.info(`Retry handler found API error with ${err.code} ${err.message}`)\n return undefined // retry handler only for logging retries, not to influence retry logic\n })\n .query(query)\n .timeout(this.config.timeout)\n\n return result.body\n } catch (error: any) {\n throw Error(`Error calling OS Places API: ${error.message}`)\n }\n }\n}\n","import { Alert, AlertsServiceAlert } from '../types/public/alertFlags/Alert'\nimport { AlertFlagLabel } from '../types/public/alertFlags/AlertFlagLabel'\n\nexport const alertFlagLabels = [\n { alertCodes: ['HA'], classes: 'dps-alert-status dps-alert-status--self-harm', label: 'ACCT open' },\n {\n alertCodes: ['HA1'],\n classes: 'dps-alert-status dps-alert-status--self-harm',\n label: 'ACCT post closure',\n },\n {\n alertCodes: ['XSA', 'SA'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Staff assaulter',\n },\n {\n alertCodes: ['XA'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Arsonist',\n },\n {\n alertCodes: ['PEEP'],\n classes: 'dps-alert-status dps-alert-status--medical',\n label: 'PEEP',\n },\n {\n alertCodes: ['HID'],\n classes: 'dps-alert-status dps-alert-status--medical',\n label: 'Hidden disability',\n },\n { alertCodes: ['XEL'], classes: 'dps-alert-status dps-alert-status--security', label: 'E-list' },\n { alertCodes: ['XELH'], classes: 'dps-alert-status dps-alert-status--security', label: 'E-list heightened' },\n { alertCodes: ['XER'], classes: 'dps-alert-status dps-alert-status--security', label: 'Escape risk' },\n {\n alertCodes: ['XRF'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Risk to females',\n },\n { alertCodes: ['XTACT'], classes: 'dps-alert-status dps-alert-status--security', label: 'TACT' },\n {\n alertCodes: ['XCO'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Corruptor',\n },\n {\n alertCodes: ['XCA'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Chemical attacker',\n },\n {\n alertCodes: ['XCI'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Concerted indiscipline',\n },\n { alertCodes: ['XR'], classes: 'dps-alert-status dps-alert-status--security', label: 'Racist' },\n {\n alertCodes: ['RTP', 'RLG'],\n classes: 'dps-alert-status dps-alert-status--risk',\n label: 'Risk to LGBT',\n },\n {\n alertCodes: ['XHT'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Hostage taker',\n },\n {\n alertCodes: ['XCU'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Controlled unlock',\n },\n {\n alertCodes: ['XGANG'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Gang member',\n },\n { alertCodes: ['CSIP'], classes: 'dps-alert-status dps-alert-status--other', label: 'CSIP' },\n { alertCodes: ['F1'], classes: 'dps-alert-status dps-alert-status--ex-armed-forces', label: 'Veteran' },\n {\n alertCodes: ['LCE'],\n classes: 'dps-alert-status dps-alert-status--care-leaver',\n label: 'Care experienced',\n },\n {\n alertCodes: ['RNO121'],\n classes: 'dps-alert-status dps-alert-status--risk',\n label: 'No one-to-one',\n },\n { alertCodes: ['RCON'], classes: 'dps-alert-status dps-alert-status--risk', label: 'Conflict' },\n {\n alertCodes: ['RCDR'],\n classes: 'dps-alert-status dps-alert-status--quarantined',\n label: 'Quarantined',\n },\n {\n alertCodes: ['URCU'],\n classes: 'dps-alert-status dps-alert-status--reverse-cohorting-unit',\n label: 'Reverse Cohorting Unit',\n },\n {\n alertCodes: ['UPIU'],\n classes: 'dps-alert-status dps-alert-status--protective-isolation-unit',\n label: 'Protective Isolation Unit',\n },\n { alertCodes: ['USU'], classes: 'dps-alert-status dps-alert-status--shielding-unit', label: 'Shielding Unit' },\n {\n alertCodes: ['URS'],\n classes: 'dps-alert-status dps-alert-status--refusing-to-shield',\n label: 'Refusing to shield',\n },\n {\n alertCodes: ['RKS'],\n classes: 'dps-alert-status dps-alert-status--risk-to-known-adults',\n label: 'Risk to known adults',\n },\n { alertCodes: ['VIP'], classes: 'dps-alert-status dps-alert-status--isolated-prisoner', label: 'Isolated' },\n {\n alertCodes: ['PVN'],\n classes: 'dps-alert-status dps-alert-status--multicase dps-alert-status--visor',\n label: 'ViSOR',\n },\n {\n alertCodes: ['XCDO'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Involved in 2024 civil disorder',\n },\n {\n alertCodes: ['XVL'],\n classes: 'dps-alert-status dps-alert-status--security',\n label: 'Violent',\n },\n].sort((a, b) => a.label.localeCompare(b.label))\n\nfunction isAlertsServiceAlert(alert: Alert): alert is AlertsServiceAlert {\n return typeof alert.alertCode !== 'string'\n}\n\nexport default function getAlertFlagLabelsForAlerts(prisonerAlerts: Alert[]): AlertFlagLabel[] {\n return alertFlagLabels.reduce(\n (acc: AlertFlagLabel[], flag: { alertCodes: string[]; classes: string; label: string }) => {\n const alertIds = prisonerAlerts\n .filter(alert => {\n const alertsServiceAlert = isAlertsServiceAlert(alert)\n const alertIsActive = alertsServiceAlert ? alert.isActive : alert.active && !alert.expired\n const prisonerAlertCode = alertsServiceAlert ? alert.alertCode.code : alert.alertCode\n\n return alertIsActive && flag.alertCodes.includes(prisonerAlertCode)\n })\n .map(alert => (isAlertsServiceAlert(alert) ? (alert.alertUuid as string) : alert.alertCode))\n\n return alertIds.length ? [...acc, { ...flag, alertIds }] : acc\n },\n [] as AlertFlagLabel[],\n )\n}\n"],"names":[],"mappings":";;;;;AAAA,MAAM,OAAO,GAAG,CAAC,GAAW,KAAc,CAAC,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC;AAEnE,MAAM,UAAU,GAAG,CAAC,IAAY,KAC9B,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI;AAE/E;;;;;AAKG;AACI,MAAM,cAAc,GAAG,CAAC,IAAY,MAAc,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAEjH,MAAM,kBAAkB,GAAG,CAAC,QAAgB,KACjD,OAAO,CAAC,QAAQ,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;;ACP5E,MAAM,mBAAmB,GAAG,qCAAqC;AACjE,MAAM,6BAA6B,GAAG,kDAAkD;AAE1E,MAAO,sBAAsB,CAAA;AAEtB,IAAA,MAAA;AACA,IAAA,iBAAA;IAFnB,WAAA,CACmB,MAAiC,EACjC,iBAAoC,EAAA;QADpC,IAAA,CAAA,MAAM,GAAN,MAAM;QACN,IAAA,CAAA,iBAAiB,GAAjB,iBAAiB;IACjC;AAEI,IAAA,MAAM,yBAAyB,CAAC,WAAmB,EAAE,mBAA4B,IAAI,EAAA;QAC1F,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,2BAA2B,CACvE,gBAAgB,GAAG,IAAI,CAAC,kBAAkB,CAAC,WAAW,CAAC,GAAG,WAAW,CACtE;QAED,MAAM,UAAU,GAAG,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC;AAEhD,QAAA,MAAM,gBAAgB,GAAG,IAAI,IAAI,CAAC,UAAU,EAAE;AAC5C,YAAA,UAAU,EAAE,IAAI;AAChB,YAAA,SAAS,EAAE,GAAG;AACd,YAAA,cAAc,EAAE,IAAI;AACpB,YAAA,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC;SAClC;aACE,MAAM,CAAC,WAAW;aAClB,GAAG,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,CAAC;QAE7B,MAAM,gBAAgB,GAAG,mBAAmB,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;AACrE,QAAA,MAAM,kBAAkB,GAAG,CAAC,CAAY,EAAE,CAAY,KACpD,CAAC,EAAE,aAAa,IAAI,CAAC,EAAE,aAAa,IAAI;cACpC,CAAC,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC,aAAa,EAAE,SAAS,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE;cAChG,CAAC;;QAGP,OAAO,CAAC,gBAAgB,CAAC,MAAM,GAAG,gBAAgB,GAAG,UAAU,EAAE,IAAI,CAAC,kBAAkB,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;IACzG;IAEO,MAAM,gBAAgB,CAAC,IAAY,EAAA;QACxC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,IAAI,CAAC;QACtE,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC;AAE5C,QAAA,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;AAAE,YAAA,OAAO,IAAI;AACpC,QAAA,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;AAAE,YAAA,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA,kCAAA,CAAoC,CAAC;QAE7E,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;IAClC;AAEO,IAAA,kBAAkB,CAAC,wBAAgC,EAAA;AACxD,QAAA,MAAM,aAAa,GAAG,6BAA6B,CAAC,IAAI,CAAC,wBAAwB,EAAE,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;AAC/G,QAAA,IAAI,CAAC,aAAa;AAAE,YAAA,OAAO,wBAAwB;AAEnD,QAAA,OAAO,CAAA,EAAG,aAAa,CAAC,CAAC,CAAC,CAAA,EAAG,aAAa,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAA,CAAA,EAAI,aAAa,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,GAAG,aAAa,CAAC,CAAC,CAAC,EAAE;IAClI;AAEQ,IAAA,cAAc,CAAC,QAA+B,EAAA;QACpD,IAAI,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,MAAM,CAAC,YAAY,KAAK,CAAC;AAAE,YAAA,OAAO,EAAE;AAEpE,QAAA,OAAO,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,GAAI,CAAC,CAAC;IACnG;AAEQ,IAAA,WAAW,CAAC,aAA2C,EAAA;QAC7D,MAAM,EACJ,IAAI,EACJ,kBAAkB,EAClB,iBAAiB,EACjB,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,SAAS,EACT,QAAQ,EACR,YAAY,EACZ,gCAAgC,GACjC,GAAG,aAAa;QAEjB,OAAO;AACL,YAAA,aAAa,EAAE,IAAI,CAAC,mBAAmB,CAAC,aAAa,CAAC;AACtD,YAAA,cAAc,EAAE,eAAe;AAC/B,YAAA,YAAY,EAAE,kBAAkB,CAAC,aAAa,CAAC;AAC/C,YAAA,eAAe,EAAE,kBAAkB,CAAC,iBAAiB,CAAC;AACtD,YAAA,gBAAgB,EAAE,kBAAkB,CAAC,iBAAiB,CAAC;AACvD,YAAA,iBAAiB,EAAE,kBAAkB,CAAC,kBAAkB,CAAC;AACzD,YAAA,QAAQ,EAAE,kBAAkB,CAAC,SAAS,CAAC;AACvC,YAAA,MAAM,EAAE,kBAAkB,CAAC,gCAAgC,CAAC;AAC5D,YAAA,QAAQ,EAAE,QAAQ;AAClB,YAAA,OAAO,EAAE,YAAY;AACrB,YAAA,IAAI,EAAE,IAAI;SACX;IACH;AAEQ,IAAA,mBAAmB,CAAC,aAA2C,EAAA;QACrE,MAAM,EAAE,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,QAAQ,EAAE,GAAG,aAAa;QAC/E,MAAM,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA,EAAA,EAAK,QAAQ,CAAA,CAAE,EAAE,EAAE,CAAC,CAAC,OAAO,CAClE,CAAA,EAAG,eAAe,CAAA,EAAA,EAAK,iBAAiB,CAAA,CAAE,EAC1C,CAAA,EAAG,eAAe,CAAA,CAAA,EAAI,iBAAiB,CAAA,CAAE,CAC1C;QACD,OAAO,CAAA,EAAG,kBAAkB,CAAC,eAAe,CAAC,CAAA,EAAA,EAAK,QAAQ,EAAE;IAC9D;AACD;;ACjGa,MAAO,iBAAiB,CAAA;AAEjB,IAAA,MAAA;AACA,IAAA,MAAA;IAFnB,WAAA,CACmB,MAAiC,EACjC,MAAsC,EAAA;QADtC,IAAA,CAAA,MAAM,GAAN,MAAM;QACN,IAAA,CAAA,MAAM,GAAN,MAAM;IACtB;IAEH,MAAM,2BAA2B,CAAC,aAAqB,EAAA;AACrD,QAAA,MAAM,WAAW,GAA2B;AAC1C,YAAA,KAAK,EAAE,aAAa;AACpB,YAAA,EAAE,EAAE,IAAI;AACR,YAAA,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM;SACxB;QAED,OAAO,IAAI,CAAC,GAAG,CAAiC,OAAO,EAAE,WAAW,CAAC;IACvE;IAEA,MAAM,kBAAkB,CAAC,IAAY,EAAA;AACnC,QAAA,MAAM,WAAW,GAA2B;YAC1C,IAAI;AACJ,YAAA,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM;SACxB;QAED,OAAO,IAAI,CAAC,GAAG,CAAiC,OAAO,EAAE,WAAW,CAAC;IACvE;AAEA,IAAA,MAAM,GAAG,CAAI,IAAY,EAAE,KAA6B,EAAA;QACtD,MAAM,QAAQ,GAAG,CAAA,EAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAA,EAAG,IAAI,CAAA,CAAE;AAC5C,QAAA,IAAI;YACF,MAAM,MAAM,GAAG,MAAM;iBAClB,GAAG,CAAC,QAAQ;iBACZ,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAI;AACrB,gBAAA,IAAI,GAAG;AAAE,oBAAA,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA,mCAAA,EAAsC,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,OAAO,CAAA,CAAE,CAAC;gBAC1F,OAAO,SAAS,CAAA;AAClB,YAAA,CAAC;iBACA,KAAK,CAAC,KAAK;AACX,iBAAA,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC;YAE/B,OAAO,MAAM,CAAC,IAAI;QACpB;QAAE,OAAO,KAAU,EAAE;YACnB,MAAM,KAAK,CAAC,CAAA,6BAAA,EAAgC,KAAK,CAAC,OAAO,CAAA,CAAE,CAAC;QAC9D;IACF;AACD;;AC5CM,MAAM,eAAe,GAAG;AAC7B,IAAA,EAAE,UAAU,EAAE,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,8CAA8C,EAAE,KAAK,EAAE,WAAW,EAAE;AACnG,IAAA;QACE,UAAU,EAAE,CAAC,KAAK,CAAC;AACnB,QAAA,OAAO,EAAE,8CAA8C;AACvD,QAAA,KAAK,EAAE,mBAAmB;AAC3B,KAAA;AACD,IAAA;AACE,QAAA,UAAU,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC;AACzB,QAAA,OAAO,EAAE,6CAA6C;AACtD,QAAA,KAAK,EAAE,iBAAiB;AACzB,KAAA;AACD,IAAA;QACE,UAAU,EAAE,CAAC,IAAI,CAAC;AAClB,QAAA,OAAO,EAAE,6CAA6C;AACtD,QAAA,KAAK,EAAE,UAAU;AAClB,KAAA;AACD,IAAA;QACE,UAAU,EAAE,CAAC,MAAM,CAAC;AACpB,QAAA,OAAO,EAAE,4CAA4C;AACrD,QAAA,KAAK,EAAE,MAAM;AACd,KAAA;AACD,IAAA;QACE,UAAU,EAAE,CAAC,KAAK,CAAC;AACnB,QAAA,OAAO,EAAE,4CAA4C;AACrD,QAAA,KAAK,EAAE,mBAAmB;AAC3B,KAAA;AACD,IAAA,EAAE,UAAU,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,6CAA6C,EAAE,KAAK,EAAE,QAAQ,EAAE;AAChG,IAAA,EAAE,UAAU,EAAE,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,6CAA6C,EAAE,KAAK,EAAE,mBAAmB,EAAE;AAC5G,IAAA,EAAE,UAAU,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,6CAA6C,EAAE,KAAK,EAAE,aAAa,EAAE;AACrG,IAAA;QACE,UAAU,EAAE,CAAC,KAAK,CAAC;AACnB,QAAA,OAAO,EAAE,6CAA6C;AACtD,QAAA,KAAK,EAAE,iBAAiB;AACzB,KAAA;AACD,IAAA,EAAE,UAAU,EAAE,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,6CAA6C,EAAE,KAAK,EAAE,MAAM,EAAE;AAChG,IAAA;QACE,UAAU,EAAE,CAAC,KAAK,CAAC;AACnB,QAAA,OAAO,EAAE,6CAA6C;AACtD,QAAA,KAAK,EAAE,WAAW;AACnB,KAAA;AACD,IAAA;QACE,UAAU,EAAE,CAAC,KAAK,CAAC;AACnB,QAAA,OAAO,EAAE,6CAA6C;AACtD,QAAA,KAAK,EAAE,mBAAmB;AAC3B,KAAA;AACD,IAAA;QACE,UAAU,EAAE,CAAC,KAAK,CAAC;AACnB,QAAA,OAAO,EAAE,6CAA6C;AACtD,QAAA,KAAK,EAAE,wBAAwB;AAChC,KAAA;AACD,IAAA,EAAE,UAAU,EAAE,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,6CAA6C,EAAE,KAAK,EAAE,QAAQ,EAAE;AAC/F,IAAA;AACE,QAAA,UAAU,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC;AAC1B,QAAA,OAAO,EAAE,yCAAyC;AAClD,QAAA,KAAK,EAAE,cAAc;AACtB,KAAA;AACD,IAAA;QACE,UAAU,EAAE,CAAC,KAAK,CAAC;AACnB,QAAA,OAAO,EAAE,6CAA6C;AACtD,QAAA,KAAK,EAAE,eAAe;AACvB,KAAA;AACD,IAAA;QACE,UAAU,EAAE,CAAC,KAAK,CAAC;AACnB,QAAA,OAAO,EAAE,6CAA6C;AACtD,QAAA,KAAK,EAAE,mBAAmB;AAC3B,KAAA;AACD,IAAA;QACE,UAAU,EAAE,CAAC,OAAO,CAAC;AACrB,QAAA,OAAO,EAAE,6CAA6C;AACtD,QAAA,KAAK,EAAE,aAAa;AACrB,KAAA;AACD,IAAA,EAAE,UAAU,EAAE,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,0CAA0C,EAAE,KAAK,EAAE,MAAM,EAAE;AAC5F,IAAA,EAAE,UAAU,EAAE,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,oDAAoD,EAAE,KAAK,EAAE,SAAS,EAAE;AACvG,IAAA;QACE,UAAU,EAAE,CAAC,KAAK,CAAC;AACnB,QAAA,OAAO,EAAE,gDAAgD;AACzD,QAAA,KAAK,EAAE,kBAAkB;AAC1B,KAAA;AACD,IAAA;QACE,UAAU,EAAE,CAAC,QAAQ,CAAC;AACtB,QAAA,OAAO,EAAE,yCAAyC;AAClD,QAAA,KAAK,EAAE,eAAe;AACvB,KAAA;AACD,IAAA,EAAE,UAAU,EAAE,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,yCAAyC,EAAE,KAAK,EAAE,UAAU,EAAE;AAC/F,IAAA;QACE,UAAU,EAAE,CAAC,MAAM,CAAC;AACpB,QAAA,OAAO,EAAE,gDAAgD;AACzD,QAAA,KAAK,EAAE,aAAa;AACrB,KAAA;AACD,IAAA;QACE,UAAU,EAAE,CAAC,MAAM,CAAC;AACpB,QAAA,OAAO,EAAE,2DAA2D;AACpE,QAAA,KAAK,EAAE,wBAAwB;AAChC,KAAA;AACD,IAAA;QACE,UAAU,EAAE,CAAC,MAAM,CAAC;AACpB,QAAA,OAAO,EAAE,8DAA8D;AACvE,QAAA,KAAK,EAAE,2BAA2B;AACnC,KAAA;AACD,IAAA,EAAE,UAAU,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,mDAAmD,EAAE,KAAK,EAAE,gBAAgB,EAAE;AAC9G,IAAA;QACE,UAAU,EAAE,CAAC,KAAK,CAAC;AACnB,QAAA,OAAO,EAAE,uDAAuD;AAChE,QAAA,KAAK,EAAE,oBAAoB;AAC5B,KAAA;AACD,IAAA;QACE,UAAU,EAAE,CAAC,KAAK,CAAC;AACnB,QAAA,OAAO,EAAE,yDAAyD;AAClE,QAAA,KAAK,EAAE,sBAAsB;AAC9B,KAAA;AACD,IAAA,EAAE,UAAU,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,sDAAsD,EAAE,KAAK,EAAE,UAAU,EAAE;AAC3G,IAAA;QACE,UAAU,EAAE,CAAC,KAAK,CAAC;AACnB,QAAA,OAAO,EAAE,sEAAsE;AAC/E,QAAA,KAAK,EAAE,OAAO;AACf,KAAA;AACD,IAAA;QACE,UAAU,EAAE,CAAC,MAAM,CAAC;AACpB,QAAA,OAAO,EAAE,6CAA6C;AACtD,QAAA,KAAK,EAAE,iCAAiC;AACzC,KAAA;AACD,IAAA;QACE,UAAU,EAAE,CAAC,KAAK,CAAC;AACnB,QAAA,OAAO,EAAE,6CAA6C;AACtD,QAAA,KAAK,EAAE,SAAS;AACjB,KAAA;CACF,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;AAEhD,SAAS,oBAAoB,CAAC,KAAY,EAAA;AACxC,IAAA,OAAO,OAAO,KAAK,CAAC,SAAS,KAAK,QAAQ;AAC5C;AAEc,SAAU,2BAA2B,CAAC,cAAuB,EAAA;IACzE,OAAO,eAAe,CAAC,MAAM,CAC3B,CAAC,GAAqB,EAAE,IAA8D,KAAI;QACxF,MAAM,QAAQ,GAAG;aACd,MAAM,CAAC,KAAK,IAAG;AACd,YAAA,MAAM,kBAAkB,GAAG,oBAAoB,CAAC,KAAK,CAAC;YACtD,MAAM,aAAa,GAAG,kBAAkB,GAAG,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO;AAC1F,YAAA,MAAM,iBAAiB,GAAG,kBAAkB,GAAG,KAAK,CAAC,SAAS,CAAC,IAAI,GAAG,KAAK,CAAC,SAAS;YAErF,OAAO,aAAa,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,iBAAiB,CAAC;AACrE,QAAA,CAAC;aACA,GAAG,CAAC,KAAK,KAAK,oBAAoB,CAAC,KAAK,CAAC,GAAI,KAAK,CAAC,SAAoB,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC;QAE9F,OAAO,QAAQ,CAAC,MAAM,GAAG,CAAC,GAAG,GAAG,EAAE,EAAE,GAAG,IAAI,EAAE,QAAQ,EAAE,CAAC,GAAG,GAAG;IAChE,CAAC,EACD,EAAsB,CACvB;AACH;;;;;;"}