Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(tooltip): announce content to assistive technology #2069

Open
wants to merge 27 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
75fea5d
feat(tooltip): add ESC to close functionality
adamjohnson Nov 25, 2024
ee1ffe3
refactor(tooltip): rename CSS class
adamjohnson Nov 25, 2024
5a52b82
fix(tooltip): add `aria-describedby`/`id` pair
adamjohnson Nov 26, 2024
34e2a63
chore(tooltip): add changeset
adamjohnson Nov 26, 2024
5ae835d
fix(tooltip): remove slotchange handler functions
adamjohnson Dec 2, 2024
d9a66f9
fix(tooltip): use native `<button>` with `aria-describedby`/ IDREF pair
adamjohnson Dec 2, 2024
dfe4fdb
docs(tooltip): add a11y markup guidance around `<button>`/`<rh-button>`
adamjohnson Dec 2, 2024
3e12c51
fix(tooltip): attach event listener to `globalThis` for improved perf…
adamjohnson Dec 3, 2024
a46cac0
Merge branch 'main' into fix/tooltip/a11y-announce-content
adamjohnson Dec 3, 2024
776ba7a
Merge branch 'main' into fix/tooltip/a11y-announce-content
adamjohnson Dec 6, 2024
9bf1a1c
fix(tooltip): announce content
bennypowers Dec 8, 2024
ab9017e
Merge branch 'main' into fix/tooltip/a11y-announce-content
adamjohnson Dec 9, 2024
cbc5dc7
Merge branch 'main' into fix/tooltip/a11y-announce-content
bennypowers Dec 10, 2024
2fa8d8e
fix(tooltip): global announcer
bennypowers Dec 10, 2024
91d1e55
test(tooltip): ax queries
bennypowers Dec 10, 2024
9720690
fix(tooltip): surrender to compilers
bennypowers Dec 10, 2024
e684697
fix(tooltip): crossbrowser aria
bennypowers Dec 10, 2024
be61a06
docs(tooltip): remove test icon
bennypowers Dec 10, 2024
b33e509
fix(tooltip): revert regression
bennypowers Dec 10, 2024
5dfe772
perf(tooltip): shave bytes
bennypowers Dec 10, 2024
4e9dd1f
Merge branch 'main' into fix/tooltip/a11y-announce-content
adamjohnson Dec 16, 2024
357d4a1
fix(tooltip): use logical properties for announcer
adamjohnson Dec 17, 2024
e24478e
docs(tooltip): remove outdated a11y docs
adamjohnson Dec 17, 2024
a9ce72a
fix(tooltip): re-add `<rh-button>` to tooltip demos
adamjohnson Dec 17, 2024
d2bda3f
Merge branch 'main' into fix/tooltip/a11y-announce-content
adamjohnson Dec 18, 2024
ee2bdea
fix(tooltip): prune unused class
adamjohnson Dec 18, 2024
cea398a
fix(tooltip): change `aria-live` to `role: status`
adamjohnson Dec 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/brown-fans-poke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rhds/elements": patch
---

`<rh-tooltip>`: make tooltip content available to assistive technology
9 changes: 9 additions & 0 deletions elements/rh-tooltip/demo/content-attributes.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<rh-tooltip content="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua. Mi eget mauris pharetra et ultrices.">
<rh-button>Tooltip</rh-button>
</rh-tooltip>

<script type="module">
import '@rhds/elements/rh-button/rh-button.js';
import '@rhds/elements/rh-tooltip/rh-tooltip.js';
</script>
1 change: 0 additions & 1 deletion elements/rh-tooltip/demo/rh-tooltip.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,3 @@
import '@rhds/elements/rh-button/rh-button.js';
import '@rhds/elements/rh-tooltip/rh-tooltip.js';
</script>

