-
Notifications
You must be signed in to change notification settings - Fork 73
/
Copy pathMarkdownTextInput.tsx
134 lines (112 loc) · 4.56 KB
/
MarkdownTextInput.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import {StyleSheet, TextInput, processColor} from 'react-native';
import React from 'react';
import type {TextInputProps} from 'react-native';
import {createWorkletRuntime, makeShareableCloneRecursive} from 'react-native-reanimated';
import type {WorkletRuntime} from 'react-native-reanimated';
import type {ShareableRef, WorkletFunction} from 'react-native-reanimated/lib/typescript/commonTypes';
import MarkdownTextInputDecoratorViewNativeComponent from './MarkdownTextInputDecoratorViewNativeComponent';
import type {MarkdownStyle} from './MarkdownTextInputDecoratorViewNativeComponent';
import NativeLiveMarkdownModule from './NativeLiveMarkdownModule';
import {mergeMarkdownStyleWithDefault} from './styleUtils';
import type {PartialMarkdownStyle} from './styleUtils';
import type {InlineImagesInputProps, MarkdownRange} from './commonTypes';
declare global {
// eslint-disable-next-line no-var
var jsi_setMarkdownRuntime: (runtime: WorkletRuntime) => void;
// eslint-disable-next-line no-var
var jsi_registerMarkdownWorklet: (shareableWorklet: ShareableRef<WorkletFunction<[string], Range[]>>) => number;
// eslint-disable-next-line no-var
var jsi_unregisterMarkdownWorklet: (parserId: number) => void;
}
let initialized = false;
let workletRuntime: WorkletRuntime | undefined;
function initializeLiveMarkdownIfNeeded() {
if (initialized) {
return;
}
if (!NativeLiveMarkdownModule) {
throw new Error('[react-native-live-markdown] NativeLiveMarkdownModule is not available');
}
NativeLiveMarkdownModule.install();
if (!global.jsi_setMarkdownRuntime) {
throw new Error('[react-native-live-markdown] global.jsi_setMarkdownRuntime is not available');
}
workletRuntime = createWorkletRuntime('LiveMarkdownRuntime');
global.jsi_setMarkdownRuntime(workletRuntime);
initialized = true;
}
function registerParser(parser: (input: string) => MarkdownRange[]): number {
initializeLiveMarkdownIfNeeded();
const shareableWorklet = makeShareableCloneRecursive(parser) as ShareableRef<WorkletFunction<[string], Range[]>>;
const parserId = global.jsi_registerMarkdownWorklet(shareableWorklet);
return parserId;
}
function unregisterParser(parserId: number) {
global.jsi_unregisterMarkdownWorklet(parserId);
}
interface MarkdownTextInputProps extends TextInputProps, InlineImagesInputProps {
markdownStyle?: PartialMarkdownStyle;
parser: (value: string) => MarkdownRange[];
}
type MarkdownTextInput = TextInput & React.Component<MarkdownTextInputProps>;
function processColorsInMarkdownStyle(input: MarkdownStyle): MarkdownStyle {
const output = JSON.parse(JSON.stringify(input));
Object.keys(output).forEach((key) => {
const obj = output[key];
Object.keys(obj).forEach((prop) => {
// TODO: use ReactNativeStyleAttributes from 'react-native/Libraries/Components/View/ReactNativeStyleAttributes'
if (!(prop === 'color' || prop.endsWith('Color'))) {
return;
}
obj[prop] = processColor(obj[prop]);
});
});
return output as MarkdownStyle;
}
function processMarkdownStyle(input: PartialMarkdownStyle | undefined): MarkdownStyle {
return processColorsInMarkdownStyle(mergeMarkdownStyleWithDefault(input));
}
const MarkdownTextInput = React.forwardRef<MarkdownTextInput, MarkdownTextInputProps>((props, ref) => {
const IS_FABRIC = 'nativeFabricUIManager' in global;
const markdownStyle = React.useMemo(() => processMarkdownStyle(props.markdownStyle), [props.markdownStyle]);
if (props.parser === undefined) {
throw new Error('[react-native-live-markdown] `parser` is undefined');
}
// eslint-disable-next-line no-underscore-dangle
const workletHash = (props.parser as {__workletHash?: number}).__workletHash;
if (workletHash === undefined) {
throw new Error('[react-native-live-markdown] `parser` is not a worklet');
}
const parserId = React.useMemo(() => {
return registerParser(props.parser);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [workletHash]);
React.useEffect(() => {
return () => unregisterParser(parserId);
}, [parserId]);
return (
<>
<TextInput
{...props}
ref={ref}
/>
<MarkdownTextInputDecoratorViewNativeComponent
style={IS_FABRIC ? styles.farAway : styles.displayNone}
markdownStyle={markdownStyle}
parserId={parserId}
/>
</>
);
});
const styles = StyleSheet.create({
displayNone: {
display: 'none',
},
farAway: {
position: 'absolute',
top: 1e8,
left: 1e8,
},
});
export type {PartialMarkdownStyle as MarkdownStyle, MarkdownTextInputProps};
export default MarkdownTextInput;