{"version":3,"file":"numbers.cjs","names":[],"sources":["../../src/utils/numbers.ts"],"sourcesContent":["import { fallbackText, isAbsent, type FormatFallbackOptions } from \"@/utils/absent\";\nimport { numberFormat } from \"@/utils/intl-cache\";\n/**\n * Clamp `value` between `min` and `max` (inclusive).\n *\n * @example\n * clamp(120, 0, 100); // 100\n * clamp(-5, 0, 100);  // 0\n * clamp(42, 0, 100);  // 42\n */\nexport function clamp(value: number, min: number, max: number): number {\n    if (Number.isNaN(value)) return value;\n    if (min > max) {\n        const swap = min;\n        min = max;\n        max = swap;\n    }\n    if (value < min) return min;\n    if (value > max) return max;\n    return value;\n}\n\n/**\n * Format a byte count as a human-readable size string.\n *\n * Scales through B, KB, MB, GB and TB using 1024 as the base. `decimals`\n * controls the fractional digits for scaled units (default 1); `0` bytes\n * always renders as `\"0 B\"`.\n *\n * An absent or unrepresentable count renders as `\"—\"`. It used to render as\n * **`\"NaN undefined\"`**: `Math.log(NaN)` walks the exponent off the end of the\n * unit table, so the unit itself came out `undefined` — the size was not merely\n * unreadable, it claimed a unit that does not exist.\n *\n * @example\n * formatBytes(0);       // \"0 B\"\n * formatBytes(1536);    // \"1.5 KB\"\n * formatBytes(1048576); // \"1 MB\"\n * formatBytes(1536, 2); // \"1.50 KB\"\n * formatBytes(null);    // \"—\"\n */\nexport function formatBytes(\n    bytes: number | null | undefined,\n    decimals: number = 1,\n    options?: FormatFallbackOptions,\n): string {\n    if (isAbsent(bytes) || !Number.isFinite(bytes)) return fallbackText(options);\n    if (bytes === 0) return \"0 B\";\n\n    const units = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"];\n    const base = 1024;\n    const exponent = Math.min(\n        Math.floor(Math.log(Math.abs(bytes)) / Math.log(base)),\n        units.length - 1,\n    );\n    const value = bytes / base ** exponent;\n    const digits = exponent === 0 ? 0 : Math.max(0, decimals);\n    const formatted = parseFloat(value.toFixed(digits)).toString();\n\n    return `${formatted} ${units[exponent]}`;\n}\n\n/**\n * What percentage of `total` is `part`, with an empty base yielding `0`.\n *\n * The guard is the whole point: `(part / total) * 100` produces `NaN` when the\n * base is zero and `Infinity` when only the base is missing, and both reach the\n * screen as a broken label — `NaN%` on an empty dashboard is the single most\n * common way a panel announces that it has no data yet.\n *\n * Returns a **0–100** number, which is what a bar width and a label want.\n * `formatPercent` takes a 0–1 fraction instead, so pair them as\n * `formatPercent(percentOf(a, b) / 100)` — or skip `percentOf` and pass the\n * fraction straight in.\n *\n * The result is not clamped: 120% of a target is a real number somebody wants to\n * see, and capping it would hide the interesting case.\n *\n * @example\n * percentOf(3, 4);  // 75\n * percentOf(5, 0);  // 0\n * percentOf(12, 8); // 150\n *\n * @param part - The measured amount.\n * @param total - The base it is measured against.\n * @returns The percentage, or `0` when the base is zero or either input is not finite.\n */\nexport function percentOf(part: number, total: number): number {\n    if (!Number.isFinite(part) || !Number.isFinite(total) || total === 0) return 0;\n    return (part / total) * 100;\n}\n\n/**\n * Format a number using compact notation (e.g. `1.2K`, `3.4M`).\n *\n * Wraps `Intl.NumberFormat` with `notation: \"compact\"`. The `locale` defaults\n * to `\"en-US\"`.\n *\n * An absent or unrepresentable value renders as `\"—\"`. The `null` case is the\n * one that matters: `Intl` coerces it to zero, so a count the backend left out\n * used to read as **`\"0\"`** — a number the reader has no reason to doubt.\n *\n * @example\n * formatCompactNumber(1234);            // \"1.2K\"\n * formatCompactNumber(5600000);         // \"5.6M\"\n * formatCompactNumber(1234, \"pt-BR\");   // \"1,2 mil\"\n * formatCompactNumber(null);            // \"—\"\n */\nexport function formatCompactNumber(\n    value: number | null | undefined,\n    locale: string = \"en-US\",\n    options?: FormatFallbackOptions,\n): string {\n    if (isAbsent(value) || !Number.isFinite(value)) return fallbackText(options);\n    return numberFormat(locale, { notation: \"compact\", maximumFractionDigits: 1 }).format(value);\n}\n"],"mappings":"8DAUA,SAAgB,EAAM,EAAe,EAAa,EAAqB,CACnE,GAAI,OAAO,MAAM,CAAK,EAAG,OAAO,EAChC,GAAI,EAAM,EAAK,CACX,IAAM,EAAO,EACb,EAAM,EACN,EAAM,CACV,CAGA,OAFI,EAAQ,EAAY,EACpB,EAAQ,EAAY,EACjB,CACX,CAqBA,SAAgB,EACZ,EACA,EAAmB,EACnB,EACM,CACN,GAAI,EAAA,SAAS,CAAK,GAAK,CAAC,OAAO,SAAS,CAAK,EAAG,OAAO,EAAA,aAAa,CAAO,EAC3E,GAAI,IAAU,EAAG,MAAO,MAExB,IAAM,EAAQ,CAAC,IAAK,KAAM,KAAM,KAAM,IAAI,EACpC,EAAO,KACP,EAAW,KAAK,IAClB,KAAK,MAAM,KAAK,IAAI,KAAK,IAAI,CAAK,CAAC,EAAI,KAAK,IAAI,CAAI,CAAC,EACrD,EAAM,OAAS,CACnB,EACM,EAAQ,EAAQ,GAAQ,EACxB,EAAS,IAAa,EAAI,EAAI,KAAK,IAAI,EAAG,CAAQ,EAGxD,MAAO,GAFW,WAAW,EAAM,QAAQ,CAAM,CAAC,CAAC,CAAC,SAE1C,EAAU,GAAG,EAAM,IACjC,CA2BA,SAAgB,EAAU,EAAc,EAAuB,CAE3D,MADI,CAAC,OAAO,SAAS,CAAI,GAAK,CAAC,OAAO,SAAS,CAAK,GAAK,IAAU,EAAU,EACrE,EAAO,EAAS,GAC5B,CAkBA,SAAgB,EACZ,EACA,EAAiB,QACjB,EACM,CAEN,OADI,EAAA,SAAS,CAAK,GAAK,CAAC,OAAO,SAAS,CAAK,EAAU,EAAA,aAAa,CAAO,EACpE,EAAA,aAAa,EAAQ,CAAE,SAAU,UAAW,sBAAuB,CAAE,CAAC,CAAC,CAAC,OAAO,CAAK,CAC/F"}