@lacspace/humanize 1.0.0 → 1.0.2
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/README.md +12 -6
- 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 +50 -10
package/README.md
CHANGED
|
@@ -52,18 +52,24 @@ Options let you switch to IEC units, long durations, more precision, Oxford comm
|
|
|
52
52
|
|
|
53
53
|
## Licensing
|
|
54
54
|
|
|
55
|
-
This package is **free** under the **[Lacspace Free Licence](https://lacspace.com/licenses/lacspace-free-1.0)** —
|
|
55
|
+
This package is **free** under the **[Lacspace Free Licence](https://lacspace.com/licenses/lacspace-free-1.0)** — permissive freedoms. Use it in personal and commercial projects at no cost; just keep the notice.
|
|
56
56
|
|
|
57
57
|
Not every Lacspace package is free. We also offer **Commercial**, **Client-specific** and **Private** packages under separate terms — see the **[Lacspace Licence Centre](https://lacspace.com/licenses)**.
|
|
58
58
|
|
|
59
|
+
<!-- LACSPACE-DEV-PLATFORM -->
|
|
60
|
+
|
|
59
61
|
---
|
|
60
62
|
|
|
61
|
-
|
|
63
|
+
## The Lacspace Developer Platform
|
|
62
64
|
|
|
63
|
-
|
|
65
|
+
`@lacspace/humanize` is part of **63 zero-dependency, isomorphic TypeScript packages** — one standard library for the modern web. Explore the ecosystem:
|
|
64
66
|
|
|
65
|
-
|
|
67
|
+
- 📦 **This package, documented** — https://developer.lacspace.com/packages/humanize
|
|
68
|
+
- 🗂️ **All 63 packages** — https://developer.lacspace.com/packages
|
|
69
|
+
- 🧭 **Developer handbook** — guides & runnable recipes — https://developer.lacspace.com/handbook
|
|
70
|
+
- 🧪 **Live playground** — run any package in your browser — https://developer.lacspace.com/playground
|
|
71
|
+
- 🖥️ **Finished app templates** — https://templates.lacspace.com
|
|
72
|
+
- 🚀 **Scaffold a full app** — `npm create lacspace-app@latest`
|
|
66
73
|
|
|
67
|
-
|
|
74
|
+
Free under the **[Lacspace Free Licence](https://lacspace.com/licenses/lacspace-free-1.0)** — a permissive, free-to-use licence.
|
|
68
75
|
|
|
69
|
-
<div align="center"><sub>Built with care by <a href="https://lacspace.com">Lacspace</a> · Lacspace Free Licence · <a href="https://github.com/lacspace/npm-packages">source</a></sub></div>
|
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,21 +1,61 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lacspace/humanize",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
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",
|
|
7
7
|
"module": "./dist/index.js",
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
|
-
"exports": {
|
|
10
|
-
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
11
24
|
"sideEffects": false,
|
|
12
|
-
"scripts": {
|
|
13
|
-
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsup",
|
|
27
|
+
"prepublishOnly": "npm run build"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"humanize",
|
|
31
|
+
"pretty-bytes",
|
|
32
|
+
"format-bytes",
|
|
33
|
+
"duration",
|
|
34
|
+
"relative-time",
|
|
35
|
+
"time-ago",
|
|
36
|
+
"ordinal",
|
|
37
|
+
"pluralize",
|
|
38
|
+
"compact-number",
|
|
39
|
+
"format-number",
|
|
40
|
+
"zero-dependency",
|
|
41
|
+
"isomorphic",
|
|
42
|
+
"typescript"
|
|
43
|
+
],
|
|
14
44
|
"author": "Lacspace <contact@lacspace.com>",
|
|
15
45
|
"license": "SEE LICENSE IN LICENSE",
|
|
16
|
-
"homepage": "https://lacspace.com/packages",
|
|
17
|
-
"repository": {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
46
|
+
"homepage": "https://developer.lacspace.com/packages/humanize",
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "git+https://github.com/lacspace/npm-packages.git",
|
|
50
|
+
"directory": "humanize"
|
|
51
|
+
},
|
|
52
|
+
"bugs": {
|
|
53
|
+
"url": "https://github.com/lacspace/npm-packages/issues"
|
|
54
|
+
},
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=18"
|
|
57
|
+
},
|
|
58
|
+
"publishConfig": {
|
|
59
|
+
"access": "public"
|
|
60
|
+
}
|
|
21
61
|
}
|