fix(combobox): submit form on [Enter] (#2005)

fix(combobox): submit form on [Enter]
This commit is contained in:
gerjanvangeest 2023-06-08 18:14:14 +02:00 committed by GitHub
parent ccc762d876
commit dbc3fc2db6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 94 additions and 4 deletions

View file

@ -0,0 +1,5 @@
---
'@lion/ui': patch
---
[combobox] submits form on [Enter]

View file

@ -406,7 +406,6 @@ export class LionCombobox extends LocalizeMixin(OverlayMixin(LionListbox)) {
* @protected * @protected
*/ */
this._ariaVersion = browserDetection.isChromium ? '1.1' : '1.0'; this._ariaVersion = browserDetection.isChromium ? '1.1' : '1.0';
/** /**
* @configure ListboxMixin * @configure ListboxMixin
* @protected * @protected
@ -1072,6 +1071,9 @@ export class LionCombobox extends LocalizeMixin(OverlayMixin(LionListbox)) {
this._setTextboxValue(''); this._setTextboxValue('');
break; break;
case 'Enter': case 'Enter':
if (this.multipleChoice && this.opened) {
ev.preventDefault();
}
if (!this.formElements[this.activeIndex]) { if (!this.formElements[this.activeIndex]) {
return; return;
} }

View file

@ -12,6 +12,7 @@ import '@lion/ui/define/lion-listbox.js';
import '@lion/ui/define/lion-option.js'; import '@lion/ui/define/lion-option.js';
import { Required, Unparseable } from '@lion/ui/form-core.js'; import { Required, Unparseable } from '@lion/ui/form-core.js';
import { defineCE, expect, fixture, html, unsafeStatic } from '@open-wc/testing'; import { defineCE, expect, fixture, html, unsafeStatic } from '@open-wc/testing';
import { sendKeys } from '@web/test-runner-commands';
import { LitElement } from 'lit'; import { LitElement } from 'lit';
import sinon from 'sinon'; import sinon from 'sinon';
@ -2678,6 +2679,57 @@ describe('lion-combobox', () => {
mimicKeyPress(visibleOptions[1], 'Enter'); mimicKeyPress(visibleOptions[1], 'Enter');
expect(el.opened).to.equal(true); expect(el.opened).to.equal(true);
}); });
it('submits form on [Enter] when listbox is closed', async () => {
const submitSpy = sinon.spy(e => e.preventDefault());
const el = /** @type {HTMLFormElement} */ (
await fixture(html`
<form @submit=${submitSpy}>
<lion-combobox name="foo" multiple-choice>
<lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
<lion-option .choiceValue="${'Chard'}">Chard</lion-option>
<lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
</lion-combobox>
<button type="submit">submit</button>
</form>
`)
);
const combobox = /** @type {LionCombobox} */ (el.querySelector('[name="foo"]'));
const { _inputNode } = getComboboxMembers(combobox);
await combobox.updateComplete;
_inputNode.focus();
await sendKeys({
press: 'Enter',
});
expect(submitSpy.callCount).to.equal(1);
});
it('does not submit form on [Enter] when listbox is opened', async () => {
const submitSpy = sinon.spy(e => e.preventDefault());
const el = /** @type {HTMLFormElement} */ (
await fixture(html`
<form @submit=${submitSpy}>
<lion-combobox name="foo" multiple-choice>
<lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
<lion-option .choiceValue="${'Chard'}">Chard</lion-option>
<lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
</lion-combobox>
<button type="submit">submit</button>
</form>
`)
);
const combobox = /** @type {LionCombobox} */ (el.querySelector('[name="foo"]'));
const { _inputNode } = getComboboxMembers(combobox);
combobox.opened = true;
await combobox.updateComplete;
_inputNode.focus();
await sendKeys({
press: 'Enter',
});
expect(submitSpy.callCount).to.equal(0);
});
}); });
describe('Match Mode', () => { describe('Match Mode', () => {

View file

@ -294,7 +294,6 @@ const ListboxMixinImplementation = superclass =>
* @protected * @protected
*/ */
this._repropagationRole = 'choice-group'; // configures FormControlMixin this._repropagationRole = 'choice-group'; // configures FormControlMixin
/** /**
* When listbox is coupled to a textbox (in case we are dealing with a combobox), * When listbox is coupled to a textbox (in case we are dealing with a combobox),
* spaces should not select an element (they need to be put in the textbox) * spaces should not select an element (they need to be put in the textbox)
@ -612,7 +611,9 @@ const ListboxMixinImplementation = superclass =>
if (key === ' ' && this._listboxReceivesNoFocus) { if (key === ' ' && this._listboxReceivesNoFocus) {
return; return;
} }
if (key === ' ') {
ev.preventDefault(); ev.preventDefault();
}
if (!this.formElements[this.activeIndex]) { if (!this.formElements[this.activeIndex]) {
return; return;
} }

View file

@ -13,6 +13,7 @@ import {
nextFrame, nextFrame,
unsafeStatic, unsafeStatic,
} from '@open-wc/testing'; } from '@open-wc/testing';
import { sendKeys } from '@web/test-runner-commands';
import sinon from 'sinon'; import sinon from 'sinon';
import { getListboxMembers } from '../../../exports/listbox-test-helpers.js'; import { getListboxMembers } from '../../../exports/listbox-test-helpers.js';
@ -673,12 +674,41 @@ export function runListboxMixinSuite(customConfig = {}) {
mimicKeyPress(_listboxNode, 'Enter'); mimicKeyPress(_listboxNode, 'Enter');
expect(options[1].checked).to.be.true; expect(options[1].checked).to.be.true;
}); });
it('submits form on [Enter] when inputNode is an instance of HTMLInputNode', async () => {
const submitSpy = sinon.spy(e => e.preventDefault());
const el = /** @type {HTMLFormElement} */ (
await _fixture(html`
<form @submit=${submitSpy}>
<${tag} name="foo">
<${optionTag} .choiceValue="${'Artichoke'}">Artichoke</${optionTag}>
<${optionTag} .choiceValue="${'Bla'}">Bla</${optionTag}>
<${optionTag} .choiceValue="${'Chard'}">Chard</${optionTag}>
</${tag}>
<button type="submit">submit</button>
</form>
`)
);
const listbox = /** @type {LionListbox} */ (el.querySelector('[name="foo"]'));
const { _inputNode } = getListboxMembers(listbox);
if (!(_inputNode instanceof HTMLInputElement)) {
return;
}
await listbox.updateComplete;
// @ts-ignore allow protected members in tests
_inputNode.focus();
await sendKeys({
press: 'Enter',
});
expect(submitSpy.callCount).to.equal(1);
});
}); });
describe('Space', () => { describe('Space', () => {
it('selects active option when "_listboxReceivesNoFocus" is true', async () => { it('selects active option when "_listboxReceivesNoFocus" is true', async () => {
// When listbox is not focusable (in case of a combobox), the user should be allowed // When listbox is not focusable (in case of a combobox), the user should be allowed
// to enter a space in the focusable element (texbox) // to enter a space in the focusable element (textbox)
const el = /** @type {LionListbox} */ ( const el = /** @type {LionListbox} */ (
await fixture(html` await fixture(html`
<${tag} opened name="foo" ._listboxReceivesNoFocus="${false}" autocomplete="none" show-all-on-empty> <${tag} opened name="foo" ._listboxReceivesNoFocus="${false}" autocomplete="none" show-all-on-empty>