All files / src/components/Spinner/variants Arc.vue

100% Statements 19/19
90.9% Branches 10/11
100% Functions 6/6
100% Lines 14/14

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 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133                                36x               36x 36x   36x   37x                 20x 36x 36x 36x   36x       36x                         37x   37x                       37x                                                                                                                    
<script setup lang="ts">
import { computed } from "vue";
import { toCssSize, toPx } from "../../../utils/size";
import { resolveTrack } from "../track";
import { clampProgress } from "../../../utils/progress";
 
interface Props {
  size?: number | string;
  color?: string;
  speed?: number;
  thickness?: number;
  track?: boolean | string;
  /** 0–100; anything non-finite keeps the arc indeterminate. */
  value?: number;
}
 
const props = withDefaults(defineProps<Props>(), {
  size: 40,
  color: "currentColor",
  speed: 1,
  thickness: 4,
  track: false,
});
 
const box = computed(() => toCssSize(props.size));
const trackInfo = computed(() => resolveTrack(props.track));
 
const progress = computed(() => clampProgress(props.value));
 
const speed = computed(() => (props.speed > 0 ? props.speed : 1));
 
// The bar uses pathLength="100", so dash lengths are percentages of the
// circle and `value` maps straight onto stroke-dasharray.
 
/* The viewBox is 44 units wide. `thickness` is in px (like circle/ring),
   so convert it to viewBox units when the size is known in px; for other
   units (rem, %) fall back to 1 unit = 1px of a 44px box. The radius
   shrinks by half the stroke so a thick stroke never clips at the edge. */
const VIEWBOX = 44;
const strokeWidth = computed(() => {
  const px = toPx(props.size);
  return px ? (props.thickness * VIEWBOX) / px : props.thickness;
});
const radius = computed(() => Math.max(1, VIEWBOX / 2 - strokeWidth.value / 2));
</script>
 
<template>
  <svg
    class="vslk-spinner-arc"
    :class="{ 'vslk-spinner-arc--indeterminate': progress === null }"
    viewBox="0 0 44 44"
    :style="{
      width: box,
      height: box,
      color: props.color,
      '--vslk-arc-rotate': `${2 / speed}s`,
      '--vslk-arc-dash': `${1.5 / speed}s`,
    }"
  >
    <circle
      v-if="trackInfo"
      class="vslk-spinner-arc__track"
      :data-vslk-track="trackInfo.attr"
      cx="22"
      cy="22"
      :r="radius"
      fill="none"
      :stroke="trackInfo.color"
      :stroke-width="strokeWidth"
    />
    <circle
      class="vslk-spinner-arc__bar"
      cx="22"
      cy="22"
      :r="radius"
      fill="none"
      stroke="currentColor"
      :stroke-width="strokeWidth"
      :stroke-linecap="progress === 0 ? 'butt' : 'round'"
      pathLength="100"
      :style="progress === null ? undefined : { strokeDasharray: `${progress} 100` }"
    />
  </svg>
</template>
 
<style scoped>
.vslk-spinner-arc {
  display: block;
  max-width: 100%;
  flex-shrink: 0;
  /* determinate: start at 12 o'clock and fill clockwise */
  transform: rotate(-90deg);
}
 
.vslk-spinner-arc__bar {
  transition: stroke-dasharray 0.3s ease;
}
 
/* Indeterminate (Material style): the whole circle turns while the arc
   grows and shrinks, so it never looks like a fixed shape spinning. */
.vslk-spinner-arc--indeterminate {
  animation: vslk-arc-rotate var(--vslk-arc-rotate) linear infinite;
}
 
.vslk-spinner-arc--indeterminate .vslk-spinner-arc__bar {
  animation: vslk-arc-dash var(--vslk-arc-dash) ease-in-out infinite;
}
 
@keyframes vslk-arc-rotate {
  from {
    transform: rotate(-90deg);
  }
  to {
    transform: rotate(270deg);
  }
}
 
@keyframes vslk-arc-dash {
  0% {
    stroke-dasharray: 1 150;
    stroke-dashoffset: 0;
  }
  50% {
    stroke-dasharray: 70 150;
    stroke-dashoffset: -28;
  }
  100% {
    stroke-dasharray: 70 150;
    stroke-dashoffset: -99;
  }
}
</style>