tax-document-input
Version:
A vanilla JavaScript plugin for automatic formatting of tax documents from different countries (CPF, CNPJ, NIF, NIPC, SSN, EIN)
198 lines (166 loc) • 5.77 kB
JavaScript
/**
* FormatManager - Gerencia formatação e máscaras de documentos
*/
export default class FormatManager {
constructor(input, countries) {
this.input = input;
this.countries = countries;
this.selectedCountry = 'br';
this.currentDocument = null;
}
/**
* Detecta o tipo de documento baseado no comprimento atual
*/
detectDocumentType() {
const value = this.input.value.replace(/\D/g, '');
const country = this.countries[this.selectedCountry];
if (!country) return;
// Ordenar documentos por prioridade (menor primeiro)
const documents = Object.entries(country.documents).sort((a, b) => {
return a[1].priority - b[1].priority;
});
// Se o valor está vazio, usar o primeiro documento (menor prioridade)
if (value.length === 0) {
this.currentDocument = documents[0][0];
return;
}
// Determinar qual tipo de documento baseado no comprimento atual
for (const [docType, docConfig] of documents) {
if (value.length <= docConfig.length) {
this.currentDocument = docType;
return;
}
}
// Se exceder todos os comprimentos, usar o último (maior)
this.currentDocument = documents[documents.length - 1][0];
}
/**
* Retorna o comprimento máximo para o país atual
*/
getMaxLength() {
const country = this.countries[this.selectedCountry];
if (!country) return 0;
return Math.max(...Object.values(country.documents).map(doc => doc.length));
}
/**
* Formata o input em tempo real
*/
formatInput(e) {
const input = e.target;
const cursorPosition = input.selectionStart;
let value = input.value.replace(/\D/g, '');
const country = this.countries[this.selectedCountry];
if (!country) return;
// Limitar ao tamanho máximo do país
const maxLength = this.getMaxLength();
value = value.substring(0, maxLength);
// Detectar tipo de documento baseado no comprimento atual
this.detectDocumentType();
const docConfig = country.documents[this.currentDocument];
if (!docConfig) return;
// Aplicar máscara
const formatted = this.applyMask(value, docConfig.mask);
// Calcular nova posição do cursor
const newCursorPosition = this.calculateCursorPosition(
input.value,
formatted,
cursorPosition
);
// Atualizar valor
input.value = formatted;
// Definir nova posição do cursor
setTimeout(() => {
input.setSelectionRange(newCursorPosition, newCursorPosition);
}, 0);
// Notificar sobre mudança de formato
this.onFormatChange?.(value, this.currentDocument);
}
/**
* Aplica a máscara ao valor
*/
applyMask(value, mask) {
let formatted = '';
let valueIndex = 0;
for (let i = 0; i < mask.length && valueIndex < value.length; i++) {
if (mask[i] === 'X') {
formatted += value[valueIndex];
valueIndex++;
} else {
formatted += mask[i];
}
}
return formatted;
}
/**
* Calcula a nova posição do cursor após formatação
*/
calculateCursorPosition(oldValue, newValue, oldCursor) {
// Contar quantos números existem antes da posição do cursor no valor antigo
let numbersBeforeCursor = 0;
for (let i = 0; i < oldCursor && i < oldValue.length; i++) {
if (/\d/.test(oldValue[i])) {
numbersBeforeCursor++;
}
}
// Encontrar a posição correspondente no novo valor
let newCursor = 0;
let numbersFound = 0;
for (let i = 0; i < newValue.length; i++) {
if (/\d/.test(newValue[i])) {
numbersFound++;
if (numbersFound > numbersBeforeCursor) {
break;
}
}
newCursor = i + 1;
}
return newCursor;
}
/**
* Define o país selecionado
*/
setSelectedCountry(countryCode) {
this.selectedCountry = countryCode;
this.currentDocument = null;
this.detectDocumentType();
}
/**
* Retorna informações do documento atual
*/
getCurrentDocumentInfo() {
const country = this.countries[this.selectedCountry];
if (!country || !this.currentDocument) return null;
const docConfig = country.documents[this.currentDocument];
return {
type: this.currentDocument,
category: docConfig.type,
length: docConfig.length,
mask: docConfig.mask,
country: this.selectedCountry
};
}
/**
* Retorna o valor sem formatação (apenas números)
*/
getCleanValue() {
return this.input.value.replace(/\D/g, '');
}
/**
* Retorna o valor formatado
*/
getFormattedValue() {
return this.input.value;
}
/**
* Limpa o input
*/
clear() {
this.input.value = '';
this.currentDocument = null;
this.detectDocumentType();
}
/**
* Callback para mudanças de formato (deve ser definido externamente)
*/
onFormatChange = null;
}