fix(form-core): remove fieldset label from input-field aria-labelledby, add opt-in via data-label

This commit is contained in:
gerjanvangeest 2024-02-05 13:39:03 +01:00 committed by Thijs Louisse
parent 87005afbd2
commit 92a5cd1e3d
4 changed files with 32 additions and 56 deletions

View file

@ -0,0 +1,6 @@
---
'@lion/ui': patch
---
[form-core] remove fieldset label from input-field aria-labelledby
[form-core] remove fieldset help-text from input-field aria-describedby

View file

@ -46,7 +46,7 @@ const FormGroupMixinImplementation = superclass =>
} }
/** /**
* The host element with role group (or radigroup or form) containing neccessary aria attributes * The host element with role group (or radio-group or form) containing necessary aria attributes
* @protected * @protected
*/ */
get _inputNode() { get _inputNode() {
@ -478,7 +478,7 @@ const FormGroupMixinImplementation = superclass =>
/** /**
* Traverses the _parentFormGroup tree, and gathers all aria description elements * Traverses the _parentFormGroup tree, and gathers all aria description elements
* (feedback and helptext) that should be provided to children. * (feedback) that should be provided to children.
* *
* In the example below, when the input for 'street' has focus, a screenreader user * In the example below, when the input for 'street' has focus, a screenreader user
* would hear the #group-error. * would hear the #group-error.
@ -503,7 +503,9 @@ const FormGroupMixinImplementation = superclass =>
const descriptionElements = parent._getAriaDescriptionElements(); const descriptionElements = parent._getAriaDescriptionElements();
const orderedEls = getAriaElementsInRightDomOrder(descriptionElements, { reverse: true }); const orderedEls = getAriaElementsInRightDomOrder(descriptionElements, { reverse: true });
orderedEls.forEach(el => { orderedEls.forEach(el => {
if (el.getAttribute('slot') === 'feedback') {
this.__descriptionElementsInParentChain.add(el); this.__descriptionElementsInParentChain.add(el);
}
}); });
// Also check if the newly added child needs to refer grandparents // Also check if the newly added child needs to refer grandparents
parent = parent._parentFormGroup; parent = parent._parentFormGroup;
@ -549,9 +551,6 @@ const FormGroupMixinImplementation = superclass =>
this.__linkParentMessages(child); this.__linkParentMessages(child);
this.validate({ clearCurrentResult: true }); this.validate({ clearCurrentResult: true });
if (typeof child.addToAriaLabelledBy === 'function' && this._labelNode) {
child.addToAriaLabelledBy(this._labelNode, { reorder: false });
}
if (!child.modelValue) { if (!child.modelValue) {
const pVals = this.__pendingValues; const pVals = this.__pendingValues;
if (pVals.modelValue && pVals.modelValue[child.name]) { if (pVals.modelValue && pVals.modelValue[child.name]) {

View file

@ -1,7 +1,6 @@
import { LitElement } from 'lit'; import { LitElement } from 'lit';
import '@lion/ui/define/lion-field.js'; import '@lion/ui/define/lion-field.js';
import '@lion/ui/define/lion-validation-feedback.js'; import '@lion/ui/define/lion-validation-feedback.js';
import { getFormControlMembers } from '@lion/ui/form-core-test-helpers.js';
import { LionInput } from '@lion/ui/input.js'; import { LionInput } from '@lion/ui/input.js';
import { localizeTearDown } from '@lion/ui/localize-test-helpers.js'; import { localizeTearDown } from '@lion/ui/localize-test-helpers.js';
import { defineCE, expect, fixture, html, unsafeStatic } from '@open-wc/testing'; import { defineCE, expect, fixture, html, unsafeStatic } from '@open-wc/testing';
@ -63,41 +62,6 @@ export function runFormGroupMixinInputSuite(cfg = {}) {
'custom[]': ['custom 1', ''], 'custom[]': ['custom 1', ''],
}); });
}); });
it('suffixes child labels with group label, just like in <fieldset>', async () => {
const el = /** @type {FormGroup} */ (
await fixture(html`
<${tag} label="set">
<${childTag} name="A" label="fieldA"></${childTag}>
<${childTag} name="B" label="fieldB"></${childTag}>
</${tag}>
`)
);
const { _labelNode } = getFormControlMembers(el);
/**
* @param {LionInput} formControl
*/
function getLabels(formControl) {
const control = getFormControlMembers(formControl);
return /** @type {string} */ (control._inputNode.getAttribute('aria-labelledby')).split(
' ',
);
}
const field1 = el.formElements[0];
const field2 = el.formElements[1];
expect(getLabels(field1)).to.eql([field1._labelNode.id, _labelNode.id]);
expect(getLabels(field2)).to.eql([field2._labelNode.id, _labelNode.id]);
// Test the cleanup on disconnected
el.removeChild(field1);
// TODO: wait for updated on disconnected to be fixed: https://github.com/lit/lit/issues/1901
// await field1.updateComplete;
// expect(getLabels(field1)).to.eql([field1._labelNode.id]);
});
}); });
describe('Screen reader relations (aria-describedby) for child inputs and fieldsets', () => { describe('Screen reader relations (aria-describedby) for child inputs and fieldsets', () => {
@ -307,21 +271,10 @@ export function runFormGroupMixinInputSuite(cfg = {}) {
await childAriaTest(await childAriaFixture('feedback')); await childAriaTest(await childAriaFixture('feedback'));
}); });
it(`reads help-text message belonging to fieldset when child input is focused
(via aria-describedby)`, async () => {
await childAriaTest(await childAriaFixture('help-text'));
});
// TODO: wait for updated on disconnected to be fixed: https://github.com/lit/lit/issues/1901 // TODO: wait for updated on disconnected to be fixed: https://github.com/lit/lit/issues/1901
it.skip(`cleans up feedback message belonging to fieldset on disconnect`, async () => { it.skip(`cleans up feedback message belonging to fieldset on disconnect`, async () => {
const el = await childAriaFixture('feedback'); const el = await childAriaFixture('feedback');
await childAriaTest(el, { cleanupPhase: true }); await childAriaTest(el, { cleanupPhase: true });
}); });
// TODO: wait for updated on disconnected to be fixed: https://github.com/lit/lit/issues/1901
it.skip(`cleans up help-text message belonging to fieldset on disconnect`, async () => {
const el = await childAriaFixture('help-text');
await childAriaTest(el, { cleanupPhase: true });
});
}); });
} }

