@desource/phone-mask-svelte

中文文档
2.1.0发布于 2026/11/07MITTypeScriptES Module构建来源已验证近 30 天下载 2万

🌍 Svelte 5 component, composable, action, and attachment for international phone number masking. Powered by @desource/phone-mask with Google libphonenumber sync.

原始使用文档
文档目录42 节
  1. ✨ Features
  2. 📦 Installation
  3. 🚀 Quick Start
  4. Importing
  5. Component Mode
  6. Composable Mode
  7. Attachment Mode
  8. Action Mode
  9. Send Raw Digits to Backend
  10. Dynamic Mask Updates on Country Change
  11. Multi-tenant: tenantId Default Country + Tenant-specific Va…
  12. 🔤 Display Formatting
  13. 📖 Component API
  14. Props
  15. Exposed Methods
  16. Snippets
  17. 🧩 Composable API
  18. Options
  19. Return Value
  20. ⚡ Attachment API
  21. Basic Usage
  22. Options
  23. Reactive Country
  24. 🎬 Action API
  25. Basic Usage
  26. Options
  27. Reactive Country
  28. Action vs Attachment
  29. 🎨 Component Styling
  30. CSS Custom Properties
  31. Dark Theme
  32. 📚 Examples
  33. With Validation
  34. Auto-detect Country
  35. Programmatic Control
  36. Multiple Inputs
  37. Custom Composable Implementation
  38. 🎯 Browser Support
  39. 📦 What's Included
  40. 🔗 Related Packages
  41. 📄 License
  42. 🤝 Contributing

@desource/phone-mask-svelte

Svelte 5 phone input component with Google's libphonenumber data

npm version code coverage SonarCloud Ask CodeWiki Ask Context7 Ask DeepWiki license

Beautiful, accessible, extreme small & tree-shakeable Svelte 5 phone input with auto-formatting, country selector, and validation.

Pasting a complete international number beginning with + detects its country and replaces the input with the number without its calling code. Detection uses mask formatting, then digit count; ambiguous matches use the calling code's default country. Fragments without a calling code keep normal insertion behavior. The detect option controls GeoIP detection separately.

Before upgrading to 2.0.0, review Migration from 1.7.0 for breaking changes and required updates.

✨ Features

  • 🌐 SSR Support — PhoneInput renders formatted HTML on the server and hydrates in the browser
  • 🎨 Beautiful UI — Modern design with light/dark themes
  • 🔍 Smart Country Search — Fuzzy matching with keyboard navigation
  • 🎭 Auto-formatting — As-you-type formatting with smart cursor
  • ✅ Validation — Built-in validation with visual feedback
  • 📋 Copy Button — One-click copy to clipboard
  • 🌐 Auto-detection — GeoIP and locale-based detection
  • ♿ Accessible — ARIA labels, keyboard navigation
  • 📱 Mobile-friendly — Optimized for touch devices
  • 🎯 TypeScript — Full type safety
  • 🧩 Four modes — Component, composable, attachment, or action
  • ⚡ Optimized — Tree-shaking and code splitting

📦 Installation

npm install @desource/phone-mask-svelte
# or
yarn add @desource/phone-mask-svelte
# or
pnpm add @desource/phone-mask-svelte

🚀 Quick Start

Importing

Component mode:

import { PhoneInput } from '@desource/phone-mask-svelte';
import '@desource/phone-mask-svelte/assets/lib.css'; // Import styles

Composable mode:

import { usePhoneMask } from '@desource/phone-mask-svelte';

Core kit utilities and mask data re-exported from the core package subpaths:

import { getFlagEmoji, formatDigitsWithMap } from '@desource/phone-mask-svelte/core';

Attachment mode (for existing <input> elements, requires Svelte 5.29+):

import { phoneMaskAttachment as phoneMask } from '@desource/phone-mask-svelte';

Action mode (for existing <input> elements, works with all Svelte 5 versions):

import { phoneMaskAction as phoneMask } from '@desource/phone-mask-svelte';

Component Mode

<script lang="ts">
  import { PhoneInput } from '@desource/phone-mask-svelte';
  import '@desource/phone-mask-svelte/assets/lib.css';

  let phone = $state('');
  let isValid = $state(false);
