Skip to content
Snippets Groups Projects
InputDiv.vue 5.61 KiB
Newer Older
<script setup lang="ts">
import type { IInputDivProps } from '@interfaces/componentsProps';
import { computed, ref, type Ref, watch } from 'vue';
import { convertThemeToColor, convertThemeToTextColor, getValueFromSize } from '../../common/helpers/common';
import { calcIndexesToValueindex, calcPartsBy, calcPartsDash, changeInputHandler, moveFocus } from './helpers';

const props = withDefaults(defineProps<IInputDivProps>(), {
  scheme: '4by1',
  size: 'normal',
  theme: 'white',
  darknessTheme: '500',
  darknessTextColor: '500',
const value = defineModel() as Ref<string>;
const valueParts = ref<string[]>([]);

watch(
  valueParts,
  () => {
    value.value = valueParts.value.join('');
  },
  { deep: true },
);
let container: HTMLElement | null;
setTimeout(() => (container = document.querySelector('#inputDiv-container')), 0);

const inputPartsBy = computed(() => calcPartsBy(props.scheme));
const isInputPartsBy = computed(() => !!inputPartsBy.value);
const inputPartsDash = computed(() => calcPartsDash(props.scheme));
const indexesToValueIndex = computed(() => calcIndexesToValueindex(isInputPartsBy.value, props.scheme));

const themeColor = computed(() => convertThemeToColor(props.theme, props.darknessTheme));
const color = computed(() =>
  props.textColor
    ? convertThemeToColor(props.textColor, props.darknessTextColor)
    : convertThemeToTextColor(props.theme, props.darknessTheme),
);
const inputWidth = computed(() => getValueFromSize(props.size, ['20px', '24px', '30px', '45px']));
const inputHeight = computed(() => getValueFromSize(props.size, ['30px', '36px', '45px', '67px']));
const fontSize = computed(() => getValueFromSize(props.size, ['12px', '16px', '24px', '32px']));
const gap = computed(() => props.gap ?? fontSize.value);
const dashRight = computed(() => +gap.value.slice(0, -2) * -0.5 - 5 + 'px');
const borderWidth = computed(() => (props.size === 'small' || props.size === 'normal' ? '1px' : '2px'));

const toggleInput = (target: any, itemIndex: number, inputIndex: number, backspace?: boolean) =>
  (valueParts.value = changeInputHandler(
    target,
    container!,
    isInputPartsBy.value,
    valueParts.value,
    indexesToValueIndex.value,
    itemIndex,
    inputIndex,
    backspace ?? false,
    props.numbersOnly,
    props.regex ?? null,
</script>

<template>
  <section id="inputDiv-container">
    <div v-show="inputPartsBy" class="list">
      <div
        v-for="(item, itemIndex) of inputPartsBy"
        :key="itemIndex"
        :class="[
          `item ${itemIndex}`,
          {
            dashed: dashed && (inputPartsBy?.length ?? -1) - 1 !== itemIndex,
          },
        ]"
      >
          v-for="(_, inputIndex) of item"
          :key="inputIndex"
          @input="toggleInput($event.target, itemIndex, +inputIndex)"
          @keydown.delete="toggleInput($event.target, itemIndex, +inputIndex, true)"
          @keydown.left="moveFocus('left', container!, isInputPartsBy, itemIndex, inputIndex)"
          @keydown.right="moveFocus('right', container!, isInputPartsBy, itemIndex, inputIndex)"
          :type="secret ? 'password' : 'text'"
          :class="[
            `input ${inputIndex}`,
            {
              firstInput: !bottomOnly && inputIndex === 0,
              lastInput: !bottomOnly && inputPartsBy && inputIndex === inputPartsBy[itemIndex].length - 1,
              bottomOnly,
            },
          ]"
    </div>
    <div v-show="inputPartsDash" class="list">
      <div
        v-for="(item, itemIndex) of inputPartsDash"
        :key="itemIndex"
        :class="[
          `item ${itemIndex}`,
          {
            dashed: dashed && (inputPartsDash?.length ?? -1) - 1 !== itemIndex,
          },
        ]"
      >
          v-for="(_, inputIndex) of item"
          :key="inputIndex"
          @input="toggleInput($event.target, itemIndex, +inputIndex)"
          @keydown.delete="toggleInput($event.target, itemIndex, +inputIndex, true)"
          @keydown.left="moveFocus('left', container!, isInputPartsBy, itemIndex, inputIndex)"
          @keydown.right="moveFocus('right', container!, isInputPartsBy, itemIndex, inputIndex)"
          :type="secret ? 'password' : 'text'"
          :class="[
            `input ${inputIndex}`,
            {
              firstInput: !bottomOnly && inputIndex === 0,
              lastInput: !bottomOnly && inputPartsDash && inputIndex === inputPartsDash[itemIndex].length - 1,
              bottomOnly,
            },
          ]"
    </div>
  </section>
</template>

<style scoped>
.list {
  display: flex;
  gap: v-bind(gap);
  all: unset;
  width: v-bind(inputWidth);
  height: v-bind(inputHeight);
  font-size: v-bind(fontSize);
  text-align: center;
  background-color: v-bind(themeColor);
  color: v-bind(color);
  border-top: v-bind(borderWidth) solid v-bind(color);
  border-bottom: v-bind(borderWidth) solid v-bind(color);
  border-right: v-bind(borderWidth) solid v-bind(color);
}
.input.bottomOnly {
  border: none;
  border-bottom: v-bind(borderWidth) solid v-bind(color);
}
.item {
  position: relative;
  display: flex;
  gap: v-bind(inputsGap);
}
.item.dashed::after {
  position: absolute;
  color: v-bind(color);
  z-index: 2;
  top: calc(50% - 2px);
  right: v-bind(dashRight);
  content: '-';
  width: 10px;
  height: 4px;
  text-align: center;
  line-height: 0;
  font-size: v-bind(fontSize);
}
.firstInput {
  border-left: v-bind(borderWidth) solid v-bind(color);
  border-top-left-radius: 5px;
  border-bottom-left-radius: 5px;
}
.lastInput {
  border-top-right-radius: 5px;
  border-bottom-right-radius: 5px;