fix(form-core): order aria-labelledby and aria-describedby based on slot order instead of dom order
This commit is contained in:
parent
5073ea4760
commit
659cbff18c
3 changed files with 53 additions and 32 deletions
5
.changeset/witty-houses-argue.md
Normal file
5
.changeset/witty-houses-argue.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'@lion/ui': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
[form-core] order aria-labelledby and aria-describedby based on slot order instead of dom order
|
||||||
|
|
@ -390,8 +390,20 @@ const FormControlMixinImplementation = superclass =>
|
||||||
const insideNodes = nodes.filter(n => this.contains(n));
|
const insideNodes = nodes.filter(n => this.contains(n));
|
||||||
const outsideNodes = nodes.filter(n => !this.contains(n));
|
const outsideNodes = nodes.filter(n => !this.contains(n));
|
||||||
|
|
||||||
|
const insideSlots = insideNodes.map(n => n.assignedSlot || n);
|
||||||
|
const orderedInsideSlots = [...getAriaElementsInRightDomOrder(insideSlots)];
|
||||||
|
/** @type {Element[]} */
|
||||||
|
const orderedInsideNodes = [];
|
||||||
|
orderedInsideSlots.forEach(assignedNode => {
|
||||||
|
insideNodes.forEach(node => {
|
||||||
|
// @ts-ignore
|
||||||
|
if (assignedNode.name === node.slot) {
|
||||||
|
orderedInsideNodes.push(node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
nodes = [...getAriaElementsInRightDomOrder(insideNodes), ...outsideNodes];
|
nodes = [...orderedInsideNodes, ...outsideNodes];
|
||||||
}
|
}
|
||||||
const string = nodes.map(n => n.id).join(' ');
|
const string = nodes.map(n => n.id).join(' ');
|
||||||
this._inputNode.setAttribute(attrName, string);
|
this._inputNode.setAttribute(attrName, string);
|
||||||
|
|
|
||||||
|
|
@ -349,7 +349,7 @@ describe('FormControlMixin', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sorts internal elements, and allows opt-out', async () => {
|
it('sorts internal elements based on assigned slots, and allows opt-out', async () => {
|
||||||
const wrapper = await fixture(html`
|
const wrapper = await fixture(html`
|
||||||
<div id="wrapper">
|
<div id="wrapper">
|
||||||
<${tag}>
|
<${tag}>
|
||||||
|
|
@ -359,9 +359,8 @@ describe('FormControlMixin', () => {
|
||||||
Added to description by default
|
Added to description by default
|
||||||
</div>
|
</div>
|
||||||
</${tag}>
|
</${tag}>
|
||||||
<div id="externalLabelB">should go after input internals</div>
|
</div>
|
||||||
<div id="externalDescriptionB">should go after input internals</div>
|
`);
|
||||||
</div>`);
|
|
||||||
const el = /** @type {FormControlMixinClass} */ (wrapper.querySelector(tagString));
|
const el = /** @type {FormControlMixinClass} */ (wrapper.querySelector(tagString));
|
||||||
const { _inputNode } = getFormControlMembers(el);
|
const { _inputNode } = getFormControlMembers(el);
|
||||||
|
|
||||||
|
|
@ -370,36 +369,41 @@ describe('FormControlMixin', () => {
|
||||||
// A real life scenario would be for instance when
|
// A real life scenario would be for instance when
|
||||||
// a Field or FormGroup would be extended and an extra slot would be added in the template
|
// a Field or FormGroup would be extended and an extra slot would be added in the template
|
||||||
const myInput = /** @type {HTMLElement} */ (wrapper.querySelector('#myInput'));
|
const myInput = /** @type {HTMLElement} */ (wrapper.querySelector('#myInput'));
|
||||||
|
const internalLabel = /** @type {HTMLElement} */ (wrapper.querySelector('#internalLabel'));
|
||||||
|
const internalDescription = /** @type {HTMLElement} */ (
|
||||||
|
wrapper.querySelector('#internalDescription')
|
||||||
|
);
|
||||||
|
|
||||||
el.addToAriaLabelledBy(myInput);
|
el.addToAriaLabelledBy(myInput);
|
||||||
await el.updateComplete;
|
await el.updateComplete;
|
||||||
el.addToAriaDescribedBy(myInput);
|
el.addToAriaDescribedBy(myInput);
|
||||||
await el.updateComplete;
|
await el.updateComplete;
|
||||||
|
|
||||||
expect(
|
|
||||||
/** @type {string} */ (_inputNode.getAttribute('aria-labelledby')).split(' '),
|
|
||||||
).to.eql(['myInput', 'internalLabel']);
|
|
||||||
expect(
|
|
||||||
/** @type {string} */ (_inputNode.getAttribute('aria-describedby')).split(' '),
|
|
||||||
).to.eql(['myInput', 'internalDescription']);
|
|
||||||
|
|
||||||
// cleanup
|
|
||||||
el.removeFromAriaLabelledBy(myInput);
|
|
||||||
await el.updateComplete;
|
|
||||||
el.removeFromAriaDescribedBy(myInput);
|
|
||||||
await el.updateComplete;
|
|
||||||
|
|
||||||
// opt-out of reorder
|
|
||||||
el.addToAriaLabelledBy(myInput, { reorder: false });
|
|
||||||
await el.updateComplete;
|
|
||||||
el.addToAriaDescribedBy(myInput, { reorder: false });
|
|
||||||
await el.updateComplete;
|
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
/** @type {string} */ (_inputNode.getAttribute('aria-labelledby')).split(' '),
|
/** @type {string} */ (_inputNode.getAttribute('aria-labelledby')).split(' '),
|
||||||
).to.eql(['internalLabel', 'myInput']);
|
).to.eql(['internalLabel', 'myInput']);
|
||||||
expect(
|
expect(
|
||||||
/** @type {string} */ (_inputNode.getAttribute('aria-describedby')).split(' '),
|
/** @type {string} */ (_inputNode.getAttribute('aria-describedby')).split(' '),
|
||||||
).to.eql(['internalDescription', 'myInput']);
|
).to.eql(['internalDescription', 'myInput']);
|
||||||
|
|
||||||
|
// cleanup
|
||||||
|
el.removeFromAriaLabelledBy(internalLabel);
|
||||||
|
await el.updateComplete;
|
||||||
|
el.removeFromAriaDescribedBy(internalDescription);
|
||||||
|
await el.updateComplete;
|
||||||
|
|
||||||
|
// opt-out of reorder
|
||||||
|
el.addToAriaLabelledBy(internalLabel, { reorder: false });
|
||||||
|
await el.updateComplete;
|
||||||
|
el.addToAriaDescribedBy(internalDescription, { reorder: false });
|
||||||
|
await el.updateComplete;
|
||||||
|
|
||||||
|
expect(
|
||||||
|
/** @type {string} */ (_inputNode.getAttribute('aria-labelledby')).split(' '),
|
||||||
|
).to.eql(['myInput', 'internalLabel']);
|
||||||
|
expect(
|
||||||
|
/** @type {string} */ (_inputNode.getAttribute('aria-describedby')).split(' '),
|
||||||
|
).to.eql(['myInput', 'internalDescription']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('respects provided order for external elements', async () => {
|
it('respects provided order for external elements', async () => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue