@ministryofjustice/hmpps-connect-dps-shared-items 2.0.0 → 2.1.1
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/README.md +34 -11
- package/dist/addressAutosuggest/osPlacesAddressService.d.ts +14 -0
- package/dist/addressAutosuggest/osPlacesAddressService.test.d.ts +1 -0
- package/dist/addressAutosuggest/osPlacesApiClient.d.ts +13 -0
- package/dist/addressAutosuggest/osPlacesApiClient.test.d.ts +1 -0
- package/dist/addressAutosuggest/testMocks/osPlacesAddressQueryResponse.d.ts +12 -0
- package/dist/addressAutosuggest/testMocks/osPlacesApiClientMock.d.ts +2 -0
- package/dist/assets/dps/components/address-autosuggest/_address-autosuggest.scss +98 -0
- package/dist/assets/dps/components/address-autosuggest/abortable-fetch.js +43 -0
- package/dist/assets/dps/components/address-autosuggest/autosuggest-address.js +90 -0
- package/dist/assets/dps/components/address-autosuggest/autosuggest-ui.js +374 -0
- package/dist/assets/dps/components/address-autosuggest/index.js +10 -0
- package/dist/assets/dps/components/address-autosuggest/macro.njk +61 -0
- package/dist/assets/js/all.js +5 -0
- package/dist/assets/scss/all.scss +2 -1
- package/dist/index.cjs +128 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +169 -2
- package/dist/index.esm.js +127 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/types/public/ConnectDpsComponentLogger.d.ts +2 -0
- package/dist/types/public/addressAutosuggest/index.d.ts +2 -0
- package/dist/types/public/addressAutosuggest/osAddress.d.ts +13 -0
- package/dist/types/public/addressAutosuggest/osPlaces.d.ts +124 -0
- package/dist/utils/utils.d.ts +8 -0
- package/package.json +10 -2
- package/readme/publishing.md +3 -3
package/README.md
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
# hmpps-connect-dps-shared-items
|
|
2
|
-
|
|
1
|
+
# hmpps-connect-dps-shared-items
|
|
2
|
+
|
|
3
|
+
A library for sharing DPS Components across applications.
|
|
3
4
|
|
|
4
5
|
## Contents
|
|
5
6
|
|
|
6
|
-
1. [
|
|
7
|
+
1. [Using the library](#using-the-library)
|
|
8
|
+
2. [Library components](#library-components)
|
|
9
|
+
3. [For library developers](#for-library-developers)
|
|
10
|
+
|
|
7
11
|
|
|
8
|
-
##
|
|
12
|
+
## Using the library
|
|
13
|
+
|
|
14
|
+
### Installation
|
|
9
15
|
|
|
10
16
|
To install the package, run the following command:
|
|
11
17
|
|
|
@@ -13,7 +19,7 @@ To install the package, run the following command:
|
|
|
13
19
|
npm install @ministryofjustice/hmpps-connect-dps-shared-items
|
|
14
20
|
```
|
|
15
21
|
|
|
16
|
-
|
|
22
|
+
### Nunjucks macro usage
|
|
17
23
|
|
|
18
24
|
Add the `hmpps-connect-dps-shared-items` assets directory to nunjucks configuration in your application:
|
|
19
25
|
|
|
@@ -30,7 +36,14 @@ Add the `hmpps-connect-dps-shared-items` assets directory to nunjucks configurat
|
|
|
30
36
|
},
|
|
31
37
|
)
|
|
32
38
|
```
|
|
33
|
-
|
|
39
|
+
|
|
40
|
+
then import the component into your nunkucks file:
|
|
41
|
+
```js
|
|
42
|
+
{% from "dps/components/alert-flag/macro.njk" import alertFlag %}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Styling
|
|
46
|
+
Include the package scss within the `index.scss` file. You can either import all:
|
|
34
47
|
```scss
|
|
35
48
|
@import 'node_modules/@ministryofjustice/hmpps-connect-dps-shared-items/dist/assets/scss/all';
|
|
36
49
|
```
|
|
@@ -38,11 +51,21 @@ or import the specific component scss you are using, e.g:
|
|
|
38
51
|
```scss
|
|
39
52
|
@import 'node_modules/@ministryofjustice/hmpps-connect-dps-shared-items/dist/assets/dps/components/alrt-flag/alert-flag';
|
|
40
53
|
```
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
54
|
+
|
|
55
|
+
### Client side javascript
|
|
56
|
+
|
|
57
|
+
In your client side javascript, include:
|
|
58
|
+
```js
|
|
59
|
+
import * as connectDps from '@ministryofjustice/hmpps-connect-dps-shared-items/dist/assets/js/all'
|
|
60
|
+
|
|
61
|
+
connectDps.initAll()
|
|
44
62
|
```
|
|
45
63
|
|
|
46
|
-
## Components:
|
|
64
|
+
## Library Components:
|
|
65
|
+
|
|
66
|
+
1. [Alert Flag](readme/components/alertFlag/alert-flag.md)
|
|
67
|
+
2. [Address Autosuggest](readme/components/addressAutosuggest/address-autosuggest.md)
|
|
68
|
+
|
|
69
|
+
## For Library Developers
|
|
47
70
|
|
|
48
|
-
1. [
|
|
71
|
+
1. [Publishing to NPM](readme/publishing.md)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { OsAddress } from '../types/public/addressAutosuggest/osAddress';
|
|
2
|
+
import OsPlacesApiClient from './osPlacesApiClient';
|
|
3
|
+
import { ConnectDpsComponentLogger } from '../types/public/ConnectDpsComponentLogger';
|
|
4
|
+
export default class OsPlacesAddressService {
|
|
5
|
+
private readonly logger;
|
|
6
|
+
private readonly osPlacesApiClient;
|
|
7
|
+
constructor(logger: ConnectDpsComponentLogger, osPlacesApiClient: OsPlacesApiClient);
|
|
8
|
+
getAddressesMatchingQuery(searchQuery: string, sanitisePostcode?: boolean): Promise<OsAddress[]>;
|
|
9
|
+
getAddressByUprn(uprn: string): Promise<OsAddress | null>;
|
|
10
|
+
sanitiseUkPostcode(stringContainingPostcode: string): string;
|
|
11
|
+
private handleResponse;
|
|
12
|
+
private toOsAddress;
|
|
13
|
+
private formatAddressString;
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ApiConfig } from '@ministryofjustice/hmpps-rest-client';
|
|
2
|
+
import { OsPlacesQueryResponse } from '../types/public/addressAutosuggest/osPlaces';
|
|
3
|
+
import { ConnectDpsComponentLogger } from '../types/public/ConnectDpsComponentLogger';
|
|
4
|
+
export default class OsPlacesApiClient {
|
|
5
|
+
private readonly logger;
|
|
6
|
+
private readonly config;
|
|
7
|
+
constructor(logger: ConnectDpsComponentLogger, config: ApiConfig & {
|
|
8
|
+
apiKey: string;
|
|
9
|
+
});
|
|
10
|
+
getAddressesByFreeTextQuery(freeTextQuery: string): Promise<OsPlacesQueryResponse>;
|
|
11
|
+
getAddressesByUprn(uprn: string): Promise<OsPlacesQueryResponse>;
|
|
12
|
+
get<T>(path: string, query: Record<string, string>): Promise<T>;
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { OsPlacesQueryResponse } from '../../types/public/addressAutosuggest/osPlaces';
|
|
2
|
+
export declare const mockOsPlacesAddressQueryResponse: OsPlacesQueryResponse;
|
|
3
|
+
export declare const mockOsPlacesAddressQueryEmptyResponse: OsPlacesQueryResponse;
|
|
4
|
+
export declare const mockOsPlacesAddressQuerySingleResponse: OsPlacesQueryResponse;
|
|
5
|
+
export declare const mockOsPlacesInvalidApiKey: {
|
|
6
|
+
fault: {
|
|
7
|
+
faultstring: string;
|
|
8
|
+
detail: {
|
|
9
|
+
errorcode: string;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
@import 'govuk-frontend/dist/govuk/helpers/colour';
|
|
2
|
+
@import 'govuk-frontend/dist/govuk/settings/colours-applied';
|
|
3
|
+
|
|
4
|
+
.hmpps-address-autosuggest {
|
|
5
|
+
position: relative;
|
|
6
|
+
max-width: 630px;
|
|
7
|
+
|
|
8
|
+
.govuk-form-group {
|
|
9
|
+
margin-bottom: 10px;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
&__combobox {
|
|
13
|
+
border-radius: 3px;
|
|
14
|
+
display: inline-block;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
&__results {
|
|
18
|
+
border: 1px solid $govuk-border-colour;
|
|
19
|
+
border-radius: 3px;
|
|
20
|
+
display: none;
|
|
21
|
+
margin: 0 0 0;
|
|
22
|
+
overflow: hidden;
|
|
23
|
+
padding: 0;
|
|
24
|
+
width: 100%;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
&__results-title {
|
|
28
|
+
background: govuk-colour("light-grey");
|
|
29
|
+
border-bottom: 1px solid $govuk-border-colour;
|
|
30
|
+
padding: 0.25rem 0.5rem;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
&__listbox {
|
|
34
|
+
background: govuk-colour("white");
|
|
35
|
+
list-style: none;
|
|
36
|
+
margin: 0;
|
|
37
|
+
padding: 0;
|
|
38
|
+
&:focus {
|
|
39
|
+
outline: none;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
&__option {
|
|
44
|
+
cursor: pointer;
|
|
45
|
+
margin: 0;
|
|
46
|
+
outline: none;
|
|
47
|
+
padding: 0.25rem 0.5rem;
|
|
48
|
+
|
|
49
|
+
&:not(:last-child) {
|
|
50
|
+
border-bottom: 1px solid $govuk-border-colour;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
&--focused:not(&--no-results) {
|
|
54
|
+
background: #1d70b8;
|
|
55
|
+
border-color: #1d70b8;
|
|
56
|
+
color: govuk-colour("white");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
&:active:not(&--no-results) {
|
|
60
|
+
background: var(--ons-color-focus);
|
|
61
|
+
color: govuk-colour("black");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
&--no-results {
|
|
65
|
+
background: govuk-colour("light-grey");
|
|
66
|
+
cursor: not-allowed;
|
|
67
|
+
padding: 0.25rem 0.5rem;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
&__error {
|
|
72
|
+
margin-top: 5px;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
&:not(&--initialised) & {
|
|
76
|
+
&__instructions,
|
|
77
|
+
&__listbox,
|
|
78
|
+
&__status {
|
|
79
|
+
display: none;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
&--has-results & {
|
|
84
|
+
&__results {
|
|
85
|
+
display: block;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
&--header {
|
|
90
|
+
.hmpps-address-autosuggest__results {
|
|
91
|
+
border: 0;
|
|
92
|
+
box-shadow: 0 0 5px 0 rgb(34 34 34 / 60%);
|
|
93
|
+
left: 0;
|
|
94
|
+
position: absolute;
|
|
95
|
+
z-index: 10;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Based on ONSDigital's https://github.com/ONSdigital/design-system/blob/main/src/js/abortable-fetch.js
|
|
2
|
+
|
|
3
|
+
export const FetchStatus = {
|
|
4
|
+
unsent: 'UNSENT',
|
|
5
|
+
loading: 'LOADING',
|
|
6
|
+
done: 'DONE',
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const abortError = 'AbortError'
|
|
10
|
+
export const abortTimeout = 'AbortTimeout'
|
|
11
|
+
|
|
12
|
+
class AbortableFetch {
|
|
13
|
+
constructor(url) {
|
|
14
|
+
this.url = url
|
|
15
|
+
this.controller = new window.AbortController()
|
|
16
|
+
this.status = FetchStatus.unsent
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async send() {
|
|
20
|
+
this.status = FetchStatus.loading
|
|
21
|
+
try {
|
|
22
|
+
setTimeout(() => {
|
|
23
|
+
this.controller.abort(abortTimeout)
|
|
24
|
+
}, 5000)
|
|
25
|
+
const response = await window.fetch(this.url, { signal: this.controller.signal })
|
|
26
|
+
|
|
27
|
+
if (!(+response.status === 200)) {
|
|
28
|
+
const error = new Error(`API error: ${response?.status ?? 500}`)
|
|
29
|
+
error.response = response
|
|
30
|
+
throw error
|
|
31
|
+
}
|
|
32
|
+
return response
|
|
33
|
+
} finally {
|
|
34
|
+
this.status = FetchStatus.done
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
abort() {
|
|
39
|
+
this.controller.abort()
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const abortableFetch = (url, options) => new AbortableFetch(url, options)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Based on ONSDigital's https://github.com/ONSdigital/design-system/blob/main/src/components/address-input/autosuggest.address.js
|
|
2
|
+
import { AutosuggestUi, NoResults } from './autosuggest-ui'
|
|
3
|
+
import { abortableFetch, abortError, FetchStatus } from './abortable-fetch'
|
|
4
|
+
|
|
5
|
+
const classUPRN = 'hmpps-js-uprn'
|
|
6
|
+
const classInputContainer = 'hmpps-address-autosuggest'
|
|
7
|
+
const classInput = 'hmpps-js-autosuggest-input'
|
|
8
|
+
|
|
9
|
+
export class AutosuggestAddress {
|
|
10
|
+
constructor(context) {
|
|
11
|
+
this.context = context
|
|
12
|
+
this.input = context.querySelector(`.${classInput}`)
|
|
13
|
+
this.container = context.querySelector(`.${classInputContainer}`)
|
|
14
|
+
this.uprn = context.querySelector(`.${classUPRN}`)
|
|
15
|
+
this.findUrl = this.container.getAttribute('data-find-url')
|
|
16
|
+
|
|
17
|
+
// State:
|
|
18
|
+
this.fetch = null
|
|
19
|
+
|
|
20
|
+
// Initialise autosuggest:
|
|
21
|
+
this.autosuggest = new AutosuggestUi({
|
|
22
|
+
context: this.container,
|
|
23
|
+
onSelect: this.onAddressSelect.bind(this),
|
|
24
|
+
suggestionFunction: this.suggestAddresses.bind(this),
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
// Check API status:
|
|
28
|
+
this.checkAPIStatus()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async checkAPIStatus() {
|
|
32
|
+
this.fetch = abortableFetch(`${this.findUrl}/SW1H9AJ`)
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const response = await this.fetch.send()
|
|
36
|
+
const status = response && (await response.json()).status
|
|
37
|
+
if (status !== 200) {
|
|
38
|
+
this.autosuggest.handleNoResults(NoResults.server_error)
|
|
39
|
+
}
|
|
40
|
+
} catch (error) {
|
|
41
|
+
if (error.name !== abortError) {
|
|
42
|
+
this.autosuggest.handleNoResults(NoResults.server_error)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async suggestAddresses(query) {
|
|
48
|
+
if (this.fetch && this.fetch.status !== FetchStatus.done) {
|
|
49
|
+
this.fetch.abort()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Reset any previously selected result:
|
|
53
|
+
this.uprn.value = ''
|
|
54
|
+
|
|
55
|
+
return await this.findAddress(query?.replace(/[^A-Z0-9 ]/gi, ''))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async findAddress(query) {
|
|
59
|
+
this.fetch = abortableFetch(`${this.findUrl}/${query}`)
|
|
60
|
+
const data = await this.fetch.send()
|
|
61
|
+
const response = await data.json()
|
|
62
|
+
const status = response.status
|
|
63
|
+
const addresses = response.results
|
|
64
|
+
return this.mapFindResults(addresses, status)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
mapFindResults(results, status) {
|
|
68
|
+
if (!results) return { status }
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
results: this.addressMapping(results),
|
|
72
|
+
status: status,
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
addressMapping(addresses) {
|
|
77
|
+
if (!addresses?.length) return []
|
|
78
|
+
|
|
79
|
+
return addresses.map(address => ({ uprn: address.uprn, displayText: address.addressString }))
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async onAddressSelect(selectedResult) {
|
|
83
|
+
if (this.fetch && this.fetch.status !== FetchStatus.done) {
|
|
84
|
+
this.fetch.abort()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this.autosuggest.input.value = selectedResult.displayText
|
|
88
|
+
this.uprn.value = selectedResult.uprn
|
|
89
|
+
}
|
|
90
|
+
}
|