</script>

<PhoneInput bind:value={phone} country="US" onvalidationchange={(v) => (isValid = v)} />

{#if isValid}
  <p>✓ Valid phone number</p>
{/if}

Composable Mode

For custom input implementations:

<script lang="ts">
  import { usePhoneMask } from '@desource/phone-mask-svelte';

  let value = $state('');

  const phoneMask = usePhoneMask({
    value: () => value,
    onChange: (digits) => (value = digits),
    country: () => 'US',
    detect: () => false
  });
</script>

<div>
  <input bind:this={phoneMask.inputRef} type="tel" placeholder="Phone number" />
  <p>Formatted: {phoneMask.fullFormatted}</p>
  <p>Valid: {phoneMask.isComplete ? 'Yes' : 'No'}</p>
  <p>Country: {phoneMask.country.name}</p>
  <button onclick={() => phoneMask.setCountry('GB')}>Use UK</button>
</div>

Attachment Mode

For existing <input> elements without a wrapper component. Requires Svelte 5.29+.

<script lang="ts">
  import { phoneMaskAttachment as phoneMask } from '@desource/phone-mask-svelte';
  import type { PMaskPhoneNumber, PMaskFull } from '@desource/phone-mask-svelte';

  let country = $state('US');

  function handleChange(phone: PMaskPhoneNumber) {
    console.log('Full:', phone.full, 'Digits:', phone.digits);
  }

  function handleCountryChange(c: PMaskFull) {
    console.log('Country:', c.name);
  }
</script>

<div class="phone-wrapper">
  <select bind:value={country}>
    <option value="US">🇺🇸 +1</option>
    <option value="GB">🇬🇧 +44</option>
    <option value="DE">🇩🇪 +49</option>
  </select>

  <input
    {@attach phoneMask({ country, onChange: handleChange, onCountryChange: handleCountryChange })}
    placeholder="Phone number"
  />
</div>

Shorthand (country code string):

<input {@attach phoneMask('US')} />

With auto-detection:

<input {@attach phoneMask({ detect: true, onChange: handleChange })} />

Action Mode

For existing <input> elements without a wrapper component. Works with all Svelte 5 versions (including pre-5.29). Reactive parameter changes are applied through Svelte's use: action update() lifecycle hook.

<script lang="ts">
  import { phoneMaskAction as phoneMask } from '@desource/phone-mask-svelte';
  import type { PMaskPhoneNumber, PMaskFull } from '@desource/phone-mask-svelte';

  let country = $state('US');

  function handleChange(phone: PMaskPhoneNumber) {
    console.log('Full:', phone.full, 'Digits:', phone.digits);
  }

  function handleCountryChange(c: PMaskFull) {
    console.log('Country:', c.name);
  }
</script>

<div class="phone-wrapper">
  <select bind:value={country}>
    <option value="US">🇺🇸 +1</option>
    <option value="GB">🇬🇧 +44</option>
    <option value="DE">🇩🇪 +49</option>
  </select>

  <input
    use:phoneMask={{ country, onChange: handleChange, onCountryChange: handleCountryChange }}
    placeholder="Phone number"
  />
</div>

Shorthand (country code string):

<input use:phoneMask={'US'} />

With auto-detection:

<input use:phoneMask={{ detect: true, onChange: handleChange }} />

Send Raw Digits to Backend

<script lang="ts">
  import { PhoneInput, type PMaskPhoneNumber } from '@desource/phone-mask-svelte';

  let digits = $state('');

  async function handlePhoneChange(phone: PMaskPhoneNumber) {
    await fetch('/api/profile/phone', {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({
        phoneDigits: phone.digits, // unformatted value for backend
        phoneFull: phone.full // optional full number with country code
      })
    });
  }
</script>

<PhoneInput bind:value={digits} country="US" onchange={handlePhoneChange} />

Dynamic Mask Updates on Country Change

<script lang="ts">
  import { usePhoneMask, type PCountryKey } from '@desource/phone-mask-svelte';

  let selectedCountry = $state<PCountryKey>('US');
  let digits = $state('');

  const phoneMask = usePhoneMask({
    value: () => digits,
    onChange: (value) => (digits = value),
    country: () => selectedCountry
  });

  function onCountrySelect(nextCountry: PCountryKey) {
    selectedCountry = nextCountry;
    phoneMask.setCountry(nextCountry); // updates mask immediately
  }
</script>

<select
  value={selectedCountry}
  onchange={(e) => onCountrySelect((e.currentTarget as HTMLSelectElement).value as PCountryKey)}
>
  <option value="US">US</option>
  <option value="GB">GB</option>
  <option value="DE">DE</option>
</select>

<input bind:this={phoneMask.inputRef} type="tel" />
<p>{phoneMask.fullFormatted}</p>
<p>{phoneMask.isComplete ? 'complete' : 'incomplete'}</p>

Multi-tenant: tenantId Default Country + Tenant-specific Validation Rules

<script lang="ts">
  import { usePhoneMask, type PCountryKey } from '@desource/phone-mask-svelte';

  type TenantPolicy = {
    defaultCountry: PCountryKey;
    prefixRule?: RegExp;
  };

  let { tenantId }: { tenantId: string } = $props();

  const TENANT_POLICIES: Record<string, TenantPolicy> = {
    acme: { defaultCountry: 'US', prefixRule: /^(202|303)\d{7}$/ },
    globex: { defaultCountry: 'GB', prefixRule: /^7\d{9}$/ }
  };

  const policy = $derived(TENANT_POLICIES[tenantId] ?? { defaultCountry: 'US' as const });
  let digits = $state('');

  const phoneMask = usePhoneMask({
    value: () => digits,
    onChange: (value) => (digits = value),
    country: () => policy.defaultCountry
  });

  const isTenantValid = $derived(
    phoneMask.isComplete && (policy.prefixRule ? policy.prefixRule.test(phoneMask.digits) : true)
  );
</script>

<input bind:this={phoneMask.inputRef} type="tel" />
<p>Default country: {policy.defaultCountry}</p>
<p>Tenant validation: {isTenantValid ? 'pass' : 'fail'}</p>

🔤 Display Formatting

Call formatPhone(value, country?, options?) directly in your template. It returns text and requires no CSS.

<script lang="ts">
  import { formatPhone } from '@desource/phone-mask-svelte/core';

  let { phone }: { phone: string } = $props();
</script>

<span>{formatPhone(phone, 'US', { code: false, flag: true })}</span>
<!-- '+12345678888' displays as '🇺🇸 234 567 8888'. -->

Omit country to detect a complete international number beginning with +; pass it for numbers without a calling code or to resolve shared calling codes such as US/CA. The optional PhoneFormatOptions argument accepts code (default true) and flag (default false).

The function works during SSR. Unsupported values remain unchanged; empty, whitespace-only, null, and undefined values return empty text. See the formatting rules for detection limits and examples.

📖 Component API

Props

Note: The component supports both controlled and bindable modes. Use bind:value for two-way binding or value + onchange for controlled mode.

interface PhoneInputProps {
  // Bindable value (digits only, without country code)
  value?: string;

  // Optional id/name applied to the underlying <input> for forms/autofill
  id?: string;
  name?: string;

  // Preselected country (ISO 3166-1 alpha-2)
  country?: CountryKey;

  // Auto-detect country from IP/locale
  detect?: boolean; // Default: true

  // Locale for country names
  locale?: string; // Default: browser language

  // Size variant
  size?: 'compact' | 'normal' | 'large'; // Default: 'normal'

  // Visual theme ("auto" | "light" | "dark")
  theme?: 'auto' | 'light' | 'dark'; // Default: 'auto'

  // Disabled state
  disabled?: boolean; // Default: false

  // Readonly state
  readonly?: boolean; // Default: false

  // Show copy button
  showCopy?: boolean; // Default: true

  // Show clear button
  showClear?: boolean; // Default: false

  // Show validation state (borders & outline)
  withValidity?: boolean; // Default: true

  // Custom search placeholder
  searchPlaceholder?: string; // Default: 'Search country or code...'

  // Custom no results text
  noResultsText?: string; // Default: 'No countries found'

  // Custom clear button label
  clearButtonLabel?: string; // Default: 'Clear phone number'

  // Dropdown menu custom CSS class
  dropdownClass?: string;

  // Disable default styles
  disableDefaultStyles?: boolean; // Default: false

  // Extra CSS class merged onto root element
  class?: string;

  // Callback when the phone number changes.
  // Provides an object with:
  // - full: Full phone number with country code (e.g. +1234567890)
  // - fullFormatted: Full phone number formatted according to country rules (e.g. +1 234-567-890)
  // - digits: Only the digits of the phone number without country code (e.g. 234567890)
  onchange?: (value: PhoneNumber) => void;

  // Callback when the selected country changes
  oncountrychange?: (country: MaskFull) => void;

  // Callback when the validation state changes
  onvalidationchange?: (isValid: boolean) => void;

  // Callback when the input is focused
  onfocus?: (event: FocusEvent) => void;

  // Callback when the input is blurred
  onblur?: (event: FocusEvent) => void;

  // Callback when phone number is copied
  oncopy?: (value: string) => void;

  // Callback when input is cleared
  onclear?: () => void;
}

Exposed Methods

Access component methods via bind:this:

<script lang="ts">
  import { PhoneInput } from '@desource/phone-mask-svelte';
  import type { PhoneInputExposed } from '@desource/phone-mask-svelte';

  let phoneInput = $state<PhoneInputExposed | null>(null);
</script>

<PhoneInput bind:this={phoneInput} />
<button onclick={() => phoneInput?.focus()}>Focus</button>
interface PhoneInputExposed {
  focus: () => void; // Focus the input
  blur: () => void; // Blur the input
  clear: () => void; // Clear the input value
  selectCountry: (code: string) => void; // Programmatically select a country by ISO code
  getFullNumber: () => string; // Returns full phone number with country code (e.g. +1234567890)
  getFullFormattedNumber: () => string; // Returns formatted number with country code (e.g. +1 234-567-890)
  getDigits: () => string; // Returns only digits without country code (e.g. 234567890)
  isValid: () => boolean; // Checks if the current phone number is valid
  isComplete: () => boolean; // Alias for isValid()
}

Snippets

<PhoneInput bind:value={phone}>
  {#snippet flag(country)}
    <img src="/flags/{country.code.toLowerCase()}.svg" alt={country.name} />
  {/snippet}

  {#snippet copysvg(copied)}
    {copied ? '✓' : '📋'}
  {/snippet}

  {#snippet clearsvg()}
    ✕
  {/snippet}

  {#snippet actionsbefore()}
    <button onclick={handleCustomAction}>Custom</button>
  {/snippet}
</PhoneInput>
SnippetPropsDescription
flagMaskFullCustom flag icon in the country list and selector
copysvgboolean (copied state)Custom copy button icon
clearsvg—Custom clear button icon
actionsbefore—Content rendered before default action buttons

🧩 Composable API

Options

Note: The composable uses getter functions for reactive options. Do NOT pass values directly.

interface UsePhoneMaskOptions {
  // Getter returning current digit value (controlled) - REQUIRED
  value: () => string;

  // Callback when the digits value changes - REQUIRED
  onChange: (digits: string) => void;

  // Getter for ISO country code (e.g., 'US', 'DE', 'GB')
  country?: () => string | undefined;

  // Getter for locale string (default: navigator.language)
  locale?: () => string | undefined;

  // Getter for auto-detect flag (default: false)
  detect?: () => boolean | undefined;

  // Callback when the phone changes (full, fullFormatted, digits)
  onPhoneChange?: (phone: PhoneNumber) => void;

  // Country change callback
  onCountryChange?: (country: MaskFull) => void;
}

Return Value

Important: Do NOT destructure the returned object — all properties are reactive getters and destructuring breaks reactivity.

interface UsePhoneMaskReturn {
  // Ref to attach to your input element
  inputRef: HTMLInputElement | null;

  // Raw digits without formatting (e.g., "1234567890")
  digits: string;

  // Phone formatter instance
  formatter: FormatterHelpers;

  // Full phone number with country code (e.g., "+11234567890")
  full: string;

  // Full phone number formatted (e.g., "+1 123-456-7890")
  fullFormatted: string;

  // Whether the phone number is complete
  isComplete: boolean;

  // Whether the input is empty
  isEmpty: boolean;

  // Whether to show validation warning
  shouldShowWarn: boolean;

  // Current country data
  country: MaskFull;

  // Current locale used for country names
  locale: string;

  // Change country programmatically
  setCountry: (countryCode?: string | null) => boolean;

  // Clear the input
  clear: () => void;
}
<script lang="ts">
  // ✅ CORRECT — access as properties
  const phoneMask = usePhoneMask(options);
  phoneMask.digits;

  // ❌ WRONG — loses reactivity
  const { digits } = usePhoneMask(options);
</script>

⚡ Attachment API

The phoneMaskAttachment Svelte attachment (Svelte 5.29+) applies phone masking directly to any <input> element via {@attach phoneMaskAttachment(...)}. Unlike use: actions, the attachment factory re-runs reactively when reactive state in the call site changes — no manual update() needed.

Basic Usage

<script lang="ts">
  import { phoneMaskAttachment } from '@desource/phone-mask-svelte';
</script>

<input {@attach phoneMaskAttachment('US')} />

Options

import type { PMaskFull, PMaskPhoneNumber } from '@desource/phone-mask-svelte';

interface PhoneMaskBindingOptions {
  // Predefined country ISO code (e.g., 'US', 'DE', 'GB')
  country?: string;

  // Locale for country names (default: navigator.language)
  locale?: string;

  // Auto-detect country from IP/locale (default: false)
  detect?: boolean;

  // Value change callback
  onChange?: (phone: PMaskPhoneNumber) => void;

  // Country change callback
  onCountryChange?: (country: PMaskFull) => void;
}

The parameter can be a country code string (shorthand) or an options object:

<!-- Shorthand -->
<input {@attach phoneMaskAttachment('DE')} />

<!-- Full options -->
<input {@attach phoneMaskAttachment({ country: 'DE', onChange: handleChange })} />

<!-- Auto-detect -->
<input {@attach phoneMaskAttachment({ detect: true, onCountryChange: handleCountryChange })} />

Reactive Country

Pass reactive state directly — the factory re-runs automatically when selectedCountry changes:

<script lang="ts">
  import { phoneMaskAttachment as phoneMask } from '@desource/phone-mask-svelte';

  let selectedCountry = $state('US');
  let phoneData = $state<{ full: string; digits: string } | null>(null);
</script>

<select bind:value={selectedCountry}>
  <option value="US">🇺🇸 United States</option>
  <option value="GB">🇬🇧 United Kingdom</option>
  <option value="DE">🇩🇪 Germany</option>
</select>

<input {@attach phoneMask({ country: selectedCountry, onChange: (p) => (phoneData = p) })} placeholder="Phone number" />

🎬 Action API

The phoneMaskAction Svelte action applies phone masking directly to any <input> element via use:phoneMaskAction. It works with all Svelte 5 versions — no Svelte 5.29+ required.

When the bound parameter object changes (e.g. a new country value), Svelte automatically calls the action's update() hook, which re-applies the new options and switches the country if needed.

Basic Usage

<script lang="ts">
  import { phoneMaskAction } from '@desource/phone-mask-svelte';
</script>

<input use:phoneMaskAction={'US'} />

Options

import type { PMaskFull, PMaskPhoneNumber } from '@desource/phone-mask-svelte';

interface PhoneMaskBindingOptions {
  // Predefined country ISO code (e.g., 'US', 'DE', 'GB')
  country?: string;

  // Locale for country names (default: navigator.language)
  locale?: string;

  // Auto-detect country from GeoIP on mount; falls back to locale detection
  detect?: boolean;

  // Value change callback — fires on every phone number change
  onChange?: (phone: PMaskPhoneNumber) => void;

  // Country change callback — fires on initial mount and on country switch
  onCountryChange?: (country: PMaskFull) => void;
}

The parameter can be a country code string (shorthand) or an options object:

<!-- Shorthand -->
<input use:phoneMaskAction={'DE'} />

<!-- Full options -->
<input use:phoneMaskAction={{ country: 'DE', onChange: handleChange }} />

<!-- Auto-detect -->
<input use:phoneMaskAction={{ detect: true, onCountryChange: handleCountryChange }} />

Reactive Country

Pass reactive $state inside the options object — Svelte calls update() automatically when selectedCountry changes:

<script lang="ts">
  import { phoneMaskAction as phoneMask } from '@desource/phone-mask-svelte';

  let selectedCountry = $state('US');
  let phoneData = $state<{ full: string; digits: string } | null>(null);
</script>

<select bind:value={selectedCountry}>
  <option value="US">🇺🇸 United States</option>
  <option value="GB">🇬🇧 United Kingdom</option>
  <option value="DE">🇩🇪 Germany</option>
</select>

<input use:phoneMask={{ country: selectedCountry, onChange: (p) => (phoneData = p) }} placeholder="Phone number" />

Action vs Attachment

use:phoneMaskAction{@attach phoneMaskAttachment(...)}
Svelte version requiredAll Svelte 5Svelte 5.29+
Reactivity mechanismupdate() hook (auto-called)Factory re-runs reactively
Manual update() neededNo (Svelte handles it)No

🎨 Component Styling

CSS Custom Properties

Customize colors via CSS variables:

.desource-phone-input,
.desource-phone-dropdown {
  /* Colors */
  --pi-bg: #ffffff;
  --pi-fg: #111827;
  --pi-muted: #6b7280;
  --pi-border: #e5e7eb;
  --pi-border-hover: #d1d5db;
  --pi-border-focus: #3b82f6;
  --pi-focus-ring: 3px solid rgb(59 130 246 / 0.15);
  --pi-disabled-bg: #f9fafb;
  --pi-disabled-fg: #9ca3af;
  /* Sizes */
  --pi-font-size: 16px;
  --pi-height: 44px;
  /* Spacing */
  --pi-padding: 12px;
  /* Border radius */
  --pi-radius: 8px;
  /* Shadows */
  --pi-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
  --pi-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -2px rgb(0 0 0 / 0.05);
  /* Validation */
  --pi-warning: #f59e0b;
  --pi-warning-light: #fbbf24;
  --pi-success: #10b981;
  --pi-focus-ring-warning: 3px solid rgb(245 158 11 / 0.15);
  --pi-focus-ring-success: 3px solid rgb(16 185 129 / 0.15);
}

Dark Theme

<PhoneInput bind:value={phone} theme="dark" />

Or with CSS:

.desource-phone-input.theme-dark {
  --pi-bg: #1f2937;
  --pi-fg: #f9fafb;
  --pi-border: #374151;
}

📚 Examples

With Validation

<script lang="ts">
  import { PhoneInput } from '@desource/phone-mask-svelte';

  let phone = $state('');
  let isValid = $state(false);

  const errorMessage = $derived(!phone ? '' : isValid ? '' : 'Please enter a valid phone number');
</script>

<div>
  <PhoneInput bind:value={phone} country="US" onvalidationchange={(v) => (isValid = v)} />

  {#if errorMessage}
    <span class="error">{errorMessage}</span>
  {/if}
</div>

Auto-detect Country

<script lang="ts">
  import { PhoneInput } from '@desource/phone-mask-svelte';
  import type { PMaskFull } from '@desource/phone-mask-svelte';

  let phone = $state('');
  let detectedCountry = $state('');

  function handleCountryChange(country: PMaskFull) {
    detectedCountry = country.name;
  }
</script>

<PhoneInput bind:value={phone} detect oncountrychange={handleCountryChange} />

{#if detectedCountry}
  <p>Detected: {detectedCountry}</p>
{/if}

Programmatic Control

<script lang="ts">
  import { PhoneInput } from '@desource/phone-mask-svelte';
  import type { PhoneInputExposed } from '@desource/phone-mask-svelte';

  let phone = $state('');
  let phoneInput = $state<PhoneInputExposed | null>(null);
</script>

<PhoneInput bind:this={phoneInput} bind:value={phone} />

<div>
  <button onclick={() => phoneInput?.focus()}>Focus</button>
  <button onclick={() => phoneInput?.clear()}>Clear</button>
  <button onclick={() => phoneInput?.selectCountry('GB')}>Switch to UK</button>
  <p>Full: {phoneInput?.getFullFormattedNumber()}</p>
  <p>Valid: {phoneInput?.isValid()}</p>
</div>

Multiple Inputs

<script lang="ts">
  import { PhoneInput } from '@desource/phone-mask-svelte';

  let form = $state({ mobile: '', home: '', work: '' });
</script>

<div class="form">
  <label>
    Mobile
    <PhoneInput bind:value={form.mobile} country="US" />
  </label>

  <label>
    Home
    <PhoneInput bind:value={form.home} country="US" />
  </label>

  <label>
    Work
    <PhoneInput bind:value={form.work} country="US" />
  </label>
</div>

Custom Composable Implementation

<script lang="ts">
  import { usePhoneMask } from '@desource/phone-mask-svelte';
  import type { PMaskPhoneNumber } from '@desource/phone-mask-svelte';

  let inputValue = $state('');
  let selectedCountry = $state('US');

  const phoneMask = usePhoneMask({
    value: () => inputValue,
    country: () => selectedCountry,
    detect: () => false,
    onChange: (digits) => {
      inputValue = digits;
    },
    onPhoneChange: (data: PMaskPhoneNumber) => {
      console.log('Phone:', data.fullFormatted);
    }
  });
</script>

<div class="custom-phone">
  <select bind:value={selectedCountry}>
    <option value="US">🇺🇸 +1</option>
    <option value="GB">🇬🇧 +44</option>
    <option value="DE">🇩🇪 +49</option>
  </select>

  <input bind:this={phoneMask.inputRef} type="tel" placeholder="Phone number" />
</div>

<p>Formatted: {phoneMask.fullFormatted}</p>
<p>Valid: {phoneMask.isComplete ? 'Yes' : 'No'}</p>
<p>Country: {phoneMask.country.name}</p>

🎯 Browser Support

  • Chrome/Edge 90+
  • Firefox 88+
  • Safari 14+
  • iOS Safari 14+
  • Chrome Mobile

📦 What's Included

@desource/phone-mask-svelte/
├── dist/
│   ├── index.js               # Main Svelte package entry
│   ├── index.d.ts             # Root TypeScript declarations
│   ├── components/
│   │   └── PhoneInput.svelte  # Consumer-compiled Svelte component
│   ├── core.js                # Core kit utilities and mask data subpath
│   ├── core.d.ts              # Core subpath declarations
│   ├── phone-mask-svelte.css  # Component styles
│   └── ...                    # Supporting modules and colocated declarations
├── README.md                  # This file
└── package.json               # Package manifest

The package ships native Svelte components and rune modules so your Svelte build compiles them for the browser or server. It requires Svelte-aware tooling; there is no precompiled CommonJS component entry.

📄 License

MIT © 2026 DeSource Labs

🤝 Contributing

See Contributing Guide for development, testing, and publication checks.


Made with ❤️ by DeSource Labs

版本与发布记录

@desource/phone-mask-svelte 共发布 18 个版本,首次发布于 2026/03/07;过去 12 个月发布了 18 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
2.1.0latest2026/11/074 天前
2.0.02026/10/3111 天前
1.7.02026/10/101 个月前
1.6.42026/09/261 个月前
1.6.32026/09/082 个月前
1.6.22026/08/242 个月前

关于 @desource/phone-mask-svelte 的常见问题

@desource/phone-mask-svelte 是什么?

🌍 Svelte 5 component, composable, action, and attachment for international phone number masking. Powered by @desource/phone-mask with Google libphonenumber sync.

如何安装 @desource/phone-mask-svelte?

在项目目录运行 npm install @desource/phone-mask-svelte,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:svelte(^5.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@desource/phone-mask-svelte 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@desource/phone-mask-svelte 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@desource/phone-mask-svelte 可以商用吗?

@desource/phone-mask-svelte 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@desource/phone-mask-svelte 的下载量有多少?

根据 npm 官方下载统计,@desource/phone-mask-svelte 在 2026/10/12 – 2026/11/10 这 30 天内被下载 19,699 次,日均约 657 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@desource/phone-mask-svelte 还在维护吗?

最新版本 2.1.0 发布于 2026/11/07(4 天前);至今共发布 18 个版本,其中过去 12 个月发布了 18 个;首次发布于 2026/03/07。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。