BetaBeta pública — Consulta las notas de lanzamiento
Documentación

Input OTP

Ejemplo básico de input otp demostrando el comportamiento principal.

Paquete
@solidiom/input-otp
Versión
0.0.1-next.0
Estado
stable

Ejemplos

import * as InputOtp from "@solidiom/input-otp"

;<InputOtp.Root>Contenido de Input OTP</InputOtp.Root>
Ver código fuente
        export function Root(props: InputOTPRootProps) {
  let inputRef: HTMLInputElement | undefined
  const inputId = createStableId("otp-input")

  const [internalValue, setInternalValue] = createSignal(props.defaultValue ?? "")
  const [isFocused, setIsFocused] = createSignal(false)

  const value = () => {
    if (props.value !== undefined) {
      return props.value() ?? ""
    }
    return internalValue()
  }

  const activeIndex = () => {
    const len = value().length
    return Math.min(len, props.maxLength - 1)
  }

  const patternRegex = props.pattern ? new RegExp(props.pattern) : undefined

  const setValue = (next: string) => {
    const truncated = next.slice(0, props.maxLength)

    // Validate each character
    if (patternRegex) {
      const valid = truncated.split("").every((ch) => patternRegex.test(ch))
      if (!valid) return
    }

    if (props.value === undefined) {
      setInternalValue(truncated)
    }
    props.onValueChange?.(truncated)

    if (truncated.length === props.maxLength) {
      props.onComplete?.(truncated)
    }
  }

  const handleInput = (e: InputEvent) => {
    const target = e.target as HTMLInputElement
    setValue(target.value)
  }

  const handleKeyDown = (e: KeyboardEvent) => {
    if (props.disabled) {
      e.preventDefault()
      return
    }
    // Allow navigation keys
    if (
      e.key === "Backspace" ||
      e.key === "Delete" ||
      e.key === "ArrowLeft" ||
      e.key === "ArrowRight"
    ) {
      return
    }
  }

  const handlePaste = (e: ClipboardEvent) => {
    e.preventDefault()
    if (props.disabled) return
    const pasted = e.clipboardData?.getData("text/plain") ?? ""
    setValue(pasted)
  }

  const focus = () => {
    inputRef?.focus()
  }

  return (
    <InputOTPContext
      value={{
        value,
        maxLength: props.maxLength,
        activeIndex,
        isFocused,
        pattern: patternRegex,
        disabled: props.disabled,
        focus,
      }}
    >
      <div
        class={props.class}
        style={props.style}
        onClick={focus}
        {...applySemanticAttrs({
          scope: "input-otp",
          part: "root",
          disabled: props.disabled,
        })}
      >
        {/* Hidden input drives the actual value */}
        <input
          ref={inputRef}
          id={inputId}
          type="text"
          inputmode="numeric"
          autocomplete="one-time-code"
          maxlength={props.maxLength}
          value={value()}
          disabled={props.disabled}
          onInput={handleInput}
          onKeyDown={handleKeyDown}
          onPaste={handlePaste}
          onFocus={() => setIsFocused(true)}
          onBlur={() => setIsFocused(false)}
          aria-label="One-time password"
          style={{
            position: "absolute",
            width: "1px",
            height: "1px",
            padding: "0",
            margin: "-1px",
            overflow: "hidden",
            clip: "rect(0, 0, 0, 0)",
            "white-space": "nowrap",
            "border-width": "0",
          }}
        />
        {props.children}
      </div>
    </InputOTPContext>
  )
}