From e037387093c26c2cae0d0fb54b85c26ebd34212e Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Mon, 12 Oct 2026 12:18:14 -0700 Subject: [PATCH 1/2] Label each window as a group for assistive technology FocusTarget wraps the contents of every window, so give it role="group" and an accessible name: "Main window", "Equalizer", "Playlist" or "Milkdrop". This tells screen reader users which window a control belongs to, and lets tests scope role-based queries to a window (several windows have a "Close" button). --- .../webamp/js/components/FocusTarget.test.tsx | 49 +++++++++++++++++++ packages/webamp/js/components/FocusTarget.tsx | 11 +++++ 2 files changed, 60 insertions(+) create mode 100644 packages/webamp/js/components/FocusTarget.test.tsx diff --git a/packages/webamp/js/components/FocusTarget.test.tsx b/packages/webamp/js/components/FocusTarget.test.tsx new file mode 100644 index 0000000000..25f335eabc --- /dev/null +++ b/packages/webamp/js/components/FocusTarget.test.tsx @@ -0,0 +1,49 @@ +// @types/react is still on v18, which does not declare React 19's `act`. +// @ts-ignore +import { act } from "react"; +import { createRoot, Root } from "react-dom/client"; +import { Provider } from "react-redux"; +import { legacy_createStore as createStore } from "redux"; +import reducer from "../reducers"; +import { WindowId } from "../types"; +import FocusTarget from "./FocusTarget"; + +// Tell React that this environment supports `act`. +// @ts-ignore +globalThis.IS_REACT_ACT_ENVIRONMENT = true; + +let container: HTMLDivElement; +let root: Root; + +beforeEach(() => { + container = document.createElement("div"); + document.body.appendChild(container); + root = createRoot(container); +}); + +afterEach(() => { + act(() => root.unmount()); + container.remove(); +}); + +describe("FocusTarget", () => { + it.each([ + ["main", "Main window"], + ["equalizer", "Equalizer"], + ["playlist", "Playlist"], + ["milkdrop", "Milkdrop"], + ])("labels the %s window as a group", (windowId, label) => { + act(() => + root.render( + + +
+ + + ) + ); + const group = container.firstElementChild!; + expect(group.getAttribute("role")).toBe("group"); + expect(group.getAttribute("aria-label")).toBe(label); + }); +}); diff --git a/packages/webamp/js/components/FocusTarget.tsx b/packages/webamp/js/components/FocusTarget.tsx index bdb4df95ec..ce1179a563 100644 --- a/packages/webamp/js/components/FocusTarget.tsx +++ b/packages/webamp/js/components/FocusTarget.tsx @@ -4,6 +4,15 @@ import * as Actions from "../actionCreators"; import * as Selectors from "../selectors"; import { useActionCreator, useTypedSelector } from "../hooks"; +// Accessible names for each window, so screen reader users know which window +// a control belongs to (several windows have a "Close" button, for example). +const WINDOW_LABELS: Record = { + main: "Main window", + equalizer: "Equalizer", + playlist: "Playlist", + milkdrop: "Milkdrop", +}; + interface Props { onKeyDown?(e: KeyboardEvent): void; windowId: WindowId; @@ -71,6 +80,8 @@ function FocusTarget({ onKeyDown, windowId, children }: Props) { onPointerDown={focusHandler} onFocus={focusHandler} tabIndex={-1} + role="group" + aria-label={WINDOW_LABELS[windowId]} style={{ height: "100%", width: "100%" }} > {children} From 0ac366e8a4944c32cd32537cbba0b1f8637af383 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Thu, 15 Oct 2026 09:30:48 -0700 Subject: [PATCH 2/2] Add changelog entry --- packages/webamp-docs/docs/12_changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/webamp-docs/docs/12_changelog.md b/packages/webamp-docs/docs/12_changelog.md index 9ebe8bba9a..3462fc22c2 100644 --- a/packages/webamp-docs/docs/12_changelog.md +++ b/packages/webamp-docs/docs/12_changelog.md @@ -15,6 +15,7 @@ If you want access to the changes in this section before they are officially rel - The Milkdrop window now has a stable id, `#milkdrop-window`, like `#main-window`, `#equalizer-window` and `#playlist-window`. - Added a new Webamp instance method: [`webamp.onMediaError(cb)`](./06_API/03_instance-methods.md#onmediaerrorcb-error--code-number---void---void), which is called when a track fails to load or play. - Added a new Webamp instance method: [`webamp.setTracks(tracks)`](./06_API/03_instance-methods.md#settrackstracks-track-void), which replaces the playlist without starting playback. +- Each window is now exposed to assistive technology as a labelled group: "Main window", "Equalizer", "Playlist" or "Milkdrop". ### Bug Fixes