{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "phone-input",
  "title": "Phone Input",
  "description": "An international phone number field: a searchable country picker, that country's calling code shown as a live prefix, and one E.164 string (\"+819012345678\") coming back out. Reach for it wherever a form asks for a number someone can actually be reached on: sign-up and onboarding; two-factor authentication and SMS one-time codes; WhatsApp, SMS and voice notification preferences; shipping, billing and delivery contact details; the callback number on a support or contact form; restaurant, clinic, salon and appointment booking; driver, courier and rider contact on a marketplace; the phone row on a CRM or address-book record; KYC and identity verification; account recovery; and the \"mobile\" field in profile, account and organisation settings. Common asks it answers: \"phone input\", \"phone number input react\", \"international phone input\", \"phone input with country code\", \"country code selector phone\", \"tel input component\", \"E.164 input\", \"shadcn phone input\", \"shadcn phone number field\", \"react-phone-number-input alternative\", \"intl-tel-input react\", \"phone field with flags\", \"dial code dropdown\", \"mobile number input\", \"international telephone input shadcn\", \"phone number formatting as you type\". Official shadcn/ui has nothing for telephones — not a phone item, not a `type=\"tel\"` anywhere in its sixty-three components, no calling codes and no country data — so the whole of this is on you, and the data is where hand-rolled versions go wrong. It is also the one field in this family that cannot read its data out of the runtime: `Intl.supportedValuesOf` rejects every phone-shaped key, `Intl.Locale` exposes no telephony property and `Intl.DisplayNames` names regions but not calling codes, so no browser knows that Japan is +81. This component therefore ships the table — 246 countries, every ISO 3166-1 country that has an assigned calling code, cross-checked between two independent sources — and carrying it is a smaller liability than it looks, because calling codes are close to frozen: the last new one was South Sudan's +211 in 2011. The table holds calling codes and never a calling code plus an area code, which is the distinction hand-rolled fields miss: the Bahamas is +1, not +1-242, because 242 belongs to the ten-digit national number a Bahamian dials. Twenty-five countries share +1 and four share +44, so the country is kept as its own piece of state and never inferred back out of the digits — a stored \"+12425550100\" cannot say whether its owner is in Nassau or Nevada, and `countryName` submits the answer alongside the number so the field comes back up on the country the person actually picked. What it does not do is as deliberate as what it does: it assembles, caps the result at E.164's fifteen digits and spaces them for reading, and it never claims a number is valid. Per-country validity is a rule for 246 countries and the library that knows them is larger than this entire registry, so it is left to your submit handler rather than faked — a field that pretends otherwise fails exactly the people whose country it got wrong. Typing is handled properly rather than approximately: digits group as you type, the caret is tracked in digits rather than in character offsets so a number stays correctable in the middle instead of only from the end, Backspace and Delete remove a digit rather than a separator that was never typed (the single most common complaint about masked inputs), pasting \"+81 90-1234-5678\" moves the country and strips the code instead of doubling it, and `format` takes a mask like \"## #### ####\" for a form that only ever collects one country's numbers. Controlled or uncontrolled, and correct in both: `value` + `onValueChange` for the number, `country` + `onCountryChange` for the country, `defaultValue`/`defaultCountry` for neither. The digits stay in local state while the value round-trips, so a parent that debounces, validates or is simply slow does not erase what was just typed — the failure that makes most hand-rolled controlled phone fields impossible to type into. It is a real composite control, not a styled div: a `type=\"tel\"` input with `inputMode=\"tel\"` and `autoComplete=\"tel-national\"`, the calling code wired into the input's `aria-describedby` so it is announced rather than being visual context a screen reader never reaches, the country picker a full `role=\"combobox\"` listbox with search, arrow keys and `aria-activedescendant`, and a `focus-within` ring around the number half. `countries` narrows it to where you operate, `priority` pins the two or three countries most sign-ups come from, `flags` adds the flag emoji (off by default — Windows ships no flag glyphs), and `getDialCode()`, `splitPhoneNumber()`, `toE164()`, `formatPhoneDigits()` and `getRegionCountry()` are exported so a confirmation screen, an SMS log or an admin table spells the same number the same way. Styled entirely with shadcn tokens (input, ring, popover, muted-foreground), so it follows light and dark mode, and it ships zero npm dependencies — no libphonenumber, no country-data package, no icon package.",
  "dependencies": [],
  "registryDependencies": [
    "https://pulld.pages.dev/r/country-select.json"
  ],
  "files": [
    {
      "path": "registry/ui/phone-input.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { CountrySelect } from \"@/registry/ui/country-select\"\n\n/**\n * The ITU-T E.164 country calling code for every ISO 3166-1 country that has one, without the `+`.\n *\n * This is the one table this family of components could not avoid shipping. `country-select`,\n * `currency-select`, `timezone-select` and `language-select` all read their data out of `Intl` at\n * runtime rather than carrying a list, and none of that works here: `Intl.supportedValuesOf`\n * rejects every phone-shaped key, `Intl.Locale` exposes no telephony property, and\n * `Intl.DisplayNames` names regions and currencies but not calling codes. No runtime knows that\n * Japan is +81.\n *\n * Carrying it is a smaller liability than it looks. Calling codes are close to frozen — the last\n * new one was South Sudan's +211 in 2011 — where currency codes changed twice in the last two\n * years, so this table does not rot the way a currency table would.\n *\n * Three ISO countries are deliberately absent, because no calling code is assigned to them at all:\n * `AQ` (Antarctica, whose stations dial through whichever operator runs them), `HM` and `UM`. They\n * exist in `country-select`, which answers a different question — an address can name a place that\n * has no telephone service.\n *\n * The value here is the *calling code*, never a calling code plus an area code. That distinction\n * matters most across the North American Numbering Plan: the Bahamas is +1, not +1-242, because 242\n * is part of the ten-digit national number a Bahamian would dial. Twenty-five countries share +1\n * this way, and four share +44, which is why the country a person picks is kept as its own piece of\n * state and never inferred from the digits alone.\n */\nexport const DIAL_CODES: Readonly<Record<string, string>> = {\n  AD: \"376\", AE: \"971\", AF: \"93\", AG: \"1\", AI: \"1\", AL: \"355\", AM: \"374\", AO: \"244\",\n  AR: \"54\", AS: \"1\", AT: \"43\", AU: \"61\", AW: \"297\", AX: \"358\", AZ: \"994\", BA: \"387\",\n  BB: \"1\", BD: \"880\", BE: \"32\", BF: \"226\", BG: \"359\", BH: \"973\", BI: \"257\", BJ: \"229\",\n  BL: \"590\", BM: \"1\", BN: \"673\", BO: \"591\", BQ: \"599\", BR: \"55\", BS: \"1\", BT: \"975\",\n  BV: \"47\", BW: \"267\", BY: \"375\", BZ: \"501\", CA: \"1\", CC: \"61\", CD: \"243\", CF: \"236\",\n  CG: \"242\", CH: \"41\", CI: \"225\", CK: \"682\", CL: \"56\", CM: \"237\", CN: \"86\", CO: \"57\",\n  CR: \"506\", CU: \"53\", CV: \"238\", CW: \"599\", CX: \"61\", CY: \"357\", CZ: \"420\", DE: \"49\",\n  DJ: \"253\", DK: \"45\", DM: \"1\", DO: \"1\", DZ: \"213\", EC: \"593\", EE: \"372\", EG: \"20\",\n  EH: \"212\", ER: \"291\", ES: \"34\", ET: \"251\", FI: \"358\", FJ: \"679\", FK: \"500\", FM: \"691\",\n  FO: \"298\", FR: \"33\", GA: \"241\", GB: \"44\", GD: \"1\", GE: \"995\", GF: \"594\", GG: \"44\",\n  GH: \"233\", GI: \"350\", GL: \"299\", GM: \"220\", GN: \"224\", GP: \"590\", GQ: \"240\", GR: \"30\",\n  GS: \"500\", GT: \"502\", GU: \"1\", GW: \"245\", GY: \"592\", HK: \"852\", HN: \"504\", HR: \"385\",\n  HT: \"509\", HU: \"36\", ID: \"62\", IE: \"353\", IL: \"972\", IM: \"44\", IN: \"91\", IO: \"246\",\n  IQ: \"964\", IR: \"98\", IS: \"354\", IT: \"39\", JE: \"44\", JM: \"1\", JO: \"962\", JP: \"81\",\n  KE: \"254\", KG: \"996\", KH: \"855\", KI: \"686\", KM: \"269\", KN: \"1\", KP: \"850\", KR: \"82\",\n  KW: \"965\", KY: \"1\", KZ: \"7\", LA: \"856\", LB: \"961\", LC: \"1\", LI: \"423\", LK: \"94\",\n  LR: \"231\", LS: \"266\", LT: \"370\", LU: \"352\", LV: \"371\", LY: \"218\", MA: \"212\", MC: \"377\",\n  MD: \"373\", ME: \"382\", MF: \"590\", MG: \"261\", MH: \"692\", MK: \"389\", ML: \"223\", MM: \"95\",\n  MN: \"976\", MO: \"853\", MP: \"1\", MQ: \"596\", MR: \"222\", MS: \"1\", MT: \"356\", MU: \"230\",\n  MV: \"960\", MW: \"265\", MX: \"52\", MY: \"60\", MZ: \"258\", NA: \"264\", NC: \"687\", NE: \"227\",\n  NF: \"672\", NG: \"234\", NI: \"505\", NL: \"31\", NO: \"47\", NP: \"977\", NR: \"674\", NU: \"683\",\n  NZ: \"64\", OM: \"968\", PA: \"507\", PE: \"51\", PF: \"689\", PG: \"675\", PH: \"63\", PK: \"92\",\n  PL: \"48\", PM: \"508\", PN: \"64\", PR: \"1\", PS: \"970\", PT: \"351\", PW: \"680\", PY: \"595\",\n  QA: \"974\", RE: \"262\", RO: \"40\", RS: \"381\", RU: \"7\", RW: \"250\", SA: \"966\", SB: \"677\",\n  SC: \"248\", SD: \"249\", SE: \"46\", SG: \"65\", SH: \"290\", SI: \"386\", SJ: \"47\", SK: \"421\",\n  SL: \"232\", SM: \"378\", SN: \"221\", SO: \"252\", SR: \"597\", SS: \"211\", ST: \"239\", SV: \"503\",\n  SX: \"1\", SY: \"963\", SZ: \"268\", TC: \"1\", TD: \"235\", TF: \"262\", TG: \"228\", TH: \"66\",\n  TJ: \"992\", TK: \"690\", TL: \"670\", TM: \"993\", TN: \"216\", TO: \"676\", TR: \"90\", TT: \"1\",\n  TV: \"688\", TW: \"886\", TZ: \"255\", UA: \"380\", UG: \"256\", US: \"1\", UY: \"598\", UZ: \"998\",\n  VA: \"39\", VC: \"1\", VE: \"58\", VG: \"1\", VI: \"1\", VN: \"84\", VU: \"678\", WF: \"681\",\n  WS: \"685\", YE: \"967\", YT: \"262\", ZA: \"27\", ZM: \"260\", ZW: \"263\",\n}\n\n/**\n * The countries this field can offer, alphabetical by code — every ISO country with a calling code.\n *\n * Pass it, or a narrowing of it, to `country-select` elsewhere in the same form so the two controls\n * agree on what exists.\n */\nexport const PHONE_COUNTRIES: readonly string[] = Object.keys(DIAL_CODES).sort()\n\n/**\n * The country shown when a calling code is shared and nothing else says which one it is.\n *\n * Thirteen codes belong to more than one country, and a number alone cannot say which: +1 is the\n * United States and twenty-four other places, +44 is the United Kingdom and three Crown\n * dependencies. When a value arrives from a database with no country beside it, one of them has to\n * be shown, and showing the most populous is the guess that is right most often. Keep the country\n * next to the number — `countryName` below writes it into the same form — and this table is never\n * consulted.\n */\nconst PRIMARY_COUNTRY: Readonly<Record<string, string>> = {\n  \"1\": \"US\", \"7\": \"RU\", \"39\": \"IT\", \"44\": \"GB\", \"47\": \"NO\", \"61\": \"AU\", \"64\": \"NZ\",\n  \"212\": \"MA\", \"262\": \"RE\", \"358\": \"FI\", \"500\": \"FK\", \"590\": \"GP\", \"599\": \"CW\",\n}\n\n/** Every distinct calling code, for the longest-prefix match in `splitPhoneNumber`. */\nconst DIAL_CODE_SET = new Set(Object.values(DIAL_CODES))\n\n/** E.164 caps a whole number — calling code and national digits together — at fifteen digits. */\nconst E164_MAX_DIGITS = 15\n\n/** The calling code for an ISO 3166-1 alpha-2 code, without the `+`, or \"\" if it has none. */\nexport function getDialCode(country: string): string {\n  return DIAL_CODES[country.toUpperCase()] ?? \"\"\n}\n\n/**\n * Splits an E.164 number into its calling code and the national digits after it.\n *\n * Exported because the split is needed wherever a stored number is shown rather than edited — a\n * confirmation screen, an SMS log, an admin table — and doing it by hand goes wrong on the codes\n * that are one digit (+1, +7) and the ones that are three (+263), which cannot be told apart\n * without the list. The longest code that matches wins, so +1 never swallows a +1-shaped prefix of\n * a longer code.\n *\n * A string with no leading `+` is not an international number and is not guessed at: its digits\n * come back as the national part with an empty calling code, which is what lets a field keep\n * accepting a local number a parent handed it unchanged.\n */\nexport function splitPhoneNumber(value: string): { dialCode: string; national: string } {\n  const digits = value.replace(/\\D/g, \"\")\n  if (!value.trim().startsWith(\"+\")) return { dialCode: \"\", national: digits }\n  for (let length = 3; length >= 1; length--) {\n    const head = digits.slice(0, length)\n    if (DIAL_CODE_SET.has(head)) return { dialCode: head, national: digits.slice(length) }\n  }\n  return { dialCode: \"\", national: digits }\n}\n\n/**\n * The E.164 string for a country and a national number, or \"\" when there are no national digits.\n *\n * \"\" rather than \"+81\" for an empty number on purpose: a calling code with nothing after it is not\n * a phone number, and a parent that stored it would later have to strip it back off before deciding\n * whether the field was filled in.\n */\nexport function toE164(country: string, national: string): string {\n  const dialCode = getDialCode(country)\n  const digits = national.replace(/\\D/g, \"\")\n  if (!dialCode || !digits) return \"\"\n  return `+${dialCode}${digits}`.slice(0, E164_MAX_DIGITS + 1)\n}\n\n/**\n * The visitor's own country, from the runtime's locale — `getRegionCountry()` on a browser set to\n * ja-JP gives \"JP\".\n *\n * Not the default, deliberately: the server's locale is the server's, so seeding the field from the\n * runtime would render one country on the server and another after hydration, and unlike a\n * mislabelled row that mismatch changes the value that gets submitted. Pass it as `defaultCountry`\n * from an effect, or from a country you already resolved from the request, when you want it.\n */\nexport function getRegionCountry(locale?: string): string {\n  try {\n    const tag = locale ?? new Intl.DateTimeFormat().resolvedOptions().locale\n    const region = new Intl.Locale(tag).maximize().region\n    return region && DIAL_CODES[region] ? region : \"\"\n  } catch {\n    return \"\"\n  }\n}\n\n/**\n * The national digits, spaced for reading.\n *\n * Groups of three, which is what E.123 uses for international notation, with a trailing lone digit\n * folded into the group before it so a ten-digit number reads \"415 555 0132\" rather than\n * \"415 555 013 2\".\n *\n * It is not the national convention, and that is a decision rather than an oversight: writing\n * 90-1234-5678 in Japan, 07911 123456 in the UK and (415) 555-0132 in the US needs a per-country\n * rule for all 246 of them, which is libphonenumber's job and half a megabyte of it. Pass `format`\n * for a form that only ever collects one country's numbers.\n */\nexport function formatPhoneDigits(digits: string, format?: string): string {\n  const clean = digits.replace(/\\D/g, \"\")\n  if (!clean) return \"\"\n  if (format !== undefined) {\n    if (format === \"\") return clean\n    let out = \"\"\n    let at = 0\n    for (const ch of format) {\n      if (at >= clean.length) break\n      if (ch === \"#\") out += clean[at++]\n      else out += ch\n    }\n    // Digits past the end of the mask are kept rather than dropped: a mask is a reading aid, and\n    // silently swallowing what someone typed is worse than a number that outgrows its shape.\n    return at < clean.length ? `${out} ${clean.slice(at)}` : out\n  }\n  const groups: string[] = []\n  for (let i = 0; i < clean.length; i += 3) groups.push(clean.slice(i, i + 3))\n  if (groups.length > 1 && groups[groups.length - 1].length === 1) {\n    groups[groups.length - 2] += groups.pop()\n  }\n  return groups.join(\" \")\n}\n\n/** How many digits `text` holds — the unit the caret is tracked in, since separators move. */\nfunction countDigits(text: string): number {\n  let n = 0\n  for (const ch of text) if (ch >= \"0\" && ch <= \"9\") n++\n  return n\n}\n\n/** The offset in `text` just after its `n`th digit, for putting the caret back after a reformat. */\nfunction caretAfterDigits(text: string, n: number): number {\n  if (n <= 0) return 0\n  let seen = 0\n  for (let i = 0; i < text.length; i++) {\n    const ch = text[i]\n    if (ch >= \"0\" && ch <= \"9\" && ++seen === n) return i + 1\n  }\n  return text.length\n}\n\n/** `digits` without the one at `index`. */\nfunction removeDigitAt(digits: string, index: number): string {\n  return digits.slice(0, index) + digits.slice(index + 1)\n}\n\nexport interface PhoneInputProps\n  extends Omit<\n    React.ComponentPropsWithoutRef<\"input\">,\n    \"value\" | \"defaultValue\" | \"onChange\" | \"type\" | \"name\" | \"prefix\"\n  > {\n  /** Controlled number in E.164 (\"+819012345678\"). \"\" is an empty field. Pair with `onValueChange`. */\n  value?: string\n  /** Starting number for an uncontrolled field. Ignored once `value` is passed. */\n  defaultValue?: string\n  /**\n   * Called with the E.164 number on every keystroke, and \"\" while the national part is empty.\n   *\n   * It fires with partial numbers as they are typed, the way any text field does — this component\n   * assembles and never judges. Nothing here can tell a finished number from half of one, because\n   * that answer is per-country and lives in libphonenumber; validate on submit, or on the server.\n   */\n  onValueChange?: (value: string) => void\n  /** Controlled ISO 3166-1 alpha-2 country, e.g. \"JP\". Pair with `onCountryChange`. */\n  country?: string\n  /** Country an uncontrolled field starts on (default \"US\"; see `getRegionCountry`). */\n  defaultCountry?: string\n  /** Called with the alpha-2 code when the country changes, including when a pasted number moves it. */\n  onCountryChange?: (country: string) => void\n  /** The countries to offer, in place of all 246. Anything without a calling code is dropped. */\n  countries?: readonly string[]\n  /** Countries pinned above the alphabet, in the order given — where your sign-ups come from. */\n  priority?: readonly string[]\n  /** Language the country names are shown in (default: the runtime's own). */\n  locale?: string\n  /** Show the flag emoji beside each country. Off by default — Windows ships no flag glyphs. */\n  flags?: boolean\n  /**\n   * Digit mask for the national number, e.g. \"## #### ####\". \"#\" is a digit slot and every other\n   * character is a literal separator. Omit for groups of three; pass \"\" for no grouping at all.\n   */\n  format?: string\n  /** Submits the E.164 number with a native form. */\n  name?: string\n  /**\n   * Submits the chosen country alongside it. Worth setting: +1 and +44 are shared by twenty-nine\n   * countries, so the number alone cannot be re-rendered on the country a person actually picked.\n   */\n  countryName?: string\n  /** Accessible name for the country control (default \"Country calling code\"). */\n  countryLabel?: string\n  /** Lands on the number input, so a `<label htmlFor>` names the field. */\n  id?: string\n  className?: string\n  disabled?: boolean\n}\n\n/**\n * A phone number field: a country picker, its calling code shown as a prefix, and a number input\n * that emits one E.164 string.\n *\n * ```tsx\n * const [phone, setPhone] = React.useState(\"\")\n *\n * return (\n *   <>\n *     <Label htmlFor=\"phone\">Mobile number</Label>\n *     <PhoneInput\n *       id=\"phone\"\n *       name=\"phone\"\n *       countryName=\"phone_country\"\n *       value={phone}\n *       onValueChange={setPhone}\n *       defaultCountry=\"JP\"\n *       priority={[\"JP\", \"US\", \"GB\"]}\n *     />\n *   </>\n * )\n * ```\n *\n * What it does and does not claim is the whole design. It assembles `+` + calling code + the digits\n * typed, caps the result at E.164's fifteen digits, and spaces those digits for reading. It does not\n * decide whether the number is real, because that is a per-country rule for 246 countries and the\n * library that knows them is far larger than everything in this registry put together. A field that\n * pretends otherwise fails people in the countries whose rules it got wrong, which is the failure\n * mode worth avoiding.\n *\n * The country is its own state rather than something read back out of the number. Twenty-five\n * countries share +1 and four share +44, so a stored \"+12425550100\" cannot say whether its owner is\n * in the Bahamas or misdialled from Nevada; keeping the country separate — and submitting it with\n * `countryName` — is what lets the field come back up on the country the person actually chose.\n */\nexport const PhoneInput = React.forwardRef<HTMLInputElement, PhoneInputProps>(\n  function PhoneInput(\n    {\n      value,\n      defaultValue,\n      onValueChange,\n      country,\n      defaultCountry = \"US\",\n      onCountryChange,\n      countries,\n      priority,\n      locale,\n      flags = false,\n      format,\n      name,\n      countryName,\n      countryLabel = \"Country calling code\",\n      placeholder = \"Phone number\",\n      id,\n      className,\n      disabled = false,\n      \"aria-label\": ariaLabel,\n      ...props\n    },\n    forwardedRef\n  ) {\n    const valueIsControlled = value !== undefined\n    const countryIsControlled = country !== undefined\n\n    // Read only by the state initialisers below, which run once. Recomputing it on later renders\n    // costs one regex and changes nothing, so it does not need to be a hook.\n    const seed = splitPhoneNumber(valueIsControlled ? value ?? \"\" : defaultValue ?? \"\")\n\n    const [innerCountry, setInnerCountry] = React.useState<string>(() => {\n      const fromSeed = seed.dialCode\n        ? countryForDial(seed.dialCode)\n        : \"\"\n      const start = (countryIsControlled ? country : \"\") || fromSeed || defaultCountry\n      // A narrowed `countries` that leaves out the starting country would otherwise open on a\n      // country the caller excluded, and submit it.\n      if (countries && !countries.includes(start)) {\n        return countries.find((code) => DIAL_CODES[code]) ?? start\n      }\n      return start\n    })\n    const [national, setNational] = React.useState<string>(() => seed.national)\n\n    const activeCountry = countryIsControlled ? country ?? \"\" : innerCountry\n    const dialCode = getDialCode(activeCountry)\n\n    const emitted = toE164(activeCountry, national)\n\n    const generatedId = React.useId()\n    const inputId = id ?? `${generatedId}-number`\n    const prefixId = `${generatedId}-dial`\n\n    const inputRef = React.useRef<HTMLInputElement>(null)\n    React.useImperativeHandle(forwardedRef, () => inputRef.current as HTMLInputElement)\n\n    // Read inside the effects below, so they compare against what is on screen right now rather\n    // than against whatever the closure captured when the prop last changed.\n    const stateRef = React.useRef({ national, emitted, activeCountry })\n    stateRef.current = { national, emitted, activeCountry }\n\n    /**\n     * The last value handed to `onValueChange`, and the previous props, so the effects below can\n     * tell \"the parent changed its mind\" from \"the parent has not re-rendered yet\".\n     *\n     * That distinction is the whole of the controlled-mode problem, and this registry has already\n     * shipped the wrong answer to it once, in `date-input`. What is on screen here — a country and\n     * some digits — is richer than the one string that comes back out, so between a keystroke and\n     * the parent's re-render the prop is genuinely stale, and a field that re-seeds from a stale\n     * prop erases the keystroke that produced it.\n     *\n     * Comparing the prop against its own previous value is what separates the two cases, and it is\n     * deliberately done here rather than left to a dependency array: deps decide when React *may*\n     * skip an effect, not when it must, so a component that is only correct while its effect is\n     * skipped is a component that breaks the first time something re-runs it.\n     */\n    const lastEmittedRef = React.useRef(emitted)\n    const prevValueRef = React.useRef(value)\n    const prevCountryRef = React.useRef(country)\n\n    /** Re-seed from `value`, but only on a change the parent actually made. */\n    React.useEffect(() => {\n      if (!valueIsControlled) return\n      if (value === prevValueRef.current) return\n      prevValueRef.current = value\n      const incoming = value ?? \"\"\n      lastEmittedRef.current = incoming\n      if (incoming === stateRef.current.emitted) return\n      const next = splitPhoneNumber(incoming)\n      const code = next.dialCode || getDialCode(stateRef.current.activeCountry)\n      setNational(next.national.slice(0, Math.max(0, E164_MAX_DIGITS - code.length)))\n      // A number whose calling code is not the one on screen moves the country with it. Leaving the\n      // country alone when it already carries that code is what keeps a Bahamian +1 number from\n      // snapping to the United States on its first round trip.\n      if (next.dialCode && next.dialCode !== getDialCode(stateRef.current.activeCountry)) {\n        const resolved = countryForDial(next.dialCode)\n        if (resolved) {\n          if (!countryIsControlled) setInnerCountry(resolved)\n          onCountryChange?.(resolved)\n        }\n      }\n    })\n\n    /**\n     * The same pull-back for a controlled country. The parent can move the country without touching\n     * the number, and the value has to follow the new calling code — nothing else would tell it.\n     */\n    React.useEffect(() => {\n      if (!countryIsControlled) return\n      if (country === prevCountryRef.current) return\n      prevCountryRef.current = country\n      const next = toE164(country ?? \"\", stateRef.current.national)\n      if (next === lastEmittedRef.current) return\n      lastEmittedRef.current = next\n      onValueChange?.(next)\n    })\n\n    /**\n     * Where the caret goes once React has re-rendered with the reformatted text.\n     *\n     * Counted in digits, not characters, because the separators move: typing the tenth digit of a\n     * number turns \"415 555 013\" into \"415 555 0132\" in one place and \"12 345 678\" into\n     * \"123 456 789\" in another. Restoring \"after the nth digit\" survives both; restoring an offset\n     * does not, and putting the caret back at the end — what a naive masked input does — makes the\n     * field impossible to correct in the middle.\n     */\n    const caretRef = React.useRef<number | null>(null)\n    React.useLayoutEffect(() => {\n      const at = caretRef.current\n      if (at === null) return\n      caretRef.current = null\n      inputRef.current?.setSelectionRange?.(at, at)\n    })\n\n    const display = formatPhoneDigits(national, format)\n\n    /**\n     * The single write path. `setNational` runs whether or not the value is controlled — see the\n     * effect above for why — and the caret is queued in digits before the text is reformatted.\n     */\n    function commit(digits: string, caretDigits: number, nextCountry?: string) {\n      const country_ = nextCountry ?? activeCountry\n      const clipped = digits.slice(0, Math.max(0, E164_MAX_DIGITS - getDialCode(country_).length))\n      setNational(clipped)\n      if (nextCountry && nextCountry !== activeCountry) {\n        if (!countryIsControlled) setInnerCountry(nextCountry)\n        onCountryChange?.(nextCountry)\n      }\n      caretRef.current = caretAfterDigits(\n        formatPhoneDigits(clipped, format),\n        Math.min(caretDigits, clipped.length)\n      )\n      lastEmittedRef.current = toE164(country_, clipped)\n      onValueChange?.(lastEmittedRef.current)\n    }\n\n    function handleChange(event: React.ChangeEvent<HTMLInputElement>) {\n      const raw = event.target.value\n      const caret = event.target.selectionStart ?? raw.length\n      const before = countDigits(raw.slice(0, caret))\n\n      // Someone pasting \"+81 90-1234-5678\" into the number half means the whole number, not a\n      // national one that happens to start with 81. Only a leading `+` says so — anything else is\n      // taken as digits under the country already chosen.\n      if (raw.trim().startsWith(\"+\")) {\n        const parsed = splitPhoneNumber(raw)\n        if (parsed.dialCode) {\n          const resolved =\n            getDialCode(activeCountry) === parsed.dialCode\n              ? activeCountry\n              : countryForDial(parsed.dialCode)\n          commit(parsed.national, parsed.national.length, resolved || undefined)\n          return\n        }\n      }\n\n      const digits = raw.replace(/\\D/g, \"\")\n      commit(digits, before)\n    }\n\n    /**\n     * Backspace and Delete are handled here rather than left to the browser because the separators\n     * are not typed and should not have to be deleted. Without this, backspacing over the space in\n     * \"415 555\" removes it, the reformat puts it straight back, and the key appears to do nothing —\n     * the single most common complaint about masked inputs.\n     */\n    function handleKeyDown(event: React.KeyboardEvent<HTMLInputElement>) {\n      props.onKeyDown?.(event)\n      if (event.defaultPrevented) return\n      if (event.key !== \"Backspace\" && event.key !== \"Delete\") return\n      const el = event.currentTarget\n      const start = el.selectionStart ?? 0\n      // A selection deletes what is selected; the browser handles that correctly and `handleChange`\n      // reformats what is left.\n      if (start !== (el.selectionEnd ?? start)) return\n      const before = countDigits(el.value.slice(0, start))\n      if (event.key === \"Backspace\") {\n        if (before === 0) return\n        event.preventDefault()\n        commit(removeDigitAt(national, before - 1), before - 1)\n      } else {\n        if (before >= national.length) return\n        event.preventDefault()\n        commit(removeDigitAt(national, before), before)\n      }\n    }\n\n    function handleCountryChange(next: string) {\n      if (!countryIsControlled) setInnerCountry(next)\n      onCountryChange?.(next)\n      // The digits stay; only the code in front of them changed. Re-clip in case the new code is\n      // longer and the number no longer fits inside E.164's fifteen.\n      const clipped = national.slice(0, Math.max(0, E164_MAX_DIGITS - getDialCode(next).length))\n      if (clipped !== national) setNational(clipped)\n      lastEmittedRef.current = toE164(next, clipped)\n      onValueChange?.(lastEmittedRef.current)\n    }\n\n    const offered = React.useMemo(() => {\n      const list = countries ?? PHONE_COUNTRIES\n      return list.filter((code) => DIAL_CODES[code])\n    }, [countries])\n\n    return (\n      <div\n        role=\"group\"\n        aria-label={ariaLabel ?? placeholder}\n        // Two columns rather than a narrow country button: the picker this composes is a full\n        // combobox whose panel is as wide as its trigger, so squeezing the trigger down to a flag\n        // and a code would truncate every country name in the list underneath it.\n        className={cn(\"grid grid-cols-[minmax(8rem,1fr)_1fr] items-start gap-2\", className)}\n      >\n        <CountrySelect\n          value={activeCountry}\n          onValueChange={handleCountryChange}\n          countries={offered}\n          priority={priority}\n          locale={locale}\n          flags={flags}\n          disabled={disabled}\n          aria-label={countryLabel}\n          placeholder=\"Country\"\n        />\n\n        <div\n          className={cn(\n            \"flex h-9 w-full items-center rounded-md border border-input bg-transparent text-sm shadow-sm transition-colors\",\n            \"focus-within:outline-none focus-within:ring-1 focus-within:ring-ring\",\n            disabled && \"cursor-not-allowed opacity-50\"\n          )}\n        >\n          {dialCode ? (\n            <span\n              id={prefixId}\n              className=\"select-none whitespace-nowrap pl-3 text-muted-foreground tabular-nums\"\n            >\n              +{dialCode}\n            </span>\n          ) : null}\n          <input\n            {...props}\n            ref={inputRef}\n            id={inputId}\n            // \"tel\" rather than \"number\": a phone number is a string of digits, not a quantity, and\n            // a number input would offer a spinner and drop a leading zero.\n            type=\"tel\"\n            inputMode=\"tel\"\n            autoComplete=\"tel-national\"\n            value={display}\n            onChange={handleChange}\n            onKeyDown={handleKeyDown}\n            disabled={disabled}\n            placeholder={placeholder}\n            // Named by the caller's own `<label htmlFor>` when they passed an `id`; otherwise this\n            // is the only name the field would have, and a placeholder is not one.\n            aria-label={ariaLabel ?? (id ? undefined : placeholder)}\n            // Points at the \"+81\", so the calling code is announced with the field rather than\n            // being a piece of visual context a screen reader never reaches.\n            aria-describedby={\n              dialCode ? [prefixId, props[\"aria-describedby\"]].filter(Boolean).join(\" \") : props[\"aria-describedby\"]\n            }\n            className={cn(\n              \"h-full w-full min-w-0 flex-1 rounded-md bg-transparent px-3 py-1 tabular-nums outline-none\",\n              \"placeholder:text-muted-foreground disabled:cursor-not-allowed\",\n              dialCode && \"pl-1\"\n            )}\n          />\n        </div>\n\n        {name ? <input type=\"hidden\" name={name} value={emitted} /> : null}\n        {countryName ? <input type=\"hidden\" name={countryName} value={activeCountry} /> : null}\n      </div>\n    )\n  }\n)\n\n/**\n * The country to show for a bare calling code: the one `PRIMARY_COUNTRY` names when the code is\n * shared, and otherwise the only country that has it.\n */\nfunction countryForDial(dialCode: string): string {\n  const primary = PRIMARY_COUNTRY[dialCode]\n  if (primary) return primary\n  for (const code of PHONE_COUNTRIES) if (DIAL_CODES[code] === dialCode) return code\n  return \"\"\n}\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}
