@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.
Files changed (31) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/LICENSE +21 -0
  3. package/README.md +529 -0
  4. package/dist/index.cjs +1301 -0
  5. package/dist/index.mjs +1284 -0
  6. package/dist/phone-mask-svelte.css +383 -0
  7. package/dist/types/composables/internal/useCopyAction.svelte.d.ts +13 -0
  8. package/dist/types/composables/internal/useCopyAction.svelte.d.ts.map +1 -0
  9. package/dist/types/composables/internal/useCountry.svelte.d.ts +17 -0
  10. package/dist/types/composables/internal/useCountry.svelte.d.ts.map +1 -0
  11. package/dist/types/composables/internal/useCountrySelector.svelte.d.ts +31 -0
  12. package/dist/types/composables/internal/useCountrySelector.svelte.d.ts.map +1 -0
  13. package/dist/types/composables/internal/useFormatter.svelte.d.ts +29 -0
  14. package/dist/types/composables/internal/useFormatter.svelte.d.ts.map +1 -0
  15. package/dist/types/composables/internal/useInputHandlers.svelte.d.ts +18 -0
  16. package/dist/types/composables/internal/useInputHandlers.svelte.d.ts.map +1 -0
  17. package/dist/types/composables/internal/useTheme.svelte.d.ts +9 -0
  18. package/dist/types/composables/internal/useTheme.svelte.d.ts.map +1 -0
  19. package/dist/types/composables/internal/useValidationHint.svelte.d.ts +6 -0
  20. package/dist/types/composables/internal/useValidationHint.svelte.d.ts.map +1 -0
  21. package/dist/types/composables/usePhoneMask.svelte.d.ts +31 -0
  22. package/dist/types/composables/usePhoneMask.svelte.d.ts.map +1 -0
  23. package/dist/types/composables/utility/useClipboard.svelte.d.ts +6 -0
  24. package/dist/types/composables/utility/useClipboard.svelte.d.ts.map +1 -0
  25. package/dist/types/composables/utility/useTimer.svelte.d.ts +8 -0
  26. package/dist/types/composables/utility/useTimer.svelte.d.ts.map +1 -0
  27. package/dist/types/index.d.ts +17 -0
  28. package/dist/types/index.d.ts.map +1 -0
  29. package/dist/types/types.d.ts +84 -0
  30. package/dist/types/types.d.ts.map +1 -0
  31. 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
+ [![npm version](https://img.shields.io/npm/v/@desource/phone-mask-svelte?color=blue&logo=svelte)](https://www.npmjs.com/package/@desource/phone-mask-svelte)
6
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/@desource/phone-mask-svelte?label=gzip%20size&color=purple)](https://bundlephobia.com/package/@desource/phone-mask-svelte)
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-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>