Files
emaui/libs/ui/src/lib/input/phone.ts

57 lines
2.2 KiB
TypeScript

import { z } from 'zod';
import { getCountryCallingCode, isValidPhoneNumber, parsePhoneNumberFromString, type CountryCode } from 'libphonenumber-js';
/**
* Accepts any international number in E.164 (`+<country><number>`) or a
* bare national number, which is assumed Ethiopian (`0911223344` ->
* `+251911223344`) for backward compatibility with existing records.
*/
export const phoneNumber = z
.string()
.trim()
.transform((v) => parsePhoneNumberFromString(v, 'ET')?.number ?? v)
.refine((v) => isValidPhoneNumber(v), {
message: 'Enter a valid phone number',
});
/** Same rules, but blank is allowed. */
export const optionalPhoneNumber = z.union([z.literal(''), phoneNumber]).optional();
/**
* Digits the field should hold after an edit. A keystroke that only removed
* a formatting character (backspacing the ')' out of "(415)") leaves the
* digits unchanged, which would otherwise make the caret stick — drop a real
* digit in that case.
*/
export function nextNationalDigits(text: string, prevDisplay: string): string {
const digits = text.replace(/\D/g, '');
const deleting = text.length < prevDisplay.length;
if (deleting && digits === prevDisplay.replace(/\D/g, '')) return digits.slice(0, -1);
return digits;
}
/** National digits of a stored value, for display in the number box. */
export function toNationalDigits(value: string, country: CountryCode): string {
if (!value) return '';
const parsed = parsePhoneNumberFromString(value);
if (parsed) return parsed.nationalNumber;
if (value.startsWith('+')) {
const prefix = `+${getCountryCallingCode(country)}`;
if (value.startsWith(prefix)) return value.slice(prefix.length).replace(/\D/g, '');
}
return value.replace(/\D/g, '');
}
/**
* E.164 for the digits typed so far. Incomplete numbers don't parse, so they
* fall back to a plain dial-code concatenation rather than collapsing to ''
* — the value has to survive mid-typing for the field to be usable.
*/
export function toE164(digits: string, country: CountryCode): string {
if (!digits) return '';
return (
parsePhoneNumberFromString(digits, country)?.number ??
`+${getCountryCallingCode(country)}${digits}`
);
}