@engr-lukman/vue-secure-pin
Version:
A secure PIN input component for Vue.js applications with virtual keyboard and PIN masking, designed for financial applications
170 lines (169 loc) • 5.69 kB
JavaScript
import { ref as m, computed as C, defineComponent as $, createElementBlock as b, openBlock as _, createCommentVNode as g, createElementVNode as u, toDisplayString as v, Fragment as x, renderList as B, onMounted as w, onUnmounted as H, normalizeClass as S, unref as k, createVNode as I } from "vue";
function R(i) {
const { maxLength: f } = i, e = m(""), o = m(""), a = C(
() => e.value.length >= f
);
return {
pinMasked: e,
pin: o,
isMaxLengthReached: a,
clear: () => {
e.value = "", o.value = "";
},
pinHandler: (p) => {
a.value || (e.value = `${e.value || ""}*`, o.value = `${o.value || ""}${p}`);
}
};
}
const s = {
maxLength: 5,
isError: !1,
errorMessage: "",
placeholder: "Enter PIN",
isEnableShuffle: !0,
showKeyboard: !0,
title: "Use this keyboard to enter your PIN",
clearLabel: "Clear"
}, O = {
key: 0,
class: "numeric-keyboard__title"
}, V = { class: "numeric-keyboard" }, A = { class: "numeric-keyboard__grid" }, U = ["onClick"], j = /* @__PURE__ */ $({
__name: "NumericKeyboard",
props: {
isEnableShuffle: { type: Boolean, default: s.isEnableShuffle },
isMaxLengthReached: { type: Boolean, default: !1 },
title: { default: s.title },
clearLabel: { default: s.clearLabel }
},
emits: ["input", "clear"],
setup(i, { emit: f }) {
const e = i, o = f, a = m([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]), d = (r) => {
if (!e.isEnableShuffle)
return r;
const t = [...r];
for (let n = t.length - 1; n > 0; n--) {
const h = Math.floor(Math.random() * (n + 1));
[t[n], t[h]] = [t[h], t[n]];
}
return t;
}, c = C(
() => e.isEnableShuffle ? d([...a.value]) : a.value
), p = (r) => {
e.isMaxLengthReached || (e.isEnableShuffle && (a.value = d(a.value)), o("input", r));
};
return (r, t) => (_(), b("div", null, [
r.title ? (_(), b("div", O, v(r.title), 1)) : g("", !0),
u("div", V, [
u("div", A, [
(_(!0), b(x, null, B(c.value.slice(0, 9), (n, h) => (_(), b("button", {
key: h,
class: "numeric-keyboard__button",
type: "button",
onClick: (y) => p(n)
}, v(n), 9, U))), 128)),
u("button", {
class: "numeric-keyboard__button numeric-keyboard__button--double",
type: "button",
onClick: t[0] || (t[0] = (n) => p(c.value[c.value.length - 1]))
}, v(c.value[c.value.length - 1]), 1),
u("button", {
class: "numeric-keyboard__button numeric-keyboard__button--clear",
type: "button",
onClick: t[1] || (t[1] = (n) => r.$emit("clear"))
}, v(r.clearLabel), 1)
])
])
]));
}
}), P = (i, f) => {
const e = i.__vccOpts || i;
for (const [o, a] of f)
e[o] = a;
return e;
}, z = /* @__PURE__ */ P(j, [["__scopeId", "data-v-c58e0ec8"]]), D = { class: "pin" }, F = ["data-placeholder"], q = { class: "pin__masked-value" }, G = {
key: 0,
class: "pin__error",
role: "alert"
}, J = /* @__PURE__ */ $({
__name: "SecurePin",
props: {
maxLength: { default: s.maxLength },
isError: { type: Boolean, default: s.isError },
errorMessage: { default: s.errorMessage },
placeholder: { default: s.placeholder },
isEnableShuffle: { type: Boolean, default: s.isEnableShuffle },
showKeyboard: { type: Boolean, default: s.showKeyboard },
title: { default: s.title },
clearLabel: { default: s.clearLabel }
},
emits: ["callbackEvent"],
setup(i, { expose: f, emit: e }) {
const o = i, a = e, d = m(o.showKeyboard), c = m(null), { pinMasked: p, pin: r, isMaxLengthReached: t, clear: n, pinHandler: h } = R({ maxLength: o.maxLength }), y = () => {
d.value = !d.value;
}, E = () => {
n(), a("callbackEvent", { pin: "", pinMasked: "" });
}, N = (l) => {
h(l), a("callbackEvent", {
pin: r.value,
pinMasked: p.value
});
}, L = (l) => {
c.value && !c.value.contains(l.target) && !l.target.closest(".pin__toggle") && (d.value = !1);
};
return f({
clearHandler: E,
toggleKeyboard: y
}), w(() => {
document.addEventListener("click", L);
}), H(() => {
document.removeEventListener("click", L);
}), (l, M) => (_(), b("div", D, [
u("div", {
class: S(["pin__field", { "pin__field--error": l.isError }])
}, [
u("div", {
class: S(["pin__display", { "pin__display--empty": !k(p) }]),
"data-placeholder": l.placeholder
}, [
u("div", q, v(k(p)), 1)
], 10, F),
u("div", { class: "pin__actions" }, [
u("div", {
class: "pin__toggle",
onClick: y
}, M[0] || (M[0] = [
u("span", { class: "pin__icon" }, "⌨️", -1)
]))
]),
l.isError ? (_(), b("div", G, v(l.errorMessage), 1)) : g("", !0)
], 2),
d.value ? (_(), b("div", {
key: 0,
ref_key: "keyboardPopup",
ref: c,
class: "pin__keyboard"
}, [
I(z, {
"is-enable-shuffle": l.isEnableShuffle,
"is-max-length-reached": k(t),
title: l.title,
"clear-label": l.clearLabel,
onInput: N,
onClear: E
}, null, 8, ["is-enable-shuffle", "is-max-length-reached", "title", "clear-label"])
], 512)) : g("", !0)
]));
}
}), K = /* @__PURE__ */ P(J, [["__scopeId", "data-v-c41fe822"]]);
function Q(i) {
i.component("SecurePin", K);
}
const W = {
install: Q,
SecurePin: K
};
export {
K as SecurePin,
W as default
};
//# sourceMappingURL=vue-secure-pin.es.js.map