@rainersoft/utils 1.2.0 → 1.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.
@@ -0,0 +1,12 @@
1
+ interface SearchOptions {
2
+ fields?: string[];
3
+ caseSensitive?: boolean;
4
+ exactMatch?: boolean;
5
+ }
6
+ declare function searchContent<T extends Record<string, any>>(query: string, content: T[], options?: SearchOptions): T[];
7
+ declare function searchWithScore<T extends Record<string, any>>(query: string, content: T[], options?: SearchOptions): T[];
8
+ declare function fuzzySearch<T extends Record<string, any>>(query: string, content: T[], options?: SearchOptions & {
9
+ threshold?: number;
10
+ }): T[];
11
+
12
+ export { type SearchOptions, fuzzySearch, searchContent, searchWithScore };
@@ -0,0 +1,12 @@
1
+ interface SearchOptions {
2
+ fields?: string[];
3
+ caseSensitive?: boolean;
4
+ exactMatch?: boolean;
5
+ }
6
+ declare function searchContent<T extends Record<string, any>>(query: string, content: T[], options?: SearchOptions): T[];
7
+ declare function searchWithScore<T extends Record<string, any>>(query: string, content: T[], options?: SearchOptions): T[];
8
+ declare function fuzzySearch<T extends Record<string, any>>(query: string, content: T[], options?: SearchOptions & {
9
+ threshold?: number;
10
+ }): T[];
11
+
12
+ export { type SearchOptions, fuzzySearch, searchContent, searchWithScore };
@@ -0,0 +1,92 @@
1
+ 'use strict';
2
+
3
+ // src/search/index.ts
4
+ function searchContent(query, content, options = {}) {
5
+ if (!query.trim()) return content;
6
+ const {
7
+ fields = ["title", "description", "content", "tags"],
8
+ caseSensitive = false,
9
+ exactMatch = false
10
+ } = options;
11
+ const searchQuery = caseSensitive ? query : query.toLowerCase();
12
+ return content.filter((item) => {
13
+ return fields.some((field) => {
14
+ const value = item[field];
15
+ if (!value) return false;
16
+ if (Array.isArray(value)) {
17
+ return value.some((v) => {
18
+ const strValue2 = caseSensitive ? String(v) : String(v).toLowerCase();
19
+ return exactMatch ? strValue2 === searchQuery : strValue2.includes(searchQuery);
20
+ });
21
+ }
22
+ const strValue = caseSensitive ? String(value) : String(value).toLowerCase();
23
+ return exactMatch ? strValue === searchQuery : strValue.includes(searchQuery);
24
+ });
25
+ });
26
+ }
27
+ function searchWithScore(query, content, options = {}) {
28
+ if (!query.trim()) return content;
29
+ const {
30
+ fields = ["title", "description", "content", "tags"],
31
+ caseSensitive = false
32
+ } = options;
33
+ const searchQuery = caseSensitive ? query : query.toLowerCase();
34
+ const scored = content.map((item) => {
35
+ let score = 0;
36
+ fields.forEach((field, index) => {
37
+ const value = item[field];
38
+ if (!value) return;
39
+ const weight = fields.length - index;
40
+ if (Array.isArray(value)) {
41
+ const matches = value.filter((v) => {
42
+ const strValue = caseSensitive ? String(v) : String(v).toLowerCase();
43
+ return strValue.includes(searchQuery);
44
+ }).length;
45
+ score += matches * weight;
46
+ } else {
47
+ const strValue = caseSensitive ? String(value) : String(value).toLowerCase();
48
+ if (strValue.includes(searchQuery)) {
49
+ score += weight;
50
+ if (strValue === searchQuery) {
51
+ score += weight * 2;
52
+ }
53
+ }
54
+ }
55
+ });
56
+ return { item, score };
57
+ });
58
+ return scored.filter(({ score }) => score > 0).sort((a, b) => b.score - a.score).map(({ item }) => item);
59
+ }
60
+ function fuzzySearch(query, content, options = {}) {
61
+ if (!query.trim()) return content;
62
+ const {
63
+ fields = ["title", "description"],
64
+ caseSensitive = false,
65
+ threshold = 0.6
66
+ // Similaridade mínima (0-1)
67
+ } = options;
68
+ const searchQuery = caseSensitive ? query : query.toLowerCase();
69
+ return content.filter((item) => {
70
+ return fields.some((field) => {
71
+ const value = item[field];
72
+ if (!value) return false;
73
+ const strValue = caseSensitive ? String(value) : String(value).toLowerCase();
74
+ const similarity = calculateSimilarity(searchQuery, strValue);
75
+ return similarity >= threshold;
76
+ });
77
+ });
78
+ }
79
+ function calculateSimilarity(str1, str2) {
80
+ if (str1 === str2) return 1;
81
+ if (str1.length === 0 || str2.length === 0) return 0;
82
+ if (str2.includes(str1)) return 0.8;
83
+ const common = str1.split("").filter((char) => str2.includes(char)).length;
84
+ const similarity = common / Math.max(str1.length, str2.length);
85
+ return similarity;
86
+ }
87
+
88
+ exports.fuzzySearch = fuzzySearch;
89
+ exports.searchContent = searchContent;
90
+ exports.searchWithScore = searchWithScore;
91
+ //# sourceMappingURL=index.js.map
92
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/search/index.ts"],"names":["strValue"],"mappings":"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","file":"index.js","sourcesContent":["/**\r\n * Search Utilities\r\n *\r\n * Utilitários genéricos para busca e filtro de conteúdo.\r\n * Funções puras, agnósticas de domínio e framework.\r\n *\r\n * @module @rainersoft/utils/search\r\n * @author Rainer Teixeira\r\n * @version 1.0.0\r\n */\r\n\r\n/**\r\n * Opções de busca\r\n */\r\nexport interface SearchOptions {\r\n /** Campos a buscar (padrão: ['title', 'description', 'content', 'tags']) */\r\n fields?: string[];\r\n /** Case sensitive (padrão: false) */\r\n caseSensitive?: boolean;\r\n /** Busca exata (padrão: false) */\r\n exactMatch?: boolean;\r\n}\r\n\r\n/**\r\n * Busca genérica em array de objetos\r\n *\r\n * Função de busca client-side para arrays de objetos genéricos.\r\n * Busca em múltiplos campos configuráveis.\r\n *\r\n * @param query - Termo de busca\r\n * @param content - Array de objetos a buscar\r\n * @param options - Opções de busca\r\n * @returns Array filtrado com resultados\r\n *\r\n * @example\r\n * ```ts\r\n * const posts = [\r\n * { title: 'Next.js Guide', description: 'Learn Next.js' },\r\n * { title: 'React Basics', description: 'React fundamentals' }\r\n * ];\r\n * \r\n * searchContent('next', posts) // [{ title: 'Next.js Guide', ... }]\r\n * searchContent('react', posts, { fields: ['title'] }) // [{ title: 'React Basics', ... }]\r\n * ```\r\n */\r\nexport function searchContent<T extends Record<string, any>>(\r\n query: string,\r\n content: T[],\r\n options: SearchOptions = {}\r\n): T[] {\r\n if (!query.trim()) return content;\r\n \r\n const {\r\n fields = ['title', 'description', 'content', 'tags'],\r\n caseSensitive = false,\r\n exactMatch = false\r\n } = options;\r\n \r\n const searchQuery = caseSensitive ? query : query.toLowerCase();\r\n \r\n return content.filter(item => {\r\n return fields.some(field => {\r\n const value = item[field];\r\n \r\n if (!value) return false;\r\n \r\n // Se for array (ex: tags)\r\n if (Array.isArray(value)) {\r\n return value.some(v => {\r\n const strValue = caseSensitive ? String(v) : String(v).toLowerCase();\r\n return exactMatch \r\n ? strValue === searchQuery \r\n : strValue.includes(searchQuery);\r\n });\r\n }\r\n \r\n // Se for string\r\n const strValue = caseSensitive ? String(value) : String(value).toLowerCase();\r\n return exactMatch \r\n ? strValue === searchQuery \r\n : strValue.includes(searchQuery);\r\n });\r\n });\r\n}\r\n\r\n/**\r\n * Busca com score de relevância\r\n *\r\n * Retorna resultados ordenados por relevância baseado em:\r\n * - Correspondência no título (peso maior)\r\n * - Correspondência na descrição\r\n * - Correspondência em outros campos\r\n *\r\n * @param query - Termo de busca\r\n * @param content - Array de objetos a buscar\r\n * @param options - Opções de busca\r\n * @returns Array ordenado por relevância\r\n *\r\n * @example\r\n * ```ts\r\n * const results = searchWithScore('next', posts);\r\n * // Resultados com título contendo 'next' aparecem primeiro\r\n * ```\r\n */\r\nexport function searchWithScore<T extends Record<string, any>>(\r\n query: string,\r\n content: T[],\r\n options: SearchOptions = {}\r\n): T[] {\r\n if (!query.trim()) return content;\r\n \r\n const {\r\n fields = ['title', 'description', 'content', 'tags'],\r\n caseSensitive = false\r\n } = options;\r\n \r\n const searchQuery = caseSensitive ? query : query.toLowerCase();\r\n \r\n // Calcula score para cada item\r\n const scored = content.map(item => {\r\n let score = 0;\r\n \r\n fields.forEach((field, index) => {\r\n const value = item[field];\r\n if (!value) return;\r\n \r\n const weight = fields.length - index; // Primeiro campo tem maior peso\r\n \r\n if (Array.isArray(value)) {\r\n const matches = value.filter(v => {\r\n const strValue = caseSensitive ? String(v) : String(v).toLowerCase();\r\n return strValue.includes(searchQuery);\r\n }).length;\r\n score += matches * weight;\r\n } else {\r\n const strValue = caseSensitive ? String(value) : String(value).toLowerCase();\r\n if (strValue.includes(searchQuery)) {\r\n score += weight;\r\n // Bonus se for match exato\r\n if (strValue === searchQuery) {\r\n score += weight * 2;\r\n }\r\n }\r\n }\r\n });\r\n \r\n return { item, score };\r\n });\r\n \r\n // Filtra apenas com score > 0 e ordena\r\n return scored\r\n .filter(({ score }) => score > 0)\r\n .sort((a, b) => b.score - a.score)\r\n .map(({ item }) => item);\r\n}\r\n\r\n/**\r\n * Busca fuzzy (tolerante a erros de digitação)\r\n *\r\n * Usa distância de Levenshtein simplificada para encontrar\r\n * correspondências aproximadas.\r\n *\r\n * @param query - Termo de busca\r\n * @param content - Array de objetos a buscar\r\n * @param options - Opções de busca + threshold\r\n * @returns Array com correspondências aproximadas\r\n *\r\n * @example\r\n * ```ts\r\n * fuzzySearch('nxt', posts) // Encontra 'next'\r\n * fuzzySearch('raect', posts) // Encontra 'react'\r\n * ```\r\n */\r\nexport function fuzzySearch<T extends Record<string, any>>(\r\n query: string,\r\n content: T[],\r\n options: SearchOptions & { threshold?: number } = {}\r\n): T[] {\r\n if (!query.trim()) return content;\r\n \r\n const {\r\n fields = ['title', 'description'],\r\n caseSensitive = false,\r\n threshold = 0.6 // Similaridade mínima (0-1)\r\n } = options;\r\n \r\n const searchQuery = caseSensitive ? query : query.toLowerCase();\r\n \r\n return content.filter(item => {\r\n return fields.some(field => {\r\n const value = item[field];\r\n if (!value) return false;\r\n \r\n const strValue = caseSensitive ? String(value) : String(value).toLowerCase();\r\n const similarity = calculateSimilarity(searchQuery, strValue);\r\n \r\n return similarity >= threshold;\r\n });\r\n });\r\n}\r\n\r\n/**\r\n * Calcula similaridade entre duas strings (0-1)\r\n * Usa algoritmo de Jaro-Winkler simplificado\r\n */\r\nfunction calculateSimilarity(str1: string, str2: string): number {\r\n if (str1 === str2) return 1;\r\n if (str1.length === 0 || str2.length === 0) return 0;\r\n \r\n // Busca substring\r\n if (str2.includes(str1)) return 0.8;\r\n \r\n // Calcula caracteres em comum\r\n const common = str1.split('').filter(char => str2.includes(char)).length;\r\n const similarity = common / Math.max(str1.length, str2.length);\r\n \r\n return similarity;\r\n}\r\n"]}
@@ -0,0 +1,88 @@
1
+ // src/search/index.ts
2
+ function searchContent(query, content, options = {}) {
3
+ if (!query.trim()) return content;
4
+ const {
5
+ fields = ["title", "description", "content", "tags"],
6
+ caseSensitive = false,
7
+ exactMatch = false
8
+ } = options;
9
+ const searchQuery = caseSensitive ? query : query.toLowerCase();
10
+ return content.filter((item) => {
11
+ return fields.some((field) => {
12
+ const value = item[field];
13
+ if (!value) return false;
14
+ if (Array.isArray(value)) {
15
+ return value.some((v) => {
16
+ const strValue2 = caseSensitive ? String(v) : String(v).toLowerCase();
17
+ return exactMatch ? strValue2 === searchQuery : strValue2.includes(searchQuery);
18
+ });
19
+ }
20
+ const strValue = caseSensitive ? String(value) : String(value).toLowerCase();
21
+ return exactMatch ? strValue === searchQuery : strValue.includes(searchQuery);
22
+ });
23
+ });
24
+ }
25
+ function searchWithScore(query, content, options = {}) {
26
+ if (!query.trim()) return content;
27
+ const {
28
+ fields = ["title", "description", "content", "tags"],
29
+ caseSensitive = false
30
+ } = options;
31
+ const searchQuery = caseSensitive ? query : query.toLowerCase();
32
+ const scored = content.map((item) => {
33
+ let score = 0;
34
+ fields.forEach((field, index) => {
35
+ const value = item[field];
36
+ if (!value) return;
37
+ const weight = fields.length - index;
38
+ if (Array.isArray(value)) {
39
+ const matches = value.filter((v) => {
40
+ const strValue = caseSensitive ? String(v) : String(v).toLowerCase();
41
+ return strValue.includes(searchQuery);
42
+ }).length;
43
+ score += matches * weight;
44
+ } else {
45
+ const strValue = caseSensitive ? String(value) : String(value).toLowerCase();
46
+ if (strValue.includes(searchQuery)) {
47
+ score += weight;
48
+ if (strValue === searchQuery) {
49
+ score += weight * 2;
50
+ }
51
+ }
52
+ }
53
+ });
54
+ return { item, score };
55
+ });
56
+ return scored.filter(({ score }) => score > 0).sort((a, b) => b.score - a.score).map(({ item }) => item);
57
+ }
58
+ function fuzzySearch(query, content, options = {}) {
59
+ if (!query.trim()) return content;
60
+ const {
61
+ fields = ["title", "description"],
62
+ caseSensitive = false,
63
+ threshold = 0.6
64
+ // Similaridade mínima (0-1)
65
+ } = options;
66
+ const searchQuery = caseSensitive ? query : query.toLowerCase();
67
+ return content.filter((item) => {
68
+ return fields.some((field) => {
69
+ const value = item[field];
70
+ if (!value) return false;
71
+ const strValue = caseSensitive ? String(value) : String(value).toLowerCase();
72
+ const similarity = calculateSimilarity(searchQuery, strValue);
73
+ return similarity >= threshold;
74
+ });
75
+ });
76
+ }
77
+ function calculateSimilarity(str1, str2) {
78
+ if (str1 === str2) return 1;
79
+ if (str1.length === 0 || str2.length === 0) return 0;
80
+ if (str2.includes(str1)) return 0.8;
81
+ const common = str1.split("").filter((char) => str2.includes(char)).length;
82
+ const similarity = common / Math.max(str1.length, str2.length);
83
+ return similarity;
84
+ }
85
+
86
+ export { fuzzySearch, searchContent, searchWithScore };
87
+ //# sourceMappingURL=index.mjs.map
88
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/search/index.ts"],"names":["strValue"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\r\n * Search Utilities\r\n *\r\n * Utilitários genéricos para busca e filtro de conteúdo.\r\n * Funções puras, agnósticas de domínio e framework.\r\n *\r\n * @module @rainersoft/utils/search\r\n * @author Rainer Teixeira\r\n * @version 1.0.0\r\n */\r\n\r\n/**\r\n * Opções de busca\r\n */\r\nexport interface SearchOptions {\r\n /** Campos a buscar (padrão: ['title', 'description', 'content', 'tags']) */\r\n fields?: string[];\r\n /** Case sensitive (padrão: false) */\r\n caseSensitive?: boolean;\r\n /** Busca exata (padrão: false) */\r\n exactMatch?: boolean;\r\n}\r\n\r\n/**\r\n * Busca genérica em array de objetos\r\n *\r\n * Função de busca client-side para arrays de objetos genéricos.\r\n * Busca em múltiplos campos configuráveis.\r\n *\r\n * @param query - Termo de busca\r\n * @param content - Array de objetos a buscar\r\n * @param options - Opções de busca\r\n * @returns Array filtrado com resultados\r\n *\r\n * @example\r\n * ```ts\r\n * const posts = [\r\n * { title: 'Next.js Guide', description: 'Learn Next.js' },\r\n * { title: 'React Basics', description: 'React fundamentals' }\r\n * ];\r\n * \r\n * searchContent('next', posts) // [{ title: 'Next.js Guide', ... }]\r\n * searchContent('react', posts, { fields: ['title'] }) // [{ title: 'React Basics', ... }]\r\n * ```\r\n */\r\nexport function searchContent<T extends Record<string, any>>(\r\n query: string,\r\n content: T[],\r\n options: SearchOptions = {}\r\n): T[] {\r\n if (!query.trim()) return content;\r\n \r\n const {\r\n fields = ['title', 'description', 'content', 'tags'],\r\n caseSensitive = false,\r\n exactMatch = false\r\n } = options;\r\n \r\n const searchQuery = caseSensitive ? query : query.toLowerCase();\r\n \r\n return content.filter(item => {\r\n return fields.some(field => {\r\n const value = item[field];\r\n \r\n if (!value) return false;\r\n \r\n // Se for array (ex: tags)\r\n if (Array.isArray(value)) {\r\n return value.some(v => {\r\n const strValue = caseSensitive ? String(v) : String(v).toLowerCase();\r\n return exactMatch \r\n ? strValue === searchQuery \r\n : strValue.includes(searchQuery);\r\n });\r\n }\r\n \r\n // Se for string\r\n const strValue = caseSensitive ? String(value) : String(value).toLowerCase();\r\n return exactMatch \r\n ? strValue === searchQuery \r\n : strValue.includes(searchQuery);\r\n });\r\n });\r\n}\r\n\r\n/**\r\n * Busca com score de relevância\r\n *\r\n * Retorna resultados ordenados por relevância baseado em:\r\n * - Correspondência no título (peso maior)\r\n * - Correspondência na descrição\r\n * - Correspondência em outros campos\r\n *\r\n * @param query - Termo de busca\r\n * @param content - Array de objetos a buscar\r\n * @param options - Opções de busca\r\n * @returns Array ordenado por relevância\r\n *\r\n * @example\r\n * ```ts\r\n * const results = searchWithScore('next', posts);\r\n * // Resultados com título contendo 'next' aparecem primeiro\r\n * ```\r\n */\r\nexport function searchWithScore<T extends Record<string, any>>(\r\n query: string,\r\n content: T[],\r\n options: SearchOptions = {}\r\n): T[] {\r\n if (!query.trim()) return content;\r\n \r\n const {\r\n fields = ['title', 'description', 'content', 'tags'],\r\n caseSensitive = false\r\n } = options;\r\n \r\n const searchQuery = caseSensitive ? query : query.toLowerCase();\r\n \r\n // Calcula score para cada item\r\n const scored = content.map(item => {\r\n let score = 0;\r\n \r\n fields.forEach((field, index) => {\r\n const value = item[field];\r\n if (!value) return;\r\n \r\n const weight = fields.length - index; // Primeiro campo tem maior peso\r\n \r\n if (Array.isArray(value)) {\r\n const matches = value.filter(v => {\r\n const strValue = caseSensitive ? String(v) : String(v).toLowerCase();\r\n return strValue.includes(searchQuery);\r\n }).length;\r\n score += matches * weight;\r\n } else {\r\n const strValue = caseSensitive ? String(value) : String(value).toLowerCase();\r\n if (strValue.includes(searchQuery)) {\r\n score += weight;\r\n // Bonus se for match exato\r\n if (strValue === searchQuery) {\r\n score += weight * 2;\r\n }\r\n }\r\n }\r\n });\r\n \r\n return { item, score };\r\n });\r\n \r\n // Filtra apenas com score > 0 e ordena\r\n return scored\r\n .filter(({ score }) => score > 0)\r\n .sort((a, b) => b.score - a.score)\r\n .map(({ item }) => item);\r\n}\r\n\r\n/**\r\n * Busca fuzzy (tolerante a erros de digitação)\r\n *\r\n * Usa distância de Levenshtein simplificada para encontrar\r\n * correspondências aproximadas.\r\n *\r\n * @param query - Termo de busca\r\n * @param content - Array de objetos a buscar\r\n * @param options - Opções de busca + threshold\r\n * @returns Array com correspondências aproximadas\r\n *\r\n * @example\r\n * ```ts\r\n * fuzzySearch('nxt', posts) // Encontra 'next'\r\n * fuzzySearch('raect', posts) // Encontra 'react'\r\n * ```\r\n */\r\nexport function fuzzySearch<T extends Record<string, any>>(\r\n query: string,\r\n content: T[],\r\n options: SearchOptions & { threshold?: number } = {}\r\n): T[] {\r\n if (!query.trim()) return content;\r\n \r\n const {\r\n fields = ['title', 'description'],\r\n caseSensitive = false,\r\n threshold = 0.6 // Similaridade mínima (0-1)\r\n } = options;\r\n \r\n const searchQuery = caseSensitive ? query : query.toLowerCase();\r\n \r\n return content.filter(item => {\r\n return fields.some(field => {\r\n const value = item[field];\r\n if (!value) return false;\r\n \r\n const strValue = caseSensitive ? String(value) : String(value).toLowerCase();\r\n const similarity = calculateSimilarity(searchQuery, strValue);\r\n \r\n return similarity >= threshold;\r\n });\r\n });\r\n}\r\n\r\n/**\r\n * Calcula similaridade entre duas strings (0-1)\r\n * Usa algoritmo de Jaro-Winkler simplificado\r\n */\r\nfunction calculateSimilarity(str1: string, str2: string): number {\r\n if (str1 === str2) return 1;\r\n if (str1.length === 0 || str2.length === 0) return 0;\r\n \r\n // Busca substring\r\n if (str2.includes(str1)) return 0.8;\r\n \r\n // Calcula caracteres em comum\r\n const common = str1.split('').filter(char => str2.includes(char)).length;\r\n const similarity = common / Math.max(str1.length, str2.length);\r\n \r\n return similarity;\r\n}\r\n"]}
@@ -1,9 +1,2 @@
1
- import { L as Locale } from '../types-tUMATEGI.mjs';
2
-
3
- type GenericStatus = 'DRAFT' | 'PENDING' | 'PUBLISHED' | 'ACTIVE' | 'INACTIVE' | 'ARCHIVED' | 'DELETED' | 'SCHEDULED' | 'COMPLETED' | 'CANCELLED' | 'APPROVED' | 'REJECTED';
4
- declare function translateStatus(status: string, locale?: Locale): string;
5
- declare function getStatusColor(status: string): string;
6
- declare function getStatusVariant(status: string): 'default' | 'secondary' | 'destructive' | 'outline';
7
- declare function translatePostStatus(status: string, locale?: Locale): string;
8
-
9
- export { type GenericStatus, getStatusColor, getStatusVariant, translatePostStatus, translateStatus };
1
+ import '../types-tUMATEGI.mjs';
2
+ export { G as GenericStatus, g as getStatusColor, a as getStatusVariant, b as translatePostStatus, t as translateStatus } from '../index-BuqX8-qm.mjs';
@@ -1,9 +1,2 @@
1
- import { L as Locale } from '../types-tUMATEGI.js';
2
-
3
- type GenericStatus = 'DRAFT' | 'PENDING' | 'PUBLISHED' | 'ACTIVE' | 'INACTIVE' | 'ARCHIVED' | 'DELETED' | 'SCHEDULED' | 'COMPLETED' | 'CANCELLED' | 'APPROVED' | 'REJECTED';
4
- declare function translateStatus(status: string, locale?: Locale): string;
5
- declare function getStatusColor(status: string): string;
6
- declare function getStatusVariant(status: string): 'default' | 'secondary' | 'destructive' | 'outline';
7
- declare function translatePostStatus(status: string, locale?: Locale): string;
8
-
9
- export { type GenericStatus, getStatusColor, getStatusVariant, translatePostStatus, translateStatus };
1
+ import '../types-tUMATEGI.js';
2
+ export { G as GenericStatus, g as getStatusColor, a as getStatusVariant, b as translatePostStatus, t as translateStatus } from '../index-BfZE8XjJ.js';
@@ -5,5 +5,10 @@ declare function removeAccents(text: string): string;
5
5
  declare function getInitials(name: string, maxInitials?: number): string;
6
6
  declare function isEmpty(text: string | null | undefined): boolean;
7
7
  declare function wordCount(text: string): number;
8
+ declare function formatPhone(phone: string): string;
9
+ declare function formatCPF(cpf: string): string;
10
+ declare function formatCNPJ(cnpj: string): string;
11
+ declare function isCPF(cpf: string): boolean;
12
+ declare function isCNPJ(cnpj: string): boolean;
8
13
 
9
- export { capitalize, getInitials, isEmpty, removeAccents, textToSlug, truncate, wordCount };
14
+ export { capitalize, formatCNPJ, formatCPF, formatPhone, getInitials, isCNPJ, isCPF, isEmpty, removeAccents, textToSlug, truncate, wordCount };
@@ -5,5 +5,10 @@ declare function removeAccents(text: string): string;
5
5
  declare function getInitials(name: string, maxInitials?: number): string;
6
6
  declare function isEmpty(text: string | null | undefined): boolean;
7
7
  declare function wordCount(text: string): number;
8
+ declare function formatPhone(phone: string): string;
9
+ declare function formatCPF(cpf: string): string;
10
+ declare function formatCNPJ(cnpj: string): string;
11
+ declare function isCPF(cpf: string): boolean;
12
+ declare function isCNPJ(cnpj: string): boolean;
8
13
 
9
- export { capitalize, getInitials, isEmpty, removeAccents, textToSlug, truncate, wordCount };
14
+ export { capitalize, formatCNPJ, formatCPF, formatPhone, getInitials, isCNPJ, isCPF, isEmpty, removeAccents, textToSlug, truncate, wordCount };
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/string/index.ts
4
4
  function textToSlug(text) {
5
- return text.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^\w\s-]/g, "").trim().replace(/\s+/g, "-").replace(/-+/g, "-");
5
+ return text.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^\w\s-]/g, "").trim().replace(/^-+|-+$/g, "").replace(/\s+/g, "-").replace(/-+/g, "-");
6
6
  }
