intl-tel-input 29.1.3 → 29.2.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.
@@ -1761,6 +1761,29 @@ var EVENTS = {
1761
1761
  // used for synthetic input trigger
1762
1762
  STRICT_REJECT: "strict:reject"
1763
1763
  };
1764
+ var ITI_SLOTS = [
1765
+ "container",
1766
+ "input",
1767
+ "countryContainer",
1768
+ "selectedCountry",
1769
+ "selectedCountryPrimary",
1770
+ "selectedFlag",
1771
+ "arrow",
1772
+ "selectedDialCode",
1773
+ "countrySelector",
1774
+ "countrySelectorContainer",
1775
+ "searchWrapper",
1776
+ "searchIcon",
1777
+ "searchInput",
1778
+ "searchClear",
1779
+ "countryList",
1780
+ "countryListItem",
1781
+ "countryListItemFlag",
1782
+ "countryName",
1783
+ "dialCode",
1784
+ "countryCheck",
1785
+ "noResults"
1786
+ ];
1764
1787
  var CLASSES = {
1765
1788
  HIDE: "iti__hide",
1766
1789
  V_HIDE: "iti__v-hide",
@@ -1818,6 +1841,7 @@ var DIAL_CODE = {
1818
1841
  NANP: "1"
1819
1842
  // North American Numbering Plan
1820
1843
  };
1844
+ var E164_MAX_DIGITS = 15;
1821
1845
  var UK = {
1822
1846
  ISO2: "gb",
1823
1847
  DIAL_CODE: "44",
@@ -1937,6 +1961,8 @@ var defaults = {
1937
1961
  allowNumberExtensions: false,
1938
1962
  // Allow alphanumeric "phonewords" (e.g. +1 800 FLOWERS) as valid numbers
1939
1963
  allowPhonewords: false,
1964
+ //* Add custom classes to the elements we generate, keyed by slot name e.g. { selectedCountry: "rounded-l-lg" }.
1965
+ classNames: {},
1940
1966
  //* Add a custom class to the (injected) container element.
1941
1967
  containerClass: "",
1942
1968
  //* Locale for localising country names via Intl.DisplayNames.
@@ -1999,6 +2025,7 @@ var isElLike = (val) => {
1999
2025
  return v.nodeType === 1 && typeof v.tagName === "string" && typeof v.appendChild === "function";
2000
2026
  };
2001
2027
  var placeholderPolicySet = new Set(Object.values(PLACEHOLDER_POLICY));
2028
+ var slotSet = new Set(ITI_SLOTS);
2002
2029
  var warn = (message) => {
2003
2030
  console.warn(`[intl-tel-input] ${message}`);
2004
2031
  };
@@ -2099,6 +2126,26 @@ var validateOptions = (customOptions) => {
2099
2126
  }
2100
2127
  validatedOptions[key] = value;
2101
2128
  break;
2129
+ case "classNames": {
2130
+ if (!isPlainObject(value)) {
2131
+ warnOption("classNames", "an object", value);
2132
+ break;
2133
+ }
2134
+ const validSlots = {};
2135
+ for (const [slot, slotValue] of Object.entries(value)) {
2136
+ if (!slotSet.has(slot)) {
2137
+ warn(
2138
+ `Unknown slot '${slot}' in 'classNames'. Valid slots: ${ITI_SLOTS.join(", ")}. Skipping.`
2139
+ );
2140
+ } else if (typeof slotValue !== "string") {
2141
+ warnOption(`classNames.${slot}`, "a string", slotValue);
2142
+ } else {
2143
+ validSlots[slot] = slotValue.trim().replace(/\s+/g, " ");
2144
+ }
2145
+ }
2146
+ validatedOptions[key] = validSlots;
2147
+ break;
2148
+ }
2102
2149
  case "countryOrder": {
2103
2150
  if (value === null) {
2104
2151
  validatedOptions[key] = value;
@@ -2511,11 +2558,18 @@ var UI = class {
2511
2558
  );
2512
2559
  }
2513
2560
  }
2561
+ //* Append any consumer-supplied classes (via the classNames option) for the given slot to our own classes for that element.
2562
+ #withSlotClass(slot, ourClasses) {
2563
+ const custom = this.#options.classNames[slot];
2564
+ return custom ? `${ourClasses} ${custom}` : ourClasses;
2565
+ }
2514
2566
  //* Generate all of the markup for the core library: the selected country overlay, and the country selector.
2515
2567
  buildMarkup(countries, searchTokens) {
2516
2568
  this.#countries = countries;
2517
2569
  this.#searchTokens = searchTokens;
2518
- this.telInputEl.classList.add("iti__tel-input");
2570
+ this.telInputEl.classList.add(
2571
+ ...this.#withSlotClass("input", "iti__tel-input").split(" ")
2572
+ );
2519
2573
  if (!this.telInputEl.hasAttribute("type")) {
2520
2574
  this.telInputEl.setAttribute("type", "tel");
2521
2575
  }
@@ -2543,7 +2597,9 @@ var UI = class {
2543
2597
  "iti--inline-country-selector": countrySelectorMode !== COUNTRY_SELECTOR_MODE.FULLSCREEN,
2544
2598
  [containerClass]: Boolean(containerClass)
2545
2599
  });
2546
- const wrapper = createEl("div", { class: parentClasses });
2600
+ const wrapper = createEl("div", {
2601
+ class: this.#withSlotClass("container", parentClasses)
2602
+ });
2547
2603
  if (this.#isRTL) {
2548
2604
  wrapper.setAttribute("dir", "ltr");
2549
2605
  }
@@ -2559,7 +2615,12 @@ var UI = class {
2559
2615
  this.#countryContainerEl = createEl(
2560
2616
  "div",
2561
2617
  // visibly hidden until we measure its width to set the input padding correctly
2562
- { class: `iti__country-container ${CLASSES.V_HIDE}` },
2618
+ {
2619
+ class: this.#withSlotClass(
2620
+ "countryContainer",
2621
+ `iti__country-container ${CLASSES.V_HIDE}`
2622
+ )
2623
+ },
2563
2624
  wrapper
2564
2625
  );
2565
2626
  if (enableCountrySelector) {
@@ -2567,7 +2628,7 @@ var UI = class {
2567
2628
  "button",
2568
2629
  {
2569
2630
  type: "button",
2570
- class: "iti__selected-country",
2631
+ class: this.#withSlotClass("selectedCountry", "iti__selected-country"),
2571
2632
  [ARIA.EXPANDED]: "false",
2572
2633
  [ARIA.LABEL]: this.#options.uiTranslations.noCountrySelected,
2573
2634
  [ARIA.HASPOPUP]: "dialog",
@@ -2581,31 +2642,44 @@ var UI = class {
2581
2642
  } else {
2582
2643
  this.#selectedCountryEl = createEl(
2583
2644
  "div",
2584
- { class: "iti__selected-country" },
2645
+ { class: this.#withSlotClass("selectedCountry", "iti__selected-country") },
2585
2646
  this.#countryContainerEl
2586
2647
  );
2587
2648
  }
2588
2649
  const selectedCountryPrimary = createEl(
2589
2650
  "div",
2590
- { class: "iti__selected-country-primary" },
2651
+ {
2652
+ class: this.#withSlotClass(
2653
+ "selectedCountryPrimary",
2654
+ "iti__selected-country-primary"
2655
+ )
2656
+ },
2591
2657
  this.#selectedCountryEl
2592
2658
  );
2593
2659
  this.#selectedFlagEl = createEl(
2594
2660
  "div",
2595
- { class: CLASSES.FLAG },
2661
+ { class: this.#withSlotClass("selectedFlag", CLASSES.FLAG) },
2596
2662
  selectedCountryPrimary
2597
2663
  );
2598
2664
  if (enableCountrySelector) {
2599
2665
  this.#arrowEl = createEl(
2600
2666
  "div",
2601
- { class: "iti__arrow", [ARIA.HIDDEN]: "true" },
2667
+ {
2668
+ class: this.#withSlotClass("arrow", "iti__arrow"),
2669
+ [ARIA.HIDDEN]: "true"
2670
+ },
2602
2671
  selectedCountryPrimary
2603
2672
  );
2604
2673
  }
2605
2674
  if (separateDialCode) {
2606
2675
  this.#selectedDialCodeEl = createEl(
2607
2676
  "div",
2608
- { class: "iti__selected-dial-code" },
2677
+ {
2678
+ class: this.#withSlotClass(
2679
+ "selectedDialCode",
2680
+ "iti__selected-dial-code"
2681
+ )
2682
+ },
2609
2683
  this.#selectedCountryEl
2610
2684
  );
2611
2685
  }
