Loading src/Playground.vue +2 −1 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import HomeIcon from '@icons/Mono/HomeIcon.vue'; import ProgressBar from '@components/ProgressBar/ProgressBar.vue'; import Carousel from '@components/Carousel/Carousel.vue'; import Toast from '@components/Toast/Toast.vue'; import InputDiv from '@components/InputDiv/InputDiv.vue'; const visibleDrawer = ref(false); const sliderOptions: ISliderOptions[] = [ Loading Loading @@ -184,7 +185,6 @@ 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 @@ -197,6 +197,7 @@ const openDrawer = () => (visibleDrawer.value = true); } " /> <InputDiv :regex="/^[0-9-+=]+$/" /> <Button label="Open toast" @click="toast2 = true" /> <Toast v-model="toast" static :duration="60" type="success" position="topLeft" width="500px" /> <Toast v-model="toast4" :duration="2" type="info" position="topLeft" width="500px" /> Loading src/common/interfaces/componentsProps.ts +3 −0 Original line number Diff line number Diff line Loading @@ -227,9 +227,12 @@ export interface ISelectProps { export interface IInputDivProps { scheme?: TInputDivScheme; size?: TSize; gap?: string; inputsGap?: string; secret?: boolean; dashed?: boolean; numbersOnly?: boolean; regex?: RegExp; bottomOnly?: boolean; theme?: TThemeColor; darknessTheme?: TDarkness; Loading src/components/InputDiv/InputDiv.stories.ts +28 −0 Original line number Diff line number Diff line Loading @@ -20,6 +20,9 @@ const meta: Meta = { numbersOnly: { control: 'boolean' }, bottomOnly: { control: 'boolean' }, scheme: { control: 'text' }, regex: { control: 'text' }, gap: { control: 'text' }, inputsGap: { control: 'text' }, darknessTheme: { control: 'select', options: ['100', '200', '300', '400', '500', '600', '700', '800', '900'] }, darknessTextColor: { control: 'select', Loading Loading @@ -76,3 +79,28 @@ type Story = StoryObj<typeof meta>; export const Simple: Story = { args: {}, }; export const Full: Story = { args: { secret: true, dashed: true, numbersOnly: true, bottomOnly: false, scheme: '2-4-3-1', size: 'large', theme: 'sky', }, }; export const Full2: Story = { args: { secret: false, dashed: true, numbersOnly: false, bottomOnly: true, scheme: '4by3', size: 'large', theme: 'white', textColor: 'blue', }, }; src/components/InputDiv/InputDiv.vue +98 −43 Original line number Diff line number Diff line Loading @@ -2,7 +2,13 @@ import type { IInputDivProps } from '@interfaces/componentsProps'; import { computed, ref, type Ref, watch } from 'vue'; import { convertThemeToColor, convertThemeToTextColor, getValueFromSize } from '@helpers/common'; import { calcPartsBy, calcPartsDash, changeInputHandler, moveFocus } from '@components/InputDiv/helpers'; import { calcIndexesToValueindex, calcPartsBy, calcPartsDash, changeInputHandler, moveFocus, } from '@components/InputDiv/helpers'; const props = withDefaults(defineProps<IInputDivProps>(), { scheme: '4by1', Loading @@ -15,39 +21,20 @@ const props = withDefaults(defineProps<IInputDivProps>(), { const value = defineModel() as Ref<string>; const valueParts = ref<string[]>([]); watch(valueParts, () => { 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(() => { 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 indexesToValueIndex = computed(() => calcIndexesToValueindex(isInputPartsBy.value, props.scheme)); const themeColor = computed(() => convertThemeToColor(props.theme, props.darknessTheme)); const color = computed(() => Loading @@ -58,49 +45,85 @@ const color = computed(() => 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!, !!inputPartsBy.value, 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}`"> <div v-for="(item, itemIndex) of inputPartsBy" :key="itemIndex" :class="[ `item ${itemIndex}`, { dashed: dashed && (inputPartsBy?.length ?? -1) - 1 !== itemIndex, }, ]" > <input 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!, !!inputPartsBy, itemIndex, inputIndex)" @keydown.right="moveFocus('right', container!, !!inputPartsBy, itemIndex, inputIndex)" type="text" :class="`input ${inputIndex}`" @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, }, ]" maxlength="2" /> </div> </div> <div v-show="inputPartsDash" class="list"> <div v-for="(item, itemIndex) of inputPartsDash" :key="itemIndex" :class="`item ${itemIndex}`"> <div v-for="(item, itemIndex) of inputPartsDash" :key="itemIndex" :class="[ `item ${itemIndex}`, { dashed: dashed && (inputPartsDash?.length ?? -1) - 1 !== itemIndex, }, ]" > <input 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!, !!inputPartsBy, itemIndex, inputIndex)" @keydown.right="moveFocus('right', container!, !!inputPartsBy, itemIndex, inputIndex)" type="text" :class="`input ${inputIndex}`" @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, }, ]" maxlength="2" /> </div> Loading @@ -111,7 +134,7 @@ const toggleInput = (target: any, itemIndex: number, inputIndex: number, backspa <style scoped> .list { display: flex; gap: v-bind(fontSize); gap: v-bind(gap); } .input { all: unset; Loading @@ -121,7 +144,39 @@ const toggleInput = (target: any, itemIndex: number, inputIndex: number, backspa text-align: center; background-color: v-bind(themeColor); color: v-bind(color); border: v-bind(borderWidth) solid black; border-radius: 5px; 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; } </style> src/components/InputDiv/helpers.ts +65 −13 Original line number Diff line number Diff line Loading @@ -9,6 +9,8 @@ export const changeInputHandler = ( itemIndex: number, inputIndex: number, backspace: boolean, numbersOnly: boolean, regex: RegExp | null, ) => { let currentInput: HTMLInputElement | null = null; let currentItem: HTMLElement | null = null; Loading @@ -27,15 +29,26 @@ export const changeInputHandler = ( } } } const valueIndex = indexesToValueIndex[(itemIndex + '-' + inputIndex) as keyof typeof indexesToValueIndex] as number; // если значение ввели if (currentInput?.value && currentItem) { const valueIndex = indexesToValueIndex[ (itemIndex + '-' + inputIndex) as keyof typeof indexesToValueIndex ] as number; if (currentInput?.value && currentItem && !backspace) { const prevIndexValue = valueParts[valueIndex]; if (target.value.length === 2) { currentInput!.value = target.value[0] === valueParts[valueIndex] ? target.value[1] : target.value[0]; currentInput!.value = target.value[0] === prevIndexValue ? target.value[1] : target.value[0]; } if (numbersOnly && !currentInput!.value.match(/[0-9-]/)) { if (!valueParts[valueIndex]) { currentInput!.value = ''; } return valueParts; } valueParts[valueIndex] = currentInput!.value; if (regex && !regex.test(valueParts.join(''))) { currentInput!.value = ''; valueParts[valueIndex] = prevIndexValue ?? ''; return valueParts; } // поиск следующего инпута в той же части (если есть) let nextInputInSameItem: HTMLInputElement | null = null; for (const child of currentItem.children) { Loading @@ -54,7 +67,7 @@ export const changeInputHandler = ( targetInput.focus(); } } } else if (backspace && currentItem) { } else if (backspace && currentItem && currentInput) { // если значение удалили let prevInputInSameItem: HTMLInputElement | null = null; for (const child of currentItem.children) { Loading @@ -63,15 +76,28 @@ export const changeInputHandler = ( break; } } if (prevInputInSameItem) { prevInputInSameItem.focus(); } else { let deletedCurrentValue = false; if (currentInput.value) { valueParts[valueIndex] = ''; currentInput.value = ''; deletedCurrentValue = true; } else if (prevInputInSameItem) { valueParts[valueIndex - 1] = ''; setTimeout(() => prevInputInSameItem.focus(), 0); prevInputInSameItem.value = ''; } if (!prevInputInSameItem) { // обработка предыдущей части, если она есть, иначе ничего не делать currentItem = list?.[currentItemIndex! - 1] as HTMLElement | null; if (currentItem) { const children = Array.from(currentItem.children); const prevItem = list?.[currentItemIndex! - 1] as HTMLElement | null; if (prevItem) { const children = Array.from(prevItem.children); const targetInput = children[children.length - 1] as HTMLInputElement; targetInput.focus(); setTimeout(() => targetInput.focus(), 0); if (!deletedCurrentValue) { targetInput.value = ''; valueParts[valueIndex - 1] = ''; } } } } Loading Loading @@ -131,6 +157,32 @@ export const moveFocus = ( } }; export const calcIndexesToValueindex = (inputPartsBy: boolean, scheme: TInputDivScheme) => { const result = {}; let index = 0; if (inputPartsBy) { const splat = 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 = scheme.split('-').map((i: string) => +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; }; export const calcPartsBy = (scheme: TInputDivScheme) => { if (!scheme.includes('by')) return null; const splat = scheme.split('by'); Loading Loading
src/Playground.vue +2 −1 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import HomeIcon from '@icons/Mono/HomeIcon.vue'; import ProgressBar from '@components/ProgressBar/ProgressBar.vue'; import Carousel from '@components/Carousel/Carousel.vue'; import Toast from '@components/Toast/Toast.vue'; import InputDiv from '@components/InputDiv/InputDiv.vue'; const visibleDrawer = ref(false); const sliderOptions: ISliderOptions[] = [ Loading Loading @@ -184,7 +185,6 @@ 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 @@ -197,6 +197,7 @@ const openDrawer = () => (visibleDrawer.value = true); } " /> <InputDiv :regex="/^[0-9-+=]+$/" /> <Button label="Open toast" @click="toast2 = true" /> <Toast v-model="toast" static :duration="60" type="success" position="topLeft" width="500px" /> <Toast v-model="toast4" :duration="2" type="info" position="topLeft" width="500px" /> Loading
src/common/interfaces/componentsProps.ts +3 −0 Original line number Diff line number Diff line Loading @@ -227,9 +227,12 @@ export interface ISelectProps { export interface IInputDivProps { scheme?: TInputDivScheme; size?: TSize; gap?: string; inputsGap?: string; secret?: boolean; dashed?: boolean; numbersOnly?: boolean; regex?: RegExp; bottomOnly?: boolean; theme?: TThemeColor; darknessTheme?: TDarkness; Loading
src/components/InputDiv/InputDiv.stories.ts +28 −0 Original line number Diff line number Diff line Loading @@ -20,6 +20,9 @@ const meta: Meta = { numbersOnly: { control: 'boolean' }, bottomOnly: { control: 'boolean' }, scheme: { control: 'text' }, regex: { control: 'text' }, gap: { control: 'text' }, inputsGap: { control: 'text' }, darknessTheme: { control: 'select', options: ['100', '200', '300', '400', '500', '600', '700', '800', '900'] }, darknessTextColor: { control: 'select', Loading Loading @@ -76,3 +79,28 @@ type Story = StoryObj<typeof meta>; export const Simple: Story = { args: {}, }; export const Full: Story = { args: { secret: true, dashed: true, numbersOnly: true, bottomOnly: false, scheme: '2-4-3-1', size: 'large', theme: 'sky', }, }; export const Full2: Story = { args: { secret: false, dashed: true, numbersOnly: false, bottomOnly: true, scheme: '4by3', size: 'large', theme: 'white', textColor: 'blue', }, };
src/components/InputDiv/InputDiv.vue +98 −43 Original line number Diff line number Diff line Loading @@ -2,7 +2,13 @@ import type { IInputDivProps } from '@interfaces/componentsProps'; import { computed, ref, type Ref, watch } from 'vue'; import { convertThemeToColor, convertThemeToTextColor, getValueFromSize } from '@helpers/common'; import { calcPartsBy, calcPartsDash, changeInputHandler, moveFocus } from '@components/InputDiv/helpers'; import { calcIndexesToValueindex, calcPartsBy, calcPartsDash, changeInputHandler, moveFocus, } from '@components/InputDiv/helpers'; const props = withDefaults(defineProps<IInputDivProps>(), { scheme: '4by1', Loading @@ -15,39 +21,20 @@ const props = withDefaults(defineProps<IInputDivProps>(), { const value = defineModel() as Ref<string>; const valueParts = ref<string[]>([]); watch(valueParts, () => { 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(() => { 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 indexesToValueIndex = computed(() => calcIndexesToValueindex(isInputPartsBy.value, props.scheme)); const themeColor = computed(() => convertThemeToColor(props.theme, props.darknessTheme)); const color = computed(() => Loading @@ -58,49 +45,85 @@ const color = computed(() => 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!, !!inputPartsBy.value, 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}`"> <div v-for="(item, itemIndex) of inputPartsBy" :key="itemIndex" :class="[ `item ${itemIndex}`, { dashed: dashed && (inputPartsBy?.length ?? -1) - 1 !== itemIndex, }, ]" > <input 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!, !!inputPartsBy, itemIndex, inputIndex)" @keydown.right="moveFocus('right', container!, !!inputPartsBy, itemIndex, inputIndex)" type="text" :class="`input ${inputIndex}`" @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, }, ]" maxlength="2" /> </div> </div> <div v-show="inputPartsDash" class="list"> <div v-for="(item, itemIndex) of inputPartsDash" :key="itemIndex" :class="`item ${itemIndex}`"> <div v-for="(item, itemIndex) of inputPartsDash" :key="itemIndex" :class="[ `item ${itemIndex}`, { dashed: dashed && (inputPartsDash?.length ?? -1) - 1 !== itemIndex, }, ]" > <input 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!, !!inputPartsBy, itemIndex, inputIndex)" @keydown.right="moveFocus('right', container!, !!inputPartsBy, itemIndex, inputIndex)" type="text" :class="`input ${inputIndex}`" @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, }, ]" maxlength="2" /> </div> Loading @@ -111,7 +134,7 @@ const toggleInput = (target: any, itemIndex: number, inputIndex: number, backspa <style scoped> .list { display: flex; gap: v-bind(fontSize); gap: v-bind(gap); } .input { all: unset; Loading @@ -121,7 +144,39 @@ const toggleInput = (target: any, itemIndex: number, inputIndex: number, backspa text-align: center; background-color: v-bind(themeColor); color: v-bind(color); border: v-bind(borderWidth) solid black; border-radius: 5px; 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; } </style>
src/components/InputDiv/helpers.ts +65 −13 Original line number Diff line number Diff line Loading @@ -9,6 +9,8 @@ export const changeInputHandler = ( itemIndex: number, inputIndex: number, backspace: boolean, numbersOnly: boolean, regex: RegExp | null, ) => { let currentInput: HTMLInputElement | null = null; let currentItem: HTMLElement | null = null; Loading @@ -27,15 +29,26 @@ export const changeInputHandler = ( } } } const valueIndex = indexesToValueIndex[(itemIndex + '-' + inputIndex) as keyof typeof indexesToValueIndex] as number; // если значение ввели if (currentInput?.value && currentItem) { const valueIndex = indexesToValueIndex[ (itemIndex + '-' + inputIndex) as keyof typeof indexesToValueIndex ] as number; if (currentInput?.value && currentItem && !backspace) { const prevIndexValue = valueParts[valueIndex]; if (target.value.length === 2) { currentInput!.value = target.value[0] === valueParts[valueIndex] ? target.value[1] : target.value[0]; currentInput!.value = target.value[0] === prevIndexValue ? target.value[1] : target.value[0]; } if (numbersOnly && !currentInput!.value.match(/[0-9-]/)) { if (!valueParts[valueIndex]) { currentInput!.value = ''; } return valueParts; } valueParts[valueIndex] = currentInput!.value; if (regex && !regex.test(valueParts.join(''))) { currentInput!.value = ''; valueParts[valueIndex] = prevIndexValue ?? ''; return valueParts; } // поиск следующего инпута в той же части (если есть) let nextInputInSameItem: HTMLInputElement | null = null; for (const child of currentItem.children) { Loading @@ -54,7 +67,7 @@ export const changeInputHandler = ( targetInput.focus(); } } } else if (backspace && currentItem) { } else if (backspace && currentItem && currentInput) { // если значение удалили let prevInputInSameItem: HTMLInputElement | null = null; for (const child of currentItem.children) { Loading @@ -63,15 +76,28 @@ export const changeInputHandler = ( break; } } if (prevInputInSameItem) { prevInputInSameItem.focus(); } else { let deletedCurrentValue = false; if (currentInput.value) { valueParts[valueIndex] = ''; currentInput.value = ''; deletedCurrentValue = true; } else if (prevInputInSameItem) { valueParts[valueIndex - 1] = ''; setTimeout(() => prevInputInSameItem.focus(), 0); prevInputInSameItem.value = ''; } if (!prevInputInSameItem) { // обработка предыдущей части, если она есть, иначе ничего не делать currentItem = list?.[currentItemIndex! - 1] as HTMLElement | null; if (currentItem) { const children = Array.from(currentItem.children); const prevItem = list?.[currentItemIndex! - 1] as HTMLElement | null; if (prevItem) { const children = Array.from(prevItem.children); const targetInput = children[children.length - 1] as HTMLInputElement; targetInput.focus(); setTimeout(() => targetInput.focus(), 0); if (!deletedCurrentValue) { targetInput.value = ''; valueParts[valueIndex - 1] = ''; } } } } Loading Loading @@ -131,6 +157,32 @@ export const moveFocus = ( } }; export const calcIndexesToValueindex = (inputPartsBy: boolean, scheme: TInputDivScheme) => { const result = {}; let index = 0; if (inputPartsBy) { const splat = 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 = scheme.split('-').map((i: string) => +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; }; export const calcPartsBy = (scheme: TInputDivScheme) => { if (!scheme.includes('by')) return null; const splat = scheme.split('by'); Loading