Skip to content

Commit

Permalink
Add support for setting start and end selection position
Browse files Browse the repository at this point in the history
  • Loading branch information
Skalakid committed Mar 1, 2024
1 parent 71a565f commit a264ab2
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 50 deletions.
15 changes: 15 additions & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ function getRandomColor() {
export default function App() {
const [value, setValue] = React.useState(DEFAULT_TEXT);
const [markdownStyle, setMarkdownStyle] = React.useState({});
const [selection, setSelection] = React.useState({start: 0, end: 0});

// TODO: use MarkdownTextInput ref instead of TextInput ref
const ref = React.useRef<TextInput>(null);
Expand Down Expand Up @@ -98,6 +99,10 @@ export default function App() {
ref={ref}
markdownStyle={markdownStyle}
placeholder="Type here..."
onSelectionChange={(e) => {
setSelection({...e.nativeEvent.selection});
}}
selection={selection}
/>
{/* <Text>TextInput singleline</Text>
<TextInput
Expand Down Expand Up @@ -154,6 +159,16 @@ export default function App() {
})
}
/>
<Button
title="Change selection"
onPress={() => {
if (!ref.current) {
return;
}
ref.current.focus();
setSelection({start: 0, end: 20});
}}
/>
</View>
);
}
Expand Down
15 changes: 9 additions & 6 deletions src/MarkdownTextInput.web.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
[onSelectionChange, setEventProps],
);

const updateSelection = useCallback((e) => {
const updateSelection = useCallback((e: SyntheticEvent<HTMLDivElement> | null = null) => {
if (!divRef.current) {
return;
}
Expand All @@ -311,8 +311,10 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
markdownHTMLInput.selectionStart = currentSelection.start;
markdownHTMLInput.selectionEnd = currentSelection.end;
contentSelection.current = currentSelection;
// console.log(selection);
handleSelectionChange(e);

if (e) {
handleSelectionChange(e);
}
}
}, []);

Expand Down Expand Up @@ -350,7 +352,7 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
onKeyPress(event);
}

updateSelection(event);
updateSelection(event as unknown as SyntheticEvent<HTMLDivElement, Event>);

if (
e.key === 'Enter' &&
Expand Down Expand Up @@ -387,7 +389,7 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
currentlyFocusedField.current = hostNode;
setEventProps(e);
if (divRef.current && contentSelection.current) {
CursorUtils.setCursorPosition(divRef.current, contentSelection.current.end, !multiline);
CursorUtils.setCursorPosition(divRef.current, contentSelection.current.end || contentSelection.current.start);
}

if (onFocus) {
Expand Down Expand Up @@ -526,7 +528,8 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
if (!divRef.current || !selection || !(selection.start !== contentSelection.current.start || selection.end !== contentSelection.current.end)) {
return;
}
CursorUtils.setCursorPosition(divRef.current, selection.start, !multiline);
CursorUtils.setCursorPosition(divRef.current, selection.start, selection.end);
updateSelection();
}, [selection]);

return (
Expand Down
75 changes: 40 additions & 35 deletions src/web/cursorUtils.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,53 @@
function createRange(node: HTMLElement, targetPosition: number, ignoreNewLines = false) {
function setCursorPosition(target: HTMLElement, start: number, end: number | null = null) {
const range = document.createRange();
range.selectNode(node);
range.selectNodeContents(target);

let pos = 0;
const stack: Node[] = [node];
while (stack.length > 0) {
const current = stack.pop();
if (!current) {
break;
}
if (current.nodeType === Node.TEXT_NODE || current.nodeName === 'BR') {
const textContentLength = current.textContent ? current.textContent.length : 0;
const len = current.nodeName === 'BR' ? 1 : textContentLength;
if (pos + len >= targetPosition) {
if (current.nodeName === 'BR') {
range.setStartAfter(current);
(current as HTMLElement).scrollIntoView();
} else {
range.setStart(current, targetPosition - pos);
const textNodes: Text[] = [];
(function findTextNodes(node: ChildNode) {
if (node.nodeType === 3) {
textNodes.push(node as Text);
} else {
for (let i = 0, len = node.childNodes.length; i < len; ++i) {
const childNode = node.childNodes[i];
if (childNode) {
findTextNodes(childNode);
}
return range;
}
pos += len;
} else if (current.childNodes && current.childNodes.length > 0) {
for (let i = current.childNodes.length - 1; i >= 0; i--) {
const currentNode = current.childNodes[i];
if (currentNode && (!ignoreNewLines || (ignoreNewLines && currentNode.nodeName !== 'BR'))) {
stack.push(currentNode);
}
})(target);

let charCount = 0;
let startNode: Text | null = null;
let endNode: Text | null = null;
const n = textNodes.length;
for (let i = 0; i < n; ++i) {
const textNode = textNodes[i];
if (textNode) {
const nextCharCount = charCount + textNode.length;

if (!startNode && start >= charCount && (start <= nextCharCount || (start === nextCharCount && i < n - 1))) {
startNode = textNode;
range.setStart(textNode, start - charCount);
if (!end) {
break;
}
}
if (end && !endNode && end >= charCount && (end <= nextCharCount || (end === nextCharCount && i < n - 1))) {
endNode = textNode;
range.setEnd(textNode, end - charCount);
}
charCount = nextCharCount;
}
}

range.setStart(node, node.childNodes.length);
return range;
}

function setCursorPosition(target: HTMLElement, targetPosition: number, ignoreNewLines = false) {
const range = createRange(target, targetPosition, ignoreNewLines);
const selection = window.getSelection();
if (selection) {
if (!end) {
range.collapse(true);
selection.removeAllRanges();
selection.addRange(range);
}

const sel = window.getSelection();
if (sel) {
sel.removeAllRanges();
sel.addRange(range);
}
}

Expand Down
11 changes: 2 additions & 9 deletions src/web/parserUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,14 +160,7 @@ function parseRangesToHTMLNodes(text: string, ranges: MarkdownRange[], markdownS
return root;
}

function parseText(
target: HTMLElement,
text: string,
curosrPositionIndex: number | null,
markdownStyle: PartialMarkdownStyle = {},
disableNewLinesInCursorPositioning = false,
alwaysMoveCursorToTheEnd = false,
) {
function parseText(target: HTMLElement, text: string, curosrPositionIndex: number | null, markdownStyle: PartialMarkdownStyle = {}, alwaysMoveCursorToTheEnd = false) {
const targetElement = target;

let cursorPosition: number | null = curosrPositionIndex;
Expand All @@ -191,7 +184,7 @@ function parseText(
if (alwaysMoveCursorToTheEnd) {
CursorUtils.moveCursorToEnd(target);
} else if (isFocused && cursorPosition !== null) {
CursorUtils.setCursorPosition(target, cursorPosition, disableNewLinesInCursorPositioning);
CursorUtils.setCursorPosition(target, cursorPosition);
}

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

0 comments on commit a264ab2

Please sign in to comment.