@ptx-showcase/utils 0.4.0 → 0.6.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.
package/README.md CHANGED
@@ -126,6 +126,30 @@ await downloadBlob(blob, { hash: 12345 })
126
126
 
127
127
  ---
128
128
 
129
+ ### getCurrencySymbol
130
+
131
+ Resolves the symbol for an ISO 4217 currency code via `Intl`. A handful of currencies (`AUD`, `CAD`, `NZD`, `CHF`, etc.) are overridden where the `Intl` narrow symbol would be ambiguous.
132
+
133
+ ```ts
134
+ // from root
135
+ import { getCurrencySymbol } from '@ptx-showcase/utils'
136
+
137
+ // from subpath
138
+ import { getCurrencySymbol } from '@ptx-showcase/utils/currency-symbol'
139
+
140
+ getCurrencySymbol('EUR') // → '€'
141
+ getCurrencySymbol('USD') // → '$'
142
+ getCurrencySymbol('AUD') // → 'A$' (override)
143
+ getCurrencySymbol('CHF') // → '₣' (override)
144
+ getCurrencySymbol('ZZZ') // → 'ZZZ' (well-formed but unknown code)
145
+ getCurrencySymbol('EURO') // → 'N/A' (not a 3-letter code)
146
+
147
+ // custom locale
148
+ getCurrencySymbol('EUR', 'de-DE') // → '€'
149
+ ```
150
+
151
+ ---
152
+
129
153
  ### handleApiError
130
154
 
