47 lines
1.4 KiB
TypeScript
47 lines
1.4 KiB
TypeScript
/**
|
|
* useHeldFlag — keep a boolean true for a minimum time after it falls.
|
|
*
|
|
* A warm-cache fetch-more load begins and ends between two renderer
|
|
* frames: the raw `isLoadingMore` signal flips true→false without a
|
|
* single paint, so the "[Fetch More]" → spinner swap never appears and
|
|
* the press looks like a no-op. Components rendering a spinner for such
|
|
* bursts read through this hook instead of the raw signal — the spinner
|
|
* stays up (and animating) for at least `minMs` after the load ends,
|
|
* guaranteeing several painted frames.
|
|
*
|
|
* Deliberately a display-layer concern: the store's own `isLoadingMore`
|
|
* keeps its exact load-window semantics (guards, tests) and only the
|
|
* rendered indicators are held.
|
|
*/
|
|
import { createSignal, createEffect, onCleanup } from "solid-js";
|
|
|
|
export function useHeldFlag(
|
|
source: () => boolean,
|
|
minMs = 250,
|
|
): () => boolean {
|
|
const [held, setHeld] = createSignal(false);
|
|
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
const clearTimer = () => {
|
|
if (timer) {
|
|
clearTimeout(timer);
|
|
timer = null;
|
|
}
|
|
};
|
|
|
|
createEffect(() => {
|
|
if (source()) {
|
|
// (Re)rising edge: show immediately; a pending fall from an
|
|
// earlier burst is cancelled.
|
|
clearTimer();
|
|
if (!held()) setHeld(true);
|
|
} else if (held()) {
|
|
// Falling edge: hold the flag up for the remaining window.
|
|
clearTimer();
|
|
timer = setTimeout(() => setHeld(false), minMs);
|
|
}
|
|
});
|
|
|
|
onCleanup(clearTimer);
|
|
return held;
|
|
}
|