-
Notifications
You must be signed in to change notification settings - Fork 220
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
馃摓 phone number formatter package (#2750)
- Loading branch information
Showing
15 changed files
with
748 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@shopify/phone': major | ||
--- | ||
|
||
Phone number formatter initial release |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# Changelog | ||
|
||
## 1.0.0 | ||
|
||
- `@shopify/phone` package |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
# `@shopify/phone` | ||
|
||
[![Build Status](https://github.com/Shopify/quilt/workflows/Node-CI/badge.svg?branch=main)](https://github.com/Shopify/quilt/actions?query=workflow%3ANode-CI) | ||
[![Build Status](https://github.com/Shopify/quilt/workflows/Ruby-CI/badge.svg?branch=main)](https://github.com/Shopify/quilt/actions?query=workflow%3ARuby-CI) | ||
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE.md) [![npm version](https://badge.fury.io/js/%40shopify%2Fphone.svg)](https://badge.fury.io/js/%40shopify%2Fphone.svg) [![npm bundle size (minified + gzip)](https://img.shields.io/bundlephobia/minzip/@shopify/phone.svg)](https://img.shields.io/bundlephobia/minzip/@shopify/phone.svg) | ||
|
||
Phone number utilities for formatting phone numbers. | ||
|
||
## Installation | ||
|
||
```bash | ||
yarn add @shopify/phone | ||
``` | ||
|
||
## PhoneNumberFormatter methods | ||
|
||
```ts | ||
import PhoneNumberFormatter from '@shopify/phone'; | ||
|
||
// Pass a region code to the constructor | ||
const phoneFormatter = new PhoneNumberFormatter('US'); | ||
const formatted = phoneFormatter.format(myPhoneNumber); | ||
``` | ||
|
||
#### `format(phoneNumber: string): string` | ||
|
||
Formats the given phone number | ||
|
||
#### `update(regionCode: string): void` | ||
|
||
Update formatter regionCode which will format number based on that (eg: 'CA' | 'JP' etc.) | ||
|
||
#### `getNormalizedNumber(phoneNumber: string): string` | ||
|
||
Returns phone number in E164 format | ||
|
||
#### `getNationalNumber(phoneNumber: string): string` | ||
|
||
Returns the non-formatted version without the country code | ||
|
||
#### `requiresItalianLeadingZero(phoneNumber: string)` | ||
|
||
Indicates if the leading zero of a national number should be retained when dialling internationally | ||
|
||
#### `updateCountryCode(phoneNumber: string): void` | ||
|
||
Updates the country code of the formatter based on the phoneNumber passed | ||
|
||
## Exported functions | ||
|
||
### `getRegionCodeFromNumber(phoneNumber: string): string` | ||
|
||
Returns the region code from the provided phone number | ||
|
||
### `getCountryCodeFromNumber(phoneNumber: string): number | undefined` | ||
|
||
Returns the country code from the provided phone number |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
{ | ||
"name": "@shopify/phone", | ||
"version": "0.0.0", | ||
"license": "MIT", | ||
"description": "Phone number utilities for formatting phone numbers", | ||
"main": "index.js", | ||
"types": "./build/ts/index.d.ts", | ||
"scripts": {}, | ||
"sideEffects": false, | ||
"publishConfig": { | ||
"access": "public", | ||
"@shopify:registry": "https://registry.npmjs.org" | ||
}, | ||
"author": "Shopify Inc.", | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/Shopify/quilt.git", | ||
"directory": "packages/phone" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/Shopify/quilt/issues" | ||
}, | ||
"homepage": "https://github.com/Shopify/quilt/blob/main/packages/phone/README.md", | ||
"engines": { | ||
"node": "^14.17.0 || >=16.0.0" | ||
}, | ||
"dependencies": { | ||
"google-libphonenumber": "^3.2.17" | ||
}, | ||
"devDependencies": { | ||
"@types/google-libphonenumber": "^7.4.16" | ||
}, | ||
"files": [ | ||
"build/", | ||
"!build/*.tsbuildinfo", | ||
"!build/ts/**/tests/", | ||
"index.js", | ||
"index.mjs", | ||
"index.esnext" | ||
], | ||
"module": "index.mjs", | ||
"esnext": "index.esnext", | ||
"exports": { | ||
".": { | ||
"types": "./build/ts/index.d.ts", | ||
"esnext": "./index.esnext", | ||
"import": "./index.mjs", | ||
"require": "./index.js" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import {buildConfig} from '../../config/rollup.mjs'; | ||
|
||
export default buildConfig(import.meta.url, { | ||
entries: ['./src/index.ts'], | ||
entrypoints: {index: './src/index.ts'}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
import {AsYouTypeFormatter, PhoneNumberFormat} from 'google-libphonenumber'; | ||
|
||
import { | ||
digitOnlyNumber, | ||
FORMATTING_CHARACTERS_REGEX, | ||
getRegionCodeForNumber, | ||
phoneUtil, | ||
} from './utilities'; | ||
|
||
const US_COUNTRY_CODE = '1'; | ||
const RUSSIA_COUNTRY_CODE = '7'; | ||
|
||
export default class PhoneNumberFormatter { | ||
formatter: AsYouTypeFormatter; | ||
regionCode: string; | ||
countryCode: number; | ||
|
||
constructor(regionCode: string) { | ||
this.regionCode = regionCode; | ||
this.countryCode = getCountryCodeFromRegionCode(regionCode); | ||
this.formatter = new AsYouTypeFormatter(this.regionCode); | ||
} | ||
|
||
format(phoneNumber: string): string { | ||
this.updateCountryCode(phoneNumber); | ||
this.formatter.clear(); | ||
return formatNumber(phoneNumber, this.formatter) || ''; | ||
} | ||
|
||
/* | ||
* Update formatter regionCode which will format number based on that | ||
* region code | ||
* @regionCode: eg: 'CA' | 'JP' etc. | ||
*/ | ||
update(regionCode: string): void { | ||
this.countryCode = getCountryCodeFromRegionCode(regionCode); | ||
this.regionCode = regionCode; | ||
this.formatter = new AsYouTypeFormatter(this.regionCode); | ||
} | ||
|
||
// This returns phone number in E164 format. | ||
getNormalizedNumber(phoneNumber: string): string { | ||
try { | ||
const parsedPhoneNumber = phoneUtil().parseAndKeepRawInput( | ||
phoneNumber, | ||
this.regionCode, | ||
); | ||
return phoneUtil().format(parsedPhoneNumber, PhoneNumberFormat.E164); | ||
} catch (_err) { | ||
return phoneNumber; | ||
} | ||
} | ||
|
||
getNationalNumber(phoneNumber: string): string { | ||
const onlyDigitsNumber = digitOnlyNumber(phoneNumber); | ||
if (!this.regionCode || onlyDigitsNumber.length < 4) { | ||
// phoneUtil() cannot find the national number when the phone number is too short | ||
// so we try to guess based on single digit country codes (1 - US, 7 - RU) | ||
if ( | ||
onlyDigitsNumber.startsWith(US_COUNTRY_CODE) || | ||
onlyDigitsNumber.startsWith(RUSSIA_COUNTRY_CODE) | ||
) { | ||
return phoneNumber.slice(3); | ||
} | ||
return ''; | ||
} | ||
try { | ||
const nationalNumber = phoneUtil() | ||
.parseAndKeepRawInput(phoneNumber, this.regionCode) | ||
.getNationalNumber(); | ||
|
||
return nationalNumber ? nationalNumber.toString() : ''; | ||
} catch (_err) { | ||
return ''; | ||
} | ||
} | ||
|
||
// Indicates if the leading zero of a national number should be retained when dialling internationally | ||
requiresItalianLeadingZero(phoneNumber: string) { | ||
try { | ||
return Boolean( | ||
phoneUtil() | ||
.parseAndKeepRawInput(phoneNumber, this.regionCode) | ||
.getItalianLeadingZero(), | ||
); | ||
} catch { | ||
return undefined; | ||
} | ||
} | ||
|
||
/* | ||
* Updates the country code of the formatter based on the phoneNumber passed | ||
*/ | ||
private updateCountryCode(phoneNumber: string): void { | ||
if (!phoneNumber.includes('+') || digitOnlyNumber(phoneNumber).length < 1) { | ||
return; | ||
} | ||
|
||
let newRegionCode; | ||
|
||
/* If country code is 1, the region code can be CA or US, so we need to | ||
* guess base on the phone number. | ||
* This can only be done when phone number is long enough | ||
*/ | ||
if (digitOnlyNumber(phoneNumber).length > 4) { | ||
newRegionCode = getRegionCodeForNumber(phoneNumber); | ||
} else { | ||
const newCountryCode = getCountryCodeFromNumber(phoneNumber); | ||
if (newCountryCode && this.countryCode !== newCountryCode) { | ||
newRegionCode = getRegionCodeForCountryCode(newCountryCode); | ||
} | ||
} | ||
|
||
if (newRegionCode) { | ||
this.update(newRegionCode); | ||
} | ||
} | ||
} | ||
|
||
function getCountryCodeFromRegionCode(regionCode: string): number { | ||
return phoneUtil().getCountryCodeForRegion(regionCode); | ||
} | ||
|
||
export function getRegionCodeFromNumber(phoneNumber: string): string { | ||
const countryCodeFromNumber = getCountryCodeFromNumber(phoneNumber); | ||
|
||
return ( | ||
getRegionCodeForNumber(phoneNumber) || | ||
(countryCodeFromNumber && | ||
getRegionCodeForCountryCode(countryCodeFromNumber)) || | ||
getRegionCodeForCountryCode(1) | ||
); | ||
} | ||
|
||
export function getCountryCodeFromNumber( | ||
phoneNumber: string, | ||
): number | undefined { | ||
/* | ||
* we map (1 - US, 7 - RU) when the phone number is too short (this handles the key by key input) | ||
*/ | ||
const onlyDigitsNumber = digitOnlyNumber(phoneNumber); | ||
if (onlyDigitsNumber.length < 4) { | ||
if (onlyDigitsNumber.startsWith(US_COUNTRY_CODE)) { | ||
return Number(US_COUNTRY_CODE); | ||
} | ||
if (onlyDigitsNumber.startsWith(RUSSIA_COUNTRY_CODE)) { | ||
return Number(RUSSIA_COUNTRY_CODE); | ||
} | ||
} | ||
|
||
try { | ||
return phoneUtil().parse(phoneNumber).getCountryCode(); | ||
} catch (_err) { | ||
return parseInt(phoneNumber.split(' ')[0].replace('+', ''), 10); | ||
} | ||
} | ||
|
||
function getRegionCodeForCountryCode(countryCode: number): string { | ||
return phoneUtil().getRegionCodeForCountryCode(countryCode); | ||
} | ||
|
||
function formatNumber( | ||
phoneNumber: string, | ||
formatter: AsYouTypeFormatter, | ||
): string | undefined { | ||
let newValue; | ||
|
||
for (const char of phoneNumber.replace(FORMATTING_CHARACTERS_REGEX, '')) { | ||
newValue = formatter.inputDigit(char); | ||
} | ||
|
||
return newValue; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export {default, getRegionCodeFromNumber} from './PhoneNumberFormatter'; | ||
export * from './utilities'; |
Oops, something went wrong.