diff --git a/README.md b/README.md
index 9f95c44e..ef9c7a29 100644
--- a/README.md
+++ b/README.md
@@ -51,7 +51,8 @@ export default class MyComponent extends Component {
```
### Props
-**type**
+
+#### type
*credit-card*: use the mask 9999 9999 9999 9999. It accepts options (see later in this doc).
*cpf*: use the mask `999.999.999-99` and `numeric` keyboard.
@@ -64,7 +65,7 @@ export default class MyComponent extends Component {
*custom*: use your custom mask (see the options props later in this doc).
-**onChangeText**
+#### onChangeText
Invoked after new value applied to mask.
```jsx
@@ -81,7 +82,7 @@ onChangeText(text) {
```
-**checkText**
+#### checkText
Allow you to check and prevent value to be inputed.
@@ -100,7 +101,8 @@ checkText(previous, next) {
checkText={this.checkText.bind(this)} />
```
-**customTextInput**
+#### customTextInput
+
You can use this prop if you want custom text input instead native TextInput component:
```jsx
@@ -118,14 +120,16 @@ const Textfield = MKTextField.textfield()
placeholder="Enter text to see events" />
```
-**TextInput Props**
+#### TextInput Props
+
You can use the native props of TextInput, with this in mind:
* onChangeText is intercepted by component.
* value is intercepted by component.
* if you pass keyboardType, it will override the keyboardType of masked component.
-**TextInput Methods**
+#### TextInput Methods
+
If you want to use the methods of the native TextInput, use the `getElement()` method:
```jsx
@@ -154,11 +158,12 @@ export default class App extends React.Component {
}
```
-**Options**
+#### Options
+
Some types accept options, use it like this: ``
-For `type={'money'}`
+**For `type={'money'}`**
* *options={...}*
* `precision` (Number, default 2): the decimal places.
* `separator` (String, default ','): the decimal separator.
@@ -167,12 +172,12 @@ For `type={'money'}`
* `suffixUnit` (String, default ''): the suffix text.
* `zeroCents` (Boolean, default false): if must show cents.
-For `type={'cel-phone'}`
+**For `type={'cel-phone'}`**
* *options={...}*
* `withDDD` (Boolean, default true): if the ddd will be include in the mask.
* `dddMask` (String, default '(99) '): the default mask applied if `withDDD` is true.
-For `type={'datetime'}`
+**For `type={'datetime'}`**
* *options={...}*
* `format` (String, default DD/MM/YYYY HH:mm:ss): moment date format. It accepts the following:
* DD/MM/YYYY HH:mm:ss
@@ -184,22 +189,64 @@ For `type={'datetime'}`
* HH
* *You can use all of dates with `-` instead of `/` if you want*
-For `type={'custom'}`
+**For `type={'custom'}`**
* *options={...}*
- * `mask` (String, default ''): your mask template.
- * `9`: accept digit.
- * `A`: accept alpha.
- * `S`: accept alphanumeric.
- * `validator` (Function, default returns true): the function use to validate value. Must return true if valid or false if invalid.
- * The function receives 2 parameters:
- * `value`: current value.
- * `settings`: current settings
- * `getRawValue` (Function, default returns the current value): the function that's invoked when `getRawValue` method from component is called.
- * The function receives 2 parameters:
- * `value`: current value.
- * `settings`: current settings
-
-For `type={'credit-card'}`
+
+```jsx
+{
+ /**
+ * mask: (String | required | default '')
+ * the mask pattern
+ * 9 - accept digit.
+ * A - accept alpha.
+ * S - accept alphanumeric.
+ * * - accept all.
+ */
+ mask: '999#AAA',
+
+ /**
+ * validator: (Function | optional | defaults returns true)
+ * use this funcion to inform if the inputed value is a valid value (for invalid phone numbers, for example). The isValid method use this validator.
+ */
+ validator: function(value, settings) {
+ return true
+ },
+ /**
+ * getRawValue: (Function | optional | defaults return current masked value)
+ * use this function to parse and return values to use what you want.
+ * for example, if you want to create a phone number mask (999) 999-99-99 but want to get only
+ * the numbers for value, use this method for this parse step.
+ */
+ getRawValue: function(value, settings) {
+ return 'my converted value';
+ },
+ /**
+ * translation: (Object | optional | defaults 9, A, S, *)
+ * the dictionary that translate mask and value.
+ * you can change defaults by simple override the key (9, A, S, *) or create some new.
+ */
+ translation: {
+ // this is a custom translation. The others (9, A, S, *) still works.
+ // this translation will be merged and turns into 9, A, S, *, #.
+ '#': function(val) {
+ if (val === ' ') {
+ return val;
+ }
+
+ // if returns null, undefined or '' (empty string), the value will be ignored.
+ return null;
+ },
+ // in this case, we will override build-in * translation (allow all characters)
+ // and set this to allow only blank spaces and some special characters.
+ '*': function(val) {
+ return [' ', '#', ',', '.', '!'].indexOf(val) >= 0 ? val : null;
+ }
+ }
+}
+
+```
+
+**For `type={'credit-card'}`**
* *options={...}*
* `obfuscated` (Boolean, default false): if the mask must be `9999 **** **** 9999`
@@ -302,6 +349,9 @@ var money = MaskService.toMask('money', '123', {
# Changelog
+## 1.5.0
+* Adding new and powerfull `custom` engine mask \m/.
+
## 1.4.0
* Adding `customTextInput` to allow other inputs instead native TextInput. (thank to [Hellon Canella](https://github.com/helloncanella))
diff --git a/__tests__/custom.mask.test.js b/__tests__/custom.mask.test.js
index 9fdcd98c..3f0d0869 100644
--- a/__tests__/custom.mask.test.js
+++ b/__tests__/custom.mask.test.js
@@ -130,4 +130,55 @@ test('123 with mask 999 results 123 and raw value 123(type Number)', () => {
expect(received).toBe(expected);
expect(receivedRawValue).toBe(expectedRawValue);
-});
\ No newline at end of file
+});
+
+test('mask with custom translation and match', () => {
+ var mask = new CustomMask();
+ var options = {
+ mask: '999&AAA',
+ translation: {
+ '&': function(val) {
+ return ['#', '.', ':'].indexOf(val) >= 0 ? val : null;
+ }
+ }
+ }
+
+ var expected = '123#ABC';
+ var received = mask.getValue('123#ABC', options);
+
+ expect(received).toBe(expected);
+});
+
+test('mask with custom translation and not match', () => {
+ var mask = new CustomMask();
+ var options = {
+ mask: '999&AAA',
+ translation: {
+ '&': function(val) {
+ return ['#', '.', ':'].indexOf(val) >= 0 ? val : null;
+ }
+ }
+ }
+
+ var expected = '123';
+ var received = mask.getValue('123|ABC', options);
+
+ expect(received).toBe(expected);
+});
+
+test('mask with custom translation and optionals and matching', () => {
+ var mask = new CustomMask();
+ var options = {
+ mask: '999***AAA&',
+ translation: {
+ '&': function(val) {
+ return ['#', '.', ':'].indexOf(val) >= 0 ? val : null;
+ }
+ }
+ }
+
+ var expected = '123|% ABC.';
+ var received = mask.getValue('123|% ABC.', options);
+
+ expect(received).toBe(expected);
+});
diff --git a/__tests__/only-numbers.mask.js b/__tests__/only-numbers.mask.test.js
similarity index 99%
rename from __tests__/only-numbers.mask.js
rename to __tests__/only-numbers.mask.test.js
index 798de3df..88d62d49 100644
--- a/__tests__/only-numbers.mask.js
+++ b/__tests__/only-numbers.mask.test.js
@@ -41,4 +41,4 @@ test('1 results 1 and raw value 1', () => {
expect(received).toBe(expected);
expect(receivedRawValue).toBe(expectedRawValue);
-});
\ No newline at end of file
+});
diff --git a/lib/masks/custom.mask.js b/lib/masks/custom.mask.js
index 7471c048..10b2e813 100644
--- a/lib/masks/custom.mask.js
+++ b/lib/masks/custom.mask.js
@@ -1,31 +1,86 @@
import BaseMask from './_base.mask';
+const DEFAULT_TRANSLATION = {
+ '9': function (val) {
+ return val.replace(/[^0-9]+/g, '');
+ },
+ 'A': function (val) {
+ return val.replace(/[^a-zA-Z]+/g, '');
+ },
+ 'S': function (val) {
+ return val.replace(/[^a-zA-Z0-9]+/g, '');
+ },
+ '*': function (val) {
+ return val
+ }
+}
+
+var invalidValues = [null, undefined, ''];
+
export default class CustomMask extends BaseMask {
- static getType() {
- return 'custom';
- }
-
- getKeyboardType() {
- return "default";
- }
-
- getValue(value, settings) {
- return this.getVMasker().toPattern(value, settings.mask);
- }
-
- 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;
- }
-}
\ No newline at end of file
+ static getType() {
+ return 'custom';
+ }
+
+ getKeyboardType() {
+ return "default";
+ }
+
+ getValue(value, settings) {
+ let { mask } = settings;
+ let translation = this.mergeSettings(DEFAULT_TRANSLATION, settings.translation);
+
+ var result = '';
+
+ const maskSize = mask.length;
+ const valueSize = value.length;
+
+ var maskResolved = 0;
+ var valueResolved = 0;
+
+ while (maskResolved < maskSize && valueResolved < valueSize) {
+ const valueChar = value[valueResolved];
+ const maskChar = mask[maskResolved];
+
+ if (valueChar === maskChar) {
+ result += valueChar;
+ maskResolved++;
+ valueResolved++;
+ continue;
+ }
+
+ const handler = translation[maskChar];
+
+ if (!handler) {
+ result += maskChar;
+ maskResolved++;
+ continue;
+ }
+
+ var masked = handler(valueChar);
+ if (invalidValues.indexOf(masked) < 0) {
+ result += masked
+ maskResolved++
+ }
+ valueResolved++
+ }
+
+ return result;
+ }
+
+ 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/package.json b/package.json
index e3360213..871ed255 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "react-native-masked-text",
- "version": "1.4.0",
+ "version": "1.5.0",
"description": "Text and TextInput with mask for React Native applications",
"main": "index.js",
"scripts": {