@tagadapay/plugin-sdk 1.0.30 → 2.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.
@@ -0,0 +1,30 @@
1
+ export interface Country {
2
+ code: string;
3
+ name: string;
4
+ iso3?: string;
5
+ numeric?: number;
6
+ uniqueKey?: string;
7
+ }
8
+ export interface State {
9
+ code: string;
10
+ name: string;
11
+ countryCode: string;
12
+ uniqueKey?: string;
13
+ }
14
+ export declare const getCountries: () => Country[];
15
+ export declare const getAllStates: () => State[];
16
+ export declare const getStatesForCountry: (countryCode: string) => State[];
17
+ export declare const findCountryByName: (countryName: string) => Country | null;
18
+ export declare const findRegionByCode: (regionCode: string) => State | null;
19
+ export declare const isValidCountryCode: (countryCode: string) => boolean;
20
+ export declare const isValidStateCode: (countryCode: string, stateCode: string) => boolean;
21
+ export declare const getCountryWithRegions: (countryCode: string) => {
22
+ country: {
23
+ code: any;
24
+ name: any;
25
+ iso3: any;
26
+ numeric: any;
27
+ uniqueKey: string;
28
+ };
29
+ regions: State[];
30
+ } | null;
@@ -0,0 +1,102 @@
1
+ // Import the ISO3166 library API functions
2
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
3
+ // @ts-ignore - iso3166-2-db doesn't have TypeScript definitions
4
+ import { getDataSet, reduce } from 'iso3166-2-db';
5
+ // Get the full dataset with default English language
6
+ const worldDatabase = reduce(getDataSet(), 'en');
7
+ // Transform the ISO3166 data into our expected format
8
+ export const getCountries = () => {
9
+ const countries = [];
10
+ Object.keys(worldDatabase).forEach((countryCode) => {
11
+ const countryData = worldDatabase[countryCode];
12
+ countries.push({
13
+ code: countryData.iso, // iso3166-1 alpha-2 code
14
+ name: countryData.name,
15
+ iso3: countryData.iso3, // iso3166-1 alpha-3 code
16
+ numeric: countryData.numeric,
17
+ uniqueKey: `country-${countryData.iso}`,
18
+ });
19
+ });
20
+ // Sort countries alphabetically by name
21
+ return countries.sort((a, b) => a.name.localeCompare(b.name));
22
+ };
23
+ // Get all states/regions for all countries
24
+ export const getAllStates = () => {
25
+ const states = [];
26
+ Object.keys(worldDatabase).forEach((countryCode) => {
27
+ const countryData = worldDatabase[countryCode];
28
+ if (countryData.regions && Array.isArray(countryData.regions)) {
29
+ countryData.regions.forEach((region, index) => {
30
+ states.push({
31
+ code: region.iso, // iso3166-2 code (the part after the dash)
32
+ name: region.name,
33
+ countryCode: countryData.iso,
34
+ uniqueKey: `state-${countryData.iso}-${region.iso}-${index}`,
35
+ });
36
+ });
37
+ }
38
+ });
39
+ // Sort states alphabetically by name
40
+ return states.sort((a, b) => a.name.localeCompare(b.name));
41
+ };
42
+ // Get states for a specific country
43
+ export const getStatesForCountry = (countryCode) => {
44
+ const countryData = worldDatabase[countryCode];
45
+ if (!countryData?.regions || !Array.isArray(countryData.regions)) {
46
+ return [];
47
+ }
48
+ return countryData.regions
49
+ .map((region, index) => ({
50
+ code: region.iso,
51
+ name: region.name,
52
+ countryCode: countryData.iso,
53
+ uniqueKey: `state-${countryData.iso}-${region.iso}-${index}`,
54
+ }))
55
+ .sort((a, b) => a.name.localeCompare(b.name));
56
+ };
57
+ // Find country by name (fuzzy search)
58
+ export const findCountryByName = (countryName) => {
59
+ const countries = getCountries();
60
+ const normalizedSearchName = countryName.toLowerCase().trim();
61
+ // Exact match first
62
+ const exactMatch = countries.find((country) => country.name.toLowerCase() === normalizedSearchName);
63
+ if (exactMatch)
64
+ return exactMatch;
65
+ // Partial match
66
+ const partialMatch = countries.find((country) => country.name.toLowerCase().includes(normalizedSearchName) ||
67
+ normalizedSearchName.includes(country.name.toLowerCase()));
68
+ return partialMatch ?? null;
69
+ };
70
+ // Find region by ISO code (e.g., "US-CA" for California)
71
+ export const findRegionByCode = (regionCode) => {
72
+ const [countryCode, stateCode] = regionCode.split('-');
73
+ if (!countryCode || !stateCode)
74
+ return null;
75
+ const states = getStatesForCountry(countryCode);
76
+ return states.find((state) => state.code === stateCode) ?? null;
77
+ };
78
+ // Validate if a country code exists
79
+ export const isValidCountryCode = (countryCode) => {
80
+ return Object.prototype.hasOwnProperty.call(worldDatabase, countryCode);
81
+ };
82
+ // Validate if a state code exists for a given country
83
+ export const isValidStateCode = (countryCode, stateCode) => {
84
+ const states = getStatesForCountry(countryCode);
85
+ return states.some((state) => state.code === stateCode);
86
+ };
87
+ // Get country info including regions
88
+ export const getCountryWithRegions = (countryCode) => {
89
+ const countryData = worldDatabase[countryCode];
90
+ if (!countryData)
91
+ return null;
92
+ return {
93
+ country: {
94
+ code: countryData.iso,
95
+ name: countryData.name,
96
+ iso3: countryData.iso3,
97
+ numeric: countryData.numeric,
98
+ uniqueKey: `country-${countryData.iso}`,
99
+ },
100
+ regions: getStatesForCountry(countryCode),
101
+ };
102
+ };
@@ -277,25 +277,25 @@ export function useAddress(options = {}) {
277
277
  // Custom styles for Google Places dropdown
278
278
  useEffect(() => {
279
279
  if (enableGooglePlaces && !document.getElementById('google-places-custom-styles')) {
280
- const autocompleteStyles = `
281
- .pac-container {
282
- border-radius: 8px;
283
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
284
- border: 1px solid #e2e8f0;
285
- margin-top: 4px;
286
- font-family: inherit;
287
- z-index: 9999;
288
- }
289
- .pac-item {
290
- padding: 10px 12px;
291
- font-size: 14px;
292
- cursor: pointer;
293
- border-top: 1px solid #f3f4f6;
294
- }
295
- .pac-item:first-child { border-top: none; }
296
- .pac-item:hover { background-color: #f8fafc; }
297
- .pac-item-selected, .pac-item-selected:hover { background-color: #eef2ff; }
298
- .pac-matched { font-weight: 600; }
280
+ const autocompleteStyles = `
281
+ .pac-container {
282
+ border-radius: 8px;
283
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
284
+ border: 1px solid #e2e8f0;
285
+ margin-top: 4px;
286
+ font-family: inherit;
287
+ z-index: 9999;
288
+ }
289
+ .pac-item {
290
+ padding: 10px 12px;
291
+ font-size: 14px;
292
+ cursor: pointer;
293
+ border-top: 1px solid #f3f4f6;
294
+ }
295
+ .pac-item:first-child { border-top: none; }
296
+ .pac-item:hover { background-color: #f8fafc; }
297
+ .pac-item-selected, .pac-item-selected:hover { background-color: #eef2ff; }
298
+ .pac-matched { font-weight: 600; }
299
299
  `;
300
300
  const styleElement = document.createElement('style');
301
301
  styleElement.id = 'google-places-custom-styles';
@@ -0,0 +1,53 @@
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;
@@ -0,0 +1,379 @@
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
+ };
@@ -0,0 +1,69 @@
1
+ declare global {
2
+ interface Window {
3
+ google?: {
4
+ maps?: {
5
+ places?: {
6
+ AutocompleteService: new () => any;
7
+ PlacesService: new (map: any) => any;
8
+ PlacesServiceStatus: {
9
+ OK: string;
10
+ ZERO_RESULTS: string;
11
+ };
12
+ };
13
+ Map: new (element: HTMLElement) => any;
14
+ };
15
+ };
16
+ }
17
+ }
18
+ export interface GooglePrediction {
19
+ place_id: string;
20
+ description: string;
21
+ structured_formatting?: {
22
+ main_text: string;
23
+ secondary_text: string;
24
+ };
25
+ }
26
+ export interface GoogleAddressComponent {
27
+ long_name: string;
28
+ short_name: string;
29
+ types: string[];
30
+ }
31
+ export interface GooglePlaceDetails {
32
+ address_components: GoogleAddressComponent[];
33
+ formatted_address: string;
34
+ }
35
+ export interface ExtractedAddress {
36
+ streetNumber: string;
37
+ route: string;
38
+ locality: string;
39
+ administrativeAreaLevel1: string;
40
+ administrativeAreaLevel1Long: string;
41
+ country: string;
42
+ postalCode: string;
43
+ }
44
+ export interface UseGoogleAutocompleteOptions {
45
+ apiKey: string;
46
+ libraries?: string[];
47
+ version?: string;
48
+ language?: string;
49
+ region?: string;
50
+ }
51
+ export interface UseGoogleAutocompleteResult {
52
+ predictions: GooglePrediction[];
53
+ isLoading: boolean;
54
+ isScriptLoaded: boolean;
55
+ searchPlaces: (input: string, countryRestriction?: string) => void;
56
+ getPlaceDetails: (placeId: string) => Promise<GooglePlaceDetails | null>;
57
+ extractAddressComponents: (place: GooglePlaceDetails) => ExtractedAddress;
58
+ clearPredictions: () => void;
59
+ }
60
+ /**
61
+ * React hook for Google Places Autocomplete with automatic script injection
62
+ * Automatically loads the Google Maps JavaScript API with Places library
63
+ */
64
+ export declare function useGoogleAutocomplete(options: UseGoogleAutocompleteOptions): UseGoogleAutocompleteResult;
65
+ /**
66
+ * Hook to check if Google Maps API is loaded
67
+ * @deprecated Use useGoogleAutocomplete with isScriptLoaded instead
68
+ */
69
+ export declare function useGoogleMapsLoaded(): boolean;