@seniorsistemas/br-validators-front 0.0.0-stage → 1.0.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +86 -2
- package/dist/angular/index.cjs +47 -0
- package/dist/angular/index.d.cts +78 -0
- package/dist/angular/index.d.ts +78 -0
- package/dist/angular/index.js +41 -0
- package/dist/chunk-4DZAZWIX.cjs +39 -0
- package/dist/chunk-5DLDDBDT.cjs +98 -0
- package/dist/chunk-5JKMEOAQ.js +39 -0
- package/dist/chunk-AN3ZYKQD.js +25 -0
- package/dist/chunk-FEY4WHL2.js +94 -0
- package/dist/chunk-HOMHHSPE.cjs +43 -0
- package/dist/chunk-IHKI43ES.cjs +30 -0
- package/dist/chunk-IOOBBQYD.js +68 -0
- package/dist/chunk-KCKWTNIK.js +35 -0
- package/dist/chunk-KV45PLCT.js +307 -0
- package/dist/chunk-L3M6L7UZ.js +48 -0
- package/dist/chunk-LFSTXSR5.cjs +71 -0
- package/dist/chunk-LJS2LBQG.cjs +58 -0
- package/dist/chunk-R5XVY4XW.cjs +313 -0
- package/dist/cnpj/index.cjs +20 -0
- package/dist/cnpj/index.d.cts +40 -0
- package/dist/cnpj/index.d.ts +40 -0
- package/dist/cnpj/index.js +3 -0
- package/dist/cpf/index.cjs +20 -0
- package/dist/cpf/index.d.cts +37 -0
- package/dist/cpf/index.d.ts +37 -0
- package/dist/cpf/index.js +3 -0
- package/dist/email/index.cjs +15 -0
- package/dist/email/index.d.cts +41 -0
- package/dist/email/index.d.ts +41 -0
- package/dist/email/index.js +2 -0
- package/dist/ie/index.cjs +24 -0
- package/dist/ie/index.d.cts +2 -0
- package/dist/ie/index.d.ts +2 -0
- package/dist/ie/index.js +3 -0
- package/dist/index-CNJEnpK2.d.ts +54 -0
- package/dist/index-CTV05-Um.d.cts +54 -0
- package/dist/index.cjs +124 -0
- package/dist/index.d.cts +50 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.js +60 -0
- package/dist/phone/index.cjs +19 -0
- package/dist/phone/index.d.cts +13 -0
- package/dist/phone/index.d.ts +13 -0
- package/dist/phone/index.js +2 -0
- package/dist/phone-De-7zP7q.d.cts +50 -0
- package/dist/phone-WbO5c-Mz.d.ts +50 -0
- package/dist/result-BWDTMsGY.d.cts +16 -0
- package/dist/result-BWDTMsGY.d.ts +16 -0
- package/package.json +157 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Senior Sistemas S.A.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,87 @@
|
|
|
1
|
-
#
|
|
1
|
+
# br-validators-front
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Validadores leves, rápidos e **sem nenhuma dependência** para documentos brasileiros, e-mail e telefone. São funções puras e síncronas, em TypeScript, para usar antes de enviar dados ao backend ou dentro de formulários. Inclui validadores prontos para Reactive Forms do Angular.
|
|
4
|
+
|
|
5
|
+
| Validador | O que valida |
|
|
6
|
+
| ------------------------------------------------- | ------------------------------------------------------- |
|
|
7
|
+
| [CPF](src/validators/cpf/README.md) | CPF, com ou sem máscara |
|
|
8
|
+
| [CNPJ](src/validators/cnpj/README.md) | CNPJ numérico e **alfanumérico** (IN RFB nº 2.229/2024) |
|
|
9
|
+
| [Inscrição Estadual](src/validators/ie/README.md) | IE das 27 UFs, conforme os roteiros do SINTEGRA |
|
|
10
|
+
| [E-mail](src/validators/email/README.md) | E-mail, com regras pensadas para formulários |
|
|
11
|
+
| [Telefone](src/validators/phone/README.md) | Celular, fixo, 0800, 4004… e internacional |
|
|
12
|
+
| [Angular](src/angular/README.md) | `ValidatorFn` para Reactive Forms |
|
|
13
|
+
|
|
14
|
+
O pacote é _tree-shakeable_: importar só `isValidCpf` traz só o código do CPF (~0,6 kB; o pacote inteiro tem ~3,3 kB, min + brotli).
|
|
15
|
+
|
|
16
|
+
## Instalação
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @seniorsistemas/br-validators-front
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Funciona em qualquer bundler (Angular CLI, Vite, webpack, esbuild) e no Node 18+, via `import` (ESM) ou `require` (CommonJS). Os tipos vêm junto.
|
|
23
|
+
|
|
24
|
+
## Uso rápido
|
|
25
|
+
|
|
26
|
+
Cada validador tem duas funções:
|
|
27
|
+
|
|
28
|
+
- `isValidXxx(valor)` devolve `true` ou `false`;
|
|
29
|
+
- `validateXxx(valor)` devolve o valor normalizado (sem máscara) ou o código do erro.
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { isValidCpf, validateCpf } from "br-validators";
|
|
33
|
+
|
|
34
|
+
isValidCpf("529.982.247-25"); // true
|
|
35
|
+
|
|
36
|
+
validateCpf("529.982.247-25"); // { valid: true, normalizedValue: '52998224725' }
|
|
37
|
+
validateCpf("529.982.247-24"); // { valid: false, error: 'INVALID_CHECK_DIGIT' }
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
O `normalizedValue` é o que normalmente se envia ao backend.
|
|
41
|
+
|
|
42
|
+
Cada validador também pode ser importado pelo seu caminho: `br-validators/cpf`, `br-validators/cnpj`, `br-validators/ie`, `br-validators/email`, `br-validators/phone` e `br-validators/angular`.
|
|
43
|
+
|
|
44
|
+
### Regras comuns
|
|
45
|
+
|
|
46
|
+
- **Máscara.** CPF e CNPJ aceitam só duas formas: sem pontuação nenhuma ou com a máscara completa, cada sinal no lugar certo. Inscrição Estadual e telefone aceitam separadores em qualquer posição. Em todos os casos, um caractere fora do esperado torna o valor inválido: `abc529.982.247-25` não é um CPF válido.
|
|
47
|
+
- **Espaços nas pontas** são ignorados.
|
|
48
|
+
- **Valor vazio.** `null`, `undefined` e texto em branco dão o erro `EMPTY`; nenhum validador lança exceção. Valores que não são texto (como `number`) dão `INVALID_FORMAT`.
|
|
49
|
+
|
|
50
|
+
### Mensagens de erro
|
|
51
|
+
|
|
52
|
+
Os validadores devolvem códigos, não textos, o que deixa a tradução para a aplicação. Para quem quiser algo pronto, há mapas de mensagens genéricas, um por idioma:
|
|
53
|
+
|
|
54
|
+
| Mapa | Idioma |
|
|
55
|
+
| -------------- | ----------------------- |
|
|
56
|
+
| `messagesPtBr` | Português (Brasil) |
|
|
57
|
+
| `messagesEnUs` | Inglês (Estados Unidos) |
|
|
58
|
+
| `messagesEsCo` | Espanhol (Colômbia) |
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
import { messagesPtBr, validateCpf } from "br-validators";
|
|
62
|
+
|
|
63
|
+
const result = validateCpf(valor);
|
|
64
|
+
if (!result.valid) exibirErro(messagesPtBr[result.error]); // 'Dígito verificador inválido'
|
|
65
|
+
if (!result.valid) exibirErro(messagesEnUs[result.error]); // 'Invalid check digit'
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Todos os mapas têm o tipo `Messages` e cobrem todos os códigos de erro; se um código novo for criado, o TypeScript acusa o idioma que ficou sem tradução.
|
|
69
|
+
|
|
70
|
+
## Desenvolvimento
|
|
71
|
+
|
|
72
|
+
| Comando | O que faz |
|
|
73
|
+
| ----------------------- | --------------------------------------------------------------- |
|
|
74
|
+
| `npm test` | Roda os testes (Vitest) |
|
|
75
|
+
| `npm run coverage` | Roda os testes com cobertura (o limite é 100%) |
|
|
76
|
+
| `npm run lint` | ESLint com `typescript-eslint` em modo estrito |
|
|
77
|
+
| `npm run format` | Formata com Prettier |
|
|
78
|
+
| `npm run build` | Gera ESM, CJS e tipos em `dist/` (tsup) |
|
|
79
|
+
| `npm run check:exports` | Confere os `exports` e os tipos (are-the-types-wrong e publint) |
|
|
80
|
+
| `npm run bench` | Gera o build e roda o [benchmark](bench/README.md) |
|
|
81
|
+
| `npm run check` | Roda tudo acima, menos o benchmark |
|
|
82
|
+
|
|
83
|
+
Os testes ficam ao lado do código (`src/validators/<validador>/*.test.ts`). Ao mudar uma regra, atualize o teste e o README do validador.
|
|
84
|
+
|
|
85
|
+
## Licença
|
|
86
|
+
|
|
87
|
+
MIT
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunk4DZAZWIX_cjs = require('../chunk-4DZAZWIX.cjs');
|
|
4
|
+
var chunkHOMHHSPE_cjs = require('../chunk-HOMHHSPE.cjs');
|
|
5
|
+
var chunkR5XVY4XW_cjs = require('../chunk-R5XVY4XW.cjs');
|
|
6
|
+
require('../chunk-IHKI43ES.cjs');
|
|
7
|
+
var chunkLFSTXSR5_cjs = require('../chunk-LFSTXSR5.cjs');
|
|
8
|
+
var chunk5DLDDBDT_cjs = require('../chunk-5DLDDBDT.cjs');
|
|
9
|
+
require('../chunk-LJS2LBQG.cjs');
|
|
10
|
+
|
|
11
|
+
// src/angular/angular.ts
|
|
12
|
+
function createValidator(key, validate) {
|
|
13
|
+
return (control) => {
|
|
14
|
+
const { value } = control;
|
|
15
|
+
if (value == null) return null;
|
|
16
|
+
if (typeof value !== "string") return { [key]: { error: "INVALID_FORMAT" } };
|
|
17
|
+
if (value.trim() === "") return null;
|
|
18
|
+
const result = validate(value, control);
|
|
19
|
+
return result.valid ? null : { [key]: { error: result.error } };
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function cpfValidator() {
|
|
23
|
+
return createValidator("cpf", chunk4DZAZWIX_cjs.validateCpf);
|
|
24
|
+
}
|
|
25
|
+
function cnpjValidator() {
|
|
26
|
+
return createValidator("cnpj", chunkHOMHHSPE_cjs.validateCnpj);
|
|
27
|
+
}
|
|
28
|
+
function ieValidator(uf, options) {
|
|
29
|
+
const ufOf = (control) => typeof uf === "function" ? uf(control) : uf;
|
|
30
|
+
const validate = createValidator(
|
|
31
|
+
"ie",
|
|
32
|
+
(value, control) => chunkR5XVY4XW_cjs.validateIe(value, ufOf(control), options)
|
|
33
|
+
);
|
|
34
|
+
return (control) => chunkR5XVY4XW_cjs.isExterior(ufOf(control)) ? null : validate(control);
|
|
35
|
+
}
|
|
36
|
+
function emailValidator(options) {
|
|
37
|
+
return createValidator("email", (value) => chunkLFSTXSR5_cjs.validateEmail(value, options));
|
|
38
|
+
}
|
|
39
|
+
function phoneValidator(options) {
|
|
40
|
+
return createValidator("phone", (value) => chunk5DLDDBDT_cjs.validatePhone(value, options));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
exports.cnpjValidator = cnpjValidator;
|
|
44
|
+
exports.cpfValidator = cpfValidator;
|
|
45
|
+
exports.emailValidator = emailValidator;
|
|
46
|
+
exports.ieValidator = ieValidator;
|
|
47
|
+
exports.phoneValidator = phoneValidator;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { EmailOptions } from '../email/index.cjs';
|
|
2
|
+
import { IeOptions } from '../ie/index.cjs';
|
|
3
|
+
import { UfInput } from '../index.cjs';
|
|
4
|
+
import { PhoneOptions } from '../index.cjs';
|
|
5
|
+
import '../result-BWDTMsGY.cjs';
|
|
6
|
+
|
|
7
|
+
/** O mínimo de um `AbstractControl` que os validadores leem: o valor. */
|
|
8
|
+
interface ControlLike {
|
|
9
|
+
readonly value: unknown;
|
|
10
|
+
}
|
|
11
|
+
/** Compatível com `ValidationErrors` do `@angular/forms`. */
|
|
12
|
+
type ValidationErrorsLike = Record<string, unknown>;
|
|
13
|
+
/**
|
|
14
|
+
* Compatível com `ValidatorFn` do `@angular/forms`. A tipagem é estrutural, então o
|
|
15
|
+
* pacote não depende do Angular e funciona com qualquer versão de Reactive Forms.
|
|
16
|
+
*/
|
|
17
|
+
type ValidatorFnLike = (control: ControlLike) => ValidationErrorsLike | null;
|
|
18
|
+
/**
|
|
19
|
+
* UF fixa, ou função que recebe o control e devolve a UF atual (ex.: lida de outro
|
|
20
|
+
* campo do mesmo formulário). Aceita também `EX` (exterior).
|
|
21
|
+
*/
|
|
22
|
+
type UfSource<C extends ControlLike = ControlLike> = UfInput | ((control: C) => string | null | undefined);
|
|
23
|
+
/**
|
|
24
|
+
* Validador de CPF para Reactive Forms. Em caso de erro: `{ cpf: { error: CpfError } }`.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* cpf = new FormControl('', [Validators.required, cpfValidator()]);
|
|
28
|
+
*/
|
|
29
|
+
declare function cpfValidator(): ValidatorFnLike;
|
|
30
|
+
/**
|
|
31
|
+
* Validador de CNPJ (numérico ou alfanumérico) para Reactive Forms.
|
|
32
|
+
* Em caso de erro: `{ cnpj: { error: CnpjError } }`.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* cnpj = new FormControl('', [Validators.required, cnpjValidator()]);
|
|
36
|
+
*/
|
|
37
|
+
declare function cnpjValidator(): ValidatorFnLike;
|
|
38
|
+
/**
|
|
39
|
+
* Validador de Inscrição Estadual para Reactive Forms.
|
|
40
|
+
* Em caso de erro: `{ ie: { error: IeError } }`.
|
|
41
|
+
*
|
|
42
|
+
* A UF pode ser fixa ou uma função que recebe o control. Para ler a UF de outro
|
|
43
|
+
* campo, tipe o parâmetro como `AbstractControl` e use `control.parent`. Nesse caso:
|
|
44
|
+
* - revalide a IE quando a UF mudar;
|
|
45
|
+
* - se o formulário já nasce com a IE preenchida, revalide também logo após criá-lo,
|
|
46
|
+
* porque o Angular valida cada control antes de ele entrar no `FormGroup`.
|
|
47
|
+
*
|
|
48
|
+
* Com a UF `EX` (exterior), o campo é sempre válido.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* form = new FormGroup({
|
|
52
|
+
* uf: new FormControl('SP'),
|
|
53
|
+
* ie: new FormControl('', [
|
|
54
|
+
* ieValidator((control: AbstractControl) => control.parent?.get('uf')?.value),
|
|
55
|
+
* ]),
|
|
56
|
+
* });
|
|
57
|
+
* form.controls.uf.valueChanges.subscribe(() => form.controls.ie.updateValueAndValidity());
|
|
58
|
+
*/
|
|
59
|
+
declare function ieValidator<C extends ControlLike = ControlLike>(uf: UfSource<C>, options?: IeOptions): (control: C) => ValidationErrorsLike | null;
|
|
60
|
+
/**
|
|
61
|
+
* Validador de e-mail para Reactive Forms (mais rigoroso que o `Validators.email`).
|
|
62
|
+
* Em caso de erro: `{ email: { error: EmailError } }`.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* email = new FormControl('', [emailValidator()]);
|
|
66
|
+
*/
|
|
67
|
+
declare function emailValidator(options?: EmailOptions): ValidatorFnLike;
|
|
68
|
+
/**
|
|
69
|
+
* Validador de telefone para Reactive Forms.
|
|
70
|
+
* Em caso de erro: `{ phone: { error: PhoneError } }`.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* celular = new FormControl('', [phoneValidator({ types: ['mobile'] })]);
|
|
74
|
+
*/
|
|
75
|
+
declare function phoneValidator(options?: PhoneOptions): ValidatorFnLike;
|
|
76
|
+
|
|
77
|
+
export { cnpjValidator, cpfValidator, emailValidator, ieValidator, phoneValidator };
|
|
78
|
+
export type { ControlLike, UfSource, ValidationErrorsLike, ValidatorFnLike };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { EmailOptions } from '../email/index.js';
|
|
2
|
+
import { IeOptions } from '../ie/index.js';
|
|
3
|
+
import { UfInput } from '../index.js';
|
|
4
|
+
import { PhoneOptions } from '../index.js';
|
|
5
|
+
import '../result-BWDTMsGY.js';
|
|
6
|
+
|
|
7
|
+
/** O mínimo de um `AbstractControl` que os validadores leem: o valor. */
|
|
8
|
+
interface ControlLike {
|
|
9
|
+
readonly value: unknown;
|
|
10
|
+
}
|
|
11
|
+
/** Compatível com `ValidationErrors` do `@angular/forms`. */
|
|
12
|
+
type ValidationErrorsLike = Record<string, unknown>;
|
|
13
|
+
/**
|
|
14
|
+
* Compatível com `ValidatorFn` do `@angular/forms`. A tipagem é estrutural, então o
|
|
15
|
+
* pacote não depende do Angular e funciona com qualquer versão de Reactive Forms.
|
|
16
|
+
*/
|
|
17
|
+
type ValidatorFnLike = (control: ControlLike) => ValidationErrorsLike | null;
|
|
18
|
+
/**
|
|
19
|
+
* UF fixa, ou função que recebe o control e devolve a UF atual (ex.: lida de outro
|
|
20
|
+
* campo do mesmo formulário). Aceita também `EX` (exterior).
|
|
21
|
+
*/
|
|
22
|
+
type UfSource<C extends ControlLike = ControlLike> = UfInput | ((control: C) => string | null | undefined);
|
|
23
|
+
/**
|
|
24
|
+
* Validador de CPF para Reactive Forms. Em caso de erro: `{ cpf: { error: CpfError } }`.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* cpf = new FormControl('', [Validators.required, cpfValidator()]);
|
|
28
|
+
*/
|
|
29
|
+
declare function cpfValidator(): ValidatorFnLike;
|
|
30
|
+
/**
|
|
31
|
+
* Validador de CNPJ (numérico ou alfanumérico) para Reactive Forms.
|
|
32
|
+
* Em caso de erro: `{ cnpj: { error: CnpjError } }`.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* cnpj = new FormControl('', [Validators.required, cnpjValidator()]);
|
|
36
|
+
*/
|
|
37
|
+
declare function cnpjValidator(): ValidatorFnLike;
|
|
38
|
+
/**
|
|
39
|
+
* Validador de Inscrição Estadual para Reactive Forms.
|
|
40
|
+
* Em caso de erro: `{ ie: { error: IeError } }`.
|
|
41
|
+
*
|
|
42
|
+
* A UF pode ser fixa ou uma função que recebe o control. Para ler a UF de outro
|
|
43
|
+
* campo, tipe o parâmetro como `AbstractControl` e use `control.parent`. Nesse caso:
|
|
44
|
+
* - revalide a IE quando a UF mudar;
|
|
45
|
+
* - se o formulário já nasce com a IE preenchida, revalide também logo após criá-lo,
|
|
46
|
+
* porque o Angular valida cada control antes de ele entrar no `FormGroup`.
|
|
47
|
+
*
|
|
48
|
+
* Com a UF `EX` (exterior), o campo é sempre válido.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* form = new FormGroup({
|
|
52
|
+
* uf: new FormControl('SP'),
|
|
53
|
+
* ie: new FormControl('', [
|
|
54
|
+
* ieValidator((control: AbstractControl) => control.parent?.get('uf')?.value),
|
|
55
|
+
* ]),
|
|
56
|
+
* });
|
|
57
|
+
* form.controls.uf.valueChanges.subscribe(() => form.controls.ie.updateValueAndValidity());
|
|
58
|
+
*/
|
|
59
|
+
declare function ieValidator<C extends ControlLike = ControlLike>(uf: UfSource<C>, options?: IeOptions): (control: C) => ValidationErrorsLike | null;
|
|
60
|
+
/**
|
|
61
|
+
* Validador de e-mail para Reactive Forms (mais rigoroso que o `Validators.email`).
|
|
62
|
+
* Em caso de erro: `{ email: { error: EmailError } }`.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* email = new FormControl('', [emailValidator()]);
|
|
66
|
+
*/
|
|
67
|
+
declare function emailValidator(options?: EmailOptions): ValidatorFnLike;
|
|
68
|
+
/**
|
|
69
|
+
* Validador de telefone para Reactive Forms.
|
|
70
|
+
* Em caso de erro: `{ phone: { error: PhoneError } }`.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* celular = new FormControl('', [phoneValidator({ types: ['mobile'] })]);
|
|
74
|
+
*/
|
|
75
|
+
declare function phoneValidator(options?: PhoneOptions): ValidatorFnLike;
|
|
76
|
+
|
|
77
|
+
export { cnpjValidator, cpfValidator, emailValidator, ieValidator, phoneValidator };
|
|
78
|
+
export type { ControlLike, UfSource, ValidationErrorsLike, ValidatorFnLike };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { validateCpf } from '../chunk-KCKWTNIK.js';
|
|
2
|
+
import { validateCnpj } from '../chunk-5JKMEOAQ.js';
|
|
3
|
+
import { isExterior, validateIe } from '../chunk-KV45PLCT.js';
|
|
4
|
+
import '../chunk-AN3ZYKQD.js';
|
|
5
|
+
import { validateEmail } from '../chunk-IOOBBQYD.js';
|
|
6
|
+
import { validatePhone } from '../chunk-FEY4WHL2.js';
|
|
7
|
+
import '../chunk-L3M6L7UZ.js';
|
|
8
|
+
|
|
9
|
+
// src/angular/angular.ts
|
|
10
|
+
function createValidator(key, validate) {
|
|
11
|
+
return (control) => {
|
|
12
|
+
const { value } = control;
|
|
13
|
+
if (value == null) return null;
|
|
14
|
+
if (typeof value !== "string") return { [key]: { error: "INVALID_FORMAT" } };
|
|
15
|
+
if (value.trim() === "") return null;
|
|
16
|
+
const result = validate(value, control);
|
|
17
|
+
return result.valid ? null : { [key]: { error: result.error } };
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function cpfValidator() {
|
|
21
|
+
return createValidator("cpf", validateCpf);
|
|
22
|
+
}
|
|
23
|
+
function cnpjValidator() {
|
|
24
|
+
return createValidator("cnpj", validateCnpj);
|
|
25
|
+
}
|
|
26
|
+
function ieValidator(uf, options) {
|
|
27
|
+
const ufOf = (control) => typeof uf === "function" ? uf(control) : uf;
|
|
28
|
+
const validate = createValidator(
|
|
29
|
+
"ie",
|
|
30
|
+
(value, control) => validateIe(value, ufOf(control), options)
|
|
31
|
+
);
|
|
32
|
+
return (control) => isExterior(ufOf(control)) ? null : validate(control);
|
|
33
|
+
}
|
|
34
|
+
function emailValidator(options) {
|
|
35
|
+
return createValidator("email", (value) => validateEmail(value, options));
|
|
36
|
+
}
|
|
37
|
+
function phoneValidator(options) {
|
|
38
|
+
return createValidator("phone", (value) => validatePhone(value, options));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export { cnpjValidator, cpfValidator, emailValidator, ieValidator, phoneValidator };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkIHKI43ES_cjs = require('./chunk-IHKI43ES.cjs');
|
|
4
|
+
var chunkLJS2LBQG_cjs = require('./chunk-LJS2LBQG.cjs');
|
|
5
|
+
|
|
6
|
+
// src/validators/cpf/cpf.ts
|
|
7
|
+
function hasMask(input) {
|
|
8
|
+
return input.length === 14 && input.charCodeAt(3) === chunkLJS2LBQG_cjs.DOT && input.charCodeAt(7) === chunkLJS2LBQG_cjs.DOT && input.charCodeAt(11) === chunkLJS2LBQG_cjs.HYPHEN;
|
|
9
|
+
}
|
|
10
|
+
function parseCpf(value) {
|
|
11
|
+
const input = chunkLJS2LBQG_cjs.readInput(value);
|
|
12
|
+
if (typeof input !== "string") return input;
|
|
13
|
+
const cpf = chunkLJS2LBQG_cjs.stripMask(input, chunkLJS2LBQG_cjs.isDigit);
|
|
14
|
+
if (cpf === null) return chunkLJS2LBQG_cjs.invalid("INVALID_FORMAT");
|
|
15
|
+
if (cpf.length !== 11) return chunkLJS2LBQG_cjs.invalid("INVALID_LENGTH");
|
|
16
|
+
if (cpf !== input && !hasMask(input)) return chunkLJS2LBQG_cjs.invalid("INVALID_FORMAT");
|
|
17
|
+
return cpf;
|
|
18
|
+
}
|
|
19
|
+
function validateCpf(value) {
|
|
20
|
+
const cpf = parseCpf(value);
|
|
21
|
+
if (typeof cpf !== "string") return cpf;
|
|
22
|
+
if (chunkLJS2LBQG_cjs.allSameChar(cpf)) return chunkLJS2LBQG_cjs.invalid("REPEATED_DIGITS");
|
|
23
|
+
if (chunkIHKI43ES_cjs.mod11(chunkIHKI43ES_cjs.cycleSum(cpf, 9, 11)) !== chunkIHKI43ES_cjs.charValue(cpf, 9) || chunkIHKI43ES_cjs.mod11(chunkIHKI43ES_cjs.cycleSum(cpf, 10, 11)) !== chunkIHKI43ES_cjs.charValue(cpf, 10)) {
|
|
24
|
+
return chunkLJS2LBQG_cjs.invalid("INVALID_CHECK_DIGIT");
|
|
25
|
+
}
|
|
26
|
+
return { valid: true, normalizedValue: cpf };
|
|
27
|
+
}
|
|
28
|
+
function isValidCpf(value) {
|
|
29
|
+
return validateCpf(value).valid;
|
|
30
|
+
}
|
|
31
|
+
function formatCpf(value) {
|
|
32
|
+
const cpf = parseCpf(value);
|
|
33
|
+
if (typeof cpf !== "string") return null;
|
|
34
|
+
return `${cpf.slice(0, 3)}.${cpf.slice(3, 6)}.${cpf.slice(6, 9)}-${cpf.slice(9)}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
exports.formatCpf = formatCpf;
|
|
38
|
+
exports.isValidCpf = isValidCpf;
|
|
39
|
+
exports.validateCpf = validateCpf;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkLJS2LBQG_cjs = require('./chunk-LJS2LBQG.cjs');
|
|
4
|
+
|
|
5
|
+
// src/validators/phone/ddd.ts
|
|
6
|
+
var DDDS = "11 12 13 14 15 16 17 18 19 21 22 24 27 28 31 32 33 34 35 37 38 41 42 43 44 45 46 47 48 49 51 53 54 55 61 62 63 64 65 66 67 68 69 71 73 74 75 77 79 81 82 83 84 85 86 87 88 89 91 92 93 94 95 96 97 98 99";
|
|
7
|
+
var dddSet;
|
|
8
|
+
function isValidDdd(ddd) {
|
|
9
|
+
dddSet != null ? dddSet : dddSet = new Set(DDDS.split(" "));
|
|
10
|
+
return dddSet.has(ddd);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// src/validators/phone/country-codes.ts
|
|
14
|
+
var COUNTRY_CODES = "1 7 20 27 30 31 32 33 34 36 39 40 41 43 44 45 46 47 48 49 51 52 53 54 55 56 57 58 60 61 62 63 64 65 66 81 82 84 86 90 91 92 93 94 95 98 211 212 213 216 218 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 260 261 262 263 264 265 266 267 268 269 290 291 297 298 299 350 351 352 353 354 355 356 357 358 359 370 371 372 373 374 375 376 377 378 379 380 381 382 383 385 386 387 389 420 421 423 500 501 502 503 504 505 506 507 508 509 590 591 592 593 594 595 596 597 598 599 670 672 673 674 675 676 677 678 679 680 681 682 683 685 686 687 688 689 690 691 692 800 808 850 852 853 855 856 870 878 880 881 882 883 886 888 960 961 962 963 964 965 966 967 968 970 971 972 973 974 975 976 977 979 992 993 994 995 996 997 998";
|
|
15
|
+
var countryCodeSet;
|
|
16
|
+
function findCountryCode(digits) {
|
|
17
|
+
countryCodeSet != null ? countryCodeSet : countryCodeSet = new Set(COUNTRY_CODES.split(" "));
|
|
18
|
+
for (let length = 1; length <= 3 && length <= digits.length; length++) {
|
|
19
|
+
const code = digits.slice(0, length);
|
|
20
|
+
if (countryCodeSet.has(code)) return code;
|
|
21
|
+
}
|
|
22
|
+
return void 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// src/validators/phone/phone.ts
|
|
26
|
+
var BRAZIL = "55";
|
|
27
|
+
var E164_MAX_LENGTH = 15;
|
|
28
|
+
var INTERNATIONAL_MIN_NATIONAL_LENGTH = 4;
|
|
29
|
+
var SPECIAL_PREFIXES = ["0300", "0303", "0500", "0800", "0900"];
|
|
30
|
+
var PLUS = 43;
|
|
31
|
+
function isPhoneSeparator(code) {
|
|
32
|
+
return code === 32 || code === chunkLJS2LBQG_cjs.DOT || code === chunkLJS2LBQG_cjs.HYPHEN || code === 40 || code === 41;
|
|
33
|
+
}
|
|
34
|
+
function extractDigits(input, start) {
|
|
35
|
+
let out = "";
|
|
36
|
+
let runStart = start;
|
|
37
|
+
for (let i = start; i < input.length; i++) {
|
|
38
|
+
const code = input.charCodeAt(i);
|
|
39
|
+
if (chunkLJS2LBQG_cjs.isDigit(code)) continue;
|
|
40
|
+
if (!isPhoneSeparator(code)) return null;
|
|
41
|
+
out += input.slice(runStart, i);
|
|
42
|
+
runStart = i + 1;
|
|
43
|
+
}
|
|
44
|
+
return out + input.slice(runStart);
|
|
45
|
+
}
|
|
46
|
+
function validatePhone(value, options) {
|
|
47
|
+
const input = chunkLJS2LBQG_cjs.readInput(value);
|
|
48
|
+
if (typeof input !== "string") return input;
|
|
49
|
+
const hasPlus = input.charCodeAt(0) === PLUS;
|
|
50
|
+
const digits = extractDigits(input, hasPlus ? 1 : 0);
|
|
51
|
+
if (digits === null || digits === "") return chunkLJS2LBQG_cjs.invalid("INVALID_FORMAT");
|
|
52
|
+
const result = hasPlus ? parseInternational(digits) : parseBrazilian(digits);
|
|
53
|
+
if (result.valid && (options == null ? void 0 : options.types) && !options.types.includes(result.type)) {
|
|
54
|
+
return chunkLJS2LBQG_cjs.invalid("TYPE_NOT_ALLOWED");
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
function isValidPhone(value, options) {
|
|
59
|
+
return validatePhone(value, options).valid;
|
|
60
|
+
}
|
|
61
|
+
function parseInternational(digits) {
|
|
62
|
+
const countryCode = findCountryCode(digits);
|
|
63
|
+
if (countryCode === void 0) return chunkLJS2LBQG_cjs.invalid("INVALID_COUNTRY_CODE");
|
|
64
|
+
if (countryCode === BRAZIL) return parseNational(digits.slice(2));
|
|
65
|
+
if (digits.length > E164_MAX_LENGTH || digits.length - countryCode.length < INTERNATIONAL_MIN_NATIONAL_LENGTH) {
|
|
66
|
+
return chunkLJS2LBQG_cjs.invalid("INVALID_LENGTH");
|
|
67
|
+
}
|
|
68
|
+
return { valid: true, normalizedValue: `+${digits}`, type: "international" };
|
|
69
|
+
}
|
|
70
|
+
function parseBrazilian(digits) {
|
|
71
|
+
const length = digits.length;
|
|
72
|
+
if (length === 11 && SPECIAL_PREFIXES.includes(digits.slice(0, 4))) {
|
|
73
|
+
return { valid: true, normalizedValue: digits, type: "special" };
|
|
74
|
+
}
|
|
75
|
+
if (length === 8 && (digits.startsWith("30") || digits.startsWith("40"))) {
|
|
76
|
+
return { valid: true, normalizedValue: digits, type: "special" };
|
|
77
|
+
}
|
|
78
|
+
if ((length === 12 || length === 13) && digits.startsWith(BRAZIL)) {
|
|
79
|
+
return parseNational(digits.slice(2));
|
|
80
|
+
}
|
|
81
|
+
if ((length === 11 || length === 12) && digits.startsWith("0")) {
|
|
82
|
+
return parseNational(digits.slice(1));
|
|
83
|
+
}
|
|
84
|
+
return parseNational(digits);
|
|
85
|
+
}
|
|
86
|
+
function parseNational(national) {
|
|
87
|
+
if (national.length !== 10 && national.length !== 11) return chunkLJS2LBQG_cjs.invalid("INVALID_LENGTH");
|
|
88
|
+
if (!isValidDdd(national.slice(0, 2))) return chunkLJS2LBQG_cjs.invalid("INVALID_DDD");
|
|
89
|
+
const first = national.charAt(2);
|
|
90
|
+
if (national.length === 11) {
|
|
91
|
+
return first === "9" ? { valid: true, normalizedValue: `+55${national}`, type: "mobile" } : chunkLJS2LBQG_cjs.invalid("INVALID_NUMBER");
|
|
92
|
+
}
|
|
93
|
+
return first >= "2" && first <= "5" ? { valid: true, normalizedValue: `+55${national}`, type: "landline" } : chunkLJS2LBQG_cjs.invalid("INVALID_NUMBER");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
exports.isValidDdd = isValidDdd;
|
|
97
|
+
exports.isValidPhone = isValidPhone;
|
|
98
|
+
exports.validatePhone = validatePhone;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { mod11, cycleSum, charValue } from './chunk-AN3ZYKQD.js';
|
|
2
|
+
import { allSameChar, invalid, readInput, stripMask, isAsciiAlphanumeric, isDigit, DOT, SLASH, HYPHEN } from './chunk-L3M6L7UZ.js';
|
|
3
|
+
|
|
4
|
+
// src/validators/cnpj/cnpj.ts
|
|
5
|
+
function hasMask(input) {
|
|
6
|
+
return input.length === 18 && input.charCodeAt(2) === DOT && input.charCodeAt(6) === DOT && input.charCodeAt(10) === SLASH && input.charCodeAt(15) === HYPHEN;
|
|
7
|
+
}
|
|
8
|
+
function parseCnpj(value) {
|
|
9
|
+
const input = readInput(value);
|
|
10
|
+
if (typeof input !== "string") return input;
|
|
11
|
+
const stripped = stripMask(input, isAsciiAlphanumeric);
|
|
12
|
+
if (stripped === null) return invalid("INVALID_FORMAT");
|
|
13
|
+
if (stripped.length !== 14) return invalid("INVALID_LENGTH");
|
|
14
|
+
if (stripped !== input && !hasMask(input)) return invalid("INVALID_FORMAT");
|
|
15
|
+
const cnpj = stripped.toUpperCase();
|
|
16
|
+
if (!isDigit(cnpj.charCodeAt(12)) || !isDigit(cnpj.charCodeAt(13))) {
|
|
17
|
+
return invalid("INVALID_FORMAT");
|
|
18
|
+
}
|
|
19
|
+
return cnpj;
|
|
20
|
+
}
|
|
21
|
+
function validateCnpj(value) {
|
|
22
|
+
const cnpj = parseCnpj(value);
|
|
23
|
+
if (typeof cnpj !== "string") return cnpj;
|
|
24
|
+
if (allSameChar(cnpj)) return invalid("REPEATED_DIGITS");
|
|
25
|
+
if (mod11(cycleSum(cnpj, 12)) !== charValue(cnpj, 12) || mod11(cycleSum(cnpj, 13)) !== charValue(cnpj, 13)) {
|
|
26
|
+
return invalid("INVALID_CHECK_DIGIT");
|
|
27
|
+
}
|
|
28
|
+
return { valid: true, normalizedValue: cnpj };
|
|
29
|
+
}
|
|
30
|
+
function isValidCnpj(value) {
|
|
31
|
+
return validateCnpj(value).valid;
|
|
32
|
+
}
|
|
33
|
+
function formatCnpj(value) {
|
|
34
|
+
const cnpj = parseCnpj(value);
|
|
35
|
+
if (typeof cnpj !== "string") return null;
|
|
36
|
+
return `${cnpj.slice(0, 2)}.${cnpj.slice(2, 5)}.${cnpj.slice(5, 8)}/${cnpj.slice(8, 12)}-${cnpj.slice(12)}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export { formatCnpj, isValidCnpj, validateCnpj };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// src/shared/check-digit.ts
|
|
2
|
+
function charValue(s, i) {
|
|
3
|
+
return s.charCodeAt(i) - 48;
|
|
4
|
+
}
|
|
5
|
+
function cycleSum(s, count, maxWeight = 9) {
|
|
6
|
+
let sum = 0;
|
|
7
|
+
let weight = 2;
|
|
8
|
+
for (let i = count - 1; i >= 0; i--) {
|
|
9
|
+
sum += charValue(s, i) * weight;
|
|
10
|
+
weight = weight === maxWeight ? 2 : weight + 1;
|
|
11
|
+
}
|
|
12
|
+
return sum;
|
|
13
|
+
}
|
|
14
|
+
function weightedSum(s, weights) {
|
|
15
|
+
let sum = 0;
|
|
16
|
+
let i = 0;
|
|
17
|
+
for (const weight of weights) sum += charValue(s, i++) * weight;
|
|
18
|
+
return sum;
|
|
19
|
+
}
|
|
20
|
+
function mod11(sum) {
|
|
21
|
+
const rest = sum % 11;
|
|
22
|
+
return rest < 2 ? 0 : 11 - rest;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export { charValue, cycleSum, mod11, weightedSum };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { readInput, invalid, isDigit, DOT, HYPHEN } from './chunk-L3M6L7UZ.js';
|
|
2
|
+
|
|
3
|
+
// src/validators/phone/ddd.ts
|
|
4
|
+
var DDDS = "11 12 13 14 15 16 17 18 19 21 22 24 27 28 31 32 33 34 35 37 38 41 42 43 44 45 46 47 48 49 51 53 54 55 61 62 63 64 65 66 67 68 69 71 73 74 75 77 79 81 82 83 84 85 86 87 88 89 91 92 93 94 95 96 97 98 99";
|
|
5
|
+
var dddSet;
|
|
6
|
+
function isValidDdd(ddd) {
|
|
7
|
+
dddSet != null ? dddSet : dddSet = new Set(DDDS.split(" "));
|
|
8
|
+
return dddSet.has(ddd);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// src/validators/phone/country-codes.ts
|
|
12
|
+
var COUNTRY_CODES = "1 7 20 27 30 31 32 33 34 36 39 40 41 43 44 45 46 47 48 49 51 52 53 54 55 56 57 58 60 61 62 63 64 65 66 81 82 84 86 90 91 92 93 94 95 98 211 212 213 216 218 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 260 261 262 263 264 265 266 267 268 269 290 291 297 298 299 350 351 352 353 354 355 356 357 358 359 370 371 372 373 374 375 376 377 378 379 380 381 382 383 385 386 387 389 420 421 423 500 501 502 503 504 505 506 507 508 509 590 591 592 593 594 595 596 597 598 599 670 672 673 674 675 676 677 678 679 680 681 682 683 685 686 687 688 689 690 691 692 800 808 850 852 853 855 856 870 878 880 881 882 883 886 888 960 961 962 963 964 965 966 967 968 970 971 972 973 974 975 976 977 979 992 993 994 995 996 997 998";
|
|
13
|
+
var countryCodeSet;
|
|
14
|
+
function findCountryCode(digits) {
|
|
15
|
+
countryCodeSet != null ? countryCodeSet : countryCodeSet = new Set(COUNTRY_CODES.split(" "));
|
|
16
|
+
for (let length = 1; length <= 3 && length <= digits.length; length++) {
|
|
17
|
+
const code = digits.slice(0, length);
|
|
18
|
+
if (countryCodeSet.has(code)) return code;
|
|
19
|
+
}
|
|
20
|
+
return void 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// src/validators/phone/phone.ts
|
|
24
|
+
var BRAZIL = "55";
|
|
25
|
+
var E164_MAX_LENGTH = 15;
|
|
26
|
+
var INTERNATIONAL_MIN_NATIONAL_LENGTH = 4;
|
|
27
|
+
var SPECIAL_PREFIXES = ["0300", "0303", "0500", "0800", "0900"];
|
|
28
|
+
var PLUS = 43;
|
|
29
|
+
function isPhoneSeparator(code) {
|
|
30
|
+
return code === 32 || code === DOT || code === HYPHEN || code === 40 || code === 41;
|
|
31
|
+
}
|
|
32
|
+
function extractDigits(input, start) {
|
|
33
|
+
let out = "";
|
|
34
|
+
let runStart = start;
|
|
35
|
+
for (let i = start; i < input.length; i++) {
|
|
36
|
+
const code = input.charCodeAt(i);
|
|
37
|
+
if (isDigit(code)) continue;
|
|
38
|
+
if (!isPhoneSeparator(code)) return null;
|
|
39
|
+
out += input.slice(runStart, i);
|
|
40
|
+
runStart = i + 1;
|
|
41
|
+
}
|
|
42
|
+
return out + input.slice(runStart);
|
|
43
|
+
}
|
|
44
|
+
function validatePhone(value, options) {
|
|
45
|
+
const input = readInput(value);
|
|
46
|
+
if (typeof input !== "string") return input;
|
|
47
|
+
const hasPlus = input.charCodeAt(0) === PLUS;
|
|
48
|
+
const digits = extractDigits(input, hasPlus ? 1 : 0);
|
|
49
|
+
if (digits === null || digits === "") return invalid("INVALID_FORMAT");
|
|
50
|
+
const result = hasPlus ? parseInternational(digits) : parseBrazilian(digits);
|
|
51
|
+
if (result.valid && (options == null ? void 0 : options.types) && !options.types.includes(result.type)) {
|
|
52
|
+
return invalid("TYPE_NOT_ALLOWED");
|
|
53
|
+
}
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
function isValidPhone(value, options) {
|
|
57
|
+
return validatePhone(value, options).valid;
|
|
58
|
+
}
|
|
59
|
+
function parseInternational(digits) {
|
|
60
|
+
const countryCode = findCountryCode(digits);
|
|
61
|
+
if (countryCode === void 0) return invalid("INVALID_COUNTRY_CODE");
|
|
62
|
+
if (countryCode === BRAZIL) return parseNational(digits.slice(2));
|
|
63
|
+
if (digits.length > E164_MAX_LENGTH || digits.length - countryCode.length < INTERNATIONAL_MIN_NATIONAL_LENGTH) {
|
|
64
|
+
return invalid("INVALID_LENGTH");
|
|
65
|
+
}
|
|
66
|
+
return { valid: true, normalizedValue: `+${digits}`, type: "international" };
|
|
67
|
+
}
|
|
68
|
+
function parseBrazilian(digits) {
|
|
69
|
+
const length = digits.length;
|
|
70
|
+
if (length === 11 && SPECIAL_PREFIXES.includes(digits.slice(0, 4))) {
|
|
71
|
+
return { valid: true, normalizedValue: digits, type: "special" };
|
|
72
|
+
}
|
|
73
|
+
if (length === 8 && (digits.startsWith("30") || digits.startsWith("40"))) {
|
|
74
|
+
return { valid: true, normalizedValue: digits, type: "special" };
|
|
75
|
+
}
|
|
76
|
+
if ((length === 12 || length === 13) && digits.startsWith(BRAZIL)) {
|
|
77
|
+
return parseNational(digits.slice(2));
|
|
78
|
+
}
|
|
79
|
+
if ((length === 11 || length === 12) && digits.startsWith("0")) {
|
|
80
|
+
return parseNational(digits.slice(1));
|
|
81
|
+
}
|
|
82
|
+
return parseNational(digits);
|
|
83
|
+
}
|
|
84
|
+
function parseNational(national) {
|
|
85
|
+
if (national.length !== 10 && national.length !== 11) return invalid("INVALID_LENGTH");
|
|
86
|
+
if (!isValidDdd(national.slice(0, 2))) return invalid("INVALID_DDD");
|
|
87
|
+
const first = national.charAt(2);
|
|
88
|
+
if (national.length === 11) {
|
|
89
|
+
return first === "9" ? { valid: true, normalizedValue: `+55${national}`, type: "mobile" } : invalid("INVALID_NUMBER");
|
|
90
|
+
}
|
|
91
|
+
return first >= "2" && first <= "5" ? { valid: true, normalizedValue: `+55${national}`, type: "landline" } : invalid("INVALID_NUMBER");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export { isValidDdd, isValidPhone, validatePhone };
|