@@ -2636,7 +2710,10 @@ var UI = class {
2636
2710
  const extraClasses = matchDropdownWidth ? "" : "iti--flexible-dropdown-width";
2637
2711
  this.#countrySelectorEl = createEl("div", {
2638
2712
  id: `iti-${this.#id}__country-selector`,
2639
- class: `iti__country-selector ${CLASSES.HIDE} ${extraClasses}`,
2713
+ class: this.#withSlotClass(
2714
+ "countrySelector",
2715
+ `iti__country-selector ${CLASSES.HIDE} ${extraClasses}`
2716
+ ),
2640
2717
  role: "dialog",
2641
2718
  [ARIA.MODAL]: "true"
2642
2719
  });
@@ -2649,7 +2726,7 @@ var UI = class {
2649
2726
  this.#countryListEl = createEl(
2650
2727
  "ul",
2651
2728
  {
2652
- class: "iti__country-list",
2729
+ class: this.#withSlotClass("countryList", "iti__country-list"),
2653
2730
  id: `iti-${this.#id}__country-listbox`,
2654
2731
  role: "listbox",
2655
2732
  [ARIA.LABEL]: uiTranslations.countryListAriaLabel
@@ -2668,7 +2745,9 @@ var UI = class {
2668
2745
  "iti--inline-country-selector": !isFullscreen,
2669
2746
  [containerClass]: Boolean(containerClass)
2670
2747
  });
2671
- this.#detachedCountrySelectorEl = createEl("div", { class: wrapperClasses });
2748
+ this.#detachedCountrySelectorEl = createEl("div", {
2749
+ class: this.#withSlotClass("countrySelectorContainer", wrapperClasses)
2750
+ });
2672
2751
  this.#detachedCountrySelectorEl.appendChild(this.#countrySelectorEl);
