diff --git a/.changeset/yellow-steaks-tie.md b/.changeset/yellow-steaks-tie.md new file mode 100644 index 000000000..c886c37dd --- /dev/null +++ b/.changeset/yellow-steaks-tie.md @@ -0,0 +1,5 @@ +--- +'@lion/ui': patch +--- + +[input-tel-dropdown] prevent jumping to input field on each arrow key in windows/linux diff --git a/packages/ui/components/input-tel-dropdown/src/LionInputTelDropdown.js b/packages/ui/components/input-tel-dropdown/src/LionInputTelDropdown.js index 0100f9b6e..328264334 100644 --- a/packages/ui/components/input-tel-dropdown/src/LionInputTelDropdown.js +++ b/packages/ui/components/input-tel-dropdown/src/LionInputTelDropdown.js @@ -324,7 +324,10 @@ export class LionInputTelDropdown extends LionInputTel { */ _onDropdownValueChange(event) { 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) { return; @@ -355,8 +358,13 @@ export class LionInputTelDropdown extends LionInputTel { } // 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(() => { this._inputNode.focus(); }); diff --git a/packages/ui/components/input-tel-dropdown/test-helpers/mimicUserChangingDropdown.js b/packages/ui/components/input-tel-dropdown/test-helpers/mimicUserChangingDropdown.js new file mode 100644 index 000000000..c320146d6 --- /dev/null +++ b/packages/ui/components/input-tel-dropdown/test-helpers/mimicUserChangingDropdown.js @@ -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')); + } +} diff --git a/packages/ui/components/input-tel-dropdown/test-suites/LionInputTelDropdown.suite.js b/packages/ui/components/input-tel-dropdown/test-suites/LionInputTelDropdown.suite.js index 053d2e9e2..5523b5dbd 100644 --- a/packages/ui/components/input-tel-dropdown/test-suites/LionInputTelDropdown.suite.js +++ b/packages/ui/components/input-tel-dropdown/test-suites/LionInputTelDropdown.suite.js @@ -11,6 +11,7 @@ import { } from '@open-wc/testing'; import sinon from 'sinon'; 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 @@ -36,24 +37,6 @@ function getDropdownValue(dropdownEl) { 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 */ @@ -335,26 +318,6 @@ export function runInputTelDropdownSuite({ klass } = { klass: LionInputTelDropdo 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'}"> `, - ); - 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 () => { const el = await fixture( html` <${tag} .allowedRegions="${[ diff --git a/packages/ui/components/input-tel-dropdown/test/LionInputTelDropdown.test.js b/packages/ui/components/input-tel-dropdown/test/LionInputTelDropdown.test.js index 8ca519cef..15753bd38 100644 --- a/packages/ui/components/input-tel-dropdown/test/LionInputTelDropdown.test.js +++ b/packages/ui/components/input-tel-dropdown/test/LionInputTelDropdown.test.js @@ -1,10 +1,10 @@ import { runInputTelSuite } from '@lion/ui/input-tel-test-suites.js'; -import { html } from 'lit'; import { repeat } from 'lit/directives/repeat.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 { 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-select-rich.js'; @@ -53,4 +53,112 @@ describe('WithFormControlInputTelDropdown', () => { // @ts-expect-error // Runs it for LionSelectRich, which uses .modelValue/@model-value-changed instead of .value/@change 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` + + ${repeat( + data.regionMetaList, + regionMeta => regionMeta.regionCode, + regionMeta => + html` `, + )} + + `; + }, + }; + } + customElements.define('input-tel-dropdown-mac', InputTelDropdownMac); + const el = /** @type {LionInputTelDropdown} */ ( + await fixture( + html` + + `, + ) + ); + 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` + + ${repeat( + data.regionMetaList, + regionMeta => regionMeta.regionCode, + regionMeta => + html` `, + )} + + `; + }, + }; + } + customElements.define('input-tel-dropdown-windows', InputTelDropdownWindows); + const el = /** @type {LionInputTelDropdown} */ ( + await fixture( + html` + + `, + ) + ); + 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); + } + }); }); diff --git a/packages/ui/exports/input-tel-dropdown-test-helpers.js b/packages/ui/exports/input-tel-dropdown-test-helpers.js new file mode 100644 index 000000000..3d3b171fc --- /dev/null +++ b/packages/ui/exports/input-tel-dropdown-test-helpers.js @@ -0,0 +1 @@ +export { mimicUserChangingDropdown } from '../components/input-tel-dropdown/test-helpers/mimicUserChangingDropdown.js';