51 lines
2.1 KiB
TypeScript
51 lines
2.1 KiB
TypeScript
/** 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`;
|
||
}
|