@desource/phone-mask-vue 0.2.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 ADDED
@@ -0,0 +1,15 @@
1
+ # @desource/phone-mask-vue
2
+
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Initial release of phone-mask monorepo packages
8
+ - Core phone masking library with Google libphonenumber integration
9
+ - Vue 3 component and directive for phone input
10
+ - Nuxt 3 module with zero-config setup
11
+
12
+ ### Patch Changes
13
+
14
+ - Updated dependencies []:
15
+ - @desource/phone-mask@0.2.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 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,554 @@
1
+ # @desource/phone-mask-vue
2
+
3
+ > Vue 3 phone input component with Google's libphonenumber data
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@desource/phone-mask-vue?color=blue&logo=vue.js)](https://www.npmjs.com/package/@desource/phone-mask-vue)
6
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/@desource/phone-mask-vue?label=gzip%20size&color=purple)](https://bundlephobia.com/package/@desource/phone-mask-vue)
7
+ [![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/DeSource-Labs/phone-mask/blob/main/LICENSE)
8
+
9
+ Beautiful, accessible, extreme small & tree-shackable Vue 3 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 directive
23
+ - ⚡ **Optimized** — Tree-shaking and code splitting
24
+
25
+ ## 📦 Installation
26
+
27
+ ```bash
28
+ npm install @desource/phone-mask-vue
29
+ # or
30
+ yarn add @desource/phone-mask-vue
31
+ # or
32
+ pnpm add @desource/phone-mask-vue
33
+ ```
34
+
35
+ ## 🚀 Quick Start
36
+
37
+ ### Importing
38
+
39
+ ```ts
40
+ import { createApp } from 'vue';
41
+ import { PhoneInput, vPhoneMask } from '@desource/phone-mask-vue';
42
+ import '@desource/phone-mask-vue/assets/lib.css'; // Import styles (for component mode only)
43
+
44
+ const app = createApp(App);
45
+
46
+ app.component('PhoneInput', PhoneInput); // Register component if you need component mode
47
+ app.directive('phone-mask', vPhoneMask); // Register directive if you need directive mode
48
+ app.mount('#app');
49
+ ```
50
+
51
+ If you need both modes:
52
+
53
+ ```ts
54
+ import { createApp } from 'vue';
55
+ import phoneMask from '@desource/phone-mask-vue';
56
+ import '@desource/phone-mask-vue/assets/lib.css'; // Import styles for component mode
57
+
58
+ const app = createApp(App);
59
+
60
+ app.use(phoneMask); // Registers both component and directive
61
+ app.mount('#app');
62
+ ```
63
+
64
+ ### Component Mode
65
+
66
+ ```vue
67
+ <script setup lang="ts">
68
+ import { ref } from 'vue';
69
+ import { PhoneInput } from '@desource/phone-mask-vue';
70
+
71
+ const phoneDigits = ref('');
72
+ const isValid = ref(false);
73
+ </script>
74
+
75
+ <template>
76
+ <PhoneInput v-model="phoneDigits" country="US" theme="light" @validation-change="isValid = $event" />
77
+
78
+ <p v-if="isValid">✓ Valid phone number</p>
79
+ </template>
80
+ ```
81
+
82
+ ### Directive Mode
83
+
84
+ For custom styling with automatic formatting:
85
+
86
+ ```vue
87
+ <script setup lang="ts">
88
+ import { ref } from 'vue';
89
+ import type { PMaskPhoneNumber } from '@desource/phone-mask-vue';
90
+
91
+ const country = ref('US');
92
+ const phone = ref('');
93
+ const digits = ref('');
94
+
95
+ const handleChange = (phone: PMaskPhoneNumber) => {
96
+ phone.value = phone.full;
97
+ digits.value = phone.digits;
98
+ };
99
+ </script>
100
+
101
+ <template>
102
+ <div class="phone-wrapper">
103
+ <select v-model="country">
104
+ <option value="US">🇺🇸 +1</option>
105
+ <option value="GB">🇬🇧 +44</option>
106
+ <option value="DE">🇩🇪 +49</option>
107
+ </select>
108
+
109
+ <input
110
+ v-phone-mask="{
111
+ country,
112
+ onChange: handleChange
113
+ }"
114
+ placeholder="Phone number"
115
+ />
116
+ </div>
117
+
118
+ <p>{{ digits }} digits entered</p>
119
+ </template>
120
+ ```
121
+
122
+ ## 📖 Component API
123
+
124
+ ### Props
125
+
126
+ ```ts
127
+ interface PhoneInputProps {
128
+ // v-model binding
129
+ modelValue?: string;
130
+
131
+ // Preselected country (ISO 3166-1 alpha-2)
132
+ country?: CountryKey;
133
+
134
+ // Auto-detect country from IP/locale
135
+ detect?: boolean; // Default: true
136
+
137
+ // Locale for country names
138
+ locale?: string; // Default: browser language
139
+
140
+ // Size variant
141
+ size?: 'compact' | 'normal' | 'large'; // Default: 'normal'
142
+
143
+ // Visual theme ("auto" | "light" | "dark")
144
+ theme?: 'auto' | 'light' | 'dark'; // Default: 'auto'
145
+
146
+ // Disabled state
147
+ disabled?: boolean; // Default: false
148
+
149
+ // Readonly state
150
+ readonly?: boolean; // Default: false
151
+
152
+ // Show copy button
153
+ showCopy?: boolean; // Default: true
154
+
155
+ // Show clear button
156
+ showClear?: boolean; // Default: false
157
+
158
+ // Show validation state (borders & outline)
159
+ withValidity?: boolean; // Default: true
160
+
161
+ // Custom search placeholder
162
+ searchPlaceholder?: string; // Default: 'Search country or code...'
163
+
164
+ // Custom no results text
165
+ noResultsText?: string; // Default: 'No countries found'
166
+
167
+ // Custom clear button label
168
+ clearButtonLabel?: string; // Default: 'Clear phone number'
169
+
170
+ // Dropdown menu custom CSS class
171
+ dropdownClass?: string;
172
+
173
+ // Disable default styles
174
+ disableDefaultStyles?: boolean; // Default: false
175
+ }
176
+ ```
177
+
178
+ ### Events
179
+
180
+ ```ts
181
+ interface PhoneInputEvents {
182
+ // v-model update
183
+ 'update:modelValue': (value: string) => void;
184
+
185
+ // Value changed
186
+ // Provides an object with:
187
+ // - full: Full phone number with country code (e.g. +1234567890)
188
+ // - fullFormatted: Full phone number formatted according to country rules (e.g. +1 234-567-890)
189
+ // - digits: Only the digits of the phone number without country code (e.g. 234567890)
190
+ change: (value: PMaskPhoneNumber) => void;
191
+
192
+ // Country changed
193
+ 'country-change': (country: PMaskFull) => void;
194
+
195
+ // Validation state changed
196
+ 'validation-change': (isValid: boolean) => void;
197
+
198
+ // Input focused
199
+ focus: (event: FocusEvent) => void;
200
+
201
+ // Input blurred
202
+ blur: (event: FocusEvent) => void;
203
+
204
+ // Copy button clicked
205
+ copy: (value: string) => void;
206
+
207
+ // When input is cleared
208
+ clear: () => void;
209
+ }
210
+ ```
211
+
212
+ ### Exposed Methods
213
+
214
+ ```ts
215
+ interface PhoneInputExpose {
216
+ // Focus the phone input
217
+ focus: () => void;
218
+
219
+ // Blur the phone input
220
+ blur: () => void;
221
+
222
+ // Clear the phone input
223
+ clear: () => void;
224
+
225
+ // Select a country by its ISO 3166-1 alpha-2 code
226
+ selectCountry: (country: CountryKey) => void;
227
+
228
+ // Get the full phone number with country code (e.g. +1234567890)
229
+ getFullNumber: () => string;
230
+
231
+ // Get the full phone number formatted according to country rules (e.g. +1 234-567-890)
232
+ getFullFormattedNumber: () => string;
233
+
234
+ // Get only the digits of the phone number without country code (e.g. 234567890)
235
+ getDigits: () => string;
236
+
237
+ // Check if the current phone number is valid
238
+ isValid: () => boolean;
239
+
240
+ // Check if the current phone number is complete
241
+ isComplete: () => boolean;
242
+ }
243
+ ```
244
+
245
+ ### Slots
246
+
247
+ - `actions-before` — Slot for custom actions before default buttons
248
+
249
+ - `flag` — Slot for custom country flag rendering in the country list and country selector
250
+
251
+ - `copy-svg` — Slot for custom copy button SVG icon
252
+
253
+ - `clear-svg` — Slot for custom clear button SVG icon
254
+
255
+ ### Usage with Refs
256
+
257
+ ```vue
258
+ <script setup lang="ts">
259
+ import { ref } from 'vue';
260
+ import { PhoneInput } from '@desource/phone-mask-vue';
261
+
262
+ const phoneInputRef = ref<InstanceType<typeof PhoneInput>>();
263
+
264
+ const focusInput = () => {
265
+ phoneInputRef.value?.focus();
266
+ };
267
+
268
+ const clearInput = () => {
269
+ phoneInputRef.value?.clear();
270
+ };
271
+ </script>
272
+
273
+ <template>
274
+ <PhoneInput ref="phoneInputRef" v-model="phone" />
275
+ <button @click="focusInput">Focus</button>
276
+ <button @click="clearInput">Clear</button>
277
+ </template>
278
+ ```
279
+
280
+ ## 🎨 Component Styling
281
+
282
+ ### CSS Custom Properties
283
+
284
+ Customize colors via CSS variables:
285
+
286
+ ```css
287
+ .phone-input,
288
+ .phone-dropdown {
289
+ /* Colors */
290
+ --pi-bg: #ffffff;
291
+ --pi-fg: #111827;
292
+ --pi-muted: #6b7280;
293
+ --pi-border: #e5e7eb;
294
+ --pi-border-hover: #d1d5db;
295
+ --pi-border-focus: #3b82f6;
296
+ --pi-focus-ring: 3px solid rgb(59 130 246 / 0.15);
297
+ --pi-disabled-bg: #f9fafb;
298
+ --pi-disabled-fg: #9ca3af;
299
+ /* Sizes */
300
+ --pi-font-size: 16px;
301
+ --pi-height: 44px;
302
+ /* Spacing */
303
+ --pi-padding: 12px;
304
+ /* Border radius */
305
+ --pi-radius: 8px;
306
+ /* Shadows */
307
+ --pi-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
308
+ --pi-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -2px rgb(0 0 0 / 0.05);
309
+ /* Validation */
310
+ --pi-warning: #f59e0b;
311
+ --pi-warning-light: #fbbf24;
312
+ --pi-success: #10b981;
313
+ --pi-focus-ring-warning: 3px solid rgb(245 158 11 / 0.15);
314
+ --pi-focus-ring-success: 3px solid rgb(16 185 129 / 0.15);
315
+ }
316
+ ```
317
+
318
+ ### Dark Theme
319
+
320
+ ```vue
321
+ <template>
322
+ <PhoneInput v-model="phone" theme="dark" />
323
+ </template>
324
+ ```
325
+
326
+ Or with CSS:
327
+
328
+ ```css
329
+ .phone-input[data-theme='dark'] {
330
+ --pi-bg: #1f2937;
331
+ --pi-fg: #f9fafb;
332
+ --pi-border: #374151;
333
+ }
334
+ ```
335
+
336
+ ## 🧩 Directive API
337
+
338
+ ### Basic Usage
339
+
340
+ ```vue
341
+ <template>
342
+ <input v-phone-mask="'US'" />
343
+ </template>
344
+ ```
345
+
346
+ ### With Options
347
+
348
+ ```ts
349
+ interface PMaskDirectiveOptions {
350
+ // Predefined country ISO code (e.g., 'US', 'DE', 'GB')
351
+ country?: string;
352
+
353
+ // Locale for country names (default: navigator.language)
354
+ locale?: string;
355
+
356
+ // Auto-detect country from IP/locale (default: false)
357
+ detect?: boolean;
358
+
359
+ // Value change callback
360
+ onChange?: (phone: PMaskPhoneNumber) => void;
361
+
362
+ // Country change callback
363
+ onCountryChange?: (country: PMaskFull) => void;
364
+ }
365
+ ```
366
+
367
+ ```vue
368
+ <template>
369
+ <input
370
+ v-phone-mask="{
371
+ country: 'US',
372
+ locale: 'en',
373
+ onChange: handleChange,
374
+ onCountryChange: handleCountryChange
375
+ }"
376
+ />
377
+ </template>
378
+ ```
379
+
380
+ ### Reactive Country
381
+
382
+ ```vue
383
+ <script setup lang="ts">
384
+ import type { PMaskPhoneNumber } from '@desource/phone-mask-vue';
385
+
386
+ const selectedCountry = ref('US');
387
+
388
+ const handleChange = (phone: PMaskPhoneNumber) => {
389
+ console.log('Phone:', phone.full, 'Digits:', phone.digits);
390
+ };
391
+ </script>
392
+
393
+ <template>
394
+ <select v-model="selectedCountry">
395
+ <option value="US">🇺🇸 United States</option>
396
+ <option value="GB">🇬🇧 United Kingdom</option>
397
+ </select>
398
+
399
+ <input
400
+ v-phone-mask="{
401
+ country: selectedCountry,
402
+ onChange: handleChange
403
+ }"
404
+ />
405
+ </template>
406
+ ```
407
+
408
+ ## 📚 Examples
409
+
410
+ ### With Validation
411
+
412
+ ```vue
413
+ <script setup lang="ts">
414
+ import { ref, computed } from 'vue';
415
+ import { PhoneInput } from '@desource/phone-mask-vue';
416
+
417
+ const phone = ref('');
418
+ const isValid = ref(false);
419
+
420
+ const errorMessage = computed(() => {
421
+ if (!phone.value) return '';
422
+ return isValid.value ? '' : 'Please enter a valid phone number';
423
+ });
424
+ </script>
425
+
426
+ <template>
427
+ <div>
428
+ <PhoneInput v-model="phone" country="US" @validation-change="isValid = $event" />
429
+
430
+ <span v-if="errorMessage" class="error">
431
+ {{ errorMessage }}
432
+ </span>
433
+ </div>
434
+ </template>
435
+ ```
436
+
437
+ ### Auto-detect Country
438
+
439
+ ```vue
440
+ <script setup lang="ts">
441
+ import { ref } from 'vue';
442
+ import { PhoneInput, type MaskFull } from '@desource/phone-mask-vue';
443
+
444
+ const phone = ref('');
445
+ const detectedCountry = ref('');
446
+
447
+ const handleCountryChange = (country: MaskFull) => {
448
+ detectedCountry.value = country.name;
449
+ };
450
+ </script>
451
+
452
+ <template>
453
+ <PhoneInput v-model="phone" detect @country-change="handleCountryChange" />
454
+
455
+ <p v-if="detectedCountry">Detected: {{ detectedCountry }}</p>
456
+ </template>
457
+ ```
458
+
459
+ ### With Form Libraries
460
+
461
+ #### VeeValidate
462
+
463
+ ```vue
464
+ <script setup lang="ts">
465
+ import { useField } from 'vee-validate';
466
+ import { PhoneInput } from '@desource/phone-mask-vue';
467
+
468
+ const { value, errorMessage } = useField('phone', (value) => {
469
+ if (!value) return 'Phone is required';
470
+ // Add custom validation
471
+ return true;
472
+ });
473
+ </script>
474
+
475
+ <template>
476
+ <PhoneInput v-model="value" />
477
+ <span v-if="errorMessage">{{ errorMessage }}</span>
478
+ </template>
479
+ ```
480
+
481
+ ### Multiple Inputs
482
+
483
+ ```vue
484
+ <script setup lang="ts">
485
+ import { reactive } from 'vue';
486
+ import { PhoneInput } from '@desource/phone-mask-vue';
487
+
488
+ const form = reactive({
489
+ mobile: '',
490
+ home: '',
491
+ work: ''
492
+ });
493
+ </script>
494
+
495
+ <template>
496
+ <div class="form">
497
+ <label>
498
+ Mobile
499
+ <PhoneInput v-model="form.mobile" country="US" />
500
+ </label>
501
+
502
+ <label>
503
+ Home
504
+ <PhoneInput v-model="form.home" country="US" />
505
+ </label>
506
+
507
+ <label>
508
+ Work
509
+ <PhoneInput v-model="form.work" country="US" />
510
+ </label>
511
+ </div>
512
+ </template>
513
+ ```
514
+
515
+ ## 🎯 Browser Support
516
+
517
+ - Chrome/Edge 90+
518
+ - Firefox 88+
519
+ - Safari 14+
520
+ - iOS Safari 14+
521
+ - Chrome Mobile
522
+
523
+ ## 📦 What's Included
524
+
525
+ ```
526
+ @desource/phone-mask-vue/
527
+ ├── dist/
528
+ │ ├── types # TypeScript definitions
529
+ │ ├── index.js # ESM bundle
530
+ │ ├── index.cjs # CommonJS bundle
531
+ │ ├── index.mjs # ESM module bundle
532
+ │ └── phone-mask-vue.css # Component styles
533
+ ├── README.md # This file
534
+ └── package.json # Package manifest
535
+ ```
536
+
537
+ ## 🔗 Related
538
+
539
+ - [@desource/phone-mask](../phone-mask) — Core library
540
+ - [@desource/phone-mask-nuxt](../phone-mask-nuxt) — Nuxt 3 module
541
+
542
+ ## 📄 License
543
+
544
+ [MIT](../../LICENSE) © 2025 DeSource Labs
545
+
546
+ ## 🤝 Contributing
547
+
548
+ See [Contributing Guide](../../CONTRIBUTING.md)
549
+
550
+ ---
551
+
552
+ <div align="center">
553
+ <sub>Made with ❤️ by <a href="https://github.com/DeSource-Labs">DeSource Labs</a></sub>
554
+ </div>