Commit 32001ced authored by Дмитрий Малюгин's avatar Дмитрий Малюгин 🕓
Browse files

feat: setting input logic in 'InputDiv'

parent 0d2d4116
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -184,6 +184,7 @@ const openDrawer = () => (visibleDrawer.value = true);

<template>
  <h2 class="title gradient-text">Playground</h2>
  <input type="text" style="border: 1px solid black" />
  <Button
    theme="black"
    label="Open all toasts"
+59 −70
Original line number Diff line number Diff line
<script setup lang="ts">
import type { IInputDivProps } from '@interfaces/componentsProps';
import { computed, type Ref } from 'vue';
import { computed, ref, type Ref, watch } from 'vue';
import { convertThemeToColor, convertThemeToTextColor, getValueFromSize } from '@helpers/common';
import { calcPartsBy, calcPartsDash } from '@components/InputDiv/helpers';
import { calcPartsBy, calcPartsDash, changeInputHandler, moveFocus } from '@components/InputDiv/helpers';

const props = withDefaults(defineProps<IInputDivProps>(), {
  scheme: '4by1',
@@ -13,11 +13,41 @@ const props = withDefaults(defineProps<IInputDivProps>(), {
});

const value = defineModel() as Ref<string>;
let container;
const valueParts = ref<string[]>([]);

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

const inputPartsBy = computed(() => calcPartsBy(props.scheme));
const inputPartsDash = computed(() => calcPartsDash(props.scheme));
const indexesToValueIndex = computed(() => {
  const result = {};
  let index = 0;
  if (inputPartsBy.value) {
    const splat = props.scheme.split('by');
    for (const itemIndex of [...Array(+splat[0]).keys()]) {
      for (const inputIndex of [...Array(+splat[1]).keys()]) {
        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
        //@ts-expect-error
        result[itemIndex + '-' + inputIndex] = index++;
      }
    }
  } else {
    const splat = props.scheme.split('-').map((i) => +i);
    for (const item of splat) {
      for (const inputIndex of [...Array(item).keys()]) {
        const itemIndex = splat.indexOf(item);
        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
        //@ts-expect-error
        result[itemIndex + '-' + inputIndex] = index++;
      }
    }
  }
  return result;
});

const themeColor = computed(() => convertThemeToColor(props.theme, props.darknessTheme));
const color = computed(() =>
@@ -30,89 +60,48 @@ const inputHeight = computed(() => getValueFromSize(props.size, ['30px', '36px',
const fontSize = computed(() => getValueFromSize(props.size, ['12px', '16px', '24px', '32px']));
const borderWidth = computed(() => (props.size === 'small' || props.size === 'normal' ? '1px' : '2px'));

const toggleInput = (itemIndex: number, inputIndex: number) => {
  let currentInput;
  let currentItem;
  const list = Array(container?.children[inputPartsBy.value ? 0 : 1].children)[0];

  cycle: for (let i = 0; i < list.length; i++) {
    const item = list[i];
    if (!item.classList.contains(itemIndex)) continue;
    for (const child of item.children) {
      if (child.classList.contains(inputIndex)) {
        currentInput = child;
        currentItem = item;
        break cycle;
      }
    }
  }
  // если значение ввели
  if (currentInput.value) {
    let nextInputInSameItem = null;
    for (const child of currentItem.children) {
      if (child.classList.contains(inputIndex + 1)) {
        nextInputInSameItem = child;
        break;
      }
    }
    if (nextInputInSameItem) {
      nextInputInSameItem.focus();
    } else {
      // обработка следующей части, если она есть, иначе ничего не делать (или оставить старое значение, что ещё лучше)
    }
  } else {
    // если значение удалили
    let prevInputInSameItem = null;
    for (const child of currentItem.children) {
      if (child.classList.contains(inputIndex - 1)) {
        prevInputInSameItem = child;
        break;
      }
    }
    if (prevInputInSameItem) {
      prevInputInSameItem.focus();
    } else {
      // обработка предыдущей части, если она есть, иначе ничего не делать
    }
  }
};
const toggleInput = (target: any, itemIndex: number, inputIndex: number, backspace?: boolean) =>
  (valueParts.value = changeInputHandler(
    target,
    container!,
    !!inputPartsBy.value,
    valueParts.value,
    indexesToValueIndex.value,
    itemIndex,
    inputIndex,
    backspace ?? false,
  ));
</script>

<template>
  <section id="inputDiv-container">
    <div v-show="inputPartsBy" class="list">
      <div v-for="(item, itemIndex) of inputPartsBy" :key="item" :class="`item ${itemIndex}`">
      <div v-for="(item, itemIndex) of inputPartsBy" :key="itemIndex" :class="`item ${itemIndex}`">
        <input
          v-for="(input, inputIndex) of item"
          v-for="(_, inputIndex) of item"
          :key="inputIndex"
          @input.prevent="toggleInput(itemIndex, +inputIndex)"
          @input="toggleInput($event.target, itemIndex, +inputIndex)"
          @keydown.delete="toggleInput($event.target, itemIndex, +inputIndex, true)"
          @keydown.left="moveFocus('left', container!, !!inputPartsBy, itemIndex, inputIndex)"
          @keydown.right="moveFocus('right', container!, !!inputPartsBy, itemIndex, inputIndex)"
          type="text"
          :class="`input ${inputIndex}`"
          maxlength="2"
        />
      </div>
    </div>
    <div v-show="inputPartsDash" class="list">
      <div
        v-for="(item, itemIndex) of inputPartsDash"
        :key="item"
        :class="[
          'item',
          {
            itemIndex,
          },
        ]"
      >
      <div v-for="(item, itemIndex) of inputPartsDash" :key="itemIndex" :class="`item ${itemIndex}`">
        <input
          v-for="(input, inputIndex) of item"
          v-for="(_, inputIndex) of item"
          :key="inputIndex"
          @input="toggleInput(itemIndex, +inputIndex)"
          @input="toggleInput($event.target, itemIndex, +inputIndex)"
          @keydown.delete="toggleInput($event.target, itemIndex, +inputIndex, true)"
          @keydown.left="moveFocus('left', container!, !!inputPartsBy, itemIndex, inputIndex)"
          @keydown.right="moveFocus('right', container!, !!inputPartsBy, itemIndex, inputIndex)"
          type="text"
          :class="[
            'input',
            {
              inputIndex,
            },
          ]"
          :class="`input ${inputIndex}`"
          maxlength="2"
        />
      </div>
    </div>
+131 −0
Original line number Diff line number Diff line
import type { TInputDivScheme } from '@interfaces/componentsProp';

export const changeInputHandler = (
  target: any,
  container: HTMLElement,
  inputPartsBy: boolean,
  valueParts: string[],
  indexesToValueIndex: Record<string, number>,
  itemIndex: number,
  inputIndex: number,
  backspace: boolean,
) => {
  let currentInput: HTMLInputElement | null = null;
  let currentItem: HTMLElement | null = null;
  let currentItemIndex: number | null;
  const list = Array.from(container?.children[inputPartsBy ? 0 : 1].children);

  cycle: for (let i = 0; i < list.length; i++) {
    const item = list[i];
    if (!item.classList.contains(String(itemIndex))) continue;
    for (const child of item.children) {
      if (child.classList.contains(String(inputIndex))) {
        currentInput = child as HTMLInputElement;
        currentItem = item as HTMLElement;
        currentItemIndex = i;
        break cycle;
      }
    }
  }
  // если значение ввели
  if (currentInput?.value && currentItem) {
    const valueIndex = indexesToValueIndex[
      (itemIndex + '-' + inputIndex) as keyof typeof indexesToValueIndex
    ] as number;
    if (target.value.length === 2) {
      currentInput!.value = target.value[0] === valueParts[valueIndex] ? target.value[1] : target.value[0];
    }
    valueParts[valueIndex] = currentInput!.value;
    // поиск следующего инпута в той же части (если есть)
    let nextInputInSameItem: HTMLInputElement | null = null;
    for (const child of currentItem.children) {
      if (child.classList.contains(String(inputIndex + 1))) {
        nextInputInSameItem = child as HTMLInputElement;
        break;
      }
    }
    if (nextInputInSameItem) {
      nextInputInSameItem.focus();
    } else {
      // обработка следующей части, если она есть, иначе ничего не делать (или оставить старое значение, что ещё лучше)
      currentItem = list?.[currentItemIndex! + 1] as HTMLElement | null;
      if (currentItem) {
        const targetInput = Array.from(currentItem.children)[0] as HTMLInputElement;
        targetInput.focus();
      }
    }
  } else if (backspace && currentItem) {
    // если значение удалили
    let prevInputInSameItem: HTMLInputElement | null = null;
    for (const child of currentItem.children) {
      if (child.classList.contains(String(inputIndex - 1))) {
        prevInputInSameItem = child as HTMLInputElement;
        break;
      }
    }
    if (prevInputInSameItem) {
      prevInputInSameItem.focus();
    } else {
      // обработка предыдущей части, если она есть, иначе ничего не делать
      currentItem = list?.[currentItemIndex! - 1] as HTMLElement | null;
      if (currentItem) {
        const children = Array.from(currentItem.children);
        const targetInput = children[children.length - 1] as HTMLInputElement;
        targetInput.focus();
      }
    }
  }
  return valueParts;
};

export const moveFocus = (
  direction: 'left' | 'right',
  container: HTMLElement,
  inputPartsBy: boolean,
  itemIndex: number,
  inputIndex: number,
) => {
  let currentItem: HTMLElement | null;
  let currentItemIndex: number | null;
  let currentInputIndex: number | null;
  const list = Array.from(container?.children[inputPartsBy ? 0 : 1].children);

  cycle: for (let i = 0; i < list.length; i++) {
    const item = list[i];
    if (!item.classList.contains(String(itemIndex))) continue;
    const itemChildren = Array.from(item.children);

    for (let j = 0; j < itemChildren.length; j++) {
      if (itemChildren[j].classList.contains(String(inputIndex))) {
        currentItem = item as HTMLElement;
        currentItemIndex = i;
        currentInputIndex = j;
        break cycle;
      }
    }
  }

  if (direction === 'left') {
    let targetInput = [...currentItem!.children][currentInputIndex! - 1] as HTMLInputElement | null;
    if (targetInput) {
      targetInput.focus();
      return;
    }
    currentItem = list?.[currentItemIndex! - 1] as HTMLElement | null;
    if (currentItem) {
      const itemChildren = [...currentItem.children];
      targetInput = itemChildren[itemChildren.length - 1] as HTMLInputElement;
      targetInput.focus();
    }
  } else {
    let targetInput = [...currentItem!.children][currentInputIndex! + 1] as HTMLInputElement | null;
    if (targetInput) {
      targetInput.focus();
      return;
    }
    currentItem = list?.[currentItemIndex! + 1] as HTMLElement | null;
    if (currentItem) {
      targetInput = [...currentItem.children][0] as HTMLInputElement;
      targetInput.focus();
    }
  }
};

export const calcPartsBy = (scheme: TInputDivScheme) => {
  if (!scheme.includes('by')) return null;
  const splat = scheme.split('by');