{
  "name": "input-otp",
  "type": "registry:component",
  "files": [
    {
      "name": "input-otp.component.ts",
      "content": "import {\n  type AfterContentInit,\n  afterNextRender,\n  booleanAttribute,\n  ChangeDetectionStrategy,\n  Component,\n  computed,\n  contentChildren,\n  type ElementRef,\n  forwardRef,\n  inject,\n  Injector,\n  input,\n  output,\n  signal,\n  ViewEncapsulation,\n  viewChildren,\n} from '@angular/core';\nimport { type ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\n\nimport type { ClassValue } from 'clsx';\n\nimport { mergeClasses } from '@/shared/utils/merge-classes';\n\nimport { ZARD_INPUT_OTP_SLOT, type ZardInputOtpSlotApi } from './input-otp.tokens';\nimport { isInputElement, isInputEvent } from './input-otp.utils';\nimport { inputOtpSlotVariants, inputOtpVariants, type ZardInputOtpSize } from './input-otp.variants';\n\ntype OnTouchedType = () => void;\ntype OnChangeType = (value: string) => void;\n\n@Component({\n  selector: 'z-input-otp, [z-input-otp]',\n  template: `\n    <div [class]=\"classes()\" [attr.data-input-otp-container]=\"''\">\n      @if (!hasSlots()) {\n        @for (i of range(); track i) {\n          <input\n            #otpInput\n            type=\"text\"\n            [value]=\"tokens()[i - 1] || ''\"\n            [attr.maxlength]=\"1\"\n            [attr.inputmode]=\"inputMode()\"\n            [attr.autocomplete]=\"'one-time-code'\"\n            [attr.aria-label]=\"ariaLabel(i)\"\n            [attr.aria-invalid]=\"zInvalid() ? 'true' : null\"\n            [attr.data-active]=\"allSelected() ? '' : null\"\n            [disabled]=\"disabled()\"\n            [readonly]=\"zReadonly()\"\n            [class]=\"slotClasses(i - 1)\"\n            (input)=\"onInput($event, i - 1)\"\n            (focus)=\"onInputFocus($event, i - 1)\"\n            (blur)=\"onInputBlur()\"\n            (paste)=\"onPaste($event)\"\n            (keydown)=\"onKeyDown($event)\"\n          />\n        }\n      }\n      <ng-content />\n    </div>\n  `,\n  providers: [\n    {\n      provide: NG_VALUE_ACCESSOR,\n      useExisting: forwardRef(() => ZardInputOtpComponent),\n      multi: true,\n    },\n  ],\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  encapsulation: ViewEncapsulation.None,\n  host: {\n    '[attr.data-slot]': '\"input-otp\"',\n    '[attr.data-disabled]': 'disabled() ? \"\" : null',\n    '(copy)': 'onCopy($event)',\n    '(cut)': 'onCut($event)',\n    '(mousedown)': 'clearSelectAll()',\n  },\n})\nexport class ZardInputOtpComponent implements ControlValueAccessor, AfterContentInit {\n  readonly inputs = viewChildren<ElementRef<HTMLInputElement>>('otpInput');\n\n  readonly zMaxLength = input<number | undefined>(undefined);\n  readonly zPattern = input<string>('[0-9]');\n  readonly class = input<ClassValue>('');\n  readonly zReadonly = input(false, { transform: booleanAttribute });\n  readonly zIntegerOnly = input(true, { transform: booleanAttribute });\n  readonly zInvalid = input(false, { transform: booleanAttribute });\n  readonly zSize = input<ZardInputOtpSize>('default');\n\n  zValueChange = output<string>();\n  zComplete = output<string>();\n\n  readonly slots = contentChildren<ZardInputOtpSlotApi>(ZARD_INPUT_OTP_SLOT, { descendants: true });\n\n  readonly tokens = signal<string[]>([]);\n  readonly disabled = signal<boolean>(false);\n  readonly focusedIndex = signal<number>(-1);\n  /** Set by Ctrl/Cmd+A: every slot reads as selected so a copy takes the whole value. */\n  readonly allSelected = signal<boolean>(false);\n  readonly classes = computed(() => mergeClasses(inputOtpVariants(), this.class()));\n  readonly inputMode = computed(() => (this.zIntegerOnly() ? 'numeric' : 'text'));\n  readonly patternRegex = computed(() => new RegExp(this.zPattern()));\n\n  readonly hasSlots = signal(false);\n  readonly effectiveMaxLength = computed(() => this.zMaxLength() ?? (this.hasSlots() ? this.slots().length : 6));\n  readonly range = computed(() => Array.from({ length: this.effectiveMaxLength() }, (_, index) => index + 1));\n\n  private onTouched: OnTouchedType = () => {\n    /* empty */\n  };\n\n  private onChange: OnChangeType = () => {\n    /* empty */\n  };\n\n  private readonly injector = inject(Injector);\n\n  ngAfterContentInit(): void {\n    if (this.slots().length > 0) {\n      this.hasSlots.set(true);\n      const maxLength = this.effectiveMaxLength();\n      const currentTokens = this.tokens();\n      if (currentTokens.length > maxLength) {\n        this.tokens.set(currentTokens.slice(0, maxLength));\n      }\n    }\n    this.syncSlots();\n  }\n\n  ariaLabel(position: number): string {\n    return `One-time password digit ${position} of ${this.effectiveMaxLength()}`;\n  }\n\n  slotClasses(index: number): string {\n    const extras: string[] = [];\n\n    if (index === 0) {\n      extras.push('rounded-l-lg border-l');\n    }\n\n    if (index === this.effectiveMaxLength() - 1) {\n      extras.push('rounded-r-lg');\n    }\n\n    return mergeClasses(inputOtpSlotVariants({ zSize: this.zSize() }), extras);\n  }\n\n  writeValue(value: string): void {\n    if (value) {\n      this.tokens.set(value.split('').slice(0, this.effectiveMaxLength()));\n    } else {\n      this.tokens.set([]);\n    }\n    this.syncSlots();\n  }\n\n  registerOnChange(fn: OnChangeType): void {\n    this.onChange = fn;\n  }\n\n  registerOnTouched(fn: OnTouchedType): void {\n    this.onTouched = fn;\n  }\n\n  setDisabledState(isDisabled: boolean): void {\n    this.disabled.set(isDisabled);\n  }\n\n  onInput(event: Event, index: number): void {\n    if (!isInputElement(event.target)) {\n      return;\n    }\n    this.allSelected.set(false);\n    const input = event.target;\n    const { value } = input;\n\n    if (index === 0 && value.length > 1) {\n      this.handlePaste(value);\n      event.stopPropagation();\n      return;\n    }\n\n    const regex = this.patternRegex();\n\n    if (value && !regex.test(value)) {\n      input.value = this.tokens()[index] || '';\n      return;\n    }\n\n    this.tokens.update(prev => {\n      const next = prev.slice();\n      next[index] = value;\n      return next;\n    });\n    this.updateModel();\n\n    const inputType = isInputEvent(event) ? event.inputType : '';\n    if (inputType === 'deleteContentBackward') {\n      this.moveToPrev(event);\n    } else if (inputType === 'insertText' || inputType === 'deleteContentForward') {\n      this.moveToNext(event);\n    }\n  }\n\n  updateModel(): void {\n    const newValue = this.tokens().join('');\n    this.emitValue(newValue);\n    this.zValueChange.emit(newValue);\n\n    if (newValue.length === this.effectiveMaxLength()) {\n      this.zComplete.emit(newValue);\n    }\n\n    this.syncSlots();\n  }\n\n  protected emitValue(newValue: string): void {\n    this.onChange(newValue);\n  }\n\n  onInputFocus(event: Event, index: number): void {\n    if (isInputElement(event.target)) {\n      event.target.select();\n    }\n    this.allSelected.set(false);\n    this.focusedIndex.set(index);\n    this.syncSlots();\n  }\n\n  onInputBlur(): void {\n    this.allSelected.set(false);\n    this.focusedIndex.set(-1);\n    this.onTouched();\n    this.syncSlots();\n  }\n\n  selectAll(): void {\n    this.allSelected.set(true);\n    this.syncSlots();\n  }\n\n  clearSelectAll(): void {\n    if (this.allSelected()) {\n      this.allSelected.set(false);\n      this.syncSlots();\n    }\n  }\n\n  onCopy(event: ClipboardEvent): void {\n    if (!this.allSelected()) {\n      return;\n    }\n\n    event.preventDefault();\n    event.clipboardData?.setData('text/plain', this.tokens().join(''));\n  }\n\n  onCut(event: ClipboardEvent): void {\n    this.onCopy(event);\n\n    if (event.defaultPrevented && !this.disabled() && !this.zReadonly()) {\n      this.clearValue();\n    }\n  }\n\n  /** Empties every slot and moves focus back to the first one. */\n  clearValue(): void {\n    this.allSelected.set(false);\n    this.tokens.set([]);\n    this.updateModel();\n    this.focusSlotAt(0);\n  }\n\n  onPaste(event: ClipboardEvent): void {\n    if (this.disabled() || this.zReadonly()) {\n      return;\n    }\n\n    const paste = event.clipboardData?.getData('text');\n    if (paste && paste.length) {\n      this.handlePaste(paste);\n    }\n\n    event.preventDefault();\n  }\n\n  handlePaste(paste: string): void {\n    const regex = this.patternRegex();\n    const pastedCode = paste\n      .substring(0, this.effectiveMaxLength())\n      .split('')\n      .filter(char => regex.test(char))\n      .join('');\n\n    this.tokens.set(pastedCode.split(''));\n    this.updateModel();\n\n    this.focusSlotAt(this.tokens().length);\n  }\n\n  /** Focuses the slot at `index`, clamped to the available range, once the view has settled. */\n  private focusSlotAt(index: number): void {\n    const target = Math.max(0, Math.min(index, this.effectiveMaxLength() - 1));\n\n    afterNextRender(\n      () => {\n        if (this.hasSlots()) {\n          this.slots()[target]?.focus();\n        } else {\n          this.inputs()[target]?.nativeElement.focus();\n        }\n      },\n      { injector: this.injector },\n    );\n  }\n\n  onKeyDown(event: KeyboardEvent): void {\n    if (event.altKey || event.ctrlKey || event.metaKey) {\n      this.onShortcut(event);\n      return;\n    }\n\n    if (!isInputElement(event.target)) {\n      return;\n    }\n    const input = event.target;\n\n    // With every slot selected, the next keystroke acts on the whole value.\n    if (this.allSelected()) {\n      if (event.key === 'Backspace' || event.key === 'Delete') {\n        event.preventDefault();\n        if (!this.zReadonly()) {\n          this.clearValue();\n        }\n        return;\n      }\n\n      if (event.key.length === 1) {\n        event.preventDefault();\n        this.allSelected.set(false);\n        if (!this.zReadonly() && this.patternRegex().test(event.key)) {\n          this.tokens.set([event.key]);\n          this.updateModel();\n          this.focusSlotAt(1);\n        } else {\n          this.syncSlots();\n        }\n        return;\n      }\n\n      this.allSelected.set(false);\n      this.syncSlots();\n    }\n\n    switch (event.key) {\n      case 'ArrowLeft':\n        this.moveToPrev(event);\n        event.preventDefault();\n        break;\n\n      case 'ArrowUp':\n      case 'ArrowDown':\n        event.preventDefault();\n        break;\n\n      case 'Backspace':\n        if (input.value.length === 0) {\n          this.moveToPrev(event);\n          event.preventDefault();\n        }\n        break;\n\n      case 'Delete':\n        break;\n\n      case 'ArrowRight':\n        this.moveToNext(event);\n        event.preventDefault();\n        break;\n\n      default: {\n        if (event.key.length > 1) {\n          return;\n        }\n\n        if (!this.patternRegex().test(event.key)) {\n          event.preventDefault();\n          break;\n        }\n\n        // A slot holds a single character, so typing over a filled one replaces it.\n        if (input.value && input.selectionStart === input.selectionEnd) {\n          input.select();\n        }\n        break;\n      }\n    }\n  }\n\n  /** Handles the modifier shortcuts the component owns; everything else falls through to the browser. */\n  private onShortcut(event: KeyboardEvent): void {\n    const key = event.key.toLowerCase();\n\n    if ((event.ctrlKey || event.metaKey) && key === 'a') {\n      event.preventDefault();\n      this.selectAll();\n      return;\n    }\n\n    // Keep the selection alive for copy/cut, drop it for anything else.\n    if (key !== 'c' && key !== 'x') {\n      this.clearSelectAll();\n    }\n  }\n\n  moveToNext(event: Event): void {\n    if (!isInputElement(event.target)) {\n      return;\n    }\n    const nextInput = this.findNextInput(event.target);\n    if (nextInput) {\n      nextInput.focus();\n      nextInput.select();\n    }\n  }\n\n  moveToPrev(event: Event): void {\n    if (!isInputElement(event.target)) {\n      return;\n    }\n    const prevInput = this.findPrevInput(event.target);\n    if (prevInput) {\n      prevInput.focus();\n      prevInput.select();\n    }\n  }\n\n  findNextInput(element: HTMLElement): HTMLInputElement | null {\n    if (element.hasAttribute('data-input-otp-container')) {\n      return null;\n    }\n\n    const nextElement = element.nextElementSibling;\n    if (!nextElement) {\n      const parent = element.parentElement;\n      if (!parent) {\n        return null;\n      }\n      return this.findNextInput(parent);\n    }\n\n    if (nextElement instanceof HTMLInputElement) {\n      return nextElement;\n    }\n\n    const inputInside = nextElement.querySelector('input');\n    if (inputInside) {\n      return inputInside;\n    }\n\n    return this.findNextInput(nextElement as HTMLElement);\n  }\n\n  findPrevInput(element: HTMLElement): HTMLInputElement | null {\n    if (element.hasAttribute('data-input-otp-container')) {\n      return null;\n    }\n\n    const prevElement = element.previousElementSibling;\n    if (!prevElement) {\n      const parent = element.parentElement;\n      if (!parent) {\n        return null;\n      }\n      return this.findPrevInput(parent);\n    }\n\n    if (prevElement instanceof HTMLInputElement) {\n      return prevElement;\n    }\n\n    const inputs = prevElement.querySelectorAll('input');\n    if (inputs.length) {\n      return inputs[inputs.length - 1];\n    }\n\n    return this.findPrevInput(prevElement as HTMLElement);\n  }\n\n  protected syncSlots(): void {\n    if (!this.hasSlots()) {\n      return;\n    }\n    const slotsArray = this.slots();\n    const focused = this.focusedIndex();\n    const tokens = this.tokens();\n    const all = this.allSelected();\n    for (let i = 0; i < slotsArray.length; i++) {\n      const char = tokens[i] || '';\n      const isActive = all || i === focused;\n      slotsArray[i].updateState(char, isActive, !all && isActive && !char);\n    }\n  }\n}\n"
    },
    {
      "name": "input-otp-signal.component.ts",
      "content": "import { ChangeDetectionStrategy, Component, effect, forwardRef, model, untracked } from '@angular/core';\nimport type { FormValueControl } from '@angular/forms/signals';\n\nimport { ZardInputOtpComponent } from './input-otp.component';\n\n@Component({\n  selector: 'z-input-otp-signal, [z-input-otp-signal]',\n  template: `\n    <div [class]=\"classes()\" [attr.data-input-otp-container]=\"''\">\n      @if (!hasSlots()) {\n        @for (i of range(); track i) {\n          <input\n            #otpInput\n            type=\"text\"\n            [value]=\"tokens()[i - 1] || ''\"\n            [attr.maxlength]=\"1\"\n            [attr.inputmode]=\"inputMode()\"\n            [attr.autocomplete]=\"'one-time-code'\"\n            [attr.aria-label]=\"ariaLabel(i)\"\n            [disabled]=\"disabled()\"\n            [readonly]=\"zReadonly()\"\n            [class]=\"slotClasses(i - 1)\"\n            (input)=\"onInput($event, i - 1)\"\n            (focus)=\"onInputFocus($event, i - 1)\"\n            (blur)=\"onInputBlur()\"\n            (paste)=\"onPaste($event)\"\n            (keydown)=\"onKeyDown($event)\"\n          />\n        }\n      }\n      <ng-content />\n    </div>\n  `,\n  providers: [\n    {\n      provide: ZardInputOtpComponent,\n      useExisting: forwardRef(() => ZardInputOtpSignalComponent),\n    },\n  ],\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  host: {\n    '[attr.data-disabled]': 'disabled() ? \"\" : null',\n  },\n})\nexport class ZardInputOtpSignalComponent extends ZardInputOtpComponent implements FormValueControl<string> {\n  readonly value = model<string>('');\n  override readonly disabled = model<boolean>(false);\n\n  constructor() {\n    super();\n\n    effect(() => {\n      const next = this.value() ?? '';\n      const current = untracked(() => this.tokens().join(''));\n      if (current !== next) {\n        super.writeValue(next);\n      }\n    });\n  }\n\n  protected override emitValue(newValue: string): void {\n    this.value.set(newValue);\n  }\n}\n"
    },
    {
      "name": "input-otp-slot.component.ts",
      "content": "import {\n  booleanAttribute,\n  ChangeDetectionStrategy,\n  Component,\n  computed,\n  type ElementRef,\n  forwardRef,\n  inject,\n  input,\n  signal,\n  viewChild,\n} from '@angular/core';\n\nimport type { ClassValue } from 'clsx';\n\nimport { mergeClasses } from '@/shared/utils/merge-classes';\n\nimport { ZardInputOtpComponent } from './input-otp.component';\nimport { ZARD_INPUT_OTP_SLOT } from './input-otp.tokens';\nimport { isInputElement } from './input-otp.utils';\nimport { inputOtpSlotVariants } from './input-otp.variants';\n\n@Component({\n  selector: 'z-input-otp-slot, [z-input-otp-slot]',\n  template: `\n    <input\n      #slotInput\n      type=\"text\"\n      [value]=\"char()\"\n      [attr.maxlength]=\"1\"\n      [attr.inputmode]=\"inputOtp?.inputMode() || 'numeric'\"\n      [attr.autocomplete]=\"'one-time-code'\"\n      [attr.aria-label]=\"ariaLabel()\"\n      [attr.aria-invalid]=\"invalid() ? 'true' : null\"\n      [disabled]=\"inputOtp?.disabled()\"\n      [readonly]=\"inputOtp?.zReadonly()\"\n      [class]=\"classes()\"\n      [attr.data-active]=\"isActive() ? '' : null\"\n      (input)=\"onInput($event)\"\n      (focus)=\"onFocus($event)\"\n      (blur)=\"onBlur()\"\n      (paste)=\"onPaste($event)\"\n      (keydown)=\"onKeyDown($event)\"\n    />\n    @if (hasFakeCaret() && !char()) {\n      <div class=\"pointer-events-none absolute inset-0 flex items-center justify-center\">\n        <div class=\"animate-caret-blink bg-foreground h-4 w-px duration-1000\"></div>\n      </div>\n    }\n  `,\n  providers: [\n    {\n      provide: ZARD_INPUT_OTP_SLOT,\n      useExisting: forwardRef(() => ZardInputOtpSlotComponent),\n    },\n  ],\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  host: {\n    '[attr.data-slot]': '\"input-otp-slot\"',\n    class: 'relative',\n  },\n})\nexport class ZardInputOtpSlotComponent {\n  readonly slotInputRef = viewChild.required<ElementRef<HTMLInputElement>>('slotInput');\n\n  inputOtp = inject(ZardInputOtpComponent, { optional: true });\n\n  readonly zIndex = input.required<number>();\n  readonly zInvalid = input(false, { transform: booleanAttribute });\n  readonly class = input<ClassValue>('');\n\n  /** A slot is invalid when marked directly or when the whole input is. */\n  readonly invalid = computed(() => this.zInvalid() || (this.inputOtp?.zInvalid() ?? false));\n\n  readonly char = signal<string>('');\n  readonly isActive = signal<boolean>(false);\n  readonly hasFakeCaret = signal<boolean>(false);\n\n  readonly classes = computed(() =>\n    mergeClasses(\n      inputOtpSlotVariants({ zSize: this.inputOtp?.zSize() ?? 'default' }),\n      // The blinking caret is rendered by this component, so the native one is hidden.\n      'caret-transparent',\n      this.class(),\n    ),\n  );\n\n  readonly ariaLabel = computed(() => {\n    const total = this.inputOtp?.effectiveMaxLength() ?? this.zIndex() + 1;\n    return `One-time password digit ${this.zIndex() + 1} of ${total}`;\n  });\n\n  getInputElement(): HTMLInputElement {\n    return this.slotInputRef().nativeElement;\n  }\n\n  focus(): void {\n    const input = this.getInputElement();\n    input.focus();\n    input.select();\n  }\n\n  onInput(event: Event): void {\n    if (!isInputElement(event.target)) {\n      return;\n    }\n    const { value } = event.target;\n\n    if (this.zIndex() === 0 && value.length > 1) {\n      this.inputOtp?.handlePaste(value);\n      event.stopPropagation();\n      return;\n    }\n\n    this.inputOtp?.onInput(event, this.zIndex());\n  }\n\n  onFocus(event: Event): void {\n    if (isInputElement(event.target)) {\n      event.target.select();\n    }\n    this.inputOtp?.onInputFocus(event, this.zIndex());\n  }\n\n  onBlur(): void {\n    this.inputOtp?.onInputBlur();\n  }\n\n  onPaste(event: ClipboardEvent): void {\n    event.preventDefault();\n    if (this.inputOtp?.disabled() || this.inputOtp?.zReadonly()) {\n      return;\n    }\n\n    const paste = event.clipboardData?.getData('text');\n    if (paste?.length) {\n      this.inputOtp?.onPaste(event);\n    }\n  }\n\n  onKeyDown(event: KeyboardEvent): void {\n    this.inputOtp?.onKeyDown(event);\n  }\n\n  updateState(char: string, isActive: boolean, hasFakeCaret: boolean): void {\n    this.char.set(char);\n    this.isActive.set(isActive);\n    this.hasFakeCaret.set(hasFakeCaret);\n\n    const input = this.getInputElement();\n    if (input) {\n      input.value = char;\n    }\n  }\n}\n"
    },
    {
      "name": "input-otp-group.component.ts",
      "content": "import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';\n\nimport type { ClassValue } from 'clsx';\n\nimport { mergeClasses } from '@/shared/utils/merge-classes';\n\nimport { inputOtpGroupVariants } from './input-otp.variants';\n\n@Component({\n  selector: 'z-input-otp-group, [z-input-otp-group]',\n  template: `\n    <div [class]=\"classes()\">\n      <ng-content />\n    </div>\n  `,\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  host: {\n    '[attr.data-slot]': '\"input-otp-group\"',\n    '[attr.data-input-otp-group]': '\"\"',\n  },\n})\nexport class ZardInputOtpGroupComponent {\n  readonly class = input<ClassValue>('');\n\n  readonly classes = computed(() => mergeClasses(inputOtpGroupVariants(), this.class()));\n}\n"
    },
    {
      "name": "input-otp-separator.component.ts",
      "content": "import { ChangeDetectionStrategy, Component, computed, inject, input } from '@angular/core';\n\nimport type { ClassValue } from 'clsx';\n\nimport { mergeClasses } from '@/shared/utils/merge-classes';\n\nimport { ZardInputOtpComponent } from './input-otp.component';\nimport { inputOtpSeparatorVariants } from './input-otp.variants';\n\n@Component({\n  selector: 'z-input-otp-separator, [z-input-otp-separator]',\n  template: `\n    <div [class]=\"classes()\">\n      <svg\n        xmlns=\"http://www.w3.org/2000/svg\"\n        viewBox=\"0 0 24 24\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        stroke-width=\"2\"\n        stroke-linecap=\"round\"\n        stroke-linejoin=\"round\"\n      >\n        <line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\" />\n      </svg>\n    </div>\n  `,\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  host: {\n    'aria-hidden': 'true',\n    '[attr.data-slot]': '\"input-otp-separator\"',\n    '[attr.data-input-otp-separator]': '\"\"',\n  },\n})\nexport class ZardInputOtpSeparatorComponent {\n  private readonly inputOtp = inject(ZardInputOtpComponent, { optional: true });\n\n  readonly class = input<ClassValue>('');\n\n  readonly classes = computed(() =>\n    mergeClasses(inputOtpSeparatorVariants({ zSize: this.inputOtp?.zSize() ?? 'default' }), this.class()),\n  );\n}\n"
    },
    {
      "name": "input-otp.imports.ts",
      "content": "import { ZardInputOtpGroupComponent } from '@/shared/components/input-otp/input-otp-group.component';\nimport { ZardInputOtpSeparatorComponent } from '@/shared/components/input-otp/input-otp-separator.component';\nimport { ZardInputOtpSignalComponent } from '@/shared/components/input-otp/input-otp-signal.component';\nimport { ZardInputOtpSlotComponent } from '@/shared/components/input-otp/input-otp-slot.component';\nimport { ZardInputOtpComponent } from '@/shared/components/input-otp/input-otp.component';\n\nexport const ZardInputOtpImports = [\n  ZardInputOtpComponent,\n  ZardInputOtpSignalComponent,\n  ZardInputOtpGroupComponent,\n  ZardInputOtpSlotComponent,\n  ZardInputOtpSeparatorComponent,\n] as const;\n"
    },
    {
      "name": "input-otp.tokens.ts",
      "content": "import { InjectionToken, type Signal } from '@angular/core';\n\nexport interface ZardInputOtpSlotApi {\n  readonly zIndex: Signal<number>;\n  focus(): void;\n  updateState(char: string, isActive: boolean, hasFakeCaret: boolean): void;\n}\n\nexport const ZARD_INPUT_OTP_SLOT = new InjectionToken<ZardInputOtpSlotApi>('ZardInputOtpSlot');\n"
    },
    {
      "name": "input-otp.utils.ts",
      "content": "/** Ready-made `zPattern` values, mirroring the ones shipped by the `input-otp` library. */\nexport const REGEXP_ONLY_DIGITS = '[0-9]';\nexport const REGEXP_ONLY_CHARS = '[a-zA-Z]';\nexport const REGEXP_ONLY_DIGITS_AND_CHARS = '[a-zA-Z0-9]';\n\nexport function isInputElement(target: EventTarget | null): target is HTMLInputElement {\n  return target instanceof HTMLInputElement;\n}\n\nexport function isInputEvent(event: Event): event is InputEvent {\n  return event instanceof InputEvent;\n}\n"
    },
    {
      "name": "input-otp.variants.ts",
      "content": "import { cva, type VariantProps } from 'class-variance-authority';\n\nexport const inputOtpVariants = cva('flex items-center has-disabled:opacity-50');\n\nexport const inputOtpGroupVariants = cva(\n  'flex items-center rounded-lg has-aria-invalid:border-destructive has-aria-invalid:ring-3 has-aria-invalid:ring-destructive/20 [&>z-input-otp-slot:first-child_input]:rounded-l-lg [&>z-input-otp-slot:first-child_input]:border-l [&>z-input-otp-slot:last-child_input]:rounded-r-lg dark:has-aria-invalid:ring-destructive/40',\n);\n\nexport const inputOtpSlotVariants = cva(\n  'relative flex items-center justify-center border-y border-r border-input bg-transparent text-center transition-all outline-none focus:z-10 focus:border-ring focus:ring-3 focus:ring-ring/50 disabled:cursor-not-allowed aria-invalid:border-destructive data-active:z-10 data-active:border-ring data-active:ring-3 data-active:ring-ring/50 data-active:aria-invalid:border-destructive data-active:aria-invalid:ring-destructive/20 dark:bg-input/30 dark:data-active:aria-invalid:ring-destructive/40',\n  {\n    variants: {\n      zSize: {\n        sm: 'size-7 text-xs',\n        default: 'size-8 text-sm',\n        lg: 'size-10 text-base',\n      },\n    },\n    defaultVariants: {\n      zSize: 'default',\n    },\n  },\n);\n\nexport const inputOtpSeparatorVariants = cva('flex items-center', {\n  variants: {\n    zSize: {\n      sm: \"[&_svg:not([class*='size-'])]:size-3\",\n      default: \"[&_svg:not([class*='size-'])]:size-4\",\n      lg: \"[&_svg:not([class*='size-'])]:size-5\",\n    },\n  },\n  defaultVariants: {\n    zSize: 'default',\n  },\n});\n\nexport type ZardInputOtpSize = NonNullable<VariantProps<typeof inputOtpSlotVariants>['zSize']>;\nexport type ZardInputOtpVariants = VariantProps<typeof inputOtpVariants>;\nexport type ZardInputOtpSlotVariants = VariantProps<typeof inputOtpSlotVariants>;\nexport type ZardInputOtpGroupVariants = VariantProps<typeof inputOtpGroupVariants>;\nexport type ZardInputOtpSeparatorVariants = VariantProps<typeof inputOtpSeparatorVariants>;\n"
    },
    {
      "name": "index.ts",
      "content": "export * from './input-otp.component';\nexport * from './input-otp-signal.component';\nexport * from './input-otp-slot.component';\nexport * from './input-otp-group.component';\nexport * from './input-otp-separator.component';\nexport * from './input-otp.imports';\nexport * from './input-otp.tokens';\nexport * from './input-otp.variants';\n"
    }
  ],
  "demos": [
    {
      "name": "alphanumeric.ts",
      "content": "import { Component } from '@angular/core';\n\nimport { ZardInputOtpImports } from '@/shared/components/input-otp/input-otp.imports';\nimport { REGEXP_ONLY_DIGITS_AND_CHARS } from '@/shared/components/input-otp/input-otp.utils';\n\n@Component({\n  selector: 'z-demo-input-otp-alphanumeric',\n  imports: [ZardInputOtpImports],\n  template: `\n    <z-input-otp [zMaxLength]=\"6\" [zPattern]=\"REGEXP_ONLY_DIGITS_AND_CHARS\" [zIntegerOnly]=\"false\">\n      <z-input-otp-group>\n        <z-input-otp-slot [zIndex]=\"0\" />\n        <z-input-otp-slot [zIndex]=\"1\" />\n        <z-input-otp-slot [zIndex]=\"2\" />\n      </z-input-otp-group>\n      <z-input-otp-separator />\n      <z-input-otp-group>\n        <z-input-otp-slot [zIndex]=\"3\" />\n        <z-input-otp-slot [zIndex]=\"4\" />\n        <z-input-otp-slot [zIndex]=\"5\" />\n      </z-input-otp-group>\n    </z-input-otp>\n  `,\n})\nexport class ZardDemoInputOtpAlphanumericComponent {\n  readonly REGEXP_ONLY_DIGITS_AND_CHARS = REGEXP_ONLY_DIGITS_AND_CHARS;\n}\n"
    },
    {
      "name": "controlled.ts",
      "content": "import { Component } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\n\nimport { ZardInputOtpImports } from '@/shared/components/input-otp/input-otp.imports';\n\n@Component({\n  selector: 'z-demo-input-otp-controlled',\n  imports: [ZardInputOtpImports, FormsModule],\n  template: `\n    <div class=\"space-y-2\">\n      <z-input-otp [zMaxLength]=\"6\" [(ngModel)]=\"value\">\n        <z-input-otp-group>\n          <z-input-otp-slot [zIndex]=\"0\" />\n          <z-input-otp-slot [zIndex]=\"1\" />\n          <z-input-otp-slot [zIndex]=\"2\" />\n          <z-input-otp-slot [zIndex]=\"3\" />\n          <z-input-otp-slot [zIndex]=\"4\" />\n          <z-input-otp-slot [zIndex]=\"5\" />\n        </z-input-otp-group>\n      </z-input-otp>\n      <div class=\"text-center text-sm\">\n        @if (value === '') {\n          Enter your one-time password.\n        } @else {\n          You entered: {{ value }}\n        }\n      </div>\n    </div>\n  `,\n})\nexport class ZardDemoInputOtpControlledComponent {\n  value = '';\n}\n"
    },
    {
      "name": "disabled.ts",
      "content": "import { Component } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\n\nimport { ZardInputOtpImports } from '@/shared/components/input-otp/input-otp.imports';\n\n@Component({\n  selector: 'z-demo-input-otp-disabled',\n  imports: [ZardInputOtpImports, FormsModule],\n  template: `\n    <z-input-otp id=\"disabled\" [zMaxLength]=\"6\" [(ngModel)]=\"value\" [disabled]=\"true\">\n      <z-input-otp-group>\n        <z-input-otp-slot [zIndex]=\"0\" />\n        <z-input-otp-slot [zIndex]=\"1\" />\n        <z-input-otp-slot [zIndex]=\"2\" />\n      </z-input-otp-group>\n      <z-input-otp-separator />\n      <z-input-otp-group>\n        <z-input-otp-slot [zIndex]=\"3\" />\n        <z-input-otp-slot [zIndex]=\"4\" />\n        <z-input-otp-slot [zIndex]=\"5\" />\n      </z-input-otp-group>\n    </z-input-otp>\n  `,\n})\nexport class ZardDemoInputOtpDisabledComponent {\n  value = '123456';\n}\n"
    },
    {
      "name": "form.ts",
      "content": "import { Component } from '@angular/core';\nimport { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';\n\nimport { NgIcon, provideIcons } from '@ng-icons/core';\nimport { lucideRefreshCw } from '@ng-icons/lucide';\n\nimport { ZardButtonComponent } from '@/shared/components/button/button.component';\nimport { ZardCardImports } from '@/shared/components/card/card.imports';\nimport { ZardFieldImports } from '@/shared/components/field/field.imports';\nimport { ZardInputOtpImports } from '@/shared/components/input-otp/input-otp.imports';\n\nconst SLOT_CLASSES =\n  '[&_[data-slot=input-otp-slot]>input]:h-12 [&_[data-slot=input-otp-slot]>input]:w-11 [&_[data-slot=input-otp-slot]>input]:text-xl';\n\n@Component({\n  selector: 'z-demo-input-otp-form',\n  imports: [ZardInputOtpImports, ZardFieldImports, ZardCardImports, ZardButtonComponent, ReactiveFormsModule, NgIcon],\n  template: `\n    <form [formGroup]=\"form\" (ngSubmit)=\"onSubmit()\">\n      <z-card class=\"mx-auto max-w-md\">\n        <div z-card-header>\n          <z-card-title zTitle=\"Verify your login\" />\n          <z-card-description [zDescription]=\"description\" />\n          <ng-template #description>\n            Enter the verification code we sent to your email address:\n            <span class=\"font-medium\">m&#64;example.com</span>\n          </ng-template>\n        </div>\n\n        <div z-card-content>\n          <div z-field>\n            <div class=\"flex items-center justify-between\">\n              <label z-field-label for=\"otp-verification\">Verification code</label>\n              <button z-button type=\"button\" zType=\"outline\" zSize=\"xs\">\n                <ng-icon name=\"lucideRefreshCw\" />\n                Resend Code\n              </button>\n            </div>\n            <z-input-otp id=\"otp-verification\" [zMaxLength]=\"6\" formControlName=\"code\">\n              <z-input-otp-group [class]=\"slotClasses\">\n                <z-input-otp-slot [zIndex]=\"0\" />\n                <z-input-otp-slot [zIndex]=\"1\" />\n                <z-input-otp-slot [zIndex]=\"2\" />\n              </z-input-otp-group>\n              <z-input-otp-separator class=\"mx-2\" />\n              <z-input-otp-group [class]=\"slotClasses\">\n                <z-input-otp-slot [zIndex]=\"3\" />\n                <z-input-otp-slot [zIndex]=\"4\" />\n                <z-input-otp-slot [zIndex]=\"5\" />\n              </z-input-otp-group>\n            </z-input-otp>\n            <p z-field-description>\n              <a href=\"#\">I no longer have access to this email address.</a>\n            </p>\n          </div>\n        </div>\n\n        <div z-card-footer>\n          <div z-field>\n            <button z-button type=\"submit\" class=\"w-full\" [disabled]=\"form.invalid\">Verify</button>\n            <div class=\"text-muted-foreground text-sm\">\n              Having trouble signing in?\n              <a href=\"#\" class=\"hover:text-primary underline underline-offset-4 transition-colors\">Contact support</a>\n            </div>\n          </div>\n        </div>\n      </z-card>\n    </form>\n  `,\n  viewProviders: [provideIcons({ lucideRefreshCw })],\n})\nexport class ZardDemoInputOtpFormComponent {\n  readonly slotClasses = SLOT_CLASSES;\n\n  readonly form = new FormGroup({\n    code: new FormControl('', [Validators.required, Validators.minLength(6)]),\n  });\n\n  onSubmit(): void {\n    if (this.form.valid) {\n      console.log('Verification code:', this.form.value.code);\n    }\n  }\n}\n"
    },
    {
      "name": "four-digits.ts",
      "content": "import { Component } from '@angular/core';\n\nimport { ZardInputOtpImports } from '@/shared/components/input-otp/input-otp.imports';\nimport { REGEXP_ONLY_DIGITS } from '@/shared/components/input-otp/input-otp.utils';\n\n@Component({\n  selector: 'z-demo-input-otp-four-digits',\n  imports: [ZardInputOtpImports],\n  template: `\n    <z-input-otp [zMaxLength]=\"4\" [zPattern]=\"REGEXP_ONLY_DIGITS\">\n      <z-input-otp-group>\n        <z-input-otp-slot [zIndex]=\"0\" />\n        <z-input-otp-slot [zIndex]=\"1\" />\n        <z-input-otp-slot [zIndex]=\"2\" />\n        <z-input-otp-slot [zIndex]=\"3\" />\n      </z-input-otp-group>\n    </z-input-otp>\n  `,\n})\nexport class ZardDemoInputOtpFourDigitsComponent {\n  readonly REGEXP_ONLY_DIGITS = REGEXP_ONLY_DIGITS;\n}\n"
    },
    {
      "name": "invalid.ts",
      "content": "import { Component } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\n\nimport { ZardInputOtpImports } from '@/shared/components/input-otp/input-otp.imports';\n\n@Component({\n  selector: 'z-demo-input-otp-invalid',\n  imports: [ZardInputOtpImports, FormsModule],\n  template: `\n    <z-input-otp [zMaxLength]=\"6\" [(ngModel)]=\"value\">\n      <z-input-otp-group>\n        <z-input-otp-slot [zIndex]=\"0\" zInvalid />\n        <z-input-otp-slot [zIndex]=\"1\" zInvalid />\n      </z-input-otp-group>\n      <z-input-otp-separator />\n      <z-input-otp-group>\n        <z-input-otp-slot [zIndex]=\"2\" zInvalid />\n        <z-input-otp-slot [zIndex]=\"3\" zInvalid />\n      </z-input-otp-group>\n      <z-input-otp-separator />\n      <z-input-otp-group>\n        <z-input-otp-slot [zIndex]=\"4\" zInvalid />\n        <z-input-otp-slot [zIndex]=\"5\" zInvalid />\n      </z-input-otp-group>\n    </z-input-otp>\n  `,\n})\nexport class ZardDemoInputOtpInvalidComponent {\n  value = '000000';\n}\n"
    },
    {
      "name": "pattern.ts",
      "content": "import { Component } from '@angular/core';\n\nimport { ZardFieldImports } from '@/shared/components/field/field.imports';\nimport { ZardInputOtpImports } from '@/shared/components/input-otp/input-otp.imports';\nimport { REGEXP_ONLY_DIGITS } from '@/shared/components/input-otp/input-otp.utils';\n\n@Component({\n  selector: 'z-demo-input-otp-pattern',\n  imports: [ZardInputOtpImports, ZardFieldImports],\n  template: `\n    <div z-field class=\"w-fit\">\n      <label z-field-label for=\"digits-only\">Digits Only</label>\n      <z-input-otp id=\"digits-only\" [zMaxLength]=\"6\" [zPattern]=\"REGEXP_ONLY_DIGITS\">\n        <z-input-otp-group>\n          <z-input-otp-slot [zIndex]=\"0\" />\n          <z-input-otp-slot [zIndex]=\"1\" />\n          <z-input-otp-slot [zIndex]=\"2\" />\n          <z-input-otp-slot [zIndex]=\"3\" />\n          <z-input-otp-slot [zIndex]=\"4\" />\n          <z-input-otp-slot [zIndex]=\"5\" />\n        </z-input-otp-group>\n      </z-input-otp>\n    </div>\n  `,\n})\nexport class ZardDemoInputOtpPatternComponent {\n  readonly REGEXP_ONLY_DIGITS = REGEXP_ONLY_DIGITS;\n}\n"
    },
    {
      "name": "preview.ts",
      "content": "import { Component } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\n\nimport { ZardInputOtpImports } from '@/shared/components/input-otp/input-otp.imports';\n\n@Component({\n  selector: 'z-demo-input-otp-preview',\n  imports: [ZardInputOtpImports, FormsModule],\n  template: `\n    <z-input-otp [zMaxLength]=\"6\" [(ngModel)]=\"value\">\n      <z-input-otp-group>\n        <z-input-otp-slot [zIndex]=\"0\" />\n        <z-input-otp-slot [zIndex]=\"1\" />\n        <z-input-otp-slot [zIndex]=\"2\" />\n        <z-input-otp-slot [zIndex]=\"3\" />\n        <z-input-otp-slot [zIndex]=\"4\" />\n        <z-input-otp-slot [zIndex]=\"5\" />\n      </z-input-otp-group>\n    </z-input-otp>\n  `,\n})\nexport class ZardDemoInputOtpPreviewComponent {\n  value = '123456';\n}\n"
    },
    {
      "name": "separator.ts",
      "content": "import { ChangeDetectionStrategy, Component } from '@angular/core';\n\nimport { ZardInputOtpImports } from '@/shared/components/input-otp/input-otp.imports';\n\n@Component({\n  selector: 'z-demo-input-otp-separator',\n  imports: [ZardInputOtpImports],\n  template: `\n    <z-input-otp [zMaxLength]=\"6\">\n      <z-input-otp-group>\n        <z-input-otp-slot [zIndex]=\"0\" />\n        <z-input-otp-slot [zIndex]=\"1\" />\n      </z-input-otp-group>\n      <z-input-otp-separator />\n      <z-input-otp-group>\n        <z-input-otp-slot [zIndex]=\"2\" />\n        <z-input-otp-slot [zIndex]=\"3\" />\n      </z-input-otp-group>\n      <z-input-otp-separator />\n      <z-input-otp-group>\n        <z-input-otp-slot [zIndex]=\"4\" />\n        <z-input-otp-slot [zIndex]=\"5\" />\n      </z-input-otp-group>\n    </z-input-otp>\n  `,\n  changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ZardDemoInputOtpSeparatorComponent {}\n"
    }
  ]
}
