Reactive mathematical utility functions and composables for Vue.js applications
—
Utilities for clamping values within bounds and controlling numerical precision with configurable rounding methods. These functions provide fine-grained control over numeric values with reactive behavior.
Reactively clamps a value between minimum and maximum bounds. Returns different ref types based on the input type to optimize for readonly vs writable scenarios.
/**
* Clamp a readonly value between min and max bounds
* @param value - Readonly reactive number or getter
* @param min - Minimum bound (reactive)
* @param max - Maximum bound (reactive)
* @returns ComputedRef with clamped value
*/
function useClamp(
value: ReadonlyRefOrGetter<number>,
min: MaybeRefOrGetter<number>,
max: MaybeRefOrGetter<number>
): ComputedRef<number>;
/**
* Clamp a writable value between min and max bounds
* @param value - Writable reactive number
* @param min - Minimum bound (reactive)
* @param max - Maximum bound (reactive)
* @returns Writable Ref that clamps on both get and set
*/
function useClamp(
value: MaybeRefOrGetter<number>,
min: MaybeRefOrGetter<number>,
max: MaybeRefOrGetter<number>
): Ref<number>;Usage Examples:
import { ref } from "vue";
import { useClamp } from "@vueuse/math";
// Basic clamping with writable ref
const value = ref(15);
const clamped = useClamp(value, 0, 10);
console.log(clamped.value); // 10 (clamped from 15)
// Setting the clamped value also clamps
clamped.value = 20;
console.log(clamped.value); // 10 (clamped)
console.log(value.value); // 10 (original ref is updated)
clamped.value = 5;
console.log(clamped.value); // 5 (within bounds)
// Reactive bounds
const minBound = ref(0);
const maxBound = ref(100);
const input = ref(150);
const boundedValue = useClamp(input, minBound, maxBound);
console.log(boundedValue.value); // 100
// Changing bounds affects the result
maxBound.value = 200;
console.log(boundedValue.value); // 150 (now within new bounds)
// Readonly usage (computed or getter)
const readonlyValue = useClamp(() => Math.random() * 200, 50, 100);
console.log(readonlyValue.value); // Between 50-100Reactively sets the precision of a number using configurable rounding methods.
/**
* Set the precision of a number with configurable rounding
* @param value - The number to set precision for
* @param digits - Number of decimal digits to preserve
* @param options - Optional configuration for rounding method
* @returns ComputedRef containing the precision-controlled number
*/
function usePrecision(
value: MaybeRefOrGetter<number>,
digits: MaybeRefOrGetter<number>,
options?: MaybeRefOrGetter<UsePrecisionOptions>
): ComputedRef<number>;
/**
* Configuration options for precision control
*/
interface UsePrecisionOptions {
/**
* Method to use for rounding
* @default 'round'
*/
math?: 'floor' | 'ceil' | 'round';
}Usage Examples:
import { ref } from "vue";
import { usePrecision } from "@vueuse/math";
// Basic precision control
const value = ref(3.14159265);
const precise = usePrecision(value, 2);
console.log(precise.value); // 3.14
// Different rounding methods
const number = ref(3.7869);
const rounded = usePrecision(number, 2, { math: 'round' });
const floored = usePrecision(number, 2, { math: 'floor' });
const ceiled = usePrecision(number, 2, { math: 'ceil' });
console.log(rounded.value); // 3.79 (default rounding)
console.log(floored.value); // 3.78 (always round down)
console.log(ceiled.value); // 3.79 (always round up)
// Reactive precision
const price = ref(19.99567);
const decimalPlaces = ref(2);
const formattedPrice = usePrecision(price, decimalPlaces);
console.log(formattedPrice.value); // 20.00
decimalPlaces.value = 3;
console.log(formattedPrice.value); // 19.996
// Different rounding methods with negative numbers
const negative = ref(-2.456);
const roundedNeg = usePrecision(negative, 1, { math: 'round' });
const flooredNeg = usePrecision(negative, 1, { math: 'floor' });
const ceiledNeg = usePrecision(negative, 1, { math: 'ceil' });
console.log(roundedNeg.value); // -2.5
console.log(flooredNeg.value); // -2.5 (floor toward negative infinity)
console.log(ceiledNeg.value); // -2.4 (ceil toward positive infinity)import { ref, computed } from "vue";
import { useClamp, usePrecision } from "@vueuse/math";
// Create a controlled input with dynamic bounds and precision
const rawInput = ref(0);
const minValue = ref(0);
const maxValue = ref(100);
const precision = ref(1);
// Clamp first, then apply precision
const clampedInput = useClamp(rawInput, minValue, maxValue);
const preciseInput = usePrecision(clampedInput, precision);
// UI control that updates all values
const slider = computed({
get: () => preciseInput.value,
set: (val: number) => {
rawInput.value = val;
}
});
// Example usage
rawInput.value = 123.456789;
console.log(slider.value); // 100.0 (clamped to max, then precision applied)
maxValue.value = 150;
console.log(slider.value); // 123.5 (now within bounds, precision applied)import { ref, computed } from "vue";
import { useClamp, usePrecision } from "@vueuse/math";
// Financial calculation with proper precision
const principal = ref(10000);
const interestRate = ref(0.05); // 5%
const years = ref(10);
// Ensure reasonable bounds for financial inputs
const clampedPrincipal = useClamp(principal, 0, 1000000);
const clampedRate = useClamp(interestRate, 0, 1); // 0-100%
const clampedYears = useClamp(years, 1, 50);
// Calculate compound interest
const futureValue = computed(() => {
const p = clampedPrincipal.value;
const r = clampedRate.value;
const t = clampedYears.value;
return p * Math.pow(1 + r, t);
});
// Format for display with proper precision
const formattedFutureValue = usePrecision(futureValue, 2);
console.log(formattedFutureValue.value); // 16288.95
// Interest calculation stays within bounds even with extreme inputs
principal.value = -5000; // Invalid negative
interestRate.value = 2.5; // Invalid > 100%
console.log(formattedFutureValue.value); // Still gives valid result due to clampingimport { ref, computed } from "vue";
import { useClamp, usePrecision } from "@vueuse/math";
// Animation progress control
const animationProgress = ref(0);
const duration = ref(1000); // ms
const currentTime = ref(0);
// Calculate normalized progress (0-1)
const rawProgress = computed(() => currentTime.value / duration.value);
const clampedProgress = useClamp(rawProgress, 0, 1);
const preciseProgress = usePrecision(clampedProgress, 3);
// Convert to percentage for display
const percentProgress = computed(() => preciseProgress.value * 100);
const displayPercent = usePrecision(percentProgress, 1);
// Simulate animation frames
function animate() {
currentTime.value += 16; // ~60fps
console.log(`Progress: ${displayPercent.value}%`);
if (preciseProgress.value < 1) {
requestAnimationFrame(animate);
}
}
animate();Install with Tessl CLI
npx tessl i tessl/npm-vueuse--math