TypeScript 87.7%
CSS 12.3%
1/**2 * Author: Simon-Pierre Boucher3 * Contact: contact@spboucher.ai4 * Project: Groupe Ka / Ka Maps5 *6 * Canadian-French price/value formatting shared by all map labels.7 */89/**10 * Compact price label for map markers, French-Canadian style.11 *12 * 289 000 $ → "289 k$"13 * 499 900 $ → "500 k$"14 * 1 250 000 $ → "1,25 M$"15 * 2 800 000 $ → "2,8 M$"16 * 950 $ (loyer) → "950 $"17 */18export function formatCompactPrice(value: number): string {19 if (!Number.isFinite(value) || value < 0) return "";20 if (value < 10_000) {21 // Rents and small amounts: whole dollars, non-breaking thin space groups.22 return `${formatGroupedInt(Math.round(value))} $`;23 }24 if (value < 1_000_000) {25 const k = Math.round(value / 1000);26 if (k >= 1000) return formatMillions(value); // 999 500+ rounds into M$27 return `${k} k$`;28 }29 return formatMillions(value);30}3132function formatMillions(value: number): string {33 const m = value / 1_000_000;34 // Two decimals under 10 M$, one is enough above; strip trailing zeros.35 const rounded = m < 10 ? Math.round(m * 100) / 100 : Math.round(m * 10) / 10;36 const text = rounded37 .toFixed(rounded < 10 ? 2 : 1)38 .replace(/\.?0+$/, "")39 .replace(".", ",");40 return `${text} M$`;41}4243/** Full price, French-Canadian: 589 000 $ (narrow no-break group separators). */44export function formatFullPrice(value: number): string {45 if (!Number.isFinite(value)) return "";46 return `${formatGroupedInt(Math.round(value))} $`;47}4849/** Signed percentage, French style: +5,8 % / -1,2 %. */50export function formatPercent(ratio: number, decimals = 1): string {51 if (!Number.isFinite(ratio)) return "";52 const pct = ratio * 100;53 const sign = pct > 0 ? "+" : "";54 return `${sign}${pct.toFixed(decimals).replace(".", ",")} %`;55}5657/** Group an integer with narrow no-break spaces: 1250000 → "1 250 000". */58function formatGroupedInt(n: number): string {59 const sign = n < 0 ? "-" : "";60 const digits = Math.abs(n).toString();61 const grouped = digits.replace(/\B(?=(\d{3})+(?!\d))/g, " ");62 return sign + grouped;63}6465/** Median of a numeric array (returns undefined for empty input). */66export function median(values: number[]): number | undefined {67 const clean = values.filter((v) => Number.isFinite(v)).sort((a, b) => a - b);68 if (clean.length === 0) return undefined;69 const mid = Math.floor(clean.length / 2);70 return clean.length % 2 === 171 ? clean[mid]72 : ((clean[mid - 1] as number) + (clean[mid] as number)) / 2;73}74