@expensify/react-native-live-markdown 0.1.157 → 0.1.159
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/MarkdownTextInput.web.js +26 -6
- package/lib/commonjs/MarkdownTextInput.web.js.map +1 -1
- package/lib/commonjs/web/inputElements/inlineImage.js +1 -1
- package/lib/commonjs/web/inputElements/inlineImage.js.map +1 -1
- package/lib/commonjs/web/utils/cursorUtils.js +17 -3
- package/lib/commonjs/web/utils/cursorUtils.js.map +1 -1
- package/lib/commonjs/web/utils/parserUtils.js +7 -14
- package/lib/commonjs/web/utils/parserUtils.js.map +1 -1
- package/lib/commonjs/web/utils/treeUtils.js +13 -0
- package/lib/commonjs/web/utils/treeUtils.js.map +1 -1
- package/lib/module/MarkdownTextInput.web.js +26 -6
- package/lib/module/MarkdownTextInput.web.js.map +1 -1
- package/lib/module/web/inputElements/inlineImage.js +1 -1
- package/lib/module/web/inputElements/inlineImage.js.map +1 -1
- package/lib/module/web/utils/cursorUtils.js +17 -3
- package/lib/module/web/utils/cursorUtils.js.map +1 -1
- package/lib/module/web/utils/parserUtils.js +8 -15
- package/lib/module/web/utils/parserUtils.js.map +1 -1
- package/lib/module/web/utils/treeUtils.js +13 -1
- package/lib/module/web/utils/treeUtils.js.map +1 -1
- package/lib/typescript/src/MarkdownTextInput.web.d.ts.map +1 -1
- package/lib/typescript/src/web/utils/cursorUtils.d.ts +2 -2
- package/lib/typescript/src/web/utils/cursorUtils.d.ts.map +1 -1
- package/lib/typescript/src/web/utils/parserUtils.d.ts +1 -1
- package/lib/typescript/src/web/utils/parserUtils.d.ts.map +1 -1
- package/lib/typescript/src/web/utils/treeUtils.d.ts +2 -1
- package/lib/typescript/src/web/utils/treeUtils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/MarkdownTextInput.web.tsx +30 -5
- package/src/web/inputElements/inlineImage.ts +1 -1
- package/src/web/utils/cursorUtils.ts +18 -3
- package/src/web/utils/parserUtils.ts +9 -14
- package/src/web/utils/treeUtils.ts +14 -1
|
@@ -112,6 +112,7 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
|
|
|
112
112
|
const history = useRef<InputHistory>();
|
|
113
113
|
const dimensions = useRef<Dimensions | null>(null);
|
|
114
114
|
const pasteContent = useRef<string | null>(null);
|
|
115
|
+
const hasJustBeenFocused = useRef<boolean>(false);
|
|
115
116
|
|
|
116
117
|
if (!history.current) {
|
|
117
118
|
history.current = new InputHistory(100, 150, value || '');
|
|
@@ -144,6 +145,7 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
|
|
|
144
145
|
cursorPosition: number | null = null,
|
|
145
146
|
shouldAddToHistory = true,
|
|
146
147
|
shouldForceDOMUpdate = false,
|
|
148
|
+
shouldScrollIntoView = false,
|
|
147
149
|
): ParseTextResult => {
|
|
148
150
|
if (!divRef.current) {
|
|
149
151
|
return {text: text || '', cursorPosition: null};
|
|
@@ -152,7 +154,7 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
|
|
|
152
154
|
if (text === null) {
|
|
153
155
|
return {text: divRef.current.value, cursorPosition: null};
|
|
154
156
|
}
|
|
155
|
-
const parsedText = updateInputStructure(target, text, cursorPosition, multiline, customMarkdownStyles, false, shouldForceDOMUpdate);
|
|
157
|
+
const parsedText = updateInputStructure(target, text, cursorPosition, multiline, customMarkdownStyles, false, shouldForceDOMUpdate, shouldScrollIntoView);
|
|
156
158
|
divRef.current.value = parsedText.text;
|
|
157
159
|
|
|
158
160
|
if (history.current && shouldAddToHistory) {
|
|
@@ -262,6 +264,19 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
|
|
|
262
264
|
[handleSelectionChange, updateRefSelectionVariables],
|
|
263
265
|
);
|
|
264
266
|
|
|
267
|
+
const handleOnSelect = useCallback(
|
|
268
|
+
(e) => {
|
|
269
|
+
updateSelection(e);
|
|
270
|
+
|
|
271
|
+
// If the input has just been focused, we need to scroll the cursor into view
|
|
272
|
+
if (divRef.current && contentSelection.current && hasJustBeenFocused.current) {
|
|
273
|
+
setCursorPosition(divRef.current, contentSelection.current?.start, contentSelection.current?.end, true);
|
|
274
|
+
hasJustBeenFocused.current = false;
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
[updateSelection],
|
|
278
|
+
);
|
|
279
|
+
|
|
265
280
|
const handleContentSizeChange = useCallback(() => {
|
|
266
281
|
if (!divRef.current || !multiline || !onContentSizeChange) {
|
|
267
282
|
return;
|
|
@@ -311,6 +326,9 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
|
|
|
311
326
|
end: newCursorPosition,
|
|
312
327
|
});
|
|
313
328
|
divRef.current.value = parsedText;
|
|
329
|
+
if (onChangeText) {
|
|
330
|
+
onChangeText(parsedText);
|
|
331
|
+
}
|
|
314
332
|
return;
|
|
315
333
|
}
|
|
316
334
|
|
|
@@ -323,7 +341,7 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
|
|
|
323
341
|
newInputUpdate = redo(divRef.current);
|
|
324
342
|
break;
|
|
325
343
|
default:
|
|
326
|
-
newInputUpdate = parseText(divRef.current, parsedText, processedMarkdownStyle, newCursorPosition, true, !inputType);
|
|
344
|
+
newInputUpdate = parseText(divRef.current, parsedText, processedMarkdownStyle, newCursorPosition, true, !inputType, inputType === 'pasteText');
|
|
327
345
|
}
|
|
328
346
|
const {text, cursorPosition} = newInputUpdate;
|
|
329
347
|
updateTextColor(divRef.current, text);
|
|
@@ -457,6 +475,7 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
|
|
|
457
475
|
|
|
458
476
|
const handleFocus: FocusEventHandler<HTMLDivElement> = useCallback(
|
|
459
477
|
(event) => {
|
|
478
|
+
hasJustBeenFocused.current = true;
|
|
460
479
|
const e = event as unknown as NativeSyntheticEvent<TextInputFocusEventData>;
|
|
461
480
|
const hostNode = e.target as unknown as HTMLDivElement;
|
|
462
481
|
currentlyFocusedField.current = hostNode;
|
|
@@ -465,11 +484,17 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
|
|
|
465
484
|
if (contentSelection.current) {
|
|
466
485
|
setCursorPosition(divRef.current, contentSelection.current.start, contentSelection.current.end);
|
|
467
486
|
} else {
|
|
468
|
-
const valueLength = value ? value.length : divRef.current.value.length;
|
|
487
|
+
const valueLength = value ? value.length : (divRef.current.value || '').length;
|
|
469
488
|
setCursorPosition(divRef.current, valueLength, null);
|
|
470
489
|
}
|
|
471
490
|
}
|
|
472
491
|
|
|
492
|
+
if (divRef.current) {
|
|
493
|
+
divRef.current.scrollIntoView({
|
|
494
|
+
block: 'nearest',
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
|
|
473
498
|
if (onFocus) {
|
|
474
499
|
setEventProps(e);
|
|
475
500
|
onFocus(e);
|
|
@@ -613,7 +638,7 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
|
|
|
613
638
|
}
|
|
614
639
|
const normalizedValue = normalizeValue(value);
|
|
615
640
|
divRef.current.value = normalizedValue;
|
|
616
|
-
parseText(divRef.current, normalizedValue, processedMarkdownStyle);
|
|
641
|
+
parseText(divRef.current, normalizedValue, processedMarkdownStyle, null, true, false, true);
|
|
617
642
|
updateTextColor(divRef.current, value);
|
|
618
643
|
},
|
|
619
644
|
[multiline, processedMarkdownStyle, value],
|
|
@@ -697,7 +722,7 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
|
|
|
697
722
|
spellCheck={spellCheck}
|
|
698
723
|
dir={dir}
|
|
699
724
|
inputMode={inputMode}
|
|
700
|
-
onSelect={
|
|
725
|
+
onSelect={handleOnSelect}
|
|
701
726
|
onTouchStart={handleTouchStart}
|
|
702
727
|
/>
|
|
703
728
|
);
|
|
@@ -95,7 +95,7 @@ function createImageElement(currentInput: MarkdownTextInputElement, targetNode:
|
|
|
95
95
|
if (timeoutMap.has(targetNode.orderIndex)) {
|
|
96
96
|
const mapItem = timeoutMap.get(targetNode.orderIndex);
|
|
97
97
|
// Check if the image URL has been changed, if not, early return so the image can be loaded asynchronously
|
|
98
|
-
const currentElement =
|
|
98
|
+
const currentElement = currentInput.querySelector(`[data-type="block"][data-id="${targetNode.orderIndex}"]`) as HTMLMarkdownElement;
|
|
99
99
|
if (mapItem?.url === url && currentElement && getImagePreviewElement(currentElement)) {
|
|
100
100
|
return;
|
|
101
101
|
}
|
|
@@ -2,7 +2,7 @@ import type {MarkdownTextInputElement} from '../../MarkdownTextInput.web';
|
|
|
2
2
|
import {findHTMLElementInTree, getTreeNodeByIndex} from './treeUtils';
|
|
3
3
|
import type {TreeNode} from './treeUtils';
|
|
4
4
|
|
|
5
|
-
function setCursorPosition(target: MarkdownTextInputElement, startIndex: number, endIndex: number | null = null) {
|
|
5
|
+
function setCursorPosition(target: MarkdownTextInputElement, startIndex: number, endIndex: number | null = null, shouldScrollIntoView = false) {
|
|
6
6
|
// We don't want to move the cursor if the target is not focused
|
|
7
7
|
if (!target.tree || target !== document.activeElement) {
|
|
8
8
|
return;
|
|
@@ -45,16 +45,31 @@ function setCursorPosition(target: MarkdownTextInputElement, startIndex: number,
|
|
|
45
45
|
selection.setBaseAndExtent(range.startContainer, range.startOffset, range.endContainer, range.endOffset);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
if (shouldScrollIntoView) {
|
|
49
|
+
scrollIntoView(target, endTreeNode);
|
|
50
|
+
}
|
|
49
51
|
}
|
|
50
52
|
|
|
51
|
-
function scrollIntoView(node: TreeNode) {
|
|
53
|
+
function scrollIntoView(target: MarkdownTextInputElement, node: TreeNode) {
|
|
54
|
+
const targetElement = target;
|
|
52
55
|
if (node.type === 'br' && node.parentNode?.parentNode?.type === 'line') {
|
|
53
56
|
// If the node is a line break, scroll to the parent paragraph, because Safari doesn't support scrollIntoView on br elements
|
|
54
57
|
node.parentNode.parentNode.element.scrollIntoView({
|
|
55
58
|
block: 'nearest',
|
|
56
59
|
});
|
|
57
60
|
} else {
|
|
61
|
+
const selection = window.getSelection();
|
|
62
|
+
if (selection && selection.rangeCount > 0) {
|
|
63
|
+
const range = selection.getRangeAt(0);
|
|
64
|
+
const caretRect = range.getBoundingClientRect();
|
|
65
|
+
const targetRect = target.getBoundingClientRect();
|
|
66
|
+
|
|
67
|
+
// In case the caret is below the visible input area, scroll to the end of the node
|
|
68
|
+
if (caretRect.top + caretRect.height > targetRect.top + targetRect.height) {
|
|
69
|
+
targetElement.scrollTop = caretRect.top + caretRect.height - targetRect.top - targetRect.height + target.scrollTop;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
58
73
|
node.element.scrollIntoView({
|
|
59
74
|
block: 'nearest',
|
|
60
75
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type {HTMLMarkdownElement, MarkdownTextInputElement} from '../../MarkdownTextInput.web';
|
|
2
|
-
import {addNodeToTree, updateTreeElementRefs} from './treeUtils';
|
|
2
|
+
import {addNodeToTree, createRootTreeNode, updateTreeElementRefs} from './treeUtils';
|
|
3
3
|
import type {NodeType, TreeNode} from './treeUtils';
|
|
4
4
|
import type {PartialMarkdownStyle} from '../../styleUtils';
|
|
5
5
|
import {getCurrentCursorPosition, moveCursorToEnd, setCursorPosition} from './cursorUtils';
|
|
@@ -154,16 +154,8 @@ function parseRangesToHTMLNodes(
|
|
|
154
154
|
) {
|
|
155
155
|
const rootElement: HTMLMarkdownElement = document.createElement('span') as HTMLMarkdownElement;
|
|
156
156
|
const textLength = text.length;
|
|
157
|
-
const rootNode: TreeNode =
|
|
158
|
-
|
|
159
|
-
start: 0,
|
|
160
|
-
length: textLength,
|
|
161
|
-
parentNode: null,
|
|
162
|
-
childNodes: [],
|
|
163
|
-
type: 'root',
|
|
164
|
-
orderIndex: '',
|
|
165
|
-
isGeneratingNewline: false,
|
|
166
|
-
};
|
|
157
|
+
const rootNode: TreeNode = createRootTreeNode(rootElement, textLength);
|
|
158
|
+
|
|
167
159
|
let currentParentNode: TreeNode = rootNode;
|
|
168
160
|
let lines = splitTextIntoLines(text);
|
|
169
161
|
|
|
@@ -266,7 +258,7 @@ function parseRangesToHTMLNodes(
|
|
|
266
258
|
return {dom: rootElement, tree: rootNode};
|
|
267
259
|
}
|
|
268
260
|
|
|
269
|
-
function moveCursor(isFocused: boolean, alwaysMoveCursorToTheEnd: boolean, cursorPosition: number | null, target: MarkdownTextInputElement) {
|
|
261
|
+
function moveCursor(isFocused: boolean, alwaysMoveCursorToTheEnd: boolean, cursorPosition: number | null, target: MarkdownTextInputElement, shouldScrollIntoView = false) {
|
|
270
262
|
if (!isFocused) {
|
|
271
263
|
return;
|
|
272
264
|
}
|
|
@@ -274,7 +266,7 @@ function moveCursor(isFocused: boolean, alwaysMoveCursorToTheEnd: boolean, curso
|
|
|
274
266
|
if (alwaysMoveCursorToTheEnd || cursorPosition === null) {
|
|
275
267
|
moveCursorToEnd(target);
|
|
276
268
|
} else if (cursorPosition !== null) {
|
|
277
|
-
setCursorPosition(target, cursorPosition);
|
|
269
|
+
setCursorPosition(target, cursorPosition, null, shouldScrollIntoView);
|
|
278
270
|
}
|
|
279
271
|
}
|
|
280
272
|
|
|
@@ -286,6 +278,7 @@ function updateInputStructure(
|
|
|
286
278
|
markdownStyle: PartialMarkdownStyle = {},
|
|
287
279
|
alwaysMoveCursorToTheEnd = false,
|
|
288
280
|
shouldForceDOMUpdate = false,
|
|
281
|
+
shouldScrollIntoView = false,
|
|
289
282
|
) {
|
|
290
283
|
const targetElement = target;
|
|
291
284
|
|
|
@@ -317,7 +310,9 @@ function updateInputStructure(
|
|
|
317
310
|
updateTreeElementRefs(tree, targetElement);
|
|
318
311
|
targetElement.tree = tree;
|
|
319
312
|
|
|
320
|
-
moveCursor(isFocused, alwaysMoveCursorToTheEnd, cursorPosition, targetElement);
|
|
313
|
+
moveCursor(isFocused, alwaysMoveCursorToTheEnd, cursorPosition, targetElement, shouldScrollIntoView);
|
|
314
|
+
} else {
|
|
315
|
+
targetElement.tree = createRootTreeNode(targetElement);
|
|
321
316
|
}
|
|
322
317
|
|
|
323
318
|
return {text, cursorPosition: cursorPosition || 0};
|
|
@@ -12,6 +12,19 @@ type TreeNode = Omit<MarkdownRange, 'type'> & {
|
|
|
12
12
|
isGeneratingNewline: boolean;
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
+
function createRootTreeNode(target: HTMLMarkdownElement, length = 0): TreeNode {
|
|
16
|
+
return {
|
|
17
|
+
element: target,
|
|
18
|
+
start: 0,
|
|
19
|
+
length,
|
|
20
|
+
parentNode: null,
|
|
21
|
+
childNodes: [],
|
|
22
|
+
type: 'root',
|
|
23
|
+
orderIndex: '',
|
|
24
|
+
isGeneratingNewline: false,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
15
28
|
function addNodeToTree(element: HTMLMarkdownElement, parentTreeNode: TreeNode, type: NodeType, length: number | null = null) {
|
|
16
29
|
const contentLength = length || (element.nodeName === 'BR' || type === 'br' ? 1 : element.value?.length) || 0;
|
|
17
30
|
const isGeneratingNewline = type === 'line' && !(element.childNodes.length === 1 && (element.childNodes[0] as HTMLElement)?.getAttribute?.('data-type') === 'br');
|
|
@@ -115,6 +128,6 @@ function getTreeNodeByIndex(treeRoot: TreeNode, index: number): TreeNode | null
|
|
|
115
128
|
return null;
|
|
116
129
|
}
|
|
117
130
|
|
|
118
|
-
export {addNodeToTree, findHTMLElementInTree, getTreeNodeByIndex, updateTreeElementRefs};
|
|
131
|
+
export {addNodeToTree, findHTMLElementInTree, getTreeNodeByIndex, updateTreeElementRefs, createRootTreeNode};
|
|
119
132
|
|
|
120
133
|
export type {TreeNode, NodeType};
|