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 | 231x 231x 959x 233x 232x 233x 231x 231x 20x 231x 231x 240x 231x 231x 231x 231x 231x 231x 231x 241x 241x 1446x 231x 231x 231x 231x 231x 9x 231x 232x 241x 241x 9x | <script setup lang="ts">
import { computed, warn, watch } from "vue";
import Circle from "./variants/Circle.vue";
import Dots from "./variants/Dots.vue";
import Pulse from "./variants/Pulse.vue";
import Bars from "./variants/Bars.vue";
import Ring from "./variants/Ring.vue";
import Orbit from "./variants/Orbit.vue";
import PulseDots from "./variants/PulseDots.vue";
import OrbitDots from "./variants/OrbitDots.vue";
import Arc from "./variants/Arc.vue";
import type { Component } from "vue";
import type { SpinnerProps, SpinnerVariantName } from "./spinner.types";
import { usePrefersReducedMotion } from "../../utils/usePrefersReducedMotion";
import { clampProgress, progressA11y } from "../../utils/progress";
import { useLoadingConfig } from "../../config";
const props = withDefaults(defineProps<SpinnerProps>(), {
/* An explicit undefined default opts out of Vue's Boolean casting (an
absent boolean prop becomes `false`), so an unset `track` can fall
through to the global config and then to each variant's own default. */
track: undefined,
});
/* explicit prop > global config > built-in default (no withDefaults on
these, so "not set" stays detectable) */
const config = useLoadingConfig();
const conf = () => config.spinner ?? {};
const size = computed(() => props.size ?? conf().size ?? 40);
const color = computed(() => props.color ?? conf().color ?? "currentColor");
const speed = computed(() => props.speed ?? conf().speed ?? 1);
const thickness = computed(() => props.thickness ?? conf().thickness ?? 4);
const track = computed(() => props.track ?? conf().track);
/* A spinner that stops entirely reads as a frozen page, so reduced
motion slows it down instead of disabling it (unlike Skeleton). */
const REDUCED_MOTION_SPEED_FACTOR = 0.5;
const prefersReducedMotion = usePrefersReducedMotion();
const effectiveSpeed = computed(() =>
prefersReducedMotion.value ? speed.value * REDUCED_MOTION_SPEED_FACTOR : speed.value
);
const variants: Record<SpinnerVariantName, Component> = {
circle: Circle,
dots: Dots,
pulse: Pulse,
bars: Bars,
ring: Ring,
orbit: Orbit,
"pulse-dots": PulseDots,
"orbit-dots": OrbitDots,
arc: Arc,
};
/** `variant` wins over the deprecated `type`; camelCase names are
* normalized to kebab-case so both v0.1.0 spellings keep working. */
const variantName = computed(() => {
// the deprecated `type` is still an explicit choice, so it beats config
const raw = props.variant ?? props.type ?? conf().variant ?? "circle";
return raw.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
});
const component = computed(() => variants[variantName.value as SpinnerVariantName] ?? Circle);
/** Prop names the current variant declares. */
const accepted = computed(() => Object.keys((component.value as { props?: object }).props ?? {}));
/* Pass each variant only the props it declares: anything else (e.g.
`thickness` on dots) would render as a stray HTML attribute. Undefined
values are dropped so the variant's own defaults apply. */
const variantProps = computed(() => {
const all: Record<string, unknown> = {
size: size.value,
color: color.value,
speed: effectiveSpeed.value,
thickness: thickness.value,
track: track.value,
value: props.value,
};
return Object.fromEntries(
Object.entries(all).filter(([k, v]) => v !== undefined && accepted.value.includes(k))
);
});
const progress = computed(() =>
accepted.value.includes("value") ? clampProgress(props.value) : null
);
watch(
() => props.value != null && !accepted.value.includes("value"),
(unsupported) => {
if (unsupported)
warn(
`[vue-smart-loading-kit] Spinner \`value\` is ignored by variant "${variantName.value}". ` +
`To show progress, use variant="arc" or <ProgressBar>.`
);
},
{ immediate: true }
);
const a11y = computed(() => progressA11y(props.label, progress.value));
</script>
<template>
<span
class="vslk-spinner-wrapper"
v-bind="a11y"
>
<component :is="component" v-bind="variantProps" />
<span v-if="props.label && progress === null" class="vslk-sr-only">{{ props.label }}</span>
</span>
</template>
<style scoped>
.vslk-spinner-wrapper {
display: inline-flex;
}
.vslk-sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
</style> |