@kigi/components 1.62.7-beta.1 → 1.62.8-beta.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kigi/components",
3
- "version": "1.62.7-beta.1",
3
+ "version": "1.62.8-beta.1",
4
4
  "description": "@kigi/components",
5
5
  "main": "src/components/index.ts",
6
6
  "scripts": {
@@ -1,82 +1,76 @@
1
1
  import './mbg-input-cnpj.scss'
2
2
  import template from './mbg-input-cnpj.html'
3
+ import {
4
+ normalizeAlphanumericCnpj,
5
+ formatAlphanumericCnpj,
6
+ isValidAlphanumericCnpj,
7
+ mbgDocumentMaskDirective,
8
+ } from '../../helpers/mbg-document-mask-directive'
9
+ import { isValidCnpj } from '../../helpers/cnpj-validador'
3
10
 
4
11
  class MbgInputCnpjController {
5
- private ngChange
6
- private ngModel
7
- private ngRequired
8
- private ngDisabled
9
- private props
10
- public valid = true
12
+ private ngChange
13
+ private ngModel
14
+ private ngRequired
15
+ private ngDisabled
16
+ private props
17
+ private allowAlphanumeric: boolean
18
+ public valid = true
11
19
 
12
- constructor(public $scope, public $element, public $attrs) {
13
- if ($attrs.ngRequired === '') {
14
- this.ngRequired = true
15
- }
16
- if ($attrs.ngDisabled === '') {
17
- this.ngDisabled = true
18
- }
19
- this.props = {
20
- placeholder: $attrs.placeholder || '',
21
- }
20
+ constructor(
21
+ public $scope,
22
+ public $element,
23
+ public $attrs,
24
+ ) {
25
+ if ($attrs.ngRequired === '') {
26
+ this.ngRequired = true
22
27
  }
23
-
24
- ngBlur(evt) {
25
- this.valid = this.validaCnpj(evt.$event.target.value)
28
+ if ($attrs.ngDisabled === '') {
29
+ this.ngDisabled = true
26
30
  }
27
-
28
- validaCnpj(value) {
29
- let original = value
30
- let firstNumbers = value.substr(0, 12)
31
- let firstCalc = this.CalcDigits(firstNumbers, 5)
32
- let secondCalc = this.CalcDigits(firstCalc, 6)
33
- if (secondCalc === original) {
34
- return true
35
- }
36
- return false
31
+ this.props = {
32
+ placeholder: $attrs.placeholder || '',
37
33
  }
34
+ }
35
+
36
+ $onInit() {
37
+ this.allowAlphanumeric = this.allowAlphanumeric === true
38
+ }
39
+
40
+ ngBlur(evt) {
41
+ const raw = evt.$event.target.value
42
+ const clean = normalizeAlphanumericCnpj(raw)
38
43
 
39
- CalcDigits(digits, positions = 10, sumDigits = 0) {
40
- digits = digits.toString()
41
- for (let i = 0; i < digits.length; i++) {
42
- sumDigits = sumDigits + (digits[i] * positions)
43
- positions--
44
- if (positions < 2) {
45
- positions = 9
46
- }
47
- }
48
- sumDigits = sumDigits % 11
49
- if (sumDigits < 2) {
50
- sumDigits = 0
51
- } else {
52
- sumDigits = 11 - sumDigits
53
- }
54
- let cnpj = digits + sumDigits
55
- return cnpj
44
+ if (clean.length === 14) {
45
+ this.valid = isValidAlphanumericCnpj(clean)
46
+ } else {
47
+ this.valid = isValidCnpj(clean)
56
48
  }
49
+ }
57
50
 
58
- onChange() {
59
- if (this.ngChange) {
60
- this.ngChange({})
61
- }
51
+ onChange() {
52
+ if (this.ngChange) {
53
+ this.ngChange({})
62
54
  }
55
+ }
63
56
  }
64
57
  MbgInputCnpjController.$inject = ['$scope', '$element', '$attrs']
65
58
 
66
59
  const mbgInputCnpj = {
67
- bindings: {
68
- ngModel: '=',
69
- ngChange: '&?',
70
- ngRequired: '=?',
71
- ngDisabled: '=?',
72
- ngBlur: '&?',
73
- ngFocus: '&?',
74
- ngKeyup: '&?',
75
- ngKeypress: '&?',
76
- ngKeydown: '&?',
77
- },
78
- template,
79
- controller: MbgInputCnpjController,
60
+ bindings: {
61
+ ngModel: '=',
62
+ ngChange: '&?',
63
+ ngRequired: '=?',
64
+ ngDisabled: '=?',
65
+ ngBlur: '&?',
66
+ ngFocus: '&?',
67
+ ngKeyup: '&?',
68
+ ngKeypress: '&?',
69
+ ngKeydown: '&?',
70
+ allowAlphanumeric: '<?',
71
+ },
72
+ template,
73
+ controller: MbgInputCnpjController,
80
74
  }
81
75
 
82
- export { mbgInputCnpj }
76
+ export { mbgInputCnpj, mbgDocumentMaskDirective }
@@ -1,10 +1,10 @@
1
1
  import * as angular from 'angular'
2
- import { mbgInputCpfCnpj, mbgAlphanumericCnpjMask } from './mbg-input-cpfcnpj'
2
+ import { mbgInputCpfCnpj, mbgDocumentMaskDirective } from './mbg-input-cpfcnpj'
3
3
 
4
4
  const mbgInputCpfCnpjModule = angular
5
5
  .module('mbg.components.mbgInputCpfCnpj', [])
6
6
  .component('mbgInputCpfCnpj', mbgInputCpfCnpj)
7
- .directive('mbgAlphanumericCnpjMask', mbgAlphanumericCnpjMask)
7
+ .directive('mbgAlphanumericCnpjMask', mbgDocumentMaskDirective)
8
8
  .name
9
9
 
10
10
  export { mbgInputCpfCnpjModule }
@@ -1,210 +1,13 @@
1
1
  import './mbg-input-cpfcnpj.scss'
2
2
  import template from './mbg-input-cpfcnpj.html'
3
+ import { mbgDocumentMaskDirective } from '../../helpers/mbg-document-mask-directive'
4
+ import { isValidCnpj } from '../../helpers/cnpj-validador'
3
5
 
4
6
  // ---------------------------------------------------------------------------
5
- // Mask formatting helpers
7
+ // Directive: mbg-document-mask-directive
8
+ // (lógica movida para o helper, re-exportada via import acima)
6
9
  // ---------------------------------------------------------------------------
7
10
 
8
- const CPF_MASK = '###.###.###-##'
9
- const CNPJ_MASK = '##.###.###/####-##'
10
-
11
- function cleanValue(value: string): string {
12
- return (value || '').replace(/[^A-Za-z0-9]/g, '').toUpperCase()
13
- }
14
-
15
- function hasLetters(value: string): boolean {
16
- return /[A-Za-z]/.test(value)
17
- }
18
-
19
- function formatCpf(clean: string): string {
20
- const digits = clean.replace(/[^0-9]/g, '').substring(0, 11)
21
- const result: string[] = []
22
- let digitIdx = 0
23
-
24
- for (let i = 0; i < CPF_MASK.length && digitIdx < digits.length; i++) {
25
- if (CPF_MASK[i] === '#') {
26
- result.push(digits[digitIdx])
27
- digitIdx++
28
- } else {
29
- result.push(CPF_MASK[i])
30
- }
31
- }
32
-
33
- return result.join('')
34
- }
35
-
36
- function formatCnpj(clean: string): string {
37
- const safe = clean.substring(0, 14)
38
- const result: string[] = []
39
- let cleanIndex = 0
40
-
41
- for (let i = 0; i < CNPJ_MASK.length && cleanIndex < safe.length; i++) {
42
- const maskChar = CNPJ_MASK[i]
43
-
44
- if (maskChar === '#') {
45
- const char = safe[cleanIndex]
46
- const isCheckDigitPosition = cleanIndex >= 12
47
-
48
- if (isCheckDigitPosition) {
49
- if (/[0-9]/.test(char)) {
50
- result.push(char)
51
- }
52
- cleanIndex++
53
- } else {
54
- if (/[A-Z0-9]/.test(char)) {
55
- result.push(char)
56
- }
57
- cleanIndex++
58
- }
59
- } else {
60
- result.push(maskChar)
61
- }
62
- }
63
-
64
- return result.join('')
65
- }
66
-
67
- function smartFormat(value: string): string {
68
- const clean = normalizeForModel(value)
69
- if (clean.length === 0) return ''
70
- if (hasLetters(clean)) return formatCnpj(clean)
71
- if (clean.length <= 11) return formatCpf(clean)
72
- return formatCnpj(clean)
73
- }
74
-
75
- function normalizeForModel(value: string): string {
76
- const clean = cleanValue(value)
77
- if (!clean) return ''
78
- if (hasLetters(clean)) return clean.substring(0, 14)
79
- if (clean.length <= 11) return clean
80
- return clean.substring(0, 14)
81
- }
82
-
83
- function isRepeatedNumeric(value: string): boolean {
84
- return /^(\d)\1+$/.test(value)
85
- }
86
-
87
- function getDigitValue(char: string): number {
88
- return char.charCodeAt(0) - 48
89
- }
90
-
91
- function calculateCpfDigit(base: string, startWeight: number): number {
92
- let sum = 0
93
-
94
- for (let i = 0; i < base.length; i++) {
95
- sum += Number(base[i]) * (startWeight - i)
96
- }
97
-
98
- const remainder = sum % 11
99
- return remainder < 2 ? 0 : 11 - remainder
100
- }
101
-
102
- function isValidCpf(value: string): boolean {
103
- if (!/^\d{11}$/.test(value) || isRepeatedNumeric(value)) return false
104
-
105
- const firstDigit = calculateCpfDigit(value.slice(0, 9), 10)
106
- const secondDigit = calculateCpfDigit(`${value.slice(0, 9)}${firstDigit}`, 11)
107
- return value === `${value.slice(0, 9)}${firstDigit}${secondDigit}`
108
- }
109
-
110
- function calculateCnpjDigit(base: string, weights: number[]): number {
111
- let sum = 0
112
-
113
- for (let i = 0; i < base.length; i++) {
114
- sum += getDigitValue(base[i]) * weights[i]
115
- }
116
-
117
- const remainder = sum % 11
118
- return remainder < 2 ? 0 : 11 - remainder
119
- }
120
-
121
- function isValidCnpj(value: string): boolean {
122
- if (!/^[A-Z0-9]{12}[0-9]{2}$/.test(value)) return false
123
- if (/^\d{14}$/.test(value) && isRepeatedNumeric(value)) return false
124
-
125
- const base = value.slice(0, 12)
126
- const firstDigit = calculateCnpjDigit(base, [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2])
127
- const secondDigit = calculateCnpjDigit(
128
- `${base}${firstDigit}`,
129
- [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2],
130
- )
131
-
132
- return value.slice(12) === `${firstDigit}${secondDigit}`
133
- }
134
-
135
- function isCpfCandidate(value: string): boolean {
136
- return /^\d*$/.test(value) && value.length <= 11
137
- }
138
-
139
- function isCnpjCandidate(value: string): boolean {
140
- return value.length > 11 || hasLetters(value)
141
- }
142
-
143
- function isDocumentValid(value: string): boolean {
144
- if (!value) return true
145
- if (isCpfCandidate(value)) {
146
- if (value.length < 11) return true
147
- return isValidCpf(value)
148
- }
149
- if (isCnpjCandidate(value)) {
150
- if (value.length < 14) return true
151
- if (value.length > 14) return false
152
- return isValidCnpj(value)
153
- }
154
- return false
155
- }
156
-
157
- // ---------------------------------------------------------------------------
158
- // Directive: mbg-alphanumeric-cnpj-mask
159
- // ---------------------------------------------------------------------------
160
-
161
- class MbgAlphanumericCnpjMaskDirective {
162
- static $inject = []
163
-
164
- require = 'ngModel'
165
- restrict = 'A'
166
-
167
- link = (_scope, _element, _attrs, ngModel) => {
168
- ngModel.$parsers.push((viewValue: string) => {
169
- const clean = normalizeForModel(viewValue || '')
170
- const formatted = smartFormat(clean)
171
-
172
- if ((viewValue || '') !== formatted) {
173
- ngModel.$setViewValue(formatted)
174
- ngModel.$render()
175
- }
176
-
177
- return clean
178
- })
179
-
180
- ngModel.$formatters.push((modelValue: string) => {
181
- return smartFormat(modelValue || '')
182
- })
183
-
184
- ngModel.$validators.cpf = (modelValue: string) => {
185
- const clean = normalizeForModel(modelValue || '')
186
- if (!clean || !isCpfCandidate(clean)) return true
187
- if (clean.length < 11) return true
188
- return isValidCpf(clean)
189
- }
190
-
191
- ngModel.$validators.cnpj = (modelValue: string) => {
192
- const clean = normalizeForModel(modelValue || '')
193
- if (!clean || !isCnpjCandidate(clean)) return true
194
- if (clean.length < 14) return true
195
- if (clean.length > 14) return false
196
- return isValidCnpj(clean)
197
- }
198
-
199
- ngModel.$validators.cpfcnpj = (modelValue: string) => {
200
- const clean = normalizeForModel(modelValue || '')
201
- return isDocumentValid(clean)
202
- }
203
- }
204
- }
205
-
206
- const mbgAlphanumericCnpjMask = () => new MbgAlphanumericCnpjMaskDirective()
207
-
208
11
  // ---------------------------------------------------------------------------
209
12
  // Component: mbg-input-cpfcnpj
210
13
  // ---------------------------------------------------------------------------
@@ -247,7 +50,7 @@ class MbgInputCpfCnpjController {
247
50
  this.validCpf(this.valid)
248
51
  }
249
52
  if (this.valid.length === 14) {
250
- this.validCnpj(this.valid)
53
+ isValidCnpj(this.valid)
251
54
  } else {
252
55
  return false
253
56
  }
@@ -273,13 +76,6 @@ class MbgInputCpfCnpjController {
273
76
  return newCpf === value
274
77
  }
275
78
 
276
- validCnpj(value) {
277
- const firstNumbers = value.substr(0, 12)
278
- const firstCalc = this.CalcDigits(firstNumbers, 5)
279
- const secondCalc = this.CalcDigits(firstCalc, 6)
280
- return secondCalc === value
281
- }
282
-
283
79
  onChange() {
284
80
  if (this.ngChange) this.ngChange({})
285
81
  }
@@ -304,4 +100,4 @@ const mbgInputCpfCnpj = {
304
100
  controller: MbgInputCpfCnpjController,
305
101
  }
306
102
 
307
- export { mbgInputCpfCnpj, mbgAlphanumericCnpjMask }
103
+ export { mbgInputCpfCnpj, mbgDocumentMaskDirective }
@@ -0,0 +1,38 @@
1
+ // Single source of truth for CNPJ normalization and validation.
2
+
3
+ // Strips punctuation/spaces, uppercases letters, truncates to 14 chars.
4
+ // Preserves letters — safe for both numeric and alphanumeric CNPJs.
5
+ export function normalizeCnpj(value: string): string {
6
+ const clean = `${value || ''}`.replace(/[^A-Za-z0-9]/g, '').toUpperCase()
7
+ return clean.substring(0, 14)
8
+ }
9
+
10
+ const WEIGHTS_D1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]
11
+ const WEIGHTS_D2 = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]
12
+
13
+ function charToValue(char: string): number {
14
+ return char.charCodeAt(0)
15
+ }
16
+
17
+ function calcDigit(base: string, weights: number[]): number {
18
+ let sum = 0
19
+ for (let i = 0; i < base.length; i++) {
20
+ sum += charToValue(base[i]) * weights[i]
21
+ }
22
+ const r = sum % 11
23
+ return r < 2 ? 0 : 11 - r
24
+ }
25
+
26
+ // Accepts raw or pre-normalized input (with or without punctuation/mask).
27
+ // Validates numeric and alphanumeric CNPJs (Receita Federal 2026).
28
+ export function isValidCnpj(value: string): boolean {
29
+ const cnpj = normalizeCnpj(value)
30
+
31
+ if (!/^[A-Z0-9]{12}[0-9]{2}$/.test(cnpj)) return false
32
+ if (/^\d{14}$/.test(cnpj) && /^(\d)\1+$/.test(cnpj)) return false
33
+
34
+ const base = cnpj.slice(0, 12)
35
+ const d1 = calcDigit(base, WEIGHTS_D1)
36
+ const d2 = calcDigit(`${base}${d1}`, WEIGHTS_D2)
37
+ return cnpj.slice(12) === `${d1}${d2}`
38
+ }
@@ -0,0 +1,156 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Shared utilities: Alphanumeric CNPJ mask, formatting and validation
3
+ // Used by: mbg-input-cpfcnpj, mbg-input-cnpj
4
+ // ---------------------------------------------------------------------------
5
+
6
+ import { isValidCnpj, normalizeCnpj } from '@kigi/components/src/helpers/cnpj-validador'
7
+ export { isValidCnpj as isValidAlphanumericCnpj }
8
+ export { normalizeCnpj as normalizeAlphanumericCnpj }
9
+
10
+ const CNPJ_MASK = '##.###.###/####-##'
11
+ const CPF_MASK = '###.###.###-##'
12
+
13
+ export function hasLetters(value: string): boolean {
14
+ return /[A-Za-z]/.test(value)
15
+ }
16
+
17
+ export function formatCpf(clean: string): string {
18
+ const digits = clean.replace(/[^0-9]/g, '').substring(0, 11)
19
+ const result: string[] = []
20
+ let digitIdx = 0
21
+ for (let i = 0; i < CPF_MASK.length && digitIdx < digits.length; i++) {
22
+ if (CPF_MASK[i] === '#') result.push(digits[digitIdx++])
23
+ else result.push(CPF_MASK[i])
24
+ }
25
+ return result.join('')
26
+ }
27
+
28
+ function smartFormat(clean: string): string {
29
+ if (!clean) return ''
30
+ if (hasLetters(clean)) return formatAlphanumericCnpj(clean)
31
+ if (clean.length <= 11) return formatCpf(clean)
32
+ return formatAlphanumericCnpj(clean)
33
+ }
34
+
35
+ export function formatAlphanumericCnpj(clean: string): string {
36
+ const safe = clean.substring(0, 14)
37
+ const result: string[] = []
38
+ let cleanIndex = 0
39
+
40
+ for (let i = 0; i < CNPJ_MASK.length && cleanIndex < safe.length; i++) {
41
+ const maskChar = CNPJ_MASK[i]
42
+
43
+ if (maskChar === '#') {
44
+ const char = safe[cleanIndex]
45
+ const isCheckDigitPosition = cleanIndex >= 12
46
+
47
+ if (isCheckDigitPosition) {
48
+ if (/[0-9]/.test(char)) result.push(char)
49
+ cleanIndex++
50
+ } else {
51
+ if (/[A-Z0-9]/.test(char)) result.push(char)
52
+ cleanIndex++
53
+ }
54
+ } else {
55
+ result.push(maskChar)
56
+ }
57
+ }
58
+
59
+ return result.join('')
60
+ }
61
+
62
+ export function isCnpjCandidate(value: string): boolean {
63
+ return value.length > 11 || hasLetters(value)
64
+ }
65
+
66
+ export function isCpfCandidate(value: string): boolean {
67
+ return /^\d*$/.test(value) && value.length <= 11
68
+ }
69
+
70
+ function isRepeatedNumeric(value: string): boolean {
71
+ return /^(\d)\1+$/.test(value)
72
+ }
73
+
74
+ function calculateCpfDigit(base: string, startWeight: number): number {
75
+ let sum = 0
76
+ for (let i = 0; i < base.length; i++) {
77
+ sum += Number(base[i]) * (startWeight - i)
78
+ }
79
+ const remainder = sum % 11
80
+ return remainder < 2 ? 0 : 11 - remainder
81
+ }
82
+
83
+ export function isValidCpf(value: string): boolean {
84
+ if (!/^\d{11}$/.test(value) || isRepeatedNumeric(value)) return false
85
+ const firstDigit = calculateCpfDigit(value.slice(0, 9), 10)
86
+ const secondDigit = calculateCpfDigit(`${value.slice(0, 9)}${firstDigit}`, 11)
87
+ return value === `${value.slice(0, 9)}${firstDigit}${secondDigit}`
88
+ }
89
+
90
+ function isDocumentValid(value: string): boolean {
91
+ if (!value) return true
92
+ if (isCpfCandidate(value)) {
93
+ if (value.length < 11) return true
94
+ return isValidCpf(value)
95
+ }
96
+ if (isCnpjCandidate(value)) {
97
+ if (value.length < 14) return true
98
+ if (value.length > 14) return false
99
+ return isValidCnpj(value)
100
+ }
101
+ return false
102
+ }
103
+
104
+ // ---------------------------------------------------------------------------
105
+ // AngularJS directive factory — reusable in any module
106
+ // ---------------------------------------------------------------------------
107
+
108
+ class MbgDocumentMaskDirective {
109
+ static $inject = []
110
+
111
+ require = 'ngModel'
112
+ restrict = 'A'
113
+
114
+ link = (_scope, _element, _attrs, ngModel) => {
115
+ ngModel.$parsers.push((viewValue: string) => {
116
+ const clean = normalizeCnpj(viewValue || '')
117
+ const formatted = smartFormat(clean)
118
+
119
+ if ((viewValue || '') !== formatted) {
120
+ ngModel.$setViewValue(formatted)
121
+ ngModel.$render()
122
+ }
123
+
124
+ return clean
125
+ })
126
+
127
+ ngModel.$formatters.push((modelValue: string) => {
128
+ return smartFormat(normalizeCnpj(modelValue || ''))
129
+ })
130
+
131
+ ngModel.$validators.cpf = (modelValue: string) => {
132
+ const clean = normalizeCnpj(modelValue || '')
133
+ if (!clean || !isCpfCandidate(clean)) return true
134
+ if (clean.length < 11) return true
135
+ return isValidCpf(clean)
136
+ }
137
+
138
+ ngModel.$validators.cnpj = (modelValue: string) => {
139
+ const clean = normalizeCnpj(modelValue || '')
140
+ if (!clean || !isCnpjCandidate(clean)) return true
141
+ if (clean.length < 14) {
142
+ if (clean.length >= 12 && /[A-Z]/.test(clean.slice(12))) return false
143
+ return true
144
+ }
145
+ if (clean.length > 14) return false
146
+ return isValidCnpj(clean)
147
+ }
148
+
149
+ ngModel.$validators.cpfcnpj = (modelValue: string) => {
150
+ const clean = normalizeCnpj(modelValue || '')
151
+ return isDocumentValid(clean)
152
+ }
153
+ }
154
+ }
155
+
156
+ export const mbgDocumentMaskDirective = () => new MbgDocumentMaskDirective()