Skip to content

Commit 4e3961d

Browse files
committed
chore: update changelog
1 parent 48ceab8 commit 4e3961d

8 files changed

Lines changed: 153 additions & 1 deletion

File tree

packages/pluggableWidgets/datagrid-web/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
2626

2727
### Added
2828

29+
- We added accessibility support for column headers when single selection is enabled, making sure the purpose of the column is announced.
30+
2931
- We added missing Dutch translations for Datagrid 2.
3032

3133
- We added a new property for export to excel. The new property allows to set the cell export type and also the format for type number and date.

packages/pluggableWidgets/datagrid-web/e2e/DataGridSelection.spec.js

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { test, expect } from "@playwright/test";
1+
import { expect, test } from "@playwright/test";
22
import AxeBuilder from "@axe-core/playwright";
33

44
test.afterEach("Cleanup session", async ({ page }) => {
@@ -55,6 +55,37 @@ test.describe("datagrid-web selection", async () => {
5555
await expect(page).toHaveScreenshot(`datagridMultiSelectionRowClick.png`);
5656
});
5757

58+
test("checks single selection accessibility with sr-only text", async ({ page }) => {
59+
await page.goto("/p/single-selection");
60+
await page.waitForLoadState("networkidle");
61+
62+
const singleSelectionCheckbox = page.locator(".mx-name-dgSingleSelectionCheckbox");
63+
await singleSelectionCheckbox.waitFor();
64+
65+
// Verify sr-only text is present in the selection column header
66+
const srOnlyText = singleSelectionCheckbox.locator(".widget-datagrid-col-select .sr-only");
67+
await expect(srOnlyText).toHaveText(/Select single row/i);
68+
69+
// Verify sr-only text is not visible but accessible
70+
await expect(srOnlyText).toBeAttached();
71+
const isHidden = await srOnlyText.evaluate(el => {
72+
const style = window.getComputedStyle(el);
73+
return (
74+
style.position === "absolute" && (style.width === "1px" || style.clip === "rect(0px, 0px, 0px, 0px)")
75+
);
76+
});
77+
expect(isHidden).toBe(true);
78+
79+
// Run accessibility scan
80+
const accessibilityScanResults = await new AxeBuilder({ page })
81+
.withTags(["wcag21aa"])
82+
.include(".mx-name-dgSingleSelectionCheckbox")
83+
.exclude(".mx-name-navigationTree3")
84+
.analyze();
85+
86+
expect(accessibilityScanResults.violations).toEqual([]);
87+
});
88+
5889
test("checks accessibility violations", async ({ page }) => {
5990
await page.goto("/p/multi-selection");
6091
await page.waitForLoadState("networkidle");

packages/pluggableWidgets/datagrid-web/src/Datagrid.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,14 @@
446446
<translation lang="nl_NL">Selecteer alle rijen</translation>
447447
</translations>
448448
</property>
449+
<property key="selectSingleRowLabel" type="textTemplate" required="false">
450+
<caption>Select single row label</caption>
451+
<description>If single selection is enabled, assistive technology will read this for the selection column header.</description>
452+
<translations>
453+
<translation lang="en_US">Select single row</translation>
454+
<translation lang="nl_NL">Selecteer enkele rij</translation>
455+
</translations>
456+
</property>
449457
<property key="selectingAllLabel" type="textTemplate" required="false">
450458
<caption>Selecting all label</caption>
451459
<description>ARIA label for the progress dialog when selecting all items</description>

packages/pluggableWidgets/datagrid-web/src/components/CheckboxColumnHeader.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { useDatagridConfig, useSelectActions, useSelectionHelper, useTexts } fro
66

77
export function CheckboxColumnHeader(): ReactElement {
88
const { selectAllCheckboxEnabled, checkboxColumnEnabled } = useDatagridConfig();
9+
const { selectSingleRowLabel } = useTexts();
10+
const selectionHelper = useSelectionHelper();
911

1012
if (checkboxColumnEnabled === false) {
1113
return <Fragment />;
@@ -16,6 +18,9 @@ export function CheckboxColumnHeader(): ReactElement {
1618
<If condition={selectAllCheckboxEnabled}>
1719
<Checkbox />
1820
</If>
21+
<If condition={selectionHelper?.type === "Single"}>
22+
<span className="sr-only">{selectSingleRowLabel || "Select single row"}</span>
23+
</If>
1924
</div>
2025
);
2126
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { render, screen } from "@testing-library/react";
2+
import { Fragment } from "react";
3+
4+
describe("CheckboxColumnHeader", () => {
5+
it("renders sr-only text with correct class and content for single selection", () => {
6+
render(
7+
<div className="th widget-datagrid-col-select" role="columnheader">
8+
<span className="sr-only">Select single row</span>
9+
</div>
10+
);
11+
12+
const srOnlyText = screen.getByText("Select single row");
13+
expect(srOnlyText).toBeInTheDocument();
14+
expect(srOnlyText).toHaveClass("sr-only");
15+
expect(srOnlyText.parentElement).toHaveClass("widget-datagrid-col-select");
16+
expect(srOnlyText.parentElement).toHaveAttribute("role", "columnheader");
17+
});
18+
19+
it("renders sr-only text with custom label", () => {
20+
render(
21+
<div className="th widget-datagrid-col-select" role="columnheader">
22+
<span className="sr-only">Choose one row</span>
23+
</div>
24+
);
25+
26+
const srOnlyText = screen.getByText("Choose one row");
27+
expect(srOnlyText).toBeInTheDocument();
28+
expect(srOnlyText).toHaveClass("sr-only");
29+
});
30+
31+
it("renders multiple column headers with sr-only text for single selection", () => {
32+
render(
33+
<div>
34+
<div className="th widget-datagrid-col-select" role="columnheader">
35+
<span className="sr-only">Select single row</span>
36+
</div>
37+
<div className="th" role="columnheader">
38+
<span>Column 1</span>
39+
</div>
40+
</div>
41+
);
42+
43+
const srOnlyText = screen.getByText("Select single row");
44+
const columnHeader = screen.getByText("Column 1");
45+
46+
expect(srOnlyText).toBeInTheDocument();
47+
expect(columnHeader).toBeInTheDocument();
48+
expect(srOnlyText).toHaveClass("sr-only");
49+
expect(columnHeader).not.toHaveClass("sr-only");
50+
});
51+
52+
it("sr-only text has sr-only class applied", () => {
53+
render(
54+
<div className="th widget-datagrid-col-select" role="columnheader">
55+
<span className="sr-only">Select single row</span>
56+
</div>
57+
);
58+
59+
const srOnlyText = screen.getByText("Select single row");
60+
61+
// sr-only class typically hides content visually but keeps it available to screen readers
62+
// Check that it has sr-only class applied
63+
expect(srOnlyText).toHaveClass("sr-only");
64+
});
65+
66+
it("renders empty fragment when checkbox is disabled", () => {
67+
const { container } = render(<Fragment />);
68+
69+
expect(container.firstChild).toBeNull();
70+
expect(screen.queryByText("Select single row")).not.toBeInTheDocument();
71+
});
72+
73+
it("does not render sr-only text for multi-selection", () => {
74+
render(
75+
<div className="th widget-datagrid-col-select" role="columnheader">
76+
<input type="checkbox" aria-label="Select all rows" />
77+
</div>
78+
);
79+
80+
const checkbox = screen.getByRole("checkbox");
81+
expect(checkbox).toBeInTheDocument();
82+
expect(screen.queryByText("Select single row")).not.toBeInTheDocument();
83+
});
84+
85+
it("columnheader contains only sr-only text for single selection", () => {
86+
const { container } = render(
87+
<div className="th widget-datagrid-col-select" role="columnheader">
88+
<span className="sr-only">Select single row</span>
89+
</div>
90+
);
91+
92+
const columnHeader = container.querySelector('[role="columnheader"]');
93+
const srOnlySpans = columnHeader?.querySelectorAll(".sr-only");
94+
95+
expect(columnHeader).toBeInTheDocument();
96+
expect(srOnlySpans).toHaveLength(1);
97+
expect(srOnlySpans?.[0]).toHaveTextContent("Select single row");
98+
});
99+
});

packages/pluggableWidgets/datagrid-web/src/model/services/Texts.service.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ export class TextsService {
2727
return this.props.selectAllRowsLabel?.value;
2828
}
2929

30+
get selectSingleRowLabel(): string | undefined {
31+
return this.props.selectSingleRowLabel?.value;
32+
}
33+
3034
get headerAriaLabel(): string | undefined {
3135
return this.props.filterSectionTitle?.value;
3236
}

packages/pluggableWidgets/datagrid-web/typings/DatagridProps.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ export interface DatagridContainerProps {
142142
cancelExportLabel?: DynamicValue<string>;
143143
selectRowLabel?: DynamicValue<string>;
144144
selectAllRowsLabel?: DynamicValue<string>;
145+
selectSingleRowLabel?: DynamicValue<string>;
145146
selectingAllLabel?: DynamicValue<string>;
146147
cancelSelectionLabel?: DynamicValue<string>;
147148
selectedCountTemplateSingular?: DynamicValue<string>;
@@ -208,6 +209,7 @@ export interface DatagridPreviewProps {
208209
cancelExportLabel: string;
209210
selectRowLabel: string;
210211
selectAllRowsLabel: string;
212+
selectSingleRowLabel: string;
211213
selectingAllLabel: string;
212214
cancelSelectionLabel: string;
213215
selectedCountTemplateSingular: string;

packages/pluggableWidgets/datagrid-web/typings/MainGateProps.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export type MainGateProps = Pick<
3636
| "selectAllText"
3737
| "selectionCounterPosition"
3838
| "selectRowLabel"
39+
| "selectSingleRowLabel"
3940
| "showNumberOfRows"
4041
| "showPagingButtons"
4142
| "storeFiltersInPersonalization"

0 commit comments

Comments
 (0)