From 32001ced7a3f311825de476009af1e0fc1a98d9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9C=D0=B0?= =?UTF-8?q?=D0=BB=D1=8E=D0=B3=D0=B8=D0=BD?= <d.malygin@iqdev.digital> Date: Sat, 15 Feb 2025 12:12:51 +0500 Subject: [PATCH] feat: setting input logic in 'InputDiv' --- src/Playground.vue | 1 + src/components/InputDiv/InputDiv.vue | 129 ++++++++++++-------------- src/components/InputDiv/helpers.ts | 131 +++++++++++++++++++++++++++ 3 files changed, 191 insertions(+), 70 deletions(-) diff --git a/src/Playground.vue b/src/Playground.vue index 7469b80..29af7d2 100644 --- a/src/Playground.vue +++ b/src/Playground.vue @@ -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" diff --git a/src/components/InputDiv/InputDiv.vue b/src/components/InputDiv/InputDiv.vue index bafe63d..61aaafd 100644 --- a/src/components/InputDiv/InputDiv.vue +++ b/src/components/InputDiv/InputDiv.vue @@ -1,8 +1,8 @@ <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> diff --git a/src/components/InputDiv/helpers.ts b/src/components/InputDiv/helpers.ts index 2299055..ab51752 100644 --- a/src/components/InputDiv/helpers.ts +++ b/src/components/InputDiv/helpers.ts @@ -1,5 +1,136 @@ 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'); -- GitLab