@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
package/dist/string.cjs
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/string.ts
|
|
4
|
+
function removeDiacritics(value) {
|
|
5
|
+
return value.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
6
|
+
}
|
|
7
|
+
function truncate(text, maxLength) {
|
|
8
|
+
if (maxLength <= 0) return "";
|
|
9
|
+
if (text.length <= maxLength) return text;
|
|
10
|
+
if (maxLength <= 3) return ".".repeat(maxLength);
|
|
11
|
+
return `${text.slice(0, maxLength - 3)}...`;
|
|
12
|
+
}
|
|
13
|
+
function capitalize(text) {
|
|
14
|
+
if (!text) return "";
|
|
15
|
+
return text.charAt(0).toUpperCase() + text.slice(1).toLowerCase();
|
|
16
|
+
}
|
|
17
|
+
function titleCase(text) {
|
|
18
|
+
return normalizeSpaces(text).split(" ").filter(Boolean).map(capitalize).join(" ");
|
|
19
|
+
}
|
|
20
|
+
function initials(text) {
|
|
21
|
+
return normalizeSpaces(text).split(" ").filter(Boolean).slice(0, 2).map((part) => part[0]?.toUpperCase() ?? "").join("");
|
|
22
|
+
}
|
|
23
|
+
function slugify(text) {
|
|
24
|
+
return removeDiacritics(text).toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
25
|
+
}
|
|
26
|
+
function normalizeSpaces(text) {
|
|
27
|
+
return text.trim().replace(/\s+/g, " ");
|
|
28
|
+
}
|
|
29
|
+
function normalizeIdentifier(text, maxLength = 120) {
|
|
30
|
+
return removeDiacritics(text).toLowerCase().trim().replace(/[^a-z0-9_-]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").slice(0, maxLength);
|
|
31
|
+
}
|
|
32
|
+
function maskMiddle(text, options = {}) {
|
|
33
|
+
const { visibleStart = 2, visibleEnd = 2, maskChar = "*" } = options;
|
|
34
|
+
if (text.length <= visibleStart + visibleEnd) return text;
|
|
35
|
+
const maskedLength = text.length - visibleStart - visibleEnd;
|
|
36
|
+
return `${text.slice(0, visibleStart)}${maskChar.repeat(maskedLength)}${text.slice(text.length - visibleEnd)}`;
|
|
37
|
+
}
|
|
38
|
+
function isBlank(value) {
|
|
39
|
+
return !value || normalizeSpaces(value).length === 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
exports.capitalize = capitalize;
|
|
43
|
+
exports.initials = initials;
|
|
44
|
+
exports.isBlank = isBlank;
|
|
45
|
+
exports.maskMiddle = maskMiddle;
|
|
46
|
+
exports.normalizeIdentifier = normalizeIdentifier;
|
|
47
|
+
exports.normalizeSpaces = normalizeSpaces;
|
|
48
|
+
exports.slugify = slugify;
|
|
49
|
+
exports.titleCase = titleCase;
|
|
50
|
+
exports.truncate = truncate;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
declare function truncate(text: string, maxLength: number): string;
|
|
2
|
+
declare function capitalize(text: string): string;
|
|
3
|
+
declare function titleCase(text: string): string;
|
|
4
|
+
declare function initials(text: string): string;
|
|
5
|
+
declare function slugify(text: string): string;
|
|
6
|
+
declare function normalizeSpaces(text: string): string;
|
|
7
|
+
declare function normalizeIdentifier(text: string, maxLength?: number): string;
|
|
8
|
+
declare function maskMiddle(text: string, options?: {
|
|
9
|
+
visibleStart?: number;
|
|
10
|
+
visibleEnd?: number;
|
|
11
|
+
maskChar?: string;
|
|
12
|
+
}): string;
|
|
13
|
+
declare function isBlank(value: string | null | undefined): boolean;
|
|
14
|
+
|
|
15
|
+
export { capitalize, initials, isBlank, maskMiddle, normalizeIdentifier, normalizeSpaces, slugify, titleCase, truncate };
|
package/dist/string.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
declare function truncate(text: string, maxLength: number): string;
|
|
2
|
+
declare function capitalize(text: string): string;
|
|
3
|
+
declare function titleCase(text: string): string;
|
|
4
|
+
declare function initials(text: string): string;
|
|
5
|
+
declare function slugify(text: string): string;
|
|
6
|
+
declare function normalizeSpaces(text: string): string;
|
|
7
|
+
declare function normalizeIdentifier(text: string, maxLength?: number): string;
|
|
8
|
+
declare function maskMiddle(text: string, options?: {
|
|
9
|
+
visibleStart?: number;
|
|
10
|
+
visibleEnd?: number;
|
|
11
|
+
maskChar?: string;
|
|
12
|
+
}): string;
|
|
13
|
+
declare function isBlank(value: string | null | undefined): boolean;
|
|
14
|
+
|
|
15
|
+
export { capitalize, initials, isBlank, maskMiddle, normalizeIdentifier, normalizeSpaces, slugify, titleCase, truncate };
|
package/dist/string.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { capitalize, initials, isBlank, maskMiddle, normalizeIdentifier, normalizeSpaces, slugify, titleCase, truncate } from './chunk-PTNHOSWH.js';
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/validation.ts
|
|
4
|
+
var patterns = {
|
|
5
|
+
// Volontairement permissive : la seule validation d'adresse qui compte est
|
|
6
|
+
// le courriel de confirmation. Une expression stricte rejette des adresses
|
|
7
|
+
// valides, ce qui est pire que d'en accepter une fausse.
|
|
8
|
+
email: /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/,
|
|
9
|
+
url: /^https?:\/\/[^\s]+$/i,
|
|
10
|
+
slug: /^[a-z0-9]+(?:-[a-z0-9]+)*$/,
|
|
11
|
+
hexColor: /^#(?:[0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i,
|
|
12
|
+
digits: /^\d+$/,
|
|
13
|
+
/** Numéro international : un `+` facultatif, puis 8 à 15 chiffres. */
|
|
14
|
+
phone: /^\+?\d{8,15}$/
|
|
15
|
+
};
|
|
16
|
+
function text(value) {
|
|
17
|
+
return typeof value === "string" ? value : String(value ?? "");
|
|
18
|
+
}
|
|
19
|
+
function isEmail(value) {
|
|
20
|
+
return patterns.email.test(text(value).trim());
|
|
21
|
+
}
|
|
22
|
+
function isUrl(value) {
|
|
23
|
+
return patterns.url.test(text(value).trim());
|
|
24
|
+
}
|
|
25
|
+
function isPhone(value) {
|
|
26
|
+
return patterns.phone.test(text(value).replace(/[\s.\-()]/g, ""));
|
|
27
|
+
}
|
|
28
|
+
function isSlug(value) {
|
|
29
|
+
return patterns.slug.test(text(value));
|
|
30
|
+
}
|
|
31
|
+
function isHexColor(value) {
|
|
32
|
+
return patterns.hexColor.test(text(value).trim());
|
|
33
|
+
}
|
|
34
|
+
function isValidDate(value) {
|
|
35
|
+
if (value instanceof Date) return !Number.isNaN(value.getTime());
|
|
36
|
+
if (typeof value !== "string" && typeof value !== "number") return false;
|
|
37
|
+
return !Number.isNaN(new Date(value).getTime());
|
|
38
|
+
}
|
|
39
|
+
function required(message = "Ce champ est obligatoire") {
|
|
40
|
+
const rule = (value) => {
|
|
41
|
+
if (value === null || value === void 0) return message;
|
|
42
|
+
if (typeof value === "string" && value.trim() === "") return message;
|
|
43
|
+
if (Array.isArray(value) && value.length === 0) return message;
|
|
44
|
+
if (value === false) return message;
|
|
45
|
+
return void 0;
|
|
46
|
+
};
|
|
47
|
+
rule.judgesEmpty = true;
|
|
48
|
+
return rule;
|
|
49
|
+
}
|
|
50
|
+
function minLength(min2, message) {
|
|
51
|
+
return (value) => text(value).length < min2 ? message ?? `Au moins ${min2} caract\xE8res` : void 0;
|
|
52
|
+
}
|
|
53
|
+
function maxLength(max2, message) {
|
|
54
|
+
return (value) => text(value).length > max2 ? message ?? `Au plus ${max2} caract\xE8res` : void 0;
|
|
55
|
+
}
|
|
56
|
+
function min(bound, message) {
|
|
57
|
+
return (value) => Number(value) < bound ? message ?? `Minimum ${bound}` : void 0;
|
|
58
|
+
}
|
|
59
|
+
function max(bound, message) {
|
|
60
|
+
return (value) => Number(value) > bound ? message ?? `Maximum ${bound}` : void 0;
|
|
61
|
+
}
|
|
62
|
+
function matches(pattern, message = "Format invalide") {
|
|
63
|
+
return (value) => pattern.test(text(value)) ? void 0 : message;
|
|
64
|
+
}
|
|
65
|
+
function email(message = "Adresse e-mail invalide") {
|
|
66
|
+
return (value) => isEmail(value) ? void 0 : message;
|
|
67
|
+
}
|
|
68
|
+
function url(message = "Adresse web invalide") {
|
|
69
|
+
return (value) => isUrl(value) ? void 0 : message;
|
|
70
|
+
}
|
|
71
|
+
function phone(message = "Num\xE9ro de t\xE9l\xE9phone invalide") {
|
|
72
|
+
return (value) => isPhone(value) ? void 0 : message;
|
|
73
|
+
}
|
|
74
|
+
function oneOf(values, message = "Valeur inattendue") {
|
|
75
|
+
return (value) => values.includes(value) ? void 0 : message;
|
|
76
|
+
}
|
|
77
|
+
function sameAs(values, field, message = "Les valeurs ne correspondent pas") {
|
|
78
|
+
return (value) => value === values[field] ? void 0 : message;
|
|
79
|
+
}
|
|
80
|
+
function all(...rules) {
|
|
81
|
+
return (value) => {
|
|
82
|
+
const empty = value === null || value === void 0 || typeof value === "string" && value.trim() === "";
|
|
83
|
+
for (const rule of rules) {
|
|
84
|
+
if (empty && !rule.judgesEmpty) continue;
|
|
85
|
+
const message = rule(value);
|
|
86
|
+
if (message) return message;
|
|
87
|
+
}
|
|
88
|
+
return void 0;
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function createValidator(schema) {
|
|
92
|
+
return (values) => {
|
|
93
|
+
const errors = {};
|
|
94
|
+
for (const key of Object.keys(schema)) {
|
|
95
|
+
const rules = schema[key];
|
|
96
|
+
if (!rules || rules.length === 0) continue;
|
|
97
|
+
const message = all(...rules)(values[key]);
|
|
98
|
+
if (message) errors[key] = message;
|
|
99
|
+
}
|
|
100
|
+
return errors;
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
exports.all = all;
|
|
105
|
+
exports.createValidator = createValidator;
|
|
106
|
+
exports.email = email;
|
|
107
|
+
exports.isEmail = isEmail;
|
|
108
|
+
exports.isHexColor = isHexColor;
|
|
109
|
+
exports.isPhone = isPhone;
|
|
110
|
+
exports.isSlug = isSlug;
|
|
111
|
+
exports.isUrl = isUrl;
|
|
112
|
+
exports.isValidDate = isValidDate;
|
|
113
|
+
exports.matches = matches;
|
|
114
|
+
exports.max = max;
|
|
115
|
+
exports.maxLength = maxLength;
|
|
116
|
+
exports.min = min;
|
|
117
|
+
exports.minLength = minLength;
|
|
118
|
+
exports.oneOf = oneOf;
|
|
119
|
+
exports.patterns = patterns;
|
|
120
|
+
exports.phone = phone;
|
|
121
|
+
exports.required = required;
|
|
122
|
+
exports.sameAs = sameAs;
|
|
123
|
+
exports.url = url;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation, sans schéma.
|
|
3
|
+
*
|
|
4
|
+
* Ce n'est pas un remplaçant de Zod : pas d'inférence de types, pas de
|
|
5
|
+
* transformation, pas de validation imbriquée. C'est le nécessaire pour un
|
|
6
|
+
* formulaire d'application de gestion, sous la forme exacte que
|
|
7
|
+
* `useLocalForm` attend — une fonction qui rend des messages par champ.
|
|
8
|
+
*
|
|
9
|
+
* Un projet qui a déjà Zod branche son schéma et n'utilise rien d'ici.
|
|
10
|
+
*/
|
|
11
|
+
/** Une règle rend un message d'erreur, ou `undefined` si la valeur passe. */
|
|
12
|
+
interface Rule<T = unknown> {
|
|
13
|
+
(value: T): string | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Vrai pour la règle qui juge une valeur vide — `required`, et elle seule.
|
|
16
|
+
* Un marqueur plutôt qu'une comparaison de nom, qui disparaît à la
|
|
17
|
+
* minification.
|
|
18
|
+
*/
|
|
19
|
+
judgesEmpty?: boolean;
|
|
20
|
+
}
|
|
21
|
+
declare const patterns: {
|
|
22
|
+
readonly email: RegExp;
|
|
23
|
+
readonly url: RegExp;
|
|
24
|
+
readonly slug: RegExp;
|
|
25
|
+
readonly hexColor: RegExp;
|
|
26
|
+
readonly digits: RegExp;
|
|
27
|
+
/** Numéro international : un `+` facultatif, puis 8 à 15 chiffres. */
|
|
28
|
+
readonly phone: RegExp;
|
|
29
|
+
};
|
|
30
|
+
declare function isEmail(value: unknown): boolean;
|
|
31
|
+
declare function isUrl(value: unknown): boolean;
|
|
32
|
+
/** Accepte les espaces et les points de séparation, qu'elle retire. */
|
|
33
|
+
declare function isPhone(value: unknown): boolean;
|
|
34
|
+
declare function isSlug(value: unknown): boolean;
|
|
35
|
+
declare function isHexColor(value: unknown): boolean;
|
|
36
|
+
/** Vrai pour une date réelle. `new Date("2026-02-30")` ne l'est pas. */
|
|
37
|
+
declare function isValidDate(value: unknown): boolean;
|
|
38
|
+
declare function required(message?: string): Rule;
|
|
39
|
+
declare function minLength(min: number, message?: string): Rule;
|
|
40
|
+
declare function maxLength(max: number, message?: string): Rule;
|
|
41
|
+
declare function min(bound: number, message?: string): Rule;
|
|
42
|
+
declare function max(bound: number, message?: string): Rule;
|
|
43
|
+
declare function matches(pattern: RegExp, message?: string): Rule;
|
|
44
|
+
declare function email(message?: string): Rule;
|
|
45
|
+
declare function url(message?: string): Rule;
|
|
46
|
+
declare function phone(message?: string): Rule;
|
|
47
|
+
declare function oneOf<T>(values: readonly T[], message?: string): Rule<T>;
|
|
48
|
+
/** Compare à un autre champ — la confirmation d'un mot de passe. */
|
|
49
|
+
declare function sameAs<T extends Record<string, unknown>>(values: T, field: keyof T & string, message?: string): Rule;
|
|
50
|
+
/**
|
|
51
|
+
* Enchaîne des règles. La première qui échoue l'emporte.
|
|
52
|
+
*
|
|
53
|
+
* Un champ vide facultatif passe : seule la règle marquée `judgesEmpty` —
|
|
54
|
+
* `required` — s'intéresse au vide. Afficher « au moins huit caractères » sur
|
|
55
|
+
* un champ qu'on n'a pas encore rempli n'apprend rien à personne.
|
|
56
|
+
*/
|
|
57
|
+
declare function all<T>(...rules: Array<Rule<T>>): Rule<T>;
|
|
58
|
+
/**
|
|
59
|
+
* Assemble un validateur de formulaire.
|
|
60
|
+
*
|
|
61
|
+
* Ce que rend cette fonction se passe directement à `useLocalForm` : c'est le
|
|
62
|
+
* point de rencontre entre ce module et le contrat de formulaire, sans que
|
|
63
|
+
* l'un importe l'autre.
|
|
64
|
+
*
|
|
65
|
+
* ```ts
|
|
66
|
+
* const validate = createValidator<Valeurs>({
|
|
67
|
+
* nom: [required()],
|
|
68
|
+
* email: [required(), email()],
|
|
69
|
+
* montant: [required(), min(0)],
|
|
70
|
+
* });
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
declare function createValidator<T extends Record<string, unknown>>(schema: {
|
|
74
|
+
[K in keyof T]?: Array<Rule<T[K]>>;
|
|
75
|
+
}): (values: T) => { [K in keyof T]?: string; };
|
|
76
|
+
|
|
77
|
+
export { type Rule, all, createValidator, email, isEmail, isHexColor, isPhone, isSlug, isUrl, isValidDate, matches, max, maxLength, min, minLength, oneOf, patterns, phone, required, sameAs, url };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation, sans schéma.
|
|
3
|
+
*
|
|
4
|
+
* Ce n'est pas un remplaçant de Zod : pas d'inférence de types, pas de
|
|
5
|
+
* transformation, pas de validation imbriquée. C'est le nécessaire pour un
|
|
6
|
+
* formulaire d'application de gestion, sous la forme exacte que
|
|
7
|
+
* `useLocalForm` attend — une fonction qui rend des messages par champ.
|
|
8
|
+
*
|
|
9
|
+
* Un projet qui a déjà Zod branche son schéma et n'utilise rien d'ici.
|
|
10
|
+
*/
|
|
11
|
+
/** Une règle rend un message d'erreur, ou `undefined` si la valeur passe. */
|
|
12
|
+
interface Rule<T = unknown> {
|
|
13
|
+
(value: T): string | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Vrai pour la règle qui juge une valeur vide — `required`, et elle seule.
|
|
16
|
+
* Un marqueur plutôt qu'une comparaison de nom, qui disparaît à la
|
|
17
|
+
* minification.
|
|
18
|
+
*/
|
|
19
|
+
judgesEmpty?: boolean;
|
|
20
|
+
}
|
|
21
|
+
declare const patterns: {
|
|
22
|
+
readonly email: RegExp;
|
|
23
|
+
readonly url: RegExp;
|
|
24
|
+
readonly slug: RegExp;
|
|
25
|
+
readonly hexColor: RegExp;
|
|
26
|
+
readonly digits: RegExp;
|
|
27
|
+
/** Numéro international : un `+` facultatif, puis 8 à 15 chiffres. */
|
|
28
|
+
readonly phone: RegExp;
|
|
29
|
+
};
|
|
30
|
+
declare function isEmail(value: unknown): boolean;
|
|
31
|
+
declare function isUrl(value: unknown): boolean;
|
|
32
|
+
/** Accepte les espaces et les points de séparation, qu'elle retire. */
|
|
33
|
+
declare function isPhone(value: unknown): boolean;
|
|
34
|
+
declare function isSlug(value: unknown): boolean;
|
|
35
|
+
declare function isHexColor(value: unknown): boolean;
|
|
36
|
+
/** Vrai pour une date réelle. `new Date("2026-02-30")` ne l'est pas. */
|
|
37
|
+
declare function isValidDate(value: unknown): boolean;
|
|
38
|
+
declare function required(message?: string): Rule;
|
|
39
|
+
declare function minLength(min: number, message?: string): Rule;
|
|
40
|
+
declare function maxLength(max: number, message?: string): Rule;
|
|
41
|
+
declare function min(bound: number, message?: string): Rule;
|
|
42
|
+
declare function max(bound: number, message?: string): Rule;
|
|
43
|
+
declare function matches(pattern: RegExp, message?: string): Rule;
|
|
44
|
+
declare function email(message?: string): Rule;
|
|
45
|
+
declare function url(message?: string): Rule;
|
|
46
|
+
declare function phone(message?: string): Rule;
|
|
47
|
+
declare function oneOf<T>(values: readonly T[], message?: string): Rule<T>;
|
|
48
|
+
/** Compare à un autre champ — la confirmation d'un mot de passe. */
|
|
49
|
+
declare function sameAs<T extends Record<string, unknown>>(values: T, field: keyof T & string, message?: string): Rule;
|
|
50
|
+
/**
|
|
51
|
+
* Enchaîne des règles. La première qui échoue l'emporte.
|
|
52
|
+
*
|
|
53
|
+
* Un champ vide facultatif passe : seule la règle marquée `judgesEmpty` —
|
|
54
|
+
* `required` — s'intéresse au vide. Afficher « au moins huit caractères » sur
|
|
55
|
+
* un champ qu'on n'a pas encore rempli n'apprend rien à personne.
|
|
56
|
+
*/
|
|
57
|
+
declare function all<T>(...rules: Array<Rule<T>>): Rule<T>;
|
|
58
|
+
/**
|
|
59
|
+
* Assemble un validateur de formulaire.
|
|
60
|
+
*
|
|
61
|
+
* Ce que rend cette fonction se passe directement à `useLocalForm` : c'est le
|
|
62
|
+
* point de rencontre entre ce module et le contrat de formulaire, sans que
|
|
63
|
+
* l'un importe l'autre.
|
|
64
|
+
*
|
|
65
|
+
* ```ts
|
|
66
|
+
* const validate = createValidator<Valeurs>({
|
|
67
|
+
* nom: [required()],
|
|
68
|
+
* email: [required(), email()],
|
|
69
|
+
* montant: [required(), min(0)],
|
|
70
|
+
* });
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
declare function createValidator<T extends Record<string, unknown>>(schema: {
|
|
74
|
+
[K in keyof T]?: Array<Rule<T[K]>>;
|
|
75
|
+
}): (values: T) => { [K in keyof T]?: string; };
|
|
76
|
+
|
|
77
|
+
export { type Rule, all, createValidator, email, isEmail, isHexColor, isPhone, isSlug, isUrl, isValidDate, matches, max, maxLength, min, minLength, oneOf, patterns, phone, required, sameAs, url };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// src/validation.ts
|
|
2
|
+
var patterns = {
|
|
3
|
+
// Volontairement permissive : la seule validation d'adresse qui compte est
|
|
4
|
+
// le courriel de confirmation. Une expression stricte rejette des adresses
|
|
5
|
+
// valides, ce qui est pire que d'en accepter une fausse.
|
|
6
|
+
email: /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/,
|
|
7
|
+
url: /^https?:\/\/[^\s]+$/i,
|
|
8
|
+
slug: /^[a-z0-9]+(?:-[a-z0-9]+)*$/,
|
|
9
|
+
hexColor: /^#(?:[0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i,
|
|
10
|
+
digits: /^\d+$/,
|
|
11
|
+
/** Numéro international : un `+` facultatif, puis 8 à 15 chiffres. */
|
|
12
|
+
phone: /^\+?\d{8,15}$/
|
|
13
|
+
};
|
|
14
|
+
function text(value) {
|
|
15
|
+
return typeof value === "string" ? value : String(value ?? "");
|
|
16
|
+
}
|
|
17
|
+
function isEmail(value) {
|
|
18
|
+
return patterns.email.test(text(value).trim());
|
|
19
|
+
}
|
|
20
|
+
function isUrl(value) {
|
|
21
|
+
return patterns.url.test(text(value).trim());
|
|
22
|
+
}
|
|
23
|
+
function isPhone(value) {
|
|
24
|
+
return patterns.phone.test(text(value).replace(/[\s.\-()]/g, ""));
|
|
25
|
+
}
|
|
26
|
+
function isSlug(value) {
|
|
27
|
+
return patterns.slug.test(text(value));
|
|
28
|
+
}
|
|
29
|
+
function isHexColor(value) {
|
|
30
|
+
return patterns.hexColor.test(text(value).trim());
|
|
31
|
+
}
|
|
32
|
+
function isValidDate(value) {
|
|
33
|
+
if (value instanceof Date) return !Number.isNaN(value.getTime());
|
|
34
|
+
if (typeof value !== "string" && typeof value !== "number") return false;
|
|
35
|
+
return !Number.isNaN(new Date(value).getTime());
|
|
36
|
+
}
|
|
37
|
+
function required(message = "Ce champ est obligatoire") {
|
|
38
|
+
const rule = (value) => {
|
|
39
|
+
if (value === null || value === void 0) return message;
|
|
40
|
+
if (typeof value === "string" && value.trim() === "") return message;
|
|
41
|
+
if (Array.isArray(value) && value.length === 0) return message;
|
|
42
|
+
if (value === false) return message;
|
|
43
|
+
return void 0;
|
|
44
|
+
};
|
|
45
|
+
rule.judgesEmpty = true;
|
|
46
|
+
return rule;
|
|
47
|
+
}
|
|
48
|
+
function minLength(min2, message) {
|
|
49
|
+
return (value) => text(value).length < min2 ? message ?? `Au moins ${min2} caract\xE8res` : void 0;
|
|
50
|
+
}
|
|
51
|
+
function maxLength(max2, message) {
|
|
52
|
+
return (value) => text(value).length > max2 ? message ?? `Au plus ${max2} caract\xE8res` : void 0;
|
|
53
|
+
}
|
|
54
|
+
function min(bound, message) {
|
|
55
|
+
return (value) => Number(value) < bound ? message ?? `Minimum ${bound}` : void 0;
|
|
56
|
+
}
|
|
57
|
+
function max(bound, message) {
|
|
58
|
+
return (value) => Number(value) > bound ? message ?? `Maximum ${bound}` : void 0;
|
|
59
|
+
}
|
|
60
|
+
function matches(pattern, message = "Format invalide") {
|
|
61
|
+
return (value) => pattern.test(text(value)) ? void 0 : message;
|
|
62
|
+
}
|
|
63
|
+
function email(message = "Adresse e-mail invalide") {
|
|
64
|
+
return (value) => isEmail(value) ? void 0 : message;
|
|
65
|
+
}
|
|
66
|
+
function url(message = "Adresse web invalide") {
|
|
67
|
+
return (value) => isUrl(value) ? void 0 : message;
|
|
68
|
+
}
|
|
69
|
+
function phone(message = "Num\xE9ro de t\xE9l\xE9phone invalide") {
|
|
70
|
+
return (value) => isPhone(value) ? void 0 : message;
|
|
71
|
+
}
|
|
72
|
+
function oneOf(values, message = "Valeur inattendue") {
|
|
73
|
+
return (value) => values.includes(value) ? void 0 : message;
|
|
74
|
+
}
|
|
75
|
+
function sameAs(values, field, message = "Les valeurs ne correspondent pas") {
|
|
76
|
+
return (value) => value === values[field] ? void 0 : message;
|
|
77
|
+
}
|
|
78
|
+
function all(...rules) {
|
|
79
|
+
return (value) => {
|
|
80
|
+
const empty = value === null || value === void 0 || typeof value === "string" && value.trim() === "";
|
|
81
|
+
for (const rule of rules) {
|
|
82
|
+
if (empty && !rule.judgesEmpty) continue;
|
|
83
|
+
const message = rule(value);
|
|
84
|
+
if (message) return message;
|
|
85
|
+
}
|
|
86
|
+
return void 0;
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function createValidator(schema) {
|
|
90
|
+
return (values) => {
|
|
91
|
+
const errors = {};
|
|
92
|
+
for (const key of Object.keys(schema)) {
|
|
93
|
+
const rules = schema[key];
|
|
94
|
+
if (!rules || rules.length === 0) continue;
|
|
95
|
+
const message = all(...rules)(values[key]);
|
|
96
|
+
if (message) errors[key] = message;
|
|
97
|
+
}
|
|
98
|
+
return errors;
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export { all, createValidator, email, isEmail, isHexColor, isPhone, isSlug, isUrl, isValidDate, matches, max, maxLength, min, minLength, oneOf, patterns, phone, required, sameAs, url };
|
package/package.json
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sia-ui/utils",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Shared utility modules for SIA UI applications.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"sia-ui",
|
|
7
|
+
"utils",
|
|
8
|
+
"currency",
|
|
9
|
+
"date",
|
|
10
|
+
"xof",
|
|
11
|
+
"xaf",
|
|
12
|
+
"intl"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"author": "Ivan Mbella",
|
|
16
|
+
"homepage": "https://github.com/BeatJo/sia-ui#readme",
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/BeatJo/sia-ui/issues"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/BeatJo/sia-ui.git",
|
|
23
|
+
"directory": "packages/utils"
|
|
24
|
+
},
|
|
25
|
+
"type": "module",
|
|
26
|
+
"sideEffects": false,
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"README.md",
|
|
30
|
+
"LICENSE"
|
|
31
|
+
],
|
|
32
|
+
"main": "./dist/index.cjs",
|
|
33
|
+
"module": "./dist/index.js",
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"exports": {
|
|
36
|
+
".": {
|
|
37
|
+
"types": "./dist/index.d.ts",
|
|
38
|
+
"import": "./dist/index.js",
|
|
39
|
+
"require": "./dist/index.cjs"
|
|
40
|
+
},
|
|
41
|
+
"./async": {
|
|
42
|
+
"types": "./dist/async.d.ts",
|
|
43
|
+
"import": "./dist/async.js",
|
|
44
|
+
"require": "./dist/async.cjs"
|
|
45
|
+
},
|
|
46
|
+
"./classname": {
|
|
47
|
+
"types": "./dist/classname.d.ts",
|
|
48
|
+
"import": "./dist/classname.js",
|
|
49
|
+
"require": "./dist/classname.cjs"
|
|
50
|
+
},
|
|
51
|
+
"./clipboard": {
|
|
52
|
+
"types": "./dist/clipboard.d.ts",
|
|
53
|
+
"import": "./dist/clipboard.js",
|
|
54
|
+
"require": "./dist/clipboard.cjs"
|
|
55
|
+
},
|
|
56
|
+
"./collection": {
|
|
57
|
+
"types": "./dist/collection.d.ts",
|
|
58
|
+
"import": "./dist/collection.js",
|
|
59
|
+
"require": "./dist/collection.cjs"
|
|
60
|
+
},
|
|
61
|
+
"./currency": {
|
|
62
|
+
"types": "./dist/currency.d.ts",
|
|
63
|
+
"import": "./dist/currency.js",
|
|
64
|
+
"require": "./dist/currency.cjs"
|
|
65
|
+
},
|
|
66
|
+
"./date": {
|
|
67
|
+
"types": "./dist/date.d.ts",
|
|
68
|
+
"import": "./dist/date.js",
|
|
69
|
+
"require": "./dist/date.cjs"
|
|
70
|
+
},
|
|
71
|
+
"./events": {
|
|
72
|
+
"types": "./dist/events.d.ts",
|
|
73
|
+
"import": "./dist/events.js",
|
|
74
|
+
"require": "./dist/events.cjs"
|
|
75
|
+
},
|
|
76
|
+
"./file": {
|
|
77
|
+
"types": "./dist/file.d.ts",
|
|
78
|
+
"import": "./dist/file.js",
|
|
79
|
+
"require": "./dist/file.cjs"
|
|
80
|
+
},
|
|
81
|
+
"./locale": {
|
|
82
|
+
"types": "./dist/locale.d.ts",
|
|
83
|
+
"import": "./dist/locale.js",
|
|
84
|
+
"require": "./dist/locale.cjs"
|
|
85
|
+
},
|
|
86
|
+
"./number": {
|
|
87
|
+
"types": "./dist/number.d.ts",
|
|
88
|
+
"import": "./dist/number.js",
|
|
89
|
+
"require": "./dist/number.cjs"
|
|
90
|
+
},
|
|
91
|
+
"./object": {
|
|
92
|
+
"types": "./dist/object.d.ts",
|
|
93
|
+
"import": "./dist/object.js",
|
|
94
|
+
"require": "./dist/object.cjs"
|
|
95
|
+
},
|
|
96
|
+
"./query": {
|
|
97
|
+
"types": "./dist/query.d.ts",
|
|
98
|
+
"import": "./dist/query.js",
|
|
99
|
+
"require": "./dist/query.cjs"
|
|
100
|
+
},
|
|
101
|
+
"./string": {
|
|
102
|
+
"types": "./dist/string.d.ts",
|
|
103
|
+
"import": "./dist/string.js",
|
|
104
|
+
"require": "./dist/string.cjs"
|
|
105
|
+
},
|
|
106
|
+
"./validation": {
|
|
107
|
+
"types": "./dist/validation.d.ts",
|
|
108
|
+
"import": "./dist/validation.js",
|
|
109
|
+
"require": "./dist/validation.cjs"
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
"publishConfig": {
|
|
113
|
+
"access": "public"
|
|
114
|
+
},
|
|
115
|
+
"devDependencies": {
|
|
116
|
+
"tsup": "^8.5.1",
|
|
117
|
+
"typescript": "^5.9.3",
|
|
118
|
+
"vitest": "^4.0.16"
|
|
119
|
+
},
|
|
120
|
+
"scripts": {
|
|
121
|
+
"build": "tsup",
|
|
122
|
+
"typecheck": "tsc --noEmit",
|
|
123
|
+
"test": "vitest run",
|
|
124
|
+
"lint": "eslint src",
|
|
125
|
+
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\""
|
|
126
|
+
}
|
|
127
|
+
}
|