feat(input-tel-dropdown): new component LionInputTelDropdown
Co-authored-by: David Vossen<David.Vossen@ing.com>
This commit is contained in:
parent
a882c94f11
commit
32b322c37e
17 changed files with 1034 additions and 0 deletions
5
.changeset/big-geese-run.md
Normal file
5
.changeset/big-geese-run.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'@lion/input-tel-dropdown': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
New component LionInpuTelDropdown
|
||||||
28
docs/components/inputs/input-tel-dropdown/examples.md
Normal file
28
docs/components/inputs/input-tel-dropdown/examples.md
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
# Inputs >> Input Tel Dropdown >> Examples ||30
|
||||||
|
|
||||||
|
```js script
|
||||||
|
import { html } from '@mdjs/mdjs-preview';
|
||||||
|
import '@lion/select-rich/define';
|
||||||
|
import './src/intl-input-tel-dropdown.js';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Input Tel International
|
||||||
|
|
||||||
|
A visually advanced Subclasser implementation of `LionInputTelDropdown`.
|
||||||
|
|
||||||
|
Inspired by:
|
||||||
|
|
||||||
|
- [intl-tel-input](https://intl-tel-input.com/)
|
||||||
|
- [react-phone-input-2](https://github.com/bl00mber/react-phone-input-2)
|
||||||
|
|
||||||
|
```js preview-story
|
||||||
|
export const IntlInputTelDropdown = () => html`
|
||||||
|
<intl-input-tel-dropdown
|
||||||
|
.preferredRegions="${['NL', 'PH']}"
|
||||||
|
.modelValue=${'+639608920056'}
|
||||||
|
label="Telephone number"
|
||||||
|
help-text="Advanced dropdown and styling"
|
||||||
|
name="phoneNumber"
|
||||||
|
></intl-input-tel-dropdown>
|
||||||
|
`;
|
||||||
|
```
|
||||||
72
docs/components/inputs/input-tel-dropdown/features.md
Normal file
72
docs/components/inputs/input-tel-dropdown/features.md
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
# Inputs >> Input Tel Dropdown >> Features ||20
|
||||||
|
|
||||||
|
```js script
|
||||||
|
import { html } from '@mdjs/mdjs-preview';
|
||||||
|
import { ref, createRef } from '@lion/core';
|
||||||
|
import { loadDefaultFeedbackMessages } from '@lion/validate-messages';
|
||||||
|
import { PhoneUtilManager } from '@lion/input-tel';
|
||||||
|
import '@lion/input-tel-dropdown/define';
|
||||||
|
import '../../../docs/systems/form/assets/h-output.js';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Input Tel Dropdown
|
||||||
|
|
||||||
|
When `.allowedRegions` is not configured, all regions/countries will be available in the dropdown
|
||||||
|
list. Once a region is chosen, its country/dial code will be adjusted with that of the new locale.
|
||||||
|
|
||||||
|
```js preview-story
|
||||||
|
export const InputTelDropdown = () => html`
|
||||||
|
<lion-input-tel-dropdown
|
||||||
|
label="Select region via dropdown"
|
||||||
|
help-text="Shows all regions by default"
|
||||||
|
name="phoneNumber"
|
||||||
|
></lion-input-tel-dropdown>
|
||||||
|
<h-output
|
||||||
|
.show="${['modelValue', 'activeRegion']}"
|
||||||
|
.readyPromise="${PhoneUtilManager.loadComplete}"
|
||||||
|
></h-output>
|
||||||
|
`;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Allowed regions
|
||||||
|
|
||||||
|
When `.allowedRegions` is configured, only those regions/countries will be available in the dropdown
|
||||||
|
list.
|
||||||
|
|
||||||
|
```js preview-story
|
||||||
|
export const allowedRegions = () => html`
|
||||||
|
<lion-input-tel-dropdown
|
||||||
|
label="Select region via dropdown"
|
||||||
|
help-text="With region code 'NL'"
|
||||||
|
.modelValue=${'+31612345678'}
|
||||||
|
name="phoneNumber"
|
||||||
|
.allowedRegions=${['NL', 'DE', 'GB']}
|
||||||
|
></lion-input-tel-dropdown>
|
||||||
|
<h-output
|
||||||
|
.show="${['modelValue', 'activeRegion']}"
|
||||||
|
.readyPromise="${PhoneUtilManager.loadComplete}"
|
||||||
|
></h-output>
|
||||||
|
`;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Preferred regions
|
||||||
|
|
||||||
|
When `.preferredRegions` is configured, they will show up on top of the dropdown list to
|
||||||
|
enhance user experience.
|
||||||
|
|
||||||
|
```js preview-story
|
||||||
|
export const preferredRegionCodes = () => html`
|
||||||
|
<lion-input-tel-dropdown
|
||||||
|
label="Select region via dropdown"
|
||||||
|
help-text="Preferred regions show on top"
|
||||||
|
.modelValue=${'+31612345678'}
|
||||||
|
name="phoneNumber"
|
||||||
|
.allowedRegions=${['NL', 'DE', 'GB', 'BE', 'US', 'CA']}
|
||||||
|
.preferredRegions=${['NL', 'DE']}
|
||||||
|
></lion-input-tel-dropdown>
|
||||||
|
<h-output
|
||||||
|
.show="${['modelValue', 'activeRegion']}"
|
||||||
|
.readyPromise="${PhoneUtilManager.loadComplete}"
|
||||||
|
></h-output>
|
||||||
|
`;
|
||||||
|
```
|
||||||
3
docs/components/inputs/input-tel-dropdown/index.md
Normal file
3
docs/components/inputs/input-tel-dropdown/index.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Inputs >> Input Tel Dropdown ||20
|
||||||
|
|
||||||
|
-> go to Overview
|
||||||
36
docs/components/inputs/input-tel-dropdown/overview.md
Normal file
36
docs/components/inputs/input-tel-dropdown/overview.md
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
# Inputs >> Input Tel Dropdown >> Overview ||10
|
||||||
|
|
||||||
|
Extension of Input Tel that prefixes a dropdown list that shows all possible regions / countries.
|
||||||
|
|
||||||
|
```js script
|
||||||
|
import { html } from '@mdjs/mdjs-preview';
|
||||||
|
import '@lion/input-tel-dropdown/define';
|
||||||
|
```
|
||||||
|
|
||||||
|
```js preview-story
|
||||||
|
export const main = () => {
|
||||||
|
return html`
|
||||||
|
<lion-input-tel-dropdown label="Telephone number" name="phoneNumber"></lion-input-tel-dropdown>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Extends our [input-tel](../input-tel/overview.md)
|
||||||
|
- Shows dropdown list with all possible regions
|
||||||
|
- Shows only allowed regions in dropdown list when .allowedRegions is configured
|
||||||
|
- Highlights regions on top of dropdown list when .preferredRegions is configured
|
||||||
|
- Generates template meta data for advanced
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm i --save @lion/input-tel-dropdown
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { LionInputTelDropdown } from '@lion/input-tel-dropdown';
|
||||||
|
// or
|
||||||
|
import '@lion/input-tel-dropdown/define';
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,124 @@
|
||||||
|
import { html, css, ScopedElementsMixin, ref, repeat } from '@lion/core';
|
||||||
|
import { LionInputTelDropdown } from '@lion/input-tel-dropdown';
|
||||||
|
import {
|
||||||
|
IntlSelectRich,
|
||||||
|
IntlOption,
|
||||||
|
IntlSeparator,
|
||||||
|
} from '../../select-rich/src/intl-select-rich.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('@lion/input-tel-dropdown/types').TemplateDataForDropdownInputTel}TemplateDataForDropdownInputTel
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Example implementation for https://intl-tel-input.com/
|
||||||
|
export class IntlInputTelDropdown extends ScopedElementsMixin(LionInputTelDropdown) {
|
||||||
|
/**
|
||||||
|
* @configure LitElement
|
||||||
|
* @enhance LionInputTelDropdown
|
||||||
|
*/
|
||||||
|
static styles = [
|
||||||
|
super.styles,
|
||||||
|
css`
|
||||||
|
:host,
|
||||||
|
::slotted(*) {
|
||||||
|
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.42857143;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host {
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group__container {
|
||||||
|
width: 100%;
|
||||||
|
height: 34px;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.42857143;
|
||||||
|
color: #555;
|
||||||
|
background-color: #fff;
|
||||||
|
background-image: none;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
-webkit-box-shadow: inset 0 1px 1px rgb(0 0 0 / 8%);
|
||||||
|
box-shadow: inset 0 1px 1px rgb(0 0 0 / 8%);
|
||||||
|
-webkit-transition: border-color ease-in-out 0.15s, -webkit-box-shadow ease-in-out 0.15s;
|
||||||
|
-o-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
|
||||||
|
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group__input {
|
||||||
|
padding: 6px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group__input ::slotted(input) {
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([focused]) .input-group__container {
|
||||||
|
border-color: #66afe9;
|
||||||
|
outline: 0;
|
||||||
|
-webkit-box-shadow: inset 0 1px 1px rgb(0 0 0 / 8%), 0 0 8px rgb(102 175 233 / 60%);
|
||||||
|
box-shadow: inset 0 1px 1px rgb(0 0 0 / 8%), 0 0 8px rgb(102 175 233 / 60%);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
|
||||||
|
static templates = {
|
||||||
|
...(super.templates || {}),
|
||||||
|
/**
|
||||||
|
* @param {TemplateDataForDropdownInputTel} templateDataForDropdown
|
||||||
|
*/
|
||||||
|
dropdown: templateDataForDropdown => {
|
||||||
|
const { refs, data } = templateDataForDropdown;
|
||||||
|
// TODO: once spread directive available, use it per ref (like ref(refs?.dropdown?.ref))
|
||||||
|
return html`
|
||||||
|
<intl-select-rich
|
||||||
|
${ref(refs?.dropdown?.ref)}
|
||||||
|
label="${refs?.dropdown?.labels?.country}"
|
||||||
|
label-sr-only
|
||||||
|
@model-value-changed="${refs?.dropdown?.listeners['model-value-changed']}"
|
||||||
|
style="${refs?.dropdown?.props?.style}"
|
||||||
|
>
|
||||||
|
${data?.regionMetaListPreferred?.length
|
||||||
|
? html` ${repeat(
|
||||||
|
data.regionMetaListPreferred,
|
||||||
|
regionMeta => regionMeta.regionCode,
|
||||||
|
regionMeta =>
|
||||||
|
html`${this.templates.dropdownOption(templateDataForDropdown, regionMeta)} `,
|
||||||
|
)}<intl-separator></intl-separator>`
|
||||||
|
: ''}
|
||||||
|
${repeat(
|
||||||
|
data.regionMetaList,
|
||||||
|
regionMeta => regionMeta.regionCode,
|
||||||
|
regionMeta =>
|
||||||
|
html`${this.templates.dropdownOption(templateDataForDropdown, regionMeta)} `,
|
||||||
|
)}
|
||||||
|
</intl-select-rich>
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @param {TemplateDataForDropdownInputTel} templateDataForDropdown
|
||||||
|
* @param {RegionMeta} regionMeta
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line class-methods-use-this
|
||||||
|
dropdownOption: (templateDataForDropdown, regionMeta) => html`
|
||||||
|
<intl-option .choiceValue="${regionMeta.regionCode}" .regionMeta="${regionMeta}">
|
||||||
|
</intl-option>
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @configure ScopedElementsMixin
|
||||||
|
*/
|
||||||
|
static scopedElements = {
|
||||||
|
...super.scopedElements,
|
||||||
|
'intl-select-rich': IntlSelectRich,
|
||||||
|
'intl-option': IntlOption,
|
||||||
|
'intl-separator': IntlSeparator,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
customElements.define('intl-input-tel-dropdown', IntlInputTelDropdown);
|
||||||
3
packages/input-tel-dropdown/README.md
Normal file
3
packages/input-tel-dropdown/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Lion Input Tel
|
||||||
|
|
||||||
|
[=> See Source <=](../../docs/components/inputs/input-tel/overview.md)
|
||||||
1
packages/input-tel-dropdown/define.js
Normal file
1
packages/input-tel-dropdown/define.js
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
import './lion-input-tel-dropdown.js';
|
||||||
3
packages/input-tel-dropdown/docs/features.md
Normal file
3
packages/input-tel-dropdown/docs/features.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Lion Input Tel Dropdown Features
|
||||||
|
|
||||||
|
[=> See Source <=](../../../docs/components/inputs/input-tel-dropdown/features.md)
|
||||||
3
packages/input-tel-dropdown/docs/overview.md
Normal file
3
packages/input-tel-dropdown/docs/overview.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Lion Input Tel Dropdown Overview
|
||||||
|
|
||||||
|
[=> See Source <=](../../../docs/components/inputs/input-tel-dropdown/overview.md)
|
||||||
1
packages/input-tel-dropdown/index.js
Normal file
1
packages/input-tel-dropdown/index.js
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export { LionInputTelDropdown } from './src/LionInputTelDropdown.js';
|
||||||
3
packages/input-tel-dropdown/lion-input-tel-dropdown.js
Normal file
3
packages/input-tel-dropdown/lion-input-tel-dropdown.js
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
import { LionInputTelDropdown } from './src/LionInputTelDropdown.js';
|
||||||
|
|
||||||
|
customElements.define('lion-input-tel-dropdown', LionInputTelDropdown);
|
||||||
58
packages/input-tel-dropdown/package.json
Normal file
58
packages/input-tel-dropdown/package.json
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
{
|
||||||
|
"name": "@lion/input-tel-dropdown",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"description": "Input field for entering phone numbers with the help of a dropdown region list",
|
||||||
|
"license": "MIT",
|
||||||
|
"author": "ing-bank",
|
||||||
|
"homepage": "https://github.com/ing-bank/lion/",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/ing-bank/lion.git",
|
||||||
|
"directory": "packages/input-tel-dropdown"
|
||||||
|
},
|
||||||
|
"main": "index.js",
|
||||||
|
"module": "index.js",
|
||||||
|
"files": [
|
||||||
|
"*.d.ts",
|
||||||
|
"*.js",
|
||||||
|
"custom-elements.json",
|
||||||
|
"docs",
|
||||||
|
"src",
|
||||||
|
"test",
|
||||||
|
"types"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"custom-elements-manifest": "custom-elements-manifest analyze --litelement --exclude \"docs/**/*\" \"test-helpers/**/*\"",
|
||||||
|
"debug": "cd ../../ && npm run debug -- --group input-tel-dropdown",
|
||||||
|
"debug:firefox": "cd ../../ && npm run debug:firefox -- --group input-tel-dropdown",
|
||||||
|
"debug:webkit": "cd ../../ && npm run debug:webkit -- --group input-tel-dropdown",
|
||||||
|
"publish-docs": "node ../../packages-node/publish-docs/src/cli.js --github-url https://github.com/ing-bank/lion/ --git-root-dir ../../",
|
||||||
|
"prepublishOnly": "npm run publish-docs && npm run custom-elements-manifest",
|
||||||
|
"test": "cd ../../ && npm run test:browser -- --group input-tel-dropdown"
|
||||||
|
},
|
||||||
|
"sideEffects": [
|
||||||
|
"lion-input-tel-dropdown.js"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@lion/core": "0.21.1",
|
||||||
|
"@lion/input-tel": "0.0.0",
|
||||||
|
"@lion/localize": "0.23.0"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"input",
|
||||||
|
"input-tel-dropdown",
|
||||||
|
"lion",
|
||||||
|
"web-components"
|
||||||
|
],
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"customElements": "custom-elements.json",
|
||||||
|
"customElementsManifest": "custom-elements.json",
|
||||||
|
"exports": {
|
||||||
|
".": "./index.js",
|
||||||
|
"./define": "./define.js",
|
||||||
|
"./test-suites": "./test-suites/index.js",
|
||||||
|
"./docs/*": "./docs/*"
|
||||||
|
}
|
||||||
|
}
|
||||||
375
packages/input-tel-dropdown/src/LionInputTelDropdown.js
Normal file
375
packages/input-tel-dropdown/src/LionInputTelDropdown.js
Normal file
|
|
@ -0,0 +1,375 @@
|
||||||
|
// @ts-expect-error ref, createRef are exported (?)
|
||||||
|
import { render, html, css, ref, createRef } from '@lion/core';
|
||||||
|
import { LionInputTel } from '@lion/input-tel';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: one could consider to implement LionInputTelDropdown as a
|
||||||
|
* [combobox](https://www.w3.org/TR/wai-aria-practices-1.2/#combobox).
|
||||||
|
* However, the country dropdown does not directly set the textbox value, it only determines
|
||||||
|
* its region code. Therefore it does not comply to this criterium:
|
||||||
|
* "A combobox is an input widget with an associated popup that enables users to select a value for
|
||||||
|
* the combobox from a collection of possible values. In some implementations,
|
||||||
|
* the popup presents allowed values, while in other implementations, the popup presents suggested
|
||||||
|
* values, and users may either select one of the suggestions or type a value".
|
||||||
|
* We therefore decided to consider the dropdown a helper mechanism that does not set, but
|
||||||
|
* contributes to and helps format and validate the actual value.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('lit/directives/ref.js').Ref} Ref
|
||||||
|
* @typedef {import('@lion/core').RenderOptions} RenderOptions
|
||||||
|
* @typedef {import('@lion/form-core/types/FormatMixinTypes').FormatHost} FormatHost
|
||||||
|
* @typedef {import('@lion/input-tel/types').FormatStrategy} FormatStrategy
|
||||||
|
* @typedef {import('@lion/input-tel/types').RegionCode} RegionCode
|
||||||
|
* @typedef {import('../types').TemplateDataForDropdownInputTel} TemplateDataForDropdownInputTel
|
||||||
|
* @typedef {import('../types').OnDropdownChangeEvent} OnDropdownChangeEvent
|
||||||
|
* @typedef {import('../types').DropdownRef} DropdownRef
|
||||||
|
* @typedef {import('../types').RegionMeta} RegionMeta
|
||||||
|
* @typedef {* & import('@lion/input-tel/lib/awesome-phonenumber-esm').default} PhoneNumber
|
||||||
|
* @typedef {import('@lion/select-rich').LionSelectRich} LionSelectRich
|
||||||
|
* @typedef {import('@lion/overlays').OverlayController} OverlayController
|
||||||
|
* @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
|
||||||
|
* prefix slot. This could be a LionSelect, a LionSelectRich or a native select.
|
||||||
|
* By default, the native `<select>` element is used for this, so that it's as lightweight as
|
||||||
|
* possible. Also, it doesn't need to be a `FormControl`, because it's purely a helper element
|
||||||
|
* to provide better UX: the modelValue (the text field) contains all needed info, since it's in
|
||||||
|
* `e164` format that contains all info (both region code and national phone number).
|
||||||
|
*/
|
||||||
|
export class LionInputTelDropdown extends LionInputTel {
|
||||||
|
/**
|
||||||
|
* @configure LitElement
|
||||||
|
* @type {any}
|
||||||
|
*/
|
||||||
|
static properties = { preferredRegions: { type: Array } };
|
||||||
|
|
||||||
|
refs = {
|
||||||
|
/** @type {DropdownRef} */
|
||||||
|
dropdown: /** @type {DropdownRef} */ (createRef()),
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method provides a TemplateData object to be fed to pure template functions, a.k.a.
|
||||||
|
* Pure Templates™. The goal is to totally decouple presentation from logic here, so that
|
||||||
|
* Subclassers can override all content without having to loose private info contained
|
||||||
|
* within the template function that was overridden.
|
||||||
|
*
|
||||||
|
* Subclassers would need to make sure all the contents of the TemplateData object are implemented
|
||||||
|
* by making sure they are coupled to the right 'ref' ([data-ref=dropdown] in this example),
|
||||||
|
* with the help of lit's spread operator directive.
|
||||||
|
* To enhance this process, the TemplateData object is completely typed. Ideally, this would be
|
||||||
|
* enhanced by providing linters that make sure all of their required members are implemented by
|
||||||
|
* a Subclasser.
|
||||||
|
* When a Subclasser wants to add more data, this can be done via:
|
||||||
|
* @example
|
||||||
|
* ```js
|
||||||
|
* get _templateDataDropdown() {
|
||||||
|
* return {
|
||||||
|
* ...super._templateDataDropdown,
|
||||||
|
* myExtraData: { x: 1, y: 2 },
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
* @overridable
|
||||||
|
* @type {TemplateDataForDropdownInputTel}
|
||||||
|
*/
|
||||||
|
get _templateDataDropdown() {
|
||||||
|
const refs = {
|
||||||
|
dropdown: {
|
||||||
|
ref: this.refs.dropdown,
|
||||||
|
props: {
|
||||||
|
style: `height: 100%;`,
|
||||||
|
},
|
||||||
|
listeners: {
|
||||||
|
change: this._onDropdownValueChange,
|
||||||
|
'model-value-changed': this._onDropdownValueChange,
|
||||||
|
},
|
||||||
|
labels: {
|
||||||
|
// TODO: localize this
|
||||||
|
selectCountry: 'Select country',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
refs,
|
||||||
|
data: {
|
||||||
|
activeRegion: this.activeRegion,
|
||||||
|
regionMetaList: this.__regionMetaList,
|
||||||
|
regionMetaListPreferred: this.__regionMetaListPreferred,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static templates = {
|
||||||
|
dropdown: (/** @type {TemplateDataForDropdownInputTel} */ templateDataForDropdown) => {
|
||||||
|
const { refs, data } = templateDataForDropdown;
|
||||||
|
const renderOption = (/** @type {RegionMeta} */ regionMeta) =>
|
||||||
|
html`${this.templates.dropdownOption(templateDataForDropdown, regionMeta)} `;
|
||||||
|
|
||||||
|
// TODO: once spread directive available, use it per ref
|
||||||
|
return html`
|
||||||
|
<select
|
||||||
|
${ref(refs?.dropdown?.ref)}
|
||||||
|
aria-label="${refs?.dropdown?.labels?.selectCountry}"
|
||||||
|
@change="${refs?.dropdown?.listeners?.change}"
|
||||||
|
style="${refs?.dropdown?.props?.style}"
|
||||||
|
>
|
||||||
|
${data?.regionMetaListPreferred?.length
|
||||||
|
? html`
|
||||||
|
${data.regionMetaListPreferred.map(renderOption)}
|
||||||
|
<option disabled>---------------</option>
|
||||||
|
${data?.regionMetaList?.map(renderOption)}
|
||||||
|
`
|
||||||
|
: html` ${data?.regionMetaList?.map(renderOption)}`}
|
||||||
|
</select>
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @param {TemplateDataForDropdownInputTel} templateDataForDropdown
|
||||||
|
* @param {RegionMeta} contextData
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line class-methods-use-this
|
||||||
|
dropdownOption: (templateDataForDropdown, { regionCode, countryCode, flagSymbol }) => html`
|
||||||
|
<option value="${regionCode}">${regionCode} (+${countryCode}) ${flagSymbol}</option>
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @configure LitElement
|
||||||
|
* @enhance LionInputTel
|
||||||
|
*/
|
||||||
|
static styles = [
|
||||||
|
super.styles,
|
||||||
|
css`
|
||||||
|
/**
|
||||||
|
* We need to align the height of the dropdown with the height of the text field.
|
||||||
|
* We target the HTMLDivElement 'this.__dropdownRenderParent' here. Its child,
|
||||||
|
* [data-ref=dropdown], recieves a 100% height as well via inline styles (since we
|
||||||
|
* can't target from shadow styles).
|
||||||
|
*/
|
||||||
|
::slotted([slot='prefix']) {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @configure SlotMixin
|
||||||
|
*/
|
||||||
|
get slots() {
|
||||||
|
return {
|
||||||
|
...super.slots,
|
||||||
|
prefix: () => this.__dropdownRenderParent,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @configure LocalizeMixin
|
||||||
|
*/
|
||||||
|
onLocaleUpdated() {
|
||||||
|
super.onLocaleUpdated();
|
||||||
|
|
||||||
|
// @ts-expect-error relatively new platform api
|
||||||
|
this.__namesForLocale = new Intl.DisplayNames([this._langIso], {
|
||||||
|
type: 'region',
|
||||||
|
});
|
||||||
|
this.__createRegionMeta();
|
||||||
|
this._scheduleLightDomRender();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @enhance LionInputTel
|
||||||
|
*/
|
||||||
|
_onPhoneNumberUtilReady() {
|
||||||
|
super._onPhoneNumberUtilReady();
|
||||||
|
this.__createRegionMeta();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @lifecycle platform
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regions that will be shown on top of the dropdown
|
||||||
|
* @type {string[]}
|
||||||
|
*/
|
||||||
|
this.preferredRegions = [];
|
||||||
|
|
||||||
|
/** @type {HTMLDivElement} */
|
||||||
|
this.__dropdownRenderParent = document.createElement('div');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains everything needed for rendering region options:
|
||||||
|
* region code, country code, display name according to locale, display name
|
||||||
|
* @type {RegionMeta[]}
|
||||||
|
*/
|
||||||
|
this.__regionMetaList = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A filtered `this.__regionMetaList`, containing all regions provided in `preferredRegions`
|
||||||
|
* @type {RegionMeta[]}
|
||||||
|
*/
|
||||||
|
this.__regionMetaListPreferred = [];
|
||||||
|
|
||||||
|
/** @type {EventListener} */
|
||||||
|
this._onDropdownValueChange = this._onDropdownValueChange.bind(this);
|
||||||
|
/** @type {EventListener} */
|
||||||
|
this.__syncRegionWithDropdown = this.__syncRegionWithDropdown.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @lifecycle LitElement
|
||||||
|
* @param {import('lit-element').PropertyValues } changedProperties
|
||||||
|
*/
|
||||||
|
willUpdate(changedProperties) {
|
||||||
|
super.willUpdate(changedProperties);
|
||||||
|
|
||||||
|
if (changedProperties.has('allowedRegions')) {
|
||||||
|
this.__createRegionMeta();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('lit-element').PropertyValues } changedProperties
|
||||||
|
*/
|
||||||
|
updated(changedProperties) {
|
||||||
|
super.updated(changedProperties);
|
||||||
|
|
||||||
|
if (changedProperties.has('_needsLightDomRender')) {
|
||||||
|
this.__renderDropdown();
|
||||||
|
}
|
||||||
|
if (changedProperties.has('activeRegion')) {
|
||||||
|
this.__syncRegionWithDropdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @protected
|
||||||
|
* @param {OnDropdownChangeEvent} event
|
||||||
|
*/
|
||||||
|
_onDropdownValueChange(event) {
|
||||||
|
const isInitializing = event.detail?.initialize || !this._phoneUtil;
|
||||||
|
if (isInitializing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const prevActiveRegion = this.activeRegion;
|
||||||
|
this._setActiveRegion(
|
||||||
|
/** @type {RegionCode} */ (event.target.value || event.target.modelValue),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Change region code in text box
|
||||||
|
// From: https://bl00mber.github.io/react-phone-input-2.html
|
||||||
|
if (prevActiveRegion !== this.activeRegion && !this.focused && this._phoneUtil) {
|
||||||
|
const prevCountryCode = this._phoneUtil.getCountryCodeForRegionCode(prevActiveRegion);
|
||||||
|
const countryCode = this._phoneUtil.getCountryCodeForRegionCode(this.activeRegion);
|
||||||
|
if (countryCode && !this.modelValue) {
|
||||||
|
// When textbox is empty, prefill it with country code
|
||||||
|
this.modelValue = `+${countryCode}`;
|
||||||
|
} else if (prevCountryCode && countryCode) {
|
||||||
|
// When textbox is not empty, replace country code
|
||||||
|
this.modelValue = this._callParser(
|
||||||
|
this.value.replace(`+${prevCountryCode}`, `+${countryCode}`),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put focus on text box
|
||||||
|
const overlayController = event.target._overlayCtrl;
|
||||||
|
if (overlayController?.isShown) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this._inputNode.focus();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// For native select
|
||||||
|
this._inputNode.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract away rendering to light dom, so that we can rerender when needed
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
__renderDropdown() {
|
||||||
|
const ctor = /** @type {typeof LionInputTelDropdown} */ (this.constructor);
|
||||||
|
// If the user locally overrode the templates, get those on the instance
|
||||||
|
const templates = this.templates || ctor.templates;
|
||||||
|
render(
|
||||||
|
templates.dropdown(this._templateDataDropdown),
|
||||||
|
this.__dropdownRenderParent,
|
||||||
|
/** @type {RenderOptions} */ ({
|
||||||
|
scopeName: this.localName,
|
||||||
|
eventContext: this,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
this.__syncRegionWithDropdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
__syncRegionWithDropdown(regionCode = this.activeRegion) {
|
||||||
|
const dropdownElement = this.refs.dropdown?.value;
|
||||||
|
if (!dropdownElement || !regionCode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ('modelValue' in dropdownElement) {
|
||||||
|
/** @type {* & FormatHost} */ (dropdownElement).modelValue = regionCode;
|
||||||
|
} else {
|
||||||
|
/** @type {HTMLSelectElement} */ (dropdownElement).value = regionCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares data for options, like "Greece (Ελλάδα)", where "Greece" is `nameForLocale` and
|
||||||
|
* "Ελλάδα" `nameForRegion`.
|
||||||
|
* This should be run on change of:
|
||||||
|
* - allowedRegions
|
||||||
|
* - _phoneUtil loaded
|
||||||
|
* - locale
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
__createRegionMeta() {
|
||||||
|
if (!this._allowedOrAllRegions?.length || !this.__namesForLocale) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.__regionMetaList = [];
|
||||||
|
this.__regionMetaListPreferred = [];
|
||||||
|
this._allowedOrAllRegions.forEach(regionCode => {
|
||||||
|
// @ts-expect-error Intl.DisplayNames platform api not yet typed
|
||||||
|
const namesForRegion = new Intl.DisplayNames([regionCode.toLowerCase()], {
|
||||||
|
type: 'region',
|
||||||
|
});
|
||||||
|
const countryCode =
|
||||||
|
this._phoneUtil && this._phoneUtil.getCountryCodeForRegionCode(regionCode);
|
||||||
|
|
||||||
|
const flagSymbol =
|
||||||
|
getRegionalIndicatorSymbol(regionCode[0]) + getRegionalIndicatorSymbol(regionCode[1]);
|
||||||
|
|
||||||
|
const destinationList = this.preferredRegions.includes(regionCode)
|
||||||
|
? this.__regionMetaListPreferred
|
||||||
|
: this.__regionMetaList;
|
||||||
|
|
||||||
|
destinationList.push({
|
||||||
|
regionCode,
|
||||||
|
countryCode,
|
||||||
|
flagSymbol,
|
||||||
|
nameForLocale: this.__namesForLocale.of(regionCode),
|
||||||
|
nameForRegion: namesForRegion.of(regionCode),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,226 @@
|
||||||
|
import {
|
||||||
|
expect,
|
||||||
|
fixture as _fixture,
|
||||||
|
fixtureSync as _fixtureSync,
|
||||||
|
html,
|
||||||
|
defineCE,
|
||||||
|
unsafeStatic,
|
||||||
|
aTimeout,
|
||||||
|
} from '@open-wc/testing';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
// @ts-ignore
|
||||||
|
import { PhoneUtilManager } from '@lion/input-tel';
|
||||||
|
// @ts-ignore
|
||||||
|
import { mockPhoneUtilManager, restorePhoneUtilManager } from '@lion/input-tel/test-helpers';
|
||||||
|
import { LionInputTelDropdown } from '../src/LionInputTelDropdown.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('@lion/core').TemplateResult} TemplateResult
|
||||||
|
* @typedef {HTMLSelectElement|HTMLElement & {modelValue:string}} DropdownElement
|
||||||
|
* @typedef {import('../types').TemplateDataForDropdownInputTel} TemplateDataForDropdownInputTel
|
||||||
|
*/
|
||||||
|
|
||||||
|
const fixture = /** @type {(arg: string | TemplateResult) => Promise<LionInputTelDropdown>} */ (
|
||||||
|
_fixture
|
||||||
|
);
|
||||||
|
const fixtureSync = /** @type {(arg: string | TemplateResult) => LionInputTelDropdown} */ (
|
||||||
|
_fixtureSync
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {DropdownElement} dropdownEl
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function getDropdownValue(dropdownEl) {
|
||||||
|
if ('modelValue' in dropdownEl) {
|
||||||
|
return dropdownEl.modelValue;
|
||||||
|
}
|
||||||
|
return dropdownEl.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {DropdownElement} dropdownEl
|
||||||
|
* @param {string} value
|
||||||
|
*/
|
||||||
|
function mimicUserChangingDropdown(dropdownEl, value) {
|
||||||
|
if ('modelValue' in dropdownEl) {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
dropdownEl.modelValue = value;
|
||||||
|
dropdownEl.dispatchEvent(
|
||||||
|
new CustomEvent('model-value-changed', { detail: { isTriggeredByUser: true } }),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
dropdownEl.value = value;
|
||||||
|
dropdownEl.dispatchEvent(new Event('change'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{ klass:LionInputTelDropdown }} config
|
||||||
|
*/
|
||||||
|
// @ts-ignore
|
||||||
|
export function runInputTelDropdownSuite({ klass = LionInputTelDropdown } = {}) {
|
||||||
|
// @ts-ignore
|
||||||
|
const tagName = defineCE(/** @type {* & HTMLElement} */ (class extends klass {}));
|
||||||
|
const tag = unsafeStatic(tagName);
|
||||||
|
|
||||||
|
describe('LionInputTelDropdown', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
// Wait till PhoneUtilManager has been loaded
|
||||||
|
await PhoneUtilManager.loadComplete;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Dropdown display', () => {
|
||||||
|
it('calls `templates.dropdown` with TemplateDataForDropdownInputTel object', async () => {
|
||||||
|
const el = fixtureSync(html` <${tag}
|
||||||
|
.modelValue="${'+31612345678'}"
|
||||||
|
.allowedRegions="${['NL', 'PH']}"
|
||||||
|
.preferredRegions="${['PH']}"
|
||||||
|
></${tag}> `);
|
||||||
|
const spy = sinon.spy(
|
||||||
|
/** @type {typeof LionInputTelDropdown} */ (el.constructor).templates,
|
||||||
|
'dropdown',
|
||||||
|
);
|
||||||
|
await el.updateComplete;
|
||||||
|
const dropdownNode = el.refs.dropdown.value;
|
||||||
|
const templateDataForDropdown = /** @type {TemplateDataForDropdownInputTel} */ (
|
||||||
|
spy.args[0][0]
|
||||||
|
);
|
||||||
|
expect(templateDataForDropdown).to.eql(
|
||||||
|
/** @type {TemplateDataForDropdownInputTel} */ ({
|
||||||
|
data: {
|
||||||
|
activeRegion: 'NL',
|
||||||
|
regionMetaList: [
|
||||||
|
{
|
||||||
|
countryCode: 31,
|
||||||
|
flagSymbol: '🇳🇱',
|
||||||
|
nameForLocale: 'Netherlands',
|
||||||
|
nameForRegion: 'Nederland',
|
||||||
|
regionCode: 'NL',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
regionMetaListPreferred: [
|
||||||
|
{
|
||||||
|
countryCode: 63,
|
||||||
|
flagSymbol: '🇵🇭',
|
||||||
|
nameForLocale: 'Philippines',
|
||||||
|
nameForRegion: 'Philippines',
|
||||||
|
regionCode: 'PH',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
refs: {
|
||||||
|
dropdown: {
|
||||||
|
labels: { selectCountry: 'Select country' },
|
||||||
|
listeners: {
|
||||||
|
// @ts-expect-error [allow-protected]
|
||||||
|
change: el._onDropdownValueChange,
|
||||||
|
// @ts-expect-error [allow-protected]
|
||||||
|
'model-value-changed': el._onDropdownValueChange,
|
||||||
|
},
|
||||||
|
props: { style: 'height: 100%;' },
|
||||||
|
ref: { value: dropdownNode },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('syncs dropdown value initially from activeRegion', async () => {
|
||||||
|
const el = await fixture(html` <${tag} .allowedRegions="${['DE']}"></${tag}> `);
|
||||||
|
expect(getDropdownValue(/** @type {DropdownElement} */ (el.refs.dropdown.value))).to.equal(
|
||||||
|
'DE',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders to prefix slot in light dom', async () => {
|
||||||
|
const el = await fixture(html` <${tag} .allowedRegions="${['DE']}"></${tag}> `);
|
||||||
|
const prefixSlot = /** @type {HTMLElement} */ (
|
||||||
|
/** @type {HTMLElement} */ (el.refs.dropdown.value).parentElement
|
||||||
|
);
|
||||||
|
expect(prefixSlot.getAttribute('slot')).to.equal('prefix');
|
||||||
|
expect(prefixSlot.slot).to.equal('prefix');
|
||||||
|
expect(prefixSlot.parentElement).to.equal(el);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rerenders light dom when PhoneUtil loaded', async () => {
|
||||||
|
const { resolveLoaded } = mockPhoneUtilManager();
|
||||||
|
const el = await fixture(html` <${tag} .allowedRegions="${['DE']}"></${tag}> `);
|
||||||
|
// @ts-ignore
|
||||||
|
const spy = sinon.spy(el, '_scheduleLightDomRender');
|
||||||
|
resolveLoaded(undefined);
|
||||||
|
await aTimeout(0);
|
||||||
|
expect(spy).to.have.been.calledOnce;
|
||||||
|
restorePhoneUtilManager();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('On dropdown value change', () => {
|
||||||
|
it('changes the currently active country code in the textbox', async () => {
|
||||||
|
const el = await fixture(
|
||||||
|
html` <${tag} .allowedRegions="${[
|
||||||
|
'NL',
|
||||||
|
'BE',
|
||||||
|
]}" .modelValue="${'+31612345678'}"></${tag}> `,
|
||||||
|
);
|
||||||
|
// @ts-ignore
|
||||||
|
mimicUserChangingDropdown(el.refs.dropdown.value, 'BE');
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(el.activeRegion).to.equal('BE');
|
||||||
|
expect(el.modelValue).to.equal('+32612345678');
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(el.value).to.equal('+32612345678');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('focuses the textbox right after selection', async () => {
|
||||||
|
const el = await fixture(
|
||||||
|
html` <${tag} .allowedRegions="${[
|
||||||
|
'NL',
|
||||||
|
'BE',
|
||||||
|
]}" .modelValue="${'+31612345678'}"></${tag}> `,
|
||||||
|
);
|
||||||
|
// @ts-ignore
|
||||||
|
mimicUserChangingDropdown(el.refs.dropdown.value, 'BE');
|
||||||
|
await el.updateComplete;
|
||||||
|
// @ts-expect-error
|
||||||
|
expect(el._inputNode).to.equal(document.activeElement);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('prefills country code when text box is empty', async () => {
|
||||||
|
const el = await fixture(html` <${tag} .allowedRegions="${['NL', 'BE']}"></${tag}> `);
|
||||||
|
// @ts-ignore
|
||||||
|
mimicUserChangingDropdown(el.refs.dropdown.value, 'BE');
|
||||||
|
await el.updateComplete;
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(el.value).to.equal('+32');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('On activeRegion change', () => {
|
||||||
|
it('updates dropdown value ', async () => {
|
||||||
|
const el = await fixture(html` <${tag} .modelValue="${'+31612345678'}"></${tag}> `);
|
||||||
|
expect(el.activeRegion).to.equal('NL');
|
||||||
|
// @ts-expect-error [allow protected]
|
||||||
|
el._setActiveRegion('BE');
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(getDropdownValue(/** @type {DropdownElement} */ (el.refs.dropdown.value))).to.equal(
|
||||||
|
'BE',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('When refs.dropdown is a FormControl (LionSelectRich/LionCombobox)', () => {
|
||||||
|
it('updates dropdown value ', async () => {
|
||||||
|
const el = await fixture(html` <${tag} .modelValue="${'+31612345678'}"></${tag}> `);
|
||||||
|
expect(el.activeRegion).to.equal('NL');
|
||||||
|
// @ts-expect-error [allow protected]
|
||||||
|
el._setActiveRegion('BE');
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(getDropdownValue(/** @type {DropdownElement} */ (el.refs.dropdown.value))).to.equal(
|
||||||
|
'BE',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
import { runInputTelSuite } from '@lion/input-tel/test-suites';
|
||||||
|
// @ts-ignore
|
||||||
|
import { ref, repeat, html } from '@lion/core';
|
||||||
|
import '@lion/select-rich/define';
|
||||||
|
import { LionInputTelDropdown } from '../src/LionInputTelDropdown.js';
|
||||||
|
import { runInputTelDropdownSuite } from '../test-suites/LionInputTelDropdown.suite.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('@lion/core').TemplateResult} TemplateResult
|
||||||
|
* @typedef {HTMLSelectElement|HTMLElement & {modelValue:string}} DropdownElement
|
||||||
|
* @typedef {import('../types').TemplateDataForDropdownInputTel} TemplateDataForDropdownInputTel
|
||||||
|
* @typedef {import('../types').RegionMeta} RegionMeta
|
||||||
|
*/
|
||||||
|
|
||||||
|
class WithFormControlInputTelDropdown extends LionInputTelDropdown {
|
||||||
|
static templates = {
|
||||||
|
...(super.templates || {}),
|
||||||
|
/**
|
||||||
|
* @param {TemplateDataForDropdownInputTel} templateDataForDropdown
|
||||||
|
*/
|
||||||
|
dropdown: templateDataForDropdown => {
|
||||||
|
const { refs, data } = templateDataForDropdown;
|
||||||
|
// TODO: once spread directive available, use it per ref (like ref(refs?.dropdown?.ref))
|
||||||
|
return html`
|
||||||
|
<lion-select-rich
|
||||||
|
${ref(refs?.dropdown?.ref)}
|
||||||
|
label="${refs?.dropdown?.labels?.country}"
|
||||||
|
label-sr-only
|
||||||
|
@model-value-changed="${refs?.dropdown?.listeners['model-value-changed']}"
|
||||||
|
style="${refs?.dropdown?.props?.style}"
|
||||||
|
>
|
||||||
|
${repeat(
|
||||||
|
data.regionMetaList,
|
||||||
|
regionMeta => regionMeta.regionCode,
|
||||||
|
regionMeta =>
|
||||||
|
html` <lion-option .choiceValue="${regionMeta.regionCode}"> </lion-option> `,
|
||||||
|
)}
|
||||||
|
</lion-select-rich>
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error
|
||||||
|
runInputTelSuite({ klass: LionInputTelDropdown });
|
||||||
|
runInputTelDropdownSuite();
|
||||||
|
// @ts-expect-error
|
||||||
|
// Runs it for LionSelectRich, which uses .modelValue/@model-value-changed instead of .value/@change
|
||||||
|
runInputTelDropdownSuite({ klass: WithFormControlInputTelDropdown });
|
||||||
44
packages/input-tel-dropdown/types/index.d.ts
vendored
Normal file
44
packages/input-tel-dropdown/types/index.d.ts
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
import { RegionCode } from '@lion/input-tel/types/types';
|
||||||
|
import { LionSelectRich } from '@lion/select-rich';
|
||||||
|
import { LionCombobox } from '@lion/combobox';
|
||||||
|
|
||||||
|
type RefTemplateData = {
|
||||||
|
ref?: { value?: HTMLElement };
|
||||||
|
props?: { [key: string]: any };
|
||||||
|
listeners?: { [key: string]: any };
|
||||||
|
labels?: { [key: string]: any };
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RegionMeta = {
|
||||||
|
countryCode: number;
|
||||||
|
regionCode: RegionCode;
|
||||||
|
nameForRegion: string;
|
||||||
|
nameForLocale: string;
|
||||||
|
flagSymbol: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OnDropdownChangeEvent = Event & {
|
||||||
|
target: { value?: string; modelValue?: string; _overlayCtrl?: OverlayController };
|
||||||
|
detail?: { initialize: boolean };
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DropdownRef = { value: HTMLSelectElement | LionSelectRich | LionCombobox | undefined };
|
||||||
|
|
||||||
|
export type TemplateDataForDropdownInputTel = {
|
||||||
|
refs: {
|
||||||
|
dropdown: RefTemplateData & {
|
||||||
|
ref: DropdownRef;
|
||||||
|
props: { style: string };
|
||||||
|
listeners: {
|
||||||
|
change: (event: OnDropdownChangeEvent) => void;
|
||||||
|
'model-value-changed': (event: OnDropdownChangeEvent) => void;
|
||||||
|
};
|
||||||
|
labels: { selectCountry: string };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
data: {
|
||||||
|
activeRegion: string | undefined;
|
||||||
|
regionMetaList: RegionMeta[];
|
||||||
|
regionMetaListPreferred: RegionMeta[];
|
||||||
|
};
|
||||||
|
};
|
||||||
Loading…
Reference in a new issue