Skip to content

Commit

Permalink
fix: use ariaTarget property to set popover ARIA attributes if possib…
Browse files Browse the repository at this point in the history
…le (#8721) (#8735)

Co-authored-by: Serhii Kulykov <iamkulykov@gmail.com>
  • Loading branch information
vaadin-bot and web-padawan authored Feb 25, 2025
1 parent 7e947c8 commit c48e044
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 69 deletions.
35 changes: 17 additions & 18 deletions packages/popover/src/vaadin-popover.js
Original file line number Diff line number Diff line change
Expand Up @@ -409,8 +409,7 @@ class Popover extends PopoverPositionMixin(
return [
'__updateContentHeight(contentHeight, _overlayElement)',
'__updateContentWidth(contentWidth, _overlayElement)',
'__openedOrTargetChanged(opened, target)',
'__overlayRoleOrTargetChanged(overlayRole, target)',
'__updateAriaAttributes(opened, overlayRole, target)',
];
}

Expand Down Expand Up @@ -578,27 +577,27 @@ class Popover extends PopoverPositionMixin(
}

/** @private */
__openedOrTargetChanged(opened, target) {
if (target) {
target.setAttribute('aria-expanded', opened ? 'true' : 'false');

if (opened) {
target.setAttribute('aria-controls', this.__overlayId);
} else {
target.removeAttribute('aria-controls');
}
}
}

/** @private */
__overlayRoleOrTargetChanged(overlayRole, target) {
__updateAriaAttributes(opened, overlayRole, target) {
if (this.__oldTarget) {
this.__oldTarget.removeAttribute('aria-haspopup');
const oldEffectiveTarget = this.__oldTarget.ariaTarget || this.__oldTarget;
oldEffectiveTarget.removeAttribute('aria-haspopup');
oldEffectiveTarget.removeAttribute('aria-expanded');
oldEffectiveTarget.removeAttribute('aria-controls');
}

if (target) {
const effectiveTarget = target.ariaTarget || target;

const isDialog = overlayRole === 'dialog' || overlayRole === 'alertdialog';
target.setAttribute('aria-haspopup', isDialog ? 'dialog' : 'true');
effectiveTarget.setAttribute('aria-haspopup', isDialog ? 'dialog' : 'true');

effectiveTarget.setAttribute('aria-expanded', opened ? 'true' : 'false');

if (opened) {
effectiveTarget.setAttribute('aria-controls', this.__overlayId);
} else {
effectiveTarget.removeAttribute('aria-controls');
}

this.__oldTarget = target;
}
Expand Down
129 changes: 78 additions & 51 deletions packages/popover/test/a11y.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,57 +49,84 @@ describe('a11y', () => {
expect(overlay.getAttribute('role')).to.equal('alertdialog');
});

it('should set aria-haspopup attribute on the target', () => {
expect(target.getAttribute('aria-haspopup')).to.equal('dialog');
});

it('should keep aria-haspopup attribute when overlayRole is set to alertdialog', async () => {
popover.overlayRole = 'alertdialog';
await nextUpdate(popover);
expect(target.getAttribute('aria-haspopup')).to.equal('dialog');
});

it('should update aria-haspopup attribute when overlayRole is set to different value', async () => {
popover.overlayRole = 'menu';
await nextUpdate(popover);
expect(target.getAttribute('aria-haspopup')).to.equal('true');
});

it('should remove aria-haspopup attribute when target is cleared', async () => {
popover.target = null;
await nextUpdate(popover);
expect(target.hasAttribute('aria-haspopup')).to.be.false;
});

it('should remove aria-controls attribute when target is cleared', async () => {
popover.target = null;
await nextUpdate(popover);
expect(target.hasAttribute('aria-haspopup')).to.be.false;
});

it('should set aria-expanded attribute on the target when closed', () => {
expect(target.getAttribute('aria-expanded')).to.equal('false');
});

it('should set aria-expanded attribute on the target when opened', async () => {
popover.opened = true;
await nextRender();
expect(target.getAttribute('aria-expanded')).to.equal('true');
});

it('should set aria-controls attribute on the target when opened', async () => {
popover.opened = true;
await nextRender();
expect(target.getAttribute('aria-controls')).to.equal(overlay.id);
});

it('should remove aria-controls attribute from the target when closed', async () => {
popover.opened = true;
await nextRender();

popover.opened = false;
await nextUpdate(popover);
expect(target.hasAttribute('aria-controls')).to.be.false;
['target', 'ariaTarget'].forEach((prop) => {
describe(prop, () => {
let element;

beforeEach(async () => {
if (prop === 'ariaTarget') {
target = fixtureSync('<div><input></div>');
popover.target = target;
element = target.firstElementChild;
target.ariaTarget = element;
await nextUpdate(popover);
} else {
element = target;
}
});

it(`should set aria-haspopup attribute on the ${prop}`, () => {
expect(element.getAttribute('aria-haspopup')).to.equal('dialog');
});

it(`should keep aria-haspopup attribute on the ${prop} when overlayRole is set to alertdialog`, async () => {
popover.overlayRole = 'alertdialog';
await nextUpdate(popover);
expect(element.getAttribute('aria-haspopup')).to.equal('dialog');
});

it(`should update aria-haspopup attribute on the ${prop} when overlayRole is set to different value`, async () => {
popover.overlayRole = 'menu';
await nextUpdate(popover);
expect(element.getAttribute('aria-haspopup')).to.equal('true');
});

it(`should set aria-expanded attribute on the ${prop} when closed`, () => {
expect(element.getAttribute('aria-expanded')).to.equal('false');
});

it(`should set aria-expanded attribute on the ${prop} when opened`, async () => {
popover.opened = true;
await nextRender();
expect(element.getAttribute('aria-expanded')).to.equal('true');
});

it(`should set aria-controls attribute on the ${prop} when opened`, async () => {
popover.opened = true;
await nextRender();
expect(element.getAttribute('aria-controls')).to.equal(overlay.id);
});

it(`should remove aria-controls attribute from the ${prop} when closed`, async () => {
popover.opened = true;
await nextRender();

popover.opened = false;
await nextUpdate(popover);
expect(element.hasAttribute('aria-controls')).to.be.false;
});

it(`should remove aria-haspopup attribute from ${prop} when target is cleared`, async () => {
popover.target = null;
await nextUpdate(popover);
expect(element.hasAttribute('aria-haspopup')).to.be.false;
});

it(`should remove aria-controls attribute from ${prop} when target is cleared`, async () => {
popover.opened = true;
await nextRender();

popover.target = null;
await nextUpdate(popover);
expect(element.hasAttribute('aria-controls')).to.be.false;
});

it(`should remove aria-expanded attribute from ${prop} when target is cleared`, async () => {
popover.target = null;
await nextUpdate(popover);
expect(element.hasAttribute('aria-expanded')).to.be.false;
});
});
});
});

Expand Down

0 comments on commit c48e044

Please sign in to comment.