format-quantity 2.0.1 → 2.1.0

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.
@@ -0,0 +1,176 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
3
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
4
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
5
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
6
+ var __spreadValues = (a, b) => {
7
+ for (var prop in b || (b = {}))
8
+ if (__hasOwnProp.call(b, prop))
9
+ __defNormalProp(a, prop, b[prop]);
10
+ if (__getOwnPropSymbols)
11
+ for (var prop of __getOwnPropSymbols(b)) {
12
+ if (__propIsEnum.call(b, prop))
13
+ __defNormalProp(a, prop, b[prop]);
14
+ }
15
+ return a;
16
+ };
17
+
18
+ // src/constants.ts
19
+ var defaultTolerance = 75e-4;
20
+ var defaultOptions = {
21
+ vulgarFractions: false,
22
+ tolerance: defaultTolerance,
23
+ fractionSlash: false,
24
+ romanNumerals: false
25
+ };
26
+ var vulgarToAsciiMap = {
27
+ "\xBC": "1/4",
28
+ "\xBD": "1/2",
29
+ "\xBE": "3/4",
30
+ "\u2150": "1/7",
31
+ "\u2151": "1/9",
32
+ "\u2152": "1/10",
33
+ "\u2153": "1/3",
34
+ "\u2154": "2/3",
35
+ "\u2155": "1/5",
36
+ "\u2156": "2/5",
37
+ "\u2157": "3/5",
38
+ "\u2158": "4/5",
39
+ "\u2159": "1/6",
40
+ "\u215A": "5/6",
41
+ "\u215B": "1/8",
42
+ "\u215C": "3/8",
43
+ "\u215D": "5/8",
44
+ "\u215E": "7/8"
45
+ };
46
+ var fractionDecimalMatches = [
47
+ [0.33, "\u2153"],
48
+ [0.66, "\u2154"],
49
+ [0.2, "\u2155"],
50
+ [0.4, "\u2156"],
51
+ [0.6, "\u2157"],
52
+ [0.8, "\u2158"],
53
+ [0.166, "\u2159"],
54
+ [0.833, "\u215A"],
55
+ [0.143, "\u2150"],
56
+ [0.111, "\u2151"],
57
+ [0.1, "\u2152"],
58
+ [0.125, "\u215B"],
59
+ [0.25, "\xBC"],
60
+ [0.375, "\u215C"],
61
+ [0.5, "\xBD"],
62
+ [0.625, "\u215D"],
63
+ [0.75, "\xBE"],
64
+ [0.875, "\u215E"],
65
+ [0.0625, "1/16"],
66
+ [0.1875, "3/16"],
67
+ [0.3125, "5/16"],
68
+ [0.4375, "7/16"],
69
+ [0.5625, "9/16"],
70
+ [0.6875, "11/16"],
71
+ [0.8125, "13/16"],
72
+ [0.9375, "15/16"]
73
+ ];
74
+
75
+ // src/formatQuantity.ts
76
+ var closeEnough = (n1, n2, tolerance) => Math.abs(n1 - n2) < tolerance;
77
+ var getFraction = (vulgarFractionOrSixteenth, { fractionSlash, vulgarFractions }) => {
78
+ var _a;
79
+ if (vulgarFractions) {
80
+ return vulgarFractionOrSixteenth;
81
+ }
82
+ const plainFraction = (_a = vulgarToAsciiMap[vulgarFractionOrSixteenth]) != null ? _a : vulgarFractionOrSixteenth;
83
+ if (fractionSlash) {
84
+ return plainFraction.replace("/", "\u2044");
85
+ }
86
+ return plainFraction;
87
+ };
88
+ var normalizeOptions = (options) => __spreadValues(__spreadValues({}, defaultOptions), typeof options === "boolean" ? { vulgarFractions: options } : options);
89
+ var romanNumeralValueKey = [
90
+ "",
91
+ "C",
92
+ "CC",
93
+ "CCC",
94
+ "CD",
95
+ "D",
96
+ "DC",
97
+ "DCC",
98
+ "DCCC",
99
+ "CM",
100
+ "",
101
+ "X",
102
+ "XX",
103
+ "XXX",
104
+ "XL",
105
+ "L",
106
+ "LX",
107
+ "LXX",
108
+ "LXXX",
109
+ "XC",
110
+ "",
111
+ "I",
112
+ "II",
113
+ "III",
114
+ "IV",
115
+ "V",
116
+ "VI",
117
+ "VII",
118
+ "VIII",
119
+ "IX"
120
+ ];
121
+ var formatRomanNumerals = (qty) => {
122
+ if (typeof qty !== "number" || isNaN(qty)) {
123
+ return null;
124
+ }
125
+ if (qty < 1 || qty >= 4e3) {
126
+ return "";
127
+ }
128
+ const floored = Math.floor(qty);
129
+ const digits = `${floored}`.split("");
130
+ let roman = "";
131
+ let i = 3;
132
+ while (i--) {
133
+ roman = `${romanNumeralValueKey[+digits.pop() + i * 10] || ""}${roman}`;
134
+ }
135
+ return `${Array(+digits.join("") + 1).join("M")}${roman}`;
136
+ };
137
+ var formatQuantity = (qty, options = defaultOptions) => {
138
+ const qtyAsNumber = typeof qty === "string" ? parseFloat(qty) : qty;
139
+ if (isNaN(qtyAsNumber) || qtyAsNumber === null) {
140
+ return null;
141
+ }
142
+ if (qtyAsNumber === 0) {
143
+ return "";
144
+ }
145
+ const opts = normalizeOptions(options != null ? options : defaultOptions);
146
+ if (opts.romanNumerals) {
147
+ return formatRomanNumerals(qtyAsNumber);
148
+ }
149
+ const absoluteValue = Math.abs(qtyAsNumber);
150
+ const flooredAbsVal = Math.floor(absoluteValue);
151
+ const flooredAbsValStr = `${qtyAsNumber < 0 ? "-" : ""}${flooredAbsVal === 0 ? "" : `${flooredAbsVal} `}`;
152
+ const decimalValue = absoluteValue - flooredAbsVal;
153
+ if (decimalValue === 0) {
154
+ return `${qtyAsNumber}`;
155
+ }
156
+ for (const [num, vf] of fractionDecimalMatches) {
157
+ if (closeEnough(decimalValue, num, opts.tolerance)) {
158
+ const fraction = getFraction(vf, opts);
159
+ const int = Object.hasOwn(vulgarToAsciiMap, fraction) ? flooredAbsValStr.trim() : flooredAbsValStr;
160
+ return `${int}${fraction}`;
161
+ }
162
+ }
163
+ return `${qtyAsNumber}`;
164
+ };
165
+
166
+ // src/index.ts
167
+ var src_default = formatQuantity;
168
+ export {
169
+ src_default as default,
170
+ defaultOptions,
171
+ defaultTolerance,
172
+ formatQuantity,
173
+ fractionDecimalMatches,
174
+ vulgarToAsciiMap
175
+ };
176
+ //# sourceMappingURL=format-quantity.legacy-esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/constants.ts","../src/formatQuantity.ts","../src/index.ts"],"sourcesContent":["import type {\n FormatQuantityOptions,\n SimpleFraction,\n Sixteenth,\n VulgarFraction,\n} from './types';\n\nexport const defaultTolerance = 0.0075 as const;\n\nexport const defaultOptions = {\n vulgarFractions: false,\n tolerance: defaultTolerance,\n fractionSlash: false,\n romanNumerals: false,\n} satisfies Required<FormatQuantityOptions>;\n\n/**\n * A map of vulgar or simple sixteenth fractions to their traditional ASCII\n * equivalents. Sixteenths map to themselves.\n */\nexport const vulgarToAsciiMap = {\n '¼': '1/4',\n '½': '1/2',\n '¾': '3/4',\n '⅐': '1/7',\n '⅑': '1/9',\n '⅒': '1/10',\n '⅓': '1/3',\n '⅔': '2/3',\n '⅕': '1/5',\n '⅖': '2/5',\n '⅗': '3/5',\n '⅘': '4/5',\n '⅙': '1/6',\n '⅚': '5/6',\n '⅛': '1/8',\n '⅜': '3/8',\n '⅝': '5/8',\n '⅞': '7/8',\n} satisfies Record<VulgarFraction, SimpleFraction>;\n\nexport const fractionDecimalMatches = [\n [0.33, '⅓'],\n [0.66, '⅔'],\n [0.2, '⅕'],\n [0.4, '⅖'],\n [0.6, '⅗'],\n [0.8, '⅘'],\n [0.166, '⅙'],\n [0.833, '⅚'],\n [0.143, '⅐'],\n [0.111, '⅑'],\n [0.1, '⅒'],\n [0.125, '⅛'],\n [0.25, '¼'],\n [0.375, '⅜'],\n [0.5, '½'],\n [0.625, '⅝'],\n [0.75, '¾'],\n [0.875, '⅞'],\n [0.0625, '1/16'],\n [0.1875, '3/16'],\n [0.3125, '5/16'],\n [0.4375, '7/16'],\n [0.5625, '9/16'],\n [0.6875, '11/16'],\n [0.8125, '13/16'],\n [0.9375, '15/16'],\n] satisfies [number, VulgarFraction | Sixteenth][];\n","import {\n defaultOptions,\n fractionDecimalMatches,\n vulgarToAsciiMap,\n} from './constants';\nimport type {\n FormatQuantity,\n FormatQuantityOptions,\n SimpleFraction,\n Sixteenth,\n VulgarFraction,\n} from './types';\n\n/**\n * Determines if two numbers are close enough to consider\n * them equal for the purposes of this package.\n */\nconst closeEnough = (n1: number, n2: number, tolerance: number) =>\n Math.abs(n1 - n2) < tolerance;\n\nconst getFraction = (\n vulgarFractionOrSixteenth: VulgarFraction | Sixteenth,\n { fractionSlash, vulgarFractions }: FormatQuantityOptions\n) => {\n if (vulgarFractions) {\n return vulgarFractionOrSixteenth;\n }\n\n const plainFraction: SimpleFraction =\n vulgarToAsciiMap[vulgarFractionOrSixteenth as VulgarFraction] ??\n vulgarFractionOrSixteenth;\n\n if (fractionSlash) {\n return plainFraction.replace('/', '⁄');\n }\n\n return plainFraction;\n};\n\nconst normalizeOptions = (\n options: Parameters<FormatQuantity>[1]\n): Required<FormatQuantityOptions> => ({\n ...defaultOptions,\n ...(typeof options === 'boolean' ? { vulgarFractions: options } : options),\n});\n\n// prettier-ignore\nconst romanNumeralValueKey = [\n \"\", \"C\", \"CC\", \"CCC\", \"CD\", \"D\", \"DC\", \"DCC\", \"DCCC\", \"CM\",\n \"\", \"X\", \"XX\", \"XXX\", \"XL\", \"L\", \"LX\", \"LXX\", \"LXXX\", \"XC\",\n \"\", \"I\", \"II\", \"III\", \"IV\", \"V\", \"VI\", \"VII\", \"VIII\", \"IX\",\n] as const;\n\n/**\n * Formats a number as Roman numerals. The number must be between\n * 1 and 3999 (inclusive).\n */\nexport const formatRomanNumerals = (qty: number) => {\n if (typeof qty !== 'number' || isNaN(qty)) {\n return null;\n }\n\n if (qty < 1 || qty >= 4000) {\n return '';\n }\n\n const floored = Math.floor(qty);\n\n const digits = `${floored}`.split('');\n let roman = '';\n let i = 3;\n while (i--) {\n roman = `${romanNumeralValueKey[+digits.pop()! + i * 10] || ''}${roman}`;\n }\n return `${Array(+digits.join('') + 1).join('M')}${roman}`;\n};\n\n/**\n * Formats a number (or string that appears to be a number)\n * as one would see it written in imperial measurements, e.g.\n * \"1 1/2\" instead of \"1.5\". To use vulgar fraction characters\n * like \"½\", pass `true` as the second argument. For other options\n * see the [documentation](https://jakeboone02.github.io/format-quantity/).\n */\nexport const formatQuantity: FormatQuantity = (\n qty,\n options = defaultOptions\n) => {\n // TODO: use numericQuantity instead of parseFloat?\n const qtyAsNumber = typeof qty === 'string' ? parseFloat(qty) : qty;\n\n // Return `null` if input is not number-like\n if (isNaN(qtyAsNumber) || qtyAsNumber === null) {\n return null;\n }\n\n // Return an empty string if the value is zero\n if (qtyAsNumber === 0) {\n return '';\n }\n\n // The default options parameter in the function signature only takes effect\n // if the parameter is `undefined`. The nullish coalescing operator below\n // covers the `null` case.\n const opts = normalizeOptions(options ?? defaultOptions);\n\n if (opts.romanNumerals) {\n return formatRomanNumerals(qtyAsNumber);\n }\n\n const absoluteValue = Math.abs(qtyAsNumber);\n const flooredAbsVal = Math.floor(absoluteValue);\n const flooredAbsValStr = `${qtyAsNumber < 0 ? '-' : ''}${\n flooredAbsVal === 0 ? '' : `${flooredAbsVal} `\n }`;\n const decimalValue = absoluteValue - flooredAbsVal;\n\n // For integers just return the given value as a string\n if (decimalValue === 0) {\n return `${qtyAsNumber}`;\n }\n\n for (const [num, vf] of fractionDecimalMatches) {\n if (closeEnough(decimalValue, num, opts.tolerance)) {\n const fraction = getFraction(vf, opts);\n const int = Object.hasOwn(vulgarToAsciiMap, fraction)\n ? flooredAbsValStr.trim()\n : flooredAbsValStr;\n return `${int}${fraction}`;\n }\n }\n\n return `${qtyAsNumber}`;\n};\n","import { formatQuantity } from './formatQuantity';\nexport * from './constants';\nexport * from './types';\nexport { formatQuantity };\nexport default formatQuantity;\n"],"mappings":"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","names":[]}
@@ -0,0 +1,161 @@
1
+ // src/constants.ts
2
+ var defaultTolerance = 75e-4;
3
+ var defaultOptions = {
4
+ vulgarFractions: false,
5
+ tolerance: defaultTolerance,
6
+ fractionSlash: false,
7
+ romanNumerals: false
8
+ };
9
+ var vulgarToAsciiMap = {
10
+ "\xBC": "1/4",
11
+ "\xBD": "1/2",
12
+ "\xBE": "3/4",
13
+ "\u2150": "1/7",
14
+ "\u2151": "1/9",
15
+ "\u2152": "1/10",
16
+ "\u2153": "1/3",
17
+ "\u2154": "2/3",
18
+ "\u2155": "1/5",
19
+ "\u2156": "2/5",
20
+ "\u2157": "3/5",
21
+ "\u2158": "4/5",
22
+ "\u2159": "1/6",
23
+ "\u215A": "5/6",
24
+ "\u215B": "1/8",
25
+ "\u215C": "3/8",
26
+ "\u215D": "5/8",
27
+ "\u215E": "7/8"
28
+ };
29
+ var fractionDecimalMatches = [
30
+ [0.33, "\u2153"],
31
+ [0.66, "\u2154"],
32
+ [0.2, "\u2155"],
33
+ [0.4, "\u2156"],
34
+ [0.6, "\u2157"],
35
+ [0.8, "\u2158"],
36
+ [0.166, "\u2159"],
37
+ [0.833, "\u215A"],
38
+ [0.143, "\u2150"],
39
+ [0.111, "\u2151"],
40
+ [0.1, "\u2152"],
41
+ [0.125, "\u215B"],
42
+ [0.25, "\xBC"],
43
+ [0.375, "\u215C"],
44
+ [0.5, "\xBD"],
45
+ [0.625, "\u215D"],
46
+ [0.75, "\xBE"],
47
+ [0.875, "\u215E"],
48
+ [0.0625, "1/16"],
49
+ [0.1875, "3/16"],
50
+ [0.3125, "5/16"],
51
+ [0.4375, "7/16"],
52
+ [0.5625, "9/16"],
53
+ [0.6875, "11/16"],
54
+ [0.8125, "13/16"],
55
+ [0.9375, "15/16"]
56
+ ];
57
+
58
+ // src/formatQuantity.ts
59
+ var closeEnough = (n1, n2, tolerance) => Math.abs(n1 - n2) < tolerance;
60
+ var getFraction = (vulgarFractionOrSixteenth, { fractionSlash, vulgarFractions }) => {
61
+ if (vulgarFractions) {
62
+ return vulgarFractionOrSixteenth;
63
+ }
64
+ const plainFraction = vulgarToAsciiMap[vulgarFractionOrSixteenth] ?? vulgarFractionOrSixteenth;
65
+ if (fractionSlash) {
66
+ return plainFraction.replace("/", "\u2044");
67
+ }
68
+ return plainFraction;
69
+ };
70
+ var normalizeOptions = (options) => ({
71
+ ...defaultOptions,
72
+ ...typeof options === "boolean" ? { vulgarFractions: options } : options
73
+ });
74
+ var romanNumeralValueKey = [
75
+ "",
76
+ "C",
77
+ "CC",
78
+ "CCC",
79
+ "CD",
80
+ "D",
81
+ "DC",
82
+ "DCC",
83
+ "DCCC",
84
+ "CM",
85
+ "",
86
+ "X",
87
+ "XX",
88
+ "XXX",
89
+ "XL",
90
+ "L",
91
+ "LX",
92
+ "LXX",
93
+ "LXXX",
94
+ "XC",
95
+ "",
96
+ "I",
97
+ "II",
98
+ "III",
99
+ "IV",
100
+ "V",
101
+ "VI",
102
+ "VII",
103
+ "VIII",
104
+ "IX"
105
+ ];
106
+ var formatRomanNumerals = (qty) => {
107
+ if (typeof qty !== "number" || isNaN(qty)) {
108
+ return null;
109
+ }
110
+ if (qty < 1 || qty >= 4e3) {
111
+ return "";
112
+ }
113
+ const floored = Math.floor(qty);
114
+ const digits = `${floored}`.split("");
115
+ let roman = "";
116
+ let i = 3;
117
+ while (i--) {
118
+ roman = `${romanNumeralValueKey[+digits.pop() + i * 10] || ""}${roman}`;
119
+ }
120
+ return `${Array(+digits.join("") + 1).join("M")}${roman}`;
121
+ };
122
+ var formatQuantity = (qty, options = defaultOptions) => {
123
+ const qtyAsNumber = typeof qty === "string" ? parseFloat(qty) : qty;
124
+ if (isNaN(qtyAsNumber) || qtyAsNumber === null) {
125
+ return null;
126
+ }
127
+ if (qtyAsNumber === 0) {
128
+ return "";
129
+ }
130
+ const opts = normalizeOptions(options ?? defaultOptions);
131
+ if (opts.romanNumerals) {
132
+ return formatRomanNumerals(qtyAsNumber);
133
+ }
134
+ const absoluteValue = Math.abs(qtyAsNumber);
135
+ const flooredAbsVal = Math.floor(absoluteValue);
136
+ const flooredAbsValStr = `${qtyAsNumber < 0 ? "-" : ""}${flooredAbsVal === 0 ? "" : `${flooredAbsVal} `}`;
137
+ const decimalValue = absoluteValue - flooredAbsVal;
138
+ if (decimalValue === 0) {
139
+ return `${qtyAsNumber}`;
140
+ }
141
+ for (const [num, vf] of fractionDecimalMatches) {
142
+ if (closeEnough(decimalValue, num, opts.tolerance)) {
143
+ const fraction = getFraction(vf, opts);
144
+ const int = Object.hasOwn(vulgarToAsciiMap, fraction) ? flooredAbsValStr.trim() : flooredAbsValStr;
145
+ return `${int}${fraction}`;
146
+ }
147
+ }
148
+ return `${qtyAsNumber}`;
149
+ };
150
+
151
+ // src/index.ts
152
+ var src_default = formatQuantity;
153
+ export {
154
+ src_default as default,
155
+ defaultOptions,
156
+ defaultTolerance,
157
+ formatQuantity,
158
+ fractionDecimalMatches,
159
+ vulgarToAsciiMap
160
+ };
161
+ //# sourceMappingURL=format-quantity.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/constants.ts","../src/formatQuantity.ts","../src/index.ts"],"sourcesContent":["import type {\n FormatQuantityOptions,\n SimpleFraction,\n Sixteenth,\n VulgarFraction,\n} from './types';\n\nexport const defaultTolerance = 0.0075 as const;\n\nexport const defaultOptions = {\n vulgarFractions: false,\n tolerance: defaultTolerance,\n fractionSlash: false,\n romanNumerals: false,\n} satisfies Required<FormatQuantityOptions>;\n\n/**\n * A map of vulgar or simple sixteenth fractions to their traditional ASCII\n * equivalents. Sixteenths map to themselves.\n */\nexport const vulgarToAsciiMap = {\n '¼': '1/4',\n '½': '1/2',\n '¾': '3/4',\n '⅐': '1/7',\n '⅑': '1/9',\n '⅒': '1/10',\n '⅓': '1/3',\n '⅔': '2/3',\n '⅕': '1/5',\n '⅖': '2/5',\n '⅗': '3/5',\n '⅘': '4/5',\n '⅙': '1/6',\n '⅚': '5/6',\n '⅛': '1/8',\n '⅜': '3/8',\n '⅝': '5/8',\n '⅞': '7/8',\n} satisfies Record<VulgarFraction, SimpleFraction>;\n\nexport const fractionDecimalMatches = [\n [0.33, '⅓'],\n [0.66, '⅔'],\n [0.2, '⅕'],\n [0.4, '⅖'],\n [0.6, '⅗'],\n [0.8, '⅘'],\n [0.166, '⅙'],\n [0.833, '⅚'],\n [0.143, '⅐'],\n [0.111, '⅑'],\n [0.1, '⅒'],\n [0.125, '⅛'],\n [0.25, '¼'],\n [0.375, '⅜'],\n [0.5, '½'],\n [0.625, '⅝'],\n [0.75, '¾'],\n [0.875, '⅞'],\n [0.0625, '1/16'],\n [0.1875, '3/16'],\n [0.3125, '5/16'],\n [0.4375, '7/16'],\n [0.5625, '9/16'],\n [0.6875, '11/16'],\n [0.8125, '13/16'],\n [0.9375, '15/16'],\n] satisfies [number, VulgarFraction | Sixteenth][];\n","import {\n defaultOptions,\n fractionDecimalMatches,\n vulgarToAsciiMap,\n} from './constants';\nimport type {\n FormatQuantity,\n FormatQuantityOptions,\n SimpleFraction,\n Sixteenth,\n VulgarFraction,\n} from './types';\n\n/**\n * Determines if two numbers are close enough to consider\n * them equal for the purposes of this package.\n */\nconst closeEnough = (n1: number, n2: number, tolerance: number) =>\n Math.abs(n1 - n2) < tolerance;\n\nconst getFraction = (\n vulgarFractionOrSixteenth: VulgarFraction | Sixteenth,\n { fractionSlash, vulgarFractions }: FormatQuantityOptions\n) => {\n if (vulgarFractions) {\n return vulgarFractionOrSixteenth;\n }\n\n const plainFraction: SimpleFraction =\n vulgarToAsciiMap[vulgarFractionOrSixteenth as VulgarFraction] ??\n vulgarFractionOrSixteenth;\n\n if (fractionSlash) {\n return plainFraction.replace('/', '⁄');\n }\n\n return plainFraction;\n};\n\nconst normalizeOptions = (\n options: Parameters<FormatQuantity>[1]\n): Required<FormatQuantityOptions> => ({\n ...defaultOptions,\n ...(typeof options === 'boolean' ? { vulgarFractions: options } : options),\n});\n\n// prettier-ignore\nconst romanNumeralValueKey = [\n \"\", \"C\", \"CC\", \"CCC\", \"CD\", \"D\", \"DC\", \"DCC\", \"DCCC\", \"CM\",\n \"\", \"X\", \"XX\", \"XXX\", \"XL\", \"L\", \"LX\", \"LXX\", \"LXXX\", \"XC\",\n \"\", \"I\", \"II\", \"III\", \"IV\", \"V\", \"VI\", \"VII\", \"VIII\", \"IX\",\n] as const;\n\n/**\n * Formats a number as Roman numerals. The number must be between\n * 1 and 3999 (inclusive).\n */\nexport const formatRomanNumerals = (qty: number) => {\n if (typeof qty !== 'number' || isNaN(qty)) {\n return null;\n }\n\n if (qty < 1 || qty >= 4000) {\n return '';\n }\n\n const floored = Math.floor(qty);\n\n const digits = `${floored}`.split('');\n let roman = '';\n let i = 3;\n while (i--) {\n roman = `${romanNumeralValueKey[+digits.pop()! + i * 10] || ''}${roman}`;\n }\n return `${Array(+digits.join('') + 1).join('M')}${roman}`;\n};\n\n/**\n * Formats a number (or string that appears to be a number)\n * as one would see it written in imperial measurements, e.g.\n * \"1 1/2\" instead of \"1.5\". To use vulgar fraction characters\n * like \"½\", pass `true` as the second argument. For other options\n * see the [documentation](https://jakeboone02.github.io/format-quantity/).\n */\nexport const formatQuantity: FormatQuantity = (\n qty,\n options = defaultOptions\n) => {\n // TODO: use numericQuantity instead of parseFloat?\n const qtyAsNumber = typeof qty === 'string' ? parseFloat(qty) : qty;\n\n // Return `null` if input is not number-like\n if (isNaN(qtyAsNumber) || qtyAsNumber === null) {\n return null;\n }\n\n // Return an empty string if the value is zero\n if (qtyAsNumber === 0) {\n return '';\n }\n\n // The default options parameter in the function signature only takes effect\n // if the parameter is `undefined`. The nullish coalescing operator below\n // covers the `null` case.\n const opts = normalizeOptions(options ?? defaultOptions);\n\n if (opts.romanNumerals) {\n return formatRomanNumerals(qtyAsNumber);\n }\n\n const absoluteValue = Math.abs(qtyAsNumber);\n const flooredAbsVal = Math.floor(absoluteValue);\n const flooredAbsValStr = `${qtyAsNumber < 0 ? '-' : ''}${\n flooredAbsVal === 0 ? '' : `${flooredAbsVal} `\n }`;\n const decimalValue = absoluteValue - flooredAbsVal;\n\n // For integers just return the given value as a string\n if (decimalValue === 0) {\n return `${qtyAsNumber}`;\n }\n\n for (const [num, vf] of fractionDecimalMatches) {\n if (closeEnough(decimalValue, num, opts.tolerance)) {\n const fraction = getFraction(vf, opts);\n const int = Object.hasOwn(vulgarToAsciiMap, fraction)\n ? flooredAbsValStr.trim()\n : flooredAbsValStr;\n return `${int}${fraction}`;\n }\n }\n\n return `${qtyAsNumber}`;\n};\n","import { formatQuantity } from './formatQuantity';\nexport * from './constants';\nexport * from './types';\nexport { formatQuantity };\nexport default formatQuantity;\n"],"mappings":"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","names":[]}
@@ -0,0 +1,2 @@
1
+ var I=.0075,n={vulgarFractions:!1,tolerance:.0075,fractionSlash:!1,romanNumerals:!1},s={"\xBC":"1/4","\xBD":"1/2","\xBE":"3/4","\u2150":"1/7","\u2151":"1/9","\u2152":"1/10","\u2153":"1/3","\u2154":"2/3","\u2155":"1/5","\u2156":"2/5","\u2157":"3/5","\u2158":"4/5","\u2159":"1/6","\u215A":"5/6","\u215B":"1/8","\u215C":"3/8","\u215D":"5/8","\u215E":"7/8"},m=[[.33,"\u2153"],[.66,"\u2154"],[.2,"\u2155"],[.4,"\u2156"],[.6,"\u2157"],[.8,"\u2158"],[.166,"\u2159"],[.833,"\u215A"],[.143,"\u2150"],[.111,"\u2151"],[.1,"\u2152"],[.125,"\u215B"],[.25,"\xBC"],[.375,"\u215C"],[.5,"\xBD"],[.625,"\u215D"],[.75,"\xBE"],[.875,"\u215E"],[.0625,"1/16"],[.1875,"3/16"],[.3125,"5/16"],[.4375,"7/16"],[.5625,"9/16"],[.6875,"11/16"],[.8125,"13/16"],[.9375,"15/16"]];var V=(t,a,r)=>Math.abs(t-a)<r,C=(t,{fractionSlash:a,vulgarFractions:r})=>{if(r)return t;let o=s[t]??t;return a?o.replace("/","\u2044"):o},X=t=>({...n,...typeof t=="boolean"?{vulgarFractions:t}:t}),b=["","C","CC","CCC","CD","D","DC","DCC","DCCC","CM","","X","XX","XXX","XL","L","LX","LXX","LXXX","XC","","I","II","III","IV","V","VI","VII","VIII","IX"],d=t=>{if(typeof t!="number"||isNaN(t))return null;if(t<1||t>=4e3)return"";let r=`${Math.floor(t)}`.split(""),o="",e=3;for(;e--;)o=`${b[+r.pop()+e*10]||""}${o}`;return`${Array(+r.join("")+1).join("M")}${o}`},f=(t,a=n)=>{let r=typeof t=="string"?parseFloat(t):t;if(isNaN(r)||r===null)return null;if(r===0)return"";let o=X(a??n);if(o.romanNumerals)return d(r);let e=Math.abs(r),i=Math.floor(e),l=`${r<0?"-":""}${i===0?"":`${i} `}`,u=e-i;if(u===0)return`${r}`;for(let[p,F]of m)if(V(u,p,o.tolerance)){let c=C(F,o);return`${Object.hasOwn(s,c)?l.trim():l}${c}`}return`${r}`};var M=f;export{M as default,n as defaultOptions,I as defaultTolerance,f as formatQuantity,m as fractionDecimalMatches,s as vulgarToAsciiMap};
2
+ //# sourceMappingURL=format-quantity.production.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/constants.ts","../src/formatQuantity.ts","../src/index.ts"],"sourcesContent":["import type {\n FormatQuantityOptions,\n SimpleFraction,\n Sixteenth,\n VulgarFraction,\n} from './types';\n\nexport const defaultTolerance = 0.0075 as const;\n\nexport const defaultOptions = {\n vulgarFractions: false,\n tolerance: defaultTolerance,\n fractionSlash: false,\n romanNumerals: false,\n} satisfies Required<FormatQuantityOptions>;\n\n/**\n * A map of vulgar or simple sixteenth fractions to their traditional ASCII\n * equivalents. Sixteenths map to themselves.\n */\nexport const vulgarToAsciiMap = {\n '¼': '1/4',\n '½': '1/2',\n '¾': '3/4',\n '⅐': '1/7',\n '⅑': '1/9',\n '⅒': '1/10',\n '⅓': '1/3',\n '⅔': '2/3',\n '⅕': '1/5',\n '⅖': '2/5',\n '⅗': '3/5',\n '⅘': '4/5',\n '⅙': '1/6',\n '⅚': '5/6',\n '⅛': '1/8',\n '⅜': '3/8',\n '⅝': '5/8',\n '⅞': '7/8',\n} satisfies Record<VulgarFraction, SimpleFraction>;\n\nexport const fractionDecimalMatches = [\n [0.33, '⅓'],\n [0.66, '⅔'],\n [0.2, '⅕'],\n [0.4, '⅖'],\n [0.6, '⅗'],\n [0.8, '⅘'],\n [0.166, '⅙'],\n [0.833, '⅚'],\n [0.143, '⅐'],\n [0.111, '⅑'],\n [0.1, '⅒'],\n [0.125, '⅛'],\n [0.25, '¼'],\n [0.375, '⅜'],\n [0.5, '½'],\n [0.625, '⅝'],\n [0.75, '¾'],\n [0.875, '⅞'],\n [0.0625, '1/16'],\n [0.1875, '3/16'],\n [0.3125, '5/16'],\n [0.4375, '7/16'],\n [0.5625, '9/16'],\n [0.6875, '11/16'],\n [0.8125, '13/16'],\n [0.9375, '15/16'],\n] satisfies [number, VulgarFraction | Sixteenth][];\n","import {\n defaultOptions,\n fractionDecimalMatches,\n vulgarToAsciiMap,\n} from './constants';\nimport type {\n FormatQuantity,\n FormatQuantityOptions,\n SimpleFraction,\n Sixteenth,\n VulgarFraction,\n} from './types';\n\n/**\n * Determines if two numbers are close enough to consider\n * them equal for the purposes of this package.\n */\nconst closeEnough = (n1: number, n2: number, tolerance: number) =>\n Math.abs(n1 - n2) < tolerance;\n\nconst getFraction = (\n vulgarFractionOrSixteenth: VulgarFraction | Sixteenth,\n { fractionSlash, vulgarFractions }: FormatQuantityOptions\n) => {\n if (vulgarFractions) {\n return vulgarFractionOrSixteenth;\n }\n\n const plainFraction: SimpleFraction =\n vulgarToAsciiMap[vulgarFractionOrSixteenth as VulgarFraction] ??\n vulgarFractionOrSixteenth;\n\n if (fractionSlash) {\n return plainFraction.replace('/', '⁄');\n }\n\n return plainFraction;\n};\n\nconst normalizeOptions = (\n options: Parameters<FormatQuantity>[1]\n): Required<FormatQuantityOptions> => ({\n ...defaultOptions,\n ...(typeof options === 'boolean' ? { vulgarFractions: options } : options),\n});\n\n// prettier-ignore\nconst romanNumeralValueKey = [\n \"\", \"C\", \"CC\", \"CCC\", \"CD\", \"D\", \"DC\", \"DCC\", \"DCCC\", \"CM\",\n \"\", \"X\", \"XX\", \"XXX\", \"XL\", \"L\", \"LX\", \"LXX\", \"LXXX\", \"XC\",\n \"\", \"I\", \"II\", \"III\", \"IV\", \"V\", \"VI\", \"VII\", \"VIII\", \"IX\",\n] as const;\n\n/**\n * Formats a number as Roman numerals. The number must be between\n * 1 and 3999 (inclusive).\n */\nexport const formatRomanNumerals = (qty: number) => {\n if (typeof qty !== 'number' || isNaN(qty)) {\n return null;\n }\n\n if (qty < 1 || qty >= 4000) {\n return '';\n }\n\n const floored = Math.floor(qty);\n\n const digits = `${floored}`.split('');\n let roman = '';\n let i = 3;\n while (i--) {\n roman = `${romanNumeralValueKey[+digits.pop()! + i * 10] || ''}${roman}`;\n }\n return `${Array(+digits.join('') + 1).join('M')}${roman}`;\n};\n\n/**\n * Formats a number (or string that appears to be a number)\n * as one would see it written in imperial measurements, e.g.\n * \"1 1/2\" instead of \"1.5\". To use vulgar fraction characters\n * like \"½\", pass `true` as the second argument. For other options\n * see the [documentation](https://jakeboone02.github.io/format-quantity/).\n */\nexport const formatQuantity: FormatQuantity = (\n qty,\n options = defaultOptions\n) => {\n // TODO: use numericQuantity instead of parseFloat?\n const qtyAsNumber = typeof qty === 'string' ? parseFloat(qty) : qty;\n\n // Return `null` if input is not number-like\n if (isNaN(qtyAsNumber) || qtyAsNumber === null) {\n return null;\n }\n\n // Return an empty string if the value is zero\n if (qtyAsNumber === 0) {\n return '';\n }\n\n // The default options parameter in the function signature only takes effect\n // if the parameter is `undefined`. The nullish coalescing operator below\n // covers the `null` case.\n const opts = normalizeOptions(options ?? defaultOptions);\n\n if (opts.romanNumerals) {\n return formatRomanNumerals(qtyAsNumber);\n }\n\n const absoluteValue = Math.abs(qtyAsNumber);\n const flooredAbsVal = Math.floor(absoluteValue);\n const flooredAbsValStr = `${qtyAsNumber < 0 ? '-' : ''}${\n flooredAbsVal === 0 ? '' : `${flooredAbsVal} `\n }`;\n const decimalValue = absoluteValue - flooredAbsVal;\n\n // For integers just return the given value as a string\n if (decimalValue === 0) {\n return `${qtyAsNumber}`;\n }\n\n for (const [num, vf] of fractionDecimalMatches) {\n if (closeEnough(decimalValue, num, opts.tolerance)) {\n const fraction = getFraction(vf, opts);\n const int = Object.hasOwn(vulgarToAsciiMap, fraction)\n ? flooredAbsValStr.trim()\n : flooredAbsValStr;\n return `${int}${fraction}`;\n }\n }\n\n return `${qtyAsNumber}`;\n};\n","import { formatQuantity } from './formatQuantity';\nexport * from './constants';\nexport * from './types';\nexport { formatQuantity };\nexport default formatQuantity;\n"],"mappings":"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","names":["defaultTolerance","defaultOptions","vulgarToAsciiMap","fractionDecimalMatches","closeEnough","n1","n2","tolerance","getFraction","vulgarFractionOrSixteenth","fractionSlash","vulgarFractions","plainFraction","vulgarToAsciiMap","normalizeOptions","options","defaultOptions","romanNumeralValueKey","formatRomanNumerals","qty","digits","roman","i","formatQuantity","qtyAsNumber","opts","absoluteValue","flooredAbsVal","flooredAbsValStr","decimalValue","num","vf","fractionDecimalMatches","fraction","src_default","formatQuantity"]}
@@ -0,0 +1,2 @@
1
+ "use strict";var FormatQuantity=(()=>{var l=Object.defineProperty;var C=Object.getOwnPropertyDescriptor;var X=Object.getOwnPropertyNames;var b=Object.prototype.hasOwnProperty;var d=(t,o)=>{for(var r in o)l(t,r,{get:o[r],enumerable:!0})},y=(t,o,r,a)=>{if(o&&typeof o=="object"||typeof o=="function")for(let e of X(o))!b.call(t,e)&&e!==r&&l(t,e,{get:()=>o[e],enumerable:!(a=C(o,e))||a.enumerable});return t};var I=t=>y(l({},"__esModule",{value:!0}),t);var N={};d(N,{default:()=>Q,defaultOptions:()=>n,defaultTolerance:()=>D,formatQuantity:()=>c,fractionDecimalMatches:()=>u,vulgarToAsciiMap:()=>i});var D=.0075,n={vulgarFractions:!1,tolerance:.0075,fractionSlash:!1,romanNumerals:!1},i={"\xBC":"1/4","\xBD":"1/2","\xBE":"3/4","\u2150":"1/7","\u2151":"1/9","\u2152":"1/10","\u2153":"1/3","\u2154":"2/3","\u2155":"1/5","\u2156":"2/5","\u2157":"3/5","\u2158":"4/5","\u2159":"1/6","\u215A":"5/6","\u215B":"1/8","\u215C":"3/8","\u215D":"5/8","\u215E":"7/8"},u=[[.33,"\u2153"],[.66,"\u2154"],[.2,"\u2155"],[.4,"\u2156"],[.6,"\u2157"],[.8,"\u2158"],[.166,"\u2159"],[.833,"\u215A"],[.143,"\u2150"],[.111,"\u2151"],[.1,"\u2152"],[.125,"\u215B"],[.25,"\xBC"],[.375,"\u215C"],[.5,"\xBD"],[.625,"\u215D"],[.75,"\xBE"],[.875,"\u215E"],[.0625,"1/16"],[.1875,"3/16"],[.3125,"5/16"],[.4375,"7/16"],[.5625,"9/16"],[.6875,"11/16"],[.8125,"13/16"],[.9375,"15/16"]];var g=(t,o,r)=>Math.abs(t-o)<r,h=(t,{fractionSlash:o,vulgarFractions:r})=>{if(r)return t;let a=i[t]??t;return o?a.replace("/","\u2044"):a},x=t=>({...n,...typeof t=="boolean"?{vulgarFractions:t}:t}),$=["","C","CC","CCC","CD","D","DC","DCC","DCCC","CM","","X","XX","XXX","XL","L","LX","LXX","LXXX","XC","","I","II","III","IV","V","VI","VII","VIII","IX"],M=t=>{if(typeof t!="number"||isNaN(t))return null;if(t<1||t>=4e3)return"";let r=`${Math.floor(t)}`.split(""),a="",e=3;for(;e--;)a=`${$[+r.pop()+e*10]||""}${a}`;return`${Array(+r.join("")+1).join("M")}${a}`},c=(t,o=n)=>{let r=typeof t=="string"?parseFloat(t):t;if(isNaN(r)||r===null)return null;if(r===0)return"";let a=x(o??n);if(a.romanNumerals)return M(r);let e=Math.abs(r),s=Math.floor(e),m=`${r<0?"-":""}${s===0?"":`${s} `}`,f=e-s;if(f===0)return`${r}`;for(let[F,V]of u)if(g(f,F,a.tolerance)){let p=h(V,a);return`${Object.hasOwn(i,p)?m.trim():m}${p}`}return`${r}`};var Q=c;return I(N);})();
2
+ //# sourceMappingURL=format-quantity.umd.min.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/constants.ts","../src/formatQuantity.ts"],"sourcesContent":["import { formatQuantity } from './formatQuantity';\nexport * from './constants';\nexport * from './types';\nexport { formatQuantity };\nexport default formatQuantity;\n","import type {\n FormatQuantityOptions,\n SimpleFraction,\n Sixteenth,\n VulgarFraction,\n} from './types';\n\nexport const defaultTolerance = 0.0075 as const;\n\nexport const defaultOptions = {\n vulgarFractions: false,\n tolerance: defaultTolerance,\n fractionSlash: false,\n romanNumerals: false,\n} satisfies Required<FormatQuantityOptions>;\n\n/**\n * A map of vulgar or simple sixteenth fractions to their traditional ASCII\n * equivalents. Sixteenths map to themselves.\n */\nexport const vulgarToAsciiMap = {\n '¼': '1/4',\n '½': '1/2',\n '¾': '3/4',\n '⅐': '1/7',\n '⅑': '1/9',\n '⅒': '1/10',\n '⅓': '1/3',\n '⅔': '2/3',\n '⅕': '1/5',\n '⅖': '2/5',\n '⅗': '3/5',\n '⅘': '4/5',\n '⅙': '1/6',\n '⅚': '5/6',\n '⅛': '1/8',\n '⅜': '3/8',\n '⅝': '5/8',\n '⅞': '7/8',\n} satisfies Record<VulgarFraction, SimpleFraction>;\n\nexport const fractionDecimalMatches = [\n [0.33, '⅓'],\n [0.66, '⅔'],\n [0.2, '⅕'],\n [0.4, '⅖'],\n [0.6, '⅗'],\n [0.8, '⅘'],\n [0.166, '⅙'],\n [0.833, '⅚'],\n [0.143, '⅐'],\n [0.111, '⅑'],\n [0.1, '⅒'],\n [0.125, '⅛'],\n [0.25, '¼'],\n [0.375, '⅜'],\n [0.5, '½'],\n [0.625, '⅝'],\n [0.75, '¾'],\n [0.875, '⅞'],\n [0.0625, '1/16'],\n [0.1875, '3/16'],\n [0.3125, '5/16'],\n [0.4375, '7/16'],\n [0.5625, '9/16'],\n [0.6875, '11/16'],\n [0.8125, '13/16'],\n [0.9375, '15/16'],\n] satisfies [number, VulgarFraction | Sixteenth][];\n","import {\n defaultOptions,\n fractionDecimalMatches,\n vulgarToAsciiMap,\n} from './constants';\nimport type {\n FormatQuantity,\n FormatQuantityOptions,\n SimpleFraction,\n Sixteenth,\n VulgarFraction,\n} from './types';\n\n/**\n * Determines if two numbers are close enough to consider\n * them equal for the purposes of this package.\n */\nconst closeEnough = (n1: number, n2: number, tolerance: number) =>\n Math.abs(n1 - n2) < tolerance;\n\nconst getFraction = (\n vulgarFractionOrSixteenth: VulgarFraction | Sixteenth,\n { fractionSlash, vulgarFractions }: FormatQuantityOptions\n) => {\n if (vulgarFractions) {\n return vulgarFractionOrSixteenth;\n }\n\n const plainFraction: SimpleFraction =\n vulgarToAsciiMap[vulgarFractionOrSixteenth as VulgarFraction] ??\n vulgarFractionOrSixteenth;\n\n if (fractionSlash) {\n return plainFraction.replace('/', '⁄');\n }\n\n return plainFraction;\n};\n\nconst normalizeOptions = (\n options: Parameters<FormatQuantity>[1]\n): Required<FormatQuantityOptions> => ({\n ...defaultOptions,\n ...(typeof options === 'boolean' ? { vulgarFractions: options } : options),\n});\n\n// prettier-ignore\nconst romanNumeralValueKey = [\n \"\", \"C\", \"CC\", \"CCC\", \"CD\", \"D\", \"DC\", \"DCC\", \"DCCC\", \"CM\",\n \"\", \"X\", \"XX\", \"XXX\", \"XL\", \"L\", \"LX\", \"LXX\", \"LXXX\", \"XC\",\n \"\", \"I\", \"II\", \"III\", \"IV\", \"V\", \"VI\", \"VII\", \"VIII\", \"IX\",\n] as const;\n\n/**\n * Formats a number as Roman numerals. The number must be between\n * 1 and 3999 (inclusive).\n */\nexport const formatRomanNumerals = (qty: number) => {\n if (typeof qty !== 'number' || isNaN(qty)) {\n return null;\n }\n\n if (qty < 1 || qty >= 4000) {\n return '';\n }\n\n const floored = Math.floor(qty);\n\n const digits = `${floored}`.split('');\n let roman = '';\n let i = 3;\n while (i--) {\n roman = `${romanNumeralValueKey[+digits.pop()! + i * 10] || ''}${roman}`;\n }\n return `${Array(+digits.join('') + 1).join('M')}${roman}`;\n};\n\n/**\n * Formats a number (or string that appears to be a number)\n * as one would see it written in imperial measurements, e.g.\n * \"1 1/2\" instead of \"1.5\". To use vulgar fraction characters\n * like \"½\", pass `true` as the second argument. For other options\n * see the [documentation](https://jakeboone02.github.io/format-quantity/).\n */\nexport const formatQuantity: FormatQuantity = (\n qty,\n options = defaultOptions\n) => {\n // TODO: use numericQuantity instead of parseFloat?\n const qtyAsNumber = typeof qty === 'string' ? parseFloat(qty) : qty;\n\n // Return `null` if input is not number-like\n if (isNaN(qtyAsNumber) || qtyAsNumber === null) {\n return null;\n }\n\n // Return an empty string if the value is zero\n if (qtyAsNumber === 0) {\n return '';\n }\n\n // The default options parameter in the function signature only takes effect\n // if the parameter is `undefined`. The nullish coalescing operator below\n // covers the `null` case.\n const opts = normalizeOptions(options ?? defaultOptions);\n\n if (opts.romanNumerals) {\n return formatRomanNumerals(qtyAsNumber);\n }\n\n const absoluteValue = Math.abs(qtyAsNumber);\n const flooredAbsVal = Math.floor(absoluteValue);\n const flooredAbsValStr = `${qtyAsNumber < 0 ? '-' : ''}${\n flooredAbsVal === 0 ? '' : `${flooredAbsVal} `\n }`;\n const decimalValue = absoluteValue - flooredAbsVal;\n\n // For integers just return the given value as a string\n if (decimalValue === 0) {\n return `${qtyAsNumber}`;\n }\n\n for (const [num, vf] of fractionDecimalMatches) {\n if (closeEnough(decimalValue, num, opts.tolerance)) {\n const fraction = getFraction(vf, opts);\n const int = Object.hasOwn(vulgarToAsciiMap, fraction)\n ? flooredAbsValStr.trim()\n : flooredAbsValStr;\n return `${int}${fraction}`;\n }\n }\n\n return `${qtyAsNumber}`;\n};\n"],"mappings":"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","names":["src_exports","__export","src_default","defaultOptions","defaultTolerance","formatQuantity","fractionDecimalMatches","vulgarToAsciiMap","defaultTolerance","defaultOptions","vulgarToAsciiMap","fractionDecimalMatches","closeEnough","n1","n2","tolerance","getFraction","vulgarFractionOrSixteenth","fractionSlash","vulgarFractions","plainFraction","vulgarToAsciiMap","normalizeOptions","options","defaultOptions","romanNumeralValueKey","formatRomanNumerals","qty","digits","roman","i","formatQuantity","qtyAsNumber","opts","absoluteValue","flooredAbsVal","flooredAbsValStr","decimalValue","num","vf","fractionDecimalMatches","fraction","src_default","formatQuantity"]}
package/package.json CHANGED
@@ -1,15 +1,23 @@
1
1
  {
2
- "version": "2.0.1",
2
+ "version": "2.1.0",
3
3
  "name": "format-quantity",
4
4
  "author": "Jake Boone <jakeboone02@gmail.com>",
5
5
  "description": "Number formatter for imperial measurements with support for vulgar fractions",
6
6
  "files": [
7
7
  "dist"
8
8
  ],
9
- "main": "./dist/format-quantity.cjs.js",
10
- "module": "./dist/format-quantity.es.js",
11
- "unpkg": "./dist/format-quantity.umd.js",
12
- "typings": "dist/index.d.ts",
9
+ "main": "./dist/cjs/index.js",
10
+ "module": "./dist/format-quantity.legacy-esm.js",
11
+ "exports": {
12
+ "./package.json": "./package.json",
13
+ ".": {
14
+ "types": "./dist/format-quantity.d.ts",
15
+ "import": "./dist/format-quantity.mjs",
16
+ "require": "./dist/cjs/index.js"
17
+ }
18
+ },
19
+ "types": "./dist/format-quantity.d.ts",
20
+ "unpkg": "./dist/format-quantity.umd.min.js",
13
21
  "license": "MIT",
14
22
  "keywords": [
15
23
  "recipe",
@@ -19,7 +27,9 @@
19
27
  "format",
20
28
  "string",
21
29
  "fractions",
22
- "imperial"
30
+ "imperial",
31
+ "roman",
32
+ "numerals"
23
33
  ],
24
34
  "bugs": {
25
35
  "url": "https://github.com/jakeboone02/format-quantity/issues"
@@ -30,28 +40,28 @@
30
40
  "url": "https://github.com/jakeboone02/format-quantity"
31
41
  },
32
42
  "scripts": {
33
- "start": "vite",
34
- "build": "yarn build:main && yarn build:types",
35
- "build:main": "vite build",
36
- "build:types": "tsc --project ./tsconfig.build.json",
37
- "test": "jest --coverage",
43
+ "start": "bun ./server.ts",
44
+ "build": "tsup",
45
+ "test": "jest",
38
46
  "watch": "jest --watch",
39
- "publish:npm": "np"
47
+ "publish:npm": "np",
48
+ "pretty-print": "prettier --write *.{mjs,ts,json} src/*.*"
40
49
  },
41
50
  "devDependencies": {
42
- "@babel/core": "^7.19.1",
43
- "@babel/preset-env": "^7.19.1",
44
- "@babel/preset-typescript": "^7.18.6",
45
- "@types/jest": "^29.0.3",
46
- "gh-pages": "^4.0.0",
47
- "jest": "^29.0.3",
48
- "np": "^7.6.2",
49
- "prettier": "^2.7.1",
50
- "typescript": "^4.8.3",
51
- "vite": "^3.1.3"
51
+ "@types/jest": "^29.5.2",
52
+ "@types/node": "^20.3.1",
53
+ "bun-types": "^0.6.9",
54
+ "jest": "^29.5.0",
55
+ "np": "^8.0.4",
56
+ "prettier": "^2.8.8",
57
+ "ts-jest": "^29.1.0",
58
+ "tsup": "^7.0.0",
59
+ "typescript": "^5.1.3"
52
60
  },
53
- "packageManager": "yarn@3.2.3",
54
61
  "publishConfig": {
55
62
  "registry": "https://registry.npmjs.org"
63
+ },
64
+ "engines": {
65
+ "node": ">=16"
56
66
  }
57
67
  }
