idr-formatting
Version:
Tiny helpers to format and parse Indonesian-style prices (IDR) with '.' thousands and ',' decimals.
156 lines (155 loc) • 5.8 kB
JavaScript
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
;