intl-tel-input 28.0.9 → 29.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/dist/css/intlTelInput-no-assets.css +20 -11
  2. package/dist/css/intlTelInput-no-assets.min.css +1 -1
  3. package/dist/css/intlTelInput.css +20 -11
  4. package/dist/css/intlTelInput.min.css +1 -1
  5. package/dist/js/data.js +1 -1
  6. package/dist/js/data.min.js +1 -1
  7. package/dist/js/intlTelInput.d.ts +38 -23
  8. package/dist/js/intlTelInput.js +392 -324
  9. package/dist/js/intlTelInput.min.js +2 -2
  10. package/dist/js/intlTelInput.mjs +391 -323
  11. package/dist/js/intlTelInputWithUtils.js +680 -614
  12. package/dist/js/intlTelInputWithUtils.min.js +2 -2
  13. package/dist/js/intlTelInputWithUtils.mjs +679 -613
  14. package/dist/js/{i18n → locale}/ar.js +3 -3
  15. package/dist/js/{i18n → locale}/bg.js +2 -2
  16. package/dist/js/{i18n → locale}/bn.js +2 -2
  17. package/dist/js/{i18n → locale}/bs.js +2 -2
  18. package/dist/js/{i18n → locale}/ca.js +2 -2
  19. package/dist/js/{i18n → locale}/cs.js +2 -2
  20. package/dist/js/{i18n → locale}/da.js +2 -2
  21. package/dist/js/{i18n → locale}/de.js +2 -2
  22. package/dist/js/{i18n → locale}/el.js +2 -2
  23. package/dist/js/{i18n → locale}/es.js +2 -2
  24. package/dist/js/{i18n → locale}/et.js +2 -2
  25. package/dist/js/{i18n → locale}/fa.js +2 -5
  26. package/dist/js/{i18n → locale}/fi.js +2 -2
  27. package/dist/js/locale/fil.js +16 -0
  28. package/dist/js/{i18n → locale}/fr.js +2 -2
  29. package/dist/js/locale/he.js +19 -0
  30. package/dist/js/{i18n → locale}/hi.js +2 -2
  31. package/dist/js/{i18n → locale}/hr.js +2 -2
  32. package/dist/js/{i18n → locale}/hu.js +2 -5
  33. package/dist/js/locale/hy.js +19 -0
  34. package/dist/js/{i18n → locale}/id.js +2 -5
  35. package/dist/js/{i18n → locale}/index.js +9 -0
  36. package/dist/js/locale/is.js +21 -0
  37. package/dist/js/{i18n → locale}/it.js +2 -2
  38. package/dist/js/{i18n → locale}/ja.js +2 -5
  39. package/dist/js/{i18n → locale}/kn.js +2 -2
  40. package/dist/js/{i18n → locale}/ko.js +2 -5
  41. package/dist/js/{i18n → locale}/lt.js +2 -2
  42. package/dist/js/locale/lv.js +24 -0
  43. package/dist/js/locale/mk.js +21 -0
  44. package/dist/js/{i18n → locale}/mr.js +2 -2
  45. package/dist/js/locale/ms.js +16 -0
  46. package/dist/js/{i18n → locale}/nl.js +2 -2
  47. package/dist/js/{i18n → locale}/no.js +2 -2
  48. package/dist/js/{i18n → locale}/pl.js +2 -2
  49. package/dist/js/{i18n → locale}/pt.js +2 -2
  50. package/dist/js/{i18n → locale}/ro.js +4 -4
  51. package/dist/js/{i18n → locale}/ru.js +2 -2
  52. package/dist/js/{i18n → locale}/sk.js +5 -8
  53. package/dist/js/{i18n → locale}/sl.js +2 -2
  54. package/dist/js/{i18n → locale}/sq.js +2 -2
  55. package/dist/js/{i18n → locale}/sr.js +2 -2
  56. package/dist/js/{i18n → locale}/sv.js +2 -5
  57. package/dist/js/locale/sw.js +19 -0
  58. package/dist/js/locale/ta.js +19 -0
  59. package/dist/js/{i18n → locale}/te.js +2 -2
  60. package/dist/js/{i18n → locale}/th.js +2 -5
  61. package/dist/js/{i18n → locale}/tr.js +2 -5
  62. package/dist/js/{i18n → locale}/uk.js +2 -2
  63. package/dist/js/{i18n → locale}/ur.js +2 -2
  64. package/dist/js/{i18n → locale}/uz.js +2 -5
  65. package/dist/js/{i18n → locale}/vi.js +2 -5
  66. package/dist/js/{i18n → locale}/zh-hk.js +2 -5
  67. package/dist/js/{i18n → locale}/zh.js +2 -5
  68. package/dist/js/locale.d.ts +124 -0
  69. package/dist/js/utils.js +51 -51
  70. package/package.json +7 -7
  71. package/dist/js/i18n.d.ts +0 -106
  72. /package/dist/js/{i18n → locale}/en.js +0 -0
  73. /package/dist/js/{i18n → locale}/types.js +0 -0
@@ -1,5 +1,5 @@
1
1
  /*
2
- * International Telephone Input v28.0.9
2
+ * International Telephone Input v29.0.0
3
3
  * git+https://github.com/jackocnr/intl-tel-input.git
4
4
  * Licensed under the MIT license
5
5
  */
