feat(web): add useDelayed rune helper
Boolean mirror that flips true only after the source stays true for delayMs. Used to hide the skeleton on sub-100ms cache hits so back-navigation feels instant.
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
// Returns a rune-wrapped boolean that mirrors the source getter, but only
|
||||
// flips to true after the source has been true for delayMs continuously.
|
||||
// Flips back to false immediately when the source becomes false.
|
||||
//
|
||||
// Used to suppress flash-of-skeleton on cache-hit navigation: skeletons
|
||||
// only render for loads that take longer than ~100ms.
|
||||
export function useDelayed(
|
||||
source: () => boolean,
|
||||
delayMs = 100
|
||||
): { readonly value: boolean } {
|
||||
let delayed = $state(false);
|
||||
let timeout: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
// Schedule the initial timeout synchronously so tests that advance fake
|
||||
// timers before the first reactive flush still see the correct value.
|
||||
if (source()) {
|
||||
timeout = setTimeout(() => {
|
||||
delayed = true;
|
||||
timeout = null;
|
||||
}, delayMs);
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
const v = source();
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
timeout = null;
|
||||
}
|
||||
if (v) {
|
||||
timeout = setTimeout(() => {
|
||||
delayed = true;
|
||||
timeout = null;
|
||||
}, delayMs);
|
||||
} else {
|
||||
delayed = false;
|
||||
}
|
||||
return () => {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
timeout = null;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
return { get value() { return delayed; } };
|
||||
}
|
||||
Reference in New Issue
Block a user