diff --git a/.changeset/tender-moles-tell.md b/.changeset/tender-moles-tell.md new file mode 100644 index 0000000000..92cc2cb219 --- /dev/null +++ b/.changeset/tender-moles-tell.md @@ -0,0 +1,5 @@ +--- +"@lion/ui": patch +--- + +fix focus first erroneous for listbox diff --git a/packages/ui/components/form/src/LionForm.js b/packages/ui/components/form/src/LionForm.js index fc7f9dca0f..8848a4b1fb 100644 --- a/packages/ui/components/form/src/LionForm.js +++ b/packages/ui/components/form/src/LionForm.js @@ -10,6 +10,15 @@ const throwFormNodeError = () => { ); }; +/** + * @param {FormRegistrarHost} formEl + * @returns {boolean} + */ +function hasFocusableChildren(formEl) { + // this implies all children have the same type (either all of them are focusable or none of them are) + return formEl.formElements?.some(child => child._focusableNode); +} + /** * LionForm: form wrapper providing extra features and integration with lion-field elements. * @@ -63,7 +72,7 @@ export class LionForm extends LionFieldset { this.dispatchEvent(new Event('submit', { bubbles: true })); if (this.hasFeedbackFor?.includes('error')) { - this._setFocusOnFirstErroneousFormElement(this); + this._setFocusOnFirstErroneousFormElement(/** @type { * & FormRegistrarHost } */ (this)); } } @@ -95,7 +104,7 @@ export class LionForm extends LionFieldset { element.formElements.find(child => child.hasFeedbackFor.includes('error')) || element.formElements[0]; - if (firstFormElWithError.formElements?.length > 0) { + if (hasFocusableChildren(firstFormElWithError)) { this._setFocusOnFirstErroneousFormElement(firstFormElWithError); } else { firstFormElWithError._focusableNode.focus(); diff --git a/packages/ui/components/form/test/lion-form.test.js b/packages/ui/components/form/test/lion-form.test.js index 74c9e6ebab..cbf55bfd7f 100644 --- a/packages/ui/components/form/test/lion-form.test.js +++ b/packages/ui/components/form/test/lion-form.test.js @@ -3,7 +3,8 @@ import '@lion/ui/define/lion-fieldset.js'; import { LionField, Required } from '@lion/ui/form-core.js'; import '@lion/ui/define/lion-field.js'; import '@lion/ui/define/lion-validation-feedback.js'; - +import '@lion/ui/define/lion-listbox.js'; +import '@lion/ui/define/lion-option.js'; import '@lion/ui/define/lion-form.js'; import { aTimeout, @@ -223,7 +224,7 @@ describe('', () => { expect(document.activeElement).to.equal(el.formElements[1]._inputNode); }); - it('sets focus on submit to the first erroneous form element with a fieldset', async () => { + it('sets focus on submit to the first erroneous form element within a fieldset', async () => { const el = await fixture(html`
@@ -266,4 +267,22 @@ describe('', () => { // @ts-ignore [allow-protected] in test expect(document.activeElement).to.equal(fieldset.formElements[0]._inputNode); }); + + it('sets focus on submit to the first form element within a erroneous listbox', async () => { + const el = await fixture(html` + + + + a + b + + + + + `); + const button = /** @type {HTMLButtonElement} */ (el.querySelector('button')); + button.click(); + const listboxEl = el.formElements[0]; + expect(document.activeElement).to.equal(listboxEl._inputNode); + }); });