{"version":3,"file":"input-otp2.mjs","names":[],"sources":["../../../../../../packages/components/input-otp/src/input-otp.vue"],"sourcesContent":["<template>\n  <div\n    :id=\"inputId\"\n    :class=\"[ns.b(), ns.m(size), ns.m(type), ns.is('disabled', disabled)]\"\n    role=\"group\"\n    :aria-label=\"\n      !isLabeledByFormItem\n        ? ariaLabel || t('el.inputOTP.groupLabel')\n        : undefined\n    \"\n    :aria-labelledby=\"isLabeledByFormItem ? formItem?.labelId : undefined\"\n  >\n    <template v-for=\"(_, index) in length\" :key=\"index\">\n      <label :class=\"ns.e('input-field')\">\n        <input\n          ref=\"inputRefs\"\n          :value=\"innerValue[index]\"\n          :class=\"ns.e('input')\"\n          :type=\"mask ? 'password' : 'text'\"\n          :disabled=\"disabled\"\n          :readonly=\"readonly\"\n          :inputmode=\"inputmode\"\n          autocomplete=\"one-time-code\"\n          :aria-label=\"t('el.inputOTP.defaultLabel', { index: index + 1 })\"\n          @focus=\"handleFocus\"\n          @blur=\"handleBlur\"\n          @click=\"focus(index)\"\n          @keydown=\"handleKeydown($event, index)\"\n          @input=\"handleInput($event, index)\"\n        />\n      </label>\n      <slot\n        v-if=\"($slots.separator || separators[index]) && index < length - 1\"\n        name=\"separator\"\n        :index=\"index\"\n      >\n        <component :is=\"() => separators[index]\" />\n      </slot>\n    </template>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { computed, ref, watch } from 'vue'\nimport { clamp } from '@vueuse/core'\nimport { useLocale, useNamespace } from '@element-plus/hooks'\nimport { NOOP, getEventCode, isFunction, rAF } from '@element-plus/utils'\nimport {\n  useFormDisabled,\n  useFormItem,\n  useFormItemInputId,\n} from '@element-plus/components/form'\nimport {\n  CHANGE_EVENT,\n  EVENT_CODE,\n  UPDATE_MODEL_EVENT,\n} from '@element-plus/constants'\nimport { inputOtpEmits } from './input-otp'\n\nimport type { InputOtpProps } from './input-otp'\n\ndefineOptions({\n  name: 'ElInputOtp',\n})\n\nconst props = withDefaults(defineProps<InputOtpProps>(), {\n  length: 6,\n  validator: () => true,\n  type: 'outlined',\n  size: 'default',\n  disabled: undefined,\n  validateEvent: true,\n})\nconst emit = defineEmits(inputOtpEmits)\n\nconst initialValue = computed(() => {\n  const value = String(props.modelValue ?? '')\n  return Array.from({ length: props.length }, (_, i) => value.charAt(i))\n})\n\nconst separators = computed(() => {\n  const { separator } = props\n  const _separator = isFunction(separator) ? separator : () => separator\n  return Array.from({ length: props.length - 1 }, (_, i) => _separator(i))\n})\n\nconst innerValue = ref<string[]>(initialValue.value)\nconst isFocused = ref(false)\nconst inputRefs = ref<(HTMLInputElement | undefined)[]>([])\n\nconst ns = useNamespace('input-otp')\nconst { t } = useLocale()\nconst { formItem } = useFormItem()\nconst { inputId, isLabeledByFormItem } = useFormItemInputId(props, {\n  formItemContext: formItem,\n})\nconst disabled = useFormDisabled()\nlet modelValueOnFocus = props.modelValue\n\nconst getFirstIndex = (maxIndex: number) => {\n  const index = innerValue.value.findIndex((char, i) => !char && i <= maxIndex)\n  return index === -1 ? maxIndex : index\n}\n\nconst handleFocus = (event: FocusEvent) => {\n  if (inputRefs.value?.includes(event.relatedTarget as any)) {\n    return\n  }\n\n  isFocused.value = true\n  emit('focus', event)\n}\n\nconst handleBlur = (event: FocusEvent) => {\n  if (inputRefs.value?.includes(event.relatedTarget as any)) {\n    return\n  }\n\n  isFocused.value = false\n  emit('blur', event)\n  if (props.validateEvent) {\n    formItem?.validate?.('blur').catch(NOOP)\n  }\n}\n\nconst updateModelValue = (emitFinish = true) => {\n  const value = innerValue.value.join('').slice(0, props.length)\n  if (value !== props.modelValue) {\n    emit(UPDATE_MODEL_EVENT, value)\n    if (emitFinish && value.length === props.length) {\n      emit('finish', value)\n    }\n  }\n}\n\nconst handleKeydown = (event: KeyboardEvent, index: number) => {\n  const code = getEventCode(event)\n  let preventDefault = true\n\n  switch (code) {\n    case EVENT_CODE.backspace:\n      if (props.readonly) break\n      innerValue.value[index] = ''\n      focus(index - 1)\n      updateModelValue()\n      break\n    case EVENT_CODE.delete:\n      if (props.readonly) break\n      innerValue.value[index] = ''\n      focus(index)\n      updateModelValue()\n      break\n    case EVENT_CODE.up:\n    case EVENT_CODE.left:\n      focus(index - 1)\n      break\n    case EVENT_CODE.down:\n    case EVENT_CODE.right:\n      focus(index + 1)\n      break\n    default:\n      preventDefault = false\n  }\n\n  if (preventDefault) {\n    event.preventDefault()\n  }\n}\n\nconst handleInput = (event: Event, index: number) => {\n  const target = event.target as HTMLInputElement\n  const targetIndex = getFirstIndex(index)\n  let focusIndex = targetIndex + 1\n  let value = target.value\n\n  // Safari and Firefox do not trigger the paste event during autofill,\n  // so the paste logic needs to be handled in the input event\n  if (value.length > 1) {\n    const chars = castValues(value, targetIndex)\n\n    // Avoid innerValue inconsistency with the input box value after pasting when char has no change\n    target.value = innerValue.value[index] ?? ''\n\n    chars.forEach((char, i) => (innerValue.value[targetIndex + i] = char))\n    focus(targetIndex + chars.length)\n    updateModelValue()\n    return\n  }\n\n  if (!props.validator(value, targetIndex)) {\n    target.value = innerValue.value[index] ?? ''\n    value = target.value\n    focusIndex = targetIndex\n  }\n\n  innerValue.value[targetIndex] = value\n  if (targetIndex !== index) {\n    target.value = innerValue.value[index] ?? ''\n  }\n  focus(focusIndex)\n  updateModelValue()\n}\n\nconst castValues = (value: InputOtpProps['modelValue'], startIndex = 0) => {\n  const chars = `${value ?? ''}`.split('')\n  const result: string[] = []\n  for (const char of chars) {\n    if (result.length + startIndex >= props.length) {\n      break\n    }\n    if (props.validator(char, result.length + startIndex)) {\n      result.push(char)\n    }\n  }\n  return result\n}\n\nconst focus = (index: number = 0) => {\n  const focusIndex = clamp(index, 0, props.length - 1)\n  const target = inputRefs.value?.[focusIndex]\n\n  if (document.activeElement !== target) {\n    target?.focus()\n  }\n  rAF(() => {\n    // When it is called, the focus may have already been captured by another element.\n    // e.g. typing quickly and deleting.\n    if (!props.readonly && document.activeElement === target) {\n      ;(target as HTMLInputElement | null)?.select()\n    }\n  })\n}\n\nconst blur = () => {\n  const target = inputRefs.value?.find(\n    (input) => document.activeElement === input\n  )\n  target?.blur()\n}\n\nwatch(\n  () => props.modelValue,\n  () => {\n    innerValue.value = initialValue.value\n\n    if (props.validateEvent) {\n      formItem?.validate?.('change').catch(NOOP)\n    }\n  }\n)\n\nwatch(\n  () => props.length,\n  () => {\n    innerValue.value = initialValue.value\n    updateModelValue(false)\n  }\n)\n\nwatch(isFocused, (value) => {\n  if (value) {\n    modelValueOnFocus = props.modelValue\n    return\n  }\n  if (modelValueOnFocus !== props.modelValue) {\n    emit(CHANGE_EVENT, props.modelValue as string)\n  }\n})\n\ndefineExpose({\n  /**\n   * @description HTML input elements array\n   */\n  inputRefs,\n  /**\n   * @description Focus an OTP input field\n   */\n  focus,\n  /**\n   * @description Blur the focused OTP input field\n   */\n  blur,\n})\n</script>\n"],"mappings":""}