@elliemae/ds-controlled-form 2.4.1-rc.5 → 2.4.1-rc.6

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.
Files changed (31) hide show
  1. package/cjs/index.js +4 -0
  2. package/cjs/mask-hook/hooks/index.js +4 -0
  3. package/cjs/mask-hook/hooks/useNumberMask.js +2 -0
  4. package/cjs/mask-hook/hooks/usePhoneMask.js +2 -0
  5. package/cjs/mask-hook/hooks/useRegExpMask.js +122 -0
  6. package/cjs/mask-hook/hooks/useSSNMask.js +2 -0
  7. package/cjs/mask-hook/hooks/useZipCodeMask.js +2 -0
  8. package/cjs/mask-hook/index.js +4 -0
  9. package/cjs/mask-hook/react-desc-prop-types.js +9 -0
  10. package/cjs/mask-hook/utils/addSpecialCharacters.js +3 -4
  11. package/cjs/mask-hook/utils/flatStringArray.js +18 -0
  12. package/cjs/mask-hook/utils/index.js +2 -0
  13. package/esm/index.js +1 -0
  14. package/esm/mask-hook/hooks/index.js +1 -0
  15. package/esm/mask-hook/hooks/useNumberMask.js +2 -0
  16. package/esm/mask-hook/hooks/usePhoneMask.js +2 -0
  17. package/esm/mask-hook/hooks/useRegExpMask.js +116 -0
  18. package/esm/mask-hook/hooks/useSSNMask.js +2 -0
  19. package/esm/mask-hook/hooks/useZipCodeMask.js +2 -0
  20. package/esm/mask-hook/index.js +1 -0
  21. package/esm/mask-hook/react-desc-prop-types.js +8 -1
  22. package/esm/mask-hook/utils/addSpecialCharacters.js +3 -4
  23. package/esm/mask-hook/utils/flatStringArray.js +14 -0
  24. package/esm/mask-hook/utils/index.js +1 -0
  25. package/package.json +23 -15
  26. package/types/mask-hook/hooks/index.d.ts +1 -0
  27. package/types/mask-hook/hooks/useRegExpMask.d.ts +4 -0
  28. package/types/mask-hook/react-desc-prop-types.d.ts +6 -1
  29. package/types/mask-hook/tests/useRegExpMask.test.d.ts +1 -0
  30. package/types/mask-hook/utils/flatStringArray.d.ts +1 -0
  31. package/types/mask-hook/utils/index.d.ts +1 -0
package/cjs/index.js CHANGED
@@ -18,6 +18,7 @@ var timeHelpers = require('./date-time-picker/utils/timeHelpers.js');
18
18
  var usePhoneMask = require('./mask-hook/hooks/usePhoneMask.js');
19
19
  var useNumberMask = require('./mask-hook/hooks/useNumberMask.js');
20
20
  var useSSNMask = require('./mask-hook/hooks/useSSNMask.js');
21
+ var useRegExpMask = require('./mask-hook/hooks/useRegExpMask.js');
21
22
  var useZipCodeMask = require('./mask-hook/hooks/useZipCodeMask.js');
22
23
  var DSInputText = require('./text-input/DSInputText.js');
23
24
  var dataTestIds = require('./text-input/exported-related/data-test-ids.js');
@@ -61,6 +62,9 @@ exports.useNumberMask = useNumberMask.useNumberMask;
61
62
  exports.UseSSNMaskWithSchema = useSSNMask.UseSSNMaskWithSchema;
62
63
  exports.getSSNMaskedValue = useSSNMask.getSSNMaskedValue;
63
64
  exports.useSSNMask = useSSNMask.useSSNMask;
65
+ exports.UseRegExpMaskWithSchema = useRegExpMask.UseRegExpMaskWithSchema;
66
+ exports.isRegExp = useRegExpMask.isRegExp;
67
+ exports.useRegExpMask = useRegExpMask.useRegExpMask;
64
68
  exports.UseZipCodeMaskWithSchema = useZipCodeMask.UseZipCodeMaskWithSchema;
65
69
  exports.getUseZipMaskedValue = useZipCodeMask.getUseZipMaskedValue;
66
70
  exports.useZipCodeMask = useZipCodeMask.useZipCodeMask;
@@ -5,6 +5,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
5
5
  var usePhoneMask = require('./usePhoneMask.js');
6
6
  var useNumberMask = require('./useNumberMask.js');
7
7
  var useSSNMask = require('./useSSNMask.js');
8
+ var useRegExpMask = require('./useRegExpMask.js');
8
9
  var useZipCodeMask = require('./useZipCodeMask.js');
9
10
 
10
11
 
@@ -18,6 +19,9 @@ exports.useNumberMask = useNumberMask.useNumberMask;
18
19
  exports.UseSSNMaskWithSchema = useSSNMask.UseSSNMaskWithSchema;
19
20
  exports.getSSNMaskedValue = useSSNMask.getSSNMaskedValue;
20
21
  exports.useSSNMask = useSSNMask.useSSNMask;
22
+ exports.UseRegExpMaskWithSchema = useRegExpMask.UseRegExpMaskWithSchema;
23
+ exports.isRegExp = useRegExpMask.isRegExp;
24
+ exports.useRegExpMask = useRegExpMask.useRegExpMask;
21
25
  exports.UseZipCodeMaskWithSchema = useZipCodeMask.UseZipCodeMaskWithSchema;
22
26
  exports.getUseZipMaskedValue = useZipCodeMask.getUseZipMaskedValue;
23
27
  exports.useZipCodeMask = useZipCodeMask.useZipCodeMask;
