Skip to content

Commit

Permalink
Merge branch 'main' into @tomekzaw/worklets
Browse files Browse the repository at this point in the history
  • Loading branch information
tomekzaw committed Sep 30, 2024
2 parents bc5c08a + fe56550 commit aa46028
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 12 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@expensify/react-native-live-markdown",
"version": "0.1.157",
"version": "0.1.158",
"description": "Drop-in replacement for React Native's TextInput component with Markdown formatting.",
"main": "lib/commonjs/index",
"module": "lib/module/index",
Expand Down
32 changes: 27 additions & 5 deletions src/MarkdownTextInput.web.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
const history = useRef<InputHistory>();
const dimensions = useRef<Dimensions | null>(null);
const pasteContent = useRef<string | null>(null);
const hasJustBeenFocused = useRef<boolean>(false);

if (!history.current) {
history.current = new InputHistory(100, 150, value || '');
Expand Down Expand Up @@ -153,6 +154,7 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
cursorPosition: number | null = null,
shouldAddToHistory = true,
shouldForceDOMUpdate = false,
shouldScrollIntoView = false,
): ParseTextResult => {
if (!divRef.current) {
return {text: text || '', cursorPosition: null};
Expand All @@ -161,7 +163,7 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
if (text === null) {
return {text: divRef.current.value, cursorPosition: null};
}
const parsedText = updateInputStructure(parserFunction, target, text, cursorPosition, multiline, customMarkdownStyles, false, shouldForceDOMUpdate);
const parsedText = updateInputStructure(parserFunction, target, text, cursorPosition, multiline, customMarkdownStyles, false, shouldForceDOMUpdate, shouldScrollIntoView);
divRef.current.value = parsedText.text;

if (history.current && shouldAddToHistory) {
Expand Down Expand Up @@ -271,6 +273,19 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
[handleSelectionChange, updateRefSelectionVariables],
);

const handleOnSelect = useCallback(
(e) => {
updateSelection(e);

// If the input has just been focused, we need to scroll the cursor into view
if (divRef.current && contentSelection.current && hasJustBeenFocused.current) {
setCursorPosition(divRef.current, contentSelection.current?.start, contentSelection.current?.end, true);
hasJustBeenFocused.current = false;
}
},
[updateSelection],
);

const handleContentSizeChange = useCallback(() => {
if (!divRef.current || !multiline || !onContentSizeChange) {
return;
Expand Down Expand Up @@ -332,7 +347,7 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
newInputUpdate = redo(divRef.current);
break;
default:
newInputUpdate = parseText(parser, divRef.current, parsedText, processedMarkdownStyle, newCursorPosition, true, !inputType);
newInputUpdate = parseText(parser, divRef.current, parsedText, processedMarkdownStyle, newCursorPosition, true, !inputType, inputType === 'pasteText');
}
const {text, cursorPosition} = newInputUpdate;
updateTextColor(divRef.current, text);
Expand Down Expand Up @@ -466,6 +481,7 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(

const handleFocus: FocusEventHandler<HTMLDivElement> = useCallback(
(event) => {
hasJustBeenFocused.current = true;
const e = event as unknown as NativeSyntheticEvent<TextInputFocusEventData>;
const hostNode = e.target as unknown as HTMLDivElement;
currentlyFocusedField.current = hostNode;
Expand All @@ -474,11 +490,17 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
if (contentSelection.current) {
setCursorPosition(divRef.current, contentSelection.current.start, contentSelection.current.end);
} else {
const valueLength = value ? value.length : divRef.current.value.length;
const valueLength = value ? value.length : (divRef.current.value || '').length;
setCursorPosition(divRef.current, valueLength, null);
}
}

if (divRef.current) {
divRef.current.scrollIntoView({
block: 'nearest',
});
}

if (onFocus) {
setEventProps(e);
onFocus(e);
Expand Down Expand Up @@ -622,7 +644,7 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
}
const normalizedValue = normalizeValue(value);
divRef.current.value = normalizedValue;
parseText(parser, divRef.current, normalizedValue, processedMarkdownStyle);
parseText(parser, divRef.current, normalizedValue, processedMarkdownStyle, null, true, false, true);
updateTextColor(divRef.current, value);
},
[multiline, processedMarkdownStyle, value],
Expand Down Expand Up @@ -706,7 +728,7 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
spellCheck={spellCheck}
dir={dir}
inputMode={inputMode}
onSelect={updateSelection}
onSelect={handleOnSelect}
onTouchStart={handleTouchStart}
/>
);
Expand Down
21 changes: 18 additions & 3 deletions src/web/utils/cursorUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type {MarkdownTextInputElement} from '../../MarkdownTextInput.web';
import {findHTMLElementInTree, getTreeNodeByIndex} from './treeUtils';
import type {TreeNode} from './treeUtils';

function setCursorPosition(target: MarkdownTextInputElement, startIndex: number, endIndex: number | null = null) {
function setCursorPosition(target: MarkdownTextInputElement, startIndex: number, endIndex: number | null = null, shouldScrollIntoView = false) {
// We don't want to move the cursor if the target is not focused
if (!target.tree || target !== document.activeElement) {
return;
Expand Down Expand Up @@ -45,16 +45,31 @@ function setCursorPosition(target: MarkdownTextInputElement, startIndex: number,
selection.setBaseAndExtent(range.startContainer, range.startOffset, range.endContainer, range.endOffset);
}

scrollIntoView(startTreeNode);
if (shouldScrollIntoView) {
scrollIntoView(target, endTreeNode);
}
}

function scrollIntoView(node: TreeNode) {
function scrollIntoView(target: MarkdownTextInputElement, node: TreeNode) {
const targetElement = target;
if (node.type === 'br' && node.parentNode?.parentNode?.type === 'line') {
// If the node is a line break, scroll to the parent paragraph, because Safari doesn't support scrollIntoView on br elements
node.parentNode.parentNode.element.scrollIntoView({
block: 'nearest',
});
} else {
const selection = window.getSelection();
if (selection && selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
const caretRect = range.getBoundingClientRect();
const targetRect = target.getBoundingClientRect();

// In case the caret is below the visible input area, scroll to the end of the node
if (caretRect.top + caretRect.height > targetRect.top + targetRect.height) {
targetElement.scrollTop = caretRect.top + caretRect.height - targetRect.top - targetRect.height + target.scrollTop;
}
}

node.element.scrollIntoView({
block: 'nearest',
});
Expand Down
7 changes: 4 additions & 3 deletions src/web/utils/parserUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,15 +266,15 @@ function parseRangesToHTMLNodes(
return {dom: rootElement, tree: rootNode};
}

function moveCursor(isFocused: boolean, alwaysMoveCursorToTheEnd: boolean, cursorPosition: number | null, target: MarkdownTextInputElement) {
function moveCursor(isFocused: boolean, alwaysMoveCursorToTheEnd: boolean, cursorPosition: number | null, target: MarkdownTextInputElement, shouldScrollIntoView = false) {
if (!isFocused) {
return;
}

if (alwaysMoveCursorToTheEnd || cursorPosition === null) {
moveCursorToEnd(target);
} else if (cursorPosition !== null) {
setCursorPosition(target, cursorPosition);
setCursorPosition(target, cursorPosition, null, shouldScrollIntoView);
}
}

Expand All @@ -287,6 +287,7 @@ function updateInputStructure(
markdownStyle: PartialMarkdownStyle = {},
alwaysMoveCursorToTheEnd = false,
shouldForceDOMUpdate = false,
shouldScrollIntoView = false,
) {
const targetElement = target;

Expand Down Expand Up @@ -318,7 +319,7 @@ function updateInputStructure(
updateTreeElementRefs(tree, targetElement);
targetElement.tree = tree;

moveCursor(isFocused, alwaysMoveCursorToTheEnd, cursorPosition, targetElement);
moveCursor(isFocused, alwaysMoveCursorToTheEnd, cursorPosition, targetElement, shouldScrollIntoView);
}

return {text, cursorPosition: cursorPosition || 0};
Expand Down

0 comments on commit aa46028

Please sign in to comment.