@mt-gloss/utils 0.0.3

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/index.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ export type { PresetDefinition, MarketPreset, DateRange, MarketEvent } from './lib/types';
2
+ export { formatValue, getValueFontSize } from './lib/formatValue';
3
+ export type { FormatValueOptions, FormattedValue } from './lib/formatValue';
4
+ export { formatTrend } from './lib/formatTrend';
5
+ export type { TrendDirection, FormattedTrend } from './lib/formatTrend';
6
+ export { formatDateRange, formatMarketDates, isMarketActive, isSameDay, isInRange, generateMarketId } from './lib/dateHelpers';
7
+ export { detectPreset, formatDateRangeWithPreset, getDateRangeDisplayInfo, formatShortYear, stripLeadingZeros, formatSingleDate, parseDateRange, isValidDateRangeFormat, getDurationDays, formatDuration, formatPreset, getPresetDateRange, formatDateRange as formatDateRangeSmart } from './lib/smartDateFormat';
8
+ export { ROLLING_OPTIONS, PERIOD_OPTIONS, TODAY_PRESET, DEFAULT_MARKET_PRESETS, PRESET_ID_MAP, PRESET_ID_REVERSE_MAP, getDateRangeForPreset, findPreset, getPresetLabel, normalizePresetId, toUppercasePresetId, getAllPresets, DEFAULT_PINNED } from './lib/presets';
9
+ //# sourceMappingURL=index.d.ts.map
package/index.js ADDED
@@ -0,0 +1,418 @@
1
+ import { startOfMonth as w, startOfYear as p, subYears as Y, endOfYear as _, startOfQuarter as U, subDays as d, startOfDay as q, endOfDay as Q } from "date-fns";
2
+ function G(t, { prefix: r = "", suffix: e = "", abbreviated: a = !0, kThreshold: o = 1e3 } = {}) {
3
+ if (t == null) {
4
+ const u = r ? `${r} —${e}` : `—${e}`;
5
+ return { display: u.trim(), full: u.trim(), length: u.trim().length };
6
+ }
7
+ const n = typeof t == "number" ? t : parseFloat(String(t).replace(/,/g, ""));
8
+ if (isNaN(n)) {
9
+ const c = `${r}${t}${e}`;
10
+ return { display: c, full: c, length: c.length };
11
+ }
12
+ let s;
13
+ a !== !1 && Math.abs(n) >= 1e6 ? s = (n / 1e6).toFixed(1).replace(/\.0$/, "") + "M" : a !== !1 && Math.abs(n) >= o ? s = (n / 1e3).toFixed(1).replace(/\.0$/, "") + "K" : s = n.toLocaleString("en-US");
14
+ const l = `${r}${s}${e}`, i = `${r}${n.toLocaleString("en-US")}${e}`;
15
+ return { display: l, full: i, length: l.length };
16
+ }
17
+ function K(t, r) {
18
+ return (r ?? 1) >= 2 ? t <= 4 || t <= 6 ? 30 : t <= 8 ? 24 : 20 : t <= 5 ? 28 : t <= 8 ? 20 : 17;
19
+ }
20
+ function X(t) {
21
+ if (t == null || t === 0)
22
+ return { text: "0%", direction: "flat" };
23
+ const r = t > 0 ? "up" : t < 0 ? "down" : "flat", e = Math.abs(t);
24
+ return { text: e >= 3 ? `${Math.round(e)}%` : `${e.toFixed(1)}%`, direction: r };
25
+ }
26
+ function Z(t, r) {
27
+ if (!t || !r) return "";
28
+ const e = t.toLocaleDateString("en-US", { month: "short" }), a = r.toLocaleDateString("en-US", { month: "short" }), o = t.getDate(), n = r.getDate(), s = t.getFullYear(), l = r.getFullYear();
29
+ return t.getTime() === r.getTime() || e === a && o === n && s === l ? `${e} ${o}, ${s}` : e === a && s === l ? `${e} ${o}–${n}` : s === l ? `${e} ${o} – ${a} ${n}` : `${e} ${o}, ${s} – ${a} ${n}, ${l}`;
30
+ }
31
+ function B(t, r) {
32
+ const e = t.toLocaleDateString("en-US", { month: "short" }), a = r.toLocaleDateString("en-US", { month: "short" }), o = t.getDate(), n = r.getDate();
33
+ return e === a ? `${e} ${o}–${n}` : `${e} ${o} – ${a} ${n}`;
34
+ }
35
+ function J(t, r = /* @__PURE__ */ new Date()) {
36
+ return !t?.startDate || !t?.endDate ? !1 : r >= t.startDate && r <= t.endDate;
37
+ }
38
+ function v(t, r) {
39
+ return !t || !r ? !1 : t.getFullYear() === r.getFullYear() && t.getMonth() === r.getMonth() && t.getDate() === r.getDate();
40
+ }
41
+ function tt(t, r, e) {
42
+ if (!t || !r || !e) return !1;
43
+ const a = t.getTime(), o = r.getTime(), n = e.getTime();
44
+ return a >= Math.min(o, n) && a <= Math.max(o, n);
45
+ }
46
+ function et() {
47
+ return `custom-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
48
+ }
49
+ const H = [
50
+ // Rolling periods (most specific first)
51
+ { id: "today", label: "Today", priority: 100 },
52
+ { id: "7d", label: "7D", priority: 90 },
53
+ { id: "30d", label: "30D", priority: 89 },
54
+ { id: "60d", label: "60D", priority: 88 },
55
+ { id: "90d", label: "90D", priority: 87 },
56
+ // To-date periods
57
+ { id: "mtd", label: "MTD", priority: 80 },
58
+ { id: "qtd", label: "QTD", priority: 79 },
59
+ { id: "ytd", label: "YTD", priority: 78 },
60
+ { id: "lytd", label: "LYTD", priority: 77 },
61
+ // Calendar periods (full periods)
62
+ { id: "this-week", label: "This Week", priority: 70 },
63
+ { id: "last-week", label: "Last Week", priority: 69 },
64
+ { id: "this-month", label: "This Month", priority: 68 },
65
+ { id: "last-month", label: "Last Month", priority: 67 },
66
+ { id: "this-quarter", label: "This Quarter", priority: 66 },
67
+ { id: "last-quarter", label: "Last Quarter", priority: 65 },
68
+ { id: "this-year", label: "This Year", priority: 64 },
69
+ { id: "last-year", label: "Last Year", priority: 63 }
70
+ ];
71
+ function f(t, r) {
72
+ return t.getFullYear() === r.getFullYear() && t.getMonth() === r.getMonth() && t.getDate() === r.getDate();
73
+ }
74
+ function M(t, r, e = /* @__PURE__ */ new Date(), a = []) {
75
+ const o = new Date(e);
76
+ o.setHours(0, 0, 0, 0);
77
+ const n = new Date(t);
78
+ n.setHours(0, 0, 0, 0);
79
+ const s = new Date(r);
80
+ s.setHours(0, 0, 0, 0);
81
+ const l = [
82
+ ...H,
83
+ ...a.map((i) => ({ ...i, priority: i.priority ?? 50 }))
84
+ ].sort((i, c) => c.priority - i.priority);
85
+ for (const i of l) {
86
+ if (i.startDate && i.endDate) {
87
+ const u = new Date(i.startDate);
88
+ u.setHours(0, 0, 0, 0);
89
+ const D = new Date(i.endDate);
90
+ if (D.setHours(0, 0, 0, 0), f(n, u) && f(s, D))
91
+ return { id: i.id, label: i.label };
92
+ continue;
93
+ }
94
+ const c = T(i.id, o);
95
+ if (c && f(n, c.start) && f(s, c.end))
96
+ return { id: i.id, label: i.label };
97
+ }
98
+ return null;
99
+ }
100
+ function at(t, r, e = /* @__PURE__ */ new Date(), a = {}) {
101
+ const {
102
+ preferPresetLabel: o = !0,
103
+ showPresetDates: n = !1,
104
+ customPresets: s = []
105
+ } = a, l = m(t, r, e);
106
+ if (!o)
107
+ return l;
108
+ const i = M(t, r, e, s);
109
+ return i ? n ? `${i.label} (${l})` : i.label : l;
110
+ }
111
+ function rt(t, r, e = /* @__PURE__ */ new Date(), a = []) {
112
+ const o = m(t, r, e), n = M(t, r, e, a);
113
+ return {
114
+ display: n ? n.label : o,
115
+ formatted: o,
116
+ preset: n
117
+ };
118
+ }
119
+ const S = "’";
120
+ function nt(t) {
121
+ const r = t.getFullYear(), e = String(r).slice(-2);
122
+ return `${S}${e}`;
123
+ }
124
+ function ot(t) {
125
+ if (t == null || t === "") return t;
126
+ if (/^\d+$/.test(t)) {
127
+ const r = t.replace(/^0+/, "");
128
+ return r === "" ? "0" : r;
129
+ }
130
+ return t;
131
+ }
132
+ function m(t, r, e = /* @__PURE__ */ new Date(), a = {}) {
133
+ const { shortYear: o = !1 } = a, n = e.getFullYear(), s = t.getMonth() + 1, l = t.getDate(), i = t.getFullYear(), c = r.getMonth() + 1, u = r.getDate(), D = r.getFullYear(), I = t.getTime() === r.getTime(), R = s === c && i === D, E = i === D, N = i === n, O = D === n, y = (C, A) => {
134
+ if (A) return "";
135
+ const b = String(C).slice(-2);
136
+ return o ? ` ${S}${b}` : `/${b}`;
137
+ }, $ = y(i, N), g = y(D, O);
138
+ return I ? `${s}/${l}${$}` : R ? `${s}/${l}–${u}${g}` : E ? `${s}/${l} – ${c}/${u}${g}` : `${s}/${l}${$} – ${c}/${u}${g}`;
139
+ }
140
+ function st(t, r = /* @__PURE__ */ new Date()) {
141
+ const e = r.getFullYear(), a = t.getMonth() + 1, o = t.getDate(), n = t.getFullYear(), s = n !== e ? `/${String(n).slice(-2)}` : "";
142
+ return `${a}/${o}${s}`;
143
+ }
144
+ function V(t, r = /* @__PURE__ */ new Date()) {
145
+ const e = r.getFullYear(), a = Math.floor(e / 100) * 100, o = t.replace(/[\u2013\u2014]/g, "-").trim(), n = o.split(/\s+-\s+/);
146
+ if (n.length === 2) {
147
+ const i = h(n[0].trim(), e, a), c = h(n[1].trim(), e, a);
148
+ return { start: i, end: c };
149
+ }
150
+ const s = o.match(/^(\d{1,2})\/(\d{1,2})-(\d{1,2})(?:\/(\d{2}))?$/);
151
+ if (s) {
152
+ const i = parseInt(s[1], 10) - 1, c = parseInt(s[2], 10), u = parseInt(s[3], 10), D = s[4] ? a + parseInt(s[4], 10) : e;
153
+ return {
154
+ start: new Date(D, i, c),
155
+ end: new Date(D, i, u)
156
+ };
157
+ }
158
+ const l = h(o, e, a);
159
+ return { start: l, end: l };
160
+ }
161
+ function h(t, r, e) {
162
+ const a = t.match(/^(\d{1,2})\/(\d{1,2})(?:\/(\d{2}))?$/);
163
+ if (!a)
164
+ throw new Error(`Invalid date format: "${t}"`);
165
+ const o = parseInt(a[1], 10) - 1, n = parseInt(a[2], 10), s = a[3] ? e + parseInt(a[3], 10) : r;
166
+ return new Date(s, o, n);
167
+ }
168
+ function it(t) {
169
+ try {
170
+ const r = V(t);
171
+ return r.start instanceof Date && !isNaN(r.start.getTime()) && r.end instanceof Date && !isNaN(r.end.getTime());
172
+ } catch {
173
+ return !1;
174
+ }
175
+ }
176
+ function x(t, r) {
177
+ return Math.round((r.getTime() - t.getTime()) / 864e5) + 1;
178
+ }
179
+ function lt(t, r) {
180
+ const e = x(t, r);
181
+ return e === 1 ? "1 day" : e < 14 ? `${e} days` : e < 60 ? `${Math.round(e / 7)} weeks` : e < 365 ? `${Math.round(e / 30)} months` : `${(e / 365).toFixed(1)} years`;
182
+ }
183
+ function ct(t, r = /* @__PURE__ */ new Date()) {
184
+ const { start: e, end: a } = T(t, r);
185
+ return { formatted: m(e, a, r), start: e, end: a };
186
+ }
187
+ function T(t, r = /* @__PURE__ */ new Date()) {
188
+ const e = new Date(r);
189
+ e.setHours(0, 0, 0, 0);
190
+ let a = new Date(e), o = new Date(e);
191
+ switch (t.toLowerCase()) {
192
+ case "today":
193
+ break;
194
+ case "7d":
195
+ a.setDate(a.getDate() - 6);
196
+ break;
197
+ case "30d":
198
+ a.setDate(a.getDate() - 29);
199
+ break;
200
+ case "60d":
201
+ a.setDate(a.getDate() - 59);
202
+ break;
203
+ case "90d":
204
+ a.setDate(a.getDate() - 89);
205
+ break;
206
+ case "mtd": {
207
+ a = new Date(e.getFullYear(), e.getMonth(), 1);
208
+ break;
209
+ }
210
+ case "qtd": {
211
+ const n = Math.floor(e.getMonth() / 3);
212
+ a = new Date(e.getFullYear(), n * 3, 1);
213
+ break;
214
+ }
215
+ case "ytd":
216
+ a = new Date(e.getFullYear(), 0, 1);
217
+ break;
218
+ case "lytd":
219
+ a = new Date(e.getFullYear() - 1, 0, 1), o = new Date(e.getFullYear() - 1, e.getMonth(), e.getDate());
220
+ break;
221
+ case "this-week": {
222
+ const n = e.getDay();
223
+ a.setDate(e.getDate() - n);
224
+ break;
225
+ }
226
+ case "last-week": {
227
+ const n = e.getDay();
228
+ o.setDate(e.getDate() - n - 1), a = new Date(o), a.setDate(a.getDate() - 6);
229
+ break;
230
+ }
231
+ case "this-month":
232
+ a = new Date(e.getFullYear(), e.getMonth(), 1), o = new Date(e.getFullYear(), e.getMonth() + 1, 0);
233
+ break;
234
+ case "last-month":
235
+ a = new Date(e.getFullYear(), e.getMonth() - 1, 1), o = new Date(e.getFullYear(), e.getMonth(), 0);
236
+ break;
237
+ case "this-quarter": {
238
+ const n = Math.floor(e.getMonth() / 3);
239
+ a = new Date(e.getFullYear(), n * 3, 1), o = new Date(e.getFullYear(), n * 3 + 3, 0);
240
+ break;
241
+ }
242
+ case "last-quarter": {
243
+ const n = Math.floor(e.getMonth() / 3) - 1, s = n < 0 ? e.getFullYear() - 1 : e.getFullYear(), l = n < 0 ? 3 : n;
244
+ a = new Date(s, l * 3, 1), o = new Date(s, l * 3 + 3, 0);
245
+ break;
246
+ }
247
+ case "this-year":
248
+ a = new Date(e.getFullYear(), 0, 1), o = new Date(e.getFullYear(), 11, 31);
249
+ break;
250
+ case "last-year":
251
+ a = new Date(e.getFullYear() - 1, 0, 1), o = new Date(e.getFullYear() - 1, 11, 31);
252
+ break;
253
+ }
254
+ return { start: a, end: o };
255
+ }
256
+ const F = [
257
+ { id: "7d", label: "7D" },
258
+ { id: "30d", label: "30D" },
259
+ { id: "60d", label: "60D" },
260
+ { id: "90d", label: "90D" }
261
+ ], k = [
262
+ { id: "mtd", label: "MTD" },
263
+ { id: "qtd", label: "QTD" },
264
+ { id: "ytd", label: "YTD" },
265
+ { id: "lytd", label: "LYTD" }
266
+ ], P = {
267
+ id: "today",
268
+ label: "Today"
269
+ }, ut = [
270
+ // Past events
271
+ {
272
+ id: "atlanta-jan",
273
+ label: "Atlanta Market",
274
+ startDate: new Date(2026, 0, 7),
275
+ endDate: new Date(2026, 0, 14),
276
+ location: "Atlanta, GA"
277
+ },
278
+ {
279
+ id: "dallas-jan",
280
+ label: "Dallas Total Home",
281
+ startDate: new Date(2026, 0, 17),
282
+ endDate: new Date(2026, 0, 22),
283
+ location: "Dallas, TX"
284
+ },
285
+ // Current event (overlaps with today -- Feb 20, 2026)
286
+ {
287
+ id: "vegas-feb",
288
+ label: "Las Vegas Market",
289
+ startDate: new Date(2026, 1, 15),
290
+ endDate: new Date(2026, 1, 24),
291
+ location: "Las Vegas, NV"
292
+ },
293
+ // Future events
294
+ {
295
+ id: "ny-mar",
296
+ label: "NY Now Spring",
297
+ startDate: new Date(2026, 2, 8),
298
+ endDate: new Date(2026, 2, 12),
299
+ location: "New York, NY"
300
+ },
301
+ {
302
+ id: "chicago-apr",
303
+ label: "Chicago Gift Show",
304
+ startDate: new Date(2026, 3, 18),
305
+ endDate: new Date(2026, 3, 22),
306
+ location: "Chicago, IL"
307
+ }
308
+ ], L = {
309
+ TODAY: "today",
310
+ MTD: "mtd",
311
+ QTD: "qtd",
312
+ YTD: "ytd",
313
+ LYTD: "lytd",
314
+ "30D": "30d",
315
+ "60D": "60d",
316
+ "90D": "90d",
317
+ "7D": "7d"
318
+ }, j = Object.fromEntries(
319
+ Object.entries(L).map(([t, r]) => [r, t])
320
+ );
321
+ function Dt(t, r = /* @__PURE__ */ new Date()) {
322
+ const e = t?.toLowerCase(), a = r;
323
+ let o, n = a;
324
+ switch (e) {
325
+ case "today":
326
+ o = q(a), n = Q(a);
327
+ break;
328
+ case "7d":
329
+ o = d(a, 6), n = a;
330
+ break;
331
+ case "30d":
332
+ o = d(a, 29), n = a;
333
+ break;
334
+ case "60d":
335
+ o = d(a, 59), n = a;
336
+ break;
337
+ case "90d":
338
+ o = d(a, 89), n = a;
339
+ break;
340
+ case "mtd":
341
+ o = w(a), n = a;
342
+ break;
343
+ case "qtd":
344
+ o = U(a), n = a;
345
+ break;
346
+ case "ytd":
347
+ o = p(a), n = a;
348
+ break;
349
+ case "lytd":
350
+ o = p(Y(a, 1)), n = _(Y(a, 1));
351
+ break;
352
+ default:
353
+ o = w(a), n = a;
354
+ }
355
+ return { start: o, end: n };
356
+ }
357
+ function z(t) {
358
+ const r = t?.toLowerCase();
359
+ if (r === "today")
360
+ return P;
361
+ const e = F.find((o) => o.id === r);
362
+ if (e) return e;
363
+ const a = k.find((o) => o.id === r);
364
+ return a || null;
365
+ }
366
+ function dt(t) {
367
+ if (!t) return "Select...";
368
+ if (t.toLowerCase() === "custom") return "Custom";
369
+ const r = z(t);
370
+ return r ? r.label : t;
371
+ }
372
+ function ft(t) {
373
+ return t ? L[t.toUpperCase()] || t.toLowerCase() : null;
374
+ }
375
+ function gt(t) {
376
+ return t ? j[t.toLowerCase()] || t.toUpperCase() : null;
377
+ }
378
+ function ht() {
379
+ return [P, ...F, ...k];
380
+ }
381
+ const mt = "mtd";
382
+ export {
383
+ ut as DEFAULT_MARKET_PRESETS,
384
+ mt as DEFAULT_PINNED,
385
+ k as PERIOD_OPTIONS,
386
+ L as PRESET_ID_MAP,
387
+ j as PRESET_ID_REVERSE_MAP,
388
+ F as ROLLING_OPTIONS,
389
+ P as TODAY_PRESET,
390
+ M as detectPreset,
391
+ z as findPreset,
392
+ Z as formatDateRange,
393
+ m as formatDateRangeSmart,
394
+ at as formatDateRangeWithPreset,
395
+ lt as formatDuration,
396
+ B as formatMarketDates,
397
+ ct as formatPreset,
398
+ nt as formatShortYear,
399
+ st as formatSingleDate,
400
+ X as formatTrend,
401
+ G as formatValue,
402
+ et as generateMarketId,
403
+ ht as getAllPresets,
404
+ rt as getDateRangeDisplayInfo,
405
+ Dt as getDateRangeForPreset,
406
+ x as getDurationDays,
407
+ T as getPresetDateRange,
408
+ dt as getPresetLabel,
409
+ K as getValueFontSize,
410
+ tt as isInRange,
411
+ J as isMarketActive,
412
+ v as isSameDay,
413
+ it as isValidDateRangeFormat,
414
+ ft as normalizePresetId,
415
+ V as parseDateRange,
416
+ ot as stripLeadingZeros,
417
+ gt as toUppercasePresetId
418
+ };
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Pure Date Helper Functions
3
+ *
4
+ * Extracted from TimeFrame/dateUtils.ts -- contains ONLY pure functions
5
+ * with zero side effects (no localStorage, no TTL, no window access).
6
+ *
7
+ * Side-effect functions (loadPreferences, savePreferences, custom market CRUD)
8
+ * remain in the app's TimeFrame/dateUtils.ts.
9
+ */
10
+ /**
11
+ * Format date range for display
12
+ * Rules:
13
+ * - Same day: "Jan 19, 2026"
14
+ * - Same month: "Jan 1-19"
15
+ * - Different months, same year: "Jan 1 - Feb 5"
16
+ * - Different years: "Jan 1, 2025 - Feb 5, 2026"
17
+ */
18
+ export declare function formatDateRange(start: Date | null, end: Date | null): string;
19
+ /**
20
+ * Format market dates for display
21
+ */
22
+ export declare function formatMarketDates(start: Date, end: Date): string;
23
+ /**
24
+ * Check if a market is currently active
25
+ */
26
+ export declare function isMarketActive(market: {
27
+ startDate?: Date;
28
+ endDate?: Date;
29
+ } | null, referenceDate?: Date): boolean;
30
+ /**
31
+ * Check if two dates are the same day
32
+ */
33
+ export declare function isSameDay(date1: Date | null, date2: Date | null): boolean;
34
+ /**
35
+ * Check if a date is within a range
36
+ */
37
+ export declare function isInRange(date: Date | null, start: Date | null, end: Date | null): boolean;
38
+ /**
39
+ * Generate a unique ID for a new custom market
40
+ */
41
+ export declare function generateMarketId(): string;
42
+ //# sourceMappingURL=dateHelpers.d.ts.map
@@ -0,0 +1,24 @@
1
+ export type TrendDirection = 'up' | 'down' | 'flat';
2
+ export interface FormattedTrend {
3
+ text: string;
4
+ direction: TrendDirection;
5
+ }
6
+ /**
7
+ * Format trend percentage per spec rules.
8
+ *
9
+ * Rules:
10
+ * - null/undefined/0: returns '0%' with 'flat' direction
11
+ * - |value| >= 3: round to whole number (e.g. 30.3 -> '30%')
12
+ * - |value| < 3 and > 0: show one decimal (e.g. 1.2 -> '1.2%')
13
+ * - direction: 'up' if positive, 'down' if negative, 'flat' if zero
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * import { formatTrend } from '@mt-gloss/utils';
18
+ *
19
+ * const result = formatTrend(12.5);
20
+ * // result.text === '13%', result.direction === 'up'
21
+ * ```
22
+ */
23
+ export declare function formatTrend(value: number | null | undefined): FormattedTrend;
24
+ //# sourceMappingURL=formatTrend.d.ts.map
@@ -0,0 +1,40 @@
1
+ export interface FormatValueOptions {
2
+ prefix?: string;
3
+ suffix?: string;
4
+ abbreviated?: boolean;
5
+ kThreshold?: number;
6
+ }
7
+ export interface FormattedValue {
8
+ display: string;
9
+ full: string;
10
+ length: number;
11
+ }
12
+ /**
13
+ * Smart value abbreviation with K/M suffixes and full-value hover.
14
+ *
15
+ * Rules:
16
+ * - |num| >= 1,000,000: divide by 1M, 1 decimal, suffix M (strip trailing .0)
17
+ * - |num| >= 1,000: divide by 1K, 1 decimal, suffix K (strip trailing .0)
18
+ * - Otherwise: full number with locale commas
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * import { formatValue } from '@mt-gloss/utils';
23
+ *
24
+ * const result = formatValue(125430, { prefix: '$', abbreviated: true });
25
+ * // result.display === '$125.4K'
26
+ * ```
27
+ */
28
+ export declare function formatValue(raw: number | string | null | undefined, { prefix, suffix, abbreviated, kThreshold }?: FormatValueOptions): FormattedValue;
29
+ /**
30
+ * Determine font size based on formatted display length and column span.
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * import { getValueFontSize } from '@mt-gloss/utils';
35
+ *
36
+ * const fontSize = getValueFontSize(5, 1); // 28
37
+ * ```
38
+ */
39
+ export declare function getValueFontSize(length: number, colSpan?: number | null): number;
40
+ //# sourceMappingURL=formatValue.d.ts.map
@@ -0,0 +1,49 @@
1
+ import { PresetDefinition, MarketPreset, DateRange } from './types';
2
+ export declare const ROLLING_OPTIONS: PresetDefinition[];
3
+ export declare const PERIOD_OPTIONS: PresetDefinition[];
4
+ export declare const TODAY_PRESET: PresetDefinition;
5
+ export declare const DEFAULT_MARKET_PRESETS: MarketPreset[];
6
+ export declare const PRESET_ID_MAP: Record<string, string>;
7
+ export declare const PRESET_ID_REVERSE_MAP: Record<string, string>;
8
+ /**
9
+ * Get date range for a preset ID
10
+ * @param {string} presetId - Preset ID (case insensitive)
11
+ * @param {Date} referenceDate - Reference date (defaults to now)
12
+ * @returns {{ start: Date, end: Date }} Date range
13
+ */
14
+ export declare function getDateRangeForPreset(presetId: string, referenceDate?: Date): DateRange;
15
+ /**
16
+ * Find a preset by ID
17
+ * @param {string} id - Preset ID (case insensitive)
18
+ * @returns {object|null} Preset object or null
19
+ */
20
+ export declare function findPreset(id: string): PresetDefinition | null;
21
+ /**
22
+ * Get human-readable display label for a preset ID.
23
+ * Handles uppercase IDs (from onChange payload) and lowercase IDs.
24
+ * @param {string} presetId - Preset ID (e.g., 'MTD', 'mtd', '30D', 'custom', 'today')
25
+ * @returns {string} Display label (e.g., 'MTD', '30D', 'Today', 'Custom', or the raw ID as fallback)
26
+ */
27
+ export declare function getPresetLabel(presetId: string | null | undefined): string;
28
+ /**
29
+ * Normalize preset ID to lowercase
30
+ * @param {string} id - Preset ID
31
+ * @returns {string|null} Normalized ID
32
+ */
33
+ export declare function normalizePresetId(id: string | null | undefined): string | null;
34
+ /**
35
+ * Convert preset ID to uppercase (for backward compatibility)
36
+ * @param {string} id - Preset ID
37
+ * @returns {string|null} Uppercase ID
38
+ */
39
+ export declare function toUppercasePresetId(id: string | null | undefined): string | null;
40
+ /**
41
+ * Get all presets as a flat array
42
+ * @returns {Array} All presets
43
+ */
44
+ export declare function getAllPresets(): PresetDefinition[];
45
+ /**
46
+ * Default pinned preset (single global default)
47
+ */
48
+ export declare const DEFAULT_PINNED: string;
49
+ //# sourceMappingURL=presets.d.ts.map
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Date Range Formatting Utility
3
+ *
4
+ * Formats date ranges with minimal text while maintaining clarity.
5
+ * Designed for use with date pickers and time frame selectors.
6
+ *
7
+ * @module dateRangeFormat
8
+ */
9
+ interface CustomPreset {
10
+ id: string;
11
+ label: string;
12
+ startDate?: Date;
13
+ endDate?: Date;
14
+ priority?: number;
15
+ }
16
+ interface PresetMatch {
17
+ id: string;
18
+ label: string;
19
+ }
20
+ interface DateRangeDisplayInfo {
21
+ display: string;
22
+ formatted: string;
23
+ preset: PresetMatch | null;
24
+ }
25
+ interface FormatDateRangeOptions {
26
+ shortYear?: boolean;
27
+ }
28
+ interface FormatWithPresetOptions {
29
+ preferPresetLabel?: boolean;
30
+ showPresetDates?: boolean;
31
+ customPresets?: CustomPreset[];
32
+ }
33
+ interface DateRangeResult {
34
+ start: Date;
35
+ end: Date;
36
+ }
37
+ interface FormatPresetResult extends DateRangeResult {
38
+ formatted: string;
39
+ }
40
+ /**
41
+ * Detect if a date range matches a known preset
42
+ */
43
+ export declare function detectPreset(start: Date, end: Date, referenceDate?: Date, customPresets?: CustomPreset[]): PresetMatch | null;
44
+ /**
45
+ * Format a date range, returning preset label if it matches a known preset
46
+ */
47
+ export declare function formatDateRangeWithPreset(start: Date, end: Date, referenceDate?: Date, options?: FormatWithPresetOptions): string;
48
+ /**
49
+ * Get display info for a date range (both label and formatted date)
50
+ */
51
+ export declare function getDateRangeDisplayInfo(start: Date, end: Date, referenceDate?: Date, customPresets?: CustomPreset[]): DateRangeDisplayInfo;
52
+ /**
53
+ * Format a date's year as curly-apostrophe + 2-digit year string.
54
+ * e.g., 2026 -> '\u201926', 2025 -> '\u201925'
55
+ */
56
+ export declare function formatShortYear(date: Date): string;
57
+ /**
58
+ * Strip leading zeros from numeric-only strings.
59
+ * Preserves at least one character (so "0" stays "0", "000" becomes "0").
60
+ * Non-numeric-prefixed strings pass through unchanged.
61
+ * Null/undefined return as-is.
62
+ */
63
+ export declare function stripLeadingZeros(value: string | null | undefined): string | null | undefined;
64
+ /**
65
+ * Format a date range with minimal redundancy
66
+ */
67
+ export declare function formatDateRange(start: Date, end: Date, referenceDate?: Date, options?: FormatDateRangeOptions): string;
68
+ /**
69
+ * Format a single date with minimal redundancy
70
+ */
71
+ export declare function formatSingleDate(date: Date, referenceDate?: Date): string;
72
+ /**
73
+ * Parse a formatted date range string back to Date objects
74
+ */
75
+ export declare function parseDateRange(formatted: string, referenceDate?: Date): DateRangeResult;
76
+ /**
77
+ * Validate that a string is a valid formatted date range
78
+ */
79
+ export declare function isValidDateRangeFormat(formatted: string): boolean;
80
+ /**
81
+ * Get the duration in days between two dates
82
+ */
83
+ export declare function getDurationDays(start: Date, end: Date): number;
84
+ /**
85
+ * Format duration as human-readable string
86
+ */
87
+ export declare function formatDuration(start: Date, end: Date): string;
88
+ /**
89
+ * Format a preset identifier to its date range string
90
+ */
91
+ export declare function formatPreset(presetId: string, referenceDate?: Date): FormatPresetResult;
92
+ /**
93
+ * Get date range for a preset identifier
94
+ */
95
+ export declare function getPresetDateRange(presetId: string, referenceDate?: Date): DateRangeResult;
96
+ declare const _default: {
97
+ formatDateRange: typeof formatDateRange;
98
+ formatSingleDate: typeof formatSingleDate;
99
+ formatDateRangeWithPreset: typeof formatDateRangeWithPreset;
100
+ formatShortYear: typeof formatShortYear;
101
+ detectPreset: typeof detectPreset;
102
+ getDateRangeDisplayInfo: typeof getDateRangeDisplayInfo;
103
+ parseDateRange: typeof parseDateRange;
104
+ isValidDateRangeFormat: typeof isValidDateRangeFormat;
105
+ getDurationDays: typeof getDurationDays;
106
+ formatDuration: typeof formatDuration;
107
+ stripLeadingZeros: typeof stripLeadingZeros;
108
+ formatPreset: typeof formatPreset;
109
+ getPresetDateRange: typeof getPresetDateRange;
110
+ };
111
+ export default _default;
112
+ //# sourceMappingURL=smartDateFormat.d.ts.map
package/lib/types.d.ts ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Definition for a date range preset (e.g., "Last 7 Days", "This Month").
3
+ */
4
+ export interface PresetDefinition {
5
+ id: string;
6
+ label: string;
7
+ getRange?: () => [Date, Date];
8
+ category?: string;
9
+ pinned?: boolean;
10
+ }
11
+ /**
12
+ * A market-specific preset that extends the base preset with location and date data.
13
+ */
14
+ export interface MarketPreset extends PresetDefinition {
15
+ startDate: Date;
16
+ endDate: Date;
17
+ location: string;
18
+ }
19
+ /**
20
+ * A simple date range with optional label.
21
+ */
22
+ export interface DateRange {
23
+ start: Date;
24
+ end: Date;
25
+ label?: string;
26
+ }
27
+ /**
28
+ * A market event with date range and location metadata.
29
+ */
30
+ export interface MarketEvent {
31
+ id: string;
32
+ name: string;
33
+ location: string;
34
+ startDate: Date;
35
+ endDate: Date;
36
+ category?: string;
37
+ }
38
+ //# sourceMappingURL=types.d.ts.map
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@mt-gloss/utils",
3
+ "version": "0.0.3",
4
+ "type": "module",
5
+ "main": "./index.js",
6
+ "module": "./index.js",
7
+ "types": "./index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": {
11
+ "types": "./index.d.ts",
12
+ "default": "./index.js"
13
+ }
14
+ }
15
+ },
16
+ "files": [
17
+ "index.js",
18
+ "index.d.ts",
19
+ "lib/**/*.d.ts",
20
+ "README.md"
21
+ ],
22
+ "peerDependencies": {
23
+ "date-fns": "^3.0.0 || ^4.0.0"
24
+ }
25
+ }