@@ -14,6 +14,8 @@ var dsUtilities = require('@elliemae/ds-utilities');
14
14
  require('core-js/modules/esnext.async-iterator.for-each.js');
15
15
  require('core-js/modules/esnext.iterator.for-each.js');
16
16
  var setCursorPosition = require('../utils/setCursorPosition.js');
17
+ require('core-js/modules/esnext.async-iterator.reduce.js');
18
+ require('core-js/modules/esnext.iterator.reduce.js');
17
19
  var reactDescPropTypes = require('../react-desc-prop-types.js');
18
20
 
19
21
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
@@ -13,6 +13,8 @@ var dsUtilities = require('@elliemae/ds-utilities');
13
13
  var addSpecialCharacters = require('../utils/addSpecialCharacters.js');
14
14
  var getPartialMaskedPos = require('../utils/getPartialMaskedPos.js');
15
15
  var setCursorPosition = require('../utils/setCursorPosition.js');
16
+ require('core-js/modules/esnext.async-iterator.reduce.js');
17
+ require('core-js/modules/esnext.iterator.reduce.js');
16
18
  var reactDescPropTypes = require('../react-desc-prop-types.js');
17
19
 
18
20
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
@@ -0,0 +1,122 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ require('core-js/modules/web.dom-collections.iterator.js');
6
+ var React = require('react');
7
+ var dsPropsHelpers = require('@elliemae/ds-props-helpers');
8
+ var dsUtilities = require('@elliemae/ds-utilities');
9
+ var addSpecialCharacters = require('../utils/addSpecialCharacters.js');
10
+ var getPartialMaskedPos = require('../utils/getPartialMaskedPos.js');
11
+ var setCursorPosition = require('../utils/setCursorPosition.js');
12
+ require('core-js/modules/esnext.async-iterator.filter.js');
13
+ require('core-js/modules/esnext.iterator.constructor.js');
14
+ require('core-js/modules/esnext.iterator.filter.js');
15
+ var flatStringArray = require('../utils/flatStringArray.js');
16
+ var reactDescPropTypes = require('../react-desc-prop-types.js');
17
+
18
+ const isRegExp = item => typeof item === 'object';
19
+
20
+ const conformValue = (rawValue, cursorPos, lastKeyCode, regExp, filledMask) => {
21
+ const specialChars = [];
22
+ const flatRegExp = flatStringArray.flatStringArray(regExp);
23
+ const rawValueArray = rawValue.split('');
24
+ let offSet = 0;
25
+ let charsFound = 0;
26
+ let specialCharsLeft = '';
27
+ let fullRegExpMatched = false;
28
+ const maskedValue = [];
29
+
30
+ for (let i = 0; i < flatRegExp.length; i += 1) {
31
+ if (isRegExp(flatRegExp[i])) {
32
+ specialCharsLeft = '';
33
+ fullRegExpMatched = false;
34
+
35
+ for (let j = charsFound + offSet; j < rawValueArray.length; j += 1) {
36
+ var _rawValueArray$j;
37
+
38
+ const match = (_rawValueArray$j = rawValueArray[j]) === null || _rawValueArray$j === void 0 ? void 0 : _rawValueArray$j.match(flatRegExp[i]);
39
+
40
+ if (match) {
41
+ maskedValue.push(match[0]);
42
+ charsFound += 1;
43
+ fullRegExpMatched = true;
44
+ break;
45
+ } else {
46
+ offSet += 1;
47
+ }
48
+ }
49
+
50
+ if (filledMask && !fullRegExpMatched) {
51
+ maskedValue.push('_');
52
+ }
53
+ } else {
54
+ specialChars.push([flatRegExp[i], i]);
55
+ if (rawValueArray[i + offSet] === flatRegExp[i]) charsFound += 1;
56
+ specialCharsLeft += flatRegExp[i];
57
+ }
58
+ }
59
+
60
+ if (!fullRegExpMatched) specialCharsLeft = '';
61
+ const maskedPos = getPartialMaskedPos.getPartialMaskedPos(rawValue, maskedValue, cursorPos);
62
+ const [maskWithSpecialsChars, finalMaskedPos] = addSpecialCharacters.addSpecialCharacters(maskedValue, specialChars, maskedPos, lastKeyCode);
63
+ return [maskWithSpecialsChars + specialCharsLeft, finalMaskedPos];
64
+ };
65
+
66
+ const useRegExpMask = props => {
67
+ const propsWithDefault = dsPropsHelpers.useMemoMergePropsWithDefault(props, reactDescPropTypes.DSMaskRegExpHookDefaultProps);
68
+ dsPropsHelpers.useValidateTypescriptPropTypes(propsWithDefault, reactDescPropTypes.DSMaskRegExpHookPropsTypes);
69
+ const {
70
+ valueSetter,
71
+ onChange: userOnChange,
72
+ onKeyDown: userOnKeyDown,
73
+ onBlur: userOnBlur,
74
+ regExp,
75
+ filledMask
76
+ } = propsWithDefault;
77
+ const lastKeyCode = React.useRef('Unidentified');
78
+ const scheduleAfterRender = dsUtilities.useCallbackAfterRender(); // Some times, the value of the mask is not gonna change (user types invalid char for example)
79
+ // This makes the input to go from "correct" -> "incorrect" -> "correct"
80
+ // But since the input is controlled, the input does not re-render
81
+ // This makes the input go mumbo-jumbo and places the cursor in the end, because we kinda changed the value async
82
+ // Solution for this is to trigger a re-render, we do this with a set state call in the on change event
83
+
84
+ const [, setKey] = React.useState(0);
85
+ const onKeyDown = React.useCallback(e => {
86
+ lastKeyCode.current = e.code;
87
+ if (userOnKeyDown) userOnKeyDown(e);
88
+ }, [userOnKeyDown]);
89
+ const onChange = React.useCallback(e => {
90
+ var _e$target$selectionEn;
91
+
92
+ const [mask, cursorPos] = conformValue(e.target.value, (_e$target$selectionEn = e.target.selectionEnd) !== null && _e$target$selectionEn !== void 0 ? _e$target$selectionEn : 0, lastKeyCode.current, regExp, filledMask);
93
+ valueSetter(prevMask => {
94
+ // Here we need to update the key
95
+ if (mask === prevMask) setKey(key => key + 1);
96
+ return mask;
97
+ });
98
+ if (userOnChange) userOnChange(e, mask);
99
+ scheduleAfterRender(() => setCursorPosition.setCursorPosition(e.target, cursorPos));
100
+ }, [valueSetter, scheduleAfterRender, userOnChange]);
101
+ const onBlur = React.useCallback(e => {
102
+ if (filledMask) {
103
+ const maskedValue = e.target.value.split('');
104
+ const flatRegExp = flatStringArray.flatStringArray(regExp);
105
+ const firstUnderscoreIdx = maskedValue.findIndex((char, idx) => char === '_' && flatRegExp[idx] !== '_');
106
+ if (firstUnderscoreIdx > -1) valueSetter(maskedValue.slice(0, firstUnderscoreIdx).join(''));
107
+ }
108
+
109
+ if (userOnBlur) userOnBlur(e);
110
+ }, [valueSetter, userOnBlur]);
111
+ return {
112
+ onKeyDown,
113
+ onChange,
114
+ onBlur
115
+ };
116
+ };
117
+ const UseRegExpMaskWithSchema = dsPropsHelpers.describe(useRegExpMask);
118
+ UseRegExpMaskWithSchema.propTypes = reactDescPropTypes.DSMaskRegExpHookPropsTypes;
119
+
120
+ exports.UseRegExpMaskWithSchema = UseRegExpMaskWithSchema;
121
+ exports.isRegExp = isRegExp;
122
+ exports.useRegExpMask = useRegExpMask;
@@ -12,6 +12,8 @@ var dsUtilities = require('@elliemae/ds-utilities');
12
12
  var addSpecialCharacters = require('../utils/addSpecialCharacters.js');
