@input-kit/phone 0.4.1 → 0.4.2

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/README.md CHANGED
@@ -1,200 +1,221 @@
1
- # @input-kit/phone
2
-
3
- [![npm version](https://img.shields.io/npm/v/%40input-kit%2Fphone)](https://www.npmjs.com/package/@input-kit/phone)
4
- [![npm downloads](https://img.shields.io/npm/dm/%40input-kit%2Fphone)](https://www.npmjs.com/package/@input-kit/phone)
5
- [![CI](https://github.com/harshit-d3v/input-kit-phone/actions/workflows/ci.yml/badge.svg)](https://github.com/harshit-d3v/input-kit-phone/actions/workflows/ci.yml)
6
- [![license](https://img.shields.io/npm/l/%40input-kit%2Fphone)](https://github.com/harshit-d3v/input-kit-phone/blob/main/LICENSE)
7
- [![GitHub stars](https://img.shields.io/github/stars/harshit-d3v/input-kit-phone?style=social)](https://github.com/harshit-d3v/input-kit-phone)
8
-
9
- Headless React phone input with a complete world country-code dataset, searchable country selection, and `libphonenumber-js` powered formatting and validation.
10
-
11
- Source, issues, and contributions: **[github.com/harshit-d3v/input-kit-phone](https://github.com/harshit-d3v/input-kit-phone)** if this package saves you time, a ⭐ there helps others find it.
12
-
13
- ## Latest update
14
-
15
- **0.4.0** **correctness fixes, please upgrade.** E.164 was built by concatenating the dial code onto whatever was typed, so the national trunk prefix people actually type survived into the international form: UK `07400123456` became `+4407400123456`, and India, Germany, France and Australia were wrong the same way. Around 106 countries were affected, validation still reported these as valid, and North American numbers never were — which is why it went unnoticed. Now resolved through libphonenumber metadata, so countries that genuinely keep a leading zero (Italy) still do. Also fixes: an inline `onValidationChange` causing "Maximum update depth exceeded"; input past the country maximum being discarded instead of truncated, which silently blanked an empty field on paste; a country selection that reverted on international numbers; the country button not announcing the selected country to screen readers; focus dropping to the top of the page when the dropdown closed; and types failing to resolve for CommonJS TypeScript consumers under `node16`/`nodenext`.
16
-
17
- **0.3.0** auto-detect respects manual country selection, metadata-based length validation (no more false `too_long` in variable-length countries), input capped at the country's maximum length, `isPhoneTooLong()`, SSR-safe caret handling, dropdown `Home`/`End` + search→list keyboard navigation, unminified published output.
18
-
19
- **0.2.2** npm README cleanup: release notes stay inline; removed pointers to repo-only markdown files.
20
-
21
- **0.2.0** structured validation (`ValidationReason`, `validatePhoneNumber`, `onValidationChange`), `parsePhoneValue`, `getCountryOptions()`, improved `PhoneInput` a11y (click-outside, listbox ARIA) and RTL tests.
22
-
23
- ## Features
24
-
25
- - **245 supported calling regions** derived from `libphonenumber-js` metadata and `world-countries`
26
- - **Headless hook** via `usePhoneInput()` plus an optional **unstyled reference** `PhoneInput` component (class names only — no bundled CSS)
27
- - **Searchable country selector** with country name, ISO code, and dial-code matching
28
- - **Real formatting and validation** powered by `libphonenumber-js`
29
- - **International detection** for pasted or typed `+` / `00` numbers
30
- - **TypeScript-first** exports for countries, helpers, hook return values, and component refs
31
-
32
- ## Installation
33
-
34
- ```bash
35
- npm install @input-kit/phone
36
- ```
37
-
38
- ## Quick Start
39
-
40
- ### Component
41
-
42
- ```tsx
43
- import { PhoneInput } from '@input-kit/phone';
44
- import './phone-input.css'; // your own stylesheet — the package ships no CSS
45
-
46
- function Example() {
47
- return (
48
- <PhoneInput
49
- defaultCountry="US"
50
- onChange={(phone, country) => {
51
- console.log(phone, country?.code);
52
- }}
53
- />
54
- );
55
- }
56
- ```
57
-
58
- ### Hook
59
-
60
- ```tsx
61
- import { usePhoneInput } from '@input-kit/phone';
62
-
63
- function Example() {
64
- const {
65
- inputProps,
66
- country,
67
- countryButtonProps,
68
- filteredCountries,
69
- selectCountry,
70
- isOpen,
71
- isValid,
72
- } = usePhoneInput({
73
- defaultCountry: 'US',
74
- onChange: (phone, nextCountry) => console.log(phone, nextCountry?.dialCode),
75
- });
76
-
77
- return (
78
- <div>
79
- <button {...countryButtonProps}>
80
- {country?.flag} {country?.dialCode}
81
- </button>
82
-
83
- {isOpen && (
84
- <div>
85
- {filteredCountries.map((candidate) => (
86
- <button key={candidate.code} onClick={() => selectCountry(candidate)}>
87
- {candidate.flag} {candidate.name} {candidate.dialCode}
88
- </button>
89
- ))}
90
- </div>
91
- )}
92
-
93
- <input {...inputProps} />
94
- {!isValid && <span>Invalid phone number</span>}
95
- </div>
96
- );
97
- }
98
- ```
99
-
100
- ## Phone values
101
-
102
- | Field | Meaning |
103
- | --- | --- |
104
- | `phone` | National digits stored by the hook (default) |
105
- | `fullPhone` | National number plus dial code when `includeDialCode` is `true` |
106
- | `onChange(phone, country)` | Same contract as `phone` / `includeDialCode` |
107
- | E.164 for APIs | `parsePhoneValue(phone, country).e164` when valid prefer this over raw concatenation |
108
-
109
- ## Known behavior
110
-
111
- Formatting, length checks, and validity follow **[libphonenumber-js](https://www.npmjs.com/package/libphonenumber-js)** (same family as `react-phone-number-input`). The package does not implement per-country rules outside that library.
112
-
113
- ## Form integrations
114
-
115
- Controlled `value` / `onChange` with `usePhoneInput` or `PhoneInput`. React Hook Form: wrap with `Controller` and pass `field.value`, `field.onChange`, and `field.onBlur`. Use `onValidationChange` to sync `isValid` / `message` with form errors. Submit-time checks: `parsePhoneValue(phone, country)` or `validatePhoneNumber`.
116
-
117
- ## Styled example
118
-
119
- A minimal Vite demo using only the hook lives in **[examples/react-styled/](./examples/react-styled/)**.
120
-
121
- ## Migration from 0.1.x
122
-
123
- Compatibility aliases remain exported. Prefer the newer names in new code:
124
-
125
- | Deprecated | Replacement |
126
- | --- | --- |
127
- | `setValue` | `setPhone` |
128
- | `value` (hook) | `phone` |
129
- | `allowedCountries` | `onlyCountries` |
130
- | `excludedCountries` | `excludeCountries` |
131
- | `autoDetectCountry` | `autoDetect` |
132
- | `toggle` / `open` / `close` | `toggleDropdown` / `openDropdown` / `closeDropdown` |
133
- | `countries` (hook list) | `filteredCountries` |
134
- | `countrySelectorProps` | `countryButtonProps` |
135
-
136
- Validation is unified in 0.2.0: `isValid` and `error` come from the same `validatePhoneNumber` call. Use `validationReason` or `onValidationChange` for structured form messages.
137
-
138
- ## Development
139
-
140
- Requires [Bun](https://bun.sh) (see `packageManager` in `package.json`).
141
-
142
- ```bash
143
- bun install
144
- bun run test
145
- bun run typecheck
146
- bun run build
147
- bun run lint
148
- ```
149
-
150
- Manual browser check: `test-demo/` (static HTML).
151
-
152
- ## Contributing
153
-
154
- Bug reports, feature requests, and pull requests are welcome — see [CONTRIBUTING.md](https://github.com/harshit-d3v/input-kit-phone/blob/main/CONTRIBUTING.md). In short: open an issue with a minimal reproduction (include the exact phone number and country for formatting/validation bugs), and for PRs run `bun run test`, `bun run typecheck`, and `bun run lint` before submitting.
155
-
156
- ## Exports
157
-
158
- ### Components and hooks
159
-
160
- - `PhoneInput`
161
- - `usePhoneInput(options)`
162
-
163
- ### Country data
164
-
165
- - `countries`
166
- - `getCountryByCode(code)`
167
- - `getCountryByDialCode(dialCode)`
168
- - `getCountriesByDialCode(dialCode)`
169
- - `getCountryOptions({ locale?, preferredCountries?, excludeCountries?, onlyCountries? })`
170
- - `detectCountryFromPhone(phone)`
171
-
172
- ### Utilities
173
-
174
- - `cleanPhone`, `formatPhone`, `unformatPhone`, `validatePhone`, `validatePhoneLength`
175
- - `validatePhoneNumber` `{ isValid, reason, message, error }`
176
- - `parsePhoneValue` → `{ country, nationalNumber, e164, isValid }`
177
- - `addDialCode`, `removeDialCode`, `filterCountries`, `getPlaceholder`
178
-
179
- Compatibility aliases: `stripNonDigits`, `detectCountry`, `formatPhoneNumber`, `parseToE164`, `getNationalNumber`, `isPhoneNumberComplete`, `formatAsYouType`, `normalizePhoneNumber`, `phoneNumbersEqual`, `getCountryDisplayLabel`, `limitInputLength`.
180
-
181
- ## `usePhoneInput(options)`
182
-
183
- | Option | Type | Default | Description |
184
- | --- | --- | --- | --- |
185
- | `defaultCountry` | `string` | `'US'` | Default selected country |
186
- | `preferredCountries` | `string[]` | - | Countries shown first in search results |
187
- | `excludeCountries` | `string[]` | - | Countries to exclude |
188
- | `onlyCountries` | `string[]` | - | Restrict selection to these countries |
189
- | `autoDetect` | `boolean` | `true` | Detect country from international numbers |
190
- | `formatOnType` | `boolean` | `true` | Apply live formatting |
191
- | `includeDialCode` | `boolean` | `false` | Return values with dial code included |
192
- | `required` | `boolean` | `false` | Empty value is invalid |
193
- | `validator` | `(phone, country) => boolean` | - | Custom validation override |
194
- | `onValidationChange` | `(state) => void` | - | Fires when validation result changes |
195
-
196
- Important returned fields: `phone`, `fullPhone`, `country`, `isValid`, `validationReason`, `error`, `onValidationChange`, `filteredCountries`, `inputProps`, `countryButtonProps`, `dropdownProps`, `getCountryOptionId`.
197
-
198
- ## License
199
-
200
- MIT © Input Kit
1
+ # @input-kit/phone
2
+
3
+ [![npm version](https://img.shields.io/npm/v/%40input-kit%2Fphone)](https://www.npmjs.com/package/@input-kit/phone)
4
+ [![npm downloads](https://img.shields.io/npm/dm/%40input-kit%2Fphone)](https://www.npmjs.com/package/@input-kit/phone)
5
+ [![CI](https://github.com/harshit-d3v/input-kit-phone/actions/workflows/ci.yml/badge.svg)](https://github.com/harshit-d3v/input-kit-phone/actions/workflows/ci.yml)
6
+ [![license](https://img.shields.io/npm/l/%40input-kit%2Fphone)](https://github.com/harshit-d3v/input-kit-phone/blob/main/LICENSE)
7
+ [![GitHub stars](https://img.shields.io/github/stars/harshit-d3v/input-kit-phone?style=social)](https://github.com/harshit-d3v/input-kit-phone)
8
+
9
+ Headless React phone input with a complete world country-code dataset, searchable country selection, and `libphonenumber-js` powered formatting and validation.
10
+
11
+ Source, issues, and contributions: **[github.com/harshit-d3v/input-kit-phone](https://github.com/harshit-d3v/input-kit-phone)**. If this package saves you time, a ⭐ there helps others find it.
12
+
13
+ ## Latest update
14
+
15
+ **0.4.2**: documents and locks the built-in **format as you type**. Live national formatting on every keystroke was already there (`formatOnType`, on by default), but it was barely documented and had no regression tests. Added a README section and seven tests covering national, international, per-country, delete and paste formatting, plus proof that the stored and submitted value stays clean. No behavior change.
16
+
17
+ **0.4.0**: **correctness fixes, please upgrade.** E.164 was built by concatenating the dial code onto whatever was typed, so the national trunk prefix people actually type survived into the international form: UK `07400123456` became `+4407400123456`, and India, Germany, France and Australia were wrong the same way. Around 106 countries were affected, validation still reported these as valid, and North American numbers never were, which is why it went unnoticed. Now resolved through libphonenumber metadata, so countries that genuinely keep a leading zero (Italy) still do. Also fixes: an inline `onValidationChange` causing "Maximum update depth exceeded"; input past the country maximum being discarded instead of truncated, which silently blanked an empty field on paste; a country selection that reverted on international numbers; the country button not announcing the selected country to screen readers; focus dropping to the top of the page when the dropdown closed; and types failing to resolve for CommonJS TypeScript consumers under `node16`/`nodenext`.
18
+
19
+ **0.3.0**: auto-detect respects manual country selection, metadata-based length validation (no more false `too_long` in variable-length countries), input capped at the country's maximum length, `isPhoneTooLong()`, SSR-safe caret handling, dropdown `Home`/`End` + search→list keyboard navigation, unminified published output.
20
+
21
+ **0.2.2**: npm README cleanup: release notes stay inline; removed pointers to repo-only markdown files.
22
+
23
+ **0.2.0**: structured validation (`ValidationReason`, `validatePhoneNumber`, `onValidationChange`), `parsePhoneValue`, `getCountryOptions()`, improved `PhoneInput` a11y (click-outside, listbox ARIA) and RTL tests.
24
+
25
+ ## Features
26
+
27
+ - **245 supported calling regions** derived from `libphonenumber-js` metadata and `world-countries`
28
+ - **Headless hook** via `usePhoneInput()` plus an optional **unstyled reference** `PhoneInput` component (class names only, no bundled CSS)
29
+ - **Searchable country selector** with country name, ISO code, and dial-code matching
30
+ - **Format as you type** in the selected country's format, on every keystroke, powered by `libphonenumber-js`
31
+ - **Real validation** powered by `libphonenumber-js`
32
+ - **International detection** for pasted or typed `+` / `00` numbers
33
+ - **TypeScript-first** exports for countries, helpers, hook return values, and component refs
34
+
35
+ ## Installation
36
+
37
+ ```bash
38
+ npm install @input-kit/phone
39
+ ```
40
+
41
+ ## Quick Start
42
+
43
+ ### Component
44
+
45
+ ```tsx
46
+ import { PhoneInput } from '@input-kit/phone';
47
+ import './phone-input.css'; // your own stylesheet, the package ships no CSS
48
+
49
+ function Example() {
50
+ return (
51
+ <PhoneInput
52
+ defaultCountry="US"
53
+ onChange={(phone, country) => {
54
+ console.log(phone, country?.code);
55
+ }}
56
+ />
57
+ );
58
+ }
59
+ ```
60
+
61
+ ### Hook
62
+
63
+ ```tsx
64
+ import { usePhoneInput } from '@input-kit/phone';
65
+
66
+ function Example() {
67
+ const {
68
+ inputProps,
69
+ country,
70
+ countryButtonProps,
71
+ filteredCountries,
72
+ selectCountry,
73
+ isOpen,
74
+ isValid,
75
+ } = usePhoneInput({
76
+ defaultCountry: 'US',
77
+ onChange: (phone, nextCountry) => console.log(phone, nextCountry?.dialCode),
78
+ });
79
+
80
+ return (
81
+ <div>
82
+ <button {...countryButtonProps}>
83
+ {country?.flag} {country?.dialCode}
84
+ </button>
85
+
86
+ {isOpen && (
87
+ <div>
88
+ {filteredCountries.map((candidate) => (
89
+ <button key={candidate.code} onClick={() => selectCountry(candidate)}>
90
+ {candidate.flag} {candidate.name} {candidate.dialCode}
91
+ </button>
92
+ ))}
93
+ </div>
94
+ )}
95
+
96
+ <input {...inputProps} />
97
+ {!isValid && <span>Invalid phone number</span>}
98
+ </div>
99
+ );
100
+ }
101
+ ```
102
+
103
+ ## Format as you type
104
+
105
+ `inputProps.value` reformats on every keystroke in the selected country's national format, so the field reads `(555) 123-4567` while the user types. Typing a `+` switches to international format (`+1 555 123 4567`). This is on by default; set `formatOnType: false` for raw digits.
106
+
107
+ The formatting is display only. `onChange`, `phone`, `fullPhone` and `parsePhoneValue` still give you the clean value, so what you store and submit is never the formatted string.
108
+
109
+ ```tsx
110
+ const { inputProps, fullPhone } = usePhoneInput({
111
+ defaultCountry: 'US',
112
+ includeDialCode: true,
113
+ onChange: (phone) => console.log(phone), // +15551234567, not "(555) 123-4567"
114
+ });
115
+
116
+ // user types 5551234567
117
+ // inputProps.value -> "(555) 123-4567"
118
+ // fullPhone -> "+15551234567"
119
+ ```
120
+
121
+ ## Phone values
122
+
123
+ | Field | Meaning |
124
+ | --- | --- |
125
+ | `phone` | National digits stored by the hook (default) |
126
+ | `fullPhone` | National number plus dial code when `includeDialCode` is `true` |
127
+ | `onChange(phone, country)` | Same contract as `phone` / `includeDialCode` |
128
+ | E.164 for APIs | `parsePhoneValue(phone, country).e164` when valid. Prefer this over raw concatenation |
129
+
130
+ ## Known behavior
131
+
132
+ Formatting, length checks, and validity follow **[libphonenumber-js](https://www.npmjs.com/package/libphonenumber-js)** (same family as `react-phone-number-input`). The package does not implement per-country rules outside that library.
133
+
134
+ ## Form integrations
135
+
136
+ Controlled `value` / `onChange` with `usePhoneInput` or `PhoneInput`. React Hook Form: wrap with `Controller` and pass `field.value`, `field.onChange`, and `field.onBlur`. Use `onValidationChange` to sync `isValid` / `message` with form errors. Submit-time checks: `parsePhoneValue(phone, country)` or `validatePhoneNumber`.
137
+
138
+ ## Styled example
139
+
140
+ A minimal Vite demo using only the hook lives in **[examples/react-styled/](./examples/react-styled/)**.
141
+
142
+ ## Migration from 0.1.x
143
+
144
+ Compatibility aliases remain exported. Prefer the newer names in new code:
145
+
146
+ | Deprecated | Replacement |
147
+ | --- | --- |
148
+ | `setValue` | `setPhone` |
149
+ | `value` (hook) | `phone` |
150
+ | `allowedCountries` | `onlyCountries` |
151
+ | `excludedCountries` | `excludeCountries` |
152
+ | `autoDetectCountry` | `autoDetect` |
153
+ | `toggle` / `open` / `close` | `toggleDropdown` / `openDropdown` / `closeDropdown` |
154
+ | `countries` (hook list) | `filteredCountries` |
155
+ | `countrySelectorProps` | `countryButtonProps` |
156
+
157
+ Validation is unified in 0.2.0: `isValid` and `error` come from the same `validatePhoneNumber` call. Use `validationReason` or `onValidationChange` for structured form messages.
158
+
159
+ ## Development
160
+
161
+ Requires [Bun](https://bun.sh) (see `packageManager` in `package.json`).
162
+
163
+ ```bash
164
+ bun install
165
+ bun run test
166
+ bun run typecheck
167
+ bun run build
168
+ bun run lint
169
+ ```
170
+
171
+ Manual browser check: `test-demo/` (static HTML).
172
+
173
+ ## Contributing
174
+
175
+ Bug reports, feature requests, and pull requests are welcome, see [CONTRIBUTING.md](https://github.com/harshit-d3v/input-kit-phone/blob/main/CONTRIBUTING.md). In short: open an issue with a minimal reproduction (include the exact phone number and country for formatting/validation bugs), and for PRs run `bun run test`, `bun run typecheck`, and `bun run lint` before submitting.
176
+
177
+ ## Exports
178
+
179
+ ### Components and hooks
180
+
181
+ - `PhoneInput`
182
+ - `usePhoneInput(options)`
183
+
184
+ ### Country data
185
+
186
+ - `countries`
187
+ - `getCountryByCode(code)`
188
+ - `getCountryByDialCode(dialCode)`
189
+ - `getCountriesByDialCode(dialCode)`
190
+ - `getCountryOptions({ locale?, preferredCountries?, excludeCountries?, onlyCountries? })`
191
+ - `detectCountryFromPhone(phone)`
192
+
193
+ ### Utilities
194
+
195
+ - `cleanPhone`, `formatPhone`, `unformatPhone`, `validatePhone`, `validatePhoneLength`
196
+ - `validatePhoneNumber` `{ isValid, reason, message, error }`
197
+ - `parsePhoneValue` → `{ country, nationalNumber, e164, isValid }`
198
+ - `addDialCode`, `removeDialCode`, `filterCountries`, `getPlaceholder`
199
+
200
+ Compatibility aliases: `stripNonDigits`, `detectCountry`, `formatPhoneNumber`, `parseToE164`, `getNationalNumber`, `isPhoneNumberComplete`, `formatAsYouType`, `normalizePhoneNumber`, `phoneNumbersEqual`, `getCountryDisplayLabel`, `limitInputLength`.
201
+
202
+ ## `usePhoneInput(options)`
203
+
204
+ | Option | Type | Default | Description |
205
+ | --- | --- | --- | --- |
206
+ | `defaultCountry` | `string` | `'US'` | Default selected country |
207
+ | `preferredCountries` | `string[]` | - | Countries shown first in search results |
208
+ | `excludeCountries` | `string[]` | - | Countries to exclude |
209
+ | `onlyCountries` | `string[]` | - | Restrict selection to these countries |
210
+ | `autoDetect` | `boolean` | `true` | Detect country from international numbers |
211
+ | `formatOnType` | `boolean` | `true` | Apply live formatting |
212
+ | `includeDialCode` | `boolean` | `false` | Return values with dial code included |
213
+ | `required` | `boolean` | `false` | Empty value is invalid |
214
+ | `validator` | `(phone, country) => boolean` | - | Custom validation override |
215
+ | `onValidationChange` | `(state) => void` | - | Fires when validation result changes |
216
+
217
+ Important returned fields: `phone`, `fullPhone`, `country`, `isValid`, `validationReason`, `error`, `onValidationChange`, `filteredCountries`, `inputProps`, `countryButtonProps`, `dropdownProps`, `getCountryOptionId`.
218
+
219
+ ## License
220
+
221
+ MIT © Input Kit