Loading src/Playground.vue +1 −0 Original line number Diff line number Diff line Loading @@ -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" Loading src/components/InputDiv/InputDiv.vue +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', Loading @@ -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(() => Loading @@ -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> Loading src/components/InputDiv/helpers.ts +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'); Loading Loading
src/Playground.vue +1 −0 Original line number Diff line number Diff line Loading @@ -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" Loading
src/components/InputDiv/InputDiv.vue +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', Loading @@ -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(() => Loading @@ -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> Loading
src/components/InputDiv/helpers.ts +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'); Loading