@forthtilliath/ts-kit 0.3.0 → 0.4.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 +21 -0
- package/dist/array/getMostRecentIds.d.ts +21 -0
- package/dist/array/getMostRecentIds.d.ts.map +1 -0
- package/dist/array/getMostRecentIds.js +30 -0
- package/dist/array/index.d.ts +3 -0
- package/dist/array/index.d.ts.map +1 -1
- package/dist/array/index.js +3 -0
- package/dist/array/nextInCycle.d.ts +15 -0
- package/dist/array/nextInCycle.d.ts.map +1 -0
- package/dist/array/nextInCycle.js +19 -0
- package/dist/array/rankByNameMatch.d.ts +19 -0
- package/dist/array/rankByNameMatch.d.ts.map +1 -0
- package/dist/array/rankByNameMatch.js +45 -0
- package/dist/date/getPeriodStartMs.d.ts +16 -0
- package/dist/date/getPeriodStartMs.d.ts.map +1 -0
- package/dist/date/getPeriodStartMs.js +24 -0
- package/dist/date/index.d.ts +1 -0
- package/dist/date/index.d.ts.map +1 -1
- package/dist/date/index.js +1 -0
- package/dist/number/formatCsvNumber.d.ts +14 -0
- package/dist/number/formatCsvNumber.d.ts.map +1 -0
- package/dist/number/formatCsvNumber.js +15 -0
- package/dist/number/index.d.ts +1 -0
- package/dist/number/index.d.ts.map +1 -1
- package/dist/number/index.js +1 -0
- package/dist/string/escapeCsvField.d.ts +14 -0
- package/dist/string/escapeCsvField.d.ts.map +1 -0
- package/dist/string/escapeCsvField.js +17 -0
- package/dist/string/escapeHtml.d.ts +12 -0
- package/dist/string/escapeHtml.d.ts.map +1 -0
- package/dist/string/escapeHtml.js +17 -0
- package/dist/string/index.d.ts +3 -0
- package/dist/string/index.d.ts.map +1 -1
- package/dist/string/index.js +3 -0
- package/dist/string/normalizeForSearch.d.ts +12 -0
- package/dist/string/normalizeForSearch.d.ts.map +1 -0
- package/dist/string/normalizeForSearch.js +25 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,6 +37,15 @@ import { chunk } from "@forthtilliath/ts-kit/array/chunk";
|
|
|
37
37
|
|
|
38
38
|
- `chunk(array, size)` — splits an array into chunks of a given size.
|
|
39
39
|
- `flattenDeep(array)` — recursively flattens nested arrays.
|
|
40
|
+
- `getMostRecentIds(rows, limit?)` — most recently occurring distinct ids
|
|
41
|
+
(`{ id, occurredAt }[]`), most recent first, deduplicated, limited
|
|
42
|
+
(default 5).
|
|
43
|
+
- `nextInCycle(ids, currentId)` — next id in a short list (e.g. cycling
|
|
44
|
+
through recents on tap) — wraps to the first if `currentId` is at the end
|
|
45
|
+
or no longer in the list.
|
|
46
|
+
- `rankByNameMatch(items, query, getName)` — ranks `items` by relevance to
|
|
47
|
+
`query`: earlier match position first, then shorter name — for a
|
|
48
|
+
search-as-you-type list.
|
|
40
49
|
|
|
41
50
|
### `async`
|
|
42
51
|
|
|
@@ -78,6 +87,9 @@ import { chunk } from "@forthtilliath/ts-kit/array/chunk";
|
|
|
78
87
|
- `isSameDay(a, b)` — checks whether two dates fall on the same local day.
|
|
79
88
|
- `formatRelativeTime(date, baseDate?, locale?)` — human-readable relative
|
|
80
89
|
time ("3 hours ago"), backed by `Intl.RelativeTimeFormat`.
|
|
90
|
+
- `getPeriodStartMs(period, now?)` — start timestamp (ms) for a
|
|
91
|
+
`"today" | "7d" | "30d" | "all"` period filter, or `null` for `"all"`.
|
|
92
|
+
`"today"` is the current calendar day, not a rolling 24h window.
|
|
81
93
|
|
|
82
94
|
### `files`
|
|
83
95
|
|
|
@@ -102,6 +114,8 @@ import { chunk } from "@forthtilliath/ts-kit/array/chunk";
|
|
|
102
114
|
- `round(value, decimals?)` — rounds to a given number of decimal places.
|
|
103
115
|
- `formatBytes(bytes, decimals?)` — human-readable byte size ("1.5 MB").
|
|
104
116
|
- `formatDuration(ms)` — human-readable duration ("1d 1h 1m 1s").
|
|
117
|
+
- `formatCsvNumber(value, decimals?)` — formats a number with a comma
|
|
118
|
+
decimal separator (French-locale spreadsheets) instead of JS's dot.
|
|
105
119
|
|
|
106
120
|
### `object`
|
|
107
121
|
|
|
@@ -122,6 +136,13 @@ import { chunk } from "@forthtilliath/ts-kit/array/chunk";
|
|
|
122
136
|
hyphenated).
|
|
123
137
|
- `pluralize(count, singular, plural?)` — naive English pluralization, with an
|
|
124
138
|
override for irregular words.
|
|
139
|
+
- `normalizeForSearch(text)` — lowercases, trims, strips accents, and expands
|
|
140
|
+
œ/æ ligatures (which `normalize("NFD")` alone doesn't decompose) — for
|
|
141
|
+
accent/case-insensitive search matching.
|
|
142
|
+
- `escapeCsvField(value)` — quotes and escapes a CSV field (RFC 4180) only if
|
|
143
|
+
it contains a `"`, `;`, or newline.
|
|
144
|
+
- `escapeHtml(text)` — basic HTML entity escaping (`&`, `<`, `>`, `"`) for
|
|
145
|
+
inserting user text into an HTML template.
|
|
125
146
|
|
|
126
147
|
## Scripts
|
|
127
148
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface RecentIdRow {
|
|
2
|
+
id: number | null;
|
|
3
|
+
occurredAt: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Returns the most recently occurring distinct ids, most recent first —
|
|
7
|
+
* useful for a "recents" picker (a food, a container, or any other dated
|
|
8
|
+
* entity). Rows with a `null` id (a reference deleted since) are ignored:
|
|
9
|
+
* there's nothing left to offer for them.
|
|
10
|
+
*
|
|
11
|
+
* @param rows - Rows carrying an id (or `null`) and an ISO/parsable date.
|
|
12
|
+
* @param limit - Max number of ids to return. Defaults to `5`.
|
|
13
|
+
* @returns Distinct ids, most recent first, limited to `limit`.
|
|
14
|
+
* @example
|
|
15
|
+
* getMostRecentIds([
|
|
16
|
+
* { id: 1, occurredAt: "2026-01-01T00:00:00.000Z" },
|
|
17
|
+
* { id: 2, occurredAt: "2026-01-03T00:00:00.000Z" },
|
|
18
|
+
* ]); // => [2, 1]
|
|
19
|
+
*/
|
|
20
|
+
export declare function getMostRecentIds(rows: RecentIdRow[], limit?: number): number[];
|
|
21
|
+
//# sourceMappingURL=getMostRecentIds.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getMostRecentIds.d.ts","sourceRoot":"","sources":["../../src/array/getMostRecentIds.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAID;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,WAAW,EAAE,EACnB,KAAK,SAAgB,GACpB,MAAM,EAAE,CAcV"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const DEFAULT_LIMIT = 5;
|
|
2
|
+
/**
|
|
3
|
+
* Returns the most recently occurring distinct ids, most recent first —
|
|
4
|
+
* useful for a "recents" picker (a food, a container, or any other dated
|
|
5
|
+
* entity). Rows with a `null` id (a reference deleted since) are ignored:
|
|
6
|
+
* there's nothing left to offer for them.
|
|
7
|
+
*
|
|
8
|
+
* @param rows - Rows carrying an id (or `null`) and an ISO/parsable date.
|
|
9
|
+
* @param limit - Max number of ids to return. Defaults to `5`.
|
|
10
|
+
* @returns Distinct ids, most recent first, limited to `limit`.
|
|
11
|
+
* @example
|
|
12
|
+
* getMostRecentIds([
|
|
13
|
+
* { id: 1, occurredAt: "2026-01-01T00:00:00.000Z" },
|
|
14
|
+
* { id: 2, occurredAt: "2026-01-03T00:00:00.000Z" },
|
|
15
|
+
* ]); // => [2, 1]
|
|
16
|
+
*/
|
|
17
|
+
export function getMostRecentIds(rows, limit = DEFAULT_LIMIT) {
|
|
18
|
+
const sorted = [...rows].sort((a, b) => new Date(b.occurredAt).getTime() - new Date(a.occurredAt).getTime());
|
|
19
|
+
const seen = new Set();
|
|
20
|
+
const result = [];
|
|
21
|
+
for (const row of sorted) {
|
|
22
|
+
if (row.id == null || seen.has(row.id))
|
|
23
|
+
continue;
|
|
24
|
+
seen.add(row.id);
|
|
25
|
+
result.push(row.id);
|
|
26
|
+
if (result.length >= limit)
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
return result;
|
|
30
|
+
}
|
package/dist/array/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/array/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/array/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC"}
|
package/dist/array/index.js
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the next element in a short list (e.g. cycling through a few
|
|
3
|
+
* recent items on tap). Wraps to the first element if the current one is no
|
|
4
|
+
* longer in the list (deleted, or out of range) or was the last one.
|
|
5
|
+
*
|
|
6
|
+
* @param ids - The list to cycle through.
|
|
7
|
+
* @param currentId - The currently selected id, or `null` for none.
|
|
8
|
+
* @returns The next id, or `null` if `ids` is empty.
|
|
9
|
+
* @example
|
|
10
|
+
* nextInCycle([1, 2, 3], 1); // => 2
|
|
11
|
+
* nextInCycle([1, 2, 3], 3); // => 1
|
|
12
|
+
* nextInCycle([1, 2, 3], null); // => 1
|
|
13
|
+
*/
|
|
14
|
+
export declare function nextInCycle(ids: number[], currentId: number | null): number | null;
|
|
15
|
+
//# sourceMappingURL=nextInCycle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nextInCycle.d.ts","sourceRoot":"","sources":["../../src/array/nextInCycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,MAAM,EAAE,EACb,SAAS,EAAE,MAAM,GAAG,IAAI,GACvB,MAAM,GAAG,IAAI,CAIf"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the next element in a short list (e.g. cycling through a few
|
|
3
|
+
* recent items on tap). Wraps to the first element if the current one is no
|
|
4
|
+
* longer in the list (deleted, or out of range) or was the last one.
|
|
5
|
+
*
|
|
6
|
+
* @param ids - The list to cycle through.
|
|
7
|
+
* @param currentId - The currently selected id, or `null` for none.
|
|
8
|
+
* @returns The next id, or `null` if `ids` is empty.
|
|
9
|
+
* @example
|
|
10
|
+
* nextInCycle([1, 2, 3], 1); // => 2
|
|
11
|
+
* nextInCycle([1, 2, 3], 3); // => 1
|
|
12
|
+
* nextInCycle([1, 2, 3], null); // => 1
|
|
13
|
+
*/
|
|
14
|
+
export function nextInCycle(ids, currentId) {
|
|
15
|
+
if (ids.length === 0)
|
|
16
|
+
return null;
|
|
17
|
+
const currentIndex = currentId == null ? -1 : ids.indexOf(currentId);
|
|
18
|
+
return ids[(currentIndex + 1) % ids.length] ?? null;
|
|
19
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ranks `items` by relevance to a text search: match position in the name
|
|
3
|
+
* (earlier is more relevant), then name length. Generic so it can be reused
|
|
4
|
+
* for any list of items with a displayable name — typically for a
|
|
5
|
+
* search-as-you-type list.
|
|
6
|
+
*
|
|
7
|
+
* @param items - The items to rank.
|
|
8
|
+
* @param query - The search text.
|
|
9
|
+
* @param getName - Reads the displayable name off an item.
|
|
10
|
+
* @returns Only the matching items, ranked most relevant first.
|
|
11
|
+
* @example
|
|
12
|
+
* rankByNameMatch(
|
|
13
|
+
* [{ label: "Salad with potato" }, { label: "Potato" }],
|
|
14
|
+
* "potato",
|
|
15
|
+
* (item) => item.label,
|
|
16
|
+
* ); // => [{ label: "Potato" }, { label: "Salad with potato" }]
|
|
17
|
+
*/
|
|
18
|
+
export declare function rankByNameMatch<T>(items: T[], query: string, getName: (item: T) => string): T[];
|
|
19
|
+
//# sourceMappingURL=rankByNameMatch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rankByNameMatch.d.ts","sourceRoot":"","sources":["../../src/array/rankByNameMatch.ts"],"names":[],"mappings":"AAiBA;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAC/B,KAAK,EAAE,CAAC,EAAE,EACV,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,GAC3B,CAAC,EAAE,CAiBL"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { normalizeForSearch } from "../string/normalizeForSearch.js";
|
|
2
|
+
// Caches each item's normalized name by object reference: useful for fixed
|
|
3
|
+
// lists (module-level constants) searched on every keystroke in a search
|
|
4
|
+
// field, to avoid renormalizing every name on each call.
|
|
5
|
+
const normalizedNameCache = new WeakMap();
|
|
6
|
+
function getNormalizedName(item, getName) {
|
|
7
|
+
if (typeof item !== "object" || item === null)
|
|
8
|
+
return normalizeForSearch(getName(item));
|
|
9
|
+
const cached = normalizedNameCache.get(item);
|
|
10
|
+
if (cached !== undefined)
|
|
11
|
+
return cached;
|
|
12
|
+
const normalized = normalizeForSearch(getName(item));
|
|
13
|
+
normalizedNameCache.set(item, normalized);
|
|
14
|
+
return normalized;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Ranks `items` by relevance to a text search: match position in the name
|
|
18
|
+
* (earlier is more relevant), then name length. Generic so it can be reused
|
|
19
|
+
* for any list of items with a displayable name — typically for a
|
|
20
|
+
* search-as-you-type list.
|
|
21
|
+
*
|
|
22
|
+
* @param items - The items to rank.
|
|
23
|
+
* @param query - The search text.
|
|
24
|
+
* @param getName - Reads the displayable name off an item.
|
|
25
|
+
* @returns Only the matching items, ranked most relevant first.
|
|
26
|
+
* @example
|
|
27
|
+
* rankByNameMatch(
|
|
28
|
+
* [{ label: "Salad with potato" }, { label: "Potato" }],
|
|
29
|
+
* "potato",
|
|
30
|
+
* (item) => item.label,
|
|
31
|
+
* ); // => [{ label: "Potato" }, { label: "Salad with potato" }]
|
|
32
|
+
*/
|
|
33
|
+
export function rankByNameMatch(items, query, getName) {
|
|
34
|
+
const normalizedQuery = normalizeForSearch(query);
|
|
35
|
+
const matches = [];
|
|
36
|
+
for (const item of items) {
|
|
37
|
+
const matchIndex = getNormalizedName(item, getName).indexOf(normalizedQuery);
|
|
38
|
+
if (matchIndex !== -1) {
|
|
39
|
+
matches.push({ item, matchIndex });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
matches.sort((a, b) => a.matchIndex - b.matchIndex ||
|
|
43
|
+
getName(a.item).length - getName(b.item).length);
|
|
44
|
+
return matches.map((m) => m.item);
|
|
45
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type PeriodFilter = "all" | "today" | "7d" | "30d";
|
|
2
|
+
/**
|
|
3
|
+
* Returns the timestamp (ms) from which a record is included for a given
|
|
4
|
+
* period filter, or `null` for `"all"` (no date filter). `"today"` starts at
|
|
5
|
+
* the current calendar day, not a rolling 24h window.
|
|
6
|
+
*
|
|
7
|
+
* @param period - The period filter.
|
|
8
|
+
* @param now - The reference date. Defaults to `new Date()`.
|
|
9
|
+
* @returns The period's start timestamp in ms, or `null` for `"all"`.
|
|
10
|
+
* @example
|
|
11
|
+
* getPeriodStartMs("today"); // => start of today, local time, in ms
|
|
12
|
+
* getPeriodStartMs("7d"); // => now minus 7 days, in ms
|
|
13
|
+
* getPeriodStartMs("all"); // => null
|
|
14
|
+
*/
|
|
15
|
+
export declare function getPeriodStartMs(period: PeriodFilter, now?: Date): number | null;
|
|
16
|
+
//# sourceMappingURL=getPeriodStartMs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getPeriodStartMs.d.ts","sourceRoot":"","sources":["../../src/date/getPeriodStartMs.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,OAAO,GAAG,IAAI,GAAG,KAAK,CAAC;AAE1D;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,YAAY,EACpB,GAAG,OAAa,GACf,MAAM,GAAG,IAAI,CASf"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the timestamp (ms) from which a record is included for a given
|
|
3
|
+
* period filter, or `null` for `"all"` (no date filter). `"today"` starts at
|
|
4
|
+
* the current calendar day, not a rolling 24h window.
|
|
5
|
+
*
|
|
6
|
+
* @param period - The period filter.
|
|
7
|
+
* @param now - The reference date. Defaults to `new Date()`.
|
|
8
|
+
* @returns The period's start timestamp in ms, or `null` for `"all"`.
|
|
9
|
+
* @example
|
|
10
|
+
* getPeriodStartMs("today"); // => start of today, local time, in ms
|
|
11
|
+
* getPeriodStartMs("7d"); // => now minus 7 days, in ms
|
|
12
|
+
* getPeriodStartMs("all"); // => null
|
|
13
|
+
*/
|
|
14
|
+
export function getPeriodStartMs(period, now = new Date()) {
|
|
15
|
+
if (period === "all")
|
|
16
|
+
return null;
|
|
17
|
+
if (period === "today") {
|
|
18
|
+
const startOfDay = new Date(now);
|
|
19
|
+
startOfDay.setHours(0, 0, 0, 0);
|
|
20
|
+
return startOfDay.getTime();
|
|
21
|
+
}
|
|
22
|
+
const days = period === "7d" ? 7 : 30;
|
|
23
|
+
return now.getTime() - days * 24 * 60 * 60 * 1000;
|
|
24
|
+
}
|
package/dist/date/index.d.ts
CHANGED
package/dist/date/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/date/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC;AACxC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/date/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC"}
|
package/dist/date/index.js
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formats a number as a raw value (no unit suffix) for use in a spreadsheet
|
|
3
|
+
* (sum, average...), with a comma decimal separator as expected by
|
|
4
|
+
* French-locale spreadsheets instead of JS's dot.
|
|
5
|
+
*
|
|
6
|
+
* @param value - The number to format.
|
|
7
|
+
* @param decimals - Number of decimal places. Defaults to `1`.
|
|
8
|
+
* @returns The formatted number, comma as decimal separator.
|
|
9
|
+
* @example
|
|
10
|
+
* formatCsvNumber(1.5); // => "1,5"
|
|
11
|
+
* formatCsvNumber(12.345, 2); // => "12,35"
|
|
12
|
+
*/
|
|
13
|
+
export declare function formatCsvNumber(value: number, decimals?: number): string;
|
|
14
|
+
//# sourceMappingURL=formatCsvNumber.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formatCsvNumber.d.ts","sourceRoot":"","sources":["../../src/number/formatCsvNumber.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,SAAI,GAAG,MAAM,CAEnE"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formats a number as a raw value (no unit suffix) for use in a spreadsheet
|
|
3
|
+
* (sum, average...), with a comma decimal separator as expected by
|
|
4
|
+
* French-locale spreadsheets instead of JS's dot.
|
|
5
|
+
*
|
|
6
|
+
* @param value - The number to format.
|
|
7
|
+
* @param decimals - Number of decimal places. Defaults to `1`.
|
|
8
|
+
* @returns The formatted number, comma as decimal separator.
|
|
9
|
+
* @example
|
|
10
|
+
* formatCsvNumber(1.5); // => "1,5"
|
|
11
|
+
* formatCsvNumber(12.345, 2); // => "12,35"
|
|
12
|
+
*/
|
|
13
|
+
export function formatCsvNumber(value, decimals = 1) {
|
|
14
|
+
return value.toFixed(decimals).replace(".", ",");
|
|
15
|
+
}
|
package/dist/number/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/number/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/number/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,YAAY,CAAC"}
|
package/dist/number/index.js
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Escapes a CSV field per RFC 4180: wraps it in double quotes and doubles any
|
|
3
|
+
* internal double quote if the value contains a double quote, a semicolon
|
|
4
|
+
* (the delimiter), or a newline — returned as-is otherwise.
|
|
5
|
+
*
|
|
6
|
+
* @param value - The raw field value.
|
|
7
|
+
* @returns The value, quoted and escaped only if needed.
|
|
8
|
+
* @example
|
|
9
|
+
* escapeCsvField("Apple"); // => "Apple"
|
|
10
|
+
* escapeCsvField("a;b"); // => '"a;b"'
|
|
11
|
+
* escapeCsvField('He said "hi"'); // => '"He said ""hi"""'
|
|
12
|
+
*/
|
|
13
|
+
export declare function escapeCsvField(value: string): string;
|
|
14
|
+
//# sourceMappingURL=escapeCsvField.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"escapeCsvField.d.ts","sourceRoot":"","sources":["../../src/string/escapeCsvField.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGpD"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Escapes a CSV field per RFC 4180: wraps it in double quotes and doubles any
|
|
3
|
+
* internal double quote if the value contains a double quote, a semicolon
|
|
4
|
+
* (the delimiter), or a newline — returned as-is otherwise.
|
|
5
|
+
*
|
|
6
|
+
* @param value - The raw field value.
|
|
7
|
+
* @returns The value, quoted and escaped only if needed.
|
|
8
|
+
* @example
|
|
9
|
+
* escapeCsvField("Apple"); // => "Apple"
|
|
10
|
+
* escapeCsvField("a;b"); // => '"a;b"'
|
|
11
|
+
* escapeCsvField('He said "hi"'); // => '"He said ""hi"""'
|
|
12
|
+
*/
|
|
13
|
+
export function escapeCsvField(value) {
|
|
14
|
+
if (!/[";\n]/.test(value))
|
|
15
|
+
return value;
|
|
16
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
17
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Escapes the HTML entities that matter when inserting arbitrary text into
|
|
3
|
+
* an HTML template (`&`, `<`, `>`, `"`), without pulling in a templating
|
|
4
|
+
* dependency.
|
|
5
|
+
*
|
|
6
|
+
* @param text - The text to escape.
|
|
7
|
+
* @returns `text` with `&`, `<`, `>` and `"` replaced by their HTML entities.
|
|
8
|
+
* @example
|
|
9
|
+
* escapeHtml("Tom & Jerry <script>"); // => "Tom & Jerry <script>"
|
|
10
|
+
*/
|
|
11
|
+
export declare function escapeHtml(text: string): string;
|
|
12
|
+
//# sourceMappingURL=escapeHtml.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"escapeHtml.d.ts","sourceRoot":"","sources":["../../src/string/escapeHtml.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAM/C"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Escapes the HTML entities that matter when inserting arbitrary text into
|
|
3
|
+
* an HTML template (`&`, `<`, `>`, `"`), without pulling in a templating
|
|
4
|
+
* dependency.
|
|
5
|
+
*
|
|
6
|
+
* @param text - The text to escape.
|
|
7
|
+
* @returns `text` with `&`, `<`, `>` and `"` replaced by their HTML entities.
|
|
8
|
+
* @example
|
|
9
|
+
* escapeHtml("Tom & Jerry <script>"); // => "Tom & Jerry <script>"
|
|
10
|
+
*/
|
|
11
|
+
export function escapeHtml(text) {
|
|
12
|
+
return text
|
|
13
|
+
.replace(/&/g, "&")
|
|
14
|
+
.replace(/</g, "<")
|
|
15
|
+
.replace(/>/g, ">")
|
|
16
|
+
.replace(/"/g, """);
|
|
17
|
+
}
|
package/dist/string/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/string/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/string/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,yBAAyB,CAAC;AACxC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC"}
|
package/dist/string/index.js
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalizes text for accent/case-insensitive search matching: expands
|
|
3
|
+
* œ/æ ligatures, strips accents, lower-cases, and trims.
|
|
4
|
+
*
|
|
5
|
+
* @param text - The text to normalize.
|
|
6
|
+
* @returns The normalized text.
|
|
7
|
+
* @example
|
|
8
|
+
* normalizeForSearch("Pâtes À La Crème"); // => "pates a la creme"
|
|
9
|
+
* normalizeForSearch("Œuf"); // => "oeuf"
|
|
10
|
+
*/
|
|
11
|
+
export declare function normalizeForSearch(text: string): string;
|
|
12
|
+
//# sourceMappingURL=normalizeForSearch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalizeForSearch.d.ts","sourceRoot":"","sources":["../../src/string/normalizeForSearch.ts"],"names":[],"mappings":"AASA;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMvD"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// The œ/æ ligatures do NOT decompose via normalize("NFD") (unlike accented
|
|
2
|
+
// letters): they're standalone Unicode letters, not a base + combining mark.
|
|
3
|
+
// Without this explicit replacement, searching "Œuf" (the real ligature)
|
|
4
|
+
// never matches "Oeuf" in data spelled without the ligature (as most text
|
|
5
|
+
// databases are).
|
|
6
|
+
function expandLigatures(text) {
|
|
7
|
+
return text.replace(/[œŒ]/g, "oe").replace(/[æÆ]/g, "ae");
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Normalizes text for accent/case-insensitive search matching: expands
|
|
11
|
+
* œ/æ ligatures, strips accents, lower-cases, and trims.
|
|
12
|
+
*
|
|
13
|
+
* @param text - The text to normalize.
|
|
14
|
+
* @returns The normalized text.
|
|
15
|
+
* @example
|
|
16
|
+
* normalizeForSearch("Pâtes À La Crème"); // => "pates a la creme"
|
|
17
|
+
* normalizeForSearch("Œuf"); // => "oeuf"
|
|
18
|
+
*/
|
|
19
|
+
export function normalizeForSearch(text) {
|
|
20
|
+
return expandLigatures(text)
|
|
21
|
+
.normalize("NFD")
|
|
22
|
+
.replace(/[̀-ͯ]/g, "")
|
|
23
|
+
.toLowerCase()
|
|
24
|
+
.trim();
|
|
25
|
+
}
|