From 1e37d911ef8aaa1ba8e9011375f095970a1fc44f Mon Sep 17 00:00:00 2001 From: Renato Barboza Date: Tue, 20 Jul 2021 02:44:08 -0300 Subject: [PATCH 01/12] remove old react-native-masked-text dependencie --- package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/package.json b/package.json index 5fc53570..fa8dd2bd 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,6 @@ }, "peerDependencies": { "@platformbuilders/helpers": ">=0.3.5", - "@platformbuilders/react-native-masked-text": ">=1.0.3", "formik": ">=2.1.6", "lodash": ">=4.17.20", "lottie-react-native": ">=3.5.0", @@ -93,7 +92,6 @@ "@commitlint/cli": "11.0.0", "@commitlint/config-conventional": "11.0.0", "@platformbuilders/helpers": "0.3.5", - "@platformbuilders/react-native-masked-text": "1.0.3", "@storybook/addon-actions": "6.0.22", "@storybook/addon-backgrounds": "6.0.22", "@storybook/addon-knobs": "6.0.22", From 8e0a86d90d098e218db00fb323724436d92b3ef0 Mon Sep 17 00:00:00 2001 From: Renato Barboza Date: Tue, 20 Jul 2021 02:44:57 -0300 Subject: [PATCH 02/12] allowJS:true to compile MaskedText in .js --- tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/tsconfig.json b/tsconfig.json index 27371b21..430dc1b7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { /* Basic Options */ + "allowJs": true, "target": "es6", "module": "es6", "lib": ["es6"], From 8fe15ade2697d4a0983aa30fc1aa08751f25459a Mon Sep 17 00:00:00 2001 From: Renato Barboza Date: Tue, 20 Jul 2021 02:46:06 -0300 Subject: [PATCH 03/12] add exports to MaskedText --- src/components/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/index.ts b/src/components/index.ts index 5546d521..eeded6f2 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -22,3 +22,4 @@ export { default as RadioButton } from './RadioButton'; export { default as SearchInput } from './SearchInput'; export { default as DisplayVersion } from './DisplayVersion'; export { default as LabelDivider } from './LabelDivider'; +export { TextInputMask, MaskService, TextMask } from './MaskedText'; From 785973922c559a49650158bb6d4ab8df0cadf84e Mon Sep 17 00:00:00 2001 From: Renato Barboza Date: Tue, 20 Jul 2021 02:47:44 -0300 Subject: [PATCH 04/12] add masks --- src/components/MaskedText/index.js | 7 + .../MaskedText/lib/base-text-component.js | 102 ++++++++++++++ .../MaskedText/lib/mask-resolver.js | 20 +++ src/components/MaskedText/lib/mask-service.js | 19 +++ .../MaskedText/lib/masks/_base.mask.js | 45 ++++++ .../MaskedText/lib/masks/bank-slip.mask.js | 45 ++++++ .../MaskedText/lib/masks/cel-phone.mask.js | 72 ++++++++++ .../MaskedText/lib/masks/cnpj.mask.js | 49 +++++++ .../MaskedText/lib/masks/cpf.mask.js | 90 ++++++++++++ .../MaskedText/lib/masks/credit-card.mask.js | 75 ++++++++++ .../MaskedText/lib/masks/custom.mask.js | 129 ++++++++++++++++++ .../MaskedText/lib/masks/datetime.mask.js | 46 +++++++ .../MaskedText/lib/masks/document-mask.js | 34 +++++ src/components/MaskedText/lib/masks/index.js | 25 ++++ .../MaskedText/lib/masks/maskInRegex.js | 16 +++ .../MaskedText/lib/masks/no-mask.mask.js | 23 ++++ .../MaskedText/lib/masks/only-numbers.mask.js | 23 ++++ .../MaskedText/lib/masks/uppercase.mask.js | 23 ++++ .../MaskedText/lib/masks/zip-code.mask.js | 34 +++++ .../MaskedText/lib/text-input-mask.js | 72 ++++++++++ src/components/MaskedText/lib/text-mask.js | 23 ++++ 21 files changed, 972 insertions(+) create mode 100644 src/components/MaskedText/index.js create mode 100644 src/components/MaskedText/lib/base-text-component.js create mode 100644 src/components/MaskedText/lib/mask-resolver.js create mode 100644 src/components/MaskedText/lib/mask-service.js create mode 100644 src/components/MaskedText/lib/masks/_base.mask.js create mode 100644 src/components/MaskedText/lib/masks/bank-slip.mask.js create mode 100644 src/components/MaskedText/lib/masks/cel-phone.mask.js create mode 100644 src/components/MaskedText/lib/masks/cnpj.mask.js create mode 100644 src/components/MaskedText/lib/masks/cpf.mask.js create mode 100644 src/components/MaskedText/lib/masks/credit-card.mask.js create mode 100644 src/components/MaskedText/lib/masks/custom.mask.js create mode 100644 src/components/MaskedText/lib/masks/datetime.mask.js create mode 100644 src/components/MaskedText/lib/masks/document-mask.js create mode 100644 src/components/MaskedText/lib/masks/index.js create mode 100644 src/components/MaskedText/lib/masks/maskInRegex.js create mode 100644 src/components/MaskedText/lib/masks/no-mask.mask.js create mode 100644 src/components/MaskedText/lib/masks/only-numbers.mask.js create mode 100644 src/components/MaskedText/lib/masks/uppercase.mask.js create mode 100644 src/components/MaskedText/lib/masks/zip-code.mask.js create mode 100644 src/components/MaskedText/lib/text-input-mask.js create mode 100644 src/components/MaskedText/lib/text-mask.js diff --git a/src/components/MaskedText/index.js b/src/components/MaskedText/index.js new file mode 100644 index 00000000..308bf47d --- /dev/null +++ b/src/components/MaskedText/index.js @@ -0,0 +1,7 @@ +import MaskService from './lib/mask-service'; +import TextInputMask from './lib/text-input-mask'; +import TextMask from './lib/text-mask'; + +module.exports.MaskService = MaskService; +module.exports.TextInputMask = TextInputMask; +module.exports.TextMask = TextMask; diff --git a/src/components/MaskedText/lib/base-text-component.js b/src/components/MaskedText/lib/base-text-component.js new file mode 100644 index 00000000..0e5cae5f --- /dev/null +++ b/src/components/MaskedText/lib/base-text-component.js @@ -0,0 +1,102 @@ +import React, { PureComponent } from 'react'; +import MaskResolver from './mask-resolver'; +import CustomMask from './masks/custom.mask'; +import { MASKS_TYPE } from './masks/maskInRegex'; + +export default class BaseTextComponent extends PureComponent { + constructor(props) { + super(props); + this._resolveMaskHandler(); + } + + componentDidMount() { + this._bindProps(this.props); + } + + componentDidUpdate(prevProps) { + this._bindProps(prevProps); + } + + updateValue(text) { + const maskedText = this._getMaskedValue(text); + const rawText = this.props.includeRawValueInChangeText + ? this.getRawValueFor(maskedText) + : undefined; + + return { + maskedText, + rawText, + }; + } + + isValid() { + return this._maskHandler.validate( + this._getDefaultValue(this.props.value), + this._getOptions(), + ); + } + + getRawValueFor(value) { + return this._maskHandler.getRawValue( + this._getDefaultValue(value), + this._getOptions(), + ); + } + + getRawValue() { + return this.getRawValueFor(this.props.value); + } + + getDisplayValueFor(value) { + return this._getMaskedValue(value); + } + + _getOptions() { + return this.props.options; + } + + _mustUpdateValue(newValue) { + return this.props.value !== newValue; + } + + _resolveMaskHandler() { + if (!MASKS_TYPE[this.props.type]) { + this._maskHandler = MaskResolver.resolve(this.props.type); + } else { + this._maskHandler = new CustomMask(); + } + } + + _bindProps(nextProps) { + if (this.props.type !== nextProps.type) { + this._resolveMaskHandler(); + } + } + + _getDefaultMaskedValue(value) { + if (this._getDefaultValue(value) === '') { + return ''; + } + + return this._getMaskedValue(value); + } + + _getMaskedValue(value) { + const defaultValue = this._getDefaultValue(value); + if (defaultValue === '') { + return ''; + } + if (MASKS_TYPE[this.props.type]) { + return this._maskHandler.getValue(defaultValue, this.props); + } + return this._maskHandler.getValue(defaultValue, this._getOptions()); + } + + _getDefaultValue(value) { + if (value === undefined || value === null) { + return ''; + } + + return value; + } +} diff --git a/src/components/MaskedText/lib/mask-resolver.js b/src/components/MaskedText/lib/mask-resolver.js new file mode 100644 index 00000000..52431083 --- /dev/null +++ b/src/components/MaskedText/lib/mask-resolver.js @@ -0,0 +1,20 @@ +import * as Masks from './masks'; + +const maskKeys = Object.keys(Masks); + +export default class MaskResolver { + static resolve(type) { + const maskKey = maskKeys.find((m) => { + const handler = Masks[m]; + return handler && handler.getType && handler.getType() === type; + }); + + const handler = Masks[maskKey]; + + if (!handler) { + throw new Error('Mask type not supported.'); + } + + return new handler(); + } +} diff --git a/src/components/MaskedText/lib/mask-service.js b/src/components/MaskedText/lib/mask-service.js new file mode 100644 index 00000000..4a1c4cff --- /dev/null +++ b/src/components/MaskedText/lib/mask-service.js @@ -0,0 +1,19 @@ +import MaskResolver from './mask-resolver'; + +export default class MaskService { + static toMask(type, value, settings) { + return MaskResolver.resolve(type).getValue(value, settings); + } + + static toRawValue(type, maskedValue, settings) { + return MaskResolver.resolve(type).getRawValue(maskedValue, settings); + } + + static isValid(type, value, settings) { + return MaskResolver.resolve(type).validate(value, settings); + } + + static getMask(type, value, settings) { + return MaskResolver.resolve(type).getMask(value, settings); + } +} diff --git a/src/components/MaskedText/lib/masks/_base.mask.js b/src/components/MaskedText/lib/masks/_base.mask.js new file mode 100644 index 00000000..51bcb9d9 --- /dev/null +++ b/src/components/MaskedText/lib/masks/_base.mask.js @@ -0,0 +1,45 @@ +export default class BaseMask { + toOnlyNumbers(value) { + if (!value) return ''; + return value.replace(/\D/g, ''); + } + + getKeyboardType() { + return 'numeric'; + } + + mergeSettings(obj1, obj2) { + const obj3 = {}; + for (var attrname in obj1) { + obj3[attrname] = obj1[attrname]; + } + for (var attrname in obj2) { + obj3[attrname] = obj2[attrname]; + } + return obj3; + } + + getRawValue(maskedValue, settings) { + return maskedValue; + } + + getDefaultValue(value) { + if (value === undefined || value === null) { + return ''; + } + + return value; + } + + getMask(value, settings) { + throw new Error('getCurrentMask is not implemented'); + } + + removeNotNumbers(text) { + return text.replace(/[^0-9]+/g, ''); + } + + removeWhiteSpaces(text) { + return (text || '').replace(/\s/g, ''); + } +} diff --git a/src/components/MaskedText/lib/masks/bank-slip.mask.js b/src/components/MaskedText/lib/masks/bank-slip.mask.js new file mode 100644 index 00000000..d60da347 --- /dev/null +++ b/src/components/MaskedText/lib/masks/bank-slip.mask.js @@ -0,0 +1,45 @@ +/* eslint-disable require-jsdoc */ +/* eslint-disable no-unused-vars */ +import BaseMask from './_base.mask'; +import CustomMask from './custom.mask'; + +const MASK_BANK_SLIP_COMMON = + '99999.99999\n99999.999999\n99999\n999999\n9\n99999999999999'; +const MASK_BANK_SLIP_ANOTHER = + '99999999999\n9\n99999999999\n9\n99999999999\n9\n99999999999\n9'; + +const checkBill = (string) => string.charAt(0) !== '8'; + +const customMaskOptions = (value) => { + if (value && checkBill(value)) { + return { mask: MASK_BANK_SLIP_COMMON }; + } + return { mask: MASK_BANK_SLIP_ANOTHER }; +}; + +const validateLength = (value, toOnlyNumbers) => + (value && checkBill(value) + ? toOnlyNumbers(value).length === 47 + : toOnlyNumbers(value).length === 48) || false; + +export default class OnlyNumbersMask extends BaseMask { + static getType() { + return 'bank-slip'; + } + + getValue(value, settings) { + return CustomMask.shared.getValue(value, customMaskOptions(value)); + } + + getRawValue(maskedValue, settings) { + return super.removeNotNumbers(maskedValue); + } + + validate(value, settings) { + return validateLength(value, this.toOnlyNumbers) || false; + } + + getMask(value, settings) { + return customMaskOptions(value).mask; + } +} diff --git a/src/components/MaskedText/lib/masks/cel-phone.mask.js b/src/components/MaskedText/lib/masks/cel-phone.mask.js new file mode 100644 index 00000000..875d758b --- /dev/null +++ b/src/components/MaskedText/lib/masks/cel-phone.mask.js @@ -0,0 +1,72 @@ +import BaseMask from './_base.mask'; +import CustomMask from './custom.mask'; + +const PHONE_8_MASK = '9999-9999'; +const PHONE_9_MASK = '99999-9999'; +const PHONE_INTERNATIONAL = '+999 999 999 999'; + +const MASK_TYPES = { + BRL: 'BRL', + INTERNATIONAL: 'INTERNATIONAL', +}; + +const CEL_PHONE_SETTINGS = { + maskType: MASK_TYPES.BRL, + withDDD: true, + dddMask: '(99) ', +}; + +export default class CelPhoneMask extends BaseMask { + static getType() { + return 'cel-phone'; + } + + getValue(value, settings) { + const cleanedValue = super.removeNotNumbers(value); + const mask = this.getMask(cleanedValue, settings); + return CustomMask.shared.getValue(cleanedValue, { mask }); + } + + getRawValue(maskedValue, settings) { + return super.removeNotNumbers(maskedValue); + } + + validate(value, settings) { + let valueToValidate = super.getDefaultValue(value); + valueToValidate = this.getValue(value, settings); + + const mask = this.getMask(value, settings); + + return valueToValidate.length === mask.length; + } + + getMask(value, settings) { + const mergedSettings = super.mergeSettings(CEL_PHONE_SETTINGS, settings); + + if (mergedSettings.maskType === MASK_TYPES.INTERNATIONAL) { + return PHONE_INTERNATIONAL; + } + + const numbers = super.removeNotNumbers(value); + let mask = PHONE_8_MASK; + + const use9DigitMask = (() => { + if (mergedSettings.withDDD) { + const numbersDDD = super.removeNotNumbers(mergedSettings.dddMask); + const remainingValueNumbers = numbers.substr(numbersDDD.length); + return remainingValueNumbers.length >= 9; + } + return numbers.length >= 9; + })(); + + if (use9DigitMask) { + mask = PHONE_9_MASK; + } + + if (mergedSettings.withDDD) { + mask = `${mergedSettings.dddMask}${mask}`; + } + + return mask; + } +} diff --git a/src/components/MaskedText/lib/masks/cnpj.mask.js b/src/components/MaskedText/lib/masks/cnpj.mask.js new file mode 100644 index 00000000..81ffbd04 --- /dev/null +++ b/src/components/MaskedText/lib/masks/cnpj.mask.js @@ -0,0 +1,49 @@ +import BaseMask from './_base.mask'; +import CustomMask from './custom.mask'; + +export const CNPJ_MASK = '99.999.999/9999-99'; + +export const validateCnpj = (cnpj) => { + const valida = new Array(6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2); + let dig1 = new Number(); + let dig2 = new Number(); + let i = 0; + + const exp = /\.|\-|\//g; + cnpj = cnpj.toString().replace(exp, ''); + const digito = new Number(eval(cnpj.charAt(12) + cnpj.charAt(13))); + + for (i = 0; i < valida.length; i++) { + dig1 += i > 0 ? cnpj.charAt(i - 1) * valida[i] : 0; + dig2 += cnpj.charAt(i) * valida[i]; + } + dig1 = dig1 % 11 < 2 ? 0 : 11 - (dig1 % 11); + dig2 = dig2 % 11 < 2 ? 0 : 11 - (dig2 % 11); + + return dig1 * 10 + dig2 == digito; +}; + +const customMaskOptions = { mask: CNPJ_MASK }; + +export default class CnpjMask extends BaseMask { + static getType() { + return 'cnpj'; + } + + getValue(value, settings) { + return CustomMask.shared.getValue(value, customMaskOptions); + } + + getRawValue(maskedValue, settings) { + return super.removeNotNumbers(maskedValue); + } + + validate(value, settings) { + const isEmpty = (value || '').trim().length === 0; + return !isEmpty && validateCnpj(value); + } + + getMask(value, settings) { + return CNPJ_MASK; + } +} diff --git a/src/components/MaskedText/lib/masks/cpf.mask.js b/src/components/MaskedText/lib/masks/cpf.mask.js new file mode 100644 index 00000000..c009dd2d --- /dev/null +++ b/src/components/MaskedText/lib/masks/cpf.mask.js @@ -0,0 +1,90 @@ +import BaseMask from './_base.mask'; +import CustomMask from './custom.mask'; + +export const CPF_MASK = '999.999.999-99'; + +export const validateCPF = (cpf) => { + if (cpf == '') { + return true; + } + + cpf = cpf.replace(/\./gi, '').replace(/-/gi, ''); + let isValid = true; + let sum; + let rest; + let i; + i = 0; + sum = 0; + + if ( + cpf.length != 11 || + cpf == '00000000000' || + cpf == '11111111111' || + cpf == '22222222222' || + cpf == '33333333333' || + cpf == '44444444444' || + cpf == '55555555555' || + cpf == '66666666666' || + cpf == '77777777777' || + cpf == '88888888888' || + cpf == '99999999999' + ) { + isValid = false; + } + + for (i = 1; i <= 9; i++) { + sum += parseInt(cpf.substring(i - 1, i)) * (11 - i); + } + + rest = (sum * 10) % 11; + + if (rest == 10 || rest == 11) { + rest = 0; + } + + if (rest != parseInt(cpf.substring(9, 10))) { + isValid = false; + } + + sum = 0; + + for (i = 1; i <= 10; i++) { + sum += parseInt(cpf.substring(i - 1, i)) * (12 - i); + } + + rest = (sum * 10) % 11; + + if (rest == 10 || rest == 11) { + rest = 0; + } + if (rest != parseInt(cpf.substring(10, 11))) { + isValid = false; + } + + return isValid; +}; + +const maskOptions = { mask: CPF_MASK }; + +export default class CpfMask extends BaseMask { + static getType() { + return 'cpf'; + } + + getValue(value, settings) { + return CustomMask.shared.getValue(value, maskOptions); + } + + getRawValue(maskedValue, settings) { + return super.removeNotNumbers(maskedValue); + } + + validate(value, settings) { + const isEmpty = (value || '').trim().length === 0; + return !isEmpty && validateCPF(value); + } + + getMask(value, settings) { + return CPF_MASK; + } +} diff --git a/src/components/MaskedText/lib/masks/credit-card.mask.js b/src/components/MaskedText/lib/masks/credit-card.mask.js new file mode 100644 index 00000000..e2b2c9ab --- /dev/null +++ b/src/components/MaskedText/lib/masks/credit-card.mask.js @@ -0,0 +1,75 @@ +import BaseMask from './_base.mask'; +import CustomMask from './custom.mask'; + +const CREDIT_CARD_MASKS = { + 'visa-or-mastercard': { + regular: '9999 9999 9999 9999', + obfuscated: '9999 **** **** 9999', + }, + amex: { + regular: '9999 999999 99999', + obfuscated: '9999 ****** 99999', + }, + diners: { + regular: '9999 999999 9999', + obfuscated: '9999 ****** 9999', + }, +}; + +const CREDIT_CARD_SETTINGS = { + obfuscated: false, + issuer: 'visa-or-mastercard', +}; + +const MASK_TRANSLATION = { + '*': (val) => null, +}; + +export default class CreditCardMask extends BaseMask { + static getType() { + return 'credit-card'; + } + + getValue(value, settings) { + const selectedMask = this.getMask(value, settings); + return CustomMask.shared.getValue(value, { + mask: selectedMask, + translation: MASK_TRANSLATION, + }); + } + + validate(value, settings) { + if (value) { + const selectedMask = this.getMask(value, settings); + return value.length === selectedMask.length; + } + + return true; + } + + getRawValue(maskedValue, settings) { + if (!maskedValue) return []; + + return maskedValue.split(' ').map((val) => { + if (!val) return ''; + + return val.trim(); + }); + } + + getMask(value, settings) { + const mergedSettings = super.mergeSettings(CREDIT_CARD_SETTINGS, settings); + const selectedMask = this._selectMask( + mergedSettings.issuer, + mergedSettings.obfuscated, + ); + + return selectedMask; + } + + _selectMask(issuer, obfuscated) { + const maskType = obfuscated ? 'obfuscated' : 'regular'; + + return CREDIT_CARD_MASKS[issuer][maskType]; + } +} diff --git a/src/components/MaskedText/lib/masks/custom.mask.js b/src/components/MaskedText/lib/masks/custom.mask.js new file mode 100644 index 00000000..40152a2d --- /dev/null +++ b/src/components/MaskedText/lib/masks/custom.mask.js @@ -0,0 +1,129 @@ +import BaseMask from './_base.mask'; +import { replaceMaskRegex } from './maskInRegex'; + +function getDefaultTranslation() { + return { + 9(val) { + return val.replace(/[^0-9]+/g, ''); + }, + A(val) { + return val.replace(/[^a-zA-Z]+/g, ''); + }, + S(val) { + return val.replace(/[^a-zA-Z0-9]+/g, ''); + }, + '*': function (val) { + return val; + }, + }; +} + +function toPattern(value, mask, translation) { + let result = ''; + + let maskCharIndex = 0; + let valueCharIndex = 0; + + while (true) { + // if mask is ended, break. + if (maskCharIndex === mask.length) { + break; + } + + // if value is ended, break. + if (valueCharIndex === value.length) { + break; + } + + const maskChar = mask[maskCharIndex]; + const valueChar = value[valueCharIndex]; + + // value equals mask, just set + if (maskChar === valueChar) { + result += maskChar; + valueCharIndex += 1; + maskCharIndex += 1; + continue; + } + + // apply translator if match + const translationHandler = translation[maskChar]; + + if (translationHandler) { + const resolverValue = translationHandler(valueChar || ''); + if (resolverValue === '') { + // valueChar replaced so don't add it to result, keep the mask at the same point and continue to next value char + valueCharIndex += 1; + continue; + } else if (resolverValue !== null) { + result += resolverValue; + valueCharIndex += 1; + } else { + result += maskChar; + } + maskCharIndex += 1; + continue; + } + + // not masked value, fixed char on mask + result += maskChar; + maskCharIndex += 1; + continue; + } + + return result; +} + +const DEFAULT_TRANSLATION = getDefaultTranslation(); + +export default class CustomMask extends BaseMask { + static getType() { + return 'custom'; + } + + static getDefaultTranslation() { + return getDefaultTranslation(); + } + + static shared = new CustomMask(); + + getKeyboardType() { + return 'default'; + } + + getValue(value, settings) { + let masked = ''; + + if (value === '') { + return value; + } + + if (settings.type) { + masked = replaceMaskRegex(value, settings.type); + } else { + const { mask } = settings; + const translation = this.mergeSettings( + DEFAULT_TRANSLATION, + settings.translation, + ); + masked = toPattern(value, mask, translation); + } + return masked; + } + + getRawValue(maskedValue, settings) { + if (!!settings && settings.getRawValue) { + return settings.getRawValue(maskedValue, settings); + } + + return maskedValue; + } + + validate(value, settings) { + if (!!settings && settings.validator) { + return settings.validator(value, settings); + } + + return true; + } +} diff --git a/src/components/MaskedText/lib/masks/datetime.mask.js b/src/components/MaskedText/lib/masks/datetime.mask.js new file mode 100644 index 00000000..b2a9ce72 --- /dev/null +++ b/src/components/MaskedText/lib/masks/datetime.mask.js @@ -0,0 +1,46 @@ +import date from 'date-and-time'; +import BaseMask from './_base.mask'; +import CustomMask from './custom.mask'; + +const DATETIME_MASK_SETTINGS = { + format: 'DD/MM/YYYY HH:mm:ss', +}; + +export default class DatetimeMask extends BaseMask { + static getType() { + return 'datetime'; + } + + getValue(value, settings) { + const mergedSettings = this._getMergedSettings(settings); + const mask = this.getMask(value, mergedSettings); + + return CustomMask.shared.getValue(value, { mask }); + } + + getRawValue(maskedValue, settings) { + const mergedSettings = this._getMergedSettings(settings); + return date.parse(maskedValue, mergedSettings.format); + } + + validate(value, settings) { + const maskedValue = this.getValue(value, settings); + const mergedSettings = this._getMergedSettings(settings); + const isValid = date.isValid(maskedValue, mergedSettings.format); + return isValid; + } + + _getMergedSettings(settings) { + return super.mergeSettings(DATETIME_MASK_SETTINGS, settings); + } + + getMask(value, settings) { + let mask = ''; + + for (let i = 0; i < settings.format.length; i++) { + mask += settings.format[i].replace(/[a-zA-Z]+/g, '9'); + } + + return mask; + } +} diff --git a/src/components/MaskedText/lib/masks/document-mask.js b/src/components/MaskedText/lib/masks/document-mask.js new file mode 100644 index 00000000..2007987d --- /dev/null +++ b/src/components/MaskedText/lib/masks/document-mask.js @@ -0,0 +1,34 @@ +import BaseMask from './_base.mask'; +import CustomMask from './custom.mask'; +import { validateCnpj, CNPJ_MASK } from './cnpj.mask'; +import { validateCPF, CPF_MASK } from './cpf.mask'; + +const customMaskOptions = (value) => { + if (value && value.length > 13 && !validateCPF(value)) { + return { mask: CNPJ_MASK }; + } + return { mask: CPF_MASK }; +}; + +export default class DocumentMask extends BaseMask { + static getType() { + return 'document'; + } + + getValue(value, settings) { + return CustomMask.shared.getValue(value, customMaskOptions(value)); + } + + getRawValue(maskedValue, settings) { + return super.removeNotNumbers(maskedValue); + } + + validate(value, settings) { + const isEmpty = (value || '').trim().length === 0; + return !isEmpty && (validateCPF(value) || validateCnpj(value)); + } + + getMask(value, settings) { + return customMaskOptions(value).mask; + } +} diff --git a/src/components/MaskedText/lib/masks/index.js b/src/components/MaskedText/lib/masks/index.js new file mode 100644 index 00000000..fd98b90c --- /dev/null +++ b/src/components/MaskedText/lib/masks/index.js @@ -0,0 +1,25 @@ +import BankSlip from './bank-slip.mask'; +import CelPhoneMask from './cel-phone.mask'; +import CnpjMask from './cnpj.mask'; +import CpfMask from './cpf.mask'; +import CreditCardMask from './credit-card.mask'; +import CustomMask from './custom.mask'; +import DatetimeMask from './datetime.mask'; +import DocumentMask from './document-mask'; +import NoMask from './no-mask.mask'; +import OnlyNumbersMask from './only-numbers.mask'; +import Uppercase from './uppercase.mask'; +import ZipCodeMask from './zip-code.mask'; + +module.exports.CelPhoneMask = CelPhoneMask; +module.exports.CnpjMask = CnpjMask; +module.exports.CpfMask = CpfMask; +module.exports.CustomMask = CustomMask; +module.exports.DatetimeMask = DatetimeMask; +module.exports.OnlyNumbersMask = OnlyNumbersMask; +module.exports.ZipCodeMask = ZipCodeMask; +module.exports.CreditCardMask = CreditCardMask; +module.exports.NoMask = NoMask; +module.exports.DocumentMask = DocumentMask; +module.exports.BankSlip = BankSlip; +module.exports.Uppercase = Uppercase; diff --git a/src/components/MaskedText/lib/masks/maskInRegex.js b/src/components/MaskedText/lib/masks/maskInRegex.js new file mode 100644 index 00000000..0a834b65 --- /dev/null +++ b/src/components/MaskedText/lib/masks/maskInRegex.js @@ -0,0 +1,16 @@ +export const MASKS_TYPE = { + 'account-bank': { + regex: /(\d{1,100})(\d{1})/, + mask: '$1-$2', + }, +}; + +export const replaceMaskRegex = (value, mask) => { + if (!value || !mask) { + return; + } + const newValue = value.match(/[a-zA-Z0-9]/g, '').join(''); + return MASKS_TYPE[mask].regex && MASKS_TYPE[mask].mask + ? newValue.replace(MASKS_TYPE[mask].regex, MASKS_TYPE[mask].mask) + : value; +}; diff --git a/src/components/MaskedText/lib/masks/no-mask.mask.js b/src/components/MaskedText/lib/masks/no-mask.mask.js new file mode 100644 index 00000000..151be9c3 --- /dev/null +++ b/src/components/MaskedText/lib/masks/no-mask.mask.js @@ -0,0 +1,23 @@ +import BaseMask from './_base.mask'; + +export default class OnlyNumbersMask extends BaseMask { + static getType() { + return 'no-mask'; + } + + getValue(value, settings) { + return String(value); + } + + getRawValue(maskedValue, settings) { + return String(maskedValue); + } + + validate(value, settings) { + return true; + } + + getMask(value, settings) { + return ''; + } +} diff --git a/src/components/MaskedText/lib/masks/only-numbers.mask.js b/src/components/MaskedText/lib/masks/only-numbers.mask.js new file mode 100644 index 00000000..7441b45e --- /dev/null +++ b/src/components/MaskedText/lib/masks/only-numbers.mask.js @@ -0,0 +1,23 @@ +import BaseMask from './_base.mask'; + +export default class OnlyNumbersMask extends BaseMask { + static getType() { + return 'only-numbers'; + } + + getValue(value, settings) { + return this.removeNotNumbers(String(value)); + } + + getRawValue(maskedValue, settings) { + return super.removeNotNumbers(String(maskedValue)); + } + + validate(value, settings) { + return true; + } + + getMask(value, settings) { + return ''; + } +} diff --git a/src/components/MaskedText/lib/masks/uppercase.mask.js b/src/components/MaskedText/lib/masks/uppercase.mask.js new file mode 100644 index 00000000..c8f4af56 --- /dev/null +++ b/src/components/MaskedText/lib/masks/uppercase.mask.js @@ -0,0 +1,23 @@ +import BaseMask from './_base.mask'; + +export default class UppercaseMask extends BaseMask { + static getType() { + return 'uppercase'; + } + + getValue(value, settings) { + return String(value).toUpperCase(); + } + + getRawValue(maskedValue, settings) { + return String(value).toUpperCase(); + } + + validate(value, settings) { + return true; + } + + getMask(value, settings) { + return ''; + } +} diff --git a/src/components/MaskedText/lib/masks/zip-code.mask.js b/src/components/MaskedText/lib/masks/zip-code.mask.js new file mode 100644 index 00000000..84fa9721 --- /dev/null +++ b/src/components/MaskedText/lib/masks/zip-code.mask.js @@ -0,0 +1,34 @@ +import BaseMask from './_base.mask'; +import CustomMask from './custom.mask'; + +const ZIP_CODE_MASK = '99999-999'; + +const MASK_OPTIONS = { + mask: ZIP_CODE_MASK, +}; + +export default class ZipCodeMask extends BaseMask { + static getType() { + return 'zip-code'; + } + + getValue(value, settings) { + return CustomMask.shared.getValue(value, MASK_OPTIONS); + } + + getRawValue(maskedValue, settings) { + return super.removeNotNumbers(maskedValue); + } + + validate(value, settings) { + if (value) { + return value.length === ZIP_CODE_MASK.length; + } + + return true; + } + + getMask(value, settings) { + return ZIP_CODE_MASK; + } +} diff --git a/src/components/MaskedText/lib/text-input-mask.js b/src/components/MaskedText/lib/text-input-mask.js new file mode 100644 index 00000000..15e6028c --- /dev/null +++ b/src/components/MaskedText/lib/text-input-mask.js @@ -0,0 +1,72 @@ +import React from 'react'; +import { TextInput } from 'react-native'; +import BaseTextComponent from './base-text-component'; + +export default class TextInputMask extends BaseTextComponent { + getElement() { + return this._inputElement; + } + + _onChangeText(text) { + if (!this._checkText(text)) { + return; + } + + const { maskedText, rawText } = this.updateValue(text); + + if (this.props.onChangeText) { + this._trySetNativeProps(maskedText); + this.props.onChangeText(maskedText, rawText); + } + } + + _trySetNativeProps(maskedText) { + try { + const element = this.getElement(); + element.setNativeProps && element.setNativeProps({ text: maskedText }); + } catch (error) { + // silent + } + } + + _checkText(text) { + if (this.props.checkText) { + return this.props.checkText(this.props.value, text); + } + + return true; + } + + _getKeyboardType() { + return this.props.keyboardType || this._maskHandler.getKeyboardType(); + } + + render() { + let Input = TextInput; + let customTextInputProps = {}; + + if (this.props.customTextInput) { + Input = this.props.customTextInput; + customTextInputProps = this.props.customTextInputProps || {}; + } + + return ( + { + if (ref) { + this._inputElement = ref; + + if (typeof this.props.refInput === 'function') { + this.props.refInput(ref); + } + } + }} + keyboardType={this._getKeyboardType()} + {...this.props} + {...customTextInputProps} + onChangeText={(text) => this._onChangeText(text)} + value={this.getDisplayValueFor(this.props.value)} + /> + ); + } +} diff --git a/src/components/MaskedText/lib/text-mask.js b/src/components/MaskedText/lib/text-mask.js new file mode 100644 index 00000000..e99b5449 --- /dev/null +++ b/src/components/MaskedText/lib/text-mask.js @@ -0,0 +1,23 @@ +import React from 'react'; +import { Text } from 'react-native'; +import BaseTextComponent from './base-text-component'; + +const TEXT_REF = '$text'; + +export default class TextMask extends BaseTextComponent { + constructor(props) { + super(props); + } + + getElement() { + return this.refs[TEXT_REF]; + } + + render() { + return ( + + {this.getDisplayValueFor(this.props.value)} + + ); + } +} From 2afd052acd811b171545a6189da1531151d5c5ad Mon Sep 17 00:00:00 2001 From: Renato Barboza Date: Tue, 20 Jul 2021 02:48:24 -0300 Subject: [PATCH 05/12] change imports to local folder MaskedText --- src/components/TextInput/MaskedTextInput/styles.ts | 2 +- src/types/TextInputType.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/TextInput/MaskedTextInput/styles.ts b/src/components/TextInput/MaskedTextInput/styles.ts index f14880a2..c93fd113 100644 --- a/src/components/TextInput/MaskedTextInput/styles.ts +++ b/src/components/TextInput/MaskedTextInput/styles.ts @@ -1,5 +1,5 @@ import styled from 'styled-components/native'; -import { TextInputMask } from '@platformbuilders/react-native-masked-text'; +import { TextInputMask } from '../../MaskedText'; import { TextInput as TextInputStyle } from '../styles'; const Input = TextInputStyle.withComponent(TextInputMask); diff --git a/src/types/TextInputType.ts b/src/types/TextInputType.ts index b7cb8a11..01448f29 100644 --- a/src/types/TextInputType.ts +++ b/src/types/TextInputType.ts @@ -6,7 +6,8 @@ import { TextStyle, ViewStyle, } from 'react-native'; -import { TextInputMaskTypeProp } from '@platformbuilders/react-native-masked-text'; + +import { TextInputMaskTypeProp } from '../components/MaskedText'; import { HitSlopType } from './Common'; import { FontType } from './IconType'; From f2a8821612547a2a42b792c5154900c9b3d9295b Mon Sep 17 00:00:00 2001 From: Renato Barboza Date: Tue, 20 Jul 2021 02:48:41 -0300 Subject: [PATCH 06/12] add masks --- src/components/MaskedText/index.d.ts | 110 +++++++++++++++++++++++++++ yarn.lock | 18 ----- 2 files changed, 110 insertions(+), 18 deletions(-) create mode 100644 src/components/MaskedText/index.d.ts diff --git a/src/components/MaskedText/index.d.ts b/src/components/MaskedText/index.d.ts new file mode 100644 index 00000000..9b62a895 --- /dev/null +++ b/src/components/MaskedText/index.d.ts @@ -0,0 +1,110 @@ +/** + * Type Definition. + * + * Using with Typescript development. + * + * Definitions by: Italo Izaac + */ + +import * as React from 'react' +import { TextInput, TextInputProps } from 'react-native' + +// Type prop of TextInputMask. +export type TextInputMaskTypeProp = + | 'document' + | 'credit-card' + | 'cpf' + | 'cnpj' + | 'zip-code' + | 'only-numbers' + | 'money' + | 'cel-phone' + | 'datetime' + | 'no-mask' + | 'custom' + | 'account-bank' + | 'uppercase' + +// Option prop of TextInputMask. +export interface TextInputMaskOptionProp { + // Money type. + precision?: number + separator?: string + delimiter?: string + unit?: string + suffixUnit?: string + zeroCents?: boolean + + // Phone type. + withDDD?: boolean + dddMask?: string + maskType?: 'BRL' | 'INTERNATIONAL' + + // Datetime type. + format?: string + + // Credit card type. + obfuscated?: boolean + issuer?: 'visa-or-mastercard' | 'diners' | 'amex' + + // Custom type. + mask?: string + validator?: (value: string, settings: TextInputMaskOptionProp) => boolean + getRawValue?: (value: string, settings: TextInputMaskOptionProp) => any + translation?: { [s: string]: (val: string) => string | null | undefined } +} + +// TextInputMask Props +export interface TextInputMaskProps extends Pick> { + type: TextInputMaskTypeProp + options?: TextInputMaskOptionProp + checkText?: (previous: string, next: string) => boolean + onChangeText?: (text: string, rawText?: string) => void + refInput?: (ref: any) => void + customTextInput?: any + customTextInputProps?: Object + includeRawValueInChangeText?: boolean +} + +// TextInputMask Component +export class TextInputMask extends React.Component {} + +// TextMask +export class TextMask extends React.Component {} + +// MaskService +export namespace MaskService { + function toMask( + type: string, + value: string, + options?: TextInputMaskOptionProp + ): string + function toRawValue( + type: string, + maskedValue: string, + options?: TextInputMaskOptionProp + ): string + function isValid( + type: string, + value: string, + options?: TextInputMaskOptionProp + ): boolean +} + +// TextInputMaskMethods +export class TextInputMaskMethods { + getElement(): TextInput + getRawValue(): string + isValid(): boolean +} + +// TextInputMasked +export type TextInputMasked = TextInputMaskMethods | null + +// TextMaskMethods +export class TextMaskMethods { + getElement(): TextInput +} + +// TextMaskInstance +export type TextMaskInstance = TextMaskMethods | null diff --git a/yarn.lock b/yarn.lock index 83b2196d..f4515c82 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2516,14 +2516,6 @@ react "^16.13.1" react-dom "^16.13.1" -"@platformbuilders/react-native-masked-text@1.0.3": - version "1.0.3" - resolved "https://npm.pkg.github.com/download/@platformbuilders/react-native-masked-text/1.0.3/4bc1a5c300014353fb8ba98c9a3c2d9467f32ac43473250baf63eb532c490fbd#ac64b15c13fbe2d93781dca00a84d70eb57f00b9" - integrity sha512-V2dgI8yr3kBAuVtPszQ06TmtIpT+lerNNFVegb68xNBLAph5kQ+P8Gi1HSOBFkL480lRUAmbyQdbRKmUiCxlxg== - dependencies: - date-and-time "0.14.2" - tinymask "1.0.2" - "@popperjs/core@^2.5.4", "@popperjs/core@^2.6.0": version "2.9.2" resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.9.2.tgz#adea7b6953cbb34651766b0548468e743c6a2353" @@ -7346,11 +7338,6 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" -date-and-time@0.14.2: - version "0.14.2" - resolved "https://registry.yarnpkg.com/date-and-time/-/date-and-time-0.14.2.tgz#a4266c3dead460f6c231fe9674e585908dac354e" - integrity sha512-EFTCh9zRSEpGPmJaexg7HTuzZHh6cnJj1ui7IGCFNXzd2QdpsNh05Db5TF3xzJm30YN+A8/6xHSuRcQqoc3kFA== - date-fns@^1.27.2: version "1.30.1" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c" @@ -16859,11 +16846,6 @@ tinycolor2@^1.4.1: resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.2.tgz#3f6a4d1071ad07676d7fa472e1fac40a719d8803" integrity sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA== -tinymask@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/tinymask/-/tinymask-1.0.2.tgz#df3775a94087b0d3d056c256e8923c01b7ec0941" - integrity sha1-3zd1qUCHsNPQVsJW6JI8AbfsCUE= - tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" From b8512165314bdcf1844a0836f5742a25b0475330 Mon Sep 17 00:00:00 2001 From: Renato Barboza Date: Wed, 21 Jul 2021 13:18:37 -0300 Subject: [PATCH 07/12] add date-and-time lib --- package.json | 1 + yarn.lock | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/package.json b/package.json index fa8dd2bd..23e41b39 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ }, "dependencies": { "babel-plugin-transform-es2015-modules-commonjs": "^6.26.2", + "date-and-time": "^1.0.1", "react-native-responsive-fontsize": "0.4.3", "react-native-smooth-pincode-input": "^1.0.9", "react-native-web": "0.13.3", diff --git a/yarn.lock b/yarn.lock index f4515c82..4737d5dd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7338,6 +7338,11 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" +date-and-time@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/date-and-time/-/date-and-time-1.0.1.tgz#4959b7faf1ec5873e59d926d4528b9223a808a57" + integrity sha512-7u+uNfnjWkX+YFQfivvW24TjaJG6ahvTrfw1auq7KlC7osuGcZBIWGBvB9UcENjH6JnLVhMqlRripk1dSHjAUA== + date-fns@^1.27.2: version "1.30.1" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c" From ee2f7f4f5c53e133aff2dc58086e6450ef478857 Mon Sep 17 00:00:00 2001 From: Renato Barboza Date: Wed, 21 Jul 2021 13:19:22 -0300 Subject: [PATCH 08/12] add hasBorder --- .../__snapshots__/Button.spec.tsx.snap | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/components/Button/__tests__/__snapshots__/Button.spec.tsx.snap b/src/components/Button/__tests__/__snapshots__/Button.spec.tsx.snap index 64728b33..78d3eacf 100644 --- a/src/components/Button/__tests__/__snapshots__/Button.spec.tsx.snap +++ b/src/components/Button/__tests__/__snapshots__/Button.spec.tsx.snap @@ -23,6 +23,7 @@ exports[`