131
155
  Parses backend error responses (including `ky`'s `HTTPError`) and reports human-readable messages through whatever notification system your project uses (`vue-sonner`, `react-hot-toast`, `console.error`, etc).
@@ -159,6 +183,57 @@ try {
159
183
 
160
184
  ---
161
185
 
186
+ ### normalizeDecimalInput / normalizePhoneNumberInput
187
+
188
+ In-place `input`/`change` event handlers that sanitize a text input's value as the user types.
189
+
190
+ - `normalizeDecimalInput` — keeps only digits and a single dot, converts a comma to a dot, prefixes a leading dot with `0`, and truncates the decimal part to `decimals` digits (default `2`).
191
+ - `normalizePhoneNumberInput` — keeps only digits, truncates to `maxDigits` (default `15`, the E.164 limit), and prefixes the result with `+`.
192
+
193
+ ```ts
194
+ // from root
195
+ import { normalizeDecimalInput, normalizePhoneNumberInput } from '@ptx-showcase/utils'
196
+
197
+ // from subpath
198
+ import { normalizeDecimalInput, normalizePhoneNumberInput } from '@ptx-showcase/utils/normalize-input'
199
+
200
+ // Vue
201
+ <input type="text" inputmode="decimal" @input="normalizeDecimalInput" />
202
+ <input type="text" inputmode="tel" @input="normalizePhoneNumberInput" />
203
+
204
+ // React (wrap to access the native event)
205
+ <input type="text" inputMode="decimal" onChange={(e) => normalizeDecimalInput(e.nativeEvent, 4)} />
206
+ <input type="text" inputMode="tel" onChange={(e) => normalizePhoneNumberInput(e.nativeEvent, 11)} />
207
+ ```
208
+
209
+ ---
210
+
211
+ ### truncateDecimals
212
+
213
+ Formats a number (or numeric string) to a fixed number of decimal places by cutting the extra digits off, **without rounding**. Accepts a string too, so values more precise than a JS `number` can hold (e.g. amounts from an API) can be truncated without a lossy float conversion first. Returns `'N/A'` for anything that isn't a finite number or a well-formed numeric string.
214
+
215
+ ```ts
216
+ // from root
217
+ import { truncateDecimals } from '@ptx-showcase/utils'
218
+
219
+ // from subpath
220
+ import { truncateDecimals } from '@ptx-showcase/utils/truncate-decimals'
221
+
222
+ truncateDecimals(4.21312312) // → '4.21'
223
+ truncateDecimals(4.995) // → '4.99' (not rounded to 5.00)
224
+ truncateDecimals(4.2) // → '4.2' (fewer decimals than requested — left untouched)
225
+ truncateDecimals(4.219, 0) // → '4'
226
+
227
+ // numeric string input — no float precision loss
228
+ truncateDecimals('4.219999999999999999999999', 2) // → '4.21'
229
+
230
+ // invalid input
231
+ truncateDecimals('not a number') // → 'N/A'
232
+ truncateDecimals(undefined) // → 'N/A'
233
+ ```
234
+
235
+ ---
236
+
162
237
  ## Available modules
163
238
 
164
239
  | Subpath | Exports |
@@ -167,8 +242,11 @@ try {
167
242
  | `@ptx-showcase/utils/build-query-params` | `buildQueryParams` |
168
243
  | `@ptx-showcase/utils/build-sort-param` | `buildSortParam` |
169
244
  | `@ptx-showcase/utils/cookies` | `setCookie`, `getCookie`, `deleteCookie`, `defaultCookieOptions` |
245
+ | `@ptx-showcase/utils/currency-symbol` | `getCurrencySymbol` |
170
246
  | `@ptx-showcase/utils/download-file` | `downloadBlob` |
171
247
  | `@ptx-showcase/utils/handle-api-error` | `handleApiError`, `initHandleApiError` |
248
+ | `@ptx-showcase/utils/normalize-input` | `normalizeDecimalInput`, `normalizePhoneNumberInput` |
249
+ | `@ptx-showcase/utils/truncate-decimals` | `truncateDecimals` |
172
250
  | `@ptx-showcase/utils/types` | `TableColumnConfig`, `PaginatedList` |
173
251
 
174
252
  ---
@@ -0,0 +1,26 @@
1
+ //#region src/currency-symbol/constants/currency-symbol.constant.ts
2
+ var e = {
3
+ CHF: "₣",
4
+ AUD: "A$",
5
+ CAD: "CA$",
6
+ NZD: "NZ$",
7
+ HKD: "HK$",
8
+ MXN: "MX$",
9
+ SGD: "S$"
10
+ }, t = (t, n = "en-US") => {
11
+ if (typeof t != "string" || t == null || t === "" || t === " " || t.length !== 3) return "N/A";
12
+ let r = e[t.toUpperCase()];
13
+ if (r !== void 0) return r;
14
+ try {
15
+ let e = new Intl.NumberFormat(n, {
16
+ style: "currency",
17
+ currency: t,
18
+ currencyDisplay: "narrowSymbol"
19
+ }).formatToParts(0).find((e) => e.type === "currency");
20
+ return e === void 0 ? t : e.value;
21
+ } catch {
22
+ return t;
23
+ }
24
+ };
25
+ //#endregion
26
+ export { t };
@@ -0,0 +1 @@
1
+ var e={CHF:`₣`,AUD:`A$`,CAD:`CA$`,NZD:`NZ$`,HKD:`HK$`,MXN:`MX$`,SGD:`S$`},t=(t,n=`en-US`)=>{if(typeof t!=`string`||t==null||t===``||t===` `||t.length!==3)return`N/A`;let r=e[t.toUpperCase()];if(r!==void 0)return r;try{let e=new Intl.NumberFormat(n,{style:`currency`,currency:t,currencyDisplay:`narrowSymbol`}).formatToParts(0).find(e=>e.type===`currency`);return e===void 0?t:e.value}catch{return t}};Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return t}});
@@ -0,0 +1,7 @@
1
+ //#region src/truncate-decimals/constants/truncate-decimals.constant.ts
2
+ var e = /^-?\d+(\.\d+)?$/, t = (t, r = 2) => typeof t == "number" ? Number.isFinite(t) ? n(String(t), r) : "N/A" : typeof t == "string" && e.test(t.trim()) ? n(t.trim(), r) : "N/A", n = (e, t) => {
3
+ let [n, r] = e.split(".");
4
+ return r === void 0 ? e : t > 0 ? `${n}.${r.slice(0, t)}` : n;
5
+ };
6
+ //#endregion
7
+ export { t };
@@ -0,0 +1 @@
1
+ var e=/^-?\d+(\.\d+)?$/,t=(t,r=2)=>typeof t==`number`?Number.isFinite(t)?n(String(t),r):`N/A`:typeof t==`string`&&e.test(t.trim())?n(t.trim(),r):`N/A`,n=(e,t)=>{let[n,r]=e.split(`.`);return r===void 0?e:t>0?`${n}.${r.slice(0,t)}`:n};Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return t}});
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Explicit code → symbol overrides, checked before falling back to `Intl`.
3
+ * Use this for symbols `Intl` doesn't resolve the way you want (missing,
4
+ * wrong, or just not the convention your product uses).
5
+ */
6
+ export declare const CURRENCY_SYMBOL_OVERRIDES: Record<string, string>;
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../chunks/currency-symbol-gb63Q_9Q.cjs");exports.getCurrencySymbol=e.t;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Returns the symbol for a given ISO 4217 currency code.
3
+ *
4
+ * Codes listed in `CURRENCY_SYMBOL_OVERRIDES` are returned as-is — this
5
+ * covers currencies whose `Intl` narrow symbol is ambiguous (e.g. `AUD`,
6
+ * `CAD`, `NZD` all narrow to a plain `$`) or otherwise not what the product
7
+ * wants. Everything else is resolved via the runtime's `Intl` data, so all
8
+ * other currencies are supported without a hardcoded map.
9
+ *
10
+ * @param code - ISO 4217 currency code (e.g. `"EUR"`)
11
+ * @param locale - Locale used to resolve the symbol. Defaults to `"en-US"`.
12
+ * @returns The currency symbol; `code` itself if it's well-formed but not a
13
+ * recognized currency; `"N/A"` if `code` isn't a 3-letter string
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * getCurrencySymbol('EUR') // => '€'
18
+ * getCurrencySymbol('USD') // => '$'
19
+ * getCurrencySymbol('AUD') // => 'A$' (override)
20
+ * getCurrencySymbol('CHF') // => '₣' (override)
21
+ * ```
22
+ */
23
+ export declare const getCurrencySymbol: (code: string, locale?: string) => string;
@@ -0,0 +1,2 @@
1
+ import { t as e } from "../chunks/currency-symbol-CBowgXv-.es";
2
+ export { e as getCurrencySymbol };
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./build-query-params/index.cjs"),t=require("./clean-object/index.cjs"),n=require("./cookies/index.cjs"),r=require("./build-sort-param/index.cjs"),i=require("./download-file/index.cjs"),a=require("./chunks/handle-api-error-D714W9rp.cjs"),o=require("./normalize-input/index.cjs");exports.buildQueryParams=e.buildQueryParams,exports.buildSortParam=r.buildSortParam,exports.cleanObject=t.cleanObject,exports.defaultCookieOptions=n.defaultCookieOptions,exports.deleteCookie=n.deleteCookie,exports.downloadBlob=i.downloadBlob,exports.getCookie=n.getCookie,exports.handleApiError=a.t,exports.initHandleApiError=a.n,exports.normalizeDecimalInput=o.normalizeDecimalInput,exports.normalizePhoneNumberInput=o.normalizePhoneNumberInput,exports.setCookie=n.setCookie;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./build-query-params/index.cjs"),t=require("./clean-object/index.cjs"),n=require("./cookies/index.cjs"),r=require("./build-sort-param/index.cjs"),i=require("./chunks/currency-symbol-gb63Q_9Q.cjs"),a=require("./download-file/index.cjs"),o=require("./chunks/handle-api-error-D714W9rp.cjs"),s=require("./normalize-input/index.cjs"),c=require("./chunks/truncate-decimals-CArL-IeO.cjs");exports.buildQueryParams=e.buildQueryParams,exports.buildSortParam=r.buildSortParam,exports.cleanObject=t.cleanObject,exports.defaultCookieOptions=n.defaultCookieOptions,exports.deleteCookie=n.deleteCookie,exports.downloadBlob=a.downloadBlob,exports.getCookie=n.getCookie,exports.getCurrencySymbol=i.t,exports.handleApiError=o.t,exports.initHandleApiError=o.n,exports.normalizeDecimalInput=s.normalizeDecimalInput,exports.normalizePhoneNumberInput=s.normalizePhoneNumberInput,exports.setCookie=n.setCookie,exports.truncateDecimals=c.t;
package/dist/index.d.ts CHANGED
@@ -3,6 +3,8 @@ export * from './clean-object';
3
3
  export * from './cookies';
4
4
  export type * from './types';
5
5
  export * from './build-sort-param';
6
+ export * from './currency-symbol';
6
7
  export * from './download-file';
7
8
  export * from './handle-api-error';
8
9
  export * from './normalize-input';
10
+ export * from './truncate-decimals';
package/dist/index.js CHANGED
@@ -2,7 +2,9 @@ import { buildQueryParams as e } from "./build-query-params/index.js";
2
2
  import { cleanObject as t } from "./clean-object/index.js";
3
3
  import { defaultCookieOptions as n, deleteCookie as r, getCookie as i, setCookie as a } from "./cookies/index.js";
4
4
  import { buildSortParam as o } from "./build-sort-param/index.js";
5
- import { downloadBlob as s } from "./download-file/index.js";
6
- import { n as c, t as l } from "./chunks/handle-api-error-CMd_LxPS.es";
7
- import { normalizeDecimalInput as u, normalizePhoneNumberInput as d } from "./normalize-input/index.js";
8
- export { e as buildQueryParams, o as buildSortParam, t as cleanObject, n as defaultCookieOptions, r as deleteCookie, s as downloadBlob, i as getCookie, l as handleApiError, c as initHandleApiError, u as normalizeDecimalInput, d as normalizePhoneNumberInput, a as setCookie };
5
+ import { t as s } from "./chunks/currency-symbol-CBowgXv-.es";
6
+ import { downloadBlob as c } from "./download-file/index.js";
7
+ import { n as l, t as u } from "./chunks/handle-api-error-CMd_LxPS.es";
8
+ import { normalizeDecimalInput as d, normalizePhoneNumberInput as f } from "./normalize-input/index.js";
9
+ import { t as p } from "./chunks/truncate-decimals-BFMoEzul.es";
10
+ export { e as buildQueryParams, o as buildSortParam, t as cleanObject, n as defaultCookieOptions, r as deleteCookie, c as downloadBlob, i as getCookie, s as getCurrencySymbol, u as handleApiError, l as initHandleApiError, d as normalizeDecimalInput, f as normalizePhoneNumberInput, a as setCookie, p as truncateDecimals };
@@ -0,0 +1 @@
1
+ export declare const NUMERIC_STRING_PATTERN: RegExp;
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../chunks/truncate-decimals-CArL-IeO.cjs");exports.truncateDecimals=e.t;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Formats a number (or numeric string) to a fixed number of decimal places
3
+ * by cutting the extra digits off, without rounding.
4
+ *
5
+ * Unlike `toFixed`, `4.21312312` truncated to 2 decimals stays `4.21`
6
+ * instead of being rounded to `4.21`/`4.22` depending on the digits that follow.
7
+ *
8
+ * Accepts a string as well as a number so that values with more precision
9
+ * than a JS `number` can hold safely (e.g. amounts coming from an API as
10
+ * strings) can be truncated without going through a lossy float conversion first.
11
+ *
12
+ * Numbers large/small enough to be stringified in exponential notation
13
+ * (e.g. `1e+21`, `1e-7`) are returned as-is, since there's no decimal part to cut.
14
+ *
15
+ * @param value - The number (or numeric string) to truncate
16
+ * @param decimals - Number of digits to keep after the decimal point.
17
+ * Pass `0` to drop the decimal part entirely. Defaults to `2`.
18
+ * @returns The truncated value as a string; `'N/A'` if `value` isn't a finite
19
+ * number or a well-formed numeric string (e.g. `undefined`, `null`, `NaN`,
20
+ * `{}`, `[]`, `'not a number'`)
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * truncateDecimals(4.21312312) // => '4.21'
25
+ * truncateDecimals(4.2) // => '4.2' (fewer decimals than requested — left untouched)
26
+ * truncateDecimals(4.219, 0) // => '4'
27
+ * truncateDecimals('4.219999999999999999999999', 2) // => '4.21' (string input, no float precision loss)
28
+ * truncateDecimals('not a number') // => 'N/A'
29
+ * truncateDecimals(undefined as unknown as number) // => 'N/A'
30
+ * ```
31
+ */
32
+ export declare const truncateDecimals: (value: number | string, decimals?: number) => string;
@@ -0,0 +1,2 @@
1
+ import { t as e } from "../chunks/truncate-decimals-BFMoEzul.es";
2
+ export { e as truncateDecimals };
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@ptx-showcase/utils",
3
3
  "author": "ptx-showcase",
