{"version":3,"sources":["../src/format.ts","../src/metadata.ts"],"sourcesContent":["import { getMetadata, type CountryMetadata, type FormatRule } from './metadata.js';\nimport type { PhoneNumber } from './types.js';\n\nexport type FormatStyle = 'NATIONAL' | 'INTERNATIONAL' | 'E164';\n\nconst applyTemplate = (template: string, groups: string[]): string =>\n  template.replace(/\\$(\\d)/g, (_match, index: string) => groups[Number(index) - 1] ?? '');\n\ninterface RuleMatch {\n  rule: FormatRule;\n  groups: string[];\n}\n\n// `skipNA` is only relevant for INTERNATIONAL - a rule's `intlFormat` of\n// exactly \"NA\" means \"don't use this rule internationally, try the next\n// matching one\" (e.g. a local-only format that omits the area code).\nconst findMatchingRule = (nationalNumber: string, rules: FormatRule[], skipNA: boolean): RuleMatch | undefined => {\n  for (const rule of rules) {\n    if (skipNA && rule.intlFormat === 'NA') {\n      continue;\n    }\n    const match = nationalNumber.match(new RegExp(`^(?:${rule.pattern})$`));\n    if (match) {\n      return { rule, groups: match.slice(1) };\n    }\n  }\n  return undefined;\n};\n\nconst formatNational = (nationalNumber: string, region: CountryMetadata): string => {\n  const found = findMatchingRule(nationalNumber, region.formats ?? [], false);\n  if (!found) {\n    return nationalNumber;\n  }\n  const { rule, groups } = found;\n\n  if (!rule.nationalPrefixFormattingRule) {\n    return applyTemplate(rule.format, groups);\n  }\n\n  // Only the first group gets prefix-wrapped - e.g. rule \"$NP$FG\" with\n  // nationalPrefix \"0\" and first group \"10\" produces \"010\", which then\n  // substitutes wherever `format` uses \"$1\".\n  const prefixedFirstGroup = rule.nationalPrefixFormattingRule\n    .replace('$NP', region.nationalPrefix ?? '')\n    .replace('$FG', groups[0] ?? '');\n  return applyTemplate(rule.format, [prefixedFirstGroup, ...groups.slice(1)]);\n};\n\nconst formatInternational = (nationalNumber: string, region: CountryMetadata): string => {\n  const found = findMatchingRule(nationalNumber, region.formats ?? [], true);\n  const grouped = found ? applyTemplate(found.rule.intlFormat ?? found.rule.format, found.groups) : nationalNumber;\n  return `+${region.callingCode} ${grouped}`;\n};\n\n// Falls back to the unformatted national number (NATIONAL) or plain\n// e164 (INTERNATIONAL/E164 with no region resolved) rather than\n// throwing - consistent with the rest of this library treating missing\n// metadata as \"can't do better,\" not an error.\nexport const format = (phoneNumber: PhoneNumber, style: FormatStyle): string => {\n  if (style === 'E164') {\n    return phoneNumber.e164;\n  }\n\n  const region = phoneNumber.region ? getMetadata()[phoneNumber.region] : undefined;\n  if (!region) {\n    return phoneNumber.e164;\n  }\n\n  if (style === 'NATIONAL') {\n    return formatNational(phoneNumber.nationalNumber, region);\n  }\n\n  return formatInternational(phoneNumber.nationalNumber, region);\n};\n","export interface TypePattern {\n  nationalNumberPattern: string;\n  possibleLengths: number[];\n}\n\nexport interface FormatRule {\n  // Full-match regex with capture groups, e.g. \"(\\d{3})(\\d{4})\".\n  pattern: string;\n  // Template using $1/$2/... for the capture groups, e.g. \"$1 $2\".\n  format: string;\n  // How the national prefix combines with the first group in NATIONAL\n  // format specifically, e.g. \"$NP$FG\" ($NP = nationalPrefix, $FG = the\n  // raw first group) - omitted means NATIONAL format has no prefix at all.\n  // Never applies to INTERNATIONAL format.\n  nationalPrefixFormattingRule?: string;\n  // Overrides `format` for INTERNATIONAL style specifically. The literal\n  // value \"NA\" means this rule should be skipped entirely for\n  // INTERNATIONAL (try the next matching rule) - e.g. local-only formats\n  // that omit the area code aren't valid once you need to dial\n  // internationally. Omitted (not \"NA\") means reuse `format` as-is.\n  intlFormat?: string;\n  // Upstream declaration order, least to most specific - each is a\n  // stricter refinement of the one before it, not an independent\n  // alternative. Only used by asYouType() to pick a candidate rule before\n  // enough digits exist for `pattern` to fully match; `format()` doesn't\n  // need this since it only ever formats complete numbers.\n  leadingDigits?: string[];\n}\n\nexport interface CountryMetadata {\n  // ISO 3166-1 alpha-2, e.g. \"US\". Kept on the value itself (not just as\n  // the object key) because callers group entries by calling code - once\n  // grouped, the original object key is gone.\n  region: string;\n  callingCode: string;\n  // General/fallback pattern - used directly when a type-specific match\n  // isn't available (e.g. a region without per-type data yet).\n  nationalNumberPattern: string;\n  possibleLengths: number[];\n  // Per-type patterns, each optional since not every region's upstream\n  // data distinguishes all of these (e.g. many small territories have no\n  // separate pager/uan/voicemail block at all).\n  types?: {\n    MOBILE?: TypePattern;\n    FIXED?: TypePattern;\n    TOLL_FREE?: TypePattern;\n    PREMIUM_RATE?: TypePattern;\n    SHARED_COST?: TypePattern;\n    PERSONAL_NUMBER?: TypePattern;\n    VOIP?: TypePattern;\n    PAGER?: TypePattern;\n    UAN?: TypePattern;\n    VOICEMAIL?: TypePattern;\n  };\n  // e.g. \"0\" for GB, \"1\" for NANP. Absent for regions with no national\n  // dialing prefix.\n  nationalPrefix?: string;\n  // In upstream declaration order - first full match against the national\n  // number wins, same resolution order as Google's own algorithm.\n  formats?: FormatRule[];\n}\n\nexport interface Metadata {\n  // Keyed by region, not calling code: a calling code can map to several\n  // regions (e.g. NANP's \"1\" covers US, Canada, and ~19 Caribbean\n  // territories), so calling code can't be a unique key.\n  [region: string]: CountryMetadata;\n}\n\n// Global state for injected metadata, initialized empty\nlet _metadata: Metadata = {};\n\nexport const setup = (config: { metadata: Metadata }) => {\n  _metadata = config.metadata;\n};\n\nexport const getMetadata = (): Metadata => _metadata;\n\n// These three all read whatever's currently injected via setup() - not\n// the full set of countries DialSense ships in data/, which this module\n// has no knowledge of at runtime. A country is only \"supported\" once its\n// metadata has actually been passed to setup().\nexport const getCountries = (): string[] => Object.keys(_metadata);\n\nexport const getCountryCallingCode = (country: string): number | undefined => {\n  const region = _metadata[country];\n  return region ? Number(region.callingCode) : undefined;\n};\n\nexport const isSupportedCountry = (country: string): boolean => country in _metadata;"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACsEA,IAAI,YAAsB,CAAC;AAMpB,IAAM,cAAc,MAAgB;;;ADvE3C,IAAM,gBAAgB,CAAC,UAAkB,WACvC,SAAS,QAAQ,WAAW,CAAC,QAAQ,UAAkB,OAAO,OAAO,KAAK,IAAI,CAAC,KAAK,EAAE;AAUxF,IAAM,mBAAmB,CAAC,gBAAwB,OAAqB,WAA2C;AAChH,aAAW,QAAQ,OAAO;AACxB,QAAI,UAAU,KAAK,eAAe,MAAM;AACtC;AAAA,IACF;AACA,UAAM,QAAQ,eAAe,MAAM,IAAI,OAAO,OAAO,KAAK,OAAO,IAAI,CAAC;AACtE,QAAI,OAAO;AACT,aAAO,EAAE,MAAM,QAAQ,MAAM,MAAM,CAAC,EAAE;AAAA,IACxC;AAAA,EACF;AACA,SAAO;AACT;AAEA,IAAM,iBAAiB,CAAC,gBAAwB,WAAoC;AAClF,QAAM,QAAQ,iBAAiB,gBAAgB,OAAO,WAAW,CAAC,GAAG,KAAK;AAC1E,MAAI,CAAC,OAAO;AACV,WAAO;AAAA,EACT;AACA,QAAM,EAAE,MAAM,OAAO,IAAI;AAEzB,MAAI,CAAC,KAAK,8BAA8B;AACtC,WAAO,cAAc,KAAK,QAAQ,MAAM;AAAA,EAC1C;AAKA,QAAM,qBAAqB,KAAK,6BAC7B,QAAQ,OAAO,OAAO,kBAAkB,EAAE,EAC1C,QAAQ,OAAO,OAAO,CAAC,KAAK,EAAE;AACjC,SAAO,cAAc,KAAK,QAAQ,CAAC,oBAAoB,GAAG,OAAO,MAAM,CAAC,CAAC,CAAC;AAC5E;AAEA,IAAM,sBAAsB,CAAC,gBAAwB,WAAoC;AACvF,QAAM,QAAQ,iBAAiB,gBAAgB,OAAO,WAAW,CAAC,GAAG,IAAI;AACzE,QAAM,UAAU,QAAQ,cAAc,MAAM,KAAK,cAAc,MAAM,KAAK,QAAQ,MAAM,MAAM,IAAI;AAClG,SAAO,IAAI,OAAO,WAAW,IAAI,OAAO;AAC1C;AAMO,IAAM,SAAS,CAAC,aAA0B,UAA+B;AAC9E,MAAI,UAAU,QAAQ;AACpB,WAAO,YAAY;AAAA,EACrB;AAEA,QAAM,SAAS,YAAY,SAAS,YAAY,EAAE,YAAY,MAAM,IAAI;AACxE,MAAI,CAAC,QAAQ;AACX,WAAO,YAAY;AAAA,EACrB;AAEA,MAAI,UAAU,YAAY;AACxB,WAAO,eAAe,YAAY,gBAAgB,MAAM;AAAA,EAC1D;AAEA,SAAO,oBAAoB,YAAY,gBAAgB,MAAM;AAC/D;","names":[]}