diff --git a/.changeset/witty-houses-argue.md b/.changeset/witty-houses-argue.md new file mode 100644 index 0000000000..f89f2a6707 --- /dev/null +++ b/.changeset/witty-houses-argue.md @@ -0,0 +1,5 @@ +--- +'@lion/ui': patch +--- + +[form-core] order aria-labelledby and aria-describedby based on slot order instead of dom order diff --git a/packages/ui/components/form-core/src/FormControlMixin.js b/packages/ui/components/form-core/src/FormControlMixin.js index fd0c0c03fe..52df9d9537 100644 --- a/packages/ui/components/form-core/src/FormControlMixin.js +++ b/packages/ui/components/form-core/src/FormControlMixin.js @@ -390,8 +390,20 @@ const FormControlMixinImplementation = superclass => const insideNodes = 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 - nodes = [...getAriaElementsInRightDomOrder(insideNodes), ...outsideNodes]; + nodes = [...orderedInsideNodes, ...outsideNodes]; } const string = nodes.map(n => n.id).join(' '); this._inputNode.setAttribute(attrName, string); diff --git a/packages/ui/components/form-core/test/FormControlMixin.test.js b/packages/ui/components/form-core/test/FormControlMixin.test.js index 7e83c1e9b3..123213732d 100644 --- a/packages/ui/components/form-core/test/FormControlMixin.test.js +++ b/packages/ui/components/form-core/test/FormControlMixin.test.js @@ -349,19 +349,18 @@ 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` -
- <${tag}> - - -
- Added to description by default -
- -
should go after input internals
-
should go after input internals
-
`); +
+ <${tag}> + + +
+ Added to description by default +
+ +
+ `); const el = /** @type {FormControlMixinClass} */ (wrapper.querySelector(tagString)); const { _inputNode } = getFormControlMembers(el); @@ -370,6 +369,11 @@ describe('FormControlMixin', () => { // 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 const myInput = /** @type {HTMLElement} */ (wrapper.querySelector('#myInput')); + const internalLabel = /** @type {HTMLElement} */ (wrapper.querySelector('#internalLabel')); + const internalDescription = /** @type {HTMLElement} */ ( + wrapper.querySelector('#internalDescription') + ); + el.addToAriaLabelledBy(myInput); await el.updateComplete; el.addToAriaDescribedBy(myInput); @@ -377,29 +381,29 @@ describe('FormControlMixin', () => { expect( /** @type {string} */ (_inputNode.getAttribute('aria-labelledby')).split(' '), - ).to.eql(['myInput', 'internalLabel']); + ).to.eql(['internalLabel', 'myInput']); expect( /** @type {string} */ (_inputNode.getAttribute('aria-describedby')).split(' '), - ).to.eql(['myInput', 'internalDescription']); + ).to.eql(['internalDescription', 'myInput']); // cleanup - el.removeFromAriaLabelledBy(myInput); + el.removeFromAriaLabelledBy(internalLabel); await el.updateComplete; - el.removeFromAriaDescribedBy(myInput); + el.removeFromAriaDescribedBy(internalDescription); await el.updateComplete; // opt-out of reorder - el.addToAriaLabelledBy(myInput, { reorder: false }); + el.addToAriaLabelledBy(internalLabel, { reorder: false }); await el.updateComplete; - el.addToAriaDescribedBy(myInput, { reorder: false }); + el.addToAriaDescribedBy(internalDescription, { reorder: false }); await el.updateComplete; expect( /** @type {string} */ (_inputNode.getAttribute('aria-labelledby')).split(' '), - ).to.eql(['internalLabel', 'myInput']); + ).to.eql(['myInput', 'internalLabel']); expect( /** @type {string} */ (_inputNode.getAttribute('aria-describedby')).split(' '), - ).to.eql(['internalDescription', 'myInput']); + ).to.eql(['myInput', 'internalDescription']); }); it('respects provided order for external elements', async () => {