diff --git a/.changeset/spotty-emus-allow.md b/.changeset/spotty-emus-allow.md new file mode 100644 index 000000000..27a1bf762 --- /dev/null +++ b/.changeset/spotty-emus-allow.md @@ -0,0 +1,5 @@ +--- +'@lion/ui': patch +--- + +[select-rich] allow arrowLeft and arrowRight to change the value when navigateWithinInvoker is true and dropdown is closed diff --git a/packages/ui/components/select-rich/src/LionSelectRich.js b/packages/ui/components/select-rich/src/LionSelectRich.js index 06d798d5c..28b176600 100644 --- a/packages/ui/components/select-rich/src/LionSelectRich.js +++ b/packages/ui/components/select-rich/src/LionSelectRich.js @@ -529,6 +529,22 @@ export class LionSelectRich extends SlotMixin(ScopedElementsMixin(OverlayMixin(L this.opened = true; } break; + case 'ArrowLeft': + ev.preventDefault(); + if (this.navigateWithinInvoker) { + this.setCheckedIndex( + this._getPreviousEnabledOption(/** @type {number} */ (this.checkedIndex)), + ); + } + break; + case 'ArrowRight': + ev.preventDefault(); + if (this.navigateWithinInvoker) { + this.setCheckedIndex( + this._getNextEnabledOption(/** @type {number} */ (this.checkedIndex)), + ); + } + break; default: if (!this._noTypeAhead) { this._handleTypeAhead(ev, { setAsChecked: true }); diff --git a/packages/ui/components/select-rich/test/lion-select-rich-interaction.test.js b/packages/ui/components/select-rich/test/lion-select-rich-interaction.test.js index ef43ec63e..72a55d29e 100644 --- a/packages/ui/components/select-rich/test/lion-select-rich-interaction.test.js +++ b/packages/ui/components/select-rich/test/lion-select-rich-interaction.test.js @@ -24,6 +24,24 @@ function mimicKeyPress(el, key, code = '') { el.dispatchEvent(new KeyboardEvent('keyup', { key, code })); } +/** + * @param {LionOption[]} options + * @param {number} selectedIndex + */ +function expectOnlyGivenOneOptionToBeChecked(options, selectedIndex) { + /** + * @param {{ checked: any; }} option + * @param {any} i + */ + options.forEach((option, i) => { + if (i === selectedIndex) { + expect(option.checked).to.be.true; + } else { + expect(option.checked).to.be.false; + } + }); +} + describe('lion-select-rich interactions', () => { describe('Interaction mode', () => { it('autodetects interactionMode if not defined', async () => { @@ -86,26 +104,91 @@ describe('lion-select-rich interactions', () => { }); }); - describe('Invoker Keyboard navigation Windows', () => { - it('navigates through list with [ArrowDown] [ArrowUp] keys checks the option while listbox unopened', async () => { - /** - * @param {LionOption[]} options - * @param {number} selectedIndex - */ - function expectOnlyGivenOneOptionToBeChecked(options, selectedIndex) { - /** - * @param {{ checked: any; }} option - * @param {any} i - */ - options.forEach((option, i) => { - if (i === selectedIndex) { - expect(option.checked).to.be.true; - } else { - expect(option.checked).to.be.false; - } - }); - } + describe('Invoker Keyboard navigation Mac', () => { + it('opens dropdown with [ArrowDown] [ArrowUp] keys or navigates through the listbox options', async () => { + const el = /** @type {LionSelectRich} */ ( + await fixture(html` + + + Item 1 + Item 2 + Item 3 + + + `) + ); + + const options = el.formElements; + expect(el.checkedIndex).to.equal(0); + expectOnlyGivenOneOptionToBeChecked(options, 0); + + el.dispatchEvent(new KeyboardEvent('keyup', { key: 'ArrowDown' })); + + expect(el.opened).to.be.true; + expect(el.checkedIndex).to.equal(0); + expectOnlyGivenOneOptionToBeChecked(options, 0); + + el.opened = false; + + el.dispatchEvent(new KeyboardEvent('keyup', { key: 'ArrowUp' })); + expect(el.opened).to.be.true; + expect(el.checkedIndex).to.equal(0); + expectOnlyGivenOneOptionToBeChecked(options, 0); + }); + + it('does not open dropdown with [ArrowLeft] [ArrowRight] keys or navigates through the listbox options', async () => { + const el = /** @type {LionSelectRich} */ ( + await fixture(html` + + + Item 1 + Item 2 + Item 3 + + + `) + ); + + const options = el.formElements; + expect(el.checkedIndex).to.equal(0); + expectOnlyGivenOneOptionToBeChecked(options, 0); + + el.dispatchEvent(new KeyboardEvent('keyup', { key: 'ArrowLeft' })); + + expect(el.opened).to.be.false; + expect(el.checkedIndex).to.equal(0); + expectOnlyGivenOneOptionToBeChecked(options, 0); + + el.dispatchEvent(new KeyboardEvent('keyup', { key: 'ArrowRight' })); + expect(el.opened).to.be.false; + expect(el.checkedIndex).to.equal(0); + expectOnlyGivenOneOptionToBeChecked(options, 0); + }); + + it('checks a value with [character] keys while listbox unopened', async () => { + const el = /** @type {LionSelectRich} */ ( + await fixture(html` + + + Red + Teal + Turquoise + + + `) + ); + + // @ts-ignore [allow-private] in test + mimicKeyPress(el, 't', 'KeyT'); + expect(el.checkedIndex).to.equal(1); + + mimicKeyPress(el, 'u', 'KeyU'); + expect(el.checkedIndex).to.equal(2); + }); + }); + describe('Invoker Keyboard navigation Windows/Linux', () => { + it('navigates through list with [ArrowDown] [ArrowUp] keys checks the option while listbox unopened', async () => { let isTriggeredByUser; const el = /** @type {LionSelectRich} */ ( await fixture(html` @@ -141,7 +224,43 @@ describe('lion-select-rich interactions', () => { expect(isTriggeredByUser).to.be.true; }); - it('checkes a value with [character] keys while listbox unopened', async () => { + it('navigates through list with [ArrowLeft] [ArrowRight] keys checks the option while listbox unopened', async () => { + let isTriggeredByUser; + const el = /** @type {LionSelectRich} */ ( + await fixture(html` + + + Item 1 + Item 2 + Item 3 + + + `) + ); + + const options = el.formElements; + expect(el.checkedIndex).to.equal(0); + expectOnlyGivenOneOptionToBeChecked(options, 0); + + el.dispatchEvent(new KeyboardEvent('keyup', { key: 'ArrowRight' })); + expect(el.checkedIndex).to.equal(1); + expectOnlyGivenOneOptionToBeChecked(options, 1); + expect(isTriggeredByUser).to.be.true; + + isTriggeredByUser = false; + + el.dispatchEvent(new KeyboardEvent('keyup', { key: 'ArrowLeft' })); + expect(el.checkedIndex).to.equal(0); + expectOnlyGivenOneOptionToBeChecked(options, 0); + expect(isTriggeredByUser).to.be.true; + }); + + it('checks a value with [character] keys while listbox unopened', async () => { const el = /** @type {LionSelectRich} */ ( await fixture(html`