7
7
  function capitalize(text) {
8
8
  return text.toLowerCase().split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
@@ -23,9 +23,81 @@ function isEmpty(text) {
23
23
  function wordCount(text) {
24
24
  return text.trim().split(/\s+/).filter((word) => word.length > 0).length;
25
25
  }
26
+ function formatPhone(phone) {
27
+ const digits = phone.replace(/\D/g, "");
28
+ if (digits.length === 11) {
29
+ return digits.replace(/(\d{2})(\d{5})(\d{4})/, "($1) $2-$3");
30
+ }
31
+ return digits.replace(/(\d{2})(\d{4})(\d{4})/, "($1) $2-$3");
32
+ }
33
+ function formatCPF(cpf) {
34
+ const digits = cpf.replace(/\D/g, "");
35
+ return digits.replace(/(\d{3})(\d{3})(\d{3})(\d{2})/, "$1.$2.$3-$4");
36
+ }
37
+ function formatCNPJ(cnpj) {
38
+ const digits = cnpj.replace(/\D/g, "");
39
+ return digits.replace(/(\d{2})(\d{3})(\d{3})(\d{4})(\d{2})/, "$1.$2.$3/$4-$5");
40
+ }
41
+ function isCPF(cpf) {
42
+ const digits = cpf.replace(/\D/g, "");
43
+ if (digits.length !== 11 || /^(\d)\1{10}$/.test(digits)) {
44
+ return false;
45
+ }
46
+ let sum = 0;
47
+ for (let i = 0; i < 9; i++) {
48
+ sum += parseInt(digits.charAt(i)) * (10 - i);
49
+ }
50
+ let remainder = sum * 10 % 11;
51
+ if (remainder === 10 || remainder === 11) remainder = 0;
52
+ if (remainder !== parseInt(digits.charAt(9))) {
53
+ return false;
54
+ }
55
+ sum = 0;
56
+ for (let i = 0; i < 10; i++) {
57
+ sum += parseInt(digits.charAt(i)) * (11 - i);
58
+ }
59
+ remainder = sum * 10 % 11;
60
+ if (remainder === 10 || remainder === 11) remainder = 0;
61
+ if (remainder !== parseInt(digits.charAt(10))) {
62
+ return false;
63
+ }
64
+ return true;
65
+ }
66
+ function isCNPJ(cnpj) {
67
+ const digits = cnpj.replace(/\D/g, "");
68
+ if (digits.length !== 14 || /^(\d)\1{13}$/.test(digits)) {
69
+ return false;
70
+ }
71
+ const weights1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
72
+ let sum = 0;
73
+ for (let i = 0; i < 12; i++) {
74
+ sum += parseInt(digits.charAt(i)) * weights1[i];
75
+ }
76
+ let remainder = sum % 11;
77
+ const digit1 = remainder < 2 ? 0 : 11 - remainder;
78
+ if (digit1 !== parseInt(digits.charAt(12))) {
79
+ return false;
80
+ }
81
+ const weights2 = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
82
+ sum = 0;
83
+ for (let i = 0; i < 13; i++) {
84
+ sum += parseInt(digits.charAt(i)) * weights2[i];
85
+ }
86
+ remainder = sum % 11;
87
+ const digit2 = remainder < 2 ? 0 : 11 - remainder;
88
+ if (digit2 !== parseInt(digits.charAt(13))) {
89
+ return false;
90
+ }
91
+ return true;
92
+ }
26
93
 
27
94
  exports.capitalize = capitalize;
95
+ exports.formatCNPJ = formatCNPJ;
96
+ exports.formatCPF = formatCPF;
97
+ exports.formatPhone = formatPhone;
28
98
  exports.getInitials = getInitials;
99
+ exports.isCNPJ = isCNPJ;
100
+ exports.isCPF = isCPF;
29
101
  exports.isEmpty = isEmpty;
30
102
  exports.removeAccents = removeAccents;
31
103
  exports.textToSlug = textToSlug;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/string/index.ts"],"names":[],"mappings":";;;AAoBO,SAAS,WAAW,IAAA,EAAsB;AAC/C,EAAA,OAAO,IAAA,CACJ,aAAY,CACZ,SAAA,CAAU,KAAK,CAAA,CACf,OAAA,CAAQ,kBAAA,EAAoB,EAAE,CAAA,CAC9B,OAAA,CAAQ,aAAa,EAAE,CAAA,CACvB,MAAK,CACL,OAAA,CAAQ,QAAQ,GAAG,CAAA,CACnB,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA;AACvB;AAWO,SAAS,WAAW,IAAA,EAAsB;AAC/C,EAAA,OAAO,IAAA,CACJ,aAAY,CACZ,KAAA,CAAM,GAAG,CAAA,CACT,GAAA,CAAI,UAAQ,IAAA,CAAK,MAAA,CAAO,CAAC,CAAA,CAAE,WAAA,KAAgB,IAAA,CAAK,KAAA,CAAM,CAAC,CAAC,CAAA,CACxD,KAAK,GAAG,CAAA;AACb;AAaO,SAAS,QAAA,CAAS,IAAA,EAAc,SAAA,EAAmB,MAAA,GAAS,KAAA,EAAe;AAChF,EAAA,IAAI,IAAA,CAAK,MAAA,IAAU,SAAA,EAAW,OAAO,IAAA;AACrC,EAAA,OAAO,KAAK,KAAA,CAAM,CAAA,EAAG,SAAA,GAAY,MAAA,CAAO,MAAM,CAAA,GAAI,MAAA;AACpD;AAWO,SAAS,cAAc,IAAA,EAAsB;AAClD,EAAA,OAAO,KAAK,SAAA,CAAU,KAAK,CAAA,CAAE,OAAA,CAAQ,oBAAoB,EAAE,CAAA;AAC7D;AAaO,SAAS,WAAA,CAAY,IAAA,EAAc,WAAA,GAAc,CAAA,EAAW;AACjE,EAAA,OAAO,IAAA,CACJ,MAAM,GAAG,CAAA,CACT,OAAO,CAAA,IAAA,KAAQ,IAAA,CAAK,MAAA,GAAS,CAAC,CAAA,CAC9B,GAAA,CAAI,UAAQ,IAAA,CAAK,CAAC,CAAC,CAAA,CACnB,IAAA,CAAK,EAAE,EACP,WAAA,EAAY,CACZ,KAAA,CAAM,CAAA,EAAG,WAAW,CAAA;AACzB;AAQO,SAAS,QAAQ,IAAA,EAA0C;AAChE,EAAA,OAAO,CAAC,IAAA,IAAQ,IAAA,CAAK,IAAA,GAAO,MAAA,KAAW,CAAA;AACzC;AAQO,SAAS,UAAU,IAAA,EAAsB;AAC9C,EAAA,OAAO,IAAA,CAAK,IAAA,EAAK,CAAE,KAAA,CAAM,KAAK,CAAA,CAAE,MAAA,CAAO,CAAA,IAAA,KAAQ,IAAA,CAAK,MAAA,GAAS,CAAC,CAAA,CAAE,MAAA;AAClE","file":"index.js","sourcesContent":["/**\r\n * String Utilities\r\n *\r\n * Utilitários para manipulação e formatação de strings.\r\n * Funções puras, sem dependências externas, prontas para qualquer plataforma.\r\n *\r\n * @module @rainersoft/utils/string\r\n * @author Rainer Teixeira\r\n */\r\n\r\n/**\r\n * Converte texto para slug URL-friendly\r\n *\r\n * @param text - Texto para converter\r\n * @returns Slug normalizado\r\n *\r\n * @example\r\n * textToSlug('Meu Post Incrível!') // 'meu-post-incrivel'\r\n * textToSlug('São Paulo - SP') // 'sao-paulo-sp'\r\n */\r\nexport function textToSlug(text: string): string {\r\n return text\r\n .toLowerCase()\r\n .normalize('NFD')\r\n .replace(/[\\u0300-\\u036f]/g, '')\r\n .replace(/[^\\w\\s-]/g, '')\r\n .trim()\r\n .replace(/\\s+/g, '-')\r\n .replace(/-+/g, '-');\r\n}\r\n\r\n/**\r\n * Capitaliza primeira letra de cada palavra\r\n *\r\n * @param text - Texto para capitalizar\r\n * @returns Texto capitalizado\r\n *\r\n * @example\r\n * capitalize('rainer teixeira') // 'Rainer Teixeira'\r\n */\r\nexport function capitalize(text: string): string {\r\n return text\r\n .toLowerCase()\r\n .split(' ')\r\n .map(word => word.charAt(0).toUpperCase() + word.slice(1))\r\n .join(' ');\r\n}\r\n\r\n/**\r\n * Trunca texto com ellipsis\r\n *\r\n * @param text - Texto para truncar\r\n * @param maxLength - Comprimento máximo\r\n * @param suffix - Sufixo (padrão: '...')\r\n * @returns Texto truncado\r\n *\r\n * @example\r\n * truncate('Texto muito longo', 10) // 'Texto muit...'\r\n */\r\nexport function truncate(text: string, maxLength: number, suffix = '...'): string {\r\n if (text.length <= maxLength) return text;\r\n return text.slice(0, maxLength - suffix.length) + suffix;\r\n}\r\n\r\n/**\r\n * Remove acentos de texto\r\n *\r\n * @param text - Texto com acentos\r\n * @returns Texto sem acentos\r\n *\r\n * @example\r\n * removeAccents('São Paulo') // 'Sao Paulo'\r\n */\r\nexport function removeAccents(text: string): string {\r\n return text.normalize('NFD').replace(/[\\u0300-\\u036f]/g, '');\r\n}\r\n\r\n/**\r\n * Extrai iniciais de um nome\r\n *\r\n * @param name - Nome completo\r\n * @param maxInitials - Máximo de iniciais (padrão: 2)\r\n * @returns Iniciais em maiúsculas\r\n *\r\n * @example\r\n * getInitials('Rainer Teixeira') // 'RT'\r\n * getInitials('João da Silva Santos', 3) // 'JSS'\r\n */\r\nexport function getInitials(name: string, maxInitials = 2): string {\r\n return name\r\n .split(' ')\r\n .filter(word => word.length > 0)\r\n .map(word => word[0])\r\n .join('')\r\n .toUpperCase()\r\n .slice(0, maxInitials);\r\n}\r\n\r\n/**\r\n * Valida se string é vazia ou apenas espaços\r\n *\r\n * @param text - Texto para validar\r\n * @returns true se vazio, false caso contrário\r\n */\r\nexport function isEmpty(text: string | null | undefined): boolean {\r\n return !text || text.trim().length === 0;\r\n}\r\n\r\n/**\r\n * Conta palavras em um texto\r\n *\r\n * @param text - Texto para contar\r\n * @returns Número de palavras\r\n */\r\nexport function wordCount(text: string): number {\r\n return text.trim().split(/\\s+/).filter(word => word.length > 0).length;\r\n}\r\n"]}
1
+ {"version":3,"sources":["../../src/string/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\r\n * String Utilities\r\n *\r\n * Utilitários para manipulação e formatação de strings.\r\n * Funções puras, sem dependências externas, prontas para qualquer plataforma.\r\n *\r\n * @module @rainersoft/utils/string\r\n * @author Rainer Teixeira\r\n */\r\n\r\n/**\r\n * Converte texto para slug URL-friendly\r\n *\r\n * @param text - Texto para converter\r\n * @returns Slug normalizado\r\n *\r\n * @example\r\n * textToSlug('Meu Post Incrível!') // 'meu-post-incrivel'\r\n * textToSlug('São Paulo - SP') // 'sao-paulo-sp'\r\n */\r\nexport function textToSlug(text: string): string {\r\n return text\r\n .toLowerCase()\r\n .normalize('NFD')\r\n .replace(/[\\u0300-\\u036f]/g, '')\r\n .replace(/[^\\w\\s-]/g, '')\r\n .trim()\r\n .replace(/^-+|-+$/g, '') // Remove hífens do início e fim\r\n .replace(/\\s+/g, '-')\r\n .replace(/-+/g, '-');\r\n}\r\n\r\n/**\r\n * Capitaliza primeira letra de cada palavra\r\n *\r\n * @param text - Texto para capitalizar\r\n * @returns Texto capitalizado\r\n *\r\n * @example\r\n * capitalize('rainer teixeira') // 'Rainer Teixeira'\r\n */\r\nexport function capitalize(text: string): string {\r\n return text\r\n .toLowerCase()\r\n .split(' ')\r\n .map(word => word.charAt(0).toUpperCase() + word.slice(1))\r\n .join(' ');\r\n}\r\n\r\n/**\r\n * Trunca texto com ellipsis\r\n *\r\n * @param text - Texto para truncar\r\n * @param maxLength - Comprimento máximo\r\n * @param suffix - Sufixo (padrão: '...')\r\n * @returns Texto truncado\r\n *\r\n * @example\r\n * truncate('Texto muito longo', 10) // 'Texto muit...'\r\n */\r\nexport function truncate(text: string, maxLength: number, suffix = '...'): string {\r\n if (text.length <= maxLength) return text;\r\n return text.slice(0, maxLength - suffix.length) + suffix;\r\n}\r\n\r\n/**\r\n * Remove acentos de texto\r\n *\r\n * @param text - Texto com acentos\r\n * @returns Texto sem acentos\r\n *\r\n * @example\r\n * removeAccents('São Paulo') // 'Sao Paulo'\r\n */\r\nexport function removeAccents(text: string): string {\r\n return text.normalize('NFD').replace(/[\\u0300-\\u036f]/g, '');\r\n}\r\n\r\n/**\r\n * Extrai iniciais de um nome\r\n *\r\n * @param name - Nome completo\r\n * @param maxInitials - Máximo de iniciais (padrão: 2)\r\n * @returns Iniciais em maiúsculas\r\n *\r\n * @example\r\n * getInitials('Rainer Teixeira') // 'RT'\r\n * getInitials('João da Silva Santos', 3) // 'JSS'\r\n */\r\nexport function getInitials(name: string, maxInitials = 2): string {\r\n return name\r\n .split(' ')\r\n .filter(word => word.length > 0)\r\n .map(word => word[0])\r\n .join('')\r\n .toUpperCase()\r\n .slice(0, maxInitials);\r\n}\r\n\r\n/**\r\n * Valida se string é vazia ou apenas espaços\r\n *\r\n * @param text - Texto para validar\r\n * @returns true se vazio, false caso contrário\r\n */\r\nexport function isEmpty(text: string | null | undefined): boolean {\r\n return !text || text.trim().length === 0;\r\n}\r\n\r\n/**\r\n * Conta palavras em um texto\r\n *\r\n * @param text - Texto para contar\r\n * @returns Número de palavras\r\n */\r\nexport function wordCount(text: string): number {\r\n return text.trim().split(/\\s+/).filter(word => word.length > 0).length;\r\n}\r\n\r\n/**\r\n * Formata um número de telefone brasileiro\r\n * \r\n * @param phone - Número de telefone (com ou sem formatação)\r\n * @returns Telefone formatado\r\n * \r\n * @example\r\n * formatPhone('11999998888') // '(11) 99999-8888'\r\n * formatPhone('1133334444') // '(11) 3333-4444'\r\n */\r\nexport function formatPhone(phone: string): string {\r\n const digits = phone.replace(/\\D/g, '');\r\n \r\n if (digits.length === 11) {\r\n return digits.replace(/(\\d{2})(\\d{5})(\\d{4})/, '($1) $2-$3');\r\n }\r\n \r\n return digits.replace(/(\\d{2})(\\d{4})(\\d{4})/, '($1) $2-$3');\r\n}\r\n\r\n/**\r\n * Formata um CPF\r\n * \r\n * @param cpf - Número do CPF (com ou sem formatação)\r\n * @returns CPF formatado\r\n * \r\n * @example\r\n * formatCPF('12345678901') // '123.456.789-01'\r\n */\r\nexport function formatCPF(cpf: string): string {\r\n const digits = cpf.replace(/\\D/g, '');\r\n return digits.replace(/(\\d{3})(\\d{3})(\\d{3})(\\d{2})/, '$1.$2.$3-$4');\r\n}\r\n\r\n/**\r\n * Formata um CNPJ\r\n * \r\n * @param cnpj - Número do CNPJ (com ou sem formatação)\r\n * @returns CNPJ formatado\r\n * \r\n * @example\r\n * formatCNPJ('12345678000199') // '12.345.678/0001-99'\r\n */\r\nexport function formatCNPJ(cnpj: string): string {\r\n const digits = cnpj.replace(/\\D/g, '');\r\n return digits.replace(/(\\d{2})(\\d{3})(\\d{3})(\\d{4})(\\d{2})/, '$1.$2.$3/$4-$5');\r\n}\r\n\r\n/**\r\n * Valida um CPF\r\n * \r\n * @param cpf - Número do CPF (com ou sem formatação)\r\n * @returns true se válido, false caso contrário\r\n * \r\n * @example\r\n * isCPF('123.456.789-09') // false (CPF inválido)\r\n */\r\nexport function isCPF(cpf: string): boolean {\r\n const digits = cpf.replace(/\\D/g, '');\r\n \r\n if (digits.length !== 11 || /^(\\d)\\1{10}$/.test(digits)) {\r\n return false;\r\n }\r\n \r\n // Cálculo do primeiro dígito verificador\r\n let sum = 0;\r\n for (let i = 0; i < 9; i++) {\r\n sum += parseInt(digits.charAt(i)) * (10 - i);\r\n }\r\n let remainder = (sum * 10) % 11;\r\n if (remainder === 10 || remainder === 11) remainder = 0;\r\n if (remainder !== parseInt(digits.charAt(9))) {\r\n return false;\r\n }\r\n \r\n // Cálculo do segundo dígito verificador\r\n sum = 0;\r\n for (let i = 0; i < 10; i++) {\r\n sum += parseInt(digits.charAt(i)) * (11 - i);\r\n }\r\n remainder = (sum * 10) % 11;\r\n if (remainder === 10 || remainder === 11) remainder = 0;\r\n if (remainder !== parseInt(digits.charAt(10))) {\r\n return false;\r\n }\r\n \r\n return true;\r\n}\r\n\r\n/**\r\n * Valida um CNPJ\r\n * \r\n * @param cnpj - Número do CNPJ (com ou sem formatação)\r\n * @returns true se válido, false caso contrário\r\n * \r\n * @example\r\n * isCNPJ('12.345.678/0001-99') // false (CNPJ inválido)\r\n */\r\nexport function isCNPJ(cnpj: string): boolean {\r\n const digits = cnpj.replace(/\\D/g, '');\r\n \r\n if (digits.length !== 14 || /^(\\d)\\1{13}$/.test(digits)) {\r\n return false;\r\n }\r\n \r\n // Cálculo do primeiro dígito verificador\r\n const weights1 = [5,4,3,2,9,8,7,6,5,4,3,2];\r\n let sum = 0;\r\n for (let i = 0; i < 12; i++) {\r\n sum += parseInt(digits.charAt(i)) * weights1[i];\r\n }\r\n let remainder = sum % 11;\r\n const digit1 = remainder < 2 ? 0 : 11 - remainder;\r\n if (digit1 !== parseInt(digits.charAt(12))) {\r\n return false;\r\n }\r\n \r\n // Cálculo do segundo dígito verificador\r\n const weights2 = [6,5,4,3,2,9,8,7,6,5,4,3,2];\r\n sum = 0;\r\n for (let i = 0; i < 13; i++) {\r\n sum += parseInt(digits.charAt(i)) * weights2[i];\r\n }\r\n remainder = sum % 11;\r\n const digit2 = remainder < 2 ? 0 : 11 - remainder;\r\n if (digit2 !== parseInt(digits.charAt(13))) {\r\n return false;\r\n }\r\n \r\n return true;\r\n}\r\n"]}
@@ -1,6 +1,6 @@
1
1
  // src/string/index.ts
2
2
  function textToSlug(text) {
3
- return text.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^\w\s-]/g, "").trim().replace(/\s+/g, "-").replace(/-+/g, "-");
3
+ return text.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^\w\s-]/g, "").trim().replace(/^-+|-+$/g, "").replace(/\s+/g, "-").replace(/-+/g, "-");
4
4
  }
