snapotp-jquery
Version:
SnapOTP is a lightweight and customizable jQuery plugin to create elegant multi-input OTP fields with features like auto-focus, paste handling, keyboard navigation, and callbacks.
170 lines (146 loc) • 4.73 kB
JavaScript
/*!
* SnapOTP - Lightweight jQuery OTP Input Plugin
* Version: 1.0.3
* Author: Nitin Rathod
* Author URL: "https://github.com/nitinramrathod"
* Website: https://nitinramrathod.github.io/snapotp-jquery
* License: MIT
* Description: A simple, customizable jQuery plugin to create elegant multi-input OTP fields with auto-focus, paste handling, keyboard navigation, and callbacks.
* Repository: https://github.com/nitinramrathod/snapotp-jquery
* Released: 2025-05-22
*/
(function ($) {
$.fn.snapOTP = function (options) {
const settings = $.extend(
{
digits: 6,
onComplete: function (code) {},
onChange: function () {},
onEnter: function () {},
containerClass: "",
inputClass: "",
type: "text",
style: "box",
},
options
);
this.each(function () {
const $container = $(this);
$container.addClass(`snap-otp-container ${settings?.containerClass}`);
$container.empty();
for (let i = 0; i < settings.digits; i++) {
$container.append(
`<input type="${settings?.type}" maxlength="2" data-style="${settings?.style}" class="snap-otp-input ${settings.inputClass}" />`
);
}
const $inputs = $container.find(".snap-otp-input");
$inputs.on("input", function (e) {
const $this = $(this);
const value = $this.val();
const index = $inputs.index(this);
if (value.length === 1 && index < settings.digits - 1) {
$inputs.eq(index + 1).focus();
}
if (value.length > 1) {
const lastChar = value.slice(-1); // get last character typed
$this.val(lastChar); // update only current field
if (index < $inputs.length - 1) {
$inputs.eq(index + 1).focus(); // just focus next, no value update
}
}
triggerChange();
checkComplete();
});
$inputs.on("paste", function (e) {
const pasteData = (e.originalEvent || e).clipboardData
.getData("text")
.trim();
e.preventDefault();
const chars = pasteData.split("");
const startIndex = $inputs.index(this);
for (
let i = 0;
i < chars.length && startIndex + i < $inputs.length;
i++
) {
$inputs.eq(startIndex + i).val(chars[i]);
}
const nextIndex = startIndex + chars.length;
if (nextIndex < $inputs.length) {
$inputs.eq(nextIndex).focus();
} else {
$inputs.last().focus();
}
triggerChange();
checkComplete();
});
$inputs.on("keydown", function (e) {
const index = $inputs.index(this);
if (e.key === "Backspace") {
if ($(this).val() === "" && index > 0) {
$inputs
.eq(index - 1)
.val("")
.focus();
}
} else if (e.key === "ArrowLeft") {
if (index > 0) {
const prev = $inputs.eq(index - 1);
prev.focus();
setCursorToEnd(prev[0]);
}
} else if (e.key === "ArrowRight") {
if (index < $inputs.length - 1) {
const next = $inputs.eq(index + 1);
next.focus();
setCursorToEnd(next[0]);
}
} else if (e.key === "Enter") {
settings.onEnter(getValue());
}
});
function setCursorToEnd(input) {
setTimeout(() => {
const length = input.value.length;
if (settings?.type === "text") {
input.setSelectionRange(1, 1);
}
}, 10);
}
function getValue() {
return $inputs
.map(function () {
return $(this).val();
})
.get()
.join("");
}
function checkComplete() {
const value = getValue();
if (value.length === settings.digits && !value.includes("")) {
settings.onComplete(value);
}
}
function triggerChange() {
settings.onChange(getValue());
}
// Reset method
$container[0].resetSnapOTP = function () {
$inputs.val("");
$inputs.first().focus();
};
$inputs.first().focus();
});
return this;
};
$.fn.getValue = function () {
const $container = this;
const $inputs = $container.find(".snap-otp-input");
return $inputs
.map(function () {
return $(this).val();
})
.get()
.join("");
};
})(jQuery);