diff --git a/README.md b/README.md index c530a21..23c450a 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ You can install OTP-designer-jquery via npm: You can also include OTP-designer-jquery directly from a CDN by adding the following script tag to your HTML file: ```HTML - + ``` ### Local Download diff --git a/dist/otpdesigner.js b/dist/otpdesigner.js index 90e9b15..6e1cca8 100644 --- a/dist/otpdesigner.js +++ b/dist/otpdesigner.js @@ -34,9 +34,28 @@ return /******/ (() => { // webpackBootstrap var ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default()((_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default())); // Module -___CSS_LOADER_EXPORT___.push([module.id, `input.otp-input { - width: 40px; - height: 40px +___CSS_LOADER_EXPORT___.push([module.id, `.otp-fake-input { + width: 50px; + height: 50px; + display: flex; + align-items: center; + justify-content: center; +} + +.otp-fake-input .otp-content { + font-size: 20px; + font-weight: 600; + color: #111; + padding-bottom: 1px; +} + +.otp-fake-input.otpdesigner__focus__ { + border: 2px solid #007bff; +} + +.realInput{ + position: absolute!important; + z-index: -2000!important; }`, ""]); // Exports /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___); @@ -552,14 +571,14 @@ const otpdesigner = function (options = {}, ...args) { else code = ""; if (code.length === data.settings.length) { for (let i = 0; i < code.length; i++) { - $('#'+optInputId + (i) + "_" + data.idSuffix).val(code[i]); + $('#'+optInputId + (i) + "_" + data.idSuffix).trigger('otp-written', [code[i]]); } collectOtpCode(data); } return results; }, focus: function (results, data) { - $('#'+optInputId + (data.settings.length - 1) + "_" + data.idSuffix).focus(); + $('#'+optInputId + (data.settings.length - 1) + "_" + data.idSuffix).otpdesigner__toggleFocus__(true); return results; }, hiddenInput: function (results, data) { @@ -603,11 +622,12 @@ const otpdesigner = function (options = {}, ...args) { }; //save the settings of the element $(this).data('otpdesigner', data); + $(this).attr('data-otpdesigner-id', idSuffix); - let $inputsParent = $('
'); - $inputsParent.attr('id', 'otp_' + idSuffix); + let $fakeInputsParent = $('
'); + $fakeInputsParent.attr('id', 'otp_' + idSuffix); if (settings.inputsParentClasses !== "") { - $inputsParent.addClass(settings.inputsParentClasses); + $fakeInputsParent.addClass(settings.inputsParentClasses); } let $hiddenInput = $(''); @@ -615,27 +635,73 @@ const otpdesigner = function (options = {}, ...args) { $hiddenInput.attr('name', 'otp_hidden_' + idSuffix); $hiddenInput.appendTo($parent); + let $realInput = $(''); + $realInput.attr('id', 'otp_real_' + idSuffix); + $realInput.attr('name', 'otp_real_' + idSuffix); + $realInput.appendTo($fakeInputsParent); + $realInput.on('input', function () { + let a = getRealInputValue(data); + resetRealInput(data); + getFocusedFakeInput(data).trigger('otp-written', [a]); + }); + $realInput.on('keydown', function (e) { + if (e.key === "ArrowLeft" || e.key === "ArrowRight" || e.key === "ArrowUp" || e.key === "ArrowDown" || e.key === "Tab") { + e.preventDefault(); + let focusedInp = getFocusedFakeInput(data); + let $inputs = $fakeInputsParent.find('.otp-fake-input'); + let index = $inputs.index(focusedInp); + if (e.key === "ArrowLeft" || e.key === "ArrowUp") { + if (index > 0) $($inputs[index - 1]).otpdesigner__toggleFocus__(true); + } + else { + if (index < $inputs.length - 1) $($inputs[index + 1]).otpdesigner__toggleFocus__(true); + } + } + }); + $realInput.on('paste', (event) => { + let pastedText = event.originalEvent.clipboardData.getData('text'); + event.preventDefault(); + let pastedTextParts = pastedText.split(''); + pastedTextParts = pastedTextParts.filter(function (value) { + return isAcceptedCharacter(value, settings.onlyNumbers); + }); + pastedText = pastedTextParts.join(''); + if (pastedText.length >= settings.length) { + pastedText = pastedText.substring(0, settings.length); + $('[data-otpdesigner-id="' + data.idSuffix + '"]').otpdesigner('set', pastedText); + } + }); + for (let i = 0; i < settings.length; i++) { - let $input = $(''); - $input.attr('id', optInputId + (i) + "_" + idSuffix); + let $fakeInput = $('
'); + $fakeInput.attr('id', optInputId + (i) + "_" + idSuffix); + $fakeInput[0].addEventListener('click', function () { + if (!$(this).otpdesigner__isFocused__()) { + $(this).otpdesigner__toggleFocus__(true); + } + else{ + toggleRealInputFocus(this, true); + } + }); + let type = "text"; if (settings.type === 'numeric') { type = "number"; } if (settings.inputsClasses !== "") { - $input.addClass(settings.inputsClasses); + $fakeInput.addClass(settings.inputsClasses); } - $input.appendTo($inputsParent); + $fakeInput.appendTo($fakeInputsParent); } - $inputsParent.appendTo($parent); - let $inputs = $inputsParent.find('.otp-input'); + $fakeInputsParent.appendTo($parent); + let $inputs = $fakeInputsParent.find('.otp-fake-input'); $inputs.each(function (i) { - $inputs[i].addEventListener("keydown", function (event) { - if (event.key === "Backspace") { - $inputs[i].value = ""; - if (i !== 0) $inputs[i - 1].focus(); - } else if (event.key === "Enter" && i === $inputs.length - 1 && $inputs[i].value !== "") { - event.preventDefault(); + $($inputs[i]).off('otp-written'); + $($inputs[i]).on("otp-written", function (event, value) { + if (value === "Backspace") { + $($inputs[i]).otpdesigner__fakeInputVal__(""); + if (i !== 0) $($inputs[i - 1]).otpdesigner__toggleFocus__(true); + } else if (value === "Enter" && i === $inputs.length - 1 && $($inputs[i]).otpdesigner__fakeInputVal__() !== "") { collectOtpCode(data, false); if (settings.enterClicked != null) { settings.enterClicked(); @@ -643,27 +709,21 @@ const otpdesigner = function (options = {}, ...args) { loseFocus(data); return; } else { - if ((event.keyCode > 95 && event.keyCode < 106) || (event.keyCode > 47 && event.keyCode < 58)) { - $inputs[i].value = event.key; - if (i !== $inputs.length - 1) $inputs[i + 1].focus(); - event.preventDefault(); - } else if (!settings.onlyNumbers && event.keyCode > 64 && event.keyCode < 91) { - $inputs[i].value = String.fromCharCode(event.keyCode); - if (i !== $inputs.length - 1) $inputs[i + 1].focus(); - event.preventDefault(); - } - else{ - event.preventDefault(); + if (isAcceptedCharacter(value, settings.onlyNumbers)) { + value = value.toLowerCase(); + $($inputs[i]).otpdesigner__fakeInputVal__(value); + if (i !== $inputs.length - 1) $($inputs[i + 1]).otpdesigner__toggleFocus__(true); } } - collectOtpCode(data); + collectOtpCode(data, (i === $inputs.length - 1)); }); - $inputs[i].addEventListener("focus", function () { + $($inputs[i]).off("focused"); + $($inputs[i]).on("focused", function () { if (!!$($inputs[i]).data('f')) return; for (let j = 0; j < i; j++) { - if ($inputs[j].value === "") { + if ($($inputs[j]).otpdesigner__fakeInputVal__() === "") { $($inputs[j]).data('f', "1"); - $($inputs[j]).focus(); + $($inputs[j]).otpdesigner__toggleFocus__(true); $($inputs[j]).removeData('f'); break; } @@ -689,10 +749,10 @@ let stringToBool = function (s) { } let collectOtpCode = (data, typingDone = true) => { - let $inputs = $('#otp_' + data.idSuffix).find('.otp-input'); + let $inputs = $('#otp_' + data.idSuffix).find('.otp-fake-input'); let code = ''; $inputs.each(function (i, e) { - code += $(e).val().trim(); + code += $(e).otpdesigner__fakeInputVal__().trim(); }); $('#otp_hidden_' + data.idSuffix).val(code); if (code.length === $inputs.length && typingDone) { @@ -705,8 +765,96 @@ let collectOtpCode = (data, typingDone = true) => { let loseFocus = (data) => { if (data.settings.enterClicked != null) return; - $('.otp-input:focus').blur(); + $('.otpdesigner__focus__').otpdesigner__toggleFocus__(false); }; + +$(document)[0].addEventListener('click', function(event) { + let $target = $(event.target); + let focused = $('.otp-fake-input'); + if( + !$target.closest('.otp-fake-input').length && + focused.length !== 0 && + focused.is(":visible") + ) { + focused.otpdesigner__toggleFocus__(false) + } +}); + +function onFakeInputFocused(ele) { + toggleRealInputFocus(ele, true); + $(ele).trigger('focused'); +} + +$.fn.otpdesigner__toggleFocus__ = function (toFocus = null) { + $(this).each(function () { + if (toFocus !== null) { + if (toFocus === true) { + $('.otpdesigner__focus__').removeClass('otpdesigner__focus__'); + $(this).addClass('otpdesigner__focus__'); + onFakeInputFocused(this); + } + else { + $(this).removeClass('otpdesigner__focus__'); + } + return; + } + if ($(this).hasClass('otpdesigner__focus__')) { + $(this).removeClass('otpdesigner__focus__'); + } + else { + $('.otpdesigner__focus__').removeClass('otpdesigner__focus__'); + $(this).addClass('otpdesigner__focus__'); + onFakeInputFocused(this); + } + }); +} + +function getFocusedFakeInput(data) { + return $('#otp_' + data.idSuffix).find('.otp-fake-input.otpdesigner__focus__'); +} + +$.fn.otpdesigner__isFocused__ = function () { + return $(this).hasClass('otpdesigner__focus__'); +} + +$.fn.otpdesigner__fakeInputVal__ = function (val = "RETURN_REQUESTED") { + if (val === "RETURN_REQUESTED") { + return $(this).find('.otp-content').html(); + } + $(this).find('.otp-content').html(val); +} + +function toggleRealInputFocus(fakeInput, toFocus) { + // noinspection JSCheckFunctionSignatures + let $realInput = $(fakeInput).parents('.fake-inputs').find('.realInput'); + if (toFocus) { + $realInput.focus(); + $realInput[0].setSelectionRange($realInput.val().length, $realInput.val().length); + } + else { + $realInput.blur(); + } +} + +function resetRealInput(data) { + let $realInput = $('#otp_' + data.idSuffix).find('.realInput'); + $realInput.val("-"); + $realInput[0].setSelectionRange($realInput.val().length, $realInput.val().length); +} + +function getRealInputValue(data) { + let $realInput = $('#otp_' + data.idSuffix).find('.realInput'); + if ($realInput.val() === "") return "Backspace"; + else if ($realInput.val() === "-\n") return "Enter"; + return $realInput.val().substring(1); +} + +const isAcceptedCharacter = (char, onlyNumbers) => { + return (otpdesigner__alphabets__.includes(char) && !onlyNumbers) || otpdesigner__numbers__.includes(char); +}; + +const otpdesigner__alphabets__ = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(""); +const otpdesigner__numbers__ = '0123456789'.split(""); ;// CONCATENATED MODULE: ./index.js diff --git a/dist/otpdesigner.min.js b/dist/otpdesigner.min.js index 24f79b3..e8a7424 100644 --- a/dist/otpdesigner.min.js +++ b/dist/otpdesigner.min.js @@ -4,4 +4,4 @@ * Released under the MIT License. * Github: github.com/HichemTab-tech/OTP-designer-jquery */ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports["OTP-designer-jquery"]=t():e["OTP-designer-jquery"]=t()}(this,(()=>(()=>{"use strict";var e={426:(e,t,n)=>{n.d(t,{Z:()=>s});var r=n(81),o=n.n(r),i=n(645),a=n.n(i)()(o());a.push([e.id,"input.otp-input {\n width: 40px;\n height: 40px\n}",""]);const s=a},645:e=>{e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var n="",r=void 0!==t[5];return t[4]&&(n+="@supports (".concat(t[4],") {")),t[2]&&(n+="@media ".concat(t[2]," {")),r&&(n+="@layer".concat(t[5].length>0?" ".concat(t[5]):""," {")),n+=e(t),r&&(n+="}"),t[2]&&(n+="}"),t[4]&&(n+="}"),n})).join("")},t.i=function(e,n,r,o,i){"string"==typeof e&&(e=[[null,e,void 0]]);var a={};if(r)for(var s=0;s0?" ".concat(d[5]):""," {").concat(d[1],"}")),d[5]=i),n&&(d[2]?(d[1]="@media ".concat(d[2]," {").concat(d[1],"}"),d[2]=n):d[2]=n),o&&(d[4]?(d[1]="@supports (".concat(d[4],") {").concat(d[1],"}"),d[4]=o):d[4]="".concat(o)),t.push(d))}},t}},81:e=>{e.exports=function(e){return e[1]}},379:e=>{var t=[];function n(e){for(var n=-1,r=0;r{var t={};e.exports=function(e,n){var r=function(e){if(void 0===t[e]){var n=document.querySelector(e);if(window.HTMLIFrameElement&&n instanceof window.HTMLIFrameElement)try{n=n.contentDocument.head}catch(e){n=null}t[e]=n}return t[e]}(e);if(!r)throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");r.appendChild(n)}},216:e=>{e.exports=function(e){var t=document.createElement("style");return e.setAttributes(t,e.attributes),e.insert(t,e.options),t}},565:(e,t,n)=>{e.exports=function(e){var t=n.nc;t&&e.setAttribute("nonce",t)}},795:e=>{e.exports=function(e){if("undefined"==typeof document)return{update:function(){},remove:function(){}};var t=e.insertStyleElement(e);return{update:function(n){!function(e,t,n){var r="";n.supports&&(r+="@supports (".concat(n.supports,") {")),n.media&&(r+="@media ".concat(n.media," {"));var o=void 0!==n.layer;o&&(r+="@layer".concat(n.layer.length>0?" ".concat(n.layer):""," {")),r+=n.css,o&&(r+="}"),n.media&&(r+="}"),n.supports&&(r+="}");var i=n.sourceMap;i&&"undefined"!=typeof btoa&&(r+="\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(i))))," */")),t.styleTagTransform(r,e,t.options)}(t,e,n)},remove:function(){!function(e){if(null===e.parentNode)return!1;e.parentNode.removeChild(e)}(t)}}}},589:e=>{e.exports=function(e,t){if(t.styleSheet)t.styleSheet.cssText=e;else{for(;t.firstChild;)t.removeChild(t.firstChild);t.appendChild(document.createTextNode(e))}}}},t={};function n(r){var o=t[r];if(void 0!==o)return o.exports;var i=t[r]={id:r,exports:{}};return e[r](i,i.exports,n),i.exports}n.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return n.d(t,{a:t}),t},n.d=(e,t)=>{for(var r in t)n.o(t,r)&&!n.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),n.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.nc=void 0;var r={};return(()=>{n.r(r),n.d(r,{otpdesigner:()=>y});var e=n(379),t=n.n(e),o=n(795),i=n.n(o),a=n(569),s=n.n(a),u=n(565),l=n.n(u),d=n(216),c=n.n(d),p=n(589),f=n.n(p),h=n(426),v={};v.styleTagTransform=f(),v.setAttributes=l(),v.insert=s().bind(null,"head"),v.domAPI=i(),v.insertStyleElement=c();t()(h.Z,v);h.Z&&h.Z.locals&&h.Z.locals;const y=function(e={},...t){const n="opt_input_";let r=[];const o={code:function(e,t){let n=$("#otp_hidden_"+t.idSuffix).val();return n=g(n)?n.trim():"",e.push({done:n.length===t.settings.length,code:n}),e},set:function(e,t,r){let o=r[0];if(o=g(o)?o.trim():"",o.length===t.settings.length){for(let e=0;e');o.attr("id","otp_"+a),""!==t.inputsParentClasses&&o.addClass(t.inputsParentClasses);let s=$('');s.attr("id","otp_hidden_"+a),s.attr("name","otp_hidden_"+a),s.appendTo(r);for(let e=0;e');r.attr("id",n+e+"_"+a);let i="text";"numeric"===t.type&&(i="number"),""!==t.inputsClasses&&r.addClass(t.inputsClasses),r.appendTo(o)}o.appendTo(r);let u=o.find(".otp-input");u.each((function(e){u[e].addEventListener("keydown",(function(n){if("Backspace"===n.key)u[e].value="",0!==e&&u[e-1].focus();else{if("Enter"===n.key&&e===u.length-1&&""!==u[e].value)return n.preventDefault(),x(i,!1),null!=t.enterClicked&&t.enterClicked(),void b(i);n.keyCode>95&&n.keyCode<106||n.keyCode>47&&n.keyCode<58?(u[e].value=n.key,e!==u.length-1&&u[e+1].focus(),n.preventDefault()):!t.onlyNumbers&&n.keyCode>64&&n.keyCode<91?(u[e].value=String.fromCharCode(n.keyCode),e!==u.length-1&&u[e+1].focus(),n.preventDefault()):n.preventDefault()}x(i)})),u[e].addEventListener("focus",(function(){if(!$(u[e]).data("f"))for(let t=0;t1?r:0===r.length?null:r[0]};let g=(e,t=!0)=>null!=e&&("string"!=typeof e||!t||""!==e),m=function(e){return["true","TRUE","1"].includes(e.toString())},x=(e,t=!0)=>{let n=$("#otp_"+e.idSuffix).find(".otp-input"),r="";n.each((function(e,t){r+=$(t).val().trim()})),$("#otp_hidden_"+e.idSuffix).val(r),r.length===n.length&&t&&(null!=e.settings.typingDone&&e.settings.typingDone(r),b(e))},b=e=>{null==e.settings.enterClicked&&$(".otp-input:focus").blur()};jQuery.fn.otpdesigner=y})(),r})())); \ No newline at end of file +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports["OTP-designer-jquery"]=e():t["OTP-designer-jquery"]=e()}(this,(()=>(()=>{"use strict";var t={426:(t,e,n)=>{n.d(e,{Z:()=>a});var o=n(81),r=n.n(o),i=n(645),s=n.n(i)()(r());s.push([t.id,".otp-fake-input {\n width: 50px;\n height: 50px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.otp-fake-input .otp-content {\n font-size: 20px;\n font-weight: 600;\n color: #111;\n padding-bottom: 1px;\n}\n\n.otp-fake-input.otpdesigner__focus__ {\n border: 2px solid #007bff;\n}\n\n.realInput{\n position: absolute!important;\n z-index: -2000!important;\n}",""]);const a=s},645:t=>{t.exports=function(t){var e=[];return e.toString=function(){return this.map((function(e){var n="",o=void 0!==e[5];return e[4]&&(n+="@supports (".concat(e[4],") {")),e[2]&&(n+="@media ".concat(e[2]," {")),o&&(n+="@layer".concat(e[5].length>0?" ".concat(e[5]):""," {")),n+=t(e),o&&(n+="}"),e[2]&&(n+="}"),e[4]&&(n+="}"),n})).join("")},e.i=function(t,n,o,r,i){"string"==typeof t&&(t=[[null,t,void 0]]);var s={};if(o)for(var a=0;a0?" ".concat(p[5]):""," {").concat(p[1],"}")),p[5]=i),n&&(p[2]?(p[1]="@media ".concat(p[2]," {").concat(p[1],"}"),p[2]=n):p[2]=n),r&&(p[4]?(p[1]="@supports (".concat(p[4],") {").concat(p[1],"}"),p[4]=r):p[4]="".concat(r)),e.push(p))}},e}},81:t=>{t.exports=function(t){return t[1]}},379:t=>{var e=[];function n(t){for(var n=-1,o=0;o{var e={};t.exports=function(t,n){var o=function(t){if(void 0===e[t]){var n=document.querySelector(t);if(window.HTMLIFrameElement&&n instanceof window.HTMLIFrameElement)try{n=n.contentDocument.head}catch(t){n=null}e[t]=n}return e[t]}(t);if(!o)throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");o.appendChild(n)}},216:t=>{t.exports=function(t){var e=document.createElement("style");return t.setAttributes(e,t.attributes),t.insert(e,t.options),e}},565:(t,e,n)=>{t.exports=function(t){var e=n.nc;e&&t.setAttribute("nonce",e)}},795:t=>{t.exports=function(t){if("undefined"==typeof document)return{update:function(){},remove:function(){}};var e=t.insertStyleElement(t);return{update:function(n){!function(t,e,n){var o="";n.supports&&(o+="@supports (".concat(n.supports,") {")),n.media&&(o+="@media ".concat(n.media," {"));var r=void 0!==n.layer;r&&(o+="@layer".concat(n.layer.length>0?" ".concat(n.layer):""," {")),o+=n.css,r&&(o+="}"),n.media&&(o+="}"),n.supports&&(o+="}");var i=n.sourceMap;i&&"undefined"!=typeof btoa&&(o+="\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(i))))," */")),e.styleTagTransform(o,t,e.options)}(e,t,n)},remove:function(){!function(t){if(null===t.parentNode)return!1;t.parentNode.removeChild(t)}(e)}}}},589:t=>{t.exports=function(t,e){if(e.styleSheet)e.styleSheet.cssText=t;else{for(;e.firstChild;)e.removeChild(e.firstChild);e.appendChild(document.createTextNode(t))}}}},e={};function n(o){var r=e[o];if(void 0!==r)return r.exports;var i=e[o]={id:o,exports:{}};return t[o](i,i.exports,n),i.exports}n.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return n.d(e,{a:e}),e},n.d=(t,e)=>{for(var o in e)n.o(e,o)&&!n.o(t,o)&&Object.defineProperty(t,o,{enumerable:!0,get:e[o]})},n.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),n.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.nc=void 0;var o={};return(()=>{n.r(o),n.d(o,{otpdesigner:()=>h});var t=n(379),e=n.n(t),r=n(795),i=n.n(r),s=n(569),a=n.n(s),u=n(565),l=n.n(u),p=n(216),d=n.n(p),c=n(589),f=n.n(c),_=n(426),g={};g.styleTagTransform=f(),g.setAttributes=l(),g.insert=a().bind(null,"head"),g.domAPI=i(),g.insertStyleElement=d();e()(_.Z,g);_.Z&&_.Z.locals&&_.Z.locals;const h=function(t={},...e){const n="opt_input_";let o=[];const r={code:function(t,e){let n=$("#otp_hidden_"+e.idSuffix).val();return n=v(n)?n.trim():"",t.push({done:n.length===e.settings.length,code:n}),t},set:function(t,e,o){let r=o[0];if(r=v(r)?r.trim():"",r.length===e.settings.length){for(let t=0;t');r.attr("id","otp_"+s),""!==e.inputsParentClasses&&r.addClass(e.inputsParentClasses);let a=$('');a.attr("id","otp_hidden_"+s),a.attr("name","otp_hidden_"+s),a.appendTo(o);let u=$('');u.attr("id","otp_real_"+s),u.attr("name","otp_real_"+s),u.appendTo(r),u.on("input",(function(){let t=function(t){let e=$("#otp_"+t.idSuffix).find(".realInput");if(""===e.val())return"Backspace";if("-\n"===e.val())return"Enter";return e.val().substring(1)}(i);!function(t){let e=$("#otp_"+t.idSuffix).find(".realInput");e.val("-"),e[0].setSelectionRange(e.val().length,e.val().length)}(i),k(i).trigger("otp-written",[t])})),u.on("keydown",(function(t){if("ArrowLeft"===t.key||"ArrowRight"===t.key||"ArrowUp"===t.key||"ArrowDown"===t.key||"Tab"===t.key){t.preventDefault();let e=k(i),n=r.find(".otp-fake-input"),o=n.index(e);"ArrowLeft"===t.key||"ArrowUp"===t.key?o>0&&$(n[o-1]).otpdesigner__toggleFocus__(!0):o{let n=t.originalEvent.clipboardData.getData("text");t.preventDefault();let o=n.split("");o=o.filter((function(t){return S(t,e.onlyNumbers)})),n=o.join(""),n.length>=e.length&&(n=n.substring(0,e.length),$('[data-otpdesigner-id="'+i.idSuffix+'"]').otpdesigner("set",n))}));for(let t=0;t
');o.attr("id",n+t+"_"+s),o[0].addEventListener("click",(function(){$(this).otpdesigner__isFocused__()?C(this,!0):$(this).otpdesigner__toggleFocus__(!0)}));let i="text";"numeric"===e.type&&(i="number"),""!==e.inputsClasses&&o.addClass(e.inputsClasses),o.appendTo(r)}r.appendTo(o);let l=r.find(".otp-fake-input");l.each((function(t){$(l[t]).off("otp-written"),$(l[t]).on("otp-written",(function(n,o){if("Backspace"===o)$(l[t]).otpdesigner__fakeInputVal__(""),0!==t&&$(l[t-1]).otpdesigner__toggleFocus__(!0);else{if("Enter"===o&&t===l.length-1&&""!==$(l[t]).otpdesigner__fakeInputVal__())return y(i,!1),null!=e.enterClicked&&e.enterClicked(),void x(i);S(o,e.onlyNumbers)&&(o=o.toLowerCase(),$(l[t]).otpdesigner__fakeInputVal__(o),t!==l.length-1&&$(l[t+1]).otpdesigner__toggleFocus__(!0))}y(i,t===l.length-1)})),$(l[t]).off("focused"),$(l[t]).on("focused",(function(){if(!$(l[t]).data("f"))for(let e=0;e1?o:0===o.length?null:o[0]};let v=(t,e=!0)=>null!=t&&("string"!=typeof t||!e||""!==t),m=function(t){return["true","TRUE","1"].includes(t.toString())},y=(t,e=!0)=>{let n=$("#otp_"+t.idSuffix).find(".otp-fake-input"),o="";n.each((function(t,e){o+=$(e).otpdesigner__fakeInputVal__().trim()})),$("#otp_hidden_"+t.idSuffix).val(o),o.length===n.length&&e&&(null!=t.settings.typingDone&&t.settings.typingDone(o),x(t))},x=t=>{null==t.settings.enterClicked&&$(".otpdesigner__focus__").otpdesigner__toggleFocus__(!1)};function b(t){C(t,!0),$(t).trigger("focused")}function k(t){return $("#otp_"+t.idSuffix).find(".otp-fake-input.otpdesigner__focus__")}function C(t,e){let n=$(t).parents(".fake-inputs").find(".realInput");e?(n.focus(),n[0].setSelectionRange(n.val().length,n.val().length)):n.blur()}$(document)[0].addEventListener("click",(function(t){let e=$(t.target),n=$(".otp-fake-input");!e.closest(".otp-fake-input").length&&0!==n.length&&n.is(":visible")&&n.otpdesigner__toggleFocus__(!1)})),$.fn.otpdesigner__toggleFocus__=function(t=null){$(this).each((function(){null===t?$(this).hasClass("otpdesigner__focus__")?$(this).removeClass("otpdesigner__focus__"):($(".otpdesigner__focus__").removeClass("otpdesigner__focus__"),$(this).addClass("otpdesigner__focus__"),b(this)):!0===t?($(".otpdesigner__focus__").removeClass("otpdesigner__focus__"),$(this).addClass("otpdesigner__focus__"),b(this)):$(this).removeClass("otpdesigner__focus__")}))},$.fn.otpdesigner__isFocused__=function(){return $(this).hasClass("otpdesigner__focus__")},$.fn.otpdesigner__fakeInputVal__=function(t="RETURN_REQUESTED"){if("RETURN_REQUESTED"===t)return $(this).find(".otp-content").html();$(this).find(".otp-content").html(t)};const S=(t,e)=>w.includes(t)&&!e||T.includes(t),w="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".split(""),T="0123456789".split("");jQuery.fn.otpdesigner=h})(),o})())); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index b03ee5d..7d40d37 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "otp-designer-jquery", - "version": "1.0.1", + "version": "2.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "otp-designer-jquery", - "version": "1.0.1", + "version": "2.0.0", "license": "MIT", "dependencies": { "jquery": "^3.7.0", diff --git a/package.json b/package.json index 92a30b5..a9dbdf2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "otp-designer-jquery", - "version": "1.0.2", + "version": "2.0.0", "description": "OTP-designer-jquery: Create custom OTP code inputs effortlessly! This jQuery plugin enables designers and developers to design visually appealing, secure OTP code input fields. Enjoy seamless integration and built-in validation events, ensuring a delightful user experience. Simplify OTP input implementation with ease.", "keywords": [ "jquery", diff --git a/src/otpdesigner.js b/src/otpdesigner.js index f12e529..a156c9b 100644 --- a/src/otpdesigner.js +++ b/src/otpdesigner.js @@ -21,14 +21,14 @@ export const otpdesigner = function (options = {}, ...args) { else code = ""; if (code.length === data.settings.length) { for (let i = 0; i < code.length; i++) { - $('#'+optInputId + (i) + "_" + data.idSuffix).val(code[i]); + $('#'+optInputId + (i) + "_" + data.idSuffix).trigger('otp-written', [code[i]]); } collectOtpCode(data); } return results; }, focus: function (results, data) { - $('#'+optInputId + (data.settings.length - 1) + "_" + data.idSuffix).focus(); + $('#'+optInputId + (data.settings.length - 1) + "_" + data.idSuffix).otpdesigner__toggleFocus__(true); return results; }, hiddenInput: function (results, data) { @@ -72,11 +72,12 @@ export const otpdesigner = function (options = {}, ...args) { }; //save the settings of the element $(this).data('otpdesigner', data); + $(this).attr('data-otpdesigner-id', idSuffix); - let $inputsParent = $('
'); - $inputsParent.attr('id', 'otp_' + idSuffix); + let $fakeInputsParent = $('
'); + $fakeInputsParent.attr('id', 'otp_' + idSuffix); if (settings.inputsParentClasses !== "") { - $inputsParent.addClass(settings.inputsParentClasses); + $fakeInputsParent.addClass(settings.inputsParentClasses); } let $hiddenInput = $(''); @@ -84,27 +85,73 @@ export const otpdesigner = function (options = {}, ...args) { $hiddenInput.attr('name', 'otp_hidden_' + idSuffix); $hiddenInput.appendTo($parent); + let $realInput = $(''); + $realInput.attr('id', 'otp_real_' + idSuffix); + $realInput.attr('name', 'otp_real_' + idSuffix); + $realInput.appendTo($fakeInputsParent); + $realInput.on('input', function () { + let a = getRealInputValue(data); + resetRealInput(data); + getFocusedFakeInput(data).trigger('otp-written', [a]); + }); + $realInput.on('keydown', function (e) { + if (e.key === "ArrowLeft" || e.key === "ArrowRight" || e.key === "ArrowUp" || e.key === "ArrowDown" || e.key === "Tab") { + e.preventDefault(); + let focusedInp = getFocusedFakeInput(data); + let $inputs = $fakeInputsParent.find('.otp-fake-input'); + let index = $inputs.index(focusedInp); + if (e.key === "ArrowLeft" || e.key === "ArrowUp") { + if (index > 0) $($inputs[index - 1]).otpdesigner__toggleFocus__(true); + } + else { + if (index < $inputs.length - 1) $($inputs[index + 1]).otpdesigner__toggleFocus__(true); + } + } + }); + $realInput.on('paste', (event) => { + let pastedText = event.originalEvent.clipboardData.getData('text'); + event.preventDefault(); + let pastedTextParts = pastedText.split(''); + pastedTextParts = pastedTextParts.filter(function (value) { + return isAcceptedCharacter(value, settings.onlyNumbers); + }); + pastedText = pastedTextParts.join(''); + if (pastedText.length >= settings.length) { + pastedText = pastedText.substring(0, settings.length); + $('[data-otpdesigner-id="' + data.idSuffix + '"]').otpdesigner('set', pastedText); + } + }); + for (let i = 0; i < settings.length; i++) { - let $input = $(''); - $input.attr('id', optInputId + (i) + "_" + idSuffix); + let $fakeInput = $('
'); + $fakeInput.attr('id', optInputId + (i) + "_" + idSuffix); + $fakeInput[0].addEventListener('click', function () { + if (!$(this).otpdesigner__isFocused__()) { + $(this).otpdesigner__toggleFocus__(true); + } + else{ + toggleRealInputFocus(this, true); + } + }); + let type = "text"; if (settings.type === 'numeric') { type = "number"; } if (settings.inputsClasses !== "") { - $input.addClass(settings.inputsClasses); + $fakeInput.addClass(settings.inputsClasses); } - $input.appendTo($inputsParent); + $fakeInput.appendTo($fakeInputsParent); } - $inputsParent.appendTo($parent); - let $inputs = $inputsParent.find('.otp-input'); + $fakeInputsParent.appendTo($parent); + let $inputs = $fakeInputsParent.find('.otp-fake-input'); $inputs.each(function (i) { - $inputs[i].addEventListener("keydown", function (event) { - if (event.key === "Backspace") { - $inputs[i].value = ""; - if (i !== 0) $inputs[i - 1].focus(); - } else if (event.key === "Enter" && i === $inputs.length - 1 && $inputs[i].value !== "") { - event.preventDefault(); + $($inputs[i]).off('otp-written'); + $($inputs[i]).on("otp-written", function (event, value) { + if (value === "Backspace") { + $($inputs[i]).otpdesigner__fakeInputVal__(""); + if (i !== 0) $($inputs[i - 1]).otpdesigner__toggleFocus__(true); + } else if (value === "Enter" && i === $inputs.length - 1 && $($inputs[i]).otpdesigner__fakeInputVal__() !== "") { collectOtpCode(data, false); if (settings.enterClicked != null) { settings.enterClicked(); @@ -112,27 +159,21 @@ export const otpdesigner = function (options = {}, ...args) { loseFocus(data); return; } else { - if ((event.keyCode > 95 && event.keyCode < 106) || (event.keyCode > 47 && event.keyCode < 58)) { - $inputs[i].value = event.key; - if (i !== $inputs.length - 1) $inputs[i + 1].focus(); - event.preventDefault(); - } else if (!settings.onlyNumbers && event.keyCode > 64 && event.keyCode < 91) { - $inputs[i].value = String.fromCharCode(event.keyCode); - if (i !== $inputs.length - 1) $inputs[i + 1].focus(); - event.preventDefault(); - } - else{ - event.preventDefault(); + if (isAcceptedCharacter(value, settings.onlyNumbers)) { + value = value.toLowerCase(); + $($inputs[i]).otpdesigner__fakeInputVal__(value); + if (i !== $inputs.length - 1) $($inputs[i + 1]).otpdesigner__toggleFocus__(true); } } - collectOtpCode(data); + collectOtpCode(data, (i === $inputs.length - 1)); }); - $inputs[i].addEventListener("focus", function () { + $($inputs[i]).off("focused"); + $($inputs[i]).on("focused", function () { if (!!$($inputs[i]).data('f')) return; for (let j = 0; j < i; j++) { - if ($inputs[j].value === "") { + if ($($inputs[j]).otpdesigner__fakeInputVal__() === "") { $($inputs[j]).data('f', "1"); - $($inputs[j]).focus(); + $($inputs[j]).otpdesigner__toggleFocus__(true); $($inputs[j]).removeData('f'); break; } @@ -158,10 +199,10 @@ let stringToBool = function (s) { } let collectOtpCode = (data, typingDone = true) => { - let $inputs = $('#otp_' + data.idSuffix).find('.otp-input'); + let $inputs = $('#otp_' + data.idSuffix).find('.otp-fake-input'); let code = ''; $inputs.each(function (i, e) { - code += $(e).val().trim(); + code += $(e).otpdesigner__fakeInputVal__().trim(); }); $('#otp_hidden_' + data.idSuffix).val(code); if (code.length === $inputs.length && typingDone) { @@ -174,5 +215,93 @@ let collectOtpCode = (data, typingDone = true) => { let loseFocus = (data) => { if (data.settings.enterClicked != null) return; - $('.otp-input:focus').blur(); -}; \ No newline at end of file + $('.otpdesigner__focus__').otpdesigner__toggleFocus__(false); +}; + +$(document)[0].addEventListener('click', function(event) { + let $target = $(event.target); + let focused = $('.otp-fake-input'); + if( + !$target.closest('.otp-fake-input').length && + focused.length !== 0 && + focused.is(":visible") + ) { + focused.otpdesigner__toggleFocus__(false) + } +}); + +function onFakeInputFocused(ele) { + toggleRealInputFocus(ele, true); + $(ele).trigger('focused'); +} + +$.fn.otpdesigner__toggleFocus__ = function (toFocus = null) { + $(this).each(function () { + if (toFocus !== null) { + if (toFocus === true) { + $('.otpdesigner__focus__').removeClass('otpdesigner__focus__'); + $(this).addClass('otpdesigner__focus__'); + onFakeInputFocused(this); + } + else { + $(this).removeClass('otpdesigner__focus__'); + } + return; + } + if ($(this).hasClass('otpdesigner__focus__')) { + $(this).removeClass('otpdesigner__focus__'); + } + else { + $('.otpdesigner__focus__').removeClass('otpdesigner__focus__'); + $(this).addClass('otpdesigner__focus__'); + onFakeInputFocused(this); + } + }); +} + +function getFocusedFakeInput(data) { + return $('#otp_' + data.idSuffix).find('.otp-fake-input.otpdesigner__focus__'); +} + +$.fn.otpdesigner__isFocused__ = function () { + return $(this).hasClass('otpdesigner__focus__'); +} + +$.fn.otpdesigner__fakeInputVal__ = function (val = "RETURN_REQUESTED") { + if (val === "RETURN_REQUESTED") { + return $(this).find('.otp-content').html(); + } + $(this).find('.otp-content').html(val); +} + +function toggleRealInputFocus(fakeInput, toFocus) { + // noinspection JSCheckFunctionSignatures + let $realInput = $(fakeInput).parents('.fake-inputs').find('.realInput'); + if (toFocus) { + $realInput.focus(); + $realInput[0].setSelectionRange($realInput.val().length, $realInput.val().length); + } + else { + $realInput.blur(); + } +} + +function resetRealInput(data) { + let $realInput = $('#otp_' + data.idSuffix).find('.realInput'); + $realInput.val("-"); + $realInput[0].setSelectionRange($realInput.val().length, $realInput.val().length); +} + +function getRealInputValue(data) { + let $realInput = $('#otp_' + data.idSuffix).find('.realInput'); + if ($realInput.val() === "") return "Backspace"; + else if ($realInput.val() === "-\n") return "Enter"; + return $realInput.val().substring(1); +} + +const isAcceptedCharacter = (char, onlyNumbers) => { + return (otpdesigner__alphabets__.includes(char) && !onlyNumbers) || otpdesigner__numbers__.includes(char); +}; + +const otpdesigner__alphabets__ = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(""); +const otpdesigner__numbers__ = '0123456789'.split(""); \ No newline at end of file diff --git a/src/style.css b/src/style.css index 088235b..514f91c 100644 --- a/src/style.css +++ b/src/style.css @@ -1,4 +1,23 @@ -input.otp-input { - width: 40px; - height: 40px +.otp-fake-input { + width: 50px; + height: 50px; + display: flex; + align-items: center; + justify-content: center; +} + +.otp-fake-input .otp-content { + font-size: 20px; + font-weight: 600; + color: #111; + padding-bottom: 1px; +} + +.otp-fake-input.otpdesigner__focus__ { + border: 2px solid #007bff; +} + +.realInput{ + position: absolute!important; + z-index: -2000!important; } \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index 0fe6e64..1a16c7a 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -7,7 +7,7 @@ const webpack = require("webpack"); // noinspection JSUnresolvedReference let bannerPlugin = new webpack.BannerPlugin({ banner: `/*! - * OTP-designer-jquery v1.0.2 + * OTP-designer-jquery v2.0.0 * (c) HichemTech * Released under the MIT License. * Github: github.com/HichemTab-tech/OTP-designer-jquery