-
+84
+
+ +{phoneCode}
+
@@ -149,7 +158,6 @@ import { usePhoneInput } from '@react-awesome/phone-input'
const Example = () => {
const { register } = usePhoneInput({
mode: 'national',
- country: 'VN',
})
return (
@@ -161,6 +169,155 @@ const Example = () => {
}
```
+## Phone Input With Fixed Country
+
+**usePhoneInput** also accepts `country` prop.
+
+When `country` is provided then the enterred value is formatted based on the provided country code and the country detection behaviour will be disabled.
+
+export const FixedCountryExample = () => {
+ const [value, setValue] = useState({
+ isPossible: false,
+ isValid: false,
+ e164Value: '',
+ country: 'VN',
+ phoneCode: '84',
+ formattedValue: '',
+ isSupported: true,
+ });
+ const { register, phoneCode } = usePhoneInput({
+ onChange: (_, m) => {
+ setValue(m);
+ },
+ country: 'VN',
+ defaultCountry: 'VN'
+ });
+
+return (
+
+
+
+
+
+
+ onChange event
+
+
+ {Object.keys(value).map((key) => {
+ const v = value[key]
+ return (
+
+ đ {key}
+
+ {v.toString()}
+
+
+ )
+ })}
+
+
+); };
+
+
+
+
+
+```jsx
+import { usePhoneInput } from '@react-awesome/phone-input'
+
+const Example = () => {
+ const { register } = usePhoneInput({
+ country: 'VN',
+ })
+
+ return (
+
+ )
+}
+```
+
+`country` can also work with national format.
+
+export const FixedCountryWithNationalExample = () => {
+ const [value, setValue] = useState({
+ isPossible: false,
+ isValid: false,
+ e164Value: '',
+ country: 'VN',
+ phoneCode: '84',
+ formattedValue: '',
+ isSupported: true,
+ });
+ const { register, phoneCode } = usePhoneInput({
+ onChange: (_, m) => {
+ setValue(m);
+ },
+ country: 'VN',
+ defaultCountry: 'VN',
+ mode: 'national'
+ });
+
+return (
+
+
+
+
+ onChange event
+
+
+ {Object.keys(value).map((key) => {
+ const v = value[key]
+ return (
+
+ đ {key}
+
+ {v.toString()}
+
+
+ )
+ })}
+
+
+); };
+
+
+
+
+
+```jsx
+import { usePhoneInput } from '@react-awesome/phone-input'
+
+const Example = () => {
+ const { register } = usePhoneInput({
+ country: 'VN',
+ mode: 'national',
+ })
+
+ return (
+
+ )
+}
+```
+
## Parameters
The `usePhoneInput` takes the following parameters:
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index ba715ef..373014b 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -1,5 +1,12 @@
# @react-awesome/components
+## 1.0.15
+
+### Patch Changes
+
+- Updated dependencies
+ - @react-awesome/phone-input@1.1.3
+
## 1.0.14
### Patch Changes
diff --git a/packages/components/package.json b/packages/components/package.json
index 7bd0fad..1ee3e9e 100644
--- a/packages/components/package.json
+++ b/packages/components/package.json
@@ -1,6 +1,6 @@
{
"name": "@react-awesome/components",
- "version": "1.0.14",
+ "version": "1.0.15",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -39,7 +39,7 @@
"access": "public"
},
"dependencies": {
- "@react-awesome/phone-input": "1.1.2",
+ "@react-awesome/phone-input": "1.1.3",
"@react-awesome/use-click-outside": "0.0.3",
"@react-awesome/use-preserve-input-caret-position": "0.0.3",
"@react-awesome/use-selection-range": "0.0.3",
diff --git a/packages/phone-input/CHANGELOG.md b/packages/phone-input/CHANGELOG.md
index dacee6e..9034ff5 100644
--- a/packages/phone-input/CHANGELOG.md
+++ b/packages/phone-input/CHANGELOG.md
@@ -1,5 +1,11 @@
# @react-awesome/phone-input
+## 1.1.3
+
+### Patch Changes
+
+- Fix phone number is not in national format
+
## 1.1.2
### Patch Changes
diff --git a/packages/phone-input/package.json b/packages/phone-input/package.json
index b9afc2f..d2b6423 100644
--- a/packages/phone-input/package.json
+++ b/packages/phone-input/package.json
@@ -1,6 +1,6 @@
{
"name": "@react-awesome/phone-input",
- "version": "1.1.2",
+ "version": "1.1.3",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
diff --git a/packages/phone-input/src/helpers/formatWithFixedCountry/formatWithFixedCountry.spec.ts b/packages/phone-input/src/helpers/formatWithFixedCountry/formatWithFixedCountry.spec.ts
new file mode 100644
index 0000000..9295867
--- /dev/null
+++ b/packages/phone-input/src/helpers/formatWithFixedCountry/formatWithFixedCountry.spec.ts
@@ -0,0 +1,11 @@
+import { formatWithFixedCountry } from './formatWithFixedCountry'
+
+describe('formatWithFixedCountry', () => {
+ it('Should leave as-is when the value is already has valid country code.', () => {
+ expect(formatWithFixedCountry('+123456', 'US')).toBe('+123456')
+ })
+
+ it('Should format to the correct country code.', () => {
+ expect(formatWithFixedCountry('+123456', 'VN')).toBe('+84123456')
+ })
+})
diff --git a/packages/phone-input/src/helpers/formatWithFixedCountry/formatWithFixedCountry.ts b/packages/phone-input/src/helpers/formatWithFixedCountry/formatWithFixedCountry.ts
new file mode 100644
index 0000000..a865d03
--- /dev/null
+++ b/packages/phone-input/src/helpers/formatWithFixedCountry/formatWithFixedCountry.ts
@@ -0,0 +1,25 @@
+import {
+ CountryCode,
+ getCountryCallingCode,
+ parseIncompletePhoneNumber,
+} from 'libphonenumber-js'
+
+export const formatWithFixedCountry = (
+ phoneValue: string,
+ country: CountryCode,
+) => {
+ if (!phoneValue) return ''
+
+ const prefix = `+${getCountryCallingCode(country)}`
+
+ if (phoneValue.startsWith(prefix))
+ return parseIncompletePhoneNumber(phoneValue)
+
+ if (phoneValue.startsWith('+'))
+ return `${prefix}${parseIncompletePhoneNumber(phoneValue.replace(/\+/g, ''))}`
+
+ if (phoneValue.startsWith('0'))
+ return `${prefix}${parseIncompletePhoneNumber(phoneValue.slice(1))}`
+
+ return `${prefix}${parseIncompletePhoneNumber(phoneValue)}`
+}
diff --git a/packages/phone-input/src/helpers/index.ts b/packages/phone-input/src/helpers/index.ts
index 3f73dfc..5e00a76 100644
--- a/packages/phone-input/src/helpers/index.ts
+++ b/packages/phone-input/src/helpers/index.ts
@@ -3,3 +3,4 @@ export * from './formatInternational/formatInternational'
export * from './getPossibleCountriesByCallingCode/getPossibleCountriesByCallingCode'
export * from './checkCountryValidity/checkCountryValidity'
export * from './formatNational/formatNational'
+export * from './formatWithFixedCountry/formatWithFixedCountry'
diff --git a/packages/phone-input/src/hooks/usePhoneInput.spec.tsx b/packages/phone-input/src/hooks/usePhoneInput.spec.tsx
index c4aa63a..7624674 100644
--- a/packages/phone-input/src/hooks/usePhoneInput.spec.tsx
+++ b/packages/phone-input/src/hooks/usePhoneInput.spec.tsx
@@ -265,7 +265,9 @@ describe('usePhoneInput', () => {
it('Should only trigger change event when value is actually changed', async () => {
const onChange = vitest.fn()
- const { container } = render(
)
+ const { container } = render(
+
,
+ )
const input = container.querySelector('input')
if (!input) {
diff --git a/packages/phone-input/src/hooks/usePhoneInput.tsx b/packages/phone-input/src/hooks/usePhoneInput.tsx
index 6a3bb08..c8f0548 100644
--- a/packages/phone-input/src/hooks/usePhoneInput.tsx
+++ b/packages/phone-input/src/hooks/usePhoneInput.tsx
@@ -6,12 +6,14 @@ import {
getCountryCallingCode,
formatIncompletePhoneNumber,
AsYouType,
+ parsePhoneNumber,
} from 'libphonenumber-js'
import { usePreserveInputCaretPosition } from '@react-awesome/use-preserve-input-caret-position'
import {
guessCountryByIncompleteNumber,
formatInternational,
formatNational,
+ formatWithFixedCountry,
checkCountryValidity,
} from '../helpers'
@@ -88,6 +90,7 @@ export const usePhoneInput = ({
/**
* States
*/
+ const [isPasted, setPasted] = React.useState
(false)
const [inputRef, setInputRef] = React.useState(null)
const [innerValue, setInnerValue] = React.useState<
NonNullable<{ phone: string; country: CountryCode }>
@@ -141,13 +144,43 @@ export const usePhoneInput = ({
/**
* Helpers
*/
+ const normalizeValue = React.useCallback(
+ (phone: string) => {
+ if (country && mode === 'national') {
+ return formatWithFixedCountry(phone, country).replace(
+ '+' + getCountryCallingCode(country),
+ '',
+ )
+ }
+
+ if (country) return formatWithFixedCountry(phone, country)
+
+ switch (mode) {
+ case 'international':
+ return formatInternational(phone)
+ case 'national':
+ return formatNational(phone)
+ default:
+ return phone
+ }
+ },
+ [country, mode],
+ )
const guessCountry = React.useCallback(
(value: string) => {
+ /**
+ * When country is passed, the guessCountry is disabled.
+ */
if (country) return country
+ /**
+ * When mode is `national`, country should be parsed based on the current selected country
+ */
+ if (mode === 'national' && innerValue.country) return innerValue.country
+
return guessCountryByIncompleteNumber(value)
},
- [country],
+ [country, innerValue.country, mode],
)
const openCountrySelect = React.useCallback(() => setSelectOpen(true), [])
const closeCountrySelect = React.useCallback(() => setSelectOpen(false), [])
@@ -157,23 +190,34 @@ export const usePhoneInput = ({
)
const generateMetadata = React.useCallback(
(value: string, currentCountry: CountryCode): PhoneInputChangeMetadata => {
- const _value = formatInternational(value)
- const guessedCountry = guessCountry(_value) || currentCountry
+ const guessedCountry = guessCountry(value) || currentCountry
+
const isSupported = checkCountryValidity(
guessedCountry,
supportedCountries,
)
+
// If country is not supported country then return the defaultCountry or the first country in the option list.
const country = isSupported
? guessedCountry
: defaultCountry || options[0].iso2
+ /**
+ * Reset asYouType to the latest country and parse from it.
+ */
+ asYouType.current = new AsYouType(country)
+ asYouType.current.reset()
+ asYouType.current.input(value)
+
const formattedValue = formatIncompletePhoneNumber(value, country)
return {
isPossible: asYouType.current.isPossible(),
isValid: asYouType.current.isValid(),
- e164Value: asYouType.current.getNumber()?.format('E.164') || '',
+ e164Value:
+ asYouType.current.getNumber()?.format('E.164', {
+ fromCountry: country,
+ }) || '',
country,
phoneCode:
asYouType.current.getCallingCode() || getCountryCallingCode(country),
@@ -196,6 +240,33 @@ export const usePhoneInput = ({
},
[closeCountrySelect, generateMetadata, onPhoneChange],
)
+ const handlePastedValue = React.useCallback(
+ (value: string) => {
+ if (isPasted && mode === 'national') {
+ const asYouPaste = new AsYouType()
+ asYouPaste.input(value)
+
+ if (value.startsWith('+')) {
+ const pastedCountry =
+ country ||
+ asYouPaste.getCountry() ||
+ guessCountryByIncompleteNumber(value)
+ if (pastedCountry) {
+ asYouType.current = new AsYouType(pastedCountry)
+ innerValue.country = pastedCountry
+ return value.replace(`+${getCountryCallingCode(pastedCountry)}`, '')
+ }
+ } else if (value.startsWith('0')) {
+ return value.slice(0)
+ }
+
+ setPasted(false)
+ }
+
+ return value
+ },
+ [country, innerValue, isPasted, mode],
+ )
/**
* Event Handlers
@@ -204,18 +275,13 @@ export const usePhoneInput = ({
(e: React.ChangeEvent) => {
const allowFormat =
mode === 'international' ? INTERNATIONAL_FORMAT : LOCAL_FORMAT
- const formatFn =
- mode === 'international' ? formatInternational : formatNational
// format raw value and assign back to the event target
- e.target.value = formatFn(e.target.value)
+ e.target.value = normalizeValue(handlePastedValue(e.target.value))
- if (e.target.value === formatFn(innerValue.phone)) return
+ if (e.target.value === normalizeValue(innerValue.phone)) return
if (allowFormat.test(e.target.value) || e.target.value === '') {
- asYouType.current.reset()
- asYouType.current.input(e.target.value)
-
const metadata = generateMetadata(e.target.value, innerValue.country)
e.target.value = metadata.formattedValue
@@ -231,13 +297,19 @@ export const usePhoneInput = ({
},
[
generateMetadata,
+ handlePastedValue,
innerValue.country,
innerValue.phone,
mode,
+ normalizeValue,
onPhoneChange,
],
)
+ const onPaste = React.useCallback(() => {
+ setPasted(true)
+ }, [])
+
const register = React.useCallback(
(
name?: string,
@@ -246,18 +318,25 @@ export const usePhoneInput = ({
React.InputHTMLAttributes,
HTMLInputElement
>,
- 'ref' | 'name' | 'type' | 'autoComplete' | 'value' | 'onChange'
+ | 'ref'
+ | 'name'
+ | 'type'
+ | 'autoComplete'
+ | 'value'
+ | 'onChange'
+ | 'onPaste'
> => {
return {
ref: setInputRef,
name,
value: innerValue.phone,
onChange,
+ onPaste,
type: 'tel',
autoComplete: 'tel',
}
},
- [innerValue.phone, onChange],
+ [innerValue.phone, onChange, onPaste],
)
/**
@@ -272,9 +351,9 @@ export const usePhoneInput = ({
}
}, [country])
- React.useEffect(() => {
- asYouType.current = new AsYouType(innerValue.country)
- }, [innerValue.country])
+ // React.useEffect(() => {
+ // asYouType.current = new AsYouType(innerValue.country)
+ // }, [innerValue.country])
React.useEffect(() => {
if (!value) return