UNPKG

idr-formatting

Version:

Tiny helpers to format and parse Indonesian-style prices (IDR) with '.' thousands and ',' decimals.

156 lines (155 loc) 5.8 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { formatIdr: () => formatIdr, parseIdr: () => parseIdr }); module.exports = __toCommonJS(index_exports); function groupThousandsStr(digits) { digits = digits.replace(/^0+(?!$)/, "") || "0"; let out = ""; for (let i = digits.length; i > 0; i -= 3) { const start = Math.max(0, i - 3); const chunk = digits.slice(start, i); out = out ? `${chunk}.${out}` : chunk; } return out; } function applyFixedDecimals(intDigits, decDigits, decimals) { const cleanI = (intDigits || "0").replace(/\D/g, "") || "0"; const cleanD = (decDigits || "").replace(/\D/g, ""); if (decimals <= 0) { const carry = cleanD[0] && +cleanD[0] >= 5 ? 1n : 0n; const big = BigInt(cleanI) + carry; return { i: big.toString(), d: "" }; } if (cleanD.length === decimals) return { i: cleanI, d: cleanD }; if (cleanD.length < decimals) return { i: cleanI, d: cleanD.padEnd(decimals, "0") }; const keep = cleanD.slice(0, decimals); const next = cleanD[decimals] ?? "0"; if (+next < 5) return { i: cleanI, d: keep }; const inc = (BigInt(cleanI + keep) + 1n).toString().padStart(cleanI.length + keep.length, "0"); const newI = inc.slice(0, inc.length - decimals) || "0"; const newD = inc.slice(-decimals); return { i: newI, d: newD }; } function keepFirstCommaOnly(s) { const idx = s.indexOf(","); if (idx < 0) return s; return s.slice(0, idx + 1) + s.slice(idx + 1).replace(/,/g, ""); } function makeFixed(sign, intDigits, decDigits) { const i = intDigits.replace(/^0+(?!$)/, "") || "0"; const d = decDigits.replace(/[^0-9]/g, ""); const scale = d.length; const units = BigInt(i + d); return { sign, units, scale, toNumber() { const n = Number(units) / Math.pow(10, scale); return sign === -1 ? -n : n; }, toString() { if (scale === 0) return (sign === -1 ? "-" : "") + units.toString(); const s = units.toString().padStart(scale + 1, "0"); const head = s.slice(0, s.length - scale); const tail = s.slice(-scale); return (sign === -1 ? "-" : "") + head + "." + tail; } }; } function isFixedIdr(v) { return !!v && typeof v === "object" && "units" in v && "scale" in v; } function formatIdr(value, options = {}) { if (value == null || value === "") return ""; if (isFixedIdr(value)) return formatIdr(value.toString(), options); const { decimals = "auto", padZeros = false } = options; let str = String(value).trim(); const negative = str.startsWith("-"); if (negative) str = str.slice(1); function finish(intDigits, rawDecimals) { if (decimals === "auto") { const iFmt = groupThousandsStr(intDigits); if (!rawDecimals) return iFmt; const dec = padZeros ? rawDecimals.padEnd(2, "0") : rawDecimals; return `${iFmt},${dec}`; } else { const { i, d } = applyFixedDecimals(intDigits, rawDecimals ?? "", decimals); const iFmt = groupThousandsStr(i); return decimals > 0 ? `${iFmt},${d}` : iFmt; } } let display = ""; if (str.includes(",")) { const only = str.replace(/[^0-9,]/g, ""); const raw = keepFirstCommaOnly(only); const [intPart, decPart = ""] = raw.split(","); const intDigits = (intPart || "").replace(/\D/g, "") || "0"; const decDigits = (decPart || "").replace(/\D/g, ""); display = finish(intDigits, decDigits); } else if (str.includes(".")) { const s = str.replace(/\s+/g, ""); const thousandPattern = /^\d{1,3}(\.\d{3})+$/; if (thousandPattern.test(s)) { const digits = s.replace(/\./g, "") || "0"; display = finish(digits); } else { const [left, right = ""] = s.split(".", 2); const intDigits = (left || "").replace(/\D/g, "") || "0"; const decDigits = (right || "").replace(/\D/g, ""); display = finish(intDigits, decDigits); } } else { const digits = str.replace(/\D/g, "") || "0"; display = finish(digits); } return negative ? `-${display}` : display; } function parseIdr(str, opts = {}) { const mode = opts.mode ?? "number"; if (str == null || str === "") return null; let s = String(str).trim(); const isNegative = s.startsWith("-"); if (isNegative) s = s.slice(1); s = s.replace(/[^0-9.,]/g, ""); if (!s) return null; s = keepFirstCommaOnly(s); const normalized = s.replace(/\./g, "").replace(",", "."); if (normalized === "" || normalized === ".") return null; const [intRaw, decRaw = ""] = normalized.split("."); const intDigits = intRaw.replace(/\D/g, "") || "0"; const decDigits = decRaw.replace(/\D/g, ""); if (mode === "fixed") { return makeFixed(isNegative ? -1 : 1, intDigits, decDigits); } else { const num = Number((isNegative ? "-" : "") + intDigits + (decDigits ? "." + decDigits : "")); return Number.isFinite(num) ? num : null; } } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { formatIdr, parseIdr }); //# sourceMappingURL=index.js.map