From 4abbc0254c6ed8217722a8c3de66a3634ccffb9d Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Tue, 2 Jun 2026 22:08:41 +0800 Subject: [PATCH] fix(reader): stop footer progress info painting a stray focus ring (#4397) (#4418) The always-on page-info footer (`.progressinfo`) is a decorative `role='presentation'` element, but it carried `tabIndex={-1}`, which made it focusable. On Android, long-pressing the footer focused the div and the WebView painted its default focus ring (`outline: auto`). Because the element is pinned `absolute bottom-0` at book-view width, the ring rendered as a content-column-wide line across the bottom of every page and persisted until focus cleared. Remove the `tabIndex` so the decorative element can no longer receive focus. The `onClick` (tap-to-cycle progress mode / dismiss popup) still fires regardless of tabindex, nothing focuses it programmatically, and the translated `aria-label` keeps it exposed to screen readers unchanged. Closes #4397 Co-authored-by: Claude Opus 4.8 (1M context) --- .../__tests__/components/ProgressBar.test.tsx | 23 +++++++++++++++++++ .../src/app/reader/components/ProgressBar.tsx | 1 - 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/apps/readest-app/src/__tests__/components/ProgressBar.test.tsx b/apps/readest-app/src/__tests__/components/ProgressBar.test.tsx index 7c481cfd..856aa706 100644 --- a/apps/readest-app/src/__tests__/components/ProgressBar.test.tsx +++ b/apps/readest-app/src/__tests__/components/ProgressBar.test.tsx @@ -178,3 +178,26 @@ describe('ProgressBar — fixed-layout remaining pages', () => { ); }); }); + +describe('ProgressBar — decorative footer is not focusable', () => { + it('does not make the progress info container focusable (no stray focus ring)', () => { + // The footer info is a decorative role="presentation" element. A negative + // tabindex made it focusable, so long-pressing it on Android focused the + // div and the WebView painted its default focus ring as a persistent line + // across the bottom of the page (issue #4397). A decorative element must + // not be focusable so it can never receive a focus ring. + currentViewSettings = { + ...baseSettings, + progressInfoMode: 'all', + } as ViewSettings; + currentProgress = makeProgress(2, 5); + currentBookData = { isFixedLayout: false }; + currentRenderer = { page: 1, pages: 4 }; + + const { container } = renderProgressBar(); + + const progressInfo = container.querySelector('.progressinfo'); + expect(progressInfo).not.toBeNull(); + expect(progressInfo!.hasAttribute('tabindex')).toBe(false); + }); +}); diff --git a/apps/readest-app/src/app/reader/components/ProgressBar.tsx b/apps/readest-app/src/app/reader/components/ProgressBar.tsx index 514140a4..0c1d2467 100644 --- a/apps/readest-app/src/app/reader/components/ProgressBar.tsx +++ b/apps/readest-app/src/app/reader/components/ProgressBar.tsx @@ -188,7 +188,6 @@ const ProgressBar: React.FC = ({ return (