Skip to content

Commit 186ccb9

Browse files
SlimDumbledodgentarocco
authored andcommitted
pagination: activate page items with Space key
* Fixes page items not being activated by the "Space" key when focused via keyboard navigation. (closes #295) * Page items are rendered by semantic-ui-react as anchors without href, with keyboard activation wired up manually for "Enter" only. Per the ARIA APG button pattern, elements behaving as buttons must be activatable with both "Enter" and "Space". * Adds a custom pageItem render function that invokes the injected onClick on "Space" keydown, prevents the default page scroll, and adds role="button" for assistive technologies.
1 parent 9903418 commit 186ccb9

2 files changed

Lines changed: 81 additions & 1 deletion

File tree

src/lib/components/Pagination/Pagination.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: 2018-2022 CERN.
2+
* SPDX-FileCopyrightText: 2018-2026 CERN.
33
* SPDX-License-Identifier: MIT
44
*/
55

@@ -10,6 +10,21 @@ import { Pagination as Paginator } from "semantic-ui-react";
1010
import { AppContext } from "../ReactSearchKit";
1111
import { ShouldRender } from "../ShouldRender";
1212

13+
const a11yPaginationItem = (Item, itemProps) => (
14+
<Item
15+
{...itemProps}
16+
role="button"
17+
onKeyDown={(event) => {
18+
// ARIA button pattern: activate on "Space" in addition to "Enter".
19+
// Mirrors the existing "Enter" handling in semantic-ui-react's PaginationItem.
20+
if (event.key === " " && !itemProps.disabled) {
21+
event.preventDefault(); // prevent page scroll
22+
itemProps.onClick(event, itemProps);
23+
}
24+
}}
25+
/>
26+
);
27+
1328
const defaultOptions = {
1429
boundaryRangeCount: 1,
1530
siblingRangeCount: 1,
@@ -141,6 +156,7 @@ const Element = ({
141156
lastItem={showLast ? undefined : null}
142157
prevItem={showPrev ? undefined : null}
143158
nextItem={showNext ? undefined : null}
159+
pageItem={a11yPaginationItem}
144160
size={size}
145161
{...props}
146162
/>
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2026 CERN.
3+
* SPDX-License-Identifier: MIT
4+
*/
5+
import { mount } from "enzyme";
6+
import React from "react";
7+
import { AppContext } from "../ReactSearchKit/AppContext";
8+
import Pagination from "./Pagination";
9+
10+
describe("test Pagination component", () => {
11+
const mountPagination = (mockUpdateQueryPage) =>
12+
mount(
13+
<AppContext.Provider value={{ buildUID: (id) => id }}>
14+
<Pagination
15+
currentPage={1}
16+
currentSize={10}
17+
loading={false}
18+
totalResults={50}
19+
updateQueryPage={mockUpdateQueryPage}
20+
/>
21+
</AppContext.Provider>
22+
);
23+
24+
const findPageItem = (wrapper, page) =>
25+
wrapper
26+
.find("a.item")
27+
.filterWhere(
28+
(node) => node.prop("value") === page && node.prop("type") === "pageItem"
29+
);
30+
31+
it("should change page when 'Space' is pressed on a page item", () => {
32+
const mockUpdateQueryPage = jest.fn();
33+
const mockPreventDefault = jest.fn();
34+
const wrapper = mountPagination(mockUpdateQueryPage);
35+
36+
findPageItem(wrapper, 2).simulate("keydown", {
37+
key: " ",
38+
preventDefault: mockPreventDefault,
39+
});
40+
41+
expect(mockUpdateQueryPage).toHaveBeenCalledWith(2);
42+
// page scroll on "Space" should be prevented
43+
expect(mockPreventDefault).toHaveBeenCalled();
44+
});
45+
46+
it("should still change page when 'Enter' is pressed on a page item", () => {
47+
const mockUpdateQueryPage = jest.fn();
48+
const wrapper = mountPagination(mockUpdateQueryPage);
49+
50+
findPageItem(wrapper, 3).simulate("keydown", {
51+
key: "Enter",
52+
keyCode: 13,
53+
which: 13,
54+
});
55+
56+
expect(mockUpdateQueryPage).toHaveBeenCalledWith(3);
57+
});
58+
59+
it("should expose page items with a 'button' role", () => {
60+
const wrapper = mountPagination(jest.fn());
61+
62+
expect(findPageItem(wrapper, 2).prop("role")).toEqual("button");
63+
});
64+
});

0 commit comments

Comments
 (0)