5
5
  function capitalize(text) {
6
6
  return text.toLowerCase().split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
@@ -21,7 +21,74 @@ function isEmpty(text) {
21
21
  function wordCount(text) {
22
22
  return text.trim().split(/\s+/).filter((word) => word.length > 0).length;
23
23
  }
24
+ function formatPhone(phone) {
25
+ const digits = phone.replace(/\D/g, "");
26
+ if (digits.length === 11) {
27
+ return digits.replace(/(\d{2})(\d{5})(\d{4})/, "($1) $2-$3");
28
+ }
29
+ return digits.replace(/(\d{2})(\d{4})(\d{4})/, "($1) $2-$3");
30
+ }
31
+ function formatCPF(cpf) {
32
+ const digits = cpf.replace(/\D/g, "");
33
+ return digits.replace(/(\d{3})(\d{3})(\d{3})(\d{2})/, "$1.$2.$3-$4");
34
+ }
35
+ function formatCNPJ(cnpj) {
36
+ const digits = cnpj.replace(/\D/g, "");
37
+ return digits.replace(/(\d{2})(\d{3})(\d{3})(\d{4})(\d{2})/, "$1.$2.$3/$4-$5");
38
+ }
39
+ function isCPF(cpf) {
40
+ const digits = cpf.replace(/\D/g, "");
41
+ if (digits.length !== 11 || /^(\d)\1{10}$/.test(digits)) {
42
+ return false;
43
+ }
44
+ let sum = 0;
45
+ for (let i = 0; i < 9; i++) {
46
+ sum += parseInt(digits.charAt(i)) * (10 - i);
47
+ }
48
+ let remainder = sum * 10 % 11;
49
+ if (remainder === 10 || remainder === 11) remainder = 0;
50
+ if (remainder !== parseInt(digits.charAt(9))) {
51
+ return false;
52
+ }
53
+ sum = 0;
54
+ for (let i = 0; i < 10; i++) {
55
+ sum += parseInt(digits.charAt(i)) * (11 - i);
56
+ }
57
+ remainder = sum * 10 % 11;
58
+ if (remainder === 10 || remainder === 11) remainder = 0;
59
+ if (remainder !== parseInt(digits.charAt(10))) {
60
+ return false;
61
+ }
62
+ return true;
63
+ }
64
+ function isCNPJ(cnpj) {
65
+ const digits = cnpj.replace(/\D/g, "");
66
+ if (digits.length !== 14 || /^(\d)\1{13}$/.test(digits)) {
67
+ return false;
68
+ }
69
+ const weights1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
70
+ let sum = 0;
71
+ for (let i = 0; i < 12; i++) {
72
+ sum += parseInt(digits.charAt(i)) * weights1[i];
73
+ }
74
+ let remainder = sum % 11;
75
+ const digit1 = remainder < 2 ? 0 : 11 - remainder;
76
+ if (digit1 !== parseInt(digits.charAt(12))) {
77
+ return false;
78
+ }
79
+ const weights2 = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
80
+ sum = 0;
81
+ for (let i = 0; i < 13; i++) {
82
+ sum += parseInt(digits.charAt(i)) * weights2[i];
83
+ }
84
+ remainder = sum % 11;
85
+ const digit2 = remainder < 2 ? 0 : 11 - remainder;
86
+ if (digit2 !== parseInt(digits.charAt(13))) {
87
+ return false;
88
+ }
89
+ return true;
90
+ }
24
91
 
25
- export { capitalize, getInitials, isEmpty, removeAccents, textToSlug, truncate, wordCount };
92
+ export { capitalize, formatCNPJ, formatCPF, formatPhone, getInitials, isCNPJ, isCPF, isEmpty, removeAccents, textToSlug, truncate, wordCount };
26
93
  //# sourceMappingURL=index.mjs.map
27
94
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/string/index.ts"],"names":[],"mappings":";AAoBO,SAAS,WAAW,IAAA,EAAsB;AAC/C,EAAA,OAAO,IAAA,CACJ,aAAY,CACZ,SAAA,CAAU,KAAK,CAAA,CACf,OAAA,CAAQ,kBAAA,EAAoB,EAAE,CAAA,CAC9B,OAAA,CAAQ,aAAa,EAAE,CAAA,CACvB,MAAK,CACL,OAAA,CAAQ,QAAQ,GAAG,CAAA,CACnB,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA;AACvB;AAWO,SAAS,WAAW,IAAA,EAAsB;AAC/C,EAAA,OAAO,IAAA,CACJ,aAAY,CACZ,KAAA,CAAM,GAAG,CAAA,CACT,GAAA,CAAI,UAAQ,IAAA,CAAK,MAAA,CAAO,CAAC,CAAA,CAAE,WAAA,KAAgB,IAAA,CAAK,KAAA,CAAM,CAAC,CAAC,CAAA,CACxD,KAAK,GAAG,CAAA;AACb;AAaO,SAAS,QAAA,CAAS,IAAA,EAAc,SAAA,EAAmB,MAAA,GAAS,KAAA,EAAe;AAChF,EAAA,IAAI,IAAA,CAAK,MAAA,IAAU,SAAA,EAAW,OAAO,IAAA;AACrC,EAAA,OAAO,KAAK,KAAA,CAAM,CAAA,EAAG,SAAA,GAAY,MAAA,CAAO,MAAM,CAAA,GAAI,MAAA;AACpD;AAWO,SAAS,cAAc,IAAA,EAAsB;AAClD,EAAA,OAAO,KAAK,SAAA,CAAU,KAAK,CAAA,CAAE,OAAA,CAAQ,oBAAoB,EAAE,CAAA;AAC7D;AAaO,SAAS,WAAA,CAAY,IAAA,EAAc,WAAA,GAAc,CAAA,EAAW;AACjE,EAAA,OAAO,IAAA,CACJ,MAAM,GAAG,CAAA,CACT,OAAO,CAAA,IAAA,KAAQ,IAAA,CAAK,MAAA,GAAS,CAAC,CAAA,CAC9B,GAAA,CAAI,UAAQ,IAAA,CAAK,CAAC,CAAC,CAAA,CACnB,IAAA,CAAK,EAAE,EACP,WAAA,EAAY,CACZ,KAAA,CAAM,CAAA,EAAG,WAAW,CAAA;AACzB;AAQO,SAAS,QAAQ,IAAA,EAA0C;AAChE,EAAA,OAAO,CAAC,IAAA,IAAQ,IAAA,CAAK,IAAA,GAAO,MAAA,KAAW,CAAA;AACzC;AAQO,SAAS,UAAU,IAAA,EAAsB;AAC9C,EAAA,OAAO,IAAA,CAAK,IAAA,EAAK,CAAE,KAAA,CAAM,KAAK,CAAA,CAAE,MAAA,CAAO,CAAA,IAAA,KAAQ,IAAA,CAAK,MAAA,GAAS,CAAC,CAAA,CAAE,MAAA;AAClE","file":"index.mjs","sourcesContent":["/**\r\n * String Utilities\r\n *\r\n * Utilitários para manipulação e formatação de strings.\r\n * Funções puras, sem dependências externas, prontas para qualquer plataforma.\r\n *\r\n * @module @rainersoft/utils/string\r\n * @author Rainer Teixeira\r\n */\r\n\r\n/**\r\n * Converte texto para slug URL-friendly\r\n *\r\n * @param text - Texto para converter\r\n * @returns Slug normalizado\r\n *\r\n * @example\r\n * textToSlug('Meu Post Incrível!') // 'meu-post-incrivel'\r\n * textToSlug('São Paulo - SP') // 'sao-paulo-sp'\r\n */\r\nexport function textToSlug(text: string): string {\r\n return text\r\n .toLowerCase()\r\n .normalize('NFD')\r\n .replace(/[\\u0300-\\u036f]/g, '')\r\n .replace(/[^\\w\\s-]/g, '')\r\n .trim()\r\n .replace(/\\s+/g, '-')\r\n .replace(/-+/g, '-');\r\n}\r\n\r\n/**\r\n * Capitaliza primeira letra de cada palavra\r\n *\r\n * @param text - Texto para capitalizar\r\n * @returns Texto capitalizado\r\n *\r\n * @example\r\n * capitalize('rainer teixeira') // 'Rainer Teixeira'\r\n */\r\nexport function capitalize(text: string): string {\r\n return text\r\n .toLowerCase()\r\n .split(' ')\r\n .map(word => word.charAt(0).toUpperCase() + word.slice(1))\r\n .join(' ');\r\n}\r\n\r\n/**\r\n * Trunca texto com ellipsis\r\n *\r\n * @param text - Texto para truncar\r\n * @param maxLength - Comprimento máximo\r\n * @param suffix - Sufixo (padrão: '...')\r\n * @returns Texto truncado\r\n *\r\n * @example\r\n * truncate('Texto muito longo', 10) // 'Texto muit...'\r\n */\r\nexport function truncate(text: string, maxLength: number, suffix = '...'): string {\r\n if (text.length <= maxLength) return text;\r\n return text.slice(0, maxLength - suffix.length) + suffix;\r\n}\r\n\r\n/**\r\n * Remove acentos de texto\r\n *\r\n * @param text - Texto com acentos\r\n * @returns Texto sem acentos\r\n *\r\n * @example\r\n * removeAccents('São Paulo') // 'Sao Paulo'\r\n */\r\nexport function removeAccents(text: string): string {\r\n return text.normalize('NFD').replace(/[\\u0300-\\u036f]/g, '');\r\n}\r\n\r\n/**\r\n * Extrai iniciais de um nome\r\n *\r\n * @param name - Nome completo\r\n * @param maxInitials - Máximo de iniciais (padrão: 2)\r\n * @returns Iniciais em maiúsculas\r\n *\r\n * @example\r\n * getInitials('Rainer Teixeira') // 'RT'\r\n * getInitials('João da Silva Santos', 3) // 'JSS'\r\n */\r\nexport function getInitials(name: string, maxInitials = 2): string {\r\n return name\r\n .split(' ')\r\n .filter(word => word.length > 0)\r\n .map(word => word[0])\r\n .join('')\r\n .toUpperCase()\r\n .slice(0, maxInitials);\r\n}\r\n\r\n/**\r\n * Valida se string é vazia ou apenas espaços\r\n *\r\n * @param text - Texto para validar\r\n * @returns true se vazio, false caso contrário\r\n */\r\nexport function isEmpty(text: string | null | undefined): boolean {\r\n return !text || text.trim().length === 0;\r\n}\r\n\r\n/**\r\n * Conta palavras em um texto\r\n *\r\n * @param text - Texto para contar\r\n * @returns Número de palavras\r\n */\r\nexport function wordCount(text: string): number {\r\n return text.trim().split(/\\s+/).filter(word => word.length > 0).length;\r\n}\r\n"]}
1
+ {"version":3,"sources":["../../src/string/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\r\n * String Utilities\r\n *\r\n * Utilitários para manipulação e formatação de strings.\r\n * Funções puras, sem dependências externas, prontas para qualquer plataforma.\r\n *\r\n * @module @rainersoft/utils/string\r\n * @author Rainer Teixeira\r\n */\r\n\r\n/**\r\n * Converte texto para slug URL-friendly\r\n *\r\n * @param text - Texto para converter\r\n * @returns Slug normalizado\r\n *\r\n * @example\r\n * textToSlug('Meu Post Incrível!') // 'meu-post-incrivel'\r\n * textToSlug('São Paulo - SP') // 'sao-paulo-sp'\r\n */\r\nexport function textToSlug(text: string): string {\r\n return text\r\n .toLowerCase()\r\n .normalize('NFD')\r\n .replace(/[\\u0300-\\u036f]/g, '')\r\n .replace(/[^\\w\\s-]/g, '')\r\n .trim()\r\n .replace(/^-+|-+$/g, '') // Remove hífens do início e fim\r\n .replace(/\\s+/g, '-')\r\n .replace(/-+/g, '-');\r\n}\r\n\r\n/**\r\n * Capitaliza primeira letra de cada palavra\r\n *\r\n * @param text - Texto para capitalizar\r\n * @returns Texto capitalizado\r\n *\r\n * @example\r\n * capitalize('rainer teixeira') // 'Rainer Teixeira'\r\n */\r\nexport function capitalize(text: string): string {\r\n return text\r\n .toLowerCase()\r\n .split(' ')\r\n .map(word => word.charAt(0).toUpperCase() + word.slice(1))\r\n .join(' ');\r\n}\r\n\r\n/**\r\n * Trunca texto com ellipsis\r\n *\r\n * @param text - Texto para truncar\r\n * @param maxLength - Comprimento máximo\r\n * @param suffix - Sufixo (padrão: '...')\r\n * @returns Texto truncado\r\n *\r\n * @example\r\n * truncate('Texto muito longo', 10) // 'Texto muit...'\r\n */\r\nexport function truncate(text: string, maxLength: number, suffix = '...'): string {\r\n if (text.length <= maxLength) return text;\r\n return text.slice(0, maxLength - suffix.length) + suffix;\r\n}\r\n\r\n/**\r\n * Remove acentos de texto\r\n *\r\n * @param text - Texto com acentos\r\n * @returns Texto sem acentos\r\n *\r\n * @example\r\n * removeAccents('São Paulo') // 'Sao Paulo'\r\n */\r\nexport function removeAccents(text: string): string {\r\n return text.normalize('NFD').replace(/[\\u0300-\\u036f]/g, '');\r\n}\r\n\r\n/**\r\n * Extrai iniciais de um nome\r\n *\r\n * @param name - Nome completo\r\n * @param maxInitials - Máximo de iniciais (padrão: 2)\r\n * @returns Iniciais em maiúsculas\r\n *\r\n * @example\r\n * getInitials('Rainer Teixeira') // 'RT'\r\n * getInitials('João da Silva Santos', 3) // 'JSS'\r\n */\r\nexport function getInitials(name: string, maxInitials = 2): string {\r\n return name\r\n .split(' ')\r\n .filter(word => word.length > 0)\r\n .map(word => word[0])\r\n .join('')\r\n .toUpperCase()\r\n .slice(0, maxInitials);\r\n}\r\n\r\n/**\r\n * Valida se string é vazia ou apenas espaços\r\n *\r\n * @param text - Texto para validar\r\n * @returns true se vazio, false caso contrário\r\n */\r\nexport function isEmpty(text: string | null | undefined): boolean {\r\n return !text || text.trim().length === 0;\r\n}\r\n\r\n/**\r\n * Conta palavras em um texto\r\n *\r\n * @param text - Texto para contar\r\n * @returns Número de palavras\r\n */\r\nexport function wordCount(text: string): number {\r\n return text.trim().split(/\\s+/).filter(word => word.length > 0).length;\r\n}\r\n\r\n/**\r\n * Formata um número de telefone brasileiro\r\n * \r\n * @param phone - Número de telefone (com ou sem formatação)\r\n * @returns Telefone formatado\r\n * \r\n * @example\r\n * formatPhone('11999998888') // '(11) 99999-8888'\r\n * formatPhone('1133334444') // '(11) 3333-4444'\r\n */\r\nexport function formatPhone(phone: string): string {\r\n const digits = phone.replace(/\\D/g, '');\r\n \r\n if (digits.length === 11) {\r\n return digits.replace(/(\\d{2})(\\d{5})(\\d{4})/, '($1) $2-$3');\r\n }\r\n \r\n return digits.replace(/(\\d{2})(\\d{4})(\\d{4})/, '($1) $2-$3');\r\n}\r\n\r\n/**\r\n * Formata um CPF\r\n * \r\n * @param cpf - Número do CPF (com ou sem formatação)\r\n * @returns CPF formatado\r\n * \r\n * @example\r\n * formatCPF('12345678901') // '123.456.789-01'\r\n */\r\nexport function formatCPF(cpf: string): string {\r\n const digits = cpf.replace(/\\D/g, '');\r\n return digits.replace(/(\\d{3})(\\d{3})(\\d{3})(\\d{2})/, '$1.$2.$3-$4');\r\n}\r\n\r\n/**\r\n * Formata um CNPJ\r\n * \r\n * @param cnpj - Número do CNPJ (com ou sem formatação)\r\n * @returns CNPJ formatado\r\n * \r\n * @example\r\n * formatCNPJ('12345678000199') // '12.345.678/0001-99'\r\n */\r\nexport function formatCNPJ(cnpj: string): string {\r\n const digits = cnpj.replace(/\\D/g, '');\r\n return digits.replace(/(\\d{2})(\\d{3})(\\d{3})(\\d{4})(\\d{2})/, '$1.$2.$3/$4-$5');\r\n}\r\n\r\n/**\r\n * Valida um CPF\r\n * \r\n * @param cpf - Número do CPF (com ou sem formatação)\r\n * @returns true se válido, false caso contrário\r\n * \r\n * @example\r\n * isCPF('123.456.789-09') // false (CPF inválido)\r\n */\r\nexport function isCPF(cpf: string): boolean {\r\n const digits = cpf.replace(/\\D/g, '');\r\n \r\n if (digits.length !== 11 || /^(\\d)\\1{10}$/.test(digits)) {\r\n return false;\r\n }\r\n \r\n // Cálculo do primeiro dígito verificador\r\n let sum = 0;\r\n for (let i = 0; i < 9; i++) {\r\n sum += parseInt(digits.charAt(i)) * (10 - i);\r\n }\r\n let remainder = (sum * 10) % 11;\r\n if (remainder === 10 || remainder === 11) remainder = 0;\r\n if (remainder !== parseInt(digits.charAt(9))) {\r\n return false;\r\n }\r\n \r\n // Cálculo do segundo dígito verificador\r\n sum = 0;\r\n for (let i = 0; i < 10; i++) {\r\n sum += parseInt(digits.charAt(i)) * (11 - i);\r\n }\r\n remainder = (sum * 10) % 11;\r\n if (remainder === 10 || remainder === 11) remainder = 0;\r\n if (remainder !== parseInt(digits.charAt(10))) {\r\n return false;\r\n }\r\n \r\n return true;\r\n}\r\n\r\n/**\r\n * Valida um CNPJ\r\n * \r\n * @param cnpj - Número do CNPJ (com ou sem formatação)\r\n * @returns true se válido, false caso contrário\r\n * \r\n * @example\r\n * isCNPJ('12.345.678/0001-99') // false (CNPJ inválido)\r\n */\r\nexport function isCNPJ(cnpj: string): boolean {\r\n const digits = cnpj.replace(/\\D/g, '');\r\n \r\n if (digits.length !== 14 || /^(\\d)\\1{13}$/.test(digits)) {\r\n return false;\r\n }\r\n \r\n // Cálculo do primeiro dígito verificador\r\n const weights1 = [5,4,3,2,9,8,7,6,5,4,3,2];\r\n let sum = 0;\r\n for (let i = 0; i < 12; i++) {\r\n sum += parseInt(digits.charAt(i)) * weights1[i];\r\n }\r\n let remainder = sum % 11;\r\n const digit1 = remainder < 2 ? 0 : 11 - remainder;\r\n if (digit1 !== parseInt(digits.charAt(12))) {\r\n return false;\r\n }\r\n \r\n // Cálculo do segundo dígito verificador\r\n const weights2 = [6,5,4,3,2,9,8,7,6,5,4,3,2];\r\n sum = 0;\r\n for (let i = 0; i < 13; i++) {\r\n sum += parseInt(digits.charAt(i)) * weights2[i];\r\n }\r\n remainder = sum % 11;\r\n const digit2 = remainder < 2 ? 0 : 11 - remainder;\r\n if (digit2 !== parseInt(digits.charAt(13))) {\r\n return false;\r\n }\r\n \r\n return true;\r\n}\r\n"]}