From ff002044aa555e50b064dd3a95c0aa819124c178 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Wed, 24 Jan 2024 12:50:13 +1100 Subject: [PATCH] fix: use leading edge debounce to sync up transitions --- src/common/events.js | 32 +++++++++++++++++++++----------- src/core/menu/islands/Footer.mjs | 16 ++++++++++------ 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/src/common/events.js b/src/common/events.js index d01939d..ff620ee 100644 --- a/src/common/events.js +++ b/src/common/events.js @@ -6,19 +6,28 @@ "use strict"; -// convenient util: batch event handling -// every ___ ms to avoid over-handling & -// any conflicts / perf.issues that may -// otherwise result. a wait time of ~200ms -// is recommended (the avg. human visual +// batch event callbacks to avoid over-handling +// and any conflicts / perf.issues that may +// otherwise result. initial call is immediate, +// following calls are delayed. a wait time of +// ~200ms is recommended (the avg. human visual // reaction time is ~180-200ms) -const debounce = (callback, wait = 200) => { - let timeoutId; - return (...args) => { - clearTimeout(timeoutId); - timeoutId = setTimeout(() => callback(...args), wait); +const sleep = async (ms) => { + return new Promise((res, rej) => setTimeout(res, ms)); + }, + debounce = (callback, ms = 200) => { + let delay, update; + const next = () => + sleep(ms).then(() => { + if (!update) return (delay = undefined); + update(), (update = undefined); + delay = next(); + }); + return (...args) => { + if (delay) update = callback.bind(this, ...args); + return delay || ((delay = next()), callback(...args)); + }; }; -}; // provides basic key/value reactivity: // this is shared between all active mods, @@ -156,6 +165,7 @@ document.addEventListener("keydown", (event) => { }); Object.assign((globalThis.__enhancerApi ??= {}), { + sleep, debounce, setState, useState, diff --git a/src/core/menu/islands/Footer.mjs b/src/core/menu/islands/Footer.mjs index 866a13f..154eb7a 100644 --- a/src/core/menu/islands/Footer.mjs +++ b/src/core/menu/islands/Footer.mjs @@ -8,7 +8,7 @@ import { Button } from "./Button.mjs"; -function Footer({ categories }) { +function Footer({ categories, transitionDuration = 150 }) { const { html, setState, useState, reloadApp } = globalThis.__enhancerApi, $reload = html`<${Button} class="ml-auto" @@ -33,12 +33,16 @@ function Footer({ categories }) { useState(["view"], ([view]) => { let [footerOpen] = useState(["databaseUpdated"]); - for (const [ids, $btn] of $categories) { - const modInCategory = ids.some((id) => id === view); - if (modInCategory) footerOpen = true; - $btn.style.display = modInCategory ? "" : "none"; - } + footerOpen ||= $categories.some(([ids]) => ids.some((id) => id === view)); setState({ footerOpen }); + if (!footerOpen) return; + + // only toggle buttons if footer is open, + // otherwise leave as is during transition + for (const [ids, $btn] of $categories) { + const viewInCategory = ids.some((id) => id === view); + $btn.style.display = viewInCategory ? "" : "none"; + } }); useState(["databaseUpdated"], ([databaseUpdated]) => { $reload.style.display = databaseUpdated ? "" : "none";