@startsimpli/utils 0.1.3 → 0.1.5

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/package.json CHANGED
@@ -1,19 +1,14 @@
1
1
  {
2
2
  "name": "@startsimpli/utils",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Pure TypeScript formatting utilities for StartSimpli apps",
5
- "main": "./dist/index.js",
6
- "types": "./dist/index.d.ts",
5
+ "main": "./src/index.ts",
6
+ "types": "./src/index.ts",
7
7
  "exports": {
8
- ".": {
9
- "types": "./dist/index.d.ts",
10
- "import": "./dist/index.mjs",
11
- "require": "./dist/index.js"
12
- }
8
+ ".": "./src/index.ts"
13
9
  },
14
10
  "files": [
15
- "src",
16
- "dist"
11
+ "src"
17
12
  ],
18
13
  "scripts": {
19
14
  "build": "tsup",
@@ -27,6 +22,5 @@
27
22
  "typescript": "^5.3.3",
28
23
  "tsup": "^8.5.1",
29
24
  "vitest": "^2.0.0"
30
- },
31
- "module": "./dist/index.mjs"
25
+ }
32
26
  }
package/dist/index.d.mts DELETED
@@ -1,89 +0,0 @@
1
- interface FormatCurrencyOptions {
2
- currency?: string;
3
- fromCents?: boolean;
4
- locale?: string;
5
- decimals?: number;
6
- }
7
- /**
8
- * Format a number as currency. Supports cents-to-dollars conversion,
9
- * configurable currency code, and locale.
10
- */
11
- declare function formatCurrency(amount: number, opts?: FormatCurrencyOptions): string;
12
- /**
13
- * Parse a currency string into a number.
14
- * Strips currency symbols, commas, whitespace, etc.
15
- * Returns NaN if the string cannot be parsed.
16
- */
17
- declare function parseCurrency(s: string): number;
18
-
19
- type DateFormat = 'short' | 'long' | 'iso' | 'relative';
20
- type DatePreset = 'today' | '7d' | '30d' | '90d';
21
- interface DateRange {
22
- from: Date;
23
- to: Date;
24
- }
25
- interface QuickDateFilter {
26
- label: string;
27
- preset: DatePreset;
28
- }
29
- declare const QUICK_DATE_FILTERS: ReadonlyArray<QuickDateFilter>;
30
- /**
31
- * Format a date in one of several preset styles.
32
- * Returns empty string for null/undefined/invalid dates.
33
- *
34
- * - 'short' (default): "Jan 5, 2024"
35
- * - 'long': "January 5, 2024"
36
- * - 'iso': "2024-01-05"
37
- * - 'relative': "2 days ago" / "just now" / "in 3 days"
38
- */
39
- declare function formatDate(date: Date | string | null | undefined, format?: DateFormat): string;
40
- /**
41
- * Format a date relative to now.
42
- * Handles both past ("2 days ago") and future ("in 3 days") dates.
43
- */
44
- declare function formatRelativeTime(date: Date | string): string;
45
- /**
46
- * Safely parse a date string. Returns null for invalid or empty input.
47
- */
48
- declare function parseDate(s: string | null | undefined): Date | null;
49
- /**
50
- * Get a date range for a named preset.
51
- * "to" is end of today, "from" is start of the lookback period.
52
- */
53
- declare function getDateRange(preset: DatePreset): DateRange;
54
-
55
- interface FormatNumberOptions {
56
- abbreviate?: boolean;
57
- decimals?: number;
58
- locale?: string;
59
- }
60
- /**
61
- * Format a number with optional abbreviation (e.g. 1200000 -> "1.2M").
62
- */
63
- declare function formatNumber(n: number, opts?: FormatNumberOptions): string;
64
- /**
65
- * Format a decimal as a percentage string.
66
- * 0.1234 -> "12.3%", 1 -> "100%"
67
- */
68
- declare function formatPercent(n: number, decimals?: number): string;
69
-
70
- /**
71
- * Get initials from a name string (up to 2 characters).
72
- * "John Doe" -> "JD", "Apple Inc" -> "AI", "" -> ""
73
- */
74
- declare function getInitials(name: string): string;
75
- /**
76
- * Truncate a string to the specified length, appending a suffix if truncated.
77
- */
78
- declare function truncate(s: string, length: number, suffix?: string): string;
79
- /**
80
- * Convert a string into a URL-friendly slug.
81
- * "Hello World!" -> "hello-world"
82
- */
83
- declare function slugify(s: string): string;
84
- /**
85
- * Capitalize the first character of a string.
86
- */
87
- declare function capitalize(s: string): string;
88
-
89
- export { type DateFormat, type DatePreset, type DateRange, type FormatCurrencyOptions, type FormatNumberOptions, QUICK_DATE_FILTERS, type QuickDateFilter, capitalize, formatCurrency, formatDate, formatNumber, formatPercent, formatRelativeTime, getDateRange, getInitials, parseCurrency, parseDate, slugify, truncate };
package/dist/index.d.ts DELETED
@@ -1,89 +0,0 @@
1
- interface FormatCurrencyOptions {
2
- currency?: string;
3
- fromCents?: boolean;
4
- locale?: string;
5
- decimals?: number;
6
- }
7
- /**
8
- * Format a number as currency. Supports cents-to-dollars conversion,
9
- * configurable currency code, and locale.
10
- */
11
- declare function formatCurrency(amount: number, opts?: FormatCurrencyOptions): string;
12
- /**
13
- * Parse a currency string into a number.
14
- * Strips currency symbols, commas, whitespace, etc.
15
- * Returns NaN if the string cannot be parsed.
16
- */
17
- declare function parseCurrency(s: string): number;
18
-
19
- type DateFormat = 'short' | 'long' | 'iso' | 'relative';
20
- type DatePreset = 'today' | '7d' | '30d' | '90d';
21
- interface DateRange {
22
- from: Date;
23
- to: Date;
24
- }
25
- interface QuickDateFilter {
26
- label: string;
27
- preset: DatePreset;
28
- }
29
- declare const QUICK_DATE_FILTERS: ReadonlyArray<QuickDateFilter>;
30
- /**
31
- * Format a date in one of several preset styles.
32
- * Returns empty string for null/undefined/invalid dates.
33
- *
34
- * - 'short' (default): "Jan 5, 2024"
35
- * - 'long': "January 5, 2024"
36
- * - 'iso': "2024-01-05"
37
- * - 'relative': "2 days ago" / "just now" / "in 3 days"
38
- */
39
- declare function formatDate(date: Date | string | null | undefined, format?: DateFormat): string;
40
- /**
41
- * Format a date relative to now.
42
- * Handles both past ("2 days ago") and future ("in 3 days") dates.
43
- */
44
- declare function formatRelativeTime(date: Date | string): string;
45
- /**
46
- * Safely parse a date string. Returns null for invalid or empty input.
47
- */
48
- declare function parseDate(s: string | null | undefined): Date | null;
49
- /**
50
- * Get a date range for a named preset.
51
- * "to" is end of today, "from" is start of the lookback period.
52
- */
53
- declare function getDateRange(preset: DatePreset): DateRange;
54
-
55
- interface FormatNumberOptions {
56
- abbreviate?: boolean;
57
- decimals?: number;
58
- locale?: string;
59
- }
60
- /**
61
- * Format a number with optional abbreviation (e.g. 1200000 -> "1.2M").
62
- */
63
- declare function formatNumber(n: number, opts?: FormatNumberOptions): string;
64
- /**
65
- * Format a decimal as a percentage string.
66
- * 0.1234 -> "12.3%", 1 -> "100%"
67
- */
68
- declare function formatPercent(n: number, decimals?: number): string;
69
-
70
- /**
71
- * Get initials from a name string (up to 2 characters).
72
- * "John Doe" -> "JD", "Apple Inc" -> "AI", "" -> ""
73
- */
74
- declare function getInitials(name: string): string;
75
- /**
76
- * Truncate a string to the specified length, appending a suffix if truncated.
77
- */
78
- declare function truncate(s: string, length: number, suffix?: string): string;
79
- /**
80
- * Convert a string into a URL-friendly slug.
81
- * "Hello World!" -> "hello-world"
82
- */
83
- declare function slugify(s: string): string;
84
- /**
85
- * Capitalize the first character of a string.
86
- */
87
- declare function capitalize(s: string): string;
88
-
89
- export { type DateFormat, type DatePreset, type DateRange, type FormatCurrencyOptions, type FormatNumberOptions, QUICK_DATE_FILTERS, type QuickDateFilter, capitalize, formatCurrency, formatDate, formatNumber, formatPercent, formatRelativeTime, getDateRange, getInitials, parseCurrency, parseDate, slugify, truncate };
package/dist/index.js DELETED
@@ -1,167 +0,0 @@
1
- 'use strict';
2
-
3
- // src/currency.ts
4
- function formatCurrency(amount, opts) {
5
- const {
6
- currency = "USD",
7
- fromCents = false,
8
- locale = "en-US",
9
- decimals
10
- } = opts ?? {};
11
- const value = fromCents ? amount / 100 : amount;
12
- const hasFraction = value !== Math.floor(value);
13
- const defaultMin = fromCents && hasFraction ? 2 : 0;
14
- const defaultMax = 2;
15
- return new Intl.NumberFormat(locale, {
16
- style: "currency",
17
- currency,
18
- minimumFractionDigits: decimals ?? defaultMin,
19
- maximumFractionDigits: decimals ?? defaultMax
20
- }).format(value);
21
- }
22
- function parseCurrency(s) {
23
- const cleaned = s.replace(/[^0-9.\-]/g, "");
24
- if (cleaned === "" || cleaned === "-") return NaN;
25
- return parseFloat(cleaned);
26
- }
27
-
28
- // src/date.ts
29
- var QUICK_DATE_FILTERS = [
30
- { label: "Today", preset: "today" },
31
- { label: "Last 7 days", preset: "7d" },
32
- { label: "Last 30 days", preset: "30d" },
33
- { label: "Last 90 days", preset: "90d" }
34
- ];
35
- function formatDate(date, format = "short") {
36
- if (date == null) return "";
37
- const d = typeof date === "string" ? new Date(date) : date;
38
- if (isNaN(d.getTime())) return "";
39
- switch (format) {
40
- case "short":
41
- return new Intl.DateTimeFormat("en-US", {
42
- month: "short",
43
- day: "numeric",
44
- year: "numeric"
45
- }).format(d);
46
- case "long":
47
- return new Intl.DateTimeFormat("en-US", {
48
- month: "long",
49
- day: "numeric",
50
- year: "numeric"
51
- }).format(d);
52
- case "iso":
53
- return d.toISOString().split("T")[0];
54
- case "relative":
55
- return formatRelativeTime(d);
56
- }
57
- }
58
- function formatRelativeTime(date) {
59
- const d = typeof date === "string" ? new Date(date) : date;
60
- if (isNaN(d.getTime())) return "";
61
- const now = /* @__PURE__ */ new Date();
62
- const diffMs = now.getTime() - d.getTime();
63
- const absDiffMs = Math.abs(diffMs);
64
- const isPast = diffMs > 0;
65
- const seconds = Math.floor(absDiffMs / 1e3);
66
- const minutes = Math.floor(seconds / 60);
67
- const hours = Math.floor(minutes / 60);
68
- const days = Math.floor(hours / 24);
69
- if (seconds < 60) return "just now";
70
- if (days > 0) {
71
- const label2 = days === 1 ? "1 day" : `${days} days`;
72
- return isPast ? `${label2} ago` : `in ${label2}`;
73
- }
74
- if (hours > 0) {
75
- const label2 = hours === 1 ? "1 hour" : `${hours} hours`;
76
- return isPast ? `${label2} ago` : `in ${label2}`;
77
- }
78
- const label = minutes === 1 ? "1 minute" : `${minutes} minutes`;
79
- return isPast ? `${label} ago` : `in ${label}`;
80
- }
81
- function parseDate(s) {
82
- if (!s || s.trim() === "") return null;
83
- const d = new Date(s);
84
- return isNaN(d.getTime()) ? null : d;
85
- }
86
- function getDateRange(preset) {
87
- const now = /* @__PURE__ */ new Date();
88
- const to = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999);
89
- const from = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0);
90
- switch (preset) {
91
- case "today":
92
- break;
93
- case "7d":
94
- from.setDate(from.getDate() - 6);
95
- break;
96
- case "30d":
97
- from.setDate(from.getDate() - 29);
98
- break;
99
- case "90d":
100
- from.setDate(from.getDate() - 89);
101
- break;
102
- }
103
- return { from, to };
104
- }
105
-
106
- // src/numbers.ts
107
- var ABBREVIATIONS = [
108
- { threshold: 1e9, suffix: "B" },
109
- { threshold: 1e6, suffix: "M" },
110
- { threshold: 1e3, suffix: "K" }
111
- ];
112
- function formatNumber(n, opts) {
113
- const { abbreviate = false, decimals, locale = "en-US" } = opts ?? {};
114
- if (abbreviate) {
115
- for (const { threshold, suffix } of ABBREVIATIONS) {
116
- if (Math.abs(n) >= threshold) {
117
- const value = n / threshold;
118
- const d = decimals ?? 1;
119
- const formatted = new Intl.NumberFormat(locale, {
120
- minimumFractionDigits: 0,
121
- maximumFractionDigits: d
122
- }).format(value);
123
- return `${formatted}${suffix}`;
124
- }
125
- }
126
- }
127
- return new Intl.NumberFormat(locale, {
128
- minimumFractionDigits: decimals ?? 0,
129
- maximumFractionDigits: decimals ?? 2
130
- }).format(n);
131
- }
132
- function formatPercent(n, decimals = 1) {
133
- return `${(n * 100).toFixed(decimals).replace(/\.0+$/, "")}%`;
134
- }
135
-
136
- // src/strings.ts
137
- function getInitials(name) {
138
- if (!name || !name.trim()) return "";
139
- return name.trim().split(/\s+/).map((word) => word[0]).join("").toUpperCase().slice(0, 2);
140
- }
141
- function truncate(s, length, suffix = "...") {
142
- if (s.length <= length) return s;
143
- return s.slice(0, length - suffix.length) + suffix;
144
- }
145
- function slugify(s) {
146
- return s.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
147
- }
148
- function capitalize(s) {
149
- if (!s) return s;
150
- return s.charAt(0).toUpperCase() + s.slice(1);
151
- }
152
-
153
- exports.QUICK_DATE_FILTERS = QUICK_DATE_FILTERS;
154
- exports.capitalize = capitalize;
155
- exports.formatCurrency = formatCurrency;
156
- exports.formatDate = formatDate;
157
- exports.formatNumber = formatNumber;
158
- exports.formatPercent = formatPercent;
159
- exports.formatRelativeTime = formatRelativeTime;
160
- exports.getDateRange = getDateRange;
161
- exports.getInitials = getInitials;
162
- exports.parseCurrency = parseCurrency;
163
- exports.parseDate = parseDate;
164
- exports.slugify = slugify;
165
- exports.truncate = truncate;
166
- //# sourceMappingURL=index.js.map
167
- //# sourceMappingURL=index.js.map
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/currency.ts","../src/date.ts","../src/numbers.ts","../src/strings.ts"],"names":["label"],"mappings":"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","file":"index.js","sourcesContent":["export interface FormatCurrencyOptions {\n currency?: string\n fromCents?: boolean\n locale?: string\n decimals?: number\n}\n\n/**\n * Format a number as currency. Supports cents-to-dollars conversion,\n * configurable currency code, and locale.\n */\nexport function formatCurrency(\n amount: number,\n opts?: FormatCurrencyOptions\n): string {\n const {\n currency = 'USD',\n fromCents = false,\n locale = 'en-US',\n decimals,\n } = opts ?? {}\n\n const value = fromCents ? amount / 100 : amount\n\n // When converting from cents, default to showing 2 decimal places\n // to avoid $0.5 instead of $0.50\n const hasFraction = value !== Math.floor(value)\n const defaultMin = (fromCents && hasFraction) ? 2 : 0\n const defaultMax = 2\n\n return new Intl.NumberFormat(locale, {\n style: 'currency',\n currency,\n minimumFractionDigits: decimals ?? defaultMin,\n maximumFractionDigits: decimals ?? defaultMax,\n }).format(value)\n}\n\n/**\n * Parse a currency string into a number.\n * Strips currency symbols, commas, whitespace, etc.\n * Returns NaN if the string cannot be parsed.\n */\nexport function parseCurrency(s: string): number {\n // Remove everything except digits, dots, minus signs\n const cleaned = s.replace(/[^0-9.\\-]/g, '')\n if (cleaned === '' || cleaned === '-') return NaN\n return parseFloat(cleaned)\n}\n","export type DateFormat = 'short' | 'long' | 'iso' | 'relative'\n\nexport type DatePreset = 'today' | '7d' | '30d' | '90d'\n\nexport interface DateRange {\n from: Date\n to: Date\n}\n\nexport interface QuickDateFilter {\n label: string\n preset: DatePreset\n}\n\nexport const QUICK_DATE_FILTERS: ReadonlyArray<QuickDateFilter> = [\n { label: 'Today', preset: 'today' },\n { label: 'Last 7 days', preset: '7d' },\n { label: 'Last 30 days', preset: '30d' },\n { label: 'Last 90 days', preset: '90d' },\n] as const\n\n/**\n * Format a date in one of several preset styles.\n * Returns empty string for null/undefined/invalid dates.\n *\n * - 'short' (default): \"Jan 5, 2024\"\n * - 'long': \"January 5, 2024\"\n * - 'iso': \"2024-01-05\"\n * - 'relative': \"2 days ago\" / \"just now\" / \"in 3 days\"\n */\nexport function formatDate(\n date: Date | string | null | undefined,\n format: DateFormat = 'short'\n): string {\n if (date == null) return ''\n\n const d = typeof date === 'string' ? new Date(date) : date\n if (isNaN(d.getTime())) return ''\n\n switch (format) {\n case 'short':\n return new Intl.DateTimeFormat('en-US', {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n }).format(d)\n\n case 'long':\n return new Intl.DateTimeFormat('en-US', {\n month: 'long',\n day: 'numeric',\n year: 'numeric',\n }).format(d)\n\n case 'iso':\n return d.toISOString().split('T')[0]\n\n case 'relative':\n return formatRelativeTime(d)\n }\n}\n\n/**\n * Format a date relative to now.\n * Handles both past (\"2 days ago\") and future (\"in 3 days\") dates.\n */\nexport function formatRelativeTime(date: Date | string): string {\n const d = typeof date === 'string' ? new Date(date) : date\n if (isNaN(d.getTime())) return ''\n\n const now = new Date()\n const diffMs = now.getTime() - d.getTime()\n const absDiffMs = Math.abs(diffMs)\n const isPast = diffMs > 0\n\n const seconds = Math.floor(absDiffMs / 1000)\n const minutes = Math.floor(seconds / 60)\n const hours = Math.floor(minutes / 60)\n const days = Math.floor(hours / 24)\n\n if (seconds < 60) return 'just now'\n\n if (days > 0) {\n const label = days === 1 ? '1 day' : `${days} days`\n return isPast ? `${label} ago` : `in ${label}`\n }\n\n if (hours > 0) {\n const label = hours === 1 ? '1 hour' : `${hours} hours`\n return isPast ? `${label} ago` : `in ${label}`\n }\n\n const label = minutes === 1 ? '1 minute' : `${minutes} minutes`\n return isPast ? `${label} ago` : `in ${label}`\n}\n\n/**\n * Safely parse a date string. Returns null for invalid or empty input.\n */\nexport function parseDate(s: string | null | undefined): Date | null {\n if (!s || s.trim() === '') return null\n const d = new Date(s)\n return isNaN(d.getTime()) ? null : d\n}\n\n/**\n * Get a date range for a named preset.\n * \"to\" is end of today, \"from\" is start of the lookback period.\n */\nexport function getDateRange(preset: DatePreset): DateRange {\n const now = new Date()\n const to = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999)\n const from = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0)\n\n switch (preset) {\n case 'today':\n // from is already start of today\n break\n case '7d':\n from.setDate(from.getDate() - 6)\n break\n case '30d':\n from.setDate(from.getDate() - 29)\n break\n case '90d':\n from.setDate(from.getDate() - 89)\n break\n }\n\n return { from, to }\n}\n","export interface FormatNumberOptions {\n abbreviate?: boolean\n decimals?: number\n locale?: string\n}\n\nconst ABBREVIATIONS = [\n { threshold: 1_000_000_000, suffix: 'B' },\n { threshold: 1_000_000, suffix: 'M' },\n { threshold: 1_000, suffix: 'K' },\n] as const\n\n/**\n * Format a number with optional abbreviation (e.g. 1200000 -> \"1.2M\").\n */\nexport function formatNumber(\n n: number,\n opts?: FormatNumberOptions\n): string {\n const { abbreviate = false, decimals, locale = 'en-US' } = opts ?? {}\n\n if (abbreviate) {\n for (const { threshold, suffix } of ABBREVIATIONS) {\n if (Math.abs(n) >= threshold) {\n const value = n / threshold\n const d = decimals ?? 1\n const formatted = new Intl.NumberFormat(locale, {\n minimumFractionDigits: 0,\n maximumFractionDigits: d,\n }).format(value)\n return `${formatted}${suffix}`\n }\n }\n }\n\n return new Intl.NumberFormat(locale, {\n minimumFractionDigits: decimals ?? 0,\n maximumFractionDigits: decimals ?? 2,\n }).format(n)\n}\n\n/**\n * Format a decimal as a percentage string.\n * 0.1234 -> \"12.3%\", 1 -> \"100%\"\n */\nexport function formatPercent(n: number, decimals = 1): string {\n return `${(n * 100).toFixed(decimals).replace(/\\.0+$/, '')}%`\n}\n","/**\n * Get initials from a name string (up to 2 characters).\n * \"John Doe\" -> \"JD\", \"Apple Inc\" -> \"AI\", \"\" -> \"\"\n */\nexport function getInitials(name: string): string {\n if (!name || !name.trim()) return ''\n return name\n .trim()\n .split(/\\s+/)\n .map((word) => word[0])\n .join('')\n .toUpperCase()\n .slice(0, 2)\n}\n\n/**\n * Truncate a string to the specified length, appending a suffix if truncated.\n */\nexport function truncate(s: string, length: number, suffix = '...'): string {\n if (s.length <= length) return s\n return s.slice(0, length - suffix.length) + suffix\n}\n\n/**\n * Convert a string into a URL-friendly slug.\n * \"Hello World!\" -> \"hello-world\"\n */\nexport function slugify(s: string): string {\n return s\n .toLowerCase()\n .trim()\n .replace(/[^\\w\\s-]/g, '')\n .replace(/[\\s_]+/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-+|-+$/g, '')\n}\n\n/**\n * Capitalize the first character of a string.\n */\nexport function capitalize(s: string): string {\n if (!s) return s\n return s.charAt(0).toUpperCase() + s.slice(1)\n}\n"]}
package/dist/index.mjs DELETED
@@ -1,153 +0,0 @@
1
- // src/currency.ts
2
- function formatCurrency(amount, opts) {
3
- const {
4
- currency = "USD",
5
- fromCents = false,
6
- locale = "en-US",
7
- decimals
8
- } = opts ?? {};
9
- const value = fromCents ? amount / 100 : amount;
10
- const hasFraction = value !== Math.floor(value);
11
- const defaultMin = fromCents && hasFraction ? 2 : 0;
12
- const defaultMax = 2;
13
- return new Intl.NumberFormat(locale, {
14
- style: "currency",
15
- currency,
16
- minimumFractionDigits: decimals ?? defaultMin,
17
- maximumFractionDigits: decimals ?? defaultMax
18
- }).format(value);
19
- }
20
- function parseCurrency(s) {
21
- const cleaned = s.replace(/[^0-9.\-]/g, "");
22
- if (cleaned === "" || cleaned === "-") return NaN;
23
- return parseFloat(cleaned);
24
- }
25
-
26
- // src/date.ts
27
- var QUICK_DATE_FILTERS = [
28
- { label: "Today", preset: "today" },
29
- { label: "Last 7 days", preset: "7d" },
30
- { label: "Last 30 days", preset: "30d" },
31
- { label: "Last 90 days", preset: "90d" }
32
- ];
33
- function formatDate(date, format = "short") {
34
- if (date == null) return "";
35
- const d = typeof date === "string" ? new Date(date) : date;
36
- if (isNaN(d.getTime())) return "";
37
- switch (format) {
38
- case "short":
39
- return new Intl.DateTimeFormat("en-US", {
40
- month: "short",
41
- day: "numeric",
42
- year: "numeric"
43
- }).format(d);
44
- case "long":
45
- return new Intl.DateTimeFormat("en-US", {
46
- month: "long",
47
- day: "numeric",
48
- year: "numeric"
49
- }).format(d);
50
- case "iso":
51
- return d.toISOString().split("T")[0];
52
- case "relative":
53
- return formatRelativeTime(d);
54
- }
55
- }
56
- function formatRelativeTime(date) {
57
- const d = typeof date === "string" ? new Date(date) : date;
58
- if (isNaN(d.getTime())) return "";
59
- const now = /* @__PURE__ */ new Date();
60
- const diffMs = now.getTime() - d.getTime();
61
- const absDiffMs = Math.abs(diffMs);
62
- const isPast = diffMs > 0;
63
- const seconds = Math.floor(absDiffMs / 1e3);
64
- const minutes = Math.floor(seconds / 60);
65
- const hours = Math.floor(minutes / 60);
66
- const days = Math.floor(hours / 24);
67
- if (seconds < 60) return "just now";
68
- if (days > 0) {
69
- const label2 = days === 1 ? "1 day" : `${days} days`;
70
- return isPast ? `${label2} ago` : `in ${label2}`;
71
- }
72
- if (hours > 0) {
73
- const label2 = hours === 1 ? "1 hour" : `${hours} hours`;
74
- return isPast ? `${label2} ago` : `in ${label2}`;
75
- }
76
- const label = minutes === 1 ? "1 minute" : `${minutes} minutes`;
77
- return isPast ? `${label} ago` : `in ${label}`;
78
- }
79
- function parseDate(s) {
80
- if (!s || s.trim() === "") return null;
81
- const d = new Date(s);
82
- return isNaN(d.getTime()) ? null : d;
83
- }
84
- function getDateRange(preset) {
85
- const now = /* @__PURE__ */ new Date();
86
- const to = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999);
87
- const from = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0);
88
- switch (preset) {
89
- case "today":
90
- break;
91
- case "7d":
92
- from.setDate(from.getDate() - 6);
93
- break;
94
- case "30d":
95
- from.setDate(from.getDate() - 29);
96
- break;
97
- case "90d":
98
- from.setDate(from.getDate() - 89);
99
- break;
100
- }
101
- return { from, to };
102
- }
103
-
104
- // src/numbers.ts
105
- var ABBREVIATIONS = [
106
- { threshold: 1e9, suffix: "B" },
107
- { threshold: 1e6, suffix: "M" },
108
- { threshold: 1e3, suffix: "K" }
109
- ];
110
- function formatNumber(n, opts) {
111
- const { abbreviate = false, decimals, locale = "en-US" } = opts ?? {};
112
- if (abbreviate) {
113
- for (const { threshold, suffix } of ABBREVIATIONS) {
114
- if (Math.abs(n) >= threshold) {
115
- const value = n / threshold;
116
- const d = decimals ?? 1;
117
- const formatted = new Intl.NumberFormat(locale, {
118
- minimumFractionDigits: 0,
119
- maximumFractionDigits: d
120
- }).format(value);
121
- return `${formatted}${suffix}`;
122
- }
123
- }
124
- }
125
- return new Intl.NumberFormat(locale, {
126
- minimumFractionDigits: decimals ?? 0,
127
- maximumFractionDigits: decimals ?? 2
128
- }).format(n);
129
- }
130
- function formatPercent(n, decimals = 1) {
131
- return `${(n * 100).toFixed(decimals).replace(/\.0+$/, "")}%`;
132
- }
133
-
134
- // src/strings.ts
135
- function getInitials(name) {
136
- if (!name || !name.trim()) return "";
137
- return name.trim().split(/\s+/).map((word) => word[0]).join("").toUpperCase().slice(0, 2);
138
- }
139
- function truncate(s, length, suffix = "...") {
140
- if (s.length <= length) return s;
141
- return s.slice(0, length - suffix.length) + suffix;
142
- }
143
- function slugify(s) {
144
- return s.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
145
- }
146
- function capitalize(s) {
147
- if (!s) return s;
148
- return s.charAt(0).toUpperCase() + s.slice(1);
149
- }
150
-
151
- export { QUICK_DATE_FILTERS, capitalize, formatCurrency, formatDate, formatNumber, formatPercent, formatRelativeTime, getDateRange, getInitials, parseCurrency, parseDate, slugify, truncate };
152
- //# sourceMappingURL=index.mjs.map
153
- //# sourceMappingURL=index.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/currency.ts","../src/date.ts","../src/numbers.ts","../src/strings.ts"],"names":["label"],"mappings":"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","file":"index.mjs","sourcesContent":["export interface FormatCurrencyOptions {\n currency?: string\n fromCents?: boolean\n locale?: string\n decimals?: number\n}\n\n/**\n * Format a number as currency. Supports cents-to-dollars conversion,\n * configurable currency code, and locale.\n */\nexport function formatCurrency(\n amount: number,\n opts?: FormatCurrencyOptions\n): string {\n const {\n currency = 'USD',\n fromCents = false,\n locale = 'en-US',\n decimals,\n } = opts ?? {}\n\n const value = fromCents ? amount / 100 : amount\n\n // When converting from cents, default to showing 2 decimal places\n // to avoid $0.5 instead of $0.50\n const hasFraction = value !== Math.floor(value)\n const defaultMin = (fromCents && hasFraction) ? 2 : 0\n const defaultMax = 2\n\n return new Intl.NumberFormat(locale, {\n style: 'currency',\n currency,\n minimumFractionDigits: decimals ?? defaultMin,\n maximumFractionDigits: decimals ?? defaultMax,\n }).format(value)\n}\n\n/**\n * Parse a currency string into a number.\n * Strips currency symbols, commas, whitespace, etc.\n * Returns NaN if the string cannot be parsed.\n */\nexport function parseCurrency(s: string): number {\n // Remove everything except digits, dots, minus signs\n const cleaned = s.replace(/[^0-9.\\-]/g, '')\n if (cleaned === '' || cleaned === '-') return NaN\n return parseFloat(cleaned)\n}\n","export type DateFormat = 'short' | 'long' | 'iso' | 'relative'\n\nexport type DatePreset = 'today' | '7d' | '30d' | '90d'\n\nexport interface DateRange {\n from: Date\n to: Date\n}\n\nexport interface QuickDateFilter {\n label: string\n preset: DatePreset\n}\n\nexport const QUICK_DATE_FILTERS: ReadonlyArray<QuickDateFilter> = [\n { label: 'Today', preset: 'today' },\n { label: 'Last 7 days', preset: '7d' },\n { label: 'Last 30 days', preset: '30d' },\n { label: 'Last 90 days', preset: '90d' },\n] as const\n\n/**\n * Format a date in one of several preset styles.\n * Returns empty string for null/undefined/invalid dates.\n *\n * - 'short' (default): \"Jan 5, 2024\"\n * - 'long': \"January 5, 2024\"\n * - 'iso': \"2024-01-05\"\n * - 'relative': \"2 days ago\" / \"just now\" / \"in 3 days\"\n */\nexport function formatDate(\n date: Date | string | null | undefined,\n format: DateFormat = 'short'\n): string {\n if (date == null) return ''\n\n const d = typeof date === 'string' ? new Date(date) : date\n if (isNaN(d.getTime())) return ''\n\n switch (format) {\n case 'short':\n return new Intl.DateTimeFormat('en-US', {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n }).format(d)\n\n case 'long':\n return new Intl.DateTimeFormat('en-US', {\n month: 'long',\n day: 'numeric',\n year: 'numeric',\n }).format(d)\n\n case 'iso':\n return d.toISOString().split('T')[0]\n\n case 'relative':\n return formatRelativeTime(d)\n }\n}\n\n/**\n * Format a date relative to now.\n * Handles both past (\"2 days ago\") and future (\"in 3 days\") dates.\n */\nexport function formatRelativeTime(date: Date | string): string {\n const d = typeof date === 'string' ? new Date(date) : date\n if (isNaN(d.getTime())) return ''\n\n const now = new Date()\n const diffMs = now.getTime() - d.getTime()\n const absDiffMs = Math.abs(diffMs)\n const isPast = diffMs > 0\n\n const seconds = Math.floor(absDiffMs / 1000)\n const minutes = Math.floor(seconds / 60)\n const hours = Math.floor(minutes / 60)\n const days = Math.floor(hours / 24)\n\n if (seconds < 60) return 'just now'\n\n if (days > 0) {\n const label = days === 1 ? '1 day' : `${days} days`\n return isPast ? `${label} ago` : `in ${label}`\n }\n\n if (hours > 0) {\n const label = hours === 1 ? '1 hour' : `${hours} hours`\n return isPast ? `${label} ago` : `in ${label}`\n }\n\n const label = minutes === 1 ? '1 minute' : `${minutes} minutes`\n return isPast ? `${label} ago` : `in ${label}`\n}\n\n/**\n * Safely parse a date string. Returns null for invalid or empty input.\n */\nexport function parseDate(s: string | null | undefined): Date | null {\n if (!s || s.trim() === '') return null\n const d = new Date(s)\n return isNaN(d.getTime()) ? null : d\n}\n\n/**\n * Get a date range for a named preset.\n * \"to\" is end of today, \"from\" is start of the lookback period.\n */\nexport function getDateRange(preset: DatePreset): DateRange {\n const now = new Date()\n const to = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999)\n const from = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0)\n\n switch (preset) {\n case 'today':\n // from is already start of today\n break\n case '7d':\n from.setDate(from.getDate() - 6)\n break\n case '30d':\n from.setDate(from.getDate() - 29)\n break\n case '90d':\n from.setDate(from.getDate() - 89)\n break\n }\n\n return { from, to }\n}\n","export interface FormatNumberOptions {\n abbreviate?: boolean\n decimals?: number\n locale?: string\n}\n\nconst ABBREVIATIONS = [\n { threshold: 1_000_000_000, suffix: 'B' },\n { threshold: 1_000_000, suffix: 'M' },\n { threshold: 1_000, suffix: 'K' },\n] as const\n\n/**\n * Format a number with optional abbreviation (e.g. 1200000 -> \"1.2M\").\n */\nexport function formatNumber(\n n: number,\n opts?: FormatNumberOptions\n): string {\n const { abbreviate = false, decimals, locale = 'en-US' } = opts ?? {}\n\n if (abbreviate) {\n for (const { threshold, suffix } of ABBREVIATIONS) {\n if (Math.abs(n) >= threshold) {\n const value = n / threshold\n const d = decimals ?? 1\n const formatted = new Intl.NumberFormat(locale, {\n minimumFractionDigits: 0,\n maximumFractionDigits: d,\n }).format(value)\n return `${formatted}${suffix}`\n }\n }\n }\n\n return new Intl.NumberFormat(locale, {\n minimumFractionDigits: decimals ?? 0,\n maximumFractionDigits: decimals ?? 2,\n }).format(n)\n}\n\n/**\n * Format a decimal as a percentage string.\n * 0.1234 -> \"12.3%\", 1 -> \"100%\"\n */\nexport function formatPercent(n: number, decimals = 1): string {\n return `${(n * 100).toFixed(decimals).replace(/\\.0+$/, '')}%`\n}\n","/**\n * Get initials from a name string (up to 2 characters).\n * \"John Doe\" -> \"JD\", \"Apple Inc\" -> \"AI\", \"\" -> \"\"\n */\nexport function getInitials(name: string): string {\n if (!name || !name.trim()) return ''\n return name\n .trim()\n .split(/\\s+/)\n .map((word) => word[0])\n .join('')\n .toUpperCase()\n .slice(0, 2)\n}\n\n/**\n * Truncate a string to the specified length, appending a suffix if truncated.\n */\nexport function truncate(s: string, length: number, suffix = '...'): string {\n if (s.length <= length) return s\n return s.slice(0, length - suffix.length) + suffix\n}\n\n/**\n * Convert a string into a URL-friendly slug.\n * \"Hello World!\" -> \"hello-world\"\n */\nexport function slugify(s: string): string {\n return s\n .toLowerCase()\n .trim()\n .replace(/[^\\w\\s-]/g, '')\n .replace(/[\\s_]+/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-+|-+$/g, '')\n}\n\n/**\n * Capitalize the first character of a string.\n */\nexport function capitalize(s: string): string {\n if (!s) return s\n return s.charAt(0).toUpperCase() + s.slice(1)\n}\n"]}