@energma/input-mask-react
Version:
A React component for input mask with dynamic formatting. No dependencies.
161 lines (160 loc) • 4.85 kB
JavaScript
import { jsx as S } from "react/jsx-runtime";
import { forwardRef as R, useRef as A, useState as F, useMemo as I } from "react";
const P = (t, p) => {
const a = [];
for (let f = 0; f < t.length; f++)
t[f] !== p && a.push(f);
return a;
}, b = (t, p) => {
switch (p) {
case "numbers":
return /\d/.test(t);
case "letters":
return /[a-zA-Z]/.test(t);
case "mixed":
default:
return /[a-zA-Z0-9]/.test(t);
}
}, V = (t, p, a, f) => {
let g = "", c = 0;
for (let s = 0; s < p.length; s++)
a.includes(s) ? g += p[s] : c < t.length ? (g += t[c], c++) : g += f;
return g;
}, M = (t, p, a) => {
let f = 0, g = 0;
for (let c = 0; c < p.length; c++)
if (a.includes(c))
f++;
else if (g < t.length)
g++, f++;
else
break;
return f;
}, Z = R(
({ schema: t, value: p = "", onChange: a, placeholder: f, ...g }, c) => {
const s = A(null), [u, k] = F(p), [B, x] = F(!1), m = I(
() => P(t.mask, t.symbol),
[t.mask]
), D = (e = t.mask, n = 0) => {
if (!t.mask) return { formatted: "", newCursorPos: 0 };
let i = e.split(""), r = n;
const o = e[n - 1] || "";
if (n > t.mask.length || n < 0)
return {
formatted: e.slice(0, t.mask.length),
newCursorPos: n
};
if (n >= 0 && !b(o, t.type))
return {
formatted: u,
newCursorPos: n - 1
};
for (let l = 0; l <= n; l++)
if (m.includes(r - 1)) {
const d = i[r - 1];
i[r - 1] = t.mask[r - 1], i[r] = d;
} else
i[l] = e[l], n == m.filter((d) => d == n)[0] && r++;
for (let l = n; l <= t.mask.length; l++)
u[l] && u[l] !== t.symbol ? i[l] = u[l] : i[l] = t.mask[l];
return {
formatted: i.join(""),
newCursorPos: r
};
};
return /* @__PURE__ */ S(
"input",
{
...g,
ref: (e) => {
typeof c == "function" ? c(e) : c && (c.current = e), s.current = e;
},
value: u,
onChange: (e) => {
e.stopPropagation(), e.preventDefault();
const n = e.target, i = e.nativeEvent, r = n.value, o = n.selectionStart || 0, l = i.inputType === "insertFromPaste";
let d, v;
if (l) {
const y = Array.from(r).filter((w) => b(w, t.type)).join("");
d = V(
y,
t.mask,
m,
t.symbol
), k(d), a == null || a({
...e,
target: { ...e.target, value: d }
}), v = M(
y,
t.mask,
m
), requestAnimationFrame(() => {
s.current && s.current.setSelectionRange(v, v);
});
} else {
const { formatted: y, newCursorPos: w } = D(
r,
o
);
k(y), a == null || a({
...e,
target: { ...e.target, value: y }
}), requestAnimationFrame(() => {
s.current && s.current.setSelectionRange(w, w);
});
}
},
onKeyDown: (e) => {
var i, r;
const n = ((i = s.current) == null ? void 0 : i.selectionStart) ?? 0;
if (e.ctrlKey && e.key === "a") {
if (e.preventDefault(), s.current) {
const o = u.length;
s.current.setSelectionRange(0, o);
}
return;
}
if (e.key === "Backspace" && n === 0 && ((r = s.current) == null ? void 0 : r.selectionEnd) === u.length) {
e.preventDefault(), k("");
return;
}
if (e.key === "Backspace" && (e.preventDefault(), n > 0)) {
let o = n - 1;
for (; o > 0 && m.includes(o); )
o--;
const l = u.substring(0, o) + t.mask[o] + u.substring(o + 1);
k(l), requestAnimationFrame(() => {
var d;
(d = s.current) == null || d.setSelectionRange(o, o);
});
}
},
onKeyUp: () => {
},
onFocus: (e) => {
e.stopPropagation(), e.preventDefault(), x(!0);
let n = -1;
const i = u.length;
for (let r = 0; r < t.mask.length; r++)
if (t.mask[r] === t.symbol && (!u[r] || u[r] === t.symbol)) {
n = r;
break;
}
requestAnimationFrame(() => {
s.current && n !== -1 ? s.current.setSelectionRange(
n,
n
) : s.current.setSelectionRange(i, i);
});
},
onBlur: (e) => {
e.stopPropagation(), e.preventDefault(), x(!1), u === t.mask && k("");
},
placeholder: f
}
);
}
);
export {
Z as MaskedInput
};