1 | import {
|
2 | DIGIT_PLACEHOLDER,
|
3 | countOccurences,
|
4 | repeat,
|
5 | cutAndStripNonPairedParens,
|
6 | closeNonPairedParens,
|
7 | stripNonPairedParens,
|
8 | populateTemplateWithDigits
|
9 | } from './AsYouTypeFormatter.util'
|
10 |
|
11 | import formatCompleteNumber, {
|
12 | canFormatCompleteNumber
|
13 | } from './AsYouTypeFormatter.complete'
|
14 |
|
15 | import parseDigits from './helpers/parseDigits'
|
16 | export { DIGIT_PLACEHOLDER } from './AsYouTypeFormatter.util'
|
17 | import { FIRST_GROUP_PATTERN } from './helpers/formatNationalNumberUsingFormat'
|
18 | import { VALID_PUNCTUATION } from './constants'
|
19 | import applyInternationalSeparatorStyle from './helpers/applyInternationalSeparatorStyle'
|
20 |
|
21 | // Used in phone number format template creation.
|
22 | // Could be any digit, I guess.
|
23 | const DUMMY_DIGIT = '9'
|
24 | // I don't know why is it exactly `15`
|
25 | const LONGEST_NATIONAL_PHONE_NUMBER_LENGTH = 15
|
26 | // Create a phone number consisting only of the digit 9 that matches the
|
27 | // `number_pattern` by applying the pattern to the "longest phone number" string.
|
28 | const LONGEST_DUMMY_PHONE_NUMBER = repeat(DUMMY_DIGIT, LONGEST_NATIONAL_PHONE_NUMBER_LENGTH)
|
29 |
|
30 | // A set of characters that, if found in a national prefix formatting rules, are an indicator to
|
31 | // us that we should separate the national prefix from the number when formatting.
|
32 | const NATIONAL_PREFIX_SEPARATORS_PATTERN = /[- ]/
|
33 |
|
34 | // Deprecated: Google has removed some formatting pattern related code from their repo.
|
35 | // https://github.com/googlei18n/libphonenumber/commit/a395b4fef3caf57c4bc5f082e1152a4d2bd0ba4c
|
36 | // "We no longer have numbers in formatting matching patterns, only \d."
|
37 | // Because this library supports generating custom metadata
|
38 | // some users may still be using old metadata so the relevant
|
39 | // code seems to stay until some next major version update.
|
40 | const SUPPORT_LEGACY_FORMATTING_PATTERNS = true
|
41 |
|
42 | // A pattern that is used to match character classes in regular expressions.
|
43 | // An example of a character class is "[1-4]".
|
44 | const CREATE_CHARACTER_CLASS_PATTERN = SUPPORT_LEGACY_FORMATTING_PATTERNS && (() => /\[([^\[\]])*\]/g)
|
45 |
|
46 | // Any digit in a regular expression that actually denotes a digit. For
|
47 | // example, in the regular expression "80[0-2]\d{6,10}", the first 2 digits
|
48 | // (8 and 0) are standalone digits, but the rest are not.
|
49 | // Two look-aheads are needed because the number following \\d could be a
|
50 | // two-digit number, since the phone number can be as long as 15 digits.
|
51 | const CREATE_STANDALONE_DIGIT_PATTERN = SUPPORT_LEGACY_FORMATTING_PATTERNS && (() => /\d(?=[^,}][^,}])/g)
|
52 |
|
53 | // A regular expression that is used to determine if a `format` is
|
54 | // suitable to be used in the "as you type formatter".
|
55 | // A `format` is suitable when the resulting formatted number has
|
56 | // the same digits as the user has entered.
|
57 | //
|
58 | // In the simplest case, that would mean that the format
|
59 | // doesn't add any additional digits when formatting a number.
|
60 | // Google says that it also shouldn't add "star" (`*`) characters,
|
61 | // like it does in some Israeli formats.
|
62 | // Such basic format would only contain "valid punctuation"
|
63 | // and "captured group" identifiers ($1, $2, etc).
|
64 | //
|
65 | // An example of a format that adds additional digits:
|
66 | //
|
67 | // Country: `AR` (Argentina).
|
68 | // Format:
|
69 | // {
|
70 | // "pattern": "(\\d)(\\d{2})(\\d{4})(\\d{4})",
|
71 | // "leading_digits_patterns": ["91"],
|
72 | // "national_prefix_formatting_rule": "0$1",
|
73 | // "format": "$2 15-$3-$4",
|
74 | // "international_format": "$1 $2 $3-$4"
|
75 | // }
|
76 | //
|
77 | // In the format above, the `format` adds `15` to the digits when formatting a number.
|
78 | // A sidenote: this format actually is suitable because `national_prefix_for_parsing`
|
79 | // has previously removed `15` from a national number, so re-adding `15` in `format`
|
80 | // doesn't actually result in any extra digits added to user's input.
|
81 | // But verifying that would be a complex procedure, so the code chooses a simpler path:
|
82 | // it simply filters out all `format`s that contain anything but "captured group" ids.
|
83 | //
|
84 | // This regular expression is called `ELIGIBLE_FORMAT_PATTERN` in Google's
|
85 | // `libphonenumber` code.
|
86 | //
|
87 | const NON_ALTERING_FORMAT_REG_EXP = new RegExp(
|
88 | '^' +
|
89 | '[' + VALID_PUNCTUATION + ']*' +
|
90 | '(\\$\\d[' + VALID_PUNCTUATION + ']*)+' +
|
91 | '$'
|
92 | )
|
93 |
|
94 | // This is the minimum length of the leading digits of a phone number
|
95 | // to guarantee the first "leading digits pattern" for a phone number format
|
96 | // to be preemptive.
|
97 | const MIN_LEADING_DIGITS_LENGTH = 3
|
98 |
|
99 | export default class AsYouTypeFormatter {
|
100 | constructor({
|
101 | state,
|
102 | metadata
|
103 | }) {
|
104 | this.metadata = metadata
|
105 | this.resetFormat()
|
106 | }
|
107 |
|
108 | resetFormat() {
|
109 | this.chosenFormat = undefined
|
110 | this.template = undefined
|
111 | this.nationalNumberTemplate = undefined
|
112 | this.populatedNationalNumberTemplate = undefined
|
113 | this.populatedNationalNumberTemplatePosition = -1
|
114 | }
|
115 |
|
116 | reset(numberingPlan, state) {
|
117 | this.resetFormat()
|
118 | if (numberingPlan) {
|
119 | this.isNANP = numberingPlan.callingCode() === '1'
|
120 | this.matchingFormats = numberingPlan.formats()
|
121 | if (state.nationalSignificantNumber) {
|
122 | this.narrowDownMatchingFormats(state)
|
123 | }
|
124 | } else {
|
125 | this.isNANP = undefined
|
126 | this.matchingFormats = []
|
127 | }
|
128 | }
|
129 |
|
130 | format(nextDigits, state) {
|
131 | // See if the phone number digits can be formatted as a complete phone number.
|
132 | // If not, use the results from `formatNationalNumberWithNextDigits()`,
|
133 | // which formats based on the chosen formatting pattern.
|
134 | //
|
135 | // Attempting to format complete phone number first is how it's done
|
136 | // in Google's `libphonenumber`, so this library just follows it.
|
137 | // Google's `libphonenumber` code doesn't explain in detail why does it
|
138 | // attempt to format digits as a complete phone number
|
139 | // instead of just going with a previoulsy (or newly) chosen `format`:
|
140 | //
|
141 | // "Checks to see if there is an exact pattern match for these digits.
|
142 | // If so, we should use this instead of any other formatting template
|
143 | // whose leadingDigitsPattern also matches the input."
|
144 | //
|
145 | if (canFormatCompleteNumber(state.nationalSignificantNumber, this.metadata)) {
|
146 | for (const format of this.matchingFormats) {
|
147 | const formattedCompleteNumber = formatCompleteNumber(
|
148 | state,
|
149 | format,
|
150 | {
|
151 | metadata: this.metadata,
|
152 | shouldTryNationalPrefixFormattingRule: format => this.shouldTryNationalPrefixFormattingRule(format, {
|
153 | international: state.international,
|
154 | nationalPrefix: state.nationalPrefix
|
155 | }),
|
156 | getSeparatorAfterNationalPrefix: this.getSeparatorAfterNationalPrefix
|
157 | }
|
158 | )
|
159 | if (formattedCompleteNumber) {
|
160 | this.resetFormat()
|
161 | this.chosenFormat = format
|
162 | this.setNationalNumberTemplate(formattedCompleteNumber.replace(/\d/g, DIGIT_PLACEHOLDER), state)
|
163 | this.populatedNationalNumberTemplate = formattedCompleteNumber
|
164 | // With a new formatting template, the matched position
|
165 | // using the old template needs to be reset.
|
166 | this.populatedNationalNumberTemplatePosition = this.template.lastIndexOf(DIGIT_PLACEHOLDER)
|
167 | return formattedCompleteNumber
|
168 | }
|
169 |
|
170 | }
|
171 | }
|
172 | // Format the digits as a partial (incomplete) phone number
|
173 | // using the previously chosen formatting pattern (or a newly chosen one).
|
174 | return this.formatNationalNumberWithNextDigits(nextDigits, state)
|
175 | }
|
176 |
|
177 | // Formats the next phone number digits.
|
178 | formatNationalNumberWithNextDigits(nextDigits, state) {
|
179 | const previouslyChosenFormat = this.chosenFormat
|
180 | // Choose a format from the list of matching ones.
|
181 | const newlyChosenFormat = this.chooseFormat(state)
|
182 | if (newlyChosenFormat) {
|
183 | if (newlyChosenFormat === previouslyChosenFormat) {
|
184 | // If it can format the next (current) digits
|
185 | // using the previously chosen phone number format
|
186 | // then return the updated formatted number.
|
187 | return this.formatNextNationalNumberDigits(nextDigits)
|
188 | } else {
|
189 | // If a more appropriate phone number format
|
190 | // has been chosen for these "leading digits",
|
191 | // then re-format the national phone number part
|
192 | // using the newly selected format.
|
193 | return this.formatNextNationalNumberDigits(state.getNationalDigits())
|
194 | }
|
195 | }
|
196 | }
|
197 |
|
198 | narrowDownMatchingFormats({
|
199 | nationalSignificantNumber,
|
200 | nationalPrefix,
|
201 | international
|
202 | }) {
|
203 | const leadingDigits = nationalSignificantNumber
|
204 |
|
205 | // "leading digits" pattern list starts with a
|
206 | // "leading digits" pattern fitting a maximum of 3 leading digits.
|
207 | // So, after a user inputs 3 digits of a national (significant) phone number
|
208 | // this national (significant) number can already be formatted.
|
209 | // The next "leading digits" pattern is for 4 leading digits max,
|
210 | // and the "leading digits" pattern after it is for 5 leading digits max, etc.
|
211 |
|
212 | // This implementation is different from Google's
|
213 | // in that it searches for a fitting format
|
214 | // even if the user has entered less than
|
215 | // `MIN_LEADING_DIGITS_LENGTH` digits of a national number.
|
216 | // Because some leading digit patterns already match for a single first digit.
|
217 | let leadingDigitsPatternIndex = leadingDigits.length - MIN_LEADING_DIGITS_LENGTH
|
218 | if (leadingDigitsPatternIndex < 0) {
|
219 | leadingDigitsPatternIndex = 0
|
220 | }
|
221 |
|
222 | this.matchingFormats = this.matchingFormats.filter(
|
223 | format => this.formatSuits(format, international, nationalPrefix)
|
224 | && this.formatMatches(format, leadingDigits, leadingDigitsPatternIndex)
|
225 | )
|
226 |
|
227 | // If there was a phone number format chosen
|
228 | // and it no longer holds given the new leading digits then reset it.
|
229 | // The test for this `if` condition is marked as:
|
230 | // "Reset a chosen format when it no longer holds given the new leading digits".
|
231 | // To construct a valid test case for this one can find a country
|
232 | // in `PhoneNumberMetadata.xml` yielding one format for 3 `<leadingDigits>`
|
233 | // and yielding another format for 4 `<leadingDigits>` (Australia in this case).
|
234 | if (this.chosenFormat && this.matchingFormats.indexOf(this.chosenFormat) === -1) {
|
235 | this.resetFormat()
|
236 | }
|
237 | }
|
238 |
|
239 | formatSuits(format, international, nationalPrefix) {
|
240 | // When a prefix before a national (significant) number is
|
241 | // simply a national prefix, then it's parsed as `this.nationalPrefix`.
|
242 | // In more complex cases, a prefix before national (significant) number
|
243 | // could include a national prefix as well as some "capturing groups",
|
244 | // and in that case there's no info whether a national prefix has been parsed.
|
245 | // If national prefix is not used when formatting a phone number
|
246 | // using this format, but a national prefix has been entered by the user,
|
247 | // and was extracted, then discard such phone number format.
|
248 | // In Google's "AsYouType" formatter code, the equivalent would be this part:
|
249 | // https://github.com/google/libphonenumber/blob/0a45cfd96e71cad8edb0e162a70fcc8bd9728933/java/libphonenumber/src/com/google/i18n/phonenumbers/AsYouTypeFormatter.java#L175-L184
|
250 | if (nationalPrefix &&
|
251 | !format.usesNationalPrefix() &&
|
252 | // !format.domesticCarrierCodeFormattingRule() &&
|
253 | !format.nationalPrefixIsOptionalWhenFormattingInNationalFormat()) {
|
254 | return false
|
255 | }
|
256 | // If national prefix is mandatory for this phone number format
|
257 | // and there're no guarantees that a national prefix is present in user input
|
258 | // then discard this phone number format as not suitable.
|
259 | // In Google's "AsYouType" formatter code, the equivalent would be this part:
|
260 | // https://github.com/google/libphonenumber/blob/0a45cfd96e71cad8edb0e162a70fcc8bd9728933/java/libphonenumber/src/com/google/i18n/phonenumbers/AsYouTypeFormatter.java#L185-L193
|
261 | if (!international &&
|
262 | !nationalPrefix &&
|
263 | format.nationalPrefixIsMandatoryWhenFormattingInNationalFormat()) {
|
264 | return false
|
265 | }
|
266 | return true
|
267 | }
|
268 |
|
269 | formatMatches(format, leadingDigits, leadingDigitsPatternIndex) {
|
270 | const leadingDigitsPatternsCount = format.leadingDigitsPatterns().length
|
271 | // If this format is not restricted to a certain
|
272 | // leading digits pattern then it fits.
|
273 | if (leadingDigitsPatternsCount === 0) {
|
274 | return true
|
275 | }
|
276 | // Start excluding any non-matching formats only when the
|
277 | // national number entered so far is at least 3 digits long,
|
278 | // otherwise format matching would give false negatives.
|
279 | // For example, when the digits entered so far are `2`
|
280 | // and the leading digits pattern is `21` –
|
281 | // it's quite obvious in this case that the format could be the one
|
282 | // but due to the absence of further digits it would give false negative.
|
283 | if (leadingDigits.length < MIN_LEADING_DIGITS_LENGTH) {
|
284 | return true
|
285 | }
|
286 | // If at least `MIN_LEADING_DIGITS_LENGTH` digits of a national number are available
|
287 | // then format matching starts narrowing down the list of possible formats
|
288 | // (only previously matched formats are considered for next digits).
|
289 | leadingDigitsPatternIndex = Math.min(leadingDigitsPatternIndex, leadingDigitsPatternsCount - 1)
|
290 | const leadingDigitsPattern = format.leadingDigitsPatterns()[leadingDigitsPatternIndex]
|
291 | // Brackets are required for `^` to be applied to
|
292 | // all or-ed (`|`) parts, not just the first one.
|
293 | return new RegExp(`^(${leadingDigitsPattern})`).test(leadingDigits)
|
294 | }
|
295 |
|
296 | getFormatFormat(format, international) {
|
297 | return international ? format.internationalFormat() : format.format()
|
298 | }
|
299 |
|
300 | chooseFormat(state) {
|
301 | // When there are multiple available formats, the formatter uses the first
|
302 | // format where a formatting template could be created.
|
303 | for (const format of this.matchingFormats.slice()) {
|
304 | // If this format is currently being used
|
305 | // and is still suitable, then stick to it.
|
306 | if (this.chosenFormat === format) {
|
307 | break
|
308 | }
|
309 | // Sometimes, a formatting rule inserts additional digits in a phone number,
|
310 | // and "as you type" formatter can't do that: it should only use the digits
|
311 | // that the user has input.
|
312 | //
|
313 | // For example, in Argentina, there's a format for mobile phone numbers:
|
314 | //
|
315 | // {
|
316 | // "pattern": "(\\d)(\\d{2})(\\d{4})(\\d{4})",
|
317 | // "leading_digits_patterns": ["91"],
|
318 | // "national_prefix_formatting_rule": "0$1",
|
319 | // "format": "$2 15-$3-$4",
|
320 | // "international_format": "$1 $2 $3-$4"
|
321 | // }
|
322 | //
|
323 | // In that format, `international_format` is used instead of `format`
|
324 | // because `format` inserts `15` in the formatted number,
|
325 | // and `AsYouType` formatter should only use the digits
|
326 | // the user has actually input, without adding any extra digits.
|
327 | // In this case, it wouldn't make a difference, because the `15`
|
328 | // is first stripped when applying `national_prefix_for_parsing`
|
329 | // and then re-added when using `format`, so in reality it doesn't
|
330 | // add any new digits to the number, but to detect that, the code
|
331 | // would have to be more complex: it would have to try formatting
|
332 | // the digits using the format and then see if any digits have
|
333 | // actually been added or removed, and then, every time a new digit
|
334 | // is input, it should re-check whether the chosen format doesn't
|
335 | // alter the digits.
|
336 | //
|
337 | // Google's code doesn't go that far, and so does this library:
|
338 | // it simply requires that a `format` doesn't add any additonal
|
339 | // digits to user's input.
|
340 | //
|
341 | // Also, people in general should move from inputting phone numbers
|
342 | // in national format (possibly with national prefixes)
|
343 | // and use international phone number format instead:
|
344 | // it's a logical thing in the modern age of mobile phones,
|
345 | // globalization and the internet.
|
346 | //
|
347 | /* istanbul ignore if */
|
348 | if (!NON_ALTERING_FORMAT_REG_EXP.test(this.getFormatFormat(format, state.international))) {
|
349 | continue
|
350 | }
|
351 | if (!this.createTemplateForFormat(format, state)) {
|
352 | // Remove the format if it can't generate a template.
|
353 | this.matchingFormats = this.matchingFormats.filter(_ => _ !== format)
|
354 | continue
|
355 | }
|
356 | this.chosenFormat = format
|
357 | break
|
358 | }
|
359 | if (!this.chosenFormat) {
|
360 | // No format matches the national (significant) phone number.
|
361 | this.resetFormat()
|
362 | }
|
363 | return this.chosenFormat
|
364 | }
|
365 |
|
366 | createTemplateForFormat(format, state) {
|
367 | // The formatter doesn't format numbers when numberPattern contains '|', e.g.
|
368 | // (20|3)\d{4}. In those cases we quickly return.
|
369 | // (Though there's no such format in current metadata)
|
370 | /* istanbul ignore if */
|
371 | if (SUPPORT_LEGACY_FORMATTING_PATTERNS && format.pattern().indexOf('|') >= 0) {
|
372 | return
|
373 | }
|
374 | // Get formatting template for this phone number format
|
375 | const template = this.getTemplateForFormat(format, state)
|
376 | // If the national number entered is too long
|
377 | // for any phone number format, then abort.
|
378 | if (template) {
|
379 | this.setNationalNumberTemplate(template, state)
|
380 | return true
|
381 | }
|
382 | }
|
383 |
|
384 | getSeparatorAfterNationalPrefix = (format) => {
|
385 | // `US` metadata doesn't have a `national_prefix_formatting_rule`,
|
386 | // so the `if` condition below doesn't apply to `US`,
|
387 | // but in reality there shoudl be a separator
|
388 | // between a national prefix and a national (significant) number.
|
389 | // So `US` national prefix separator is a "special" "hardcoded" case.
|
390 | if (this.isNANP) {
|
391 | return ' '
|
392 | }
|
393 | // If a `format` has a `national_prefix_formatting_rule`
|
394 | // and that rule has a separator after a national prefix,
|
395 | // then it means that there should be a separator
|
396 | // between a national prefix and a national (significant) number.
|
397 | if (format &&
|
398 | format.nationalPrefixFormattingRule() &&
|
399 | NATIONAL_PREFIX_SEPARATORS_PATTERN.test(format.nationalPrefixFormattingRule())) {
|
400 | return ' '
|
401 | }
|
402 | // At this point, there seems to be no clear evidence that
|
403 | // there should be a separator between a national prefix
|
404 | // and a national (significant) number. So don't insert one.
|
405 | return ''
|
406 | }
|
407 |
|
408 | getInternationalPrefixBeforeCountryCallingCode({ IDDPrefix, missingPlus }, options) {
|
409 | if (IDDPrefix) {
|
410 | return options && options.spacing === false ? IDDPrefix : IDDPrefix + ' '
|
411 | }
|
412 | if (missingPlus) {
|
413 | return ''
|
414 | }
|
415 | return '+'
|
416 | }
|
417 |
|
418 | getTemplate(state) {
|
419 | if (!this.template) {
|
420 | return
|
421 | }
|
422 | // `this.template` holds the template for a "complete" phone number.
|
423 | // The currently entered phone number is most likely not "complete",
|
424 | // so trim all non-populated digits.
|
425 | let index = -1
|
426 | let i = 0
|
427 | const internationalPrefix = state.international ? this.getInternationalPrefixBeforeCountryCallingCode(state, { spacing: false }) : ''
|
428 | while (i < internationalPrefix.length + state.getDigitsWithoutInternationalPrefix().length) {
|
429 | index = this.template.indexOf(DIGIT_PLACEHOLDER, index + 1)
|
430 | i++
|
431 | }
|
432 | return cutAndStripNonPairedParens(this.template, index + 1)
|
433 | }
|
434 |
|
435 | setNationalNumberTemplate(template, state) {
|
436 | this.nationalNumberTemplate = template
|
437 | this.populatedNationalNumberTemplate = template
|
438 | // With a new formatting template, the matched position
|
439 | // using the old template needs to be reset.
|
440 | this.populatedNationalNumberTemplatePosition = -1
|
441 | // For convenience, the public `.template` property
|
442 | // contains the whole international number
|
443 | // if the phone number being input is international:
|
444 | // 'x' for the '+' sign, 'x'es for the country phone code,
|
445 | // a spacebar and then the template for the formatted national number.
|
446 | if (state.international) {
|
447 | this.template =
|
448 | this.getInternationalPrefixBeforeCountryCallingCode(state).replace(/[\d\+]/g, DIGIT_PLACEHOLDER) +
|
449 | repeat(DIGIT_PLACEHOLDER, state.callingCode.length) +
|
450 | ' ' +
|
451 | template
|
452 | } else {
|
453 | this.template = template
|
454 | }
|
455 | }
|
456 |
|
457 | /**
|
458 | * Generates formatting template for a national phone number,
|
459 | * optionally containing a national prefix, for a format.
|
460 | * @param {Format} format
|
461 | * @param {string} nationalPrefix
|
462 | * @return {string}
|
463 | */
|
464 | getTemplateForFormat(format, {
|
465 | nationalSignificantNumber,
|
466 | international,
|
467 | nationalPrefix,
|
468 | complexPrefixBeforeNationalSignificantNumber
|
469 | }) {
|
470 | let pattern = format.pattern()
|
471 |
|
472 | /* istanbul ignore else */
|
473 | if (SUPPORT_LEGACY_FORMATTING_PATTERNS) {
|
474 | pattern = pattern
|
475 | // Replace anything in the form of [..] with \d
|
476 | .replace(CREATE_CHARACTER_CLASS_PATTERN(), '\\d')
|
477 | // Replace any standalone digit (not the one in `{}`) with \d
|
478 | .replace(CREATE_STANDALONE_DIGIT_PATTERN(), '\\d')
|
479 | }
|
480 |
|
481 | // Generate a dummy national number (consisting of `9`s)
|
482 | // that fits this format's `pattern`.
|
483 | //
|
484 | // This match will always succeed,
|
485 | // because the "longest dummy phone number"
|
486 | // has enough length to accomodate any possible
|
487 | // national phone number format pattern.
|
488 | //
|
489 | let digits = LONGEST_DUMMY_PHONE_NUMBER.match(pattern)[0]
|
490 |
|
491 | // If the national number entered is too long
|
492 | // for any phone number format, then abort.
|
493 | if (nationalSignificantNumber.length > digits.length) {
|
494 | return
|
495 | }
|
496 |
|
497 | // Get a formatting template which can be used to efficiently format
|
498 | // a partial number where digits are added one by one.
|
499 |
|
500 | // Below `strictPattern` is used for the
|
501 | // regular expression (with `^` and `$`).
|
502 | // This wasn't originally in Google's `libphonenumber`
|
503 | // and I guess they don't really need it
|
504 | // because they're not using "templates" to format phone numbers
|
505 | // but I added `strictPattern` after encountering
|
506 | // South Korean phone number formatting bug.
|
507 | //
|
508 | // Non-strict regular expression bug demonstration:
|
509 | //
|
510 | // this.nationalSignificantNumber : `111111111` (9 digits)
|
511 | //
|
512 | // pattern : (\d{2})(\d{3,4})(\d{4})
|
513 | // format : `$1 $2 $3`
|
514 | // digits : `9999999999` (10 digits)
|
515 | //
|
516 | // '9999999999'.replace(new RegExp(/(\d{2})(\d{3,4})(\d{4})/g), '$1 $2 $3') = "99 9999 9999"
|
517 | //
|
518 | // template : xx xxxx xxxx
|
519 | //
|
520 | // But the correct template in this case is `xx xxx xxxx`.
|
521 | // The template was generated incorrectly because of the
|
522 | // `{3,4}` variability in the `pattern`.
|
523 | //
|
524 | // The fix is, if `this.nationalSignificantNumber` has already sufficient length
|
525 | // to satisfy the `pattern` completely then `this.nationalSignificantNumber`
|
526 | // is used instead of `digits`.
|
527 |
|
528 | const strictPattern = new RegExp('^' + pattern + '$')
|
529 | const nationalNumberDummyDigits = nationalSignificantNumber.replace(/\d/g, DUMMY_DIGIT)
|
530 |
|
531 | // If `this.nationalSignificantNumber` has already sufficient length
|
532 | // to satisfy the `pattern` completely then use it
|
533 | // instead of `digits`.
|
534 | if (strictPattern.test(nationalNumberDummyDigits)) {
|
535 | digits = nationalNumberDummyDigits
|
536 | }
|
537 |
|
538 | let numberFormat = this.getFormatFormat(format, international)
|
539 | let nationalPrefixIncludedInTemplate
|
540 |
|
541 | // If a user did input a national prefix (and that's guaranteed),
|
542 | // and if a `format` does have a national prefix formatting rule,
|
543 | // then see if that national prefix formatting rule
|
544 | // prepends exactly the same national prefix the user has input.
|
545 | // If that's the case, then use the `format` with the national prefix formatting rule.
|
546 | // Otherwise, use the `format` without the national prefix formatting rule,
|
547 | // and prepend a national prefix manually to it.
|
548 | if (this.shouldTryNationalPrefixFormattingRule(format, { international, nationalPrefix })) {
|
549 | const numberFormatWithNationalPrefix = numberFormat.replace(
|
550 | FIRST_GROUP_PATTERN,
|
551 | format.nationalPrefixFormattingRule()
|
552 | )
|
553 | // If `national_prefix_formatting_rule` of a `format` simply prepends
|
554 | // national prefix at the start of a national (significant) number,
|
555 | // then such formatting can be used with `AsYouType` formatter.
|
556 | // There seems to be no `else` case: everywhere in metadata,
|
557 | // national prefix formatting rule is national prefix + $1,
|
558 | // or `($1)`, in which case such format isn't even considered
|
559 | // when the user has input a national prefix.
|
560 | /* istanbul ignore else */
|
561 | if (parseDigits(format.nationalPrefixFormattingRule()) === (nationalPrefix || '') + parseDigits('$1')) {
|
562 | numberFormat = numberFormatWithNationalPrefix
|
563 | nationalPrefixIncludedInTemplate = true
|
564 | // Replace all digits of the national prefix in the formatting template
|
565 | // with `DIGIT_PLACEHOLDER`s.
|
566 | if (nationalPrefix) {
|
567 | let i = nationalPrefix.length
|
568 | while (i > 0) {
|
569 | numberFormat = numberFormat.replace(/\d/, DIGIT_PLACEHOLDER)
|
570 | i--
|
571 | }
|
572 | }
|
573 | }
|
574 | }
|
575 |
|
576 | // Generate formatting template for this phone number format.
|
577 | let template = digits
|
578 | // Format the dummy phone number according to the format.
|
579 | .replace(new RegExp(pattern), numberFormat)
|
580 | // Replace each dummy digit with a DIGIT_PLACEHOLDER.
|
581 | .replace(new RegExp(DUMMY_DIGIT, 'g'), DIGIT_PLACEHOLDER)
|
582 |
|
583 | // If a prefix of a national (significant) number is not as simple
|
584 | // as just a basic national prefix, then just prepend such prefix
|
585 | // before the national (significant) number, optionally spacing
|
586 | // the two with a whitespace.
|
587 | if (!nationalPrefixIncludedInTemplate) {
|
588 | if (complexPrefixBeforeNationalSignificantNumber) {
|
589 | // Prepend the prefix to the template manually.
|
590 | template = repeat(DIGIT_PLACEHOLDER, complexPrefixBeforeNationalSignificantNumber.length) +
|
591 | ' ' +
|
592 | template
|
593 | } else if (nationalPrefix) {
|
594 | // Prepend national prefix to the template manually.
|
595 | template = repeat(DIGIT_PLACEHOLDER, nationalPrefix.length) +
|
596 | this.getSeparatorAfterNationalPrefix(format) +
|
597 | template
|
598 | }
|
599 | }
|
600 |
|
601 | if (international) {
|
602 | template = applyInternationalSeparatorStyle(template)
|
603 | }
|
604 |
|
605 | return template
|
606 | }
|
607 |
|
608 | formatNextNationalNumberDigits(digits) {
|
609 | const result = populateTemplateWithDigits(
|
610 | this.populatedNationalNumberTemplate,
|
611 | this.populatedNationalNumberTemplatePosition,
|
612 | digits
|
613 | )
|
614 |
|
615 | if (!result) {
|
616 | // Reset the format.
|
617 | this.resetFormat()
|
618 | return
|
619 | }
|
620 |
|
621 | this.populatedNationalNumberTemplate = result[0]
|
622 | this.populatedNationalNumberTemplatePosition = result[1]
|
623 |
|
624 | // Return the formatted phone number so far.
|
625 | return cutAndStripNonPairedParens(this.populatedNationalNumberTemplate, this.populatedNationalNumberTemplatePosition + 1)
|
626 |
|
627 | // The old way which was good for `input-format` but is not so good
|
628 | // for `react-phone-number-input`'s default input (`InputBasic`).
|
629 | // return closeNonPairedParens(this.populatedNationalNumberTemplate, this.populatedNationalNumberTemplatePosition + 1)
|
630 | // .replace(new RegExp(DIGIT_PLACEHOLDER, 'g'), ' ')
|
631 | }
|
632 |
|
633 | shouldTryNationalPrefixFormattingRule = (format, { international, nationalPrefix }) => {
|
634 | if (format.nationalPrefixFormattingRule()) {
|
635 | // In some countries, `national_prefix_formatting_rule` is `($1)`,
|
636 | // so it applies even if the user hasn't input a national prefix.
|
637 | // `format.usesNationalPrefix()` detects such cases.
|
638 | const usesNationalPrefix = format.usesNationalPrefix()
|
639 | if ((usesNationalPrefix && nationalPrefix) ||
|
640 | (!usesNationalPrefix && !international)) {
|
641 | return true
|
642 | }
|
643 | }
|
644 | }
|
645 | } |
\ | No newline at end of file |