From 7e2002f86a1a9115c8f98eddbc19804bafb0f759 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Tue, 12 Mar 2024 17:19:02 +0100 Subject: [PATCH] Fix scroll on enter (#220) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michał Skałka --- src/MarkdownTextInput.web.tsx | 10 +--------- src/web/cursorUtils.ts | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/MarkdownTextInput.web.tsx b/src/MarkdownTextInput.web.tsx index 5d69381e6..8411fe38e 100644 --- a/src/MarkdownTextInput.web.tsx +++ b/src/MarkdownTextInput.web.tsx @@ -15,7 +15,6 @@ import {StyleSheet} from 'react-native'; import * as ParseUtils from './web/parserUtils'; import * as CursorUtils from './web/cursorUtils'; import * as StyleUtils from './styleUtils'; -import * as BrowserUtils from './web/browserUtils'; import type * as MarkdownTextInputDecoratorViewNativeComponent from './MarkdownTextInputDecoratorViewNativeComponent'; import './web/MarkdownTextInput.css'; import InputHistory from './web/InputHistory'; @@ -381,14 +380,7 @@ const MarkdownTextInput = React.forwardRef( // We need to change normal behavior of "Enter" key to insert a line breaks, to prevent wrapping contentEditable text in
tags. // Thanks to that in every situation we have proper amount of new lines in our parsed text. Without it pressing enter in empty lines will add 2 more new lines. document.execCommand('insertLineBreak'); - - const range = window.getSelection(); - if (range && !BrowserUtils.isFirefox) { - const scrollMarkerNode = document.createElement('div'); - range.getRangeAt(0).insertNode(scrollMarkerNode); - scrollMarkerNode.scrollIntoView(); - scrollMarkerNode.remove(); - } + CursorUtils.scrollCursorIntoView(divRef.current as HTMLInputElement); } if (!e.shiftKey && ((shouldBlurOnSubmit && hostNode !== null) || !multiline)) { diff --git a/src/web/cursorUtils.ts b/src/web/cursorUtils.ts index bf12c8e69..b93c9c181 100644 --- a/src/web/cursorUtils.ts +++ b/src/web/cursorUtils.ts @@ -1,3 +1,5 @@ +import * as BrowserUtils from './browserUtils'; + function findTextNodes(textNodes: Text[], node: ChildNode) { if (node.nodeType === Node.TEXT_NODE) { textNodes.push(node as Text); @@ -51,6 +53,8 @@ function setCursorPosition(target: HTMLElement, start: number, end: number | nul selection.removeAllRanges(); selection.addRange(range); } + + scrollCursorIntoView(target as HTMLInputElement); } function moveCursorToEnd(target: HTMLElement) { @@ -85,4 +89,31 @@ function removeSelection() { } } -export {getCurrentCursorPosition, moveCursorToEnd, setCursorPosition, removeSelection}; +function scrollCursorIntoView(target: HTMLInputElement) { + if (target.selectionStart === null || !target.value || BrowserUtils.isFirefox) { + return; + } + + const selection = window.getSelection(); + if (!selection) { + return; + } + + const caretRect = selection.getRangeAt(0).getClientRects()[0]; + const editableRect = target.getBoundingClientRect(); + + // Adjust for padding and border + const paddingTop = parseFloat(window.getComputedStyle(target).paddingTop); + const borderTop = parseFloat(window.getComputedStyle(target).borderTopWidth); + + if (caretRect && !(caretRect.top >= editableRect.top + paddingTop + borderTop && caretRect.bottom <= editableRect.bottom - 2 * (paddingTop - borderTop))) { + const topToCaret = caretRect.top - editableRect.top; + const inputHeight = editableRect.height; + // Chrome Rects don't include padding & border, so we're adding them manually + const inputOffset = caretRect.height - inputHeight + paddingTop + borderTop + (BrowserUtils.isChromium ? 0 : 4 * (paddingTop + borderTop)); + + target.scrollTo(0, topToCaret + target.scrollTop + inputOffset); + } +} + +export {getCurrentCursorPosition, moveCursorToEnd, setCursorPosition, removeSelection, scrollCursorIntoView};