intl-tel-input 18.1.0 → 18.1.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/.eslintrc +2 -8
- package/.vscode/settings.json +8 -0
- package/CHANGELOG.md +3 -0
- package/Gruntfile.js +0 -2
- package/README.md +14 -25
- package/build/js/data.js +1 -1
- package/build/js/data.min.js +1 -1
- package/build/js/intlTelInput-jquery.js +160 -59
- package/build/js/intlTelInput-jquery.min.js +3 -3
- package/build/js/intlTelInput.js +160 -59
- package/build/js/intlTelInput.min.js +3 -3
- package/composer.json +1 -1
- package/demo.html +42 -44
- package/demo_rtl.html +21 -23
- package/grunt/template.js +2 -265
- package/package.json +4 -1
- package/src/js/intlTelInput.js +563 -333
- package/examples/css/countrySync.css +0 -10
- package/examples/css/isValidNumber.css +0 -12
- package/examples/css/prism.css +0 -126
- package/examples/gen/country-sync.html +0 -98
- package/examples/gen/default-country-ip.html +0 -62
- package/examples/gen/display-number.html +0 -47
- package/examples/gen/hidden-input.html +0 -54
- package/examples/gen/init-promise.html +0 -66
- package/examples/gen/is-valid-number.html +0 -86
- package/examples/gen/js/countrySync.js +0 -31
- package/examples/gen/js/defaultCountryIp.js +0 -11
- package/examples/gen/js/displayNumber.js +0 -4
- package/examples/gen/js/hiddenInput.js +0 -5
- package/examples/gen/js/initPromise.js +0 -9
- package/examples/gen/js/isValidNumber.js +0 -37
- package/examples/gen/js/modifyCountryData.js +0 -11
- package/examples/gen/js/multipleInstances.js +0 -13
- package/examples/gen/js/nationalMode.js +0 -18
- package/examples/gen/js/onlyCountriesEurope.js +0 -8
- package/examples/gen/modify-country-data.html +0 -52
- package/examples/gen/multiple-instances.html +0 -60
- package/examples/gen/national-mode.html +0 -63
- package/examples/gen/only-countries-europe.html +0 -49
- package/examples/js/countrySync.js.ejs +0 -31
- package/examples/js/defaultCountryIp.js.ejs +0 -11
- package/examples/js/displayNumber.js.ejs +0 -4
- package/examples/js/hiddenInput.js.ejs +0 -5
- package/examples/js/initPromise.js.ejs +0 -9
- package/examples/js/isValidNumber.js.ejs +0 -37
- package/examples/js/modifyCountryData.js.ejs +0 -11
- package/examples/js/multipleInstances.js.ejs +0 -13
- package/examples/js/nationalMode.js.ejs +0 -18
- package/examples/js/onlyCountriesEurope.js.ejs +0 -8
- package/examples/js/prism.js +0 -11
- package/examples/partials/countrySync.html +0 -13
- package/examples/partials/defaultCountryIp.html +0 -5
- package/examples/partials/displayNumber.html +0 -1
- package/examples/partials/hiddenInput.html +0 -4
- package/examples/partials/initPromise.html +0 -8
- package/examples/partials/isValidNumber.html +0 -3
- package/examples/partials/multipleInstances.html +0 -3
- package/examples/partials/nationalMode.html +0 -2
- package/examples/partials/simpleInput.html +0 -1
- package/examples/template.html.ejs +0 -43
package/src/js/intlTelInput.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
const intlTelInputGlobals = {
|
|
2
2
|
getInstance: (input) => {
|
|
3
|
-
const id = input.getAttribute(
|
|
3
|
+
const id = input.getAttribute("data-intl-tel-input-id");
|
|
4
4
|
return window.intlTelInputGlobals.instances[id];
|
|
5
5
|
},
|
|
6
6
|
instances: {},
|
|
7
7
|
// using a global like this allows us to mock it in the tests
|
|
8
|
-
documentReady: () => document.readyState ===
|
|
8
|
+
documentReady: () => document.readyState === "complete"
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
if (typeof window ===
|
|
11
|
+
if (typeof window === "object") {
|
|
12
|
+
window.intlTelInputGlobals = intlTelInputGlobals;
|
|
13
|
+
}
|
|
12
14
|
|
|
13
15
|
// these vars persist through all instances of the plugin
|
|
14
16
|
let id = 0;
|
|
@@ -19,9 +21,9 @@ const defaults = {
|
|
|
19
21
|
// also listen for blur/submit and auto remove dial code if that's all there is
|
|
20
22
|
autoInsertDialCode: false,
|
|
21
23
|
// add a placeholder in the input with an example number for the selected country
|
|
22
|
-
autoPlaceholder:
|
|
24
|
+
autoPlaceholder: "polite",
|
|
23
25
|
// modify the parentClass
|
|
24
|
-
customContainer:
|
|
26
|
+
customContainer: "",
|
|
25
27
|
// modify the auto placeholder
|
|
26
28
|
customPlaceholder: null,
|
|
27
29
|
// append menu to specified element
|
|
@@ -33,29 +35,46 @@ const defaults = {
|
|
|
33
35
|
// geoIp lookup function
|
|
34
36
|
geoIpLookup: null,
|
|
35
37
|
// inject a hidden input with this name, and on submit, populate it with the result of getNumber
|
|
36
|
-
hiddenInput:
|
|
38
|
+
hiddenInput: "",
|
|
37
39
|
// initial country
|
|
38
|
-
initialCountry:
|
|
40
|
+
initialCountry: "",
|
|
39
41
|
// localized country names e.g. { 'de': 'Deutschland' }
|
|
40
42
|
localizedCountries: null,
|
|
41
|
-
//
|
|
43
|
+
// national vs international formatting for numbers e.g. placeholders and displaying existing numbers
|
|
42
44
|
nationalMode: true,
|
|
43
45
|
// display only these countries
|
|
44
46
|
onlyCountries: [],
|
|
45
47
|
// number type to use for placeholders
|
|
46
|
-
placeholderNumberType:
|
|
48
|
+
placeholderNumberType: "MOBILE",
|
|
47
49
|
// the countries at the top of the list. defaults to united states and united kingdom
|
|
48
|
-
preferredCountries: [
|
|
50
|
+
preferredCountries: ["us", "gb"],
|
|
49
51
|
// display the country dial code next to the selected flag
|
|
50
52
|
separateDialCode: false,
|
|
51
53
|
// option to hide the flags - must be used with separateDialCode, or allowDropdown=false
|
|
52
54
|
showFlags: true,
|
|
53
55
|
// specify the path to the libphonenumber script to enable validation/formatting
|
|
54
|
-
utilsScript:
|
|
56
|
+
utilsScript: ""
|
|
55
57
|
};
|
|
56
58
|
// https://en.wikipedia.org/wiki/List_of_North_American_Numbering_Plan_area_codes#Non-geographic_area_codes
|
|
57
|
-
const regionlessNanpNumbers = [
|
|
58
|
-
|
|
59
|
+
const regionlessNanpNumbers = [
|
|
60
|
+
"800",
|
|
61
|
+
"822",
|
|
62
|
+
"833",
|
|
63
|
+
"844",
|
|
64
|
+
"855",
|
|
65
|
+
"866",
|
|
66
|
+
"877",
|
|
67
|
+
"880",
|
|
68
|
+
"881",
|
|
69
|
+
"882",
|
|
70
|
+
"883",
|
|
71
|
+
"884",
|
|
72
|
+
"885",
|
|
73
|
+
"886",
|
|
74
|
+
"887",
|
|
75
|
+
"888",
|
|
76
|
+
"889"
|
|
77
|
+
];
|
|
59
78
|
|
|
60
79
|
// utility function to iterate over an object. can't use Object.entries or native forEach because
|
|
61
80
|
// of IE11
|
|
@@ -66,7 +85,6 @@ const forEachProp = (obj, callback) => {
|
|
|
66
85
|
}
|
|
67
86
|
};
|
|
68
87
|
|
|
69
|
-
|
|
70
88
|
// run a method on each instance of the plugin
|
|
71
89
|
const forEachInstance = (method) => {
|
|
72
90
|
forEachProp(window.intlTelInputGlobals.instances, (key) => {
|
|
@@ -74,7 +92,6 @@ const forEachInstance = (method) => {
|
|
|
74
92
|
});
|
|
75
93
|
};
|
|
76
94
|
|
|
77
|
-
|
|
78
95
|
// this is our plugin class that we will create an instance of
|
|
79
96
|
// eslint-disable-next-line no-unused-vars
|
|
80
97
|
class Iti {
|
|
@@ -90,22 +107,29 @@ class Iti {
|
|
|
90
107
|
const customOptions = options || {};
|
|
91
108
|
this.options = {};
|
|
92
109
|
forEachProp(defaults, (key, value) => {
|
|
93
|
-
this.options[key] =
|
|
110
|
+
this.options[key] = customOptions.hasOwnProperty(key)
|
|
111
|
+
? customOptions[key]
|
|
112
|
+
: value;
|
|
94
113
|
});
|
|
95
114
|
|
|
96
|
-
this.hadInitialPlaceholder = Boolean(input.getAttribute(
|
|
115
|
+
this.hadInitialPlaceholder = Boolean(input.getAttribute("placeholder"));
|
|
97
116
|
}
|
|
98
117
|
|
|
99
118
|
_init() {
|
|
100
119
|
// if in nationalMode, do not insert dial codes
|
|
101
|
-
if (this.options.nationalMode)
|
|
120
|
+
if (this.options.nationalMode) {
|
|
121
|
+
this.options.autoInsertDialCode = false;
|
|
122
|
+
}
|
|
102
123
|
|
|
103
124
|
// if separateDialCode enabled, do not insert dial codes
|
|
104
|
-
if (this.options.separateDialCode)
|
|
125
|
+
if (this.options.separateDialCode) {
|
|
126
|
+
this.options.autoInsertDialCode = false;
|
|
127
|
+
}
|
|
105
128
|
|
|
106
129
|
// force showFlags=true if there's a dropdown and we're not displaying the dial code,
|
|
107
130
|
// as otherwise you just have a down arrow on it's own which doesn't make sense
|
|
108
|
-
const forceShowFlags =
|
|
131
|
+
const forceShowFlags =
|
|
132
|
+
this.options.allowDropdown && !this.options.separateDialCode;
|
|
109
133
|
if (!this.options.showFlags && forceShowFlags) {
|
|
110
134
|
this.options.showFlags = true;
|
|
111
135
|
}
|
|
@@ -115,20 +139,25 @@ class Iti {
|
|
|
115
139
|
// Note: for some reason jasmine breaks if you put this in the main Plugin function with the
|
|
116
140
|
// rest of these declarations
|
|
117
141
|
// Note: to target Android Mobiles (and not Tablets), we must find 'Android' and 'Mobile'
|
|
118
|
-
this.isMobile =
|
|
142
|
+
this.isMobile =
|
|
143
|
+
/Android.+Mobile|webOS|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
|
|
144
|
+
navigator.userAgent
|
|
145
|
+
);
|
|
119
146
|
|
|
120
147
|
if (this.isMobile) {
|
|
121
148
|
// trigger the mobile dropdown css
|
|
122
|
-
document.body.classList.add(
|
|
149
|
+
document.body.classList.add("iti-mobile");
|
|
123
150
|
|
|
124
151
|
// on mobile, we want a full screen dropdown, so we must append it to the body
|
|
125
|
-
if (!this.options.dropdownContainer)
|
|
152
|
+
if (!this.options.dropdownContainer) {
|
|
153
|
+
this.options.dropdownContainer = document.body;
|
|
154
|
+
}
|
|
126
155
|
}
|
|
127
156
|
|
|
128
157
|
// these promises get resolved when their individual requests complete
|
|
129
158
|
// this way the dev can do something like iti.promise.then(...) to know when all requests are
|
|
130
159
|
// complete
|
|
131
|
-
if (typeof Promise !==
|
|
160
|
+
if (typeof Promise !== "undefined") {
|
|
132
161
|
const autoCountryPromise = new Promise((resolve, reject) => {
|
|
133
162
|
this.resolveAutoCountryPromise = resolve;
|
|
134
163
|
this.rejectAutoCountryPromise = reject;
|
|
@@ -164,12 +193,10 @@ class Iti {
|
|
|
164
193
|
this._initRequests();
|
|
165
194
|
}
|
|
166
195
|
|
|
167
|
-
|
|
168
196
|
/********************
|
|
169
197
|
* PRIVATE METHODS
|
|
170
198
|
********************/
|
|
171
199
|
|
|
172
|
-
|
|
173
200
|
// prepare all of the country data, including onlyCountries, excludeCountries and
|
|
174
201
|
// preferredCountries options
|
|
175
202
|
_processCountryData() {
|
|
@@ -183,7 +210,9 @@ class Iti {
|
|
|
183
210
|
this._processPreferredCountries();
|
|
184
211
|
|
|
185
212
|
// translate countries according to localizedCountries option
|
|
186
|
-
if (this.options.localizedCountries)
|
|
213
|
+
if (this.options.localizedCountries) {
|
|
214
|
+
this._translateCountriesByLocale();
|
|
215
|
+
}
|
|
187
216
|
|
|
188
217
|
// sort countries by name
|
|
189
218
|
if (this.options.onlyCountries.length || this.options.localizedCountries) {
|
|
@@ -191,7 +220,6 @@ class Iti {
|
|
|
191
220
|
}
|
|
192
221
|
}
|
|
193
222
|
|
|
194
|
-
|
|
195
223
|
// add a country code to this.countryCodes
|
|
196
224
|
_addCountryCode(iso2, countryCode, priority) {
|
|
197
225
|
if (countryCode.length > this.countryCodeMaxLen) {
|
|
@@ -202,29 +230,31 @@ class Iti {
|
|
|
202
230
|
}
|
|
203
231
|
// bail if we already have this country for this countryCode
|
|
204
232
|
for (let i = 0; i < this.countryCodes[countryCode].length; i++) {
|
|
205
|
-
if (this.countryCodes[countryCode][i] === iso2)
|
|
233
|
+
if (this.countryCodes[countryCode][i] === iso2) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
206
236
|
}
|
|
207
237
|
// check for undefined as 0 is falsy
|
|
208
|
-
const index =
|
|
238
|
+
const index =
|
|
239
|
+
priority !== undefined ? priority : this.countryCodes[countryCode].length;
|
|
209
240
|
this.countryCodes[countryCode][index] = iso2;
|
|
210
241
|
}
|
|
211
242
|
|
|
212
|
-
|
|
213
243
|
// process onlyCountries or excludeCountries array if present
|
|
214
244
|
_processAllCountries() {
|
|
215
245
|
if (this.options.onlyCountries.length) {
|
|
216
|
-
const lowerCaseOnlyCountries = this.options.onlyCountries.map(
|
|
217
|
-
country
|
|
246
|
+
const lowerCaseOnlyCountries = this.options.onlyCountries.map((country) =>
|
|
247
|
+
country.toLowerCase()
|
|
218
248
|
);
|
|
219
249
|
this.countries = allCountries.filter(
|
|
220
|
-
country => lowerCaseOnlyCountries.indexOf(country.iso2) > -1
|
|
250
|
+
(country) => lowerCaseOnlyCountries.indexOf(country.iso2) > -1
|
|
221
251
|
);
|
|
222
252
|
} else if (this.options.excludeCountries.length) {
|
|
223
253
|
const lowerCaseExcludeCountries = this.options.excludeCountries.map(
|
|
224
|
-
country => country.toLowerCase()
|
|
254
|
+
(country) => country.toLowerCase()
|
|
225
255
|
);
|
|
226
256
|
this.countries = allCountries.filter(
|
|
227
|
-
country => lowerCaseExcludeCountries.indexOf(country.iso2) === -1
|
|
257
|
+
(country) => lowerCaseExcludeCountries.indexOf(country.iso2) === -1
|
|
228
258
|
);
|
|
229
259
|
} else {
|
|
230
260
|
this.countries = allCountries;
|
|
@@ -243,12 +273,15 @@ class Iti {
|
|
|
243
273
|
|
|
244
274
|
// sort by country name
|
|
245
275
|
_countryNameSort(a, b) {
|
|
246
|
-
if (a.name < b.name)
|
|
247
|
-
|
|
276
|
+
if (a.name < b.name) {
|
|
277
|
+
return -1;
|
|
278
|
+
}
|
|
279
|
+
if (a.name > b.name) {
|
|
280
|
+
return 1;
|
|
281
|
+
}
|
|
248
282
|
return 0;
|
|
249
283
|
}
|
|
250
284
|
|
|
251
|
-
|
|
252
285
|
// process the countryCodes map
|
|
253
286
|
_processCountryCodes() {
|
|
254
287
|
this.countryCodeMaxLen = 0;
|
|
@@ -260,7 +293,9 @@ class Iti {
|
|
|
260
293
|
// first: add dial codes
|
|
261
294
|
for (let i = 0; i < this.countries.length; i++) {
|
|
262
295
|
const c = this.countries[i];
|
|
263
|
-
if (!this.dialCodes[c.dialCode])
|
|
296
|
+
if (!this.dialCodes[c.dialCode]) {
|
|
297
|
+
this.dialCodes[c.dialCode] = true;
|
|
298
|
+
}
|
|
264
299
|
this._addCountryCode(c.iso2, c.dialCode, c.priority);
|
|
265
300
|
}
|
|
266
301
|
|
|
@@ -290,7 +325,6 @@ class Iti {
|
|
|
290
325
|
}
|
|
291
326
|
}
|
|
292
327
|
|
|
293
|
-
|
|
294
328
|
// process preferred countries - iterate through the preferences, fetching the country data for
|
|
295
329
|
// each one
|
|
296
330
|
_processPreferredCountries() {
|
|
@@ -298,28 +332,35 @@ class Iti {
|
|
|
298
332
|
for (let i = 0; i < this.options.preferredCountries.length; i++) {
|
|
299
333
|
const countryCode = this.options.preferredCountries[i].toLowerCase();
|
|
300
334
|
const countryData = this._getCountryData(countryCode, false, true);
|
|
301
|
-
if (countryData)
|
|
335
|
+
if (countryData) {
|
|
336
|
+
this.preferredCountries.push(countryData);
|
|
337
|
+
}
|
|
302
338
|
}
|
|
303
339
|
}
|
|
304
340
|
|
|
305
|
-
|
|
306
341
|
// create a DOM element
|
|
307
342
|
_createEl(name, attrs, container) {
|
|
308
343
|
const el = document.createElement(name);
|
|
309
|
-
if (attrs)
|
|
310
|
-
|
|
344
|
+
if (attrs) {
|
|
345
|
+
forEachProp(attrs, (key, value) => el.setAttribute(key, value));
|
|
346
|
+
}
|
|
347
|
+
if (container) {
|
|
348
|
+
container.appendChild(el);
|
|
349
|
+
}
|
|
311
350
|
return el;
|
|
312
351
|
}
|
|
313
352
|
|
|
314
|
-
|
|
315
353
|
// generate all of the markup for the plugin: the selected flag overlay, and the dropdown
|
|
316
354
|
_generateMarkup() {
|
|
317
355
|
// if autocomplete does not exist on the element and its form, then
|
|
318
356
|
// prevent autocomplete as there's no safe, cross-browser event we can react to, so it can
|
|
319
357
|
// easily put the plugin in an inconsistent state e.g. the wrong flag selected for the
|
|
320
|
-
// autocompleted number, which on submit could mean wrong number is saved
|
|
321
|
-
if (
|
|
322
|
-
this.telInput.
|
|
358
|
+
// autocompleted number, which on submit could mean wrong number is saved
|
|
359
|
+
if (
|
|
360
|
+
!this.telInput.hasAttribute("autocomplete") &&
|
|
361
|
+
!(this.telInput.form && this.telInput.form.hasAttribute("autocomplete"))
|
|
362
|
+
) {
|
|
363
|
+
this.telInput.setAttribute("autocomplete", "off");
|
|
323
364
|
}
|
|
324
365
|
|
|
325
366
|
const {
|
|
@@ -328,24 +369,34 @@ class Iti {
|
|
|
328
369
|
showFlags,
|
|
329
370
|
customContainer,
|
|
330
371
|
hiddenInput,
|
|
331
|
-
dropdownContainer
|
|
372
|
+
dropdownContainer
|
|
332
373
|
} = this.options;
|
|
333
374
|
|
|
334
375
|
// containers (mostly for positioning)
|
|
335
|
-
let parentClass =
|
|
336
|
-
if (allowDropdown)
|
|
337
|
-
|
|
338
|
-
|
|
376
|
+
let parentClass = "iti";
|
|
377
|
+
if (allowDropdown) {
|
|
378
|
+
parentClass += " iti--allow-dropdown";
|
|
379
|
+
}
|
|
380
|
+
if (separateDialCode) {
|
|
381
|
+
parentClass += " iti--separate-dial-code";
|
|
382
|
+
}
|
|
383
|
+
if (showFlags) {
|
|
384
|
+
parentClass += " iti--show-flags";
|
|
385
|
+
}
|
|
339
386
|
if (customContainer) {
|
|
340
387
|
parentClass += ` ${customContainer}`;
|
|
341
388
|
}
|
|
342
389
|
|
|
343
|
-
const wrapper = this._createEl(
|
|
390
|
+
const wrapper = this._createEl("div", { class: parentClass });
|
|
344
391
|
this.telInput.parentNode.insertBefore(wrapper, this.telInput);
|
|
345
392
|
// only hide the flagsContainer if allowDropdown, showFlags and separateDialCode are all false
|
|
346
|
-
const showFlagsContainer =
|
|
393
|
+
const showFlagsContainer = allowDropdown || showFlags || separateDialCode;
|
|
347
394
|
if (showFlagsContainer) {
|
|
348
|
-
this.flagsContainer = this._createEl(
|
|
395
|
+
this.flagsContainer = this._createEl(
|
|
396
|
+
"div",
|
|
397
|
+
{ class: "iti__flag-container" },
|
|
398
|
+
wrapper
|
|
399
|
+
);
|
|
349
400
|
}
|
|
350
401
|
wrapper.appendChild(this.telInput);
|
|
351
402
|
|
|
@@ -353,58 +404,78 @@ class Iti {
|
|
|
353
404
|
// using Aria tags for "Select-Only Combobox Example"
|
|
354
405
|
// https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-select-only/
|
|
355
406
|
if (showFlagsContainer) {
|
|
356
|
-
this.selectedFlag = this._createEl(
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
407
|
+
this.selectedFlag = this._createEl(
|
|
408
|
+
"div",
|
|
409
|
+
{
|
|
410
|
+
class: "iti__selected-flag",
|
|
411
|
+
...(allowDropdown && {
|
|
412
|
+
role: "combobox",
|
|
413
|
+
"aria-haspopup": "listbox",
|
|
414
|
+
"aria-controls": `iti-${this.id}__country-listbox`,
|
|
415
|
+
"aria-owns": `iti-${this.id}__country-listbox`,
|
|
416
|
+
"aria-expanded": "false",
|
|
417
|
+
"aria-label": "Telephone country code"
|
|
418
|
+
})
|
|
419
|
+
},
|
|
420
|
+
this.flagsContainer
|
|
421
|
+
);
|
|
367
422
|
}
|
|
368
423
|
if (showFlags) {
|
|
369
|
-
this.selectedFlagInner = this._createEl(
|
|
424
|
+
this.selectedFlagInner = this._createEl(
|
|
425
|
+
"div",
|
|
426
|
+
{ class: "iti__flag" },
|
|
427
|
+
this.selectedFlag
|
|
428
|
+
);
|
|
370
429
|
}
|
|
371
430
|
|
|
372
431
|
if (this.selectedFlag && this.telInput.disabled) {
|
|
373
|
-
this.selectedFlag.setAttribute(
|
|
432
|
+
this.selectedFlag.setAttribute("aria-disabled", "true");
|
|
374
433
|
}
|
|
375
434
|
|
|
376
435
|
if (separateDialCode) {
|
|
377
|
-
this.selectedDialCode = this._createEl(
|
|
436
|
+
this.selectedDialCode = this._createEl(
|
|
437
|
+
"div",
|
|
438
|
+
{ class: "iti__selected-dial-code" },
|
|
439
|
+
this.selectedFlag
|
|
440
|
+
);
|
|
378
441
|
}
|
|
379
442
|
|
|
380
443
|
if (allowDropdown) {
|
|
381
444
|
if (!this.telInput.disabled) {
|
|
382
445
|
// make element focusable and tab navigable
|
|
383
|
-
this.selectedFlag.setAttribute(
|
|
446
|
+
this.selectedFlag.setAttribute("tabindex", "0");
|
|
384
447
|
}
|
|
385
448
|
|
|
386
|
-
this.dropdownArrow = this._createEl(
|
|
449
|
+
this.dropdownArrow = this._createEl(
|
|
450
|
+
"div",
|
|
451
|
+
{ class: "iti__arrow" },
|
|
452
|
+
this.selectedFlag
|
|
453
|
+
);
|
|
387
454
|
|
|
388
455
|
// country dropdown: preferred countries, then divider, then all countries
|
|
389
|
-
this.countryList = this._createEl(
|
|
390
|
-
class:
|
|
456
|
+
this.countryList = this._createEl("ul", {
|
|
457
|
+
class: "iti__country-list iti__hide",
|
|
391
458
|
id: `iti-${this.id}__country-listbox`,
|
|
392
|
-
role:
|
|
393
|
-
|
|
459
|
+
role: "listbox",
|
|
460
|
+
"aria-label": "List of countries"
|
|
394
461
|
});
|
|
395
462
|
if (this.preferredCountries.length) {
|
|
396
|
-
this._appendListItems(this.preferredCountries,
|
|
397
|
-
this._createEl(
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
463
|
+
this._appendListItems(this.preferredCountries, "iti__preferred", true);
|
|
464
|
+
this._createEl(
|
|
465
|
+
"li",
|
|
466
|
+
{
|
|
467
|
+
class: "iti__divider",
|
|
468
|
+
role: "separator",
|
|
469
|
+
"aria-disabled": "true"
|
|
470
|
+
},
|
|
471
|
+
this.countryList
|
|
472
|
+
);
|
|
402
473
|
}
|
|
403
|
-
this._appendListItems(this.countries,
|
|
474
|
+
this._appendListItems(this.countries, "iti__standard");
|
|
404
475
|
|
|
405
476
|
// create dropdownContainer markup
|
|
406
477
|
if (dropdownContainer) {
|
|
407
|
-
this.dropdown = this._createEl(
|
|
478
|
+
this.dropdown = this._createEl("div", { class: "iti iti--container" });
|
|
408
479
|
this.dropdown.appendChild(this.countryList);
|
|
409
480
|
} else {
|
|
410
481
|
this.flagsContainer.appendChild(this.countryList);
|
|
@@ -413,31 +484,32 @@ class Iti {
|
|
|
413
484
|
|
|
414
485
|
if (hiddenInput) {
|
|
415
486
|
let hiddenInputName = hiddenInput;
|
|
416
|
-
const name = this.telInput.getAttribute(
|
|
487
|
+
const name = this.telInput.getAttribute("name");
|
|
417
488
|
if (name) {
|
|
418
|
-
const i = name.lastIndexOf(
|
|
489
|
+
const i = name.lastIndexOf("[");
|
|
419
490
|
// if input name contains square brackets, then give the hidden input the same name,
|
|
420
491
|
// replacing the contents of the last set of brackets with the given hiddenInput name
|
|
421
|
-
if (i !== -1)
|
|
492
|
+
if (i !== -1) {
|
|
493
|
+
hiddenInputName = `${name.substr(0, i)}[${hiddenInputName}]`;
|
|
494
|
+
}
|
|
422
495
|
}
|
|
423
|
-
this.hiddenInput = this._createEl(
|
|
424
|
-
type:
|
|
425
|
-
name: hiddenInputName
|
|
496
|
+
this.hiddenInput = this._createEl("input", {
|
|
497
|
+
type: "hidden",
|
|
498
|
+
name: hiddenInputName
|
|
426
499
|
});
|
|
427
500
|
wrapper.appendChild(this.hiddenInput);
|
|
428
501
|
}
|
|
429
502
|
}
|
|
430
503
|
|
|
431
|
-
|
|
432
504
|
// add a country <li> to the countryList <ul> container
|
|
433
505
|
_appendListItems(countries, className, preferred) {
|
|
434
506
|
// we create so many DOM elements, it is faster to build a temp string
|
|
435
507
|
// and then add everything to the DOM in one go at the end
|
|
436
|
-
let tmp =
|
|
508
|
+
let tmp = "";
|
|
437
509
|
// for each country
|
|
438
510
|
for (let i = 0; i < countries.length; i++) {
|
|
439
511
|
const c = countries[i];
|
|
440
|
-
const idSuffix = preferred ?
|
|
512
|
+
const idSuffix = preferred ? "-preferred" : "";
|
|
441
513
|
// open the list item
|
|
442
514
|
tmp += `<li class='iti__country ${className}' tabIndex='-1' id='iti-${this.id}__item-${c.iso2}${idSuffix}' role='option' data-dial-code='${c.dialCode}' data-country-code='${c.iso2}' aria-selected='false'>`;
|
|
443
515
|
// add the flag
|
|
@@ -448,12 +520,11 @@ class Iti {
|
|
|
448
520
|
tmp += `<span class='iti__country-name'>${c.name}</span>`;
|
|
449
521
|
tmp += `<span class='iti__dial-code'>+${c.dialCode}</span>`;
|
|
450
522
|
// close the list item
|
|
451
|
-
tmp +=
|
|
523
|
+
tmp += "</li>";
|
|
452
524
|
}
|
|
453
|
-
this.countryList.insertAdjacentHTML(
|
|
525
|
+
this.countryList.insertAdjacentHTML("beforeend", tmp);
|
|
454
526
|
}
|
|
455
527
|
|
|
456
|
-
|
|
457
528
|
// set the initial state of the input value and the selected flag by:
|
|
458
529
|
// 1. extracting a dial code from the given number
|
|
459
530
|
// 2. using explicit initialCountry
|
|
@@ -463,32 +534,33 @@ class Iti {
|
|
|
463
534
|
// fix firefox bug: when first load page (with input with value set to number with intl dial
|
|
464
535
|
// code) and initialising plugin removes the dial code from the input, then refresh page,
|
|
465
536
|
// and we try to init plugin again but this time on number without dial code so get grey flag
|
|
466
|
-
const attributeValue = this.telInput.getAttribute(
|
|
537
|
+
const attributeValue = this.telInput.getAttribute("value");
|
|
467
538
|
const inputValue = this.telInput.value;
|
|
468
|
-
const useAttribute =
|
|
539
|
+
const useAttribute =
|
|
540
|
+
attributeValue &&
|
|
541
|
+
attributeValue.charAt(0) === "+" &&
|
|
542
|
+
(!inputValue || inputValue.charAt(0) !== "+");
|
|
469
543
|
const val = useAttribute ? attributeValue : inputValue;
|
|
470
544
|
const dialCode = this._getDialCode(val);
|
|
471
545
|
const isRegionlessNanp = this._isRegionlessNanp(val);
|
|
472
|
-
const {
|
|
473
|
-
initialCountry,
|
|
474
|
-
autoInsertDialCode,
|
|
475
|
-
} = this.options;
|
|
546
|
+
const { initialCountry, autoInsertDialCode } = this.options;
|
|
476
547
|
|
|
477
548
|
// if we already have a dial code, and it's not a regionlessNanp, we can go ahead and set the
|
|
478
549
|
// flag, else fall back to the default country
|
|
479
550
|
if (dialCode && !isRegionlessNanp) {
|
|
480
551
|
this._updateFlagFromNumber(val);
|
|
481
|
-
} else if (initialCountry !==
|
|
552
|
+
} else if (initialCountry !== "auto") {
|
|
482
553
|
// see if we should select a flag
|
|
483
554
|
if (initialCountry) {
|
|
484
555
|
this._setFlag(initialCountry.toLowerCase());
|
|
485
556
|
} else {
|
|
486
557
|
if (dialCode && isRegionlessNanp) {
|
|
487
558
|
// has intl dial code, is regionless nanp, and no initialCountry, so default to US
|
|
488
|
-
this._setFlag(
|
|
559
|
+
this._setFlag("us");
|
|
489
560
|
} else {
|
|
490
561
|
// no dial code and no initialCountry, so default to first in list
|
|
491
|
-
this.defaultCountry =
|
|
562
|
+
this.defaultCountry = this.preferredCountries.length
|
|
563
|
+
? this.preferredCountries[0].iso2
|
|
492
564
|
: this.countries[0].iso2;
|
|
493
565
|
if (!val) {
|
|
494
566
|
this._setFlag(this.defaultCountry);
|
|
@@ -504,36 +576,47 @@ class Iti {
|
|
|
504
576
|
// NOTE: if initialCountry is set to auto, that will be handled separately
|
|
505
577
|
|
|
506
578
|
// format - note this wont be run after _updateDialCode as that's only called if no val
|
|
507
|
-
if (val)
|
|
579
|
+
if (val) {
|
|
580
|
+
this._updateValFromNumber(val);
|
|
581
|
+
}
|
|
508
582
|
}
|
|
509
583
|
|
|
510
|
-
|
|
511
584
|
// initialise the main event listeners: input keyup, and click selected flag
|
|
512
585
|
_initListeners() {
|
|
513
586
|
this._initKeyListeners();
|
|
514
|
-
if (this.options.autoInsertDialCode)
|
|
515
|
-
|
|
516
|
-
|
|
587
|
+
if (this.options.autoInsertDialCode) {
|
|
588
|
+
this._initBlurListeners();
|
|
589
|
+
}
|
|
590
|
+
if (this.options.allowDropdown) {
|
|
591
|
+
this._initDropdownListeners();
|
|
592
|
+
}
|
|
593
|
+
if (this.hiddenInput) {
|
|
594
|
+
this._initHiddenInputListener();
|
|
595
|
+
}
|
|
517
596
|
}
|
|
518
597
|
|
|
519
|
-
|
|
520
598
|
// update hidden input on form submit
|
|
521
599
|
_initHiddenInputListener() {
|
|
522
600
|
this._handleHiddenInputSubmit = () => {
|
|
523
601
|
this.hiddenInput.value = this.getNumber();
|
|
524
602
|
};
|
|
525
|
-
if (this.telInput.form)
|
|
603
|
+
if (this.telInput.form) {
|
|
604
|
+
this.telInput.form.addEventListener(
|
|
605
|
+
"submit",
|
|
606
|
+
this._handleHiddenInputSubmit
|
|
607
|
+
);
|
|
608
|
+
}
|
|
526
609
|
}
|
|
527
610
|
|
|
528
|
-
|
|
529
611
|
// iterate through parent nodes to find the closest label ancestor, if it exists
|
|
530
612
|
_getClosestLabel() {
|
|
531
613
|
let el = this.telInput;
|
|
532
|
-
while (el && el.tagName !==
|
|
614
|
+
while (el && el.tagName !== "LABEL") {
|
|
615
|
+
el = el.parentNode;
|
|
616
|
+
}
|
|
533
617
|
return el;
|
|
534
618
|
}
|
|
535
619
|
|
|
536
|
-
|
|
537
620
|
// initialise the dropdown listeners
|
|
538
621
|
_initDropdownListeners() {
|
|
539
622
|
// hack for input nested inside label (which is valid markup): clicking the selected-flag to
|
|
@@ -541,28 +624,41 @@ class Iti {
|
|
|
541
624
|
// close it again
|
|
542
625
|
this._handleLabelClick = (e) => {
|
|
543
626
|
// if the dropdown is closed, then focus the input, else ignore the click
|
|
544
|
-
if (this.countryList.classList.contains(
|
|
545
|
-
|
|
627
|
+
if (this.countryList.classList.contains("iti__hide")) {
|
|
628
|
+
this.telInput.focus();
|
|
629
|
+
} else {
|
|
630
|
+
e.preventDefault();
|
|
631
|
+
}
|
|
546
632
|
};
|
|
547
633
|
const label = this._getClosestLabel();
|
|
548
|
-
if (label)
|
|
634
|
+
if (label) {
|
|
635
|
+
label.addEventListener("click", this._handleLabelClick);
|
|
636
|
+
}
|
|
549
637
|
|
|
550
638
|
// toggle country dropdown on click
|
|
551
639
|
this._handleClickSelectedFlag = () => {
|
|
552
640
|
// only intercept this event if we're opening the dropdown
|
|
553
641
|
// else let it bubble up to the top ("click-off-to-close" listener)
|
|
554
642
|
// we cannot just stopPropagation as it may be needed to close another instance
|
|
555
|
-
if (
|
|
643
|
+
if (
|
|
644
|
+
this.countryList.classList.contains("iti__hide") &&
|
|
645
|
+
!this.telInput.disabled &&
|
|
646
|
+
!this.telInput.readOnly
|
|
647
|
+
) {
|
|
556
648
|
this._showDropdown();
|
|
557
649
|
}
|
|
558
650
|
};
|
|
559
|
-
this.selectedFlag.addEventListener(
|
|
651
|
+
this.selectedFlag.addEventListener("click", this._handleClickSelectedFlag);
|
|
560
652
|
|
|
561
653
|
// open dropdown list if currently focused
|
|
562
654
|
this._handleFlagsContainerKeydown = (e) => {
|
|
563
|
-
const isDropdownHidden = this.countryList.classList.contains(
|
|
655
|
+
const isDropdownHidden = this.countryList.classList.contains("iti__hide");
|
|
564
656
|
|
|
565
|
-
if (
|
|
657
|
+
if (
|
|
658
|
+
isDropdownHidden &&
|
|
659
|
+
["ArrowUp", "Up", "ArrowDown", "Down", " ", "Enter"].indexOf(e.key) !==
|
|
660
|
+
-1
|
|
661
|
+
) {
|
|
566
662
|
// prevent form from being submitted if "ENTER" was pressed
|
|
567
663
|
e.preventDefault();
|
|
568
664
|
// prevent event from being handled again by document
|
|
@@ -571,12 +667,16 @@ class Iti {
|
|
|
571
667
|
}
|
|
572
668
|
|
|
573
669
|
// allow navigation from dropdown to input on TAB
|
|
574
|
-
if (e.key ===
|
|
670
|
+
if (e.key === "Tab") {
|
|
671
|
+
this._closeDropdown();
|
|
672
|
+
}
|
|
575
673
|
};
|
|
576
|
-
this.flagsContainer.addEventListener(
|
|
674
|
+
this.flagsContainer.addEventListener(
|
|
675
|
+
"keydown",
|
|
676
|
+
this._handleFlagsContainerKeydown
|
|
677
|
+
);
|
|
577
678
|
}
|
|
578
679
|
|
|
579
|
-
|
|
580
680
|
// init many requests: utils script / geo ip lookup
|
|
581
681
|
_initRequests() {
|
|
582
682
|
// if the user has specified the path to the utils script, fetch it on window.load, else resolve
|
|
@@ -586,17 +686,21 @@ class Iti {
|
|
|
586
686
|
window.intlTelInputGlobals.loadUtils(this.options.utilsScript);
|
|
587
687
|
} else {
|
|
588
688
|
// wait until the load event so we don't block any other requests e.g. the flags image
|
|
589
|
-
window.addEventListener(
|
|
689
|
+
window.addEventListener("load", () => {
|
|
590
690
|
window.intlTelInputGlobals.loadUtils(this.options.utilsScript);
|
|
591
691
|
});
|
|
592
692
|
}
|
|
593
|
-
} else
|
|
693
|
+
} else {
|
|
694
|
+
this.resolveUtilsScriptPromise();
|
|
695
|
+
}
|
|
594
696
|
|
|
595
|
-
if (this.options.initialCountry ===
|
|
596
|
-
|
|
697
|
+
if (this.options.initialCountry === "auto") {
|
|
698
|
+
this._loadAutoCountry();
|
|
699
|
+
} else {
|
|
700
|
+
this.resolveAutoCountryPromise();
|
|
701
|
+
}
|
|
597
702
|
}
|
|
598
703
|
|
|
599
|
-
|
|
600
704
|
// perform the geo ip lookup
|
|
601
705
|
_loadAutoCountry() {
|
|
602
706
|
// 3 options:
|
|
@@ -609,22 +713,24 @@ class Iti {
|
|
|
609
713
|
// don't do this twice!
|
|
610
714
|
window.intlTelInputGlobals.startedLoadingAutoCountry = true;
|
|
611
715
|
|
|
612
|
-
if (typeof this.options.geoIpLookup ===
|
|
613
|
-
this.options.geoIpLookup(
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
716
|
+
if (typeof this.options.geoIpLookup === "function") {
|
|
717
|
+
this.options.geoIpLookup(
|
|
718
|
+
(countryCode) => {
|
|
719
|
+
window.intlTelInputGlobals.autoCountry = countryCode.toLowerCase();
|
|
720
|
+
// tell all instances the auto country is ready
|
|
721
|
+
// TODO: this should just be the current instances
|
|
722
|
+
// UPDATE: use setTimeout in case their geoIpLookup function calls this callback straight
|
|
723
|
+
// away (e.g. if they have already done the geo ip lookup somewhere else). Using
|
|
724
|
+
// setTimeout means that the current thread of execution will finish before executing
|
|
725
|
+
// this, which allows the plugin to finish initialising.
|
|
726
|
+
setTimeout(() => forEachInstance("handleAutoCountry"));
|
|
727
|
+
},
|
|
728
|
+
() => forEachInstance("rejectAutoCountryPromise")
|
|
729
|
+
);
|
|
623
730
|
}
|
|
624
731
|
}
|
|
625
732
|
}
|
|
626
733
|
|
|
627
|
-
|
|
628
734
|
// initialize any key listeners
|
|
629
735
|
_initKeyListeners() {
|
|
630
736
|
// update flag on keyup
|
|
@@ -633,71 +739,70 @@ class Iti {
|
|
|
633
739
|
this._triggerCountryChange();
|
|
634
740
|
}
|
|
635
741
|
};
|
|
636
|
-
this.telInput.addEventListener(
|
|
742
|
+
this.telInput.addEventListener("keyup", this._handleKeyupEvent);
|
|
637
743
|
|
|
638
744
|
// update flag on cut/paste events (now supported in all major browsers)
|
|
639
745
|
this._handleClipboardEvent = () => {
|
|
640
746
|
// hack because "paste" event is fired before input is updated
|
|
641
747
|
setTimeout(this._handleKeyupEvent);
|
|
642
748
|
};
|
|
643
|
-
this.telInput.addEventListener(
|
|
644
|
-
this.telInput.addEventListener(
|
|
749
|
+
this.telInput.addEventListener("cut", this._handleClipboardEvent);
|
|
750
|
+
this.telInput.addEventListener("paste", this._handleClipboardEvent);
|
|
645
751
|
}
|
|
646
752
|
|
|
647
|
-
|
|
648
753
|
// adhere to the input's maxlength attr
|
|
649
754
|
_cap(number) {
|
|
650
|
-
const max = this.telInput.getAttribute(
|
|
651
|
-
return
|
|
755
|
+
const max = this.telInput.getAttribute("maxlength");
|
|
756
|
+
return max && number.length > max ? number.substr(0, max) : number;
|
|
652
757
|
}
|
|
653
758
|
|
|
654
|
-
|
|
655
759
|
// listen for blur/submit (for autoInsertDialCode feature)
|
|
656
760
|
_initBlurListeners() {
|
|
657
761
|
// on blur or form submit: if just a dial code then remove it
|
|
658
762
|
this._handleSubmitOrBlurEvent = () => {
|
|
659
763
|
this._removeEmptyDialCode();
|
|
660
764
|
};
|
|
661
|
-
if (this.telInput.form)
|
|
662
|
-
|
|
765
|
+
if (this.telInput.form) {
|
|
766
|
+
this.telInput.form.addEventListener(
|
|
767
|
+
"submit",
|
|
768
|
+
this._handleSubmitOrBlurEvent
|
|
769
|
+
);
|
|
770
|
+
}
|
|
771
|
+
this.telInput.addEventListener("blur", this._handleSubmitOrBlurEvent);
|
|
663
772
|
|
|
664
773
|
// made the decision not to trigger blur() now, because would only do anything in the case
|
|
665
774
|
// where they manually set the initial value to just a dial code, in which case they probably
|
|
666
775
|
// want it to be displayed.
|
|
667
776
|
}
|
|
668
777
|
|
|
669
|
-
|
|
670
778
|
// clear the input if it just contains a dial code
|
|
671
779
|
_removeEmptyDialCode() {
|
|
672
|
-
if (this.telInput.value.charAt(0) ===
|
|
780
|
+
if (this.telInput.value.charAt(0) === "+") {
|
|
673
781
|
const numeric = this._getNumeric(this.telInput.value);
|
|
674
782
|
// if just a plus, or if just a dial code
|
|
675
783
|
if (!numeric || this.selectedCountryData.dialCode === numeric) {
|
|
676
|
-
this.telInput.value =
|
|
784
|
+
this.telInput.value = "";
|
|
677
785
|
}
|
|
678
786
|
}
|
|
679
787
|
}
|
|
680
788
|
|
|
681
|
-
|
|
682
789
|
// extract the numeric digits from the given string
|
|
683
790
|
_getNumeric(s) {
|
|
684
|
-
return s.replace(/\D/g,
|
|
791
|
+
return s.replace(/\D/g, "");
|
|
685
792
|
}
|
|
686
793
|
|
|
687
|
-
|
|
688
794
|
// trigger a custom event on the input
|
|
689
795
|
_trigger(name) {
|
|
690
796
|
// have to use old school document.createEvent as IE11 doesn't support `new Event()` syntax
|
|
691
|
-
const e = document.createEvent(
|
|
797
|
+
const e = document.createEvent("Event");
|
|
692
798
|
e.initEvent(name, true, true); // can bubble, and is cancellable
|
|
693
799
|
this.telInput.dispatchEvent(e);
|
|
694
800
|
}
|
|
695
801
|
|
|
696
|
-
|
|
697
802
|
// show the dropdown
|
|
698
803
|
_showDropdown() {
|
|
699
|
-
this.countryList.classList.remove(
|
|
700
|
-
this.selectedFlag.setAttribute(
|
|
804
|
+
this.countryList.classList.remove("iti__hide");
|
|
805
|
+
this.selectedFlag.setAttribute("aria-expanded", "true");
|
|
701
806
|
|
|
702
807
|
this._setDropdownPosition();
|
|
703
808
|
|
|
@@ -711,19 +816,20 @@ class Iti {
|
|
|
711
816
|
this._bindDropdownListeners();
|
|
712
817
|
|
|
713
818
|
// update the arrow
|
|
714
|
-
this.dropdownArrow.classList.add(
|
|
819
|
+
this.dropdownArrow.classList.add("iti__arrow--up");
|
|
715
820
|
|
|
716
|
-
this._trigger(
|
|
821
|
+
this._trigger("open:countrydropdown");
|
|
717
822
|
}
|
|
718
823
|
|
|
719
|
-
|
|
720
824
|
// make sure the el has the className or not, depending on the value of shouldHaveClass
|
|
721
825
|
_toggleClass(el, className, shouldHaveClass) {
|
|
722
|
-
if (shouldHaveClass && !el.classList.contains(className))
|
|
723
|
-
|
|
826
|
+
if (shouldHaveClass && !el.classList.contains(className)) {
|
|
827
|
+
el.classList.add(className);
|
|
828
|
+
} else if (!shouldHaveClass && el.classList.contains(className)) {
|
|
829
|
+
el.classList.remove(className);
|
|
830
|
+
}
|
|
724
831
|
}
|
|
725
832
|
|
|
726
|
-
|
|
727
833
|
// decide where to position dropdown (depends on position within viewport, and scroll)
|
|
728
834
|
_setDropdownPosition() {
|
|
729
835
|
if (this.options.dropdownContainer) {
|
|
@@ -733,23 +839,32 @@ class Iti {
|
|
|
733
839
|
if (!this.isMobile) {
|
|
734
840
|
const pos = this.telInput.getBoundingClientRect();
|
|
735
841
|
// windowTop from https://stackoverflow.com/a/14384091/217866
|
|
736
|
-
const windowTop =
|
|
842
|
+
const windowTop =
|
|
843
|
+
window.pageYOffset || document.documentElement.scrollTop;
|
|
737
844
|
const inputTop = pos.top + windowTop;
|
|
738
845
|
const dropdownHeight = this.countryList.offsetHeight;
|
|
739
846
|
// dropdownFitsBelow = (dropdownBottom < windowBottom)
|
|
740
|
-
const dropdownFitsBelow =
|
|
741
|
-
|
|
742
|
-
|
|
847
|
+
const dropdownFitsBelow =
|
|
848
|
+
inputTop + this.telInput.offsetHeight + dropdownHeight <
|
|
849
|
+
windowTop + window.innerHeight;
|
|
850
|
+
const dropdownFitsAbove = inputTop - dropdownHeight > windowTop;
|
|
743
851
|
|
|
744
852
|
// by default, the dropdown will be below the input. If we want to position it above the
|
|
745
853
|
// input, we add the dropup class.
|
|
746
|
-
this._toggleClass(
|
|
854
|
+
this._toggleClass(
|
|
855
|
+
this.countryList,
|
|
856
|
+
"iti__country-list--dropup",
|
|
857
|
+
!dropdownFitsBelow && dropdownFitsAbove
|
|
858
|
+
);
|
|
747
859
|
|
|
748
860
|
// if dropdownContainer is enabled, calculate postion
|
|
749
861
|
if (this.options.dropdownContainer) {
|
|
750
862
|
// by default the dropdown will be directly over the input because it's not in the flow.
|
|
751
863
|
// If we want to position it below, we need to add some extra top value.
|
|
752
|
-
const extraTop =
|
|
864
|
+
const extraTop =
|
|
865
|
+
!dropdownFitsBelow && dropdownFitsAbove
|
|
866
|
+
? 0
|
|
867
|
+
: this.telInput.offsetHeight;
|
|
753
868
|
|
|
754
869
|
// calculate placement
|
|
755
870
|
this.dropdown.style.top = `${inputTop + extraTop}px`;
|
|
@@ -757,21 +872,25 @@ class Iti {
|
|
|
757
872
|
|
|
758
873
|
// close menu on window scroll
|
|
759
874
|
this._handleWindowScroll = () => this._closeDropdown();
|
|
760
|
-
window.addEventListener(
|
|
875
|
+
window.addEventListener("scroll", this._handleWindowScroll);
|
|
761
876
|
}
|
|
762
877
|
}
|
|
763
878
|
}
|
|
764
879
|
|
|
765
|
-
|
|
766
880
|
// iterate through parent nodes to find the closest list item
|
|
767
881
|
_getClosestListItem(target) {
|
|
768
882
|
let el = target;
|
|
769
|
-
while (
|
|
883
|
+
while (
|
|
884
|
+
el &&
|
|
885
|
+
el !== this.countryList &&
|
|
886
|
+
!el.classList.contains("iti__country")
|
|
887
|
+
) {
|
|
888
|
+
el = el.parentNode;
|
|
889
|
+
}
|
|
770
890
|
// if we reached the countryList element, then return null
|
|
771
|
-
return
|
|
891
|
+
return el === this.countryList ? null : el;
|
|
772
892
|
}
|
|
773
893
|
|
|
774
|
-
|
|
775
894
|
// we only bind dropdown listeners when the dropdown is open
|
|
776
895
|
_bindDropdownListeners() {
|
|
777
896
|
// when mouse over a list item, just highlight that one
|
|
@@ -779,32 +898,44 @@ class Iti {
|
|
|
779
898
|
this._handleMouseoverCountryList = (e) => {
|
|
780
899
|
// handle event delegation, as we're listening for this event on the countryList
|
|
781
900
|
const listItem = this._getClosestListItem(e.target);
|
|
782
|
-
if (listItem)
|
|
901
|
+
if (listItem) {
|
|
902
|
+
this._highlightListItem(listItem, false);
|
|
903
|
+
}
|
|
783
904
|
};
|
|
784
|
-
this.countryList.addEventListener(
|
|
905
|
+
this.countryList.addEventListener(
|
|
906
|
+
"mouseover",
|
|
907
|
+
this._handleMouseoverCountryList
|
|
908
|
+
);
|
|
785
909
|
|
|
786
910
|
// listen for country selection
|
|
787
911
|
this._handleClickCountryList = (e) => {
|
|
788
912
|
const listItem = this._getClosestListItem(e.target);
|
|
789
|
-
if (listItem)
|
|
913
|
+
if (listItem) {
|
|
914
|
+
this._selectListItem(listItem);
|
|
915
|
+
}
|
|
790
916
|
};
|
|
791
|
-
this.countryList.addEventListener(
|
|
917
|
+
this.countryList.addEventListener("click", this._handleClickCountryList);
|
|
792
918
|
|
|
793
919
|
// click off to close
|
|
794
920
|
// (except when this initial opening click is bubbling up)
|
|
795
921
|
// we cannot just stopPropagation as it may be needed to close another instance
|
|
796
922
|
let isOpening = true;
|
|
797
923
|
this._handleClickOffToClose = () => {
|
|
798
|
-
if (!isOpening)
|
|
924
|
+
if (!isOpening) {
|
|
925
|
+
this._closeDropdown();
|
|
926
|
+
}
|
|
799
927
|
isOpening = false;
|
|
800
928
|
};
|
|
801
|
-
document.documentElement.addEventListener(
|
|
929
|
+
document.documentElement.addEventListener(
|
|
930
|
+
"click",
|
|
931
|
+
this._handleClickOffToClose
|
|
932
|
+
);
|
|
802
933
|
|
|
803
934
|
// listen for up/down scrolling, enter to select, or letters to jump to country name.
|
|
804
935
|
// use keydown as keypress doesn't fire for non-char keys and we want to catch if they
|
|
805
936
|
// just hit down and hold it to scroll down (no keyup event).
|
|
806
937
|
// listen on the document because that's where key events are triggered if no input has focus
|
|
807
|
-
let query =
|
|
938
|
+
let query = "";
|
|
808
939
|
let queryTimer = null;
|
|
809
940
|
this._handleKeydownOnDropdown = (e) => {
|
|
810
941
|
// prevent down key from scrolling the whole page,
|
|
@@ -812,52 +943,72 @@ class Iti {
|
|
|
812
943
|
e.preventDefault();
|
|
813
944
|
|
|
814
945
|
// up and down to navigate
|
|
815
|
-
if (
|
|
946
|
+
if (
|
|
947
|
+
e.key === "ArrowUp" ||
|
|
948
|
+
e.key === "Up" ||
|
|
949
|
+
e.key === "ArrowDown" ||
|
|
950
|
+
e.key === "Down"
|
|
951
|
+
) {
|
|
952
|
+
this._handleUpDownKey(e.key);
|
|
953
|
+
}
|
|
816
954
|
// enter to select
|
|
817
|
-
else if (e.key ===
|
|
955
|
+
else if (e.key === "Enter") {
|
|
956
|
+
this._handleEnterKey();
|
|
957
|
+
}
|
|
818
958
|
// esc to close
|
|
819
|
-
else if (e.key ===
|
|
959
|
+
else if (e.key === "Escape") {
|
|
960
|
+
this._closeDropdown();
|
|
961
|
+
}
|
|
820
962
|
// alpha chars to perform search
|
|
821
963
|
// regex allows one latin alpha char or space, based on https://stackoverflow.com/a/26900132/217866)
|
|
822
964
|
else if (/^[a-zA-ZÀ-ÿа-яА-Я ]$/.test(e.key)) {
|
|
823
965
|
// jump to countries that start with the query string
|
|
824
|
-
if (queryTimer)
|
|
966
|
+
if (queryTimer) {
|
|
967
|
+
clearTimeout(queryTimer);
|
|
968
|
+
}
|
|
825
969
|
query += e.key.toLowerCase();
|
|
826
970
|
this._searchForCountry(query);
|
|
827
971
|
// if the timer hits 1 second, reset the query
|
|
828
972
|
queryTimer = setTimeout(() => {
|
|
829
|
-
query =
|
|
973
|
+
query = "";
|
|
830
974
|
}, 1000);
|
|
831
975
|
}
|
|
832
976
|
};
|
|
833
|
-
document.addEventListener(
|
|
977
|
+
document.addEventListener("keydown", this._handleKeydownOnDropdown);
|
|
834
978
|
}
|
|
835
979
|
|
|
836
|
-
|
|
837
980
|
// highlight the next/prev item in the list (and ensure it is visible)
|
|
838
981
|
_handleUpDownKey(key) {
|
|
839
|
-
let next =
|
|
982
|
+
let next =
|
|
983
|
+
key === "ArrowUp" || key === "Up"
|
|
984
|
+
? this.highlightedItem.previousElementSibling
|
|
985
|
+
: this.highlightedItem.nextElementSibling;
|
|
840
986
|
if (next) {
|
|
841
987
|
// skip the divider
|
|
842
|
-
if (next.classList.contains(
|
|
843
|
-
next =
|
|
988
|
+
if (next.classList.contains("iti__divider")) {
|
|
989
|
+
next =
|
|
990
|
+
key === "ArrowUp" || key === "Up"
|
|
991
|
+
? next.previousElementSibling
|
|
992
|
+
: next.nextElementSibling;
|
|
844
993
|
}
|
|
845
994
|
this._highlightListItem(next, true);
|
|
846
995
|
}
|
|
847
996
|
}
|
|
848
997
|
|
|
849
|
-
|
|
850
998
|
// select the currently highlighted item
|
|
851
999
|
_handleEnterKey() {
|
|
852
|
-
if (this.highlightedItem)
|
|
1000
|
+
if (this.highlightedItem) {
|
|
1001
|
+
this._selectListItem(this.highlightedItem);
|
|
1002
|
+
}
|
|
853
1003
|
}
|
|
854
1004
|
|
|
855
|
-
|
|
856
1005
|
// find the first list item whose name starts with the query string
|
|
857
1006
|
_searchForCountry(query) {
|
|
858
1007
|
for (let i = 0; i < this.countries.length; i++) {
|
|
859
1008
|
if (this._startsWith(this.countries[i].name, query)) {
|
|
860
|
-
const listItem = this.countryList.querySelector(
|
|
1009
|
+
const listItem = this.countryList.querySelector(
|
|
1010
|
+
`#iti-${this.id}__item-${this.countries[i].iso2}`
|
|
1011
|
+
);
|
|
861
1012
|
// update highlighting and scroll
|
|
862
1013
|
this._highlightListItem(listItem, false);
|
|
863
1014
|
this._scrollTo(listItem, true);
|
|
@@ -866,47 +1017,60 @@ class Iti {
|
|
|
866
1017
|
}
|
|
867
1018
|
}
|
|
868
1019
|
|
|
869
|
-
|
|
870
1020
|
// check if string a starts with string b
|
|
871
1021
|
_startsWith(a, b) {
|
|
872
|
-
return
|
|
1022
|
+
return a.substr(0, b.length).toLowerCase() === b;
|
|
873
1023
|
}
|
|
874
1024
|
|
|
875
|
-
|
|
876
1025
|
// update the input's value to the given val (format first if possible)
|
|
877
1026
|
// NOTE: this is called from _setInitialState, handleUtils and setNumber
|
|
878
1027
|
_updateValFromNumber(originalNumber) {
|
|
879
1028
|
let number = originalNumber;
|
|
880
|
-
if (
|
|
881
|
-
|
|
1029
|
+
if (
|
|
1030
|
+
this.options.formatOnDisplay &&
|
|
1031
|
+
window.intlTelInputUtils &&
|
|
1032
|
+
this.selectedCountryData
|
|
1033
|
+
) {
|
|
1034
|
+
const useNational =
|
|
1035
|
+
this.options.nationalMode ||
|
|
1036
|
+
(number.charAt(0) !== "+" && !this.options.separateDialCode);
|
|
882
1037
|
const { NATIONAL, INTERNATIONAL } = intlTelInputUtils.numberFormat;
|
|
883
1038
|
const format = useNational ? NATIONAL : INTERNATIONAL;
|
|
884
|
-
number = intlTelInputUtils.formatNumber(
|
|
1039
|
+
number = intlTelInputUtils.formatNumber(
|
|
1040
|
+
number,
|
|
1041
|
+
this.selectedCountryData.iso2,
|
|
1042
|
+
format
|
|
1043
|
+
);
|
|
885
1044
|
}
|
|
886
1045
|
|
|
887
1046
|
number = this._beforeSetNumber(number);
|
|
888
1047
|
this.telInput.value = number;
|
|
889
1048
|
}
|
|
890
1049
|
|
|
891
|
-
|
|
892
1050
|
// check if need to select a new flag based on the given number
|
|
893
1051
|
// Note: called from _setInitialState, keyup handler, setNumber
|
|
894
1052
|
_updateFlagFromNumber(originalNumber) {
|
|
895
|
-
// if we
|
|
1053
|
+
// if we already have US/Canada selected, make sure the number starts
|
|
896
1054
|
// with a +1 so _getDialCode will be able to extract the area code
|
|
897
1055
|
// update: if we dont yet have selectedCountryData, but we're here (trying to update the flag
|
|
898
1056
|
// from the number), that means we're initialising the plugin with a number that already has a
|
|
899
1057
|
// dial code, so fine to ignore this bit
|
|
900
1058
|
let number = originalNumber;
|
|
901
1059
|
const selectedDialCode = this.selectedCountryData.dialCode;
|
|
902
|
-
const isNanp =
|
|
903
|
-
if (number &&
|
|
904
|
-
if (number.charAt(0) !==
|
|
1060
|
+
const isNanp = selectedDialCode === "1";
|
|
1061
|
+
if (number && isNanp && number.charAt(0) !== "+") {
|
|
1062
|
+
if (number.charAt(0) !== "1") {
|
|
1063
|
+
number = `1${number}`;
|
|
1064
|
+
}
|
|
905
1065
|
number = `+${number}`;
|
|
906
1066
|
}
|
|
907
1067
|
|
|
908
1068
|
// if separateDialCode enabled, then consider the selected dial code to be part of the number
|
|
909
|
-
if (
|
|
1069
|
+
if (
|
|
1070
|
+
this.options.separateDialCode &&
|
|
1071
|
+
selectedDialCode &&
|
|
1072
|
+
number.charAt(0) !== "+"
|
|
1073
|
+
) {
|
|
910
1074
|
number = `+${selectedDialCode}${number}`;
|
|
911
1075
|
}
|
|
912
1076
|
|
|
@@ -920,9 +1084,11 @@ class Iti {
|
|
|
920
1084
|
// longer than the matched dial code because in this case we need to make sure that if
|
|
921
1085
|
// there are multiple country matches, that the first one is selected (note: we could
|
|
922
1086
|
// just check that here, but it requires the same loop that we already have later)
|
|
923
|
-
const alreadySelected =
|
|
924
|
-
|
|
925
|
-
|
|
1087
|
+
const alreadySelected =
|
|
1088
|
+
countryCodes.indexOf(this.selectedCountryData.iso2) !== -1 &&
|
|
1089
|
+
numeric.length <= dialCode.length - 1;
|
|
1090
|
+
const isRegionlessNanpNumber =
|
|
1091
|
+
selectedDialCode === "1" && this._isRegionlessNanp(numeric);
|
|
926
1092
|
|
|
927
1093
|
// only update the flag if:
|
|
928
1094
|
// A) NOT (we currently have a NANP flag selected, and the number is a regionlessNanp)
|
|
@@ -938,12 +1104,12 @@ class Iti {
|
|
|
938
1104
|
}
|
|
939
1105
|
}
|
|
940
1106
|
}
|
|
941
|
-
} else if (number.charAt(0) ===
|
|
1107
|
+
} else if (number.charAt(0) === "+" && numeric.length) {
|
|
942
1108
|
// invalid dial code, so empty
|
|
943
1109
|
// Note: use getNumeric here because the number has not been formatted yet, so could contain
|
|
944
1110
|
// bad chars
|
|
945
|
-
countryCode =
|
|
946
|
-
} else if (!number || number ===
|
|
1111
|
+
countryCode = "";
|
|
1112
|
+
} else if (!number || number === "+") {
|
|
947
1113
|
// empty, or just a plus, so default
|
|
948
1114
|
countryCode = this.defaultCountry;
|
|
949
1115
|
}
|
|
@@ -954,35 +1120,41 @@ class Iti {
|
|
|
954
1120
|
return false;
|
|
955
1121
|
}
|
|
956
1122
|
|
|
957
|
-
|
|
958
1123
|
// check if the given number is a regionless NANP number (expects the number to contain an
|
|
959
1124
|
// international dial code)
|
|
960
1125
|
_isRegionlessNanp(number) {
|
|
961
1126
|
const numeric = this._getNumeric(number);
|
|
962
|
-
if (numeric.charAt(0) ===
|
|
1127
|
+
if (numeric.charAt(0) === "1") {
|
|
963
1128
|
const areaCode = numeric.substr(1, 3);
|
|
964
|
-
return
|
|
1129
|
+
return regionlessNanpNumbers.indexOf(areaCode) !== -1;
|
|
965
1130
|
}
|
|
966
1131
|
return false;
|
|
967
1132
|
}
|
|
968
1133
|
|
|
969
|
-
|
|
970
1134
|
// remove highlighting from other list items and highlight the given item
|
|
971
1135
|
_highlightListItem(listItem, shouldFocus) {
|
|
972
1136
|
const prevItem = this.highlightedItem;
|
|
973
|
-
if (prevItem)
|
|
1137
|
+
if (prevItem) {
|
|
1138
|
+
prevItem.classList.remove("iti__highlight");
|
|
1139
|
+
}
|
|
974
1140
|
this.highlightedItem = listItem;
|
|
975
|
-
this.highlightedItem.classList.add(
|
|
976
|
-
this.selectedFlag.setAttribute(
|
|
977
|
-
|
|
978
|
-
|
|
1141
|
+
this.highlightedItem.classList.add("iti__highlight");
|
|
1142
|
+
this.selectedFlag.setAttribute(
|
|
1143
|
+
"aria-activedescendant",
|
|
1144
|
+
listItem.getAttribute("id")
|
|
1145
|
+
);
|
|
1146
|
+
|
|
1147
|
+
if (shouldFocus) {
|
|
1148
|
+
this.highlightedItem.focus();
|
|
1149
|
+
}
|
|
979
1150
|
}
|
|
980
1151
|
|
|
981
|
-
|
|
982
1152
|
// find the country data for the given country code
|
|
983
1153
|
// the ignoreOnlyCountriesOption is only used during init() while parsing the onlyCountries array
|
|
984
1154
|
_getCountryData(countryCode, ignoreOnlyCountriesOption, allowFail) {
|
|
985
|
-
const countryList =
|
|
1155
|
+
const countryList = ignoreOnlyCountriesOption
|
|
1156
|
+
? allCountries
|
|
1157
|
+
: this.countries;
|
|
986
1158
|
for (let i = 0; i < countryList.length; i++) {
|
|
987
1159
|
if (countryList[i].iso2 === countryCode) {
|
|
988
1160
|
return countryList[i];
|
|
@@ -994,33 +1166,44 @@ class Iti {
|
|
|
994
1166
|
throw new Error(`No country data for '${countryCode}'`);
|
|
995
1167
|
}
|
|
996
1168
|
|
|
997
|
-
|
|
998
1169
|
// select the given flag, update the placeholder and the active list item
|
|
999
1170
|
// Note: called from _setInitialState, _updateFlagFromNumber, _selectListItem, setCountry
|
|
1000
1171
|
_setFlag(countryCode) {
|
|
1001
|
-
const prevCountry =
|
|
1172
|
+
const prevCountry = this.selectedCountryData.iso2
|
|
1173
|
+
? this.selectedCountryData
|
|
1174
|
+
: {};
|
|
1002
1175
|
|
|
1003
1176
|
// do this first as it will throw an error and stop if countryCode is invalid
|
|
1004
|
-
this.selectedCountryData =
|
|
1177
|
+
this.selectedCountryData = countryCode
|
|
1178
|
+
? this._getCountryData(countryCode, false, false)
|
|
1179
|
+
: {};
|
|
1005
1180
|
// update the defaultCountry - we only need the iso2 from now on, so just store that
|
|
1006
1181
|
if (this.selectedCountryData.iso2) {
|
|
1007
1182
|
this.defaultCountry = this.selectedCountryData.iso2;
|
|
1008
1183
|
}
|
|
1009
1184
|
|
|
1010
1185
|
if (this.options.showFlags) {
|
|
1011
|
-
this.selectedFlagInner.setAttribute(
|
|
1186
|
+
this.selectedFlagInner.setAttribute(
|
|
1187
|
+
"class",
|
|
1188
|
+
`iti__flag iti__${countryCode}`
|
|
1189
|
+
);
|
|
1012
1190
|
}
|
|
1013
1191
|
// update the selected country's title attribute
|
|
1014
1192
|
if (this.selectedFlag) {
|
|
1015
|
-
const title =
|
|
1016
|
-
|
|
1193
|
+
const title = countryCode
|
|
1194
|
+
? `${this.selectedCountryData.name}: +${this.selectedCountryData.dialCode}`
|
|
1195
|
+
: "Unknown";
|
|
1196
|
+
this.selectedFlag.setAttribute("title", title);
|
|
1017
1197
|
}
|
|
1018
1198
|
|
|
1019
1199
|
if (this.options.separateDialCode) {
|
|
1020
|
-
const dialCode = this.selectedCountryData.dialCode
|
|
1200
|
+
const dialCode = this.selectedCountryData.dialCode
|
|
1201
|
+
? `+${this.selectedCountryData.dialCode}`
|
|
1202
|
+
: "";
|
|
1021
1203
|
this.selectedDialCode.innerHTML = dialCode;
|
|
1022
1204
|
// offsetWidth is zero if input is in a hidden container during initialisation
|
|
1023
|
-
const selectedFlagWidth =
|
|
1205
|
+
const selectedFlagWidth =
|
|
1206
|
+
this.selectedFlag.offsetWidth || this._getHiddenSelectedFlagWidth();
|
|
1024
1207
|
|
|
1025
1208
|
// add 6px of padding after the grey selected-dial-code box, as this is what we use in the css
|
|
1026
1209
|
this.telInput.style.paddingLeft = `${selectedFlagWidth + 6}px`;
|
|
@@ -1033,23 +1216,28 @@ class Iti {
|
|
|
1033
1216
|
if (this.options.allowDropdown) {
|
|
1034
1217
|
const prevItem = this.activeItem;
|
|
1035
1218
|
if (prevItem) {
|
|
1036
|
-
prevItem.classList.remove(
|
|
1037
|
-
prevItem.setAttribute(
|
|
1219
|
+
prevItem.classList.remove("iti__active");
|
|
1220
|
+
prevItem.setAttribute("aria-selected", "false");
|
|
1038
1221
|
}
|
|
1039
1222
|
if (countryCode) {
|
|
1040
1223
|
// check if there is a preferred item first, else fall back to standard
|
|
1041
|
-
const nextItem =
|
|
1042
|
-
|
|
1043
|
-
|
|
1224
|
+
const nextItem =
|
|
1225
|
+
this.countryList.querySelector(
|
|
1226
|
+
`#iti-${this.id}__item-${countryCode}-preferred`
|
|
1227
|
+
) ||
|
|
1228
|
+
this.countryList.querySelector(
|
|
1229
|
+
`#iti-${this.id}__item-${countryCode}`
|
|
1230
|
+
);
|
|
1231
|
+
nextItem.setAttribute("aria-selected", "true");
|
|
1232
|
+
nextItem.classList.add("iti__active");
|
|
1044
1233
|
this.activeItem = nextItem;
|
|
1045
1234
|
}
|
|
1046
1235
|
}
|
|
1047
1236
|
|
|
1048
1237
|
// return if the flag has changed or not
|
|
1049
|
-
return
|
|
1238
|
+
return prevCountry.iso2 !== countryCode;
|
|
1050
1239
|
}
|
|
1051
1240
|
|
|
1052
|
-
|
|
1053
1241
|
// when the input is in a hidden container during initialisation, we must inject some markup
|
|
1054
1242
|
// into the end of the DOM to calculate the correct offsetWidth
|
|
1055
1243
|
// NOTE: this is only used when separateDialCode is enabled, so flagsContainer and selectedFlag
|
|
@@ -1058,7 +1246,7 @@ class Iti {
|
|
|
1058
1246
|
// to get the right styling to apply, all we need is a shallow clone of the container,
|
|
1059
1247
|
// and then to inject a deep clone of the selectedFlag element
|
|
1060
1248
|
const containerClone = this.telInput.parentNode.cloneNode();
|
|
1061
|
-
containerClone.style.visibility =
|
|
1249
|
+
containerClone.style.visibility = "hidden";
|
|
1062
1250
|
document.body.appendChild(containerClone);
|
|
1063
1251
|
|
|
1064
1252
|
const flagsContainerClone = this.flagsContainer.cloneNode();
|
|
@@ -1072,35 +1260,48 @@ class Iti {
|
|
|
1072
1260
|
return width;
|
|
1073
1261
|
}
|
|
1074
1262
|
|
|
1075
|
-
|
|
1076
1263
|
// update the input placeholder to an example number from the currently selected country
|
|
1077
1264
|
_updatePlaceholder() {
|
|
1078
|
-
const shouldSetPlaceholder =
|
|
1265
|
+
const shouldSetPlaceholder =
|
|
1266
|
+
this.options.autoPlaceholder === "aggressive" ||
|
|
1267
|
+
(!this.hadInitialPlaceholder &&
|
|
1268
|
+
this.options.autoPlaceholder === "polite");
|
|
1079
1269
|
if (window.intlTelInputUtils && shouldSetPlaceholder) {
|
|
1080
|
-
const numberType =
|
|
1081
|
-
|
|
1270
|
+
const numberType =
|
|
1271
|
+
intlTelInputUtils.numberType[this.options.placeholderNumberType];
|
|
1272
|
+
let placeholder = this.selectedCountryData.iso2
|
|
1273
|
+
? intlTelInputUtils.getExampleNumber(
|
|
1274
|
+
this.selectedCountryData.iso2,
|
|
1275
|
+
this.options.nationalMode,
|
|
1276
|
+
numberType
|
|
1277
|
+
)
|
|
1278
|
+
: "";
|
|
1082
1279
|
|
|
1083
1280
|
placeholder = this._beforeSetNumber(placeholder);
|
|
1084
|
-
if (typeof this.options.customPlaceholder ===
|
|
1085
|
-
placeholder = this.options.customPlaceholder(
|
|
1281
|
+
if (typeof this.options.customPlaceholder === "function") {
|
|
1282
|
+
placeholder = this.options.customPlaceholder(
|
|
1283
|
+
placeholder,
|
|
1284
|
+
this.selectedCountryData
|
|
1285
|
+
);
|
|
1086
1286
|
}
|
|
1087
|
-
this.telInput.setAttribute(
|
|
1287
|
+
this.telInput.setAttribute("placeholder", placeholder);
|
|
1088
1288
|
}
|
|
1089
1289
|
}
|
|
1090
1290
|
|
|
1091
|
-
|
|
1092
1291
|
// called when the user selects a list item from the dropdown
|
|
1093
1292
|
_selectListItem(listItem) {
|
|
1094
1293
|
// update selected flag and active list item
|
|
1095
|
-
const flagChanged = this._setFlag(
|
|
1294
|
+
const flagChanged = this._setFlag(
|
|
1295
|
+
listItem.getAttribute("data-country-code")
|
|
1296
|
+
);
|
|
1096
1297
|
this._closeDropdown();
|
|
1097
1298
|
|
|
1098
|
-
this._updateDialCode(listItem.getAttribute(
|
|
1299
|
+
this._updateDialCode(listItem.getAttribute("data-dial-code"));
|
|
1099
1300
|
|
|
1100
1301
|
// focus the input
|
|
1101
1302
|
this.telInput.focus();
|
|
1102
|
-
// put cursor at end - this fix is required for FF and IE11 (with
|
|
1103
|
-
//
|
|
1303
|
+
// put cursor at end - this fix is required for FF and IE11 (with auto inserting dial code),
|
|
1304
|
+
// who try to put the cursor at the beginning the first time
|
|
1104
1305
|
const len = this.telInput.value.length;
|
|
1105
1306
|
this.telInput.setSelectionRange(len, len);
|
|
1106
1307
|
|
|
@@ -1109,32 +1310,40 @@ class Iti {
|
|
|
1109
1310
|
}
|
|
1110
1311
|
}
|
|
1111
1312
|
|
|
1112
|
-
|
|
1113
1313
|
// close the dropdown and unbind any listeners
|
|
1114
1314
|
_closeDropdown() {
|
|
1115
|
-
this.countryList.classList.add(
|
|
1116
|
-
this.selectedFlag.setAttribute(
|
|
1117
|
-
this.selectedFlag.removeAttribute(
|
|
1315
|
+
this.countryList.classList.add("iti__hide");
|
|
1316
|
+
this.selectedFlag.setAttribute("aria-expanded", "false");
|
|
1317
|
+
this.selectedFlag.removeAttribute("aria-activedescendant");
|
|
1118
1318
|
|
|
1119
1319
|
// update the arrow
|
|
1120
|
-
this.dropdownArrow.classList.remove(
|
|
1320
|
+
this.dropdownArrow.classList.remove("iti__arrow--up");
|
|
1121
1321
|
|
|
1122
1322
|
// unbind key events
|
|
1123
|
-
document.removeEventListener(
|
|
1124
|
-
document.documentElement.removeEventListener(
|
|
1125
|
-
|
|
1126
|
-
|
|
1323
|
+
document.removeEventListener("keydown", this._handleKeydownOnDropdown);
|
|
1324
|
+
document.documentElement.removeEventListener(
|
|
1325
|
+
"click",
|
|
1326
|
+
this._handleClickOffToClose
|
|
1327
|
+
);
|
|
1328
|
+
this.countryList.removeEventListener(
|
|
1329
|
+
"mouseover",
|
|
1330
|
+
this._handleMouseoverCountryList
|
|
1331
|
+
);
|
|
1332
|
+
this.countryList.removeEventListener("click", this._handleClickCountryList);
|
|
1127
1333
|
|
|
1128
1334
|
// remove menu from container
|
|
1129
1335
|
if (this.options.dropdownContainer) {
|
|
1130
|
-
if (!this.isMobile)
|
|
1131
|
-
|
|
1336
|
+
if (!this.isMobile) {
|
|
1337
|
+
window.removeEventListener("scroll", this._handleWindowScroll);
|
|
1338
|
+
}
|
|
1339
|
+
if (this.dropdown.parentNode) {
|
|
1340
|
+
this.dropdown.parentNode.removeChild(this.dropdown);
|
|
1341
|
+
}
|
|
1132
1342
|
}
|
|
1133
1343
|
|
|
1134
|
-
this._trigger(
|
|
1344
|
+
this._trigger("close:countrydropdown");
|
|
1135
1345
|
}
|
|
1136
1346
|
|
|
1137
|
-
|
|
1138
1347
|
// check if an element is visible within it's container, else scroll until it is
|
|
1139
1348
|
_scrollTo(element, middle) {
|
|
1140
1349
|
const container = this.countryList;
|
|
@@ -1147,21 +1356,24 @@ class Iti {
|
|
|
1147
1356
|
const elementTop = element.getBoundingClientRect().top + windowTop;
|
|
1148
1357
|
const elementBottom = elementTop + elementHeight;
|
|
1149
1358
|
let newScrollTop = elementTop - containerTop + container.scrollTop;
|
|
1150
|
-
const middleOffset =
|
|
1359
|
+
const middleOffset = containerHeight / 2 - elementHeight / 2;
|
|
1151
1360
|
|
|
1152
1361
|
if (elementTop < containerTop) {
|
|
1153
1362
|
// scroll up
|
|
1154
|
-
if (middle)
|
|
1363
|
+
if (middle) {
|
|
1364
|
+
newScrollTop -= middleOffset;
|
|
1365
|
+
}
|
|
1155
1366
|
container.scrollTop = newScrollTop;
|
|
1156
1367
|
} else if (elementBottom > containerBottom) {
|
|
1157
1368
|
// scroll down
|
|
1158
|
-
if (middle)
|
|
1369
|
+
if (middle) {
|
|
1370
|
+
newScrollTop += middleOffset;
|
|
1371
|
+
}
|
|
1159
1372
|
const heightDifference = containerHeight - elementHeight;
|
|
1160
1373
|
container.scrollTop = newScrollTop - heightDifference;
|
|
1161
1374
|
}
|
|
1162
1375
|
}
|
|
1163
1376
|
|
|
1164
|
-
|
|
1165
1377
|
// replace any existing dial code with the new one
|
|
1166
1378
|
// Note: called from _selectListItem and setCountry
|
|
1167
1379
|
_updateDialCode(newDialCodeBare) {
|
|
@@ -1170,8 +1382,8 @@ class Iti {
|
|
|
1170
1382
|
const newDialCode = `+${newDialCodeBare}`;
|
|
1171
1383
|
|
|
1172
1384
|
let newNumber;
|
|
1173
|
-
if (inputVal.charAt(0) ===
|
|
1174
|
-
// there's a plus so we're dealing with a replacement
|
|
1385
|
+
if (inputVal.charAt(0) === "+") {
|
|
1386
|
+
// there's a plus so we're dealing with a replacement
|
|
1175
1387
|
const prevDialCode = this._getDialCode(inputVal);
|
|
1176
1388
|
if (prevDialCode) {
|
|
1177
1389
|
// current number contains a valid dial code, so replace it
|
|
@@ -1193,14 +1405,13 @@ class Iti {
|
|
|
1193
1405
|
}
|
|
1194
1406
|
}
|
|
1195
1407
|
|
|
1196
|
-
|
|
1197
1408
|
// try and extract a valid international dial code from a full telephone number
|
|
1198
1409
|
// Note: returns the raw string inc plus character and any whitespace/dots etc
|
|
1199
1410
|
_getDialCode(number, includeAreaCode) {
|
|
1200
|
-
let dialCode =
|
|
1411
|
+
let dialCode = "";
|
|
1201
1412
|
// only interested in international numbers (starting with a plus)
|
|
1202
|
-
if (number.charAt(0) ===
|
|
1203
|
-
let numericChars =
|
|
1413
|
+
if (number.charAt(0) === "+") {
|
|
1414
|
+
let numericChars = "";
|
|
1204
1415
|
// iterate over chars
|
|
1205
1416
|
for (let i = 0; i < number.length; i++) {
|
|
1206
1417
|
const c = number.charAt(i);
|
|
@@ -1230,7 +1441,6 @@ class Iti {
|
|
|
1230
1441
|
return dialCode;
|
|
1231
1442
|
}
|
|
1232
1443
|
|
|
1233
|
-
|
|
1234
1444
|
// get the input val, adding the dial code if separateDialCode is enabled
|
|
1235
1445
|
_getFullNumber() {
|
|
1236
1446
|
const val = this.telInput.value.trim();
|
|
@@ -1238,16 +1448,20 @@ class Iti {
|
|
|
1238
1448
|
let prefix;
|
|
1239
1449
|
const numericVal = this._getNumeric(val);
|
|
1240
1450
|
|
|
1241
|
-
if (
|
|
1451
|
+
if (
|
|
1452
|
+
this.options.separateDialCode &&
|
|
1453
|
+
val.charAt(0) !== "+" &&
|
|
1454
|
+
dialCode &&
|
|
1455
|
+
numericVal
|
|
1456
|
+
) {
|
|
1242
1457
|
// when using separateDialCode, it is visible so is effectively part of the typed number
|
|
1243
1458
|
prefix = `+${dialCode}`;
|
|
1244
1459
|
} else {
|
|
1245
|
-
prefix =
|
|
1460
|
+
prefix = "";
|
|
1246
1461
|
}
|
|
1247
1462
|
return prefix + val;
|
|
1248
1463
|
}
|
|
1249
1464
|
|
|
1250
|
-
|
|
1251
1465
|
// remove the dial code if separateDialCode is enabled
|
|
1252
1466
|
// also cap the length if the input has a maxlength attribute
|
|
1253
1467
|
_beforeSetNumber(originalNumber) {
|
|
@@ -1262,7 +1476,10 @@ class Iti {
|
|
|
1262
1476
|
// some NANP numbers will have a hyphen e.g. +1 684-733-1234 - in both cases we want to get
|
|
1263
1477
|
// rid of it
|
|
1264
1478
|
// NOTE: don't just trim all non-numerics as may want to preserve an open parenthesis etc
|
|
1265
|
-
const start =
|
|
1479
|
+
const start =
|
|
1480
|
+
number[dialCode.length] === " " || number[dialCode.length] === "-"
|
|
1481
|
+
? dialCode.length + 1
|
|
1482
|
+
: dialCode.length;
|
|
1266
1483
|
number = number.substr(start);
|
|
1267
1484
|
}
|
|
1268
1485
|
}
|
|
@@ -1270,21 +1487,18 @@ class Iti {
|
|
|
1270
1487
|
return this._cap(number);
|
|
1271
1488
|
}
|
|
1272
1489
|
|
|
1273
|
-
|
|
1274
1490
|
// trigger the 'countrychange' event
|
|
1275
1491
|
_triggerCountryChange() {
|
|
1276
|
-
this._trigger(
|
|
1492
|
+
this._trigger("countrychange");
|
|
1277
1493
|
}
|
|
1278
1494
|
|
|
1279
|
-
|
|
1280
1495
|
/**************************
|
|
1281
1496
|
* SECRET PUBLIC METHODS
|
|
1282
1497
|
**************************/
|
|
1283
1498
|
|
|
1284
|
-
|
|
1285
1499
|
// this is called when the geoip call returns
|
|
1286
1500
|
handleAutoCountry() {
|
|
1287
|
-
if (this.options.initialCountry ===
|
|
1501
|
+
if (this.options.initialCountry === "auto") {
|
|
1288
1502
|
// we must set this even if there is an initial val in the input: in case the initial val is
|
|
1289
1503
|
// invalid and they delete it - they should see their auto country
|
|
1290
1504
|
this.defaultCountry = window.intlTelInputGlobals.autoCountry;
|
|
@@ -1296,7 +1510,6 @@ class Iti {
|
|
|
1296
1510
|
}
|
|
1297
1511
|
}
|
|
1298
1512
|
|
|
1299
|
-
|
|
1300
1513
|
// this is called when the utils request completes
|
|
1301
1514
|
handleUtils() {
|
|
1302
1515
|
// if the request was successful
|
|
@@ -1310,12 +1523,10 @@ class Iti {
|
|
|
1310
1523
|
this.resolveUtilsScriptPromise();
|
|
1311
1524
|
}
|
|
1312
1525
|
|
|
1313
|
-
|
|
1314
1526
|
/********************
|
|
1315
1527
|
* PUBLIC METHODS
|
|
1316
1528
|
********************/
|
|
1317
1529
|
|
|
1318
|
-
|
|
1319
1530
|
// remove plugin
|
|
1320
1531
|
destroy() {
|
|
1321
1532
|
const { form } = this.telInput;
|
|
@@ -1323,29 +1534,41 @@ class Iti {
|
|
|
1323
1534
|
if (this.options.allowDropdown) {
|
|
1324
1535
|
// make sure the dropdown is closed (and unbind listeners)
|
|
1325
1536
|
this._closeDropdown();
|
|
1326
|
-
this.selectedFlag.removeEventListener(
|
|
1327
|
-
|
|
1537
|
+
this.selectedFlag.removeEventListener(
|
|
1538
|
+
"click",
|
|
1539
|
+
this._handleClickSelectedFlag
|
|
1540
|
+
);
|
|
1541
|
+
this.flagsContainer.removeEventListener(
|
|
1542
|
+
"keydown",
|
|
1543
|
+
this._handleFlagsContainerKeydown
|
|
1544
|
+
);
|
|
1328
1545
|
// label click hack
|
|
1329
1546
|
const label = this._getClosestLabel();
|
|
1330
|
-
if (label)
|
|
1547
|
+
if (label) {
|
|
1548
|
+
label.removeEventListener("click", this._handleLabelClick);
|
|
1549
|
+
}
|
|
1331
1550
|
}
|
|
1332
1551
|
|
|
1333
1552
|
// unbind hiddenInput listeners
|
|
1334
|
-
if (this.hiddenInput && form)
|
|
1553
|
+
if (this.hiddenInput && form) {
|
|
1554
|
+
form.removeEventListener("submit", this._handleHiddenInputSubmit);
|
|
1555
|
+
}
|
|
1335
1556
|
|
|
1336
1557
|
// unbind autoInsertDialCode listeners
|
|
1337
1558
|
if (this.options.autoInsertDialCode) {
|
|
1338
|
-
if (form)
|
|
1339
|
-
|
|
1559
|
+
if (form) {
|
|
1560
|
+
form.removeEventListener("submit", this._handleSubmitOrBlurEvent);
|
|
1561
|
+
}
|
|
1562
|
+
this.telInput.removeEventListener("blur", this._handleSubmitOrBlurEvent);
|
|
1340
1563
|
}
|
|
1341
1564
|
|
|
1342
1565
|
// unbind key events, and cut/paste events
|
|
1343
|
-
this.telInput.removeEventListener(
|
|
1344
|
-
this.telInput.removeEventListener(
|
|
1345
|
-
this.telInput.removeEventListener(
|
|
1566
|
+
this.telInput.removeEventListener("keyup", this._handleKeyupEvent);
|
|
1567
|
+
this.telInput.removeEventListener("cut", this._handleClipboardEvent);
|
|
1568
|
+
this.telInput.removeEventListener("paste", this._handleClipboardEvent);
|
|
1346
1569
|
|
|
1347
1570
|
// remove attribute of id instance: data-intl-tel-input-id
|
|
1348
|
-
this.telInput.removeAttribute(
|
|
1571
|
+
this.telInput.removeAttribute("data-intl-tel-input-id");
|
|
1349
1572
|
|
|
1350
1573
|
// remove markup (but leave the original input)
|
|
1351
1574
|
const wrapper = this.telInput.parentNode;
|
|
@@ -1355,41 +1578,46 @@ class Iti {
|
|
|
1355
1578
|
delete window.intlTelInputGlobals.instances[this.id];
|
|
1356
1579
|
}
|
|
1357
1580
|
|
|
1358
|
-
|
|
1359
1581
|
// get the extension from the current number
|
|
1360
1582
|
getExtension() {
|
|
1361
1583
|
if (window.intlTelInputUtils) {
|
|
1362
|
-
return intlTelInputUtils.getExtension(
|
|
1584
|
+
return intlTelInputUtils.getExtension(
|
|
1585
|
+
this._getFullNumber(),
|
|
1586
|
+
this.selectedCountryData.iso2
|
|
1587
|
+
);
|
|
1363
1588
|
}
|
|
1364
|
-
return
|
|
1589
|
+
return "";
|
|
1365
1590
|
}
|
|
1366
1591
|
|
|
1367
|
-
|
|
1368
1592
|
// format the number to the given format
|
|
1369
1593
|
getNumber(format) {
|
|
1370
1594
|
if (window.intlTelInputUtils) {
|
|
1371
1595
|
const { iso2 } = this.selectedCountryData;
|
|
1372
|
-
return intlTelInputUtils.formatNumber(
|
|
1596
|
+
return intlTelInputUtils.formatNumber(
|
|
1597
|
+
this._getFullNumber(),
|
|
1598
|
+
iso2,
|
|
1599
|
+
format
|
|
1600
|
+
);
|
|
1373
1601
|
}
|
|
1374
|
-
return
|
|
1602
|
+
return "";
|
|
1375
1603
|
}
|
|
1376
1604
|
|
|
1377
|
-
|
|
1378
1605
|
// get the type of the entered number e.g. landline/mobile
|
|
1379
1606
|
getNumberType() {
|
|
1380
1607
|
if (window.intlTelInputUtils) {
|
|
1381
|
-
return intlTelInputUtils.getNumberType(
|
|
1608
|
+
return intlTelInputUtils.getNumberType(
|
|
1609
|
+
this._getFullNumber(),
|
|
1610
|
+
this.selectedCountryData.iso2
|
|
1611
|
+
);
|
|
1382
1612
|
}
|
|
1383
1613
|
return -99;
|
|
1384
1614
|
}
|
|
1385
1615
|
|
|
1386
|
-
|
|
1387
1616
|
// get the country data for the currently selected flag
|
|
1388
1617
|
getSelectedCountryData() {
|
|
1389
1618
|
return this.selectedCountryData;
|
|
1390
1619
|
}
|
|
1391
1620
|
|
|
1392
|
-
|
|
1393
1621
|
// get the validation error
|
|
1394
1622
|
getValidationError() {
|
|
1395
1623
|
if (window.intlTelInputUtils) {
|
|
@@ -1399,15 +1627,14 @@ class Iti {
|
|
|
1399
1627
|
return -99;
|
|
1400
1628
|
}
|
|
1401
1629
|
|
|
1402
|
-
|
|
1403
1630
|
// validate the input val - assumes the global function isValidNumber (from utilsScript)
|
|
1404
1631
|
isValidNumber() {
|
|
1405
1632
|
const val = this._getFullNumber().trim();
|
|
1406
|
-
|
|
1407
|
-
|
|
1633
|
+
return window.intlTelInputUtils
|
|
1634
|
+
? intlTelInputUtils.isValidNumber(val, this.selectedCountryData.iso2)
|
|
1635
|
+
: null;
|
|
1408
1636
|
}
|
|
1409
1637
|
|
|
1410
|
-
|
|
1411
1638
|
// update the selected flag, and update the input val accordingly
|
|
1412
1639
|
setCountry(originalCountryCode) {
|
|
1413
1640
|
const countryCode = originalCountryCode.toLowerCase();
|
|
@@ -1419,7 +1646,6 @@ class Iti {
|
|
|
1419
1646
|
}
|
|
1420
1647
|
}
|
|
1421
1648
|
|
|
1422
|
-
|
|
1423
1649
|
// set the input value and update the flag
|
|
1424
1650
|
setNumber(number) {
|
|
1425
1651
|
// we must update the flag first, which updates this.selectedCountryData, which is used for
|
|
@@ -1438,57 +1664,61 @@ class Iti {
|
|
|
1438
1664
|
}
|
|
1439
1665
|
}
|
|
1440
1666
|
|
|
1441
|
-
|
|
1442
1667
|
/********************
|
|
1443
1668
|
* STATIC METHODS
|
|
1444
1669
|
********************/
|
|
1445
1670
|
|
|
1446
|
-
|
|
1447
1671
|
// get the country data object
|
|
1448
1672
|
intlTelInputGlobals.getCountryData = () => allCountries;
|
|
1449
1673
|
|
|
1450
|
-
|
|
1451
1674
|
// inject a <script> element to load utils.js
|
|
1452
1675
|
const injectScript = (path, handleSuccess, handleFailure) => {
|
|
1453
1676
|
// inject a new script element into the page
|
|
1454
|
-
const script = document.createElement(
|
|
1677
|
+
const script = document.createElement("script");
|
|
1455
1678
|
script.onload = () => {
|
|
1456
|
-
forEachInstance(
|
|
1457
|
-
if (handleSuccess)
|
|
1679
|
+
forEachInstance("handleUtils");
|
|
1680
|
+
if (handleSuccess) {
|
|
1681
|
+
handleSuccess();
|
|
1682
|
+
}
|
|
1458
1683
|
};
|
|
1459
1684
|
script.onerror = () => {
|
|
1460
|
-
forEachInstance(
|
|
1461
|
-
if (handleFailure)
|
|
1685
|
+
forEachInstance("rejectUtilsScriptPromise");
|
|
1686
|
+
if (handleFailure) {
|
|
1687
|
+
handleFailure();
|
|
1688
|
+
}
|
|
1462
1689
|
};
|
|
1463
|
-
script.className =
|
|
1690
|
+
script.className = "iti-load-utils";
|
|
1464
1691
|
script.async = true;
|
|
1465
1692
|
script.src = path;
|
|
1466
1693
|
document.body.appendChild(script);
|
|
1467
1694
|
};
|
|
1468
1695
|
|
|
1469
|
-
|
|
1470
1696
|
// load the utils script
|
|
1471
1697
|
intlTelInputGlobals.loadUtils = (path) => {
|
|
1472
1698
|
// 2 options:
|
|
1473
1699
|
// 1) not already started loading (start)
|
|
1474
1700
|
// 2) already started loading (do nothing - just wait for the onload callback to fire, which will
|
|
1475
1701
|
// trigger handleUtils on all instances, invoking their resolveUtilsScriptPromise functions)
|
|
1476
|
-
if (
|
|
1702
|
+
if (
|
|
1703
|
+
!window.intlTelInputUtils &&
|
|
1704
|
+
!window.intlTelInputGlobals.startedLoadingUtilsScript
|
|
1705
|
+
) {
|
|
1477
1706
|
// only do this once
|
|
1478
1707
|
window.intlTelInputGlobals.startedLoadingUtilsScript = true;
|
|
1479
1708
|
|
|
1480
1709
|
// if we have promises, then return a promise
|
|
1481
|
-
if (typeof Promise !==
|
|
1482
|
-
return new Promise((resolve, reject) =>
|
|
1710
|
+
if (typeof Promise !== "undefined") {
|
|
1711
|
+
return new Promise((resolve, reject) =>
|
|
1712
|
+
injectScript(path, resolve, reject)
|
|
1713
|
+
);
|
|
1483
1714
|
}
|
|
1484
1715
|
injectScript(path);
|
|
1485
1716
|
}
|
|
1486
1717
|
return null;
|
|
1487
1718
|
};
|
|
1488
1719
|
|
|
1489
|
-
|
|
1490
1720
|
// default options
|
|
1491
1721
|
intlTelInputGlobals.defaults = defaults;
|
|
1492
1722
|
|
|
1493
1723
|
// version
|
|
1494
|
-
intlTelInputGlobals.version =
|
|
1724
|
+
intlTelInputGlobals.version = "<%= version %>";
|