4 changes: 0 additions & 4 deletions elements/rh-tooltip/rh-tooltip.css
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,6 @@
var(--rh-tooltip__content--BackgroundColor, var(--rh-color-surface-darkest, #151515)));
}

.c {
display: contents;
}

.open #tooltip {
opacity: 1;
}
Expand Down
67 changes: 61 additions & 6 deletions elements/rh-tooltip/rh-tooltip.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { html, LitElement } from 'lit';
import { html, LitElement, isServer } from 'lit';
import { customElement } from 'lit/decorators/custom-element.js';
import { property } from 'lit/decorators/property.js';
import { classMap } from 'lit/directives/class-map.js';
Expand Down Expand Up @@ -43,6 +43,41 @@ export class RhTooltip extends LitElement {

static readonly styles = [styles];

private static instances = new Set<RhTooltip>();

static {
if (!isServer) {
globalThis.addEventListener('keydown', (event: KeyboardEvent) => {
const { instances } = RhTooltip;
for (const instance of instances) {
instance.#onKeydown(event);
}
});
RhTooltip.initAnnouncer();
}
}

private static announcer: HTMLElement;

private static announce(message: string) {
this.announcer.innerText = message;
}

private static initAnnouncer() {
document.body.append((this.announcer = Object.assign(document.createElement('div'), {
role: 'status',
// apply `.visually-hidden` styles
style: /* css */`
position: fixed;
inset-block-start: 0;
inset-inline-start: 0;
overflow: hidden;
clip: rect(0,0,0,0);
white-space: nowrap;
border: 0;`,
})));
}

/** The position of the tooltip, relative to the invoking content */
@property() position: Placement = 'top';

Expand All @@ -57,10 +92,22 @@ export class RhTooltip extends LitElement {

#initialized = false;

get #content() {
if (!this.#float.open || isServer) {
return '';
} else {
return this.content || (this.shadowRoot
?.getElementById('content') as HTMLSlotElement)
?.assignedNodes().map(x => x.textContent ?? '')
?.join(' ');
}
}

override connectedCallback(): void {
super.connectedCallback();
ENTER_EVENTS.forEach(evt => this.addEventListener(evt, this.show));
EXIT_EVENTS.forEach(evt => this.addEventListener(evt, this.hide));
RhTooltip.instances.add(this);
}

override render() {
Expand All @@ -71,15 +118,15 @@ export class RhTooltip extends LitElement {
<div id="container"
style="${styleMap(styles)}"
class="${classMap({ open,
'initialized': !!this.#initialized,
initialized: !!this.#initialized,
[on]: !!on,
[anchor]: !!anchor,
[alignment]: !!alignment })}">
<div class="c" role="tooltip" aria-labelledby="tooltip">
<slot id="invoker"></slot>
<div id="invoker">
<slot id="invoker-slot"></slot>
</div>
<div class="c" aria-hidden="${String(!open) as 'true' | 'false'}">
<slot id="tooltip" name="content">${this.content}</slot>
<div id="tooltip" role="status">
<slot id="content" name="content">${this.content}</slot>
</div>
</div>
`;
Expand All @@ -94,12 +141,20 @@ export class RhTooltip extends LitElement {
: { mainAxis: 15, alignmentAxis: -4 };
await this.#float.show({ offset, placement });
this.#initialized ||= true;
RhTooltip.announce(this.#content);
}

/** Hide the tooltip */
async hide() {
await this.#float.hide();
RhTooltip.announcer.innerText = '';
}

#onKeydown = (event: KeyboardEvent): void => {
if (event.key === 'Escape') {
this.hide();
}
};
}

declare global {
Expand Down
6 changes: 2 additions & 4 deletions elements/rh-tooltip/test/rh-tooltip.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,8 @@ describe('<rh-tooltip>', function() {
beforeEach(() => sendMouseToTooltip(element));
it('content should be available to assistive technology', async function() {
await sendMouseToTooltip(element);
const snapshot = await a11ySnapshot();
expect(snapshot.children?.length).to.equal(1);
expect(snapshot.children?.at(0)?.role).to.equal('text');
expect(snapshot.children?.at(0)?.name).to.equal(content);
expect(await a11ySnapshot())
.to.have.axQuery({ role: 'text', name: content });
});
});
});
Expand Down
Loading