13
13
  var getPartialMaskedPos = require('../utils/getPartialMaskedPos.js');
14
14
  var setCursorPosition = require('../utils/setCursorPosition.js');
15
+ require('core-js/modules/esnext.async-iterator.reduce.js');
16
+ require('core-js/modules/esnext.iterator.reduce.js');
15
17
  var reactDescPropTypes = require('../react-desc-prop-types.js');
16
18
 
17
19
  const ssnSpecialChars = [['-', 3], ['-', 6]];
@@ -12,6 +12,8 @@ var dsUtilities = require('@elliemae/ds-utilities');
12
12
  var addSpecialCharacters = require('../utils/addSpecialCharacters.js');
13
13
  var getPartialMaskedPos = require('../utils/getPartialMaskedPos.js');
14
14
  var setCursorPosition = require('../utils/setCursorPosition.js');
15
+ require('core-js/modules/esnext.async-iterator.reduce.js');
16
+ require('core-js/modules/esnext.iterator.reduce.js');
15
17
  var reactDescPropTypes = require('../react-desc-prop-types.js');
16
18
 
17
19
  const zipCodeSpecialChars = [['-', 5]];
@@ -5,6 +5,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
5
5
  var usePhoneMask = require('./hooks/usePhoneMask.js');
6
6
  var useNumberMask = require('./hooks/useNumberMask.js');
7
7
  var useSSNMask = require('./hooks/useSSNMask.js');
8
+ var useRegExpMask = require('./hooks/useRegExpMask.js');
8
9
  var useZipCodeMask = require('./hooks/useZipCodeMask.js');
9
10
 
10
11
 
@@ -18,6 +19,9 @@ exports.useNumberMask = useNumberMask.useNumberMask;
18
19
  exports.UseSSNMaskWithSchema = useSSNMask.UseSSNMaskWithSchema;
19
20
  exports.getSSNMaskedValue = useSSNMask.getSSNMaskedValue;
20
21
  exports.useSSNMask = useSSNMask.useSSNMask;
22
+ exports.UseRegExpMaskWithSchema = useRegExpMask.UseRegExpMaskWithSchema;
23
+ exports.isRegExp = useRegExpMask.isRegExp;
24
+ exports.useRegExpMask = useRegExpMask.useRegExpMask;
21
25
  exports.UseZipCodeMaskWithSchema = useZipCodeMask.UseZipCodeMaskWithSchema;
22
26
  exports.getUseZipMaskedValue = useZipCodeMask.getUseZipMaskedValue;
23
27
  exports.useZipCodeMask = useZipCodeMask.useZipCodeMask;
