{"version":3,"sources":["../../../src/lib/otp-input.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n  composeRefs,\n  useComposedRefs,\n  useControllableState,\n} from \"radix-ui/internal\";\nimport { Form } from \"radix-ui\";\nimport { Grid } from \"@radix-ui/themes\";\nimport * as React from \"react\";\nimport { TextField } from \"./elements.js\";\n\ninterface OptContextType {\n  value: string[];\n  readOnly?: boolean;\n  state?: \"valid\" | \"invalid\";\n  onEnterPressed: () => void;\n  onChildAdd: (input: HTMLInputElement) => void;\n  onCharChange: (char: string, index: number) => void;\n  allChildrenAdded: boolean;\n}\n\nconst OtpContext = React.createContext<OptContextType | undefined>(undefined);\n\ntype OtpRootProps = React.ComponentPropsWithoutRef<typeof Grid> & {\n  onValueChange?: (value: string) => void;\n  id?: string;\n  name?: string;\n  readOnly?: boolean;\n  state?: \"valid\" | \"invalid\";\n  value?: string;\n  defaultValue?: string;\n  autoSubmit?: boolean;\n};\n\nexport const Root = React.forwardRef<HTMLInputElement, OtpRootProps>(\n  function Root(\n    {\n      name,\n      id,\n      defaultValue,\n      value: valueProp,\n      onValueChange,\n      autoSubmit,\n      children,\n      readOnly,\n      state,\n      ...gridProps\n    },\n    forwardedRef,\n  ) {\n    const [lastCharIndex, setLastCharIndex] = React.useState<number>(0);\n    const [allChildrenAdded, setAllChildrenAdded] =\n      React.useState<boolean>(false);\n    const childCount = React.Children.count(children);\n\n    const [value, setValue] = useControllableState({\n      prop: getValueAsArray(valueProp, childCount),\n      defaultProp: getValueAsArray(defaultValue, childCount) ?? [],\n      onChange: (value) => onValueChange?.(value.join(\"\")),\n    });\n\n    const hiddenInputRef = React.useRef<HTMLInputElement>(null);\n    const childrenRefs = React.useRef<HTMLInputElement[]>([]);\n\n    const attemptAutoSubmit = React.useCallback(\n      (enterPressed = false) => {\n        if (\n          autoSubmit &&\n          value &&\n          value.every((char) => char !== \"\") &&\n          (enterPressed || lastCharIndex + 1 === childCount)\n        ) {\n          hiddenInputRef.current?.form?.requestSubmit();\n        }\n      },\n      [value, childCount, lastCharIndex, autoSubmit],\n    );\n\n    const handleEnterPressed = React.useCallback(\n      () => attemptAutoSubmit(true),\n      [attemptAutoSubmit],\n    );\n\n    const handleChildAdd = React.useCallback(\n      (input: HTMLInputElement) => {\n        if (input) {\n          input.dataset.index = `${childrenRefs.current.length}`;\n          childrenRefs.current.push(input);\n        } else {\n          childrenRefs.current.pop();\n        }\n\n        if (childrenRefs.current.length === childCount) {\n          setAllChildrenAdded(true);\n        }\n      },\n      [childCount],\n    );\n\n    const handleCharChange = React.useCallback(\n      (char: string, index: number) => {\n        setValue((previousValue) => {\n          const arrayToCopy = previousValue ?? createEmptyArray(childCount);\n          const newValue = [...arrayToCopy];\n          newValue[index] = char;\n          return newValue;\n        });\n        setLastCharIndex(index);\n      },\n      [childCount, setValue],\n    );\n\n    const otpContext = React.useMemo(\n      () => ({\n        value: value ?? createEmptyArray(childCount),\n        readOnly,\n        state,\n        allChildrenAdded,\n        onEnterPressed: handleEnterPressed,\n        onChildAdd: handleChildAdd,\n        onCharChange: handleCharChange,\n      }),\n      [\n        value,\n        allChildrenAdded,\n        readOnly,\n        state,\n        childCount,\n        handleEnterPressed,\n        handleChildAdd,\n        handleCharChange,\n      ],\n    );\n\n    React.useEffect(attemptAutoSubmit, [attemptAutoSubmit]);\n\n    return (\n      <OtpContext.Provider value={otpContext}>\n        <Grid\n          columns={`repeat(${childCount}, 1fr)`}\n          {...gridProps}\n          onPaste={(event: React.ClipboardEvent<HTMLDivElement>) => {\n            event.preventDefault();\n            const pastedValue = event.clipboardData.getData(\"Text\");\n            const sanitizedValue = pastedValue\n              .replace(/[^\\d]/g, \"\")\n              .slice(0, childCount);\n            const value = sanitizedValue\n              .padEnd(childCount, \"#\")\n              .split(\"\")\n              .map((char) => (char === \"#\" ? \"\" : char));\n\n            setValue(value);\n            setLastCharIndex(sanitizedValue.length - 1);\n\n            const index = Math.min(sanitizedValue.length, childCount - 1);\n            childrenRefs.current?.[index]?.focus();\n          }}\n        >\n          {children}\n          <input\n            ref={composeRefs(forwardedRef, hiddenInputRef)}\n            defaultValue={value?.join(\"\")}\n            minLength={childCount}\n            name={name}\n            type=\"hidden\"\n          />\n        </Grid>\n      </OtpContext.Provider>\n    );\n  },\n);\n\ninterface InputProps extends React.ComponentProps<typeof TextField> {\n  autoComplete?: \"one-time-code\" | \"off\";\n}\n\nexport const Input = React.forwardRef<HTMLInputElement, InputProps>(\n  function Input(\n    { style, readOnly, autoComplete = \"off\", ...props },\n    forwardedRef,\n  ) {\n    const otpContext = useOptContext();\n    const inputRef = React.useRef<HTMLInputElement>(null);\n    const composedInputRef = useComposedRefs(\n      forwardedRef,\n      inputRef,\n      otpContext.onChildAdd,\n    );\n\n    // FIXME: This should be refactored\n    // eslint-disable-next-line react-hooks/refs\n    const index = Number(inputRef.current?.dataset.index ?? -1);\n    const char = otpContext.value[index] ?? \"\";\n\n    return (\n      <Form.Field name={`otp-${index}`} asChild>\n        <Form.Control asChild>\n          <TextField\n            ref={composedInputRef}\n            autoComplete={index === 0 ? autoComplete : \"off\"}\n            color={otpContext.state === \"invalid\" ? \"red\" : undefined}\n            inputMode=\"numeric\"\n            maxLength={1}\n            pattern=\"\\d{1}\"\n            readOnly={readOnly ?? otpContext.readOnly}\n            size=\"3\"\n            value={char}\n            variant={otpContext.state === \"invalid\" ? \"soft\" : undefined}\n            style={{\n              ...style,\n              height: \"auto\",\n              \"--text-field-padding\": 0,\n              textAlign: \"center\",\n            }}\n            onChange={(event) => {\n              // Only update the value if it matches the input pattern (number only)\n              if (event.target.validity.valid) {\n                const char = event.target.value;\n                const index = Number(event.target.dataset.index ?? -1);\n                otpContext.onCharChange(char, index);\n                if (char !== \"\") {\n                  focusSibling(event.currentTarget, { back: char === \"\" });\n                }\n              }\n            }}\n            onKeyDown={(event) => {\n              if (event.key === \"ArrowLeft\") {\n                focusSibling(event.currentTarget, { back: true });\n                event.preventDefault();\n              } else if (event.key === \"ArrowRight\") {\n                focusSibling(event.currentTarget);\n                event.preventDefault();\n              } else if (event.key === \"Backspace\" && char === \"\") {\n                focusSibling(event.currentTarget, { back: true });\n              } else if (event.key === \"Enter\" && char !== \"\") {\n                otpContext.onEnterPressed();\n              }\n            }}\n            {...props}\n          />\n        </Form.Control>\n      </Form.Field>\n    );\n  },\n);\n\nconst useOptContext = () => {\n  const optContext = React.useContext(OtpContext);\n\n  if (!optContext) {\n    throw new Error(\n      \"OtpInput compound components cannot be rendered outside the OtpRoot component\",\n    );\n  }\n\n  return optContext;\n};\n\nfunction focusSibling(input: HTMLInputElement, { back = false } = {}) {\n  const sibling = back\n    ? input.parentElement?.previousSibling\n    : input.parentElement?.nextSibling;\n  const siblingInput = sibling?.firstChild;\n  if (siblingInput && siblingInput instanceof HTMLInputElement) {\n    siblingInput?.focus();\n    siblingInput?.select();\n  }\n}\n\nconst getValueAsArray = (value: string | undefined, length: number) => {\n  if (!value) {\n    return undefined;\n  }\n\n  return createEmptyArray(length).map((_, index) => value?.[index] ?? \"\");\n};\n\nconst createEmptyArray = (length: number): string[] =>\n  Array.from<string>({ length }).fill(\"\");\n"],"mappings":"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","names":["Root","value","Input","char","index"]}