nuxt
Version:
134 lines (133 loc) • 3.65 kB
JavaScript
import { useNuxtApp } from "../nuxt.js";
import { getCurrentScope, onScopeDispose, shallowReadonly, shallowRef } from "vue";
//#region src/app/composables/loading-indicator.ts
function defaultEstimatedProgress(duration, elapsed) {
const completionPercentage = elapsed / duration * 100;
return 2 / Math.PI * 100 * Math.atan(completionPercentage / 50);
}
function createLoadingIndicator(opts = {}) {
const { duration = 2e3, throttle = 200, hideDelay = 500, resetDelay = 400 } = opts;
const getProgress = opts.estimatedProgress || defaultEstimatedProgress;
const nuxtApp = useNuxtApp();
const progress = shallowRef(0);
const isLoading = shallowRef(false);
const error = shallowRef(false);
let done = false;
let rafId;
let throttleTimeout;
let hideTimeout;
let resetTimeout;
const start = (opts = {}) => {
_clearTimeouts();
error.value = false;
set(0, opts);
};
function set(at = 0, opts = {}) {
if (nuxtApp.isHydrating) return;
if (at >= 100) return finish({ force: opts.force });
_clearTimeouts();
clear();
progress.value = at < 0 ? 0 : at;
const throttleTime = opts.force ? 0 : throttle;
if (throttleTime && import.meta.client) throttleTimeout = setTimeout(() => {
isLoading.value = true;
_startProgress();
}, throttleTime);
else {
isLoading.value = true;
_startProgress();
}
}
function _hide() {
if (import.meta.client) hideTimeout = setTimeout(() => {
isLoading.value = false;
resetTimeout = setTimeout(() => {
progress.value = 0;
}, resetDelay);
}, hideDelay);
}
function finish(opts = {}) {
progress.value = 100;
done = true;
clear();
_clearTimeouts();
if (opts.error) error.value = true;
if (opts.force) {
progress.value = 0;
isLoading.value = false;
} else _hide();
}
function _clearTimeouts() {
if (import.meta.client) {
clearTimeout(hideTimeout);
clearTimeout(resetTimeout);
}
}
function clear() {
if (import.meta.client) {
clearTimeout(throttleTimeout);
cancelAnimationFrame(rafId);
}
}
function _startProgress() {
done = false;
let startTimeStamp;
function step(timeStamp) {
if (done) return;
startTimeStamp ??= timeStamp;
const elapsed = timeStamp - startTimeStamp;
progress.value = Math.max(0, Math.min(100, getProgress(duration, elapsed)));
if (import.meta.client) rafId = requestAnimationFrame(step);
}
if (import.meta.client) rafId = requestAnimationFrame(step);
}
let _cleanup = () => {};
if (import.meta.client) {
const unsubLoadingStartHook = nuxtApp.hook("page:loading:start", () => {
start();
});
const unsubLoadingFinishHook = nuxtApp.hook("page:loading:end", () => {
finish();
});
const unsubError = nuxtApp.hook("vue:error", () => finish({ error: true }));
_cleanup = () => {
unsubError();
unsubLoadingStartHook();
unsubLoadingFinishHook();
clear();
_clearTimeouts();
};
}
return {
_cleanup,
progress: shallowReadonly(progress),
isLoading: shallowReadonly(isLoading),
error: shallowReadonly(error),
start,
set,
finish,
clear
};
}
/**
* composable to handle the loading state of the page
* @since 3.9.0
*/
function useLoadingIndicator(opts = {}) {
const nuxtApp = useNuxtApp();
const indicator = nuxtApp._loadingIndicator ||= createLoadingIndicator(opts);
if (import.meta.client && getCurrentScope()) {
nuxtApp._loadingIndicatorDeps ||= 0;
nuxtApp._loadingIndicatorDeps++;
onScopeDispose(() => {
nuxtApp._loadingIndicatorDeps--;
if (nuxtApp._loadingIndicatorDeps === 0) {
indicator._cleanup();
delete nuxtApp._loadingIndicator;
}
});
}
return indicator;
}
//#endregion
export { useLoadingIndicator };