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 +5 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/styles.css +33 -14
- package/package.json +1 -1
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
|
-
|
|
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" : "";
|
package/dist/index.cjs.map
CHANGED
|
@@ -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":"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","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
|
-
|
|
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:
|
|
9
|
-
height: 3.
|
|
10
|
-
border:
|
|
11
|
-
border-radius:
|
|
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.
|
|
24
|
+
font-size: 1.5rem;
|
|
14
25
|
font-weight: 600;
|
|
15
|
-
|
|
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: #
|
|
20
|
-
box-shadow: 0 0 0
|
|
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: #
|
|
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
|
-
}
|