@desource/phone-mask-svelte
Svelte 5 phone input component with Google's libphonenumber data
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 —
PhoneInputrenders 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:valuefor two-way binding orvalue+onchangefor 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>
| Snippet | Props | Description |
|---|---|---|
flag | MaskFull | Custom flag icon in the country list and selector |
copysvg | boolean (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 required | All Svelte 5 | Svelte 5.29+ |
| Reactivity mechanism | update() hook (auto-called) | Factory re-runs reactively |
Manual update() needed | No (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.
🔗 Related Packages
- @desource/phone-mask — Core library
- @desource/phone-mask-angular — Angular bindings
- @desource/phone-mask-react — React bindings
- @desource/phone-mask-vue — Vue 3 bindings
- @desource/phone-mask-nuxt — Nuxt module
📄 License
MIT © 2026 DeSource Labs
🤝 Contributing
See Contributing Guide for development, testing, and publication checks.