intl-tel-input 18.3.5 → 18.5.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.
- package/README.md +10 -2
- package/build/css/intlTelInput.css +38 -29
- package/build/css/intlTelInput.min.css +1 -1
- package/build/js/data.js +1 -1
- package/build/js/data.min.js +1 -1
- package/build/js/intlTelInput-jquery.js +156 -55
- package/build/js/intlTelInput-jquery.min.js +3 -3
- package/build/js/intlTelInput.js +156 -55
- package/build/js/intlTelInput.min.js +3 -3
- package/composer.json +1 -1
- package/demo.html +7 -1
- package/grunt/tmpl/sprite.scss.mustache +1 -1
- package/package.json +1 -1
- package/src/css/intlTelInput.scss +35 -27
- package/src/css/sprite.scss +1 -1
- package/src/js/intlTelInput.js +194 -70
- package/src/spec/tests/core/dropdownShortcuts.js +7 -8
- package/src/spec/tests/options/useFullscreenPopup.js +13 -25
package/demo.html
CHANGED
|
@@ -21,8 +21,14 @@
|
|
|
21
21
|
// allowDropdown: false,
|
|
22
22
|
// autoInsertDialCode: true,
|
|
23
23
|
// autoPlaceholder: "off",
|
|
24
|
-
//
|
|
24
|
+
// countrySearch: true,
|
|
25
|
+
// customContainer: "test",
|
|
26
|
+
// customPlaceholder: function(selectedCountryPlaceholder, selectedCountryData) {
|
|
27
|
+
// return "e.g. " + selectedCountryPlaceholder;
|
|
28
|
+
// },
|
|
29
|
+
// dropdownContainer: document.querySelector('#custom-container'),
|
|
25
30
|
// excludeCountries: ["us"],
|
|
31
|
+
// fixDropdownWidth: true,
|
|
26
32
|
// formatOnDisplay: false,
|
|
27
33
|
// geoIpLookup: function(callback) {
|
|
28
34
|
// fetch("https://ipapi.co/json")
|
package/package.json
CHANGED
|
@@ -60,9 +60,9 @@ $mobilePopupMargin: 30px !default;
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
// specify types to increase specificity e.g. to override bootstrap v2.3
|
|
63
|
-
input,
|
|
64
|
-
input[type="text"],
|
|
65
|
-
input[type="tel"] {
|
|
63
|
+
input.iti__tel-input,
|
|
64
|
+
input.iti__tel-input[type="text"],
|
|
65
|
+
input.iti__tel-input[type="tel"] {
|
|
66
66
|
position: relative;
|
|
67
67
|
// input is bottom level, below selected flag and dropdown
|
|
68
68
|
z-index: 0;
|
|
@@ -129,35 +129,24 @@ $mobilePopupMargin: 30px !default;
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
// the dropdown
|
|
132
|
-
&
|
|
132
|
+
&__dropdown-content {
|
|
133
133
|
position: absolute;
|
|
134
134
|
// popup so render above everything else
|
|
135
135
|
z-index: 2;
|
|
136
136
|
|
|
137
|
-
// override default list styles
|
|
138
|
-
list-style: none;
|
|
139
|
-
|
|
140
137
|
// place menu above the input element
|
|
141
138
|
&--dropup {
|
|
142
139
|
bottom: 100%;
|
|
143
140
|
margin-bottom: (-$borderWidth);
|
|
144
141
|
}
|
|
145
142
|
|
|
146
|
-
padding: 0;
|
|
147
143
|
// margin-left to compensate for the padding on the parent
|
|
148
|
-
margin:
|
|
144
|
+
margin-left: (-$borderWidth);
|
|
149
145
|
|
|
150
146
|
box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.2);
|
|
151
147
|
background-color: white;
|
|
152
148
|
border: $borderWidth solid $greyBorder;
|
|
153
149
|
|
|
154
|
-
// don't let the contents wrap AKA the container will be as wide as the contents
|
|
155
|
-
white-space: nowrap;
|
|
156
|
-
// except on small screens, where we force the dropdown width to match the input
|
|
157
|
-
@media (max-width: 500px) {
|
|
158
|
-
white-space: normal;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
150
|
max-height: 200px;
|
|
162
151
|
overflow-y: scroll;
|
|
163
152
|
|
|
@@ -167,6 +156,25 @@ $mobilePopupMargin: 30px !default;
|
|
|
167
156
|
// Stackoverflow question about it: https://stackoverflow.com/questions/33601165/scrolling-slow-on-mobile-ios-when-using-overflowscroll
|
|
168
157
|
-webkit-overflow-scrolling: touch;
|
|
169
158
|
}
|
|
159
|
+
&__search-input {
|
|
160
|
+
width: 100%;
|
|
161
|
+
border-width: 0;
|
|
162
|
+
}
|
|
163
|
+
&__country-list {
|
|
164
|
+
// override default list styles
|
|
165
|
+
list-style: none;
|
|
166
|
+
padding: 0;
|
|
167
|
+
margin: 0;
|
|
168
|
+
}
|
|
169
|
+
&--flexible-dropdown-width &__country-list {
|
|
170
|
+
// don't let the contents wrap AKA the container will be as wide as the contents
|
|
171
|
+
white-space: nowrap;
|
|
172
|
+
|
|
173
|
+
// except on small screens, where we force the dropdown width to match the input
|
|
174
|
+
@media (max-width: 500px) {
|
|
175
|
+
white-space: normal;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
170
178
|
|
|
171
179
|
// dropdown flags need consistent width, so wrap in a container
|
|
172
180
|
&__flag-box {
|
|
@@ -212,9 +220,9 @@ $mobilePopupMargin: 30px !default;
|
|
|
212
220
|
// these settings are independent of each other, but both move selected flag to left of input
|
|
213
221
|
&--allow-dropdown,
|
|
214
222
|
&--separate-dial-code {
|
|
215
|
-
input,
|
|
216
|
-
input[type="text"],
|
|
217
|
-
input[type="tel"] {
|
|
223
|
+
input.iti__tel-input,
|
|
224
|
+
input.iti__tel-input[type="text"],
|
|
225
|
+
input.iti__tel-input[type="tel"] {
|
|
218
226
|
padding-right: $inputPadding;
|
|
219
227
|
padding-left: $selectedFlagArrowWidth + $inputPadding;
|
|
220
228
|
margin-left: 0;
|
|
@@ -245,8 +253,8 @@ $mobilePopupMargin: 30px !default;
|
|
|
245
253
|
}
|
|
246
254
|
}
|
|
247
255
|
// disable hover state when input is disabled
|
|
248
|
-
|
|
249
|
-
|
|
256
|
+
.iti__flag-container:has(+ input[disabled]):hover,
|
|
257
|
+
.iti__flag-container:has(+ input[readonly]):hover {
|
|
250
258
|
cursor: default;
|
|
251
259
|
.iti__selected-flag {
|
|
252
260
|
background-color: transparent;
|
|
@@ -284,9 +292,9 @@ $mobilePopupMargin: 30px !default;
|
|
|
284
292
|
}
|
|
285
293
|
}
|
|
286
294
|
|
|
287
|
-
// overrides for mobile popup
|
|
288
|
-
.iti
|
|
289
|
-
|
|
295
|
+
// overrides for mobile popup
|
|
296
|
+
.iti--fullscreen-popup {
|
|
297
|
+
&.iti--container {
|
|
290
298
|
background-color: rgba(0, 0, 0, 0.5);
|
|
291
299
|
top: 0;
|
|
292
300
|
bottom: 0;
|
|
@@ -299,11 +307,11 @@ $mobilePopupMargin: 30px !default;
|
|
|
299
307
|
flex-direction: column;
|
|
300
308
|
justify-content: center;
|
|
301
309
|
}
|
|
302
|
-
|
|
310
|
+
.iti__dropdown-content {
|
|
303
311
|
max-height: 100%;
|
|
304
312
|
position: relative; // override needed in order to get full-width working properly
|
|
305
313
|
}
|
|
306
|
-
|
|
314
|
+
.iti__country {
|
|
307
315
|
padding: 10px 10px;
|
|
308
316
|
// increase line height because dropdown copy is v likely to overflow on mobile and when it does it needs to be well spaced
|
|
309
317
|
line-height: 1.5em;
|
|
@@ -321,7 +329,7 @@ $mobilePopupMargin: 30px !default;
|
|
|
321
329
|
background-color: #dbdbdb;
|
|
322
330
|
background-position: $flagWidth 0;
|
|
323
331
|
|
|
324
|
-
@media (
|
|
332
|
+
@media (min-resolution: 2x) {
|
|
325
333
|
background-image: url("#{$flagsImagePath}#{$flagsImageName}@2x.#{$flagsImageExtension}#{$flagsImageQuery}");
|
|
326
334
|
}
|
|
327
335
|
}
|
package/src/css/sprite.scss
CHANGED
package/src/js/intlTelInput.js
CHANGED
|
@@ -22,6 +22,8 @@ const defaults = {
|
|
|
22
22
|
autoInsertDialCode: false,
|
|
23
23
|
// add a placeholder in the input with an example number for the selected country
|
|
24
24
|
autoPlaceholder: "polite",
|
|
25
|
+
// add a country search input at the top of the dropdown
|
|
26
|
+
countrySearch: false,
|
|
25
27
|
// modify the parentClass
|
|
26
28
|
customContainer: "",
|
|
27
29
|
// modify the auto placeholder
|
|
@@ -30,6 +32,8 @@ const defaults = {
|
|
|
30
32
|
dropdownContainer: null,
|
|
31
33
|
// don't display these countries
|
|
32
34
|
excludeCountries: [],
|
|
35
|
+
// fix the dropdown width to the input width (rather than being as wide as the longest country name)
|
|
36
|
+
fixDropdownWidth: false,
|
|
33
37
|
// format the input value during initialisation and on setNumber
|
|
34
38
|
formatOnDisplay: true,
|
|
35
39
|
// geoIp lookup function
|
|
@@ -126,6 +130,17 @@ class Iti {
|
|
|
126
130
|
}
|
|
127
131
|
|
|
128
132
|
_init() {
|
|
133
|
+
// if showing fullscreen popup, do not fix the width
|
|
134
|
+
if (this.options.useFullscreenPopup) {
|
|
135
|
+
this.options.fixDropdownWidth = false;
|
|
136
|
+
this.options.countrySearch = false;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// when search enabled, we must fix the width else it would change with different results
|
|
140
|
+
if (this.options.countrySearch) {
|
|
141
|
+
this.options.fixDropdownWidth = true;
|
|
142
|
+
}
|
|
143
|
+
|
|
129
144
|
// if in nationalMode, do not insert dial codes
|
|
130
145
|
if (this.options.nationalMode) {
|
|
131
146
|
this.options.autoInsertDialCode = false;
|
|
@@ -144,14 +159,9 @@ class Iti {
|
|
|
144
159
|
this.options.showFlags = true;
|
|
145
160
|
}
|
|
146
161
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
document.body
|
|
150
|
-
|
|
151
|
-
// on mobile, we want a full screen dropdown, so we must append it to the body
|
|
152
|
-
if (!this.options.dropdownContainer) {
|
|
153
|
-
this.options.dropdownContainer = document.body;
|
|
154
|
-
}
|
|
162
|
+
// on mobile, we want a full screen dropdown, so we must append it to the body
|
|
163
|
+
if (this.options.useFullscreenPopup && !this.options.dropdownContainer) {
|
|
164
|
+
this.options.dropdownContainer = document.body;
|
|
155
165
|
}
|
|
156
166
|
|
|
157
167
|
// check if input has one parent with RTL
|
|
@@ -355,6 +365,8 @@ class Iti {
|
|
|
355
365
|
|
|
356
366
|
// generate all of the markup for the plugin: the selected flag overlay, and the dropdown
|
|
357
367
|
_generateMarkup() {
|
|
368
|
+
this.telInput.classList.add("iti__tel-input");
|
|
369
|
+
|
|
358
370
|
// if autocomplete does not exist on the element and its form, then
|
|
359
371
|
// prevent autocomplete as there's no safe, cross-browser event we can react to, so it can
|
|
360
372
|
// easily put the plugin in an inconsistent state e.g. the wrong flag selected for the
|
|
@@ -372,7 +384,10 @@ class Iti {
|
|
|
372
384
|
showFlags,
|
|
373
385
|
customContainer,
|
|
374
386
|
hiddenInput,
|
|
375
|
-
dropdownContainer
|
|
387
|
+
dropdownContainer,
|
|
388
|
+
fixDropdownWidth,
|
|
389
|
+
useFullscreenPopup,
|
|
390
|
+
countrySearch
|
|
376
391
|
} = this.options;
|
|
377
392
|
|
|
378
393
|
// containers (mostly for positioning)
|
|
@@ -386,6 +401,9 @@ class Iti {
|
|
|
386
401
|
if (showFlags) {
|
|
387
402
|
parentClass += " iti--show-flags";
|
|
388
403
|
}
|
|
404
|
+
if (!fixDropdownWidth) {
|
|
405
|
+
parentClass += " iti--flexible-dropdown-width";
|
|
406
|
+
}
|
|
389
407
|
if (customContainer) {
|
|
390
408
|
parentClass += ` ${customContainer}`;
|
|
391
409
|
}
|
|
@@ -454,14 +472,34 @@ class Iti {
|
|
|
454
472
|
this.selectedFlag
|
|
455
473
|
);
|
|
456
474
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
class: "iti__country-list iti__hide",
|
|
460
|
-
id: `iti-${this.id}__country-listbox`,
|
|
461
|
-
role: "listbox",
|
|
462
|
-
"aria-label": "List of countries"
|
|
475
|
+
this.dropdownContent = this._createEl("div", {
|
|
476
|
+
class: "iti__dropdown-content iti__hide"
|
|
463
477
|
});
|
|
464
|
-
|
|
478
|
+
|
|
479
|
+
if (countrySearch) {
|
|
480
|
+
this.searchInput = this._createEl(
|
|
481
|
+
"input",
|
|
482
|
+
{
|
|
483
|
+
type: "text",
|
|
484
|
+
class: "iti__search-input",
|
|
485
|
+
placeholder: "Search"
|
|
486
|
+
},
|
|
487
|
+
this.dropdownContent
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// country list: preferred countries, then divider, then all countries
|
|
492
|
+
this.countryList = this._createEl(
|
|
493
|
+
"ul",
|
|
494
|
+
{
|
|
495
|
+
class: "iti__country-list",
|
|
496
|
+
id: `iti-${this.id}__country-listbox`,
|
|
497
|
+
role: "listbox",
|
|
498
|
+
"aria-label": "List of countries"
|
|
499
|
+
},
|
|
500
|
+
this.dropdownContent
|
|
501
|
+
);
|
|
502
|
+
if (this.preferredCountries.length && !countrySearch) {
|
|
465
503
|
this._appendListItems(this.preferredCountries, "iti__preferred", true);
|
|
466
504
|
this._createEl(
|
|
467
505
|
"li",
|
|
@@ -476,10 +514,15 @@ class Iti {
|
|
|
476
514
|
|
|
477
515
|
// create dropdownContainer markup
|
|
478
516
|
if (dropdownContainer) {
|
|
479
|
-
|
|
480
|
-
|
|
517
|
+
const fullscreenClass = useFullscreenPopup
|
|
518
|
+
? "iti--fullscreen-popup"
|
|
519
|
+
: "";
|
|
520
|
+
this.dropdown = this._createEl("div", {
|
|
521
|
+
class: `iti iti--container ${fullscreenClass}`
|
|
522
|
+
});
|
|
523
|
+
this.dropdown.appendChild(this.dropdownContent);
|
|
481
524
|
} else {
|
|
482
|
-
this.flagsContainer.appendChild(this.
|
|
525
|
+
this.flagsContainer.appendChild(this.dropdownContent);
|
|
483
526
|
}
|
|
484
527
|
}
|
|
485
528
|
|
|
@@ -502,28 +545,39 @@ class Iti {
|
|
|
502
545
|
}
|
|
503
546
|
}
|
|
504
547
|
|
|
505
|
-
// add a country <li> to the countryList <ul> container
|
|
548
|
+
// for each of the passed countries: add a country <li> to the countryList <ul> container
|
|
506
549
|
_appendListItems(countries, className, preferred) {
|
|
507
|
-
// we create so many DOM elements, it is faster to build a temp string
|
|
508
|
-
// and then add everything to the DOM in one go at the end
|
|
509
|
-
let tmp = "";
|
|
510
|
-
// for each country
|
|
511
550
|
for (let i = 0; i < countries.length; i++) {
|
|
512
551
|
const c = countries[i];
|
|
513
552
|
const idSuffix = preferred ? "-preferred" : "";
|
|
514
|
-
|
|
515
|
-
|
|
553
|
+
|
|
554
|
+
const listItem = this._createEl(
|
|
555
|
+
"li",
|
|
556
|
+
{
|
|
557
|
+
id: `iti-${this.id}__item-${c.iso2}${idSuffix}`,
|
|
558
|
+
class: `iti__country ${className}`,
|
|
559
|
+
tabindex: "-1",
|
|
560
|
+
role: "option",
|
|
561
|
+
"data-dial-code": c.dialCode,
|
|
562
|
+
"data-country-code": c.iso2,
|
|
563
|
+
"aria-selected": "false"
|
|
564
|
+
},
|
|
565
|
+
this.countryList
|
|
566
|
+
);
|
|
567
|
+
// store this for later use e.g. country search filtering
|
|
568
|
+
c.node = listItem;
|
|
569
|
+
|
|
570
|
+
let content = "";
|
|
516
571
|
// add the flag
|
|
517
572
|
if (this.options.showFlags) {
|
|
518
|
-
|
|
573
|
+
content += `<div class='iti__flag-box'><div class='iti__flag iti__${c.iso2}'></div></div>`;
|
|
519
574
|
}
|
|
520
575
|
// and the country name and dial code
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
576
|
+
content += `<span class='iti__country-name'>${c.name}</span>`;
|
|
577
|
+
content += `<span class='iti__dial-code'>+${c.dialCode}</span>`;
|
|
578
|
+
|
|
579
|
+
listItem.insertAdjacentHTML("beforeend", content);
|
|
525
580
|
}
|
|
526
|
-
this.countryList.insertAdjacentHTML("beforeend", tmp);
|
|
527
581
|
}
|
|
528
582
|
|
|
529
583
|
// set the initial state of the input value and the selected flag by:
|
|
@@ -625,7 +679,7 @@ class Iti {
|
|
|
625
679
|
// close it again
|
|
626
680
|
this._handleLabelClick = (e) => {
|
|
627
681
|
// if the dropdown is closed, then focus the input, else ignore the click
|
|
628
|
-
if (this.
|
|
682
|
+
if (this.dropdownContent.classList.contains("iti__hide")) {
|
|
629
683
|
this.telInput.focus();
|
|
630
684
|
} else {
|
|
631
685
|
e.preventDefault();
|
|
@@ -642,7 +696,7 @@ class Iti {
|
|
|
642
696
|
// else let it bubble up to the top ("click-off-to-close" listener)
|
|
643
697
|
// we cannot just stopPropagation as it may be needed to close another instance
|
|
644
698
|
if (
|
|
645
|
-
this.
|
|
699
|
+
this.dropdownContent.classList.contains("iti__hide") &&
|
|
646
700
|
!this.telInput.disabled &&
|
|
647
701
|
!this.telInput.readOnly
|
|
648
702
|
) {
|
|
@@ -651,14 +705,14 @@ class Iti {
|
|
|
651
705
|
};
|
|
652
706
|
this.selectedFlag.addEventListener("click", this._handleClickSelectedFlag);
|
|
653
707
|
|
|
654
|
-
// open dropdown
|
|
708
|
+
// open dropdown if selected flag is focused and they press up/down/space/enter
|
|
655
709
|
this._handleFlagsContainerKeydown = (e) => {
|
|
656
|
-
const isDropdownHidden =
|
|
710
|
+
const isDropdownHidden =
|
|
711
|
+
this.dropdownContent.classList.contains("iti__hide");
|
|
657
712
|
|
|
658
713
|
if (
|
|
659
714
|
isDropdownHidden &&
|
|
660
|
-
["ArrowUp", "
|
|
661
|
-
-1
|
|
715
|
+
["ArrowUp", "ArrowDown", " ", "Enter"].includes(e.key)
|
|
662
716
|
) {
|
|
663
717
|
// prevent form from being submitted if "ENTER" was pressed
|
|
664
718
|
e.preventDefault();
|
|
@@ -802,13 +856,20 @@ class Iti {
|
|
|
802
856
|
|
|
803
857
|
// show the dropdown
|
|
804
858
|
_showDropdown() {
|
|
805
|
-
this.
|
|
859
|
+
if (this.options.fixDropdownWidth) {
|
|
860
|
+
this.dropdownContent.style.width = `${this.telInput.offsetWidth}px`;
|
|
861
|
+
}
|
|
862
|
+
this.dropdownContent.classList.remove("iti__hide");
|
|
806
863
|
this.selectedFlag.setAttribute("aria-expanded", "true");
|
|
807
864
|
|
|
808
865
|
this._setDropdownPosition();
|
|
809
866
|
|
|
810
|
-
|
|
811
|
-
|
|
867
|
+
if (this.options.countrySearch) {
|
|
868
|
+
// start by highlighting the first item in the list
|
|
869
|
+
this._highlightListItem(this.countryList.firstElementChild, false);
|
|
870
|
+
this.searchInput.focus();
|
|
871
|
+
} else if (this.activeItem) {
|
|
872
|
+
// update highlighting and scroll to active list item
|
|
812
873
|
this._highlightListItem(this.activeItem, false);
|
|
813
874
|
this._scrollTo(this.activeItem, true);
|
|
814
875
|
}
|
|
@@ -843,7 +904,7 @@ class Iti {
|
|
|
843
904
|
const windowTop =
|
|
844
905
|
window.pageYOffset || document.documentElement.scrollTop;
|
|
845
906
|
const inputTop = pos.top + windowTop;
|
|
846
|
-
const dropdownHeight = this.
|
|
907
|
+
const dropdownHeight = this.dropdownContent.offsetHeight;
|
|
847
908
|
// dropdownFitsBelow = (dropdownBottom < windowBottom)
|
|
848
909
|
const dropdownFitsBelow =
|
|
849
910
|
inputTop + this.telInput.offsetHeight + dropdownHeight <
|
|
@@ -853,7 +914,7 @@ class Iti {
|
|
|
853
914
|
// by default, the dropdown will be below the input. If we want to position it above the
|
|
854
915
|
// input, we add the dropup class.
|
|
855
916
|
this._toggleClass(
|
|
856
|
-
this.
|
|
917
|
+
this.dropdownContent,
|
|
857
918
|
"iti__country-list--dropup",
|
|
858
919
|
!dropdownFitsBelow && dropdownFitsAbove
|
|
859
920
|
);
|
|
@@ -932,7 +993,7 @@ class Iti {
|
|
|
932
993
|
this._handleClickOffToClose
|
|
933
994
|
);
|
|
934
995
|
|
|
935
|
-
// listen for up/down scrolling, enter to select, or
|
|
996
|
+
// listen for up/down scrolling, enter to select, or escape to close
|
|
936
997
|
// use keydown as keypress doesn't fire for non-char keys and we want to catch if they
|
|
937
998
|
// just hit down and hold it to scroll down (no keyup event).
|
|
938
999
|
// listen on the document because that's where key events are triggered if no input has focus
|
|
@@ -941,28 +1002,28 @@ class Iti {
|
|
|
941
1002
|
this._handleKeydownOnDropdown = (e) => {
|
|
942
1003
|
// prevent down key from scrolling the whole page,
|
|
943
1004
|
// and enter key from submitting a form etc
|
|
944
|
-
e.
|
|
1005
|
+
if (["ArrowUp", "ArrowDown", "Enter", "Escape"].includes(e.key)) {
|
|
1006
|
+
e.preventDefault();
|
|
1007
|
+
e.stopPropagation();
|
|
945
1008
|
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
e.key === "
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
}
|
|
959
|
-
// esc to close
|
|
960
|
-
else if (e.key === "Escape") {
|
|
961
|
-
this._closeDropdown();
|
|
1009
|
+
// up and down to navigate
|
|
1010
|
+
if (e.key === "ArrowUp" || e.key === "ArrowDown") {
|
|
1011
|
+
this._handleUpDownKey(e.key);
|
|
1012
|
+
}
|
|
1013
|
+
// enter to select
|
|
1014
|
+
else if (e.key === "Enter") {
|
|
1015
|
+
this._handleEnterKey();
|
|
1016
|
+
}
|
|
1017
|
+
// esc to close
|
|
1018
|
+
else if (e.key === "Escape") {
|
|
1019
|
+
this._closeDropdown();
|
|
1020
|
+
}
|
|
962
1021
|
}
|
|
1022
|
+
|
|
963
1023
|
// alpha chars to perform search
|
|
964
1024
|
// regex allows one latin alpha char or space, based on https://stackoverflow.com/a/26900132/217866)
|
|
965
|
-
|
|
1025
|
+
if (!this.options.countrySearch && /^[a-zA-ZÀ-ÿа-яА-Я ]$/.test(e.key)) {
|
|
1026
|
+
e.stopPropagation();
|
|
966
1027
|
// jump to countries that start with the query string
|
|
967
1028
|
if (queryTimer) {
|
|
968
1029
|
clearTimeout(queryTimer);
|
|
@@ -976,23 +1037,85 @@ class Iti {
|
|
|
976
1037
|
}
|
|
977
1038
|
};
|
|
978
1039
|
document.addEventListener("keydown", this._handleKeydownOnDropdown);
|
|
1040
|
+
|
|
1041
|
+
if (this.options.countrySearch) {
|
|
1042
|
+
const doFilter = () => {
|
|
1043
|
+
const inputQuery = this.searchInput.value.trim();
|
|
1044
|
+
if (inputQuery) {
|
|
1045
|
+
this._filterCountries(inputQuery.toLowerCase());
|
|
1046
|
+
} else {
|
|
1047
|
+
this._filterCountries(null, true);
|
|
1048
|
+
}
|
|
1049
|
+
};
|
|
1050
|
+
|
|
1051
|
+
let keyupTimer = null;
|
|
1052
|
+
this._handleSearchChange = () => {
|
|
1053
|
+
// filtering country nodes is expensive (lots of DOM manipulation), so rate limit it
|
|
1054
|
+
if (keyupTimer) {
|
|
1055
|
+
clearTimeout(keyupTimer);
|
|
1056
|
+
}
|
|
1057
|
+
keyupTimer = setTimeout(() => {
|
|
1058
|
+
doFilter();
|
|
1059
|
+
keyupTimer = null;
|
|
1060
|
+
}, 100);
|
|
1061
|
+
};
|
|
1062
|
+
this.searchInput.addEventListener("input", this._handleSearchChange);
|
|
1063
|
+
|
|
1064
|
+
// stop propagation on search input click, so doesn't trigger click-off-to-close listener
|
|
1065
|
+
this.searchInput.addEventListener("click", (e) => e.stopPropagation());
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
_filterCountries(query, isReset = false) {
|
|
1070
|
+
let isFirst = true;
|
|
1071
|
+
this.countryList.innerHTML = "";
|
|
1072
|
+
for (let i = 0; i < this.countries.length; i++) {
|
|
1073
|
+
const c = this.countries[i];
|
|
1074
|
+
const nameLower = c.name.toLowerCase();
|
|
1075
|
+
const fullDialCode = `+${c.dialCode}`;
|
|
1076
|
+
if (
|
|
1077
|
+
isReset ||
|
|
1078
|
+
nameLower.includes(query) ||
|
|
1079
|
+
fullDialCode.includes(query)
|
|
1080
|
+
) {
|
|
1081
|
+
this.countryList.appendChild(c.node);
|
|
1082
|
+
// highlight the first item
|
|
1083
|
+
if (isFirst) {
|
|
1084
|
+
this._highlightListItem(c.node, false);
|
|
1085
|
+
isFirst = false;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
979
1089
|
}
|
|
980
1090
|
|
|
981
1091
|
// highlight the next/prev item in the list (and ensure it is visible)
|
|
982
1092
|
_handleUpDownKey(key) {
|
|
983
1093
|
let next =
|
|
984
|
-
key === "ArrowUp"
|
|
1094
|
+
key === "ArrowUp"
|
|
985
1095
|
? this.highlightedItem.previousElementSibling
|
|
986
1096
|
: this.highlightedItem.nextElementSibling;
|
|
987
1097
|
if (next) {
|
|
988
1098
|
// skip the divider
|
|
989
1099
|
if (next.classList.contains("iti__divider")) {
|
|
990
1100
|
next =
|
|
991
|
-
key === "ArrowUp"
|
|
1101
|
+
key === "ArrowUp"
|
|
992
1102
|
? next.previousElementSibling
|
|
993
1103
|
: next.nextElementSibling;
|
|
994
1104
|
}
|
|
995
|
-
|
|
1105
|
+
} else if (this.countryList.childElementCount > 1) {
|
|
1106
|
+
// otherwise, we must be at the end, so loop round again
|
|
1107
|
+
next =
|
|
1108
|
+
key === "ArrowUp"
|
|
1109
|
+
? this.countryList.lastElementChild
|
|
1110
|
+
: this.countryList.firstElementChild;
|
|
1111
|
+
}
|
|
1112
|
+
if (next) {
|
|
1113
|
+
// if country search enabled, dont lose focus from the search input on up/down
|
|
1114
|
+
const doFocus = !this.options.countrySearch;
|
|
1115
|
+
this._highlightListItem(next, doFocus);
|
|
1116
|
+
if (this.options.countrySearch) {
|
|
1117
|
+
this._scrollTo(next, false);
|
|
1118
|
+
}
|
|
996
1119
|
}
|
|
997
1120
|
}
|
|
998
1121
|
|
|
@@ -1007,9 +1130,7 @@ class Iti {
|
|
|
1007
1130
|
_searchForCountry(query) {
|
|
1008
1131
|
for (let i = 0; i < this.countries.length; i++) {
|
|
1009
1132
|
if (this._startsWith(this.countries[i].name, query)) {
|
|
1010
|
-
const listItem = this.
|
|
1011
|
-
`#iti-${this.id}__item-${this.countries[i].iso2}`
|
|
1012
|
-
);
|
|
1133
|
+
const listItem = this.countries[i].node;
|
|
1013
1134
|
// update highlighting and scroll
|
|
1014
1135
|
this._highlightListItem(listItem, false);
|
|
1015
1136
|
this._scrollTo(listItem, true);
|
|
@@ -1333,7 +1454,7 @@ class Iti {
|
|
|
1333
1454
|
|
|
1334
1455
|
// close the dropdown and unbind any listeners
|
|
1335
1456
|
_closeDropdown() {
|
|
1336
|
-
this.
|
|
1457
|
+
this.dropdownContent.classList.add("iti__hide");
|
|
1337
1458
|
this.selectedFlag.setAttribute("aria-expanded", "false");
|
|
1338
1459
|
this.selectedFlag.removeAttribute("aria-activedescendant");
|
|
1339
1460
|
|
|
@@ -1342,6 +1463,9 @@ class Iti {
|
|
|
1342
1463
|
|
|
1343
1464
|
// unbind key events
|
|
1344
1465
|
document.removeEventListener("keydown", this._handleKeydownOnDropdown);
|
|
1466
|
+
if (this.options.countrySearch) {
|
|
1467
|
+
this.searchInput.removeEventListener("input", this._handleSearchChange);
|
|
1468
|
+
}
|
|
1345
1469
|
document.documentElement.removeEventListener(
|
|
1346
1470
|
"click",
|
|
1347
1471
|
this._handleClickOffToClose
|
|
@@ -1367,7 +1491,7 @@ class Iti {
|
|
|
1367
1491
|
|
|
1368
1492
|
// check if an element is visible within it's container, else scroll until it is
|
|
1369
1493
|
_scrollTo(element, middle) {
|
|
1370
|
-
const container = this.
|
|
1494
|
+
const container = this.dropdownContent;
|
|
1371
1495
|
// windowTop from https://stackoverflow.com/a/14384091/217866
|
|
1372
1496
|
const windowTop = window.pageYOffset || document.documentElement.scrollTop;
|
|
1373
1497
|
const containerHeight = container.offsetHeight;
|
|
@@ -58,10 +58,10 @@ describe("dropdown shortcuts: init plugin (with nationalMode=false, autoInsertDi
|
|
|
58
58
|
expect(getListElement()).not.toBeVisible();
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
-
it("pressing up while on the top item
|
|
61
|
+
it("pressing up while on the top item highlights the bottom item", function() {
|
|
62
62
|
triggerKeyOnBody("ArrowUp");
|
|
63
|
-
var
|
|
64
|
-
expect(
|
|
63
|
+
var lastItem = getListElement().find("li.iti__country:last");
|
|
64
|
+
expect(lastItem).toHaveClass("iti__highlight");
|
|
65
65
|
});
|
|
66
66
|
|
|
67
67
|
it("pressing z highlights Zambia", function() {
|
|
@@ -82,23 +82,22 @@ describe("dropdown shortcuts: init plugin (with nationalMode=false, autoInsertDi
|
|
|
82
82
|
|
|
83
83
|
describe("typing z then i then DOWN", function() {
|
|
84
84
|
|
|
85
|
-
var lastItem;
|
|
86
|
-
|
|
87
85
|
beforeEach(function() {
|
|
88
|
-
lastItem = getListElement().find("li.iti__country:last");
|
|
89
86
|
triggerKeyOnBody("z");
|
|
90
87
|
triggerKeyOnBody("i");
|
|
91
88
|
triggerKeyOnBody("ArrowDown");
|
|
92
89
|
});
|
|
93
90
|
|
|
94
91
|
it("highlights the last item, which is Åland Islands", function() {
|
|
92
|
+
var lastItem = getListElement().find("li.iti__country:last");
|
|
95
93
|
expect(lastItem).toHaveClass("iti__highlight");
|
|
96
94
|
expect(lastItem.attr("data-country-code")).toEqual("ax");
|
|
97
95
|
});
|
|
98
96
|
|
|
99
|
-
it("pressing down while on the last item
|
|
97
|
+
it("pressing down while on the last item highlights the first item", function() {
|
|
100
98
|
triggerKeyOnBody("ArrowDown");
|
|
101
|
-
|
|
99
|
+
var topItem = getListElement().find("li.iti__country:eq(0)");
|
|
100
|
+
expect(topItem).toHaveClass("iti__highlight");
|
|
102
101
|
});
|
|
103
102
|
});
|
|
104
103
|
|