Skip to content

Commit 580f209

Browse files
committed
web: Add ARIA fixes, live region reporting.
1 parent 1f3ced4 commit 580f209

4 files changed

Lines changed: 71 additions & 37 deletions

File tree

web/src/elements/router/RouterOutlet.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ export class RouterOutlet extends AKElement {
6969

7070
//#region Properties
7171

72+
public override role = "presentation";
73+
7274
@property({ attribute: false })
7375
current?: RouteMatch;
7476

web/src/user/LibraryApplication/index.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@ export interface AKLibraryAppProps extends HTMLAttributes<HTMLDivElement> {
2828
application?: Application;
2929
editURL?: string | URL | null;
3030
background?: string | null;
31-
appIndex: number;
32-
groupIndex: number;
3331
targetRef?: RefOrCallback | null;
3432
}
3533

@@ -69,7 +67,10 @@ export const AKLibraryApp: LitFC<AKLibraryAppProps> = ({
6967
const primaryRef = targetRef ? ref(targetRef) : nothing;
7068

7169
const extendedProps = {
72-
"aria-label": msg(str`Open "${application.name}"`),
70+
"aria-label": msg(str`Open "${application.name}"`, {
71+
id: "library.application.card.aria-label",
72+
desc: "Screen reader label for the application card",
73+
}),
7374
"tabindex": "0",
7475
"class": "card-header-aspect-wrapper",
7576
"title": ifPresent(application.name),
@@ -80,9 +81,7 @@ export const AKLibraryApp: LitFC<AKLibraryAppProps> = ({
8081
return html`<div
8182
part="card-wrapper"
8283
data-application-name=${ifPresent(dataID)}
83-
aria-describedby=${descriptionID}
8484
style=${styleMap({ background: background || null })}
85-
${spread(props)}
8685
>
8786
<div part="card" class="pf-c-card pf-m-hoverable pf-m-compact ${classMap(classes)}">
8887
<ak-app-icon
@@ -95,6 +94,7 @@ export const AKLibraryApp: LitFC<AKLibraryAppProps> = ({
9594
? html`<div
9695
${primaryRef}
9796
role="button"
97+
aria-describedby=${descriptionID}
9898
@click=${launchModal}
9999
${spread(extendedProps)}
100100
>
@@ -104,6 +104,7 @@ export const AKLibraryApp: LitFC<AKLibraryAppProps> = ({
104104
${primaryRef}
105105
href=${ifPresent(application.launchUrl)}
106106
target=${ifPresent(application.openInNewTab, "_blank")}
107+
aria-describedby=${descriptionID}
107108
${spread(extendedProps)}
108109
>${cardHeader}</a
109110
>`}

web/src/user/LibraryPage/ApplicationList.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { kebabCase } from "change-case";
1717
import { HTMLAttributes } from "react";
1818

1919
import { msg } from "@lit/localize";
20-
import { html, nothing } from "lit";
20+
import { html } from "lit";
2121
import { RefOrCallback } from "lit/directives/ref.js";
2222
import { repeat } from "lit/directives/repeat.js";
2323

@@ -78,7 +78,7 @@ export const AKLibraryApplicationList: LitFC<AKLibraryApplicationListProps> = ({
7878
${repeat(
7979
apps,
8080
(application) => application.pk,
81-
(application, appIndex) => {
81+
(application) => {
8282
const selected = selectedApp === application;
8383
8484
const editURL = editable
@@ -87,13 +87,9 @@ export const AKLibraryApplicationList: LitFC<AKLibraryApplicationListProps> = ({
8787
8888
return AKLibraryApp({
8989
application,
90-
appIndex,
91-
groupIndex,
9290
background,
9391
editURL,
94-
"targetRef": selected ? targetRef : null,
95-
"aria-selected": selected,
96-
"aria-current": selected ? "true" : undefined,
92+
targetRef: selected ? targetRef : null,
9793
});
9894
},
9995
)}

web/src/user/LibraryPage/ak-library-impl.ts

Lines changed: 60 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import Fuse from "fuse.js";
2626
import { msg, str } from "@lit/localize";
2727
import { html, nothing, PropertyValues } from "lit";
2828
import { customElement, property, state } from "lit/decorators.js";
29+
import { guard } from "lit/directives/guard.js";
2930
import { createRef } from "lit/directives/ref.js";
3031

3132
import PFButton from "@patternfly/patternfly/components/Button/button.css";
@@ -324,8 +325,21 @@ export class LibraryPage extends WithSession(AKElement) {
324325
placeholder=${msg("Search for an application by name...")}
325326
value=${ifPresent(this.query)}
326327
list=${ifPresent(LibraryPage.DataListEnabled, "application-search-options")}
328+
aria-describedby="search-action-hint"
327329
/>
328330
${this.renderDataList()}
331+
332+
<span id="search-action-hint" class="sr-only">
333+
${this.selectedApp
334+
? msg(str`Press Enter to open ${this.selectedApp.name}`, {
335+
id: "user.library.search.enter-to-open-hint",
336+
desc: "Screen reader hint to inform the user they can open the selected application by pressing Enter",
337+
})
338+
: msg("Type to filter applications", {
339+
id: "user.library.search.type-to-filter-hint",
340+
desc: "Screen reader hint to inform the user they can filter the application list by typing",
341+
})}
342+
</span>
329343
</form>
330344
</search>`;
331345
}
@@ -367,27 +381,56 @@ export class LibraryPage extends WithSession(AKElement) {
367381
return this.renderNoAppsFound();
368382
}
369383

370-
public override render() {
384+
protected renderApplicationStatusOutput() {
371385
const count = this.visibleApplications.length;
372386
const { query } = this;
373387

374-
let message: string;
388+
return guard([count, query], () => {
389+
let message: string;
390+
391+
if (query) {
392+
// We must present the count within the label to ensure that the screen reader
393+
// considers the update significant enough to read on each change,
394+
// rather than the on just the first render.
395+
message =
396+
count === 1
397+
? msg(str`${count} application found for "${query}"`, {
398+
id: "user.library.application-count-singular-with-query",
399+
})
400+
: msg(str`${count} applications found for "${query}"`, {
401+
id: "user.library.application-count-plural-with-query",
402+
});
403+
} else {
404+
message =
405+
count === 1
406+
? msg(str`${count} application available`, {
407+
id: "user.library.application-count-singular",
408+
})
409+
: msg(str`${count} applications available`, {
410+
id: "user.library.application-count-plural",
411+
});
412+
}
375413

376-
if (query) {
377-
// We must present the count within the label to ensure that the screen reader
378-
// considers the update significant enough to read on each change,
379-
// rather than the on just the first render.
380-
message =
381-
count === 1
382-
? msg(str`${count} application found for "${query}"`)
383-
: msg(str`${count} applications found for "${query}"`);
384-
} else {
385-
message =
386-
count === 1
387-
? msg(str`${count} application available`)
388-
: msg(str`${count} applications available`);
389-
}
414+
return html`<output
415+
class="sr-only"
416+
for="application-search-input"
417+
form="application-search-form"
418+
aria-live="polite"
419+
>
420+
<p>${message}</p>
421+
<p>
422+
${this.selectedApp
423+
? msg(str`Press Enter to open ${this.selectedApp.name}`, {
424+
id: "user.library.application-count.enter-to-open-hint",
425+
desc: "Screen reader hint to inform the user they can open the selected application by pressing Enter",
426+
})
427+
: nothing}
428+
</p>
429+
</output>`;
430+
});
431+
}
390432

433+
protected override render() {
391434
return html`<div class="pf-c-page__main">
392435
<div class="pf-c-page__header pf-c-content">
393436
<h1 class="pf-c-page__title">${msg("My applications")}</h1>
@@ -399,15 +442,7 @@ export class LibraryPage extends WithSession(AKElement) {
399442
class="pf-c-page__main-section"
400443
aria-label=${msg("Application list")}
401444
>
402-
<output
403-
class="sr-only"
404-
for="application-search-input"
405-
form="application-search-form"
406-
aria-live="polite"
407-
>
408-
<p>${message}</p>
409-
</output>
410-
${this.renderState()}
445+
${this.renderApplicationStatusOutput} ${this.renderState()}
411446
</main>
412447
</div>`;
413448
}

0 commit comments

Comments
 (0)