@tagadapay/plugin-sdk 2.1.0 → 2.1.3
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/react/hooks/useCheckout.d.ts +0 -5
- package/dist/react/hooks/useCheckout.js +15 -32
- package/dist/react/hooks/useISOData.js +37 -5
- package/dist/react/hooks/useLogin.js +4 -4
- package/dist/react/hooks/useOffers.js +7 -2
- package/dist/react/hooks/useProducts.js +3 -2
- package/dist/react/index.d.ts +4 -5
- package/dist/react/index.js +2 -3
- package/dist/react/providers/TagadaProvider.d.ts +1 -3
- package/dist/react/providers/TagadaProvider.js +89 -43
- package/dist/react/services/apiService.d.ts +4 -2
- package/dist/react/services/apiService.js +6 -2
- package/package.json +1 -2
- package/dist/react/components/AddressForm.example.d.ts +0 -1
- package/dist/react/components/AddressForm.example.js +0 -32
- package/dist/react/hooks/useAddress.d.ts +0 -59
- package/dist/react/hooks/useAddress.js +0 -557
- package/dist/react/hooks/useAddressV2.d.ts +0 -53
- package/dist/react/hooks/useAddressV2.js +0 -379
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { type Country as ISO3166Country, type State as ISO3166State } from '../../data/iso3166';
|
|
2
|
-
export type Country = ISO3166Country;
|
|
3
|
-
export type State = ISO3166State;
|
|
4
|
-
export interface AddressField {
|
|
5
|
-
value: string;
|
|
6
|
-
isValid: boolean;
|
|
7
|
-
error?: string;
|
|
8
|
-
touched?: boolean;
|
|
9
|
-
}
|
|
10
|
-
export interface AddressData {
|
|
11
|
-
firstName: string;
|
|
12
|
-
lastName: string;
|
|
13
|
-
email: string;
|
|
14
|
-
phone: string;
|
|
15
|
-
country: string;
|
|
16
|
-
address1: string;
|
|
17
|
-
address2: string;
|
|
18
|
-
city: string;
|
|
19
|
-
state: string;
|
|
20
|
-
postal: string;
|
|
21
|
-
}
|
|
22
|
-
export interface UseAddressV2Config {
|
|
23
|
-
autoValidate?: boolean;
|
|
24
|
-
enableGooglePlaces?: boolean;
|
|
25
|
-
googlePlacesApiKey?: string;
|
|
26
|
-
countryRestrictions?: string[];
|
|
27
|
-
onFieldsChange?: (data: AddressData) => void;
|
|
28
|
-
debounceConfig?: {
|
|
29
|
-
autoSaveDelay?: number;
|
|
30
|
-
enabled?: boolean;
|
|
31
|
-
};
|
|
32
|
-
initialValues?: Partial<AddressData>;
|
|
33
|
-
}
|
|
34
|
-
export interface UseAddressV2Return {
|
|
35
|
-
fields: Record<keyof AddressData, AddressField>;
|
|
36
|
-
setValue: (field: keyof AddressData, value: string) => void;
|
|
37
|
-
setValues: (values: Partial<AddressData>) => void;
|
|
38
|
-
getValue: (field: keyof AddressData) => string;
|
|
39
|
-
getValues: () => AddressData;
|
|
40
|
-
validateField: (field: keyof AddressData) => boolean;
|
|
41
|
-
validateAll: () => boolean;
|
|
42
|
-
isValid: boolean;
|
|
43
|
-
reset: () => void;
|
|
44
|
-
countries: Country[];
|
|
45
|
-
states: State[];
|
|
46
|
-
getStatesForCountry: (countryCode: string) => State[];
|
|
47
|
-
addressRef: React.RefObject<HTMLInputElement | null>;
|
|
48
|
-
addressInputValue: string;
|
|
49
|
-
setAddressInputValue: (value: string) => void;
|
|
50
|
-
handleFieldChange: (field: keyof AddressData) => (value: string) => void;
|
|
51
|
-
handleFieldBlur: (field: keyof AddressData) => () => void;
|
|
52
|
-
}
|
|
53
|
-
export declare const useAddressV2: (config?: UseAddressV2Config) => UseAddressV2Return;
|
|
@@ -1,379 +0,0 @@
|
|
|
1
|
-
import { useState, useMemo, useCallback, useRef, useEffect } from 'react';
|
|
2
|
-
import { usePlacesWidget } from 'react-google-autocomplete';
|
|
3
|
-
// Import the standardized ISO3166 data
|
|
4
|
-
import { getCountries, getAllStates, getStatesForCountry, isValidCountryCode, isValidStateCode, } from '../../data/iso3166';
|
|
5
|
-
// Default field structure
|
|
6
|
-
const createDefaultField = (value = '') => ({
|
|
7
|
-
value,
|
|
8
|
-
isValid: true,
|
|
9
|
-
error: undefined,
|
|
10
|
-
touched: false,
|
|
11
|
-
});
|
|
12
|
-
// Default form data
|
|
13
|
-
const createDefaultFormData = (initialValues = {}) => ({
|
|
14
|
-
firstName: createDefaultField(initialValues.firstName),
|
|
15
|
-
lastName: createDefaultField(initialValues.lastName),
|
|
16
|
-
email: createDefaultField(initialValues.email),
|
|
17
|
-
phone: createDefaultField(initialValues.phone),
|
|
18
|
-
country: createDefaultField(initialValues.country),
|
|
19
|
-
address1: createDefaultField(initialValues.address1),
|
|
20
|
-
address2: createDefaultField(initialValues.address2),
|
|
21
|
-
city: createDefaultField(initialValues.city),
|
|
22
|
-
state: createDefaultField(initialValues.state),
|
|
23
|
-
postal: createDefaultField(initialValues.postal),
|
|
24
|
-
});
|
|
25
|
-
// Validation functions
|
|
26
|
-
const isValidEmail = (email) => {
|
|
27
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
28
|
-
return emailRegex.test(email);
|
|
29
|
-
};
|
|
30
|
-
const isValidPhone = (phone) => {
|
|
31
|
-
const phoneRegex = /^[+]?[\d\s\-()]+$/;
|
|
32
|
-
return phone.length >= 10 && phoneRegex.test(phone);
|
|
33
|
-
};
|
|
34
|
-
const isValidPostal = (postal, country) => {
|
|
35
|
-
if (!postal)
|
|
36
|
-
return false;
|
|
37
|
-
const patterns = {
|
|
38
|
-
US: /^\d{5}(-\d{4})?$/,
|
|
39
|
-
CA: /^[A-Za-z]\d[A-Za-z] \d[A-Za-z]\d$/,
|
|
40
|
-
GB: /^[A-Z]{1,2}\d[A-Z\d]? \d[A-Z]{2}$/,
|
|
41
|
-
DE: /^\d{5}$/,
|
|
42
|
-
FR: /^\d{5}$/,
|
|
43
|
-
};
|
|
44
|
-
const pattern = patterns[country];
|
|
45
|
-
return pattern ? pattern.test(postal) : postal.length >= 3;
|
|
46
|
-
};
|
|
47
|
-
export const useAddressV2 = (config = {}) => {
|
|
48
|
-
const { autoValidate = false, enableGooglePlaces = false, googlePlacesApiKey, countryRestrictions = [], onFieldsChange, debounceConfig = { autoSaveDelay: 1000, enabled: true }, initialValues = {}, } = config;
|
|
49
|
-
// State management
|
|
50
|
-
const [fields, setFields] = useState(() => createDefaultFormData(initialValues));
|
|
51
|
-
// Google Places integration
|
|
52
|
-
const [addressInputValue, setAddressInputValue] = useState(initialValues.address1 || '');
|
|
53
|
-
const addressRef = useRef(null);
|
|
54
|
-
// Debounce timeout ref
|
|
55
|
-
const debounceTimeoutRef = useRef(null);
|
|
56
|
-
// Memoized countries with restrictions applied (using ISO3166 data)
|
|
57
|
-
const countries = useMemo(() => {
|
|
58
|
-
const allCountries = getCountries(); // Already includes uniqueKey and is deduplicated
|
|
59
|
-
return countryRestrictions.length > 0
|
|
60
|
-
? allCountries.filter((country) => countryRestrictions.includes(country.code))
|
|
61
|
-
: allCountries;
|
|
62
|
-
}, [countryRestrictions]);
|
|
63
|
-
// Memoized states for all countries (using ISO3166 data)
|
|
64
|
-
const allStates = useMemo(() => {
|
|
65
|
-
return getAllStates(); // Already includes uniqueKey and is deduplicated
|
|
66
|
-
}, []);
|
|
67
|
-
// Get states for current selected country
|
|
68
|
-
const states = useMemo(() => {
|
|
69
|
-
const selectedCountry = fields.country.value;
|
|
70
|
-
if (!selectedCountry)
|
|
71
|
-
return [];
|
|
72
|
-
return allStates.filter((state) => state.countryCode === selectedCountry);
|
|
73
|
-
}, [allStates, fields.country.value]);
|
|
74
|
-
// Get states for any country (utility function) - use ISO3166 function
|
|
75
|
-
const getStatesForCountryFn = useCallback((countryCode) => {
|
|
76
|
-
return getStatesForCountry(countryCode); // Use the ISO3166 function directly
|
|
77
|
-
}, []);
|
|
78
|
-
// Validation function for a single field
|
|
79
|
-
const validateField = useCallback((field) => {
|
|
80
|
-
const value = fields[field].value;
|
|
81
|
-
switch (field) {
|
|
82
|
-
case 'email':
|
|
83
|
-
return value ? isValidEmail(value) : true;
|
|
84
|
-
case 'phone':
|
|
85
|
-
return value ? isValidPhone(value) : true;
|
|
86
|
-
case 'postal':
|
|
87
|
-
return value ? isValidPostal(value, fields.country.value) : true;
|
|
88
|
-
case 'firstName':
|
|
89
|
-
case 'lastName':
|
|
90
|
-
case 'address1':
|
|
91
|
-
case 'city':
|
|
92
|
-
return value.trim().length > 0;
|
|
93
|
-
case 'country': {
|
|
94
|
-
return isValidCountryCode(value);
|
|
95
|
-
}
|
|
96
|
-
case 'state': {
|
|
97
|
-
if (!fields.country.value)
|
|
98
|
-
return true;
|
|
99
|
-
return isValidStateCode(fields.country.value, value);
|
|
100
|
-
}
|
|
101
|
-
default:
|
|
102
|
-
return true;
|
|
103
|
-
}
|
|
104
|
-
}, [fields]);
|
|
105
|
-
// Validate all fields
|
|
106
|
-
const validateAll = useCallback(() => {
|
|
107
|
-
const fieldsToValidate = [
|
|
108
|
-
'firstName',
|
|
109
|
-
'lastName',
|
|
110
|
-
'email',
|
|
111
|
-
'phone',
|
|
112
|
-
'country',
|
|
113
|
-
'address1',
|
|
114
|
-
'city',
|
|
115
|
-
'state',
|
|
116
|
-
'postal',
|
|
117
|
-
];
|
|
118
|
-
return fieldsToValidate.every((field) => validateField(field));
|
|
119
|
-
}, [validateField]);
|
|
120
|
-
// Check if form is currently valid
|
|
121
|
-
const isValid = useMemo(() => validateAll(), [validateAll]);
|
|
122
|
-
// Get validation error message
|
|
123
|
-
const getFieldError = useCallback((field, value) => {
|
|
124
|
-
if (!value &&
|
|
125
|
-
['firstName', 'lastName', 'email', 'phone', 'country', 'address1', 'city'].includes(field)) {
|
|
126
|
-
return `${field.charAt(0).toUpperCase() + field.slice(1)} is required`;
|
|
127
|
-
}
|
|
128
|
-
switch (field) {
|
|
129
|
-
case 'email':
|
|
130
|
-
return value && !isValidEmail(value) ? 'Please enter a valid email address' : undefined;
|
|
131
|
-
case 'phone':
|
|
132
|
-
return value && !isValidPhone(value) ? 'Please enter a valid phone number' : undefined;
|
|
133
|
-
case 'postal':
|
|
134
|
-
return value && !isValidPostal(value, fields.country.value)
|
|
135
|
-
? 'Please enter a valid postal code'
|
|
136
|
-
: undefined;
|
|
137
|
-
case 'country':
|
|
138
|
-
return value && !isValidCountryCode(value) ? 'Please select a valid country' : undefined;
|
|
139
|
-
case 'state': {
|
|
140
|
-
if (!fields.country.value)
|
|
141
|
-
return undefined;
|
|
142
|
-
const countryStates = getStatesForCountry(fields.country.value);
|
|
143
|
-
if (countryStates.length > 0 && !isValidStateCode(fields.country.value, value)) {
|
|
144
|
-
return 'Please select a valid state/province';
|
|
145
|
-
}
|
|
146
|
-
return undefined;
|
|
147
|
-
}
|
|
148
|
-
default:
|
|
149
|
-
return undefined;
|
|
150
|
-
}
|
|
151
|
-
}, [fields.country.value]);
|
|
152
|
-
// Refs for stable references to avoid recreating callbacks
|
|
153
|
-
const onFieldsChangeRef = useRef(onFieldsChange);
|
|
154
|
-
const debounceConfigRef = useRef(debounceConfig);
|
|
155
|
-
// Update refs when values change
|
|
156
|
-
useEffect(() => {
|
|
157
|
-
onFieldsChangeRef.current = onFieldsChange;
|
|
158
|
-
}, [onFieldsChange]);
|
|
159
|
-
useEffect(() => {
|
|
160
|
-
debounceConfigRef.current = debounceConfig;
|
|
161
|
-
}, [debounceConfig]);
|
|
162
|
-
// Store fields ref for stable access
|
|
163
|
-
const fieldsRef = useRef(fields);
|
|
164
|
-
useEffect(() => {
|
|
165
|
-
fieldsRef.current = fields;
|
|
166
|
-
}, [fields]);
|
|
167
|
-
// Debounced onFieldsChange callback (stable reference - NO DEPENDENCIES!)
|
|
168
|
-
const triggerFieldsChange = useCallback(() => {
|
|
169
|
-
const currentConfig = debounceConfigRef.current;
|
|
170
|
-
const currentCallback = onFieldsChangeRef.current;
|
|
171
|
-
if (!currentConfig.enabled || !currentCallback)
|
|
172
|
-
return;
|
|
173
|
-
if (debounceTimeoutRef.current) {
|
|
174
|
-
clearTimeout(debounceTimeoutRef.current);
|
|
175
|
-
}
|
|
176
|
-
debounceTimeoutRef.current = setTimeout(() => {
|
|
177
|
-
// Get current field values at execution time, not creation time
|
|
178
|
-
const currentFields = fieldsRef.current;
|
|
179
|
-
const currentValues = {
|
|
180
|
-
firstName: currentFields.firstName.value,
|
|
181
|
-
lastName: currentFields.lastName.value,
|
|
182
|
-
email: currentFields.email.value,
|
|
183
|
-
phone: currentFields.phone.value,
|
|
184
|
-
country: currentFields.country.value,
|
|
185
|
-
address1: currentFields.address1.value,
|
|
186
|
-
address2: currentFields.address2.value,
|
|
187
|
-
city: currentFields.city.value,
|
|
188
|
-
state: currentFields.state.value,
|
|
189
|
-
postal: currentFields.postal.value,
|
|
190
|
-
};
|
|
191
|
-
currentCallback(currentValues);
|
|
192
|
-
}, currentConfig.autoSaveDelay);
|
|
193
|
-
}, []); // NO DEPENDENCIES - truly stable!
|
|
194
|
-
// Set single field value with validation
|
|
195
|
-
const setValue = useCallback((field, value, triggerSave = false) => {
|
|
196
|
-
setFields((prev) => {
|
|
197
|
-
const error = autoValidate ? getFieldError(field, value) : undefined;
|
|
198
|
-
const newFields = {
|
|
199
|
-
...prev,
|
|
200
|
-
[field]: {
|
|
201
|
-
value,
|
|
202
|
-
isValid: !error,
|
|
203
|
-
error,
|
|
204
|
-
touched: true,
|
|
205
|
-
},
|
|
206
|
-
};
|
|
207
|
-
// Reset state when country changes
|
|
208
|
-
if (field === 'country') {
|
|
209
|
-
newFields.state = createDefaultField('');
|
|
210
|
-
}
|
|
211
|
-
return newFields;
|
|
212
|
-
});
|
|
213
|
-
// Handle address input value for Google Places
|
|
214
|
-
if (field === 'address1') {
|
|
215
|
-
setAddressInputValue(value);
|
|
216
|
-
}
|
|
217
|
-
// Only trigger auto-save when explicitly requested (on blur, not on every keystroke)
|
|
218
|
-
if (triggerSave) {
|
|
219
|
-
triggerFieldsChange();
|
|
220
|
-
}
|
|
221
|
-
}, [autoValidate, getFieldError, triggerFieldsChange]);
|
|
222
|
-
// Set multiple field values
|
|
223
|
-
const setValues = useCallback((values) => {
|
|
224
|
-
setFields((prev) => {
|
|
225
|
-
const newFields = { ...prev };
|
|
226
|
-
Object.entries(values).forEach(([key, value]) => {
|
|
227
|
-
const field = key;
|
|
228
|
-
const error = autoValidate ? getFieldError(field, value || '') : undefined;
|
|
229
|
-
newFields[field] = {
|
|
230
|
-
value: value || '',
|
|
231
|
-
isValid: !error,
|
|
232
|
-
error,
|
|
233
|
-
touched: true,
|
|
234
|
-
};
|
|
235
|
-
});
|
|
236
|
-
// Reset state if country changed
|
|
237
|
-
if (values.country !== undefined) {
|
|
238
|
-
newFields.state = createDefaultField(values.state || '');
|
|
239
|
-
}
|
|
240
|
-
return newFields;
|
|
241
|
-
});
|
|
242
|
-
// Update address input value if address1 was changed
|
|
243
|
-
if (values.address1 !== undefined) {
|
|
244
|
-
setAddressInputValue(values.address1);
|
|
245
|
-
}
|
|
246
|
-
// Trigger debounced callback
|
|
247
|
-
triggerFieldsChange();
|
|
248
|
-
}, [autoValidate, getFieldError, triggerFieldsChange]);
|
|
249
|
-
// Get single field value
|
|
250
|
-
const getValue = useCallback((field) => {
|
|
251
|
-
return fields[field].value;
|
|
252
|
-
}, [fields]);
|
|
253
|
-
// Get all field values
|
|
254
|
-
const getValues = useCallback(() => {
|
|
255
|
-
return {
|
|
256
|
-
firstName: fields.firstName.value,
|
|
257
|
-
lastName: fields.lastName.value,
|
|
258
|
-
email: fields.email.value,
|
|
259
|
-
phone: fields.phone.value,
|
|
260
|
-
country: fields.country.value,
|
|
261
|
-
address1: fields.address1.value,
|
|
262
|
-
address2: fields.address2.value,
|
|
263
|
-
city: fields.city.value,
|
|
264
|
-
state: fields.state.value,
|
|
265
|
-
postal: fields.postal.value,
|
|
266
|
-
};
|
|
267
|
-
}, [fields]);
|
|
268
|
-
// Reset form
|
|
269
|
-
const reset = useCallback(() => {
|
|
270
|
-
setFields(createDefaultFormData());
|
|
271
|
-
setAddressInputValue('');
|
|
272
|
-
}, []);
|
|
273
|
-
// Optimized field change handler factory
|
|
274
|
-
const handleFieldChange = useCallback((field) => {
|
|
275
|
-
return (value) => setValue(field, value);
|
|
276
|
-
}, [setValue]);
|
|
277
|
-
// Optimized field blur handler factory
|
|
278
|
-
const handleFieldBlur = useCallback((field) => {
|
|
279
|
-
return () => {
|
|
280
|
-
if (!autoValidate) {
|
|
281
|
-
setFields((prev) => {
|
|
282
|
-
const value = prev[field].value;
|
|
283
|
-
const error = getFieldError(field, value);
|
|
284
|
-
return {
|
|
285
|
-
...prev,
|
|
286
|
-
[field]: {
|
|
287
|
-
...prev[field],
|
|
288
|
-
isValid: !error,
|
|
289
|
-
error,
|
|
290
|
-
touched: true,
|
|
291
|
-
},
|
|
292
|
-
};
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
};
|
|
296
|
-
}, [autoValidate, getFieldError]);
|
|
297
|
-
// Google Places autocomplete setup - we'll return the ref for manual connection
|
|
298
|
-
const placesConfig = useMemo(() => ({
|
|
299
|
-
apiKey: googlePlacesApiKey,
|
|
300
|
-
onPlaceSelected: (place) => {
|
|
301
|
-
if (!place.geometry || !enableGooglePlaces)
|
|
302
|
-
return;
|
|
303
|
-
const addressComponents = place.address_components || [];
|
|
304
|
-
const newValues = {};
|
|
305
|
-
addressComponents.forEach((component) => {
|
|
306
|
-
const types = component.types;
|
|
307
|
-
if (types.includes('street_number')) {
|
|
308
|
-
newValues.address1 = `${component.long_name} ${newValues.address1 || ''}`.trim();
|
|
309
|
-
}
|
|
310
|
-
else if (types.includes('route')) {
|
|
311
|
-
newValues.address1 = `${newValues.address1 || ''} ${component.long_name}`.trim();
|
|
312
|
-
}
|
|
313
|
-
else if (types.includes('locality')) {
|
|
314
|
-
newValues.city = component.long_name;
|
|
315
|
-
}
|
|
316
|
-
else if (types.includes('administrative_area_level_1')) {
|
|
317
|
-
newValues.state = component.short_name;
|
|
318
|
-
}
|
|
319
|
-
else if (types.includes('country')) {
|
|
320
|
-
newValues.country = component.short_name;
|
|
321
|
-
}
|
|
322
|
-
else if (types.includes('postal_code')) {
|
|
323
|
-
newValues.postal = component.long_name;
|
|
324
|
-
}
|
|
325
|
-
});
|
|
326
|
-
// Only update if country is in restrictions (if any)
|
|
327
|
-
if (newValues.country && countryRestrictions.length > 0) {
|
|
328
|
-
if (!countryRestrictions.includes(newValues.country)) {
|
|
329
|
-
console.warn(`Google Places returned country ${newValues.country} which is not in restrictions`);
|
|
330
|
-
return;
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
setValues(newValues);
|
|
334
|
-
},
|
|
335
|
-
options: {
|
|
336
|
-
types: ['address'],
|
|
337
|
-
...(countryRestrictions.length > 0 && {
|
|
338
|
-
componentRestrictions: { country: countryRestrictions },
|
|
339
|
-
}),
|
|
340
|
-
},
|
|
341
|
-
}), [googlePlacesApiKey, enableGooglePlaces, countryRestrictions, setValues]);
|
|
342
|
-
// Use the Google Places hook - always call the hook to avoid conditional hook calls
|
|
343
|
-
const googlePlacesHook = usePlacesWidget(enableGooglePlaces
|
|
344
|
-
? placesConfig
|
|
345
|
-
: {
|
|
346
|
-
apiKey: '',
|
|
347
|
-
onPlaceSelected: () => {
|
|
348
|
-
// No-op when Google Places is disabled
|
|
349
|
-
},
|
|
350
|
-
options: {},
|
|
351
|
-
});
|
|
352
|
-
// Cleanup debounce timeout on unmount
|
|
353
|
-
useEffect(() => {
|
|
354
|
-
return () => {
|
|
355
|
-
if (debounceTimeoutRef.current) {
|
|
356
|
-
clearTimeout(debounceTimeoutRef.current);
|
|
357
|
-
}
|
|
358
|
-
};
|
|
359
|
-
}, []);
|
|
360
|
-
return {
|
|
361
|
-
fields,
|
|
362
|
-
setValue,
|
|
363
|
-
setValues,
|
|
364
|
-
getValue,
|
|
365
|
-
getValues,
|
|
366
|
-
validateField,
|
|
367
|
-
validateAll,
|
|
368
|
-
isValid,
|
|
369
|
-
reset,
|
|
370
|
-
countries,
|
|
371
|
-
states,
|
|
372
|
-
getStatesForCountry: getStatesForCountryFn,
|
|
373
|
-
addressRef: enableGooglePlaces ? (googlePlacesHook.ref ?? addressRef) : addressRef,
|
|
374
|
-
addressInputValue,
|
|
375
|
-
setAddressInputValue,
|
|
376
|
-
handleFieldChange,
|
|
377
|
-
handleFieldBlur,
|
|
378
|
-
};
|
|
379
|
-
};
|