@@ -1,12 +0,0 @@
1
- import type { SimpleFraction, VulgarFraction } from './types';
2
- export declare const defaultTolerance = 0.0075;
3
- export declare const vulgarFractions: string[];
4
- /**
5
- * A map of vulgar or simple fractions to their traditional ASCII equivalents.
6
- * Simple fractions map to themselves.
7
- */
8
- export declare const vulgarToPlainMap: Record<string, SimpleFraction>;
9
- export declare const fractionDecimalMatches: [
10
- number,
11
- VulgarFraction | SimpleFraction
12
- ][];
@@ -1,2 +0,0 @@
1
- "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const f=.0075,B=["\xBC","\xBD","\xBE","\u2150","\u2151","\u2152","\u2153","\u2154","\u2155","\u2156","\u2157","\u2158","\u2159","\u215A","\u215B","\u215C","\u215D","\u215E"],r={"\xBC":"1/4","\xBD":"1/2","\xBE":"3/4","\u2150":"1/7","\u2151":"1/9","\u2152":"1/10","\u2153":"1/3","\u2154":"2/3","\u2155":"1/5","\u2156":"2/5","\u2157":"3/5","\u2158":"4/5","\u2159":"1/6","\u215A":"5/6","\u215B":"1/8","\u215C":"3/8","\u215D":"5/8","\u215E":"7/8","1/16":"1/16","3/16":"3/16","5/16":"5/16","7/16":"7/16","9/16":"9/16","11/16":"11/16","13/16":"13/16","15/16":"15/16"},g=[[.33,"\u2153"],[.66,"\u2154"],[.2,"\u2155"],[.4,"\u2156"],[.6,"\u2157"],[.8,"\u2158"],[.166,"\u2159"],[.833,"\u215A"],[.143,"\u2150"],[.111,"\u2151"],[.1,"\u2152"],[.125,"\u215B"],[.25,"\xBC"],[.375,"\u215C"],[.5,"\xBD"],[.625,"\u215D"],[.75,"\xBE"],[.875,"\u215E"],[.0625,"1/16"],[.1875,"3/16"],[.3125,"5/16"],[.4375,"7/16"],[.5625,"9/16"],[.6875,"11/16"],[.8125,"13/16"],[.9375,"15/16"]],D=(t,u,a)=>Math.abs(t-u)<a,M=(t,u)=>u.vulgarFractions?t:u.fractionSlash?r[t].replace("/","\u2044"):r[t],d=(t,u)=>{var s;const a=typeof t=="string"?parseFloat(t):t;if(isNaN(a)||a===null)return null;if(a===0)return"";const n=Math.abs(a),e=Math.floor(n),o=`${a<0?"-":""}${e===0?"":`${e} `}`,c=n-e;if(c===0)return`${a}`;const l=typeof u=="boolean"?{vulgarFractions:u}:u!=null?u:{};for(const[v,x]of g)if(D(c,v,(s=l.tolerance)!=null?s:f)){const i=M(x,l);return`${B.includes(i)?o.trim():o}${i}`}return`${a}`};exports.default=d;exports.defaultTolerance=f;exports.formatQuantity=d;exports.fractionDecimalMatches=g;exports.vulgarFractions=B;exports.vulgarToPlainMap=r;
2
- //# sourceMappingURL=format-quantity.cjs.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"format-quantity.cjs.js","sources":["../src/constants.ts","../src/formatQuantity.ts"],"sourcesContent":["import type { SimpleFraction, VulgarFraction } from './types';\n\nexport const defaultTolerance = 0.0075;\n\nexport const vulgarFractions = [\n '¼',\n '½',\n '¾',\n '⅐',\n '⅑',\n '⅒',\n '⅓',\n '⅔',\n '⅕',\n '⅖',\n '⅗',\n '⅘',\n '⅙',\n '⅚',\n '⅛',\n '⅜',\n '⅝',\n '⅞',\n];\n\n/**\n * A map of vulgar or simple fractions to their traditional ASCII equivalents.\n * Simple fractions map to themselves.\n */\nexport const vulgarToPlainMap: Record<string, SimpleFraction> = {\n '¼': '1/4',\n '½': '1/2',\n '¾': '3/4',\n '⅐': '1/7',\n '⅑': '1/9',\n '⅒': '1/10',\n '⅓': '1/3',\n '⅔': '2/3',\n '⅕': '1/5',\n '⅖': '2/5',\n '⅗': '3/5',\n '⅘': '4/5',\n '⅙': '1/6',\n '⅚': '5/6',\n '⅛': '1/8',\n '⅜': '3/8',\n '⅝': '5/8',\n '⅞': '7/8',\n '1/16': '1/16',\n '3/16': '3/16',\n '5/16': '5/16',\n '7/16': '7/16',\n '9/16': '9/16',\n '11/16': '11/16',\n '13/16': '13/16',\n '15/16': '15/16',\n};\n\nexport const fractionDecimalMatches: [\n number,\n VulgarFraction | SimpleFraction\n][] = [\n [0.33, '⅓'],\n [0.66, '⅔'],\n [0.2, '⅕'],\n [0.4, '⅖'],\n [0.6, '⅗'],\n [0.8, '⅘'],\n [0.166, '⅙'],\n [0.833, '⅚'],\n [0.143, '⅐'],\n [0.111, '⅑'],\n [0.1, '⅒'],\n [0.125, '⅛'],\n [0.25, '¼'],\n [0.375, '⅜'],\n [0.5, '½'],\n [0.625, '⅝'],\n [0.75, '¾'],\n [0.875, '⅞'],\n [0.0625, '1/16'],\n [0.1875, '3/16'],\n [0.3125, '5/16'],\n [0.4375, '7/16'],\n [0.5625, '9/16'],\n [0.6875, '11/16'],\n [0.8125, '13/16'],\n [0.9375, '15/16'],\n];\n","import {\n defaultTolerance,\n fractionDecimalMatches,\n vulgarFractions,\n vulgarToPlainMap,\n} from './constants';\nimport type {\n FormatQuantity,\n FormatQuantityOptions,\n SimpleFraction,\n VulgarFraction,\n} from './types';\n\n/**\n * Determines if two numbers are close enough to consider\n * them equal for the purposes of this package.\n */\nconst closeEnough = (n1: number, n2: number, tolerance: number) =>\n Math.abs(n1 - n2) < tolerance;\n\nconst getFraction = (\n fraction: VulgarFraction | SimpleFraction,\n opts: FormatQuantityOptions\n) =>\n opts.vulgarFractions\n ? fraction\n : opts.fractionSlash\n ? vulgarToPlainMap[fraction].replace('/', '⁄')\n : vulgarToPlainMap[fraction];\n\n/**\n * Formats a number (or string that appears to be a number)\n * as one would see it written in imperial measurements, e.g.\n * \"1 1/2\" instead of \"1.5\". To use vulgar fraction characters\n * like \"½\", pass `true` as the second argument. For other options\n * see the [documentation](https://jakeboone02.github.io/format-quantity/).\n */\nexport const formatQuantity: FormatQuantity = (qty, options) => {\n const dQty = typeof qty === 'string' ? parseFloat(qty) : qty;\n\n // Return `null` if input is not number-like\n if (isNaN(dQty) || dQty === null) {\n return null;\n }\n\n // Return an empty string if the value is zero\n if (dQty === 0) {\n return '';\n }\n\n const dQtyAbs = Math.abs(dQty);\n const iFloor = Math.floor(dQtyAbs);\n const sFloor = `${dQty < 0 ? '-' : ''}${iFloor === 0 ? '' : `${iFloor} `}`;\n const dDecimal = dQtyAbs - iFloor;\n\n // For integers just return the given value as a string\n if (dDecimal === 0) {\n return `${dQty}`;\n }\n\n const opts: FormatQuantityOptions =\n typeof options === 'boolean' ? { vulgarFractions: options } : options ?? {};\n\n for (const [num, vf] of fractionDecimalMatches) {\n if (closeEnough(dDecimal, num, opts.tolerance ?? defaultTolerance)) {\n const fraction = getFraction(vf, opts);\n const int = vulgarFractions.includes(fraction) ? sFloor.trim() : sFloor;\n return `${int}${fraction}`;\n }\n }\n\n return `${dQty}`;\n};\n"],"names":["defaultTolerance","vulgarFractions","vulgarToPlainMap","fractionDecimalMatches","closeEnough","n1","n2","tolerance","getFraction","fraction","opts","formatQuantity","qty","options","dQty","dQtyAbs","iFloor","sFloor","dDecimal","num","vf","_a"],"mappings":"4GAEO,MAAMA,EAAmB,MAEnBC,EAAkB,CAC7B,OACA,OACA,OACA,SACA,SACA,SACA,SACA,SACA,SACA,SACA,SACA,SACA,SACA,SACA,SACA,SACA,SACA,QACF,EAMaC,EAAmD,CAC9D,OAAK,MACL,OAAK,MACL,OAAK,MACL,SAAK,MACL,SAAK,MACL,SAAK,OACL,SAAK,MACL,SAAK,MACL,SAAK,MACL,SAAK,MACL,SAAK,MACL,SAAK,MACL,SAAK,MACL,SAAK,MACL,SAAK,MACL,SAAK,MACL,SAAK,MACL,SAAK,MACL,OAAQ,OACR,OAAQ,OACR,OAAQ,OACR,OAAQ,OACR,OAAQ,OACR,QAAS,QACT,QAAS,QACT,QAAS,OACX,EAEaC,EAGP,CACJ,CAAC,IAAM,QAAG,EACV,CAAC,IAAM,QAAG,EACV,CAAC,GAAK,QAAG,EACT,CAAC,GAAK,QAAG,EACT,CAAC,GAAK,QAAG,EACT,CAAC,GAAK,QAAG,EACT,CAAC,KAAO,QAAG,EACX,CAAC,KAAO,QAAG,EACX,CAAC,KAAO,QAAG,EACX,CAAC,KAAO,QAAG,EACX,CAAC,GAAK,QAAG,EACT,CAAC,KAAO,QAAG,EACX,CAAC,IAAM,MAAG,EACV,CAAC,KAAO,QAAG,EACX,CAAC,GAAK,MAAG,EACT,CAAC,KAAO,QAAG,EACX,CAAC,IAAM,MAAG,EACV,CAAC,KAAO,QAAG,EACX,CAAC,MAAQ,MAAM,EACf,CAAC,MAAQ,MAAM,EACf,CAAC,MAAQ,MAAM,EACf,CAAC,MAAQ,MAAM,EACf,CAAC,MAAQ,MAAM,EACf,CAAC,MAAQ,OAAO,EAChB,CAAC,MAAQ,OAAO,EAChB,CAAC,MAAQ,OAAO,CAClB,ECvEMC,EAAc,CAACC,EAAYC,EAAYC,IAC3C,KAAK,IAAIF,EAAKC,CAAE,EAAIC,EAEhBC,EAAc,CAClBC,EACAC,IAEAA,EAAK,gBACDD,EACAC,EAAK,cACLR,EAAiBO,GAAU,QAAQ,IAAK,QAAG,EAC3CP,EAAiBO,GASVE,EAAiC,CAACC,EAAKC,IAAY,OAC9D,MAAMC,EAAO,OAAOF,GAAQ,SAAW,WAAWA,CAAG,EAAIA,EAGzD,GAAI,MAAME,CAAI,GAAKA,IAAS,KACnB,OAAA,KAIT,GAAIA,IAAS,EACJ,MAAA,GAGH,MAAAC,EAAU,KAAK,IAAID,CAAI,EACvBE,EAAS,KAAK,MAAMD,CAAO,EAC3BE,EAAS,GAAGH,EAAO,EAAI,IAAM,KAAKE,IAAW,EAAI,GAAK,GAAGA,OACzDE,EAAWH,EAAUC,EAG3B,GAAIE,IAAa,EACf,MAAO,GAAGJ,IAGN,MAAAJ,EACJ,OAAOG,GAAY,UAAY,CAAE,gBAAiBA,CAAA,EAAYA,GAAA,KAAAA,EAAW,GAE3E,SAAW,CAACM,EAAKC,CAAE,IAAKjB,EACtB,GAAIC,EAAYc,EAAUC,GAAKE,EAAAX,EAAK,YAAL,KAAAW,EAAkBrB,CAAgB,EAAG,CAC5D,MAAAS,EAAWD,EAAYY,EAAIV,CAAI,EAErC,MAAO,GADKT,EAAgB,SAASQ,CAAQ,EAAIQ,EAAO,KAAS,EAAAA,IACjDR,GAClB,CAGF,MAAO,GAAGK,GACZ"}