diff --git a/packages/webamp/js/components/Balance.test.ts b/packages/webamp/js/components/Balance.test.ts new file mode 100644 index 00000000..fa17779e --- /dev/null +++ b/packages/webamp/js/components/Balance.test.ts @@ -0,0 +1,9 @@ +import { balanceValueText } from "./Balance"; + +describe("balanceValueText", () => { + it("describes the balance", () => { + expect(balanceValueText(0)).toBe("Center"); + expect(balanceValueText(-20)).toBe("20% Left"); + expect(balanceValueText(100)).toBe("100% Right"); + }); +}); diff --git a/packages/webamp/js/components/Balance.tsx b/packages/webamp/js/components/Balance.tsx index 07c9d176..3722ce06 100644 --- a/packages/webamp/js/components/Balance.tsx +++ b/packages/webamp/js/components/Balance.tsx @@ -4,6 +4,14 @@ import * as Actions from "../actionCreators"; import * as Selectors from "../selectors"; import { useTypedSelector, useActionCreator } from "../hooks"; +// E.g. "Center" or "20% Left" +export function balanceValueText(balance: number): string { + if (balance === 0) { + return "Center"; + } + return `${Math.abs(balance)}% ${balance > 0 ? "Right" : "Left"}`; +} + interface Props { id?: string; style?: React.CSSProperties; @@ -29,6 +37,7 @@ export default function Balance({ style, className, id }: Props) { onPointerDown={() => setFocus("balance")} onPointerUp={unsetFocus} title="Balance" + aria-valuetext={balanceValueText(balance)} /> ); } diff --git a/packages/webamp/js/components/MainWindow/Position.test.tsx b/packages/webamp/js/components/MainWindow/Position.test.tsx index f08d949f..a5d3dc19 100644 --- a/packages/webamp/js/components/MainWindow/Position.test.tsx +++ b/packages/webamp/js/components/MainWindow/Position.test.tsx @@ -125,4 +125,14 @@ describe("Position", () => { pointer("pointerup"); expect(seeks()).toEqual([60]); }); + + it("describes the position as elapsed time of total time", () => { + expect(input.getAttribute("aria-valuetext")).toBe("0:30 of 2:00"); + }); + + it("describes the scrub position while dragging", () => { + pointer("pointerdown"); + changeValue("50"); + expect(input.getAttribute("aria-valuetext")).toBe("1:00 of 2:00"); + }); }); diff --git a/packages/webamp/js/components/MainWindow/Position.tsx b/packages/webamp/js/components/MainWindow/Position.tsx index 3b0cb4f2..67037111 100644 --- a/packages/webamp/js/components/MainWindow/Position.tsx +++ b/packages/webamp/js/components/MainWindow/Position.tsx @@ -1,6 +1,7 @@ import { FormEvent, KeyboardEvent, memo, useCallback, useRef } from "react"; import * as Selectors from "../../selectors"; import * as Actions from "../../actionCreators"; +import { getTimeStr } from "../../utils"; import { useTypedSelector, useTypedDispatch } from "../../hooks"; function usePosition() { @@ -10,14 +11,21 @@ function usePosition() { const scrubPosition = useTypedSelector(Selectors.getUserInputScrubPosition); const userInputFocus = useTypedSelector(Selectors.getUserInputFocus); - const displayedPosition = - userInputFocus === "position" ? scrubPosition : position; + const scrubbing = userInputFocus === "position"; + const displayedPosition = scrubbing ? scrubPosition : position; - return [position, displayedPosition]; + // E.g. "0:32 of 2:00" + let valueText; + if (duration != null) { + const time = scrubbing ? (duration * scrubPosition) / 100 : timeElapsed; + valueText = `${getTimeStr(time)} of ${getTimeStr(duration)}`; + } + + return [position, displayedPosition, valueText] as const; } const Position = memo(() => { - const [position, displayedPosition] = usePosition(); + const [position, displayedPosition, valueText] = usePosition(); const dispatch = useTypedDispatch(); const seekToPercentComplete = useCallback( @@ -124,6 +132,7 @@ const Position = memo(() => { onPointerUp={handlePointerUp} onPointerDown={handlePointerDown} title="Seeking Bar" + aria-valuetext={valueText} /> ); }); diff --git a/packages/webamp/js/components/Volume.tsx b/packages/webamp/js/components/Volume.tsx index f43c75ac..359c0365 100644 --- a/packages/webamp/js/components/Volume.tsx +++ b/packages/webamp/js/components/Volume.tsx @@ -29,6 +29,7 @@ export default function Volume({ id, style, className }: Props) { onPointerDown={() => setFocus("volume")} onPointerUp={unsetFocus} title="Volume Bar" + aria-valuetext={`${volume}%`} /> ); }