@hero-design/rn 8.109.2 → 8.110.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.
- package/.turbo/turbo-build.log +5 -5
- package/CHANGELOG.md +6 -0
- package/es/index.js +2115 -1946
- package/lib/index.js +2115 -1946
- package/package.json +2 -2
- package/src/components/RichTextEditor/BaseRichTextEditor.tsx +17 -0
- package/src/components/RichTextEditor/EditorToolbar.tsx +18 -59
- package/src/components/RichTextEditor/__tests__/EditorToolbar.spec.tsx +31 -10
- package/src/components/RichTextEditor/__tests__/RichTextEditor.spec.tsx +22 -0
- package/src/components/RichTextEditor/__tests__/__snapshots__/RichTextEditor.spec.tsx.snap +8 -0
- package/src/components/RichTextEditor/constants.ts +4 -0
- package/src/components/RichTextEditor/hooks/useRichTextEditorEvents.ts +22 -3
- package/stats/8.109.2/rn-stats.html +1 -3
- package/stats/8.110.0/rn-stats.html +4844 -0
- package/types/components/RichTextEditor/constants.d.ts +6 -2
- package/types/components/RichTextEditor/hooks/useRichTextEditorEvents.d.ts +17 -2
- package/types/components/RichTextEditor/index.d.ts +54 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hero-design/rn",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.110.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"module": "es/index.js",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"@hero-design/colors": "8.46.2",
|
|
26
26
|
"d3": "^7.8.5",
|
|
27
27
|
"date-fns": "^2.30.0",
|
|
28
|
-
"hero-editor": "^1.
|
|
28
|
+
"hero-editor": "^1.17.0",
|
|
29
29
|
"nanoid": "^5.0.9"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
@@ -132,6 +132,9 @@ const BaseRichTextEditor = ({
|
|
|
132
132
|
body {
|
|
133
133
|
margin: 0;
|
|
134
134
|
}
|
|
135
|
+
a {
|
|
136
|
+
color: ${theme.__hd__.typography.colors.primary};
|
|
137
|
+
}
|
|
135
138
|
</style>
|
|
136
139
|
</head>
|
|
137
140
|
<body>
|
|
@@ -263,6 +266,11 @@ const BaseRichTextEditor = ({
|
|
|
263
266
|
case '@hero-editor/webview/editor-change':
|
|
264
267
|
if (messageData) {
|
|
265
268
|
onChange(messageData.value);
|
|
269
|
+
Events.emit(
|
|
270
|
+
emitter,
|
|
271
|
+
normalizeEventName('editor-change'),
|
|
272
|
+
messageData
|
|
273
|
+
);
|
|
266
274
|
}
|
|
267
275
|
|
|
268
276
|
break;
|
|
@@ -274,6 +282,14 @@ const BaseRichTextEditor = ({
|
|
|
274
282
|
handleEditorLayoutEvent(messageData);
|
|
275
283
|
break;
|
|
276
284
|
|
|
285
|
+
case '@hero-editor/webview/request-upsert-link':
|
|
286
|
+
Events.emit(
|
|
287
|
+
emitter,
|
|
288
|
+
normalizeEventName('request-upsert-link'),
|
|
289
|
+
messageData
|
|
290
|
+
);
|
|
291
|
+
break;
|
|
292
|
+
|
|
277
293
|
default:
|
|
278
294
|
break;
|
|
279
295
|
}
|
|
@@ -291,6 +307,7 @@ const BaseRichTextEditor = ({
|
|
|
291
307
|
hideKeyboardAccessoryView
|
|
292
308
|
ref={webview}
|
|
293
309
|
style={style}
|
|
310
|
+
bounces={false}
|
|
294
311
|
testID={testID}
|
|
295
312
|
source={{ html }}
|
|
296
313
|
onMessage={onMessage}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import type { ComponentType } from 'react';
|
|
3
3
|
import Icon from '../Icon';
|
|
4
4
|
import { EditorEvents, ToolbarEvents } from './constants';
|
|
@@ -96,12 +96,6 @@ export interface EditorToolbarProps {
|
|
|
96
96
|
testID?: string;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
type ButtonType = {
|
|
100
|
-
id: number;
|
|
101
|
-
buttonName: ToolbarButtonName;
|
|
102
|
-
selected: boolean;
|
|
103
|
-
};
|
|
104
|
-
|
|
105
99
|
const EditorToolbar = ({
|
|
106
100
|
name,
|
|
107
101
|
buttons = defaultButtons,
|
|
@@ -109,19 +103,10 @@ const EditorToolbar = ({
|
|
|
109
103
|
}: EditorToolbarProps) => {
|
|
110
104
|
const [show, setShow] = useState(false);
|
|
111
105
|
|
|
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
106
|
const { emitEvent, subscribeToEvents } = useRichTextEditorEvents(name);
|
|
107
|
+
const [toolbarButtonState, setToolbarButtonState] = useState<
|
|
108
|
+
Record<string, boolean>
|
|
109
|
+
>({});
|
|
125
110
|
|
|
126
111
|
useEffect(() => {
|
|
127
112
|
const removeFocusListener = subscribeToEvents(
|
|
@@ -131,72 +116,46 @@ const EditorToolbar = ({
|
|
|
131
116
|
const removeBlurListener = subscribeToEvents(EditorEvents.EditorBlur, () =>
|
|
132
117
|
setShow(false)
|
|
133
118
|
);
|
|
119
|
+
const removeEditorChangeListener = subscribeToEvents(
|
|
120
|
+
EditorEvents.EditorChange,
|
|
121
|
+
({ toolbarState = {} }) => {
|
|
122
|
+
setToolbarButtonState(toolbarState);
|
|
123
|
+
}
|
|
124
|
+
);
|
|
134
125
|
|
|
135
126
|
return () => {
|
|
136
127
|
removeFocusListener();
|
|
137
128
|
removeBlurListener();
|
|
129
|
+
removeEditorChangeListener();
|
|
138
130
|
};
|
|
139
131
|
}, []);
|
|
140
132
|
|
|
141
|
-
const toggleToolbarButton = useCallback(
|
|
142
|
-
({ buttonName: currentButtonName, selected: prevSelected }: ButtonType) => {
|
|
143
|
-
const currentButtonConfig = buttonConfigs[currentButtonName];
|
|
144
|
-
const isStandalone =
|
|
145
|
-
currentButtonConfig && currentButtonConfig.standalone;
|
|
146
|
-
|
|
147
|
-
setToolbarButtonArray((prevState) =>
|
|
148
|
-
prevState.map((updatingButton) => {
|
|
149
|
-
if (updatingButton.buttonName === currentButtonName) {
|
|
150
|
-
return {
|
|
151
|
-
...updatingButton,
|
|
152
|
-
selected: !prevSelected,
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const updatingButtonConfig = buttonConfigs[updatingButton.buttonName];
|
|
157
|
-
const shouldToggleOff =
|
|
158
|
-
!prevSelected &&
|
|
159
|
-
isStandalone &&
|
|
160
|
-
updatingButtonConfig &&
|
|
161
|
-
updatingButtonConfig.standalone;
|
|
162
|
-
|
|
163
|
-
return {
|
|
164
|
-
...updatingButton,
|
|
165
|
-
selected: shouldToggleOff ? false : updatingButton.selected,
|
|
166
|
-
};
|
|
167
|
-
})
|
|
168
|
-
);
|
|
169
|
-
},
|
|
170
|
-
[]
|
|
171
|
-
);
|
|
172
|
-
|
|
173
133
|
const toolbarButtons = useMemo(
|
|
174
134
|
() =>
|
|
175
|
-
|
|
176
|
-
if (button
|
|
177
|
-
return <StyledSeparator key={
|
|
135
|
+
buttons.map((button, index) => {
|
|
136
|
+
if (button === '|') {
|
|
137
|
+
return <StyledSeparator key={index} />;
|
|
178
138
|
}
|
|
179
|
-
const config = buttonConfigs[button
|
|
139
|
+
const config = buttonConfigs[button];
|
|
180
140
|
if (config) {
|
|
181
141
|
return (
|
|
182
142
|
<ToolbarButton
|
|
183
|
-
key={button
|
|
143
|
+
key={button}
|
|
184
144
|
testID={config.icon}
|
|
185
145
|
icon={config.icon}
|
|
186
146
|
onPress={() => {
|
|
187
|
-
toggleToolbarButton(button);
|
|
188
147
|
emitEvent({
|
|
189
148
|
type: config.eventName,
|
|
190
149
|
data: null,
|
|
191
150
|
});
|
|
192
151
|
}}
|
|
193
|
-
selected={
|
|
152
|
+
selected={toolbarButtonState[config.eventName]}
|
|
194
153
|
/>
|
|
195
154
|
);
|
|
196
155
|
}
|
|
197
156
|
return null;
|
|
198
157
|
}),
|
|
199
|
-
[
|
|
158
|
+
[toolbarButtonState, buttons]
|
|
200
159
|
);
|
|
201
160
|
|
|
202
161
|
if (show) {
|
|
@@ -3,7 +3,7 @@ import { act, fireEvent, waitFor } from '@testing-library/react-native';
|
|
|
3
3
|
import type { RenderAPI } from '@testing-library/react-native';
|
|
4
4
|
import renderWithTheme from '../../../testHelpers/renderWithTheme';
|
|
5
5
|
import EditorToolbar from '../EditorToolbar';
|
|
6
|
-
import { emitter as
|
|
6
|
+
import { emitter as editorEventEmitter } from '../EditorEvent';
|
|
7
7
|
import { theme } from '../../../index';
|
|
8
8
|
import * as Events from '../utils/events';
|
|
9
9
|
|
|
@@ -24,7 +24,7 @@ describe('EditorToolbar', () => {
|
|
|
24
24
|
<EditorToolbar name="toolbar" testID="toolbar" />
|
|
25
25
|
);
|
|
26
26
|
act(() => {
|
|
27
|
-
|
|
27
|
+
editorEventEmitter.emit('toolbar/editor-focus');
|
|
28
28
|
});
|
|
29
29
|
await waitFor(() => wrapper.getByTestId('toolbar'));
|
|
30
30
|
});
|
|
@@ -47,7 +47,7 @@ describe('EditorToolbar', () => {
|
|
|
47
47
|
|
|
48
48
|
it('should hide toolbar when blur', async () => {
|
|
49
49
|
act(() => {
|
|
50
|
-
|
|
50
|
+
editorEventEmitter.emit('toolbar/editor-blur');
|
|
51
51
|
});
|
|
52
52
|
await waitFor(() => wrapper.queryAllByTestId('toolbar').length === 0);
|
|
53
53
|
expect(wrapper.queryAllByTestId('toolbar')).toHaveLength(0);
|
|
@@ -55,7 +55,7 @@ describe('EditorToolbar', () => {
|
|
|
55
55
|
|
|
56
56
|
describe("should change button's background color when pressing", () => {
|
|
57
57
|
it('should send event and highlight buttons correctly', () => {
|
|
58
|
-
const
|
|
58
|
+
const emittedEvents: string[] = [];
|
|
59
59
|
|
|
60
60
|
const eventNameAndTestIDArray = [
|
|
61
61
|
{
|
|
@@ -89,8 +89,8 @@ describe('EditorToolbar', () => {
|
|
|
89
89
|
];
|
|
90
90
|
|
|
91
91
|
eventNameAndTestIDArray.forEach(({ eventName, testID }) => {
|
|
92
|
-
Events.on(
|
|
93
|
-
|
|
92
|
+
Events.on(editorEventEmitter, eventName, () => {
|
|
93
|
+
emittedEvents.push(testID);
|
|
94
94
|
});
|
|
95
95
|
|
|
96
96
|
expect(wrapper.getByTestId(testID)).toHaveStyle({
|
|
@@ -98,9 +98,6 @@ describe('EditorToolbar', () => {
|
|
|
98
98
|
});
|
|
99
99
|
|
|
100
100
|
fireEvent(wrapper.getByTestId(testID), 'press');
|
|
101
|
-
expect(wrapper.getByTestId(testID)).toHaveStyle({
|
|
102
|
-
backgroundColor: theme.colors.highlightedSurface,
|
|
103
|
-
});
|
|
104
101
|
});
|
|
105
102
|
|
|
106
103
|
const standaloneButtonTestIDs = [
|
|
@@ -114,7 +111,7 @@ describe('EditorToolbar', () => {
|
|
|
114
111
|
});
|
|
115
112
|
});
|
|
116
113
|
|
|
117
|
-
expect(
|
|
114
|
+
expect(emittedEvents).toMatchObject([
|
|
118
115
|
'format-bold',
|
|
119
116
|
'format-italic',
|
|
120
117
|
'format-underlined',
|
|
@@ -124,6 +121,30 @@ describe('EditorToolbar', () => {
|
|
|
124
121
|
'format-heading2',
|
|
125
122
|
]);
|
|
126
123
|
});
|
|
124
|
+
|
|
125
|
+
it('should highlight buttons correctly', () => {
|
|
126
|
+
act(() => {
|
|
127
|
+
editorEventEmitter.emit('toolbar/editor-change', {
|
|
128
|
+
toolbarState: {
|
|
129
|
+
bold: true,
|
|
130
|
+
italic: true,
|
|
131
|
+
underline: true,
|
|
132
|
+
'heading-one': true,
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
[
|
|
138
|
+
'format-bold',
|
|
139
|
+
'format-italic',
|
|
140
|
+
'format-underlined',
|
|
141
|
+
'format-heading1',
|
|
142
|
+
].forEach((testID) => {
|
|
143
|
+
expect(wrapper.getByTestId(testID)).toHaveStyle({
|
|
144
|
+
backgroundColor: theme.colors.highlightedSurface,
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
});
|
|
127
148
|
});
|
|
128
149
|
});
|
|
129
150
|
});
|
|
@@ -206,6 +206,28 @@ describe('RichTextEditor', () => {
|
|
|
206
206
|
});
|
|
207
207
|
});
|
|
208
208
|
});
|
|
209
|
+
|
|
210
|
+
describe('received event request-upsert-link', () => {
|
|
211
|
+
it('should emit correct data', () => {
|
|
212
|
+
const emittedEvents: string[] = [];
|
|
213
|
+
Events.on(
|
|
214
|
+
EditorEventEmitter,
|
|
215
|
+
'rich-text-editor/request-upsert-link',
|
|
216
|
+
() => {
|
|
217
|
+
emittedEvents.push('called');
|
|
218
|
+
}
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
act(() => {
|
|
222
|
+
onMessageOfLatestRendering({
|
|
223
|
+
nativeEvent: {
|
|
224
|
+
data: '{"type": "@hero-editor/webview/request-upsert-link", "data" : {}}',
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
expect(emittedEvents).toMatchObject(['called']);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
209
231
|
});
|
|
210
232
|
|
|
211
233
|
describe('RichTextEditorRef methods', () => {
|
|
@@ -156,6 +156,7 @@ exports[`RichTextEditor onMessage received event editor-layout should update hei
|
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
158
|
accessible={true}
|
|
159
|
+
bounces={false}
|
|
159
160
|
focusable={true}
|
|
160
161
|
hideKeyboardAccessoryView={true}
|
|
161
162
|
keyboardDisplayRequiresUserAction={false}
|
|
@@ -185,6 +186,9 @@ exports[`RichTextEditor onMessage received event editor-layout should update hei
|
|
|
185
186
|
body {
|
|
186
187
|
margin: 0;
|
|
187
188
|
}
|
|
189
|
+
a {
|
|
190
|
+
color: #401960;
|
|
191
|
+
}
|
|
188
192
|
</style>
|
|
189
193
|
</head>
|
|
190
194
|
<body>
|
|
@@ -494,6 +498,7 @@ exports[`RichTextEditor should render correctly 1`] = `
|
|
|
494
498
|
}
|
|
495
499
|
}
|
|
496
500
|
accessible={true}
|
|
501
|
+
bounces={false}
|
|
497
502
|
focusable={true}
|
|
498
503
|
hideKeyboardAccessoryView={true}
|
|
499
504
|
keyboardDisplayRequiresUserAction={false}
|
|
@@ -523,6 +528,9 @@ exports[`RichTextEditor should render correctly 1`] = `
|
|
|
523
528
|
body {
|
|
524
529
|
margin: 0;
|
|
525
530
|
}
|
|
531
|
+
a {
|
|
532
|
+
color: #401960;
|
|
533
|
+
}
|
|
526
534
|
</style>
|
|
527
535
|
</head>
|
|
528
536
|
<body>
|
|
@@ -6,9 +6,13 @@ export enum ToolbarEvents {
|
|
|
6
6
|
NumberedList = 'numbered-list',
|
|
7
7
|
HeadingOne = 'heading-one',
|
|
8
8
|
HeadingTwo = 'heading-two',
|
|
9
|
+
AddLink = 'add-link',
|
|
10
|
+
ApplyLink = 'link',
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
export enum EditorEvents {
|
|
12
14
|
EditorFocus = 'editor-focus',
|
|
13
15
|
EditorBlur = 'editor-blur',
|
|
16
|
+
EditorChange = 'editor-change',
|
|
17
|
+
UpsertLink = 'request-upsert-link',
|
|
14
18
|
}
|
|
@@ -6,6 +6,23 @@ import type { EditorEvents, ToolbarEvents } from '../constants';
|
|
|
6
6
|
|
|
7
7
|
type SubscribableEvent = ToolbarEvents | EditorEvents;
|
|
8
8
|
|
|
9
|
+
type EventData = {
|
|
10
|
+
[EditorEvents.UpsertLink]: {
|
|
11
|
+
text: string;
|
|
12
|
+
url: string;
|
|
13
|
+
};
|
|
14
|
+
[EditorEvents.EditorChange]: {
|
|
15
|
+
value: string;
|
|
16
|
+
toolbarState: Record<string, boolean>;
|
|
17
|
+
};
|
|
18
|
+
[ToolbarEvents.ApplyLink]: {
|
|
19
|
+
text: string;
|
|
20
|
+
url: string;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type EventDataByEventName<T> = T extends keyof EventData ? EventData[T] : void;
|
|
25
|
+
|
|
9
26
|
/**
|
|
10
27
|
* Hook to subscribe to events for a rich text editor
|
|
11
28
|
* @param editorName - The name of the editor to subscribe to events for
|
|
@@ -26,12 +43,12 @@ const useRichTextEditorEvents = (editorName: string) => {
|
|
|
26
43
|
const subscribeToEvents = useCallback(
|
|
27
44
|
<TEventName extends SubscribableEvent>(
|
|
28
45
|
eventName: TEventName,
|
|
29
|
-
onEvent: (data:
|
|
46
|
+
onEvent: (data: EventDataByEventName<TEventName>) => void
|
|
30
47
|
) =>
|
|
31
48
|
Events.on(
|
|
32
49
|
emitter,
|
|
33
50
|
normalizeEventName(eventName),
|
|
34
|
-
(eventData:
|
|
51
|
+
(eventData: EventDataByEventName<TEventName>) => {
|
|
35
52
|
onEvent(eventData);
|
|
36
53
|
}
|
|
37
54
|
),
|
|
@@ -44,7 +61,9 @@ const useRichTextEditorEvents = (editorName: string) => {
|
|
|
44
61
|
data,
|
|
45
62
|
}: {
|
|
46
63
|
type: TEventName;
|
|
47
|
-
data:
|
|
64
|
+
data: TEventName extends keyof EventData
|
|
65
|
+
? EventData[TEventName]
|
|
66
|
+
: unknown;
|
|
48
67
|
}) => {
|
|
49
68
|
Events.emit(emitter, normalizeEventName(type), data);
|
|
50
69
|
},
|