From f272f63afc3b3eb7017557d29c194810a1e522c3 Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Thu, 10 Nov 2022 18:28:03 +0400 Subject: [PATCH] Teact: Preserve caret at end in controlled inputs --- src/lib/teact/teact-dom.ts | 49 +++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/src/lib/teact/teact-dom.ts b/src/lib/teact/teact-dom.ts index ffa34b94f..3971c162b 100644 --- a/src/lib/teact/teact-dom.ts +++ b/src/lib/teact/teact-dom.ts @@ -23,9 +23,15 @@ import { DEBUG } from '../../config'; import { addEventListener, removeAllDelegatedListeners, removeEventListener } from './dom-events'; import { unique } from '../../util/iteratees'; -type VirtualDomHead = { +interface VirtualDomHead { children: [VirtualElement] | []; -}; +} + +interface SelectionState { + selectionStart: number | null; + selectionEnd: number | null; + isCaretAtEnd: boolean; +} const FILTERED_ATTRIBUTES = new Set(['key', 'ref', 'teactFastList', 'teactOrderKey']); const HTML_ATTRIBUTES = new Set(['dir', 'role', 'form']); @@ -558,20 +564,18 @@ function processControlled(tag: string, props: AnyLiteral) { onInput?.(e); onChange?.(e); - if (value !== undefined) { + if (value !== undefined && value !== e.currentTarget.value) { const { selectionStart, selectionEnd } = e.currentTarget; + const isCaretAtEnd = selectionStart === selectionEnd && selectionEnd === e.currentTarget.value.length; - if (e.currentTarget.value !== value) { - e.currentTarget.value = value; + e.currentTarget.value = value; - if (selectionStart !== undefined && selectionEnd !== undefined) { - e.currentTarget.setSelectionRange(selectionStart, selectionEnd); + if (typeof selectionStart === 'number' && typeof selectionEnd === 'number') { + e.currentTarget.setSelectionRange(selectionStart, selectionEnd); - // eslint-disable-next-line no-underscore-dangle - e.currentTarget.dataset.__teactSelectionStart = String(selectionStart); - // eslint-disable-next-line no-underscore-dangle - e.currentTarget.dataset.__teactSelectionEnd = String(selectionEnd); - } + const selectionState: SelectionState = { selectionStart, selectionEnd, isCaretAtEnd }; + // eslint-disable-next-line no-underscore-dangle + e.currentTarget.dataset.__teactSelectionState = JSON.stringify(selectionState); } } @@ -630,15 +634,22 @@ function setAttribute(element: HTMLElement, key: string, value: any) { element.className = value; // An optimization attempt } else if (key === 'value') { - if ((element as HTMLInputElement).value !== value) { - const { - __teactSelectionStart: selectionStart, __teactSelectionEnd: selectionEnd, - } = (element as HTMLInputElement).dataset; + const inputEl = element as HTMLInputElement; - (element as HTMLInputElement).value = value; + if (inputEl.value !== value) { + inputEl.value = value; - if (selectionStart !== undefined && selectionEnd !== undefined) { - (element as HTMLInputElement).setSelectionRange(Number(selectionStart), Number(selectionEnd)); + // eslint-disable-next-line no-underscore-dangle + const selectionStateJson = inputEl.dataset.__teactSelectionState; + if (selectionStateJson) { + const { selectionStart, selectionEnd, isCaretAtEnd } = JSON.parse(selectionStateJson) as SelectionState; + + if (isCaretAtEnd) { + const length = inputEl.value.length; + inputEl.setSelectionRange(length, length); + } else if (typeof selectionStart === 'number' && typeof selectionEnd === 'number') { + inputEl.setSelectionRange(selectionStart, selectionEnd); + } } } } else if (key === 'style') {