@desource/phone-mask-svelte 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 +35 -0
- package/LICENSE +21 -0
- package/README.md +529 -0
- package/dist/index.cjs +1301 -0
- package/dist/index.mjs +1284 -0
- package/dist/phone-mask-svelte.css +383 -0
- package/dist/types/composables/internal/useCopyAction.svelte.d.ts +13 -0
- package/dist/types/composables/internal/useCopyAction.svelte.d.ts.map +1 -0
- package/dist/types/composables/internal/useCountry.svelte.d.ts +17 -0
- package/dist/types/composables/internal/useCountry.svelte.d.ts.map +1 -0
- package/dist/types/composables/internal/useCountrySelector.svelte.d.ts +31 -0
- package/dist/types/composables/internal/useCountrySelector.svelte.d.ts.map +1 -0
- package/dist/types/composables/internal/useFormatter.svelte.d.ts +29 -0
- package/dist/types/composables/internal/useFormatter.svelte.d.ts.map +1 -0
- package/dist/types/composables/internal/useInputHandlers.svelte.d.ts +18 -0
- package/dist/types/composables/internal/useInputHandlers.svelte.d.ts.map +1 -0
- package/dist/types/composables/internal/useTheme.svelte.d.ts +9 -0
- package/dist/types/composables/internal/useTheme.svelte.d.ts.map +1 -0
- package/dist/types/composables/internal/useValidationHint.svelte.d.ts +6 -0
- package/dist/types/composables/internal/useValidationHint.svelte.d.ts.map +1 -0
- package/dist/types/composables/usePhoneMask.svelte.d.ts +31 -0
- package/dist/types/composables/usePhoneMask.svelte.d.ts.map +1 -0
- package/dist/types/composables/utility/useClipboard.svelte.d.ts +6 -0
- package/dist/types/composables/utility/useClipboard.svelte.d.ts.map +1 -0
- package/dist/types/composables/utility/useTimer.svelte.d.ts +8 -0
- package/dist/types/composables/utility/useTimer.svelte.d.ts.map +1 -0
- package/dist/types/index.d.ts +17 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/types.d.ts +84 -0
- package/dist/types/types.d.ts.map +1 -0
- package/package.json +77 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# @desource/phone-mask-svelte
|
|
2
|
+
|
|
3
|
+
## 1.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- Core Upgrades:
|
|
8
|
+
- Refactored core architecture for better separation of concerns and reusability
|
|
9
|
+
- Added zero-config browser script support path
|
|
10
|
+
- Improved Intl.DisplayNames caching for performance
|
|
11
|
+
- Fixed various edge cases in formatting and event handling
|
|
12
|
+
- Added comprehensive unit tests for core utilities and handlers
|
|
13
|
+
|
|
14
|
+
- React Upgrades:
|
|
15
|
+
- Major internal refactor to a controlled pattern with consistent hooks
|
|
16
|
+
- Improved React 18/19 compatibility for ref behavior
|
|
17
|
+
- Added and expanded unit/e2e tests for React components and behavior
|
|
18
|
+
|
|
19
|
+
- Vue Upgrades:
|
|
20
|
+
- Refactored to align composable structure and reuse core utilities
|
|
21
|
+
- Fixed directive behavior for external value updates and dropdown close logic
|
|
22
|
+
- Added substantial unit/e2e test coverage for Vue composables and directive
|
|
23
|
+
|
|
24
|
+
- π Svelte Upgrades:
|
|
25
|
+
- Introduced Svelte version of phone-mask library
|
|
26
|
+
|
|
27
|
+
- Nuxt Upgrades:
|
|
28
|
+
- Added full test foundation with @nuxt/test-utils
|
|
29
|
+
- Added unit tests for module and runtime behavior
|
|
30
|
+
- Added e2e fixtures/tests and shared e2e utilities
|
|
31
|
+
|
|
32
|
+
### Patch Changes
|
|
33
|
+
|
|
34
|
+
- Updated dependencies []:
|
|
35
|
+
- @desource/phone-mask@1.0.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DeSource Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
# @desource/phone-mask-svelte
|
|
2
|
+
|
|
3
|
+
> Svelte 5 phone input component with Google's libphonenumber data
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@desource/phone-mask-svelte)
|
|
6
|
+
[](https://bundlephobia.com/package/@desource/phone-mask-svelte)
|
|
7
|
+
[](https://github.com/DeSource-Labs/phone-mask/blob/main/LICENSE)
|
|
8
|
+
|
|
9
|
+
Beautiful, accessible, extreme small & tree-shakeable Svelte 5 phone input with auto-formatting, country selector, and validation.
|
|
10
|
+
|
|
11
|
+
## β¨ Features
|
|
12
|
+
|
|
13
|
+
- π¨ **Beautiful UI** β Modern design with light/dark themes
|
|
14
|
+
- π **Smart Country Search** β Fuzzy matching with keyboard navigation
|
|
15
|
+
- π **Auto-formatting** β As-you-type formatting with smart cursor
|
|
16
|
+
- β
**Validation** β Built-in validation with visual feedback
|
|
17
|
+
- π **Copy Button** β One-click copy to clipboard
|
|
18
|
+
- π **Auto-detection** β GeoIP and locale-based detection
|
|
19
|
+
- βΏ **Accessible** β ARIA labels, keyboard navigation
|
|
20
|
+
- π± **Mobile-friendly** β Optimized for touch devices
|
|
21
|
+
- π― **TypeScript** β Full type safety
|
|
22
|
+
- π§© **Two modes** β Component or composable
|
|
23
|
+
- β‘ **Optimized** β Tree-shaking and code splitting
|
|
24
|
+
|
|
25
|
+
## π¦ Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install @desource/phone-mask-svelte
|
|
29
|
+
# or
|
|
30
|
+
yarn add @desource/phone-mask-svelte
|
|
31
|
+
# or
|
|
32
|
+
pnpm add @desource/phone-mask-svelte
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## π Quick Start
|
|
36
|
+
|
|
37
|
+
### Importing
|
|
38
|
+
|
|
39
|
+
Component mode:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { PhoneInput } from '@desource/phone-mask-svelte';
|
|
43
|
+
import '@desource/phone-mask-svelte/assets/lib.css'; // Import styles
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Composable mode:
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { usePhoneMask } from '@desource/phone-mask-svelte';
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Component Mode
|
|
53
|
+
|
|
54
|
+
```svelte
|
|
55
|
+
<script lang="ts">
|
|
56
|
+
import { PhoneInput } from '@desource/phone-mask-svelte';
|
|
57
|
+
import '@desource/phone-mask-svelte/assets/lib.css';
|
|
58
|
+
|
|
59
|
+
let phone = $state('');
|
|
60
|
+
let isValid = $state(false);
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
<PhoneInput bind:value={phone} country="US" onvalidationchange={(v) => (isValid = v)} />
|
|
64
|
+
|
|
65
|
+
{#if isValid}
|
|
66
|
+
<p>β Valid phone number</p>
|
|
67
|
+
{/if}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Composable Mode
|
|
71
|
+
|
|
72
|
+
For custom input implementations:
|
|
73
|
+
|
|
74
|
+
```svelte
|
|
75
|
+
<script lang="ts">
|
|
76
|
+
import { usePhoneMask } from '@desource/phone-mask-svelte';
|
|
77
|
+
|
|
78
|
+
let value = $state('');
|
|
79
|
+
|
|
80
|
+
const phoneMask = usePhoneMask({
|
|
81
|
+
value: () => value,
|
|
82
|
+
onChange: (digits) => (value = digits),
|
|
83
|
+
country: () => 'US',
|
|
84
|
+
detect: () => false
|
|
85
|
+
});
|
|
86
|
+
</script>
|
|
87
|
+
|
|
88
|
+
<div>
|
|
89
|
+
<input bind:this={phoneMask.inputRef} type="tel" placeholder="Phone number" />
|
|
90
|
+
<p>Formatted: {phoneMask.fullFormatted}</p>
|
|
91
|
+
<p>Valid: {phoneMask.isComplete ? 'Yes' : 'No'}</p>
|
|
92
|
+
<p>Country: {phoneMask.country.name}</p>
|
|
93
|
+
<button onclick={() => phoneMask.setCountry('GB')}>Use UK</button>
|
|
94
|
+
</div>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## π Component API
|
|
98
|
+
|
|
99
|
+
### Props
|
|
100
|
+
|
|
101
|
+
> **Note:** The component supports both controlled and bindable modes. Use `bind:value` for two-way binding or `value` + `onchange` for controlled mode.
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
interface PhoneInputProps {
|
|
105
|
+
// Bindable value (digits only, without country code)
|
|
106
|
+
value?: string;
|
|
107
|
+
|
|
108
|
+
// Preselected country (ISO 3166-1 alpha-2)
|
|
109
|
+
country?: CountryKey;
|
|
110
|
+
|
|
111
|
+
// Auto-detect country from IP/locale
|
|
112
|
+
detect?: boolean; // Default: true
|
|
113
|
+
|
|
114
|
+
// Locale for country names
|
|
115
|
+
locale?: string; // Default: browser language
|
|
116
|
+
|
|
117
|
+
// Size variant
|
|
118
|
+
size?: 'compact' | 'normal' | 'large'; // Default: 'normal'
|
|
119
|
+
|
|
120
|
+
// Visual theme ("auto" | "light" | "dark")
|
|
121
|
+
theme?: 'auto' | 'light' | 'dark'; // Default: 'auto'
|
|
122
|
+
|
|
123
|
+
// Disabled state
|
|
124
|
+
disabled?: boolean; // Default: false
|
|
125
|
+
|
|
126
|
+
// Readonly state
|
|
127
|
+
readonly?: boolean; // Default: false
|
|
128
|
+
|
|
129
|
+
// Show copy button
|
|
130
|
+
showCopy?: boolean; // Default: true
|
|
131
|
+
|
|
132
|
+
// Show clear button
|
|
133
|
+
showClear?: boolean; // Default: false
|
|
134
|
+
|
|
135
|
+
// Show validation state (borders & outline)
|
|
136
|
+
withValidity?: boolean; // Default: true
|
|
137
|
+
|
|
138
|
+
// Custom search placeholder
|
|
139
|
+
searchPlaceholder?: string; // Default: 'Search country or code...'
|
|
140
|
+
|
|
141
|
+
// Custom no results text
|
|
142
|
+
noResultsText?: string; // Default: 'No countries found'
|
|
143
|
+
|
|
144
|
+
// Custom clear button label
|
|
145
|
+
clearButtonLabel?: string; // Default: 'Clear phone number'
|
|
146
|
+
|
|
147
|
+
// Dropdown menu custom CSS class
|
|
148
|
+
dropdownClass?: string;
|
|
149
|
+
|
|
150
|
+
// Disable default styles
|
|
151
|
+
disableDefaultStyles?: boolean; // Default: false
|
|
152
|
+
|
|
153
|
+
// Extra CSS class merged onto root element
|
|
154
|
+
class?: string;
|
|
155
|
+
|
|
156
|
+
// Callback when the phone number changes.
|
|
157
|
+
// Provides an object with:
|
|
158
|
+
// - full: Full phone number with country code (e.g. +1234567890)
|
|
159
|
+
// - fullFormatted: Full phone number formatted according to country rules (e.g. +1 234-567-890)
|
|
160
|
+
// - digits: Only the digits of the phone number without country code (e.g. 234567890)
|
|
161
|
+
onchange?: (value: PhoneNumber) => void;
|
|
162
|
+
|
|
163
|
+
// Callback when the selected country changes
|
|
164
|
+
oncountrychange?: (country: MaskFull) => void;
|
|
165
|
+
|
|
166
|
+
// Callback when the validation state changes
|
|
167
|
+
onvalidationchange?: (isValid: boolean) => void;
|
|
168
|
+
|
|
169
|
+
// Callback when the input is focused
|
|
170
|
+
onfocus?: (event: FocusEvent) => void;
|
|
171
|
+
|
|
172
|
+
// Callback when the input is blurred
|
|
173
|
+
onblur?: (event: FocusEvent) => void;
|
|
174
|
+
|
|
175
|
+
// Callback when phone number is copied
|
|
176
|
+
oncopy?: (value: string) => void;
|
|
177
|
+
|
|
178
|
+
// Callback when input is cleared
|
|
179
|
+
onclear?: () => void;
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Exposed Methods
|
|
184
|
+
|
|
185
|
+
Access component methods via `bind:this`:
|
|
186
|
+
|
|
187
|
+
```svelte
|
|
188
|
+
<script lang="ts">
|
|
189
|
+
import { PhoneInput } from '@desource/phone-mask-svelte';
|
|
190
|
+
import type { PhoneInputExposed } from '@desource/phone-mask-svelte';
|
|
191
|
+
|
|
192
|
+
let phoneInput = $state<PhoneInputExposed | null>(null);
|
|
193
|
+
</script>
|
|
194
|
+
|
|
195
|
+
<PhoneInput bind:this={phoneInput} />
|
|
196
|
+
<button onclick={() => phoneInput?.focus()}>Focus</button>
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
interface PhoneInputExposed {
|
|
201
|
+
focus: () => void; // Focus the input
|
|
202
|
+
blur: () => void; // Blur the input
|
|
203
|
+
clear: () => void; // Clear the input value
|
|
204
|
+
selectCountry: (code: string) => void; // Programmatically select a country by ISO code
|
|
205
|
+
getFullNumber: () => string; // Returns full phone number with country code (e.g. +1234567890)
|
|
206
|
+
getFullFormattedNumber: () => string; // Returns formatted number with country code (e.g. +1 234-567-890)
|
|
207
|
+
getDigits: () => string; // Returns only digits without country code (e.g. 234567890)
|
|
208
|
+
isValid: () => boolean; // Checks if the current phone number is valid
|
|
209
|
+
isComplete: () => boolean; // Alias for isValid()
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Snippets
|
|
214
|
+
|
|
215
|
+
```svelte
|
|
216
|
+
<PhoneInput bind:value={phone}>
|
|
217
|
+
{#snippet flag(country)}
|
|
218
|
+
<img src="/flags/{country.code.toLowerCase()}.svg" alt={country.name} />
|
|
219
|
+
{/snippet}
|
|
220
|
+
|
|
221
|
+
{#snippet copysvg(copied)}
|
|
222
|
+
{copied ? 'β' : 'π'}
|
|
223
|
+
{/snippet}
|
|
224
|
+
|
|
225
|
+
{#snippet clearsvg()}
|
|
226
|
+
β
|
|
227
|
+
{/snippet}
|
|
228
|
+
|
|
229
|
+
{#snippet actionsbefore()}
|
|
230
|
+
<button onclick={handleCustomAction}>Custom</button>
|
|
231
|
+
{/snippet}
|
|
232
|
+
</PhoneInput>
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
| Snippet | Props | Description |
|
|
236
|
+
| --------------- | ------------------------ | ------------------------------------------------- |
|
|
237
|
+
| `flag` | `MaskFull` | Custom flag icon in the country list and selector |
|
|
238
|
+
| `copysvg` | `boolean` (copied state) | Custom copy button icon |
|
|
239
|
+
| `clearsvg` | β | Custom clear button icon |
|
|
240
|
+
| `actionsbefore` | β | Content rendered before default action buttons |
|
|
241
|
+
|
|
242
|
+
## π§© Composable API
|
|
243
|
+
|
|
244
|
+
### Options
|
|
245
|
+
|
|
246
|
+
> **Note:** The composable uses getter functions for reactive options. Do NOT pass values directly.
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
interface UsePhoneMaskOptions {
|
|
250
|
+
// Getter returning current digit value (controlled) - REQUIRED
|
|
251
|
+
value: () => string;
|
|
252
|
+
|
|
253
|
+
// Callback when the digits value changes - REQUIRED
|
|
254
|
+
onChange: (digits: string) => void;
|
|
255
|
+
|
|
256
|
+
// Getter for ISO country code (e.g., 'US', 'DE', 'GB')
|
|
257
|
+
country?: () => string | undefined;
|
|
258
|
+
|
|
259
|
+
// Getter for locale string (default: navigator.language)
|
|
260
|
+
locale?: () => string | undefined;
|
|
261
|
+
|
|
262
|
+
// Getter for auto-detect flag (default: false)
|
|
263
|
+
detect?: () => boolean | undefined;
|
|
264
|
+
|
|
265
|
+
// Callback when the phone changes (full, fullFormatted, digits)
|
|
266
|
+
onPhoneChange?: (phone: PhoneNumber) => void;
|
|
267
|
+
|
|
268
|
+
// Country change callback
|
|
269
|
+
onCountryChange?: (country: MaskFull) => void;
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Return Value
|
|
274
|
+
|
|
275
|
+
> **Important:** Do NOT destructure the returned object β all properties are reactive getters and destructuring breaks reactivity.
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
interface UsePhoneMaskReturn {
|
|
279
|
+
inputRef: HTMLInputElement | null; // Bind to <input> with bind:this
|
|
280
|
+
digits: string;
|
|
281
|
+
full: string;
|
|
282
|
+
fullFormatted: string;
|
|
283
|
+
isComplete: boolean;
|
|
284
|
+
isEmpty: boolean;
|
|
285
|
+
shouldShowWarn: boolean;
|
|
286
|
+
country: MaskFull;
|
|
287
|
+
setCountry: (countryCode?: string | null) => boolean;
|
|
288
|
+
clear: () => void;
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
```svelte
|
|
293
|
+
<script lang="ts">
|
|
294
|
+
// β
CORRECT β access as properties
|
|
295
|
+
const phoneMask = usePhoneMask(options);
|
|
296
|
+
phoneMask.digits;
|
|
297
|
+
|
|
298
|
+
// β WRONG β loses reactivity
|
|
299
|
+
const { digits } = usePhoneMask(options);
|
|
300
|
+
</script>
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## π¨ Component Styling
|
|
304
|
+
|
|
305
|
+
### CSS Custom Properties
|
|
306
|
+
|
|
307
|
+
Customize colors via CSS variables:
|
|
308
|
+
|
|
309
|
+
```css
|
|
310
|
+
.phone-input,
|
|
311
|
+
.phone-dropdown {
|
|
312
|
+
/* Colors */
|
|
313
|
+
--pi-bg: #ffffff;
|
|
314
|
+
--pi-fg: #111827;
|
|
315
|
+
--pi-muted: #6b7280;
|
|
316
|
+
--pi-border: #e5e7eb;
|
|
317
|
+
--pi-border-hover: #d1d5db;
|
|
318
|
+
--pi-border-focus: #3b82f6;
|
|
319
|
+
--pi-focus-ring: 3px solid rgb(59 130 246 / 0.15);
|
|
320
|
+
--pi-disabled-bg: #f9fafb;
|
|
321
|
+
--pi-disabled-fg: #9ca3af;
|
|
322
|
+
/* Sizes */
|
|
323
|
+
--pi-font-size: 16px;
|
|
324
|
+
--pi-height: 44px;
|
|
325
|
+
/* Spacing */
|
|
326
|
+
--pi-padding: 12px;
|
|
327
|
+
/* Border radius */
|
|
328
|
+
--pi-radius: 8px;
|
|
329
|
+
/* Shadows */
|
|
330
|
+
--pi-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
|
331
|
+
--pi-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -2px rgb(0 0 0 / 0.05);
|
|
332
|
+
/* Validation */
|
|
333
|
+
--pi-warning: #f59e0b;
|
|
334
|
+
--pi-warning-light: #fbbf24;
|
|
335
|
+
--pi-success: #10b981;
|
|
336
|
+
--pi-focus-ring-warning: 3px solid rgb(245 158 11 / 0.15);
|
|
337
|
+
--pi-focus-ring-success: 3px solid rgb(16 185 129 / 0.15);
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Dark Theme
|
|
342
|
+
|
|
343
|
+
```svelte
|
|
344
|
+
<PhoneInput bind:value={phone} theme="dark" />
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Or with CSS:
|
|
348
|
+
|
|
349
|
+
```css
|
|
350
|
+
.phone-input[data-theme='dark'] {
|
|
351
|
+
--pi-bg: #1f2937;
|
|
352
|
+
--pi-fg: #f9fafb;
|
|
353
|
+
--pi-border: #374151;
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
## π Examples
|
|
358
|
+
|
|
359
|
+
### With Validation
|
|
360
|
+
|
|
361
|
+
```svelte
|
|
362
|
+
<script lang="ts">
|
|
363
|
+
import { PhoneInput } from '@desource/phone-mask-svelte';
|
|
364
|
+
|
|
365
|
+
let phone = $state('');
|
|
366
|
+
let isValid = $state(false);
|
|
367
|
+
|
|
368
|
+
const errorMessage = $derived(!phone ? '' : isValid ? '' : 'Please enter a valid phone number');
|
|
369
|
+
</script>
|
|
370
|
+
|
|
371
|
+
<div>
|
|
372
|
+
<PhoneInput bind:value={phone} country="US" onvalidationchange={(v) => (isValid = v)} />
|
|
373
|
+
|
|
374
|
+
{#if errorMessage}
|
|
375
|
+
<span class="error">{errorMessage}</span>
|
|
376
|
+
{/if}
|
|
377
|
+
</div>
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Auto-detect Country
|
|
381
|
+
|
|
382
|
+
```svelte
|
|
383
|
+
<script lang="ts">
|
|
384
|
+
import { PhoneInput } from '@desource/phone-mask-svelte';
|
|
385
|
+
import type { PMaskFull } from '@desource/phone-mask-svelte';
|
|
386
|
+
|
|
387
|
+
let phone = $state('');
|
|
388
|
+
let detectedCountry = $state('');
|
|
389
|
+
|
|
390
|
+
function handleCountryChange(country: PMaskFull) {
|
|
391
|
+
detectedCountry = country.name;
|
|
392
|
+
}
|
|
393
|
+
</script>
|
|
394
|
+
|
|
395
|
+
<PhoneInput bind:value={phone} detect oncountrychange={handleCountryChange} />
|
|
396
|
+
|
|
397
|
+
{#if detectedCountry}
|
|
398
|
+
<p>Detected: {detectedCountry}</p>
|
|
399
|
+
{/if}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### Programmatic Control
|
|
403
|
+
|
|
404
|
+
```svelte
|
|
405
|
+
<script lang="ts">
|
|
406
|
+
import { PhoneInput } from '@desource/phone-mask-svelte';
|
|
407
|
+
import type { PhoneInputExposed } from '@desource/phone-mask-svelte';
|
|
408
|
+
|
|
409
|
+
let phone = $state('');
|
|
410
|
+
let phoneInput = $state<PhoneInputExposed | null>(null);
|
|
411
|
+
</script>
|
|
412
|
+
|
|
413
|
+
<PhoneInput bind:this={phoneInput} bind:value={phone} />
|
|
414
|
+
|
|
415
|
+
<div>
|
|
416
|
+
<button onclick={() => phoneInput?.focus()}>Focus</button>
|
|
417
|
+
<button onclick={() => phoneInput?.clear()}>Clear</button>
|
|
418
|
+
<button onclick={() => phoneInput?.selectCountry('GB')}>Switch to UK</button>
|
|
419
|
+
<p>Full: {phoneInput?.getFullFormattedNumber()}</p>
|
|
420
|
+
<p>Valid: {phoneInput?.isValid()}</p>
|
|
421
|
+
</div>
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
### Multiple Inputs
|
|
425
|
+
|
|
426
|
+
```svelte
|
|
427
|
+
<script lang="ts">
|
|
428
|
+
import { PhoneInput } from '@desource/phone-mask-svelte';
|
|
429
|
+
|
|
430
|
+
let form = $state({ mobile: '', home: '', work: '' });
|
|
431
|
+
</script>
|
|
432
|
+
|
|
433
|
+
<div class="form">
|
|
434
|
+
<label>
|
|
435
|
+
Mobile
|
|
436
|
+
<PhoneInput bind:value={form.mobile} country="US" />
|
|
437
|
+
</label>
|
|
438
|
+
|
|
439
|
+
<label>
|
|
440
|
+
Home
|
|
441
|
+
<PhoneInput bind:value={form.home} country="US" />
|
|
442
|
+
</label>
|
|
443
|
+
|
|
444
|
+
<label>
|
|
445
|
+
Work
|
|
446
|
+
<PhoneInput bind:value={form.work} country="US" />
|
|
447
|
+
</label>
|
|
448
|
+
</div>
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### Custom Composable Implementation
|
|
452
|
+
|
|
453
|
+
```svelte
|
|
454
|
+
<script lang="ts">
|
|
455
|
+
import { usePhoneMask } from '@desource/phone-mask-svelte';
|
|
456
|
+
import type { PMaskPhoneNumber } from '@desource/phone-mask-svelte';
|
|
457
|
+
|
|
458
|
+
let inputValue = $state('');
|
|
459
|
+
let selectedCountry = $state('US');
|
|
460
|
+
|
|
461
|
+
const phoneMask = usePhoneMask({
|
|
462
|
+
value: () => inputValue,
|
|
463
|
+
country: () => selectedCountry,
|
|
464
|
+
detect: () => false,
|
|
465
|
+
onChange: (digits) => {
|
|
466
|
+
inputValue = digits;
|
|
467
|
+
},
|
|
468
|
+
onPhoneChange: (data: PMaskPhoneNumber) => {
|
|
469
|
+
console.log('Phone:', data.fullFormatted);
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
</script>
|
|
473
|
+
|
|
474
|
+
<div class="custom-phone">
|
|
475
|
+
<select bind:value={selectedCountry}>
|
|
476
|
+
<option value="US">πΊπΈ +1</option>
|
|
477
|
+
<option value="GB">π¬π§ +44</option>
|
|
478
|
+
<option value="DE">π©πͺ +49</option>
|
|
479
|
+
</select>
|
|
480
|
+
|
|
481
|
+
<input bind:this={phoneMask.inputRef} type="tel" placeholder="Phone number" />
|
|
482
|
+
</div>
|
|
483
|
+
|
|
484
|
+
<p>Formatted: {phoneMask.fullFormatted}</p>
|
|
485
|
+
<p>Valid: {phoneMask.isComplete ? 'Yes' : 'No'}</p>
|
|
486
|
+
<p>Country: {phoneMask.country.name}</p>
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
## π― Browser Support
|
|
490
|
+
|
|
491
|
+
- Chrome/Edge 90+
|
|
492
|
+
- Firefox 88+
|
|
493
|
+
- Safari 14+
|
|
494
|
+
- iOS Safari 14+
|
|
495
|
+
- Chrome Mobile
|
|
496
|
+
|
|
497
|
+
## π¦ What's Included
|
|
498
|
+
|
|
499
|
+
```
|
|
500
|
+
@desource/phone-mask-svelte/
|
|
501
|
+
βββ dist/
|
|
502
|
+
β βββ types/ # TypeScript declaration files
|
|
503
|
+
β βββ index.mjs # ES module bundle
|
|
504
|
+
β βββ index.cjs # CommonJS bundle
|
|
505
|
+
β βββ phone-mask-svelte.css # Component styles
|
|
506
|
+
βββ README.md # This file
|
|
507
|
+
βββ package.json # Package manifest
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
## π Related
|
|
511
|
+
|
|
512
|
+
- [@desource/phone-mask](../phone-mask) β Core library
|
|
513
|
+
- [@desource/phone-mask-nuxt](../phone-mask-nuxt) β Nuxt module
|
|
514
|
+
- [@desource/phone-mask-vue](../phone-mask-vue) β Vue 3 bindings
|
|
515
|
+
- [@desource/phone-mask-react](../phone-mask-react) β React bindings
|
|
516
|
+
|
|
517
|
+
## π License
|
|
518
|
+
|
|
519
|
+
[MIT](../../LICENSE) Β© 2026 DeSource Labs
|
|
520
|
+
|
|
521
|
+
## π€ Contributing
|
|
522
|
+
|
|
523
|
+
See [Contributing Guide](../../CONTRIBUTING.md)
|
|
524
|
+
|
|
525
|
+
---
|
|
526
|
+
|
|
527
|
+
<div align="center">
|
|
528
|
+
<sub>Made with β€οΈ by <a href="https://github.com/DeSource-Labs">DeSource Labs</a></sub>
|
|
529
|
+
</div>
|