Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/webamp-docs/docs/12_changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
49 changes: 49 additions & 0 deletions packages/webamp/js/components/FocusTarget.test.tsx
Original file line number Diff line number Diff line change
@@ -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(
<Provider store={createStore(reducer)}>
<FocusTarget windowId={windowId as WindowId}>
<div />
</FocusTarget>
</Provider>
)
);
const group = container.firstElementChild!;
expect(group.getAttribute("role")).toBe("group");
expect(group.getAttribute("aria-label")).toBe(label);
});
});
11 changes: 11 additions & 0 deletions packages/webamp/js/components/FocusTarget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<WindowId, string> = {
main: "Main window",
equalizer: "Equalizer",
playlist: "Playlist",
milkdrop: "Milkdrop",
};

interface Props {
onKeyDown?(e: KeyboardEvent): void;
windowId: WindowId;
Expand Down Expand Up @@ -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}
Expand Down
Loading