fix(input-tel-dropdown): prevent jumping to input field on each arrow key in windows/linux
Apply suggestions from code review
This commit is contained in:
parent
bf2a2e02ef
commit
cf616e1e6b
6 changed files with 150 additions and 43 deletions
5
.changeset/yellow-steaks-tie.md
Normal file
5
.changeset/yellow-steaks-tie.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'@lion/ui': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
[input-tel-dropdown] prevent jumping to input field on each arrow key in windows/linux
|
||||||
|
|
@ -324,7 +324,10 @@ export class LionInputTelDropdown extends LionInputTel {
|
||||||
*/
|
*/
|
||||||
_onDropdownValueChange(event) {
|
_onDropdownValueChange(event) {
|
||||||
const isInitializing = event.detail?.initialize || !this._phoneUtil;
|
const isInitializing = event.detail?.initialize || !this._phoneUtil;
|
||||||
const dropdownValue = /** @type {RegionCode} */ (event.target.modelValue || event.target.value);
|
const dropdownElement = event.target;
|
||||||
|
const dropdownValue = /** @type {RegionCode} */ (
|
||||||
|
dropdownElement.modelValue || dropdownElement.value
|
||||||
|
);
|
||||||
|
|
||||||
if (isInitializing || this.activeRegion === dropdownValue) {
|
if (isInitializing || this.activeRegion === dropdownValue) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -355,8 +358,13 @@ export class LionInputTelDropdown extends LionInputTel {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put focus on text box
|
// Put focus on text box
|
||||||
const overlayController = event.target._overlayCtrl;
|
//
|
||||||
if (overlayController?.isShown) {
|
// A LionSelectRich with interactionMode set on windows/linux
|
||||||
|
// will set each item on arrow key up/down to activeElement
|
||||||
|
// which causes the focus to jump every time to the inputNode
|
||||||
|
const overlayController = dropdownElement._overlayCtrl;
|
||||||
|
// @ts-ignore interactionMode only exists on LionSelectRich not on HTMLSelectElement
|
||||||
|
if (overlayController?.isShown && dropdownElement.interactionMode !== 'windows/linux') {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this._inputNode.focus();
|
this._inputNode.focus();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
/**
|
||||||
|
* @typedef {HTMLSelectElement|HTMLElement & {modelValue:string}} DropdownElement
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {DropdownElement} dropdownEl
|
||||||
|
* @param {string} value
|
||||||
|
*/
|
||||||
|
export 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'));
|
||||||
|
dropdownEl.dispatchEvent(new Event('input'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,6 +11,7 @@ import {
|
||||||
} from '@open-wc/testing';
|
} from '@open-wc/testing';
|
||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
import { LionInputTelDropdown } from '@lion/ui/input-tel-dropdown.js';
|
import { LionInputTelDropdown } from '@lion/ui/input-tel-dropdown.js';
|
||||||
|
import { mimicUserChangingDropdown } from '@lion/ui/input-tel-dropdown-test-helpers.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('lit').TemplateResult} TemplateResult
|
* @typedef {import('lit').TemplateResult} TemplateResult
|
||||||
|
|
@ -36,24 +37,6 @@ function getDropdownValue(dropdownEl) {
|
||||||
return dropdownEl.value;
|
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
|
* @param {{ klass:LionInputTelDropdown }} config
|
||||||
*/
|
*/
|
||||||
|
|
@ -335,26 +318,6 @@ export function runInputTelDropdownSuite({ klass } = { klass: LionInputTelDropdo
|
||||||
expect(el.value).to.equal('+32');
|
expect(el.value).to.equal('+32');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('focuses the textbox right after selection if selected via opened dropdown', async () => {
|
|
||||||
const el = await fixture(
|
|
||||||
html` <${tag} .allowedRegions="${[
|
|
||||||
'NL',
|
|
||||||
'BE',
|
|
||||||
]}" .modelValue="${'+31612345678'}"></${tag}> `,
|
|
||||||
);
|
|
||||||
const dropdownElement = el.refs.dropdown.value;
|
|
||||||
// @ts-expect-error [allow-protected-in-tests]
|
|
||||||
if (dropdownElement?._overlayCtrl) {
|
|
||||||
// @ts-expect-error [allow-protected-in-tests]
|
|
||||||
dropdownElement._overlayCtrl.show();
|
|
||||||
mimicUserChangingDropdown(dropdownElement, 'BE');
|
|
||||||
await el.updateComplete;
|
|
||||||
await aTimeout(0);
|
|
||||||
// @ts-expect-error [allow-protected-in-tests]
|
|
||||||
expect(el._inputNode).to.equal(document.activeElement);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('keeps focus on dropdownElement after selection if selected via unopened dropdown', async () => {
|
it('keeps focus on dropdownElement after selection if selected via unopened dropdown', async () => {
|
||||||
const el = await fixture(
|
const el = await fixture(
|
||||||
html` <${tag} .allowedRegions="${[
|
html` <${tag} .allowedRegions="${[
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { runInputTelSuite } from '@lion/ui/input-tel-test-suites.js';
|
import { runInputTelSuite } from '@lion/ui/input-tel-test-suites.js';
|
||||||
import { html } from 'lit';
|
|
||||||
import { repeat } from 'lit/directives/repeat.js';
|
import { repeat } from 'lit/directives/repeat.js';
|
||||||
import { ref } from 'lit/directives/ref.js';
|
import { ref } from 'lit/directives/ref.js';
|
||||||
|
import { aTimeout, expect, fixture, html } from '@open-wc/testing';
|
||||||
import { LionInputTelDropdown } from '@lion/ui/input-tel-dropdown.js';
|
import { LionInputTelDropdown } from '@lion/ui/input-tel-dropdown.js';
|
||||||
import { runInputTelDropdownSuite } from '@lion/ui/input-tel-dropdown-test-suites.js';
|
import { runInputTelDropdownSuite } from '@lion/ui/input-tel-dropdown-test-suites.js';
|
||||||
|
import { mimicUserChangingDropdown } from '@lion/ui/input-tel-dropdown-test-helpers.js';
|
||||||
|
|
||||||
import '@lion/ui/define/lion-option.js';
|
import '@lion/ui/define/lion-option.js';
|
||||||
import '@lion/ui/define/lion-select-rich.js';
|
import '@lion/ui/define/lion-select-rich.js';
|
||||||
|
|
@ -53,4 +53,112 @@ describe('WithFormControlInputTelDropdown', () => {
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
// Runs it for LionSelectRich, which uses .modelValue/@model-value-changed instead of .value/@change
|
// Runs it for LionSelectRich, which uses .modelValue/@model-value-changed instead of .value/@change
|
||||||
runInputTelDropdownSuite({ klass: WithFormControlInputTelDropdown });
|
runInputTelDropdownSuite({ klass: WithFormControlInputTelDropdown });
|
||||||
|
|
||||||
|
it('focuses the textbox right after selection if selected via opened dropdown if interaction-mode is mac', async () => {
|
||||||
|
class InputTelDropdownMac 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}"
|
||||||
|
interaction-mode="mac"
|
||||||
|
>
|
||||||
|
${repeat(
|
||||||
|
data.regionMetaList,
|
||||||
|
regionMeta => regionMeta.regionCode,
|
||||||
|
regionMeta =>
|
||||||
|
html` <lion-option .choiceValue="${regionMeta.regionCode}"> </lion-option> `,
|
||||||
|
)}
|
||||||
|
</lion-select-rich>
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
customElements.define('input-tel-dropdown-mac', InputTelDropdownMac);
|
||||||
|
const el = /** @type {LionInputTelDropdown} */ (
|
||||||
|
await fixture(
|
||||||
|
html`
|
||||||
|
<input-tel-dropdown-mac
|
||||||
|
.allowedRegions="${['NL', 'BE']}"
|
||||||
|
.modelValue="${'+31612345678'}"
|
||||||
|
></input-tel-dropdown-mac>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const dropdownElement = el.refs.dropdown.value;
|
||||||
|
// @ts-expect-error [allow-protected-in-tests]
|
||||||
|
if (dropdownElement?._overlayCtrl) {
|
||||||
|
// @ts-expect-error [allow-protected-in-tests]
|
||||||
|
dropdownElement._overlayCtrl.show();
|
||||||
|
mimicUserChangingDropdown(dropdownElement, 'BE');
|
||||||
|
await el.updateComplete;
|
||||||
|
await aTimeout(0);
|
||||||
|
// @ts-expect-error [allow-protected-in-tests]
|
||||||
|
expect(el._inputNode).to.equal(document.activeElement);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not focus the textbox right after selection if selected via opened dropdown if interaction-mode is windows/linux', async () => {
|
||||||
|
class InputTelDropdownWindows 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}"
|
||||||
|
interaction-mode="windows/linux"
|
||||||
|
>
|
||||||
|
${repeat(
|
||||||
|
data.regionMetaList,
|
||||||
|
regionMeta => regionMeta.regionCode,
|
||||||
|
regionMeta =>
|
||||||
|
html` <lion-option .choiceValue="${regionMeta.regionCode}"> </lion-option> `,
|
||||||
|
)}
|
||||||
|
</lion-select-rich>
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
customElements.define('input-tel-dropdown-windows', InputTelDropdownWindows);
|
||||||
|
const el = /** @type {LionInputTelDropdown} */ (
|
||||||
|
await fixture(
|
||||||
|
html`
|
||||||
|
<input-tel-dropdown-windows
|
||||||
|
.allowedRegions="${['NL', 'BE']}"
|
||||||
|
.modelValue="${'+31612345678'}"
|
||||||
|
></input-tel-dropdown-windows>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const dropdownElement = el.refs.dropdown.value;
|
||||||
|
// @ts-expect-error [allow-protected-in-tests]
|
||||||
|
if (dropdownElement?._overlayCtrl) {
|
||||||
|
// @ts-expect-error [allow-protected-in-tests]
|
||||||
|
dropdownElement._overlayCtrl.show();
|
||||||
|
mimicUserChangingDropdown(dropdownElement, 'BE');
|
||||||
|
await el.updateComplete;
|
||||||
|
await aTimeout(0);
|
||||||
|
// @ts-expect-error [allow-protected-in-tests]
|
||||||
|
expect(el._inputNode).to.not.equal(document.activeElement);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
1
packages/ui/exports/input-tel-dropdown-test-helpers.js
Normal file
1
packages/ui/exports/input-tel-dropdown-test-helpers.js
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export { mimicUserChangingDropdown } from '../components/input-tel-dropdown/test-helpers/mimicUserChangingDropdown.js';
|
||||||
Loading…
Reference in a new issue