feat(tooltip): add invoker relation for accessibility

This commit is contained in:
Thijs Louisse 2020-06-06 15:15:09 +02:00 committed by qa46hx
parent dfe1905e7c
commit 3f84a3bab8
3 changed files with 61 additions and 2 deletions

View file

@ -80,6 +80,24 @@ import '@lion/tooltip/lion-tooltip.js';
## Examples
### invokerRelation
There is a difference between tooltips used as a primary label or as a description. In most cases a button will already have its own label, so the tooltip will be used as a description with extra information, which is already set as default. Only in case of icon buttons you want to use the tooltip as the primary label. To do so you need to set the `invokerRelation` to `label`.
> For detailed information please read: [inclusive tooltips](https://inclusive-components.design/tooltips-toggletips/#inclusivetooltips).
```js preview-story
export const invokerRelation = () => html`
<style>
${tooltipDemoStyles}
</style>
<lion-tooltip .config=${{ invokerRelation: 'label' }}>
<button slot="invoker" class="demo-tooltip-invoker">📅</button>
<div slot="content" class="demo-tooltip-content">Agenda<div>
</lion-tooltip>
`;
```
### Placements
You can easily change the placement of the content node relative to the invoker.

View file

@ -12,6 +12,10 @@ export class LionTooltip extends OverlayMixin(LitElement) {
reflect: true,
attribute: 'has-arrow',
},
invokerRelation: {
type: String,
attribute: 'invoker-relation',
},
};
}
@ -70,6 +74,17 @@ export class LionTooltip extends OverlayMixin(LitElement) {
constructor() {
super();
/**
* Whether an arrow should be displayed
* @type {boolean}
*/
this.hasArrow = false;
/**
* Decides whether the tooltip invoker text should be considered a description
* (sets aria-describedby) or a label (sets aria-labelledby).
* @type {'label'\'description'}
*/
this.invokerRelation = 'description';
this._mouseActive = false;
this._keyActive = false;
this.__setupRepositionCompletePromise();
@ -79,7 +94,6 @@ export class LionTooltip extends OverlayMixin(LitElement) {
if (super.connectedCallback) {
super.connectedCallback();
}
this._overlayContentNode.setAttribute('role', 'tooltip');
}
render() {
@ -128,8 +142,9 @@ export class LionTooltip extends OverlayMixin(LitElement) {
this.__syncFromPopperState(data);
},
},
isTooltip: true,
handlesAccessibility: true,
isTooltip: true,
invokerRelation: this.invokerRelation,
};
}

View file

@ -206,6 +206,32 @@ describe('lion-tooltip', () => {
expect(content.getAttribute('role')).to.be.equal('tooltip');
});
it('should have aria-describedby role set on the invoker', async () => {
const el = await fixture(html`
<lion-tooltip>
<div slot="content">Hey there</div>
<button slot="invoker">Tooltip button</button>
</lion-tooltip>
`);
const content = el.querySelector('[slot=content]');
const invoker = el.querySelector('[slot=invoker]');
expect(invoker.getAttribute('aria-describedby')).to.be.equal(content.id);
expect(invoker.getAttribute('aria-labelledby')).to.be.equal(null);
});
it('should have aria-labelledby role set on the invoker when [ invoker-relation="label"]', async () => {
const el = await fixture(html`
<lion-tooltip invoker-relation="label">
<div slot="content">Hey there</div>
<button slot="invoker">Tooltip button</button>
</lion-tooltip>
`);
const content = el.querySelector('[slot=content]');
const invoker = el.querySelector('[slot=invoker]');
expect(invoker.getAttribute('aria-describedby')).to.be.equal(null);
expect(invoker.getAttribute('aria-labelledby')).to.be.equal(content.id);
});
it('should be accessible when closed', async () => {
const el = await fixture(html`
<lion-tooltip>