View file

@ -1356,6 +1356,24 @@ export function runFormGroupMixinSuite(cfg = {}) {
expect(el.hasAttribute('aria-labelledby')).to.equal(true); expect(el.hasAttribute('aria-labelledby')).to.equal(true);
expect(el.getAttribute('aria-labelledby')).contains(label.id); expect(el.getAttribute('aria-labelledby')).contains(label.id);
}); });
it('sets an aria-describedby to its children from element with slot="feedback"', async () => {
const el = /** @type {FormGroup} */ (
await fixture(html`
<${tag}>
<label slot="label">My Label</label>
<div slot="feedback">My Feedback</div>
${inputSlots}
</${tag}>
`)
);
const feedback = /** @type {HTMLElement} */ (
Array.from(el.children).find(child => child.slot === 'feedback')
);
expect(el.formElements[0]._inputNode.getAttribute('aria-describedby')).contains(
feedback.id,
);
});
}); });
describe('Dynamically rendered children', () => { describe('Dynamically rendered children', () => {
@ -1395,7 +1413,7 @@ export function runFormGroupMixinSuite(cfg = {}) {
const dynamicChildrenTag = unsafeStatic(dynamicChildrenTagString); const dynamicChildrenTag = unsafeStatic(dynamicChildrenTagString);
it(`when rendering children right from the start, sets their values correctly it(`when rendering children right from the start, sets their values correctly
based on prefilled model/seriazedValue`, async () => { based on prefilled model/serializedValue`, async () => {
const el = /** @type {DynamicCWrapper} */ ( const el = /** @type {DynamicCWrapper} */ (
await fixture(html` await fixture(html`
<${dynamicChildrenTag} <${dynamicChildrenTag}
@ -1430,7 +1448,7 @@ export function runFormGroupMixinSuite(cfg = {}) {
}); });
it(`when rendering children delayed, sets their values it(`when rendering children delayed, sets their values
correctly based on prefilled model/seriazedValue`, async () => { correctly based on prefilled model/serializedValue`, async () => {
const el = /** @type {DynamicCWrapper} */ ( const el = /** @type {DynamicCWrapper} */ (
await fixture(html` await fixture(html`
<${dynamicChildrenTag} .modelValue="${{ firstName: 'foo', lastName: 'bar' }}"> <${dynamicChildrenTag} .modelValue="${{ firstName: 'foo', lastName: 'bar' }}">
@ -1463,7 +1481,7 @@ export function runFormGroupMixinSuite(cfg = {}) {
}); });
it(`when rendering children partly delayed, sets their values correctly based on it(`when rendering children partly delayed, sets their values correctly based on
prefilled model/seriazedValue`, async () => { prefilled model/serializedValue`, async () => {
const el = /** @type {DynamicCWrapper} */ ( const el = /** @type {DynamicCWrapper} */ (
await fixture(html` await fixture(html`
<${dynamicChildrenTag} .fields="${['firstName']}" .modelValue="${{ <${dynamicChildrenTag} .fields="${['firstName']}" .modelValue="${{