/** Clamps `value` into `[lo, hi]`. When `lo > hi` (e.g. a popup wider than the screen) `lo` wins. */ export function clamp(value: number, lo: number, hi: number): number { return Math.max(lo, Math.min(hi, value)); } /** Whole-pixel top offset that centres an item of `itemHeight` inside `containerHeight`. */ export function centreY(containerHeight: number, itemHeight: number): number { return Math.round((containerHeight - itemHeight) / 2); } /** Decimal places a number field shows unless it asks for its own (`NumberFieldOptions.decimals`). */ export const NUMBER_DECIMALS = 3; /** * Rounds to `decimals` places and drops trailing zeros: `100`, `0.5`, `9.091`. A value that rounds * to zero never carries a minus sign. */ export function formatNumber(value: number, decimals = NUMBER_DECIMALS): string { const text = value.toFixed(decimals); const trimmed = text.includes('.') ? text.replace(/\.?0+$/, '') : text; return trimmed === '-0' ? '0' : trimmed; } /** Rounds to `decimals` places, which also removes binary-float noise (0.1 × 3 = 0.30000000000000004). */ export function roundTo(value: number, decimals = NUMBER_DECIMALS): number { return Number.isFinite(value) ? Number(value.toFixed(decimals)) : value; } /** * Rounds `value` onto the grid of `stepSize`, so scrubbing lands on clean values even though * pointer deltas are fractional. */ export function snapToStep(value: number, stepSize: number): number { if (!(stepSize > 0) || !Number.isFinite(value)) return value; return roundTo(Math.round(value / stepSize) * stepSize); } /** * Zoom multiplier for one wheel event. Exponential, so scrolling up and back down by the same * amount lands exactly where it started. The caller clamps into its own range. */ export function zoomFactor(deltaY: number): number { return Math.exp(-deltaY * 0.0015); } export function formatBytes(bytes: number): string { if (bytes >= 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} MB`; // A small sprite is a few hundred bytes, and "0 KB" reads like a broken file. return bytes >= 1024 ? `${Math.round(bytes / 1024)} KB` : `${bytes} B`; }