@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hero-design/rn",
3
- "version": "8.109.2",
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.16.0",
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, { useCallback, useEffect, useMemo, useState } from '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
- toolbarButtonArray.map((button) => {
176
- if (button.buttonName === '|') {
177
- return <StyledSeparator key={button.id} />;
135
+ buttons.map((button, index) => {
136
+ if (button === '|') {
137
+ return <StyledSeparator key={index} />;
178
138
  }
179
- const config = buttonConfigs[button.buttonName];
139
+ const config = buttonConfigs[button];
180
140
  if (config) {
181
141
  return (
182
142
  <ToolbarButton
183
- key={button.id}
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={button.selected}
152
+ selected={toolbarButtonState[config.eventName]}
194
153
  />
195
154
  );
196
155
  }
197
156
  return null;
198
157
  }),
199
- [toolbarButtonArray]
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 editorEventEmmitor } from '../EditorEvent';
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
- editorEventEmmitor.emit('toolbar/editor-focus');
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
- editorEventEmmitor.emit('toolbar/editor-blur');
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 emmitedEvents: string[] = [];
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(editorEventEmmitor, eventName, () => {
93
- emmitedEvents.push(testID);
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(emmitedEvents).toMatchObject([
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: unknown) => void
46
+ onEvent: (data: EventDataByEventName<TEventName>) => void
30
47
  ) =>
31
48
  Events.on(
32
49
  emitter,
33
50
  normalizeEventName(eventName),
34
- (eventData: unknown) => {
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: unknown;
64
+ data: TEventName extends keyof EventData
65
+ ? EventData[TEventName]
66
+ : unknown;
48
67
  }) => {
49
68
  Events.emit(emitter, normalizeEventName(type), data);
50
69
  },