2673
2752
  } else {
2674
2753
  this.#countryContainerEl.appendChild(this.#countrySelectorEl);
@@ -2689,13 +2768,13 @@ var UI = class {
2689
2768
  const { uiTranslations, searchInputClass } = this.#options;
2690
2769
  const searchWrapper = createEl(
2691
2770
  "div",
2692
- { class: "iti__search-input-wrapper" },
2771
+ { class: this.#withSlotClass("searchWrapper", "iti__search-input-wrapper") },
2693
2772
  this.#countrySelectorEl
2694
2773
  );
2695
2774
  this.#searchIconEl = createEl(
2696
2775
  "span",
2697
2776
  {
2698
- class: "iti__search-icon",
2777
+ class: this.#withSlotClass("searchIcon", "iti__search-icon"),
2699
2778
  [ARIA.HIDDEN]: "true"
2700
2779
  },
2701
2780
  searchWrapper
@@ -2707,7 +2786,10 @@ var UI = class {
2707
2786
  id: `iti-${this.#id}__search-input`,
2708
2787
  // Chrome says inputs need either a name or an id
2709
2788
  type: "search",
2710
- class: `iti__search-input ${searchInputClass}`,
2789
+ class: this.#withSlotClass(
2790
+ "searchInput",
2791
+ `iti__search-input ${searchInputClass}`
2792
+ ),
2711
2793
  placeholder: uiTranslations.searchPlaceholder,
2712
2794
  // 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
2713
2795
  role: "combobox",
@@ -2723,7 +2805,10 @@ var UI = class {
2723
2805
  "button",
2724
2806
  {
2725
2807
  type: "button",
2726
- class: `iti__search-clear ${CLASSES.HIDE}`,
2808
+ class: this.#withSlotClass(
2809
+ "searchClear",
2810
+ `iti__search-clear ${CLASSES.HIDE}`
2811
+ ),
2727
2812
  [ARIA.LABEL]: uiTranslations.clearSearchAriaLabel,
2728
2813
  tabindex: "-1"
2729
2814
  },
@@ -2738,7 +2823,7 @@ var UI = class {
2738
2823
  this.#noResultsMessageEl = createEl(
2739
2824
  "div",
2740
2825
  {
2741
- class: `iti__no-results ${CLASSES.HIDE}`,
2826
+ class: this.#withSlotClass("noResults", `iti__no-results ${CLASSES.HIDE}`),
2742
2827
  [ARIA.HIDDEN]: "true"
2743
2828
  // all a11y messaging happens in this.#searchResultsLiveRegionEl
2744
2829
  },
@@ -2792,11 +2877,9 @@ var UI = class {
2792
2877
  //* For each country: add a country list item <li> to the countryList <ul> container.
2793
2878
  #appendListItems() {
2794
2879
  const frag = document.createDocumentFragment();
2880
+ const liClass = this.#withSlotClass("countryListItem", CLASSES.COUNTRY_ITEM);
2795
2881
  for (let i = 0; i < this.#countries.length; i++) {
2796
2882
  const c = this.#countries[i];
2797
- const liClass = buildClassNames({
2798
- [CLASSES.COUNTRY_ITEM]: true
2799
- });
2800
2883
  const listItem = createEl("li", {
2801
2884
  id: `iti-${this.#id}__item-${c.iso2}`,
2802
2885
  class: liClass,
@@ -2808,11 +2891,28 @@ var UI = class {
2808
2891
  listItem.dataset[DATA_KEYS.ISO2] = c.iso2;
2809
2892
  this.#listItemByIso2.set(c.iso2, listItem);
2810
2893
  if (this.#options.showFlags) {
2811
- createEl("div", { class: `${CLASSES.FLAG} iti__${c.iso2}` }, listItem);
2894
+ createEl(
2895
+ "div",
2896
+ {
2897
+ class: this.#withSlotClass(
2898
+ "countryListItemFlag",
2899
+ `${CLASSES.FLAG} iti__${c.iso2}`
2900
+ )
2901
+ },
2902
+ listItem
2903
+ );
2812
2904
  }
2813
- const nameEl = createEl("span", { class: "iti__country-name" }, listItem);
2905
+ const nameEl = createEl(
2906
+ "span",
2907
+ { class: this.#withSlotClass("countryName", "iti__country-name") },
2908
+ listItem
2909
+ );
2814
2910
  nameEl.textContent = `${c.name} `;
2815
- const dialEl = createEl("span", { class: "iti__dial-code" }, nameEl);
2911
+ const dialEl = createEl(
2912
+ "span",
2913
+ { class: this.#withSlotClass("dialCode", "iti__dial-code") },
2914
+ nameEl
2915
+ );
2816
2916
  if (this.#isRTL) {
2817
2917
  dialEl.setAttribute("dir", "ltr");
2818
2918
  }
@@ -3259,7 +3359,10 @@ var UI = class {
3259
3359
  newListItem.setAttribute(ARIA.SELECTED, "true");
3260
3360
  const checkIcon = createEl(
3261
3361
  "span",
3262
- { class: "iti__country-check", [ARIA.HIDDEN]: "true" },
3362
+ {
3363
+ class: this.#withSlotClass("countryCheck", "iti__country-check"),
3364
+ [ARIA.HIDDEN]: "true"
3365
+ },
3263
3366
  newListItem
3264
3367
  );
3265
3368
  checkIcon.appendChild(buildCheckIcon());
@@ -3446,7 +3549,10 @@ var UI = class {
3446
3549
  this.#updateSelectedListItem(iso2);
3447
3550
  }
3448
3551
  if (this.#selectedCountryEl) {
3449
- const flagClass = iso2 && showFlags ? `${CLASSES.FLAG} iti__${iso2}` : `${CLASSES.FLAG} ${CLASSES.GLOBE}`;
3552
+ const flagClass = this.#withSlotClass(
3553
+ "selectedFlag",
3554
+ iso2 && showFlags ? `${CLASSES.FLAG} iti__${iso2}` : `${CLASSES.FLAG} ${CLASSES.GLOBE}`
3555
+ );
3450
3556
  let ariaLabel, title;
3451
3557
  let flagContent = null;
3452
3558
  if (iso2) {
@@ -4076,8 +4182,8 @@ var Iti = class _Iti {
4076
4182
  const after = inputValue.slice(selEnd ?? void 0);
4077
4183
  const newValue = before + normalisedKey + after;
4078
4184
  const newFullNumber = this.#buildFullNumber(newValue);
4079
- let hasExceededMaxLength = false;
4080
- if (intlTelInput.utils && this.#maxCoreNumberLength) {
4185
+ let hasExceededMaxLength = getNumeric(newFullNumber).length > E164_MAX_DIGITS;
4186
+ if (!hasExceededMaxLength && intlTelInput.utils && this.#maxCoreNumberLength) {
4081
4187
  const coreNumber = intlTelInput.utils.getCoreNumber(
4082
4188
  newFullNumber,
4083
4189
  this.#selectedCountry?.iso2
@@ -4142,44 +4248,35 @@ var Iti = class _Iti {
4142
4248
  let newValue = before + sanitised + after;
4143
4249
  let rejectReason = sanitised !== pasted ? "invalid" : null;
4144
4250
  if (newValue.length > 30) {
4145
- this.#ui.playStrictRejectAnimation();
4146
- this.#dispatchEvent(EVENTS.STRICT_REJECT, {
4147
- source: "paste",
4148
- rejectedInput: pastedRaw,
4149
- reason: "max-length"
4150
- });
4151
- this.#restoreValueBeforeStrictPaste(pasteSnapshot);
4251
+ this.#rejectStrictPasteAsTooLong(pasteSnapshot);
4152
4252
  return true;
4153
4253
  }
4154
- if (newValue.length > 5 && intlTelInput.utils) {
4254
+ const excessDigits = getNumeric(this.#buildFullNumber(newValue)).length - E164_MAX_DIGITS;
4255
+ if (excessDigits > 0) {
4256
+ if (selEnd !== originalValue.length) {
4257
+ this.#rejectStrictPasteAsTooLong(pasteSnapshot);
4258
+ return true;
4259
+ }
4260
+ newValue = newValue.slice(0, newValue.length - excessDigits);
4261
+ rejectReason = "max-length";
4262
+ }
4263
+ if (this.#maxCoreNumberLength && newValue.length > 5 && intlTelInput.utils) {
4155
4264
  let coreNumber = intlTelInput.utils.getCoreNumber(newValue, iso2);
4156
4265
  while (coreNumber.length === 0 && newValue.length > 0) {
4157
4266
  newValue = newValue.slice(0, -1);
4158
4267
  coreNumber = intlTelInput.utils.getCoreNumber(newValue, iso2);
4159
4268
  }
4160
4269
  if (!coreNumber) {
4161
- this.#ui.playStrictRejectAnimation();
4162
- this.#dispatchEvent(EVENTS.STRICT_REJECT, {
4163
- source: "paste",
4164
- rejectedInput: pastedRaw,
4165
- reason: "max-length"
4166
- });
4167
- this.#restoreValueBeforeStrictPaste(pasteSnapshot);
4270
+ this.#rejectStrictPasteAsTooLong(pasteSnapshot);
4168
4271
  return true;
4169
4272
  }
4170
- if (this.#maxCoreNumberLength && coreNumber.length > this.#maxCoreNumberLength) {
4273
+ if (coreNumber.length > this.#maxCoreNumberLength) {
4171
4274
  if (selEnd === originalValue.length) {
4172
4275
  const trimLength = coreNumber.length - this.#maxCoreNumberLength;
4173
4276
  newValue = newValue.slice(0, newValue.length - trimLength);
4174
4277
  rejectReason = "max-length";
4175
4278
  } else {
4176
- this.#ui.playStrictRejectAnimation();
4177
- this.#dispatchEvent(EVENTS.STRICT_REJECT, {
4178
- source: "paste",
4179
- rejectedInput: pastedRaw,
4180
- reason: "max-length"
4181
- });
4182
- this.#restoreValueBeforeStrictPaste(pasteSnapshot);
4279
+ this.#rejectStrictPasteAsTooLong(pasteSnapshot);
4183
4280
  return true;
4184
4281
  }
4185
4282
  }
@@ -4199,6 +4296,16 @@ var Iti = class _Iti {
4199
4296
  }
4200
4297
  return false;
4201
4298
  }
4299
+ // Reject a paste entirely because it would exceed the max length, restoring the previous value.
4300
+ #rejectStrictPasteAsTooLong(pasteSnapshot) {
4301
+ this.#ui.playStrictRejectAnimation();
4302
+ this.#dispatchEvent(EVENTS.STRICT_REJECT, {
4303
+ source: "paste",
4304
+ rejectedInput: pasteSnapshot.pastedRaw,
4305
+ reason: "max-length"
4306
+ });
4307
+ this.#restoreValueBeforeStrictPaste(pasteSnapshot);
4308
+ }
4202
4309
  #restoreValueBeforeStrictPaste(pasteSnapshot) {
4203
4310
  this.#setTelInputValue(pasteSnapshot.value);
4204
4311
  this.#ui.telInputEl.setSelectionRange(
@@ -4307,6 +4414,9 @@ var Iti = class _Iti {
4307
4414
  if (currentDial && currentDial.startsWith(numeric)) {
4308
4415
  return null;
4309
4416
  }
4417
+ if (!selectedIso2) {
4418
+ return null;
4419
+ }
4310
4420
  return "";
4311
4421
  } else if ((!number || number === "+") && !selectedIso2 && this.#fallbackCountryIso2) {
4312
4422
  return this.#fallbackCountryIso2;
@@ -4843,7 +4953,7 @@ var intlTelInput = Object.assign(
4843
4953
  attachUtils,
4844
4954
  startedLoadingUtils: false,
4845
4955
  startedLoadingAutoCountry: false,
4846
- version: "29.1.3",
4956
+ version: "29.2.1",
4847
4957
  NUMBER_FORMAT,
4848
4958
  NUMBER_TYPE,
4849
4959
  VALIDATION_ERROR,