fix(select-rich): allow arrowLeft and arrowRight to change the value when navigateWithinInvoker is true and dropdown is closed
This commit is contained in:
parent
3fe4555cba
commit
41fecd367e
3 changed files with 161 additions and 21 deletions
5
.changeset/spotty-emus-allow.md
Normal file
5
.changeset/spotty-emus-allow.md
Normal file
|
|
@ -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
|
||||||
|
|
@ -489,7 +489,7 @@ export class LionSelectRich extends SlotMixin(ScopedElementsMixin(OverlayMixin(L
|
||||||
* @param {KeyboardEvent} ev
|
* @param {KeyboardEvent} ev
|
||||||
* @protected
|
* @protected
|
||||||
*/
|
*/
|
||||||
// TODO: rename to _onKeyUp in v1
|
// TODO: rename to #invokerOnKeyUp() (and move event listener to the invoker) in v1
|
||||||
__onKeyUp(ev) {
|
__onKeyUp(ev) {
|
||||||
if (this.disabled || this.readOnly) {
|
if (this.disabled || this.readOnly) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -529,6 +529,22 @@ export class LionSelectRich extends SlotMixin(ScopedElementsMixin(OverlayMixin(L
|
||||||
this.opened = true;
|
this.opened = true;
|
||||||
}
|
}
|
||||||
break;
|
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:
|
default:
|
||||||
if (!this._noTypeAhead) {
|
if (!this._noTypeAhead) {
|
||||||
this._handleTypeAhead(ev, { setAsChecked: true });
|
this._handleTypeAhead(ev, { setAsChecked: true });
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,24 @@ function mimicKeyPress(el, key, code = '') {
|
||||||
el.dispatchEvent(new KeyboardEvent('keyup', { 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('lion-select-rich interactions', () => {
|
||||||
describe('Interaction mode', () => {
|
describe('Interaction mode', () => {
|
||||||
it('autodetects interactionMode if not defined', async () => {
|
it('autodetects interactionMode if not defined', async () => {
|
||||||
|
|
@ -86,26 +104,91 @@ describe('lion-select-rich interactions', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Invoker Keyboard navigation Windows', () => {
|
describe('Invoker Keyboard navigation Mac', () => {
|
||||||
it('navigates through list with [ArrowDown] [ArrowUp] keys checks the option while listbox unopened', async () => {
|
it('opens dropdown with [ArrowDown] [ArrowUp] keys or navigates through the listbox options', async () => {
|
||||||
/**
|
const el = /** @type {LionSelectRich} */ (
|
||||||
* @param {LionOption[]} options
|
await fixture(html`
|
||||||
* @param {number} selectedIndex
|
<lion-select-rich interaction-mode="mac">
|
||||||
*/
|
<lion-options slot="input">
|
||||||
function expectOnlyGivenOneOptionToBeChecked(options, selectedIndex) {
|
<lion-option .choiceValue=${10}>Item 1</lion-option>
|
||||||
/**
|
<lion-option .choiceValue=${20}>Item 2</lion-option>
|
||||||
* @param {{ checked: any; }} option
|
<lion-option .choiceValue=${30}>Item 3</lion-option>
|
||||||
* @param {any} i
|
</lion-options>
|
||||||
*/
|
</lion-select-rich>
|
||||||
options.forEach((option, i) => {
|
`)
|
||||||
if (i === selectedIndex) {
|
);
|
||||||
expect(option.checked).to.be.true;
|
|
||||||
} else {
|
|
||||||
expect(option.checked).to.be.false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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`
|
||||||
|
<lion-select-rich interaction-mode="mac">
|
||||||
|
<lion-options slot="input">
|
||||||
|
<lion-option .choiceValue=${10}>Item 1</lion-option>
|
||||||
|
<lion-option .choiceValue=${20}>Item 2</lion-option>
|
||||||
|
<lion-option .choiceValue=${30}>Item 3</lion-option>
|
||||||
|
</lion-options>
|
||||||
|
</lion-select-rich>
|
||||||
|
`)
|
||||||
|
);
|
||||||
|
|
||||||
|
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`
|
||||||
|
<lion-select-rich interaction-mode="mac">
|
||||||
|
<lion-options slot="input">
|
||||||
|
<lion-option .choiceValue=${'red'}>Red</lion-option>
|
||||||
|
<lion-option .choiceValue=${'teal'}>Teal</lion-option>
|
||||||
|
<lion-option .choiceValue=${'turquoise'}>Turquoise</lion-option>
|
||||||
|
</lion-options>
|
||||||
|
</lion-select-rich>
|
||||||
|
`)
|
||||||
|
);
|
||||||
|
|
||||||
|
// @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;
|
let isTriggeredByUser;
|
||||||
const el = /** @type {LionSelectRich} */ (
|
const el = /** @type {LionSelectRich} */ (
|
||||||
await fixture(html`
|
await fixture(html`
|
||||||
|
|
@ -141,7 +224,43 @@ describe('lion-select-rich interactions', () => {
|
||||||
expect(isTriggeredByUser).to.be.true;
|
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`
|
||||||
|
<lion-select-rich
|
||||||
|
interaction-mode="windows/linux"
|
||||||
|
@model-value-changed="${(/** @type {CustomEvent} */ event) => {
|
||||||
|
isTriggeredByUser = event.detail.isTriggeredByUser;
|
||||||
|
}}"
|
||||||
|
>
|
||||||
|
<lion-options slot="input">
|
||||||
|
<lion-option .choiceValue=${10}>Item 1</lion-option>
|
||||||
|
<lion-option .choiceValue=${20}>Item 2</lion-option>
|
||||||
|
<lion-option .choiceValue=${30}>Item 3</lion-option>
|
||||||
|
</lion-options>
|
||||||
|
</lion-select-rich>
|
||||||
|
`)
|
||||||
|
);
|
||||||
|
|
||||||
|
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} */ (
|
const el = /** @type {LionSelectRich} */ (
|
||||||
await fixture(html`
|
await fixture(html`
|
||||||
<lion-select-rich interaction-mode="windows/linux">
|
<lion-select-rich interaction-mode="windows/linux">
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue