otp-auto-fetch-input 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -186,10 +186,13 @@ var OtpInput = ({
186
186
  event.preventDefault();
187
187
  const text = event.clipboardData.getData("text").replace(/\D/g, "").slice(0, length);
188
188
  if (!text) return;
189
- emitValue(text, "paste");
189
+ const nextSlots = Array.from({ length }, (_, idx) => text[idx] ?? "");
190
+ const nextValue = nextSlots.join("");
191
+ emitValue(nextValue, "paste");
190
192
  setEnteredIndex(Math.min(text.length - 1, length - 1));
191
- const focusIndex = Math.min(text.length, length - 1);
193
+ const focusIndex = Math.min(text.length - 1, length - 1);
192
194
  refs.current[focusIndex]?.focus();
195
+ refs.current[focusIndex]?.select();
193
196
  };
194
197
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: ["otp-root", className].filter(Boolean).join(" "), children: slots.map((digit, index) => {
195
198
  const enteredClass = enteredIndex === index ? "entered" : "";
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/OtpInput.tsx","../src/useWebOtp.ts"],"sourcesContent":["export { OtpInput } from \"./OtpInput\";\nexport { useWebOtp } from \"./useWebOtp\";\nexport type { OtpInputProps, OtpAutoFillSource, WebOtpResult } from \"./types\";\n","import React, { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { useWebOtp } from \"./useWebOtp\";\nimport type { OtpAutoFillSource, OtpInputProps } from \"./types\";\n\nconst isDigit = (value: string) => /^\\d$/.test(value);\n\nconst toSlots = (value: string, length: number) => {\n const normalized = value.replace(/\\D/g, \"\").slice(0, length);\n return Array.from({ length }, (_, idx) => normalized[idx] ?? \"\");\n};\n\nexport const OtpInput = ({\n length = 6,\n value,\n onChange,\n onComplete,\n autoFocus = true,\n disabled = false,\n autoFetch = true,\n fetchTimeoutMs = 60_000,\n className,\n inputClassName,\n allowPaste = true,\n onError,\n}: OtpInputProps) => {\n const [enteredIndex, setEnteredIndex] = useState<number | null>(null);\n const refs = useRef<Array<HTMLInputElement | null>>([]);\n\n const slots = useMemo(() => toSlots(value, length), [value, length]);\n\n const emitValue = useCallback(\n (next: string, source: OtpAutoFillSource) => {\n const trimmed = next.replace(/\\D/g, \"\").slice(0, length);\n onChange(trimmed, source);\n if (trimmed.length === length) {\n onComplete?.(trimmed, source);\n }\n },\n [length, onChange, onComplete],\n );\n\n useWebOtp(\n autoFetch,\n fetchTimeoutMs,\n ({ code, source }) => {\n emitValue(code, source);\n refs.current[Math.min(code.length, length) - 1]?.blur();\n },\n onError,\n );\n\n useEffect(() => {\n if (!autoFocus || disabled) return;\n const firstEmpty = slots.findIndex((slot) => slot === \"\");\n const targetIndex = firstEmpty === -1 ? length - 1 : firstEmpty;\n refs.current[targetIndex]?.focus();\n }, [autoFocus, disabled, length, slots]);\n\n useEffect(() => {\n if (enteredIndex === null) return;\n const timer = setTimeout(() => setEnteredIndex(null), 220);\n return () => clearTimeout(timer);\n }, [enteredIndex]);\n\n const handleChangeAt = (index: number, raw: string) => {\n if (disabled) return;\n const nextChar = raw.slice(-1);\n if (!isDigit(nextChar)) return;\n\n const nextSlots = [...slots];\n nextSlots[index] = nextChar;\n const nextValue = nextSlots.join(\"\");\n\n setEnteredIndex(index);\n emitValue(nextValue, \"manual\");\n\n const nextIndex = Math.min(index + 1, length - 1);\n refs.current[nextIndex]?.focus();\n refs.current[nextIndex]?.select();\n };\n\n const clearAt = (index: number) => {\n const nextSlots = [...slots];\n nextSlots[index] = \"\";\n emitValue(nextSlots.join(\"\"), \"manual\");\n };\n\n const handleKeyDown = (index: number, event: React.KeyboardEvent<HTMLInputElement>) => {\n if (disabled) return;\n\n if (event.key === \"Backspace\") {\n event.preventDefault();\n if (slots[index]) {\n clearAt(index);\n return;\n }\n const prev = Math.max(index - 1, 0);\n clearAt(prev);\n refs.current[prev]?.focus();\n return;\n }\n\n if (event.key === \"ArrowLeft\") {\n event.preventDefault();\n refs.current[Math.max(index - 1, 0)]?.focus();\n return;\n }\n\n if (event.key === \"ArrowRight\") {\n event.preventDefault();\n refs.current[Math.min(index + 1, length - 1)]?.focus();\n return;\n }\n\n if (event.key.length === 1 && !/\\d/.test(event.key)) {\n event.preventDefault();\n }\n };\n\n const handlePaste = (event: React.ClipboardEvent<HTMLInputElement>) => {\n if (!allowPaste || disabled) return;\n event.preventDefault();\n\n const text = event.clipboardData.getData(\"text\").replace(/\\D/g, \"\").slice(0, length);\n if (!text) return;\n\n emitValue(text, \"paste\");\n setEnteredIndex(Math.min(text.length - 1, length - 1));\n\n const focusIndex = Math.min(text.length, length - 1);\n refs.current[focusIndex]?.focus();\n };\n\n return (\n <div className={[\"otp-root\", className].filter(Boolean).join(\" \")}>\n {slots.map((digit, index) => {\n const enteredClass = enteredIndex === index ? \"entered\" : \"\";\n const filledClass = digit ? \"filled\" : \"\";\n return (\n <input\n key={index}\n ref={(el) => {\n refs.current[index] = el;\n }}\n value={digit}\n disabled={disabled}\n type=\"text\"\n maxLength={1}\n inputMode=\"numeric\"\n pattern=\"[0-9]*\"\n autoComplete={index === 0 ? \"one-time-code\" : \"off\"}\n className={[\"otp-slot\", filledClass, enteredClass, inputClassName].filter(Boolean).join(\" \")}\n onChange={(e) => handleChangeAt(index, e.target.value)}\n onKeyDown={(e) => handleKeyDown(index, e)}\n onPaste={handlePaste}\n aria-label={`OTP digit ${index + 1}`}\n />\n );\n })}\n </div>\n );\n};\n","import { useCallback, useEffect, useRef } from \"react\";\nimport type { WebOtpResult } from \"./types\";\n\ninterface OtpCredential extends Credential {\n code: string;\n}\n\ntype OtpGetOptions = CredentialRequestOptions & {\n otp?: { transport: string[] };\n};\n\ntype OtpAwareCredentialsContainer = CredentialsContainer & {\n get: (options?: OtpGetOptions) => Promise<Credential | null>;\n};\n\nconst hasWebOtp = () => {\n if (typeof window === \"undefined\") return false;\n if (!window.isSecureContext) return false;\n return typeof (navigator.credentials as Partial<OtpAwareCredentialsContainer> | undefined)?.get === \"function\";\n};\n\nexport const useWebOtp = (\n enabled: boolean,\n timeoutMs: number,\n onCode: (result: WebOtpResult) => void,\n onError?: (error: Error) => void,\n) => {\n const abortRef = useRef<AbortController | null>(null);\n\n const stop = useCallback(() => {\n abortRef.current?.abort();\n abortRef.current = null;\n }, []);\n\n const start = useCallback(async () => {\n if (!enabled || !hasWebOtp()) return;\n\n stop();\n const controller = new AbortController();\n abortRef.current = controller;\n\n const timer = setTimeout(() => {\n controller.abort();\n }, timeoutMs);\n\n try {\n const credentials = navigator.credentials as OtpAwareCredentialsContainer;\n const credential = (await credentials.get?.({\n otp: { transport: [\"sms\"] },\n signal: controller.signal,\n })) as OtpCredential | null;\n\n if (credential?.code) {\n onCode({ code: credential.code, source: \"webotp\" });\n }\n } catch (err) {\n const error = err as Error;\n if (error.name !== \"AbortError\") {\n onError?.(error);\n }\n } finally {\n clearTimeout(timer);\n abortRef.current = null;\n }\n }, [enabled, onCode, onError, stop, timeoutMs]);\n\n useEffect(() => {\n if (!enabled) return;\n void start();\n return () => {\n stop();\n };\n }, [enabled, start, stop]);\n\n return {\n isSupported: hasWebOtp(),\n start,\n stop,\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,IAAAA,gBAAyE;;;ACAzE,mBAA+C;AAe/C,IAAM,YAAY,MAAM;AACtB,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,MAAI,CAAC,OAAO,gBAAiB,QAAO;AACpC,SAAO,OAAQ,UAAU,aAAmE,QAAQ;AACtG;AAEO,IAAM,YAAY,CACvB,SACA,WACA,QACA,YACG;AACH,QAAM,eAAW,qBAA+B,IAAI;AAEpD,QAAM,WAAO,0BAAY,MAAM;AAC7B,aAAS,SAAS,MAAM;AACxB,aAAS,UAAU;AAAA,EACrB,GAAG,CAAC,CAAC;AAEL,QAAM,YAAQ,0BAAY,YAAY;AACpC,QAAI,CAAC,WAAW,CAAC,UAAU,EAAG;AAE9B,SAAK;AACL,UAAM,aAAa,IAAI,gBAAgB;AACvC,aAAS,UAAU;AAEnB,UAAM,QAAQ,WAAW,MAAM;AAC7B,iBAAW,MAAM;AAAA,IACnB,GAAG,SAAS;AAEZ,QAAI;AACF,YAAM,cAAc,UAAU;AAC9B,YAAM,aAAc,MAAM,YAAY,MAAM;AAAA,QAC1C,KAAK,EAAE,WAAW,CAAC,KAAK,EAAE;AAAA,QAC1B,QAAQ,WAAW;AAAA,MACrB,CAAC;AAED,UAAI,YAAY,MAAM;AACpB,eAAO,EAAE,MAAM,WAAW,MAAM,QAAQ,SAAS,CAAC;AAAA,MACpD;AAAA,IACF,SAAS,KAAK;AACZ,YAAM,QAAQ;AACd,UAAI,MAAM,SAAS,cAAc;AAC/B,kBAAU,KAAK;AAAA,MACjB;AAAA,IACF,UAAE;AACA,mBAAa,KAAK;AAClB,eAAS,UAAU;AAAA,IACrB;AAAA,EACF,GAAG,CAAC,SAAS,QAAQ,SAAS,MAAM,SAAS,CAAC;AAE9C,8BAAU,MAAM;AACd,QAAI,CAAC,QAAS;AACd,SAAK,MAAM;AACX,WAAO,MAAM;AACX,WAAK;AAAA,IACP;AAAA,EACF,GAAG,CAAC,SAAS,OAAO,IAAI,CAAC;AAEzB,SAAO;AAAA,IACL,aAAa,UAAU;AAAA,IACvB;AAAA,IACA;AAAA,EACF;AACF;;;AD4DU;AAvIV,IAAM,UAAU,CAAC,UAAkB,OAAO,KAAK,KAAK;AAEpD,IAAM,UAAU,CAAC,OAAe,WAAmB;AACjD,QAAM,aAAa,MAAM,QAAQ,OAAO,EAAE,EAAE,MAAM,GAAG,MAAM;AAC3D,SAAO,MAAM,KAAK,EAAE,OAAO,GAAG,CAAC,GAAG,QAAQ,WAAW,GAAG,KAAK,EAAE;AACjE;AAEO,IAAM,WAAW,CAAC;AAAA,EACvB,SAAS;AAAA,EACT;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,iBAAiB;AAAA,EACjB;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb;AACF,MAAqB;AACnB,QAAM,CAAC,cAAc,eAAe,QAAI,wBAAwB,IAAI;AACpE,QAAM,WAAO,sBAAuC,CAAC,CAAC;AAEtD,QAAM,YAAQ,uBAAQ,MAAM,QAAQ,OAAO,MAAM,GAAG,CAAC,OAAO,MAAM,CAAC;AAEnE,QAAM,gBAAY;AAAA,IAChB,CAAC,MAAc,WAA8B;AAC3C,YAAM,UAAU,KAAK,QAAQ,OAAO,EAAE,EAAE,MAAM,GAAG,MAAM;AACvD,eAAS,SAAS,MAAM;AACxB,UAAI,QAAQ,WAAW,QAAQ;AAC7B,qBAAa,SAAS,MAAM;AAAA,MAC9B;AAAA,IACF;AAAA,IACA,CAAC,QAAQ,UAAU,UAAU;AAAA,EAC/B;AAEA;AAAA,IACE;AAAA,IACA;AAAA,IACA,CAAC,EAAE,MAAM,OAAO,MAAM;AACpB,gBAAU,MAAM,MAAM;AACtB,WAAK,QAAQ,KAAK,IAAI,KAAK,QAAQ,MAAM,IAAI,CAAC,GAAG,KAAK;AAAA,IACxD;AAAA,IACA;AAAA,EACF;AAEA,+BAAU,MAAM;AACd,QAAI,CAAC,aAAa,SAAU;AAC5B,UAAM,aAAa,MAAM,UAAU,CAAC,SAAS,SAAS,EAAE;AACxD,UAAM,cAAc,eAAe,KAAK,SAAS,IAAI;AACrD,SAAK,QAAQ,WAAW,GAAG,MAAM;AAAA,EACnC,GAAG,CAAC,WAAW,UAAU,QAAQ,KAAK,CAAC;AAEvC,+BAAU,MAAM;AACd,QAAI,iBAAiB,KAAM;AAC3B,UAAM,QAAQ,WAAW,MAAM,gBAAgB,IAAI,GAAG,GAAG;AACzD,WAAO,MAAM,aAAa,KAAK;AAAA,EACjC,GAAG,CAAC,YAAY,CAAC;AAEjB,QAAM,iBAAiB,CAAC,OAAe,QAAgB;AACrD,QAAI,SAAU;AACd,UAAM,WAAW,IAAI,MAAM,EAAE;AAC7B,QAAI,CAAC,QAAQ,QAAQ,EAAG;AAExB,UAAM,YAAY,CAAC,GAAG,KAAK;AAC3B,cAAU,KAAK,IAAI;AACnB,UAAM,YAAY,UAAU,KAAK,EAAE;AAEnC,oBAAgB,KAAK;AACrB,cAAU,WAAW,QAAQ;AAE7B,UAAM,YAAY,KAAK,IAAI,QAAQ,GAAG,SAAS,CAAC;AAChD,SAAK,QAAQ,SAAS,GAAG,MAAM;AAC/B,SAAK,QAAQ,SAAS,GAAG,OAAO;AAAA,EAClC;AAEA,QAAM,UAAU,CAAC,UAAkB;AACjC,UAAM,YAAY,CAAC,GAAG,KAAK;AAC3B,cAAU,KAAK,IAAI;AACnB,cAAU,UAAU,KAAK,EAAE,GAAG,QAAQ;AAAA,EACxC;AAEA,QAAM,gBAAgB,CAAC,OAAe,UAAiD;AACrF,QAAI,SAAU;AAEd,QAAI,MAAM,QAAQ,aAAa;AAC7B,YAAM,eAAe;AACrB,UAAI,MAAM,KAAK,GAAG;AAChB,gBAAQ,KAAK;AACb;AAAA,MACF;AACA,YAAM,OAAO,KAAK,IAAI,QAAQ,GAAG,CAAC;AAClC,cAAQ,IAAI;AACZ,WAAK,QAAQ,IAAI,GAAG,MAAM;AAC1B;AAAA,IACF;AAEA,QAAI,MAAM,QAAQ,aAAa;AAC7B,YAAM,eAAe;AACrB,WAAK,QAAQ,KAAK,IAAI,QAAQ,GAAG,CAAC,CAAC,GAAG,MAAM;AAC5C;AAAA,IACF;AAEA,QAAI,MAAM,QAAQ,cAAc;AAC9B,YAAM,eAAe;AACrB,WAAK,QAAQ,KAAK,IAAI,QAAQ,GAAG,SAAS,CAAC,CAAC,GAAG,MAAM;AACrD;AAAA,IACF;AAEA,QAAI,MAAM,IAAI,WAAW,KAAK,CAAC,KAAK,KAAK,MAAM,GAAG,GAAG;AACnD,YAAM,eAAe;AAAA,IACvB;AAAA,EACF;AAEA,QAAM,cAAc,CAAC,UAAkD;AACrE,QAAI,CAAC,cAAc,SAAU;AAC7B,UAAM,eAAe;AAErB,UAAM,OAAO,MAAM,cAAc,QAAQ,MAAM,EAAE,QAAQ,OAAO,EAAE,EAAE,MAAM,GAAG,MAAM;AACnF,QAAI,CAAC,KAAM;AAEX,cAAU,MAAM,OAAO;AACvB,oBAAgB,KAAK,IAAI,KAAK,SAAS,GAAG,SAAS,CAAC,CAAC;AAErD,UAAM,aAAa,KAAK,IAAI,KAAK,QAAQ,SAAS,CAAC;AACnD,SAAK,QAAQ,UAAU,GAAG,MAAM;AAAA,EAClC;AAEA,SACE,4CAAC,SAAI,WAAW,CAAC,YAAY,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG,GAC7D,gBAAM,IAAI,CAAC,OAAO,UAAU;AAC3B,UAAM,eAAe,iBAAiB,QAAQ,YAAY;AAC1D,UAAM,cAAc,QAAQ,WAAW;AACvC,WACE;AAAA,MAAC;AAAA;AAAA,QAEC,KAAK,CAAC,OAAO;AACX,eAAK,QAAQ,KAAK,IAAI;AAAA,QACxB;AAAA,QACA,OAAO;AAAA,QACP;AAAA,QACA,MAAK;AAAA,QACL,WAAW;AAAA,QACX,WAAU;AAAA,QACV,SAAQ;AAAA,QACR,cAAc,UAAU,IAAI,kBAAkB;AAAA,QAC9C,WAAW,CAAC,YAAY,aAAa,cAAc,cAAc,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAAA,QAC3F,UAAU,CAAC,MAAM,eAAe,OAAO,EAAE,OAAO,KAAK;AAAA,QACrD,WAAW,CAAC,MAAM,cAAc,OAAO,CAAC;AAAA,QACxC,SAAS;AAAA,QACT,cAAY,aAAa,QAAQ,CAAC;AAAA;AAAA,MAf7B;AAAA,IAgBP;AAAA,EAEJ,CAAC,GACH;AAEJ;","names":["import_react"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/OtpInput.tsx","../src/useWebOtp.ts"],"sourcesContent":["export { OtpInput } from \"./OtpInput\";\nexport { useWebOtp } from \"./useWebOtp\";\nexport type { OtpInputProps, OtpAutoFillSource, WebOtpResult } from \"./types\";\n","import React, { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { useWebOtp } from \"./useWebOtp\";\nimport type { OtpAutoFillSource, OtpInputProps } from \"./types\";\n\nconst isDigit = (value: string) => /^\\d$/.test(value);\n\nconst toSlots = (value: string, length: number) => {\n const normalized = value.replace(/\\D/g, \"\").slice(0, length);\n return Array.from({ length }, (_, idx) => normalized[idx] ?? \"\");\n};\n\nexport const OtpInput = ({\n length = 6,\n value,\n onChange,\n onComplete,\n autoFocus = true,\n disabled = false,\n autoFetch = true,\n fetchTimeoutMs = 60_000,\n className,\n inputClassName,\n allowPaste = true,\n onError,\n}: OtpInputProps) => {\n const [enteredIndex, setEnteredIndex] = useState<number | null>(null);\n const refs = useRef<Array<HTMLInputElement | null>>([]);\n\n const slots = useMemo(() => toSlots(value, length), [value, length]);\n\n const emitValue = useCallback(\n (next: string, source: OtpAutoFillSource) => {\n const trimmed = next.replace(/\\D/g, \"\").slice(0, length);\n onChange(trimmed, source);\n if (trimmed.length === length) {\n onComplete?.(trimmed, source);\n }\n },\n [length, onChange, onComplete],\n );\n\n useWebOtp(\n autoFetch,\n fetchTimeoutMs,\n ({ code, source }) => {\n emitValue(code, source);\n refs.current[Math.min(code.length, length) - 1]?.blur();\n },\n onError,\n );\n\n useEffect(() => {\n if (!autoFocus || disabled) return;\n const firstEmpty = slots.findIndex((slot) => slot === \"\");\n const targetIndex = firstEmpty === -1 ? length - 1 : firstEmpty;\n refs.current[targetIndex]?.focus();\n }, [autoFocus, disabled, length, slots]);\n\n useEffect(() => {\n if (enteredIndex === null) return;\n const timer = setTimeout(() => setEnteredIndex(null), 220);\n return () => clearTimeout(timer);\n }, [enteredIndex]);\n\n const handleChangeAt = (index: number, raw: string) => {\n if (disabled) return;\n const nextChar = raw.slice(-1);\n if (!isDigit(nextChar)) return;\n\n const nextSlots = [...slots];\n nextSlots[index] = nextChar;\n const nextValue = nextSlots.join(\"\");\n\n setEnteredIndex(index);\n emitValue(nextValue, \"manual\");\n\n const nextIndex = Math.min(index + 1, length - 1);\n refs.current[nextIndex]?.focus();\n refs.current[nextIndex]?.select();\n };\n\n const clearAt = (index: number) => {\n const nextSlots = [...slots];\n nextSlots[index] = \"\";\n emitValue(nextSlots.join(\"\"), \"manual\");\n };\n\n const handleKeyDown = (index: number, event: React.KeyboardEvent<HTMLInputElement>) => {\n if (disabled) return;\n\n if (event.key === \"Backspace\") {\n event.preventDefault();\n if (slots[index]) {\n clearAt(index);\n return;\n }\n const prev = Math.max(index - 1, 0);\n clearAt(prev);\n refs.current[prev]?.focus();\n return;\n }\n\n if (event.key === \"ArrowLeft\") {\n event.preventDefault();\n refs.current[Math.max(index - 1, 0)]?.focus();\n return;\n }\n\n if (event.key === \"ArrowRight\") {\n event.preventDefault();\n refs.current[Math.min(index + 1, length - 1)]?.focus();\n return;\n }\n\n if (event.key.length === 1 && !/\\d/.test(event.key)) {\n event.preventDefault();\n }\n };\n\n const handlePaste = (event: React.ClipboardEvent<HTMLInputElement>) => {\n if (!allowPaste || disabled) return;\n event.preventDefault();\n\n const text = event.clipboardData.getData(\"text\").replace(/\\D/g, \"\").slice(0, length);\n if (!text) return;\n\n // Split OTP and fill all cells\n const nextSlots = Array.from({ length }, (_, idx) => text[idx] ?? \"\");\n const nextValue = nextSlots.join(\"\");\n emitValue(nextValue, \"paste\");\n setEnteredIndex(Math.min(text.length - 1, length - 1));\n\n // Move focus to last filled cell\n const focusIndex = Math.min(text.length - 1, length - 1);\n refs.current[focusIndex]?.focus();\n refs.current[focusIndex]?.select();\n };\n\n return (\n <div className={[\"otp-root\", className].filter(Boolean).join(\" \")}>\n {slots.map((digit, index) => {\n const enteredClass = enteredIndex === index ? \"entered\" : \"\";\n const filledClass = digit ? \"filled\" : \"\";\n return (\n <input\n key={index}\n ref={(el) => {\n refs.current[index] = el;\n }}\n value={digit}\n disabled={disabled}\n type=\"text\"\n maxLength={1}\n inputMode=\"numeric\"\n pattern=\"[0-9]*\"\n autoComplete={index === 0 ? \"one-time-code\" : \"off\"}\n className={[\"otp-slot\", filledClass, enteredClass, inputClassName].filter(Boolean).join(\" \")}\n onChange={(e) => handleChangeAt(index, e.target.value)}\n onKeyDown={(e) => handleKeyDown(index, e)}\n onPaste={handlePaste}\n aria-label={`OTP digit ${index + 1}`}\n />\n );\n })}\n </div>\n );\n};\n","import { useCallback, useEffect, useRef } from \"react\";\nimport type { WebOtpResult } from \"./types\";\n\ninterface OtpCredential extends Credential {\n code: string;\n}\n\ntype OtpGetOptions = CredentialRequestOptions & {\n otp?: { transport: string[] };\n};\n\ntype OtpAwareCredentialsContainer = CredentialsContainer & {\n get: (options?: OtpGetOptions) => Promise<Credential | null>;\n};\n\nconst hasWebOtp = () => {\n if (typeof window === \"undefined\") return false;\n if (!window.isSecureContext) return false;\n return typeof (navigator.credentials as Partial<OtpAwareCredentialsContainer> | undefined)?.get === \"function\";\n};\n\nexport const useWebOtp = (\n enabled: boolean,\n timeoutMs: number,\n onCode: (result: WebOtpResult) => void,\n onError?: (error: Error) => void,\n) => {\n const abortRef = useRef<AbortController | null>(null);\n\n const stop = useCallback(() => {\n abortRef.current?.abort();\n abortRef.current = null;\n }, []);\n\n const start = useCallback(async () => {\n if (!enabled || !hasWebOtp()) return;\n\n stop();\n const controller = new AbortController();\n abortRef.current = controller;\n\n const timer = setTimeout(() => {\n controller.abort();\n }, timeoutMs);\n\n try {\n const credentials = navigator.credentials as OtpAwareCredentialsContainer;\n const credential = (await credentials.get?.({\n otp: { transport: [\"sms\"] },\n signal: controller.signal,\n })) as OtpCredential | null;\n\n if (credential?.code) {\n onCode({ code: credential.code, source: \"webotp\" });\n }\n } catch (err) {\n const error = err as Error;\n if (error.name !== \"AbortError\") {\n onError?.(error);\n }\n } finally {\n clearTimeout(timer);\n abortRef.current = null;\n }\n }, [enabled, onCode, onError, stop, timeoutMs]);\n\n useEffect(() => {\n if (!enabled) return;\n void start();\n return () => {\n stop();\n };\n }, [enabled, start, stop]);\n\n return {\n isSupported: hasWebOtp(),\n start,\n stop,\n };\n};\n"],"mappings":"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","names":["import_react"]}
package/dist/index.js CHANGED
@@ -159,10 +159,13 @@ var OtpInput = ({
159
159
  event.preventDefault();
160
160
  const text = event.clipboardData.getData("text").replace(/\D/g, "").slice(0, length);
161
161
  if (!text) return;
162
- emitValue(text, "paste");
162
+ const nextSlots = Array.from({ length }, (_, idx) => text[idx] ?? "");
163
+ const nextValue = nextSlots.join("");
164
+ emitValue(nextValue, "paste");
163
165
  setEnteredIndex(Math.min(text.length - 1, length - 1));
164
- const focusIndex = Math.min(text.length, length - 1);
166
+ const focusIndex = Math.min(text.length - 1, length - 1);
165
167
  refs.current[focusIndex]?.focus();
168
+ refs.current[focusIndex]?.select();
166
169
  };
167
170
  return /* @__PURE__ */ jsx("div", { className: ["otp-root", className].filter(Boolean).join(" "), children: slots.map((digit, index) => {
168
171
  const enteredClass = enteredIndex === index ? "entered" : "";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/OtpInput.tsx","../src/useWebOtp.ts"],"sourcesContent":["import React, { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { useWebOtp } from \"./useWebOtp\";\nimport type { OtpAutoFillSource, OtpInputProps } from \"./types\";\n\nconst isDigit = (value: string) => /^\\d$/.test(value);\n\nconst toSlots = (value: string, length: number) => {\n const normalized = value.replace(/\\D/g, \"\").slice(0, length);\n return Array.from({ length }, (_, idx) => normalized[idx] ?? \"\");\n};\n\nexport const OtpInput = ({\n length = 6,\n value,\n onChange,\n onComplete,\n autoFocus = true,\n disabled = false,\n autoFetch = true,\n fetchTimeoutMs = 60_000,\n className,\n inputClassName,\n allowPaste = true,\n onError,\n}: OtpInputProps) => {\n const [enteredIndex, setEnteredIndex] = useState<number | null>(null);\n const refs = useRef<Array<HTMLInputElement | null>>([]);\n\n const slots = useMemo(() => toSlots(value, length), [value, length]);\n\n const emitValue = useCallback(\n (next: string, source: OtpAutoFillSource) => {\n const trimmed = next.replace(/\\D/g, \"\").slice(0, length);\n onChange(trimmed, source);\n if (trimmed.length === length) {\n onComplete?.(trimmed, source);\n }\n },\n [length, onChange, onComplete],\n );\n\n useWebOtp(\n autoFetch,\n fetchTimeoutMs,\n ({ code, source }) => {\n emitValue(code, source);\n refs.current[Math.min(code.length, length) - 1]?.blur();\n },\n onError,\n );\n\n useEffect(() => {\n if (!autoFocus || disabled) return;\n const firstEmpty = slots.findIndex((slot) => slot === \"\");\n const targetIndex = firstEmpty === -1 ? length - 1 : firstEmpty;\n refs.current[targetIndex]?.focus();\n }, [autoFocus, disabled, length, slots]);\n\n useEffect(() => {\n if (enteredIndex === null) return;\n const timer = setTimeout(() => setEnteredIndex(null), 220);\n return () => clearTimeout(timer);\n }, [enteredIndex]);\n\n const handleChangeAt = (index: number, raw: string) => {\n if (disabled) return;\n const nextChar = raw.slice(-1);\n if (!isDigit(nextChar)) return;\n\n const nextSlots = [...slots];\n nextSlots[index] = nextChar;\n const nextValue = nextSlots.join(\"\");\n\n setEnteredIndex(index);\n emitValue(nextValue, \"manual\");\n\n const nextIndex = Math.min(index + 1, length - 1);\n refs.current[nextIndex]?.focus();\n refs.current[nextIndex]?.select();\n };\n\n const clearAt = (index: number) => {\n const nextSlots = [...slots];\n nextSlots[index] = \"\";\n emitValue(nextSlots.join(\"\"), \"manual\");\n };\n\n const handleKeyDown = (index: number, event: React.KeyboardEvent<HTMLInputElement>) => {\n if (disabled) return;\n\n if (event.key === \"Backspace\") {\n event.preventDefault();\n if (slots[index]) {\n clearAt(index);\n return;\n }\n const prev = Math.max(index - 1, 0);\n clearAt(prev);\n refs.current[prev]?.focus();\n return;\n }\n\n if (event.key === \"ArrowLeft\") {\n event.preventDefault();\n refs.current[Math.max(index - 1, 0)]?.focus();\n return;\n }\n\n if (event.key === \"ArrowRight\") {\n event.preventDefault();\n refs.current[Math.min(index + 1, length - 1)]?.focus();\n return;\n }\n\n if (event.key.length === 1 && !/\\d/.test(event.key)) {\n event.preventDefault();\n }\n };\n\n const handlePaste = (event: React.ClipboardEvent<HTMLInputElement>) => {\n if (!allowPaste || disabled) return;\n event.preventDefault();\n\n const text = event.clipboardData.getData(\"text\").replace(/\\D/g, \"\").slice(0, length);\n if (!text) return;\n\n emitValue(text, \"paste\");\n setEnteredIndex(Math.min(text.length - 1, length - 1));\n\n const focusIndex = Math.min(text.length, length - 1);\n refs.current[focusIndex]?.focus();\n };\n\n return (\n <div className={[\"otp-root\", className].filter(Boolean).join(\" \")}>\n {slots.map((digit, index) => {\n const enteredClass = enteredIndex === index ? \"entered\" : \"\";\n const filledClass = digit ? \"filled\" : \"\";\n return (\n <input\n key={index}\n ref={(el) => {\n refs.current[index] = el;\n }}\n value={digit}\n disabled={disabled}\n type=\"text\"\n maxLength={1}\n inputMode=\"numeric\"\n pattern=\"[0-9]*\"\n autoComplete={index === 0 ? \"one-time-code\" : \"off\"}\n className={[\"otp-slot\", filledClass, enteredClass, inputClassName].filter(Boolean).join(\" \")}\n onChange={(e) => handleChangeAt(index, e.target.value)}\n onKeyDown={(e) => handleKeyDown(index, e)}\n onPaste={handlePaste}\n aria-label={`OTP digit ${index + 1}`}\n />\n );\n })}\n </div>\n );\n};\n","import { useCallback, useEffect, useRef } from \"react\";\nimport type { WebOtpResult } from \"./types\";\n\ninterface OtpCredential extends Credential {\n code: string;\n}\n\ntype OtpGetOptions = CredentialRequestOptions & {\n otp?: { transport: string[] };\n};\n\ntype OtpAwareCredentialsContainer = CredentialsContainer & {\n get: (options?: OtpGetOptions) => Promise<Credential | null>;\n};\n\nconst hasWebOtp = () => {\n if (typeof window === \"undefined\") return false;\n if (!window.isSecureContext) return false;\n return typeof (navigator.credentials as Partial<OtpAwareCredentialsContainer> | undefined)?.get === \"function\";\n};\n\nexport const useWebOtp = (\n enabled: boolean,\n timeoutMs: number,\n onCode: (result: WebOtpResult) => void,\n onError?: (error: Error) => void,\n) => {\n const abortRef = useRef<AbortController | null>(null);\n\n const stop = useCallback(() => {\n abortRef.current?.abort();\n abortRef.current = null;\n }, []);\n\n const start = useCallback(async () => {\n if (!enabled || !hasWebOtp()) return;\n\n stop();\n const controller = new AbortController();\n abortRef.current = controller;\n\n const timer = setTimeout(() => {\n controller.abort();\n }, timeoutMs);\n\n try {\n const credentials = navigator.credentials as OtpAwareCredentialsContainer;\n const credential = (await credentials.get?.({\n otp: { transport: [\"sms\"] },\n signal: controller.signal,\n })) as OtpCredential | null;\n\n if (credential?.code) {\n onCode({ code: credential.code, source: \"webotp\" });\n }\n } catch (err) {\n const error = err as Error;\n if (error.name !== \"AbortError\") {\n onError?.(error);\n }\n } finally {\n clearTimeout(timer);\n abortRef.current = null;\n }\n }, [enabled, onCode, onError, stop, timeoutMs]);\n\n useEffect(() => {\n if (!enabled) return;\n void start();\n return () => {\n stop();\n };\n }, [enabled, start, stop]);\n\n return {\n isSupported: hasWebOtp(),\n start,\n stop,\n };\n};\n"],"mappings":"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","names":["useCallback","useEffect","useRef","useRef","useCallback","useEffect"]}
1
+ {"version":3,"sources":["../src/OtpInput.tsx","../src/useWebOtp.ts"],"sourcesContent":["import React, { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { useWebOtp } from \"./useWebOtp\";\nimport type { OtpAutoFillSource, OtpInputProps } from \"./types\";\n\nconst isDigit = (value: string) => /^\\d$/.test(value);\n\nconst toSlots = (value: string, length: number) => {\n const normalized = value.replace(/\\D/g, \"\").slice(0, length);\n return Array.from({ length }, (_, idx) => normalized[idx] ?? \"\");\n};\n\nexport const OtpInput = ({\n length = 6,\n value,\n onChange,\n onComplete,\n autoFocus = true,\n disabled = false,\n autoFetch = true,\n fetchTimeoutMs = 60_000,\n className,\n inputClassName,\n allowPaste = true,\n onError,\n}: OtpInputProps) => {\n const [enteredIndex, setEnteredIndex] = useState<number | null>(null);\n const refs = useRef<Array<HTMLInputElement | null>>([]);\n\n const slots = useMemo(() => toSlots(value, length), [value, length]);\n\n const emitValue = useCallback(\n (next: string, source: OtpAutoFillSource) => {\n const trimmed = next.replace(/\\D/g, \"\").slice(0, length);\n onChange(trimmed, source);\n if (trimmed.length === length) {\n onComplete?.(trimmed, source);\n }\n },\n [length, onChange, onComplete],\n );\n\n useWebOtp(\n autoFetch,\n fetchTimeoutMs,\n ({ code, source }) => {\n emitValue(code, source);\n refs.current[Math.min(code.length, length) - 1]?.blur();\n },\n onError,\n );\n\n useEffect(() => {\n if (!autoFocus || disabled) return;\n const firstEmpty = slots.findIndex((slot) => slot === \"\");\n const targetIndex = firstEmpty === -1 ? length - 1 : firstEmpty;\n refs.current[targetIndex]?.focus();\n }, [autoFocus, disabled, length, slots]);\n\n useEffect(() => {\n if (enteredIndex === null) return;\n const timer = setTimeout(() => setEnteredIndex(null), 220);\n return () => clearTimeout(timer);\n }, [enteredIndex]);\n\n const handleChangeAt = (index: number, raw: string) => {\n if (disabled) return;\n const nextChar = raw.slice(-1);\n if (!isDigit(nextChar)) return;\n\n const nextSlots = [...slots];\n nextSlots[index] = nextChar;\n const nextValue = nextSlots.join(\"\");\n\n setEnteredIndex(index);\n emitValue(nextValue, \"manual\");\n\n const nextIndex = Math.min(index + 1, length - 1);\n refs.current[nextIndex]?.focus();\n refs.current[nextIndex]?.select();\n };\n\n const clearAt = (index: number) => {\n const nextSlots = [...slots];\n nextSlots[index] = \"\";\n emitValue(nextSlots.join(\"\"), \"manual\");\n };\n\n const handleKeyDown = (index: number, event: React.KeyboardEvent<HTMLInputElement>) => {\n if (disabled) return;\n\n if (event.key === \"Backspace\") {\n event.preventDefault();\n if (slots[index]) {\n clearAt(index);\n return;\n }\n const prev = Math.max(index - 1, 0);\n clearAt(prev);\n refs.current[prev]?.focus();\n return;\n }\n\n if (event.key === \"ArrowLeft\") {\n event.preventDefault();\n refs.current[Math.max(index - 1, 0)]?.focus();\n return;\n }\n\n if (event.key === \"ArrowRight\") {\n event.preventDefault();\n refs.current[Math.min(index + 1, length - 1)]?.focus();\n return;\n }\n\n if (event.key.length === 1 && !/\\d/.test(event.key)) {\n event.preventDefault();\n }\n };\n\n const handlePaste = (event: React.ClipboardEvent<HTMLInputElement>) => {\n if (!allowPaste || disabled) return;\n event.preventDefault();\n\n const text = event.clipboardData.getData(\"text\").replace(/\\D/g, \"\").slice(0, length);\n if (!text) return;\n\n // Split OTP and fill all cells\n const nextSlots = Array.from({ length }, (_, idx) => text[idx] ?? \"\");\n const nextValue = nextSlots.join(\"\");\n emitValue(nextValue, \"paste\");\n setEnteredIndex(Math.min(text.length - 1, length - 1));\n\n // Move focus to last filled cell\n const focusIndex = Math.min(text.length - 1, length - 1);\n refs.current[focusIndex]?.focus();\n refs.current[focusIndex]?.select();\n };\n\n return (\n <div className={[\"otp-root\", className].filter(Boolean).join(\" \")}>\n {slots.map((digit, index) => {\n const enteredClass = enteredIndex === index ? \"entered\" : \"\";\n const filledClass = digit ? \"filled\" : \"\";\n return (\n <input\n key={index}\n ref={(el) => {\n refs.current[index] = el;\n }}\n value={digit}\n disabled={disabled}\n type=\"text\"\n maxLength={1}\n inputMode=\"numeric\"\n pattern=\"[0-9]*\"\n autoComplete={index === 0 ? \"one-time-code\" : \"off\"}\n className={[\"otp-slot\", filledClass, enteredClass, inputClassName].filter(Boolean).join(\" \")}\n onChange={(e) => handleChangeAt(index, e.target.value)}\n onKeyDown={(e) => handleKeyDown(index, e)}\n onPaste={handlePaste}\n aria-label={`OTP digit ${index + 1}`}\n />\n );\n })}\n </div>\n );\n};\n","import { useCallback, useEffect, useRef } from \"react\";\nimport type { WebOtpResult } from \"./types\";\n\ninterface OtpCredential extends Credential {\n code: string;\n}\n\ntype OtpGetOptions = CredentialRequestOptions & {\n otp?: { transport: string[] };\n};\n\ntype OtpAwareCredentialsContainer = CredentialsContainer & {\n get: (options?: OtpGetOptions) => Promise<Credential | null>;\n};\n\nconst hasWebOtp = () => {\n if (typeof window === \"undefined\") return false;\n if (!window.isSecureContext) return false;\n return typeof (navigator.credentials as Partial<OtpAwareCredentialsContainer> | undefined)?.get === \"function\";\n};\n\nexport const useWebOtp = (\n enabled: boolean,\n timeoutMs: number,\n onCode: (result: WebOtpResult) => void,\n onError?: (error: Error) => void,\n) => {\n const abortRef = useRef<AbortController | null>(null);\n\n const stop = useCallback(() => {\n abortRef.current?.abort();\n abortRef.current = null;\n }, []);\n\n const start = useCallback(async () => {\n if (!enabled || !hasWebOtp()) return;\n\n stop();\n const controller = new AbortController();\n abortRef.current = controller;\n\n const timer = setTimeout(() => {\n controller.abort();\n }, timeoutMs);\n\n try {\n const credentials = navigator.credentials as OtpAwareCredentialsContainer;\n const credential = (await credentials.get?.({\n otp: { transport: [\"sms\"] },\n signal: controller.signal,\n })) as OtpCredential | null;\n\n if (credential?.code) {\n onCode({ code: credential.code, source: \"webotp\" });\n }\n } catch (err) {\n const error = err as Error;\n if (error.name !== \"AbortError\") {\n onError?.(error);\n }\n } finally {\n clearTimeout(timer);\n abortRef.current = null;\n }\n }, [enabled, onCode, onError, stop, timeoutMs]);\n\n useEffect(() => {\n if (!enabled) return;\n void start();\n return () => {\n stop();\n };\n }, [enabled, start, stop]);\n\n return {\n isSupported: hasWebOtp(),\n start,\n stop,\n };\n};\n"],"mappings":"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","names":["useCallback","useEffect","useRef","useRef","useCallback","useEffect"]}
package/dist/styles.css CHANGED
@@ -4,31 +4,54 @@
4
4
  align-items: center;
5
5
  }
6
6
 
7
+
8
+ .otp-root {
9
+ display: flex;
10
+ gap: 1rem;
11
+ align-items: center;
12
+ justify-content: center;
13
+ margin-top: 2rem;
14
+ }
15
+ text-align: center;
16
+
7
17
  .otp-slot {
8
- width: 2.75rem;
9
- height: 3.2rem;
10
- border: 1px solid #c9ced6;
11
- border-radius: 0.65rem;
18
+ width: 3rem;
19
+ height: 3.5rem;
20
+ border: 2px solid #e0e7ef;
21
+ border-radius: 1rem;
22
+ background: #f8fafc;
12
23
  text-align: center;
13
- font-size: 1.35rem;
24
+ font-size: 1.5rem;
14
25
  font-weight: 600;
15
- transition: border-color 160ms ease, box-shadow 160ms ease, transform 160ms ease;
26
+ color: #22223b;
27
+ box-shadow: 0 2px 8px rgba(34, 34, 59, 0.07);
28
+ transition: border-color 180ms, box-shadow 180ms, transform 180ms;
29
+ outline: none;
16
30
  }
17
31
 
32
+
18
33
  .otp-slot:focus {
19
- border-color: #0e7490;
20
- box-shadow: 0 0 0 3px rgba(14, 116, 144, 0.2);
21
- outline: none;
34
+ border-color: #6366f1;
35
+ box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.15);
22
36
  }
37
+ animation: otp-pop 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
23
38
 
24
39
  .otp-slot.filled {
25
- border-color: #1d4ed8;
40
+ border-color: #10b981;
41
+ background: #e0f2f1;
26
42
  }
43
+ 0% {
27
44
 
28
45
  .otp-slot.entered {
29
46
  animation: otp-pop 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
30
47
  }
48
+ transform: scale(1.08);
31
49
 
50
+ .otp-slot:hover {
51
+ border-color: #6366f1;
52
+ box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.10);
53
+ }
54
+ }
32
55
  @keyframes otp-pop {
33
56
  0% {
34
57
  transform: scale(0.92);
@@ -36,7 +59,3 @@
36
59
  55% {
37
60
  transform: scale(1.08);
38
61
  }
39
- 100% {
40
- transform: scale(1);
41
- }
42
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "otp-auto-fetch-input",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Animated OTP input for React with WebOTP auto-fetch support",
5
5
  "license": "MIT",
6
6
  "type": "module",