@@ -9,149 +9,149 @@ import validations from "./metadata/validations.json";
9
9
const slots = new Set ( "." ) ;
10
10
11
11
export const getMetadata = ( rawValue : string , countriesList : typeof countries = countries , country : any = null ) => {
12
- country = country == null && rawValue . startsWith ( "44" ) ? "gb" : country ;
13
- if ( country != null ) {
14
- countriesList = countriesList . filter ( ( c ) => c [ 0 ] === country ) ;
15
- countriesList = countriesList . sort ( ( a , b ) => b [ 2 ] . length - a [ 2 ] . length ) ;
16
- }
17
- return countriesList . find ( ( c ) => rawValue . startsWith ( c [ 2 ] ) ) ;
12
+ country = country == null && rawValue . startsWith ( "44" ) ? "gb" : country ;
13
+ if ( country != null ) {
14
+ countriesList = countriesList . filter ( ( c ) => c [ 0 ] === country ) ;
15
+ countriesList = countriesList . sort ( ( a , b ) => b [ 2 ] . length - a [ 2 ] . length ) ;
16
+ }
17
+ return countriesList . find ( ( c ) => rawValue . startsWith ( c [ 2 ] ) ) ;
18
18
}
19
19
20
20
export const getCountry = ( countryCode : keyof typeof countries ) => {
21
- return countries . find ( ( [ iso ] ) => iso === countryCode ) ;
21
+ return countries . find ( ( [ iso ] ) => iso === countryCode ) ;
22
22
}
23
23
24
24
export const getRawValue = ( value : PhoneNumber | string ) => {
25
- if ( typeof value === "string" ) return value . replaceAll ( / \D / g, "" ) ;
26
- return [ value ?. countryCode , value ?. areaCode , value ?. phoneNumber ] . filter ( Boolean ) . join ( "" ) ;
25
+ if ( typeof value === "string" ) return value . replaceAll ( / \D / g, "" ) ;
26
+ return [ value ?. countryCode , value ?. areaCode , value ?. phoneNumber ] . filter ( Boolean ) . join ( "" ) ;
27
27
}
28
28
29
29
export const displayFormat = ( value : string ) => {
30
- /** Returns the formatted value that can be displayed as an actual input value */
31
- return value . replace ( / [ . \s \D ] + $ / , "" ) . replace ( / ( \( \d + ) $ / , "$1)" ) ;
30
+ /** Returns the formatted value that can be displayed as an actual input value */
31
+ return value . replace ( / [ . \s \D ] + $ / , "" ) . replace ( / ( \( \d + ) $ / , "$1)" ) ;
32
32
}
33
33
34
34
export const cleanInput = ( input : any , pattern : string ) => {
35
- input = input . match ( / \d / g) || [ ] ;
36
- return Array . from ( pattern , c => input [ 0 ] === c || slots . has ( c ) ? input . shift ( ) || c : c ) ;
35
+ input = input . match ( / \d / g) || [ ] ;
36
+ return Array . from ( pattern , c => input [ 0 ] === c || slots . has ( c ) ? input . shift ( ) || c : c ) ;
37
37
}
38
38
39
39
export const getFormattedNumber = ( rawValue : any , pattern : string ) => {
40
- /** Returns the reformatted input value based on the given pattern */
41
- return displayFormat ( cleanInput ( rawValue , pattern . replaceAll ( / \d / g, "." ) ) . join ( "" ) ) ;
40
+ /** Returns the reformatted input value based on the given pattern */
41
+ return displayFormat ( cleanInput ( rawValue , pattern . replaceAll ( / \d / g, "." ) ) . join ( "" ) ) ;
42
42
}
43
43
44
44
export const checkValidity = ( metadata : PhoneNumber , strict : boolean = false ) => {
45
- /** Checks if both the area code and phone number match the validation pattern */
46
- const pattern = ( validations as any ) [ metadata . isoCode as keyof typeof validations ] [ Number ( strict ) ] ;
47
- return new RegExp ( pattern ) . test ( [ metadata . areaCode , metadata . phoneNumber ] . filter ( Boolean ) . join ( "" ) ) ;
45
+ /** Checks if both the area code and phone number match the validation pattern */
46
+ const pattern = ( validations as any ) [ metadata . isoCode as keyof typeof validations ] [ Number ( strict ) ] ;
47
+ return new RegExp ( pattern ) . test ( [ metadata . areaCode , metadata . phoneNumber ] . filter ( Boolean ) . join ( "" ) ) ;
48
48
}
49
49
50
50
export const getDefaultISO2Code = ( ) => {
51
- /** Returns the default ISO2 code, based on the user's timezone */
52
- return ( timezones [ Intl . DateTimeFormat ( ) . resolvedOptions ( ) . timeZone as keyof typeof timezones ] || "" ) || "us" ;
51
+ /** Returns the default ISO2 code, based on the user's timezone */
52
+ return ( timezones [ Intl . DateTimeFormat ( ) . resolvedOptions ( ) . timeZone as keyof typeof timezones ] || "" ) || "us" ;
53
53
}
54
54
55
55
export const parsePhoneNumber = ( formattedNumber : string , countriesList : typeof countries = countries , country : any = null ) : PhoneNumber => {
56
- const value = getRawValue ( formattedNumber ) ;
57
- const isoCode = getMetadata ( value , countriesList , country ) ?. [ 0 ] || getDefaultISO2Code ( ) ;
58
- const countryCodePattern = / \+ \d + / ;
59
- const areaCodePattern = / \( ( \d + ) \) / ;
56
+ const value = getRawValue ( formattedNumber ) ;
57
+ const isoCode = getMetadata ( value , countriesList , country ) ?. [ 0 ] || getDefaultISO2Code ( ) ;
58
+ const countryCodePattern = / \+ \d + / ;
59
+ const areaCodePattern = / \( ( \d + ) \) / ;
60
60
61
- /** Parses the matching partials of the phone number by predefined regex patterns */
62
- const countryCodeMatch = formattedNumber ? ( formattedNumber . match ( countryCodePattern ) || [ ] ) : [ ] ;
63
- const areaCodeMatch = formattedNumber ? ( formattedNumber . match ( areaCodePattern ) || [ ] ) : [ ] ;
61
+ /** Parses the matching partials of the phone number by predefined regex patterns */
62
+ const countryCodeMatch = formattedNumber ? ( formattedNumber . match ( countryCodePattern ) || [ ] ) : [ ] ;
63
+ const areaCodeMatch = formattedNumber ? ( formattedNumber . match ( areaCodePattern ) || [ ] ) : [ ] ;
64
64
65
- /** Converts the parsed values of the country and area codes to integers if values present */
66
- const countryCode = countryCodeMatch . length > 0 ? parseInt ( countryCodeMatch [ 0 ] ) : null ;
67
- const areaCode = areaCodeMatch . length > 1 ? areaCodeMatch [ 1 ] : null ;
65
+ /** Converts the parsed values of the country and area codes to integers if values present */
66
+ const countryCode = countryCodeMatch . length > 0 ? parseInt ( countryCodeMatch [ 0 ] ) : null ;
67
+ const areaCode = areaCodeMatch . length > 1 ? areaCodeMatch [ 1 ] : null ;
68
68
69
- /** Parses the phone number by removing the country and area codes from the formatted value */
70
- const phoneNumberPattern = new RegExp ( `^${ countryCode } ${ ( areaCode || "" ) } (\\d+)` ) ;
71
- const phoneNumberMatch = value ? ( value . match ( phoneNumberPattern ) || [ ] ) : [ ] ;
72
- const phoneNumber = phoneNumberMatch . length > 1 ? phoneNumberMatch [ 1 ] : null ;
69
+ /** Parses the phone number by removing the country and area codes from the formatted value */
70
+ const phoneNumberPattern = new RegExp ( `^${ countryCode } ${ ( areaCode || "" ) } (\\d+)` ) ;
71
+ const phoneNumberMatch = value ? ( value . match ( phoneNumberPattern ) || [ ] ) : [ ] ;
72
+ const phoneNumber = phoneNumberMatch . length > 1 ? phoneNumberMatch [ 1 ] : null ;
73
73
74
- return { countryCode, areaCode, phoneNumber, isoCode} ;
74
+ return { countryCode, areaCode, phoneNumber, isoCode} ;
75
75
}
76
76
77
77
export const usePhone = ( {
78
- query = "" ,
79
- country = "" ,
80
- countryCode = "" ,
81
- initialValue = "" ,
82
- onlyCountries = [ ] ,
83
- excludeCountries = [ ] ,
84
- preferredCountries = [ ] ,
85
- } : usePhoneOptions ) => {
86
- const defaultValue = getRawValue ( initialValue ) ;
87
- const defaultMetadata = getMetadata ( defaultValue ) || countries . find ( ( [ iso ] ) => iso === country ) ;
88
- const defaultValueState = defaultValue || countries . find ( ( [ iso ] ) => iso === defaultMetadata ?. [ 0 ] ) ?. [ 2 ] as string ;
89
-
90
- const backRef = useRef < boolean > ( false ) ;
91
- const [ value , setValue ] = useState < string > ( defaultValueState ) ;
92
-
93
- const countriesOnly = useMemo ( ( ) => {
94
- const allowList = onlyCountries . length > 0 ? onlyCountries : countries . map ( ( [ iso ] ) => iso ) ;
95
- return countries . map ( ( [ iso ] ) => iso ) . filter ( ( iso ) => {
96
- return allowList . includes ( iso ) && ! excludeCountries . includes ( iso ) ;
97
- } ) ;
98
- } , [ onlyCountries , excludeCountries ] )
99
-
100
- const countriesList = useMemo ( ( ) => {
101
- const filteredCountries = countries . filter ( ( [ iso , name , _1 , dial ] ) => {
102
- return countriesOnly . includes ( iso ) && (
103
- name . toLowerCase ( ) . startsWith ( query . toLowerCase ( ) ) || dial . includes ( query )
104
- ) ;
105
- } ) ;
106
- return [
107
- ...filteredCountries . filter ( ( [ iso ] ) => preferredCountries . includes ( iso ) ) ,
108
- ...filteredCountries . filter ( ( [ iso ] ) => ! preferredCountries . includes ( iso ) ) ,
109
- ] ;
110
- } , [ countriesOnly , preferredCountries , query ] )
111
-
112
- const metadata = useMemo ( ( ) => {
113
- const calculatedMetadata = getMetadata ( getRawValue ( value ) , countriesList , countryCode ) ;
114
- if ( countriesList . find ( ( [ iso ] ) => iso === calculatedMetadata ?. [ 0 ] || iso === defaultMetadata ?. [ 0 ] ) ) {
115
- return calculatedMetadata || defaultMetadata ;
116
- }
117
- return countriesList [ 0 ] ;
118
- } , [ countriesList , countryCode , defaultMetadata , value ] )
119
-
120
- const pattern = useMemo ( ( ) => {
121
- return metadata ?. [ 3 ] || defaultMetadata ?. [ 3 ] || "" ;
122
- } , [ defaultMetadata , metadata ] )
123
-
124
- const clean = useCallback ( ( input : any ) => {
125
- return cleanInput ( input , pattern . replaceAll ( / \d / g, "." ) ) ;
126
- } , [ pattern ] )
127
-
128
- const first = useMemo ( ( ) => {
129
- return [ ...pattern ] . findIndex ( c => slots . has ( c ) ) ;
130
- } , [ pattern ] )
131
-
132
- const prev = useMemo ( ( j = 0 ) => {
133
- return Array . from ( pattern . replaceAll ( / \d / g, "." ) , ( c , i ) => {
134
- return slots . has ( c ) ? j = i + 1 : j ;
135
- } ) ;
136
- } , [ pattern ] )
137
-
138
- const format = useCallback ( ( { target} : ChangeEvent < HTMLInputElement > ) => {
139
- const [ i , j ] = [ target . selectionStart , target . selectionEnd ] . map ( ( i : any ) => {
140
- i = clean ( target . value . slice ( 0 , i ) ) . findIndex ( c => slots . has ( c ) ) ;
141
- return i < 0 ? prev [ prev . length - 1 ] : backRef . current ? prev [ i - 1 ] || first : i ;
142
- } ) ;
143
- target . value = getFormattedNumber ( target . value , pattern ) ;
144
- target . setSelectionRange ( i , j ) ;
145
- backRef . current = false ;
146
- setValue ( target . value ) ;
147
- } , [ clean , first , pattern , prev ] )
148
-
149
- return {
150
- clean,
151
- value,
152
- format,
153
- metadata,
154
- setValue,
155
- countriesList,
156
- }
78
+ query = "" ,
79
+ country = "" ,
80
+ countryCode = "" ,
81
+ initialValue = "" ,
82
+ onlyCountries = [ ] ,
83
+ excludeCountries = [ ] ,
84
+ preferredCountries = [ ] ,
85
+ } : usePhoneOptions ) => {
86
+ const defaultValue = getRawValue ( initialValue ) ;
87
+ const defaultMetadata = getMetadata ( defaultValue ) || countries . find ( ( [ iso ] ) => iso === country ) ;
88
+ const defaultValueState = defaultValue || countries . find ( ( [ iso ] ) => iso === defaultMetadata ?. [ 0 ] ) ?. [ 2 ] as string ;
89
+
90
+ const backRef = useRef < boolean > ( false ) ;
91
+ const [ value , setValue ] = useState < string > ( defaultValueState ) ;
92
+
93
+ const countriesOnly = useMemo ( ( ) => {
94
+ const allowList = onlyCountries . length > 0 ? onlyCountries : countries . map ( ( [ iso ] ) => iso ) ;
95
+ return countries . map ( ( [ iso ] ) => iso ) . filter ( ( iso ) => {
96
+ return allowList . includes ( iso ) && ! excludeCountries . includes ( iso ) ;
97
+ } ) ;
98
+ } , [ onlyCountries , excludeCountries ] )
99
+
100
+ const countriesList = useMemo ( ( ) => {
101
+ const filteredCountries = countries . filter ( ( [ iso , name , _1 , dial ] ) => {
102
+ return countriesOnly . includes ( iso ) && (
103
+ name . toLowerCase ( ) . startsWith ( query . toLowerCase ( ) ) || dial . includes ( query )
104
+ ) ;
105
+ } ) ;
106
+ return [
107
+ ...filteredCountries . filter ( ( [ iso ] ) => preferredCountries . includes ( iso ) ) ,
108
+ ...filteredCountries . filter ( ( [ iso ] ) => ! preferredCountries . includes ( iso ) ) ,
109
+ ] ;
110
+ } , [ countriesOnly , preferredCountries , query ] )
111
+
112
+ const metadata = useMemo ( ( ) => {
113
+ const calculatedMetadata = getMetadata ( getRawValue ( value ) , countriesList , countryCode ) ;
114
+ if ( countriesList . find ( ( [ iso ] ) => iso === calculatedMetadata ?. [ 0 ] || iso === defaultMetadata ?. [ 0 ] ) ) {
115
+ return calculatedMetadata || defaultMetadata ;
116
+ }
117
+ return countriesList [ 0 ] ;
118
+ } , [ countriesList , countryCode , defaultMetadata , value ] )
119
+
120
+ const pattern = useMemo ( ( ) => {
121
+ return metadata ?. [ 3 ] || defaultMetadata ?. [ 3 ] || "" ;
122
+ } , [ defaultMetadata , metadata ] )
123
+
124
+ const clean = useCallback ( ( input : any ) => {
125
+ return cleanInput ( input , pattern . replaceAll ( / \d / g, "." ) ) ;
126
+ } , [ pattern ] )
127
+
128
+ const first = useMemo ( ( ) => {
129
+ return [ ...pattern ] . findIndex ( c => slots . has ( c ) ) ;
130
+ } , [ pattern ] )
131
+
132
+ const prev = useMemo ( ( j = 0 ) => {
133
+ return Array . from ( pattern . replaceAll ( / \d / g, "." ) , ( c , i ) => {
134
+ return slots . has ( c ) ? j = i + 1 : j ;
135
+ } ) ;
136
+ } , [ pattern ] )
137
+
138
+ const format = useCallback ( ( { target} : ChangeEvent < HTMLInputElement > ) => {
139
+ const [ i , j ] = [ target . selectionStart , target . selectionEnd ] . map ( ( i : any ) => {
140
+ i = clean ( target . value . slice ( 0 , i ) ) . findIndex ( c => slots . has ( c ) ) ;
141
+ return i < 0 ? prev [ prev . length - 1 ] : backRef . current ? prev [ i - 1 ] || first : i ;
142
+ } ) ;
143
+ target . value = getFormattedNumber ( target . value , pattern ) ;
144
+ target . setSelectionRange ( i , j ) ;
145
+ backRef . current = false ;
146
+ setValue ( target . value ) ;
147
+ } , [ clean , first , pattern , prev ] )
148
+
149
+ return {
150
+ clean,
151
+ value,
152
+ format,
153
+ metadata,
154
+ setValue,
155
+ countriesList,
156
+ }
157
157
}
0 commit comments