This commit is contained in:
2025-07-23 16:57:31 +03:30
parent 96b0d450b6
commit ffe1d06893
4701 changed files with 449700 additions and 367 deletions
+190
View File
@@ -0,0 +1,190 @@
import { defineComponent as w, ref as f, computed as K, watch as T, onMounted as $, openBlock as c, createElementBlock as h, createElementVNode as F, normalizeClass as M, createCommentVNode as j, Fragment as R, renderList as N, createBlock as W } from "vue";
const H = { style: { display: "flex", "align-items": "center" } }, S = ["type", "inputmode", "placeholder", "disabled", "maxlength", "value"], V = { key: 0 }, q = ["innerHTML"], z = /* @__PURE__ */ w({
__name: "single-otp-input",
props: {
inputType: { default: "tel" },
inputmode: { default: "numeric" },
value: { default: "" },
separator: { default: "" },
focus: { type: Boolean, default: !1 },
inputClasses: { default: "" },
conditionalClass: { default: "" },
shouldAutoFocus: { type: Boolean, default: !1 },
isLastChild: { type: Boolean, default: !1 },
placeholder: { default: "" },
isDisabled: { type: Boolean, default: !1 }
},
emits: ["on-change", "on-keydown", "on-paste", "on-focus", "on-blur"],
setup(b, { emit: I }) {
const s = b, l = I, i = f(s.value || ""), a = f(null), n = (e) => {
const u = e.target.value;
return u && u.trim().length > 1 ? (e.clipboardData = {
getData: () => u.trim()
}, l("on-paste", e)) : l("on-change", u);
}, v = (e) => e >= 65 && e <= 90, D = (e) => e >= 48 && e <= 57 || e >= 96 && e <= 105, k = (e) => {
s.isDisabled && e.preventDefault();
const u = e || window.event, r = u.which ? u.which : u.keyCode;
D(r) || s.inputType === "letter-numeric" && v(r) || [8, 9, 13, 37, 39, 46, 86].includes(r) ? l("on-keydown", e) : u.preventDefault();
}, m = (e) => l("on-paste", e), d = () => (a.value.select(), l("on-focus")), y = () => l("on-blur"), g = K(
() => ["letter-numeric", "number"].includes(s.inputType) ? "text" : s.inputType
);
return T(
() => s.value,
(e, u) => {
e !== u && (i.value = e);
}
), T(
() => s.focus,
(e, u) => {
u !== e && a.value && s.focus && (a.value.focus(), a.value.select());
}
), $(() => {
a.value && s.focus && s.shouldAutoFocus && (a.value.focus(), a.value.select());
}), (e, u) => (c(), h("div", H, [
F("input", {
"data-test": "single-input",
type: g.value,
inputmode: e.inputmode,
placeholder: e.placeholder,
disabled: e.isDisabled,
ref_key: "input",
ref: a,
min: "0",
max: "9",
maxlength: e.isLastChild ? 1 : void 0,
pattern: "[0-9]",
value: i.value,
class: M([e.inputClasses, e.conditionalClass, { "is-complete": i.value }]),
onInput: n,
onKeydown: k,
onPaste: m,
onFocus: d,
onBlur: y
}, null, 42, S),
!e.isLastChild && e.separator ? (c(), h("span", V, [
F("span", { innerHTML: e.separator }, null, 8, q)
])) : j("", !0)
]));
}
}), G = {
style: { display: "flex" },
class: "otp-input-container"
}, J = {
key: 0,
autocomplete: "off",
name: "hidden",
type: "text",
style: { display: "none" }
}, Q = 8, U = 37, X = 39, Y = 46, x = /* @__PURE__ */ w({
__name: "vue3-otp-input",
props: {
value: { default: "" },
numInputs: { default: 4 },
separator: { default: "" },
inputClasses: { default: "" },
conditionalClass: { default: () => [] },
inputType: {},
inputmode: { default: "text" },
shouldAutoFocus: { type: Boolean, default: !1 },
placeholder: { default: () => [] },
isDisabled: { type: Boolean, default: !1 },
shouldFocusOrder: { type: Boolean }
},
emits: ["update:value", "on-change", "on-complete"],
setup(b, { expose: I, emit: s }) {
const l = b, i = s, a = f(0), n = f([]), v = f([]);
T(
() => l.value,
(t) => {
if (t.length === l.numInputs || n.value.length === 0) {
const o = t.split("");
n.value = o;
}
},
{ immediate: !0 }
);
const D = (t) => {
a.value = t;
}, k = () => {
a.value = -1;
}, m = () => n.value.join("").length === l.numInputs ? (i("update:value", n.value.join("")), i("on-complete", n.value.join(""))) : "Wait until the user enters the required number of characters", d = (t) => {
a.value = Math.max(Math.min(l.numInputs - 1, t), 0);
}, y = () => {
d(a.value + 1);
}, g = () => {
d(a.value - 1);
}, e = (t) => {
v.value = Object.assign([], n.value), n.value[a.value] = t.toString(), v.value.join("") !== n.value.join("") && (i("update:value", n.value.join("")), i("on-change", n.value.join("")), m());
}, u = (t) => {
t.preventDefault();
const o = t.clipboardData.getData("text/plain").slice(0, l.numInputs - a.value).split("");
if (l.inputType === "number" && !o.join("").match(/^\d+$/) || l.inputType === "letter-numeric" && !o.join("").match(/^\w+$/))
return "Invalid pasted data";
const p = n.value.slice(0, a.value).concat(o);
return p.slice(0, l.numInputs).forEach(function(O, C) {
n.value[C] = O;
}), d(p.slice(0, l.numInputs).length), m();
}, r = (t) => {
e(t), y();
}, A = () => {
n.value.length > 0 && (i("update:value", ""), i("on-change", "")), n.value = [], a.value = 0;
}, E = (t) => {
const o = t.split("");
o.length === l.numInputs && (n.value = o, i("update:value", n.value.join("")), i("on-complete", n.value.join("")));
}, L = (t, o) => {
switch (t.keyCode) {
case Q:
t.preventDefault(), e(""), g();
break;
case Y:
t.preventDefault(), e("");
break;
case U:
t.preventDefault(), g();
break;
case X:
t.preventDefault(), y();
break;
default:
_(o);
break;
}
}, _ = (t) => {
l.shouldFocusOrder && setTimeout(() => {
const o = n.value.join("").length;
t - o >= 0 && (a.value = o, n.value[t] = "");
}, 100);
};
return I({
clearInput: A,
fillInput: E
}), (t, o) => (c(), h("div", G, [
t.inputType === "password" ? (c(), h("input", J)) : j("", !0),
(c(!0), h(R, null, N(t.numInputs, (P, p) => {
var O, C;
return c(), W(z, {
key: p,
focus: a.value === p,
value: n.value[p],
separator: t.separator,
"input-type": t.inputType,
inputmode: t.inputmode,
"input-classes": t.inputClasses,
conditionalClass: (O = t.conditionalClass) == null ? void 0 : O[p],
"is-last-child": p === t.numInputs - 1,
"should-auto-focus": t.shouldAutoFocus,
placeholder: (C = t.placeholder) == null ? void 0 : C[p],
"is-disabled": t.isDisabled,
onOnChange: r,
onOnKeydown: (B) => L(B, p),
onOnPaste: u,
onOnFocus: (B) => D(p),
onOnBlur: k
}, null, 8, ["focus", "value", "separator", "input-type", "inputmode", "input-classes", "conditionalClass", "is-last-child", "should-auto-focus", "placeholder", "is-disabled", "onOnKeydown", "onOnFocus"]);
}), 128))
]));
}
});
export {
x as default
};
+74
View File
@@ -0,0 +1,74 @@
{
"name": "vue3-otp-input",
"version": "0.5.30",
"description": "A fully customizable, OTP (one-time password) input component built with Vue 3.x and Vue Composition API.",
"author": "Ejiro Asiuwhu <ejiroasiuwhu10@gmail.com>",
"repository": "https://github.com/ejirocodes/vue3-otp-input",
"bugs": {
"url": "https://github.com/ejirocodes/vue3-otp-input/issues"
},
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc && vite build",
"preview": "vite preview",
"test:unit": "vitest",
"build-only": "vite build",
"type-check": "vue-tsc --build --force"
},
"main": "dist/vue3-otp-input.umd.cjs",
"module": "dist/vue3-otp-input.js",
"browser": "dist/vue3-otp-input.js",
"unpkg": "dist/vue3-otp-input.min.js",
"files": [
"dist/*"
],
"types": "dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/vue3-otp-input.js",
"require": "./dist/vue3-otp-input.umd.cjs"
}
},
"peerDependencies": {
"vue": "^3.0.*"
},
"dependencies": {
"vue": "^3.4.27"
},
"devDependencies": {
"@types/jsdom": "^21.1.6",
"@types/node": "^20.12.13",
"@vitejs/plugin-vue": "^5.0.4",
"@vue/test-utils": "^2.4.6",
"@vue/tsconfig": "^0.5.1",
"esbuild": "0.21.4",
"jsdom": "^24.1.0",
"typescript": "~5.4.5",
"vite": "^5.2.12",
"vite-plugin-dts": "^3.9.1",
"vitest": "^1.6.0",
"vue-tsc": "^2.0.19"
},
"keywords": [
"vue 3 otp input",
"vue otp input",
"vue",
"vue 3",
"vue composition API",
"otp",
"input",
"otp input",
"one-time-password",
"one-time password",
"one time password",
"one time password input",
"one time password input component",
"one time password input component built with vue",
"one time password input component built with vue 3",
"vue split input"
],
"license": "MIT",
"readme": "README.md"
}