Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | 8x 8x 96x 96x 96x 93x 96x 4x 4x 4x 96x 65x 65x 65x 96x 96x 158x 151x 146x 76x 76x 76x 75x 75x 25x 75x 70x 70x 70x 18x 18x 5x 4x 4x 96x 96x | import { onScopeDispose, readonly, ref, toValue, watch, type MaybeRefOrGetter, type Ref } from "vue";
export interface DelayedLoadingOptions {
/** ms a load must last before the loader appears. Default 200. */
delay?: MaybeRefOrGetter<number>;
/** ms the loader stays up once shown, so it never just blinks. Default 500. */
minDuration?: MaybeRefOrGetter<number>;
}
export const DEFAULT_DELAY = 200;
export const DEFAULT_MIN_DURATION = 500;
/**
* Turns a raw `loading` flag into "should a loader be visible":
* - a load shorter than `delay` never shows a loader (no flash);
* - once shown, the loader stays for at least `minDuration`;
* - a load that restarts while the loader is still up keeps it up.
*
* Options are read when a load starts or ends, so they may be reactive.
*/
export function useDelayedLoading(
loading: MaybeRefOrGetter<boolean>,
options: DelayedLoadingOptions = {}
): Readonly<Ref<boolean>> {
const show = ref(false);
let shownAt = 0;
let delayTimer: ReturnType<typeof setTimeout> | undefined;
let hideTimer: ReturnType<typeof setTimeout> | undefined;
const ms = (value: MaybeRefOrGetter<number> | undefined, fallback: number) =>
Math.max(0, toValue(value) ?? fallback);
const clear = () => {
clearTimeout(delayTimer);
clearTimeout(hideTimer);
delayTimer = hideTimer = undefined;
};
const reveal = () => {
delayTimer = undefined;
shownAt = Date.now();
show.value = true;
};
// On the server the delay can never elapse and nothing would clear the
// timers, so the loader simply stays hidden until the client takes over.
const isServer = typeof window === "undefined";
watch(
() => toValue(loading),
(isLoading) => {
if (isServer) return;
if (isLoading) {
// Restarted while held up by minDuration: just stay visible.
clearTimeout(hideTimer);
hideTimer = undefined;
if (show.value || delayTimer) return;
const delay = ms(options.delay, DEFAULT_DELAY);
if (delay === 0) reveal();
else delayTimer = setTimeout(reveal, delay);
return;
}
// Finished before the delay: the loader never appears.
clearTimeout(delayTimer);
delayTimer = undefined;
if (!show.value) return;
const remaining = ms(options.minDuration, DEFAULT_MIN_DURATION) - (Date.now() - shownAt);
if (remaining <= 0) show.value = false;
else
hideTimer = setTimeout(() => {
hideTimer = undefined;
show.value = false;
}, remaining);
},
// sync: react to every change, even two within one tick
{ immediate: true, flush: "sync" }
);
onScopeDispose(clear);
return readonly(show);
}
|