intl-tel-input 27.2.1 → 27.3.1

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.
@@ -1743,13 +1743,7 @@ for (const c of rawCountryData) {
1743
1743
  dialCode: c[1],
1744
1744
  priority: c[2] || 0,
1745
1745
  areaCodes: c[3] || null,
1746
- nationalPrefix: c[4] || null,
1747
- normalisedName: "",
1748
- // populated in the plugin
1749
- initials: "",
1750
- // populated in the plugin
1751
- dialCodePlus: ""
1752
- // populated in the plugin
1746
+ nationalPrefix: c[4] || null
1753
1747
  });
1754
1748
  }
1755
1749
  var iso2Set = new Set(allCountries.map((c) => c.iso2));
@@ -1966,6 +1960,8 @@ var defaults = {
1966
1960
  searchInputClass: "",
1967
1961
  //* Display the international dial code next to the selected flag.
1968
1962
  separateDialCode: false,
1963
+ //* When strictMode rejects a key (etc), play a short feedback animation
1964
+ strictRejectAnimation: false,
1969
1965
  //* Show flags - for both the selected country, and in the country dropdown
1970
1966
  showFlags: true,
1971
1967
  //* Only allow certain chars e.g. a plus followed by numeric digits, and cap at max valid length.
@@ -2041,6 +2037,7 @@ var validateOptions = (customOptions) => {
2041
2037
  case "showFlags":
2042
2038
  case "separateDialCode":
2043
2039
  case "strictMode":
2040
+ case "strictRejectAnimation":
2044
2041
  case "useFullscreenPopup":
2045
2042
  if (typeof value !== "boolean") {
2046
2043
  warnOption(key, "a boolean", value);
@@ -2259,7 +2256,20 @@ var buildGlobeIcon = () => `
2259
2256
  </svg>`;
2260
2257
 
2261
2258
  // src/js/core/countrySearch.ts
2262
- var getMatchedCountries = (countries, query) => {
2259
+ var buildSearchTokens = (countries) => {
2260
+ const tokens = /* @__PURE__ */ new Map();
2261
+ for (const c of countries) {
2262
+ const normalisedName = normaliseString(c.name);
2263
+ const initials = normalisedName.split(/[^a-z]/).map((word) => word[0]).join("");
2264
+ tokens.set(c.iso2, {
2265
+ normalisedName,
2266
+ initials,
2267
+ dialCodePlus: `+${c.dialCode}`
2268
+ });
2269
+ }
2270
+ return tokens;
2271
+ };
2272
+ var getMatchedCountries = (countries, searchTokens, query) => {
2263
2273
  const normalisedQuery = normaliseString(query);
2264
2274
  const iso2Matches = [];
2265
2275
  const nameStartsWith = [];
@@ -2268,17 +2278,18 @@ var getMatchedCountries = (countries, query) => {
2268
2278
  const dialCodeContains = [];
2269
2279
  const initialsMatches = [];
2270
2280
  for (const c of countries) {
2281
+ const t = searchTokens.get(c.iso2);
2271
2282
  if (c.iso2 === normalisedQuery) {
2272
2283
  iso2Matches.push(c);
2273
- } else if (c.normalisedName.startsWith(normalisedQuery)) {
2284
+ } else if (t.normalisedName.startsWith(normalisedQuery)) {
2274
2285
  nameStartsWith.push(c);
2275
- } else if (c.normalisedName.includes(normalisedQuery)) {
2286
+ } else if (t.normalisedName.includes(normalisedQuery)) {
2276
2287
  nameContains.push(c);
2277
- } else if (normalisedQuery === c.dialCode || normalisedQuery === c.dialCodePlus) {
2288
+ } else if (normalisedQuery === c.dialCode || normalisedQuery === t.dialCodePlus) {
2278
2289
  dialCodeMatches.push(c);
2279
- } else if (c.dialCodePlus.includes(normalisedQuery)) {
2290
+ } else if (t.dialCodePlus.includes(normalisedQuery)) {
2280
2291
  dialCodeContains.push(c);
2281
- } else if (c.initials.includes(normalisedQuery)) {
2292
+ } else if (t.initials.includes(normalisedQuery)) {
2282
2293
  initialsMatches.push(c);
2283
2294
  }
2284
2295
  }
@@ -2293,16 +2304,75 @@ var getMatchedCountries = (countries, query) => {
2293
2304
  ...initialsMatches
2294
2305
  ];
2295
2306
  };
2296
- var findFirstCountryStartingWith = (countries, query) => {
2307
+ var findFirstCountryStartingWith = (countries, searchTokens, query) => {
2297
2308
  const normalisedQuery = normaliseString(query);
2298
2309
  for (const c of countries) {
2299
- if (c.normalisedName.startsWith(normalisedQuery)) {
2310
+ const { normalisedName } = searchTokens.get(c.iso2);
2311
+ if (normalisedName.startsWith(normalisedQuery)) {
2300
2312
  return c;
2301
2313
  }
2302
2314
  }
2303
2315
  return null;
2304
2316
  };
2305
2317
 
2318
+ // src/js/core/numerals.ts
2319
+ var Numerals = class _Numerals {
2320
+ #userNumeralSet;
2321
+ //* Stateless conversion of any Arabic-Indic / Persian digits to ASCII 0-9.
2322
+ //* Use this when you need to normalise digits without affecting any instance's tracked numeral set (e.g. for the country-search query).
2323
+ static toAscii(str) {
2324
+ if (!str) {
2325
+ return "";
2326
+ }
2327
+ return str.replace(
2328
+ /[٠-٩]/g,
2329
+ (ch) => String.fromCharCode(48 + (ch.charCodeAt(0) - 1632))
2330
+ ).replace(
2331
+ /[۰-۹]/g,
2332
+ (ch) => String.fromCharCode(48 + (ch.charCodeAt(0) - 1776))
2333
+ );
2334
+ }
2335
+ constructor(initialValue) {
2336
+ if (initialValue) {
2337
+ this.#updateNumeralSet(initialValue);
2338
+ }
2339
+ }
2340
+ // If any Arabic-Indic digits, then label it as that set. Same for Persian. Otherwise assume ASCII.
2341
+ #updateNumeralSet(str) {
2342
+ if (/[٠-٩]/.test(str)) {
2343
+ this.#userNumeralSet = "arabic-indic";
2344
+ } else if (/[۰-۹]/.test(str)) {
2345
+ this.#userNumeralSet = "persian";
2346
+ } else {
2347
+ this.#userNumeralSet = "ascii";
2348
+ }
2349
+ }
2350
+ // Denormalise ASCII 0-9 to the user's numeral set. If not yet known, return as-is.
2351
+ // NOTE: normalise is always called before this, so it should be impossible for the numeral set to be unknown at this point.
2352
+ denormalise(str) {
2353
+ if (!this.#userNumeralSet || this.#userNumeralSet === "ascii") {
2354
+ return str;
2355
+ }
2356
+ const base = this.#userNumeralSet === "arabic-indic" ? 1632 : 1776;
2357
+ return str.replace(/[0-9]/g, (d) => String.fromCharCode(base + Number(d)));
2358
+ }
2359
+ // Normalize Eastern Arabic (U+0660-0669) and Persian/Extended Arabic-Indic (U+06F0-06F9) numerals to ASCII 0-9.
2360
+ // Tracks the user's numeral set as a side effect so denormalise can mirror it back.
2361
+ normalise(str) {
2362
+ if (!str) {
2363
+ return "";
2364
+ }
2365
+ this.#updateNumeralSet(str);
2366
+ if (this.#userNumeralSet === "ascii") {
2367
+ return str;
2368
+ }
2369
+ return _Numerals.toAscii(str);
2370
+ }
2371
+ isAscii() {
2372
+ return !this.#userNumeralSet || this.#userNumeralSet === "ascii";
2373
+ }
2374
+ };
2375
+
2306
2376
  // src/js/core/ui.ts
2307
2377
  var UI = class _UI {
2308
2378
  // private
@@ -2311,6 +2381,7 @@ var UI = class _UI {
2311
2381
  #isRTL;
2312
2382
  #originalPaddingLeft = "";
2313
2383
  #countries;
2384
+ #searchTokens;
2314
2385
  #searchDebounceTimer = null;
2315
2386
  #inlineDropdownHeight;
2316
2387
  #countryContainerEl;
@@ -2356,8 +2427,9 @@ var UI = class _UI {
2356
2427
  }
2357
2428
  }
2358
2429
  //* Generate all of the markup for the plugin: the selected country overlay, and the dropdown.
2359
- buildMarkup(countries) {
2430
+ buildMarkup(countries, searchTokens) {
2360
2431
  this.#countries = countries;
2432
+ this.#searchTokens = searchTokens;
2361
2433
  this.telInputEl.classList.add("iti__tel-input");
2362
2434
  if (!this.telInputEl.hasAttribute("type")) {
2363
2435
  this.telInputEl.setAttribute("type", "tel");
@@ -2725,7 +2797,12 @@ var UI = class _UI {
2725
2797
  if (query === "") {
2726
2798
  matchedCountries = this.#countries;
2727
2799
  } else {
2728
- matchedCountries = getMatchedCountries(this.#countries, query);
2800
+ const normalisedQuery = Numerals.toAscii(query);
2801
+ matchedCountries = getMatchedCountries(
2802
+ this.#countries,
2803
+ this.#searchTokens,
2804
+ normalisedQuery
2805
+ );
2729
2806
  }
2730
2807
  this.#showFilteredCountries(matchedCountries);
2731
2808
  }
@@ -3020,7 +3097,11 @@ var UI = class _UI {
3020
3097
  }
3021
3098
  //* Hidden search (countrySearch disabled): jump to the first list item whose name starts with the query.
3022
3099
  #searchForCountry(query) {
3023
- const match = findFirstCountryStartingWith(this.#countries, query);
3100
+ const match = findFirstCountryStartingWith(
3101
+ this.#countries,
3102
+ this.#searchTokens,
3103
+ query
3104
+ );
3024
3105
  if (match) {
3025
3106
  const listItem = this.#listItemByIso2.get(match.iso2);
3026
3107
  this.#highlightListItem(listItem);
@@ -3332,13 +3413,6 @@ var sortCountries = (countries, options) => {
3332
3413
  return a.name.localeCompare(b.name);
3333
3414
  });
3334
3415
  };
3335
- var cacheSearchTokens = (countries) => {
3336
- for (const c of countries) {
3337
- c.normalisedName = normaliseString(c.name);
3338
- c.initials = c.normalisedName.split(/[^a-z]/).map((word) => word[0]).join("");
3339
- c.dialCodePlus = `+${c.dialCode}`;
3340
- }
3341
- };
3342
3416
 
3343
3417
  // src/js/data/intl-regionless.ts
3344
3418
  var regionlessDialCodes = /* @__PURE__ */ new Set([
@@ -3424,54 +3498,6 @@ var isRegionlessNanp = (number) => {
3424
3498
  return false;
3425
3499
  };
3426
3500
 
3427
- // src/js/core/numerals.ts
3428
- var Numerals = class {
3429
- #userNumeralSet;
3430
- constructor(initialValue) {
3431
- if (initialValue) {
3432
- this.#updateNumeralSet(initialValue);
3433
- }
3434
- }
3435
- // If any Arabic-Indic digits, then label it as that set. Same for Persian. Otherwise assume ASCII.
3436
- #updateNumeralSet(str) {
3437
- if (/[\u0660-\u0669]/.test(str)) {
3438
- this.#userNumeralSet = "arabic-indic";
3439
- } else if (/[\u06F0-\u06F9]/.test(str)) {
3440
- this.#userNumeralSet = "persian";
3441
- } else {
3442
- this.#userNumeralSet = "ascii";
3443
- }
3444
- }
3445
- // Denormalise ASCII 0-9 to the user's numeral set. If not yet known, return as-is.
3446
- // NOTE: normalise is always called before this, so it should be impossible for the numeral set to be unknown at this point.
3447
- denormalise(str) {
3448
- if (!this.#userNumeralSet || this.#userNumeralSet === "ascii") {
3449
- return str;
3450
- }
3451
- const base = this.#userNumeralSet === "arabic-indic" ? 1632 : 1776;
3452
- return str.replace(/[0-9]/g, (d) => String.fromCharCode(base + Number(d)));
3453
- }
3454
- // Normalize Eastern Arabic (U+0660-0669) and Persian/Extended Arabic-Indic (U+06F0-06F9) numerals to ASCII 0-9
3455
- normalise(str) {
3456
- if (!str) {
3457
- return "";
3458
- }
3459
- this.#updateNumeralSet(str);
3460
- if (this.#userNumeralSet === "ascii") {
3461
- return str;
3462
- }
3463
- const base = this.#userNumeralSet === "arabic-indic" ? 1632 : 1776;
3464
- const regex = this.#userNumeralSet === "arabic-indic" ? /[\u0660-\u0669]/g : /[\u06F0-\u06F9]/g;
3465
- return str.replace(
3466
- regex,
3467
- (ch) => String.fromCharCode(48 + (ch.charCodeAt(0) - base))
3468
- );
3469
- }
3470
- isAscii() {
3471
- return !this.#userNumeralSet || this.#userNumeralSet === "ascii";
3472
- }
3473
- };
3474
-
3475
3501
  // src/js/intlTelInput.ts
3476
3502
  var nextId = 0;
3477
3503
  var ensureUtils = (methodName) => {
@@ -3506,6 +3532,7 @@ var Iti = class _Iti {
3506
3532
  #dialCodeToIso2Map;
3507
3533
  #dialCodes;
3508
3534
  #countryByIso2;
3535
+ #searchTokens;
3509
3536
  #selectedCountry = null;
3510
3537
  #maxCoreNumberLength = null;
3511
3538
  #fallbackCountryIso2;
@@ -3529,9 +3556,7 @@ var Iti = class _Iti {
3529
3556
  this.#numerals = new Numerals(input.value);
3530
3557
  this.promise = this.#createInitPromise(this.#options);
3531
3558
  this.#countries = processAllCountries(this.#options);
3532
- const { dialCodes, dialCodeMaxLength, dialCodeToIso2Map } = processDialCodes(
3533
- this.#countries
3534
- );
3559
+ const { dialCodes, dialCodeMaxLength, dialCodeToIso2Map } = processDialCodes(this.#countries);
3535
3560
  this.#dialCodes = dialCodes;
3536
3561
  this.#dialCodeMaxLength = dialCodeMaxLength;
3537
3562
  this.#dialCodeToIso2Map = dialCodeToIso2Map;
@@ -3564,7 +3589,7 @@ var Iti = class _Iti {
3564
3589
  #init() {
3565
3590
  this.#abortController = new AbortController();
3566
3591
  this.#processCountryData();
3567
- this.#ui.buildMarkup(this.#countries);
3592
+ this.#ui.buildMarkup(this.#countries, this.#searchTokens);
3568
3593
  this.#setInitialState();
3569
3594
  this.#initListeners();
3570
3595
  this.#startAsyncLoads();
@@ -3579,7 +3604,7 @@ var Iti = class _Iti {
3579
3604
  #processCountryData() {
3580
3605
  generateCountryNames(this.#countries, this.#options);
3581
3606
  sortCountries(this.#countries, this.#options);
3582
- cacheSearchTokens(this.#countries);
3607
+ this.#searchTokens = buildSearchTokens(this.#countries);
3583
3608
  }
3584
3609
  //* Set the initial state of the input value and the selected country by:
3585
3610
  //* 1. Extracting a dial code from the given number
@@ -3702,7 +3727,7 @@ var Iti = class _Iti {
3702
3727
  #bindAllTelInputListeners() {
3703
3728
  this.#bindInputListener();
3704
3729
  this.#bindKeydownListener();
3705
- this.#bindPasteListener();
3730
+ this.#bindStrictPasteListener();
3706
3731
  }
3707
3732
  //* 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)
3708
3733
  #handleAndroidPlusKey(inputValue) {
@@ -3713,6 +3738,7 @@ var Iti = class _Iti {
3713
3738
  #handleAndroidStrictReject(inputValue, rejectedInput) {
3714
3739
  const newCaretPos = this.#removeJustTypedChar(inputValue);
3715
3740
  this.#ui.telInputEl.setSelectionRange(newCaretPos, newCaretPos);
3741
+ this.#playStrictRejectAnimation();
3716
3742
  this.#dispatchEvent(EVENTS.STRICT_REJECT, {
3717
3743
  source: "key",
3718
3744
  rejectedInput,
@@ -3772,7 +3798,13 @@ var Iti = class _Iti {
3772
3798
  //* On input event: (1) Update selected country, (2) Format-as-you-type.
3773
3799
  //* Note that this fires AFTER the input is updated.
3774
3800
  #handleInputEvent = (e) => {
3775
- const { strictMode, formatAsYouType, separateDialCode, allowDropdown, countrySearch } = this.#options;
3801
+ const {
3802
+ strictMode,
3803
+ formatAsYouType,
3804
+ separateDialCode,
3805
+ allowDropdown,
3806
+ countrySearch
3807
+ } = this.#options;
3776
3808
  const detail = e?.detail;
3777
3809
  if (detail?.["isCountryChange"]) {
3778
3810
  return;
@@ -3854,6 +3886,7 @@ var Iti = class _Iti {
3854
3886
  const newCountry = this.#resolveCountryChangeFromNumber(newFullNumber);
3855
3887
  const isChangingDialCode = newCountry !== null;
3856
3888
  if (!isAllowedChar || hasExceededMaxLength && !isChangingDialCode && !isInitialPlus) {
3889
+ this.#playStrictRejectAnimation();
3857
3890
  this.#dispatchEvent(EVENTS.STRICT_REJECT, {
3858
3891
  source: "key",
3859
3892
  rejectedInput: e.key,
@@ -3862,15 +3895,16 @@ var Iti = class _Iti {
3862
3895
  e.preventDefault();
3863
3896
  }
3864
3897
  };
3865
- #bindPasteListener() {
3898
+ #bindStrictPasteListener() {
3866
3899
  if (!this.#options.strictMode) {
3867
3900
  return;
3868
3901
  }
3869
- this.#ui.telInputEl.addEventListener("paste", this.#handlePasteEvent, {
3902
+ this.#ui.telInputEl.addEventListener("paste", this.#handleStrictPasteEvent, {
3870
3903
  signal: this.#abortController.signal
3871
3904
  });
3872
3905
  }
3873
- #handlePasteEvent = (e) => {
3906
+ // Handle paste events when strictMode is enabled by sanitising the pasted content before it's inserted into the input, and rejecting it entirely if it would result in an invalid number
3907
+ #handleStrictPasteEvent = (e) => {
3874
3908
  e.preventDefault();
3875
3909
  const input = this.#ui.telInputEl;
3876
3910
  const selStart = input.selectionStart;
@@ -3889,6 +3923,15 @@ var Iti = class _Iti {
3889
3923
  const sanitised = hasLeadingPlus && allowLeadingPlus ? `+${numerics}` : numerics;
3890
3924
  let newValue = before + sanitised + after;
3891
3925
  let rejectReason = sanitised !== pasted ? "invalid" : null;
3926
+ if (newValue.length > 30) {
3927
+ this.#playStrictRejectAnimation();
3928
+ this.#dispatchEvent(EVENTS.STRICT_REJECT, {
3929
+ source: "paste",
3930
+ rejectedInput: pastedRaw,
3931
+ reason: "max-length"
3932
+ });
3933
+ return;
3934
+ }
3892
3935
  if (newValue.length > 5 && intlTelInput.utils) {
3893
3936
  let coreNumber = intlTelInput.utils.getCoreNumber(newValue, iso2);
3894
3937
  while (coreNumber.length === 0 && newValue.length > 0) {
@@ -3896,6 +3939,7 @@ var Iti = class _Iti {
3896
3939
  coreNumber = intlTelInput.utils.getCoreNumber(newValue, iso2);
3897
3940
  }
3898
3941
  if (!coreNumber) {
3942
+ this.#playStrictRejectAnimation();
3899
3943
  this.#dispatchEvent(EVENTS.STRICT_REJECT, {
3900
3944
  source: "paste",
3901
3945
  rejectedInput: pastedRaw,
@@ -3909,6 +3953,7 @@ var Iti = class _Iti {
3909
3953
  newValue = newValue.slice(0, newValue.length - trimLength);
3910
3954
  rejectReason = "max-length";
3911
3955
  } else {
3956
+ this.#playStrictRejectAnimation();
3912
3957
  this.#dispatchEvent(EVENTS.STRICT_REJECT, {
3913
3958
  source: "paste",
3914
3959
  rejectedInput: pastedRaw,
@@ -3923,6 +3968,9 @@ var Iti = class _Iti {
3923
3968
  input.setSelectionRange(caretPos, caretPos);
3924
3969
  input.dispatchEvent(new InputEvent("input", { bubbles: true }));
3925
3970
  if (rejectReason) {
3971
+ if (pasted.length > 0 && sanitised.length === 0) {
3972
+ this.#playStrictRejectAnimation();
3973
+ }
3926
3974
  this.#dispatchEvent(EVENTS.STRICT_REJECT, {
3927
3975
  source: "paste",
3928
3976
  rejectedInput: pastedRaw,
@@ -3935,6 +3983,21 @@ var Iti = class _Iti {
3935
3983
  const max = Number(this.#ui.telInputEl.getAttribute("maxlength"));
3936
3984
  return max && number.length > max ? number.substring(0, max) : number;
3937
3985
  }
3986
+ //* Play the strict-reject animation (shake, or background-colour flash under prefers-reduced-motion) on the wrapper.
3987
+ //* Called when strictMode rejects the whole input (keystroke, or whole paste).
3988
+ //* Uses the wrapper (not the input) so any separateDialCode / country button move together with the input.
3989
+ #playStrictRejectAnimation() {
3990
+ if (!this.#options.strictRejectAnimation) {
3991
+ return;
3992
+ }
3993
+ const wrapperEl = this.#ui.telInputEl.parentElement;
3994
+ if (!wrapperEl) {
3995
+ return;
3996
+ }
3997
+ wrapperEl.classList.remove("iti__strict-reject-animation");
3998
+ void wrapperEl.offsetWidth;
3999
+ wrapperEl.classList.add("iti__strict-reject-animation");
4000
+ }
3938
4001
  //* Trigger a custom event on the input (typed via ItiEventMap).
3939
4002
  #dispatchEvent(name, detailProps = {}) {
3940
4003
  const e = new CustomEvent(name, {
@@ -4571,7 +4634,7 @@ var intlTelInput = Object.assign(
4571
4634
  attachUtils,
4572
4635
  startedLoadingUtils: false,
4573
4636
  startedLoadingAutoCountry: false,
4574
- version: "27.2.1"
4637
+ version: "27.3.1"
4575
4638
  }
4576
4639
  );
4577
4640
  var intlTelInput_default = intlTelInput;