@hero-design/rn-work-uikit 1.3.1 → 1.5.0

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.
Files changed (44) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/lib/index.js +19933 -735
  3. package/package.json +3 -2
  4. package/src/components/DatePicker/__tests__/__snapshots__/index.spec.tsx.snap +3 -3
  5. package/src/components/FormGroup/__tests__/__snapshots__/index.spec.tsx.snap +903 -0
  6. package/src/components/FormGroup/__tests__/index.spec.tsx +306 -0
  7. package/src/components/FormGroup/__tests__/utils.spec.ts +73 -0
  8. package/src/components/FormGroup/index.tsx +106 -0
  9. package/src/components/FormGroup/utils.ts +67 -0
  10. package/src/components/RichTextEditor/EditorEvent.ts +7 -0
  11. package/src/components/RichTextEditor/EditorToolbar.tsx +216 -0
  12. package/src/components/RichTextEditor/MentionList.tsx +99 -0
  13. package/src/components/RichTextEditor/RichTextEditor.tsx +88 -0
  14. package/src/components/RichTextEditor/RichTextEditorInput.tsx +292 -0
  15. package/src/components/RichTextEditor/StyledRichTextEditor.tsx +15 -0
  16. package/src/components/RichTextEditor/StyledToolbar.ts +32 -0
  17. package/src/components/RichTextEditor/__mocks__/hero-editor.js +3 -0
  18. package/src/components/RichTextEditor/__mocks__/heroEditorApp.ts +2 -0
  19. package/src/components/RichTextEditor/__tests__/EditorToolbar.spec.tsx +144 -0
  20. package/src/components/RichTextEditor/__tests__/MentionList.spec.tsx +105 -0
  21. package/src/components/RichTextEditor/__tests__/RichTextEditorInput.spec.tsx +136 -0
  22. package/src/components/RichTextEditor/__tests__/__snapshots__/EditorToolbar.spec.tsx.snap +414 -0
  23. package/src/components/RichTextEditor/__tests__/__snapshots__/MentionList.spec.tsx.snap +13 -0
  24. package/src/components/RichTextEditor/constants.ts +9 -0
  25. package/src/{hero-editor.d.ts → components/RichTextEditor/hero-editor.d.ts} +6 -0
  26. package/src/components/RichTextEditor/heroEditorApp.ts +3 -0
  27. package/src/components/RichTextEditor/index.tsx +20 -0
  28. package/src/components/RichTextEditor/types.ts +87 -0
  29. package/src/components/RichTextEditor/utils/events.ts +31 -0
  30. package/src/components/RichTextEditor/utils/rnWebView.tsx +30 -0
  31. package/src/components/Select/__tests__/__snapshots__/index.spec.tsx.snap +24 -2
  32. package/src/components/Select/index.tsx +11 -10
  33. package/src/components/TextInput/Group/__tests__/__snapshots__/index.spec.tsx.snap +3 -3
  34. package/src/components/TextInput/Group/index.tsx +6 -1
  35. package/src/components/TextInput/InputComponent.tsx +59 -18
  36. package/src/components/TextInput/InputRow.tsx +13 -7
  37. package/src/components/TextInput/StyledTextInput.tsx +3 -3
  38. package/src/components/TextInput/__tests__/__snapshots__/index.spec.tsx.snap +17 -17
  39. package/src/components/TextInput/index.tsx +22 -13
  40. package/src/components/TextInput/types.ts +30 -5
  41. package/src/index.ts +3 -1
  42. package/src/utils/hooks.ts +10 -0
  43. package/stats/1.5.0/rn-work-uikit-stats.html +4844 -0
  44. package/stats/1.3.1/rn-work-uikit-stats.html +0 -4844
