page PinInputcomponent { seo { title = "PinInput component" description = "Secure multi-cell PIN and verification-code input with regex and paste support." } view {
W WRNexusJS
advanced-forms component

PinInput

Secure multi-cell PIN and verification-code input with regex and paste support.

@wrnexus/ui 0.3.626 props0 slots6 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<PinInput
  size="default"
  color="primary"
  label="Verification code"
  name="pin"
  value="value"
/>

Legacy mount name: data-component="PinInput".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
labelstringOptional"Verification code"
namestringOptional"pin"
valuestringOptional""
lengthnumberOptional4
patternstringOptional"[0-9]"
typestringOptional"text"
inputModestringOptional"numeric"
placeholderstringOptional"○"
autocompletestringOptional"one-time-code"
maskedbooleanOptionalfalse
disabledbooleanOptionalfalse
readonlybooleanOptionalfalse
requiredbooleanOptionalfalse
autoFocusbooleanOptionalfalse
autoSubmitbooleanOptionalfalse
allowPastebooleanOptionaltrue
clearablebooleanOptionaltrue
clearLabelstringOptional"Clear code"
separatorstringOptional""
groupSizenumberOptional0
helpTextstringOptional""
invalidbooleanOptionalfalse
validationMessagestringOptional""
classstringOptional""

Slots

This component does not declare a slot.

Events

  • input
  • change
  • complete
  • paste
  • clear
  • error

Source contract

Installed source: components/PinInput.wrn

component PinInput {
  props {
    size = "default"
    color = "primary"
    label = "Verification code"
    name = "pin"
    value = ""
    length = 4
    pattern = "[0-9]"
    type = "text"
    inputMode = "numeric"
    placeholder = "○"
    autocomplete = "one-time-code"
    masked = false
    disabled = false
    readonly = false
    required = false
    autoFocus = false
    autoSubmit = false
    allowPaste = true
    clearable = true
    clearLabel = "Clear code"
    separator = ""
    groupSize = 0
    helpText = ""
    invalid = false
    validationMessage = ""
    class = ""
    @event input = function
    @event change = function
    @event complete = function
    @event paste = function
    @event clear = function
    @event error = function
  }

  functions {
    function inputIndexes() {
      return [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].slice(
        0,
        Math.max(1, Math.min(12, Number(length)))
      )
    }
  }

  view {
        <fieldset
      {...attrs}
      class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--pin-input {invalid ? 'wire-next--invalid' : ''} {disabled ? 'wire-next--disabled' : ''} {class}"
      data-wrn-pin-input
      data-length="{length}"
      data-pattern="{pattern}"
      data-allow-paste="{allowPaste ? 'true' : 'false'}"
      data-auto-submit="{autoSubmit ? 'true' : 'false'}"
      data-value="{value}"
      disabled="{disabled}"
      aria-describedby="{validationMessage ? `${name}-validation` : helpText ? `${name}-help` : ''}"
    >
        <div class="wire-next__pin-heading">
        <legend>{label}</legend>
        <button
          type="button"
          class="wire-next__pin-clear"
          data-pin-clear
          aria-label="{clearLabel}"
          title="{clearLabel}"
          style="{clearable && value ? '' : 'display: none'}"
        >
        <span class="icon-[lucide--rotate-ccw]" aria-hidden="true">
        </span>
        <span>{clearLabel}</span>
        </button>
        </div>
        <div class="wire-next__pin-cells" role="group" aria-label="{label}">
        {#each inputIndexes() as index}
          <input
            data-pin-cell
            data-pin-index="{index}"
            id="{name}-{index}"
            type="{masked ? 'password' : type}"
            inputmode="{inputMode}"
            maxlength="1"
            value="{value[index] || ''}"
            placeholder="{placeholder}"
            autocomplete="{index === 0 ? autocomplete : 'off'}"
            aria-label="{label} digit {index + 1} of {length}"
            aria-required="{required ? 'true' : 'false'}"
            readonly="{readonly}"
            disabled="{disabled}"
            autofocus="{autoFocus && index === 0}"
          />
          {#if separator && groupSize !== 0 && (index + 1) % groupSize === 0 && index + 1 !== length}
            <span class="wire-next__pin-separator" aria-hidden="true">{separator}</span>
          {/if}
        {/each}
      </div>
        <input
        type="hidden"
        name="{name}"
        value="{value}"
        required="{required}"
        data-pin-value
      />

      {#if helpText && !validationMessage}<small id="{name}-help">{helpText}</small>{/if}
      <small
        id="{name}-validation"
        class="wire-next__validation"
        data-error="{name}"
      >{validationMessage}</small>
        </fieldset>
    }
}
} }