4
4
  "description": "Shared utilities for PTX Showcase projects",
5
- "version": "0.4.0",
5
+ "version": "0.6.0",
6
6
  "private": false,
7
7
  "type": "module",
8
8
  "license": "MIT",
@@ -51,6 +51,11 @@
51
51
  "require": "./dist/clean-object/index.cjs",
52
52
  "types": "./dist/clean-object/index.d.ts"
53
53
  },
54
+ "./currency-symbol": {
55
+ "import": "./dist/currency-symbol/index.js",
56
+ "require": "./dist/currency-symbol/index.cjs",
57
+ "types": "./dist/currency-symbol/index.d.ts"
58
+ },
54
59
  "./download-file": {
55
60
  "import": "./dist/download-file/index.js",
56
61
  "require": "./dist/download-file/index.cjs",
@@ -66,6 +71,11 @@
66
71
  "require": "./dist/normalize-input/index.cjs",
67
72
  "types": "./dist/normalize-input/index.d.ts"
68
73
  },
74
+ "./truncate-decimals": {
75
+ "import": "./dist/truncate-decimals/index.js",
76
+ "require": "./dist/truncate-decimals/index.cjs",
77
+ "types": "./dist/truncate-decimals/index.d.ts"
78
+ },
69
79
  "./types": {
70
80
  "import": "./dist/types/index.js",
71
81
  "require": "./dist/types/index.cjs",
@@ -87,20 +97,20 @@
87
97
  "access": "public"
88
98
  },
89
99
  "devDependencies": {
90
- "@types/node": "^26.1.2",
91
- "bumpp": "^12.2.0",
92
- "oxfmt": "^0.62.0",
93
- "oxlint": "^1.77.0",
100
+ "@types/node": "^26.4.0",
101
+ "bumpp": "^12.2.2",
102
+ "oxfmt": "^0.65.0",
103
+ "oxlint": "^1.80.0",
94
104
  "typescript": "~6.0.3",
95
- "vite": "^8.2.1",
105
+ "vite": "^8.2.2",
96
106
  "vite-plugin-dts": "^5.0.3",
97
- "vitest": "^4.1.10"
107
+ "vitest": "^4.1.11"
98
108
  },
99
109
  "dependencies": {
100
110
  "@types/file-saver": "^2.0.7",
101
111
  "@types/js-cookie": "^3.0.6",
102
112
  "file-saver": "^2.0.5",
103
113
  "js-cookie": "^3.0.8",
104
- "ky": "^2.0.2"
114
+ "ky": "^2.1.0"
105
115
  }
106
116
  }