@lacspace/humanize 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +8 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -95,7 +95,14 @@ function compact(n, decimals = 1) {
|
|
|
95
95
|
function number(n, opts = {}) {
|
|
96
96
|
const sep = opts.separator ?? ",";
|
|
97
97
|
const dec = opts.decimal ?? ".";
|
|
98
|
-
|
|
98
|
+
if (!Number.isFinite(n)) return String(n);
|
|
99
|
+
const str = String(n);
|
|
100
|
+
if (str.includes("e") || str.includes("E")) {
|
|
101
|
+
const grouped = n.toLocaleString("en-US", { maximumFractionDigits: 20, useGrouping: true });
|
|
102
|
+
const [g, f] = grouped.split(".");
|
|
103
|
+
return g.split(",").join(sep) + (f ? dec + f : "");
|
|
104
|
+
}
|
|
105
|
+
const [int, frac] = str.split(".");
|
|
99
106
|
const sign = int.startsWith("-") ? "-" : "";
|
|
100
107
|
const digits = int.replace("-", "").replace(/\B(?=(\d{3})+(?!\d))/g, sep);
|
|
101
108
|
return sign + digits + (frac ? dec + frac : "");
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * @lacspace/humanize\n *\n * Turn machine values into human-readable text — bytes, durations, relative\n * time, ordinals, plurals, compact numbers and grammatical lists.\n * Zero dependencies, isomorphic.\n */\n\n/* ------------------------------ bytes ------------------------------ */\n\nconst BYTE_UNITS = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\", \"PB\", \"EB\"];\nconst BYTE_UNITS_IEC = [\"B\", \"KiB\", \"MiB\", \"GiB\", \"TiB\", \"PiB\", \"EiB\"];\n\nexport interface BytesOptions {\n /** Use 1024-based IEC units (KiB, MiB…). Default false (1000-based). */\n binary?: boolean;\n /** Decimal places. Default 1 (0 for bytes). */\n decimals?: number;\n}\n\n/** Human-readable file size. `bytes(1536)` → \"1.5 KB\". */\nexport function bytes(n: number, opts: BytesOptions = {}): string {\n const base = opts.binary ? 1024 : 1000;\n const units = opts.binary ? BYTE_UNITS_IEC : BYTE_UNITS;\n const neg = n < 0 ? \"-\" : \"\";\n let v = Math.abs(n);\n if (v < base) return `${neg}${v} B`;\n let i = 0;\n while (v >= base && i < units.length - 1) { v /= base; i++; }\n const dp = opts.decimals ?? 1;\n return `${neg}${v.toFixed(dp)} ${units[i]}`;\n}\n\n/** Parse a size string back to bytes. `parseBytes(\"1.5 KB\")` → 1500. */\nexport function parseBytes(input: string): number {\n const m = /^\\s*(-?[\\d.]+)\\s*([a-z]+)?\\s*$/i.exec(input);\n if (!m) return NaN;\n const value = parseFloat(m[1]!);\n const unit = (m[2] ?? \"B\").toUpperCase();\n const binary = unit.includes(\"I\");\n const idx = (binary ? BYTE_UNITS_IEC : BYTE_UNITS).findIndex((u) => u.toUpperCase() === unit);\n if (idx === -1) return unit === \"B\" ? value : NaN;\n return value * (binary ? 1024 : 1000) ** idx;\n}\n\n/* ------------------------------ duration ------------------------------ */\n\nconst DUR = [\n { u: \"y\", ms: 31536000000, long: \"year\" },\n { u: \"mo\", ms: 2592000000, long: \"month\" },\n { u: \"d\", ms: 86400000, long: \"day\" },\n { u: \"h\", ms: 3600000, long: \"hour\" },\n { u: \"m\", ms: 60000, long: \"minute\" },\n { u: \"s\", ms: 1000, long: \"second\" },\n { u: \"ms\", ms: 1, long: \"millisecond\" },\n];\n\nexport interface DurationOptions {\n /** Max number of units to show. Default 2. */\n units?: number;\n /** Use full words (\"2 hours 3 minutes\"). Default false (\"2h 3m\"). */\n long?: boolean;\n}\n\n/** Human-readable duration. `duration(90061000)` → \"1d 1h\". */\nexport function duration(ms: number, opts: DurationOptions = {}): string {\n const max = opts.units ?? 2;\n let rem = Math.abs(Math.round(ms));\n if (rem === 0) return opts.long ? \"0 seconds\" : \"0s\";\n const parts: string[] = [];\n for (const d of DUR) {\n if (rem < d.ms) continue;\n const count = Math.floor(rem / d.ms);\n rem -= count * d.ms;\n parts.push(opts.long ? `${count} ${plural(d.long, count)}` : `${count}${d.u}`);\n if (parts.length >= max) break;\n }\n return (ms < 0 ? \"-\" : \"\") + parts.join(opts.long ? \" \" : \" \");\n}\n\n/* ------------------------------ relative time ------------------------------ */\n\nconst RT: [number, string][] = [\n [31536000, \"year\"], [2592000, \"month\"], [604800, \"week\"],\n [86400, \"day\"], [3600, \"hour\"], [60, \"minute\"], [1, \"second\"],\n];\n\n/** Relative time. `relativeTime(Date.now() - 3600e3)` → \"1 hour ago\". */\nexport function relativeTime(date: Date | number | string, base: Date | number = Date.now()): string {\n const t = date instanceof Date ? date.getTime() : typeof date === \"string\" ? new Date(date).getTime() : date;\n const b = base instanceof Date ? base.getTime() : base;\n const diff = Math.round((b - t) / 1000);\n const abs = Math.abs(diff);\n if (abs < 5) return \"just now\";\n for (const [secs, unit] of RT) {\n if (abs >= secs) {\n const count = Math.floor(abs / secs);\n const label = `${count} ${plural(unit, count)}`;\n return diff >= 0 ? `${label} ago` : `in ${label}`;\n }\n }\n return \"just now\";\n}\n\n/* ------------------------------ numbers ------------------------------ */\n\n/** Ordinal suffix. `ordinal(21)` → \"21st\". */\nexport function ordinal(n: number): string {\n const abs = Math.abs(n) % 100;\n const last = abs % 10;\n const suffix = abs >= 11 && abs <= 13 ? \"th\" : last === 1 ? \"st\" : last === 2 ? \"nd\" : last === 3 ? \"rd\" : \"th\";\n return `${n}${suffix}`;\n}\n\n/** Compact number. `compact(1234567)` → \"1.2M\". */\nexport function compact(n: number, decimals = 1): string {\n const neg = n < 0 ? \"-\" : \"\";\n const v = Math.abs(n);\n const units: [number, string][] = [[1e12, \"T\"], [1e9, \"B\"], [1e6, \"M\"], [1e3, \"K\"]];\n for (const [threshold, u] of units) {\n if (v >= threshold) {\n const num = v / threshold;\n return `${neg}${(num % 1 === 0 ? num.toFixed(0) : num.toFixed(decimals))}${u}`;\n }\n }\n return `${neg}${v}`;\n}\n\n/** Group digits with separators. `number(1234567.5)` → \"1,234,567.5\". */\nexport function number(n: number, opts: { separator?: string; decimal?: string } = {}): string {\n const sep = opts.separator ?? \",\";\n const dec = opts.decimal ?? \".\";\n const [int, frac] = String(n).split(\".\");\n const sign = int!.startsWith(\"-\") ? \"-\" : \"\";\n const digits = int!.replace(\"-\", \"\").replace(/\\B(?=(\\d{3})+(?!\\d))/g, sep);\n return sign + digits + (frac ? dec + frac : \"\");\n}\n\n/* ------------------------------ words ------------------------------ */\n\n/** Pluralize a word for a count (naive English + irregulars). */\nexport function plural(word: string, count: number, pluralForm?: string): string {\n if (count === 1) return word;\n if (pluralForm) return pluralForm;\n if (/(s|x|z|ch|sh)$/i.test(word)) return `${word}es`;\n if (/[^aeiou]y$/i.test(word)) return `${word.slice(0, -1)}ies`;\n return `${word}s`;\n}\n\n/** \"5 items\" / \"1 item\". */\nexport function pluralize(count: number, word: string, pluralForm?: string): string {\n return `${count} ${plural(word, count, pluralForm)}`;\n}\n\n/** Grammatical list. `list([\"a\",\"b\",\"c\"])` → \"a, b and c\". */\nexport function list(items: string[], opts: { conjunction?: string; oxford?: boolean } = {}): string {\n const conj = opts.conjunction ?? \"and\";\n if (items.length === 0) return \"\";\n if (items.length === 1) return items[0]!;\n if (items.length === 2) return `${items[0]} ${conj} ${items[1]}`;\n const head = items.slice(0, -1).join(\", \");\n return `${head}${opts.oxford ? \",\" : \"\"} ${conj} ${items[items.length - 1]}`;\n}\n\n/** Truncate to a max length with an ellipsis on a word boundary. */\nexport function truncate(text: string, maxLength: number, ellipsis = \"…\"): string {\n if (text.length <= maxLength) return text;\n const cut = text.slice(0, maxLength - ellipsis.length);\n const lastSpace = cut.lastIndexOf(\" \");\n return (lastSpace > maxLength * 0.6 ? cut.slice(0, lastSpace) : cut).replace(/[\\s.,;:!?-]+$/, \"\") + ellipsis;\n}\n\n/** Title Case a string. */\nexport function titleCase(text: string): string {\n return text.toLowerCase().replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * @lacspace/humanize\n *\n * Turn machine values into human-readable text — bytes, durations, relative\n * time, ordinals, plurals, compact numbers and grammatical lists.\n * Zero dependencies, isomorphic.\n */\n\n/* ------------------------------ bytes ------------------------------ */\n\nconst BYTE_UNITS = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\", \"PB\", \"EB\"];\nconst BYTE_UNITS_IEC = [\"B\", \"KiB\", \"MiB\", \"GiB\", \"TiB\", \"PiB\", \"EiB\"];\n\nexport interface BytesOptions {\n /** Use 1024-based IEC units (KiB, MiB…). Default false (1000-based). */\n binary?: boolean;\n /** Decimal places. Default 1 (0 for bytes). */\n decimals?: number;\n}\n\n/** Human-readable file size. `bytes(1536)` → \"1.5 KB\". */\nexport function bytes(n: number, opts: BytesOptions = {}): string {\n const base = opts.binary ? 1024 : 1000;\n const units = opts.binary ? BYTE_UNITS_IEC : BYTE_UNITS;\n const neg = n < 0 ? \"-\" : \"\";\n let v = Math.abs(n);\n if (v < base) return `${neg}${v} B`;\n let i = 0;\n while (v >= base && i < units.length - 1) { v /= base; i++; }\n const dp = opts.decimals ?? 1;\n return `${neg}${v.toFixed(dp)} ${units[i]}`;\n}\n\n/** Parse a size string back to bytes. `parseBytes(\"1.5 KB\")` → 1500. */\nexport function parseBytes(input: string): number {\n const m = /^\\s*(-?[\\d.]+)\\s*([a-z]+)?\\s*$/i.exec(input);\n if (!m) return NaN;\n const value = parseFloat(m[1]!);\n const unit = (m[2] ?? \"B\").toUpperCase();\n const binary = unit.includes(\"I\");\n const idx = (binary ? BYTE_UNITS_IEC : BYTE_UNITS).findIndex((u) => u.toUpperCase() === unit);\n if (idx === -1) return unit === \"B\" ? value : NaN;\n return value * (binary ? 1024 : 1000) ** idx;\n}\n\n/* ------------------------------ duration ------------------------------ */\n\nconst DUR = [\n { u: \"y\", ms: 31536000000, long: \"year\" },\n { u: \"mo\", ms: 2592000000, long: \"month\" },\n { u: \"d\", ms: 86400000, long: \"day\" },\n { u: \"h\", ms: 3600000, long: \"hour\" },\n { u: \"m\", ms: 60000, long: \"minute\" },\n { u: \"s\", ms: 1000, long: \"second\" },\n { u: \"ms\", ms: 1, long: \"millisecond\" },\n];\n\nexport interface DurationOptions {\n /** Max number of units to show. Default 2. */\n units?: number;\n /** Use full words (\"2 hours 3 minutes\"). Default false (\"2h 3m\"). */\n long?: boolean;\n}\n\n/** Human-readable duration. `duration(90061000)` → \"1d 1h\". */\nexport function duration(ms: number, opts: DurationOptions = {}): string {\n const max = opts.units ?? 2;\n let rem = Math.abs(Math.round(ms));\n if (rem === 0) return opts.long ? \"0 seconds\" : \"0s\";\n const parts: string[] = [];\n for (const d of DUR) {\n if (rem < d.ms) continue;\n const count = Math.floor(rem / d.ms);\n rem -= count * d.ms;\n parts.push(opts.long ? `${count} ${plural(d.long, count)}` : `${count}${d.u}`);\n if (parts.length >= max) break;\n }\n return (ms < 0 ? \"-\" : \"\") + parts.join(opts.long ? \" \" : \" \");\n}\n\n/* ------------------------------ relative time ------------------------------ */\n\nconst RT: [number, string][] = [\n [31536000, \"year\"], [2592000, \"month\"], [604800, \"week\"],\n [86400, \"day\"], [3600, \"hour\"], [60, \"minute\"], [1, \"second\"],\n];\n\n/** Relative time. `relativeTime(Date.now() - 3600e3)` → \"1 hour ago\". */\nexport function relativeTime(date: Date | number | string, base: Date | number = Date.now()): string {\n const t = date instanceof Date ? date.getTime() : typeof date === \"string\" ? new Date(date).getTime() : date;\n const b = base instanceof Date ? base.getTime() : base;\n const diff = Math.round((b - t) / 1000);\n const abs = Math.abs(diff);\n if (abs < 5) return \"just now\";\n for (const [secs, unit] of RT) {\n if (abs >= secs) {\n const count = Math.floor(abs / secs);\n const label = `${count} ${plural(unit, count)}`;\n return diff >= 0 ? `${label} ago` : `in ${label}`;\n }\n }\n return \"just now\";\n}\n\n/* ------------------------------ numbers ------------------------------ */\n\n/** Ordinal suffix. `ordinal(21)` → \"21st\". */\nexport function ordinal(n: number): string {\n const abs = Math.abs(n) % 100;\n const last = abs % 10;\n const suffix = abs >= 11 && abs <= 13 ? \"th\" : last === 1 ? \"st\" : last === 2 ? \"nd\" : last === 3 ? \"rd\" : \"th\";\n return `${n}${suffix}`;\n}\n\n/** Compact number. `compact(1234567)` → \"1.2M\". */\nexport function compact(n: number, decimals = 1): string {\n const neg = n < 0 ? \"-\" : \"\";\n const v = Math.abs(n);\n const units: [number, string][] = [[1e12, \"T\"], [1e9, \"B\"], [1e6, \"M\"], [1e3, \"K\"]];\n for (const [threshold, u] of units) {\n if (v >= threshold) {\n const num = v / threshold;\n return `${neg}${(num % 1 === 0 ? num.toFixed(0) : num.toFixed(decimals))}${u}`;\n }\n }\n return `${neg}${v}`;\n}\n\n/** Group digits with separators. `number(1234567.5)` → \"1,234,567.5\". */\nexport function number(n: number, opts: { separator?: string; decimal?: string } = {}): string {\n const sep = opts.separator ?? \",\";\n const dec = opts.decimal ?? \".\";\n if (!Number.isFinite(n)) return String(n);\n const str = String(n);\n // Exponential notation (e.g. 1e21, 1e-7) mangles a naive split — expand it via\n // locale grouping (en-US: \",\" groups, \".\" decimal) then apply the custom separators.\n if (str.includes(\"e\") || str.includes(\"E\")) {\n const grouped = n.toLocaleString(\"en-US\", { maximumFractionDigits: 20, useGrouping: true });\n const [g, f] = grouped.split(\".\");\n return g!.split(\",\").join(sep) + (f ? dec + f : \"\");\n }\n const [int, frac] = str.split(\".\");\n const sign = int!.startsWith(\"-\") ? \"-\" : \"\";\n const digits = int!.replace(\"-\", \"\").replace(/\\B(?=(\\d{3})+(?!\\d))/g, sep);\n return sign + digits + (frac ? dec + frac : \"\");\n}\n\n/* ------------------------------ words ------------------------------ */\n\n/** Pluralize a word for a count (naive English + irregulars). */\nexport function plural(word: string, count: number, pluralForm?: string): string {\n if (count === 1) return word;\n if (pluralForm) return pluralForm;\n if (/(s|x|z|ch|sh)$/i.test(word)) return `${word}es`;\n if (/[^aeiou]y$/i.test(word)) return `${word.slice(0, -1)}ies`;\n return `${word}s`;\n}\n\n/** \"5 items\" / \"1 item\". */\nexport function pluralize(count: number, word: string, pluralForm?: string): string {\n return `${count} ${plural(word, count, pluralForm)}`;\n}\n\n/** Grammatical list. `list([\"a\",\"b\",\"c\"])` → \"a, b and c\". */\nexport function list(items: string[], opts: { conjunction?: string; oxford?: boolean } = {}): string {\n const conj = opts.conjunction ?? \"and\";\n if (items.length === 0) return \"\";\n if (items.length === 1) return items[0]!;\n if (items.length === 2) return `${items[0]} ${conj} ${items[1]}`;\n const head = items.slice(0, -1).join(\", \");\n return `${head}${opts.oxford ? \",\" : \"\"} ${conj} ${items[items.length - 1]}`;\n}\n\n/** Truncate to a max length with an ellipsis on a word boundary. */\nexport function truncate(text: string, maxLength: number, ellipsis = \"…\"): string {\n if (text.length <= maxLength) return text;\n const cut = text.slice(0, maxLength - ellipsis.length);\n const lastSpace = cut.lastIndexOf(\" \");\n return (lastSpace > maxLength * 0.6 ? cut.slice(0, lastSpace) : cut).replace(/[\\s.,;:!?-]+$/, \"\") + ellipsis;\n}\n\n/** Title Case a string. */\nexport function titleCase(text: string): string {\n return text.toLowerCase().replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n"]}
|
package/dist/index.js
CHANGED
|
@@ -93,7 +93,14 @@ function compact(n, decimals = 1) {
|
|
|
93
93
|
function number(n, opts = {}) {
|
|
94
94
|
const sep = opts.separator ?? ",";
|
|
95
95
|
const dec = opts.decimal ?? ".";
|
|
96
|
-
|
|
96
|
+
if (!Number.isFinite(n)) return String(n);
|
|
97
|
+
const str = String(n);
|
|
98
|
+
if (str.includes("e") || str.includes("E")) {
|
|
99
|
+
const grouped = n.toLocaleString("en-US", { maximumFractionDigits: 20, useGrouping: true });
|
|
100
|
+
const [g, f] = grouped.split(".");
|
|
101
|
+
return g.split(",").join(sep) + (f ? dec + f : "");
|
|
102
|
+
}
|
|
103
|
+
const [int, frac] = str.split(".");
|
|
97
104
|
const sign = int.startsWith("-") ? "-" : "";
|
|
98
105
|
const digits = int.replace("-", "").replace(/\B(?=(\d{3})+(?!\d))/g, sep);
|
|
99
106
|
return sign + digits + (frac ? dec + frac : "");
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @lacspace/humanize\n *\n * Turn machine values into human-readable text — bytes, durations, relative\n * time, ordinals, plurals, compact numbers and grammatical lists.\n * Zero dependencies, isomorphic.\n */\n\n/* ------------------------------ bytes ------------------------------ */\n\nconst BYTE_UNITS = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\", \"PB\", \"EB\"];\nconst BYTE_UNITS_IEC = [\"B\", \"KiB\", \"MiB\", \"GiB\", \"TiB\", \"PiB\", \"EiB\"];\n\nexport interface BytesOptions {\n /** Use 1024-based IEC units (KiB, MiB…). Default false (1000-based). */\n binary?: boolean;\n /** Decimal places. Default 1 (0 for bytes). */\n decimals?: number;\n}\n\n/** Human-readable file size. `bytes(1536)` → \"1.5 KB\". */\nexport function bytes(n: number, opts: BytesOptions = {}): string {\n const base = opts.binary ? 1024 : 1000;\n const units = opts.binary ? BYTE_UNITS_IEC : BYTE_UNITS;\n const neg = n < 0 ? \"-\" : \"\";\n let v = Math.abs(n);\n if (v < base) return `${neg}${v} B`;\n let i = 0;\n while (v >= base && i < units.length - 1) { v /= base; i++; }\n const dp = opts.decimals ?? 1;\n return `${neg}${v.toFixed(dp)} ${units[i]}`;\n}\n\n/** Parse a size string back to bytes. `parseBytes(\"1.5 KB\")` → 1500. */\nexport function parseBytes(input: string): number {\n const m = /^\\s*(-?[\\d.]+)\\s*([a-z]+)?\\s*$/i.exec(input);\n if (!m) return NaN;\n const value = parseFloat(m[1]!);\n const unit = (m[2] ?? \"B\").toUpperCase();\n const binary = unit.includes(\"I\");\n const idx = (binary ? BYTE_UNITS_IEC : BYTE_UNITS).findIndex((u) => u.toUpperCase() === unit);\n if (idx === -1) return unit === \"B\" ? value : NaN;\n return value * (binary ? 1024 : 1000) ** idx;\n}\n\n/* ------------------------------ duration ------------------------------ */\n\nconst DUR = [\n { u: \"y\", ms: 31536000000, long: \"year\" },\n { u: \"mo\", ms: 2592000000, long: \"month\" },\n { u: \"d\", ms: 86400000, long: \"day\" },\n { u: \"h\", ms: 3600000, long: \"hour\" },\n { u: \"m\", ms: 60000, long: \"minute\" },\n { u: \"s\", ms: 1000, long: \"second\" },\n { u: \"ms\", ms: 1, long: \"millisecond\" },\n];\n\nexport interface DurationOptions {\n /** Max number of units to show. Default 2. */\n units?: number;\n /** Use full words (\"2 hours 3 minutes\"). Default false (\"2h 3m\"). */\n long?: boolean;\n}\n\n/** Human-readable duration. `duration(90061000)` → \"1d 1h\". */\nexport function duration(ms: number, opts: DurationOptions = {}): string {\n const max = opts.units ?? 2;\n let rem = Math.abs(Math.round(ms));\n if (rem === 0) return opts.long ? \"0 seconds\" : \"0s\";\n const parts: string[] = [];\n for (const d of DUR) {\n if (rem < d.ms) continue;\n const count = Math.floor(rem / d.ms);\n rem -= count * d.ms;\n parts.push(opts.long ? `${count} ${plural(d.long, count)}` : `${count}${d.u}`);\n if (parts.length >= max) break;\n }\n return (ms < 0 ? \"-\" : \"\") + parts.join(opts.long ? \" \" : \" \");\n}\n\n/* ------------------------------ relative time ------------------------------ */\n\nconst RT: [number, string][] = [\n [31536000, \"year\"], [2592000, \"month\"], [604800, \"week\"],\n [86400, \"day\"], [3600, \"hour\"], [60, \"minute\"], [1, \"second\"],\n];\n\n/** Relative time. `relativeTime(Date.now() - 3600e3)` → \"1 hour ago\". */\nexport function relativeTime(date: Date | number | string, base: Date | number = Date.now()): string {\n const t = date instanceof Date ? date.getTime() : typeof date === \"string\" ? new Date(date).getTime() : date;\n const b = base instanceof Date ? base.getTime() : base;\n const diff = Math.round((b - t) / 1000);\n const abs = Math.abs(diff);\n if (abs < 5) return \"just now\";\n for (const [secs, unit] of RT) {\n if (abs >= secs) {\n const count = Math.floor(abs / secs);\n const label = `${count} ${plural(unit, count)}`;\n return diff >= 0 ? `${label} ago` : `in ${label}`;\n }\n }\n return \"just now\";\n}\n\n/* ------------------------------ numbers ------------------------------ */\n\n/** Ordinal suffix. `ordinal(21)` → \"21st\". */\nexport function ordinal(n: number): string {\n const abs = Math.abs(n) % 100;\n const last = abs % 10;\n const suffix = abs >= 11 && abs <= 13 ? \"th\" : last === 1 ? \"st\" : last === 2 ? \"nd\" : last === 3 ? \"rd\" : \"th\";\n return `${n}${suffix}`;\n}\n\n/** Compact number. `compact(1234567)` → \"1.2M\". */\nexport function compact(n: number, decimals = 1): string {\n const neg = n < 0 ? \"-\" : \"\";\n const v = Math.abs(n);\n const units: [number, string][] = [[1e12, \"T\"], [1e9, \"B\"], [1e6, \"M\"], [1e3, \"K\"]];\n for (const [threshold, u] of units) {\n if (v >= threshold) {\n const num = v / threshold;\n return `${neg}${(num % 1 === 0 ? num.toFixed(0) : num.toFixed(decimals))}${u}`;\n }\n }\n return `${neg}${v}`;\n}\n\n/** Group digits with separators. `number(1234567.5)` → \"1,234,567.5\". */\nexport function number(n: number, opts: { separator?: string; decimal?: string } = {}): string {\n const sep = opts.separator ?? \",\";\n const dec = opts.decimal ?? \".\";\n const [int, frac] = String(n).split(\".\");\n const sign = int!.startsWith(\"-\") ? \"-\" : \"\";\n const digits = int!.replace(\"-\", \"\").replace(/\\B(?=(\\d{3})+(?!\\d))/g, sep);\n return sign + digits + (frac ? dec + frac : \"\");\n}\n\n/* ------------------------------ words ------------------------------ */\n\n/** Pluralize a word for a count (naive English + irregulars). */\nexport function plural(word: string, count: number, pluralForm?: string): string {\n if (count === 1) return word;\n if (pluralForm) return pluralForm;\n if (/(s|x|z|ch|sh)$/i.test(word)) return `${word}es`;\n if (/[^aeiou]y$/i.test(word)) return `${word.slice(0, -1)}ies`;\n return `${word}s`;\n}\n\n/** \"5 items\" / \"1 item\". */\nexport function pluralize(count: number, word: string, pluralForm?: string): string {\n return `${count} ${plural(word, count, pluralForm)}`;\n}\n\n/** Grammatical list. `list([\"a\",\"b\",\"c\"])` → \"a, b and c\". */\nexport function list(items: string[], opts: { conjunction?: string; oxford?: boolean } = {}): string {\n const conj = opts.conjunction ?? \"and\";\n if (items.length === 0) return \"\";\n if (items.length === 1) return items[0]!;\n if (items.length === 2) return `${items[0]} ${conj} ${items[1]}`;\n const head = items.slice(0, -1).join(\", \");\n return `${head}${opts.oxford ? \",\" : \"\"} ${conj} ${items[items.length - 1]}`;\n}\n\n/** Truncate to a max length with an ellipsis on a word boundary. */\nexport function truncate(text: string, maxLength: number, ellipsis = \"…\"): string {\n if (text.length <= maxLength) return text;\n const cut = text.slice(0, maxLength - ellipsis.length);\n const lastSpace = cut.lastIndexOf(\" \");\n return (lastSpace > maxLength * 0.6 ? cut.slice(0, lastSpace) : cut).replace(/[\\s.,;:!?-]+$/, \"\") + ellipsis;\n}\n\n/** Title Case a string. */\nexport function titleCase(text: string): string {\n return text.toLowerCase().replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @lacspace/humanize\n *\n * Turn machine values into human-readable text — bytes, durations, relative\n * time, ordinals, plurals, compact numbers and grammatical lists.\n * Zero dependencies, isomorphic.\n */\n\n/* ------------------------------ bytes ------------------------------ */\n\nconst BYTE_UNITS = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\", \"PB\", \"EB\"];\nconst BYTE_UNITS_IEC = [\"B\", \"KiB\", \"MiB\", \"GiB\", \"TiB\", \"PiB\", \"EiB\"];\n\nexport interface BytesOptions {\n /** Use 1024-based IEC units (KiB, MiB…). Default false (1000-based). */\n binary?: boolean;\n /** Decimal places. Default 1 (0 for bytes). */\n decimals?: number;\n}\n\n/** Human-readable file size. `bytes(1536)` → \"1.5 KB\". */\nexport function bytes(n: number, opts: BytesOptions = {}): string {\n const base = opts.binary ? 1024 : 1000;\n const units = opts.binary ? BYTE_UNITS_IEC : BYTE_UNITS;\n const neg = n < 0 ? \"-\" : \"\";\n let v = Math.abs(n);\n if (v < base) return `${neg}${v} B`;\n let i = 0;\n while (v >= base && i < units.length - 1) { v /= base; i++; }\n const dp = opts.decimals ?? 1;\n return `${neg}${v.toFixed(dp)} ${units[i]}`;\n}\n\n/** Parse a size string back to bytes. `parseBytes(\"1.5 KB\")` → 1500. */\nexport function parseBytes(input: string): number {\n const m = /^\\s*(-?[\\d.]+)\\s*([a-z]+)?\\s*$/i.exec(input);\n if (!m) return NaN;\n const value = parseFloat(m[1]!);\n const unit = (m[2] ?? \"B\").toUpperCase();\n const binary = unit.includes(\"I\");\n const idx = (binary ? BYTE_UNITS_IEC : BYTE_UNITS).findIndex((u) => u.toUpperCase() === unit);\n if (idx === -1) return unit === \"B\" ? value : NaN;\n return value * (binary ? 1024 : 1000) ** idx;\n}\n\n/* ------------------------------ duration ------------------------------ */\n\nconst DUR = [\n { u: \"y\", ms: 31536000000, long: \"year\" },\n { u: \"mo\", ms: 2592000000, long: \"month\" },\n { u: \"d\", ms: 86400000, long: \"day\" },\n { u: \"h\", ms: 3600000, long: \"hour\" },\n { u: \"m\", ms: 60000, long: \"minute\" },\n { u: \"s\", ms: 1000, long: \"second\" },\n { u: \"ms\", ms: 1, long: \"millisecond\" },\n];\n\nexport interface DurationOptions {\n /** Max number of units to show. Default 2. */\n units?: number;\n /** Use full words (\"2 hours 3 minutes\"). Default false (\"2h 3m\"). */\n long?: boolean;\n}\n\n/** Human-readable duration. `duration(90061000)` → \"1d 1h\". */\nexport function duration(ms: number, opts: DurationOptions = {}): string {\n const max = opts.units ?? 2;\n let rem = Math.abs(Math.round(ms));\n if (rem === 0) return opts.long ? \"0 seconds\" : \"0s\";\n const parts: string[] = [];\n for (const d of DUR) {\n if (rem < d.ms) continue;\n const count = Math.floor(rem / d.ms);\n rem -= count * d.ms;\n parts.push(opts.long ? `${count} ${plural(d.long, count)}` : `${count}${d.u}`);\n if (parts.length >= max) break;\n }\n return (ms < 0 ? \"-\" : \"\") + parts.join(opts.long ? \" \" : \" \");\n}\n\n/* ------------------------------ relative time ------------------------------ */\n\nconst RT: [number, string][] = [\n [31536000, \"year\"], [2592000, \"month\"], [604800, \"week\"],\n [86400, \"day\"], [3600, \"hour\"], [60, \"minute\"], [1, \"second\"],\n];\n\n/** Relative time. `relativeTime(Date.now() - 3600e3)` → \"1 hour ago\". */\nexport function relativeTime(date: Date | number | string, base: Date | number = Date.now()): string {\n const t = date instanceof Date ? date.getTime() : typeof date === \"string\" ? new Date(date).getTime() : date;\n const b = base instanceof Date ? base.getTime() : base;\n const diff = Math.round((b - t) / 1000);\n const abs = Math.abs(diff);\n if (abs < 5) return \"just now\";\n for (const [secs, unit] of RT) {\n if (abs >= secs) {\n const count = Math.floor(abs / secs);\n const label = `${count} ${plural(unit, count)}`;\n return diff >= 0 ? `${label} ago` : `in ${label}`;\n }\n }\n return \"just now\";\n}\n\n/* ------------------------------ numbers ------------------------------ */\n\n/** Ordinal suffix. `ordinal(21)` → \"21st\". */\nexport function ordinal(n: number): string {\n const abs = Math.abs(n) % 100;\n const last = abs % 10;\n const suffix = abs >= 11 && abs <= 13 ? \"th\" : last === 1 ? \"st\" : last === 2 ? \"nd\" : last === 3 ? \"rd\" : \"th\";\n return `${n}${suffix}`;\n}\n\n/** Compact number. `compact(1234567)` → \"1.2M\". */\nexport function compact(n: number, decimals = 1): string {\n const neg = n < 0 ? \"-\" : \"\";\n const v = Math.abs(n);\n const units: [number, string][] = [[1e12, \"T\"], [1e9, \"B\"], [1e6, \"M\"], [1e3, \"K\"]];\n for (const [threshold, u] of units) {\n if (v >= threshold) {\n const num = v / threshold;\n return `${neg}${(num % 1 === 0 ? num.toFixed(0) : num.toFixed(decimals))}${u}`;\n }\n }\n return `${neg}${v}`;\n}\n\n/** Group digits with separators. `number(1234567.5)` → \"1,234,567.5\". */\nexport function number(n: number, opts: { separator?: string; decimal?: string } = {}): string {\n const sep = opts.separator ?? \",\";\n const dec = opts.decimal ?? \".\";\n if (!Number.isFinite(n)) return String(n);\n const str = String(n);\n // Exponential notation (e.g. 1e21, 1e-7) mangles a naive split — expand it via\n // locale grouping (en-US: \",\" groups, \".\" decimal) then apply the custom separators.\n if (str.includes(\"e\") || str.includes(\"E\")) {\n const grouped = n.toLocaleString(\"en-US\", { maximumFractionDigits: 20, useGrouping: true });\n const [g, f] = grouped.split(\".\");\n return g!.split(\",\").join(sep) + (f ? dec + f : \"\");\n }\n const [int, frac] = str.split(\".\");\n const sign = int!.startsWith(\"-\") ? \"-\" : \"\";\n const digits = int!.replace(\"-\", \"\").replace(/\\B(?=(\\d{3})+(?!\\d))/g, sep);\n return sign + digits + (frac ? dec + frac : \"\");\n}\n\n/* ------------------------------ words ------------------------------ */\n\n/** Pluralize a word for a count (naive English + irregulars). */\nexport function plural(word: string, count: number, pluralForm?: string): string {\n if (count === 1) return word;\n if (pluralForm) return pluralForm;\n if (/(s|x|z|ch|sh)$/i.test(word)) return `${word}es`;\n if (/[^aeiou]y$/i.test(word)) return `${word.slice(0, -1)}ies`;\n return `${word}s`;\n}\n\n/** \"5 items\" / \"1 item\". */\nexport function pluralize(count: number, word: string, pluralForm?: string): string {\n return `${count} ${plural(word, count, pluralForm)}`;\n}\n\n/** Grammatical list. `list([\"a\",\"b\",\"c\"])` → \"a, b and c\". */\nexport function list(items: string[], opts: { conjunction?: string; oxford?: boolean } = {}): string {\n const conj = opts.conjunction ?? \"and\";\n if (items.length === 0) return \"\";\n if (items.length === 1) return items[0]!;\n if (items.length === 2) return `${items[0]} ${conj} ${items[1]}`;\n const head = items.slice(0, -1).join(\", \");\n return `${head}${opts.oxford ? \",\" : \"\"} ${conj} ${items[items.length - 1]}`;\n}\n\n/** Truncate to a max length with an ellipsis on a word boundary. */\nexport function truncate(text: string, maxLength: number, ellipsis = \"…\"): string {\n if (text.length <= maxLength) return text;\n const cut = text.slice(0, maxLength - ellipsis.length);\n const lastSpace = cut.lastIndexOf(\" \");\n return (lastSpace > maxLength * 0.6 ? cut.slice(0, lastSpace) : cut).replace(/[\\s.,;:!?-]+$/, \"\") + ellipsis;\n}\n\n/** Title Case a string. */\nexport function titleCase(text: string): string {\n return text.toLowerCase().replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lacspace/humanize",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Turn machine values into human-readable text — bytes, durations, relative time, ordinals, plurals, compact numbers and grammatical lists. Zero-dependency, isomorphic.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|