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.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  See the Github Releases page for changelog: https://github.com/jackocnr/intl-tel-input/releases
2
2
 
3
- Or to view a specific version, e.g. v27.2.1, update the URL accordingly, e.g. https://github.com/jackocnr/intl-tel-input/releases/tag/v27.2.1
3
+ Or to view a specific version, e.g. v27.3.1, update the URL accordingly, e.g. https://github.com/jackocnr/intl-tel-input/releases/tag/v27.3.1
4
4
 
5
5
  ## Breaking changes
6
6
 
package/README.md CHANGED
@@ -23,20 +23,31 @@ We provide React, Vue, Angular and Svelte (beta) components alongside the regula
23
23
  We have a newly updated website, where you can find [a full set of docs](https://intl-tel-input.com/docs/integrations), a [live playground](https://intl-tel-input.com/playground/) where you can try out all of the options, as well as plenty of [examples](https://intl-tel-input.com/examples/validation-practical.html) of different setups.
24
24
 
25
25
  ## Features
26
- * Automatically select the user's current country using an IP lookup
27
- * Automatically set the input placeholder to an example number for the selected country
28
- * Navigate the country dropdown by typing a country's name, or using the up/down keys
29
- * Automatically format the number as the user types
30
- * Optionally, only allow numeric characters and cap the number at the maximum valid length
31
- * The user types their national number, and the plugin gives you the full standardised international number
32
- * Number validation, including specific error types
33
- * High-resolution flag images
34
- * Accessibility provided via ARIA tags
35
- * Typescript type definitions included
36
- * Easily customise styles by overriding CSS variables, e.g. support dark mode
37
- * React, Vue, Angular and Svelte components also included
38
- * Translations provided in over 40 languages, as well as support for RTL layout and alternative numeral sets
39
- * Lots of initialisation options for customisation, as well as instance methods/events for interaction
26
+
27
+ 🔍 **Fast country picking**
28
+ * Search by country name or dial code
29
+ * Full keyboard navigation
30
+
31
+ **Smart defaults**
32
+ * Optionally auto-detect the user's country via IP lookup
33
+ * Example placeholders per country
34
+
35
+ 📞 **Formatting & output**
36
+ * Formats the number as the user types
37
+ * Extract standard E.164 numbers to store
38
+
39
+ 🛡️ **Validation**
40
+ * Validate numbers with specific error types
41
+ * Strict mode: only allow valid digits and enforce max length
42
+
43
+ 🌍 **International & accessible**
44
+ * Translated into 40+ languages, with support for RTL and alternative numerals
45
+ * Screen reader-friendly ARIA markup
46
+
47
+ 🎛️ **Customisable**
48
+ * Override CSS variables (e.g. dark mode)
49
+ * Optionally display the dial code next to the number
50
+ * Extensive initialisation options, methods, and events
40
51
 
41
52
  ## Contributing
42
53
  See the [contributing guide](https://github.com/jackocnr/intl-tel-input/blob/master/.github/CONTRIBUTING.md) for instructions on setting up the project and making changes, and also on how to update the flag images, or how to add a new translation.
@@ -45,6 +45,7 @@ declare class IntlTelInput implements AfterViewInit, OnDestroy, OnChanges, Contr
45
45
  separateDialCode?: AllOptions["separateDialCode"];
46
46
  showFlags?: AllOptions["showFlags"];
47
47
  strictMode?: AllOptions["strictMode"];
48
+ strictRejectAnimation?: AllOptions["strictRejectAnimation"];
48
49
  useFullscreenPopup?: AllOptions["useFullscreenPopup"];
49
50
  numberChange: EventEmitter<string>;
50
51
  countryChange: EventEmitter<string>;
@@ -61,6 +62,7 @@ declare class IntlTelInput implements AfterViewInit, OnDestroy, OnChanges, Contr
61
62
  click: EventEmitter<MouseEvent>;
62
63
  private iti?;
63
64
  private appliedInputAttrKeys;
65
+ private pluginInputClasses;
64
66
  private lastEmittedNumber?;
65
67
  private lastEmittedCountry?;
66
68
  private lastEmittedValidity?;
@@ -103,6 +105,6 @@ declare class IntlTelInput implements AfterViewInit, OnDestroy, OnChanges, Contr
103
105
  validate(_control: AbstractControl): ValidationErrors | null;
104
106
  registerOnValidatorChange(fn: () => void): void;
105
107
  static ɵfac: i0.ɵɵFactoryDeclaration<IntlTelInput, never>;
106
- static ɵcmp: i0.ɵɵComponentDeclaration<IntlTelInput, "intl-tel-input", never, { "initialValue": { "alias": "initialValue"; "required": false; }; "usePreciseValidation": { "alias": "usePreciseValidation"; "required": false; }; "inputAttributes": { "alias": "inputAttributes"; "required": false; }; "disabled": { "alias": "disabled"; "required": false; }; "readonly": { "alias": "readonly"; "required": false; }; "allowDropdown": { "alias": "allowDropdown"; "required": false; }; "allowedNumberTypes": { "alias": "allowedNumberTypes"; "required": false; }; "allowNumberExtensions": { "alias": "allowNumberExtensions"; "required": false; }; "allowPhonewords": { "alias": "allowPhonewords"; "required": false; }; "autoPlaceholder": { "alias": "autoPlaceholder"; "required": false; }; "containerClass": { "alias": "containerClass"; "required": false; }; "countryNameLocale": { "alias": "countryNameLocale"; "required": false; }; "countryOrder": { "alias": "countryOrder"; "required": false; }; "countrySearch": { "alias": "countrySearch"; "required": false; }; "customPlaceholder": { "alias": "customPlaceholder"; "required": false; }; "dropdownAlwaysOpen": { "alias": "dropdownAlwaysOpen"; "required": false; }; "dropdownContainer": { "alias": "dropdownContainer"; "required": false; }; "excludeCountries": { "alias": "excludeCountries"; "required": false; }; "fixDropdownWidth": { "alias": "fixDropdownWidth"; "required": false; }; "formatAsYouType": { "alias": "formatAsYouType"; "required": false; }; "formatOnDisplay": { "alias": "formatOnDisplay"; "required": false; }; "geoIpLookup": { "alias": "geoIpLookup"; "required": false; }; "hiddenInput": { "alias": "hiddenInput"; "required": false; }; "i18n": { "alias": "i18n"; "required": false; }; "initialCountry": { "alias": "initialCountry"; "required": false; }; "loadUtils": { "alias": "loadUtils"; "required": false; }; "nationalMode": { "alias": "nationalMode"; "required": false; }; "onlyCountries": { "alias": "onlyCountries"; "required": false; }; "placeholderNumberType": { "alias": "placeholderNumberType"; "required": false; }; "searchInputClass": { "alias": "searchInputClass"; "required": false; }; "separateDialCode": { "alias": "separateDialCode"; "required": false; }; "showFlags": { "alias": "showFlags"; "required": false; }; "strictMode": { "alias": "strictMode"; "required": false; }; "useFullscreenPopup": { "alias": "useFullscreenPopup"; "required": false; }; }, { "numberChange": "numberChange"; "countryChange": "countryChange"; "validityChange": "validityChange"; "errorCodeChange": "errorCodeChange"; "openCountryDropdown": "openCountryDropdown"; "closeCountryDropdown": "closeCountryDropdown"; "strictReject": "strictReject"; "blur": "blur"; "focus": "focus"; "keydown": "keydown"; "keyup": "keyup"; "paste": "paste"; "click": "click"; }, never, never, true, never>;
108
+ static ɵcmp: i0.ɵɵComponentDeclaration<IntlTelInput, "intl-tel-input", never, { "initialValue": { "alias": "initialValue"; "required": false; }; "usePreciseValidation": { "alias": "usePreciseValidation"; "required": false; }; "inputAttributes": { "alias": "inputAttributes"; "required": false; }; "disabled": { "alias": "disabled"; "required": false; }; "readonly": { "alias": "readonly"; "required": false; }; "allowDropdown": { "alias": "allowDropdown"; "required": false; }; "allowedNumberTypes": { "alias": "allowedNumberTypes"; "required": false; }; "allowNumberExtensions": { "alias": "allowNumberExtensions"; "required": false; }; "allowPhonewords": { "alias": "allowPhonewords"; "required": false; }; "autoPlaceholder": { "alias": "autoPlaceholder"; "required": false; }; "containerClass": { "alias": "containerClass"; "required": false; }; "countryNameLocale": { "alias": "countryNameLocale"; "required": false; }; "countryOrder": { "alias": "countryOrder"; "required": false; }; "countrySearch": { "alias": "countrySearch"; "required": false; }; "customPlaceholder": { "alias": "customPlaceholder"; "required": false; }; "dropdownAlwaysOpen": { "alias": "dropdownAlwaysOpen"; "required": false; }; "dropdownContainer": { "alias": "dropdownContainer"; "required": false; }; "excludeCountries": { "alias": "excludeCountries"; "required": false; }; "fixDropdownWidth": { "alias": "fixDropdownWidth"; "required": false; }; "formatAsYouType": { "alias": "formatAsYouType"; "required": false; }; "formatOnDisplay": { "alias": "formatOnDisplay"; "required": false; }; "geoIpLookup": { "alias": "geoIpLookup"; "required": false; }; "hiddenInput": { "alias": "hiddenInput"; "required": false; }; "i18n": { "alias": "i18n"; "required": false; }; "initialCountry": { "alias": "initialCountry"; "required": false; }; "loadUtils": { "alias": "loadUtils"; "required": false; }; "nationalMode": { "alias": "nationalMode"; "required": false; }; "onlyCountries": { "alias": "onlyCountries"; "required": false; }; "placeholderNumberType": { "alias": "placeholderNumberType"; "required": false; }; "searchInputClass": { "alias": "searchInputClass"; "required": false; }; "separateDialCode": { "alias": "separateDialCode"; "required": false; }; "showFlags": { "alias": "showFlags"; "required": false; }; "strictMode": { "alias": "strictMode"; "required": false; }; "strictRejectAnimation": { "alias": "strictRejectAnimation"; "required": false; }; "useFullscreenPopup": { "alias": "useFullscreenPopup"; "required": false; }; }, { "numberChange": "numberChange"; "countryChange": "countryChange"; "validityChange": "validityChange"; "errorCodeChange": "errorCodeChange"; "openCountryDropdown": "openCountryDropdown"; "closeCountryDropdown": "closeCountryDropdown"; "strictReject": "strictReject"; "blur": "blur"; "focus": "focus"; "keydown": "keydown"; "keyup": "keyup"; "paste": "paste"; "click": "click"; }, never, never, true, never>;
107
109
  }
108
110
  export default IntlTelInput;
@@ -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;
@@ -4621,6 +4684,7 @@ var IntlTelInput = class _IntlTelInput {
4621
4684
  separateDialCode;
4622
4685
  showFlags;
4623
4686
  strictMode;
4687
+ strictRejectAnimation;
4624
4688
  useFullscreenPopup;
4625
4689
  numberChange = new EventEmitter();
4626
4690
  countryChange = new EventEmitter();
@@ -4637,6 +4701,8 @@ var IntlTelInput = class _IntlTelInput {
4637
4701
  click = new EventEmitter();
4638
4702
  iti;
4639
4703
  appliedInputAttrKeys = /* @__PURE__ */ new Set();
4704
+ // Classes the plugin adds directly to the input (e.g. iti__tel-input)
4705
+ pluginInputClasses = "";
4640
4706
  lastEmittedNumber;
4641
4707
  lastEmittedCountry;
4642
4708
  lastEmittedValidity;
@@ -4661,6 +4727,7 @@ var IntlTelInput = class _IntlTelInput {
4661
4727
  };
4662
4728
  ngAfterViewInit() {
4663
4729
  this.iti = intlTelInput_default(this.inputRef.nativeElement, this.buildInitOptions());
4730
+ this.pluginInputClasses = this.inputRef.nativeElement.className;
4664
4731
  this.inputRef.nativeElement.addEventListener("open:countrydropdown", this.handleOpenDropdown);
4665
4732
  this.inputRef.nativeElement.addEventListener("close:countrydropdown", this.handleCloseDropdown);
4666
4733
  this.inputRef.nativeElement.addEventListener("strict:reject", this.handleStrictReject);
@@ -4719,6 +4786,7 @@ var IntlTelInput = class _IntlTelInput {
4719
4786
  separateDialCode: this.separateDialCode,
4720
4787
  showFlags: this.showFlags,
4721
4788
  strictMode: this.strictMode,
4789
+ strictRejectAnimation: this.strictRejectAnimation,
4722
4790
  useFullscreenPopup: this.useFullscreenPopup
4723
4791
  };
4724
4792
  return Object.fromEntries(Object.entries(options).filter(([, value]) => value !== void 0));
@@ -4824,7 +4892,8 @@ var IntlTelInput = class _IntlTelInput {
4824
4892
  warnInputAttr(key);
4825
4893
  } else {
4826
4894
  currentKeys.add(key);
4827
- this.inputRef.nativeElement.setAttribute(key, value);
4895
+ const next = key === "class" && this.pluginInputClasses ? `${this.pluginInputClasses} ${value}` : value;
4896
+ this.inputRef.nativeElement.setAttribute(key, next);
4828
4897
  }
4829
4898
  });
4830
4899
  this.appliedInputAttrKeys.forEach((key) => {
@@ -4885,7 +4954,7 @@ var IntlTelInput = class _IntlTelInput {
4885
4954
  let _t;
4886
4955
  i0.\u0275\u0275queryRefresh(_t = i0.\u0275\u0275loadQuery()) && (ctx.inputRef = _t.first);
4887
4956
  }
4888
- }, inputs: { initialValue: "initialValue", usePreciseValidation: "usePreciseValidation", inputAttributes: "inputAttributes", disabled: "disabled", readonly: "readonly", allowDropdown: "allowDropdown", allowedNumberTypes: "allowedNumberTypes", allowNumberExtensions: "allowNumberExtensions", allowPhonewords: "allowPhonewords", autoPlaceholder: "autoPlaceholder", containerClass: "containerClass", countryNameLocale: "countryNameLocale", countryOrder: "countryOrder", countrySearch: "countrySearch", customPlaceholder: "customPlaceholder", dropdownAlwaysOpen: "dropdownAlwaysOpen", dropdownContainer: "dropdownContainer", excludeCountries: "excludeCountries", fixDropdownWidth: "fixDropdownWidth", formatAsYouType: "formatAsYouType", formatOnDisplay: "formatOnDisplay", geoIpLookup: "geoIpLookup", hiddenInput: "hiddenInput", i18n: "i18n", initialCountry: "initialCountry", loadUtils: "loadUtils", nationalMode: "nationalMode", onlyCountries: "onlyCountries", placeholderNumberType: "placeholderNumberType", searchInputClass: "searchInputClass", separateDialCode: "separateDialCode", showFlags: "showFlags", strictMode: "strictMode", useFullscreenPopup: "useFullscreenPopup" }, outputs: { numberChange: "numberChange", countryChange: "countryChange", validityChange: "validityChange", errorCodeChange: "errorCodeChange", openCountryDropdown: "openCountryDropdown", closeCountryDropdown: "closeCountryDropdown", strictReject: "strictReject", blur: "blur", focus: "focus", keydown: "keydown", keyup: "keyup", paste: "paste", click: "click" }, features: [i0.\u0275\u0275ProvidersFeature([
4957
+ }, inputs: { initialValue: "initialValue", usePreciseValidation: "usePreciseValidation", inputAttributes: "inputAttributes", disabled: "disabled", readonly: "readonly", allowDropdown: "allowDropdown", allowedNumberTypes: "allowedNumberTypes", allowNumberExtensions: "allowNumberExtensions", allowPhonewords: "allowPhonewords", autoPlaceholder: "autoPlaceholder", containerClass: "containerClass", countryNameLocale: "countryNameLocale", countryOrder: "countryOrder", countrySearch: "countrySearch", customPlaceholder: "customPlaceholder", dropdownAlwaysOpen: "dropdownAlwaysOpen", dropdownContainer: "dropdownContainer", excludeCountries: "excludeCountries", fixDropdownWidth: "fixDropdownWidth", formatAsYouType: "formatAsYouType", formatOnDisplay: "formatOnDisplay", geoIpLookup: "geoIpLookup", hiddenInput: "hiddenInput", i18n: "i18n", initialCountry: "initialCountry", loadUtils: "loadUtils", nationalMode: "nationalMode", onlyCountries: "onlyCountries", placeholderNumberType: "placeholderNumberType", searchInputClass: "searchInputClass", separateDialCode: "separateDialCode", showFlags: "showFlags", strictMode: "strictMode", strictRejectAnimation: "strictRejectAnimation", useFullscreenPopup: "useFullscreenPopup" }, outputs: { numberChange: "numberChange", countryChange: "countryChange", validityChange: "validityChange", errorCodeChange: "errorCodeChange", openCountryDropdown: "openCountryDropdown", closeCountryDropdown: "closeCountryDropdown", strictReject: "strictReject", blur: "blur", focus: "focus", keydown: "keydown", keyup: "keyup", paste: "paste", click: "click" }, features: [i0.\u0275\u0275ProvidersFeature([
4889
4958
  {
4890
4959
  provide: NG_VALUE_ACCESSOR,
4891
4960
  useExisting: forwardRef(() => _IntlTelInput),
@@ -5019,6 +5088,8 @@ var IntlTelInput = class _IntlTelInput {
5019
5088
  type: Input
5020
5089
  }], strictMode: [{
5021
5090
  type: Input
5091
+ }], strictRejectAnimation: [{
5092
+ type: Input
5022
5093
  }], useFullscreenPopup: [{
5023
5094
  type: Input
5024
5095
  }], numberChange: [{