svelte-tel-input 0.14.2 → 1.0.0
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/CHANGELOG.md +20 -0
- package/README.md +36 -11
- package/assets/allCountry.js +1 -0
- package/components/Input/AdvancedTelInput.svelte +12 -39
- package/components/Input/AdvancedTelInput.svelte.d.ts +5 -5
- package/components/Input/TelInput.svelte +94 -31
- package/components/Input/TelInput.svelte.d.ts +21 -7
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/package.json +41 -105
- package/{style → styles}/flags.css +0 -0
- package/types/index.d.ts +24 -1
- package/utils/directives/telInputAction.d.ts +3 -0
- package/utils/directives/telInputAction.js +20 -0
- package/utils/helpers.d.ts +71 -4
- package/utils/helpers.js +220 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# svelte-tel-input
|
|
2
2
|
|
|
3
|
+
## 1.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- feat: prevent typing non-tel input characters into the input field. ([#108](https://github.com/gyurielf/svelte-tel-input/pull/108))
|
|
8
|
+
|
|
9
|
+
- fix: now you can use value prop as a single entry ([#108](https://github.com/gyurielf/svelte-tel-input/pull/108))
|
|
10
|
+
|
|
11
|
+
- feat: support monorepos ([#108](https://github.com/gyurielf/svelte-tel-input/pull/108))
|
|
12
|
+
|
|
13
|
+
- feat: sanitize pasted E164 number ([#108](https://github.com/gyurielf/svelte-tel-input/pull/108))
|
|
14
|
+
|
|
15
|
+
- feat: switch country automatically if the pasted/entered phone number contains a valid country calling code ([#108](https://github.com/gyurielf/svelte-tel-input/pull/108))
|
|
16
|
+
|
|
17
|
+
- fix: clear input on manual country change ([#108](https://github.com/gyurielf/svelte-tel-input/pull/108))
|
|
18
|
+
|
|
19
|
+
### Minor Changes
|
|
20
|
+
|
|
21
|
+
- chore: tweak types ([#108](https://github.com/gyurielf/svelte-tel-input/pull/108))
|
|
22
|
+
|
|
3
23
|
## 0.14.2
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -6,7 +6,14 @@
|
|
|
6
6
|
|
|
7
7
|
> Lightweight svelte tel/phone input standardizer.
|
|
8
8
|
|
|
9
|
-
The package is
|
|
9
|
+
The package is recently bumped to `1.0`. If you experience any problems, please open an issue, to be able to me to fix it.
|
|
10
|
+
|
|
11
|
+
## Goals
|
|
12
|
+
|
|
13
|
+
- Solve the problem that a users can enter the same phone number in different formats.
|
|
14
|
+
- Storing a phone number in a standard format, that can be indexable and searchable in any database.
|
|
15
|
+
- Should be accessible for the the browser. Eg. for a `<a href="tel+36201234567 />`.
|
|
16
|
+
- The stored phone number format can be useable for any SMS gateway(e.g for 2FA) and if somebody can call the number from anywhere, it should work.
|
|
10
17
|
|
|
11
18
|
## Installation
|
|
12
19
|
|
|
@@ -16,6 +23,14 @@ Svelte Tel Input is distributed via [npm](https://www.npmjs.com/package/svelte-t
|
|
|
16
23
|
npm install --save svelte-tel-input
|
|
17
24
|
```
|
|
18
25
|
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
- Parse and validate phone number.You can store one exact format (`E164`), no matter how users type their phone numbers.
|
|
29
|
+
- Format (specified to its country), to make it more readable.
|
|
30
|
+
- Optionally it can set the user's current country automatically, via IP lookup.
|
|
31
|
+
- Prevent non-digits typing into the input, except the `+` sign (and `space` optionally).
|
|
32
|
+
- Handle copy-pasted phone numbers, it's sanitize non-digit characters except the `+` sign (and `space` optionally).
|
|
33
|
+
|
|
19
34
|
## Usage
|
|
20
35
|
|
|
21
36
|
### Basic
|
|
@@ -25,30 +40,42 @@ npm install --save svelte-tel-input
|
|
|
25
40
|
```html
|
|
26
41
|
<script lang="ts">
|
|
27
42
|
import TelInput from 'svelte-tel-input';
|
|
28
|
-
import type { NormalizedTelNumber, CountryCode } from 'svelte-tel-input/types';
|
|
43
|
+
import type { NormalizedTelNumber, CountryCode, E164Number } from 'svelte-tel-input/types';
|
|
29
44
|
|
|
30
45
|
// Any Country Code Alpha-2 (ISO 3166)
|
|
31
|
-
let country: CountryCode = '
|
|
46
|
+
let country: CountryCode | null = 'HU';
|
|
32
47
|
|
|
48
|
+
// You must use E164 number format. It's guarantee the parsing and storing consistency.
|
|
49
|
+
let value: E164Number | null = '+36301234567';
|
|
50
|
+
|
|
51
|
+
// Optional - Extended information about the parsed phone number
|
|
33
52
|
let parsedTelInput: NormalizedTelNumber | null = null;
|
|
34
53
|
</script>
|
|
35
54
|
|
|
36
|
-
<TelInput
|
|
55
|
+
<TelInput bind:country bind:parsedTelInput bind:value class="any class passed down" />
|
|
37
56
|
```
|
|
38
57
|
|
|
39
58
|
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
40
59
|
|
|
41
|
-
##
|
|
60
|
+
## API
|
|
42
61
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
62
|
+
The default export of the library is the main TelInput component. It has the following props:
|
|
63
|
+
|
|
64
|
+
| Props | Type | Default Value | Usage |
|
|
65
|
+
| -------------- | --------------------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
66
|
+
| country | `CountryCode \| null` | `null` | It's accept any Country Code Alpha-2 (ISO 3166). You can set manually (e.g: by the user via a select). The parser will inspect the entered phone number and if it detect a valid country calling code, then it's automatically set the country to according to the detected country calling code. E.g: `+36` -> `HU` |
|
|
67
|
+
| disabled | `boolean` | `false` | It's block the parser and prevent entering input. You must handle its styling on your own. |
|
|
68
|
+
| valid | `boolean` | `true` | Indicates whether the entered tel number validity. |
|
|
69
|
+
| value | `E164Number \| null` | `null` | [E164](https://en.wikipedia.org/wiki/E.164) is the international format of phone.numbers. This is the main entry point to store and/or load an existent phone number. |
|
|
70
|
+
| parsedTelInput | `NormalizedTelInput \|null` | `null` | All of the formatted results of the tel input. |
|
|
71
|
+
| class | `string` | `` | You can pass down any classname to the component |
|
|
47
72
|
|
|
48
73
|
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
49
74
|
|
|
50
75
|
## Dependencies
|
|
51
76
|
|
|
77
|
+
[svelte](https://svelte.dev/)
|
|
78
|
+
|
|
52
79
|
[libphonenumber-js](https://gitlab.com/catamphetamine/libphonenumber-js)
|
|
53
80
|
|
|
54
81
|
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
@@ -70,8 +97,6 @@ npm install --save svelte-tel-input
|
|
|
70
97
|
- [x] Add basics docs and examples
|
|
71
98
|
- [ ] Add advanced examples
|
|
72
99
|
- [ ] Improve A11Y
|
|
73
|
-
- [ ] Default country sould be optional. ( ip | browserLang |off )
|
|
74
|
-
- [ ] Simlify code and types
|
|
75
100
|
|
|
76
101
|
See the [open issues](https://github.com/gyurielf/svelte-tel-input/issues) for a list of proposed features (and known issues).
|
|
77
102
|
|
package/assets/allCountry.js
CHANGED
|
@@ -270,6 +270,7 @@ const allCountries = [
|
|
|
270
270
|
['Tokelau', 'tk', '690'],
|
|
271
271
|
['Tonga', 'to', '676'],
|
|
272
272
|
['Trinidad and Tobago', 'tt', '1', 22, ['868']],
|
|
273
|
+
['Tristan da Cunha', 'ta', '290'],
|
|
273
274
|
['Tunisia (تونس)', 'tn', '216'],
|
|
274
275
|
['Turkey (Türkiye)', 'tr', '90'],
|
|
275
276
|
['Turkmenistan', 'tm', '993'],
|
|
@@ -4,31 +4,14 @@ import { clickOutsideAction } from '../../utils/directives/clickOutsideAction';
|
|
|
4
4
|
import TelInput from './TelInput.svelte';
|
|
5
5
|
import { isSelected } from '../../utils/helpers';
|
|
6
6
|
export let searchText = '';
|
|
7
|
-
|
|
8
|
-
id: 'HU',
|
|
9
|
-
label: 'Hungary (Magyarország) +36',
|
|
10
|
-
name: 'Hungary (Magyarország)',
|
|
11
|
-
iso2: 'HU',
|
|
12
|
-
dialCode: '36',
|
|
13
|
-
priority: 0,
|
|
14
|
-
areaCodes: null
|
|
15
|
-
};
|
|
7
|
+
let selected;
|
|
16
8
|
export let clickOutside = true;
|
|
17
9
|
export let closeOnClick = true;
|
|
18
10
|
export let disabled = false;
|
|
19
|
-
export let parsedTelInput =
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
countryCallingCode: '36',
|
|
24
|
-
formattedNumber: '+36 30 123 4567',
|
|
25
|
-
nationalNumber: '301234567',
|
|
26
|
-
formatInternational: '+36 30 123 4567',
|
|
27
|
-
formatOriginal: '30 123 4567',
|
|
28
|
-
formatNational: '06 30 123 4567',
|
|
29
|
-
uri: 'tel:+36301234567',
|
|
30
|
-
e164: '+36301234567'
|
|
31
|
-
};
|
|
11
|
+
export let parsedTelInput = null;
|
|
12
|
+
export let value;
|
|
13
|
+
$: selectedCountryDialCode =
|
|
14
|
+
normalizedCountries.find((el) => el.iso2 === selected)?.dialCode || null;
|
|
32
15
|
let isOpen = false;
|
|
33
16
|
$: isValid = parsedTelInput?.isValid ?? false;
|
|
34
17
|
const toggleDropDown = (e) => {
|
|
@@ -59,18 +42,7 @@ const handleSelect = (val, e) => {
|
|
|
59
42
|
if (disabled)
|
|
60
43
|
return;
|
|
61
44
|
e?.preventDefault();
|
|
62
|
-
if (
|
|
63
|
-
if (typeof selected === 'object' && typeof val === 'object' && selected.id !== val.id) {
|
|
64
|
-
selected = val;
|
|
65
|
-
onChange(val);
|
|
66
|
-
selectClick();
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
dispatch('same', { option: val });
|
|
70
|
-
selectClick();
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
else if (((selected === undefined || selected === null) && typeof val === 'object') ||
|
|
45
|
+
if (((selected === undefined || selected === null) && typeof val === 'object') ||
|
|
74
46
|
(typeof selected === typeof val && selected !== val)) {
|
|
75
47
|
selected = val;
|
|
76
48
|
onChange(val);
|
|
@@ -102,8 +74,8 @@ const onChange = (selectedCountry) => {
|
|
|
102
74
|
>
|
|
103
75
|
{#if selected && selected !== null}
|
|
104
76
|
<div class="inline-flex items-center text-left">
|
|
105
|
-
<span class="flag flag-{selected.
|
|
106
|
-
<span class=" text-gray-500">+{
|
|
77
|
+
<span class="flag flag-{selected.toLowerCase()} flex-shrink-0 mr-3" />
|
|
78
|
+
<span class=" text-gray-500">+{selectedCountryDialCode}</span>
|
|
107
79
|
</div>
|
|
108
80
|
{:else}
|
|
109
81
|
Please select
|
|
@@ -147,7 +119,7 @@ const onChange = (selectedCountry) => {
|
|
|
147
119
|
/>
|
|
148
120
|
{/if}
|
|
149
121
|
{#each filteredItems as country (country.id)}
|
|
150
|
-
{@const isActive = isSelected(country, selected)}
|
|
122
|
+
{@const isActive = isSelected(country.iso2, selected)}
|
|
151
123
|
<li role="option" aria-selected={isActive}>
|
|
152
124
|
<button
|
|
153
125
|
type="button"
|
|
@@ -157,7 +129,7 @@ const onChange = (selectedCountry) => {
|
|
|
157
129
|
? 'bg-gray-600 dark:text-white'
|
|
158
130
|
: 'dark:hover:text-white dark:text-gray-400'}"
|
|
159
131
|
on:click={(e) => {
|
|
160
|
-
handleSelect(country, e);
|
|
132
|
+
handleSelect(country.iso2, e);
|
|
161
133
|
}}
|
|
162
134
|
>
|
|
163
135
|
<div class="inline-flex items-center text-left">
|
|
@@ -176,8 +148,9 @@ const onChange = (selectedCountry) => {
|
|
|
176
148
|
|
|
177
149
|
<TelInput
|
|
178
150
|
id="tel-input"
|
|
179
|
-
country={selected
|
|
151
|
+
bind:country={selected}
|
|
180
152
|
bind:parsedTelInput
|
|
153
|
+
bind:value
|
|
181
154
|
class="border border-gray-300 border-l-gray-100 dark:border-l-gray-700 dark:border-gray-600 {isValid
|
|
182
155
|
? `bg-gray-50 dark:bg-gray-700
|
|
183
156
|
dark:placeholder-gray-400 dark:text-white text-gray-900`
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { SvelteComponentTyped } from "svelte";
|
|
2
|
-
import type { NormalizedTelNumber,
|
|
2
|
+
import type { NormalizedTelNumber, E164Number } from '../../types';
|
|
3
3
|
declare const __propDef: {
|
|
4
4
|
props: {
|
|
5
5
|
searchText?: string | undefined;
|
|
6
|
-
selected?: Country | null | undefined;
|
|
7
6
|
clickOutside?: boolean | undefined;
|
|
8
7
|
closeOnClick?: boolean | undefined;
|
|
9
8
|
disabled?: boolean | undefined;
|
|
10
9
|
parsedTelInput?: NormalizedTelNumber | null | undefined;
|
|
10
|
+
value: E164Number | null;
|
|
11
11
|
};
|
|
12
12
|
events: {
|
|
13
13
|
change: CustomEvent<any>;
|
|
@@ -16,9 +16,9 @@ declare const __propDef: {
|
|
|
16
16
|
};
|
|
17
17
|
slots: {};
|
|
18
18
|
};
|
|
19
|
-
export
|
|
20
|
-
export
|
|
21
|
-
export
|
|
19
|
+
export type AdvancedTelInputProps = typeof __propDef.props;
|
|
20
|
+
export type AdvancedTelInputEvents = typeof __propDef.events;
|
|
21
|
+
export type AdvancedTelInputSlots = typeof __propDef.slots;
|
|
22
22
|
export default class AdvancedTelInput extends SvelteComponentTyped<AdvancedTelInputProps, AdvancedTelInputEvents, AdvancedTelInputSlots> {
|
|
23
23
|
}
|
|
24
24
|
export {};
|
|
@@ -1,42 +1,96 @@
|
|
|
1
|
-
<script>import {
|
|
2
|
-
import
|
|
1
|
+
<script>import { createEventDispatcher, onMount } from 'svelte';
|
|
2
|
+
import metadata from 'libphonenumber-js/metadata.min.json';
|
|
3
3
|
import { parsePhoneNumberWithError, ParseError } from 'libphonenumber-js';
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
import { telInputAction } from '../../utils/directives/telInputAction';
|
|
5
|
+
import { normalizeTelInput, getCountryForPartialE164Number } from '../../utils/helpers';
|
|
6
|
+
import { watcher } from '../../stores';
|
|
7
|
+
const dispatch = createEventDispatcher();
|
|
8
|
+
export let country;
|
|
9
|
+
export let value;
|
|
10
|
+
export let parsedTelInput;
|
|
11
|
+
export let valid = true;
|
|
8
12
|
export let disabled = false;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
let inputValue = value;
|
|
14
|
+
let prevCountry = country;
|
|
15
|
+
const handleInputAction = (value) => {
|
|
16
|
+
handleParsePhoneNumber(value, country);
|
|
17
|
+
};
|
|
18
|
+
const updateCountry = (countryCode) => {
|
|
19
|
+
country = countryCode;
|
|
20
|
+
prevCountry = countryCode;
|
|
21
|
+
return country;
|
|
22
|
+
};
|
|
23
|
+
const handleParsePhoneNumber = (input, currCountry = null) => {
|
|
24
|
+
if (input) {
|
|
25
|
+
const numberHasCountry = getCountryForPartialE164Number(input, { metadata });
|
|
26
|
+
if (numberHasCountry && numberHasCountry !== prevCountry) {
|
|
27
|
+
updateCountry(numberHasCountry);
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
parsedTelInput = normalizeTelInput(parsePhoneNumberWithError(input, currCountry ?? numberHasCountry));
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
if (err instanceof ParseError) {
|
|
34
|
+
// Not a phone number, non-existent country, etc.
|
|
35
|
+
parsedTelInput = {
|
|
36
|
+
isValid: false,
|
|
37
|
+
error: err.message
|
|
38
|
+
};
|
|
39
|
+
dispatch('parseError', err.message);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
throw err;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// It's keep the html input value on the first parsed format, or the user's format.
|
|
46
|
+
if (parsedTelInput?.isValid && parsedTelInput?.formatOriginal) {
|
|
47
|
+
// It's need for refreshing html input value, if it is the same as the previouly parsed.
|
|
48
|
+
if (inputValue === parsedTelInput?.formatOriginal) {
|
|
49
|
+
inputValue = null;
|
|
50
|
+
}
|
|
51
|
+
inputValue = parsedTelInput?.formatOriginal;
|
|
52
|
+
}
|
|
53
|
+
value = parsedTelInput?.e164 ?? null;
|
|
54
|
+
valid = parsedTelInput?.isValid ?? false;
|
|
55
|
+
dispatch('valid', valid);
|
|
56
|
+
dispatch('parseInput', parsedTelInput);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
if (currCountry !== prevCountry) {
|
|
60
|
+
value = null;
|
|
61
|
+
inputValue = '';
|
|
62
|
+
valid = true;
|
|
63
|
+
}
|
|
64
|
+
prevCountry = currCountry;
|
|
13
65
|
}
|
|
14
|
-
});
|
|
15
|
-
const handleInput = (event) => {
|
|
16
|
-
const inputVal = event.target.value.replace(/\s/g, '');
|
|
17
|
-
rawTelInput = inputVal;
|
|
18
|
-
handleParsePhoneNumber(country, inputVal);
|
|
19
66
|
};
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
67
|
+
const initialize = () => {
|
|
68
|
+
if (value && country) {
|
|
69
|
+
handleParsePhoneNumber(value, country);
|
|
23
70
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
error: err.message
|
|
30
|
-
};
|
|
71
|
+
else if (value) {
|
|
72
|
+
const numberHasCountry = getCountryForPartialE164Number(value, { metadata });
|
|
73
|
+
if (numberHasCountry) {
|
|
74
|
+
updateCountry(numberHasCountry);
|
|
75
|
+
handleParsePhoneNumber(value, country);
|
|
31
76
|
}
|
|
32
77
|
else {
|
|
33
|
-
|
|
78
|
+
handleParsePhoneNumber(value);
|
|
34
79
|
}
|
|
35
80
|
}
|
|
81
|
+
else if (parsedTelInput && parsedTelInput.phoneNumber) {
|
|
82
|
+
handleParsePhoneNumber(parsedTelInput.phoneNumber, country);
|
|
83
|
+
}
|
|
36
84
|
};
|
|
85
|
+
onMount(() => {
|
|
86
|
+
initialize();
|
|
87
|
+
});
|
|
88
|
+
let initRun = true;
|
|
37
89
|
const watchFunction = () => {
|
|
38
|
-
if (
|
|
39
|
-
handleParsePhoneNumber(
|
|
90
|
+
if (!initRun) {
|
|
91
|
+
handleParsePhoneNumber(null, country);
|
|
92
|
+
}
|
|
93
|
+
initRun = false;
|
|
40
94
|
};
|
|
41
95
|
const countryChangeWatch = watcher(null, watchFunction);
|
|
42
96
|
$: $countryChangeWatch = country;
|
|
@@ -44,9 +98,18 @@ $: $countryChangeWatch = country;
|
|
|
44
98
|
|
|
45
99
|
<input
|
|
46
100
|
{disabled}
|
|
47
|
-
value={parsedTelInput?.formatOriginal ?? rawTelInput ?? ''}
|
|
48
101
|
type="tel"
|
|
49
|
-
|
|
102
|
+
value={inputValue}
|
|
50
103
|
{...$$restProps}
|
|
51
|
-
|
|
104
|
+
class={$$props.class}
|
|
105
|
+
on:beforeinput
|
|
106
|
+
on:blur
|
|
107
|
+
on:change
|
|
108
|
+
on:focus
|
|
109
|
+
on:input
|
|
110
|
+
on:keydown
|
|
111
|
+
on:keypress
|
|
112
|
+
on:keyup
|
|
113
|
+
on:paste
|
|
114
|
+
use:telInputAction={handleInputAction}
|
|
52
115
|
/>
|
|
@@ -1,21 +1,35 @@
|
|
|
1
1
|
import { SvelteComponentTyped } from "svelte";
|
|
2
|
-
import type { NormalizedTelNumber, CountryCode } from '../../types';
|
|
2
|
+
import type { NormalizedTelNumber, CountryCode, E164Number } from '../../types';
|
|
3
3
|
declare const __propDef: {
|
|
4
4
|
props: {
|
|
5
5
|
[x: string]: any;
|
|
6
|
-
country
|
|
7
|
-
|
|
8
|
-
parsedTelInput
|
|
6
|
+
country: CountryCode | null;
|
|
7
|
+
value: E164Number | null;
|
|
8
|
+
parsedTelInput: Partial<NormalizedTelNumber> | null;
|
|
9
|
+
valid?: boolean | undefined;
|
|
9
10
|
disabled?: boolean | undefined;
|
|
10
11
|
};
|
|
11
12
|
events: {
|
|
13
|
+
beforeinput: InputEvent;
|
|
14
|
+
blur: FocusEvent;
|
|
15
|
+
change: Event;
|
|
16
|
+
focus: FocusEvent;
|
|
17
|
+
input: Event;
|
|
18
|
+
keydown: KeyboardEvent;
|
|
19
|
+
keypress: KeyboardEvent;
|
|
20
|
+
keyup: KeyboardEvent;
|
|
21
|
+
paste: ClipboardEvent;
|
|
22
|
+
parseError: CustomEvent<any>;
|
|
23
|
+
valid: CustomEvent<any>;
|
|
24
|
+
parseInput: CustomEvent<any>;
|
|
25
|
+
} & {
|
|
12
26
|
[evt: string]: CustomEvent<any>;
|
|
13
27
|
};
|
|
14
28
|
slots: {};
|
|
15
29
|
};
|
|
16
|
-
export
|
|
17
|
-
export
|
|
18
|
-
export
|
|
30
|
+
export type TelInputProps = typeof __propDef.props;
|
|
31
|
+
export type TelInputEvents = typeof __propDef.events;
|
|
32
|
+
export type TelInputSlots = typeof __propDef.slots;
|
|
19
33
|
export default class TelInput extends SvelteComponentTyped<TelInputProps, TelInputEvents, TelInputSlots> {
|
|
20
34
|
}
|
|
21
35
|
export {};
|
package/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { default } from './components/Input/TelInput.svelte';
|
|
2
|
-
export { getCurrentCountry, isSelected } from './utils/helpers';
|
|
2
|
+
export { getCurrentCountry, isSelected, inputParser, inspectAllowedChars } from './utils/helpers';
|
|
3
3
|
export { clickOutsideAction } from './utils/directives/clickOutsideAction';
|
|
4
4
|
export { normalizedCountries } from './assets';
|
package/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { default } from './components/Input/TelInput.svelte';
|
|
2
|
-
export { getCurrentCountry, isSelected } from './utils/helpers';
|
|
2
|
+
export { getCurrentCountry, isSelected, inputParser, inspectAllowedChars } from './utils/helpers';
|
|
3
3
|
export { clickOutsideAction } from './utils/directives/clickOutsideAction';
|
|
4
4
|
export { normalizedCountries } from './assets';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelte-tel-input",
|
|
3
3
|
"description": "svelte-tel-input",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "1.0.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/gyurielf/svelte-tel-input.git"
|
|
@@ -21,126 +21,62 @@
|
|
|
21
21
|
"node": ">= 16"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"libphonenumber-js": "^1.10.
|
|
24
|
+
"libphonenumber-js": "^1.10.16",
|
|
25
|
+
"svelte": "^3.55.0"
|
|
25
26
|
},
|
|
26
27
|
"devDependencies": {
|
|
27
|
-
"@babel/core": "^7.
|
|
28
|
-
"@babel/preset-env": "^7.
|
|
29
|
-
"@changesets/cli": "^2.
|
|
30
|
-
"@changesets/get-github-info": "^0.5.
|
|
31
|
-
"@changesets/types": "^5.2.
|
|
28
|
+
"@babel/core": "^7.20.12",
|
|
29
|
+
"@babel/preset-env": "^7.20.2",
|
|
30
|
+
"@changesets/cli": "^2.26.0",
|
|
31
|
+
"@changesets/get-github-info": "^0.5.2",
|
|
32
|
+
"@changesets/types": "^5.2.1",
|
|
32
33
|
"@macfja/svelte-persistent-store": "^2.2.1",
|
|
33
|
-
"@sveltejs/adapter-static": "^1.0.
|
|
34
|
-
"@sveltejs/kit": "^1.0.
|
|
35
|
-
"@sveltejs/package": "^1.0.
|
|
34
|
+
"@sveltejs/adapter-static": "^1.0.1",
|
|
35
|
+
"@sveltejs/kit": "^1.0.7",
|
|
36
|
+
"@sveltejs/package": "^1.0.2",
|
|
36
37
|
"@testing-library/jest-dom": "^5.16.5",
|
|
37
38
|
"@testing-library/svelte": "^3.2.2",
|
|
38
|
-
"@types/jest": "^29.2.
|
|
39
|
-
"@typescript-eslint/eslint-plugin": "^5.
|
|
40
|
-
"@typescript-eslint/parser": "^5.
|
|
41
|
-
"autoprefixer": "^10.4.
|
|
42
|
-
"babel-jest": "^29.
|
|
43
|
-
"babel-loader": "^
|
|
44
|
-
"cssnano": "^5.1.
|
|
39
|
+
"@types/jest": "^29.2.5",
|
|
40
|
+
"@typescript-eslint/eslint-plugin": "^5.48.0",
|
|
41
|
+
"@typescript-eslint/parser": "^5.48.0",
|
|
42
|
+
"autoprefixer": "^10.4.13",
|
|
43
|
+
"babel-jest": "^29.3.1",
|
|
44
|
+
"babel-loader": "^9.1.2",
|
|
45
|
+
"cssnano": "^5.1.14",
|
|
45
46
|
"dotenv": "^16.0.3",
|
|
46
|
-
"
|
|
47
|
-
"eslint
|
|
48
|
-
"eslint-
|
|
47
|
+
"edit-package-json": "^0.8.7",
|
|
48
|
+
"eslint": "^8.31.0",
|
|
49
|
+
"eslint-config-prettier": "^8.6.0",
|
|
50
|
+
"eslint-plugin-jest": "^27.2.1",
|
|
49
51
|
"eslint-plugin-svelte3": "^4.0.0",
|
|
50
|
-
"husky": "^8.0.
|
|
51
|
-
"jest": "^29.
|
|
52
|
+
"husky": "^8.0.3",
|
|
53
|
+
"jest": "^29.3.1",
|
|
52
54
|
"jest-matchmedia-mock": "^1.1.0",
|
|
53
55
|
"micromatch": "^4.0.5",
|
|
54
|
-
"postcss": "^8.4.
|
|
56
|
+
"postcss": "^8.4.21",
|
|
55
57
|
"postcss-load-config": "^4.0.1",
|
|
56
|
-
"prettier": "^2.
|
|
57
|
-
"prettier-plugin-svelte": "^2.
|
|
58
|
-
"svelte": "^3.
|
|
59
|
-
"svelte-
|
|
60
|
-
"svelte-inview": "^3.0.1",
|
|
58
|
+
"prettier": "^2.8.1",
|
|
59
|
+
"prettier-plugin-svelte": "^2.9.0",
|
|
60
|
+
"svelte-check": "^3.0.1",
|
|
61
|
+
"svelte-inview": "^3.0.2",
|
|
61
62
|
"svelte-jester": "^2.3.2",
|
|
62
|
-
"svelte-loader": "^3.1.
|
|
63
|
-
"svelte-preprocess": "^
|
|
64
|
-
"svelte2tsx": "^0.
|
|
65
|
-
"tailwindcss": "^3.2.
|
|
63
|
+
"svelte-loader": "^3.1.4",
|
|
64
|
+
"svelte-preprocess": "^5.0.0",
|
|
65
|
+
"svelte2tsx": "^0.6.0",
|
|
66
|
+
"tailwindcss": "^3.2.4",
|
|
66
67
|
"ts-jest": "^29.0.3",
|
|
67
68
|
"tsconfig-paths-webpack-plugin": "^4.0.0",
|
|
68
|
-
"tslib": "^2.4.
|
|
69
|
-
"typescript": "^4.
|
|
70
|
-
"vite": "^
|
|
69
|
+
"tslib": "^2.4.1",
|
|
70
|
+
"typescript": "^4.9.4",
|
|
71
|
+
"vite": "^4.0.4"
|
|
71
72
|
},
|
|
72
|
-
"standard-version": {
|
|
73
|
-
"skip": {
|
|
74
|
-
"tag": true
|
|
75
|
-
},
|
|
76
|
-
"types": [
|
|
77
|
-
{
|
|
78
|
-
"type": "chore",
|
|
79
|
-
"section": "Others (chore)",
|
|
80
|
-
"hidden": false
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
"type": "revert",
|
|
84
|
-
"section": "Reverts",
|
|
85
|
-
"hidden": false
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
"type": "feat",
|
|
89
|
-
"section": "Features",
|
|
90
|
-
"hidden": false
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
"type": "fix",
|
|
94
|
-
"section": "Bug Fixes",
|
|
95
|
-
"hidden": false
|
|
96
|
-
},
|
|
97
|
-
{
|
|
98
|
-
"type": "improvement",
|
|
99
|
-
"section": "Feature Improvements",
|
|
100
|
-
"hidden": false
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
"type": "docs",
|
|
104
|
-
"section": "Docs",
|
|
105
|
-
"hidden": false
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
"type": "style",
|
|
109
|
-
"section": "Styling",
|
|
110
|
-
"hidden": false
|
|
111
|
-
},
|
|
112
|
-
{
|
|
113
|
-
"type": "refactor",
|
|
114
|
-
"section": "Code Refactoring",
|
|
115
|
-
"hidden": false
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
"type": "perf",
|
|
119
|
-
"section": "Performance Improvements",
|
|
120
|
-
"hidden": false
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
"type": "test",
|
|
124
|
-
"section": "Tests",
|
|
125
|
-
"hidden": false
|
|
126
|
-
},
|
|
127
|
-
{
|
|
128
|
-
"type": "build",
|
|
129
|
-
"section": "Build System",
|
|
130
|
-
"hidden": false
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
"type": "ci",
|
|
134
|
-
"section": "CI",
|
|
135
|
-
"hidden": false
|
|
136
|
-
}
|
|
137
|
-
]
|
|
138
|
-
},
|
|
139
|
-
"svelte": ".",
|
|
140
73
|
"type": "module",
|
|
141
74
|
"license": "MIT",
|
|
75
|
+
"types": "./types/index.d.ts",
|
|
142
76
|
"exports": {
|
|
143
77
|
"./package.json": "./package.json",
|
|
144
|
-
".": "./index.js"
|
|
145
|
-
|
|
78
|
+
".": "./index.js",
|
|
79
|
+
"./styles/flags.css": "./styles/flags.css"
|
|
80
|
+
},
|
|
81
|
+
"svelte": "./index.js"
|
|
146
82
|
}
|
|
File without changes
|
package/types/index.d.ts
CHANGED
|
@@ -3,8 +3,10 @@ import type {
|
|
|
3
3
|
CountryCode,
|
|
4
4
|
E164Number,
|
|
5
5
|
NationalNumber,
|
|
6
|
+
MetadataJson,
|
|
6
7
|
PhoneNumber
|
|
7
8
|
} from 'libphonenumber-js';
|
|
9
|
+
import type { Countries } from 'libphonenumber-js/types';
|
|
8
10
|
|
|
9
11
|
export interface Country {
|
|
10
12
|
id: string;
|
|
@@ -50,4 +52,25 @@ export interface NormalizedTelNumber {
|
|
|
50
52
|
export type PhoneNumberParseError = 'NOT_A_NUMBER' | 'INVALID_COUNTRY' | 'TOO_SHORT' | 'TOO_LONG';
|
|
51
53
|
export type PhoneType = 'FIXED_LINE' | 'MOBILE';
|
|
52
54
|
|
|
53
|
-
export type {
|
|
55
|
+
export type {
|
|
56
|
+
CountryCallingCode,
|
|
57
|
+
CountryCode,
|
|
58
|
+
E164Number,
|
|
59
|
+
NationalNumber,
|
|
60
|
+
PhoneNumber,
|
|
61
|
+
Countries,
|
|
62
|
+
MetadataJson
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export interface TelInputValidity {
|
|
66
|
+
value: boolean | null;
|
|
67
|
+
errorMessage?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export type TelInputDispatchEvents = {
|
|
71
|
+
country: CountryCode | null;
|
|
72
|
+
parseError: string;
|
|
73
|
+
parseInput: Partial<NormalizedTelNumber> | null;
|
|
74
|
+
valid: boolean;
|
|
75
|
+
value: E164Number | null;
|
|
76
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { inspectAllowedChars, inputParser } from '../..';
|
|
2
|
+
export const telInputAction = (node, handler) => {
|
|
3
|
+
const onInput = (event) => {
|
|
4
|
+
if (node && node.contains(event.target)) {
|
|
5
|
+
const value = event.target.value;
|
|
6
|
+
const formattedInput = inputParser(value, {
|
|
7
|
+
parseCharacter: inspectAllowedChars,
|
|
8
|
+
allowSpaces: true
|
|
9
|
+
});
|
|
10
|
+
node.value = formattedInput;
|
|
11
|
+
handler(formattedInput);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
node.addEventListener('input', onInput, true);
|
|
15
|
+
return {
|
|
16
|
+
destroy() {
|
|
17
|
+
node.removeEventListener('input', onInput, true);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
};
|
package/utils/helpers.d.ts
CHANGED
|
@@ -1,10 +1,77 @@
|
|
|
1
|
-
import type { PhoneNumber } from '../types';
|
|
1
|
+
import type { PhoneNumber, MetadataJson, Countries, E164Number, CountryCode } from '../types';
|
|
2
2
|
export declare const capitalize: (str: string) => string;
|
|
3
3
|
export declare const getCurrentCountry: () => Promise<string | undefined>;
|
|
4
4
|
export declare const isNumber: (value: number) => boolean;
|
|
5
|
-
export declare const normalizeTelInput: (input
|
|
6
|
-
[k: string]: string | boolean |
|
|
5
|
+
export declare const normalizeTelInput: (input?: PhoneNumber) => {
|
|
6
|
+
[k: string]: string | boolean | E164Number | import("libphonenumber-js/types").CountryCallingCode | import("libphonenumber-js/types").NationalNumber | null | undefined;
|
|
7
7
|
};
|
|
8
8
|
export declare const isSelected: <T extends {
|
|
9
9
|
id: string;
|
|
10
|
-
}>(itemToSelect: T, selectedItem: T | null | undefined) => boolean;
|
|
10
|
+
}>(itemToSelect: string | T, selectedItem: string | T | null | undefined) => boolean;
|
|
11
|
+
export declare const getInternationalPhoneNumberPrefix: (country: CountryCode, metadata: MetadataJson) => string;
|
|
12
|
+
/**
|
|
13
|
+
* Trims phone number digits if they exceed the maximum possible length
|
|
14
|
+
* for a national (significant) number for the country.
|
|
15
|
+
* @param {string} number - A possibly incomplete phone number digits string. Can be a possibly incomplete E.164 phone number.
|
|
16
|
+
* @param {string} country
|
|
17
|
+
* @param {object} metadata - `libphonenumber-js` metadata.
|
|
18
|
+
* @return {string} Can be empty.
|
|
19
|
+
*/
|
|
20
|
+
export declare const trimNumber: (number: E164Number, country: CountryCode, metadata: MetadataJson) => string;
|
|
21
|
+
export declare const getMaxNumberLength: (country: CountryCode, metadata: MetadataJson) => number;
|
|
22
|
+
/**
|
|
23
|
+
* If the phone number being input is an international one
|
|
24
|
+
* then tries to derive the country from the phone number.
|
|
25
|
+
* (regardless of whether there's any country currently selected)
|
|
26
|
+
* @param {string} partialE164Number - A possibly incomplete E.164 phone number.
|
|
27
|
+
* @param {string?} country - Currently selected country.
|
|
28
|
+
* @param {string[]?} countries - A list of available countries. If not passed then "all countries" are assumed.
|
|
29
|
+
* @param {object} metadata - `libphonenumber-js` metadata.
|
|
30
|
+
* @return {string?}
|
|
31
|
+
*/
|
|
32
|
+
export declare const getCountryForPartialE164Number: (partialE164Number: E164Number, { country, countries, required, metadata }: {
|
|
33
|
+
country?: CountryCode | undefined;
|
|
34
|
+
countries?: Countries[] | undefined;
|
|
35
|
+
required?: boolean | undefined;
|
|
36
|
+
metadata: MetadataJson;
|
|
37
|
+
}) => CountryCode | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* Determines the country for a given (possibly incomplete) E.164 phone number.
|
|
40
|
+
* @param {string} number - A possibly incomplete E.164 phone number.
|
|
41
|
+
* @param {object} metadata - `libphonenumber-js` metadata.
|
|
42
|
+
* @return {string?}
|
|
43
|
+
*/
|
|
44
|
+
export declare const getCountryFromPossiblyIncompleteInternationalPhoneNumber: (number: E164Number, metadata: MetadataJson) => CountryCode | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* Parses a partially entered national phone number digits
|
|
47
|
+
* (or a partially entered E.164 international phone number)
|
|
48
|
+
* and returns the national significant number part.
|
|
49
|
+
* National significant number returned doesn't come with a national prefix.
|
|
50
|
+
* @param {string} number - National number digits. Or possibly incomplete E.164 phone number.
|
|
51
|
+
* @param {string?} country
|
|
52
|
+
* @param {object} metadata - `libphonenumber-js` metadata.
|
|
53
|
+
* @return {string} [result]
|
|
54
|
+
*/
|
|
55
|
+
export declare const getNationalSignificantNumberDigits: (number: E164Number, country: CountryCode, metadata: MetadataJson) => import("libphonenumber-js/types").NationalNumber | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Checks if a partially entered E.164 phone number could belong to a country.
|
|
58
|
+
* @param {string} number
|
|
59
|
+
* @param {CountryCode} country
|
|
60
|
+
* @return {boolean}
|
|
61
|
+
*/
|
|
62
|
+
export declare const couldNumberBelongToCountry: (number: E164Number, country: CountryCode, metadata: MetadataJson) => boolean;
|
|
63
|
+
export declare const isSupportedCountry: (country: CountryCode, metadata: MetadataJson) => boolean;
|
|
64
|
+
/**
|
|
65
|
+
* These mappings map a character (key) to a specific digit that should
|
|
66
|
+
* replace it for normalization purposes.
|
|
67
|
+
* @param {string} character
|
|
68
|
+
* @returns {string}
|
|
69
|
+
*/
|
|
70
|
+
export declare const allowedCharacters: (character: string, { spaces }?: {
|
|
71
|
+
spaces?: boolean | undefined;
|
|
72
|
+
}) => string;
|
|
73
|
+
export declare const inputParser: (text: string, { allowSpaces, parseCharacter }: {
|
|
74
|
+
allowSpaces: boolean;
|
|
75
|
+
parseCharacter: (characted: string, val: string, allowSpaces?: boolean) => string;
|
|
76
|
+
}) => string;
|
|
77
|
+
export declare const inspectAllowedChars: (character: string, value: string, allowSpaces?: boolean) => string;
|
package/utils/helpers.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { AsYouType, Metadata, getCountryCallingCode } from 'libphonenumber-js/core';
|
|
1
2
|
export const capitalize = (str) => {
|
|
2
3
|
return (str && str[0].toUpperCase() + str.slice(1).toLowerCase()) || '';
|
|
3
4
|
};
|
|
5
|
+
// Use carefully, it can be rate limited.
|
|
4
6
|
export const getCurrentCountry = async () => {
|
|
5
7
|
try {
|
|
6
8
|
const response = await (await fetch('https://ip2c.org/s')).text();
|
|
@@ -23,6 +25,7 @@ export const normalizeTelInput = (input) => {
|
|
|
23
25
|
const filteredResult = Object.fromEntries(Object.entries({
|
|
24
26
|
countryCode: input ? input.country : null,
|
|
25
27
|
isValid: input ? input.isValid() : false,
|
|
28
|
+
isPossible: input ? input.isPossible() : false,
|
|
26
29
|
phoneNumber: input ? input.number : null,
|
|
27
30
|
countryCallingCode: input ? input.countryCallingCode : null,
|
|
28
31
|
formattedNumber: input ? input.formatInternational() : null,
|
|
@@ -57,7 +60,224 @@ export const isSelected = (itemToSelect, selectedItem) => {
|
|
|
57
60
|
return false;
|
|
58
61
|
}
|
|
59
62
|
}
|
|
63
|
+
else if (itemToSelect === selectedItem) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
60
66
|
else {
|
|
61
67
|
return false;
|
|
62
68
|
}
|
|
63
69
|
};
|
|
70
|
+
export const getInternationalPhoneNumberPrefix = (country, metadata) => {
|
|
71
|
+
const ONLY_DIGITS_REGEXP = /^\d+$/;
|
|
72
|
+
// Standard international phone number prefix: "+" and "country calling code".
|
|
73
|
+
let prefix = '+' + getCountryCallingCode(country, metadata);
|
|
74
|
+
// Get "leading digits" for a phone number of the country.
|
|
75
|
+
// If there're "leading digits" then they can be part of the prefix too.
|
|
76
|
+
const newMetadata = new Metadata(metadata);
|
|
77
|
+
const leadingDigits = newMetadata.numberingPlan?.leadingDigits();
|
|
78
|
+
if (leadingDigits && ONLY_DIGITS_REGEXP.test(leadingDigits)) {
|
|
79
|
+
prefix += leadingDigits;
|
|
80
|
+
}
|
|
81
|
+
return prefix;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Trims phone number digits if they exceed the maximum possible length
|
|
85
|
+
* for a national (significant) number for the country.
|
|
86
|
+
* @param {string} number - A possibly incomplete phone number digits string. Can be a possibly incomplete E.164 phone number.
|
|
87
|
+
* @param {string} country
|
|
88
|
+
* @param {object} metadata - `libphonenumber-js` metadata.
|
|
89
|
+
* @return {string} Can be empty.
|
|
90
|
+
*/
|
|
91
|
+
export const trimNumber = (number, country, metadata) => {
|
|
92
|
+
const nationalSignificantNumberPart = getNationalSignificantNumberDigits(number, country, metadata);
|
|
93
|
+
if (nationalSignificantNumberPart) {
|
|
94
|
+
const overflowDigitsCount = nationalSignificantNumberPart.length - getMaxNumberLength(country, metadata);
|
|
95
|
+
if (overflowDigitsCount > 0) {
|
|
96
|
+
return number.slice(0, number.length - overflowDigitsCount);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return number;
|
|
100
|
+
};
|
|
101
|
+
export const getMaxNumberLength = (country, metadata) => {
|
|
102
|
+
// Get "possible lengths" for a phone number of the country.
|
|
103
|
+
const newMetadata = new Metadata(metadata);
|
|
104
|
+
newMetadata.selectNumberingPlan(country);
|
|
105
|
+
// Return the last "possible length".
|
|
106
|
+
if (newMetadata.numberingPlan) {
|
|
107
|
+
return newMetadata.numberingPlan.possibleLengths()[newMetadata.numberingPlan.possibleLengths().length - 1];
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
throw new Error('There is no metadata object.');
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* If the phone number being input is an international one
|
|
115
|
+
* then tries to derive the country from the phone number.
|
|
116
|
+
* (regardless of whether there's any country currently selected)
|
|
117
|
+
* @param {string} partialE164Number - A possibly incomplete E.164 phone number.
|
|
118
|
+
* @param {string?} country - Currently selected country.
|
|
119
|
+
* @param {string[]?} countries - A list of available countries. If not passed then "all countries" are assumed.
|
|
120
|
+
* @param {object} metadata - `libphonenumber-js` metadata.
|
|
121
|
+
* @return {string?}
|
|
122
|
+
*/
|
|
123
|
+
export const getCountryForPartialE164Number = (partialE164Number, { country, countries, required, metadata }) => {
|
|
124
|
+
if (partialE164Number === '+') {
|
|
125
|
+
// Don't change the currently selected country yet.
|
|
126
|
+
return country;
|
|
127
|
+
}
|
|
128
|
+
const derived_country = getCountryFromPossiblyIncompleteInternationalPhoneNumber(partialE164Number, metadata);
|
|
129
|
+
// If a phone number is being input in international form
|
|
130
|
+
// and the country can already be derived from it,
|
|
131
|
+
// then select that country.
|
|
132
|
+
if (derived_country && (!countries || countries.indexOf(derived_country) >= 0)) {
|
|
133
|
+
return derived_country;
|
|
134
|
+
}
|
|
135
|
+
// If "International" country option has not been disabled
|
|
136
|
+
// and the international phone number entered doesn't correspond
|
|
137
|
+
// to the currently selected country then reset the currently selected country.
|
|
138
|
+
else if (country &&
|
|
139
|
+
!required &&
|
|
140
|
+
!couldNumberBelongToCountry(partialE164Number, country, metadata)) {
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
// Don't change the currently selected country.
|
|
144
|
+
return country;
|
|
145
|
+
};
|
|
146
|
+
/**
|
|
147
|
+
* Determines the country for a given (possibly incomplete) E.164 phone number.
|
|
148
|
+
* @param {string} number - A possibly incomplete E.164 phone number.
|
|
149
|
+
* @param {object} metadata - `libphonenumber-js` metadata.
|
|
150
|
+
* @return {string?}
|
|
151
|
+
*/
|
|
152
|
+
export const getCountryFromPossiblyIncompleteInternationalPhoneNumber = (number, metadata) => {
|
|
153
|
+
const formatter = new AsYouType(undefined, metadata);
|
|
154
|
+
formatter.input(number);
|
|
155
|
+
// // `001` is a special "non-geograpical entity" code
|
|
156
|
+
// // in Google's `libphonenumber` library.
|
|
157
|
+
// if (formatter.getCountry() === '001') {
|
|
158
|
+
// return
|
|
159
|
+
// }
|
|
160
|
+
return formatter.getCountry();
|
|
161
|
+
};
|
|
162
|
+
/**
|
|
163
|
+
* Parses a partially entered national phone number digits
|
|
164
|
+
* (or a partially entered E.164 international phone number)
|
|
165
|
+
* and returns the national significant number part.
|
|
166
|
+
* National significant number returned doesn't come with a national prefix.
|
|
167
|
+
* @param {string} number - National number digits. Or possibly incomplete E.164 phone number.
|
|
168
|
+
* @param {string?} country
|
|
169
|
+
* @param {object} metadata - `libphonenumber-js` metadata.
|
|
170
|
+
* @return {string} [result]
|
|
171
|
+
*/
|
|
172
|
+
export const getNationalSignificantNumberDigits = (number, country, metadata) => {
|
|
173
|
+
// Create "as you type" formatter.
|
|
174
|
+
const formatter = new AsYouType(country, metadata);
|
|
175
|
+
// Input partial national phone number.
|
|
176
|
+
formatter.input(number);
|
|
177
|
+
// Return the parsed partial national phone number.
|
|
178
|
+
const phoneNumber = formatter.getNumber();
|
|
179
|
+
return phoneNumber && phoneNumber.nationalNumber;
|
|
180
|
+
};
|
|
181
|
+
/**
|
|
182
|
+
* Checks if a partially entered E.164 phone number could belong to a country.
|
|
183
|
+
* @param {string} number
|
|
184
|
+
* @param {CountryCode} country
|
|
185
|
+
* @return {boolean}
|
|
186
|
+
*/
|
|
187
|
+
export const couldNumberBelongToCountry = (number, country, metadata) => {
|
|
188
|
+
const intlPhoneNumberPrefix = getInternationalPhoneNumberPrefix(country, metadata);
|
|
189
|
+
let i = 0;
|
|
190
|
+
while (i < number.length && i < intlPhoneNumberPrefix.length) {
|
|
191
|
+
if (number[i] !== intlPhoneNumberPrefix[i]) {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
i++;
|
|
195
|
+
}
|
|
196
|
+
return true;
|
|
197
|
+
};
|
|
198
|
+
export const isSupportedCountry = (country, metadata) => {
|
|
199
|
+
return metadata.countries[country] !== undefined;
|
|
200
|
+
};
|
|
201
|
+
/**
|
|
202
|
+
* These mappings map a character (key) to a specific digit that should
|
|
203
|
+
* replace it for normalization purposes.
|
|
204
|
+
* @param {string} character
|
|
205
|
+
* @returns {string}
|
|
206
|
+
*/
|
|
207
|
+
export const allowedCharacters = (character, { spaces } = {
|
|
208
|
+
spaces: false
|
|
209
|
+
}) => {
|
|
210
|
+
const DIGITS = {
|
|
211
|
+
'0': '0',
|
|
212
|
+
'1': '1',
|
|
213
|
+
'2': '2',
|
|
214
|
+
'3': '3',
|
|
215
|
+
'4': '4',
|
|
216
|
+
'5': '5',
|
|
217
|
+
'6': '6',
|
|
218
|
+
'7': '7',
|
|
219
|
+
'8': '8',
|
|
220
|
+
'9': '9',
|
|
221
|
+
'\uFF10': '0',
|
|
222
|
+
'\uFF11': '1',
|
|
223
|
+
'\uFF12': '2',
|
|
224
|
+
'\uFF13': '3',
|
|
225
|
+
'\uFF14': '4',
|
|
226
|
+
'\uFF15': '5',
|
|
227
|
+
'\uFF16': '6',
|
|
228
|
+
'\uFF17': '7',
|
|
229
|
+
'\uFF18': '8',
|
|
230
|
+
'\uFF19': '9',
|
|
231
|
+
'\u0660': '0',
|
|
232
|
+
'\u0661': '1',
|
|
233
|
+
'\u0662': '2',
|
|
234
|
+
'\u0663': '3',
|
|
235
|
+
'\u0664': '4',
|
|
236
|
+
'\u0665': '5',
|
|
237
|
+
'\u0666': '6',
|
|
238
|
+
'\u0667': '7',
|
|
239
|
+
'\u0668': '8',
|
|
240
|
+
'\u0669': '9',
|
|
241
|
+
'\u06F0': '0',
|
|
242
|
+
'\u06F1': '1',
|
|
243
|
+
'\u06F2': '2',
|
|
244
|
+
'\u06F3': '3',
|
|
245
|
+
'\u06F4': '4',
|
|
246
|
+
'\u06F5': '5',
|
|
247
|
+
'\u06F6': '6',
|
|
248
|
+
'\u06F7': '7',
|
|
249
|
+
'\u06F8': '8',
|
|
250
|
+
'\u06F9': '9' // Eastern-Arabic digit 9,
|
|
251
|
+
};
|
|
252
|
+
// Allow spaces
|
|
253
|
+
if (spaces) {
|
|
254
|
+
const regex = new RegExp('[\\t\\n\\v\\f\\r \\u00a0\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u200b\\u2028\\u2029\\u3000]', 'g');
|
|
255
|
+
if (regex.test(character)) {
|
|
256
|
+
return character;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
// Allow digits
|
|
260
|
+
return DIGITS[character];
|
|
261
|
+
};
|
|
262
|
+
export const inputParser = (text, { allowSpaces, parseCharacter }) => {
|
|
263
|
+
let value = '';
|
|
264
|
+
let index = 0;
|
|
265
|
+
while (index < text.length) {
|
|
266
|
+
const character = parseCharacter(text[index], value, allowSpaces);
|
|
267
|
+
if (character !== undefined) {
|
|
268
|
+
value += character;
|
|
269
|
+
}
|
|
270
|
+
index++;
|
|
271
|
+
}
|
|
272
|
+
return value;
|
|
273
|
+
};
|
|
274
|
+
export const inspectAllowedChars = (character, value, allowSpaces) => {
|
|
275
|
+
// Leading plus is allowed
|
|
276
|
+
if (character === '+') {
|
|
277
|
+
if (!value) {
|
|
278
|
+
return character;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// Allowed characters
|
|
282
|
+
return allowedCharacters(character, { spaces: allowSpaces });
|
|
283
|
+
};
|