@@ -25,6 +25,9 @@ const DSMaskHookDefaultProps = {
25
25
  const DSMaskPhoneHookDefaultProps = _objectSpread(_objectSpread({}, DSMaskHookDefaultProps), {}, {
26
26
  isInternational: false
27
27
  });
28
+ const DSMaskRegExpHookDefaultProps = _objectSpread(_objectSpread({}, DSMaskHookDefaultProps), {}, {
29
+ filledMask: false
30
+ });
28
31
  const DSMaskNumberHookDefaultProps = _objectSpread(_objectSpread({}, DSMaskHookDefaultProps), {}, {
29
32
  suffix: '',
30
33
  prefix: '',
@@ -38,6 +41,10 @@ const DSMaskHookPropsTypes = {
38
41
  onChange: dsPropsHelpers.PropTypes.func.description('Callback for onChange'),
39
42
  onKeyDown: dsPropsHelpers.PropTypes.func.description('Callback for onKeyDown')
40
43
  };
44
+ const DSMaskRegExpHookPropsTypes = _objectSpread(_objectSpread({}, DSMaskHookPropsTypes), {}, {
45
+ regExp: dsPropsHelpers.PropTypes.arrayOf(dsPropsHelpers.PropTypes.oneOfType([dsPropsHelpers.PropTypes.string, dsPropsHelpers.PropTypes.instanceOf(RegExp)])).description('Array with regexp or special chars per character').isRequired,
46
+ filledMask: dsPropsHelpers.PropTypes.bool.description('Show blank chars positions').defaultValue(false)
47
+ });
41
48
  const DSMaskPhoneHookPropsTypes = _objectSpread(_objectSpread({}, DSMaskHookPropsTypes), {}, {
42
49
  isInternational: dsPropsHelpers.PropTypes.bool.description('Use or not internaltional phone mask number').defaultValue(false)
43
50
  });
@@ -56,3 +63,5 @@ exports.DSMaskNumberHookDefaultProps = DSMaskNumberHookDefaultProps;
56
63
  exports.DSMaskNumberHookPropsTypes = DSMaskNumberHookPropsTypes;
57
64
  exports.DSMaskPhoneHookDefaultProps = DSMaskPhoneHookDefaultProps;
58
65
  exports.DSMaskPhoneHookPropsTypes = DSMaskPhoneHookPropsTypes;
66
+ exports.DSMaskRegExpHookDefaultProps = DSMaskRegExpHookDefaultProps;
67
+ exports.DSMaskRegExpHookPropsTypes = DSMaskRegExpHookPropsTypes;
@@ -38,10 +38,9 @@ const addSpecialCharacters = (maskWithoutSpecialChars, specialCharsDescr, masked
38
38
  });
39
39
  } else if (lastKeyCode === 'Backspace' && finalMaskedPos) {
40
40
  // If erasing, move the cursor back if we are in a special chars area
41
- specialCharsDescr.forEach(_ref3 => {
42
- let [chars, pos] = _ref3;
43
- if (pos + 1 <= finalMaskedPos && finalMaskedPos <= pos + chars.length) finalMaskedPos = pos;
44
- });
41
+ for (let i = specialCharsDescr.length - 1; i >= 0; i -= 1) {
42
+ if (specialCharsDescr[i][1] + 1 <= finalMaskedPos && finalMaskedPos <= specialCharsDescr[i][1] + specialCharsDescr[i][0].length) [, finalMaskedPos] = specialCharsDescr[i];
43
+ }
45
44
  }
46
45
 
47
46
  return [finalMask.join(''), finalMaskedPos];
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ require('core-js/modules/esnext.async-iterator.reduce.js');
6
+ require('core-js/modules/esnext.iterator.constructor.js');
7
+ require('core-js/modules/esnext.iterator.reduce.js');
8
+ require('core-js/modules/web.dom-collections.iterator.js');
9
+
10
+ const flatStringArray = stringArray => stringArray.reduce((acc, curr) => {
11
+ if (typeof curr === 'string') {
12
+ return [...acc, ...curr.split('')];
13
+ }
14
+
15
+ return [...acc, curr];
16
+ }, []);
17
+
18
+ exports.flatStringArray = flatStringArray;
@@ -6,6 +6,7 @@ var addSpecialCharacters = require('./addSpecialCharacters.js');
6
6
  var getPartialMaskedPos = require('./getPartialMaskedPos.js');
7
7
  var setCursorPosition = require('./setCursorPosition.js');
8
8
  var sharedFilters = require('./sharedFilters.js');
9
+ var flatStringArray = require('./flatStringArray.js');
9
10
 
10
11
 
11
12
 
@@ -13,3 +14,4 @@ exports.addSpecialCharacters = addSpecialCharacters.addSpecialCharacters;
13
14
  exports.getPartialMaskedPos = getPartialMaskedPos.getPartialMaskedPos;
14
15
  exports.setCursorPosition = setCursorPosition.setCursorPosition;
15
16
  exports.filterNumbersOnly = sharedFilters.filterNumbersOnly;
17
+ exports.flatStringArray = flatStringArray.flatStringArray;
package/esm/index.js CHANGED
@@ -14,6 +14,7 @@ export { getValidationTimeStringMetaInfo } from './date-time-picker/utils/timeHe
14
14
  export { UsePhoneMaskWithSchema, getPhoneMaskedValue, usePhoneMask } from './mask-hook/hooks/usePhoneMask.js';
15
15
  export { UseNumberMaskWithSchema, getNumberMaskedValue, useNumberMask } from './mask-hook/hooks/useNumberMask.js';
16
16
  export { UseSSNMaskWithSchema, getSSNMaskedValue, useSSNMask } from './mask-hook/hooks/useSSNMask.js';
17
+ export { UseRegExpMaskWithSchema, isRegExp, useRegExpMask } from './mask-hook/hooks/useRegExpMask.js';
17
18
  export { UseZipCodeMaskWithSchema, getUseZipMaskedValue, useZipCodeMask } from './mask-hook/hooks/useZipCodeMask.js';
18
19
  export { DSInputText, DSInputTextWithSchema } from './text-input/DSInputText.js';
19
20
  export { DSInputTextDataTestIds } from './text-input/exported-related/data-test-ids.js';
@@ -1,4 +1,5 @@
1
1
  export { UsePhoneMaskWithSchema, getPhoneMaskedValue, usePhoneMask } from './usePhoneMask.js';
2
2
  export { UseNumberMaskWithSchema, getNumberMaskedValue, useNumberMask } from './useNumberMask.js';
3
3
  export { UseSSNMaskWithSchema, getSSNMaskedValue, useSSNMask } from './useSSNMask.js';
4
+ export { UseRegExpMaskWithSchema, isRegExp, useRegExpMask } from './useRegExpMask.js';
4
5
  export { UseZipCodeMaskWithSchema, getUseZipMaskedValue, useZipCodeMask } from './useZipCodeMask.js';
@@ -10,6 +10,8 @@ import { useCallbackAfterRender } from '@elliemae/ds-utilities';
10
10
  import 'core-js/modules/esnext.async-iterator.for-each.js';
11
11
  import 'core-js/modules/esnext.iterator.for-each.js';
12
12
  import { setCursorPosition } from '../utils/setCursorPosition.js';
13
+ import 'core-js/modules/esnext.async-iterator.reduce.js';
14
+ import 'core-js/modules/esnext.iterator.reduce.js';
13
15
  import { DSMaskNumberHookPropsTypes, DSMaskNumberHookDefaultProps } from '../react-desc-prop-types.js';
14
16
 
15
17
  const _excluded = ["valueSetter", "onChange", "onKeyDown", "onBlur"];
@@ -9,6 +9,8 @@ import { useCallbackAfterRender } from '@elliemae/ds-utilities';
9
9
  import { addSpecialCharacters } from '../utils/addSpecialCharacters.js';
10
10
  import { getPartialMaskedPos } from '../utils/getPartialMaskedPos.js';
11
11
  import { setCursorPosition } from '../utils/setCursorPosition.js';
12
+ import 'core-js/modules/esnext.async-iterator.reduce.js';
13
+ import 'core-js/modules/esnext.iterator.reduce.js';
12
14
  import { DSMaskPhoneHookPropsTypes, DSMaskPhoneHookDefaultProps } from '../react-desc-prop-types.js';
13
15
 
14
16
  const _excluded = ["valueSetter", "onChange", "onKeyDown", "onBlur"];
@@ -0,0 +1,116 @@
1
+ import 'core-js/modules/web.dom-collections.iterator.js';
2
+ import { useRef, useState, useCallback } from 'react';
3
+ import { describe, useMemoMergePropsWithDefault, useValidateTypescriptPropTypes } from '@elliemae/ds-props-helpers';
4
+ import { useCallbackAfterRender } from '@elliemae/ds-utilities';
5
+ import { addSpecialCharacters } from '../utils/addSpecialCharacters.js';
6
+ import { getPartialMaskedPos } from '../utils/getPartialMaskedPos.js';
7
+ import { setCursorPosition } from '../utils/setCursorPosition.js';
8
+ import 'core-js/modules/esnext.async-iterator.filter.js';
9
+ import 'core-js/modules/esnext.iterator.constructor.js';
10
+ import 'core-js/modules/esnext.iterator.filter.js';
11
+ import { flatStringArray } from '../utils/flatStringArray.js';
12
+ import { DSMaskRegExpHookPropsTypes, DSMaskRegExpHookDefaultProps } from '../react-desc-prop-types.js';
13
+
14
+ const isRegExp = item => typeof item === 'object';
15
+
16
+ const conformValue = (rawValue, cursorPos, lastKeyCode, regExp, filledMask) => {
17
+ const specialChars = [];
18
+ const flatRegExp = flatStringArray(regExp);
19
+ const rawValueArray = rawValue.split('');
20
+ let offSet = 0;
21
+ let charsFound = 0;
22
+ let specialCharsLeft = '';
23
+ let fullRegExpMatched = false;
24
+ const maskedValue = [];
25
+
26
+ for (let i = 0; i < flatRegExp.length; i += 1) {
27
+ if (isRegExp(flatRegExp[i])) {
28
+ specialCharsLeft = '';
29
+ fullRegExpMatched = false;
30
+
31
+ for (let j = charsFound + offSet; j < rawValueArray.length; j += 1) {
32
+ var _rawValueArray$j;
33
+
34
+ const match = (_rawValueArray$j = rawValueArray[j]) === null || _rawValueArray$j === void 0 ? void 0 : _rawValueArray$j.match(flatRegExp[i]);
35
+
36
+ if (match) {
37
+ maskedValue.push(match[0]);
38
+ charsFound += 1;
39
+ fullRegExpMatched = true;
40
+ break;
41
+ } else {
42
+ offSet += 1;
43
+ }
44
+ }
45
+
46
+ if (filledMask && !fullRegExpMatched) {
47
+ maskedValue.push('_');
48
+ }
49
+ } else {
50
+ specialChars.push([flatRegExp[i], i]);
51
+ if (rawValueArray[i + offSet] === flatRegExp[i]) charsFound += 1;
52
+ specialCharsLeft += flatRegExp[i];
53
+ }
54
+ }
55
+
56
+ if (!fullRegExpMatched) specialCharsLeft = '';
57
+ const maskedPos = getPartialMaskedPos(rawValue, maskedValue, cursorPos);
58
+ const [maskWithSpecialsChars, finalMaskedPos] = addSpecialCharacters(maskedValue, specialChars, maskedPos, lastKeyCode);
59
+ return [maskWithSpecialsChars + specialCharsLeft, finalMaskedPos];
60
+ };
61
+
62
+ const useRegExpMask = props => {
63
+ const propsWithDefault = useMemoMergePropsWithDefault(props, DSMaskRegExpHookDefaultProps);
64
+ useValidateTypescriptPropTypes(propsWithDefault, DSMaskRegExpHookPropsTypes);
65
+ const {
66
+ valueSetter,
67
+ onChange: userOnChange,
68
+ onKeyDown: userOnKeyDown,
69
+ onBlur: userOnBlur,
70
+ regExp,
71
+ filledMask
72
+ } = propsWithDefault;
73
+ const lastKeyCode = useRef('Unidentified');
74
+ const scheduleAfterRender = useCallbackAfterRender(); // Some times, the value of the mask is not gonna change (user types invalid char for example)
75
+ // This makes the input to go from "correct" -> "incorrect" -> "correct"
76
+ // But since the input is controlled, the input does not re-render
77
+ // This makes the input go mumbo-jumbo and places the cursor in the end, because we kinda changed the value async
78
+ // Solution for this is to trigger a re-render, we do this with a set state call in the on change event
79
+
80
+ const [, setKey] = useState(0);
81
+ const onKeyDown = useCallback(e => {
82
+ lastKeyCode.current = e.code;
83
+ if (userOnKeyDown) userOnKeyDown(e);
84
+ }, [userOnKeyDown]);
85
+ const onChange = useCallback(e => {
86
+ var _e$target$selectionEn;
87
+
88
+ const [mask, cursorPos] = conformValue(e.target.value, (_e$target$selectionEn = e.target.selectionEnd) !== null && _e$target$selectionEn !== void 0 ? _e$target$selectionEn : 0, lastKeyCode.current, regExp, filledMask);
89
+ valueSetter(prevMask => {
90
+ // Here we need to update the key
91
+ if (mask === prevMask) setKey(key => key + 1);
92
+ return mask;
93
+ });
94
+ if (userOnChange) userOnChange(e, mask);
95
+ scheduleAfterRender(() => setCursorPosition(e.target, cursorPos));
96
+ }, [valueSetter, scheduleAfterRender, userOnChange]);
97
+ const onBlur = useCallback(e => {
98
+ if (filledMask) {
99
+ const maskedValue = e.target.value.split('');
100
+ const flatRegExp = flatStringArray(regExp);
101
+ const firstUnderscoreIdx = maskedValue.findIndex((char, idx) => char === '_' && flatRegExp[idx] !== '_');
102
+ if (firstUnderscoreIdx > -1) valueSetter(maskedValue.slice(0, firstUnderscoreIdx).join(''));
103
+ }
104
+
105
+ if (userOnBlur) userOnBlur(e);
106
+ }, [valueSetter, userOnBlur]);
107
+ return {
108
+ onKeyDown,
109
+ onChange,
110
+ onBlur
111
+ };
112
+ };
113
+ const UseRegExpMaskWithSchema = describe(useRegExpMask);
114
+ UseRegExpMaskWithSchema.propTypes = DSMaskRegExpHookPropsTypes;
115
+
116
+ export { UseRegExpMaskWithSchema, isRegExp, useRegExpMask };
@@ -8,6 +8,8 @@ import { useCallbackAfterRender } from '@elliemae/ds-utilities';
8
8
  import { addSpecialCharacters } from '../utils/addSpecialCharacters.js';
9
9
  import { getPartialMaskedPos } from '../utils/getPartialMaskedPos.js';
10
10
  import { setCursorPosition } from '../utils/setCursorPosition.js';
11
+ import 'core-js/modules/esnext.async-iterator.reduce.js';
12
+ import 'core-js/modules/esnext.iterator.reduce.js';
11
13
  import { DSMaskHookPropsTypes, DSMaskHookDefaultProps } from '../react-desc-prop-types.js';
12
14
 
13
15
  const ssnSpecialChars = [['-', 3], ['-', 6]];
@@ -8,6 +8,8 @@ import { useCallbackAfterRender } from '@elliemae/ds-utilities';
8
8
  import { addSpecialCharacters } from '../utils/addSpecialCharacters.js';
9
9
  import { getPartialMaskedPos } from '../utils/getPartialMaskedPos.js';
10
10
  import { setCursorPosition } from '../utils/setCursorPosition.js';
11
+ import 'core-js/modules/esnext.async-iterator.reduce.js';
12
+ import 'core-js/modules/esnext.iterator.reduce.js';
11
13
  import { DSMaskHookPropsTypes, DSMaskHookDefaultProps } from '../react-desc-prop-types.js';
12
14
 
13
15
  const zipCodeSpecialChars = [['-', 5]];
@@ -1,4 +1,5 @@
1
1
  export { UsePhoneMaskWithSchema, getPhoneMaskedValue, usePhoneMask } from './hooks/usePhoneMask.js';
2
2
  export { UseNumberMaskWithSchema, getNumberMaskedValue, useNumberMask } from './hooks/useNumberMask.js';
3
3
  export { UseSSNMaskWithSchema, getSSNMaskedValue, useSSNMask } from './hooks/useSSNMask.js';
4
+ export { UseRegExpMaskWithSchema, isRegExp, useRegExpMask } from './hooks/useRegExpMask.js';
4
5
  export { UseZipCodeMaskWithSchema, getUseZipMaskedValue, useZipCodeMask } from './hooks/useZipCodeMask.js';
@@ -17,6 +17,9 @@ const DSMaskHookDefaultProps = {
17
17
  const DSMaskPhoneHookDefaultProps = _objectSpread(_objectSpread({}, DSMaskHookDefaultProps), {}, {
18
18
  isInternational: false
19
19
  });
20
+ const DSMaskRegExpHookDefaultProps = _objectSpread(_objectSpread({}, DSMaskHookDefaultProps), {}, {
21
+ filledMask: false
22
+ });
20
23
  const DSMaskNumberHookDefaultProps = _objectSpread(_objectSpread({}, DSMaskHookDefaultProps), {}, {
21
24
  suffix: '',
22
25
  prefix: '',
@@ -30,6 +33,10 @@ const DSMaskHookPropsTypes = {
30
33
  onChange: PropTypes.func.description('Callback for onChange'),
31
34
  onKeyDown: PropTypes.func.description('Callback for onKeyDown')
32
35
  };
36
+ const DSMaskRegExpHookPropsTypes = _objectSpread(_objectSpread({}, DSMaskHookPropsTypes), {}, {
37
+ regExp: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(RegExp)])).description('Array with regexp or special chars per character').isRequired,
38
+ filledMask: PropTypes.bool.description('Show blank chars positions').defaultValue(false)
39
+ });
33
40
  const DSMaskPhoneHookPropsTypes = _objectSpread(_objectSpread({}, DSMaskHookPropsTypes), {}, {
34
41
  isInternational: PropTypes.bool.description('Use or not internaltional phone mask number').defaultValue(false)
35
42
  });
@@ -42,4 +49,4 @@ const DSMaskNumberHookPropsTypes = _objectSpread(_objectSpread({}, DSMaskHookPro
42
49
  decimalRequired: PropTypes.bool.description('Wether decimal part is mandatory').defaultValue(false)
43
50
  });
44
51
 
45
- export { DSMaskHookDefaultProps, DSMaskHookPropsTypes, DSMaskNumberHookDefaultProps, DSMaskNumberHookPropsTypes, DSMaskPhoneHookDefaultProps, DSMaskPhoneHookPropsTypes };
52
+ export { DSMaskHookDefaultProps, DSMaskHookPropsTypes, DSMaskNumberHookDefaultProps, DSMaskNumberHookPropsTypes, DSMaskPhoneHookDefaultProps, DSMaskPhoneHookPropsTypes, DSMaskRegExpHookDefaultProps, DSMaskRegExpHookPropsTypes };
@@ -34,10 +34,9 @@ const addSpecialCharacters = (maskWithoutSpecialChars, specialCharsDescr, masked
34
34
  });