@@ -0,0 +1,216 @@
1
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
+ import type { ComponentType } from 'react';
3
+ import { Icon, IconProps } from '@hero-design/rn';
4
+ import { ToolbarEvents } from './constants';
5
+ import { emitter } from './EditorEvent';
6
+ import {
7
+ StyledSeparator,
8
+ StyledToolbar,
9
+ StyledToolbarButton,
10
+ } from './StyledToolbar';
11
+ import { ToolbarButtonName } from './types';
12
+ import * as Events from './utils/events';
13
+
14
+ type ToolbarButtonProps = {
15
+ icon: IconProps['icon'];
16
+ onPress: () => void;
17
+ testID?: string;
18
+ selected: boolean;
19
+ };
20
+ const ToolbarButton: ComponentType<ToolbarButtonProps> = ({
21
+ icon,
22
+ onPress,
23
+ testID,
24
+ selected,
25
+ }: ToolbarButtonProps) => (
26
+ <StyledToolbarButton selected={selected} onPress={onPress} testID={testID}>
27
+ <Icon size="small" icon={icon} />
28
+ </StyledToolbarButton>
29
+ );
30
+
31
+ interface ButtonConfig {
32
+ icon: IconProps['icon'];
33
+ eventName: ToolbarEvents;
34
+ standalone?: boolean;
35
+ }
36
+
37
+ const buttonConfigs: Partial<Record<ToolbarButtonName, ButtonConfig>> = {
38
+ bold: {
39
+ icon: 'format-bold',
40
+ eventName: ToolbarEvents.Bold,
41
+ },
42
+ italic: {
43
+ icon: 'format-italic',
44
+ eventName: ToolbarEvents.Italic,
45
+ },
46
+ underline: {
47
+ icon: 'format-underlined',
48
+ eventName: ToolbarEvents.Underline,
49
+ },
50
+ bulletedList: {
51
+ icon: 'format-list-bulleted',
52
+ eventName: ToolbarEvents.BulletedList,
53
+ standalone: true,
54
+ },
55
+ numberedList: {
56
+ icon: 'format-list-numbered',
57
+ eventName: ToolbarEvents.NumberedList,
58
+ standalone: true,
59
+ },
60
+ headingOne: {
61
+ icon: 'format-heading1',
62
+ eventName: ToolbarEvents.HeadingOne,
63
+ standalone: true,
64
+ },
65
+ headingTwo: {
66
+ icon: 'format-heading2',
67
+ eventName: ToolbarEvents.HeadingTwo,
68
+ standalone: true,
69
+ },
70
+ };
71
+
72
+ const defaultButtons: ToolbarButtonName[] = [
73
+ 'bold',
74
+ 'italic',
75
+ 'underline',
76
+ '|',
77
+ 'bulletedList',
78
+ 'numberedList',
79
+ '|',
80
+ 'headingOne',
81
+ 'headingTwo',
82
+ ];
83
+
84
+ export interface EditorToolbarProps {
85
+ /**
86
+ * List of buttons to display in toolbar
87
+ */
88
+ buttons?: ToolbarButtonName[];
89
+ /**
90
+ * Unique name used to communicate with webview, should be the same with the RichTextEditor component it used with
91
+ */
92
+ name: string;
93
+ /**
94
+ * Testing ID of the component
95
+ */
96
+ testID?: string;
97
+ }
98
+
99
+ type ButtonType = {
100
+ id: number;
101
+ buttonName: ToolbarButtonName;
102
+ selected: boolean;
103
+ };
104
+
105
+ const EditorToolbar = ({
106
+ name,
107
+ buttons = defaultButtons,
108
+ testID,
109
+ }: EditorToolbarProps) => {
110
+ const [show, setShow] = useState(false);
111
+
112
+ const initialToolbarButtonArray: ButtonType[] = buttons.map(
113
+ (button, index) => ({
114
+ id: index + 1,
115
+ buttonName: button,
116
+ selected: false,
117
+ })
118
+ );
119
+
120
+ const [toolbarButtonArray, setToolbarButtonArray] = useState<ButtonType[]>(
121
+ initialToolbarButtonArray
122
+ );
123
+
124
+ const normalizeEventName = useCallback(
125
+ (event: string): string => `${name}/${event}`,
126
+ [name]
127
+ );
128
+
129
+ const toggleToolbarButton = useCallback(
130
+ ({ buttonName: currentButtonName, selected: prevSelected }: ButtonType) => {
131
+ const currentButtonConfig = buttonConfigs[currentButtonName];
132
+ const isStandalone =
133
+ currentButtonConfig && currentButtonConfig.standalone;
134
+
135
+ setToolbarButtonArray((prevState) =>
136
+ prevState.map((updatingButton) => {
137
+ if (updatingButton.buttonName === currentButtonName) {
138
+ return {
139
+ ...updatingButton,
140
+ selected: !prevSelected,
141
+ };
142
+ }
143
+
144
+ const updatingButtonConfig = buttonConfigs[updatingButton.buttonName];
145
+ const shouldToggleOff =
146
+ !prevSelected &&
147
+ isStandalone &&
148
+ updatingButtonConfig &&
149
+ updatingButtonConfig.standalone;
150
+
151
+ return {
152
+ ...updatingButton,
153
+ selected: shouldToggleOff ? false : updatingButton.selected,
154
+ };
155
+ })
156
+ );
157
+ },
158
+ []
159
+ );
160
+
161
+ useEffect(() => {
162
+ const removeFocusListener = Events.on(
163
+ emitter,
164
+ normalizeEventName('editor-focus'),
165
+ () => setShow(true)
166
+ );
167
+
168
+ const removeBlurListener = Events.on(
169
+ emitter,
170
+ normalizeEventName('editor-blur'),
171
+ () => setShow(false)
172
+ );
173
+
174
+ return () => {
175
+ removeFocusListener();
176
+ removeBlurListener();
177
+ };
178
+ }, [normalizeEventName]);
179
+
180
+ const toolbarButtons = useMemo(
181
+ () =>
182
+ toolbarButtonArray.map((button) => {
183
+ if (button.buttonName === '|') {
184
+ return <StyledSeparator key={button.id} />;
185
+ }
186
+ const config = buttonConfigs[button.buttonName];
187
+ if (config) {
188
+ return (
189
+ <ToolbarButton
190
+ key={button.id}
191
+ testID={config.icon}
192
+ icon={config.icon}
193
+ onPress={() => {
194
+ toggleToolbarButton(button);
195
+ Events.emit(
196
+ emitter,
197
+ normalizeEventName(config.eventName),
198
+ null
199
+ );
200
+ }}
201
+ selected={button.selected}
202
+ />
203
+ );
204
+ }
205
+ return null;
206
+ }),
207
+ [toolbarButtonArray, normalizeEventName, toggleToolbarButton]
208
+ );
209
+
210
+ if (show) {
211
+ return <StyledToolbar testID={testID}>{toolbarButtons}</StyledToolbar>;
212
+ }
213
+ return null;
214
+ };
215
+
216
+ export default EditorToolbar;
@@ -0,0 +1,99 @@
1
+ import React, { useCallback, useEffect, useState } from 'react';
2
+ import { View } from 'react-native';
3
+ import { useTheme } from '../../theme';
4
+ import { emitter } from './EditorEvent';
5
+ import * as Events from './utils/events';
6
+
7
+ const isEmptyString = (s: string | null): boolean => !s || s.length === 0;
8
+
9
+ interface OnSelectOptionsType<TMetaData> {
10
+ highlighted: boolean;
11
+ meta?: TMetaData;
12
+ }
13
+
14
+ export interface MentionListProps<TMetaData = unknown> {
15
+ /**
16
+ * Unique name used to communicate with webview, should be the same with the RichTextEditor component it used with
17
+ */
18
+ name: string;
19
+ /**
20
+ * Function used to render mention options
21
+ */
22
+ render: (
23
+ searchText: string,
24
+ onSelect: (
25
+ id: string,
26
+ name: string,
27
+ options?: OnSelectOptionsType<TMetaData>
28
+ ) => void
29
+ ) => JSX.Element;
30
+ }
31
+
32
+ const MentionList = <TMetaData,>({
33
+ name: eventPrefix,
34
+ render,
35
+ }: MentionListProps<TMetaData>) => {
36
+ const theme = useTheme();
37
+ const [search, setSearch] = useState<string>('');
38
+ const [target, setTarget] = useState<string | null>(null);
39
+ const normalizeEventName = useCallback(
40
+ (event: string) => `${eventPrefix}/${event}`,
41
+ [eventPrefix]
42
+ );
43
+
44
+ useEffect(() => {
45
+ const removeMentionSearchListener = Events.on(
46
+ emitter,
47
+ normalizeEventName('mention-search'),
48
+ ({
49
+ search: nextSearch,
50
+ target: nextTarget,
51
+ }: {
52
+ search?: string;
53
+ target?: string;
54
+ }) => {
55
+ setSearch(nextSearch || '');
56
+ setTarget(nextTarget || null);
57
+ }
58
+ );
59
+
60
+ return () => {
61
+ removeMentionSearchListener();
62
+ };
63
+ }, [eventPrefix]);
64
+
65
+ if (isEmptyString(search)) {
66
+ return null;
67
+ }
68
+
69
+ return (
70
+ <View>
71
+ {render(search, (id, name, options = { highlighted: false }) => {
72
+ const { highlighted, meta } = options;
73
+
74
+ const highlightStyle = {
75
+ color: theme.colors.secondary,
76
+ borderRadius: theme.__hd__.richTextEditor.radii.mention,
77
+ padding: highlighted ? theme.__hd__.richTextEditor.space.mention : 0,
78
+ background: highlighted
79
+ ? theme.colors.highlightedSurface
80
+ : 'transparent',
81
+ marginTop: theme.space.xxsmall,
82
+ marginBottom: theme.space.xxsmall,
83
+ display: 'inline-flex',
84
+ };
85
+ const data = {
86
+ id,
87
+ name,
88
+ target,
89
+ meta,
90
+ style: highlightStyle,
91
+ };
92
+
93
+ Events.emit(emitter, normalizeEventName('mention-apply'), data);
94
+ })}
95
+ </View>
96
+ );
97
+ };
98
+
99
+ export default MentionList;
@@ -0,0 +1,88 @@
1
+ import React, { useMemo } from 'react';
2
+ import type { ComponentType, ReactElement, Ref } from 'react';
3
+ import type { StyleProp, ViewStyle } from 'react-native';
4
+
5
+ import { plainSerializer } from 'hero-editor';
6
+ import TextInput from '../TextInput';
7
+ import RichTextEditorInput from './RichTextEditorInput';
8
+ import type { EditorValue, TextUnit } from './types';
9
+ import { StyledWrapper } from './StyledRichTextEditor';
10
+
11
+ export interface RichTextEditorRef {
12
+ requestBlur: VoidFunction;
13
+ insertNodes: (nodes: Record<string, unknown>[]) => void;
14
+ deleteBackward: (unit?: TextUnit) => void;
15
+ setReadOnly: (readOnly: boolean) => void;
16
+ }
17
+
18
+ export interface RichTextEditorProps {
19
+ autoFocus?: boolean;
20
+ error?: string;
21
+ value?: EditorValue;
22
+ name: string;
23
+ onChange: (data: EditorValue) => void;
24
+ onCursorChange?: (params: { position: { top: number } }) => void;
25
+ placeholder?: string;
26
+ style?: StyleProp<ViewStyle>;
27
+ label: string;
28
+ helpText?: string;
29
+ required?: boolean;
30
+ testID?: string;
31
+ forwardedRef?: Ref<RichTextEditorRef>;
32
+ }
33
+
34
+ const defaultValue: EditorValue = [
35
+ {
36
+ type: 'paragraph',
37
+ children: [{ text: '' }],
38
+ },
39
+ ];
40
+
41
+ const RichTextEditor: ComponentType<RichTextEditorProps> = ({
42
+ autoFocus = true,
43
+ name,
44
+ placeholder = '',
45
+ onChange,
46
+ onCursorChange,
47
+ error = '',
48
+ style = {},
49
+ label,
50
+ helpText,
51
+ required,
52
+ testID,
53
+ forwardedRef,
54
+ value = defaultValue,
55
+ }: RichTextEditorProps): ReactElement => {
56
+ const plain = useMemo(() => plainSerializer(value), [value]);
57
+
58
+ return (
59
+ <StyledWrapper>
60
+ <TextInput
61
+ autoFocus={autoFocus}
62
+ error={error}
63
+ label={label}
64
+ required={required}
65
+ testID={testID}
66
+ placeholder={placeholder}
67
+ value={plain}
68
+ style={style}
69
+ helpText={helpText}
70
+ renderInputValue={(inputProps, ref) => (
71
+ <RichTextEditorInput
72
+ {...inputProps}
73
+ ref={ref}
74
+ value={value}
75
+ autoFocus={autoFocus}
76
+ name={name}
77
+ onChange={onChange}
78
+ onCursorChange={onCursorChange}
79
+ forwardedRef={forwardedRef}
80
+ placeholder={placeholder}
81
+ />
82
+ )}
83
+ />
84
+ </StyledWrapper>
85
+ );
86
+ };
87
+
88
+ export default RichTextEditor;
@@ -0,0 +1,292 @@
1
+ import React, {
2
+ forwardRef,
3
+ useCallback,
4
+ useEffect,
5
+ useImperativeHandle,
6
+ useLayoutEffect,
7
+ useMemo,
8
+ useRef,
9
+ useState,
10
+ } from 'react';
11
+ import type { Ref } from 'react';
12
+ import { WebView } from 'react-native-webview';
13
+
14
+ import { View } from 'react-native';
15
+ import { isAndroid } from '../../utils/helpers';
16
+ import { emitter } from './EditorEvent';
17
+ import * as Events from './utils/events';
18
+ import heroEditorApp from './heroEditorApp';
19
+ import { ToolbarEvents } from './constants';
20
+ import type { EditorValue, RichTextEditorRef, TextUnit } from './types';
21
+ import {
22
+ postMessage,
23
+ requestBlurEditor,
24
+ requestFocusEditor,
25
+ } from './utils/rnWebView';
26
+ import type { WebViewEventMessage } from './utils/rnWebView';
27
+ import { useTheme } from '../../theme';
28
+ import { StyledWebView } from './StyledRichTextEditor';
29
+ import { TextInputRef } from '../TextInput/types';
30
+
31
+ const RichTextEditorInput = forwardRef<
32
+ TextInputRef,
33
+ {
34
+ value: EditorValue;
35
+ name: string;
36
+ autoFocus: boolean;
37
+ placeholder: string;
38
+ onChange: (data: EditorValue) => void;
39
+ onCursorChange?: (params: { position: { top: number } }) => void;
40
+ forwardedRef?: Ref<RichTextEditorRef>;
41
+ onFocus?: () => void;
42
+ onBlur?: () => void;
43
+ }
44
+ >(
45
+ (
46
+ {
47
+ value,
48
+ name,
49
+ autoFocus,
50
+ placeholder,
51
+ onChange,
52
+ onCursorChange,
53
+ forwardedRef,
54
+ onFocus,
55
+ onBlur,
56
+ },
57
+ ref
58
+ ) => {
59
+ const theme = useTheme();
60
+ const webview = useRef<WebView | null>(null);
61
+ const [isFocused, setIsFocused] = useState(false);
62
+ const [webviewHeight, setWebviewHeight] = useState<number | undefined>(24);
63
+ const initialValueRef = useRef(value);
64
+
65
+ const normalizeEventName = (event: string) => `${name}/${event}`;
66
+ const postMessageToWebview = useCallback((message: WebViewEventMessage) => {
67
+ if (webview && webview.current) {
68
+ postMessage(webview.current, message);
69
+ }
70
+ }, []);
71
+
72
+ useEffect(() => {
73
+ const removeFocusListener = Events.on(
74
+ emitter,
75
+ normalizeEventName('editor-focus'),
76
+ () => {
77
+ onFocus?.();
78
+ setIsFocused(true);
79
+ }
80
+ );
81
+
82
+ const removeBlurListener = Events.on(
83
+ emitter,
84
+ normalizeEventName('editor-blur'),
85
+ () => {
86
+ setIsFocused(false);
87
+ onBlur?.();
88
+ }
89
+ );
90
+
91
+ return () => {
92
+ removeFocusListener();
93
+ removeBlurListener();
94
+ };
95
+ }, [name, onFocus, onBlur]);
96
+
97
+ const html = useMemo(() => {
98
+ const initialValueString = JSON.stringify(initialValueRef.current);
99
+
100
+ return `
101
+ <!DOCTYPE html>
102
+ <html>
103
+ <head>
104
+ <meta charset="utf-8">
105
+ <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0">
106
+ <style>
107
+ body {
108
+ margin: 0;
109
+ }
110
+ </style>
111
+ </head>
112
+ <body>
113
+ <div id="root"></div>
114
+ <script>
115
+ window.__editorConfigs = {
116
+ placeholder: "${placeholder}",
117
+ initialValue: ${initialValueString},
118
+ isAndroid: ${isAndroid ? 'true' : 'false'},
119
+ autoFocus: ${autoFocus},
120
+ style: {
121
+ padding: '0 !important',
122
+ fontSize: ${theme.__hd__.richTextEditor.fontSizes.editor},
123
+ color: '${theme.__hd__.richTextEditor.colors.text}'
124
+ }
125
+ };
126
+ ${heroEditorApp}
127
+ </script>
128
+ </body>
129
+ </html>
130
+ `;
131
+ }, [placeholder, autoFocus, theme]);
132
+
133
+ const requestBlur = useCallback(() => {
134
+ if (webview.current && isFocused) {
135
+ requestBlurEditor(webview.current);
136
+ }
137
+ }, [isFocused]);
138
+
139
+ const insertNodes = useCallback((nodes: Record<string, unknown>[]) => {
140
+ postMessageToWebview({
141
+ type: '@hero-editor/webview/editor-insert-nodes',
142
+ data: { nodes },
143
+ });
144
+ }, []);
145
+
146
+ const deleteBackward = useCallback((unit?: TextUnit) => {
147
+ postMessageToWebview({
148
+ type: '@hero-editor/webview/editor-delete-backward',
149
+ data: { unit },
150
+ });
151
+ }, []);
152
+
153
+ const setReadOnly = useCallback((readOnly: boolean) => {
154
+ postMessageToWebview({
155
+ type: '@hero-editor/webview/editor-read-only',
156
+ data: { readOnly },
157
+ });
158
+ }, []);
159
+
160
+ useImperativeHandle(
161
+ forwardedRef,
162
+ () => ({ requestBlur, insertNodes, deleteBackward, setReadOnly }),
163
+ [requestBlur, deleteBackward, insertNodes, setReadOnly]
164
+ );
165
+
166
+ useLayoutEffect(() => {
167
+ const toolbarEventListenerRemovers = Object.values(ToolbarEvents).map(
168
+ (eventName) =>
169
+ Events.on(emitter, normalizeEventName(eventName), (data) => {
170
+ postMessageToWebview({
171
+ type: `@hero-editor/webview/${eventName}`,
172
+ data,
173
+ });
174
+ })
175
+ );
176
+
177
+ const removeMentionApplyListener = Events.on(
178
+ emitter,
179
+ normalizeEventName('mention-apply'),
180
+ (data) =>
181
+ postMessageToWebview({
182
+ type: '@hero-editor/webview/mention-apply',
183
+ data,
184
+ })
185
+ );
186
+
187
+ return () => {
188
+ removeMentionApplyListener();
189
+ toolbarEventListenerRemovers.forEach((remover) => remover());
190
+ };
191
+ }, [name]);
192
+
193
+ const handleEditorLayoutEvent = useCallback(
194
+ (messageData: Record<string, unknown>) => {
195
+ const editorLayout = messageData
196
+ ? {
197
+ width: Number(messageData.width),
198
+ height: Number(messageData.height),
199
+ }
200
+ : undefined;
201
+
202
+ if (editorLayout) {
203
+ setWebviewHeight(editorLayout.height);
204
+ }
205
+ },
206
+ []
207
+ );
208
+
209
+ const onMessage = useCallback(
210
+ (event?: { nativeEvent?: { data?: string } }) => {
211
+ const message = event?.nativeEvent?.data
212
+ ? JSON.parse(event?.nativeEvent?.data)
213
+ : undefined;
214
+
215
+ const messageType = message?.type;
216
+ const messageData = message?.data;
217
+
218
+ switch (messageType) {
219
+ case '@hero-editor/webview/editor-focus':
220
+ Events.emit(emitter, normalizeEventName('editor-focus'), undefined);
221
+ break;
222
+ case '@hero-editor/webview/editor-blur':
223
+ Events.emit(emitter, normalizeEventName('editor-blur'), undefined);
224
+ break;
225
+ case '@hero-editor/webview/mention-search':
226
+ Events.emit(
227
+ emitter,
228
+ normalizeEventName('mention-search'),
229
+ messageData
230
+ );
231
+ break;
232
+ case '@hero-editor/webview/editor-change':
233
+ if (messageData) {
234
+ onChange(messageData.value);
235
+ }
236
+ break;
237
+ case '@hero-editor/webview/cursor-change':
238
+ onCursorChange?.(messageData);
239
+ break;
240
+ case '@hero-editor/webview/editor-layout':
241
+ handleEditorLayoutEvent(messageData);
242
+ break;
243
+ default:
244
+ break;
245
+ }
246
+ },
247
+ [name, onChange, onCursorChange, handleEditorLayoutEvent]
248
+ );
249
+
250
+ useImperativeHandle(
251
+ ref,
252
+ () => ({
253
+ focus: () => {
254
+ if (webview.current) {
255
+ requestFocusEditor(webview.current);
256
+ }
257
+ },
258
+ isFocused: () => isFocused,
259
+ clear: () => {
260
+ // Not applicable for WebView, but required by the interface
261
+ },
262
+ blur: () => {
263
+ if (webview.current) {
264
+ requestBlurEditor(webview.current);
265
+ }
266
+ },
267
+ }),
268
+ [isFocused]
269
+ );
270
+
271
+ return (
272
+ <View
273
+ style={{
274
+ height: webviewHeight,
275
+ }}
276
+ >
277
+ <StyledWebView
278
+ ref={webview}
279
+ testID="webview"
280
+ originWhitelist={['*']}
281
+ source={{ html }}
282
+ onMessage={onMessage}
283
+ scrollEnabled={false}
284
+ hideKeyboardAccessoryView
285
+ keyboardDisplayRequiresUserAction={false}
286
+ />
287
+ </View>
288
+ );
289
+ }
290
+ );
291
+
292
+ export default RichTextEditorInput;
@@ -0,0 +1,15 @@
1
+ import { styled } from '@hero-design/rn';
2
+ import { View } from 'react-native';
3
+ import { WebView } from 'react-native-webview';
4
+
5
+ export const StyledWrapper = styled(View)(({ theme }) => ({
6
+ marginBottom: theme.__hd__.richTextEditor.space.wrapperMarginBottom,
7
+ }));
8
+
9
+ export const StyledWebView = styled(WebView)(({ theme }) => ({
10
+ minHeight: theme.__hd__.richTextEditor.sizes.editorMinHeight,
11
+ backgroundColor: 'transparent',
12
+ textAlignVertical: 'center',
13
+ fontSize: theme.__hd__.textInput.fontSizes.text,
14
+ marginHorizontal: theme.__hd__.textInput.space.inputHorizontalMargin,
15
+ }));