'use strict'; /** * Construct a new Cleave instance by passing the configuration object * * @param {String | HTMLElement} element * @param {Object} opts */ var Cleave = function (element, opts) { var owner = this; var hasMultipleElements = false; if (typeof element === 'string') { owner.element = document.querySelector(element); hasMultipleElements = document.querySelectorAll(element).length > 1; } else { if (typeof element.length !== 'undefined' && element.length > 0) { owner.element = element[0]; hasMultipleElements = element.length > 1; } else { owner.element = element; } } if (!owner.element) { throw new Error('[cleave.js] Please check the element'); } if (hasMultipleElements) { try { // eslint-disable-next-line console.warn('[cleave.js] Multiple input fields matched, cleave.js will only take the first one.'); } catch (e) { // Old IE } } opts.initValue = owner.element.value; owner.properties = Cleave.DefaultProperties.assign({}, opts); owner.init(); }; Cleave.prototype = { init: function () { var owner = this, pps = owner.properties; // no need to use this lib if (!pps.numeral && !pps.phone && !pps.creditCard && !pps.time && !pps.date && (pps.blocksLength === 0 && !pps.prefix)) { owner.onInput(pps.initValue); return; } pps.maxLength = Cleave.Util.getMaxLength(pps.blocks); owner.isAndroid = Cleave.Util.isAndroid(); owner.lastInputValue = ''; owner.isBackward = ''; owner.onChangeListener = owner.onChange.bind(owner); owner.onKeyDownListener = owner.onKeyDown.bind(owner); owner.onFocusListener = owner.onFocus.bind(owner); owner.onCutListener = owner.onCut.bind(owner); owner.onCopyListener = owner.onCopy.bind(owner); owner.initSwapHiddenInput(); owner.element.addEventListener('input', owner.onChangeListener); owner.element.addEventListener('keydown', owner.onKeyDownListener); owner.element.addEventListener('focus', owner.onFocusListener); owner.element.addEventListener('cut', owner.onCutListener); owner.element.addEventListener('copy', owner.onCopyListener); owner.initPhoneFormatter(); owner.initDateFormatter(); owner.initTimeFormatter(); owner.initNumeralFormatter(); // avoid touch input field if value is null // otherwise Firefox will add red box-shadow for if (pps.initValue || (pps.prefix && !pps.noImmediatePrefix)) { owner.onInput(pps.initValue); } }, initSwapHiddenInput: function () { var owner = this, pps = owner.properties; if (!pps.swapHiddenInput) return; var inputFormatter = owner.element.cloneNode(true); owner.element.parentNode.insertBefore(inputFormatter, owner.element); owner.elementSwapHidden = owner.element; owner.elementSwapHidden.type = 'hidden'; owner.element = inputFormatter; owner.element.id = ''; }, initNumeralFormatter: function () { var owner = this, pps = owner.properties; if (!pps.numeral) { return; } pps.numeralFormatter = new Cleave.NumeralFormatter( pps.numeralDecimalMark, pps.numeralIntegerScale, pps.numeralDecimalScale, pps.numeralThousandsGroupStyle, pps.numeralPositiveOnly, pps.stripLeadingZeroes, pps.prefix, pps.signBeforePrefix, pps.tailPrefix, pps.delimiter ); }, initTimeFormatter: function() { var owner = this, pps = owner.properties; if (!pps.time) { return; } pps.timeFormatter = new Cleave.TimeFormatter(pps.timePattern, pps.timeFormat); pps.blocks = pps.timeFormatter.getBlocks(); pps.blocksLength = pps.blocks.length; pps.maxLength = Cleave.Util.getMaxLength(pps.blocks); }, initDateFormatter: function () { var owner = this, pps = owner.properties; if (!pps.date) { return; } pps.dateFormatter = new Cleave.DateFormatter(pps.datePattern, pps.dateMin, pps.dateMax); pps.blocks = pps.dateFormatter.getBlocks(); pps.blocksLength = pps.blocks.length; pps.maxLength = Cleave.Util.getMaxLength(pps.blocks); }, initPhoneFormatter: function () { var owner = this, pps = owner.properties; if (!pps.phone) { return; } // Cleave.AsYouTypeFormatter should be provided by // external google closure lib try { pps.phoneFormatter = new Cleave.PhoneFormatter( new pps.root.Cleave.AsYouTypeFormatter(pps.phoneRegionCode), pps.delimiter ); } catch (ex) { throw new Error('[cleave.js] Please include phone-type-formatter.{country}.js lib'); } }, onKeyDown: function (event) { var owner = this, charCode = event.which || event.keyCode; owner.lastInputValue = owner.element.value; owner.isBackward = charCode === 8; }, onChange: function (event) { var owner = this, pps = owner.properties, Util = Cleave.Util; owner.isBackward = owner.isBackward || event.inputType === 'deleteContentBackward'; var postDelimiter = Util.getPostDelimiter(owner.lastInputValue, pps.delimiter, pps.delimiters); if (owner.isBackward && postDelimiter) { pps.postDelimiterBackspace = postDelimiter; } else { pps.postDelimiterBackspace = false; } this.onInput(this.element.value); }, onFocus: function () { var owner = this, pps = owner.properties; owner.lastInputValue = owner.element.value; if (pps.prefix && pps.noImmediatePrefix && !owner.element.value) { this.onInput(pps.prefix); } Cleave.Util.fixPrefixCursor(owner.element, pps.prefix, pps.delimiter, pps.delimiters); }, onCut: function (e) { if (!Cleave.Util.checkFullSelection(this.element.value)) return; this.copyClipboardData(e); this.onInput(''); }, onCopy: function (e) { if (!Cleave.Util.checkFullSelection(this.element.value)) return; this.copyClipboardData(e); }, copyClipboardData: function (e) { var owner = this, pps = owner.properties, Util = Cleave.Util, inputValue = owner.element.value, textToCopy = ''; if (!pps.copyDelimiter) { textToCopy = Util.stripDelimiters(inputValue, pps.delimiter, pps.delimiters); } else { textToCopy = inputValue; } try { if (e.clipboardData) { e.clipboardData.setData('Text', textToCopy); } else { window.clipboardData.setData('Text', textToCopy); } e.preventDefault(); } catch (ex) { // empty } }, onInput: function (value) { var owner = this, pps = owner.properties, Util = Cleave.Util; // case 1: delete one more character "4" // 1234*| -> hit backspace -> 123| // case 2: last character is not delimiter which is: // 12|34* -> hit backspace -> 1|34* // note: no need to apply this for numeral mode var postDelimiterAfter = Util.getPostDelimiter(value, pps.delimiter, pps.delimiters); if (!pps.numeral && pps.postDelimiterBackspace && !postDelimiterAfter) { value = Util.headStr(value, value.length - pps.postDelimiterBackspace.length); } // phone formatter if (pps.phone) { if (pps.prefix && (!pps.noImmediatePrefix || value.length)) { pps.result = pps.prefix + pps.phoneFormatter.format(value).slice(pps.prefix.length); } else { pps.result = pps.phoneFormatter.format(value); } owner.updateValueState(); return; } // numeral formatter if (pps.numeral) { // Do not show prefix when noImmediatePrefix is specified // This mostly because we need to show user the native input placeholder if (pps.prefix && pps.noImmediatePrefix && value.length === 0) { pps.result = ''; } else { pps.result = pps.numeralFormatter.format(value); } owner.updateValueState(); return; } // date if (pps.date) { value = pps.dateFormatter.getValidatedDate(value); } // time if (pps.time) { value = pps.timeFormatter.getValidatedTime(value); } // strip delimiters value = Util.stripDelimiters(value, pps.delimiter, pps.delimiters); // strip prefix value = Util.getPrefixStrippedValue(value, pps.prefix, pps.prefixLength, pps.result, pps.delimiter, pps.delimiters, pps.noImmediatePrefix, pps.tailPrefix, pps.signBeforePrefix); // strip non-numeric characters value = pps.numericOnly ? Util.strip(value, /[^\d]/g) : value; // convert case value = pps.uppercase ? value.toUpperCase() : value; value = pps.lowercase ? value.toLowerCase() : value; // prevent from showing prefix when no immediate option enabled with empty input value if (pps.prefix) { if (pps.tailPrefix) { value = value + pps.prefix; } else { value = pps.prefix + value; } // no blocks specified, no need to do formatting if (pps.blocksLength === 0) { pps.result = value; owner.updateValueState(); return; } } // update credit card props if (pps.creditCard) { owner.updateCreditCardPropsByValue(value); } // strip over length characters value = Util.headStr(value, pps.maxLength); // apply blocks pps.result = Util.getFormattedValue( value, pps.blocks, pps.blocksLength, pps.delimiter, pps.delimiters, pps.delimiterLazyShow ); owner.updateValueState(); }, updateCreditCardPropsByValue: function (value) { var owner = this, pps = owner.properties, Util = Cleave.Util, creditCardInfo; // At least one of the first 4 characters has changed if (Util.headStr(pps.result, 4) === Util.headStr(value, 4)) { return; } creditCardInfo = Cleave.CreditCardDetector.getInfo(value, pps.creditCardStrictMode); pps.blocks = creditCardInfo.blocks; pps.blocksLength = pps.blocks.length; pps.maxLength = Util.getMaxLength(pps.blocks); // credit card type changed if (pps.creditCardType !== creditCardInfo.type) { pps.creditCardType = creditCardInfo.type; pps.onCreditCardTypeChanged.call(owner, pps.creditCardType); } }, updateValueState: function () { var owner = this, Util = Cleave.Util, pps = owner.properties; if (!owner.element) { return; } var endPos = owner.element.selectionEnd; var oldValue = owner.element.value; var newValue = pps.result; endPos = Util.getNextCursorPosition(endPos, oldValue, newValue, pps.delimiter, pps.delimiters); // fix Android browser type="text" input field // cursor not jumping issue if (owner.isAndroid) { window.setTimeout(function () { owner.element.value = newValue; Util.setSelection(owner.element, endPos, pps.document, false); owner.callOnValueChanged(); }, 1); return; } owner.element.value = newValue; if (pps.swapHiddenInput) owner.elementSwapHidden.value = owner.getRawValue(); Util.setSelection(owner.element, endPos, pps.document, false); owner.callOnValueChanged(); }, callOnValueChanged: function () { var owner = this, pps = owner.properties; pps.onValueChanged.call(owner, { target: { name: owner.element.name, value: pps.result, rawValue: owner.getRawValue() } }); }, setPhoneRegionCode: function (phoneRegionCode) { var owner = this, pps = owner.properties; pps.phoneRegionCode = phoneRegionCode; owner.initPhoneFormatter(); owner.onChange(); }, setRawValue: function (value) { var owner = this, pps = owner.properties; value = value !== undefined && value !== null ? value.toString() : ''; if (pps.numeral) { value = value.replace('.', pps.numeralDecimalMark); } pps.postDelimiterBackspace = false; owner.element.value = value; owner.onInput(value); }, getRawValue: function () { var owner = this, pps = owner.properties, Util = Cleave.Util, rawValue = owner.element.value; if (pps.rawValueTrimPrefix) { rawValue = Util.getPrefixStrippedValue(rawValue, pps.prefix, pps.prefixLength, pps.result, pps.delimiter, pps.delimiters, pps.noImmediatePrefix, pps.tailPrefix, pps.signBeforePrefix); } if (pps.numeral) { rawValue = pps.numeralFormatter.getRawValue(rawValue); } else { rawValue = Util.stripDelimiters(rawValue, pps.delimiter, pps.delimiters); } return rawValue; }, getISOFormatDate: function () { var owner = this, pps = owner.properties; return pps.date ? pps.dateFormatter.getISOFormatDate() : ''; }, getISOFormatTime: function () { var owner = this, pps = owner.properties; return pps.time ? pps.timeFormatter.getISOFormatTime() : ''; }, getFormattedValue: function () { return this.element.value; }, destroy: function () { var owner = this; owner.element.removeEventListener('input', owner.onChangeListener); owner.element.removeEventListener('keydown', owner.onKeyDownListener); owner.element.removeEventListener('focus', owner.onFocusListener); owner.element.removeEventListener('cut', owner.onCutListener); owner.element.removeEventListener('copy', owner.onCopyListener); }, toString: function () { return '[Cleave Object]'; } }; Cleave.NumeralFormatter = require('../src/shortcuts/NumeralFormatter'); Cleave.DateFormatter = require('../src/shortcuts/DateFormatter'); Cleave.TimeFormatter = require('../src/shortcuts/TimeFormatter'); Cleave.PhoneFormatter = require('../src/shortcuts/PhoneFormatter'); Cleave.CreditCardDetector = require('../src/shortcuts/CreditCardDetector'); Cleave.Util = require('../src/utils/Util'); Cleave.DefaultProperties = require('../src/common/DefaultProperties'); // for angular directive ((typeof global === 'object' && global) ? global : window)['Cleave'] = Cleave; // CommonJS module.exports = Cleave;