35
35
  } else if (lastKeyCode === 'Backspace' && finalMaskedPos) {
36
36
  // If erasing, move the cursor back if we are in a special chars area
37
- specialCharsDescr.forEach(_ref3 => {
38
- let [chars, pos] = _ref3;
39
- if (pos + 1 <= finalMaskedPos && finalMaskedPos <= pos + chars.length) finalMaskedPos = pos;
40
- });
37
+ for (let i = specialCharsDescr.length - 1; i >= 0; i -= 1) {
38
+ if (specialCharsDescr[i][1] + 1 <= finalMaskedPos && finalMaskedPos <= specialCharsDescr[i][1] + specialCharsDescr[i][0].length) [, finalMaskedPos] = specialCharsDescr[i];
39
+ }
41
40
  }
42
41
 
43
42
  return [finalMask.join(''), finalMaskedPos];
@@ -0,0 +1,14 @@
1
+ import 'core-js/modules/esnext.async-iterator.reduce.js';
2
+ import 'core-js/modules/esnext.iterator.constructor.js';
3
+ import 'core-js/modules/esnext.iterator.reduce.js';
4
+ import 'core-js/modules/web.dom-collections.iterator.js';
5
+
6
+ const flatStringArray = stringArray => stringArray.reduce((acc, curr) => {
7
+ if (typeof curr === 'string') {
8
+ return [...acc, ...curr.split('')];
9
+ }
10
+
11
+ return [...acc, curr];
12
+ }, []);
13
+
14
+ export { flatStringArray };
@@ -2,3 +2,4 @@ export { addSpecialCharacters } from './addSpecialCharacters.js';
2
2
  export { getPartialMaskedPos } from './getPartialMaskedPos.js';
