diff --git a/frontend/__tests__/setup-tests.ts b/frontend/__tests__/setup-tests.ts
index 21a133a1ff32..0456f9681161 100644
--- a/frontend/__tests__/setup-tests.ts
+++ b/frontend/__tests__/setup-tests.ts
@@ -65,6 +65,8 @@ vi.mock("../src/ts/utils/dom", async (importOriginal) => {
getOffsetLeft: vi.fn().mockReturnValue(0),
animate: vi.fn().mockResolvedValue(null),
promiseAnimate: vi.fn().mockResolvedValue(null),
+ slideUp: vi.fn().mockResolvedValue(null),
+ slideDown: vi.fn().mockResolvedValue(null),
native: document.createElement("div"),
// @ts-expect-error - mocking private method
hasValue: vi.fn().mockReturnValue(false),
diff --git a/frontend/src/ts/commandline/lists/result-screen.ts b/frontend/src/ts/commandline/lists/result-screen.ts
index 60b528594ca4..1abf3acdf436 100644
--- a/frontend/src/ts/commandline/lists/result-screen.ts
+++ b/frontend/src/ts/commandline/lists/result-screen.ts
@@ -97,7 +97,7 @@ const commands: Command[] = [
display: "Toggle word history",
icon: "fa-align-left",
exec: (): void => {
- TestUI.toggleResultWords();
+ void TestUI.toggleResultWords();
},
available: (): boolean => {
return TestState.resultVisible;
diff --git a/frontend/src/ts/pages/settings.ts b/frontend/src/ts/pages/settings.ts
index 125c18d18b73..afe9c1c34c68 100644
--- a/frontend/src/ts/pages/settings.ts
+++ b/frontend/src/ts/pages/settings.ts
@@ -753,30 +753,14 @@ function toggleSettingsGroup(groupName: string): void {
const groupEl = qs(`.pageSettings .settingsGroup.${groupName}`);
if (!groupEl?.hasClass("slideup")) {
- groupEl?.setStyle({ overflow: "hidden" })?.animate({
- height: 0,
- duration: 250,
- onComplete: () => {
- groupEl
- ?.hide()
- .setStyle({ height: "", overflow: "" })
- .addClass("slideup");
- },
- });
+ void groupEl?.slideUp(250);
+ groupEl?.addClass("slideup");
$(`.pageSettings .sectionGroupTitle[group=${groupName}]`).addClass(
"rotateIcon",
);
} else {
- groupEl?.show();
- groupEl?.setStyle({ height: "", overflow: "hidden" });
- const height = groupEl.getOffsetHeight();
- groupEl?.animate({
- height: [0, height],
- duration: 250,
- onComplete: () => {
- groupEl?.setStyle({ height: "", overflow: "" }).removeClass("slideup");
- },
- });
+ void groupEl?.slideDown(250);
+ groupEl?.removeClass("slideup");
$(`.pageSettings .sectionGroupTitle[group=${groupName}]`).removeClass(
"rotateIcon",
);
diff --git a/frontend/src/ts/test/replay.ts b/frontend/src/ts/test/replay.ts
index 0e24c4c61047..81659e268c1d 100644
--- a/frontend/src/ts/test/replay.ts
+++ b/frontend/src/ts/test/replay.ts
@@ -2,6 +2,7 @@ import config from "../config";
import * as Sound from "../controllers/sound-controller";
import * as TestInput from "./test-input";
import * as Arrays from "../utils/arrays";
+import { qsr } from "../utils/dom";
type ReplayAction =
| "correctLetter"
@@ -30,6 +31,8 @@ let stopwatchList: NodeJS.Timeout[] = [];
const toggleButton = document.getElementById("playpauseReplayButton")
?.children[0];
+const replayEl = qsr(".pageTest #resultReplay");
+
function replayGetWordsList(wordsListFromScript: string[]): void {
wordsList = wordsListFromScript;
}
@@ -187,24 +190,11 @@ function loadOldReplay(): number {
}
function toggleReplayDisplay(): void {
- if ($("#resultReplay").stop(true, true).hasClass("hidden")) {
+ if (replayEl.isHidden()) {
initializeReplayPrompt();
loadOldReplay();
//show
- if (!$("#watchReplayButton").hasClass("loaded")) {
- $("#words").html(
- `
`,
- );
- $("#resultReplay")
- .removeClass("hidden")
- .css("display", "none")
- .slideDown(250);
- } else {
- $("#resultReplay")
- .removeClass("hidden")
- .css("display", "none")
- .slideDown(250);
- }
+ void replayEl.slideDown(250);
} else {
//hide
if (
@@ -213,9 +203,7 @@ function toggleReplayDisplay(): void {
) {
pauseReplay();
}
- $("#resultReplay").slideUp(250, () => {
- $("#resultReplay").addClass("hidden");
- });
+ void replayEl.slideUp(250);
}
}
diff --git a/frontend/src/ts/test/result.ts b/frontend/src/ts/test/result.ts
index ec2b9f068179..21eef47ee016 100644
--- a/frontend/src/ts/test/result.ts
+++ b/frontend/src/ts/test/result.ts
@@ -1121,7 +1121,7 @@ export async function update(
);
if (Config.alwaysShowWordsHistory && canQuickRestart && !GlarsesMode.get()) {
- TestUI.toggleResultWords(true);
+ void TestUI.toggleResultWords(true);
}
AdController.updateFooterAndVerticalAds(true);
void Funbox.clear();
diff --git a/frontend/src/ts/test/test-ui.ts b/frontend/src/ts/test/test-ui.ts
index d10e6e6651a8..73c0110ff816 100644
--- a/frontend/src/ts/test/test-ui.ts
+++ b/frontend/src/ts/test/test-ui.ts
@@ -57,6 +57,7 @@ import * as XPBar from "../elements/xp-bar";
import * as ModesNotice from "../elements/modes-notice";
import * as Last10Average from "../elements/last-10-average";
import * as MemoryFunboxTimer from "./funbox/memory-funbox-timer";
+import { qsr } from "../utils/dom";
export const updateHintsPositionDebounced = Misc.debounceUntilResolved(
updateHintsPosition,
@@ -67,6 +68,7 @@ const wordsEl = document.querySelector(".pageTest #words") as HTMLElement;
const wordsWrapperEl = document.querySelector(
".pageTest #wordsWrapper",
) as HTMLElement;
+const resultWordsHistoryEl = qsr(".pageTest #resultWordsHistory");
export let activeWordTop = 0;
export let activeWordHeight = 0;
@@ -1391,42 +1393,18 @@ async function loadWordsHistory(): Promise {
return true;
}
-export function toggleResultWords(noAnimation = false): void {
- if (TestState.resultVisible) {
- ResultWordHighlight.updateToggleWordsHistoryTime();
- if ($("#resultWordsHistory").stop(true, true).hasClass("hidden")) {
- //show
-
- if ($("#resultWordsHistory .words .word").length === 0) {
- void loadWordsHistory().then(() => {
- if (Config.burstHeatmap) {
- void applyBurstHeatmap();
- }
- $("#resultWordsHistory")
- .removeClass("hidden")
- .css("display", "none")
- .slideDown(noAnimation ? 0 : 250, () => {
- if (Config.burstHeatmap) {
- void applyBurstHeatmap();
- }
- });
- });
- } else {
- if (Config.burstHeatmap) {
- void applyBurstHeatmap();
- }
- $("#resultWordsHistory")
- .removeClass("hidden")
- .css("display", "none")
- .slideDown(noAnimation ? 0 : 250);
- }
- } else {
- //hide
+export async function toggleResultWords(noAnimation = false): Promise {
+ if (!TestState.resultVisible) return;
+ ResultWordHighlight.updateToggleWordsHistoryTime();
- $("#resultWordsHistory").slideUp(250, () => {
- $("#resultWordsHistory").addClass("hidden");
- });
+ if (resultWordsHistoryEl.isHidden()) {
+ if (resultWordsHistoryEl.qsa(".words .word").length === 0) {
+ await loadWordsHistory();
}
+ void resultWordsHistoryEl.slideDown(noAnimation ? 0 : 250);
+ void applyBurstHeatmap();
+ } else {
+ void resultWordsHistoryEl.slideUp(noAnimation ? 0 : 250);
}
}
@@ -2005,7 +1983,7 @@ $("#wordsInput").on("focusout", () => {
});
$(".pageTest").on("click", "#showWordHistoryButton", () => {
- toggleResultWords();
+ void toggleResultWords();
});
$("#wordsWrapper").on("click", () => {
diff --git a/frontend/src/ts/utils/dom.ts b/frontend/src/ts/utils/dom.ts
index d0525086bb93..f37cff4abe09 100644
--- a/frontend/src/ts/utils/dom.ts
+++ b/frontend/src/ts/utils/dom.ts
@@ -209,6 +209,13 @@ export class ElementWithUtils {
return this;
}
+ /**
+ * Check if the element has the "hidden" class
+ */
+ isHidden(): boolean {
+ return this.hasClass("hidden");
+ }
+
/**
* Check if element is visible
*/
@@ -669,6 +676,69 @@ export class ElementWithUtils {
});
}
+ /**
+ * Animate the element sliding down (expanding height from 0 to full height)
+ * @param duration The duration of the animation in milliseconds (default: 250ms)
+ */
+ async slideDown(duration = 250): Promise {
+ this.show().setStyle({
+ height: "",
+ overflow: "hidden",
+ marginTop: "",
+ marginBottom: "",
+ });
+ const height = this.getOffsetHeight();
+ const computed = getComputedStyle(this.native);
+ const marginTop = computed.marginTop;
+ const marginBottom = computed.marginBottom;
+ this.setStyle({ height: "0px", marginTop: "0px", marginBottom: "0px" });
+ await this.promiseAnimate({
+ height: [0, height],
+ marginTop: [0, marginTop],
+ marginBottom: [0, marginBottom],
+ duration,
+ onComplete: () => {
+ this.setStyle({
+ height: "",
+ overflow: "",
+ marginTop: "",
+ marginBottom: "",
+ });
+ },
+ });
+ }
+
+ /**
+ * Animate the element sliding up (collapsing height from full height to 0)
+ * @param duration The duration of the animation in milliseconds (default: 250ms)
+ */
+ async slideUp(duration = 250): Promise {
+ this.show().setStyle({
+ overflow: "hidden",
+ height: "",
+ marginTop: "",
+ marginBottom: "",
+ });
+ const height = this.getOffsetHeight();
+ const computed = getComputedStyle(this.native);
+ const marginTop = computed.marginTop;
+ const marginBottom = computed.marginBottom;
+ await this.promiseAnimate({
+ height: [height, 0],
+ marginTop: [marginTop, 0],
+ marginBottom: [marginBottom, 0],
+ duration,
+ onComplete: () => {
+ this.setStyle({
+ height: "",
+ overflow: "",
+ marginTop: "",
+ marginBottom: "",
+ }).hide();
+ },
+ });
+ }
+
/**
* Focus the element
*/