app/enhance/pronounce.ts
53 lines and 8 functions in DrDustinEdwards/dustinedwards-info, at commit 1590ba4 on 2026-10-24. This file on GitHub.
Functions
| Function | Lines | Complexity | Covered | CRAP | Mutation |
|---|---|---|---|---|---|
| paint | 15 to 18 | 1 | No data: no coverage report in this map | No data: no coverage report in this map | No data: no mutants in this code |
| clip | 20 to 36 | 2 | No data: no coverage report in this map | No data: no coverage report in this map | No data: no mutants in this code |
| <anonymous:25> | 25 to 25 | 1 | No data: no coverage report in this map | No data: no coverage report in this map | No data: no mutants in this code |
| <anonymous:26> | 26 to 26 | 1 | No data: no coverage report in this map | No data: no coverage report in this map | No data: no mutants in this code |
| <anonymous:27> | 27 to 27 | 1 | No data: no coverage report in this map | No data: no coverage report in this map | No data: no mutants in this code |
| <anonymous:29> | 29 to 33 | 1 | No data: no coverage report in this map | No data: no coverage report in this map | No data: no mutants in this code |
| <anonymous:38> | 38 to 55 | 2 | No data: no coverage report in this map | No data: no coverage report in this map | No data: no mutants in this code |
| <anonymous:46> | 46 to 54 | 3 | No data: no coverage report in this map | No data: no coverage report in this map | No data: no mutants in this code |
Imports
It imports no other module of this repo. No module of this repo imports it.
Source
// The pronunciation button on a dictionary entry (app/components/dictionary-entry.tsx). The server
// renders a plain link to the clip, which works with script off; this swaps in the real button, which
// plays the clip in place on click, Enter or Space (a native <button> gives all three). Nothing plays
// until asked, and the clip is fetched on the first press, not before. aria-pressed carries the state
// while it plays; a second press stops it.
for (const root of document.querySelectorAll<HTMLElement>("[data-term-entry]")) {
const button = root.querySelector<HTMLButtonElement>("[data-term-listen]");
const link = root.querySelector<HTMLAnchorElement>("[data-term-audio-link]");
const src = button?.dataset.termListen;
if (!button || !link || !src) continue;
let audio: HTMLAudioElement | null = null;
const paint = (playing: boolean) => {
button.setAttribute("aria-pressed", String(playing));
button.classList.toggle("is-playing", playing);
};
const clip = () => {
if (audio) return audio;
const created = new Audio();
created.preload = "none";
created.src = src;
created.addEventListener("play", () => paint(true));
created.addEventListener("pause", () => paint(false));
created.addEventListener("ended", () => paint(false));
// A clip that cannot load brings the plain link back, so the reader still has a way to hear it.
created.addEventListener("error", () => {
paint(false);
button.hidden = true;
link.hidden = false;
});
audio = created;
return created;
};
button.addEventListener("click", () => {
const player = clip();
if (!player.paused) {
player.pause();
player.currentTime = 0;
return;
}
player.currentTime = 0;
player.play().catch((error: unknown) => {
paint(false);
// A second press while the first play was still starting: the reader stopped it, nothing failed.
if (error instanceof DOMException && error.name === "AbortError") return;
// A refused play is reported, not swallowed; the link stays one press away.
button.hidden = true;
link.hidden = false;
throw error;
});
});
link.hidden = true;
button.hidden = false;
}