3
3
  export { setCursorPosition } from './setCursorPosition.js';
4
4
  export { filterNumbersOnly } from './sharedFilters.js';
5
+ export { flatStringArray } from './flatStringArray.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elliemae/ds-controlled-form",
3
- "version": "2.4.1-rc.5",
3
+ "version": "2.4.1-rc.6",
4
4
  "license": "MIT",
5
5
  "description": "ICE MT - Dimsum - Controlled Form Controllers",
6
6
  "module": "./esm/index.js",
@@ -91,6 +91,10 @@
91
91
  "import": "./esm/mask-hook/utils/getPartialMaskedPos.js",
92
92
  "require": "./cjs/mask-hook/utils/getPartialMaskedPos.js"
93
93
  },
94
+ "./mask-hook/utils/flatStringArray": {
95
+ "import": "./esm/mask-hook/utils/flatStringArray.js",
96
+ "require": "./cjs/mask-hook/utils/flatStringArray.js"
97
+ },
94
98
  "./mask-hook/utils/addSpecialCharacters": {
95
99
  "import": "./esm/mask-hook/utils/addSpecialCharacters.js",
96
100
  "require": "./cjs/mask-hook/utils/addSpecialCharacters.js"
@@ -111,6 +115,10 @@
111
115
  "import": "./esm/mask-hook/hooks/useSSNMask.js",
112
116
  "require": "./cjs/mask-hook/hooks/useSSNMask.js"
113
117
  },
