Fix/input tel format options (#1733)
* fix(input-tel): remove unwanted characters in parser * feat(input-tel-dropdown): export getFlagSymbol function * feat(input-tel): add formatCountryCodeStyle option
This commit is contained in:
parent
25e8de40b7
commit
7239d60466
17 changed files with 402 additions and 40 deletions
5
.changeset/chilled-rules-explode.md
Normal file
5
.changeset/chilled-rules-explode.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'@lion/input-tel-dropdown': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
substract and export getFlagSymbol function
|
||||||
6
.changeset/plenty-penguins-greet.md
Normal file
6
.changeset/plenty-penguins-greet.md
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
'@lion/input-tel': patch
|
||||||
|
'@lion/input-tel-dropdown': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Add option to style the country-code with parentheses in the formatter
|
||||||
5
.changeset/tame-papayas-double.md
Normal file
5
.changeset/tame-papayas-double.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'@lion/input-tel': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Remove unwanted characters in input-tel parser
|
||||||
|
|
@ -81,3 +81,79 @@ export const preferredRegionCodes = () => {
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Format
|
||||||
|
|
||||||
|
### Format strategy
|
||||||
|
|
||||||
|
Determines what the formatter output should look like.
|
||||||
|
Formatting strategies as provided by awesome-phonenumber / google-libphonenumber.
|
||||||
|
|
||||||
|
Possible values:
|
||||||
|
|
||||||
|
| strategy | output |
|
||||||
|
| :------------ | ------------------------------------: |
|
||||||
|
| e164 | `+46707123456` |
|
||||||
|
| international | `+46 70 712 34 56` |
|
||||||
|
| national | not applicable for input-tel-dropdown |
|
||||||
|
| significant | not applicable for input-tel-dropdown |
|
||||||
|
| rfc3966 | `tel:+46-70-712-34-56` |
|
||||||
|
|
||||||
|
Also see:
|
||||||
|
|
||||||
|
- [awesome-phonenumber documentation](https://www.npmjs.com/package/awesome-phonenumber)
|
||||||
|
|
||||||
|
```js preview-story
|
||||||
|
export const formatStrategy = () => {
|
||||||
|
loadDefaultFeedbackMessages();
|
||||||
|
const inputTel = createRef();
|
||||||
|
return html`
|
||||||
|
<select @change="${({ target }) => (inputTel.value.formatStrategy = target.value)}">
|
||||||
|
<option value="e164">e164</option>
|
||||||
|
<option value="international">international</option>
|
||||||
|
<option value="rfc3966">rfc3966</option>
|
||||||
|
</select>
|
||||||
|
<lion-input-tel-dropdown
|
||||||
|
${ref(inputTel)}
|
||||||
|
label="Format strategy"
|
||||||
|
help-text="Choose a strategy above"
|
||||||
|
format-strategy="e164"
|
||||||
|
name="phoneNumber"
|
||||||
|
></lion-input-tel-dropdown>
|
||||||
|
<h-output
|
||||||
|
.show="${['modelValue', 'formatStrategy']}"
|
||||||
|
.readyPromise="${PhoneUtilManager.loadComplete}"
|
||||||
|
></h-output>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Format country code style
|
||||||
|
|
||||||
|
You can style the country code with parentheses.
|
||||||
|
|
||||||
|
```js preview-story
|
||||||
|
export const formatCountryCodeStyle = () => {
|
||||||
|
loadDefaultFeedbackMessages();
|
||||||
|
const inputTel = createRef();
|
||||||
|
return html`
|
||||||
|
<select @change="${({ target }) => (inputTel.value.formatStrategy = target.value)}">
|
||||||
|
<option value="e164">e164</option>
|
||||||
|
<option value="international">international</option>
|
||||||
|
<option value="rfc3966">rfc3966</option>
|
||||||
|
</select>
|
||||||
|
<lion-input-tel-dropdown
|
||||||
|
${ref(inputTel)}
|
||||||
|
label="Format strategy"
|
||||||
|
help-text="Choose a strategy above"
|
||||||
|
format-country-code-style="parentheses"
|
||||||
|
format-strategy="e164"
|
||||||
|
name="phoneNumber"
|
||||||
|
></lion-input-tel-dropdown>
|
||||||
|
<h-output
|
||||||
|
.show="${['modelValue', 'formatStrategy']}"
|
||||||
|
.readyPromise="${PhoneUtilManager.loadComplete}"
|
||||||
|
></h-output>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -176,7 +176,9 @@ export const oneAllowedRegion = () => {
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
## Format strategy
|
## Format
|
||||||
|
|
||||||
|
### Format strategy
|
||||||
|
|
||||||
Determines what the formatter output should look like.
|
Determines what the formatter output should look like.
|
||||||
Formatting strategies as provided by awesome-phonenumber / google-libphonenumber.
|
Formatting strategies as provided by awesome-phonenumber / google-libphonenumber.
|
||||||
|
|
@ -223,7 +225,37 @@ export const formatStrategy = () => {
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
## Live format
|
### Format country code style
|
||||||
|
|
||||||
|
You can also style the country code with parentheses.
|
||||||
|
|
||||||
|
```js preview-story
|
||||||
|
export const formatCountryCodeStyle = () => {
|
||||||
|
loadDefaultFeedbackMessages();
|
||||||
|
const inputTel = createRef();
|
||||||
|
return html`
|
||||||
|
<select @change="${({ target }) => (inputTel.value.formatStrategy = target.value)}">
|
||||||
|
<option value="e164">e164</option>
|
||||||
|
<option value="international">international</option>
|
||||||
|
<option value="rfc3966">rfc3966</option>
|
||||||
|
</select>
|
||||||
|
<lion-input-tel
|
||||||
|
${ref(inputTel)}
|
||||||
|
label="Format strategy"
|
||||||
|
help-text="Choose a strategy above"
|
||||||
|
.modelValue=${'+46707123456'}
|
||||||
|
format-country-code-style="parentheses"
|
||||||
|
name="phoneNumber"
|
||||||
|
></lion-input-tel>
|
||||||
|
<h-output
|
||||||
|
.show="${['modelValue', 'formatStrategy']}"
|
||||||
|
.readyPromise="${PhoneUtilManager.loadComplete}"
|
||||||
|
></h-output>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Live format
|
||||||
|
|
||||||
Type '6' in the example below to see how the phone number is formatted during typing.
|
Type '6' in the example below to see how the phone number is formatted during typing.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
|
export { getFlagSymbol } from './src/getFlagSymbol.js';
|
||||||
export { LionInputTelDropdown } from './src/LionInputTelDropdown.js';
|
export { LionInputTelDropdown } from './src/LionInputTelDropdown.js';
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
import { render, html, css, ref, createRef } from '@lion/core';
|
import { render, html, css, ref, createRef } from '@lion/core';
|
||||||
import { LionInputTel } from '@lion/input-tel';
|
import { LionInputTel } from '@lion/input-tel';
|
||||||
import { localize } from '@lion/localize';
|
import { localize } from '@lion/localize';
|
||||||
|
import { getFlagSymbol } from './getFlagSymbol.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Note: one could consider to implement LionInputTelDropdown as a
|
* Note: one could consider to implement LionInputTelDropdown as a
|
||||||
|
|
@ -31,14 +32,6 @@ import { localize } from '@lion/localize';
|
||||||
* @typedef {TemplateDataForDropdownInputTel & {data: {regionMetaList:RegionMeta[]}}} TemplateDataForIntlInputTel
|
* @typedef {TemplateDataForDropdownInputTel & {data: {regionMetaList:RegionMeta[]}}} TemplateDataForIntlInputTel
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// eslint-disable-next-line prefer-destructuring
|
|
||||||
/**
|
|
||||||
* @param {string} char
|
|
||||||
*/
|
|
||||||
function getRegionalIndicatorSymbol(char) {
|
|
||||||
return String.fromCodePoint(0x1f1e6 - 65 + char.toUpperCase().charCodeAt(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* LionInputTelDropdown renders a dropdown like element next to the text field, inside the
|
* LionInputTelDropdown renders a dropdown like element next to the text field, inside the
|
||||||
* prefix slot. This could be a LionSelect, a LionSelectRich or a native select.
|
* prefix slot. This could be a LionSelect, a LionSelectRich or a native select.
|
||||||
|
|
@ -288,7 +281,11 @@ export class LionInputTelDropdown extends LionInputTel {
|
||||||
_initModelValueBasedOnDropdown() {
|
_initModelValueBasedOnDropdown() {
|
||||||
if (!this._initialModelValue && !this.dirty && this._phoneUtil) {
|
if (!this._initialModelValue && !this.dirty && this._phoneUtil) {
|
||||||
const countryCode = this._phoneUtil.getCountryCodeForRegionCode(this.activeRegion);
|
const countryCode = this._phoneUtil.getCountryCodeForRegionCode(this.activeRegion);
|
||||||
|
if (this.formatCountryCodeStyle === 'parentheses') {
|
||||||
|
this.__initializedRegionCode = `(+${countryCode})`;
|
||||||
|
} else {
|
||||||
this.__initializedRegionCode = `+${countryCode}`;
|
this.__initializedRegionCode = `+${countryCode}`;
|
||||||
|
}
|
||||||
this.modelValue = this.__initializedRegionCode;
|
this.modelValue = this.__initializedRegionCode;
|
||||||
this._initialModelValue = this.__initializedRegionCode;
|
this._initialModelValue = this.__initializedRegionCode;
|
||||||
this.initInteractionState();
|
this.initInteractionState();
|
||||||
|
|
@ -335,9 +332,13 @@ export class LionInputTelDropdown extends LionInputTel {
|
||||||
} else {
|
} else {
|
||||||
// In case of dropdown has +31, and input has only +3
|
// In case of dropdown has +31, and input has only +3
|
||||||
const valueObj = this.value.split(' ');
|
const valueObj = this.value.split(' ');
|
||||||
|
if (this.formatCountryCodeStyle === 'parentheses' && !this.value.includes('(')) {
|
||||||
|
this.modelValue = this._callParser(this.value.replace(valueObj[0], `(+${countryCode})`));
|
||||||
|
} else {
|
||||||
this.modelValue = this._callParser(this.value.replace(valueObj[0], `+${countryCode}`));
|
this.modelValue = this._callParser(this.value.replace(valueObj[0], `+${countryCode}`));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Put focus on text box
|
// Put focus on text box
|
||||||
const overlayController = event.target._overlayCtrl;
|
const overlayController = event.target._overlayCtrl;
|
||||||
|
|
@ -419,8 +420,7 @@ export class LionInputTelDropdown extends LionInputTel {
|
||||||
const countryCode =
|
const countryCode =
|
||||||
this._phoneUtil && this._phoneUtil.getCountryCodeForRegionCode(regionCode);
|
this._phoneUtil && this._phoneUtil.getCountryCodeForRegionCode(regionCode);
|
||||||
|
|
||||||
const flagSymbol =
|
const flagSymbol = getFlagSymbol(regionCode);
|
||||||
getRegionalIndicatorSymbol(regionCode[0]) + getRegionalIndicatorSymbol(regionCode[1]);
|
|
||||||
|
|
||||||
const destinationList = this.preferredRegions.includes(regionCode)
|
const destinationList = this.preferredRegions.includes(regionCode)
|
||||||
? this.__regionMetaListPreferred
|
? this.__regionMetaListPreferred
|
||||||
|
|
|
||||||
13
packages/input-tel-dropdown/src/getFlagSymbol.js
Normal file
13
packages/input-tel-dropdown/src/getFlagSymbol.js
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
/**
|
||||||
|
* @param {string} char
|
||||||
|
*/
|
||||||
|
function getRegionalIndicatorSymbol(char) {
|
||||||
|
return String.fromCodePoint(0x1f1e6 - 65 + char.toUpperCase().charCodeAt(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} regionCode
|
||||||
|
*/
|
||||||
|
export function getFlagSymbol(regionCode) {
|
||||||
|
return getRegionalIndicatorSymbol(regionCode[0]) + getRegionalIndicatorSymbol(regionCode[1]);
|
||||||
|
}
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
import {
|
|
||||||
expect,
|
|
||||||
fixture as _fixture,
|
|
||||||
fixtureSync as _fixtureSync,
|
|
||||||
html,
|
|
||||||
defineCE,
|
|
||||||
unsafeStatic,
|
|
||||||
aTimeout,
|
|
||||||
} from '@open-wc/testing';
|
|
||||||
import sinon from 'sinon';
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { PhoneUtilManager } from '@lion/input-tel';
|
import { PhoneUtilManager } from '@lion/input-tel';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { mockPhoneUtilManager, restorePhoneUtilManager } from '@lion/input-tel/test-helpers';
|
import { mockPhoneUtilManager, restorePhoneUtilManager } from '@lion/input-tel/test-helpers';
|
||||||
|
import {
|
||||||
|
aTimeout,
|
||||||
|
defineCE,
|
||||||
|
expect,
|
||||||
|
fixture as _fixture,
|
||||||
|
fixtureSync as _fixtureSync,
|
||||||
|
html,
|
||||||
|
unsafeStatic,
|
||||||
|
} from '@open-wc/testing';
|
||||||
|
import sinon from 'sinon';
|
||||||
import { LionInputTelDropdown } from '../src/LionInputTelDropdown.js';
|
import { LionInputTelDropdown } from '../src/LionInputTelDropdown.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -252,17 +252,18 @@ export function runInputTelDropdownSuite({ klass } = { klass: LionInputTelDropdo
|
||||||
await aTimeout(0);
|
await aTimeout(0);
|
||||||
expect(spy).to.have.been.calledOnce;
|
expect(spy).to.have.been.calledOnce;
|
||||||
restorePhoneUtilManager();
|
restorePhoneUtilManager();
|
||||||
|
spy.restore();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('On dropdown value change', () => {
|
describe('On dropdown value change', () => {
|
||||||
it('changes the currently active country code in the textbox', async () => {
|
it('changes the currently active country code in the textbox', async () => {
|
||||||
const el = await fixture(
|
const el = await fixture(html`
|
||||||
html` <${tag} .allowedRegions="${[
|
<${tag}
|
||||||
'NL',
|
.allowedRegions="${['NL', 'BE']}"
|
||||||
'BE',
|
.modelValue="${'+31612345678'}"
|
||||||
]}" .modelValue="${'+31612345678'}"></${tag}> `,
|
></${tag}>
|
||||||
);
|
`);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
mimicUserChangingDropdown(el.refs.dropdown.value, 'BE');
|
mimicUserChangingDropdown(el.refs.dropdown.value, 'BE');
|
||||||
await el.updateComplete;
|
await el.updateComplete;
|
||||||
|
|
@ -282,6 +283,21 @@ export function runInputTelDropdownSuite({ klass } = { klass: LionInputTelDropdo
|
||||||
expect(el.value).to.equal('+32');
|
expect(el.value).to.equal('+32');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('changes the currently active country code in the textbox when empty with parentheses', async () => {
|
||||||
|
const el = await fixture(
|
||||||
|
html` <${tag} format-country-code-style="parentheses" .allowedRegions="${[
|
||||||
|
'NL',
|
||||||
|
'BE',
|
||||||
|
]}"></${tag}> `,
|
||||||
|
);
|
||||||
|
el.value = '';
|
||||||
|
// @ts-ignore
|
||||||
|
mimicUserChangingDropdown(el.refs.dropdown.value, 'BE');
|
||||||
|
await el.updateComplete;
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(el.value).to.equal('(+32)');
|
||||||
|
});
|
||||||
|
|
||||||
it('changes the currently active country code in the textbox when invalid', async () => {
|
it('changes the currently active country code in the textbox when invalid', async () => {
|
||||||
const el = await fixture(html` <${tag} .allowedRegions="${['NL', 'BE']}"></${tag}> `);
|
const el = await fixture(html` <${tag} .allowedRegions="${['NL', 'BE']}"></${tag}> `);
|
||||||
el.value = '+3';
|
el.value = '+3';
|
||||||
|
|
@ -381,5 +397,19 @@ export function runInputTelDropdownSuite({ klass } = { klass: LionInputTelDropdo
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('is empthy', () => {
|
||||||
|
it('ignores initial countrycode', async () => {
|
||||||
|
const el = await fixture(html` <${tag}></${tag}> `);
|
||||||
|
// @ts-ignore
|
||||||
|
expect(el._isEmpty()).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores initial countrycode with parentheses', async () => {
|
||||||
|
const el = await fixture(html` <${tag} format-country-code-style="parentheses"></${tag}> `);
|
||||||
|
// @ts-ignore
|
||||||
|
expect(el._isEmpty()).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import { localizeNamespaceLoader } from './localizeNamespaceLoader.js';
|
||||||
* @typedef {import('awesome-phonenumber').PhoneNumberTypes} PhoneNumberTypes
|
* @typedef {import('awesome-phonenumber').PhoneNumberTypes} PhoneNumberTypes
|
||||||
* @typedef {import('@lion/form-core/types/FormatMixinTypes').FormatOptions} FormatOptions
|
* @typedef {import('@lion/form-core/types/FormatMixinTypes').FormatOptions} FormatOptions
|
||||||
* @typedef {* & import('awesome-phonenumber').default} AwesomePhoneNumber
|
* @typedef {* & import('awesome-phonenumber').default} AwesomePhoneNumber
|
||||||
* @typedef {FormatOptions & {regionCode: RegionCode; formatStrategy: PhoneNumberFormat}} FormatOptionsTel
|
* @typedef {FormatOptions & {regionCode: RegionCode; formatStrategy: PhoneNumberFormat; formatCountryCodeStyle: string;}} FormatOptionsTel
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export class LionInputTel extends LocalizeMixin(LionInput) {
|
export class LionInputTel extends LocalizeMixin(LionInput) {
|
||||||
|
|
@ -24,6 +24,7 @@ export class LionInputTel extends LocalizeMixin(LionInput) {
|
||||||
static properties = {
|
static properties = {
|
||||||
allowedRegions: { type: Array },
|
allowedRegions: { type: Array },
|
||||||
formatStrategy: { type: String, attribute: 'format-strategy' },
|
formatStrategy: { type: String, attribute: 'format-strategy' },
|
||||||
|
formatCountryCodeStyle: { type: String, attribute: 'format-country-code-style' },
|
||||||
activeRegion: { type: String },
|
activeRegion: { type: String },
|
||||||
_phoneUtil: { type: Object, state: true },
|
_phoneUtil: { type: Object, state: true },
|
||||||
_needsLightDomRender: { type: Number, state: true },
|
_needsLightDomRender: { type: Number, state: true },
|
||||||
|
|
@ -139,6 +140,13 @@ export class LionInputTel extends LocalizeMixin(LionInput) {
|
||||||
*/
|
*/
|
||||||
this.formatStrategy = 'international';
|
this.formatStrategy = 'international';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extra styling of the format strategy
|
||||||
|
* default | parentheses
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
this.formatCountryCodeStyle = 'default';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The regions that should be considered when international phone numbers are detected.
|
* The regions that should be considered when international phone numbers are detected.
|
||||||
* (when not configured, all regions worldwide will be considered)
|
* (when not configured, all regions worldwide will be considered)
|
||||||
|
|
@ -208,6 +216,12 @@ export class LionInputTel extends LocalizeMixin(LionInput) {
|
||||||
(this.formatOptions).formatStrategy = this.formatStrategy;
|
(this.formatOptions).formatStrategy = this.formatStrategy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (changedProperties.has('formatCountryCodeStyle')) {
|
||||||
|
this._calculateValues({ source: null });
|
||||||
|
/** @type {FormatOptionsTel} */
|
||||||
|
(this.formatOptions).formatCountryCodeStyle = this.formatCountryCodeStyle;
|
||||||
|
}
|
||||||
|
|
||||||
if (changedProperties.has('modelValue') || changedProperties.has('allowedRegions')) {
|
if (changedProperties.has('modelValue') || changedProperties.has('allowedRegions')) {
|
||||||
this.__calculateActiveRegion();
|
this.__calculateActiveRegion();
|
||||||
}
|
}
|
||||||
|
|
@ -239,6 +253,7 @@ export class LionInputTel extends LocalizeMixin(LionInput) {
|
||||||
return formatPhoneNumber(modelValue, {
|
return formatPhoneNumber(modelValue, {
|
||||||
regionCode: /** @type {RegionCode} */ (this.activeRegion),
|
regionCode: /** @type {RegionCode} */ (this.activeRegion),
|
||||||
formatStrategy: this.formatStrategy,
|
formatStrategy: this.formatStrategy,
|
||||||
|
formatCountryCodeStyle: this.formatCountryCodeStyle,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -265,6 +280,7 @@ export class LionInputTel extends LocalizeMixin(LionInput) {
|
||||||
return liveFormatPhoneNumber(viewValue, {
|
return liveFormatPhoneNumber(viewValue, {
|
||||||
regionCode: /** @type {RegionCode} */ (this.activeRegion),
|
regionCode: /** @type {RegionCode} */ (this.activeRegion),
|
||||||
formatStrategy: this.formatStrategy,
|
formatStrategy: this.formatStrategy,
|
||||||
|
formatCountryCodeStyle: this.formatCountryCodeStyle,
|
||||||
currentCaretIndex,
|
currentCaretIndex,
|
||||||
prevViewValue,
|
prevViewValue,
|
||||||
});
|
});
|
||||||
|
|
@ -313,7 +329,10 @@ export class LionInputTel extends LocalizeMixin(LionInput) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Try to derive action region from user value
|
// 2. Try to derive action region from user value
|
||||||
const value = !(this.modelValue instanceof Unparseable) ? this.modelValue : this.value;
|
const regex = /[+0-9]+/gi;
|
||||||
|
const value = !(this.modelValue instanceof Unparseable)
|
||||||
|
? this.modelValue
|
||||||
|
: this.value.match(regex)?.join('');
|
||||||
const regionDerivedFromValue = value && this._phoneUtil && this._phoneUtil(value).g?.regionCode;
|
const regionDerivedFromValue = value && this._phoneUtil && this._phoneUtil(value).g?.regionCode;
|
||||||
|
|
||||||
if (regionDerivedFromValue && this._allowedOrAllRegions.includes(regionDerivedFromValue)) {
|
if (regionDerivedFromValue && this._allowedOrAllRegions.includes(regionDerivedFromValue)) {
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,37 @@ import { PhoneUtilManager } from './PhoneUtilManager.js';
|
||||||
* @typedef {* & import('awesome-phonenumber').default} AwesomePhoneNumber
|
* @typedef {* & import('awesome-phonenumber').default} AwesomePhoneNumber
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} value
|
||||||
|
* @param {object} options
|
||||||
|
* @param {RegionCode} options.regionCode
|
||||||
|
* @param {string} options.formatCountryCodeStyle
|
||||||
|
*/
|
||||||
|
export function getFormatCountryCodeStyle(value, { regionCode, formatCountryCodeStyle }) {
|
||||||
|
const countryCode = PhoneUtilManager?.PhoneUtil?.getCountryCodeForRegionCode(regionCode);
|
||||||
|
if (
|
||||||
|
formatCountryCodeStyle === 'parentheses' &&
|
||||||
|
countryCode &&
|
||||||
|
value.includes(`+${countryCode}`) &&
|
||||||
|
!value.includes(`(`)
|
||||||
|
) {
|
||||||
|
return value.replace(`+${countryCode}`, `(+${countryCode})`);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} modelValue
|
* @param {string} modelValue
|
||||||
* @param {object} options
|
* @param {object} options
|
||||||
* @param {RegionCode} options.regionCode
|
* @param {RegionCode} options.regionCode
|
||||||
* @param {PhoneNumberFormat} [options.formatStrategy='international']
|
* @param {PhoneNumberFormat} [options.formatStrategy='international']
|
||||||
|
* @param {string} [options.formatCountryCodeStyle='default']
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function formatPhoneNumber(modelValue, { regionCode, formatStrategy = 'international' }) {
|
export function formatPhoneNumber(
|
||||||
|
modelValue,
|
||||||
|
{ regionCode, formatStrategy = 'international', formatCountryCodeStyle = 'default' },
|
||||||
|
) {
|
||||||
// Do not format when not loaded
|
// Do not format when not loaded
|
||||||
if (!PhoneUtilManager.isLoaded) {
|
if (!PhoneUtilManager.isLoaded) {
|
||||||
return modelValue;
|
return modelValue;
|
||||||
|
|
@ -50,8 +73,15 @@ export function formatPhoneNumber(modelValue, { regionCode, formatStrategy = 'in
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (formatCountryCodeStyle !== 'default') {
|
||||||
|
return getFormatCountryCodeStyle(formattedValue, { regionCode, formatCountryCodeStyle });
|
||||||
|
}
|
||||||
return formattedValue;
|
return formattedValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (formatCountryCodeStyle !== 'default') {
|
||||||
|
return getFormatCountryCodeStyle(modelValue, { regionCode, formatCountryCodeStyle });
|
||||||
|
}
|
||||||
return modelValue;
|
return modelValue;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,11 @@ export function parsePhoneNumber(viewValue, { regionCode }) {
|
||||||
|
|
||||||
// eslint-disable-next-line prefer-destructuring
|
// eslint-disable-next-line prefer-destructuring
|
||||||
const PhoneNumber = /** @type {AwesomePhoneNumber} */ (PhoneUtilManager.PhoneUtil);
|
const PhoneNumber = /** @type {AwesomePhoneNumber} */ (PhoneUtilManager.PhoneUtil);
|
||||||
|
const regex = /[+0-9]+/gi;
|
||||||
|
const strippedViewValue = viewValue.match(regex)?.join('');
|
||||||
let pn;
|
let pn;
|
||||||
try {
|
try {
|
||||||
pn = PhoneNumber(viewValue, regionCode);
|
pn = PhoneNumber(strippedViewValue, regionCode);
|
||||||
// eslint-disable-next-line no-empty
|
// eslint-disable-next-line no-empty
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { formatPhoneNumber, getFormatCountryCodeStyle } from './formatters.js';
|
||||||
import { PhoneUtilManager } from './PhoneUtilManager.js';
|
import { PhoneUtilManager } from './PhoneUtilManager.js';
|
||||||
import { formatPhoneNumber } from './formatters.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('../types').RegionCode} RegionCode
|
* @typedef {import('../types').RegionCode} RegionCode
|
||||||
|
|
@ -14,11 +14,12 @@ import { formatPhoneNumber } from './formatters.js';
|
||||||
* @param {string} options.prevViewValue
|
* @param {string} options.prevViewValue
|
||||||
* @param {number} options.currentCaretIndex
|
* @param {number} options.currentCaretIndex
|
||||||
* @param {PhoneNumberFormat} options.formatStrategy
|
* @param {PhoneNumberFormat} options.formatStrategy
|
||||||
|
* @param {string?} [options.formatCountryCodeStyle='default']
|
||||||
* @returns {{viewValue:string; caretIndex:number;}|undefined}
|
* @returns {{viewValue:string; caretIndex:number;}|undefined}
|
||||||
*/
|
*/
|
||||||
export function liveFormatPhoneNumber(
|
export function liveFormatPhoneNumber(
|
||||||
viewValue,
|
viewValue,
|
||||||
{ regionCode, formatStrategy, prevViewValue, currentCaretIndex },
|
{ regionCode, formatStrategy, prevViewValue, currentCaretIndex, formatCountryCodeStyle },
|
||||||
) {
|
) {
|
||||||
const diff = viewValue.length - prevViewValue.length;
|
const diff = viewValue.length - prevViewValue.length;
|
||||||
// Do not format when not loaded
|
// Do not format when not loaded
|
||||||
|
|
@ -36,7 +37,12 @@ export function liveFormatPhoneNumber(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const newViewValue = formatPhoneNumber(ayt.number(), { regionCode, formatStrategy });
|
let parenthesesAdded = false;
|
||||||
|
let newViewValue = formatPhoneNumber(ayt.number(), { regionCode, formatStrategy });
|
||||||
|
if (formatCountryCodeStyle === 'parentheses' && regionCode && !newViewValue.includes(`(`)) {
|
||||||
|
newViewValue = getFormatCountryCodeStyle(newViewValue, { regionCode, formatCountryCodeStyle });
|
||||||
|
parenthesesAdded = true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given following situation:
|
* Given following situation:
|
||||||
|
|
@ -45,7 +51,16 @@ export function liveFormatPhoneNumber(
|
||||||
* - prevViewValue `+36123` (we inserted '1' at position 2)
|
* - prevViewValue `+36123` (we inserted '1' at position 2)
|
||||||
* => we should get `+31 6123`, and new caretIndex should be 3, and not newViewValue.length
|
* => we should get `+31 6123`, and new caretIndex should be 3, and not newViewValue.length
|
||||||
*/
|
*/
|
||||||
|
const countryCode = PhoneUtilManager?.PhoneUtil?.getCountryCodeForRegionCode(regionCode);
|
||||||
const diffBetweenNewAndCurrent = newViewValue.length - viewValue.length;
|
const diffBetweenNewAndCurrent = newViewValue.length - viewValue.length;
|
||||||
const newCaretIndex = currentCaretIndex + diffBetweenNewAndCurrent;
|
let newCaretIndex = currentCaretIndex + diffBetweenNewAndCurrent;
|
||||||
|
if (
|
||||||
|
parenthesesAdded &&
|
||||||
|
countryCode &&
|
||||||
|
viewValue.length > countryCode.toString().length + 1 &&
|
||||||
|
currentCaretIndex <= countryCode.toString().length + 1
|
||||||
|
) {
|
||||||
|
newCaretIndex -= 2;
|
||||||
|
}
|
||||||
return newViewValue ? { viewValue: newViewValue, caretIndex: newCaretIndex } : undefined;
|
return newViewValue ? { viewValue: newViewValue, caretIndex: newCaretIndex } : undefined;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import {
|
||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
import { mimicUserInput } from '@lion/form-core/test-helpers';
|
import { mimicUserInput } from '@lion/form-core/test-helpers';
|
||||||
import { localize } from '@lion/localize';
|
import { localize } from '@lion/localize';
|
||||||
|
import { Unparseable } from '@lion/form-core';
|
||||||
import { LionInputTel } from '../src/LionInputTel.js';
|
import { LionInputTel } from '../src/LionInputTel.js';
|
||||||
import { PhoneNumber } from '../src/validators.js';
|
import { PhoneNumber } from '../src/validators.js';
|
||||||
import { PhoneUtilManager } from '../src/PhoneUtilManager.js';
|
import { PhoneUtilManager } from '../src/PhoneUtilManager.js';
|
||||||
|
|
@ -103,6 +104,28 @@ function runActiveRegionTests({ tag, phoneUtilLoadedAfterInit }) {
|
||||||
expect(el.activeRegion).to.equal('NL');
|
expect(el.activeRegion).to.equal('NL');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('deducts it from value when modelValue is unparseable', async () => {
|
||||||
|
const modelValue = new Unparseable('+316');
|
||||||
|
const el = await fixture(html` <${tag} .modelValue=${modelValue}></${tag}> `);
|
||||||
|
if (resolvePhoneUtilLoaded) {
|
||||||
|
resolvePhoneUtilLoaded(undefined);
|
||||||
|
await el.updateComplete;
|
||||||
|
}
|
||||||
|
// Region code for country code '31' is 'NL'
|
||||||
|
expect(el.activeRegion).to.equal('NL');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deducts it from value when modelValue is unparseable and contains parentheses', async () => {
|
||||||
|
const modelValue = new Unparseable('(+31)6');
|
||||||
|
const el = await fixture(html` <${tag} .modelValue=${modelValue}></${tag}> `);
|
||||||
|
if (resolvePhoneUtilLoaded) {
|
||||||
|
resolvePhoneUtilLoaded(undefined);
|
||||||
|
await el.updateComplete;
|
||||||
|
}
|
||||||
|
// Region code for country code '31' is 'NL'
|
||||||
|
expect(el.activeRegion).to.equal('NL');
|
||||||
|
});
|
||||||
|
|
||||||
// 3. **locale**: try to get the region from locale (`html[lang]` attribute)
|
// 3. **locale**: try to get the region from locale (`html[lang]` attribute)
|
||||||
it('automatically bases it on current locale when nothing preconfigured', async () => {
|
it('automatically bases it on current locale when nothing preconfigured', async () => {
|
||||||
const el = await fixture(html` <${tag}></${tag}> `);
|
const el = await fixture(html` <${tag}></${tag}> `);
|
||||||
|
|
@ -270,6 +293,16 @@ export function runInputTelSuite({ klass = LionInputTel } = {}) {
|
||||||
await aTimeout(0);
|
await aTimeout(0);
|
||||||
expect(el.formattedValue).to.equal('612345678');
|
expect(el.formattedValue).to.equal('612345678');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('formats according to formatCountryCodeStyle', async () => {
|
||||||
|
const el = await fixture(
|
||||||
|
html` <${tag} format-country-code-style="parentheses" .modelValue="${'+31612345678'}" .allowedRegions="${[
|
||||||
|
'NL',
|
||||||
|
]}"></${tag}> `,
|
||||||
|
);
|
||||||
|
await aTimeout(0);
|
||||||
|
expect(el.formattedValue).to.equal('(+31) 6 12345678');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: this should be allowed for in FormatMixin =>
|
// TODO: this should be allowed for in FormatMixin =>
|
||||||
|
|
|
||||||
|
|
@ -25,4 +25,42 @@ describe('formatPhoneNumber', () => {
|
||||||
formatPhoneNumber('+46707123456', { regionCode: 'SE', formatStrategy: 'significant' }),
|
formatPhoneNumber('+46707123456', { regionCode: 'SE', formatStrategy: 'significant' }),
|
||||||
).to.equal('707123456');
|
).to.equal('707123456');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('formats a phone number according to provided formatCountryCodeStyle', () => {
|
||||||
|
expect(
|
||||||
|
formatPhoneNumber('0707123456', {
|
||||||
|
regionCode: 'SE',
|
||||||
|
formatStrategy: 'e164',
|
||||||
|
formatCountryCodeStyle: 'parentheses',
|
||||||
|
}),
|
||||||
|
).to.equal('(+46)707123456');
|
||||||
|
expect(
|
||||||
|
formatPhoneNumber('+46707123456', {
|
||||||
|
regionCode: 'SE',
|
||||||
|
formatStrategy: 'international',
|
||||||
|
formatCountryCodeStyle: 'parentheses',
|
||||||
|
}),
|
||||||
|
).to.equal('(+46) 70 712 34 56');
|
||||||
|
expect(
|
||||||
|
formatPhoneNumber('+46707123456', {
|
||||||
|
regionCode: 'SE',
|
||||||
|
formatStrategy: 'national',
|
||||||
|
formatCountryCodeStyle: 'parentheses',
|
||||||
|
}),
|
||||||
|
).to.equal('070-712 34 56');
|
||||||
|
expect(
|
||||||
|
formatPhoneNumber('+46707123456', {
|
||||||
|
regionCode: 'SE',
|
||||||
|
formatStrategy: 'rfc3966',
|
||||||
|
formatCountryCodeStyle: 'parentheses',
|
||||||
|
}),
|
||||||
|
).to.equal('tel:(+46)-70-712-34-56');
|
||||||
|
expect(
|
||||||
|
formatPhoneNumber('+46707123456', {
|
||||||
|
regionCode: 'SE',
|
||||||
|
formatStrategy: 'significant',
|
||||||
|
formatCountryCodeStyle: 'parentheses',
|
||||||
|
}),
|
||||||
|
).to.equal('707123456');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -13,4 +13,12 @@ describe('parsePhoneNumber', () => {
|
||||||
expect(parsePhoneNumber('0707123456', { regionCode: 'NL' })).to.equal('+31707123456');
|
expect(parsePhoneNumber('0707123456', { regionCode: 'NL' })).to.equal('+31707123456');
|
||||||
expect(parsePhoneNumber('0707123456', { regionCode: 'DE' })).to.equal('+49707123456');
|
expect(parsePhoneNumber('0707123456', { regionCode: 'DE' })).to.equal('+49707123456');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('removes unwanted characters', () => {
|
||||||
|
expect(parsePhoneNumber('(+31)707123456', { regionCode: 'NL' })).to.equal('+31707123456');
|
||||||
|
expect(parsePhoneNumber('+31 70 7123456', { regionCode: 'NL' })).to.equal('+31707123456');
|
||||||
|
expect(parsePhoneNumber('+31-70-7123456', { regionCode: 'NL' })).to.equal('+31707123456');
|
||||||
|
expect(parsePhoneNumber('+31|70|7123456', { regionCode: 'NL' })).to.equal('+31707123456');
|
||||||
|
expect(parsePhoneNumber('tel:+31707123456', { regionCode: 'NL' })).to.equal('+31707123456');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ describe('liveFormatPhoneNumber', () => {
|
||||||
prevViewValue: '+36123',
|
prevViewValue: '+36123',
|
||||||
currentCaretIndex: 2,
|
currentCaretIndex: 2,
|
||||||
}),
|
}),
|
||||||
).to.eql({ viewValue: '+31 6 123', caretIndex: 4 });
|
).to.eql({ caretIndex: 4, viewValue: '+31 6 123' });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('live formats a complete view value', () => {
|
it('live formats a complete view value', () => {
|
||||||
|
|
@ -29,4 +29,54 @@ describe('liveFormatPhoneNumber', () => {
|
||||||
}),
|
}),
|
||||||
).to.eql({ caretIndex: 12, viewValue: '+31 6 12345678' });
|
).to.eql({ caretIndex: 12, viewValue: '+31 6 12345678' });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('with formatCountryCodeStyle is set to parantheses', () => {
|
||||||
|
it('live formats an incomplete view value', () => {
|
||||||
|
expect(
|
||||||
|
liveFormatPhoneNumber('+316123', {
|
||||||
|
regionCode: 'NL',
|
||||||
|
formatStrategy: 'international',
|
||||||
|
prevViewValue: '+31613',
|
||||||
|
currentCaretIndex: 5,
|
||||||
|
formatCountryCodeStyle: 'parentheses',
|
||||||
|
}),
|
||||||
|
).to.eql({ caretIndex: 9, viewValue: '(+31) 6 123' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('live formats a complete view value', () => {
|
||||||
|
expect(
|
||||||
|
liveFormatPhoneNumber('+31612345678', {
|
||||||
|
regionCode: 'NL',
|
||||||
|
formatStrategy: 'international',
|
||||||
|
prevViewValue: '+3161234578',
|
||||||
|
currentCaretIndex: 10,
|
||||||
|
formatCountryCodeStyle: 'parentheses',
|
||||||
|
}),
|
||||||
|
).to.eql({ caretIndex: 14, viewValue: '(+31) 6 12345678' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not update if parentheses are already in place', () => {
|
||||||
|
expect(
|
||||||
|
liveFormatPhoneNumber('(+31)6123', {
|
||||||
|
regionCode: 'NL',
|
||||||
|
formatStrategy: 'international',
|
||||||
|
prevViewValue: '(+31)123',
|
||||||
|
currentCaretIndex: 5,
|
||||||
|
formatCountryCodeStyle: 'parentheses',
|
||||||
|
}),
|
||||||
|
).to.eql({ caretIndex: 5, viewValue: '(+31)6123' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets the correct caretIndex if currentCaretIndex in between the countryCode', () => {
|
||||||
|
expect(
|
||||||
|
liveFormatPhoneNumber('+316123', {
|
||||||
|
regionCode: 'NL',
|
||||||
|
formatStrategy: 'international',
|
||||||
|
prevViewValue: '+36123',
|
||||||
|
currentCaretIndex: 2,
|
||||||
|
formatCountryCodeStyle: 'parentheses',
|
||||||
|
}),
|
||||||
|
).to.eql({ caretIndex: 4, viewValue: '(+31) 6 123' });
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue