All files / src/utils pageProgress.ts

92.3% Statements 24/26
100% Branches 6/6
80% Functions 8/10
100% Lines 21/21

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                                                  47x 47x 44x   25x     17x         8x               4x 4x 1x       1x                   7x 7x 7x 6x 6x   7x 6x 5x 5x     7x 7x    
import { computed, inject, ref, warn, type ComputedRef, type InjectionKey } from "vue";
 
/** The three router methods used, so vue-router is not a dependency. */
export interface RouterLike {
  beforeEach(guard: (...args: never[]) => unknown): () => void;
  afterEach(hook: (...args: never[]) => unknown): () => void;
  onError(handler: (...args: never[]) => unknown): () => void;
}
 
export interface PageProgress {
  /** True while at least one started task has not finished. */
  readonly loading: ComputedRef<boolean>;
  /** Marks one task as started. Every start() needs a matching done(). */
  start(): void;
  /** Marks one task as finished. Extra calls are ignored. */
  done(): void;
}
 
/**
 * The state behind <PageProgress>: a counter of running tasks, so several
 * parallel requests keep the bar up until the last one finishes. It holds
 * no timers (the bar's own component does the animation), so it is safe to
 * create on the server.
 */
export function createPageProgress(): PageProgress {
  const pending = ref(0);
  return {
    loading: computed(() => pending.value > 0),
    start() {
      pending.value++;
    },
    done() {
      pending.value = Math.max(0, pending.value - 1);
    },
  };
}
 
export const PAGE_PROGRESS: InjectionKey<PageProgress> = Symbol("vue-smart-loading-kit:page-progress");
 
/**
 * The app's page progress (created by the plugin), to wrap any task:
 *   const progress = usePageProgress()
 *   progress.start(); await fetchData(); progress.done()
 */
export function usePageProgress(): PageProgress {
  const progress = inject(PAGE_PROGRESS, null);
  if (progress) return progress;
  warn(
    "[vue-smart-loading-kit] usePageProgress() needs the plugin: app.use(VueSmartLoadingKit). " +
      "Without it, start() and done() do nothing."
  );
  return createPageProgress();
}
 
/**
 * Starts the bar when a navigation begins and finishes it when it ends or
 * fails. A navigation holds exactly one slot however many times it is
 * redirected, and only releases its own slot, so it never ends a task that
 * was started manually.
 */
export function bindRouter(progress: PageProgress, router: RouterLike): () => void {
  let navigating = false;
  const begin = () => {
    if (navigating) return;
    navigating = true;
    progress.start();
  };
  const end = () => {
    if (!navigating) return;
    navigating = false;
    progress.done();
  };
 
  const offs = [router.beforeEach(begin), router.afterEach(end), router.onError(end)];
  return () => offs.forEach((off) => off());
}