118
+ "./mask-hook/hooks/useRegExpMask": {
119
+ "import": "./esm/mask-hook/hooks/useRegExpMask.js",
120
+ "require": "./cjs/mask-hook/hooks/useRegExpMask.js"
121
+ },
114
122
  "./mask-hook/hooks/usePhoneMask": {
115
123
  "import": "./esm/mask-hook/hooks/usePhoneMask.js",
116
124
  "require": "./cjs/mask-hook/hooks/usePhoneMask.js"
@@ -800,20 +808,20 @@
800
808
  "build": "node ../../scripts/build/build.js"
801
809
  },
802
810
  "dependencies": {
803
- "@elliemae/ds-button": "2.4.1-rc.5",
804
- "@elliemae/ds-circular-progress-indicator": "2.4.1-rc.5",
805
- "@elliemae/ds-controlled-form": "2.4.1-rc.5",
806
- "@elliemae/ds-form": "2.4.1-rc.5",
807
- "@elliemae/ds-form-layout-blocks": "2.4.1-rc.5",
808
- "@elliemae/ds-grid": "2.4.1-rc.5",
809
- "@elliemae/ds-icon": "2.4.1-rc.5",
810
- "@elliemae/ds-icons": "2.4.1-rc.5",
811
- "@elliemae/ds-pills": "2.4.1-rc.5",
812
- "@elliemae/ds-popperjs": "2.4.1-rc.5",
813
- "@elliemae/ds-props-helpers": "2.4.1-rc.5",
814
- "@elliemae/ds-system": "2.4.1-rc.5",
815
- "@elliemae/ds-tooltip": "2.4.1-rc.5",
816
- "@elliemae/ds-truncated-tooltip-text": "2.4.1-rc.5",
811
+ "@elliemae/ds-button": "2.4.1-rc.6",
812
+ "@elliemae/ds-circular-progress-indicator": "2.4.1-rc.6",
813
+ "@elliemae/ds-controlled-form": "2.4.1-rc.6",
814
+ "@elliemae/ds-form": "2.4.1-rc.6",
815
+ "@elliemae/ds-form-layout-blocks": "2.4.1-rc.6",
816
+ "@elliemae/ds-grid": "2.4.1-rc.6",
817
+ "@elliemae/ds-icon": "2.4.1-rc.6",
818
+ "@elliemae/ds-icons": "2.4.1-rc.6",
819
+ "@elliemae/ds-pills": "2.4.1-rc.6",
820
+ "@elliemae/ds-popperjs": "2.4.1-rc.6",
821
+ "@elliemae/ds-props-helpers": "2.4.1-rc.6",
822
+ "@elliemae/ds-system": "2.4.1-rc.6",
823
+ "@elliemae/ds-tooltip": "2.4.1-rc.6",
824
+ "@elliemae/ds-truncated-tooltip-text": "2.4.1-rc.6",
817
825
  "prop-types": "~15.7.2",
818
826
  "react-desc": "~4.1.3",
819
827
  "react-popper": "~2.2.5",
@@ -1,4 +1,5 @@
1
1
  export * from './usePhoneMask';
2
2
  export * from './useNumberMask';
3
3
  export * from './useSSNMask';
4
+ export * from './useRegExpMask';
4
5
  export * from './useZipCodeMask';
@@ -0,0 +1,4 @@
1
+ import { DSMaskT } from '../react-desc-prop-types';
2
+ export declare const isRegExp: (item: RegExp | string) => item is RegExp;
3
+ export declare const useRegExpMask: DSMaskT.Hook;
4
+ export declare const UseRegExpMaskWithSchema: import("@elliemae/ds-props-helpers/types/propTypes/types").DocumentedReactComponent<DSMaskT.HookArgs>;
@@ -22,13 +22,18 @@ export declare namespace DSMaskT {
22
22
  interface PhoneHookArgs extends HookArgs {
23
23
  isInternational: boolean;
24
24
  }
25
+ interface RegExpHookArgs extends HookArgs {
26
+ filledMask: boolean;
27
+ }
25
28
  type Hook = (args: HookArgs) => HookReturn;
26
29
  type PhoneHook = (args: PhoneHookArgs) => HookReturn;
27
30
  type NumberHook = (args: NumberHookArgs) => HookReturn;
28
31
  }
29
- export declare const DSMaskHookDefaultProps: Partial<DSMaskT.PhoneHookArgs>;
32
+ export declare const DSMaskHookDefaultProps: Partial<DSMaskT.HookReturn>;
30
33
  export declare const DSMaskPhoneHookDefaultProps: Partial<DSMaskT.PhoneHookArgs>;
34
+ export declare const DSMaskRegExpHookDefaultProps: Partial<DSMaskT.RegExpHookArgs>;
31
35
  export declare const DSMaskNumberHookDefaultProps: Partial<DSMaskT.NumberHookArgs>;
32
36
  export declare const DSMaskHookPropsTypes: React.WeakValidationMap<unknown>;
37
+ export declare const DSMaskRegExpHookPropsTypes: React.WeakValidationMap<unknown>;
33
38
  export declare const DSMaskPhoneHookPropsTypes: React.WeakValidationMap<unknown>;
34
39
  export declare const DSMaskNumberHookPropsTypes: React.WeakValidationMap<unknown>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export declare const flatStringArray: (stringArray: string[]) => string[];
@@ -2,3 +2,4 @@ export * from './addSpecialCharacters';
2
2
  export * from './getPartialMaskedPos';
3
3
  export * from './setCursorPosition';
4
4
  export * from './sharedFilters';
5
+ export * from './flatStringArray';