@@ -26,9 +26,11 @@ var _factory = (() => {
26
26
  // packages/core/src/js/intlTelInput.ts
27
27
  var intlTelInput_exports = {};
28
28
  __export(intlTelInput_exports, {
29
+ COUNTRY_SELECTOR_MODE: () => COUNTRY_SELECTOR_MODE,
29
30
  Iti: () => Iti,
30
31
  NUMBER_FORMAT: () => NUMBER_FORMAT,
31
32
  NUMBER_TYPE: () => NUMBER_TYPE,
33
+ PLACEHOLDER_POLICY: () => PLACEHOLDER_POLICY,
32
34
  VALIDATION_ERROR: () => VALIDATION_ERROR,
33
35
  default: () => intlTelInput_default
34
36
  });
@@ -1787,8 +1789,8 @@ var _factory = (() => {
1787
1789
 
1788
1790
  // packages/core/src/js/constants.ts
1789
1791
  var EVENTS = {
1790
- OPEN_COUNTRY_DROPDOWN: "open:countrydropdown",
1791
- CLOSE_COUNTRY_DROPDOWN: "close:countrydropdown",
1792
+ OPEN_COUNTRY_SELECTOR: "open:countryselector",
1793
+ CLOSE_COUNTRY_SELECTOR: "close:countryselector",
1792
1794
  COUNTRY_CHANGE: "countrychange",
1793
1795
  INPUT: "input",
1794
1796
  // used for synthetic input trigger
@@ -1802,7 +1804,8 @@ var _factory = (() => {
1802
1804
  FLAG: "iti__flag",
1803
1805
  LOADING: "iti__loading",
1804
1806
  COUNTRY_ITEM: "iti__country",
1805
- HIGHLIGHT: "iti__highlight"
1807
+ HIGHLIGHT: "iti__highlight",
1808
+ STRICT_REJECT_ANIMATION: "iti__strict-reject-animation"
1806
1809
  };
1807
1810
  var KEYS = {
1808
1811
  ARROW_UP: "ArrowUp",
@@ -1834,9 +1837,9 @@ var _factory = (() => {
1834
1837
  var LAYOUT = {
1835
1838
  NARROW_VIEWPORT_WIDTH: 500,
1836
1839
  // keep in sync with .iti__country-list CSS media query
1837
- FALLBACK_SELECTED_WITH_DIAL_WIDTH: 78,
1840
+ FALLBACK_SELECTED_COUNTRY_WITH_DIAL_WIDTH: 78,
1838
1841
  // px width fallback when separateDialCode enabled
1839
- FALLBACK_SELECTED_NO_DIAL_WIDTH: 42,
1842
+ FALLBACK_SELECTED_COUNTRY_NO_DIAL_WIDTH: 42,
1840
1843
  // px width fallback when no separate dial code
1841
1844
  INPUT_PADDING_EXTRA_LEFT: 6,
1842
1845
  // px gap between selected country container and input text
@@ -1864,14 +1867,17 @@ var _factory = (() => {
1864
1867
  DIAL_CODE: "1"
1865
1868
  // +1 United States
1866
1869
  };
1867
- var PLACEHOLDER_MODES = {
1868
- AGGRESSIVE: "aggressive",
1869
- POLITE: "polite",
1870
- OFF: "off"
1871
- };
1872
- var INITIAL_COUNTRY = {
1873
- AUTO: "auto"
1870
+ var PLACEHOLDER_POLICY = {
1871
+ AGGRESSIVE: "AGGRESSIVE",
1872
+ POLITE: "POLITE",
1873
+ OFF: "OFF"
1874
1874
  };
1875
+ var COUNTRY_SELECTOR_MODES = [
1876
+ "OFF",
1877
+ "DROPDOWN",
1878
+ "FULLSCREEN",
1879
+ "AUTO"
1880
+ ];
1875
1881
  var NUMBER_FORMATS = [
1876
1882
  "E164",
1877
1883
  "INTERNATIONAL",
@@ -1904,8 +1910,9 @@ var _factory = (() => {
1904
1910
  var NUMBER_FORMAT = toEnumObject(NUMBER_FORMATS);
1905
1911
  var NUMBER_TYPE = toEnumObject(NUMBER_TYPES);
1906
1912
  var VALIDATION_ERROR = toEnumObject(VALIDATION_ERRORS);
1913
+ var COUNTRY_SELECTOR_MODE = toEnumObject(COUNTRY_SELECTOR_MODES);
1907
1914
  var DATA_KEYS = {
1908
- // e.g. <li data-iso2="us"> for country items in dropdown
1915
+ // e.g. <li data-iso2="us"> for country items in the country list
1909
1916
  ISO2: "iso2",
1910
1917
  DIAL_CODE: "dialCode",
1911
1918
  // e.g. <input data-intl-tel-input-id="0"> on the input element
@@ -1923,7 +1930,7 @@ var _factory = (() => {
1923
1930
  MODAL: "aria-modal"
1924
1931
  };
1925
1932
 
1926
- // packages/core/src/js/i18n/en.ts
1933
+ // packages/core/src/js/locale/en.ts
1927
1934
  var interfaceTranslations = {
1928
1935
  selectedCountryAriaLabel: "Change country for phone number, currently selected ${countryName} (${dialCode})",
1929
1936
  noCountrySelected: "Select country for phone number",
@@ -1946,63 +1953,63 @@ var _factory = (() => {
1946
1953
  // packages/core/src/js/core/options.ts
1947
1954
  var mediaQuery = (q) => typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia(q).matches;
1948
1955
  var isNarrowViewport = () => mediaQuery(`(max-width: ${LAYOUT.NARROW_VIEWPORT_WIDTH}px)`);
1949
- var computeDefaultUseFullscreenPopup = () => {
1956
+ var resolveAutoCountrySelectorMode = () => {
1950
1957
  if (typeof navigator !== "undefined" && typeof window !== "undefined") {
1951
1958
  const isShortViewport = mediaQuery("(max-height: 600px)");
1952
1959
  const isCoarsePointer = mediaQuery("(pointer: coarse)");
1953
- return isNarrowViewport() || isCoarsePointer && isShortViewport;
1960
+ if (isNarrowViewport() || isCoarsePointer && isShortViewport) {
1961
+ return COUNTRY_SELECTOR_MODE.FULLSCREEN;
1962
+ }
1954
1963
  }
1955
- return false;
1964
+ return COUNTRY_SELECTOR_MODE.DROPDOWN;
1956
1965
  };
1957
1966
  var defaults = {
1958
- //* Whether or not to allow the dropdown.
1959
- allowDropdown: true,
1967
+ //* How the country selector is displayed. "DROPDOWN" vs "FULLSCREEN", or "AUTO" to decide itself, or "OFF".
1968
+ countrySelectorMode: COUNTRY_SELECTOR_MODE.AUTO,
1960
1969
  //* The number type to enforce during validation.
1961
1970
  allowedNumberTypes: [NUMBER_TYPE.MOBILE, NUMBER_TYPE.FIXED_LINE],
1962
1971
  //* Whether or not to allow extensions after the main number.
1963
1972
  allowNumberExtensions: false,
1964
1973
  // Allow alphanumeric "phonewords" (e.g. +1 800 FLOWERS) as valid numbers
1965
1974
  allowPhonewords: false,
1966
- //* Add a placeholder in the input with an example number for the selected country.
1967
- autoPlaceholder: PLACEHOLDER_MODES.POLITE,
1968
1975
  //* Add a custom class to the (injected) container element.
1969
1976
  containerClass: "",
1970
1977
  //* Locale for localising country names via Intl.DisplayNames.
1971
1978
  countryNameLocale: "en",
1972
1979
  //* Override individual country names by iso2 code.
1973
1980
  countryNameOverrides: {},
1974
- //* The order of the countries in the dropdown. Defaults to alphabetical.
1981
+ //* The order of the countries in the country list. Defaults to alphabetical.
1975
1982
  countryOrder: null,
1976
- //* Add a country search input at the top of the dropdown.
1983
+ //* Add a country search input at the top of the country selector.
1977
1984
  countrySearch: true,
1978
1985
  //* Modify the auto placeholder.
1979
1986
  customPlaceholder: null,
1980
1987
  //* Always show the dropdown
1981
1988
  dropdownAlwaysOpen: false,
1982
- //* Append menu to specified element.
1983
- dropdownContainer: null,
1989
+ //* Optional DOM element to append the dropdown to (used to escape ancestors with overflow:hidden, or to mount in a custom container). Only consulted in dropdown rendering; ignored when the country selector renders as a fullscreen popup.
1990
+ dropdownParent: null,
1984
1991
  //* Don't display these countries.
1985
1992
  excludeCountries: null,
1986
1993
  //* Fix the dropdown width to the input width (rather than being as wide as the longest country name).
1987
- fixDropdownWidth: true,
1994
+ matchDropdownWidth: true,
1988
1995
  //* Format the number as the user types
1989
1996
  formatAsYouType: true,
1990
- //* Format the input value during initialisation and on setNumber.
1991
- formatOnDisplay: true,
1992
- //* geoIp lookup function.
1993
- geoIpLookup: null,
1994
- //* Inject a hidden input with the name returned from this function, and on submit, populate it with the result of getNumber.
1995
- hiddenInput: null,
1996
- //* Internationalise the core library text e.g. search input placeholder, country names.
1997
- i18n: {},
1997
+ //* Inject hidden inputs with the names returned from this function, and on submit, populate them with the full number and selected country iso2.
1998
+ hiddenInputs: null,
1999
+ //* Translations for the core library UI strings e.g. search input placeholder, country names.
2000
+ uiTranslations: {},
1998
2001
  //* Initial country.
1999
2002
  initialCountry: "",
2003
+ //* Async lookup function used to determine the initial country (e.g. via IP). Ignored if initialCountry is set.
2004
+ initialCountryLookup: null,
2000
2005
  //* A function to load the utils script.
2001
2006
  loadUtils: null,
2002
- //* National vs international formatting for numbers e.g. placeholders and displaying existing numbers.
2003
- nationalMode: false,
2007
+ //* Format used when displaying numbers (placeholder examples and stored values). One of "E164", "INTERNATIONAL", "NATIONAL".
2008
+ numberDisplayFormat: NUMBER_FORMAT.INTERNATIONAL,
2004
2009
  //* Display only these countries.
2005
2010
  onlyCountries: null,
2011
+ //* When to set the placeholder to an example number for the selected country: "POLITE" only when the input has no manually-set placeholder, "AGGRESSIVE" always, "OFF" never.
2012
+ placeholderNumberPolicy: PLACEHOLDER_POLICY.POLITE,
2006
2013
  //* Number type to use for placeholders.
2007
2014
  placeholderNumberType: NUMBER_TYPE.MOBILE,
2008
2015
  //* Add custom classes to the search input element.
@@ -2011,12 +2018,10 @@ var _factory = (() => {
2011
2018
  separateDialCode: true,
2012
2019
  //* When strictMode rejects a key (etc), play a short feedback animation
2013
2020
  strictRejectAnimation: true,
2014
- //* Show flags - for both the selected country, and in the country dropdown
2021
+ //* Show flags - for both the selected country, and in the country list
2015
2022
  showFlags: true,
2016
2023
  //* Only allow certain chars e.g. a plus followed by numeric digits, and cap at max valid length.
2017
- strictMode: true,
2018
- //* Use full screen popup instead of dropdown for country list.
2019
- useFullscreenPopup: computeDefaultUseFullscreenPopup()
2024
+ strictMode: true
2020
2025
  };
2021
2026
  var toString = (val) => JSON.stringify(val);
2022
2027
  var isPlainObject = (val) => Boolean(val) && typeof val === "object" && !Array.isArray(val);
@@ -2028,7 +2033,7 @@ var _factory = (() => {
2028
2033
  const v = val;
2029
2034
  return v.nodeType === 1 && typeof v.tagName === "string" && typeof v.appendChild === "function";
2030
2035
  };
2031
- var placeholderModeSet = new Set(Object.values(PLACEHOLDER_MODES));
2036
+ var placeholderPolicySet = new Set(Object.values(PLACEHOLDER_POLICY));
2032
2037
  var warn = (message) => {
2033
2038
  console.warn(`[intl-tel-input] ${message}`);
2034
2039
  };
@@ -2074,30 +2079,48 @@ var _factory = (() => {
2074
2079
  continue;
2075
2080
  }
2076
2081
  switch (key) {
2077
- case "allowDropdown":
2078
2082
  case "allowNumberExtensions":
2079
2083
  case "allowPhonewords":
2080
2084
  case "countrySearch":
2081
2085
  case "dropdownAlwaysOpen":
2082
- case "fixDropdownWidth":
2086
+ case "matchDropdownWidth":
2083
2087
  case "formatAsYouType":
2084
- case "formatOnDisplay":
2085
- case "nationalMode":
2086
2088
  case "showFlags":
2087
2089
  case "separateDialCode":
2088
2090
  case "strictMode":
2089
2091
  case "strictRejectAnimation":
2090
- case "useFullscreenPopup":
2091
2092
  if (typeof value !== "boolean") {
2092
2093
  warnOption(key, "a boolean", value);
2093
2094
  break;
2094
2095
  }
2095
2096
  validatedOptions[key] = value;
2096
2097
  break;
2097
- case "autoPlaceholder":
2098
- if (typeof value !== "string" || !placeholderModeSet.has(value)) {
2099
- const validModes = Array.from(placeholderModeSet).join(", ");
2100
- warnOption("autoPlaceholder", `one of ${validModes}`, value);
2098
+ case "countrySelectorMode":
2099
+ if (typeof value !== "string" || !COUNTRY_SELECTOR_MODES.includes(value)) {
2100
+ warnOption(
2101
+ "countrySelectorMode",
2102
+ `one of ${COUNTRY_SELECTOR_MODES.map((m) => `"${m}"`).join(", ")}`,
2103
+ value
2104
+ );
2105
+ break;
2106
+ }
2107
+ validatedOptions[key] = value;
2108
+ break;
2109
+ case "numberDisplayFormat":
2110
+ if (typeof value !== "string" || value === NUMBER_FORMAT.RFC3966 || !(value === NUMBER_FORMAT.E164 || value === NUMBER_FORMAT.INTERNATIONAL || value === NUMBER_FORMAT.NATIONAL)) {
2111
+ warnOption(
2112
+ "numberDisplayFormat",
2113
+ 'one of "E164", "INTERNATIONAL", "NATIONAL"',
2114
+ value
2115
+ );
2116
+ break;
2117
+ }
2118
+ validatedOptions[key] = value;
2119
+ break;
2120
+ case "placeholderNumberPolicy":
2121
+ if (typeof value !== "string" || !placeholderPolicySet.has(value)) {
2122
+ const validPolicies = Array.from(placeholderPolicySet).join(", ");
2123
+ warnOption("placeholderNumberPolicy", `one of ${validPolicies}`, value);
2101
2124
  break;
2102
2125
  }
2103
2126
  validatedOptions[key] = value;
@@ -2123,8 +2146,8 @@ var _factory = (() => {
2123
2146
  break;
2124
2147
  }
2125
2148
  case "customPlaceholder":
2126
- case "geoIpLookup":
2127
- case "hiddenInput":
2149
+ case "hiddenInputs":
2150
+ case "initialCountryLookup":
2128
2151
  case "loadUtils":
2129
2152
  if (value !== null && !isFunction(value)) {
2130
2153
  warnOption(key, "a function or null", value);
@@ -2132,9 +2155,9 @@ var _factory = (() => {
2132
2155
  }
2133
2156
  validatedOptions[key] = value;
2134
2157
  break;
2135
- case "dropdownContainer":
2158
+ case "dropdownParent":
2136
2159
  if (value !== null && !isElLike(value)) {
2137
- warnOption("dropdownContainer", "an HTMLElement or null", value);
2160
+ warnOption("dropdownParent", "an HTMLElement or null", value);
2138
2161
  break;
2139
2162
  }
2140
2163
  validatedOptions[key] = value;
@@ -2151,9 +2174,9 @@ var _factory = (() => {
2151
2174
  }
2152
2175
  break;
2153
2176
  }
2154
- case "i18n":
2177
+ case "uiTranslations":
2155
2178
  if (value && !isPlainObject(value)) {
2156
- warnOption("i18n", "an object", value);
2179
+ warnOption("uiTranslations", "an object", value);
2157
2180
  break;
2158
2181
  }
2159
2182
  validatedOptions[key] = value;
@@ -2171,12 +2194,8 @@ var _factory = (() => {
2171
2194
  break;
2172
2195
  }
2173
2196
  const lower = value.toLowerCase();
2174
- if (lower && lower !== INITIAL_COUNTRY.AUTO && !isIso2(lower)) {
2175
- warnOption(
2176
- "initialCountry",
2177
- "a valid iso2 country code or 'auto'",
2178
- value
2179
- );
2197
+ if (lower && !isIso2(lower)) {
2198
+ warnOption("initialCountry", "a valid iso2 country code", value);
2180
2199
  break;
2181
2200
  }
2182
2201
  validatedOptions[key] = value;
@@ -2239,30 +2258,29 @@ var _factory = (() => {
2239
2258
  }
2240
2259
  };
2241
2260
  var applyOptionSideEffects = (o) => {
2261
+ if (o.countrySelectorMode === COUNTRY_SELECTOR_MODE.AUTO) {
2262
+ o.countrySelectorMode = resolveAutoCountrySelectorMode();
2263
+ }
2242
2264
  if (o.dropdownAlwaysOpen) {
2243
- o.useFullscreenPopup = false;
2244
- o.allowDropdown = true;
2265
+ o.countrySelectorMode = COUNTRY_SELECTOR_MODE.DROPDOWN;
2245
2266
  }
2246
- if (o.useFullscreenPopup) {
2247
- o.fixDropdownWidth = false;
2267
+ if (o.countrySelectorMode === COUNTRY_SELECTOR_MODE.FULLSCREEN) {
2268
+ o.matchDropdownWidth = false;
2248
2269
  } else {
2249
2270
  if (isNarrowViewport()) {
2250
- o.fixDropdownWidth = true;
2271
+ o.matchDropdownWidth = true;
2251
2272
  }
2252
2273
  }
2253
2274
  if (o.onlyCountries?.length === 1) {
2254
2275
  o.initialCountry = o.onlyCountries[0];
2255
2276
  }
2256
- if (o.separateDialCode) {
2257
- o.nationalMode = false;
2277
+ if (o.separateDialCode && o.numberDisplayFormat === NUMBER_FORMAT.NATIONAL) {
2278
+ o.numberDisplayFormat = NUMBER_FORMAT.INTERNATIONAL;
2258
2279
  }
2259
- if (o.allowDropdown && !o.showFlags && !o.separateDialCode) {
2260
- o.nationalMode = false;
2280
+ if (o.countrySelectorMode !== COUNTRY_SELECTOR_MODE.OFF && !o.showFlags && !o.separateDialCode && o.numberDisplayFormat === NUMBER_FORMAT.NATIONAL) {
2281
+ o.numberDisplayFormat = NUMBER_FORMAT.INTERNATIONAL;
2261
2282
  }
2262
- if (o.useFullscreenPopup && !o.dropdownContainer) {
2263
- o.dropdownContainer = document.body;
2264
- }
2265
- o.i18n = { ...en_default, ...o.i18n };
2283
+ o.uiTranslations = { ...en_default, ...o.uiTranslations };
2266
2284
  };
2267
2285
 
2268
2286
  // packages/core/src/js/helpers/string.ts
@@ -2473,6 +2491,7 @@ var _factory = (() => {
2473
2491
  };
2474
2492
 
2475
2493
  // packages/core/src/js/core/ui.ts
2494
+ var supportsCssAnchor = typeof CSS !== "undefined" && typeof CSS.supports === "function" && CSS.supports("anchor-name: --x");
2476
2495
  var UI = class _UI {
2477
2496
  // private
2478
2497
  #options;
@@ -2487,8 +2506,8 @@ var _factory = (() => {
2487
2506
  #selectedCountryEl;
2488
2507
  #selectedFlagEl;
2489
2508
  #selectedDialCodeEl;
2490
- #dropdownArrowEl;
2491
- #dropdownContentEl;
2509
+ #arrowEl;
2510
+ #countrySelectorEl;
2492
2511
  #searchIconEl;
2493
2512
  #searchInputEl;
2494
2513
  #searchClearButtonEl;
@@ -2497,11 +2516,11 @@ var _factory = (() => {
2497
2516
  #hiddenInputCountryEl;
2498
2517
  #noResultsMessageEl;
2499
2518
  #searchResultsLiveRegionEl;
2500
- #detachedDropdownEl;
2519
+ #detachedCountrySelectorEl;
2501
2520
  #selectedListItemEl = null;
2502
2521
  #highlightedListItemEl = null;
2503
2522
  #listItemByIso2 = /* @__PURE__ */ new Map();
2504
- #dropdownAbortController = null;
2523
+ #countrySelectorAbortController = null;
2505
2524
  #resizeObserver;
2506
2525
  // public
2507
2526
  telInputEl;
@@ -2526,7 +2545,7 @@ var _factory = (() => {
2526
2545
  );
2527
2546
  }
2528
2547
  }
2529
- //* Generate all of the markup for the core library: the selected country overlay, and the dropdown.
2548
+ //* Generate all of the markup for the core library: the selected country overlay, and the country selector.
2530
2549
  buildMarkup(countries, searchTokens) {
2531
2550
  this.#countries = countries;
2532
2551
  this.#searchTokens = searchTokens;
@@ -2549,13 +2568,13 @@ var _factory = (() => {
2549
2568
  this.ensureDropdownWidthSet();
2550
2569
  }
2551
2570
  #createWrapperAndInsert() {
2552
- const { allowDropdown, showFlags, containerClass, useFullscreenPopup } = this.#options;
2571
+ const { countrySelectorMode, showFlags, containerClass } = this.#options;
2553
2572
  const parentClasses = buildClassNames({
2554
2573
  iti: true,
2555
2574
  "iti--input-container": true,
2556
- "iti--allow-dropdown": allowDropdown,
2575
+ "iti--has-country-selector": countrySelectorMode !== COUNTRY_SELECTOR_MODE.OFF,
2557
2576
  "iti--show-flags": showFlags,
2558
- "iti--inline-dropdown": !useFullscreenPopup,
2577
+ "iti--inline-country-selector": countrySelectorMode !== COUNTRY_SELECTOR_MODE.FULLSCREEN,
2559
2578
  [containerClass]: Boolean(containerClass)
2560
2579
  });
2561
2580
  const wrapper = createEl("div", { class: parentClasses });
@@ -2566,8 +2585,9 @@ var _factory = (() => {
2566
2585
  return wrapper;
2567
2586
  }
2568
2587
  #buildCountryContainer(wrapper) {
2569
- const { allowDropdown, separateDialCode, showFlags } = this.#options;
2570
- if (!allowDropdown && !showFlags && !separateDialCode) {
2588
+ const { countrySelectorMode, separateDialCode, showFlags } = this.#options;
2589
+ const enableCountrySelector = countrySelectorMode !== COUNTRY_SELECTOR_MODE.OFF;
2590
+ if (!enableCountrySelector && !showFlags && !separateDialCode) {
2571
2591
  return;
2572
2592
  }
2573
2593
  this.#countryContainerEl = createEl(
@@ -2576,16 +2596,16 @@ var _factory = (() => {
2576
2596
  { class: `iti__country-container ${CLASSES.V_HIDE}` },
2577
2597
  wrapper
2578
2598
  );
2579
- if (allowDropdown) {
2599
+ if (enableCountrySelector) {
2580
2600
  this.#selectedCountryEl = createEl(
2581
2601
  "button",
2582
2602
  {
2583
2603
  type: "button",
2584
2604
  class: "iti__selected-country",
2585
2605
  [ARIA.EXPANDED]: "false",
2586
- [ARIA.LABEL]: this.#options.i18n.noCountrySelected,
2606
+ [ARIA.LABEL]: this.#options.uiTranslations.noCountrySelected,
2587
2607
  [ARIA.HASPOPUP]: "dialog",
2588
- [ARIA.CONTROLS]: `iti-${this.#id}__dropdown-content`
2608
+ [ARIA.CONTROLS]: `iti-${this.#id}__country-selector`
2589
2609
  },
2590
2610
  this.#countryContainerEl
2591
2611
  );
@@ -2609,8 +2629,8 @@ var _factory = (() => {
2609
2629
  { class: CLASSES.FLAG },
2610
2630
  selectedCountryPrimary
2611
2631
  );
2612
- if (allowDropdown) {
2613
- this.#dropdownArrowEl = createEl(
2632
+ if (enableCountrySelector) {
2633
+ this.#arrowEl = createEl(
2614
2634
  "div",
2615
2635
  { class: "iti__arrow", [ARIA.HIDDEN]: "true" },
2616
2636
  selectedCountryPrimary
@@ -2623,38 +2643,39 @@ var _factory = (() => {
2623
2643
  this.#selectedCountryEl
2624
2644
  );
2625
2645
  }
2626
- if (allowDropdown) {
2627
- this.#buildDropdownContent();
2646
+ if (enableCountrySelector) {
2647
+ this.#buildCountrySelector();
2628
2648
  }
2629
2649
  }
2630
2650
  ensureDropdownWidthSet() {
2631
- const { fixDropdownWidth, allowDropdown } = this.#options;
2632
- if (!allowDropdown || !fixDropdownWidth || this.#dropdownContentEl.style.width) {
2651
+ const { matchDropdownWidth, countrySelectorMode } = this.#options;
2652
+ if (countrySelectorMode === COUNTRY_SELECTOR_MODE.OFF || !matchDropdownWidth || this.#countrySelectorEl.style.width) {
2633
2653
  return;
2634
2654
  }
2635
2655
  const inputWidth = this.telInputEl.offsetWidth;
2636
2656
  if (inputWidth > 0) {
2637
- this.#dropdownContentEl.style.width = `${inputWidth}px`;
2657
+ this.#countrySelectorEl.style.width = `${inputWidth}px`;
2638
2658
  }
2639
2659
  }
2640
- #buildDropdownContent() {
2660
+ #buildCountrySelector() {
2641
2661
  const {
2642
- fixDropdownWidth,
2643
- useFullscreenPopup,
2662
+ matchDropdownWidth,
2663
+ countrySelectorMode,
2644
2664
  countrySearch,
2645
- i18n,
2646
- dropdownContainer,
2665
+ uiTranslations,
2647
2666
  containerClass
2648
2667
  } = this.#options;
2649
- const extraClasses = fixDropdownWidth ? "" : "iti--flexible-dropdown-width";
2650
- this.#dropdownContentEl = createEl("div", {
2651
- id: `iti-${this.#id}__dropdown-content`,
2652
- class: `iti__dropdown-content ${CLASSES.HIDE} ${extraClasses}`,
2668
+ const isFullscreen = countrySelectorMode === COUNTRY_SELECTOR_MODE.FULLSCREEN;
2669
+ const detachedParent = this.#getDetachedParent();
2670
+ const extraClasses = matchDropdownWidth ? "" : "iti--flexible-dropdown-width";
2671
+ this.#countrySelectorEl = createEl("div", {
2672
+ id: `iti-${this.#id}__country-selector`,
2673
+ class: `iti__country-selector ${CLASSES.HIDE} ${extraClasses}`,
2653
2674
  role: "dialog",
2654
2675
  [ARIA.MODAL]: "true"
2655
2676
  });
2656
2677
  if (this.#isRTL) {
2657
- this.#dropdownContentEl.setAttribute("dir", "rtl");
2678
+ this.#countrySelectorEl.setAttribute("dir", "rtl");
2658
2679
  }
2659
2680
  if (countrySearch) {
2660
2681
  this.#buildSearchUI();
@@ -2665,40 +2686,54 @@ var _factory = (() => {
2665
2686
  class: "iti__country-list",
2666
2687
  id: `iti-${this.#id}__country-listbox`,
2667
2688
  role: "listbox",
2668
- [ARIA.LABEL]: i18n.countryListAriaLabel
2689
+ [ARIA.LABEL]: uiTranslations.countryListAriaLabel
2669
2690
  },
2670
- this.#dropdownContentEl
2691
+ this.#countrySelectorEl
2671
2692
  );
2672
2693
  this.#appendListItems();
2673
2694
  if (countrySearch) {
2674
2695
  this.#updateSearchResultsA11yText();
2675
2696
  }
2676
- if (!useFullscreenPopup) {
2697
+ if (!isFullscreen) {
2677
2698
  this.#inlineDropdownHeight = this.#getHiddenInlineDropdownHeight();
2678
2699
  if (countrySearch) {
2679
- this.#dropdownContentEl.style.height = `${this.#inlineDropdownHeight}px`;
2700
+ this.#countrySelectorEl.style.height = `${this.#inlineDropdownHeight}px`;
2680
2701
  }
2681
2702
  }
2682
- if (dropdownContainer) {
2683
- const dropdownClasses = buildClassNames({
2703
+ if (detachedParent) {
2704
+ const wrapperClasses = buildClassNames({
2684
2705
  iti: true,
2685
- "iti--container": true,
2686
- "iti--fullscreen-popup": useFullscreenPopup,
2687
- "iti--inline-dropdown": !useFullscreenPopup,
2706
+ "iti--detached-country-selector": true,
2707
+ "iti--fullscreen-popup": isFullscreen,
2708
+ "iti--inline-country-selector": !isFullscreen,
2688
2709
  [containerClass]: Boolean(containerClass)
2689
2710
  });
2690
- this.#detachedDropdownEl = createEl("div", { class: dropdownClasses });
2691
- this.#detachedDropdownEl.appendChild(this.#dropdownContentEl);
2711
+ this.#detachedCountrySelectorEl = createEl("div", { class: wrapperClasses });
2712
+ this.#detachedCountrySelectorEl.appendChild(this.#countrySelectorEl);
2713
+ if (!isFullscreen) {
2714
+ this.#setupCssAnchorPositioning();
2715
+ }
2692
2716
  } else {
2693
- this.#countryContainerEl.appendChild(this.#dropdownContentEl);
2717
+ this.#countryContainerEl.appendChild(this.#countrySelectorEl);
2694
2718
  }
2695
2719
  }
2720
+ //* Resolve the DOM element to attach the country selector to. Fullscreen always uses document.body; dropdown uses the consumer-supplied dropdownParent (if any); otherwise the country selector renders inline within the input wrapper (no detached element).
2721
+ #getDetachedParent() {
2722
+ const { countrySelectorMode, dropdownParent } = this.#options;
2723
+ if (countrySelectorMode === COUNTRY_SELECTOR_MODE.FULLSCREEN) {
2724
+ return document.body;
2725
+ }
2726
+ if (countrySelectorMode === COUNTRY_SELECTOR_MODE.DROPDOWN) {
2727
+ return dropdownParent;
2728
+ }
2729
+ return null;
2730
+ }
2696
2731
  #buildSearchUI() {
2697
- const { i18n, searchInputClass } = this.#options;
2732
+ const { uiTranslations, searchInputClass } = this.#options;
2698
2733
  const searchWrapper = createEl(
2699
2734
  "div",
2700
2735
  { class: "iti__search-input-wrapper" },
2701
- this.#dropdownContentEl
2736
+ this.#countrySelectorEl
2702
2737
  );
2703
2738
  this.#searchIconEl = createEl(
2704
2739
  "span",
@@ -2716,11 +2751,11 @@ var _factory = (() => {
2716
2751
  // Chrome says inputs need either a name or an id
2717
2752
  type: "search",
2718
2753
  class: `iti__search-input ${searchInputClass}`,
2719
- placeholder: i18n.searchPlaceholder,
2754
+ placeholder: uiTranslations.searchPlaceholder,
2720
2755
  // role=combobox + aria-autocomplete=list + aria-activedescendant allows maintaining focus on the search input while allowing users to navigate search results with up/down keyboard keys
2721
2756
  role: "combobox",
2722
2757
  [ARIA.EXPANDED]: "true",
2723
- [ARIA.LABEL]: i18n.searchPlaceholder,
2758
+ [ARIA.LABEL]: uiTranslations.searchPlaceholder,
2724
2759
  [ARIA.CONTROLS]: `iti-${this.#id}__country-listbox`,
2725
2760
  [ARIA.AUTOCOMPLETE]: "list",
2726
2761
  autocomplete: "off"
@@ -2732,7 +2767,7 @@ var _factory = (() => {
2732
2767
  {
2733
2768
  type: "button",
2734
2769
  class: `iti__search-clear ${CLASSES.HIDE}`,
2735
- [ARIA.LABEL]: i18n.clearSearchAriaLabel,
2770
+ [ARIA.LABEL]: uiTranslations.clearSearchAriaLabel,
2736
2771
  tabindex: "-1"
2737
2772
  },
2738
2773
  searchWrapper
@@ -2741,7 +2776,7 @@ var _factory = (() => {
2741
2776
  this.#searchResultsLiveRegionEl = createEl(
2742
2777
  "span",
2743
2778
  { class: "iti__a11y-text" },
2744
- this.#dropdownContentEl
2779
+ this.#countrySelectorEl
2745
2780
  );
2746
2781
  this.#noResultsMessageEl = createEl(
2747
2782
  "div",
@@ -2750,9 +2785,9 @@ var _factory = (() => {
2750
2785
  [ARIA.HIDDEN]: "true"
2751
2786
  // all a11y messaging happens in this.#searchResultsLiveRegionEl
2752
2787
  },
2753
- this.#dropdownContentEl
2788
+ this.#countrySelectorEl
2754
2789
  );
2755
- this.#noResultsMessageEl.textContent = i18n.searchEmptyState ?? null;
2790
+ this.#noResultsMessageEl.textContent = uiTranslations.searchEmptyState ?? null;
2756
2791
  }
2757
2792
  #updateInputPaddingAndReveal() {
2758
2793
  if (!this.#countryContainerEl) {
@@ -2762,12 +2797,12 @@ var _factory = (() => {
2762
2797
  this.#countryContainerEl.classList.remove(CLASSES.V_HIDE);
2763
2798
  }
2764
2799
  #buildHiddenInputs(wrapper) {
2765
- const { hiddenInput } = this.#options;
2766
- if (!hiddenInput) {
2800
+ const { hiddenInputs } = this.#options;
2801
+ if (!hiddenInputs) {
2767
2802
  return;
2768
2803
  }
2769
2804
  const telInputName = this.telInputEl.getAttribute("name") || "";
2770
- const names = hiddenInput(telInputName);
2805
+ const names = hiddenInputs(telInputName);
2771
2806
  if (names.phone) {
2772
2807
  const existingInput = this.telInputEl.form?.querySelector(
2773
2808
  `input[name="${names.phone}"]`
@@ -2832,7 +2867,7 @@ var _factory = (() => {
2832
2867
  //* Update the input padding to make space for (1) the selected country/globe, (2) the arrow, and (3) the separate dial code, all of which are optional, hence handling this in the JS rather than CSS.
2833
2868
  #updateInputPadding() {
2834
2869
  if (this.#selectedCountryEl) {
2835
- const fallbackWidth = this.#options.separateDialCode ? LAYOUT.FALLBACK_SELECTED_WITH_DIAL_WIDTH : LAYOUT.FALLBACK_SELECTED_NO_DIAL_WIDTH;
2870
+ const fallbackWidth = this.#options.separateDialCode ? LAYOUT.FALLBACK_SELECTED_COUNTRY_WITH_DIAL_WIDTH : LAYOUT.FALLBACK_SELECTED_COUNTRY_NO_DIAL_WIDTH;
2836
2871
  const selectedCountryWidth = this.#selectedCountryEl.offsetWidth || this.#getHiddenSelectedCountryWidth() || fallbackWidth;
2837
2872
  const inputPadding = selectedCountryWidth + LAYOUT.INPUT_PADDING_EXTRA_LEFT;
2838
2873
  this.telInputEl.style.paddingLeft = `${inputPadding}px`;
@@ -2886,24 +2921,24 @@ var _factory = (() => {
2886
2921
  // Get the dropdown height (before it is added to the DOM)
2887
2922
  #getHiddenInlineDropdownHeight() {
2888
2923
  const body = _UI.#getBody();
2889
- this.#dropdownContentEl.classList.remove(CLASSES.HIDE);
2924
+ this.#countrySelectorEl.classList.remove(CLASSES.HIDE);
2890
2925
  const tempContainer = createEl("div", {
2891
- class: "iti iti--inline-dropdown"
2926
+ class: "iti iti--inline-country-selector"
2892
2927
  });
2893
- tempContainer.appendChild(this.#dropdownContentEl);
2928
+ tempContainer.appendChild(this.#countrySelectorEl);
2894
2929
  tempContainer.style.visibility = "hidden";
2895
2930
  body.appendChild(tempContainer);
2896
- const height = this.#dropdownContentEl.offsetHeight;
2931
+ const height = this.#countrySelectorEl.offsetHeight;
2897
2932
  body.removeChild(tempContainer);
2898
2933
  tempContainer.style.visibility = "";
2899
- this.#dropdownContentEl.classList.add(CLASSES.HIDE);
2934
+ this.#countrySelectorEl.classList.add(CLASSES.HIDE);
2900
2935
  return height > 0 ? height : LAYOUT.FALLBACK_DROPDOWN_HEIGHT;
2901
2936
  }
2902
2937
  //* Update search results text (for a11y).
2903
2938
  #updateSearchResultsA11yText() {
2904
- const { i18n } = this.#options;
2939
+ const { uiTranslations } = this.#options;
2905
2940
  const count = this.#countryListEl.childElementCount;
2906
- this.#searchResultsLiveRegionEl.textContent = i18n.searchSummaryAria(count);
2941
+ this.#searchResultsLiveRegionEl.textContent = uiTranslations.searchSummaryAria(count);
2907
2942
  }
2908
2943
  //* Country search: Filter the countries according to the search query.
2909
2944
  #filterCountriesByQuery(query) {
@@ -2921,8 +2956,8 @@ var _factory = (() => {
2921
2956
  this.#showFilteredCountries(matchedCountries);
2922
2957
  }
2923
2958
  //* Pre-fill the search input with "+" and show all countries
2924
- //* (used when user types "+" in the phone input to open the dropdown).
2925
- //* Explicitly focus the search input (openDropdown skips this when
2959
+ //* (used when user types "+" in the phone input to open the country selector).
2960
+ //* Explicitly focus the search input (openCountrySelector skips this when
2926
2961
  //* dropdownAlwaysOpen, but here we need focus to redirect subsequent keystrokes).
2927
2962
  prefillSearchWithPlus() {
2928
2963
  this.#searchInputEl.value = "+";
@@ -3005,15 +3040,15 @@ var _factory = (() => {
3005
3040
  { signal }
3006
3041
  );
3007
3042
  }
3008
- //* Wire up triggers that open/close the dropdown: label click (focus input or swallow repeat click),
3043
+ //* Wire up triggers that open/close the country selector: label click (focus input or swallow repeat click),
3009
3044
  //* selected-country click (open), and keydown on countryContainer (open on arrow/space/enter, close on tab).
3010
- bindAllInitialDropdownListeners(signal, onOpen, onClose) {
3045
+ bindAllInitialCountrySelectorListeners(signal, onOpen, onClose) {
3011
3046
  const label = this.telInputEl.closest("label");
3012
3047
  if (label) {
3013
3048
  label.addEventListener(
3014
3049
  "click",
3015
3050
  (e) => {
3016
- if (!this.isDropdownOpen()) {
3051
+ if (!this.isCountrySelectorOpen()) {
3017
3052
  this.telInputEl.focus();
3018
3053
  } else {
3019
3054
  e.preventDefault();
@@ -3025,7 +3060,7 @@ var _factory = (() => {
3025
3060
  this.#selectedCountryEl.addEventListener(
3026
3061
  "click",
3027
3062
  () => {
3028
- if (!this.isDropdownOpen() && !this.telInputEl.disabled && !this.telInputEl.readOnly) {
3063
+ if (!this.isCountrySelectorOpen() && !this.telInputEl.disabled && !this.telInputEl.readOnly) {
3029
3064
  onOpen();
3030
3065
  }
3031
3066
  },
@@ -3040,7 +3075,7 @@ var _factory = (() => {
3040
3075
  KEYS.SPACE,
3041
3076
  KEYS.ENTER
3042
3077
  ];
3043
- if (!this.isDropdownOpen() && openKeys.includes(e.key)) {
3078
+ if (!this.isCountrySelectorOpen() && openKeys.includes(e.key)) {
3044
3079
  e.preventDefault();
3045
3080
  e.stopPropagation();
3046
3081
  onOpen();
@@ -3052,24 +3087,24 @@ var _factory = (() => {
3052
3087
  { signal }
3053
3088
  );
3054
3089
  }
3055
- //* Open the dropdown: create a fresh AbortController, do the DOM work, and wire up all
3056
- //* dropdown-open listeners (which invoke the caller's onSelect / onClose callbacks).
3057
- openDropdown(onSelect, onClose) {
3058
- const { countrySearch, dropdownAlwaysOpen, dropdownContainer } = this.#options;
3059
- this.#dropdownAbortController = new AbortController();
3090
+ //* Open the country selector: create a fresh AbortController, do the DOM work, and wire up all
3091
+ //* open-state listeners (which invoke the caller's onSelect / onClose callbacks).
3092
+ openCountrySelector(onSelect, onClose) {
3093
+ const { countrySearch, dropdownAlwaysOpen } = this.#options;
3094
+ this.#countrySelectorAbortController = new AbortController();
3060
3095
  this.ensureDropdownWidthSet();
3061
- if (dropdownContainer) {
3062
- this.#injectAndPositionDetachedDropdown();
3096
+ if (this.#detachedCountrySelectorEl) {
3097
+ this.#injectAndPositionDetachedCountrySelector();
3063
3098
  } else {
3064
- const positionBelow = this.#shouldPositionInlineDropdownBelowInput();
3099
+ const positionBelow = this.#shouldPositionDropdownBelowInput();
3065
3100
  const distance = this.telInputEl.offsetHeight + LAYOUT.DROPDOWN_MARGIN;
3066
3101
  if (positionBelow) {
3067
- this.#dropdownContentEl.style.top = `${distance}px`;
3102
+ this.#countrySelectorEl.style.top = `${distance}px`;
3068
3103
  } else {
3069
- this.#dropdownContentEl.style.bottom = `${distance}px`;
3104
+ this.#countrySelectorEl.style.bottom = `${distance}px`;
3070
3105
  }
3071
3106
  }
3072
- this.#dropdownContentEl.classList.remove(CLASSES.HIDE);
3107
+ this.#countrySelectorEl.classList.remove(CLASSES.HIDE);
3073
3108
  this.#selectedCountryEl.setAttribute(ARIA.EXPANDED, "true");
3074
3109
  const itemToHighlight = this.#selectedListItemEl ?? this.#countryListEl.firstElementChild;
3075
3110
  if (itemToHighlight) {
@@ -3078,7 +3113,7 @@ var _factory = (() => {
3078
3113
  if (countrySearch && !dropdownAlwaysOpen) {
3079
3114
  this.#searchInputEl.focus();
3080
3115
  }
3081
- if (this.#options.useFullscreenPopup && this.#detachedDropdownEl && window.visualViewport) {
3116
+ if (this.#options.countrySelectorMode === COUNTRY_SELECTOR_MODE.FULLSCREEN && this.#detachedCountrySelectorEl && window.visualViewport) {
3082
3117
  window.visualViewport.addEventListener(
3083
3118
  "resize",
3084
3119
  () => {
@@ -3087,29 +3122,29 @@ var _factory = (() => {
3087
3122
  this.#scrollCountryListToItem(this.#highlightedListItemEl);
3088
3123
  }
3089
3124
  },
3090
- { signal: this.#dropdownAbortController.signal }
3125
+ { signal: this.#countrySelectorAbortController.signal }
3091
3126
  );
3092
3127
  }
3093
- this.#dropdownArrowEl.classList.add(CLASSES.ARROW_UP);
3094
- this.#bindDropdownOpenListeners(onSelect, onClose);
3128
+ this.#arrowEl.classList.add(CLASSES.ARROW_UP);
3129
+ this.#bindCountrySelectorOpenListeners(onSelect, onClose);
3095
3130
  }
3096
- //* Wire up all listeners needed while the dropdown is open: list-item hover (highlight),
3131
+ //* Wire up all listeners needed while the country selector is open: list-item hover (highlight),
3097
3132
  //* list-item click & enter key (select), click-off & escape (close), search input (filter),
3098
- //* (when countrySearch disabled) typed-char hidden search, and (when dropdown is in an external
3099
- //* container) close on window scroll.
3100
- #bindDropdownOpenListeners(onSelect, onClose) {
3101
- const signal = this.#dropdownAbortController.signal;
3133
+ //* (when countrySearch disabled) typed-char hidden search, and (when the country selector is in an
3134
+ //* external container) update (fixed) position on scroll/resize.
3135
+ #bindCountrySelectorOpenListeners(onSelect, onClose) {
3136
+ const signal = this.#countrySelectorAbortController.signal;
3102
3137
  this.#bindListItemHover(signal);
3103
3138
  this.#bindListItemClick(signal, onSelect);
3104
3139
  if (!this.#options.dropdownAlwaysOpen) {
3105
3140
  this.#bindOutsideClickToClose(signal, onClose);
3106
3141
  }
3107
- this.#bindDropdownKeydownListener(signal, onSelect, onClose);
3142
+ this.#bindCountrySelectorKeydownListener(signal, onSelect, onClose);
3108
3143
  if (this.#options.countrySearch) {
3109
3144
  this.#bindSearchInputListener(signal);
3110
3145
  }
3111
- if (!this.#options.useFullscreenPopup && this.#options.dropdownContainer) {
3112
- window.addEventListener("scroll", onClose, { signal });
3146
+ if (this.#options.countrySelectorMode === COUNTRY_SELECTOR_MODE.DROPDOWN && this.#options.dropdownParent && !supportsCssAnchor) {
3147
+ document.addEventListener("scroll", onClose, { signal, capture: true, passive: true });
3113
3148
  }
3114
3149
  }
3115
3150
  //* When mouse over a list item, just highlight that one (so if they hit "enter" we know which to select).
@@ -3142,13 +3177,13 @@ var _factory = (() => {
3142
3177
  { signal }
3143
3178
  );
3144
3179
  }
3145
- //* Invoke onClickOff when the user clicks anywhere outside the dropdown.
3180
+ //* Invoke onClickOff when the user clicks anywhere outside the country selector.
3146
3181
  #bindOutsideClickToClose(signal, onClickOff) {
3147
3182
  setTimeout(() => {
3148
3183
  document.documentElement.addEventListener(
3149
3184
  "click",
3150
3185
  (e) => {
3151
- if (!this.#dropdownContentEl.contains(e.target)) {
3186
+ if (!this.#countrySelectorEl.contains(e.target)) {
3152
3187
  onClickOff();
3153
3188
  }
3154
3189
  },
@@ -3156,11 +3191,10 @@ var _factory = (() => {
3156
3191
  );
3157
3192
  }, 0);
3158
3193
  }
3159
- //* Keyboard navigation while the dropdown is open: arrow keys navigate, hidden-search keys filter,
3160
- //* and enter/escape invoke the caller's callbacks (which handle country selection / dropdown close).
3161
- //* Listens on document because key events go there when no input has focus.
3194
+ //* Keyboard navigation while the country selector is open: arrow keys navigate, hidden-search keys filter,
3195
+ //* and enter/escape invoke the caller's callbacks (which handle country selection / close).
3162
3196
  //* Uses keydown rather than keypress so non-char keys (arrow, esc) fire and so holding a key repeats.
3163
- #bindDropdownKeydownListener(signal, onEnter, onEscape) {
3197
+ #bindCountrySelectorKeydownListener(signal, onEnter, onEscape) {
3164
3198
  let query = "";
3165
3199
  let queryTimer = null;
3166
3200
  const handleKeydown = (e) => {
@@ -3182,7 +3216,7 @@ var _factory = (() => {
3182
3216
  this.#selectedCountryEl.focus();
3183
3217
  }
3184
3218
  }
3185
- if (!this.#options.countrySearch && e.target !== this.telInputEl && REGEX.HIDDEN_SEARCH_CHAR.test(e.key)) {
3219
+ if (!this.#options.countrySearch && REGEX.HIDDEN_SEARCH_CHAR.test(e.key)) {
3186
3220
  e.stopPropagation();
3187
3221
  if (queryTimer) {
3188
3222
  clearTimeout(queryTimer);
@@ -3194,7 +3228,8 @@ var _factory = (() => {
3194
3228
  }, TIMINGS.HIDDEN_SEARCH_RESET_MS);
3195
3229
  }
3196
3230
  };
3197
- document.addEventListener("keydown", handleKeydown, { signal });
3231
+ this.#selectedCountryEl?.addEventListener("keydown", handleKeydown, { signal });
3232
+ this.#countrySelectorEl?.addEventListener("keydown", handleKeydown, { signal });
3198
3233
  }
3199
3234
  //* Wire up country search input listener: typing filters the list, the clear button resets it.
3200
3235
  #bindSearchInputListener(signal) {
@@ -3231,7 +3266,7 @@ var _factory = (() => {
3231
3266
  this.#highlightListItem(next);
3232
3267
  }
3233
3268
  }
3234
- // Update the selected list item in the dropdown
3269
+ // Update the selected list item in the country list
3235
3270
  #updateSelectedListItem(iso2) {
3236
3271
  if (this.#selectedListItemEl && this.#selectedListItemEl.dataset[DATA_KEYS.ISO2] !== iso2) {
3237
3272
  this.#selectedListItemEl.setAttribute(ARIA.SELECTED, "false");
@@ -3282,12 +3317,12 @@ var _factory = (() => {
3282
3317
  this.#countryListEl.scrollTop = 0;
3283
3318
  this.#updateSearchResultsA11yText();
3284
3319
  }
3285
- // UI: Close the dropdown (DOM + abort dropdown-scoped listeners).
3286
- closeDropdown() {
3287
- const { countrySearch, dropdownContainer } = this.#options;
3288
- this.#dropdownAbortController.abort();
3289
- this.#dropdownAbortController = null;
3290
- this.#dropdownContentEl.classList.add(CLASSES.HIDE);
3320
+ // UI: Close the country selector (DOM + abort scoped listeners).
3321
+ closeCountrySelector() {
3322
+ const { countrySearch } = this.#options;
3323
+ this.#countrySelectorAbortController.abort();
3324
+ this.#countrySelectorAbortController = null;
3325
+ this.#countrySelectorEl.classList.add(CLASSES.HIDE);
3291
3326
  this.#selectedCountryEl.setAttribute(ARIA.EXPANDED, "false");
3292
3327
  if (countrySearch) {
3293
3328
  this.#searchInputEl.removeAttribute(ARIA.ACTIVE_DESCENDANT);
@@ -3298,19 +3333,19 @@ var _factory = (() => {
3298
3333
  this.#highlightedListItemEl = null;
3299
3334
  }
3300
3335
  }
3301
- this.#dropdownArrowEl.classList.remove(CLASSES.ARROW_UP);
3302
- if (dropdownContainer) {
3303
- this.#detachedDropdownEl.remove();
3304
- this.#detachedDropdownEl.style.top = "";
3305
- this.#detachedDropdownEl.style.bottom = "";
3306
- this.#detachedDropdownEl.style.paddingLeft = "";
3307
- this.#detachedDropdownEl.style.paddingRight = "";
3336
+ this.#arrowEl.classList.remove(CLASSES.ARROW_UP);
3337
+ if (this.#detachedCountrySelectorEl) {
3338
+ this.#detachedCountrySelectorEl.remove();
3339
+ this.#detachedCountrySelectorEl.style.top = "";
3340
+ this.#detachedCountrySelectorEl.style.bottom = "";
3341
+ this.#detachedCountrySelectorEl.style.paddingLeft = "";
3342
+ this.#detachedCountrySelectorEl.style.paddingRight = "";
3308
3343
  } else {
3309
- this.#dropdownContentEl.style.top = "";
3310
- this.#dropdownContentEl.style.bottom = "";
3344
+ this.#countrySelectorEl.style.top = "";
3345
+ this.#countrySelectorEl.style.bottom = "";
3311
3346
  }
3312
3347
  }
3313
- #shouldPositionInlineDropdownBelowInput() {
3348
+ #shouldPositionDropdownBelowInput() {
3314
3349
  if (this.#options.dropdownAlwaysOpen) {
3315
3350
  return true;
3316
3351
  }
@@ -3319,50 +3354,83 @@ var _factory = (() => {
3319
3354
  const spaceBelow = window.innerHeight - inputPos.bottom;
3320
3355
  return spaceBelow >= this.#inlineDropdownHeight || spaceBelow >= spaceAbove;
3321
3356
  }
3322
- // inject dropdown into container and apply positioning styles
3323
- #injectAndPositionDetachedDropdown() {
3324
- const { dropdownContainer, useFullscreenPopup } = this.#options;
3325
- if (useFullscreenPopup) {
3357
+ // inject the country selector into its detached wrapper and apply positioning styles
3358
+ #injectAndPositionDetachedCountrySelector() {
3359
+ const isFullscreen = this.#options.countrySelectorMode === COUNTRY_SELECTOR_MODE.FULLSCREEN;
3360
+ const detachedParent = this.#getDetachedParent();
3361
+ if (isFullscreen) {
3326
3362
  if (window.innerWidth >= LAYOUT.NARROW_VIEWPORT_WIDTH) {
3327
3363
  const inputPos = this.telInputEl.getBoundingClientRect();
3328
- this.#detachedDropdownEl.style.paddingLeft = `${inputPos.left}px`;
3329
- this.#detachedDropdownEl.style.paddingRight = `${window.innerWidth - inputPos.right}px`;
3364
+ this.#detachedCountrySelectorEl.style.paddingLeft = `${inputPos.left}px`;
3365
+ this.#detachedCountrySelectorEl.style.paddingRight = `${window.innerWidth - inputPos.right}px`;
3330
3366
  }
3331
- } else {
3367
+ } else if (!supportsCssAnchor) {
3332
3368
  const inputPos = this.telInputEl.getBoundingClientRect();
3333
- this.#detachedDropdownEl.style.left = `${inputPos.left}px`;
3334
- const positionBelow = this.#shouldPositionInlineDropdownBelowInput();
3335
- if (positionBelow) {
3336
- this.#detachedDropdownEl.style.top = `${inputPos.bottom + LAYOUT.DROPDOWN_MARGIN}px`;
3369
+ this.#detachedCountrySelectorEl.style.left = `${inputPos.left}px`;
3370
+ if (this.#shouldPositionDropdownBelowInput()) {
3371
+ this.#detachedCountrySelectorEl.style.top = `${inputPos.bottom + LAYOUT.DROPDOWN_MARGIN}px`;
3337
3372
  } else {
3338
- this.#detachedDropdownEl.style.top = "unset";
3339
- this.#detachedDropdownEl.style.bottom = `${window.innerHeight - inputPos.top + LAYOUT.DROPDOWN_MARGIN}px`;
3373
+ this.#detachedCountrySelectorEl.style.top = "unset";
3374
+ this.#detachedCountrySelectorEl.style.bottom = `${window.innerHeight - inputPos.top + LAYOUT.DROPDOWN_MARGIN}px`;
3340
3375
  }
3341
3376
  }
3342
- dropdownContainer.appendChild(this.#detachedDropdownEl);
3377
+ detachedParent.appendChild(this.#detachedCountrySelectorEl);
3378
+ }
3379
+ //* Wire up CSS Anchor Positioning between the input and the detached country selector using a
3380
+ //* unique anchor name per instance. Called once at build time — the matching styles in
3381
+ //* intlTelInput.css only take effect in browsers that support anchor(); elsewhere these
3382
+ //* properties are inert. We append our name to any existing anchor-name (read via
3383
+ //* getComputedStyle so we pick up CSS-defined values), so consumer-set anchors on the input
3384
+ //* are preserved. Caveat: this snapshots the consumer's value once — if they later change
3385
+ //* anchor-name via CSS (e.g. a class swap), our inline write will shadow the change.
3386
+ #setupCssAnchorPositioning() {
3387
+ const anchorName = `--iti-anchor-${this.#id}`;
3388
+ const existing = getComputedStyle(this.telInputEl).anchorName;
3389
+ this.telInputEl.style.anchorName = existing && existing !== "none" ? `${existing}, ${anchorName}` : anchorName;
3390
+ this.#detachedCountrySelectorEl.style.positionAnchor = anchorName;
3343
3391
  }
3344
3392
  // Adjust the fullscreen popup dimensions to match the visual viewport,
3345
3393
  // so it stays above the virtual keyboard on mobile devices.
3346
3394
  #adjustFullscreenPopupToViewport() {
3347
3395
  const vv = window.visualViewport;
3348
- if (!vv || !this.#detachedDropdownEl) {
3396
+ if (!vv || !this.#detachedCountrySelectorEl) {
3349
3397
  return;
3350
3398
  }
3351
3399
  const virtualKeyboardHeight = window.innerHeight - vv.height;
3352
- this.#detachedDropdownEl.style.bottom = `${virtualKeyboardHeight}px`;
3400
+ this.#detachedCountrySelectorEl.style.bottom = `${virtualKeyboardHeight}px`;
3353
3401
  }
3354
- // UI: Whether the dropdown is currently open (visible).
3355
- isDropdownOpen() {
3356
- return !this.#dropdownContentEl.classList.contains(CLASSES.HIDE);
3402
+ // UI: Whether the country selector is currently open (visible).
3403
+ isCountrySelectorOpen() {
3404
+ return !this.#countrySelectorEl.classList.contains(CLASSES.HIDE);
3357
3405
  }
3358
3406
  // Toggle the loading spinner on the selected flag (used during auto-country geoIP lookup).
3359
3407
  setLoading(isLoading) {
3360
3408
  this.#selectedFlagEl.classList.toggle(CLASSES.LOADING, isLoading);
3361
3409
  }
3410
+ //* Play the strict-reject animation (shake, or background-colour flash under prefers-reduced-motion) on the wrapper.
3411
+ //* Called when strictMode rejects the whole input (keystroke, or whole paste).
3412
+ //* Uses the wrapper (not the input) so any separateDialCode / country button move together with the input.
3413
+ playStrictRejectAnimation() {
3414
+ if (!this.#options.strictRejectAnimation) {
3415
+ return;
3416
+ }
3417
+ const wrapperEl = this.telInputEl.parentElement;
3418
+ if (!wrapperEl) {
3419
+ return;
3420
+ }
3421
+ wrapperEl.classList.remove(CLASSES.STRICT_REJECT_ANIMATION);
3422
+ void wrapperEl.offsetWidth;
3423
+ wrapperEl.classList.add(CLASSES.STRICT_REJECT_ANIMATION);
3424
+ wrapperEl.addEventListener(
3425
+ "animationend",
3426
+ () => wrapperEl.classList.remove(CLASSES.STRICT_REJECT_ANIMATION),
3427
+ { once: true }
3428
+ );
3429
+ }
3362
3430
  isLoading() {
3363
3431
  return this.#selectedFlagEl.classList.contains(CLASSES.LOADING);
3364
3432
  }
3365
- // Set the disabled state of the input and dropdown.
3433
+ // Set the disabled state of the input and country selector.
3366
3434
  setDisabled(disabled) {
3367
3435
  this.telInputEl.disabled = disabled;
3368
3436
  if (this.#selectedCountryEl) {
@@ -3373,7 +3441,7 @@ var _factory = (() => {
3373
3441
  }
3374
3442
  }
3375
3443
  }
3376
- // Set the readonly state of the input and dropdown.
3444
+ // Set the readonly state of the input and country selector.
3377
3445
  setReadonly(readonly) {
3378
3446
  this.telInputEl.readOnly = readonly;
3379
3447
  if (this.#selectedCountryEl) {
@@ -3384,12 +3452,12 @@ var _factory = (() => {
3384
3452
  }
3385
3453
  }
3386
3454
  }
3387
- setCountry(selectedCountryData) {
3388
- const { allowDropdown, showFlags, separateDialCode, i18n } = this.#options;
3389
- const name = selectedCountryData?.name;
3390
- const dialCode = selectedCountryData?.dialCode;
3391
- const iso2 = selectedCountryData?.iso2 ?? "";
3392
- if (allowDropdown) {
3455
+ setSelectedCountry(selectedCountry) {
3456
+ const { countrySelectorMode, showFlags, separateDialCode, uiTranslations } = this.#options;
3457
+ const name = selectedCountry?.name;
3458
+ const dialCode = selectedCountry?.dialCode;
3459
+ const iso2 = selectedCountry?.iso2 ?? "";
3460
+ if (countrySelectorMode !== COUNTRY_SELECTOR_MODE.OFF) {
3393
3461
  this.#updateSelectedListItem(iso2);
3394
3462
  }
3395
3463
  if (this.#selectedCountryEl) {
@@ -3398,13 +3466,13 @@ var _factory = (() => {
3398
3466
  let flagContent = null;
3399
3467
  if (iso2) {
3400
3468
  title = name;
3401
- ariaLabel = i18n.selectedCountryAriaLabel.replace("${countryName}", name).replace("${dialCode}", `+${dialCode}`);
3469
+ ariaLabel = uiTranslations.selectedCountryAriaLabel.replace("${countryName}", name).replace("${dialCode}", `+${dialCode}`);
3402
3470
  if (!showFlags) {
3403
3471
  flagContent = buildGlobeIcon();
3404
3472
  }
3405
3473
  } else {
3406
- title = i18n.noCountrySelected;
3407
- ariaLabel = i18n.noCountrySelected;
3474
+ title = uiTranslations.noCountrySelected;
3475
+ ariaLabel = uiTranslations.noCountrySelected;
3408
3476
  flagContent = buildGlobeIcon();
3409
3477
  }
3410
3478
  this.#selectedFlagEl.className = flagClass;
@@ -3553,17 +3621,17 @@ var _factory = (() => {
3553
3621
  };
3554
3622
 
3555
3623
  // packages/core/src/js/format/formatting.ts
3556
- var stripSeparateDialCode = (fullNumber, hasValidDialCode, separateDialCode, selectedCountryData) => {
3624
+ var stripSeparateDialCode = (fullNumber, hasValidDialCode, separateDialCode, selectedCountry) => {
3557
3625
  if (!separateDialCode || !hasValidDialCode) {
3558
3626
  return fullNumber;
3559
3627
  }
3560
- const dialCode = `+${selectedCountryData.dialCode}`;
3628
+ const dialCode = `+${selectedCountry.dialCode}`;
3561
3629
  const start = fullNumber[dialCode.length] === " " || fullNumber[dialCode.length] === "-" ? dialCode.length + 1 : dialCode.length;
3562
3630
  return fullNumber.substring(start);
3563
3631
  };
3564
- var formatNumberAsYouType = (fullNumber, telInputValue, utils, selectedCountryData, separateDialCode) => {
3565
- const result = utils ? utils.formatNumberAsYouType(fullNumber, selectedCountryData?.iso2) : fullNumber;
3566
- const dialCode = selectedCountryData?.dialCode;
3632
+ var formatNumberAsYouType = (fullNumber, telInputValue, utils, selectedCountry, separateDialCode) => {
3633
+ const result = utils ? utils.formatNumberAsYouType(fullNumber, selectedCountry?.iso2) : fullNumber;
3634
+ const dialCode = selectedCountry?.dialCode;
3567
3635
  if (separateDialCode && telInputValue.charAt(0) !== "+" && result.includes(`+${dialCode}`)) {
3568
3636
  const afterDialCode = result.split(`+${dialCode}`)[1] || "";
3569
3637
  return afterDialCode.trim();
@@ -3694,8 +3762,8 @@ var _factory = (() => {
3694
3762
  this.#ui.telInputEl.value = this.#numerals.denormalise(asciiValue);
3695
3763
  }
3696
3764
  #createInitPromise(options) {
3697
- const { initialCountry, geoIpLookup, loadUtils } = options;
3698
- const needsAutoCountryDeferred = initialCountry === INITIAL_COUNTRY.AUTO && Boolean(geoIpLookup);
3765
+ const { initialCountry, initialCountryLookup, loadUtils } = options;
3766
+ const needsAutoCountryDeferred = !initialCountry && Boolean(initialCountryLookup);
3699
3767
  const needsUtilsDeferred = Boolean(loadUtils) && !intlTelInput.utils;
3700
3768
  if (needsAutoCountryDeferred) {
3701
3769
  this.#autoCountryDeferred = createDeferred();
@@ -3717,7 +3785,7 @@ var _factory = (() => {
3717
3785
  this.#initListeners();
3718
3786
  this.#startAsyncLoads();
3719
3787
  if (this.#options.dropdownAlwaysOpen) {
3720
- this.#openDropdown();
3788
+ this.openCountrySelector();
3721
3789
  }
3722
3790
  }
3723
3791
  //********************
@@ -3740,8 +3808,8 @@ var _factory = (() => {
3740
3808
  const value = useAttribute ? attributeValue : inputValue;
3741
3809
  const dialCode = this.#getDialCode(value);
3742
3810
  const isRegionlessNanpNumber = isRegionlessNanp(value);
3743
- const { initialCountry, geoIpLookup } = this.#options;
3744
- const isAutoCountry = initialCountry === INITIAL_COUNTRY.AUTO && geoIpLookup;
3811
+ const { initialCountry, initialCountryLookup } = this.#options;
3812
+ const isAutoCountry = !initialCountry && Boolean(initialCountryLookup);
3745
3813
  const resolvedInitialCountry = isAutoCountry && intlTelInput.autoCountry ? intlTelInput.autoCountry : initialCountry;
3746
3814
  const doingAutoCountryLookup = isAutoCountry && !overrideAutoCountry && !intlTelInput.autoCountry;
3747
3815
  const isValidInitialCountry = isIso2(resolvedInitialCountry);
@@ -3770,11 +3838,11 @@ var _factory = (() => {
3770
3838
  //* Initialise the main event listeners: input keyup, and click selected country.
3771
3839
  #initListeners() {
3772
3840
  this.#bindAllTelInputListeners();
3773
- if (this.#options.allowDropdown) {
3774
- this.#ui.bindAllInitialDropdownListeners(
3841
+ if (this.#options.countrySelectorMode !== COUNTRY_SELECTOR_MODE.OFF) {
3842
+ this.#ui.bindAllInitialCountrySelectorListeners(
3775
3843
  this.#abortController.signal,
3776
- () => this.#openDropdown(),
3777
- () => this.#closeDropdown()
3844
+ () => this.openCountrySelector(),
3845
+ () => this.#closeCountrySelectorInternal()
3778
3846
  );
3779
3847
  }
3780
3848
  this.#ui.bindHiddenInputSubmitListener(
@@ -3783,7 +3851,7 @@ var _factory = (() => {
3783
3851
  () => this.#selectedCountry?.iso2 || ""
3784
3852
  );
3785
3853
  }
3786
- //* Init requests: utils script / geo ip lookup.
3854
+ //* Init requests: utils script / initial country lookup.
3787
3855
  #startAsyncLoads() {
3788
3856
  if (this.#utilsDeferred) {
3789
3857
  const { loadUtils } = this.#options;
@@ -3807,7 +3875,7 @@ var _factory = (() => {
3807
3875
  }
3808
3876
  }
3809
3877
  }
3810
- //* Perform the geo ip lookup.
3878
+ //* Perform the initial country lookup.
3811
3879
  async #loadAutoCountry() {
3812
3880
  if (intlTelInput.autoCountry) {
3813
3881
  this.#handleAutoCountryLoaded();
@@ -3818,14 +3886,14 @@ var _factory = (() => {
3818
3886
  return;
3819
3887
  }
3820
3888
  intlTelInput.startedLoadingAutoCountry = true;
3821
- if (typeof this.#options.geoIpLookup === "function") {
3889
+ if (typeof this.#options.initialCountryLookup === "function") {
3822
3890
  let timeoutId;
3823
3891
  try {
3824
3892
  const iso2 = await Promise.race([
3825
- this.#options.geoIpLookup(),
3893
+ this.#options.initialCountryLookup(),
3826
3894
  new Promise((_, reject) => {
3827
3895
  timeoutId = setTimeout(
3828
- () => reject(new Error("intl-tel-input: geoIpLookup timed out after 10s")),
3896
+ () => reject(new Error("intl-tel-input: initialCountryLookup timed out after 10s")),
3829
3897
  1e4
3830
3898
  );
3831
3899
  })
@@ -3848,8 +3916,8 @@ var _factory = (() => {
3848
3916
  }
3849
3917
  }
3850
3918
  }
3851
- #openDropdownWithPlus() {
3852
- this.#openDropdown();
3919
+ #openCountrySelectorWithPlus() {
3920
+ this.openCountrySelector();
3853
3921
  this.#ui.prefillSearchWithPlus();
3854
3922
  }
3855
3923
  //* Delete the character just typed (the one immediately before the caret). Used by Android workarounds where we can't preventDefault on keydown.
@@ -3869,13 +3937,13 @@ var _factory = (() => {
3869
3937
  //* Android workaround for handling plus when separateDialCode enabled (as impossible to handle with keydown/keyup, for which e.key always returns "Unidentified", see https://stackoverflow.com/q/59584061/217866)
3870
3938
  #handleAndroidPlusKey(inputValue) {
3871
3939
  this.#removeJustTypedChar(inputValue);
3872
- this.#openDropdownWithPlus();
3940
+ this.#openCountrySelectorWithPlus();
3873
3941
  }
3874
3942
  //* Android strictMode workaround: the keydown-based filter can't block these because e.key is "Unidentified" on Android virtual keyboards, so strip them here on input.
3875
3943
  #handleAndroidStrictReject(inputValue, rejectedInput) {
3876
3944
  const newCaretPos = this.#removeJustTypedChar(inputValue);
3877
3945
  this.#ui.telInputEl.setSelectionRange(newCaretPos, newCaretPos);
3878
- this.#playStrictRejectAnimation();
3946
+ this.#ui.playStrictRejectAnimation();
3879
3947
  this.#dispatchEvent(EVENTS.STRICT_REJECT, {
3880
3948
  source: "key",
3881
3949
  rejectedInput,
@@ -3939,7 +4007,7 @@ var _factory = (() => {
3939
4007
  strictMode,
3940
4008
  formatAsYouType,
3941
4009
  separateDialCode,
3942
- allowDropdown,
4010
+ countrySelectorMode,
3943
4011
  countrySearch
3944
4012
  } = this.#options;
3945
4013
  const detail = e?.detail;
@@ -3949,7 +4017,7 @@ var _factory = (() => {
3949
4017
  let inputValue = this.#getTelInputValue();
3950
4018
  const isPaste = e?.inputType === INPUT_TYPES.PASTE;
3951
4019
  const isStrictPaste = strictMode && isPaste;
3952
- if (this.#isAndroid && !isPaste && e?.data === "+" && separateDialCode && allowDropdown && countrySearch) {
4020
+ if (this.#isAndroid && !isPaste && e?.data === "+" && separateDialCode && countrySelectorMode !== COUNTRY_SELECTOR_MODE.OFF && countrySearch) {
3953
4021
  this.#handleAndroidPlusKey(inputValue);
3954
4022
  return;
3955
4023
  }
@@ -3996,13 +4064,13 @@ var _factory = (() => {
3996
4064
  //* On keydown event: (1) if strictMode then prevent invalid characters, (2) if separateDialCode then handle plus key
3997
4065
  //* Note that this fires BEFORE the input is updated.
3998
4066
  #handleKeydownEvent = (e) => {
3999
- const { strictMode, separateDialCode, allowDropdown, countrySearch } = this.#options;
4067
+ const { strictMode, separateDialCode, countrySelectorMode, countrySearch } = this.#options;
4000
4068
  if (!e.key || e.key.length !== 1 || e.altKey || e.ctrlKey || e.metaKey) {
4001
4069
  return;
4002
4070
  }
4003
- if (separateDialCode && allowDropdown && countrySearch && e.key === "+") {
4071
+ if (separateDialCode && countrySelectorMode !== COUNTRY_SELECTOR_MODE.OFF && countrySearch && e.key === "+") {
4004
4072
  e.preventDefault();
4005
- this.#openDropdownWithPlus();
4073
+ this.#openCountrySelectorWithPlus();
4006
4074
  return;
4007
4075
  }
4008
4076
  if (!strictMode) {
@@ -4032,7 +4100,7 @@ var _factory = (() => {
4032
4100
  const newCountry = this.#resolveCountryChangeFromNumber(newFullNumber);
4033
4101
  const isChangingDialCode = newCountry !== null;
4034
4102
  if (!isAllowedChar || hasExceededMaxLength && !isChangingDialCode && !isInitialPlus) {
4035
- this.#playStrictRejectAnimation();
4103
+ this.#ui.playStrictRejectAnimation();
4036
4104
  this.#dispatchEvent(EVENTS.STRICT_REJECT, {
4037
4105
  source: "key",
4038
4106
  rejectedInput: e.key,
@@ -4087,7 +4155,7 @@ var _factory = (() => {
4087
4155
  let newValue = before + sanitised + after;
4088
4156
  let rejectReason = sanitised !== pasted ? "invalid" : null;
4089
4157
  if (newValue.length > 30) {
4090
- this.#playStrictRejectAnimation();
4158
+ this.#ui.playStrictRejectAnimation();
4091
4159
  this.#dispatchEvent(EVENTS.STRICT_REJECT, {
4092
4160
  source: "paste",
4093
4161
  rejectedInput: pastedRaw,
@@ -4103,7 +4171,7 @@ var _factory = (() => {
4103
4171
  coreNumber = intlTelInput.utils.getCoreNumber(newValue, iso2);
4104
4172
  }
4105
4173
  if (!coreNumber) {
4106
- this.#playStrictRejectAnimation();
4174
+ this.#ui.playStrictRejectAnimation();
4107
4175
  this.#dispatchEvent(EVENTS.STRICT_REJECT, {
4108
4176
  source: "paste",
4109
4177
  rejectedInput: pastedRaw,
@@ -4118,7 +4186,7 @@ var _factory = (() => {
4118
4186
  newValue = newValue.slice(0, newValue.length - trimLength);
4119
4187
  rejectReason = "max-length";
4120
4188
  } else {
4121
- this.#playStrictRejectAnimation();
4189
+ this.#ui.playStrictRejectAnimation();
4122
4190
  this.#dispatchEvent(EVENTS.STRICT_REJECT, {
4123
4191
  source: "paste",
4124
4192
  rejectedInput: pastedRaw,
@@ -4134,7 +4202,7 @@ var _factory = (() => {
4134
4202
  input.setSelectionRange(caretPos, caretPos);
4135
4203
  if (rejectReason) {
4136
4204
  if (pasted.length > 0 && sanitised.length === 0) {
4137
- this.#playStrictRejectAnimation();
4205
+ this.#ui.playStrictRejectAnimation();
4138
4206
  }
4139
4207
  this.#dispatchEvent(EVENTS.STRICT_REJECT, {
4140
4208
  source: "paste",
@@ -4156,21 +4224,6 @@ var _factory = (() => {
4156
4224
  const max = Number(this.#ui.telInputEl.getAttribute("maxlength"));
4157
4225
  return max && number.length > max ? number.substring(0, max) : number;
4158
4226
  }
4159
- //* Play the strict-reject animation (shake, or background-colour flash under prefers-reduced-motion) on the wrapper.
4160
- //* Called when strictMode rejects the whole input (keystroke, or whole paste).
4161
- //* Uses the wrapper (not the input) so any separateDialCode / country button move together with the input.
4162
- #playStrictRejectAnimation() {
4163
- if (!this.#options.strictRejectAnimation) {
4164
- return;
4165
- }
4166
- const wrapperEl = this.#ui.telInputEl.parentElement;
4167
- if (!wrapperEl) {
4168
- return;
4169
- }
4170
- wrapperEl.classList.remove("iti__strict-reject-animation");
4171
- void wrapperEl.offsetWidth;
4172
- wrapperEl.classList.add("iti__strict-reject-animation");
4173
- }
4174
4227
  //* Trigger a custom event on the input (typed via ItiEventMap).
4175
4228
  #dispatchEvent(name, detailProps = {}) {
4176
4229
  const e = new CustomEvent(name, {
@@ -4180,27 +4233,36 @@ var _factory = (() => {
4180
4233
  });
4181
4234
  this.#ui.telInputEl.dispatchEvent(e);
4182
4235
  }
4183
- //* Open the dropdown. Bail if already open — otherwise the existing AbortController gets overwritten
4184
- //* and its listeners leak. Reachable via openDropdownWithPlus when dropdownAlwaysOpen is set
4185
- #openDropdown() {
4186
- if (this.#ui.isDropdownOpen()) {
4236
+ //* Open the country selector. Bail if already open — otherwise the existing AbortController gets overwritten
4237
+ //* and its listeners leak. Reachable via openCountrySelectorWithPlus when dropdownAlwaysOpen is set.
4238
+ //* Public so consumers can programmatically open the country selector.
4239
+ openCountrySelector() {
4240
+ if (this.#ui.isCountrySelectorOpen()) {
4187
4241
  return;
4188
4242
  }
4189
- this.#ui.openDropdown(
4243
+ this.#ui.openCountrySelector(
4190
4244
  (li) => this.#selectListItem(li),
4191
- () => this.#closeDropdown()
4245
+ () => this.#closeCountrySelectorInternal()
4192
4246
  );
4193
- this.#dispatchEvent(EVENTS.OPEN_COUNTRY_DROPDOWN);
4247
+ this.#dispatchEvent(EVENTS.OPEN_COUNTRY_SELECTOR);
4194
4248
  }
4195
4249
  //* Update the input's value to the given number (format first if possible)
4196
4250
  //* NOTE: this is called from setInitialState, handleUtilsLoaded and setNumber.
4197
4251
  #updateValueFromNumber(fullNumber) {
4198
- const { formatOnDisplay, nationalMode, separateDialCode } = this.#options;
4252
+ const { numberDisplayFormat, separateDialCode } = this.#options;
4199
4253
  let number = fullNumber;
4200
- if (formatOnDisplay && intlTelInput.utils && this.#selectedCountry) {
4254
+ if (intlTelInput.utils && this.#selectedCountry) {
4201
4255
  const isRegionless = hasRegionlessDialCode(fullNumber);
4202
- const useNational = nationalMode && !isRegionless || !number.startsWith("+") && !separateDialCode;
4203
- const format = useNational ? NUMBER_FORMAT.NATIONAL : NUMBER_FORMAT.INTERNATIONAL;
4256
+ const preserveUserNational = !number.startsWith("+") && !separateDialCode;
4257
+ const useNational = numberDisplayFormat === NUMBER_FORMAT.NATIONAL && !isRegionless || preserveUserNational;
4258
+ let format;
4259
+ if (useNational) {
4260
+ format = NUMBER_FORMAT.NATIONAL;
4261
+ } else if (numberDisplayFormat === NUMBER_FORMAT.E164 && !isRegionless) {
4262
+ format = NUMBER_FORMAT.E164;
4263
+ } else {
4264
+ format = NUMBER_FORMAT.INTERNATIONAL;
4265
+ }
4204
4266
  number = intlTelInput.utils.formatNumber(
4205
4267
  number,
4206
4268
  this.#selectedCountry?.iso2,
@@ -4297,14 +4359,14 @@ var _factory = (() => {
4297
4359
  return null;
4298
4360
  }
4299
4361
  //* Update the selected country, dial code (if separateDialCode), placeholder, title, and selected list item.
4300
- //* Note: called from setInitialState, updateCountryFromNumber, selectListItem, setCountry.
4362
+ //* Note: called from setInitialState, updateCountryFromNumber, selectListItem, setSelectedCountry.
4301
4363
  #updateSelectedCountry(iso2) {
4302
4364
  const prevIso2 = this.#selectedCountry?.iso2 || "";
4303
4365
  this.#selectedCountry = iso2 ? this.#countryByIso2.get(iso2) : null;
4304
4366
  if (this.#selectedCountry) {
4305
4367
  this.#fallbackCountryIso2 = this.#selectedCountry.iso2;
4306
4368
  }
4307
- this.#ui.setCountry(this.#selectedCountry);
4369
+ this.#ui.setSelectedCountry(this.#selectedCountry);
4308
4370
  this.#updatePlaceholder();
4309
4371
  this.#updateMaxCoreNumberLength();
4310
4372
  return prevIso2 !== iso2;
@@ -4322,12 +4384,11 @@ var _factory = (() => {
4322
4384
  }
4323
4385
  let exampleNumber = intlTelInput.utils.getExampleNumber(
4324
4386
  iso2,
4325
- false,
4326
4387
  placeholderNumberType,
4327
- true
4388
+ NUMBER_FORMAT.E164
4328
4389
  );
4329
4390
  let validNumber = exampleNumber;
4330
- while (intlTelInput.utils.isPossibleNumber(
4391
+ while (intlTelInput.utils.isValidNumber(
4331
4392
  exampleNumber,
4332
4393
  iso2,
4333
4394
  allowedNumberTypes
@@ -4344,19 +4405,19 @@ var _factory = (() => {
4344
4405
  //* Update the input placeholder to an example number from the currently selected country.
4345
4406
  #updatePlaceholder() {
4346
4407
  const {
4347
- autoPlaceholder,
4408
+ placeholderNumberPolicy,
4348
4409
  placeholderNumberType,
4349
- nationalMode,
4410
+ numberDisplayFormat,
4350
4411
  customPlaceholder
4351
4412
  } = this.#options;
4352
- const shouldSetPlaceholder = autoPlaceholder === PLACEHOLDER_MODES.AGGRESSIVE || !this.#ui.hadInitialPlaceholder && autoPlaceholder === PLACEHOLDER_MODES.POLITE;
4413
+ const shouldSetPlaceholder = placeholderNumberPolicy === PLACEHOLDER_POLICY.AGGRESSIVE || !this.#ui.hadInitialPlaceholder && placeholderNumberPolicy === PLACEHOLDER_POLICY.POLITE;
4353
4414
  if (!intlTelInput.utils || !shouldSetPlaceholder) {
4354
4415
  return;
4355
4416
  }
4356
4417
  let placeholder = this.#selectedCountry ? intlTelInput.utils.getExampleNumber(
4357
4418
  this.#selectedCountry.iso2,
4358
- nationalMode,
4359
- placeholderNumberType
4419
+ placeholderNumberType,
4420
+ numberDisplayFormat
4360
4421
  ) : "";
4361
4422
  placeholder = this.#prepareNumberForInput(placeholder);
4362
4423
  if (typeof customPlaceholder === "function") {
@@ -4364,36 +4425,40 @@ var _factory = (() => {
4364
4425
  }
4365
4426
  this.#ui.telInputEl.setAttribute("placeholder", placeholder);
4366
4427
  }
4367
- //* Called when the user selects a list item from the dropdown (no-op if listItem is null).
4428
+ //* Called when the user selects a list item from the country list (no-op if listItem is null).
4368
4429
  #selectListItem(listItem) {
4369
4430
  if (!listItem) {
4370
4431
  return;
4371
4432
  }
4372
4433
  const iso2 = listItem.dataset[DATA_KEYS.ISO2];
4373
4434
  const countryChanged = this.#updateSelectedCountry(iso2);
4374
- this.#closeDropdown();
4435
+ this.#closeCountrySelectorInternal();
4375
4436
  const dialCode = listItem.dataset[DATA_KEYS.DIAL_CODE];
4376
4437
  this.#updateDialCode(dialCode);
4377
- if (this.#options.formatOnDisplay) {
4378
- const inputValue = this.#getTelInputValue();
4379
- this.#updateValueFromNumber(inputValue);
4380
- }
4438
+ const inputValue = this.#getTelInputValue();
4439
+ this.#updateValueFromNumber(inputValue);
4381
4440
  this.#ui.telInputEl.focus();
4382
4441
  if (countryChanged) {
4383
4442
  this.#dispatchCountryChangeEvent();
4384
4443
  this.#dispatchEvent(EVENTS.INPUT, { isCountryChange: true });
4385
4444
  }
4386
4445
  }
4387
- //* Close the dropdown and unbind any listeners.
4388
- #closeDropdown(isDestroy) {
4389
- if (!this.#ui.isDropdownOpen() || this.#options.dropdownAlwaysOpen && !isDestroy) {
4446
+ //* Public: close the country selector (consumer-callable; delegates to the internal helper
4447
+ //* without the destroy-specific path).
4448
+ closeCountrySelector() {
4449
+ this.#closeCountrySelectorInternal();
4450
+ }
4451
+ //* Close the country selector and unbind any listeners. The isDestroy flag forces close even
4452
+ //* when dropdownAlwaysOpen is set, so destroy() can fully tear down.
4453
+ #closeCountrySelectorInternal(isDestroy) {
4454
+ if (!this.#ui.isCountrySelectorOpen() || this.#options.dropdownAlwaysOpen && !isDestroy) {
4390
4455
  return;
4391
4456
  }
4392
- this.#ui.closeDropdown();
4393
- this.#dispatchEvent(EVENTS.CLOSE_COUNTRY_DROPDOWN);
4457
+ this.#ui.closeCountrySelector();
4458
+ this.#dispatchEvent(EVENTS.CLOSE_COUNTRY_SELECTOR);
4394
4459
  }
4395
4460
  //* Replace any existing dial code with the new one
4396
- //* Note: called from selectListItem and setCountry
4461
+ //* Note: called from selectListItem and setSelectedCountry
4397
4462
  #updateDialCode(newDialCodeDigits) {
4398
4463
  const inputValue = this.#getTelInputValue();
4399
4464
  if (!inputValue.startsWith("+")) {
@@ -4473,7 +4538,7 @@ var _factory = (() => {
4473
4538
  //**************************
4474
4539
  //* INTERNAL METHODS
4475
4540
  //**************************
4476
- //* Called when the geoip call returns.
4541
+ //* Called when the initial country lookup returns.
4477
4542
  #handleAutoCountryLoaded() {
4478
4543
  if (!this.#autoCountryDeferred || !intlTelInput.autoCountry) {
4479
4544
  return;
@@ -4482,15 +4547,17 @@ var _factory = (() => {
4482
4547
  this.#autoCountryDeferred.resolve();
4483
4548
  return;
4484
4549
  }
4485
- if (this.#ui.isLoading()) {
4486
- this.setCountry(intlTelInput.autoCountry);
4550
+ const isFocused = document.activeElement === this.#ui.telInputEl;
4551
+ const hasTypedValue = Boolean(this.#getTelInputValue());
4552
+ if (this.#ui.isLoading() && !(isFocused && hasTypedValue)) {
4553
+ this.setSelectedCountry(intlTelInput.autoCountry);
4487
4554
  } else {
4488
4555
  this.#fallbackCountryIso2 = intlTelInput.autoCountry;
4489
4556
  }
4490
4557
  this.#ui.setLoading(false);
4491
4558
  this.#autoCountryDeferred.resolve();
4492
4559
  }
4493
- //* Called when the geoip call fails or times out.
4560
+ //* Called when the initial country lookup fails or times out.
4494
4561
  #handleAutoCountryFailure() {
4495
4562
  if (!this.#isActive) {
4496
4563
  this.#autoCountryDeferred?.reject();
@@ -4511,7 +4578,8 @@ var _factory = (() => {
4511
4578
  return;
4512
4579
  }
4513
4580
  const inputValue = this.#getTelInputValue();
4514
- if (inputValue) {
4581
+ const isFocused = document.activeElement === this.#ui.telInputEl;
4582
+ if (inputValue && !isFocused) {
4515
4583
  this.#updateValueFromNumber(inputValue);
4516
4584
  }
4517
4585
  if (this.#selectedCountry) {
@@ -4537,8 +4605,8 @@ var _factory = (() => {
4537
4605
  return;
4538
4606
  }
4539
4607
  this.#isActive = false;
4540
- if (this.#options.allowDropdown) {
4541
- this.#closeDropdown(true);
4608
+ if (this.#options.countrySelectorMode !== COUNTRY_SELECTOR_MODE.OFF) {
4609
+ this.#closeCountrySelectorInternal(true);
4542
4610
  }
4543
4611
  this.#abortController.abort();
4544
4612
  this.#ui.destroy();
@@ -4586,7 +4654,7 @@ var _factory = (() => {
4586
4654
  );
4587
4655
  }
4588
4656
  //* Get the country data for the currently selected country.
4589
- getSelectedCountryData() {
4657
+ getSelectedCountry() {
4590
4658
  return this.#selectedCountry ?? null;
4591
4659
  }
4592
4660
  //* Get the validation error e.g. "TOO_SHORT" / "TOO_LONG", or null if it can't be determined / instance is destroyed.
@@ -4641,7 +4709,7 @@ var _factory = (() => {
4641
4709
  if (!this.#selectedCountry && !hasRegionlessDialCode(value)) {
4642
4710
  return false;
4643
4711
  }
4644
- const check = mode === "precise" ? intlTelInput.utils.isValidNumber : intlTelInput.utils.isPossibleNumber;
4712
+ const check = mode === "precise" ? intlTelInput.utils.isValidNumberPrecise : intlTelInput.utils.isValidNumber;
4645
4713
  if (!check(value, iso2, allowedNumberTypes)) {
4646
4714
  return false;
4647
4715
  }
@@ -4654,7 +4722,7 @@ var _factory = (() => {
4654
4722
  return true;
4655
4723
  }
4656
4724
  //* Update the selected country, and update the input value accordingly.
4657
- setCountry(iso2) {
4725
+ setSelectedCountry(iso2) {
4658
4726
  if (!this.#isActive) {
4659
4727
  return;
4660
4728
  }
@@ -4669,10 +4737,8 @@ var _factory = (() => {
4669
4737
  }
4670
4738
  this.#updateSelectedCountry(iso2Lower);
4671
4739
  this.#updateDialCode(this.#selectedCountry?.dialCode || "");
4672
- if (this.#options.formatOnDisplay) {
4673
- const inputValue = this.#getTelInputValue();
4674
- this.#updateValueFromNumber(inputValue);
4675
- }
4740
+ const inputValue = this.#getTelInputValue();
4741
+ this.#updateValueFromNumber(inputValue);
4676
4742
  this.#dispatchCountryChangeEvent();
4677
4743
  this.#dispatchEvent(EVENTS.INPUT, { isCountryChange: true });
4678
4744
  }
@@ -4697,14 +4763,14 @@ var _factory = (() => {
4697
4763
  this.#options.placeholderNumberType = type;
4698
4764
  this.#updatePlaceholder();
4699
4765
  }
4700
- // Set the disabled state of the input and dropdown.
4766
+ // Set the disabled state of the input and country selector.
4701
4767
  setDisabled(disabled) {
4702
4768
  if (!this.#isActive) {
4703
4769
  return;
4704
4770
  }
4705
4771
  this.#ui.setDisabled(disabled);
4706
4772
  }
4707
- // Set the readonly state of the input and dropdown.
4773
+ // Set the readonly state of the input and country selector.
4708
4774
  setReadonly(readonly) {
4709
4775
  if (!this.#isActive) {
4710
4776
  return;
@@ -4714,7 +4780,7 @@ var _factory = (() => {
4714
4780
  //********************
4715
4781
  //* STATIC METHODS
4716
4782
  //********************
4717
- // Internal instance notification used by utils/geoip loaders.
4783
+ // Internal instance notification used by utils/initial-country loaders.
4718
4784
  // Kept public so module-level helpers (e.g. attachUtils) can call it, while still allowing
4719
4785
  // access to private instance methods.
4720
4786
  static forEachInstance(method, ...args) {
@@ -4778,8 +4844,8 @@ var _factory = (() => {
4778
4844
  defaults,
4779
4845
  //* Using a static var like this allows us to mock it in the tests.
4780
4846
  documentReady: () => document.readyState === "complete",
4781
- //* Get the country data object.
4782
- getCountryData: () => data_default,
4847
+ //* Get the full list of all countries the library knows about.
4848
+ getAllCountries: () => data_default,
4783
4849
  //* A getter for the core library instance.
4784
4850
  getInstance: (input) => {
4785
4851
  const id = input.dataset[DATA_KEYS.INSTANCE_ID];
@@ -4790,10 +4856,12 @@ var _factory = (() => {
4790
4856
  attachUtils,
4791
4857
  startedLoadingUtils: false,
4792
4858
  startedLoadingAutoCountry: false,
4793
- version: "28.0.9",
4859
+ version: "29.0.0",
4794
4860
  NUMBER_FORMAT,
4795
4861
  NUMBER_TYPE,
4796
- VALIDATION_ERROR
4862
+ VALIDATION_ERROR,
4863
+ PLACEHOLDER_POLICY,
4864
+ COUNTRY_SELECTOR_MODE
4797
4865
  }
4798
4866
  );
4799
4867
  var intlTelInput_default = intlTelInput;