From 518371c3e73dd803048097b3cdea731f6dbedf22 Mon Sep 17 00:00:00 2001
From: Thiago Contardi
Date: Fri, 9 Feb 2024 16:47:42 -0300
Subject: [PATCH 01/97] feat: added new credit card form
---
Helper/Data.php | 2 -
view/frontend/requirejs-config.js | 9 +
view/frontend/web/css/checkout.less | 286 ++
view/frontend/web/js/credit-card/card.js | 211 +
view/frontend/web/js/credit-card/mask.js | 3511 +++++++++++++++++
.../web/js/view/payment/method-renderer/cc.js | 34 +-
.../web/template/payment/form/cc.html | 173 +-
7 files changed, 4114 insertions(+), 112 deletions(-)
create mode 100644 view/frontend/requirejs-config.js
create mode 100644 view/frontend/web/js/credit-card/card.js
create mode 100644 view/frontend/web/js/credit-card/mask.js
diff --git a/Helper/Data.php b/Helper/Data.php
index 121da57..e0b4940 100644
--- a/Helper/Data.php
+++ b/Helper/Data.php
@@ -47,8 +47,6 @@ class Data extends \Magento\Payment\Helper\Data
{
public const FINGERPRINT_URL = 'https://static.traycheckout.com.br/js/finger_print.js';
- public const DEFAULT_CURRENCY = 'BRL';
-
protected array $methodNames = [
'MC' => 'Mastercard',
'AU' => 'Aura',
diff --git a/view/frontend/requirejs-config.js b/view/frontend/requirejs-config.js
new file mode 100644
index 0000000..7689886
--- /dev/null
+++ b/view/frontend/requirejs-config.js
@@ -0,0 +1,9 @@
+var config = {
+ paths: {
+ 'vindi-cc-form': 'Vindi_VP/js/credit-card/card',
+ 'vindi-cc-mask': 'Vindi_VP/js/credit-card/mask',
+ },
+ shim: {
+ 'vindi-cc-mask': {}
+ }
+};
diff --git a/view/frontend/web/css/checkout.less b/view/frontend/web/css/checkout.less
index ec137b1..fa9b6e5 100644
--- a/view/frontend/web/css/checkout.less
+++ b/view/frontend/web/css/checkout.less
@@ -52,4 +52,290 @@
}
}
+#payment_form_vindi_vp_cc {
+ max-width: 500px;
+}
+
+.container-vindi_vp_cc .vindi-cc-exp-cvv {
+ display: flex;
+ justify-content: space-between;
+ span {
+ display: block;
+ width: 48%;
+ }
+}
+
+/* CREDIT CARD IMAGE STYLING */
+.container-vindi_vp_cc .vind-cc-preload * {
+ -webkit-transition: none !important;
+ -moz-transition: none !important;
+ -ms-transition: none !important;
+ -o-transition: none !important;
+}
+
+.container-vindi_vp_cc .vindi-vp-cc-container {
+ display: flex;
+ justify-content: center;
+ width: 100%;
+ max-height: 251px;
+ height: 54vw;
+ padding: 20px;
+}
+
+.container-vindi_vp_cc #vindi-vp-cc-ccsingle {
+ position: absolute;
+ right: 15px;
+ top: 20px;
+}
+
+.container-vindi_vp_cc #vindi-vp-cc-ccsingle svg {
+ width: 100px;
+ max-height: 60px;
+}
+
+.container-vindi_vp_cc .vind-cc-creditcard svg#vindi-vp-cc-cardfront,
+.container-vindi_vp_cc .vind-cc-creditcard svg#vindi-vp-cc-cardback {
+ width: 100%;
+ -webkit-box-shadow: 1px 5px 6px 0px black;
+ box-shadow: 1px 5px 6px 0px black;
+ border-radius: 22px;
+}
+
+.container-vindi_vp_cc #generatecard{
+ cursor: pointer;
+ float: right;
+ font-size: 12px;
+ color: #fff;
+ padding: 2px 4px;
+ background-color: #909090;
+ border-radius: 4px;
+ cursor: pointer;
+ float:right;
+}
+
+/* CHANGEABLE CARD ELEMENTS */
+.container-vindi_vp_cc .vind-cc-creditcard .lightcolor,
+.container-vindi_vp_cc .vind-cc-creditcard .darkcolor {
+ -webkit-transition: fill .5s;
+ transition: fill .5s;
+}
+
+.container-vindi_vp_cc .vind-cc-creditcard .lightblue {
+ fill: #03A9F4;
+}
+
+.container-vindi_vp_cc .vind-cc-creditcard .lightbluedark {
+ fill: #0288D1;
+}
+
+.container-vindi_vp_cc .vind-cc-creditcard .red {
+ fill: #ef5350;
+}
+
+.container-vindi_vp_cc .vind-cc-creditcard .reddark {
+ fill: #d32f2f;
+}
+
+.container-vindi_vp_cc .vind-cc-creditcard .purple {
+ fill: #ab47bc;
+}
+
+.container-vindi_vp_cc .vind-cc-creditcard .purpledark {
+ fill: #7b1fa2;
+}
+
+.container-vindi_vp_cc .vind-cc-creditcard .cyan {
+ fill: #26c6da;
+}
+
+.container-vindi_vp_cc .vind-cc-creditcard .cyandark {
+ fill: #0097a7;
+}
+
+.container-vindi_vp_cc .vind-cc-creditcard .green {
+ fill: #66bb6a;
+}
+
+.container-vindi_vp_cc .vind-cc-creditcard .greendark {
+ fill: #388e3c;
+}
+
+.container-vindi_vp_cc .vind-cc-creditcard .lime {
+ fill: #d4e157;
+}
+
+.container-vindi_vp_cc .vind-cc-creditcard .limedark {
+ fill: #afb42b;
+}
+
+.container-vindi_vp_cc .vind-cc-creditcard .yellow {
+ fill: #ffeb3b;
+}
+
+.container-vindi_vp_cc .vind-cc-creditcard .yellowdark {
+ fill: #f9a825;
+}
+
+.container-vindi_vp_cc .vind-cc-creditcard .orange {
+ fill: #ff9800;
+}
+
+.container-vindi_vp_cc .vind-cc-creditcard .orangedark {
+ fill: #ef6c00;
+}
+
+.container-vindi_vp_cc .vind-cc-creditcard .grey {
+ fill: #3f3f3f;
+}
+
+.container-vindi_vp_cc .vind-cc-creditcard .greydark {
+ fill: #1f1f1f;
+}
+
+/* FRONT OF CARD */
+.container-vindi_vp_cc #svgname {
+ text-transform: uppercase;
+}
+
+.container-vindi_vp_cc #vindi-vp-cc-cardfront .st2 {
+ fill: #FFFFFF;
+}
+
+.container-vindi_vp_cc #vindi-vp-cc-cardfront .st3 {
+ font-family: 'Source Code Pro', monospace;
+ font-weight: 600;
+}
+
+.container-vindi_vp_cc #vindi-vp-cc-cardfront .st4 {
+ font-size: 54.7817px;
+}
+
+.container-vindi_vp_cc #vindi-vp-cc-cardfront .st5 {
+ font-family: 'Source Code Pro', monospace;
+ font-weight: 400;
+}
+
+.container-vindi_vp_cc #vindi-vp-cc-cardfront .st6 {
+ font-size: 33.1112px;
+}
+
+.container-vindi_vp_cc #vindi-vp-cc-cardfront .st7 {
+ opacity: 0.6;
+ fill: #FFFFFF;
+}
+
+.container-vindi_vp_cc #vindi-vp-cc-cardfront .st8 {
+ font-size: 24px;
+}
+
+.container-vindi_vp_cc #vindi-vp-cc-cardfront .st9 {
+ font-size: 36.5498px;
+}
+
+.container-vindi_vp_cc #vindi-vp-cc-cardfront .st10 {
+ font-family: 'Source Code Pro', monospace;
+ font-weight: 300;
+}
+
+.container-vindi_vp_cc #vindi-vp-cc-cardfront .st11 {
+ font-size: 16.1716px;
+}
+
+.container-vindi_vp_cc #vindi-vp-cc-cardfront .st12 {
+ fill: #4C4C4C;
+}
+
+/* BACK OF CARD */
+.container-vindi_vp_cc #vindi-vp-cc-cardback .st0 {
+ fill: none;
+ stroke: #0F0F0F;
+ stroke-miterlimit: 10;
+}
+
+.container-vindi_vp_cc #vindi-vp-cc-cardback .st2 {
+ fill: #111111;
+}
+
+.container-vindi_vp_cc #vindi-vp-cc-cardback .st3 {
+ fill: #F2F2F2;
+}
+
+.container-vindi_vp_cc #vindi-vp-cc-cardback .st4 {
+ fill: #D8D2DB;
+}
+
+.container-vindi_vp_cc #vindi-vp-cc-cardback .st5 {
+ fill: #C4C4C4;
+}
+
+.container-vindi_vp_cc #vindi-vp-cc-cardback .st6 {
+ font-family: 'Source Code Pro', monospace;
+ font-weight: 400;
+}
+
+.container-vindi_vp_cc #vindi-vp-cc-cardback .st7 {
+ font-size: 27px;
+}
+
+.container-vindi_vp_cc #vindi-vp-cc-cardback .st8 {
+ opacity: 0.6;
+}
+
+.container-vindi_vp_cc #vindi-vp-cc-cardback .st9 {
+ fill: #FFFFFF;
+}
+
+.container-vindi_vp_cc #vindi-vp-cc-cardback .st10 {
+ font-size: 24px;
+}
+
+.container-vindi_vp_cc #vindi-vp-cc-cardback .st11 {
+ fill: #EAEAEA;
+}
+
+.container-vindi_vp_cc #vindi-vp-cc-cardback .st12 {
+ font-family: 'Rock Salt', cursive;
+}
+
+.container-vindi_vp_cc #vindi-vp-cc-cardback .st13 {
+ font-size: 37.769px;
+}
+
+/* FLIP ANIMATION */
+.container-vindi_vp_cc .vindi-vp-cc-container {
+ perspective: 1000px;
+}
+
+.container-vindi_vp_cc .vind-cc-creditcard {
+ width: 100%;
+ max-width: 400px;
+ -webkit-transform-style: preserve-3d;
+ transform-style: preserve-3d;
+ transition: -webkit-transform 0.6s;
+ -webkit-transition: -webkit-transform 0.6s;
+ transition: transform 0.6s;
+ transition: transform 0.6s, -webkit-transform 0.6s;
+ cursor: pointer;
+}
+
+.container-vindi_vp_cc .vind-cc-creditcard .vindi-vp-cc-front,
+.container-vindi_vp_cc .vind-cc-creditcard .vindi-vp-cc-back {
+ position: absolute;
+ width: 100%;
+ max-width: 400px;
+ -webkit-backface-visibility: hidden;
+ backface-visibility: hidden;
+ -webkit-font-smoothing: antialiased;
+ color: #47525d;
+}
+
+.container-vindi_vp_cc .vind-cc-creditcard .vindi-vp-cc-back {
+ -webkit-transform: rotateY(180deg);
+ transform: rotateY(180deg);
+}
+
+.container-vindi_vp_cc .vind-cc-creditcard.flipped {
+ -webkit-transform: rotateY(180deg);
+ transform: rotateY(180deg);
+}
diff --git a/view/frontend/web/js/credit-card/card.js b/view/frontend/web/js/credit-card/card.js
new file mode 100644
index 0000000..ac7b369
--- /dev/null
+++ b/view/frontend/web/js/credit-card/card.js
@@ -0,0 +1,211 @@
+define(['vindi-cc-mask'], function () {
+ 'use strict';
+
+ return async function (ccName, ccNumber, ccExpDate, ccCvv, ccsingle, ccFront, ccBack) {
+
+ const currentYear = parseInt(new Date().getFullYear().toString().substring(2, 4));
+ const nextYear = currentYear + 1;
+
+ let cardFront = '';
+ let cardBack = '';
+
+ ccFront.insertAdjacentHTML("beforeend", cardFront);
+ ccBack.innerHTML = cardBack;
+
+ //Mask the Credit Card Number Input
+ var ccNumberMask = new VindiCCMask(ccNumber, {
+ mask: [
+ {
+ mask: '000000000000000',
+ svgPattern: '0000 000000 00000',
+ regex: '^3[47]\\d{0,13}',
+ cardtype: 'american express'
+ },
+ {
+ mask: '0000000000000000',
+ svgPattern: '0000 0000 0000 0000',
+ regex: '^(5[1-5]\\d{0,2}|22[2-9]\\d{0,1}|2[3-7]\\d{0,2})\\d{0,12}',
+ cardtype: 'mastercard'
+ },
+ {
+ mask: '0000000000000000',
+ svgPattern: '0000 0000 0000 0000',
+ regex: '^(?:5[0678]\\d{0,2}|6304|67\\d{0,2})\\d{0,12}',
+ cardtype: 'maestro'
+ },
+ {
+ mask: '0000000000000000',
+ svgPattern: '0000 0000 0000 0000',
+ regex: '^4\\d{0,15}',
+ cardtype: 'visa'
+ },
+ {
+ mask: '0000000000000000',
+ svgPattern: '0000 0000 0000 0000',
+ cardtype: 'Unknown'
+ }
+ ],
+ dispatch: function (appended, dynamicMasked) {
+ var number = (dynamicMasked.value + appended).replace(/\D/g, '');
+
+ for (var i = 0; i < dynamicMasked.compiledMasks.length; i++) {
+ let re = new RegExp(dynamicMasked.compiledMasks[i].regex);
+ if (number.match(re) != null) {
+ return dynamicMasked.compiledMasks[i];
+ }
+ }
+ }
+ });
+
+ //Mask the Expiration Date
+ var ccExpDateMask = new VindiCCMask(ccExpDate, {
+ mask: 'MM{/}YY',
+ groups: {
+ YY: new VindiCCMask.MaskedPattern.Group.Range([currentYear, 99]),
+ MM: new VindiCCMask.MaskedPattern.Group.Range([1, 12]),
+ }
+ });
+
+ //Mask the security code
+ var ccCvvMask = new VindiCCMask(ccCvv, {
+ mask: '0000',
+ });
+
+ let amex_single = ``;
+ let visa_single = ``;
+ let maestro_single = ``;
+ let mastercard_single = ``;
+
+ //pop in the appropriate card icon when detected
+ ccNumberMask.on("accept", function () {
+ switch (ccNumberMask.masked.currentMask.cardtype) {
+ case 'american express':
+ ccsingle.innerHTML = amex_single;
+ swapColor('green');
+ break;
+ case 'visa':
+ ccsingle.innerHTML = visa_single;
+ swapColor('lime');
+ break;
+ case 'maestro':
+ ccsingle.innerHTML = maestro_single;
+ swapColor('yellow');
+ break;
+ case 'mastercard':
+ ccsingle.innerHTML = mastercard_single;
+ swapColor('lightblue');
+
+ break;
+ default:
+ ccsingle.innerHTML = '';
+ swapColor('grey');
+ break;
+ }
+
+ });
+
+
+ // CREDIT CARD IMAGE JS
+ document.querySelector('.vind-cc-preload').classList.remove('vind-cc-preload');
+ document.getElementById('svgexpire').innerHTML = '01/' + nextYear;
+ document.querySelector('.vind-cc-creditcard').addEventListener('click', function () {
+ if (this.classList.contains('flipped')) {
+ this.classList.remove('flipped');
+ } else {
+ this.classList.add('flipped');
+ }
+ })
+
+ //On Input Change Events
+ ccName.addEventListener('input', function () {
+ if (ccName.value.length == 0) {
+ document.getElementById('svgname').innerHTML = 'João da Silva';
+ document.getElementById('svgnameback').innerHTML = 'João da Silva';
+ } else {
+ document.getElementById('svgname').innerHTML = this.value;
+ document.getElementById('svgnameback').innerHTML = this.value;
+ }
+ });
+
+ ccNumberMask.on('accept', function () {
+ if (ccNumberMask.value.length == 0) {
+ document.getElementById('svgnumber').innerHTML = '0123 4567 8910 1112';
+ } else {
+ let pattern = ccNumberMask.masked.currentMask.svgPattern;
+ document.getElementById('svgnumber').innerHTML = applyPattern(ccNumberMask.value, pattern);
+ }
+ });
+
+ ccExpDateMask.on('accept', function () {
+ if (ccExpDateMask.value.length == 0) {
+ document.getElementById('svgexpire').innerHTML = '01/' + nextYear;
+ } else {
+ document.getElementById('svgexpire').innerHTML = ccExpDateMask.value;
+ }
+ });
+
+ ccCvvMask.on('accept', function () {
+ if (ccCvvMask.value.length == 0) {
+ document.getElementById('svgsecurity').innerHTML = '985';
+ } else {
+ document.getElementById('svgsecurity').innerHTML = ccCvvMask.value;
+ }
+ });
+
+ //On Focus Events
+ ccCvv.addEventListener('focus', function () {
+ document.querySelector('.vind-cc-creditcard').classList.add('flipped');
+ });
+
+ ccCvv.addEventListener('blur', function () {
+ document.querySelector('.vind-cc-creditcard').classList.remove('flipped');
+ });
+
+
+ //define the color swap function
+ const swapColor = function (basecolor) {
+ document.querySelectorAll('.lightcolor')
+ .forEach(function (input) {
+ input.setAttribute('class', '');
+ input.setAttribute('class', 'lightcolor ' + basecolor);
+ });
+ document.querySelectorAll('.darkcolor')
+ .forEach(function (input) {
+ input.setAttribute('class', '');
+ input.setAttribute('class', 'darkcolor ' + basecolor + 'dark');
+ });
+ };
+
+
+ const applyPattern = function(inputNumber, pattern) {
+ // Remove any non-digit characters from the input number
+ const cleanedNumber = String(inputNumber).replace(/\D/g, '');
+
+ // Initialize variables for the result and pattern index
+ let result = '';
+ let patternIndex = 0;
+
+ // Iterate through each character in the pattern
+ for (let i = 0; i < pattern.length; i++) {
+ // Check if the current character is a placeholder (0) or other character
+ if (pattern[i] === '0') {
+ // Check if there are still digits in the cleaned number
+ if (patternIndex < cleanedNumber.length) {
+ // Append the next digit from the cleaned number to the result
+ result += cleanedNumber[patternIndex];
+ patternIndex++;
+ } else {
+ // If no more digits in the cleaned number, break the loop
+ break;
+ }
+ } else {
+ // If the current character is not a placeholder, append it to the result
+ result += pattern[i];
+ }
+ }
+
+ return result;
+ }
+
+ }
+});
diff --git a/view/frontend/web/js/credit-card/mask.js b/view/frontend/web/js/credit-card/mask.js
new file mode 100644
index 0000000..3dd7b19
--- /dev/null
+++ b/view/frontend/web/js/credit-card/mask.js
@@ -0,0 +1,3511 @@
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+ typeof define === 'function' && define.amd ? define(factory) :
+ (global.VindiCCMask = factory());
+}(this, (function () { 'use strict';
+
+ // 7.2.1 RequireObjectCoercible(argument)
+ var _defined = function (it) {
+ if (it == undefined) throw TypeError("Can't call method on " + it);
+ return it;
+ };
+
+ // 7.1.13 ToObject(argument)
+
+ var _toObject = function (it) {
+ return Object(_defined(it));
+ };
+
+ var hasOwnProperty = {}.hasOwnProperty;
+ var _has = function (it, key) {
+ return hasOwnProperty.call(it, key);
+ };
+
+ var toString = {}.toString;
+
+ var _cof = function (it) {
+ return toString.call(it).slice(8, -1);
+ };
+
+ // fallback for non-array-like ES3 and non-enumerable old V8 strings
+
+ // eslint-disable-next-line no-prototype-builtins
+ var _iobject = Object('z').propertyIsEnumerable(0) ? Object : function (it) {
+ return _cof(it) == 'String' ? it.split('') : Object(it);
+ };
+
+ // to indexed object, toObject with fallback for non-array-like ES3 strings
+
+
+ var _toIobject = function (it) {
+ return _iobject(_defined(it));
+ };
+
+ // 7.1.4 ToInteger
+ var ceil = Math.ceil;
+ var floor = Math.floor;
+ var _toInteger = function (it) {
+ return isNaN(it = +it) ? 0 : (it > 0 ? floor : ceil)(it);
+ };
+
+ // 7.1.15 ToLength
+
+ var min = Math.min;
+ var _toLength = function (it) {
+ return it > 0 ? min(_toInteger(it), 0x1fffffffffffff) : 0; // pow(2, 53) - 1 == 9007199254740991
+ };
+
+ var max = Math.max;
+ var min$1 = Math.min;
+ var _toAbsoluteIndex = function (index, length) {
+ index = _toInteger(index);
+ return index < 0 ? max(index + length, 0) : min$1(index, length);
+ };
+
+ // false -> Array#indexOf
+ // true -> Array#includes
+
+
+
+ var _arrayIncludes = function (IS_INCLUDES) {
+ return function ($this, el, fromIndex) {
+ var O = _toIobject($this);
+ var length = _toLength(O.length);
+ var index = _toAbsoluteIndex(fromIndex, length);
+ var value;
+ // Array#includes uses SameValueZero equality algorithm
+ // eslint-disable-next-line no-self-compare
+ if (IS_INCLUDES && el != el) while (length > index) {
+ value = O[index++];
+ // eslint-disable-next-line no-self-compare
+ if (value != value) return true;
+ // Array#indexOf ignores holes, Array#includes - not
+ } else for (;length > index; index++) if (IS_INCLUDES || index in O) {
+ if (O[index] === el) return IS_INCLUDES || index || 0;
+ } return !IS_INCLUDES && -1;
+ };
+ };
+
+ function createCommonjsModule(fn, module) {
+ return module = { exports: {} }, fn(module, module.exports), module.exports;
+ }
+
+ var _global = createCommonjsModule(function (module) {
+ // https://github.com/zloirock/core-js/issues/86#issuecomment-115759028
+ var global = module.exports = typeof window != 'undefined' && window.Math == Math
+ ? window : typeof self != 'undefined' && self.Math == Math ? self
+ // eslint-disable-next-line no-new-func
+ : Function('return this')();
+ if (typeof __g == 'number') __g = global; // eslint-disable-line no-undef
+ });
+
+ var SHARED = '__core-js_shared__';
+ var store = _global[SHARED] || (_global[SHARED] = {});
+ var _shared = function (key) {
+ return store[key] || (store[key] = {});
+ };
+
+ var id = 0;
+ var px = Math.random();
+ var _uid = function (key) {
+ return 'Symbol('.concat(key === undefined ? '' : key, ')_', (++id + px).toString(36));
+ };
+
+ var shared = _shared('keys');
+
+ var _sharedKey = function (key) {
+ return shared[key] || (shared[key] = _uid(key));
+ };
+
+ var arrayIndexOf = _arrayIncludes(false);
+ var IE_PROTO = _sharedKey('IE_PROTO');
+
+ var _objectKeysInternal = function (object, names) {
+ var O = _toIobject(object);
+ var i = 0;
+ var result = [];
+ var key;
+ for (key in O) if (key != IE_PROTO) _has(O, key) && result.push(key);
+ // Don't enum bug & hidden keys
+ while (names.length > i) if (_has(O, key = names[i++])) {
+ ~arrayIndexOf(result, key) || result.push(key);
+ }
+ return result;
+ };
+
+ // IE 8- don't enum bug keys
+ var _enumBugKeys = (
+ 'constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf'
+ ).split(',');
+
+ // 19.1.2.14 / 15.2.3.14 Object.keys(O)
+
+
+
+ var _objectKeys = Object.keys || function keys(O) {
+ return _objectKeysInternal(O, _enumBugKeys);
+ };
+
+ var _core = createCommonjsModule(function (module) {
+ var core = module.exports = { version: '2.5.5' };
+ if (typeof __e == 'number') __e = core; // eslint-disable-line no-undef
+ });
+ var _core_1 = _core.version;
+
+ var _isObject = function (it) {
+ return typeof it === 'object' ? it !== null : typeof it === 'function';
+ };
+
+ var _anObject = function (it) {
+ if (!_isObject(it)) throw TypeError(it + ' is not an object!');
+ return it;
+ };
+
+ var _fails = function (exec) {
+ try {
+ return !!exec();
+ } catch (e) {
+ return true;
+ }
+ };
+
+ // Thank's IE8 for his funny defineProperty
+ var _descriptors = !_fails(function () {
+ return Object.defineProperty({}, 'a', { get: function () { return 7; } }).a != 7;
+ });
+
+ var document$1 = _global.document;
+ // typeof document.createElement is 'object' in old IE
+ var is = _isObject(document$1) && _isObject(document$1.createElement);
+ var _domCreate = function (it) {
+ return is ? document$1.createElement(it) : {};
+ };
+
+ var _ie8DomDefine = !_descriptors && !_fails(function () {
+ return Object.defineProperty(_domCreate('div'), 'a', { get: function () { return 7; } }).a != 7;
+ });
+
+ // 7.1.1 ToPrimitive(input [, PreferredType])
+
+ // instead of the ES6 spec version, we didn't implement @@toPrimitive case
+ // and the second argument - flag - preferred type is a string
+ var _toPrimitive = function (it, S) {
+ if (!_isObject(it)) return it;
+ var fn, val;
+ if (S && typeof (fn = it.toString) == 'function' && !_isObject(val = fn.call(it))) return val;
+ if (typeof (fn = it.valueOf) == 'function' && !_isObject(val = fn.call(it))) return val;
+ if (!S && typeof (fn = it.toString) == 'function' && !_isObject(val = fn.call(it))) return val;
+ throw TypeError("Can't convert object to primitive value");
+ };
+
+ var dP = Object.defineProperty;
+
+ var f = _descriptors ? Object.defineProperty : function defineProperty(O, P, Attributes) {
+ _anObject(O);
+ P = _toPrimitive(P, true);
+ _anObject(Attributes);
+ if (_ie8DomDefine) try {
+ return dP(O, P, Attributes);
+ } catch (e) { /* empty */ }
+ if ('get' in Attributes || 'set' in Attributes) throw TypeError('Accessors not supported!');
+ if ('value' in Attributes) O[P] = Attributes.value;
+ return O;
+ };
+
+ var _objectDp = {
+ f: f
+ };
+
+ var _propertyDesc = function (bitmap, value) {
+ return {
+ enumerable: !(bitmap & 1),
+ configurable: !(bitmap & 2),
+ writable: !(bitmap & 4),
+ value: value
+ };
+ };
+
+ var _hide = _descriptors ? function (object, key, value) {
+ return _objectDp.f(object, key, _propertyDesc(1, value));
+ } : function (object, key, value) {
+ object[key] = value;
+ return object;
+ };
+
+ var _redefine = createCommonjsModule(function (module) {
+ var SRC = _uid('src');
+ var TO_STRING = 'toString';
+ var $toString = Function[TO_STRING];
+ var TPL = ('' + $toString).split(TO_STRING);
+
+ _core.inspectSource = function (it) {
+ return $toString.call(it);
+ };
+
+ (module.exports = function (O, key, val, safe) {
+ var isFunction = typeof val == 'function';
+ if (isFunction) _has(val, 'name') || _hide(val, 'name', key);
+ if (O[key] === val) return;
+ if (isFunction) _has(val, SRC) || _hide(val, SRC, O[key] ? '' + O[key] : TPL.join(String(key)));
+ if (O === _global) {
+ O[key] = val;
+ } else if (!safe) {
+ delete O[key];
+ _hide(O, key, val);
+ } else if (O[key]) {
+ O[key] = val;
+ } else {
+ _hide(O, key, val);
+ }
+ // add fake Function#toString for correct work wrapped methods / constructors with methods like LoDash isNative
+ })(Function.prototype, TO_STRING, function toString() {
+ return typeof this == 'function' && this[SRC] || $toString.call(this);
+ });
+ });
+
+ var _aFunction = function (it) {
+ if (typeof it != 'function') throw TypeError(it + ' is not a function!');
+ return it;
+ };
+
+ // optional / simple context binding
+
+ var _ctx = function (fn, that, length) {
+ _aFunction(fn);
+ if (that === undefined) return fn;
+ switch (length) {
+ case 1: return function (a) {
+ return fn.call(that, a);
+ };
+ case 2: return function (a, b) {
+ return fn.call(that, a, b);
+ };
+ case 3: return function (a, b, c) {
+ return fn.call(that, a, b, c);
+ };
+ }
+ return function (/* ...args */) {
+ return fn.apply(that, arguments);
+ };
+ };
+
+ var PROTOTYPE = 'prototype';
+
+ var $export = function (type, name, source) {
+ var IS_FORCED = type & $export.F;
+ var IS_GLOBAL = type & $export.G;
+ var IS_STATIC = type & $export.S;
+ var IS_PROTO = type & $export.P;
+ var IS_BIND = type & $export.B;
+ var target = IS_GLOBAL ? _global : IS_STATIC ? _global[name] || (_global[name] = {}) : (_global[name] || {})[PROTOTYPE];
+ var exports = IS_GLOBAL ? _core : _core[name] || (_core[name] = {});
+ var expProto = exports[PROTOTYPE] || (exports[PROTOTYPE] = {});
+ var key, own, out, exp;
+ if (IS_GLOBAL) source = name;
+ for (key in source) {
+ // contains in native
+ own = !IS_FORCED && target && target[key] !== undefined;
+ // export native or passed
+ out = (own ? target : source)[key];
+ // bind timers to global for call from export context
+ exp = IS_BIND && own ? _ctx(out, _global) : IS_PROTO && typeof out == 'function' ? _ctx(Function.call, out) : out;
+ // extend global
+ if (target) _redefine(target, key, out, type & $export.U);
+ // export
+ if (exports[key] != out) _hide(exports, key, exp);
+ if (IS_PROTO && expProto[key] != out) expProto[key] = out;
+ }
+ };
+ _global.core = _core;
+ // type bitmap
+ $export.F = 1; // forced
+ $export.G = 2; // global
+ $export.S = 4; // static
+ $export.P = 8; // proto
+ $export.B = 16; // bind
+ $export.W = 32; // wrap
+ $export.U = 64; // safe
+ $export.R = 128; // real proto method for `library`
+ var _export = $export;
+
+ // most Object methods by ES6 should accept primitives
+
+
+
+ var _objectSap = function (KEY, exec) {
+ var fn = (_core.Object || {})[KEY] || Object[KEY];
+ var exp = {};
+ exp[KEY] = exec(fn);
+ _export(_export.S + _export.F * _fails(function () { fn(1); }), 'Object', exp);
+ };
+
+ // 19.1.2.14 Object.keys(O)
+
+
+
+ _objectSap('keys', function () {
+ return function keys(it) {
+ return _objectKeys(_toObject(it));
+ };
+ });
+
+ var keys = _core.Object.keys;
+
+ var _stringRepeat = function repeat(count) {
+ var str = String(_defined(this));
+ var res = '';
+ var n = _toInteger(count);
+ if (n < 0 || n == Infinity) throw RangeError("Count can't be negative");
+ for (;n > 0; (n >>>= 1) && (str += str)) if (n & 1) res += str;
+ return res;
+ };
+
+ _export(_export.P, 'String', {
+ // 21.1.3.13 String.prototype.repeat(count)
+ repeat: _stringRepeat
+ });
+
+ var repeat = _core.String.repeat;
+
+ // https://github.com/tc39/proposal-string-pad-start-end
+
+
+
+
+ var _stringPad = function (that, maxLength, fillString, left) {
+ var S = String(_defined(that));
+ var stringLength = S.length;
+ var fillStr = fillString === undefined ? ' ' : String(fillString);
+ var intMaxLength = _toLength(maxLength);
+ if (intMaxLength <= stringLength || fillStr == '') return S;
+ var fillLen = intMaxLength - stringLength;
+ var stringFiller = _stringRepeat.call(fillStr, Math.ceil(fillLen / fillStr.length));
+ if (stringFiller.length > fillLen) stringFiller = stringFiller.slice(0, fillLen);
+ return left ? stringFiller + S : S + stringFiller;
+ };
+
+ var navigator = _global.navigator;
+
+ var _userAgent = navigator && navigator.userAgent || '';
+
+ // https://github.com/tc39/proposal-string-pad-start-end
+
+
+
+
+ // https://github.com/zloirock/core-js/issues/280
+ _export(_export.P + _export.F * /Version\/10\.\d+(\.\d+)? Safari\//.test(_userAgent), 'String', {
+ padStart: function padStart(maxLength /* , fillString = ' ' */) {
+ return _stringPad(this, maxLength, arguments.length > 1 ? arguments[1] : undefined, true);
+ }
+ });
+
+ var padStart = _core.String.padStart;
+
+ // https://github.com/tc39/proposal-string-pad-start-end
+
+
+
+
+ // https://github.com/zloirock/core-js/issues/280
+ _export(_export.P + _export.F * /Version\/10\.\d+(\.\d+)? Safari\//.test(_userAgent), 'String', {
+ padEnd: function padEnd(maxLength /* , fillString = ' ' */) {
+ return _stringPad(this, maxLength, arguments.length > 1 ? arguments[1] : undefined, false);
+ }
+ });
+
+ var padEnd = _core.String.padEnd;
+
+ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
+ return typeof obj;
+ } : function (obj) {
+ return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
+ };
+
+ var classCallCheck = function (instance, Constructor) {
+ if (!(instance instanceof Constructor)) {
+ throw new TypeError("Cannot call a class as a function");
+ }
+ };
+
+ var createClass = function () {
+ function defineProperties(target, props) {
+ for (var i = 0; i < props.length; i++) {
+ var descriptor = props[i];
+ descriptor.enumerable = descriptor.enumerable || false;
+ descriptor.configurable = true;
+ if ("value" in descriptor) descriptor.writable = true;
+ Object.defineProperty(target, descriptor.key, descriptor);
+ }
+ }
+
+ return function (Constructor, protoProps, staticProps) {
+ if (protoProps) defineProperties(Constructor.prototype, protoProps);
+ if (staticProps) defineProperties(Constructor, staticProps);
+ return Constructor;
+ };
+ }();
+
+ var _extends = Object.assign || function (target) {
+ for (var i = 1; i < arguments.length; i++) {
+ var source = arguments[i];
+
+ for (var key in source) {
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
+ target[key] = source[key];
+ }
+ }
+ }
+
+ return target;
+ };
+
+ var get = function get(object, property, receiver) {
+ if (object === null) object = Function.prototype;
+ var desc = Object.getOwnPropertyDescriptor(object, property);
+
+ if (desc === undefined) {
+ var parent = Object.getPrototypeOf(object);
+
+ if (parent === null) {
+ return undefined;
+ } else {
+ return get(parent, property, receiver);
+ }
+ } else if ("value" in desc) {
+ return desc.value;
+ } else {
+ var getter = desc.get;
+
+ if (getter === undefined) {
+ return undefined;
+ }
+
+ return getter.call(receiver);
+ }
+ };
+
+ var inherits = function (subClass, superClass) {
+ if (typeof superClass !== "function" && superClass !== null) {
+ throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
+ }
+
+ subClass.prototype = Object.create(superClass && superClass.prototype, {
+ constructor: {
+ value: subClass,
+ enumerable: false,
+ writable: true,
+ configurable: true
+ }
+ });
+ if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
+ };
+
+ var possibleConstructorReturn = function (self, call) {
+ if (!self) {
+ throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
+ }
+
+ return call && (typeof call === "object" || typeof call === "function") ? call : self;
+ };
+
+ var set = function set(object, property, value, receiver) {
+ var desc = Object.getOwnPropertyDescriptor(object, property);
+
+ if (desc === undefined) {
+ var parent = Object.getPrototypeOf(object);
+
+ if (parent !== null) {
+ set(parent, property, value, receiver);
+ }
+ } else if ("value" in desc && desc.writable) {
+ desc.value = value;
+ } else {
+ var setter = desc.set;
+
+ if (setter !== undefined) {
+ setter.call(receiver, value);
+ }
+ }
+
+ return value;
+ };
+
+ var slicedToArray = function () {
+ function sliceIterator(arr, i) {
+ var _arr = [];
+ var _n = true;
+ var _d = false;
+ var _e = undefined;
+
+ try {
+ for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
+ _arr.push(_s.value);
+
+ if (i && _arr.length === i) break;
+ }
+ } catch (err) {
+ _d = true;
+ _e = err;
+ } finally {
+ try {
+ if (!_n && _i["return"]) _i["return"]();
+ } finally {
+ if (_d) throw _e;
+ }
+ }
+
+ return _arr;
+ }
+
+ return function (arr, i) {
+ if (Array.isArray(arr)) {
+ return arr;
+ } else if (Symbol.iterator in Object(arr)) {
+ return sliceIterator(arr, i);
+ } else {
+ throw new TypeError("Invalid attempt to destructure non-iterable instance");
+ }
+ };
+ }();
+
+ var toConsumableArray = function (arr) {
+ if (Array.isArray(arr)) {
+ for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i];
+
+ return arr2;
+ } else {
+ return Array.from(arr);
+ }
+ };
+
+ /** Checks if value is string */
+ function isString(str) {
+ return typeof str === 'string' || str instanceof String;
+ }
+
+ /** Conforms string with fallback */
+ function conform(res, str) {
+ var fallback = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
+
+ return isString(res) ? res : res ? str : fallback;
+ }
+
+ /**
+ Direction
+ @prop {number} NONE
+ @prop {number} LEFT
+ @prop {number} RIGHT
+ */
+ var DIRECTION = {
+ NONE: 0,
+ LEFT: -1,
+ RIGHT: 1
+ /**
+ Direction
+ @enum {number}
+ */
+ };
+
+ /** Returns next char position in direction */
+ function indexInDirection(pos, direction) {
+ if (direction === DIRECTION.LEFT) --pos;
+ return pos;
+ }
+
+ /** Escapes regular expression control chars */
+ function escapeRegExp(str) {
+ return str.replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1');
+ }
+
+ // cloned from https://github.com/epoberezkin/fast-deep-equal with small changes
+ function objectIncludes(b, a) {
+ if (a === b) return true;
+
+ var arrA = Array.isArray(a),
+ arrB = Array.isArray(b),
+ i;
+
+ if (arrA && arrB) {
+ if (a.length != b.length) return false;
+ for (i = 0; i < a.length; i++) {
+ if (!objectIncludes(a[i], b[i])) return false;
+ }return true;
+ }
+
+ if (arrA != arrB) return false;
+
+ if (a && b && (typeof a === 'undefined' ? 'undefined' : _typeof(a)) === 'object' && (typeof b === 'undefined' ? 'undefined' : _typeof(b)) === 'object') {
+ var keys = Object.keys(a);
+ // if (keys.length !== Object.keys(b).length) return false;
+
+ var dateA = a instanceof Date,
+ dateB = b instanceof Date;
+ if (dateA && dateB) return a.getTime() == b.getTime();
+ if (dateA != dateB) return false;
+
+ var regexpA = a instanceof RegExp,
+ regexpB = b instanceof RegExp;
+ if (regexpA && regexpB) return a.toString() == b.toString();
+ if (regexpA != regexpB) return false;
+
+ for (i = 0; i < keys.length; i++) {
+ if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false;
+ }for (i = 0; i < keys.length; i++) {
+ if (!objectIncludes(a[keys[i]], b[keys[i]])) return false;
+ }return true;
+ }
+
+ return false;
+ }
+
+ /* eslint-disable no-undef */
+ var g = typeof window !== 'undefined' && window || typeof global !== 'undefined' && global.global === global && global || typeof self !== 'undefined' && self.self === self && self || {};
+
+ /** Provides details of changing input */
+
+ var ActionDetails = function () {
+ /** Old input value */
+
+ /** Current input value */
+ function ActionDetails(value, cursorPos, oldValue, oldSelection) {
+ classCallCheck(this, ActionDetails);
+
+ this.value = value;
+ this.cursorPos = cursorPos;
+ this.oldValue = oldValue;
+ this.oldSelection = oldSelection;
+
+ // double check if left part was changed (autofilling, other non-standard input triggers)
+ while (this.value.slice(0, this.startChangePos) !== this.oldValue.slice(0, this.startChangePos)) {
+ --this.oldSelection.start;
+ }
+ }
+
+ /**
+ Start changing position
+ @readonly
+ */
+
+ /** Old selection */
+
+ /** Current cursor position */
+
+
+ createClass(ActionDetails, [{
+ key: 'startChangePos',
+ get: function get$$1() {
+ return Math.min(this.cursorPos, this.oldSelection.start);
+ }
+
+ /**
+ Inserted symbols count
+ @readonly
+ */
+
+ }, {
+ key: 'insertedCount',
+ get: function get$$1() {
+ return this.cursorPos - this.startChangePos;
+ }
+
+ /**
+ Inserted symbols
+ @readonly
+ */
+
+ }, {
+ key: 'inserted',
+ get: function get$$1() {
+ return this.value.substr(this.startChangePos, this.insertedCount);
+ }
+
+ /**
+ Removed symbols count
+ @readonly
+ */
+
+ }, {
+ key: 'removedCount',
+ get: function get$$1() {
+ // Math.max for opposite operation
+ return Math.max(this.oldSelection.end - this.startChangePos ||
+ // for Delete
+ this.oldValue.length - this.value.length, 0);
+ }
+
+ /**
+ Removed symbols
+ @readonly
+ */
+
+ }, {
+ key: 'removed',
+ get: function get$$1() {
+ return this.oldValue.substr(this.startChangePos, this.removedCount);
+ }
+
+ /**
+ Unchanged head symbols
+ @readonly
+ */
+
+ }, {
+ key: 'head',
+ get: function get$$1() {
+ return this.value.substring(0, this.startChangePos);
+ }
+
+ /**
+ Unchanged tail symbols
+ @readonly
+ */
+
+ }, {
+ key: 'tail',
+ get: function get$$1() {
+ return this.value.substring(this.startChangePos + this.insertedCount);
+ }
+
+ /**
+ Remove direction
+ @readonly
+ */
+
+ }, {
+ key: 'removeDirection',
+ get: function get$$1() {
+ if (!this.removedCount || this.insertedCount) return DIRECTION.NONE;
+
+ // align right if delete at right or if range removed (event with backspace)
+ return this.oldSelection.end === this.cursorPos || this.oldSelection.start === this.cursorPos ? DIRECTION.RIGHT : DIRECTION.LEFT;
+ }
+ }]);
+ return ActionDetails;
+ }();
+
+ /**
+ Provides details of changing model value
+ @param {Object} [details]
+ @param {string} [details.inserted] - Inserted symbols
+ @param {boolean} [details.overflow] - Is overflowed
+ @param {number} [details.removeCount] - Removed symbols count
+ @param {number} [details.shift] - Additional offset if any changes occurred before current position
+ */
+ var ChangeDetails = function () {
+ /** Additional offset if any changes occurred before current position */
+
+ /** Inserted symbols */
+ function ChangeDetails(details) {
+ classCallCheck(this, ChangeDetails);
+
+ _extends(this, {
+ inserted: '',
+ overflow: false,
+ shift: 0
+ }, details);
+ }
+
+ /**
+ Aggregate changes
+ @returns {ChangeDetails} `this`
+ */
+
+ /** Is overflowed */
+
+
+ createClass(ChangeDetails, [{
+ key: 'aggregate',
+ value: function aggregate(details) {
+ if (details.rawInserted) this.rawInserted += details.rawInserted;
+ this.inserted += details.inserted;
+ this.shift += details.shift;
+ this.overflow = this.overflow || details.overflow;
+ return this;
+ }
+
+ /** Total offset considering all changes */
+
+ }, {
+ key: 'offset',
+ get: function get$$1() {
+ return this.shift + this.inserted.length;
+ }
+
+ /** Raw inserted is used by dynamic mask */
+
+ }, {
+ key: 'rawInserted',
+ get: function get$$1() {
+ return this._rawInserted != null ? this._rawInserted : this.inserted;
+ },
+ set: function set$$1(rawInserted) {
+ this._rawInserted = rawInserted;
+ }
+ }]);
+ return ChangeDetails;
+ }();
+
+ /** Supported mask type */
+
+
+ /** Append flags */
+
+
+ /** Extract flags */
+
+ /** Provides common masking stuff */
+ var Masked = function () {
+ /** Does additional processing in the end of editing */
+
+ /** Transforms value before mask processing */
+ function Masked(opts) {
+ classCallCheck(this, Masked);
+
+ this._value = '';
+ this._update(opts);
+ this.isInitialized = true;
+ }
+
+ /** Sets and applies new options */
+
+ /** */
+
+ /** Validates if value is acceptable */
+ // $Shape; TODO after fix https://github.com/facebook/flow/issues/4773
+
+ /** @type {Mask} */
+
+
+ createClass(Masked, [{
+ key: 'updateOptions',
+ value: function updateOptions(opts) {
+ this.withValueRefresh(this._update.bind(this, opts));
+ }
+
+ /**
+ Sets new options
+ @protected
+ */
+
+ }, {
+ key: '_update',
+ value: function _update(opts) {
+ _extends(this, opts);
+ }
+
+ /** Clones masked with options and value */
+
+ }, {
+ key: 'clone',
+ value: function clone() {
+ var m = new Masked(this);
+ m._value = this.value.slice();
+ return m;
+ }
+
+ /** */
+
+ }, {
+ key: 'assign',
+ value: function assign(source) {
+ // $FlowFixMe
+ return _extends(this, source);
+ }
+
+ /** Resets value */
+
+ }, {
+ key: 'reset',
+ value: function reset() {
+ this._value = '';
+ }
+
+ /** */
+
+ }, {
+ key: 'resolve',
+
+
+ /** Resolve new value */
+ value: function resolve(value) {
+ this.reset();
+ this._append(value, { input: true });
+ this._appendTail();
+ this.doCommit();
+ return this.value;
+ }
+
+ /** */
+
+ }, {
+ key: 'nearestInputPos',
+
+
+ /** Finds nearest input position in direction */
+ value: function nearestInputPos(cursorPos, direction) {
+ return cursorPos;
+ }
+
+ /** Extracts value in range considering flags */
+
+ }, {
+ key: 'extractInput',
+ value: function extractInput() {
+ var fromPos = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
+ var toPos = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.value.length;
+
+ return this.value.slice(fromPos, toPos);
+ }
+
+ /** Extracts tail in range */
+
+ }, {
+ key: '_extractTail',
+ value: function _extractTail() {
+ var fromPos = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
+ var toPos = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.value.length;
+
+ return {
+ value: this.extractInput(fromPos, toPos),
+ fromPos: fromPos,
+ toPos: toPos
+ };
+ }
+
+ /** Appends tail */
+
+ }, {
+ key: '_appendTail',
+ value: function _appendTail(tail) {
+ return this._append(tail ? tail.value : '', { tail: true });
+ }
+
+ /** Appends symbols considering flags */
+
+ }, {
+ key: '_append',
+ value: function _append(str) {
+ var flags = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
+
+ var oldValueLength = this.value.length;
+ var consistentValue = this.clone();
+ var overflow = false;
+
+ str = this.doPrepare(str, flags);
+
+ for (var ci = 0; ci < str.length; ++ci) {
+ this._value += str[ci];
+ if (this.doValidate(flags) === false) {
+ this.assign(consistentValue);
+ if (!flags.input) {
+ // in `input` mode dont skip invalid chars
+ overflow = true;
+ break;
+ }
+ }
+
+ consistentValue = this.clone();
+ }
+
+ return new ChangeDetails({
+ inserted: this.value.slice(oldValueLength),
+ overflow: overflow
+ });
+ }
+
+ /** Appends symbols considering tail */
+
+ }, {
+ key: 'appendWithTail',
+ value: function appendWithTail(str, tail) {
+ // TODO refactor
+ var aggregateDetails = new ChangeDetails();
+ var consistentValue = this.clone();
+ var consistentAppended = void 0;
+
+ for (var ci = 0; ci < str.length; ++ci) {
+ var ch = str[ci];
+
+ var appendDetails = this._append(ch, { input: true });
+ consistentAppended = this.clone();
+ var tailAppended = !appendDetails.overflow && !this._appendTail(tail).overflow;
+ if (!tailAppended || this.doValidate({ tail: true }) === false) {
+ this.assign(consistentValue);
+ break;
+ }
+
+ this.assign(consistentAppended);
+ consistentValue = this.clone();
+ aggregateDetails.aggregate(appendDetails);
+ }
+
+ // TODO needed for cases when
+ // 1) REMOVE ONLY AND NO LOOP AT ALL
+ // 2) last loop iteration removes tail
+ // 3) when breaks on tail insert
+
+ // aggregate only shift from tail
+ aggregateDetails.shift += this._appendTail(tail).shift;
+
+ return aggregateDetails;
+ }
+
+ /** */
+
+ }, {
+ key: 'remove',
+ value: function remove() {
+ var from = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
+ var count = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.value.length - from;
+
+ this._value = this.value.slice(0, from) + this.value.slice(from + count);
+ return new ChangeDetails();
+ }
+
+ /** Calls function and reapplies current value */
+
+ }, {
+ key: 'withValueRefresh',
+ value: function withValueRefresh(fn) {
+ if (this._refreshing || !this.isInitialized) return fn();
+ this._refreshing = true;
+
+ var unmasked = this.unmaskedValue;
+ var value = this.value;
+
+ var ret = fn();
+
+ // try to update with raw value first to keep fixed chars
+ if (this.resolve(value) !== value) {
+ // or fallback to unmasked
+ this.unmaskedValue = unmasked;
+ }
+
+ delete this._refreshing;
+ return ret;
+ }
+
+ /**
+ Prepares string before mask processing
+ @protected
+ */
+
+ }, {
+ key: 'doPrepare',
+ value: function doPrepare(str) {
+ var flags = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
+
+ return this.prepare ? this.prepare(str, this, flags) : str;
+ }
+
+ /**
+ Validates if value is acceptable
+ @protected
+ */
+
+ }, {
+ key: 'doValidate',
+ value: function doValidate(flags) {
+ return !this.validate || this.validate(this.value, this, flags);
+ }
+
+ /**
+ Does additional processing in the end of editing
+ @protected
+ */
+
+ }, {
+ key: 'doCommit',
+ value: function doCommit() {
+ if (this.commit) this.commit(this.value, this);
+ }
+
+ // TODO
+ // insert (str, fromPos, flags)
+
+ /** */
+
+ }, {
+ key: 'splice',
+ value: function splice(start, deleteCount, inserted, removeDirection) {
+ var tailPos = start + deleteCount;
+ var tail = this._extractTail(tailPos);
+
+ var startChangePos = this.nearestInputPos(start, removeDirection);
+ var changeDetails = new ChangeDetails({
+ shift: startChangePos - start // adjust shift if start was aligned
+ }).aggregate(this.remove(startChangePos)).aggregate(this.appendWithTail(inserted, tail));
+
+ return changeDetails;
+ }
+ }, {
+ key: 'value',
+ get: function get$$1() {
+ return this._value;
+ },
+ set: function set$$1(value) {
+ this.resolve(value);
+ }
+ }, {
+ key: 'unmaskedValue',
+ get: function get$$1() {
+ return this.value;
+ },
+ set: function set$$1(value) {
+ this.reset();
+ this._append(value);
+ this._appendTail();
+ this.doCommit();
+ }
+
+ /** */
+
+ }, {
+ key: 'typedValue',
+ get: function get$$1() {
+ return this.unmaskedValue;
+ },
+ set: function set$$1(value) {
+ this.unmaskedValue = value;
+ }
+
+ /** Value that includes raw user input */
+
+ }, {
+ key: 'rawInputValue',
+ get: function get$$1() {
+ return this.extractInput(0, this.value.length, { raw: true });
+ },
+ set: function set$$1(value) {
+ this.reset();
+ this._append(value, { raw: true });
+ this._appendTail();
+ this.doCommit();
+ }
+
+ /** */
+
+ }, {
+ key: 'isComplete',
+ get: function get$$1() {
+ return true;
+ }
+ }]);
+ return Masked;
+ }();
+
+ /** Get Masked class by mask type */
+ function maskedClass(mask) {
+ if (mask == null) {
+ throw new Error('mask property should be defined');
+ }
+
+ if (mask instanceof RegExp) return g.VindiCCMask.MaskedRegExp;
+ if (isString(mask)) return g.VindiCCMask.MaskedPattern;
+ if (mask instanceof Date || mask === Date) return g.VindiCCMask.MaskedDate;
+ if (mask instanceof Number || typeof mask === 'number' || mask === Number) return g.VindiCCMask.MaskedNumber;
+ if (Array.isArray(mask) || mask === Array) return g.VindiCCMask.MaskedDynamic;
+ // $FlowFixMe
+ if (mask.prototype instanceof g.VindiCCMask.Masked) return mask;
+ // $FlowFixMe
+ if (mask instanceof Function) return g.VindiCCMask.MaskedFunction;
+
+ console.warn('Mask not found for mask', mask); // eslint-disable-line no-console
+ return g.VindiCCMask.Masked;
+ }
+
+ /** Creates new {@link Masked} depending on mask type */
+ function createMask(opts) {
+ opts = _extends({}, opts); // clone
+ var mask = opts.mask;
+
+ if (mask instanceof g.VindiCCMask.Masked) return mask;
+
+ var MaskedClass = maskedClass(mask);
+ return new MaskedClass(opts);
+ }
+
+ /** */
+
+ /** */
+ var PatternDefinition = function () {
+ /** */
+
+ /** */
+
+ /** */
+
+ /** */
+ function PatternDefinition(opts) {
+ classCallCheck(this, PatternDefinition);
+ // TODO flow
+ _extends(this, opts);
+
+ if (this.mask) {
+ this._masked = createMask(opts);
+ }
+ }
+
+ /** */
+
+ /** */
+
+ /** */
+
+ /** */
+
+ /** */
+
+
+ /** */
+
+
+ createClass(PatternDefinition, [{
+ key: 'reset',
+ value: function reset() {
+ this.isHollow = false;
+ this.isRawInput = false;
+ if (this._masked) this._masked.reset();
+ }
+
+ /** */
+
+ }, {
+ key: 'resolve',
+
+
+ /** */
+ value: function resolve(ch) {
+ if (!this._masked) return false;
+ return this._masked.resolve(ch);
+ }
+ }, {
+ key: 'isInput',
+ get: function get$$1() {
+ return this.type === PatternDefinition.TYPES.INPUT;
+ }
+
+ /** */
+
+ }, {
+ key: 'isHiddenHollow',
+ get: function get$$1() {
+ return this.isHollow && this.optional;
+ }
+ }]);
+ return PatternDefinition;
+ }();
+
+ PatternDefinition.DEFAULTS = {
+ '0': /\d/,
+ 'a': /[\u0041-\u005A\u0061-\u007A\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]/, // http://stackoverflow.com/a/22075070
+ '*': /./
+ };
+ /**
+ @prop {string} INPUT
+ @prop {string} FIXED
+ */
+ PatternDefinition.TYPES = {
+ INPUT: 'input',
+ FIXED: 'fixed'
+ };
+
+ /** */
+
+
+ /** */
+
+ /**
+ Pattern group symbols from parent
+ @param {MaskedPattern} masked - Internal {@link masked} model
+ @param {Object} opts
+ @param {string} opts.name - Group name
+ @param {number} opts.offset - Group offset in masked definitions array
+ @param {string} opts.mask - Group mask
+ @param {Function} [opts.validate] - Custom group validator
+ */
+ var PatternGroup = function () {
+ /** Group mask */
+
+ /** Group name */
+
+ /** */
+ function PatternGroup(masked, _ref) {
+ var name = _ref.name,
+ offset = _ref.offset,
+ mask = _ref.mask,
+ validate = _ref.validate;
+ classCallCheck(this, PatternGroup);
+
+ this.masked = masked;
+ this.name = name;
+ this.offset = offset;
+ this.mask = mask;
+ this.validate = validate || function () {
+ return true;
+ };
+ }
+
+ /** Slice of internal {@link masked} value */
+
+ /** Custom group validator */
+
+ /** Group offset in masked definitions array */
+
+
+ /** Internal {@link masked} model */
+
+ /** */
+
+
+ createClass(PatternGroup, [{
+ key: 'doValidate',
+
+
+ /** Validates if current value is acceptable */
+ value: function doValidate(flags) {
+ return this.validate(this.value, this, flags);
+ }
+ }, {
+ key: 'value',
+ get: function get$$1() {
+ return this.masked.value.slice(this.masked.mapDefIndexToPos(this.offset), this.masked.mapDefIndexToPos(this.offset + this.mask.length));
+ }
+
+ /** Unmasked slice of internal {@link masked} value */
+
+ }, {
+ key: 'unmaskedValue',
+ get: function get$$1() {
+ return this.masked.extractInput(this.masked.mapDefIndexToPos(this.offset), this.masked.mapDefIndexToPos(this.offset + this.mask.length));
+ }
+ }]);
+ return PatternGroup;
+ }();
+ var RangeGroup = function () {
+ /** @type {Function} */
+ function RangeGroup(_ref2) {
+ var _ref3 = slicedToArray(_ref2, 2),
+ from = _ref3[0],
+ to = _ref3[1];
+
+ var maxlen = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : String(to).length;
+ classCallCheck(this, RangeGroup);
+
+ this._from = from;
+ this._to = to;
+ this._maxLength = maxlen;
+ this.validate = this.validate.bind(this);
+
+ this._update();
+ }
+ /** @type {string} */
+
+
+ createClass(RangeGroup, [{
+ key: '_update',
+ value: function _update() {
+ this._maxLength = Math.max(this._maxLength, String(this.to).length);
+ this.mask = '0'.repeat(this._maxLength);
+ }
+ }, {
+ key: 'validate',
+ value: function validate(str) {
+ var minstr = '';
+ var maxstr = '';
+
+ var _ref4 = str.match(/^(\D*)(\d*)(\D*)/) || [],
+ _ref5 = slicedToArray(_ref4, 3),
+ placeholder = _ref5[1],
+ num = _ref5[2];
+
+ if (num) {
+ minstr = '0'.repeat(placeholder.length) + num;
+ maxstr = '9'.repeat(placeholder.length) + num;
+ }
+
+ var firstNonZero = str.search(/[^0]/);
+ if (firstNonZero === -1 && str.length <= this._matchFrom) return true;
+
+ minstr = minstr.padEnd(this._maxLength, '0');
+ maxstr = maxstr.padEnd(this._maxLength, '9');
+
+ return this.from <= Number(maxstr) && Number(minstr) <= this.to;
+ }
+ }, {
+ key: 'to',
+ get: function get$$1() {
+ return this._to;
+ },
+ set: function set$$1(to) {
+ this._to = to;
+ this._update();
+ }
+ }, {
+ key: 'from',
+ get: function get$$1() {
+ return this._from;
+ },
+ set: function set$$1(from) {
+ this._from = from;
+ this._update();
+ }
+ }, {
+ key: 'maxLength',
+ get: function get$$1() {
+ return this._maxLength;
+ },
+ set: function set$$1(maxLength) {
+ this._maxLength = maxLength;
+ this._update();
+ }
+ }, {
+ key: '_matchFrom',
+ get: function get$$1() {
+ return this.maxLength - String(this.from).length;
+ }
+ }]);
+ return RangeGroup;
+ }();
+
+ /** Pattern group that validates enum values */
+ function EnumGroup(enums) {
+ return {
+ mask: '*'.repeat(enums[0].length),
+ validate: function validate(value, group, flags) {
+ return enums.some(function (e) {
+ return e.indexOf(group.unmaskedValue) >= 0;
+ });
+ }
+ };
+ }
+
+ PatternGroup.Range = RangeGroup;
+ PatternGroup.Enum = EnumGroup;
+
+ var ChunksTailDetails = function () {
+ function ChunksTailDetails(chunks) {
+ classCallCheck(this, ChunksTailDetails);
+
+ this.chunks = chunks;
+ }
+
+ createClass(ChunksTailDetails, [{
+ key: 'value',
+ get: function get$$1() {
+ return this.chunks.map(function (c) {
+ return c.value;
+ }).join('');
+ }
+ }, {
+ key: 'fromPos',
+ get: function get$$1() {
+ var firstChunk = this.chunks[0];
+ return firstChunk && firstChunk.stop;
+ }
+ }, {
+ key: 'toPos',
+ get: function get$$1() {
+ var lastChunk = this.chunks[this.chunks.length - 1];
+ return lastChunk && lastChunk.stop;
+ }
+ }]);
+ return ChunksTailDetails;
+ }();
+
+ /**
+ Pattern mask
+ @param {Object} opts
+ @param {Object} opts.groups
+ @param {Object} opts.definitions
+ @param {string} opts.placeholderChar
+ @param {boolean} opts.lazy
+ */
+ var MaskedPattern = function (_Masked) {
+ inherits(MaskedPattern, _Masked);
+
+ // TODO mask type
+ /** Single char for empty input */
+
+
+ /** */
+ function MaskedPattern() {
+ var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
+ classCallCheck(this, MaskedPattern);
+ // TODO type $Shape={} does not work
+ opts.definitions = _extends({}, PatternDefinition.DEFAULTS, opts.definitions);
+ return possibleConstructorReturn(this, (MaskedPattern.__proto__ || Object.getPrototypeOf(MaskedPattern)).call(this, _extends({}, MaskedPattern.DEFAULTS, opts)));
+ }
+
+ /**
+ @override
+ @param {Object} opts
+ */
+
+ /** Show placeholder only when needed */
+
+ /** */
+
+
+ createClass(MaskedPattern, [{
+ key: '_update',
+ value: function _update() {
+ var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
+
+ opts.definitions = _extends({}, this.definitions, opts.definitions);
+ get(MaskedPattern.prototype.__proto__ || Object.getPrototypeOf(MaskedPattern.prototype), '_update', this).call(this, opts);
+ this._rebuildMask();
+ }
+
+ /** */
+
+ }, {
+ key: '_rebuildMask',
+ value: function _rebuildMask() {
+ var _this2 = this;
+
+ var defs = this.definitions;
+ this._charDefs = [];
+ this._groupDefs = [];
+
+ var pattern = this.mask;
+ if (!pattern || !defs) return;
+
+ var unmaskingBlock = false;
+ var optionalBlock = false;
+ var stopAlign = false;
+
+ var _loop = function _loop(_i) {
+ if (_this2.groups) {
+ var p = pattern.slice(_i);
+ var gNames = Object.keys(_this2.groups).filter(function (gName) {
+ return p.indexOf(gName) === 0;
+ });
+ // order by key length
+ gNames.sort(function (a, b) {
+ return b.length - a.length;
+ });
+ // use group name with max length
+ var gName = gNames[0];
+ if (gName) {
+ var group = _this2.groups[gName];
+ _this2._groupDefs.push(new PatternGroup(_this2, {
+ name: gName,
+ offset: _this2._charDefs.length,
+ mask: group.mask,
+ validate: group.validate
+ }));
+ pattern = pattern.replace(gName, group.mask);
+ }
+ }
+
+ var char = pattern[_i];
+ var type = char in defs ? PatternDefinition.TYPES.INPUT : PatternDefinition.TYPES.FIXED;
+ var unmasking = type === PatternDefinition.TYPES.INPUT || unmaskingBlock;
+ var optional = type === PatternDefinition.TYPES.INPUT && optionalBlock;
+
+ if (char === MaskedPattern.STOP_CHAR) {
+ stopAlign = true;
+ return 'continue';
+ }
+
+ if (char === '{' || char === '}') {
+ unmaskingBlock = !unmaskingBlock;
+ return 'continue';
+ }
+
+ if (char === '[' || char === ']') {
+ optionalBlock = !optionalBlock;
+ return 'continue';
+ }
+
+ if (char === MaskedPattern.ESCAPE_CHAR) {
+ ++_i;
+ char = pattern[_i];
+ if (!char) return 'break';
+ type = PatternDefinition.TYPES.FIXED;
+ }
+
+ _this2._charDefs.push(new PatternDefinition({
+ char: char,
+ type: type,
+ optional: optional,
+ stopAlign: stopAlign,
+ unmasking: unmasking,
+ mask: type === PatternDefinition.TYPES.INPUT ? defs[char] : function (value) {
+ return value === char;
+ }
+ }));
+
+ stopAlign = false;
+ i = _i;
+ };
+
+ _loop2: for (var i = 0; i < pattern.length; ++i) {
+ var _ret = _loop(i);
+
+ switch (_ret) {
+ case 'continue':
+ continue;
+
+ case 'break':
+ break _loop2;}
+ }
+ }
+
+ /**
+ @override
+ */
+
+ }, {
+ key: 'doValidate',
+ value: function doValidate() {
+ var _babelHelpers$get;
+
+ for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
+ args[_key] = arguments[_key];
+ }
+
+ return this._groupDefs.every(function (g$$1) {
+ return g$$1.doValidate.apply(g$$1, toConsumableArray(args));
+ }) && (_babelHelpers$get = get(MaskedPattern.prototype.__proto__ || Object.getPrototypeOf(MaskedPattern.prototype), 'doValidate', this)).call.apply(_babelHelpers$get, [this].concat(toConsumableArray(args)));
+ }
+
+ /**
+ @override
+ */
+
+ }, {
+ key: 'clone',
+ value: function clone() {
+ var _this3 = this;
+
+ var m = new MaskedPattern(this);
+ m._value = this.value;
+ // $FlowFixMe
+ m._charDefs.forEach(function (d, i) {
+ return _extends(d, _this3._charDefs[i]);
+ });
+ // $FlowFixMe
+ m._groupDefs.forEach(function (d, i) {
+ return _extends(d, _this3._groupDefs[i]);
+ });
+ return m;
+ }
+
+ /**
+ @override
+ */
+
+ }, {
+ key: 'reset',
+ value: function reset() {
+ get(MaskedPattern.prototype.__proto__ || Object.getPrototypeOf(MaskedPattern.prototype), 'reset', this).call(this);
+ this._charDefs.forEach(function (d) {
+ delete d.isHollow;
+ });
+ }
+
+ /**
+ @override
+ */
+
+ }, {
+ key: 'hiddenHollowsBefore',
+
+
+ /** */
+ value: function hiddenHollowsBefore(defIndex) {
+ return this._charDefs.slice(0, defIndex).filter(function (d) {
+ return d.isHiddenHollow;
+ }).length;
+ }
+
+ /** Map definition index to position on view */
+
+ }, {
+ key: 'mapDefIndexToPos',
+ value: function mapDefIndexToPos(defIndex) {
+ return defIndex - this.hiddenHollowsBefore(defIndex);
+ }
+
+ /** Map position on view to definition index */
+
+ }, {
+ key: 'mapPosToDefIndex',
+ value: function mapPosToDefIndex(pos) {
+ var defIndex = pos;
+ for (var di = 0; di < this._charDefs.length; ++di) {
+ var def = this._charDefs[di];
+ if (di >= defIndex) break;
+ if (def.isHiddenHollow) ++defIndex;
+ }
+ return defIndex;
+ }
+
+ /**
+ @override
+ */
+
+ }, {
+ key: '_appendTail',
+
+
+ /**
+ @override
+ */
+ value: function _appendTail(tail) {
+ var details = new ChangeDetails();
+ if (tail) {
+ details.aggregate(tail instanceof ChunksTailDetails ? this._appendChunks(tail.chunks, { tail: true }) : get(MaskedPattern.prototype.__proto__ || Object.getPrototypeOf(MaskedPattern.prototype), '_appendTail', this).call(this, tail));
+ }
+ return details.aggregate(this._appendPlaceholder());
+ }
+
+ /**
+ @override
+ */
+
+ }, {
+ key: '_append',
+ value: function _append(str) {
+ var flags = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
+
+ var oldValueLength = this.value.length;
+ var rawInserted = '';
+ var overflow = false;
+
+ str = this.doPrepare(str, flags);
+
+ for (var ci = 0, di = this.mapPosToDefIndex(this.value.length); ci < str.length;) {
+ var ch = str[ci];
+ var def = this._charDefs[di];
+
+ // check overflow
+ if (def == null) {
+ overflow = true;
+ break;
+ }
+
+ // reset
+ def.isHollow = false;
+
+ var resolved = void 0,
+ skipped = void 0;
+ var chres = conform(def.resolve(ch), ch);
+
+ if (def.type === PatternDefinition.TYPES.INPUT) {
+ if (chres) {
+ this._value += chres;
+ if (!this.doValidate()) {
+ chres = '';
+ this._value = this.value.slice(0, -1);
+ }
+ }
+
+ resolved = !!chres;
+ skipped = !chres && !def.optional;
+
+ if (!chres) {
+ if (!def.optional && !flags.input && !this.lazy) {
+ this._value += this.placeholderChar;
+ skipped = false;
+ }
+ if (!skipped) def.isHollow = true;
+ } else {
+ rawInserted += chres;
+ }
+ } else {
+ this._value += def.char;
+ resolved = chres && (def.unmasking || flags.input || flags.raw) && !flags.tail;
+ def.isRawInput = resolved && (flags.raw || flags.input);
+ if (def.isRawInput) rawInserted += def.char;
+ }
+
+ if (!skipped) ++di;
+ if (resolved || skipped) ++ci;
+ }
+
+ return new ChangeDetails({
+ inserted: this.value.slice(oldValueLength),
+ rawInserted: rawInserted,
+ overflow: overflow
+ });
+ }
+
+ /** Appends chunks splitted by stop chars */
+
+ }, {
+ key: '_appendChunks',
+ value: function _appendChunks(chunks) {
+ var details = new ChangeDetails();
+
+ for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
+ args[_key2 - 1] = arguments[_key2];
+ }
+
+ for (var ci = 0; ci < chunks.length; ++ci) {
+ var _chunks$ci = chunks[ci],
+ stop = _chunks$ci.stop,
+ value = _chunks$ci.value;
+
+ var fromDef = stop != null && this._charDefs[stop];
+ // lets double check if stopAlign is here
+ if (fromDef && fromDef.stopAlign) details.aggregate(this._appendPlaceholder(stop));
+ if (details.aggregate(this._append.apply(this, [value].concat(toConsumableArray(args)))).overflow) break;
+ }
+ return details;
+ }
+
+ /**
+ @override
+ */
+
+ }, {
+ key: '_extractTail',
+ value: function _extractTail() {
+ var fromPos = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
+ var toPos = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.value.length;
+
+ return new ChunksTailDetails(this._extractInputChunks(fromPos, toPos));
+ }
+
+ /**
+ @override
+ */
+
+ }, {
+ key: 'extractInput',
+ value: function extractInput() {
+ var fromPos = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
+ var toPos = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.value.length;
+ var flags = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
+
+ if (fromPos === toPos) return '';
+
+ var str = this.value;
+ var input = '';
+
+ var toDefIndex = this.mapPosToDefIndex(toPos);
+ for (var ci = fromPos, di = this.mapPosToDefIndex(fromPos); ci < toPos && ci < str.length && di < toDefIndex; ++di) {
+ var ch = str[ci];
+ var def = this._charDefs[di];
+
+ if (!def) break;
+ if (def.isHiddenHollow) continue;
+
+ if (def.isInput && !def.isHollow || flags.raw && !def.isInput && def.isRawInput) input += ch;
+ ++ci;
+ }
+ return input;
+ }
+
+ /** Extracts chunks from input splitted by stop chars */
+
+ }, {
+ key: '_extractInputChunks',
+ value: function _extractInputChunks() {
+ var _this4 = this;
+
+ var fromPos = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
+ var toPos = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.value.length;
+
+ if (fromPos === toPos) return [];
+
+ var fromDefIndex = this.mapPosToDefIndex(fromPos);
+ var toDefIndex = this.mapPosToDefIndex(toPos);
+ var stopDefIndices = this._charDefs.map(function (d, i) {
+ return [d, i];
+ }).slice(fromDefIndex, toDefIndex).filter(function (_ref) {
+ var _ref2 = slicedToArray(_ref, 1),
+ d = _ref2[0];
+
+ return d.stopAlign;
+ }).map(function (_ref3) {
+ var _ref4 = slicedToArray(_ref3, 2),
+ i = _ref4[1];
+
+ return i;
+ });
+
+ var stops = [fromDefIndex].concat(toConsumableArray(stopDefIndices), [toDefIndex]);
+
+ return stops.map(function (s, i) {
+ return {
+ stop: stopDefIndices.indexOf(s) >= 0 ? s : null,
+
+ value: _this4.extractInput(_this4.mapDefIndexToPos(s), _this4.mapDefIndexToPos(stops[++i]))
+ };
+ }).filter(function (_ref5) {
+ var stop = _ref5.stop,
+ value = _ref5.value;
+ return stop != null || value;
+ });
+ }
+
+ /** Appends placeholder depending on laziness */
+
+ }, {
+ key: '_appendPlaceholder',
+ value: function _appendPlaceholder(toDefIndex) {
+ var oldValueLength = this.value.length;
+ var maxDefIndex = toDefIndex || this._charDefs.length;
+ for (var di = this.mapPosToDefIndex(this.value.length); di < maxDefIndex; ++di) {
+ var def = this._charDefs[di];
+ if (def.isInput) def.isHollow = true;
+
+ if (!this.lazy || toDefIndex) {
+ this._value += !def.isInput && def.char != null ? def.char : !def.optional ? this.placeholderChar : '';
+ }
+ }
+ return new ChangeDetails({
+ inserted: this.value.slice(oldValueLength)
+ });
+ }
+
+ /**
+ @override
+ */
+
+ }, {
+ key: 'remove',
+ value: function remove() {
+ var from = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
+ var count = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.value.length - from;
+
+ var fromDefIndex = this.mapPosToDefIndex(from);
+ var toDefIndex = this.mapPosToDefIndex(from + count);
+ this._charDefs.slice(fromDefIndex, toDefIndex).forEach(function (d) {
+ return d.reset();
+ });
+
+ return get(MaskedPattern.prototype.__proto__ || Object.getPrototypeOf(MaskedPattern.prototype), 'remove', this).call(this, from, count);
+ }
+
+ /**
+ @override
+ */
+
+ }, {
+ key: 'nearestInputPos',
+ value: function nearestInputPos(cursorPos) {
+ var direction = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : DIRECTION.NONE;
+
+ var step = direction || DIRECTION.RIGHT;
+
+ var initialDefIndex = this.mapPosToDefIndex(cursorPos);
+ var initialDef = this._charDefs[initialDefIndex];
+ var di = initialDefIndex;
+
+ var firstInputIndex = void 0,
+ firstFilledInputIndex = void 0,
+ firstVisibleHollowIndex = void 0,
+ nextdi = void 0;
+
+ // check if chars at right is acceptable for LEFT and NONE directions
+ if (direction !== DIRECTION.RIGHT && (initialDef && initialDef.isInput ||
+ // in none direction latest position is acceptable also
+ direction === DIRECTION.NONE && cursorPos === this.value.length)) {
+ firstInputIndex = initialDefIndex;
+ if (initialDef && !initialDef.isHollow) firstFilledInputIndex = initialDefIndex;
+ }
+
+ if (firstFilledInputIndex == null && direction == DIRECTION.LEFT || firstInputIndex == null) {
+ // search forward
+ for (nextdi = indexInDirection(di, step); 0 <= nextdi && nextdi < this._charDefs.length; di += step, nextdi += step) {
+ var nextDef = this._charDefs[nextdi];
+ if (firstInputIndex == null && nextDef.isInput) {
+ firstInputIndex = di;
+ if (direction === DIRECTION.NONE) break;
+ }
+ if (firstVisibleHollowIndex == null && nextDef.isHollow && !nextDef.isHiddenHollow) firstVisibleHollowIndex = di;
+ if (nextDef.isInput && !nextDef.isHollow) {
+ firstFilledInputIndex = di;
+ break;
+ }
+ }
+ }
+
+ // for lazy if has aligned left inside fixed and has came to the start - use start position
+ if (direction === DIRECTION.LEFT && di === 0 && this.lazy && !this.extractInput() && (!initialDef || !initialDef.isInput)) firstInputIndex = 0;
+
+ if (direction === DIRECTION.LEFT || firstInputIndex == null) {
+ // search backward
+ step = -step;
+ var overflow = false;
+
+ // find hollows only before initial pos
+ for (nextdi = indexInDirection(di, step); 0 <= nextdi && nextdi < this._charDefs.length; di += step, nextdi += step) {
+ var _nextDef = this._charDefs[nextdi];
+ if (_nextDef.isInput) {
+ firstInputIndex = di;
+ if (_nextDef.isHollow && !_nextDef.isHiddenHollow) break;
+ }
+
+ // if hollow not found before start position - set `overflow`
+ // and try to find just any input
+ if (di === initialDefIndex) overflow = true;
+
+ // first input found
+ if (overflow && firstInputIndex != null) break;
+ }
+
+ // process overflow
+ overflow = overflow || nextdi >= this._charDefs.length;
+ if (overflow && firstInputIndex != null) di = firstInputIndex;
+ } else if (firstFilledInputIndex == null) {
+ // adjust index if delete at right and filled input not found at right
+ di = firstVisibleHollowIndex != null ? firstVisibleHollowIndex : firstInputIndex;
+ }
+
+ return this.mapDefIndexToPos(di);
+ }
+
+ /** Get group by name */
+
+ }, {
+ key: 'group',
+ value: function group(name) {
+ return this.groupsByName(name)[0];
+ }
+
+ /** Get all groups by name */
+
+ }, {
+ key: 'groupsByName',
+ value: function groupsByName(name) {
+ return this._groupDefs.filter(function (g$$1) {
+ return g$$1.name === name;
+ });
+ }
+ }, {
+ key: 'isComplete',
+ get: function get$$1() {
+ var _this5 = this;
+
+ return !this._charDefs.some(function (d, i) {
+ return d.isInput && !d.optional && (d.isHollow || !_this5.extractInput(i, i + 1));
+ });
+ }
+ }, {
+ key: 'unmaskedValue',
+ get: function get$$1() {
+ var str = this.value;
+ var unmasked = '';
+
+ for (var ci = 0, di = 0; ci < str.length && di < this._charDefs.length; ++di) {
+ var ch = str[ci];
+ var def = this._charDefs[di];
+
+ if (def.isHiddenHollow) continue;
+ if (def.unmasking && !def.isHollow) unmasked += ch;
+ ++ci;
+ }
+
+ return unmasked;
+ },
+ set: function set$$1(unmaskedValue) {
+ set(MaskedPattern.prototype.__proto__ || Object.getPrototypeOf(MaskedPattern.prototype), 'unmaskedValue', unmaskedValue, this);
+ }
+ }]);
+ return MaskedPattern;
+ }(Masked);
+
+ MaskedPattern.DEFAULTS = {
+ lazy: true,
+ placeholderChar: '_'
+ };
+ MaskedPattern.STOP_CHAR = '`';
+ MaskedPattern.ESCAPE_CHAR = '\\';
+ MaskedPattern.Definition = PatternDefinition;
+ MaskedPattern.Group = PatternGroup;
+
+ /** Date mask */
+
+ var MaskedDate = function (_MaskedPattern) {
+ inherits(MaskedDate, _MaskedPattern);
+
+ /**
+ @param {Object} opts
+ */
+
+ /** Start date */
+
+ /** Format Date to string */
+ function MaskedDate(opts) {
+ classCallCheck(this, MaskedDate);
+ return possibleConstructorReturn(this, (MaskedDate.__proto__ || Object.getPrototypeOf(MaskedDate)).call(this, _extends({}, MaskedDate.DEFAULTS, opts)));
+ }
+
+ /**
+ @override
+ */
+
+ /** End date */
+
+ /** Pattern mask for date according to {@link MaskedDate#format} */
+
+
+ /** Parse string to Date */
+
+
+ createClass(MaskedDate, [{
+ key: '_update',
+ value: function _update(opts) {
+ if (opts.mask === Date) delete opts.mask;
+ if (opts.pattern) {
+ opts.mask = opts.pattern;
+ delete opts.pattern;
+ }
+
+ var groups = opts.groups;
+ opts.groups = _extends({}, MaskedDate.GET_DEFAULT_GROUPS());
+ // adjust year group
+ if (opts.min) opts.groups.Y.from = opts.min.getFullYear();
+ if (opts.max) opts.groups.Y.to = opts.max.getFullYear();
+ _extends(opts.groups, groups);
+
+ get(MaskedDate.prototype.__proto__ || Object.getPrototypeOf(MaskedDate.prototype), '_update', this).call(this, opts);
+ }
+
+ /**
+ @override
+ */
+
+ }, {
+ key: 'doValidate',
+ value: function doValidate() {
+ var _babelHelpers$get;
+
+ for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
+ args[_key] = arguments[_key];
+ }
+
+ var valid = (_babelHelpers$get = get(MaskedDate.prototype.__proto__ || Object.getPrototypeOf(MaskedDate.prototype), 'doValidate', this)).call.apply(_babelHelpers$get, [this].concat(toConsumableArray(args)));
+ var date = this.date;
+
+ return valid && (!this.isComplete || this.isDateExist(this.value) && date && (this.min == null || this.min <= date) && (this.max == null || date <= this.max));
+ }
+
+ /** Checks if date is exists */
+
+ }, {
+ key: 'isDateExist',
+ value: function isDateExist(str) {
+ return this.format(this.parse(str)) === str;
+ }
+
+ /** Parsed Date */
+
+ }, {
+ key: 'date',
+ get: function get$$1() {
+ return this.isComplete ? this.parse(this.value) : null;
+ },
+ set: function set$$1(date) {
+ this.value = this.format(date);
+ }
+
+ /**
+ @override
+ */
+
+ }, {
+ key: 'typedValue',
+ get: function get$$1() {
+ return this.date;
+ },
+ set: function set$$1(value) {
+ this.date = value;
+ }
+ }]);
+ return MaskedDate;
+ }(MaskedPattern);
+
+ MaskedDate.DEFAULTS = {
+ pattern: 'd{.}`m{.}`Y',
+ format: function format(date) {
+ var day = String(date.getDate()).padStart(2, '0');
+ var month = String(date.getMonth() + 1).padStart(2, '0');
+ var year = date.getFullYear();
+
+ return [day, month, year].join('.');
+ },
+ parse: function parse(str) {
+ var _str$split = str.split('.'),
+ _str$split2 = slicedToArray(_str$split, 3),
+ day = _str$split2[0],
+ month = _str$split2[1],
+ year = _str$split2[2];
+
+ return new Date(year, month - 1, day);
+ }
+ };
+ MaskedDate.GET_DEFAULT_GROUPS = function () {
+ return {
+ d: new PatternGroup.Range([1, 31]),
+ m: new PatternGroup.Range([1, 12]),
+ Y: new PatternGroup.Range([1900, 9999])
+ };
+ };
+
+ /**
+ Generic element API to use with mask
+ @interface
+ */
+ var MaskElement = function () {
+ function MaskElement() {
+ classCallCheck(this, MaskElement);
+ }
+
+ createClass(MaskElement, [{
+ key: 'select',
+ value: function select(start, end) {
+ if (start == null || end == null || start === this.selectionStart && end === this.selectionEnd) return;
+
+ try {
+ this._unsafeSelect(start, end);
+ } catch (e) {}
+ }
+ }, {
+ key: '_unsafeSelect',
+ value: function _unsafeSelect(start, end) {}
+ }, {
+ key: 'isActive',
+ value: function isActive() {
+ return false;
+ }
+ }, {
+ key: 'bindEvents',
+ value: function bindEvents(handlers) {}
+ }, {
+ key: 'unbindEvents',
+ value: function unbindEvents() {}
+ }, {
+ key: 'selectionStart',
+ get: function get$$1() {
+ var start = void 0;
+ try {
+ start = this._unsafeSelectionStart;
+ } catch (e) {}
+
+ return start != null ? start : this.value.length;
+ }
+ }, {
+ key: 'selectionEnd',
+ get: function get$$1() {
+ var end = void 0;
+ try {
+ end = this._unsafeSelectionEnd;
+ } catch (e) {}
+
+ return end != null ? end : this.value.length;
+ }
+ }]);
+ return MaskElement;
+ }();
+
+ var HTMLMaskElement = function (_MaskElement) {
+ inherits(HTMLMaskElement, _MaskElement);
+
+ function HTMLMaskElement(input) {
+ classCallCheck(this, HTMLMaskElement);
+
+ var _this = possibleConstructorReturn(this, (HTMLMaskElement.__proto__ || Object.getPrototypeOf(HTMLMaskElement)).call(this));
+
+ _this.input = input;
+ _this._handlers = {};
+ return _this;
+ }
+
+ createClass(HTMLMaskElement, [{
+ key: 'isActive',
+ value: function isActive() {
+ return this.input === document.activeElement;
+ }
+ }, {
+ key: '_unsafeSelect',
+ value: function _unsafeSelect(start, end) {
+ this.input.setSelectionRange(start, end);
+ }
+ }, {
+ key: 'bindEvents',
+ value: function bindEvents(handlers) {
+ var _this2 = this;
+
+ Object.keys(handlers).forEach(function (event) {
+ return _this2._toggleEventHandler(HTMLMaskElement.EVENTS_MAP[event], handlers[event]);
+ });
+ }
+ }, {
+ key: 'unbindEvents',
+ value: function unbindEvents() {
+ var _this3 = this;
+
+ Object.keys(this._handlers).forEach(function (event) {
+ return _this3._toggleEventHandler(event);
+ });
+ }
+ }, {
+ key: '_toggleEventHandler',
+ value: function _toggleEventHandler(event, handler) {
+ if (this._handlers[event]) {
+ this.input.removeEventListener(event, this._handlers[event]);
+ delete this._handlers[event];
+ }
+
+ if (handler) {
+ this.input.addEventListener(event, handler);
+ this._handlers[event] = handler;
+ }
+ }
+ }, {
+ key: '_unsafeSelectionStart',
+ get: function get$$1() {
+ return this.input.selectionStart;
+ }
+ }, {
+ key: '_unsafeSelectionEnd',
+ get: function get$$1() {
+ return this.input.selectionEnd;
+ }
+ }, {
+ key: 'value',
+ get: function get$$1() {
+ return this.input.value;
+ },
+ set: function set$$1(value) {
+ this.input.value = value;
+ }
+ }]);
+ return HTMLMaskElement;
+ }(MaskElement);
+
+ HTMLMaskElement.EVENTS_MAP = {
+ selectionChange: 'keydown',
+ input: 'input',
+ drop: 'drop',
+ click: 'click',
+ focus: 'focus',
+ commit: 'change'
+ };
+
+ /** Listens to element events and controls changes between element and {@link Masked} */
+
+ var InputMask = function () {
+
+ /**
+ @param {MaskElement|HTMLInputElement|HTMLTextAreaElement} el
+ @param {Object} opts
+ */
+
+ /**
+ View element
+ @readonly
+ */
+ function InputMask(el, opts) {
+ classCallCheck(this, InputMask);
+
+ this.el = el instanceof MaskElement ? el : new HTMLMaskElement(el);
+ this.masked = createMask(opts);
+
+ this._listeners = {};
+ this._value = '';
+ this._unmaskedValue = '';
+
+ this._saveSelection = this._saveSelection.bind(this);
+ this._onInput = this._onInput.bind(this);
+ this._onChange = this._onChange.bind(this);
+ this._onDrop = this._onDrop.bind(this);
+ this.alignCursor = this.alignCursor.bind(this);
+ this.alignCursorFriendly = this.alignCursorFriendly.bind(this);
+
+ this._bindEvents();
+
+ // refresh
+ this.updateValue();
+ this._onChange();
+ }
+
+ /** Read or update mask */
+
+
+ /**
+ Internal {@link Masked} model
+ @readonly
+ */
+
+
+ createClass(InputMask, [{
+ key: '_bindEvents',
+
+
+ /**
+ Starts listening to element events
+ @protected
+ */
+ value: function _bindEvents() {
+ this.el.bindEvents({
+ selectionChange: this._saveSelection,
+ input: this._onInput,
+ drop: this._onDrop,
+ click: this.alignCursorFriendly,
+ focus: this.alignCursorFriendly,
+ commit: this._onChange
+ });
+ }
+
+ /**
+ Stops listening to element events
+ @protected
+ */
+
+ }, {
+ key: '_unbindEvents',
+ value: function _unbindEvents() {
+ this.el.unbindEvents();
+ }
+
+ /**
+ Fires custom event
+ @protected
+ */
+
+ }, {
+ key: '_fireEvent',
+ value: function _fireEvent(ev) {
+ var listeners = this._listeners[ev];
+ if (!listeners) return;
+
+ listeners.forEach(function (l) {
+ return l();
+ });
+ }
+
+ /**
+ Current selection start
+ @readonly
+ */
+
+ }, {
+ key: '_saveSelection',
+
+
+ /**
+ Stores current selection
+ @protected
+ */
+ value: function _saveSelection() /* ev */{
+ if (this.value !== this.el.value) {
+ console.warn('Uncontrolled input change, refresh mask manually!'); // eslint-disable-line no-console
+ }
+ this._selection = {
+ start: this.selectionStart,
+ end: this.cursorPos
+ };
+ }
+
+ /** Syncronizes model value from view */
+
+ }, {
+ key: 'updateValue',
+ value: function updateValue() {
+ this.masked.value = this.el.value;
+ }
+
+ /** Syncronizes view from model value, fires change events */
+
+ }, {
+ key: 'updateControl',
+ value: function updateControl() {
+ var newUnmaskedValue = this.masked.unmaskedValue;
+ var newValue = this.masked.value;
+ var isChanged = this.unmaskedValue !== newUnmaskedValue || this.value !== newValue;
+
+ this._unmaskedValue = newUnmaskedValue;
+ this._value = newValue;
+
+ if (this.el.value !== newValue) this.el.value = newValue;
+ if (isChanged) this._fireChangeEvents();
+ }
+
+ /** Updates options with deep equal check, recreates @{link Masked} model if mask type changes */
+
+ }, {
+ key: 'updateOptions',
+ value: function updateOptions(opts) {
+ opts = _extends({}, opts);
+
+ this.mask = opts.mask;
+ delete opts.mask;
+
+ // check if changed
+ if (objectIncludes(this.masked, opts)) return;
+
+ this.masked.updateOptions(opts);
+ this.updateControl();
+ }
+
+ /** Updates cursor */
+
+ }, {
+ key: 'updateCursor',
+ value: function updateCursor(cursorPos) {
+ if (cursorPos == null) return;
+ this.cursorPos = cursorPos;
+
+ // also queue change cursor for mobile browsers
+ this._delayUpdateCursor(cursorPos);
+ }
+
+ /**
+ Delays cursor update to support mobile browsers
+ @private
+ */
+
+ }, {
+ key: '_delayUpdateCursor',
+ value: function _delayUpdateCursor(cursorPos) {
+ var _this = this;
+
+ this._abortUpdateCursor();
+ this._changingCursorPos = cursorPos;
+ this._cursorChanging = setTimeout(function () {
+ if (!_this.el) return; // if was destroyed
+ _this.cursorPos = _this._changingCursorPos;
+ _this._abortUpdateCursor();
+ }, 10);
+ }
+
+ /**
+ Fires custom events
+ @protected
+ */
+
+ }, {
+ key: '_fireChangeEvents',
+ value: function _fireChangeEvents() {
+ this._fireEvent('accept');
+ if (this.masked.isComplete) this._fireEvent('complete');
+ }
+
+ /**
+ Aborts delayed cursor update
+ @private
+ */
+
+ }, {
+ key: '_abortUpdateCursor',
+ value: function _abortUpdateCursor() {
+ if (this._cursorChanging) {
+ clearTimeout(this._cursorChanging);
+ delete this._cursorChanging;
+ }
+ }
+
+ /** Aligns cursor to nearest available position */
+
+ }, {
+ key: 'alignCursor',
+ value: function alignCursor() {
+ this.cursorPos = this.masked.nearestInputPos(this.cursorPos, DIRECTION.LEFT);
+ }
+
+ /** Aligns cursor only if selection is empty */
+
+ }, {
+ key: 'alignCursorFriendly',
+ value: function alignCursorFriendly() {
+ if (this.selectionStart !== this.cursorPos) return;
+ this.alignCursor();
+ }
+
+ /** Adds listener on custom event */
+
+ }, {
+ key: 'on',
+ value: function on(ev, handler) {
+ if (!this._listeners[ev]) this._listeners[ev] = [];
+ this._listeners[ev].push(handler);
+ return this;
+ }
+
+ /** Removes custom event listener */
+
+ }, {
+ key: 'off',
+ value: function off(ev, handler) {
+ if (!this._listeners[ev]) return;
+ if (!handler) {
+ delete this._listeners[ev];
+ return;
+ }
+ var hIndex = this._listeners[ev].indexOf(handler);
+ if (hIndex >= 0) this._listeners[ev].splice(hIndex, 1);
+ return this;
+ }
+
+ /** Handles view input event */
+
+ }, {
+ key: '_onInput',
+ value: function _onInput() {
+ this._abortUpdateCursor();
+
+ // fix strange IE behavior
+ if (!this._selection) return this.updateValue();
+
+ var details = new ActionDetails(
+ // new state
+ this.el.value, this.cursorPos,
+ // old state
+ this.value, this._selection);
+
+ var offset = this.masked.splice(details.startChangePos, details.removed.length, details.inserted, details.removeDirection).offset;
+
+ var cursorPos = this.masked.nearestInputPos(details.startChangePos + offset, details.removeDirection);
+
+ this.updateControl();
+ this.updateCursor(cursorPos);
+ }
+
+ /** Handles view change event and commits model value */
+
+ }, {
+ key: '_onChange',
+ value: function _onChange() {
+ if (this.value !== this.el.value) {
+ this.updateValue();
+ }
+ this.masked.doCommit();
+ this.updateControl();
+ }
+
+ /** Handles view drop event, prevents by default */
+
+ }, {
+ key: '_onDrop',
+ value: function _onDrop(ev) {
+ ev.preventDefault();
+ ev.stopPropagation();
+ }
+
+ /** Unbind view events and removes element reference */
+
+ }, {
+ key: 'destroy',
+ value: function destroy() {
+ this._unbindEvents();
+ // $FlowFixMe why not do so?
+ this._listeners.length = 0;
+ delete this.el;
+ }
+ }, {
+ key: 'mask',
+ get: function get$$1() {
+ return this.masked.mask;
+ },
+ set: function set$$1(mask) {
+ if (mask == null || mask === this.masked.mask || mask === Date && this.masked instanceof MaskedDate) return;
+
+ if (this.masked.constructor === maskedClass(mask)) {
+ this.masked.mask = mask;
+ return;
+ }
+
+ var masked = createMask({ mask: mask });
+ masked.unmaskedValue = this.masked.unmaskedValue;
+ this.masked = masked;
+ }
+
+ /** Raw value */
+
+ }, {
+ key: 'value',
+ get: function get$$1() {
+ return this._value;
+ },
+ set: function set$$1(str) {
+ this.masked.value = str;
+ this.updateControl();
+ this.alignCursor();
+ }
+
+ /** Unmasked value */
+
+ }, {
+ key: 'unmaskedValue',
+ get: function get$$1() {
+ return this._unmaskedValue;
+ },
+ set: function set$$1(str) {
+ this.masked.unmaskedValue = str;
+ this.updateControl();
+ this.alignCursor();
+ }
+
+ /** Typed unmasked value */
+
+ }, {
+ key: 'typedValue',
+ get: function get$$1() {
+ return this.masked.typedValue;
+ },
+ set: function set$$1(val) {
+ this.masked.typedValue = val;
+ this.updateControl();
+ this.alignCursor();
+ }
+ }, {
+ key: 'selectionStart',
+ get: function get$$1() {
+ return this._cursorChanging ? this._changingCursorPos : this.el.selectionStart;
+ }
+
+ /** Current cursor position */
+
+ }, {
+ key: 'cursorPos',
+ get: function get$$1() {
+ return this._cursorChanging ? this._changingCursorPos : this.el.selectionEnd;
+ },
+ set: function set$$1(pos) {
+ if (!this.el.isActive) return;
+
+ this.el.select(pos, pos);
+ this._saveSelection();
+ }
+ }]);
+ return InputMask;
+ }();
+
+ /**
+ Number mask
+ @param {Object} opts
+ @param {string} opts.radix - Single char
+ @param {string} opts.thousandsSeparator - Single char
+ @param {Array} opts.mapToRadix - Array of single chars
+ @param {number} opts.min
+ @param {number} opts.max
+ @param {number} opts.scale - Digits after point
+ @param {boolean} opts.signed - Allow negative
+ @param {boolean} opts.normalizeZeros - Flag to remove leading and trailing zeros in the end of editing
+ @param {boolean} opts.padFractionalZeros - Flag to pad trailing zeros after point in the end of editing
+ */
+ var MaskedNumber = function (_Masked) {
+ inherits(MaskedNumber, _Masked);
+
+ /** Flag to remove leading and trailing zeros in the end of editing */
+
+ /** Digits after point */
+
+ /** */
+
+ /** Single char */
+ function MaskedNumber(opts) {
+ classCallCheck(this, MaskedNumber);
+ return possibleConstructorReturn(this, (MaskedNumber.__proto__ || Object.getPrototypeOf(MaskedNumber)).call(this, _extends({}, MaskedNumber.DEFAULTS, opts)));
+ }
+
+ /**
+ @override
+ */
+
+ /** Flag to pad trailing zeros after point in the end of editing */
+
+ /** */
+
+ /** */
+
+ /** Array of single chars */
+
+
+ /** Single char */
+
+
+ createClass(MaskedNumber, [{
+ key: '_update',
+ value: function _update(opts) {
+ get(MaskedNumber.prototype.__proto__ || Object.getPrototypeOf(MaskedNumber.prototype), '_update', this).call(this, opts);
+ this._updateRegExps();
+ }
+
+ /** */
+
+ }, {
+ key: '_updateRegExps',
+ value: function _updateRegExps() {
+ // use different regexp to process user input (more strict, input suffix) and tail shifting
+ var start = '^';
+
+ var midInput = '';
+ var mid = '';
+ if (this.allowNegative) {
+ midInput += '([+|\\-]?|([+|\\-]?(0|([1-9]+\\d*))))';
+ mid += '[+|\\-]?';
+ } else {
+ midInput += '(0|([1-9]+\\d*))';
+ }
+ mid += '\\d*';
+
+ var end = (this.scale ? '(' + this.radix + '\\d{0,' + this.scale + '})?' : '') + '$';
+
+ this._numberRegExpInput = new RegExp(start + midInput + end);
+ this._numberRegExp = new RegExp(start + mid + end);
+ this._mapToRadixRegExp = new RegExp('[' + this.mapToRadix.map(escapeRegExp).join('') + ']', 'g');
+ this._thousandsSeparatorRegExp = new RegExp(escapeRegExp(this.thousandsSeparator), 'g');
+ }
+
+ /**
+ @override
+ */
+
+ }, {
+ key: '_extractTail',
+ value: function _extractTail() {
+ var fromPos = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
+ var toPos = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.value.length;
+
+ var tail = get(MaskedNumber.prototype.__proto__ || Object.getPrototypeOf(MaskedNumber.prototype), '_extractTail', this).call(this, fromPos, toPos);
+
+ return _extends({}, tail, {
+ value: this._removeThousandsSeparators(tail.value)
+ });
+ }
+
+ /** */
+
+ }, {
+ key: '_removeThousandsSeparators',
+ value: function _removeThousandsSeparators(value) {
+ return value.replace(this._thousandsSeparatorRegExp, '');
+ }
+
+ /** */
+
+ }, {
+ key: '_insertThousandsSeparators',
+ value: function _insertThousandsSeparators(value) {
+ // https://stackoverflow.com/questions/2901102/how-to-print-a-number-with-commas-as-thousands-separators-in-javascript
+ var parts = value.split(this.radix);
+ parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, this.thousandsSeparator);
+ return parts.join(this.radix);
+ }
+
+ /**
+ @override
+ */
+
+ }, {
+ key: 'doPrepare',
+ value: function doPrepare(str) {
+ var _babelHelpers$get;
+
+ for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
+ args[_key - 1] = arguments[_key];
+ }
+
+ return (_babelHelpers$get = get(MaskedNumber.prototype.__proto__ || Object.getPrototypeOf(MaskedNumber.prototype), 'doPrepare', this)).call.apply(_babelHelpers$get, [this, this._removeThousandsSeparators(str.replace(this._mapToRadixRegExp, this.radix))].concat(toConsumableArray(args)));
+ }
+
+ /**
+ @override
+ */
+
+ }, {
+ key: 'appendWithTail',
+ value: function appendWithTail() {
+ var _babelHelpers$get2;
+
+ var previousValue = this.value;
+ this._value = this._removeThousandsSeparators(this.value);
+ var startChangePos = this.value.length;
+
+ for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
+ args[_key2] = arguments[_key2];
+ }
+
+ var appendDetails = (_babelHelpers$get2 = get(MaskedNumber.prototype.__proto__ || Object.getPrototypeOf(MaskedNumber.prototype), 'appendWithTail', this)).call.apply(_babelHelpers$get2, [this].concat(toConsumableArray(args)));
+ this._value = this._insertThousandsSeparators(this.value);
+
+ // calculate offsets after insert separators
+ var beforeTailPos = startChangePos + appendDetails.inserted.length;
+ for (var pos = 0; pos <= beforeTailPos; ++pos) {
+ if (this.value[pos] === this.thousandsSeparator) {
+ if (pos < startChangePos ||
+ // check high bound
+ // if separator is still there - consider it also
+ pos === startChangePos && previousValue[pos] === this.thousandsSeparator) {
+ ++startChangePos;
+ }
+ if (pos < beforeTailPos) ++beforeTailPos;
+ }
+ }
+
+ // adjust details with separators
+ appendDetails.rawInserted = appendDetails.inserted;
+ appendDetails.inserted = this.value.slice(startChangePos, beforeTailPos);
+ appendDetails.shift += startChangePos - previousValue.length;
+
+ return appendDetails;
+ }
+
+ /**
+ @override
+ */
+
+ }, {
+ key: 'nearestInputPos',
+ value: function nearestInputPos(cursorPos, direction) {
+ if (!direction) return cursorPos;
+
+ var nextPos = indexInDirection(cursorPos, direction);
+ if (this.value[nextPos] === this.thousandsSeparator) cursorPos += direction;
+ return cursorPos;
+ }
+
+ /**
+ @override
+ */
+
+ }, {
+ key: 'doValidate',
+ value: function doValidate(flags) {
+ var regexp = flags.input ? this._numberRegExpInput : this._numberRegExp;
+
+ // validate as string
+ var valid = regexp.test(this._removeThousandsSeparators(this.value));
+
+ if (valid) {
+ // validate as number
+ var number = this.number;
+ valid = valid && !isNaN(number) && (
+ // check min bound for negative values
+ this.min == null || this.min >= 0 || this.min <= this.number) && (
+ // check max bound for positive values
+ this.max == null || this.max <= 0 || this.number <= this.max);
+ }
+
+ return valid && get(MaskedNumber.prototype.__proto__ || Object.getPrototypeOf(MaskedNumber.prototype), 'doValidate', this).call(this, flags);
+ }
+
+ /**
+ @override
+ */
+
+ }, {
+ key: 'doCommit',
+ value: function doCommit() {
+ var number = this.number;
+ var validnum = number;
+
+ // check bounds
+ if (this.min != null) validnum = Math.max(validnum, this.min);
+ if (this.max != null) validnum = Math.min(validnum, this.max);
+
+ if (validnum !== number) this.unmaskedValue = String(validnum);
+
+ var formatted = this.value;
+
+ if (this.normalizeZeros) formatted = this._normalizeZeros(formatted);
+ if (this.padFractionalZeros) formatted = this._padFractionalZeros(formatted);
+
+ this._value = this._insertThousandsSeparators(formatted);
+ get(MaskedNumber.prototype.__proto__ || Object.getPrototypeOf(MaskedNumber.prototype), 'doCommit', this).call(this);
+ }
+
+ /** */
+
+ }, {
+ key: '_normalizeZeros',
+ value: function _normalizeZeros(value) {
+ var parts = this._removeThousandsSeparators(value).split(this.radix);
+
+ // remove leading zeros
+ parts[0] = parts[0].replace(/^(\D*)(0*)(\d*)/, function (match, sign, zeros, num) {
+ return sign + num;
+ });
+ // add leading zero
+ if (value.length && !/\d$/.test(parts[0])) parts[0] = parts[0] + '0';
+
+ if (parts.length > 1) {
+ parts[1] = parts[1].replace(/0*$/, ''); // remove trailing zeros
+ if (!parts[1].length) parts.length = 1; // remove fractional
+ }
+
+ return this._insertThousandsSeparators(parts.join(this.radix));
+ }
+
+ /** */
+
+ }, {
+ key: '_padFractionalZeros',
+ value: function _padFractionalZeros(value) {
+ if (!value) return value;
+
+ var parts = value.split(this.radix);
+ if (parts.length < 2) parts.push('');
+ parts[1] = parts[1].padEnd(this.scale, '0');
+ return parts.join(this.radix);
+ }
+
+ /**
+ @override
+ */
+
+ }, {
+ key: 'unmaskedValue',
+ get: function get$$1() {
+ return this._removeThousandsSeparators(this._normalizeZeros(this.value)).replace(this.radix, '.');
+ },
+ set: function set$$1(unmaskedValue) {
+ set(MaskedNumber.prototype.__proto__ || Object.getPrototypeOf(MaskedNumber.prototype), 'unmaskedValue', unmaskedValue.replace('.', this.radix), this);
+ }
+
+ /** Parsed Number */
+
+ }, {
+ key: 'number',
+ get: function get$$1() {
+ return Number(this.unmaskedValue);
+ },
+ set: function set$$1(number) {
+ this.unmaskedValue = String(number);
+ }
+
+ /**
+ @override
+ */
+
+ }, {
+ key: 'typedValue',
+ get: function get$$1() {
+ return this.number;
+ },
+ set: function set$$1(value) {
+ this.number = value;
+ }
+
+ /**
+ Is negative allowed
+ @readonly
+ */
+
+ }, {
+ key: 'allowNegative',
+ get: function get$$1() {
+ return this.signed || this.min != null && this.min < 0 || this.max != null && this.max < 0;
+ }
+ }]);
+ return MaskedNumber;
+ }(Masked);
+
+ MaskedNumber.DEFAULTS = {
+ radix: ',',
+ thousandsSeparator: '',
+ mapToRadix: ['.'],
+ scale: 2,
+ signed: false,
+ normalizeZeros: true,
+ padFractionalZeros: false
+ };
+
+ /** Masking by RegExp */
+
+ var MaskedRegExp = function (_Masked) {
+ inherits(MaskedRegExp, _Masked);
+
+ function MaskedRegExp() {
+ classCallCheck(this, MaskedRegExp);
+ return possibleConstructorReturn(this, (MaskedRegExp.__proto__ || Object.getPrototypeOf(MaskedRegExp)).apply(this, arguments));
+ }
+
+ createClass(MaskedRegExp, [{
+ key: '_update',
+
+ /**
+ @override
+ @param {Object} opts
+ */
+ value: function _update(opts) {
+ opts.validate = function (value) {
+ return value.search(opts.mask) >= 0;
+ };
+ get(MaskedRegExp.prototype.__proto__ || Object.getPrototypeOf(MaskedRegExp.prototype), '_update', this).call(this, opts);
+ }
+ }]);
+ return MaskedRegExp;
+ }(Masked);
+
+ /** Masking by custom Function */
+
+ var MaskedFunction = function (_Masked) {
+ inherits(MaskedFunction, _Masked);
+
+ function MaskedFunction() {
+ classCallCheck(this, MaskedFunction);
+ return possibleConstructorReturn(this, (MaskedFunction.__proto__ || Object.getPrototypeOf(MaskedFunction)).apply(this, arguments));
+ }
+
+ createClass(MaskedFunction, [{
+ key: '_update',
+
+ /**
+ @override
+ @param {Object} opts
+ */
+ value: function _update(opts) {
+ opts.validate = opts.mask;
+ get(MaskedFunction.prototype.__proto__ || Object.getPrototypeOf(MaskedFunction.prototype), '_update', this).call(this, opts);
+ }
+ }]);
+ return MaskedFunction;
+ }(Masked);
+
+ /** Dynamic mask for choosing apropriate mask in run-time */
+ var MaskedDynamic = function (_Masked) {
+ inherits(MaskedDynamic, _Masked);
+
+ /**
+ @param {Object} opts
+ */
+
+ /** Compliled {@link Masked} options */
+ function MaskedDynamic(opts) {
+ classCallCheck(this, MaskedDynamic);
+
+ var _this = possibleConstructorReturn(this, (MaskedDynamic.__proto__ || Object.getPrototypeOf(MaskedDynamic)).call(this, _extends({}, MaskedDynamic.DEFAULTS, opts)));
+
+ _this.currentMask = null;
+ return _this;
+ }
+
+ /**
+ @override
+ */
+
+ /** Chooses {@link Masked} depending on input value */
+
+ /** Currently chosen mask */
+
+
+ createClass(MaskedDynamic, [{
+ key: '_update',
+ value: function _update(opts) {
+ get(MaskedDynamic.prototype.__proto__ || Object.getPrototypeOf(MaskedDynamic.prototype), '_update', this).call(this, opts);
+ // mask could be totally dynamic with only `dispatch` option
+ this.compiledMasks = Array.isArray(opts.mask) ? opts.mask.map(function (m) {
+ return createMask(m);
+ }) : [];
+ }
+
+ /**
+ @override
+ */
+
+ }, {
+ key: '_append',
+ value: function _append(str) {
+ for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
+ args[_key - 1] = arguments[_key];
+ }
+
+ str = this.doPrepare.apply(this, [str].concat(toConsumableArray(args)));
+
+ var details = this._applyDispatch.apply(this, [str].concat(toConsumableArray(args)));
+
+ if (this.currentMask) {
+ var _currentMask;
+
+ details.aggregate((_currentMask = this.currentMask)._append.apply(_currentMask, [str].concat(toConsumableArray(args))));
+ }
+
+ return details;
+ }
+ }, {
+ key: '_applyDispatch',
+ value: function _applyDispatch() {
+ var appended = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
+
+ var oldValueLength = this.value.length;
+ var inputValue = this.rawInputValue;
+ var oldMask = this.currentMask;
+ var details = new ChangeDetails();
+
+ // dispatch SHOULD NOT modify mask
+
+ for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
+ args[_key2 - 1] = arguments[_key2];
+ }
+
+ this.currentMask = this.doDispatch.apply(this, [appended].concat(toConsumableArray(args)));
+
+ // restore state after dispatch
+ if (this.currentMask && this.currentMask !== oldMask) {
+ // if mask changed reapply input
+ this.currentMask.reset();
+ // $FlowFixMe - it's ok, we don't change current mask
+ this.currentMask._append(inputValue, { raw: true });
+ details.shift = this.value.length - oldValueLength;
+ }
+
+ return details;
+ }
+
+ /**
+ @override
+ */
+
+ }, {
+ key: 'doDispatch',
+ value: function doDispatch(appended) {
+ var flags = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
+
+ return this.dispatch(appended, this, flags);
+ }
+
+ /**
+ @override
+ */
+
+ }, {
+ key: 'clone',
+ value: function clone() {
+ var m = new MaskedDynamic(this);
+ m._value = this.value;
+
+ // try to keep reference to compiled masks
+ var currentMaskIndex = this.compiledMasks.indexOf(this.currentMask);
+ if (this.currentMask) {
+ m.currentMask = currentMaskIndex >= 0 ? m.compiledMasks[currentMaskIndex].assign(this.currentMask) : this.currentMask.clone();
+ }
+
+ return m;
+ }
+
+ /**
+ @override
+ */
+
+ }, {
+ key: 'reset',
+ value: function reset() {
+ if (this.currentMask) this.currentMask.reset();
+ this.compiledMasks.forEach(function (cm) {
+ return cm.reset();
+ });
+ }
+
+ /**
+ @override
+ */
+
+ }, {
+ key: 'remove',
+
+
+ /**
+ @override
+ */
+ value: function remove() {
+ var details = new ChangeDetails();
+ if (this.currentMask) {
+ var _currentMask2;
+
+ details.aggregate((_currentMask2 = this.currentMask).remove.apply(_currentMask2, arguments))
+ // update with dispatch
+ .aggregate(this._applyDispatch());
+ }
+
+ return details;
+ }
+
+ /**
+ @override
+ */
+
+ }, {
+ key: 'extractInput',
+ value: function extractInput() {
+ var _currentMask3;
+
+ return this.currentMask ? (_currentMask3 = this.currentMask).extractInput.apply(_currentMask3, arguments) : '';
+ }
+
+ /**
+ @override
+ */
+
+ }, {
+ key: '_extractTail',
+ value: function _extractTail() {
+ var _currentMask4, _babelHelpers$get;
+
+ for (var _len3 = arguments.length, args = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
+ args[_key3] = arguments[_key3];
+ }
+
+ return this.currentMask ? (_currentMask4 = this.currentMask)._extractTail.apply(_currentMask4, toConsumableArray(args)) : (_babelHelpers$get = get(MaskedDynamic.prototype.__proto__ || Object.getPrototypeOf(MaskedDynamic.prototype), '_extractTail', this)).call.apply(_babelHelpers$get, [this].concat(toConsumableArray(args)));
+ }
+
+ /**
+ @override
+ */
+
+ }, {
+ key: '_appendTail',
+ value: function _appendTail(tail) {
+ var details = new ChangeDetails();
+ if (tail) details.aggregate(this._applyDispatch(tail.value));
+
+ return details.aggregate(this.currentMask ? this.currentMask._appendTail(tail) : get(MaskedDynamic.prototype.__proto__ || Object.getPrototypeOf(MaskedDynamic.prototype), '_appendTail', this).call(this, tail));
+ }
+
+ /**
+ @override
+ */
+
+ }, {
+ key: 'doCommit',
+ value: function doCommit() {
+ if (this.currentMask) this.currentMask.doCommit();
+ get(MaskedDynamic.prototype.__proto__ || Object.getPrototypeOf(MaskedDynamic.prototype), 'doCommit', this).call(this);
+ }
+
+ /**
+ @override
+ */
+
+ }, {
+ key: 'nearestInputPos',
+ value: function nearestInputPos() {
+ var _currentMask5, _babelHelpers$get2;
+
+ for (var _len4 = arguments.length, args = Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
+ args[_key4] = arguments[_key4];
+ }
+
+ return this.currentMask ? (_currentMask5 = this.currentMask).nearestInputPos.apply(_currentMask5, toConsumableArray(args)) : (_babelHelpers$get2 = get(MaskedDynamic.prototype.__proto__ || Object.getPrototypeOf(MaskedDynamic.prototype), 'nearestInputPos', this)).call.apply(_babelHelpers$get2, [this].concat(toConsumableArray(args)));
+ }
+ }, {
+ key: 'value',
+ get: function get$$1() {
+ return this.currentMask ? this.currentMask.value : '';
+ },
+ set: function set$$1(value) {
+ set(MaskedDynamic.prototype.__proto__ || Object.getPrototypeOf(MaskedDynamic.prototype), 'value', value, this);
+ }
+
+ /**
+ @override
+ */
+
+ }, {
+ key: 'unmaskedValue',
+ get: function get$$1() {
+ return this.currentMask ? this.currentMask.unmaskedValue : '';
+ },
+ set: function set$$1(unmaskedValue) {
+ set(MaskedDynamic.prototype.__proto__ || Object.getPrototypeOf(MaskedDynamic.prototype), 'unmaskedValue', unmaskedValue, this);
+ }
+
+ /**
+ @override
+ */
+
+ }, {
+ key: 'typedValue',
+ get: function get$$1() {
+ return this.currentMask ? this.currentMask.typedValue : '';
+ },
+ set: function set$$1(value) {
+ var unmaskedValue = String(value);
+ if (this.currentMask) {
+ this.currentMask.typedValue = value;
+ unmaskedValue = this.currentMask.unmaskedValue;
+ }
+ this.unmaskedValue = unmaskedValue;
+ }
+
+ /**
+ @override
+ */
+
+ }, {
+ key: 'isComplete',
+ get: function get$$1() {
+ return !!this.currentMask && this.currentMask.isComplete;
+ }
+ }]);
+ return MaskedDynamic;
+ }(Masked);
+
+
+ MaskedDynamic.DEFAULTS = {
+ dispatch: function dispatch(appended, masked, flags) {
+ if (!masked.compiledMasks.length) return;
+
+ var inputValue = masked.rawInputValue;
+
+ // simulate input
+ var inputs = masked.compiledMasks.map(function (cm, index) {
+ var m = cm.clone();
+ m.rawInputValue = inputValue;
+ m._append(appended, flags);
+
+ return { value: m.rawInputValue.length, index: index };
+ });
+
+ // pop masks with longer values first
+ inputs.sort(function (i1, i2) {
+ return i2.value - i1.value;
+ });
+
+ return masked.compiledMasks[inputs[0].index];
+ }
+ };
+
+ /**
+ * Applies mask on element.
+ * @constructor
+ * @param {HTMLInputElement|HTMLTextAreaElement|MaskElement} el - Element to apply mask
+ * @param {Object} opts - Custom mask options
+ * @return {InputMask}
+ */
+ function VindiCCMask(el) {
+ var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
+
+ // currently available only for input-like elements
+ return new InputMask(el, opts);
+ }
+
+ /** {@link InputMask} */
+ VindiCCMask.InputMask = InputMask;
+
+ /** {@link Masked} */
+ VindiCCMask.Masked = Masked;
+ /** {@link MaskedPattern} */
+ VindiCCMask.MaskedPattern = MaskedPattern;
+ /** {@link MaskedNumber} */
+ VindiCCMask.MaskedNumber = MaskedNumber;
+ /** {@link MaskedDate} */
+ VindiCCMask.MaskedDate = MaskedDate;
+ /** {@link MaskedRegExp} */
+ VindiCCMask.MaskedRegExp = MaskedRegExp;
+ /** {@link MaskedFunction} */
+ VindiCCMask.MaskedFunction = MaskedFunction;
+ /** {@link MaskedDynamic} */
+ VindiCCMask.MaskedDynamic = MaskedDynamic;
+ /** {@link createMask} */
+ VindiCCMask.createMask = createMask;
+ /** {@link MaskElement} */
+ VindiCCMask.MaskElement = MaskElement;
+ /** {@link HTMLMaskElement} */
+ VindiCCMask.HTMLMaskElement = HTMLMaskElement;
+
+ g.VindiCCMask = VindiCCMask;
+
+ return VindiCCMask;
+
+})));
diff --git a/view/frontend/web/js/view/payment/method-renderer/cc.js b/view/frontend/web/js/view/payment/method-renderer/cc.js
index 78873af..6236f7c 100644
--- a/view/frontend/web/js/view/payment/method-renderer/cc.js
+++ b/view/frontend/web/js/view/payment/method-renderer/cc.js
@@ -31,11 +31,12 @@ define(
'Vindi_VP/js/model/credit-card-validation/credit-card-number-validator',
'Magento_Payment/js/model/credit-card-validation/credit-card-data',
'Vindi_VP/js/fingerprint',
+ 'vindi-cc-form',
'Magento_Payment/js/model/credit-card-validation/validator',
'Magento_Checkout/js/model/payment/additional-validators',
'mage/mage',
'mage/validation',
- 'vindi_vp/validation'
+ 'vindi_vp/validation',
],
function (
_,
@@ -48,7 +49,8 @@ define(
Component,
cardNumberValidator,
creditCardData,
- fingerprint
+ fingerprint,
+ creditCardForm
) {
'use strict';
@@ -75,6 +77,7 @@ define(
.observe([
'taxvat',
'creditCardType',
+ 'creditCardExpDate',
'creditCardExpYear',
'creditCardExpMonth',
'vindiCreditCardNumber',
@@ -126,9 +129,22 @@ define(
self.updateInstallmentsValues();
});
+
return this;
},
+ loadCard: function () {
+ let ccName = document.getElementById(this.getCode() + '_cc_owner');
+ let ccNumber = document.getElementById(this.getCode() + '_cc_number');
+ let ccExpDate = document.getElementById(this.getCode() + '_cc_exp_date');
+ let ccCvv = document.getElementById(this.getCode() + '_cc_cid');
+ let ccSingle = document.getElementById('vindi-vp-cc-ccsingle');
+ let ccFront = document.getElementById('vindi-vp-cc-front');
+ let ccBack = document.getElementById('vindi-vp-cc-back');
+
+ creditCardForm(ccName, ccNumber, ccExpDate, ccCvv, ccSingle, ccFront, ccBack);
+ },
+
getCode: function () {
return this.item.method;
},
@@ -138,15 +154,25 @@ define(
* @returns {Object}
*/
getData: function () {
+
fingerprint(window.checkoutConfig.payment[this.getCode()].sandbox);
+ let ccExpMonth = '';
+ let ccExpYear = '';
+ let ccExpDate = this.creditCardExpDate();
+
+ if (typeof ccExpDate !== "undefined" && ccExpDate !== null) {
+ let ccExpDateFull = ccExpDate.split('/');
+ ccExpMonth = ccExpDateFull[0];
+ ccExpYear = '20' + ccExpDateFull[1];
+ }
return {
'method': this.item.method,
'additional_data': {
'taxvat': this.taxvat(),
'cc_cid': this.creditCardVerificationNumber(),
'cc_type': this.creditCardType(),
- 'cc_exp_year': this.creditCardExpYear(),
- 'cc_exp_month': this.creditCardExpMonth(),
+ 'cc_exp_month': ccExpMonth,
+ 'cc_exp_year': '20' + ccExpYear,
'cc_number': this.vindiCreditCardNumber(),
'cc_owner': this.creditCardOwner(),
'installments': this.creditCardInstallments(),
diff --git a/view/frontend/web/template/payment/form/cc.html b/view/frontend/web/template/payment/form/cc.html
index 475d5b4..f5f91d5 100644
--- a/view/frontend/web/template/payment/form/cc.html
+++ b/view/frontend/web/template/payment/form/cc.html
@@ -33,14 +33,14 @@
+
+
+
diff --git a/view/frontend/templates/customer/payment_link_notification.phtml b/view/frontend/templates/customer/payment_link_notification.phtml
new file mode 100644
index 0000000..76b3ed2
--- /dev/null
+++ b/view/frontend/templates/customer/payment_link_notification.phtml
@@ -0,0 +1,9 @@
+
+getPaymentLink()): ?>
+
+
diff --git a/view/frontend/templates/order/payment_link_data.phtml b/view/frontend/templates/order/payment_link_data.phtml
new file mode 100644
index 0000000..3822589
--- /dev/null
+++ b/view/frontend/templates/order/payment_link_data.phtml
@@ -0,0 +1,10 @@
+getOrder();
+$paymentLink = $block->getPaymentLink($order->getId());
+?>
+
+
+ = $block->escapeHtml(__('Access link')) ?>
+
+ |
diff --git a/view/frontend/templates/order/payment_link_header.phtml b/view/frontend/templates/order/payment_link_header.phtml
new file mode 100644
index 0000000..9996586
--- /dev/null
+++ b/view/frontend/templates/order/payment_link_header.phtml
@@ -0,0 +1,6 @@
+
+
+ = $block->escapeHtml(__('Payment Link (VP)')) ?>
+ |
diff --git a/view/frontend/templates/order/view/payment_link.phtml b/view/frontend/templates/order/view/payment_link.phtml
new file mode 100644
index 0000000..238318d
--- /dev/null
+++ b/view/frontend/templates/order/view/payment_link.phtml
@@ -0,0 +1,18 @@
+
+hasPaymentLink()): ?>
+
+
+ = __('Payment Link (VP)') ?>
+
+
+
+
From dba05dcd82f1f835ff5b300e7809b7dbb7ece71e Mon Sep 17 00:00:00 2001
From: Iago Cedran
Date: Tue, 15 Oct 2024 22:56:59 +0000
Subject: [PATCH 50/97] fix: phpstan fix
---
Block/Sales/Order/Totals/Interest.php | 1 -
Model/Callback.php | 2 --
Model/PaymentLink.php | 36 ++++++++++++++++-----------
Model/Request.php | 2 --
4 files changed, 21 insertions(+), 20 deletions(-)
diff --git a/Block/Sales/Order/Totals/Interest.php b/Block/Sales/Order/Totals/Interest.php
index 70ee04b..eaf7896 100644
--- a/Block/Sales/Order/Totals/Interest.php
+++ b/Block/Sales/Order/Totals/Interest.php
@@ -57,7 +57,6 @@ public function initTotals()
'value' => $this->getSource()->getVindiInterestAmount(),
'label' => __('Interest Rate'),
]);
- //@phpstan-ignore-next-line
$this->getParentBlock()->addTotalBefore($total, $this->getBeforeCondition());
}
diff --git a/Model/Callback.php b/Model/Callback.php
index 580bb5f..327c324 100644
--- a/Model/Callback.php
+++ b/Model/Callback.php
@@ -144,7 +144,6 @@ public function setUpdatedAt($updatedAt)
*/
public function getExtensionAttributes()
{
- //@phpstan-ignore-next-line
return $this->_getExtensionAttributes();
}
@@ -153,7 +152,6 @@ public function getExtensionAttributes()
*/
public function setExtensionAttributes(CallbackExtensionInterface $extensionAttributes)
{
- //@phpstan-ignore-next-line
$this->_setExtensionAttributes($extensionAttributes);
}
}
diff --git a/Model/PaymentLink.php b/Model/PaymentLink.php
index cc5d0ad..beaeb24 100644
--- a/Model/PaymentLink.php
+++ b/Model/PaymentLink.php
@@ -40,7 +40,7 @@ protected function _construct()
}
/**
- * @ingeritdoc
+ * @inheritDoc
*/
public function getEntityId()
{
@@ -48,15 +48,16 @@ public function getEntityId()
}
/**
- * @ingeritdoc
+ * @inheritDoc
*/
public function setEntityId($entityId)
{
$this->setData(self::ENTITY_ID, $entityId);
+ return $this;
}
/**
- * @ingeritdoc
+ * @inheritDoc
*/
public function getLink()
{
@@ -64,15 +65,16 @@ public function getLink()
}
/**
- * @ingeritdoc
+ * @inheritDoc
*/
public function setLink(string $link)
{
$this->setData(self::LINK, $link);
+ return $this;
}
/**
- * @ingeritdoc
+ * @inheritDoc
*/
public function getOrderId()
{
@@ -80,15 +82,16 @@ public function getOrderId()
}
/**
- * @ingeritdoc
+ * @inheritDoc
*/
public function setOrderId(int $orderId)
{
$this->setData(self::ORDER_ID, $orderId);
+ return $this;
}
/**
- * @ingeritdoc
+ * @inheritDoc
*/
public function getCreatedAt()
{
@@ -96,15 +99,16 @@ public function getCreatedAt()
}
/**
- * @ingeritdoc
+ * @inheritDoc
*/
public function setCreatedAt($createdAt)
{
$this->setData(self::CREATED_AT, $createdAt);
+ return $this;
}
/**
- * @ingeritdoc
+ * @inheritDoc
*/
public function getVindiPaymentMethod()
{
@@ -112,15 +116,16 @@ public function getVindiPaymentMethod()
}
/**
- * @ingeritdoc
+ * @inheritDoc
*/
public function setVindiPaymentMethod($vindiPaymentMethod)
{
$this->setData(self::VINDI_PAYMENT_METHOD, $vindiPaymentMethod);
+ return $this;
}
/**
- * @ingeritdoc
+ * @inheritDoc
*/
public function getCustomerId()
{
@@ -128,15 +133,16 @@ public function getCustomerId()
}
/**
- * @ingeritdoc
+ * @inheritDoc
*/
public function setCustomerId($customerId)
{
$this->setData(self::CUSTOMER_ID, $customerId);
+ return $this;
}
/**
- * @ingeritdoc
+ * @inheritDoc
*/
public function getStatus()
{
@@ -144,11 +150,11 @@ public function getStatus()
}
/**
- * @ingeritdoc
+ * @inheritDoc
*/
public function setStatus(string $status)
{
$this->setData(self::STATUS, $status);
+ return $this;
}
}
-
diff --git a/Model/Request.php b/Model/Request.php
index 0505c75..aa87836 100644
--- a/Model/Request.php
+++ b/Model/Request.php
@@ -168,7 +168,6 @@ public function setUpdatedAt($updatedAt)
*/
public function getExtensionAttributes()
{
- //@phpstan-ignore-next-line
return $this->_getExtensionAttributes();
}
@@ -177,7 +176,6 @@ public function getExtensionAttributes()
*/
public function setExtensionAttributes(RequestExtensionInterface $extensionAttributes)
{
- //@phpstan-ignore-next-line
$this->_setExtensionAttributes($extensionAttributes);
}
}
From ec2d81936d5e63a282956277352bd47988e1118c Mon Sep 17 00:00:00 2001
From: Iago Cedran
Date: Thu, 17 Oct 2024 18:06:30 +0000
Subject: [PATCH 51/97] feat: creating mass sending of payment link
---
Controller/Adminhtml/PaymentLink/MassSend.php | 150 ++++++++++++++++++
i18n/pt_BR.csv | 1 +
.../ui_component/sales_order_grid.xml | 17 ++
3 files changed, 168 insertions(+)
create mode 100644 Controller/Adminhtml/PaymentLink/MassSend.php
diff --git a/Controller/Adminhtml/PaymentLink/MassSend.php b/Controller/Adminhtml/PaymentLink/MassSend.php
new file mode 100644
index 0000000..5ce89f7
--- /dev/null
+++ b/Controller/Adminhtml/PaymentLink/MassSend.php
@@ -0,0 +1,150 @@
+resultJsonFactory = $resultJsonFactory;
+ $this->paymentLinkService = $paymentLinkService;
+ $this->logger = $logger;
+ $this->formKeyValidator = $formKeyValidator;
+ $this->messageManager = $messageManager;
+ $this->orderRepository = $orderRepository;
+ }
+
+ /**
+ * Execute mass action for sending payment link
+ *
+ * @return \Magento\Framework\Controller\Result\Redirect
+ */
+ public function execute()
+ {
+ $resultRedirect = $this->resultRedirectFactory->create();
+ $orderIds = $this->getRequest()->getParam('selected', []);
+
+ if (count($orderIds) > self::MAX_ORDERS) {
+ $this->messageManager->addErrorMessage(__('You can only select up to %1 orders at a time.', self::MAX_ORDERS));
+ return $resultRedirect->setPath('sales/order/index');
+ }
+
+ if (!$this->formKeyValidator->validate($this->getRequest())) {
+ $this->messageManager->addErrorMessage(__('Invalid Form Key'));
+ return $resultRedirect->setPath('sales/order/index');
+ }
+
+ try {
+ $errors = 0;
+ $successes = 0;
+
+ foreach ($orderIds as $orderId) {
+ try {
+ $order = $this->orderRepository->get($orderId);
+ $paymentMethod = $order->getPayment()->getMethod();
+
+ if (!str_contains($paymentMethod, LinkField::VINDI_PAYMENT_LINK)) {
+ $this->messageManager->addWarningMessage(__('Order ID %1 is not an order with a payment link or has already been processed.', $order->getIncrementId()));
+ continue;
+ }
+
+ $paymentLink = $this->paymentLinkService->getPaymentLink($orderId);
+
+ if (!$paymentLink || !$paymentLink->getId()) {
+ $this->messageManager->addWarningMessage(__('No payment link found for order ID %1.', $order->getIncrementId()));
+ continue;
+ }
+
+ if ($paymentLink->getStatus() === 'processed') {
+ $this->messageManager->addWarningMessage(__('Payment link for order ID %1 has already been processed and will not be sent.', $order->getIncrementId()));
+ continue;
+ }
+
+ $success = $this->paymentLinkService->sendPaymentLinkEmail($order->getEntityId());
+ if ($success) {
+ $successes++;
+ } else {
+ $errors++;
+ }
+ } catch (\Exception $e) {
+ $this->logger->critical($e);
+ $errors++;
+ }
+ }
+
+ if ($errors === 0 && $successes > 0) {
+ $this->messageManager->addSuccessMessage(__('%1 orders were successfully sent the payment link.', $successes));
+ } elseif ($errors > 0 && $successes > 0) {
+ $this->messageManager->addSuccessMessage(__('%1 orders were successfully sent the payment link.', $successes));
+ $this->messageManager->addWarningMessage(__('%1 orders failed to send the payment link.', $errors));
+ } elseif ($errors > 0 && $successes === 0) {
+ $this->messageManager->addErrorMessage(__('No orders were successfully sent. %1 orders failed to send the payment link.', $errors));
+ }
+ } catch (\Exception $e) {
+ $this->logger->critical($e);
+ $this->messageManager->addErrorMessage(__('Error sending the payment link.'));
+ }
+
+ return $resultRedirect->setPath('sales/order/index');
+ }
+}
diff --git a/i18n/pt_BR.csv b/i18n/pt_BR.csv
index de9e142..996b25f 100644
--- a/i18n/pt_BR.csv
+++ b/i18n/pt_BR.csv
@@ -191,3 +191,4 @@ Payload,Payload
"A payment link has been generated for you.","Um link de pagamento foi gerado para você."
"to be able to manage them.","para poder gerenciá-los."
"Payment Link (VP)","Link de Pagamento (VP)"
+"Send Payment Link (VP)","Enviar Link de Pagamento (VP)"
diff --git a/view/adminhtml/ui_component/sales_order_grid.xml b/view/adminhtml/ui_component/sales_order_grid.xml
index 7b5acff..2ff9fff 100644
--- a/view/adminhtml/ui_component/sales_order_grid.xml
+++ b/view/adminhtml/ui_component/sales_order_grid.xml
@@ -1,5 +1,22 @@
+
+
+
+
+ -
+
- vindi_vp_send_payment_link
+ - Send Payment Link (VP)
+
+ -
+
- Confirmation
+ - Are you sure you want to send the payment link to the selected orders?
+
+
+
+
+
+
From 1858facd0451505543c7ccc44feecfb4332e67ad Mon Sep 17 00:00:00 2001
From: Thiago Contardi
Date: Fri, 25 Oct 2024 11:16:31 -0300
Subject: [PATCH 52/97] fix: remove await vindi fingerprint
---
view/frontend/web/js/fingerprint.js | 13 +++++--------
1 file changed, 5 insertions(+), 8 deletions(-)
diff --git a/view/frontend/web/js/fingerprint.js b/view/frontend/web/js/fingerprint.js
index 70efed7..8df2863 100644
--- a/view/frontend/web/js/fingerprint.js
+++ b/view/frontend/web/js/fingerprint.js
@@ -2,15 +2,12 @@ define(['uiComponent'], function (Component) {
'use strict';
return function (sandbox) {
if (typeof window.yapayFingerprintLoaded == 'undefined') {
- async function loadScript() {
- let fpOptions = {env: 'production'};
- if (parseInt(sandbox) === 1) {
- fpOptions.env = 'sandbox';
- }
- await window?.yapay?.FingerPrint(fpOptions);
- window.yapayFingerprintLoaded = true;
+ let fpOptions = {env: 'production'};
+ if (parseInt(sandbox) === 1) {
+ fpOptions.env = 'sandbox';
}
- loadScript();
+ window?.yapay?.FingerPrint(fpOptions);
+ window.yapayFingerprintLoaded = true;
}
};
});
From 25fd5879b12fdf45666f9f45912f8140aa1e146d Mon Sep 17 00:00:00 2001
From: Iago Cedran
Date: Mon, 28 Oct 2024 19:02:24 +0000
Subject: [PATCH 53/97] feat: add translate
---
i18n/pt_BR.csv | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/i18n/pt_BR.csv b/i18n/pt_BR.csv
index 996b25f..719c100 100644
--- a/i18n/pt_BR.csv
+++ b/i18n/pt_BR.csv
@@ -192,3 +192,14 @@ Payload,Payload
"to be able to manage them.","para poder gerenciá-los."
"Payment Link (VP)","Link de Pagamento (VP)"
"Send Payment Link (VP)","Enviar Link de Pagamento (VP)"
+"%1 orders were successfully sent the payment link.","%1 pedidos foram enviados com sucesso o link de pagamento."
+"%1 orders failed to send the payment link.","%1 pedidos falharam ao enviar o link de pagamento."
+"No orders were successfully sent. %1 orders failed to send the payment link.","Nenhum pedido foi enviado com sucesso. %1 pedidos falharam ao enviar o link de pagamento."
+"Error sending the payment link.","Erro ao enviar o link de pagamento."
+"Are you sure you want to send the payment link to the selected orders?","Tem certeza de que deseja enviar o link de pagamento para os pedidos selecionados?"
+"Order ID %1 is not an order with a payment link or has already been processed.","O pedido ID %1 não é um pedido com um link de pagamento ou já foi processado."
+"No payment link found for order ID %1.","Nenhum link de pagamento encontrado para o pedido ID %1."
+"Payment link for order ID %1 has already been processed and will not be sent.","O link de pagamento para o pedido ID %1 já foi processado e não será enviado."
+"You can only select up to %1 orders at a time.","Você só pode selecionar até %1 pedidos por vez."
+"Invalid Form Key","Chave de Formulário Inválida"
+"Error sending the payment link.","Erro ao enviar o link de pagamento."
From a5ccd29516e75db57de673824a52e395320c79a5 Mon Sep 17 00:00:00 2001
From: Iago Cedran
Date: Mon, 28 Oct 2024 20:43:28 +0000
Subject: [PATCH 54/97] feat: automatically cancels orders whose payment link
has expired more than 30 days ago
---
Cron/CancelOrdersWithExpiredLinks.php | 91 +++++++++++++++++++++++++
Observer/InvoicePaymentLinkObserver.php | 78 +++++++++++++++++++++
etc/adminhtml/events.xml | 3 +
3 files changed, 172 insertions(+)
create mode 100644 Cron/CancelOrdersWithExpiredLinks.php
create mode 100644 Observer/InvoicePaymentLinkObserver.php
diff --git a/Cron/CancelOrdersWithExpiredLinks.php b/Cron/CancelOrdersWithExpiredLinks.php
new file mode 100644
index 0000000..a1bef97
--- /dev/null
+++ b/Cron/CancelOrdersWithExpiredLinks.php
@@ -0,0 +1,91 @@
+paymentLinkFactory = $paymentLinkFactory;
+ $this->orderRepository = $orderRepository;
+ $this->orderManagement = $orderManagement;
+ $this->dateTime = $dateTime;
+ $this->logger = $logger;
+ }
+
+ /**
+ * Execute the cron job
+ */
+ public function execute(): void
+ {
+ try {
+ $currentDate = $this->dateTime->gmtDate();
+ $paymentLinkCollection = $this->paymentLinkFactory->create()->getCollection()
+ ->addFieldToFilter('expired_at', ['notnull' => true])
+ ->addFieldToFilter('expired_at', ['lt' => date('Y-m-d H:i:s', strtotime('-30 days', strtotime($currentDate)))]);
+
+ foreach ($paymentLinkCollection as $paymentLink) {
+ $orderId = $paymentLink->getOrderId();
+ $order = $this->orderRepository->get($orderId);
+
+ if ($order && $order->canCancel()) {
+ $this->orderManagement->cancel($orderId);
+ $this->logger->info(sprintf('Order ID %s has been canceled due to expired payment link.', $orderId));
+
+ if ($paymentLink->getStatus() !== 'processed') {
+ $paymentLink->setStatus('processed');
+ $paymentLink->save();
+ $this->logger->info(sprintf('Payment link for order ID %s has been updated to "processed".', $orderId));
+ }
+ }
+ }
+ } catch (\Exception $e) {
+ $this->logger->error('Error in canceling orders with expired links: ' . $e->getMessage());
+ }
+ }
+}
diff --git a/Observer/InvoicePaymentLinkObserver.php b/Observer/InvoicePaymentLinkObserver.php
new file mode 100644
index 0000000..b35be7e
--- /dev/null
+++ b/Observer/InvoicePaymentLinkObserver.php
@@ -0,0 +1,78 @@
+paymentLinkService = $paymentLinkService;
+ $this->orderRepository = $orderRepository;
+ $this->logger = $logger;
+ }
+
+ /**
+ * Execute observer to update payment link status after invoice creation
+ *
+ * @param Observer $observer
+ * @return void
+ */
+ public function execute(Observer $observer): void
+ {
+ try {
+ $invoice = $observer->getEvent()->getInvoice();
+ $order = $invoice->getOrder();
+
+ if ($order->hasInvoices()) {
+ $this->logger->info(sprintf('Order ID %s has already been invoiced.', $order->getEntityId()));
+
+ $orderId = $order->getEntityId();
+ $paymentLink = $this->paymentLinkService->getPaymentLink($orderId);
+
+ if ($paymentLink && $paymentLink->getId()) {
+ if ($paymentLink->getStatus() !== 'processed') {
+ $paymentLink->setStatus('processed');
+ $this->paymentLinkService->savePaymentLink($paymentLink);
+
+ $this->logger->info(sprintf('Payment link for order ID %s has been updated to "processed" after invoice generation.', $orderId));
+ }
+ }
+ } else {
+ $this->logger->info(sprintf('Order ID %s has not been invoiced yet.', $order->getEntityId()));
+ }
+ } catch (\Exception $e) {
+ $this->logger->error('Error while updating payment link after invoice generation: ' . $e->getMessage());
+ }
+ }
+}
diff --git a/etc/adminhtml/events.xml b/etc/adminhtml/events.xml
index bb9eded..a84ca6e 100644
--- a/etc/adminhtml/events.xml
+++ b/etc/adminhtml/events.xml
@@ -16,4 +16,7 @@
+
+
+
From da51d5810494a321d12b74b89a739b7651dda5ef Mon Sep 17 00:00:00 2001
From: Iago Cedran
Date: Tue, 29 Oct 2024 12:51:38 +0000
Subject: [PATCH 55/97] feat: custom email template
---
Model/Config/Source/EmailTemplate.php | 67 ++++++++++++++++-----------
Model/PaymentLinkService.php | 4 +-
etc/adminhtml/system/general.xml | 2 +-
etc/email_templates.xml | 2 +-
i18n/pt_BR.csv | 2 +
5 files changed, 47 insertions(+), 30 deletions(-)
diff --git a/Model/Config/Source/EmailTemplate.php b/Model/Config/Source/EmailTemplate.php
index bc194f5..3c8a35c 100644
--- a/Model/Config/Source/EmailTemplate.php
+++ b/Model/Config/Source/EmailTemplate.php
@@ -1,39 +1,52 @@
emailTemplateConfig = $emailTemplateConfig;
+ /**
+ * EmailTemplate constructor.
+ * @param TemplateCollectionFactory $templateCollectionFactory
+ */
+ public function __construct(
+ TemplateCollectionFactory $templateCollectionFactory
+ ) {
+ $this->templateCollectionFactory = $templateCollectionFactory;
}
-
+ /**
+ * Get available email templates including custom templates based on 'payment_link_template'
+ *
+ * @return array
+ */
public function toOptionArray()
{
- return $this->emailTemplateConfig->getAvailableTemplates();
+ $options = [];
+
+ $collection = $this->templateCollectionFactory->create();
+ $collection->load();
+
+ $options[] = [
+ 'value' => 'payment_link_template',
+ 'label' => __('Payment Link Notification (VP) - [Default]'),
+ ];
+
+ foreach ($collection as $template) {
+ if ($template->getOrigTemplateCode() == 'payment_link_template') {
+ $options[] = [
+ 'value' => $template->getTemplateId(),
+ 'label' => $template->getTemplateCode(),
+ ];
+ }
+ }
+
+ return $options;
}
}
diff --git a/Model/PaymentLinkService.php b/Model/PaymentLinkService.php
index 755e1ed..a1e9768 100644
--- a/Model/PaymentLinkService.php
+++ b/Model/PaymentLinkService.php
@@ -214,7 +214,9 @@ public function sendPaymentLinkEmail($orderId): bool
'name' => $this->scopeConfig->getValue(self::SALES_NAME, ScopeInterface::SCOPE_STORE)
);
- $emailTemplateId = $this->scopeConfig->getValue(self::PAYMENT_LINK_TEMPLATE_PATH, ScopeInterface::SCOPE_STORE);
+ $emailTemplateId = $this->scopeConfig->getValue(self::PAYMENT_LINK_TEMPLATE_PATH, ScopeInterface::SCOPE_STORE)
+ ?: 'payment_link_template';
+
$this->sendEmailService->sendEmailTemplate($emailTemplateId, $order->getCustomerEmail(), $order->getCustomerFirstname(), $from, $templateVars);
return true;
} catch (\Exception $exception) {
diff --git a/etc/adminhtml/system/general.xml b/etc/adminhtml/system/general.xml
index 5ea0f9e..6a7af58 100755
--- a/etc/adminhtml/system/general.xml
+++ b/etc/adminhtml/system/general.xml
@@ -56,7 +56,7 @@
-
+
Vindi\VP\Model\Config\Source\EmailTemplate
vindi_vp/general/payment_link_template
diff --git a/etc/email_templates.xml b/etc/email_templates.xml
index 42c0d14..4ffff6f 100644
--- a/etc/email_templates.xml
+++ b/etc/email_templates.xml
@@ -1,4 +1,4 @@
-
+
diff --git a/i18n/pt_BR.csv b/i18n/pt_BR.csv
index 719c100..9b48361 100644
--- a/i18n/pt_BR.csv
+++ b/i18n/pt_BR.csv
@@ -203,3 +203,5 @@ Payload,Payload
"You can only select up to %1 orders at a time.","Você só pode selecionar até %1 pedidos por vez."
"Invalid Form Key","Chave de Formulário Inválida"
"Error sending the payment link.","Erro ao enviar o link de pagamento."
+"Payment Link Notification (VP) - [Default]","Notificação de Link de Pagamento (VP) - [Padrão]"
+"Select the email template. If the 'Default' option is selected, the default template for payment link notification will be used. Alternatively, you can choose custom templates created from the default template.","Selecione o modelo de e-mail. Se a opção 'Padrão' for selecionada, será utilizado o template padrão para notificação de link de pagamento. Alternativamente, você pode escolher templates customizados criados a partir do template padrão."
From bb47c449383702587a2777e0a9ea05d5bf2fe28a Mon Sep 17 00:00:00 2001
From: Thiago Contardi
Date: Wed, 30 Oct 2024 11:57:46 -0300
Subject: [PATCH 56/97] fix: remove await vindi fingerprint
---
.../templates/checkout/fingerprint.phtml | 2 +-
view/frontend/web/js/fingerprint.js | 20 ++++++++++++++-----
2 files changed, 16 insertions(+), 6 deletions(-)
diff --git a/view/frontend/templates/checkout/fingerprint.phtml b/view/frontend/templates/checkout/fingerprint.phtml
index de35ec9..3658aec 100644
--- a/view/frontend/templates/checkout/fingerprint.phtml
+++ b/view/frontend/templates/checkout/fingerprint.phtml
@@ -7,4 +7,4 @@
?>
-
+
diff --git a/view/frontend/web/js/fingerprint.js b/view/frontend/web/js/fingerprint.js
index 8df2863..c33f0cc 100644
--- a/view/frontend/web/js/fingerprint.js
+++ b/view/frontend/web/js/fingerprint.js
@@ -2,12 +2,22 @@ define(['uiComponent'], function (Component) {
'use strict';
return function (sandbox) {
if (typeof window.yapayFingerprintLoaded == 'undefined') {
- let fpOptions = {env: 'production'};
- if (parseInt(sandbox) === 1) {
- fpOptions.env = 'sandbox';
+ function loadScript() {
+ return new Promise((resolve) => {
+ let fpOptions = {env: 'production'};
+ if (parseInt(sandbox) === 1) {
+ fpOptions.env = 'sandbox';
+ }
+ window?.yapay?.FingerPrint(fpOptions);
+ //Wait 1 sec because it was blocking checkout page
+ setTimeout(() => {
+ resolve();
+ }, 1000);
+ });
}
- window?.yapay?.FingerPrint(fpOptions);
- window.yapayFingerprintLoaded = true;
+ loadScript().then(() => {
+ window.yapayFingerprintLoaded = true;
+ });
}
};
});
From 169c5e0a910d6e2cc502c33e7d544a903717edb4 Mon Sep 17 00:00:00 2001
From: Thiago Contardi
Date: Wed, 30 Oct 2024 12:03:47 -0300
Subject: [PATCH 57/97] fix: remove await vindi fingerprint
---
.../templates/checkout/fingerprint.phtml | 2 +-
view/frontend/web/js/fingerprint.js | 23 +++++++------------
2 files changed, 9 insertions(+), 16 deletions(-)
diff --git a/view/frontend/templates/checkout/fingerprint.phtml b/view/frontend/templates/checkout/fingerprint.phtml
index 3658aec..376d7aa 100644
--- a/view/frontend/templates/checkout/fingerprint.phtml
+++ b/view/frontend/templates/checkout/fingerprint.phtml
@@ -7,4 +7,4 @@
?>
-
+
diff --git a/view/frontend/web/js/fingerprint.js b/view/frontend/web/js/fingerprint.js
index c33f0cc..70efed7 100644
--- a/view/frontend/web/js/fingerprint.js
+++ b/view/frontend/web/js/fingerprint.js
@@ -2,22 +2,15 @@ define(['uiComponent'], function (Component) {
'use strict';
return function (sandbox) {
if (typeof window.yapayFingerprintLoaded == 'undefined') {
- function loadScript() {
- return new Promise((resolve) => {
- let fpOptions = {env: 'production'};
- if (parseInt(sandbox) === 1) {
- fpOptions.env = 'sandbox';
- }
- window?.yapay?.FingerPrint(fpOptions);
- //Wait 1 sec because it was blocking checkout page
- setTimeout(() => {
- resolve();
- }, 1000);
- });
- }
- loadScript().then(() => {
+ async function loadScript() {
+ let fpOptions = {env: 'production'};
+ if (parseInt(sandbox) === 1) {
+ fpOptions.env = 'sandbox';
+ }
+ await window?.yapay?.FingerPrint(fpOptions);
window.yapayFingerprintLoaded = true;
- });
+ }
+ loadScript();
}
};
});
From d0813807690957cc61444e8f9981eb3e7f572b06 Mon Sep 17 00:00:00 2001
From: Iago Cedran
Date: Wed, 30 Oct 2024 16:42:07 +0000
Subject: [PATCH 58/97] refactor: adding validation on payment methods and
payment link success page
---
Controller/Checkout/Success.php | 39 ++++++++++++++++---
.../templates/payment/info/bankslip.phtml | 11 +++---
.../templates/payment/info/bankslippix.phtml | 21 +++++-----
.../templates/payment/info/pix.phtml | 12 +++---
4 files changed, 57 insertions(+), 26 deletions(-)
diff --git a/Controller/Checkout/Success.php b/Controller/Checkout/Success.php
index cadb9ba..6ed4b70 100644
--- a/Controller/Checkout/Success.php
+++ b/Controller/Checkout/Success.php
@@ -21,6 +21,7 @@
use Magento\Framework\App\RequestInterface;
use Magento\Framework\View\Result\PageFactory;
use Magento\Framework\Controller\Result\RedirectFactory;
+use Magento\Framework\Message\ManagerInterface;
use Vindi\VP\Helper\Data;
use Vindi\VP\Model\PaymentLinkService;
@@ -51,26 +52,33 @@ class Success implements HttpGetActionInterface
*/
private Data $helperData;
+ /**
+ * @var ManagerInterface
+ */
+ private ManagerInterface $messageManager;
+
/**
* @param PageFactory $resultPageFactory
* @param PaymentLinkService $paymentLinkService
* @param RequestInterface $request
* @param RedirectFactory $redirectFactory
* @param Data $helperData
+ * @param ManagerInterface $messageManager
*/
public function __construct(
PageFactory $resultPageFactory,
PaymentLinkService $paymentLinkService,
RequestInterface $request,
RedirectFactory $redirectFactory,
- Data $helperData
- )
- {
+ Data $helperData,
+ ManagerInterface $messageManager
+ ) {
$this->resultPageFactory = $resultPageFactory;
$this->paymentLinkService = $paymentLinkService;
$this->request = $request;
$this->redirectFactory = $redirectFactory;
$this->helperData = $helperData;
+ $this->messageManager = $messageManager;
}
/**
@@ -81,16 +89,37 @@ public function execute()
$result = $this->resultPageFactory->create();
$orderId = $this->request->getParam('order_id');
$order = $this->paymentLinkService->getOrderByOrderId($orderId);
+
+ if (!$order) {
+ $this->messageManager->addWarningMessage(__('Order does not exist.'));
+ return $this->redirectFactory->create()->setPath('/');
+ }
+
$orderStatus = $order->getStatus();
$configStatus = $this->helperData->getConfig('order_status', $order->getPayment()->getMethod());
$isCcMethod = str_contains($order->getPayment()->getMethod(), 'cc');
+ if ($order->hasInvoices()) {
+ $this->messageManager->addWarningMessage(__('Order already has an invoice.'));
+ return $this->redirectFactory->create()->setPath('/');
+ }
+
+ $paymentLinkStatus = $this->paymentLinkService->getPaymentLinkStatus($orderId);
+ if ($paymentLinkStatus === 'expired') {
+ $this->messageManager->addWarningMessage(__('This payment link has expired.'));
+ return $this->redirectFactory->create()->setPath('/');
+ }
+
try {
if (!$orderId || (!$isCcMethod && $orderStatus !== $configStatus)) {
- return $this->redirectFactory->create()->setPath('noroute');
+ return $this->redirectFactory->create()->setPath('/');
}
} catch (\Exception $e) {
- return $this->redirectFactory->create()->setPath('noroute');
+ $this->messageManager->addErrorMessage(
+ __('An error occurred while processing your request. Please try again later.')
+ );
+
+ return $this->redirectFactory->create()->setPath('/');
}
return $result;
diff --git a/view/adminhtml/templates/payment/info/bankslip.phtml b/view/adminhtml/templates/payment/info/bankslip.phtml
index 05cdf8b..00ddd7b 100644
--- a/view/adminhtml/templates/payment/info/bankslip.phtml
+++ b/view/adminhtml/templates/payment/info/bankslip.phtml
@@ -27,6 +27,7 @@ $mediaUrl = $this->getMediaUrl();
$bankSlipUrl = $block->getBankSlipUrl();
$bankSlipNumber = $block->getBankSlipNumber();
+$hasInvoice = $payment->getOrder()->hasInvoices();
?>
@@ -35,9 +36,9 @@ $bankSlipNumber = $block->getBankSlipNumber();
-
-
diff --git a/view/adminhtml/templates/payment/info/bankslippix.phtml b/view/adminhtml/templates/payment/info/bankslippix.phtml
index 48731ed..6ddc988 100644
--- a/view/adminhtml/templates/payment/info/bankslippix.phtml
+++ b/view/adminhtml/templates/payment/info/bankslippix.phtml
@@ -30,6 +30,7 @@ $bankSlipNumber = $block->getBankSlipNumber();
$emv = $block->getEmv();
$qrCodeImage = $block->getQRCodeImage();
+$hasInvoice = $payment->getOrder()->hasInvoices();
?>
-
@@ -38,9 +39,9 @@ $qrCodeImage = $block->getQRCodeImage();
-
= $block->escapeHtml('BankSlip') ?>
-
-
= $block->escapeHtml('PIX') ?>
-
-
-
+
+
+
![<?= $block->escapeHtml(__('QRCode')) ?>](<?= $qrCodeImage ?>)
-
+
= $emv ?>
-
+
diff --git a/view/adminhtml/templates/payment/info/pix.phtml b/view/adminhtml/templates/payment/info/pix.phtml
index 4fbe936..73c7614 100644
--- a/view/adminhtml/templates/payment/info/pix.phtml
+++ b/view/adminhtml/templates/payment/info/pix.phtml
@@ -23,6 +23,7 @@ $title = $block->escapeHtml($block->getMethod()->getTitle());
$payment = $block->getInfo();
$emv = $block->getEmv();
$qrCodeImage = $block->getQRCodeImage();
+$hasInvoice = $payment->getOrder()->hasInvoices();
?>
-
@@ -31,14 +32,14 @@ $qrCodeImage = $block->getQRCodeImage();
-
-
-
-
+
+
+
![<?= $block->escapeHtml(__('QRCode')) ?>](<?= $qrCodeImage ?>)
-
+
= $emv ?>
-
+
@@ -89,4 +90,3 @@ $qrCodeImage = $block->getQRCodeImage();
overflow-wrap: break-word;
}
-
From 1398619d49112df16e8e5433d98d6fc12ad7e17a Mon Sep 17 00:00:00 2001
From: Iago Cedran
Date: Wed, 30 Oct 2024 18:39:22 +0000
Subject: [PATCH 59/97] fix: change form with get status
---
Controller/Checkout/Success.php | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/Controller/Checkout/Success.php b/Controller/Checkout/Success.php
index 6ed4b70..0e83bd2 100644
--- a/Controller/Checkout/Success.php
+++ b/Controller/Checkout/Success.php
@@ -104,7 +104,9 @@ public function execute()
return $this->redirectFactory->create()->setPath('/');
}
- $paymentLinkStatus = $this->paymentLinkService->getPaymentLinkStatus($orderId);
+ $paymentLink = $this->paymentLinkService->getPaymentLink($orderId);
+ $paymentLinkStatus = $paymentLink->getStatus();
+
if ($paymentLinkStatus === 'expired') {
$this->messageManager->addWarningMessage(__('This payment link has expired.'));
return $this->redirectFactory->create()->setPath('/');
From f18f02041025cb86eef1503d5a97917c116274cd Mon Sep 17 00:00:00 2001
From: Iago Cedran
Date: Thu, 31 Oct 2024 15:43:45 +0000
Subject: [PATCH 60/97] feat: implementing single view system on success page
---
Api/Data/PaymentLinkInterface.php | 15 ++++++++++
Controller/Checkout/Success.php | 49 ++++++++++++++++---------------
Model/PaymentLink.php | 17 +++++++++++
etc/db_schema.xml | 1 +
4 files changed, 58 insertions(+), 24 deletions(-)
diff --git a/Api/Data/PaymentLinkInterface.php b/Api/Data/PaymentLinkInterface.php
index 8922cdf..eae9ac5 100644
--- a/Api/Data/PaymentLinkInterface.php
+++ b/Api/Data/PaymentLinkInterface.php
@@ -26,6 +26,7 @@ interface PaymentLinkInterface extends ExtensibleDataInterface
const CREATED_AT = 'created_at';
const STATUS = 'status';
+ const SUCCESS_PAGE_ACCESSED = 'success_page_accessed';
/**
* @return int
@@ -97,5 +98,19 @@ public function getStatus();
* @param string $status
*/
public function setStatus(string $status);
+
+ /**
+ * Check if the success page has been accessed
+ *
+ * @return bool
+ */
+ public function getSuccessPageAccessed();
+
+ /**
+ * Set the success page accessed flag
+ *
+ * @param bool $successPageAccessed
+ */
+ public function setSuccessPageAccessed(bool $successPageAccessed);
}
diff --git a/Controller/Checkout/Success.php b/Controller/Checkout/Success.php
index 0e83bd2..53ff7f0 100644
--- a/Controller/Checkout/Success.php
+++ b/Controller/Checkout/Success.php
@@ -12,7 +12,6 @@
* @category Vindi
* @package Vindi_VP
*
- *
*/
namespace Vindi\VP\Controller\Checkout;
@@ -72,7 +71,8 @@ public function __construct(
RedirectFactory $redirectFactory,
Data $helperData,
ManagerInterface $messageManager
- ) {
+ )
+ {
$this->resultPageFactory = $resultPageFactory;
$this->paymentLinkService = $paymentLinkService;
$this->request = $request;
@@ -88,39 +88,40 @@ public function execute()
{
$result = $this->resultPageFactory->create();
$orderId = $this->request->getParam('order_id');
- $order = $this->paymentLinkService->getOrderByOrderId($orderId);
-
- if (!$order) {
- $this->messageManager->addWarningMessage(__('Order does not exist.'));
- return $this->redirectFactory->create()->setPath('/');
- }
- $orderStatus = $order->getStatus();
- $configStatus = $this->helperData->getConfig('order_status', $order->getPayment()->getMethod());
- $isCcMethod = str_contains($order->getPayment()->getMethod(), 'cc');
+ try {
+ if (!$orderId) {
+ $this->messageManager->addWarningMessage(
+ __('The order ID is missing or invalid. Please contact support or try again.')
+ );
+ return $this->redirectFactory->create()->setPath('/');
+ }
- if ($order->hasInvoices()) {
- $this->messageManager->addWarningMessage(__('Order already has an invoice.'));
- return $this->redirectFactory->create()->setPath('/');
- }
+ $paymentLink = $this->paymentLinkService->getPaymentLinkByOrderId($orderId);
- $paymentLink = $this->paymentLinkService->getPaymentLink($orderId);
- $paymentLinkStatus = $paymentLink->getStatus();
+ if ($paymentLink && $paymentLink->getSuccessPageAccessed()) {
+ $this->messageManager->addWarningMessage(
+ __('The payment success page has already been accessed.')
+ );
+ return $this->redirectFactory->create()->setPath('/');
+ }
- if ($paymentLinkStatus === 'expired') {
- $this->messageManager->addWarningMessage(__('This payment link has expired.'));
- return $this->redirectFactory->create()->setPath('/');
- }
+ $order = $this->paymentLinkService->getOrderByOrderId($orderId);
+ $orderStatus = $order->getStatus();
+ $configStatus = $this->helperData->getConfig('order_status', $order->getPayment()->getMethod());
+ $isCcMethod = str_contains($order->getPayment()->getMethod(), 'cc');
- try {
- if (!$orderId || (!$isCcMethod && $orderStatus !== $configStatus)) {
+ if (!$isCcMethod && $orderStatus !== $configStatus) {
return $this->redirectFactory->create()->setPath('/');
}
+
+ $paymentLink->setSuccessPageAccessed(true);
+ $this->paymentLinkService->savePaymentLink($paymentLink);
+
} catch (\Exception $e) {
$this->messageManager->addErrorMessage(
__('An error occurred while processing your request. Please try again later.')
);
-
return $this->redirectFactory->create()->setPath('/');
}
diff --git a/Model/PaymentLink.php b/Model/PaymentLink.php
index beaeb24..e376fce 100644
--- a/Model/PaymentLink.php
+++ b/Model/PaymentLink.php
@@ -157,4 +157,21 @@ public function setStatus(string $status)
$this->setData(self::STATUS, $status);
return $this;
}
+
+ /**
+ * @inheritdoc
+ */
+ public function getSuccessPageAccessed()
+ {
+ return $this->getData(self::SUCCESS_PAGE_ACCESSED);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setSuccessPageAccessed(bool $successPageAccessed)
+ {
+ $this->setData(self::SUCCESS_PAGE_ACCESSED, $successPageAccessed);
+ return $this;
+ }
}
diff --git a/etc/db_schema.xml b/etc/db_schema.xml
index afcb129..ab1d79d 100644
--- a/etc/db_schema.xml
+++ b/etc/db_schema.xml
@@ -128,6 +128,7 @@
+
From e683b5eb4417fd35ff44200f7bdd5878353950eb Mon Sep 17 00:00:00 2001
From: Iago Cedran
Date: Tue, 5 Nov 2024 14:39:09 +0000
Subject: [PATCH 61/97] feat: translate
---
i18n/pt_BR.csv | 3 +++
1 file changed, 3 insertions(+)
diff --git a/i18n/pt_BR.csv b/i18n/pt_BR.csv
index 719c100..d6863ad 100644
--- a/i18n/pt_BR.csv
+++ b/i18n/pt_BR.csv
@@ -203,3 +203,6 @@ Payload,Payload
"You can only select up to %1 orders at a time.","Você só pode selecionar até %1 pedidos por vez."
"Invalid Form Key","Chave de Formulário Inválida"
"Error sending the payment link.","Erro ao enviar o link de pagamento."
+"Confirmation","Confirmação"
+"records","registros"
+"%1 records","%1 registros"
From 771f0999a403c93852dc4a172efb9b704117bde0 Mon Sep 17 00:00:00 2001
From: Thiago Contardi
Date: Wed, 30 Oct 2024 11:57:46 -0300
Subject: [PATCH 62/97] fix: remove await vindi fingerprint
---
.../templates/checkout/fingerprint.phtml | 2 +-
view/frontend/web/js/fingerprint.js | 20 ++++++++++++++-----
2 files changed, 16 insertions(+), 6 deletions(-)
diff --git a/view/frontend/templates/checkout/fingerprint.phtml b/view/frontend/templates/checkout/fingerprint.phtml
index de35ec9..3658aec 100644
--- a/view/frontend/templates/checkout/fingerprint.phtml
+++ b/view/frontend/templates/checkout/fingerprint.phtml
@@ -7,4 +7,4 @@
?>
-
+
diff --git a/view/frontend/web/js/fingerprint.js b/view/frontend/web/js/fingerprint.js
index 8df2863..c33f0cc 100644
--- a/view/frontend/web/js/fingerprint.js
+++ b/view/frontend/web/js/fingerprint.js
@@ -2,12 +2,22 @@ define(['uiComponent'], function (Component) {
'use strict';
return function (sandbox) {
if (typeof window.yapayFingerprintLoaded == 'undefined') {
- let fpOptions = {env: 'production'};
- if (parseInt(sandbox) === 1) {
- fpOptions.env = 'sandbox';
+ function loadScript() {
+ return new Promise((resolve) => {
+ let fpOptions = {env: 'production'};
+ if (parseInt(sandbox) === 1) {
+ fpOptions.env = 'sandbox';
+ }
+ window?.yapay?.FingerPrint(fpOptions);
+ //Wait 1 sec because it was blocking checkout page
+ setTimeout(() => {
+ resolve();
+ }, 1000);
+ });
}
- window?.yapay?.FingerPrint(fpOptions);
- window.yapayFingerprintLoaded = true;
+ loadScript().then(() => {
+ window.yapayFingerprintLoaded = true;
+ });
}
};
});
From 56883521f94872d986bf2e7979de9aa4aee887ef Mon Sep 17 00:00:00 2001
From: Thiago Contardi
Date: Wed, 30 Oct 2024 12:03:47 -0300
Subject: [PATCH 63/97] fix: remove await vindi fingerprint
---
.../templates/checkout/fingerprint.phtml | 2 +-
view/frontend/web/js/fingerprint.js | 23 +++++++------------
2 files changed, 9 insertions(+), 16 deletions(-)
diff --git a/view/frontend/templates/checkout/fingerprint.phtml b/view/frontend/templates/checkout/fingerprint.phtml
index 3658aec..376d7aa 100644
--- a/view/frontend/templates/checkout/fingerprint.phtml
+++ b/view/frontend/templates/checkout/fingerprint.phtml
@@ -7,4 +7,4 @@
?>
-
+
diff --git a/view/frontend/web/js/fingerprint.js b/view/frontend/web/js/fingerprint.js
index c33f0cc..70efed7 100644
--- a/view/frontend/web/js/fingerprint.js
+++ b/view/frontend/web/js/fingerprint.js
@@ -2,22 +2,15 @@ define(['uiComponent'], function (Component) {
'use strict';
return function (sandbox) {
if (typeof window.yapayFingerprintLoaded == 'undefined') {
- function loadScript() {
- return new Promise((resolve) => {
- let fpOptions = {env: 'production'};
- if (parseInt(sandbox) === 1) {
- fpOptions.env = 'sandbox';
- }
- window?.yapay?.FingerPrint(fpOptions);
- //Wait 1 sec because it was blocking checkout page
- setTimeout(() => {
- resolve();
- }, 1000);
- });
- }
- loadScript().then(() => {
+ async function loadScript() {
+ let fpOptions = {env: 'production'};
+ if (parseInt(sandbox) === 1) {
+ fpOptions.env = 'sandbox';
+ }
+ await window?.yapay?.FingerPrint(fpOptions);
window.yapayFingerprintLoaded = true;
- });
+ }
+ loadScript();
}
};
});
From 3f5d185cd1d5ce9354b9c2f837ed77dd9434f23a Mon Sep 17 00:00:00 2001
From: Iago Cedran
Date: Tue, 5 Nov 2024 18:51:40 +0000
Subject: [PATCH 64/97] feat: expire_at field
---
Api/Data/PaymentLinkInterface.php | 15 +++++++++++++++
Model/PaymentLink.php | 17 +++++++++++++++++
etc/db_schema.xml | 1 +
3 files changed, 33 insertions(+)
diff --git a/Api/Data/PaymentLinkInterface.php b/Api/Data/PaymentLinkInterface.php
index 8922cdf..d55302d 100644
--- a/Api/Data/PaymentLinkInterface.php
+++ b/Api/Data/PaymentLinkInterface.php
@@ -26,6 +26,7 @@ interface PaymentLinkInterface extends ExtensibleDataInterface
const CREATED_AT = 'created_at';
const STATUS = 'status';
+ const EXPIRED_AT = 'expired_at';
/**
* @return int
@@ -97,5 +98,19 @@ public function getStatus();
* @param string $status
*/
public function setStatus(string $status);
+
+ /**
+ * Get the expiration date of the payment link
+ *
+ * @return string|null
+ */
+ public function getExpiredAt();
+
+ /**
+ * Set the expiration date of the payment link
+ *
+ * @param string|null $expiredAt
+ */
+ public function setExpiredAt($expiredAt);
}
diff --git a/Model/PaymentLink.php b/Model/PaymentLink.php
index beaeb24..11fb861 100644
--- a/Model/PaymentLink.php
+++ b/Model/PaymentLink.php
@@ -157,4 +157,21 @@ public function setStatus(string $status)
$this->setData(self::STATUS, $status);
return $this;
}
+
+ /**
+ * @inheritdoc
+ */
+ public function getExpiredAt()
+ {
+ return $this->getData(self::EXPIRED_AT);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setExpiredAt($expiredAt)
+ {
+ $this->setData(self::EXPIRED_AT, $expiredAt);
+ return $this;
+ }
}
diff --git a/etc/db_schema.xml b/etc/db_schema.xml
index afcb129..cd73478 100644
--- a/etc/db_schema.xml
+++ b/etc/db_schema.xml
@@ -128,6 +128,7 @@
+
From 3a439fdf489ca8d56de7235fb309da6a812f6048 Mon Sep 17 00:00:00 2001
From: Iago Cedran
Date: Tue, 5 Nov 2024 19:15:44 +0000
Subject: [PATCH 65/97] update
---
.../RunCancelOrdersWithExpiredLinks.php | 67 +++++++++++++++++++
1 file changed, 67 insertions(+)
create mode 100644 Console/Command/RunCancelOrdersWithExpiredLinks.php
diff --git a/Console/Command/RunCancelOrdersWithExpiredLinks.php b/Console/Command/RunCancelOrdersWithExpiredLinks.php
new file mode 100644
index 0000000..8dbe410
--- /dev/null
+++ b/Console/Command/RunCancelOrdersWithExpiredLinks.php
@@ -0,0 +1,67 @@
+cancelOrdersWithExpiredLinks = $cancelOrdersWithExpiredLinks;
+ $this->logger = $logger;
+ parent::__construct();
+ }
+
+ /**
+ * Configure the command
+ */
+ protected function configure()
+ {
+ $this->setName('vindi:payment:cancel-orders-with-expired-links');
+ $this->setDescription('Manually run the cron to cancel orders with expired payment links');
+ parent::configure();
+ }
+
+ /**
+ * Execute the command
+ *
+ * @param InputInterface $input
+ * @param OutputInterface $output
+ * @return int
+ */
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ try {
+ $this->cancelOrdersWithExpiredLinks->execute();
+ $output->writeln('Orders with expired payment links have been canceled successfully.');
+ return Command::SUCCESS;
+ } catch (\Exception $e) {
+ $this->logger->error('Error while canceling orders with expired payment links: ' . $e->getMessage());
+ $output->writeln('An error occurred while canceling orders with expired payment links.');
+ return Command::FAILURE;
+ }
+ }
+}
From e5f0679adbfb64795f03c6d146c85740bffe7b52 Mon Sep 17 00:00:00 2001
From: Iago Cedran
Date: Tue, 5 Nov 2024 19:18:30 +0000
Subject: [PATCH 66/97] update
---
etc/di.xml | 1 +
1 file changed, 1 insertion(+)
diff --git a/etc/di.xml b/etc/di.xml
index ff0cb16..217a13a 100644
--- a/etc/di.xml
+++ b/etc/di.xml
@@ -40,6 +40,7 @@
- Vindi\VP\Console\Command\RunUpdateExpiredLinks
+ - Vindi\VP\Console\Command\RunCancelOrdersWithExpiredLinks
From 90dafe82c9a778cfa7e30fd5e134c0bdeba94ba6 Mon Sep 17 00:00:00 2001
From: Iago Cedran
Date: Tue, 5 Nov 2024 19:28:49 +0000
Subject: [PATCH 67/97] update
---
Console/Command/RunCancelOrdersWithExpiredLinks.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Console/Command/RunCancelOrdersWithExpiredLinks.php b/Console/Command/RunCancelOrdersWithExpiredLinks.php
index 8dbe410..d270d26 100644
--- a/Console/Command/RunCancelOrdersWithExpiredLinks.php
+++ b/Console/Command/RunCancelOrdersWithExpiredLinks.php
@@ -4,7 +4,7 @@
namespace Vindi\VP\Console\Command;
-use Vindi\Payment\Cron\CancelOrdersWithExpiredLinks;
+use Vindi\VP\Cron\CancelOrdersWithExpiredLinks;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
From f1aa52a719bda58b0e6159d736ea3ef958b3e34d Mon Sep 17 00:00:00 2001
From: Iago Cedran
Date: Tue, 5 Nov 2024 19:34:14 +0000
Subject: [PATCH 68/97] update
---
Console/Command/RunCancelOrdersWithExpiredLinks.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Console/Command/RunCancelOrdersWithExpiredLinks.php b/Console/Command/RunCancelOrdersWithExpiredLinks.php
index d270d26..4b70666 100644
--- a/Console/Command/RunCancelOrdersWithExpiredLinks.php
+++ b/Console/Command/RunCancelOrdersWithExpiredLinks.php
@@ -40,7 +40,7 @@ public function __construct(
*/
protected function configure()
{
- $this->setName('vindi:payment:cancel-orders-with-expired-links');
+ $this->setName('vindi:vp:cancel-orders-with-expired-links');
$this->setDescription('Manually run the cron to cancel orders with expired payment links');
parent::configure();
}
From 743fb061120b5138c07cedbfeb91cb5595385d6a Mon Sep 17 00:00:00 2001
From: Iago Cedran
Date: Fri, 8 Nov 2024 13:03:23 +0000
Subject: [PATCH 69/97] fix: processing payment link when canceling
---
Block/Adminhtml/Order/LinkField.php | 22 ++++
Observer/CancelOrderObserver.php | 75 +++++++++++++
etc/adminhtml/events.xml | 3 +
view/adminhtml/templates/link-field.phtml | 126 ++++++++++++----------
4 files changed, 169 insertions(+), 57 deletions(-)
create mode 100644 Observer/CancelOrderObserver.php
diff --git a/Block/Adminhtml/Order/LinkField.php b/Block/Adminhtml/Order/LinkField.php
index 2fb289d..1cf0b64 100644
--- a/Block/Adminhtml/Order/LinkField.php
+++ b/Block/Adminhtml/Order/LinkField.php
@@ -76,4 +76,26 @@ public function getPaymentMethod()
}
return null;
}
+
+ /**
+ * Check if the payment link status is "processed"
+ *
+ * @return bool
+ */
+ public function isLinkPaid(): bool
+ {
+ $paymentLink = $this->paymentLinkService->getPaymentLink($this->getOrderId());
+ return $paymentLink->getStatus() === 'processed';
+ }
+
+ /**
+ * Check if the payment link is expired
+ *
+ * @return bool
+ */
+ public function isLinkExpired(): bool
+ {
+ $paymentLink = $this->paymentLinkService->getPaymentLink($this->getOrderId());
+ return $paymentLink->getStatus() === 'expired';
+ }
}
diff --git a/Observer/CancelOrderObserver.php b/Observer/CancelOrderObserver.php
new file mode 100644
index 0000000..e9a2590
--- /dev/null
+++ b/Observer/CancelOrderObserver.php
@@ -0,0 +1,75 @@
+paymentLinkService = $paymentLinkService;
+ $this->orderRepository = $orderRepository;
+ $this->logger = $logger;
+ }
+
+ /**
+ * Execute observer to handle order cancellation
+ *
+ * @param Observer $observer
+ * @return void
+ */
+ public function execute(Observer $observer): void
+ {
+ try {
+ $order = $observer->getEvent()->getOrder();
+
+ if ($order->isCanceled()) {
+ $orderId = $order->getEntityId();
+
+ $paymentLink = $this->paymentLinkService->getPaymentLink($orderId);
+
+ if ($paymentLink && $paymentLink->getId()) {
+ if ($paymentLink->getStatus() !== 'processed') {
+ $paymentLink->setStatus('processed');
+ $this->paymentLinkService->savePaymentLink($paymentLink);
+
+ $this->logger->info(sprintf('Payment link for order ID %s has been updated to "processed".', $orderId));
+ }
+ }
+ }
+ } catch (\Exception $e) {
+ $this->logger->error('Error while updating payment link for canceled order: ' . $e->getMessage());
+ }
+ }
+}
+
diff --git a/etc/adminhtml/events.xml b/etc/adminhtml/events.xml
index a84ca6e..5d84355 100644
--- a/etc/adminhtml/events.xml
+++ b/etc/adminhtml/events.xml
@@ -19,4 +19,7 @@
+
+
+
diff --git a/view/adminhtml/templates/link-field.phtml b/view/adminhtml/templates/link-field.phtml
index bdc3314..d528ffc 100644
--- a/view/adminhtml/templates/link-field.phtml
+++ b/view/adminhtml/templates/link-field.phtml
@@ -7,70 +7,82 @@
$paymentLink = $block->getPaymentLink();
?>
getPaymentMethod(), $block::VINDI_PAYMENT_LINK)): ?>
-
-
+
-
+
From 8647ed23bb511593e55de0afb3ba57878466400b Mon Sep 17 00:00:00 2001
From: Iago Cedran
Date: Fri, 8 Nov 2024 15:18:37 +0000
Subject: [PATCH 70/97] fix: Adjust payment data displays if the order has
already been invoiced
---
.../templates/payment/info/bankslip.phtml | 17 --
.../templates/payment/info/bankslippix.phtml | 17 --
.../templates/payment/info/pix.phtml | 17 --
.../templates/payment/info/bankslip.phtml | 191 +++++++--------
.../templates/payment/info/bankslippix.phtml | 219 +++++++++---------
.../frontend/templates/payment/info/pix.phtml | 36 +--
6 files changed, 227 insertions(+), 270 deletions(-)
diff --git a/view/adminhtml/templates/payment/info/bankslip.phtml b/view/adminhtml/templates/payment/info/bankslip.phtml
index 00ddd7b..da5c0a5 100644
--- a/view/adminhtml/templates/payment/info/bankslip.phtml
+++ b/view/adminhtml/templates/payment/info/bankslip.phtml
@@ -125,23 +125,6 @@ $hasInvoice = $payment->getOrder()->hasInvoices();
-
-
- -
-
- $value):?>
-
- = $block->escapeHtml($label) ?> |
-
- = /* @noEscape */ nl2br($block->escapeHtml(
- implode("\n", $block->getValueAsArray($value, false)), ['a'])
- ) ?>
- |
-
-
-
-
-
= $block->getChildHtml() ?>
diff --git a/view/adminhtml/templates/payment/info/bankslippix.phtml b/view/adminhtml/templates/payment/info/bankslippix.phtml
index 6ddc988..69822c6 100644
--- a/view/adminhtml/templates/payment/info/bankslippix.phtml
+++ b/view/adminhtml/templates/payment/info/bankslippix.phtml
@@ -142,23 +142,6 @@ $hasInvoice = $payment->getOrder()->hasInvoices();
-
-
- -
-
- $value):?>
-
- = $block->escapeHtml($label) ?> |
-
- = /* @noEscape */ nl2br($block->escapeHtml(
- implode("\n", $block->getValueAsArray($value, false)), ['a'])
- ) ?>
- |
-
-
-
-
-
= $block->getChildHtml() ?>
diff --git a/view/adminhtml/templates/payment/info/pix.phtml b/view/adminhtml/templates/payment/info/pix.phtml
index 73c7614..5f82a75 100644
--- a/view/adminhtml/templates/payment/info/pix.phtml
+++ b/view/adminhtml/templates/payment/info/pix.phtml
@@ -42,23 +42,6 @@ $hasInvoice = $payment->getOrder()->hasInvoices();
-
-
-
-
-
- $value):?>
-
- = $block->escapeHtml($label) ?> |
-
- = /* @noEscape */ nl2br($block->escapeHtml(
- implode("\n", $block->getValueAsArray($value, false)), ['a'])
- ) ?>
- |
-
-
-
-
-
= $block->getChildHtml() ?>
diff --git a/view/frontend/templates/payment/info/bankslip.phtml b/view/frontend/templates/payment/info/bankslip.phtml
index 11728bd..0560261 100644
--- a/view/frontend/templates/payment/info/bankslip.phtml
+++ b/view/frontend/templates/payment/info/bankslip.phtml
@@ -25,112 +25,115 @@ $title = $block->escapeHtml($block->getMethod()->getTitle());
$payment = $block->getInfo();
$bankSlipUrl = $block->getBankSlipUrl();
$bankSlipNumber = $block->getBankSlipNumber();
+$hasInvoice = $payment->getOrder()->hasInvoices();
?>
-
= $block->escapeHtml($title) ?>
- -
-