ui-formatter 0.1.0 โ†’ 0.1.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 CHANGED
@@ -1 +1,446 @@
1
- # ui-formatter
1
+ # ui-formatter
2
+
3
+ A lightweight, zero-dependency TypeScript utility library for formatting dates, times, numbers, and phone numbers.
4
+
5
+ Designed for applications that require **high performance**, **small bundle size**, and **no external dependencies**.
6
+
7
+ ---
8
+
9
+ ## Features
10
+
11
+ - ๐Ÿš€ Zero dependencies
12
+ - โšก High-performance implementations
13
+ - ๐Ÿ“… Date formatting
14
+ - ๐Ÿ•’ Time formatting
15
+ - ๐Ÿ”ข Number formatting
16
+ - โ˜Ž๏ธ Phone/Fax formatting
17
+ - ๐Ÿงน Phone normalization
18
+ - ๐Ÿ’ฏ TypeScript support
19
+ - ๐ŸŒ Works in Browser and Node.js
20
+
21
+ ---
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ npm install ui-formatter
27
+ ```
28
+
29
+ or
30
+
31
+ ```bash
32
+ yarn add ui-formatter
33
+ ```
34
+
35
+ ---
36
+
37
+ ## Quick Example
38
+
39
+ ```ts
40
+ import {
41
+ formatDate,
42
+ formatDateTime,
43
+ formatNumber,
44
+ formatInteger,
45
+ formatPhone
46
+ } from "ui-formatter"
47
+
48
+ const date = new Date()
49
+
50
+ formatDate(date, "MM/dd/yyyy")
51
+ // 07/02/2026
52
+
53
+ formatDateTime(date, "yyyy-MM-dd")
54
+ // 2026-07-02 14:35
55
+
56
+ formatNumber(1234567.89, 2)
57
+ // 1,234,567.89
58
+
59
+ formatInteger(1000000)
60
+ // 1,000,000
61
+
62
+ formatPhone("8005551234")
63
+ // 800 555-1234
64
+ ```
65
+
66
+ ---
67
+
68
+ # API
69
+
70
+ ---
71
+
72
+ ## Date Utilities
73
+
74
+ ### getDateFormat()
75
+
76
+ Returns the default date format.
77
+
78
+ ```ts
79
+ const format = getDateFormat()
80
+
81
+ // M/d/yyyy
82
+ ```
83
+
84
+ ---
85
+
86
+ ### addDays()
87
+
88
+ Returns a new date with the specified number of days added.
89
+
90
+ ```ts
91
+ const tomorrow = addDays(new Date(), 1)
92
+ ```
93
+
94
+ ---
95
+
96
+ ### formatDate()
97
+
98
+ Formats a Date using a custom format.
99
+
100
+ Supported tokens:
101
+
102
+ | Token | Description |
103
+ |--------|-------------|
104
+ | yyyy | 4-digit year |
105
+ | yy | 2-digit year |
106
+ | MM | 2-digit month |
107
+ | M | Month |
108
+ | dd | 2-digit day |
109
+ | d | Day |
110
+
111
+ Example
112
+
113
+ ```ts
114
+ formatDate(new Date(), "yyyy-MM-dd")
115
+
116
+ // 2026-07-02
117
+ ```
118
+
119
+ ---
120
+
121
+ ### formatTime()
122
+
123
+ Returns
124
+
125
+ ```
126
+ HH:mm
127
+ ```
128
+
129
+ Example
130
+
131
+ ```ts
132
+ formatTime(new Date())
133
+
134
+ // 15:30
135
+ ```
136
+
137
+ ---
138
+
139
+ ### formatLongTime()
140
+
141
+ Returns
142
+
143
+ ```
144
+ HH:mm:ss
145
+ ```
146
+
147
+ Example
148
+
149
+ ```ts
150
+ formatLongTime(new Date())
151
+
152
+ // 15:30:45
153
+ ```
154
+
155
+ ---
156
+
157
+ ### formatFullTime()
158
+
159
+ Returns
160
+
161
+ ```
162
+ HH:mm:ss.SSS
163
+ ```
164
+
165
+ Example
166
+
167
+ ```ts
168
+ formatFullTime(new Date())
169
+
170
+ // 15:30:45.123
171
+ ```
172
+
173
+ ---
174
+
175
+ ### formatDateTime()
176
+
177
+ Formats
178
+
179
+ ```
180
+ <Date> <HH:mm>
181
+ ```
182
+
183
+ Example
184
+
185
+ ```ts
186
+ formatDateTime(new Date(), "yyyy-MM-dd")
187
+
188
+ // 2026-07-02 15:30
189
+ ```
190
+
191
+ ---
192
+
193
+ ### formatLongDateTime()
194
+
195
+ Formats
196
+
197
+ ```
198
+ <Date> <HH:mm:ss>
199
+ ```
200
+
201
+ ---
202
+
203
+ ### formatFullDateTime()
204
+
205
+ Formats
206
+
207
+ ```
208
+ <Date> <HH:mm:ss.SSS>
209
+ ```
210
+
211
+ ---
212
+
213
+ ### datetimeToString()
214
+
215
+ Converts a Date into ISO-like format.
216
+
217
+ ```ts
218
+ datetimeToString(new Date())
219
+
220
+ // 2026-07-02T15:30:45
221
+ ```
222
+
223
+ ---
224
+
225
+ ### dateToString()
226
+
227
+ Returns a compact timestamp.
228
+
229
+ ```ts
230
+ dateToString(new Date())
231
+
232
+ // 20260702153045
233
+ ```
234
+
235
+ Milliseconds may be included.
236
+
237
+ ```ts
238
+ dateToString(new Date(), true)
239
+
240
+ // 20260702153045123
241
+ ```
242
+
243
+ ---
244
+
245
+ # Number Utilities
246
+
247
+ ---
248
+
249
+ ### formatInteger()
250
+
251
+ Formats an integer with thousands separators.
252
+
253
+ ```ts
254
+ formatInteger(123456789)
255
+
256
+ // 123,456,789
257
+ ```
258
+
259
+ Custom separator
260
+
261
+ ```ts
262
+ formatInteger(123456789, ".")
263
+
264
+ // 123.456.789
265
+ ```
266
+
267
+ ---
268
+
269
+ ### formatNumber()
270
+
271
+ Formats decimal numbers.
272
+
273
+ ```ts
274
+ formatNumber(1234567.89, 2)
275
+
276
+ // 1,234,567.89
277
+ ```
278
+
279
+ Custom separators
280
+
281
+ ```ts
282
+ formatNumber(
283
+ 1234567.89,
284
+ 2,
285
+ ",",
286
+ "."
287
+ )
288
+
289
+ // 1.234.567,89
290
+ ```
291
+
292
+ ---
293
+
294
+ # Phone Utilities
295
+
296
+ ---
297
+
298
+ ### normalizePhone()
299
+
300
+ Removes all characters except digits and `+`.
301
+
302
+ ```ts
303
+ normalizePhone("(800) 555-1234")
304
+
305
+ // 8005551234
306
+ ```
307
+
308
+ ---
309
+
310
+ ### normalizeFax()
311
+
312
+ Alias of
313
+
314
+ ```
315
+ normalizePhone()
316
+ ```
317
+
318
+ ---
319
+
320
+ ### formatPhone()
321
+
322
+ Formats phone numbers into a readable representation.
323
+
324
+ ```ts
325
+ formatPhone("8005551234")
326
+
327
+ // 800 555-1234
328
+ ```
329
+
330
+ Supports
331
+
332
+ - US numbers
333
+ - international numbers
334
+ - partial numbers
335
+
336
+ ---
337
+
338
+ ### formatFax()
339
+
340
+ Formats fax numbers.
341
+
342
+ ```ts
343
+ formatFax("0212345678")
344
+
345
+ // 02-12345678
346
+ ```
347
+
348
+ ---
349
+
350
+ # Utility Functions
351
+
352
+ ---
353
+
354
+ ### isNotEmpty()
355
+
356
+ Checks whether an array or string is not empty.
357
+
358
+ ```ts
359
+ isNotEmpty([])
360
+
361
+ // false
362
+
363
+ isNotEmpty("hello")
364
+
365
+ // true
366
+ ```
367
+
368
+ ---
369
+
370
+ ### isChecked()
371
+
372
+ Utility for HTML templates.
373
+
374
+ ```ts
375
+ isChecked(true)
376
+
377
+ // checked
378
+
379
+ isChecked(false)
380
+
381
+ // ""
382
+ ```
383
+
384
+ Can also compare string values.
385
+
386
+ ```ts
387
+ isChecked("admin", "admin")
388
+
389
+ // checked
390
+ ```
391
+
392
+ ---
393
+
394
+ # Design Goals
395
+
396
+ This library is designed with the following principles:
397
+
398
+ - Zero runtime dependencies
399
+ - High performance
400
+ - Minimal allocations
401
+ - Small bundle size
402
+ - Predictable behavior
403
+ - Browser and Node compatibility
404
+
405
+ Several implementations avoid regular expressions and repeated string concatenation in favor of allocation-friendly algorithms.
406
+
407
+ ---
408
+
409
+ # Performance
410
+
411
+ The library includes optimized implementations for:
412
+
413
+ - Manual date parsing
414
+ - Number formatting
415
+ - Integer formatting
416
+ - Phone normalization
417
+
418
+ Many functions use:
419
+
420
+ - single-pass parsing
421
+ - preallocated buffers
422
+ - minimal temporary objects
423
+
424
+ making them suitable for high-throughput applications.
425
+
426
+ ---
427
+
428
+ # Browser Support
429
+
430
+ Works in:
431
+
432
+ - Chrome
433
+ - Firefox
434
+ - Safari
435
+ - Edge
436
+ - Node.js
437
+ - Bun
438
+ - Deno
439
+
440
+ ---
441
+
442
+ # TypeScript
443
+
444
+ Fully written in TypeScript.
445
+
446
+ No additional typings are required.
package/lib/index.js CHANGED
@@ -141,35 +141,88 @@ function pad3(n) {
141
141
  }
142
142
  return n < 10 ? "00" + n : "0" + n.toString();
143
143
  }
144
- function formatNumber(v, scale, d, g) {
145
- if (v == null) {
144
+ function formatInteger(v, groupSeparator) {
145
+ if (groupSeparator === void 0) { groupSeparator = ","; }
146
+ if (v == null || !Number.isFinite(v)) {
146
147
  return "";
147
148
  }
148
- if (!d && !g) {
149
- g = ",";
150
- d = ".";
149
+ var isNegative = v < 0;
150
+ var n = Math.abs(Math.trunc(v));
151
+ if (n < 1000) {
152
+ return isNegative ? "-" + n : "" + n;
151
153
  }
152
- else if (!g) {
153
- g = d === "," ? "." : ",";
154
+ var buffer = new Array(32);
155
+ var i = buffer.length;
156
+ var digitCount = 0;
157
+ while (n > 0) {
158
+ if (digitCount > 0 && digitCount % 3 === 0) {
159
+ buffer[--i] = groupSeparator;
160
+ }
161
+ var digit = n % 10;
162
+ buffer[--i] = String.fromCharCode(48 + digit);
163
+ n = Math.floor(n / 10);
164
+ digitCount++;
165
+ }
166
+ if (isNegative) {
167
+ buffer[--i] = "-";
168
+ }
169
+ return buffer.slice(i).join("");
170
+ }
171
+ exports.formatInteger = formatInteger;
172
+ function formatNumber(v, precision, decimalSeparator, groupSeparator) {
173
+ if (precision === void 0) { precision = 0; }
174
+ if (v == null || !Number.isFinite(v)) {
175
+ return "";
154
176
  }
155
- var s = scale === 0 || scale ? v.toFixed(scale) : v.toString();
156
- var x = s.split(".", 2);
157
- var y = x[0];
158
- var arr = [];
159
- var len = y.length - 1;
160
- for (var k = 0; k < len; k++) {
161
- arr.push(y[len - k]);
162
- if ((k + 1) % 3 === 0) {
163
- arr.push(g);
177
+ var d = ".";
178
+ var g = ",";
179
+ if (decimalSeparator && groupSeparator) {
180
+ d = decimalSeparator;
181
+ g = groupSeparator;
182
+ }
183
+ else if (decimalSeparator && !groupSeparator) {
184
+ d = decimalSeparator;
185
+ if (d === "ูซ") {
186
+ g = "ูฌ";
187
+ }
188
+ else {
189
+ g = d === "," ? "." : ",";
164
190
  }
165
191
  }
166
- arr.push(y[0]);
167
- if (x.length === 1) {
168
- return arr.reverse().join("");
192
+ var negative = v < 0;
193
+ var s = Math.abs(v).toFixed(precision);
194
+ var dot = s.indexOf(".");
195
+ var intEnd = dot >= 0 ? dot : s.length;
196
+ var fracLen = dot >= 0 ? s.length - dot - 1 : 0;
197
+ var intLen = intEnd;
198
+ var groups = intLen > 3 ? ((intLen - 1) / 3) | 0 : 0;
199
+ var outLen = (negative ? 1 : 0) + intLen + groups * g.length + (fracLen > 0 ? d.length + fracLen : 0);
200
+ var out = new Array(outLen);
201
+ var p = 0;
202
+ if (negative) {
203
+ out[p++] = "-";
204
+ }
205
+ var firstGroup = intLen % 3;
206
+ if (firstGroup === 0) {
207
+ firstGroup = 3;
169
208
  }
170
- else {
171
- return arr.reverse().join("") + d + x[1];
209
+ for (var i = 0; i < intLen; i++) {
210
+ if (i > 0 && (i === firstGroup || (i > firstGroup && (i - firstGroup) % 3 === 0))) {
211
+ for (var j = 0; j < g.length; j++) {
212
+ out[p++] = g[j];
213
+ }
214
+ }
215
+ out[p++] = s[i];
216
+ }
217
+ if (fracLen > 0) {
218
+ for (var j = 0; j < d.length; j++) {
219
+ out[p++] = d[j];
220
+ }
221
+ for (var i = dot + 1; i < s.length; i++) {
222
+ out[p++] = s[i];
223
+ }
172
224
  }
225
+ return out.join("");
173
226
  }
174
227
  exports.formatNumber = formatNumber;
175
228
  var formatter = (function () {
@@ -180,7 +233,7 @@ var formatter = (function () {
180
233
  return "";
181
234
  }
182
235
  var s = phone;
183
- var x = removePhoneFormat(phone);
236
+ var x = normalizePhone(phone);
184
237
  if (x.length === 10) {
185
238
  var USNumber = x.match(formatter.usPhone);
186
239
  if (USNumber != null) {
@@ -207,7 +260,7 @@ var formatter = (function () {
207
260
  return "";
208
261
  }
209
262
  var s = fax;
210
- var x = removePhoneFormat(fax);
263
+ var x = normalizeFax(fax);
211
264
  var l = x.length;
212
265
  if (l <= 6) {
213
266
  s = x;
@@ -232,20 +285,30 @@ var formatter = (function () {
232
285
  }
233
286
  return s;
234
287
  };
235
- formatter.fax = / |\-|\.|\(|\)/g;
236
- formatter.phone = / |\-|\.|\(|\)/g;
237
288
  formatter.usPhone = /(\d{3})(\d{3})(\d{4})/;
238
289
  return formatter;
239
290
  }());
240
291
  exports.formatter = formatter;
241
- function removePhoneFormat(phone) {
242
- return phone ? phone.replace(formatter.phone, "") : "";
292
+ function normalizePhone(s) {
293
+ if (!s) {
294
+ return "";
295
+ }
296
+ var len = s.length;
297
+ var buf = new Array(len);
298
+ var j = 0;
299
+ for (var i = 0; i < len; i++) {
300
+ var c = s.charCodeAt(i);
301
+ if ((c >= 48 && c <= 57) || c === 43) {
302
+ buf[j++] = s[i];
303
+ }
304
+ }
305
+ return j === len ? buf.join("") : buf.slice(0, j).join("");
243
306
  }
244
- exports.removePhoneFormat = removePhoneFormat;
245
- function removeFaxFormat(fax) {
246
- return fax ? fax.replace(formatter.fax, "") : "";
307
+ exports.normalizePhone = normalizePhone;
308
+ function normalizeFax(fax) {
309
+ return normalizePhone(fax);
247
310
  }
248
- exports.removeFaxFormat = removeFaxFormat;
311
+ exports.normalizeFax = normalizeFax;
249
312
  function formatPhone(phone) {
250
313
  return formatter.formatPhone(phone);
251
314
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ui-formatter",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Format web UI",
5
5
  "main": "./lib/index.js",
6
6
  "types": "./src/index.ts",
package/src/index.ts CHANGED
@@ -140,40 +140,121 @@ function pad3(n: number): string {
140
140
  return n < 10 ? "00" + n : "0" + n.toString()
141
141
  }
142
142
 
143
- export function formatNumber(v?: number | null, scale?: number, d?: string | null, g?: string): any {
144
- if (v == null) {
143
+ export function formatInteger(v: number | null | undefined, groupSeparator: string = ","): any {
144
+ if (v == null || !Number.isFinite(v)) {
145
145
  return ""
146
146
  }
147
- if (!d && !g) {
148
- g = ","
149
- d = "."
150
- } else if (!g) {
151
- g = d === "," ? "." : ","
147
+
148
+ const isNegative = v < 0
149
+ let n = Math.abs(Math.trunc(v))
150
+
151
+ // Fast path
152
+ if (n < 1000) {
153
+ return isNegative ? `-${n}` : `${n}`
152
154
  }
153
- const s = scale === 0 || scale ? v.toFixed(scale) : v.toString()
154
- const x = s.split(".", 2)
155
- const y = x[0]
156
- const arr: string[] = []
157
- const len = y.length - 1
158
- for (let k = 0; k < len; k++) {
159
- arr.push(y[len - k])
160
- if ((k + 1) % 3 === 0) {
161
- arr.push(g)
155
+
156
+ // Max length:
157
+ // digits (up to 16 for JS safe int) + separators (~5) + sign
158
+ const buffer = new Array(32)
159
+ let i = buffer.length
160
+
161
+ let digitCount = 0
162
+
163
+ while (n > 0) {
164
+ // Insert separator every 3 digits
165
+ if (digitCount > 0 && digitCount % 3 === 0) {
166
+ buffer[--i] = groupSeparator
162
167
  }
168
+
169
+ const digit = n % 10
170
+ buffer[--i] = String.fromCharCode(48 + digit)
171
+
172
+ n = Math.floor(n / 10) // safe version
173
+ digitCount++
163
174
  }
164
- arr.push(y[0])
165
- if (x.length === 1) {
166
- return arr.reverse().join("")
167
- } else {
168
- return arr.reverse().join("") + d + x[1]
175
+
176
+ if (isNegative) {
177
+ buffer[--i] = "-"
169
178
  }
179
+
180
+ // Slice only used portion and join once
181
+ return buffer.slice(i).join("")
182
+ }
183
+ export function formatNumber(v?: number | null, precision = 0, decimalSeparator?: string | null, groupSeparator?: string | null): any {
184
+ if (v == null || !Number.isFinite(v)) {
185
+ return ""
186
+ }
187
+ let d = "."
188
+ let g = ","
189
+ if (decimalSeparator && groupSeparator) {
190
+ d = decimalSeparator
191
+ g = groupSeparator
192
+ } else if (decimalSeparator && !groupSeparator) {
193
+ d = decimalSeparator
194
+ if (d === "ูซ") {
195
+ g = "ูฌ"
196
+ } else {
197
+ g = d === "," ? "." : ","
198
+ }
199
+ }
200
+ const negative = v < 0
201
+
202
+ // unavoidable allocation
203
+ const s = Math.abs(v).toFixed(precision)
204
+
205
+ const dot = s.indexOf(".")
206
+
207
+ const intEnd = dot >= 0 ? dot : s.length
208
+ const fracLen = dot >= 0 ? s.length - dot - 1 : 0
209
+
210
+ const intLen = intEnd
211
+ const groups = intLen > 3 ? ((intLen - 1) / 3) | 0 : 0
212
+
213
+ const outLen = (negative ? 1 : 0) + intLen + groups * g.length + (fracLen > 0 ? d.length + fracLen : 0)
214
+
215
+ const out = new Array<string>(outLen)
216
+
217
+ let p = 0
218
+
219
+ if (negative) {
220
+ out[p++] = "-"
221
+ }
222
+
223
+ // integer part
224
+ let firstGroup = intLen % 3
225
+ if (firstGroup === 0) {
226
+ firstGroup = 3
227
+ }
228
+
229
+ for (let i = 0; i < intLen; i++) {
230
+ if (i > 0 && (i === firstGroup || (i > firstGroup && (i - firstGroup) % 3 === 0))) {
231
+ for (let j = 0; j < g.length; j++) {
232
+ out[p++] = g[j]
233
+ }
234
+ }
235
+
236
+ out[p++] = s[i]
237
+ }
238
+
239
+ // fractional part
240
+ if (fracLen > 0) {
241
+ for (let j = 0; j < d.length; j++) {
242
+ out[p++] = d[j]
243
+ }
244
+
245
+ for (let i = dot + 1; i < s.length; i++) {
246
+ out[p++] = s[i]
247
+ }
248
+ }
249
+
250
+ return out.join("")
170
251
  }
171
252
 
172
253
  // tslint:disable-next-line:class-name
173
254
  export class formatter {
174
255
  // private static _preg = / |\+|\-|\.|\(|\)/g;
175
- static fax = / |\-|\.|\(|\)/g
176
- static phone = / |\-|\.|\(|\)/g
256
+ // static fax = / |\-|\.|\(|\)/g
257
+ // static phone = / |\-|\.|\(|\)/g
177
258
  static usPhone = /(\d{3})(\d{3})(\d{4})/
178
259
  static formatPhone(phone?: string | null): string {
179
260
  if (!phone) {
@@ -182,7 +263,7 @@ export class formatter {
182
263
  // reformat phone number
183
264
  // 555 123-4567 or (+1) 555 123-4567
184
265
  let s = phone
185
- const x = removePhoneFormat(phone)
266
+ const x = normalizePhone(phone)
186
267
  if (x.length === 10) {
187
268
  const USNumber = x.match(formatter.usPhone)
188
269
  if (USNumber != null) {
@@ -208,7 +289,7 @@ export class formatter {
208
289
  // reformat phone number
209
290
  // 035-456745 or 02-1234567
210
291
  let s = fax
211
- const x = removePhoneFormat(fax)
292
+ const x = normalizeFax(fax)
212
293
  const l = x.length
213
294
  if (l <= 6) {
214
295
  s = x
@@ -230,11 +311,23 @@ export class formatter {
230
311
  return s
231
312
  }
232
313
  }
233
- export function removePhoneFormat(phone?: string): string {
234
- return phone ? phone.replace(formatter.phone, "") : ""
314
+ export function normalizePhone(s?: string | null): string {
315
+ if (!s) {
316
+ return ""
317
+ }
318
+ const len = s.length
319
+ const buf = new Array<string>(len)
320
+ let j = 0
321
+ for (let i = 0; i < len; i++) {
322
+ const c = s.charCodeAt(i)
323
+ if ((c >= 48 && c <= 57) || c === 43) {
324
+ buf[j++] = s[i]
325
+ }
326
+ }
327
+ return j === len ? buf.join("") : buf.slice(0, j).join("")
235
328
  }
236
- export function removeFaxFormat(fax?: string): string {
237
- return fax ? fax.replace(formatter.fax, "") : ""
329
+ export function normalizeFax(fax?: string): string {
330
+ return normalizePhone(fax)
238
331
  }
239
332
  export function formatPhone(phone?: string | null): string {
240
333
  return formatter.formatPhone(phone)