@sia-ui/utils 0.1.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/LICENSE +21 -0
- package/README.md +39 -0
- package/dist/async.cjs +134 -0
- package/dist/async.d.cts +81 -0
- package/dist/async.d.ts +81 -0
- package/dist/async.js +1 -0
- package/dist/chunk-63IWZEOC.js +51 -0
- package/dist/chunk-6P4YWHCJ.js +47 -0
- package/dist/chunk-BMNZVRFN.js +25 -0
- package/dist/chunk-DKWI2AZG.js +128 -0
- package/dist/chunk-DY6WSWXH.js +60 -0
- package/dist/chunk-ETJDEEF5.js +129 -0
- package/dist/chunk-FX7K6FKW.js +57 -0
- package/dist/chunk-JHYMIU2G.js +121 -0
- package/dist/chunk-L4BLBOLO.js +93 -0
- package/dist/chunk-PTNHOSWH.js +40 -0
- package/dist/chunk-QMPHN3LG.js +78 -0
- package/dist/chunk-RIFUQ7JM.js +47 -0
- package/dist/chunk-YU5NRSDP.js +11 -0
- package/dist/classname.cjs +28 -0
- package/dist/classname.d.cts +15 -0
- package/dist/classname.d.ts +15 -0
- package/dist/classname.js +1 -0
- package/dist/clipboard.cjs +51 -0
- package/dist/clipboard.d.cts +16 -0
- package/dist/clipboard.d.ts +16 -0
- package/dist/clipboard.js +1 -0
- package/dist/collection.cjs +109 -0
- package/dist/collection.d.cts +44 -0
- package/dist/collection.d.ts +44 -0
- package/dist/collection.js +1 -0
- package/dist/currency.cjs +64 -0
- package/dist/currency.d.cts +16 -0
- package/dist/currency.d.ts +16 -0
- package/dist/currency.js +1 -0
- package/dist/date.cjs +140 -0
- package/dist/date.d.cts +32 -0
- package/dist/date.d.ts +32 -0
- package/dist/date.js +1 -0
- package/dist/events.cjs +13 -0
- package/dist/events.d.cts +10 -0
- package/dist/events.d.ts +10 -0
- package/dist/events.js +1 -0
- package/dist/file.cjs +141 -0
- package/dist/file.d.cts +58 -0
- package/dist/file.d.ts +58 -0
- package/dist/file.js +1 -0
- package/dist/index.cjs +977 -0
- package/dist/index.d.cts +13 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +13 -0
- package/dist/locale.cjs +58 -0
- package/dist/locale.d.cts +51 -0
- package/dist/locale.d.ts +51 -0
- package/dist/locale.js +1 -0
- package/dist/number.cjs +61 -0
- package/dist/number.d.cts +14 -0
- package/dist/number.d.ts +14 -0
- package/dist/number.js +1 -0
- package/dist/object.cjs +87 -0
- package/dist/object.d.cts +48 -0
- package/dist/object.d.ts +48 -0
- package/dist/object.js +1 -0
- package/dist/query.cjs +65 -0
- package/dist/query.d.cts +48 -0
- package/dist/query.d.ts +48 -0
- package/dist/query.js +1 -0
- package/dist/string.cjs +50 -0
- package/dist/string.d.cts +15 -0
- package/dist/string.d.ts +15 -0
- package/dist/string.js +1 -0
- package/dist/validation.cjs +123 -0
- package/dist/validation.d.cts +77 -0
- package/dist/validation.d.ts +77 -0
- package/dist/validation.js +102 -0
- package/package.json +127 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// src/file.ts
|
|
2
|
+
var UNITS = ["o", "ko", "Mo", "Go", "To"];
|
|
3
|
+
function formatBytes(bytes, options = {}) {
|
|
4
|
+
const { decimals = 1, locale = "fr-FR" } = options;
|
|
5
|
+
if (!Number.isFinite(bytes) || bytes <= 0) return "0 o";
|
|
6
|
+
const exponent = Math.min(
|
|
7
|
+
Math.floor(Math.log(bytes) / Math.log(1024)),
|
|
8
|
+
UNITS.length - 1
|
|
9
|
+
);
|
|
10
|
+
const value = bytes / 1024 ** exponent;
|
|
11
|
+
return `${value.toLocaleString(locale, {
|
|
12
|
+
// Les octets n'ont pas de décimale : « 1 024,0 o » n'a pas de sens.
|
|
13
|
+
maximumFractionDigits: exponent === 0 ? 0 : decimals
|
|
14
|
+
})} ${UNITS[exponent]}`;
|
|
15
|
+
}
|
|
16
|
+
function extension(name) {
|
|
17
|
+
const index = name.lastIndexOf(".");
|
|
18
|
+
if (index <= 0 || index === name.length - 1) return "";
|
|
19
|
+
return name.slice(index + 1).toLowerCase();
|
|
20
|
+
}
|
|
21
|
+
function baseName(name) {
|
|
22
|
+
const index = name.lastIndexOf(".");
|
|
23
|
+
return index <= 0 ? name : name.slice(0, index);
|
|
24
|
+
}
|
|
25
|
+
var BY_EXTENSION = {
|
|
26
|
+
jpg: "image",
|
|
27
|
+
jpeg: "image",
|
|
28
|
+
png: "image",
|
|
29
|
+
gif: "image",
|
|
30
|
+
webp: "image",
|
|
31
|
+
avif: "image",
|
|
32
|
+
svg: "image",
|
|
33
|
+
bmp: "image",
|
|
34
|
+
heic: "image",
|
|
35
|
+
mp4: "video",
|
|
36
|
+
webm: "video",
|
|
37
|
+
mov: "video",
|
|
38
|
+
avi: "video",
|
|
39
|
+
mkv: "video",
|
|
40
|
+
mp3: "audio",
|
|
41
|
+
wav: "audio",
|
|
42
|
+
ogg: "audio",
|
|
43
|
+
m4a: "audio",
|
|
44
|
+
flac: "audio",
|
|
45
|
+
pdf: "pdf",
|
|
46
|
+
zip: "archive",
|
|
47
|
+
rar: "archive",
|
|
48
|
+
"7z": "archive",
|
|
49
|
+
tar: "archive",
|
|
50
|
+
gz: "archive",
|
|
51
|
+
xls: "spreadsheet",
|
|
52
|
+
xlsx: "spreadsheet",
|
|
53
|
+
csv: "spreadsheet",
|
|
54
|
+
ods: "spreadsheet",
|
|
55
|
+
doc: "document",
|
|
56
|
+
docx: "document",
|
|
57
|
+
odt: "document",
|
|
58
|
+
txt: "document",
|
|
59
|
+
rtf: "document",
|
|
60
|
+
md: "document"
|
|
61
|
+
};
|
|
62
|
+
function fileKind(file) {
|
|
63
|
+
if (typeof file !== "string" && file.type) {
|
|
64
|
+
const [group] = file.type.split("/");
|
|
65
|
+
if (group === "image") return "image";
|
|
66
|
+
if (group === "video") return "video";
|
|
67
|
+
if (group === "audio") return "audio";
|
|
68
|
+
if (file.type === "application/pdf") return "pdf";
|
|
69
|
+
}
|
|
70
|
+
const name = typeof file === "string" ? file : file.name;
|
|
71
|
+
return BY_EXTENSION[extension(name)] ?? "other";
|
|
72
|
+
}
|
|
73
|
+
function isImage(file) {
|
|
74
|
+
return fileKind(file) === "image";
|
|
75
|
+
}
|
|
76
|
+
function exceedsSize(file, maxMegabytes) {
|
|
77
|
+
return file.size > maxMegabytes * 1024 * 1024;
|
|
78
|
+
}
|
|
79
|
+
function matchesAccept(file, accept) {
|
|
80
|
+
const rules = accept.split(",").map((rule) => rule.trim().toLowerCase()).filter(Boolean);
|
|
81
|
+
if (rules.length === 0) return true;
|
|
82
|
+
const type = file.type.toLowerCase();
|
|
83
|
+
const ext = `.${extension(file.name)}`;
|
|
84
|
+
return rules.some((rule) => {
|
|
85
|
+
if (rule.startsWith(".")) return rule === ext;
|
|
86
|
+
if (rule.endsWith("/*")) return type.startsWith(rule.slice(0, -1));
|
|
87
|
+
return rule === type;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function requireBrowser(what) {
|
|
91
|
+
if (typeof window === "undefined") {
|
|
92
|
+
throw new Error(`${what} n'est disponible que dans un navigateur.`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function readAsDataUrl(file) {
|
|
96
|
+
requireBrowser("readAsDataUrl");
|
|
97
|
+
return new Promise((resolve, reject) => {
|
|
98
|
+
const reader = new FileReader();
|
|
99
|
+
reader.onload = () => resolve(String(reader.result));
|
|
100
|
+
reader.onerror = () => reject(reader.error ?? new Error("Lecture \xE9chou\xE9e"));
|
|
101
|
+
reader.readAsDataURL(file);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
function readAsText(file, encoding = "utf-8") {
|
|
105
|
+
requireBrowser("readAsText");
|
|
106
|
+
return new Promise((resolve, reject) => {
|
|
107
|
+
const reader = new FileReader();
|
|
108
|
+
reader.onload = () => resolve(String(reader.result));
|
|
109
|
+
reader.onerror = () => reject(reader.error ?? new Error("Lecture \xE9chou\xE9e"));
|
|
110
|
+
reader.readAsText(file, encoding);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
function downloadBlob(blob, filename) {
|
|
114
|
+
requireBrowser("downloadBlob");
|
|
115
|
+
const url = URL.createObjectURL(blob);
|
|
116
|
+
const link = document.createElement("a");
|
|
117
|
+
link.href = url;
|
|
118
|
+
link.download = filename;
|
|
119
|
+
document.body.appendChild(link);
|
|
120
|
+
link.click();
|
|
121
|
+
link.remove();
|
|
122
|
+
URL.revokeObjectURL(url);
|
|
123
|
+
}
|
|
124
|
+
function downloadText(content, filename, type = "text/plain;charset=utf-8") {
|
|
125
|
+
const prefix = type.startsWith("text/csv") ? "\uFEFF" : "";
|
|
126
|
+
downloadBlob(new Blob([prefix + content], { type }), filename);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export { baseName, downloadBlob, downloadText, exceedsSize, extension, fileKind, formatBytes, isImage, matchesAccept, readAsDataUrl, readAsText };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// src/currency.ts
|
|
2
|
+
function getNumberFormat(locale, options) {
|
|
3
|
+
return new Intl.NumberFormat(locale, options);
|
|
4
|
+
}
|
|
5
|
+
function normalizeNumberInput(input, decimalSeparator) {
|
|
6
|
+
const trimmed = input.trim();
|
|
7
|
+
const sanitized = trimmed.replace(/[^\d,.-]/g, "");
|
|
8
|
+
if (!sanitized) return "";
|
|
9
|
+
if (decimalSeparator) {
|
|
10
|
+
const normalized = decimalSeparator === "," ? sanitized.replace(/\./g, "").replace(",", ".") : sanitized.replace(/,/g, "");
|
|
11
|
+
return normalized;
|
|
12
|
+
}
|
|
13
|
+
const lastComma = sanitized.lastIndexOf(",");
|
|
14
|
+
const lastDot = sanitized.lastIndexOf(".");
|
|
15
|
+
if (lastComma > lastDot) {
|
|
16
|
+
return sanitized.replace(/\./g, "").replace(",", ".");
|
|
17
|
+
}
|
|
18
|
+
return sanitized.replace(/,/g, "");
|
|
19
|
+
}
|
|
20
|
+
function formatCurrency(value, options = {}) {
|
|
21
|
+
const { locale = "fr-FR", currency = "XAF", ...formatOptions } = options;
|
|
22
|
+
return getNumberFormat(locale, {
|
|
23
|
+
style: "currency",
|
|
24
|
+
currency,
|
|
25
|
+
maximumFractionDigits: currency === "XAF" || currency === "XOF" ? 0 : 2,
|
|
26
|
+
...formatOptions
|
|
27
|
+
}).format(value);
|
|
28
|
+
}
|
|
29
|
+
function formatAmount(value, options = {}) {
|
|
30
|
+
const { locale = "fr-FR", ...formatOptions } = options;
|
|
31
|
+
return getNumberFormat(locale, {
|
|
32
|
+
maximumFractionDigits: 2,
|
|
33
|
+
...formatOptions
|
|
34
|
+
}).format(value);
|
|
35
|
+
}
|
|
36
|
+
function parseCurrency(input, options = {}) {
|
|
37
|
+
const normalized = normalizeNumberInput(input, options.decimalSeparator);
|
|
38
|
+
if (!normalized) return null;
|
|
39
|
+
const value = Number(normalized);
|
|
40
|
+
return Number.isFinite(value) ? value : null;
|
|
41
|
+
}
|
|
42
|
+
function minorToMajor(amount, digits = 2) {
|
|
43
|
+
return amount / 10 ** digits;
|
|
44
|
+
}
|
|
45
|
+
function majorToMinor(amount, digits = 2) {
|
|
46
|
+
return Math.round(amount * 10 ** digits);
|
|
47
|
+
}
|
|
48
|
+
function getCurrencySymbol(currency, locale = "fr-FR") {
|
|
49
|
+
const parts = getNumberFormat(locale, {
|
|
50
|
+
style: "currency",
|
|
51
|
+
currency,
|
|
52
|
+
currencyDisplay: "narrowSymbol"
|
|
53
|
+
}).formatToParts(1);
|
|
54
|
+
return parts.find((part) => part.type === "currency")?.value ?? currency;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export { formatAmount, formatCurrency, getCurrencySymbol, majorToMinor, minorToMajor, parseCurrency };
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// src/date.ts
|
|
2
|
+
function toDate(value) {
|
|
3
|
+
const date = value instanceof Date ? new Date(value.getTime()) : new Date(value);
|
|
4
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
5
|
+
}
|
|
6
|
+
function shiftDate(value, mutate) {
|
|
7
|
+
const date = toDate(value);
|
|
8
|
+
if (!date) return null;
|
|
9
|
+
const next = new Date(date.getTime());
|
|
10
|
+
mutate(next);
|
|
11
|
+
return next;
|
|
12
|
+
}
|
|
13
|
+
function formatWith(locale, value, options) {
|
|
14
|
+
return new Intl.DateTimeFormat(locale, options).format(value);
|
|
15
|
+
}
|
|
16
|
+
function parseDate(value) {
|
|
17
|
+
return /* @__PURE__ */ new Date(`${value}T00:00:00.000Z`);
|
|
18
|
+
}
|
|
19
|
+
function diffInDays(startDate, endDate) {
|
|
20
|
+
const start = parseDate(startDate);
|
|
21
|
+
const end = parseDate(endDate);
|
|
22
|
+
const diffInMilliseconds = end.getTime() - start.getTime();
|
|
23
|
+
return Math.trunc(diffInMilliseconds / (1e3 * 60 * 60 * 24));
|
|
24
|
+
}
|
|
25
|
+
function formatDate(value, options = {}) {
|
|
26
|
+
const date = toDate(value);
|
|
27
|
+
if (!date) return "";
|
|
28
|
+
const { locale = "fr-FR", ...formatOptions } = options;
|
|
29
|
+
return formatWith(locale, date, { dateStyle: "medium", ...formatOptions });
|
|
30
|
+
}
|
|
31
|
+
function formatDateTime(value, options = {}) {
|
|
32
|
+
const date = toDate(value);
|
|
33
|
+
if (!date) return "";
|
|
34
|
+
const { locale = "fr-FR", ...formatOptions } = options;
|
|
35
|
+
return formatWith(locale, date, { dateStyle: "medium", timeStyle: "short", ...formatOptions });
|
|
36
|
+
}
|
|
37
|
+
function formatRelativeDate(value, options = {}) {
|
|
38
|
+
const date = toDate(value);
|
|
39
|
+
const now = toDate(options.now ?? /* @__PURE__ */ new Date());
|
|
40
|
+
if (!date || !now) return "";
|
|
41
|
+
const diffMs = date.getTime() - now.getTime();
|
|
42
|
+
const units = [
|
|
43
|
+
["year", 1e3 * 60 * 60 * 24 * 365],
|
|
44
|
+
["month", 1e3 * 60 * 60 * 24 * 30],
|
|
45
|
+
["day", 1e3 * 60 * 60 * 24],
|
|
46
|
+
["hour", 1e3 * 60 * 60],
|
|
47
|
+
["minute", 1e3 * 60],
|
|
48
|
+
["second", 1e3]
|
|
49
|
+
];
|
|
50
|
+
for (const [unit, size] of units) {
|
|
51
|
+
if (Math.abs(diffMs) >= size || unit === "second") {
|
|
52
|
+
const valueForUnit = Math.round(diffMs / size);
|
|
53
|
+
return new Intl.RelativeTimeFormat(options.locale ?? "fr-FR", { numeric: "auto" }).format(valueForUnit, unit);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return "";
|
|
57
|
+
}
|
|
58
|
+
function formatTimeAgo(value, options = {}) {
|
|
59
|
+
const relative = formatRelativeDate(value, options);
|
|
60
|
+
if (!relative) return "";
|
|
61
|
+
const prefix = options.prefix?.trim();
|
|
62
|
+
return prefix ? `${prefix} ${relative}` : relative;
|
|
63
|
+
}
|
|
64
|
+
function formatDateRange(start, end, options = {}) {
|
|
65
|
+
const startDate = toDate(start);
|
|
66
|
+
const endDate = toDate(end);
|
|
67
|
+
if (!startDate || !endDate) return "";
|
|
68
|
+
const { locale = "fr-FR", ...formatOptions } = options;
|
|
69
|
+
const formatter = new Intl.DateTimeFormat(locale, { dateStyle: "medium", ...formatOptions });
|
|
70
|
+
if (typeof formatter.formatRange === "function") {
|
|
71
|
+
return formatter.formatRange(startDate, endDate);
|
|
72
|
+
}
|
|
73
|
+
return `${formatter.format(startDate)} - ${formatter.format(endDate)}`;
|
|
74
|
+
}
|
|
75
|
+
function isPastDate(value, now = /* @__PURE__ */ new Date()) {
|
|
76
|
+
const date = toDate(value);
|
|
77
|
+
const reference = toDate(now);
|
|
78
|
+
if (!date || !reference) return false;
|
|
79
|
+
return date.getTime() < reference.getTime();
|
|
80
|
+
}
|
|
81
|
+
function toIsoDate(value) {
|
|
82
|
+
const date = toDate(value);
|
|
83
|
+
if (!date) return "";
|
|
84
|
+
const year = date.getFullYear();
|
|
85
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
86
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
87
|
+
return `${year}-${month}-${day}`;
|
|
88
|
+
}
|
|
89
|
+
function toDisplayDate(value, locale = "fr-FR") {
|
|
90
|
+
return formatDate(value, { locale });
|
|
91
|
+
}
|
|
92
|
+
function addDays(value, days) {
|
|
93
|
+
return shiftDate(value, (date) => date.setDate(date.getDate() + days));
|
|
94
|
+
}
|
|
95
|
+
function subDays(value, days) {
|
|
96
|
+
return addDays(value, -days);
|
|
97
|
+
}
|
|
98
|
+
function addMonths(value, months) {
|
|
99
|
+
return shiftDate(value, (date) => date.setMonth(date.getMonth() + months));
|
|
100
|
+
}
|
|
101
|
+
function subMonths(value, months) {
|
|
102
|
+
return addMonths(value, -months);
|
|
103
|
+
}
|
|
104
|
+
function addYears(value, years) {
|
|
105
|
+
return shiftDate(value, (date) => date.setFullYear(date.getFullYear() + years));
|
|
106
|
+
}
|
|
107
|
+
function subYears(value, years) {
|
|
108
|
+
return addYears(value, -years);
|
|
109
|
+
}
|
|
110
|
+
function getFirstDayOfMonth(value) {
|
|
111
|
+
const date = toDate(value);
|
|
112
|
+
if (!date) return null;
|
|
113
|
+
return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1));
|
|
114
|
+
}
|
|
115
|
+
function getLastDayOfMonth(value) {
|
|
116
|
+
const date = toDate(value);
|
|
117
|
+
if (!date) return null;
|
|
118
|
+
return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, 0));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export { addDays, addMonths, addYears, diffInDays, formatDate, formatDateRange, formatDateTime, formatRelativeDate, formatTimeAgo, getFirstDayOfMonth, getLastDayOfMonth, isPastDate, parseDate, subDays, subMonths, subYears, toDisplayDate, toIsoDate };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// src/collection.ts
|
|
2
|
+
function isDefined(value) {
|
|
3
|
+
return value !== null && value !== void 0;
|
|
4
|
+
}
|
|
5
|
+
function unique(values) {
|
|
6
|
+
return [...new Set(values)];
|
|
7
|
+
}
|
|
8
|
+
function uniqueBy(values, key) {
|
|
9
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10
|
+
const out = [];
|
|
11
|
+
for (const item of values) {
|
|
12
|
+
const id = key(item);
|
|
13
|
+
if (seen.has(id)) continue;
|
|
14
|
+
seen.add(id);
|
|
15
|
+
out.push(item);
|
|
16
|
+
}
|
|
17
|
+
return out;
|
|
18
|
+
}
|
|
19
|
+
function compact(values) {
|
|
20
|
+
return values.filter(isDefined);
|
|
21
|
+
}
|
|
22
|
+
function groupBy(values, key) {
|
|
23
|
+
const out = {};
|
|
24
|
+
for (const item of values) {
|
|
25
|
+
const group = key(item);
|
|
26
|
+
(out[group] ??= []).push(item);
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
function keyBy(values, key) {
|
|
31
|
+
const out = {};
|
|
32
|
+
for (const item of values) out[key(item)] = item;
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
function chunk(values, size) {
|
|
36
|
+
if (size <= 0) return [[...values]];
|
|
37
|
+
const out = [];
|
|
38
|
+
for (let index = 0; index < values.length; index += size) {
|
|
39
|
+
out.push(values.slice(index, index + size));
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
function partition(values, predicate) {
|
|
44
|
+
const yes = [];
|
|
45
|
+
const no = [];
|
|
46
|
+
values.forEach((item, index) => {
|
|
47
|
+
(predicate(item, index) ? yes : no).push(item);
|
|
48
|
+
});
|
|
49
|
+
return [yes, no];
|
|
50
|
+
}
|
|
51
|
+
function sortBy(values, key, direction = "asc", locale = "fr-FR") {
|
|
52
|
+
const sign = direction === "asc" ? 1 : -1;
|
|
53
|
+
return [...values].sort((a, b) => {
|
|
54
|
+
const left = key(a);
|
|
55
|
+
const right = key(b);
|
|
56
|
+
if (!isDefined(left)) return isDefined(right) ? 1 : 0;
|
|
57
|
+
if (!isDefined(right)) return -1;
|
|
58
|
+
if (typeof left === "string" && typeof right === "string") {
|
|
59
|
+
return left.localeCompare(right, locale) * sign;
|
|
60
|
+
}
|
|
61
|
+
const l = left instanceof Date ? left.getTime() : Number(left);
|
|
62
|
+
const r = right instanceof Date ? right.getTime() : Number(right);
|
|
63
|
+
return (l - r) * sign;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function sum(values) {
|
|
67
|
+
return values.reduce((total, value) => total + value, 0);
|
|
68
|
+
}
|
|
69
|
+
function sumBy(values, key) {
|
|
70
|
+
return values.reduce((total, item) => total + key(item), 0);
|
|
71
|
+
}
|
|
72
|
+
function averageBy(values, key) {
|
|
73
|
+
if (values.length === 0) return 0;
|
|
74
|
+
return sumBy(values, key) / values.length;
|
|
75
|
+
}
|
|
76
|
+
function range(start, end, step = 1) {
|
|
77
|
+
if (step === 0) return [];
|
|
78
|
+
const out = [];
|
|
79
|
+
if (step > 0) for (let i = start; i <= end; i += step) out.push(i);
|
|
80
|
+
else for (let i = start; i >= end; i += step) out.push(i);
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
function move(values, from, to) {
|
|
84
|
+
const out = [...values];
|
|
85
|
+
const [item] = out.splice(from, 1);
|
|
86
|
+
if (item !== void 0) out.splice(to, 0, item);
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
function toggleItem(values, item) {
|
|
90
|
+
return values.includes(item) ? values.filter((entry) => entry !== item) : [...values, item];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export { averageBy, chunk, compact, groupBy, isDefined, keyBy, move, partition, range, sortBy, sum, sumBy, toggleItem, unique, uniqueBy };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// src/string.ts
|
|
2
|
+
function removeDiacritics(value) {
|
|
3
|
+
return value.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
4
|
+
}
|
|
5
|
+
function truncate(text, maxLength) {
|
|
6
|
+
if (maxLength <= 0) return "";
|
|
7
|
+
if (text.length <= maxLength) return text;
|
|
8
|
+
if (maxLength <= 3) return ".".repeat(maxLength);
|
|
9
|
+
return `${text.slice(0, maxLength - 3)}...`;
|
|
10
|
+
}
|
|
11
|
+
function capitalize(text) {
|
|
12
|
+
if (!text) return "";
|
|
13
|
+
return text.charAt(0).toUpperCase() + text.slice(1).toLowerCase();
|
|
14
|
+
}
|
|
15
|
+
function titleCase(text) {
|
|
16
|
+
return normalizeSpaces(text).split(" ").filter(Boolean).map(capitalize).join(" ");
|
|
17
|
+
}
|
|
18
|
+
function initials(text) {
|
|
19
|
+
return normalizeSpaces(text).split(" ").filter(Boolean).slice(0, 2).map((part) => part[0]?.toUpperCase() ?? "").join("");
|
|
20
|
+
}
|
|
21
|
+
function slugify(text) {
|
|
22
|
+
return removeDiacritics(text).toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
23
|
+
}
|
|
24
|
+
function normalizeSpaces(text) {
|
|
25
|
+
return text.trim().replace(/\s+/g, " ");
|
|
26
|
+
}
|
|
27
|
+
function normalizeIdentifier(text, maxLength = 120) {
|
|
28
|
+
return removeDiacritics(text).toLowerCase().trim().replace(/[^a-z0-9_-]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").slice(0, maxLength);
|
|
29
|
+
}
|
|
30
|
+
function maskMiddle(text, options = {}) {
|
|
31
|
+
const { visibleStart = 2, visibleEnd = 2, maskChar = "*" } = options;
|
|
32
|
+
if (text.length <= visibleStart + visibleEnd) return text;
|
|
33
|
+
const maskedLength = text.length - visibleStart - visibleEnd;
|
|
34
|
+
return `${text.slice(0, visibleStart)}${maskChar.repeat(maskedLength)}${text.slice(text.length - visibleEnd)}`;
|
|
35
|
+
}
|
|
36
|
+
function isBlank(value) {
|
|
37
|
+
return !value || normalizeSpaces(value).length === 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export { capitalize, initials, isBlank, maskMiddle, normalizeIdentifier, normalizeSpaces, slugify, titleCase, truncate };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// src/object.ts
|
|
2
|
+
function isPlainObject(value) {
|
|
3
|
+
if (typeof value !== "object" || value === null) return false;
|
|
4
|
+
if (Array.isArray(value)) return false;
|
|
5
|
+
const proto = Object.getPrototypeOf(value);
|
|
6
|
+
return proto === Object.prototype || proto === null;
|
|
7
|
+
}
|
|
8
|
+
function pick(source, keys) {
|
|
9
|
+
const out = {};
|
|
10
|
+
for (const key of keys) {
|
|
11
|
+
if (key in source) out[key] = source[key];
|
|
12
|
+
}
|
|
13
|
+
return out;
|
|
14
|
+
}
|
|
15
|
+
function omit(source, keys) {
|
|
16
|
+
const out = { ...source };
|
|
17
|
+
for (const key of keys) delete out[key];
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
function isEmpty(value) {
|
|
21
|
+
if (value === null || value === void 0) return true;
|
|
22
|
+
if (typeof value === "string") return value.trim().length === 0;
|
|
23
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
24
|
+
if (value instanceof Map || value instanceof Set) return value.size === 0;
|
|
25
|
+
if (isPlainObject(value)) return Object.keys(value).length === 0;
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
function deepMerge(base, patch) {
|
|
29
|
+
const out = { ...base };
|
|
30
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
31
|
+
if (value === void 0) continue;
|
|
32
|
+
const current = out[key];
|
|
33
|
+
out[key] = isPlainObject(current) && isPlainObject(value) ? deepMerge(current, value) : value;
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
function getPath(source, path, fallback) {
|
|
38
|
+
const segments = path.split(".").filter(Boolean);
|
|
39
|
+
let current = source;
|
|
40
|
+
for (const segment of segments) {
|
|
41
|
+
if (current === null || current === void 0) return fallback;
|
|
42
|
+
if (typeof current !== "object") return fallback;
|
|
43
|
+
current = current[segment];
|
|
44
|
+
}
|
|
45
|
+
return current ?? fallback;
|
|
46
|
+
}
|
|
47
|
+
function setPath(source, path, value) {
|
|
48
|
+
const segments = path.split(".").filter(Boolean);
|
|
49
|
+
if (segments.length === 0) return source;
|
|
50
|
+
const [head, ...rest] = segments;
|
|
51
|
+
const child = source[head];
|
|
52
|
+
return {
|
|
53
|
+
...source,
|
|
54
|
+
[head]: rest.length === 0 ? value : setPath(isPlainObject(child) ? child : {}, rest.join("."), value)
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function isEqual(a, b) {
|
|
58
|
+
if (Object.is(a, b)) return true;
|
|
59
|
+
if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
|
|
60
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
61
|
+
return a.length === b.length && a.every((item, i) => isEqual(item, b[i]));
|
|
62
|
+
}
|
|
63
|
+
if (isPlainObject(a) && isPlainObject(b)) {
|
|
64
|
+
const keys = Object.keys(a);
|
|
65
|
+
if (keys.length !== Object.keys(b).length) return false;
|
|
66
|
+
return keys.every((key) => key in b && isEqual(a[key], b[key]));
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
function diff(before, after) {
|
|
71
|
+
const out = {};
|
|
72
|
+
for (const [key, value] of Object.entries(after)) {
|
|
73
|
+
if (!isEqual(before[key], value)) out[key] = value;
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export { deepMerge, diff, getPath, isEmpty, isEqual, omit, pick, setPath };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// src/clipboard.ts
|
|
2
|
+
function canCopy() {
|
|
3
|
+
return typeof navigator !== "undefined" && typeof navigator.clipboard?.writeText === "function";
|
|
4
|
+
}
|
|
5
|
+
async function copyText(text) {
|
|
6
|
+
if (typeof document === "undefined") return false;
|
|
7
|
+
if (canCopy()) {
|
|
8
|
+
try {
|
|
9
|
+
await navigator.clipboard.writeText(text);
|
|
10
|
+
return true;
|
|
11
|
+
} catch {
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return copyWithTextarea(text);
|
|
15
|
+
}
|
|
16
|
+
async function readText() {
|
|
17
|
+
if (typeof navigator === "undefined" || typeof navigator.clipboard?.readText !== "function") {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
return await navigator.clipboard.readText();
|
|
22
|
+
} catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function copyWithTextarea(text) {
|
|
27
|
+
const field = document.createElement("textarea");
|
|
28
|
+
field.value = text;
|
|
29
|
+
field.setAttribute("readonly", "");
|
|
30
|
+
field.style.position = "fixed";
|
|
31
|
+
field.style.top = "-9999px";
|
|
32
|
+
field.style.opacity = "0";
|
|
33
|
+
document.body.appendChild(field);
|
|
34
|
+
const previous = document.activeElement;
|
|
35
|
+
try {
|
|
36
|
+
field.select();
|
|
37
|
+
field.setSelectionRange(0, field.value.length);
|
|
38
|
+
return document.execCommand("copy");
|
|
39
|
+
} catch {
|
|
40
|
+
return false;
|
|
41
|
+
} finally {
|
|
42
|
+
field.remove();
|
|
43
|
+
previous?.focus?.();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export { canCopy, copyText, readText };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/classname.ts
|
|
4
|
+
function append(tokens, value) {
|
|
5
|
+
if (!value) return;
|
|
6
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
7
|
+
tokens.push(String(value));
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
if (Array.isArray(value)) {
|
|
11
|
+
for (const item of value) append(tokens, item);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
for (const [key, enabled] of Object.entries(value)) {
|
|
15
|
+
if (enabled) tokens.push(key);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function cn(...inputs) {
|
|
19
|
+
const tokens = [];
|
|
20
|
+
for (const input of inputs) append(tokens, input);
|
|
21
|
+
return tokens.join(" ");
|
|
22
|
+
}
|
|
23
|
+
function toneClass(block, tone) {
|
|
24
|
+
return `${block}--${tone}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
exports.cn = cn;
|
|
28
|
+
exports.toneClass = toneClass;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
type ClassValue = string | number | boolean | null | undefined | ClassDictionary | ClassArray;
|
|
2
|
+
type ClassDictionary = Record<string, boolean | null | undefined>;
|
|
3
|
+
type ClassArray = ClassValue[];
|
|
4
|
+
declare function cn(...inputs: ClassValue[]): string;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Le nom de classe d'un ton : `toneClass("sia-button", "danger")`.
|
|
8
|
+
*
|
|
9
|
+
* Ici et non dans `@sia-ui/tokens` : le ton est une valeur, mais un nom de
|
|
10
|
+
* classe est une convention de rendu. Une implémentation native traduira le
|
|
11
|
+
* même ton en objet de style et n'appellera jamais cette fonction.
|
|
12
|
+
*/
|
|
13
|
+
declare function toneClass(block: string, tone: string): string;
|
|
14
|
+
|
|
15
|
+
export { type ClassValue, cn, toneClass };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
type ClassValue = string | number | boolean | null | undefined | ClassDictionary | ClassArray;
|
|
2
|
+
type ClassDictionary = Record<string, boolean | null | undefined>;
|
|
3
|
+
type ClassArray = ClassValue[];
|
|
4
|
+
declare function cn(...inputs: ClassValue[]): string;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Le nom de classe d'un ton : `toneClass("sia-button", "danger")`.
|
|
8
|
+
*
|
|
9
|
+
* Ici et non dans `@sia-ui/tokens` : le ton est une valeur, mais un nom de
|
|
10
|
+
* classe est une convention de rendu. Une implémentation native traduira le
|
|
11
|
+
* même ton en objet de style et n'appellera jamais cette fonction.
|
|
12
|
+
*/
|
|
13
|
+
declare function toneClass(block: string, tone: string): string;
|
|
14
|
+
|
|
15
|
+
export { type ClassValue, cn, toneClass };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { cn, toneClass } from './chunk-BMNZVRFN.js';
|