Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,31 @@

All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.

### [4.4.6](https://github.com/assisrafael/angular-input-masks/compare/v4.4.5...v4.4.6) (2026-10-28)



### [4.4.5](https://github.com/assisrafael/angular-input-masks/compare/v4.4.4...v4.4.5) (2026-06-19)



### [4.4.4](https://github.com/assisrafael/angular-input-masks/compare/v4.4.3...v4.4.4) (2026-06-19)



### [4.4.3](https://github.com/assisrafael/angular-input-masks/compare/v4.4.2...v4.4.3) (2026-06-19)


### Bug Fixes

* **br:** support Brazilian alphanumeric CNPJ format in masks, filters and validation ([8aaba52](https://github.com/assisrafael/angular-input-masks/commit/8aaba52))



### [4.4.2](https://github.com/assisrafael/angular-input-masks/compare/v4.4.1...v4.4.2) (2026-06-17)



### [4.4.1](https://github.com/assisrafael/angular-input-masks/compare/v4.4.0...v4.4.1) (2019-05-29)


Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "angular-input-masks",
"version": "4.4.1",
"version": "4.4.6",
"description": "Personalized input masks for AngularJS",
"repository": {
"type": "git",
Expand Down
6 changes: 3 additions & 3 deletions src/br/cnpj/cnpj.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ var BrV = require('br-validations');

var maskFactory = require('../../helpers/mask-factory');

var cnpjPattern = new StringMask('00.000.000\/0000-00');
var cnpjPattern = new StringMask('AA.AAA.AAA\/AAAA-00');

module.exports = maskFactory({
clearValue: function(rawValue) {
return rawValue.replace(/[^\d]/g, '').slice(0, 14);
return rawValue.replace(/[^a-zA-Z0-9]/g, '').toUpperCase().slice(0, 14);
},
format: function(cleanValue) {
return (cnpjPattern.apply(cleanValue) || '').trim().replace(/[^0-9]$/, '');
return (cnpjPattern.apply(cleanValue) || '').trim();
},
validations: {
cnpj: function(value) {
Expand Down
19 changes: 14 additions & 5 deletions src/br/cnpj/cnpj.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,23 @@ describe('ui-br-cnpj-mask', function() {
expect(maskedModel.$formatters.length).toBe(model.$formatters.length + 1);
});

it('should format initial model values', function() {
var input = TestUtil.compile('<input ng-model="model" ui-br-cnpj-mask>', {
model: '13883875000120'
it('should format initial numeric model values', function() {
var input = TestUtil.compile('<input ng-model="model" ui-br-cnpj-mask>', {
model: '13883875000120'
});

var model = input.controller('ngModel');
expect(model.$viewValue).toBe('13.883.875/0001-20');
});

it('should format initial alphanumeric model values', function() {
var input = TestUtil.compile('<input ng-model="model" ui-br-cnpj-mask>', {
model: '6MDP40BD000175'
});

var model = input.controller('ngModel');
expect(model.$viewValue).toBe('13.883.875/0001-20');
});
expect(model.$viewValue).toBe('6M.DP4.0BD/0001-75');
});

it('should handle corner cases', angular.mock.inject(function($rootScope) {
var input = TestUtil.compile('<input ng-model="model" ui-br-cnpj-mask>');
Expand Down
13 changes: 5 additions & 8 deletions src/br/cpf-cnpj/cpf-cnpj.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,20 @@ var StringMask = require('string-mask');
var BrV = require('br-validations');
var maskFactory = require('../../helpers/mask-factory');

var cnpjPattern = new StringMask('00.000.000\/0000-00');
var cnpjPattern = new StringMask('AA.AAA.AAA\/AAAA-00');
var cpfPattern = new StringMask('000.000.000-00');

module.exports = maskFactory({
clearValue: function(rawValue) {
return rawValue.replace(/[^\d]/g, '').slice(0, 14);
return rawValue.replace(/[^a-zA-Z0-9]/g, '').toUpperCase().slice(0, 14);
},
format: function(cleanValue) {
var formatedValue;
if (!cleanValue) return '';

if (cleanValue.length > 11) {
formatedValue = cnpjPattern.apply(cleanValue);
} else {
formatedValue = cpfPattern.apply(cleanValue) || '';
return (cnpjPattern.apply(cleanValue) || '').trim();
}

return formatedValue.trim().replace(/[^0-9]$/, '');
return (cpfPattern.apply(cleanValue) || '').trim().replace(/[^0-9]$/, '');
},
validations: {
cpf: function(value) {
Expand Down
42 changes: 42 additions & 0 deletions src/global/date-time/date-time-pt-br.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<title>Date Time Spec</title>
<script src="/node_modules/angular/angular.min.js"></script>
<script src="/node_modules/angular-i18n/angular-locale_pt-br.js"></script>
<script src="/releases/angular-input-masks-dependencies.js"></script>
<script src="/releases/angular-input-masks.js"></script>
<script>
angular.module('dateTimeTest', ['ui.utils.masks'])
.controller('ctrl', function ctrl($scope) {
$scope.initializedDateTimeMask = new Date();
$scope.initializedWithISOStringDateTimeMask = (new Date()).toISOString();
});
</script>
</head>
<body ng-app="dateTimeTest">
<form name="form" ng-controller="ctrl">
<h2>ui-date-time-mask</h2>
<input type="text" name="dateTimeMaskInput" ng-model="dateTimeMask" ui-date-time-mask>
<br>
ModelValue: <span ng-bind="dateTimeMask"></span>
<br>
Errors: <span ng-bind="form.dateTimeMaskInput.$error | json"></span>
<br>
<br>
<input type="text" name="initializedDateTimeMask" ng-model="initializedDateTimeMask" ui-date-time-mask>
<br>
ModelValue: <span ng-bind="initializedDateTimeMask"></span>
<br>
Errors: <span ng-bind="form.initializedDateTimeMask.$error | json"></span>
<br>
<input type="text" name="initializedWithISOStringDateTimeMask" ng-model="initializedWithISOStringDateTimeMask" ui-date-time-mask>
<br>
ModelValue: <span ng-bind="initializedWithISOStringDateTimeMask"></span>
<br>
Errors: <span ng-bind="form.initializedWithISOStringDateTimeMask.$error | json"></span>
<br>
</form>
</body>
</html>
41 changes: 41 additions & 0 deletions src/global/date-time/date-time.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Date Time Spec</title>
<script src="/node_modules/angular/angular.min.js"></script>
<script src="/releases/angular-input-masks-dependencies.js"></script>
<script src="/releases/angular-input-masks.js"></script>
<script>
angular.module('dateTimeTest', ['ui.utils.masks'])
.controller('ctrl', function ctrl($scope) {
$scope.initializedDateTimeMask = new Date();
$scope.initializedWithISOStringDateTimeMask = (new Date()).toISOString();
});
</script>
</head>
<body ng-app="dateTimeTest">
<form name="form" ng-controller="ctrl">
<h2>ui-date-time-mask</h2>
<input type="text" name="dateTimeMaskInput" ng-model="dateTimeMask" ui-date-time-mask="YYYY-MM-DD HH:mm:ss">
<br>
ModelValue: <span ng-bind="dateTimeMask"></span>
<br>
Errors: <span ng-bind="form.dateTimeMaskInput.$error | json"></span>
<br>
<br>
<input type="text" name="initializedDateTimeMask" ng-model="initializedDateTimeMask" ui-date-time-mask="YYYY-MM-DD HH:mm:ss">
<br>
ModelValue: <span ng-bind="initializedDateTimeMask"></span>
<br>
Errors: <span ng-bind="form.initializedDateTimeMask.$error | json"></span>
<br>
<input type="text" name="initializedWithISOStringDateTimeMask" ng-model="initializedWithISOStringDateTimeMask" ui-date-time-mask="YYYY-MM-DD HH:mm:ss">
<br>
ModelValue: <span ng-bind="initializedWithISOStringDateTimeMask"></span>
<br>
Errors: <span ng-bind="form.initializedWithISOStringDateTimeMask.$error | json"></span>
<br>
</form>
</body>
</html>
135 changes: 135 additions & 0 deletions src/global/date-time/date-time.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
'use strict';

var formatDate = require('date-fns/format');
var parseDate = require('date-fns/parse');
var isValidDate = require('date-fns/isValid');
var StringMask = require('string-mask');

var dateFormatMapByLocale = {
'pt-br': 'dd/MM/yyyy',
'es-ar': 'dd/MM/yyyy',
'en-us': 'MM/dd/yyyy',
'fr-fr': 'dd/MM/yyyy',
'ru' : 'dd.MM.yyyy'
};

function normalizarParaObjetoDate(value) {
if (!value) return null;

if (typeof value === 'object' && value instanceof Date) {
return isNaN(value.getTime()) ? null : value;
}

var str = value.toString().trim();
if (str.length < 8) {
return null;
}

var partesDataHora = str.split('T');
var apenasData = partesDataHora[0].split(' ')[0].replace(/\//g, '-');
var partes = apenasData.split('-');

if (partes.length !== 3) {
var dataNativa = new Date(value);
return isNaN(dataNativa.getTime()) ? null : dataNativa;
}

var ano, mes, dia;
if (partes[0].length === 4) {
ano = parseInt(partes[0], 10);
mes = parseInt(partes[1], 10) - 1;
dia = parseInt(partes[2], 10);
} else if (partes[2].length === 4) {
dia = parseInt(partes[0], 10);
mes = parseInt(partes[1], 10) - 1;
ano = parseInt(partes[2], 10);
} else {
return null;
}

var hora = 0, minuto = 0, segundo = 0;
var parteHorario = partesDataHora[1] || (str.includes(' ') ? str.split(' ')[1] : null);

if (parteHorario) {
var apenasHoraMinSeg = parteHorario.split('.')[0].split('-')[0].split('+')[0].replace('Z', '');
var componentesHora = apenasHoraMinSeg.split(':');

hora = parseInt(componentesHora[0] || 0, 10);
minuto = parseInt(componentesHora[1] || 0, 10);
segundo = parseInt(componentesHora[2] || 0, 10);
}

var dataFinal = new Date(ano, mes, dia, hora, minuto, segundo);
return isNaN(dataFinal.getTime()) ? null : dataFinal;
}

function DateTimeMaskDirective($locale) {
var currentLocale = ($locale.id || 'pt-br').toLowerCase();
var dateFormat = dateFormatMapByLocale[currentLocale] || 'dd/MM/yyyy';

return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ctrl) {
attrs.parse = attrs.parse || 'true';

// Define se o formato de hora será completo (HH:mm:ss) ou curto (HH:mm)
var timeFormat = (attrs.uiDateTimeMask === 'short') ? 'HH:mm' : 'HH:mm:ss';
var dynamicFormat = dateFormat + ' ' + timeFormat;

// Substitui as letras por zeros para a máscara de caracteres do StringMask
var maskPattern = dynamicFormat.replace(/[DMYHms]/g, '0');
var dateTimeMask = new StringMask(maskPattern);
var unformattedLength = maskPattern.replace(/[^0-9]/g, '').length;

function formatter(value) {
if (ctrl.$isEmpty(value)) {
return null;
}

var dataValida = normalizarParaObjetoDate(value);
var cleanValue = value;

if (dataValida) {
cleanValue = formatDate(dataValida, dynamicFormat);
}

cleanValue = cleanValue.replace(/[^0-9]/g, '').slice(0, unformattedLength);
var formattedValue = dateTimeMask.apply(cleanValue) || '';

return formattedValue.trim().replace(/[^0-9]$/, '');
}

ctrl.$formatters.push(formatter);

ctrl.$parsers.push(function parser(value) {
if (ctrl.$isEmpty(value)) {
return value;
}

var formattedValue = formatter(value);

if (ctrl.$viewValue !== formattedValue) {
ctrl.$setViewValue(formattedValue);
ctrl.$render();
}

return attrs.parse === 'false'
? formattedValue
: parseDate(formattedValue, dynamicFormat, new Date());
});

ctrl.$validators.dateTime = function validator(modelValue, viewValue) {
if (ctrl.$isEmpty(modelValue)) {
return true;
}

return isValidDate(parseDate(viewValue, dynamicFormat, new Date())) && viewValue.length === dynamicFormat.length;
};
}
};
}

DateTimeMaskDirective.$inject = ['$locale'];

module.exports = DateTimeMaskDirective;
Loading