@descope-ui/descope-address-field 0.0.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/CHANGELOG.md +13 -0
- package/e2e/descope-address-field-connector-google.spec.ts +190 -0
- package/e2e/descope-address-field-connector-radar.spec.ts +167 -0
- package/e2e/descope-address-field.spec.ts +464 -0
- package/e2e/mocks.ts +41 -0
- package/package.json +33 -0
- package/project.json +17 -0
- package/src/component/AddressFieldClass.js +135 -0
- package/src/component/descope-address-field-internal/AddressFieldInternal.js +139 -0
- package/src/component/descope-address-field-internal/index.js +3 -0
- package/src/component/index.js +9 -0
- package/src/connectors/google.js +119 -0
- package/src/connectors/googleScriptInit.js +12 -0
- package/src/connectors/index.js +2 -0
- package/src/connectors/radar.js +100 -0
- package/src/theme.js +16 -0
- package/stories/descope-address-field.stories.js +166 -0
- package/stories/mockConnector.js +82 -0
- package/testDriver/addressFieldTestDriver.ts +49 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { createBaseInputClass } from '@descope-ui/common/base-classes';
|
|
2
|
+
import {
|
|
3
|
+
getComponentName,
|
|
4
|
+
forwardAttrs,
|
|
5
|
+
syncAttrs,
|
|
6
|
+
} from '@descope-ui/common/components-helpers';
|
|
7
|
+
import { connectorMixin } from '@descope-ui/common/components-mixins';
|
|
8
|
+
import { compose } from '@descope-ui/common/utils';
|
|
9
|
+
import { GoogleMapsConnector, RadarConnector } from '../../connectors';
|
|
10
|
+
|
|
11
|
+
export const componentName = getComponentName('address-field-internal');
|
|
12
|
+
|
|
13
|
+
const GOOGLE_MAPS_CONNECTOR_TEMPLATE = 'google-maps-places';
|
|
14
|
+
const RADAR_CONNECTOR_TEMPLATE = 'radar';
|
|
15
|
+
|
|
16
|
+
const CONNECTOR_CLASSES = {
|
|
17
|
+
[GOOGLE_MAPS_CONNECTOR_TEMPLATE]: GoogleMapsConnector,
|
|
18
|
+
[RADAR_CONNECTOR_TEMPLATE]: RadarConnector,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const BaseInputClass = createBaseInputClass({
|
|
22
|
+
componentName,
|
|
23
|
+
baseSelector: '',
|
|
24
|
+
});
|
|
25
|
+
const initConnectorAttrs = ['public-api-key'];
|
|
26
|
+
const observedAttrs = [...initConnectorAttrs];
|
|
27
|
+
|
|
28
|
+
class RawAddressFieldInternal extends BaseInputClass {
|
|
29
|
+
static get observedAttributes() {
|
|
30
|
+
return [].concat(BaseInputClass.observedAttributes || [], observedAttrs);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get errorMsgValueMissing() {
|
|
34
|
+
return (
|
|
35
|
+
this.getAttribute('data-errormessage-value-missing') ||
|
|
36
|
+
this.defaultErrorMsgValueMissing
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
constructor() {
|
|
41
|
+
super();
|
|
42
|
+
|
|
43
|
+
this.innerHTML = `
|
|
44
|
+
<style>
|
|
45
|
+
:host {
|
|
46
|
+
display: inline-block;
|
|
47
|
+
box-sizing: border-box;
|
|
48
|
+
user-select: none;
|
|
49
|
+
max-width: 100%;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
:host ::slotted {
|
|
53
|
+
padding: 0;
|
|
54
|
+
}
|
|
55
|
+
</style>
|
|
56
|
+
<div>
|
|
57
|
+
<descope-autocomplete-field></descope-autocomplete-field>
|
|
58
|
+
</div>
|
|
59
|
+
`;
|
|
60
|
+
|
|
61
|
+
this.autocompleteField = this.querySelector('descope-autocomplete-field');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
get value() {
|
|
65
|
+
return this.autocompleteField.value;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
set value(val) {
|
|
69
|
+
this.autocompleteField.value = val;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
focus() {
|
|
73
|
+
this.autocompleteField.focus();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
init() {
|
|
77
|
+
// This event listener needs to be placed before the super.init() call
|
|
78
|
+
this.addEventListener('focus', (e) => {
|
|
79
|
+
// we want to ignore focus events we are dispatching
|
|
80
|
+
if (e.isTrusted) this.autocompleteField.focus();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
super.init?.();
|
|
84
|
+
this.initAutocomplete();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
initAutocomplete() {
|
|
88
|
+
forwardAttrs(this, this.autocompleteField, {
|
|
89
|
+
includeAttrs: [
|
|
90
|
+
'size',
|
|
91
|
+
'bordered',
|
|
92
|
+
'label',
|
|
93
|
+
'label-type',
|
|
94
|
+
'placeholder',
|
|
95
|
+
'disabled',
|
|
96
|
+
'readonly',
|
|
97
|
+
'required',
|
|
98
|
+
'full-width',
|
|
99
|
+
'helper-text',
|
|
100
|
+
'error-message',
|
|
101
|
+
'default-value',
|
|
102
|
+
'data-errormessage-value-missing',
|
|
103
|
+
'st-host-direction',
|
|
104
|
+
'allow-custom-value',
|
|
105
|
+
'min-search-length',
|
|
106
|
+
'st-error-message-icon',
|
|
107
|
+
'st-error-message-icon-size',
|
|
108
|
+
'st-error-message-icon-padding',
|
|
109
|
+
],
|
|
110
|
+
});
|
|
111
|
+
// This is required since when we remove the invalid attribute from the autocomplete field,
|
|
112
|
+
// we want to reflect the change in the address field component
|
|
113
|
+
syncAttrs(this, this.autocompleteField, { includeAttrs: ['invalid'] });
|
|
114
|
+
|
|
115
|
+
// Bind the connector fetch results fn to the autocomplete field
|
|
116
|
+
this.autocompleteField.fetchResults = this.fetchConnectorResults.bind(this);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
attributeChangedCallback(attrName, oldValue, newValue) {
|
|
120
|
+
super.attributeChangedCallback?.(attrName, oldValue, newValue);
|
|
121
|
+
|
|
122
|
+
if (oldValue !== newValue) {
|
|
123
|
+
if (initConnectorAttrs.includes(attrName)) {
|
|
124
|
+
this.initializeConnector();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
getValidity() {
|
|
130
|
+
if (this.isRequired && !this.value) {
|
|
131
|
+
return { valueMissing: true };
|
|
132
|
+
}
|
|
133
|
+
return { valid: true };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export const AddressFieldInternal = compose(
|
|
138
|
+
connectorMixin({ connectorClasses: CONNECTOR_CLASSES }),
|
|
139
|
+
)(RawAddressFieldInternal);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import '@vaadin/custom-field';
|
|
2
|
+
import '@descope-ui/descope-autocomplete-field';
|
|
3
|
+
import './descope-address-field-internal';
|
|
4
|
+
|
|
5
|
+
import { componentName, AddressFieldClass } from './AddressFieldClass';
|
|
6
|
+
|
|
7
|
+
customElements.define(componentName, AddressFieldClass);
|
|
8
|
+
|
|
9
|
+
export { AddressFieldClass, componentName };
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/* global google */
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createBaseConnectorClass,
|
|
5
|
+
CONNECTOR_ERRORS,
|
|
6
|
+
} from '@descope-ui/common/base-classes';
|
|
7
|
+
import { initGoogleMapsLoader } from './googleScriptInit';
|
|
8
|
+
|
|
9
|
+
export class GoogleMapsConnector extends createBaseConnectorClass() {
|
|
10
|
+
#autocompleteService = null;
|
|
11
|
+
|
|
12
|
+
#initializationPromise = null;
|
|
13
|
+
|
|
14
|
+
constructor(getAttribute) {
|
|
15
|
+
super(getAttribute);
|
|
16
|
+
// Start initialization but don't block constructor
|
|
17
|
+
this.#initializationPromise = this.#initializeAutocompleteService();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// eslint-disable-next-line class-methods-use-this
|
|
21
|
+
getRequiredParams() {
|
|
22
|
+
return ['public-api-key'];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// eslint-disable-next-line class-methods-use-this
|
|
26
|
+
getOptionalParams() {
|
|
27
|
+
return [
|
|
28
|
+
{
|
|
29
|
+
key: 'includedPrimaryTypes',
|
|
30
|
+
attribute: 'address-types',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
key: 'language',
|
|
34
|
+
attribute: 'address-language',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
key: 'region',
|
|
38
|
+
attribute: 'address-region',
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async #initializeAutocompleteService() {
|
|
44
|
+
const apiKey = this.getAttribute('public-api-key');
|
|
45
|
+
if (this.#autocompleteService || !apiKey) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
initGoogleMapsLoader(apiKey);
|
|
51
|
+
|
|
52
|
+
const { AutocompleteSuggestion } =
|
|
53
|
+
await google.maps.importLibrary('places');
|
|
54
|
+
this.#autocompleteService = AutocompleteSuggestion;
|
|
55
|
+
} catch (error) {
|
|
56
|
+
// eslint-disable-next-line no-console
|
|
57
|
+
console.error(
|
|
58
|
+
'Failed to initialize Google Maps Autocomplete service:',
|
|
59
|
+
error,
|
|
60
|
+
);
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async _fetchResults(query) {
|
|
66
|
+
if (!query?.trim()) {
|
|
67
|
+
return { results: [] };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
await this.#initializationPromise;
|
|
72
|
+
|
|
73
|
+
const request = {
|
|
74
|
+
input: query,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// Add optional parameters
|
|
78
|
+
this.getOptionalParams().forEach(({ attribute, key, defaultValue }) => {
|
|
79
|
+
const value = this.getAttribute(attribute) || defaultValue;
|
|
80
|
+
if (value) {
|
|
81
|
+
if (key === 'includedPrimaryTypes') {
|
|
82
|
+
request[key] = value
|
|
83
|
+
.split(',')
|
|
84
|
+
.map((type) => type.trim())
|
|
85
|
+
.filter(Boolean);
|
|
86
|
+
} else {
|
|
87
|
+
request[key] = value;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const { suggestions } =
|
|
93
|
+
await this.#autocompleteService.fetchAutocompleteSuggestions(request);
|
|
94
|
+
return this.#parseResponse(suggestions);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
// eslint-disable-next-line no-console
|
|
97
|
+
console.error('Google Maps Places Autocomplete failed:', error);
|
|
98
|
+
return { results: [], error: CONNECTOR_ERRORS.FETCH_RESULTS_ERROR };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// eslint-disable-next-line class-methods-use-this
|
|
103
|
+
#parseResponse(suggestions) {
|
|
104
|
+
if (!suggestions?.length) {
|
|
105
|
+
return { results: [] };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
results: suggestions.map((suggestion) => {
|
|
110
|
+
const prediction = suggestion.placePrediction;
|
|
111
|
+
return {
|
|
112
|
+
id: prediction.placeId,
|
|
113
|
+
label: prediction.text.text,
|
|
114
|
+
value: prediction.text.text,
|
|
115
|
+
};
|
|
116
|
+
}),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const initGoogleMapsLoader = (apiKey) => {
|
|
2
|
+
if (window.google?.maps) {
|
|
3
|
+
return;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/* eslint-disable */
|
|
7
|
+
// prettier-ignore
|
|
8
|
+
(g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})({
|
|
9
|
+
key: apiKey,
|
|
10
|
+
});
|
|
11
|
+
/* eslint-enable */
|
|
12
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createBaseConnectorClass,
|
|
3
|
+
CONNECTOR_ERRORS,
|
|
4
|
+
} from '@descope-ui/common/base-classes';
|
|
5
|
+
|
|
6
|
+
const RADAR_AUTOCOMPLETE_URL = 'https://api.radar.io/v1/search/autocomplete';
|
|
7
|
+
|
|
8
|
+
export class RadarConnector extends createBaseConnectorClass() {
|
|
9
|
+
// eslint-disable-next-line class-methods-use-this
|
|
10
|
+
getRequiredParams() {
|
|
11
|
+
return ['public-api-key'];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// eslint-disable-next-line class-methods-use-this
|
|
15
|
+
getOptionalParams() {
|
|
16
|
+
return [
|
|
17
|
+
{
|
|
18
|
+
key: 'layers',
|
|
19
|
+
attribute: 'address-types',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
key: 'limit',
|
|
23
|
+
attribute: 'address-limit',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
key: 'lang',
|
|
27
|
+
attribute: 'address-language',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
key: 'countryCode',
|
|
31
|
+
attribute: 'address-region',
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async _fetchResults(query) {
|
|
37
|
+
if (!query?.trim()) {
|
|
38
|
+
return { results: [] };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const apiKey = this.getAttribute('public-api-key');
|
|
42
|
+
const params = new URLSearchParams({
|
|
43
|
+
query: query.trim(),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Add optional parameters
|
|
47
|
+
this.getOptionalParams().forEach(({ attribute, key, defaultValue }) => {
|
|
48
|
+
const value = this.getAttribute(attribute) || defaultValue;
|
|
49
|
+
if (value) {
|
|
50
|
+
if (key === 'layers' || key === 'countryCode') {
|
|
51
|
+
params.append(
|
|
52
|
+
key,
|
|
53
|
+
value
|
|
54
|
+
.split(',')
|
|
55
|
+
.map((type) => type.trim())
|
|
56
|
+
.filter(Boolean)
|
|
57
|
+
.join(','),
|
|
58
|
+
);
|
|
59
|
+
} else {
|
|
60
|
+
params.append(key, value);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const response = await fetch(
|
|
67
|
+
`${RADAR_AUTOCOMPLETE_URL}?${params.toString()}`,
|
|
68
|
+
{
|
|
69
|
+
headers: {
|
|
70
|
+
Authorization: apiKey,
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
if (!response.ok) {
|
|
76
|
+
throw new Error(`Radar API returned ${response.status}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const data = await response.json();
|
|
80
|
+
return this.#parseResponse(data);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
// eslint-disable-next-line no-console
|
|
83
|
+
console.error('Radar Places Autocomplete failed:', error);
|
|
84
|
+
return { results: [], error: CONNECTOR_ERRORS.FETCH_RESULTS_ERROR };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// eslint-disable-next-line class-methods-use-this
|
|
89
|
+
#parseResponse(data) {
|
|
90
|
+
if (!data?.addresses?.length) {
|
|
91
|
+
return { results: [] };
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
results: data.addresses.map((address) => ({
|
|
95
|
+
label: address.formattedAddress,
|
|
96
|
+
value: address.formattedAddress,
|
|
97
|
+
})),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
package/src/theme.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { AddressFieldClass } from './component/AddressFieldClass';
|
|
2
|
+
import { refs } from '@descope-ui/theme-input-wrapper';
|
|
3
|
+
|
|
4
|
+
const vars = AddressFieldClass.cssVarList;
|
|
5
|
+
|
|
6
|
+
const addressField = {
|
|
7
|
+
[vars.hostWidth]: refs.width,
|
|
8
|
+
[vars.hostDirection]: refs.direction,
|
|
9
|
+
|
|
10
|
+
_fullWidth: {
|
|
11
|
+
[vars.hostWidth]: '100%',
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default addressField;
|
|
16
|
+
export { vars };
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { componentName } from '../src/component';
|
|
2
|
+
import { withForm } from '@descope-ui/common/sb-helpers';
|
|
3
|
+
import { createBaseConnectorClass } from '@descope-ui/common/base-classes';
|
|
4
|
+
import {
|
|
5
|
+
labelControl,
|
|
6
|
+
placeholderControl,
|
|
7
|
+
sizeControl,
|
|
8
|
+
fullWidthControl,
|
|
9
|
+
directionControl,
|
|
10
|
+
disabledControl,
|
|
11
|
+
readOnlyControl,
|
|
12
|
+
requiredControl,
|
|
13
|
+
borderedControl,
|
|
14
|
+
errorMissingValueControl,
|
|
15
|
+
inputLabelTypeControl,
|
|
16
|
+
errorMessageIconControl,
|
|
17
|
+
errorMessageIconAttrs,
|
|
18
|
+
} from '@descope-ui/common/sb-controls';
|
|
19
|
+
import { MockConnector, MOCK_CONNECTOR_TEMPLATE } from './mockConnector';
|
|
20
|
+
|
|
21
|
+
const Template = ({
|
|
22
|
+
label,
|
|
23
|
+
placeholder,
|
|
24
|
+
size,
|
|
25
|
+
bordered,
|
|
26
|
+
direction,
|
|
27
|
+
required,
|
|
28
|
+
disabled,
|
|
29
|
+
readonly,
|
|
30
|
+
'default-value': defaultValue,
|
|
31
|
+
'full-width': fullWidth,
|
|
32
|
+
'label-type': labelType,
|
|
33
|
+
'data-errormessage-value-missing': customErrorMessage,
|
|
34
|
+
'allow-custom-value': allowCustomValue,
|
|
35
|
+
'min-search-length': minSearchLength,
|
|
36
|
+
'connector-template': connectorTemplate,
|
|
37
|
+
'public-api-key': apiKey,
|
|
38
|
+
'address-types': addressTypes,
|
|
39
|
+
'address-language': addressLanguage,
|
|
40
|
+
'address-region': addressRegion,
|
|
41
|
+
'address-limit': addressLimit,
|
|
42
|
+
errorMsgIcon,
|
|
43
|
+
}) => {
|
|
44
|
+
const addMockConnectorScript = `
|
|
45
|
+
<script>
|
|
46
|
+
const createBaseConnectorClass = ${createBaseConnectorClass};
|
|
47
|
+
setTimeout(() => {
|
|
48
|
+
const addressField = document.querySelector("descope-address-field");
|
|
49
|
+
if (addressField) {
|
|
50
|
+
addressField.connectorClasses = {
|
|
51
|
+
...addressField.connectorClasses,
|
|
52
|
+
"${MOCK_CONNECTOR_TEMPLATE}": ${MockConnector}
|
|
53
|
+
};
|
|
54
|
+
addressField.setAttribute("connector-template", "${MOCK_CONNECTOR_TEMPLATE}");
|
|
55
|
+
}
|
|
56
|
+
}, 0);
|
|
57
|
+
</script>`;
|
|
58
|
+
|
|
59
|
+
return withForm(`
|
|
60
|
+
<descope-address-field
|
|
61
|
+
size="${size}"
|
|
62
|
+
bordered="${bordered}"
|
|
63
|
+
label="${label || ''}"
|
|
64
|
+
label-type="${labelType || ''}"
|
|
65
|
+
disabled="${disabled || false}"
|
|
66
|
+
placeholder="${placeholder || ''}"
|
|
67
|
+
readonly="${readonly || false}"
|
|
68
|
+
required="${required || false}"
|
|
69
|
+
full-width="${fullWidth || false}"
|
|
70
|
+
st-host-direction="${direction ?? ''}"
|
|
71
|
+
default-value="${defaultValue || ''}"
|
|
72
|
+
data-errormessage-value-missing="${customErrorMessage || ''}"
|
|
73
|
+
allow-custom-value="${allowCustomValue || false}"
|
|
74
|
+
min-search-length="${minSearchLength || ''}"
|
|
75
|
+
connector-template="${
|
|
76
|
+
connectorTemplate !== MOCK_CONNECTOR_TEMPLATE ? connectorTemplate : ''
|
|
77
|
+
}"
|
|
78
|
+
public-api-key="${apiKey || ''}"
|
|
79
|
+
address-types="${addressTypes || ''}"
|
|
80
|
+
address-language="${addressLanguage || ''}"
|
|
81
|
+
address-region="${addressRegion || ''}"
|
|
82
|
+
address-limit="${addressLimit || ''}"
|
|
83
|
+
${errorMsgIcon ? errorMessageIconAttrs : ''}
|
|
84
|
+
></descope-address-field>
|
|
85
|
+
${connectorTemplate === MOCK_CONNECTOR_TEMPLATE ? addMockConnectorScript : ''}
|
|
86
|
+
`);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export default {
|
|
90
|
+
component: componentName,
|
|
91
|
+
title: 'descope-address-field',
|
|
92
|
+
argTypes: {
|
|
93
|
+
...labelControl,
|
|
94
|
+
...placeholderControl,
|
|
95
|
+
...inputLabelTypeControl,
|
|
96
|
+
...sizeControl,
|
|
97
|
+
...fullWidthControl,
|
|
98
|
+
...disabledControl,
|
|
99
|
+
...readOnlyControl,
|
|
100
|
+
...requiredControl,
|
|
101
|
+
...borderedControl,
|
|
102
|
+
...errorMissingValueControl,
|
|
103
|
+
...directionControl,
|
|
104
|
+
'min-search-length': {
|
|
105
|
+
name: 'Min Search Length',
|
|
106
|
+
control: { type: 'number' },
|
|
107
|
+
},
|
|
108
|
+
'allow-custom-value': {
|
|
109
|
+
name: 'Allow Custom Value',
|
|
110
|
+
control: { type: 'boolean' },
|
|
111
|
+
},
|
|
112
|
+
'connector-template': {
|
|
113
|
+
name: 'Connector Template',
|
|
114
|
+
control: { type: 'text' },
|
|
115
|
+
},
|
|
116
|
+
'public-api-key': {
|
|
117
|
+
name: 'API Key',
|
|
118
|
+
control: { type: 'text' },
|
|
119
|
+
},
|
|
120
|
+
'address-types': {
|
|
121
|
+
name: 'Address Types',
|
|
122
|
+
control: { type: 'text' },
|
|
123
|
+
},
|
|
124
|
+
'address-language': {
|
|
125
|
+
name: 'Language',
|
|
126
|
+
control: { type: 'text' },
|
|
127
|
+
},
|
|
128
|
+
'address-region': {
|
|
129
|
+
name: 'Region',
|
|
130
|
+
control: { type: 'text' },
|
|
131
|
+
},
|
|
132
|
+
'address-limit': {
|
|
133
|
+
name: 'Address Limit',
|
|
134
|
+
control: { type: 'number' },
|
|
135
|
+
},
|
|
136
|
+
...errorMessageIconControl,
|
|
137
|
+
'default-value': {
|
|
138
|
+
name: 'Default Value',
|
|
139
|
+
control: { type: 'text' },
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export const Default = Template.bind({});
|
|
145
|
+
|
|
146
|
+
Default.args = {
|
|
147
|
+
bordered: true,
|
|
148
|
+
size: 'md',
|
|
149
|
+
'connector-template': MOCK_CONNECTOR_TEMPLATE,
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export const GoogleMapsPlaces = Template.bind({});
|
|
153
|
+
|
|
154
|
+
GoogleMapsPlaces.args = {
|
|
155
|
+
...Default.args,
|
|
156
|
+
'connector-template': 'google-maps-places',
|
|
157
|
+
'public-api-key': '',
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
export const Radar = Template.bind({});
|
|
161
|
+
|
|
162
|
+
Radar.args = {
|
|
163
|
+
...Default.args,
|
|
164
|
+
'connector-template': 'radar',
|
|
165
|
+
'public-api-key': '',
|
|
166
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { createBaseConnectorClass } from '@descope-ui/common/base-classes';
|
|
2
|
+
export const MOCK_CONNECTOR_TEMPLATE = 'mock-connector';
|
|
3
|
+
|
|
4
|
+
export class MockConnector extends createBaseConnectorClass() {
|
|
5
|
+
get mockAddresses() {
|
|
6
|
+
return [
|
|
7
|
+
{
|
|
8
|
+
id: 'mock-1',
|
|
9
|
+
address: '123 Mock Street, Mock City, MC 12345',
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
id: 'mock-2',
|
|
13
|
+
address: '456 Test Avenue, Mock Town, TT 67890',
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: 'mock-3',
|
|
17
|
+
address: '789 Sample Boulevard, Mock City, DC 54321',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: 'mock-4',
|
|
21
|
+
address: '321 Example Lane, Mock Valley, TV 98765',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: 'mock-5',
|
|
25
|
+
address: '5511 Test Lane, Test Valley, TV 98765',
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// eslint-disable-next-line class-methods-use-this
|
|
31
|
+
getRequiredParams() {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// eslint-disable-next-line class-methods-use-this
|
|
36
|
+
async _fetchResults(query) {
|
|
37
|
+
let lowerQuery = query?.toLowerCase();
|
|
38
|
+
if (!lowerQuery || !lowerQuery.trim()) {
|
|
39
|
+
return {
|
|
40
|
+
results: [],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
const results = [...this.mockAddresses];
|
|
45
|
+
// Add dynamic address at the start if query doesn't start with "no"
|
|
46
|
+
if (!lowerQuery.startsWith('no')) {
|
|
47
|
+
results.unshift({
|
|
48
|
+
id: 'mock-dynamic',
|
|
49
|
+
address: `${query} Custom Road, Sample City, SC 54321`,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let responseDelay = 100 + Math.random() * 200;
|
|
54
|
+
if (lowerQuery.startsWith('loading')) {
|
|
55
|
+
responseDelay = 5000;
|
|
56
|
+
} else if (lowerQuery.startsWith('fast')) {
|
|
57
|
+
responseDelay = 0;
|
|
58
|
+
lowerQuery = lowerQuery.replace('fast', '');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (lowerQuery.startsWith('error')) {
|
|
62
|
+
setTimeout(() => {
|
|
63
|
+
reject(new Error('something went wrong'));
|
|
64
|
+
}, responseDelay);
|
|
65
|
+
} else {
|
|
66
|
+
setTimeout(() => {
|
|
67
|
+
resolve({
|
|
68
|
+
results: results
|
|
69
|
+
.filter((result) =>
|
|
70
|
+
result.address.toLowerCase().includes(lowerQuery),
|
|
71
|
+
)
|
|
72
|
+
.map((result) => ({
|
|
73
|
+
id: result.id,
|
|
74
|
+
label: result.address,
|
|
75
|
+
value: result.address,
|
|
76
|
+
})),
|
|
77
|
+
});
|
|
78
|
+
}, responseDelay);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|