From e3a1131ada731777d3ec64193b34c429e29ea502 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ama=C3=ABl=20Rosales?= <137775508+Rosales-Amael@users.noreply.github.com> Date: Fri, 12 Jun 2026 15:31:54 +0200 Subject: [PATCH 1/2] 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. --- package-lock.json | 18 ------ src/lib/components/Pagination/Pagination.js | 16 +++++ .../components/Pagination/Pagination.test.js | 64 +++++++++++++++++++ 3 files changed, 80 insertions(+), 18 deletions(-) create mode 100644 src/lib/components/Pagination/Pagination.test.js diff --git a/package-lock.json b/package-lock.json index 52e1cb33..7bdf13c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21603,24 +21603,6 @@ } } }, - "node_modules/tailwindcss/node_modules/yaml": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", - "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, "node_modules/tapable": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.2.tgz", diff --git a/src/lib/components/Pagination/Pagination.js b/src/lib/components/Pagination/Pagination.js index 1ce3d00b..777b46ce 100644 --- a/src/lib/components/Pagination/Pagination.js +++ b/src/lib/components/Pagination/Pagination.js @@ -10,6 +10,21 @@ import { Pagination as Paginator } from "semantic-ui-react"; import { AppContext } from "../ReactSearchKit"; import { ShouldRender } from "../ShouldRender"; +const a11yPaginationItem = (Item, itemProps) => ( + { + // ARIA button pattern: activate on "Space" in addition to "Enter". + // Mirrors the existing "Enter" handling in semantic-ui-react's PaginationItem. + if (event.key === " " && !itemProps.disabled) { + event.preventDefault(); // prevent page scroll + itemProps.onClick(event, itemProps); + } + }} + /> +); + const defaultOptions = { boundaryRangeCount: 1, siblingRangeCount: 1, @@ -141,6 +156,7 @@ const Element = ({ lastItem={showLast ? undefined : null} prevItem={showPrev ? undefined : null} nextItem={showNext ? undefined : null} + pageItem={a11yPaginationItem} size={size} {...props} /> diff --git a/src/lib/components/Pagination/Pagination.test.js b/src/lib/components/Pagination/Pagination.test.js new file mode 100644 index 00000000..20d0672b --- /dev/null +++ b/src/lib/components/Pagination/Pagination.test.js @@ -0,0 +1,64 @@ +/* + * SPDX-FileCopyrightText: 2026 CERN. + * SPDX-License-Identifier: MIT + */ +import { mount } from "enzyme"; +import React from "react"; +import { AppContext } from "../ReactSearchKit/AppContext"; +import Pagination from "./Pagination"; + +describe("test Pagination component", () => { + const mountPagination = (mockUpdateQueryPage) => + mount( + id }}> + + + ); + + const findPageItem = (wrapper, page) => + wrapper + .find("a.item") + .filterWhere( + (node) => node.prop("value") === page && node.prop("type") === "pageItem" + ); + + it("should change page when 'Space' is pressed on a page item", () => { + const mockUpdateQueryPage = jest.fn(); + const mockPreventDefault = jest.fn(); + const wrapper = mountPagination(mockUpdateQueryPage); + + findPageItem(wrapper, 2).simulate("keydown", { + key: " ", + preventDefault: mockPreventDefault, + }); + + expect(mockUpdateQueryPage).toHaveBeenCalledWith(2); + // page scroll on "Space" should be prevented + expect(mockPreventDefault).toHaveBeenCalled(); + }); + + it("should still change page when 'Enter' is pressed on a page item", () => { + const mockUpdateQueryPage = jest.fn(); + const wrapper = mountPagination(mockUpdateQueryPage); + + findPageItem(wrapper, 3).simulate("keydown", { + key: "Enter", + keyCode: 13, + which: 13, + }); + + expect(mockUpdateQueryPage).toHaveBeenCalledWith(3); + }); + + it("should expose page items with a 'button' role", () => { + const wrapper = mountPagination(jest.fn()); + + expect(findPageItem(wrapper, 2).prop("role")).toEqual("button"); + }); +}); From 98cbcbdd68a383a95ac4705f8ad1fd9078c85024 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ama=C3=ABl=20Rosales?= <137775508+Rosales-Amael@users.noreply.github.com> Date: Fri, 12 Jun 2026 15:31:54 +0200 Subject: [PATCH 2/2] 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. --- package-lock.json | 18 ------ src/lib/components/Pagination/Pagination.js | 18 +++++- .../components/Pagination/Pagination.test.js | 64 +++++++++++++++++++ 3 files changed, 81 insertions(+), 19 deletions(-) create mode 100644 src/lib/components/Pagination/Pagination.test.js diff --git a/package-lock.json b/package-lock.json index 52e1cb33..7bdf13c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21603,24 +21603,6 @@ } } }, - "node_modules/tailwindcss/node_modules/yaml": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", - "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, "node_modules/tapable": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.2.tgz", diff --git a/src/lib/components/Pagination/Pagination.js b/src/lib/components/Pagination/Pagination.js index 1ce3d00b..4d87409d 100644 --- a/src/lib/components/Pagination/Pagination.js +++ b/src/lib/components/Pagination/Pagination.js @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2018-2022 CERN. + * SPDX-FileCopyrightText: 2018-2026 CERN. * SPDX-License-Identifier: MIT */ @@ -10,6 +10,21 @@ import { Pagination as Paginator } from "semantic-ui-react"; import { AppContext } from "../ReactSearchKit"; import { ShouldRender } from "../ShouldRender"; +const a11yPaginationItem = (Item, itemProps) => ( + { + // ARIA button pattern: activate on "Space" in addition to "Enter". + // Mirrors the existing "Enter" handling in semantic-ui-react's PaginationItem. + if (event.key === " " && !itemProps.disabled) { + event.preventDefault(); // prevent page scroll + itemProps.onClick(event, itemProps); + } + }} + /> +); + const defaultOptions = { boundaryRangeCount: 1, siblingRangeCount: 1, @@ -141,6 +156,7 @@ const Element = ({ lastItem={showLast ? undefined : null} prevItem={showPrev ? undefined : null} nextItem={showNext ? undefined : null} + pageItem={a11yPaginationItem} size={size} {...props} /> diff --git a/src/lib/components/Pagination/Pagination.test.js b/src/lib/components/Pagination/Pagination.test.js new file mode 100644 index 00000000..20d0672b --- /dev/null +++ b/src/lib/components/Pagination/Pagination.test.js @@ -0,0 +1,64 @@ +/* + * SPDX-FileCopyrightText: 2026 CERN. + * SPDX-License-Identifier: MIT + */ +import { mount } from "enzyme"; +import React from "react"; +import { AppContext } from "../ReactSearchKit/AppContext"; +import Pagination from "./Pagination"; + +describe("test Pagination component", () => { + const mountPagination = (mockUpdateQueryPage) => + mount( + id }}> + + + ); + + const findPageItem = (wrapper, page) => + wrapper + .find("a.item") + .filterWhere( + (node) => node.prop("value") === page && node.prop("type") === "pageItem" + ); + + it("should change page when 'Space' is pressed on a page item", () => { + const mockUpdateQueryPage = jest.fn(); + const mockPreventDefault = jest.fn(); + const wrapper = mountPagination(mockUpdateQueryPage); + + findPageItem(wrapper, 2).simulate("keydown", { + key: " ", + preventDefault: mockPreventDefault, + }); + + expect(mockUpdateQueryPage).toHaveBeenCalledWith(2); + // page scroll on "Space" should be prevented + expect(mockPreventDefault).toHaveBeenCalled(); + }); + + it("should still change page when 'Enter' is pressed on a page item", () => { + const mockUpdateQueryPage = jest.fn(); + const wrapper = mountPagination(mockUpdateQueryPage); + + findPageItem(wrapper, 3).simulate("keydown", { + key: "Enter", + keyCode: 13, + which: 13, + }); + + expect(mockUpdateQueryPage).toHaveBeenCalledWith(3); + }); + + it("should expose page items with a 'button' role", () => { + const wrapper = mountPagination(jest.fn()); + + expect(findPageItem(wrapper, 2).prop("role")).toEqual("button"); + }); +});