@apollohg/react-native-rich-text-editor 2.0.0 → 2.0.1
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/README.md +1 -1
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/android/build.gradle +3 -0
- package/app.plugin.js +42 -39
- package/dist/AtomHost.js +18 -11
- package/dist/EditorAddon.js +4 -2
- package/dist/EditorAddonNormalization.js +17 -9
- package/dist/EditorMentionThemeNormalization.js +6 -3
- package/dist/EditorMentionThemeValidation.js +28 -11
- package/dist/EditorStyleSheetNormalization.js +84 -37
- package/dist/EditorTheme.d.ts +4 -0
- package/dist/EditorTheme.js +5 -3
- package/dist/EditorToolbarComponent.d.ts +1 -1
- package/dist/EditorToolbarComponent.js +1 -1
- package/dist/EditorToolbarRegistry.js +5 -5
- package/dist/EditorToolbarTypes.d.ts +12 -12
- package/dist/EditorToolbarVisuals.js +1 -1
- package/dist/ExternalTextComposition.js +89 -43
- package/dist/ImageLoadingPolicy.d.ts +1 -1
- package/dist/NativeEditorBoundaryError.d.ts +7 -7
- package/dist/NativeEditorBoundaryError.js +9 -5
- package/dist/NativeEditorCollaborationTransport.d.ts +7 -7
- package/dist/NativeEditorCollaborationTransport.js +28 -15
- package/dist/NativeEditorCreateConfig.js +11 -6
- package/dist/NativeEditorCreateJson.js +51 -25
- package/dist/NativeEditorCreateValidation.js +11 -6
- package/dist/NativeEditorDocumentBridge.d.ts +1 -1
- package/dist/NativeEditorDocumentBridge.js +32 -18
- package/dist/NativeEditorDocumentHandle.d.ts +4 -4
- package/dist/NativeEditorDocumentHandle.js +1 -1
- package/dist/NativeEditorLocalAwareness.js +25 -13
- package/dist/NativeEditorNativeModule.d.ts +8 -1
- package/dist/NativeEditorNativeModule.js +7 -5
- package/dist/NativeEditorRenderNormalization.js +66 -25
- package/dist/NativeEditorResultNormalization.js +53 -29
- package/dist/NativeEditorTypes.d.ts +10 -10
- package/dist/NativeEditorV2Decimal.js +6 -3
- package/dist/NativeProseViewer.js +11 -3
- package/dist/ResourceLimits.d.ts +2 -2
- package/dist/ResourceLimits.js +7 -2
- package/dist/RichTextEditor.js +1 -1
- package/dist/RichTextEditorNativeTypes.d.ts +1 -1
- package/dist/RichTextEditorNativeView.d.ts +1 -3
- package/dist/RichTextEditorSerialization.js +24 -14
- package/dist/RichTextEditorTypes.d.ts +1 -1
- package/dist/YjsCollaboration.d.ts +7 -7
- package/dist/YjsCollaboration.js +42 -26
- package/dist/addons.d.ts +4 -4
- package/dist/addons.js +3 -3
- package/dist/atomInstances.js +6 -3
- package/dist/atomUpdates.js +7 -4
- package/dist/atoms.js +20 -12
- package/dist/attributeValidation.js +55 -24
- package/dist/contentExpression.js +80 -42
- package/dist/listMarkers.d.ts +1 -1
- package/dist/listMarkers.js +1 -1
- package/dist/schemaDefinition.d.ts +1 -1
- package/dist/schemaDefinition.js +23 -14
- package/dist/schemaDocument.js +1 -1
- package/dist/schemaDocumentConstruction.js +25 -14
- package/dist/schemaNormalization.js +51 -29
- package/dist/schemaPresets.d.ts +1 -1
- package/dist/schemaPresets.js +9 -4
- package/dist/schemaResolution.js +46 -25
- package/dist/specs/PreparedProseViewerNativeComponent.d.ts +4 -0
- package/dist/useEditorToolbarInteractions.d.ts +2 -2
- package/dist/useEditorToolbarInteractions.js +45 -23
- package/dist/useEditorToolbarItems.d.ts +1 -1
- package/dist/useEditorToolbarItems.js +4 -3
- package/dist/useEditorToolbarPresentation.d.ts +3 -3
- package/dist/useEditorToolbarPresentation.js +4 -4
- package/dist/useEditorToolbarState.d.ts +1 -1
- package/dist/useEditorToolbarState.js +1 -1
- package/dist/useFocusPreservingFrames.js +11 -8
- package/dist/useNativeEditor.d.ts +1 -1
- package/dist/useNativeEditor.js +21 -11
- package/dist/useRichTextEditorCommands.d.ts +2 -2
- package/dist/useRichTextEditorCommands.js +59 -29
- package/dist/useRichTextEditorEvents.d.ts +3 -3
- package/dist/useRichTextEditorEvents.js +75 -32
- package/dist/useRichTextEditorMentions.d.ts +4 -4
- package/dist/useRichTextEditorMentions.js +48 -29
- package/dist/useRichTextEditorPresentation.d.ts +4 -4
- package/dist/useRichTextEditorPresentation.js +25 -23
- package/dist/useRichTextEditorState.d.ts +3 -3
- package/dist/useRichTextEditorState.js +20 -15
- package/dist/useRichTextEditorUpdates.d.ts +1 -1
- package/dist/useRichTextEditorUpdates.js +99 -41
- package/dist/useViewerAtoms.js +63 -36
- package/ios/EditorCore.xcframework/Info.plist +5 -5
- package/ios/EditorCore.xcframework/ios-arm64/libeditor_core.a +0 -0
- package/ios/EditorCore.xcframework/ios-arm64_x86_64-simulator/libeditor_core.a +0 -0
- package/package.json +11 -3
- package/react-native.config.js +1 -1
- package/rust/android/arm64-v8a/libeditor_core.so +0 -0
- package/rust/android/armeabi-v7a/libeditor_core.so +0 -0
- package/rust/android/x86/libeditor_core.so +0 -0
- package/rust/android/x86_64/libeditor_core.so +0 -0
- package/src/AtomHost.tsx +58 -28
- package/src/EditorAddon.ts +18 -8
- package/src/EditorAddonNormalization.ts +40 -11
- package/src/EditorMentionThemeNormalization.ts +15 -4
- package/src/EditorMentionThemeValidation.ts +51 -14
- package/src/EditorStyleSheet.ts +1 -0
- package/src/EditorStyleSheetNormalization.ts +195 -64
- package/src/EditorTheme.ts +13 -5
- package/src/EditorToolbarComponent.tsx +2 -1
- package/src/EditorToolbarRegistry.tsx +23 -13
- package/src/EditorToolbarTypes.tsx +12 -12
- package/src/EditorToolbarVisuals.tsx +6 -3
- package/src/EditorUpdateRevision.ts +1 -0
- package/src/ExternalTextComposition.ts +259 -49
- package/src/ImageLoadingPolicy.ts +3 -1
- package/src/NativeEditorBoundaryError.ts +38 -11
- package/src/NativeEditorCollaborationTransport.ts +108 -27
- package/src/NativeEditorCreateConfig.ts +92 -19
- package/src/NativeEditorCreateJson.ts +163 -26
- package/src/NativeEditorCreateValidation.ts +33 -12
- package/src/NativeEditorDocumentBridge.ts +111 -19
- package/src/NativeEditorDocumentHandle.ts +9 -4
- package/src/NativeEditorLocalAwareness.ts +70 -15
- package/src/NativeEditorNativeModule.ts +13 -9
- package/src/NativeEditorRenderNormalization.ts +128 -29
- package/src/NativeEditorResultNormalization.ts +143 -32
- package/src/NativeEditorTypes.ts +12 -11
- package/src/NativeEditorV2Decimal.ts +12 -3
- package/src/NativeProseViewer.tsx +46 -16
- package/src/ResourceLimits.ts +15 -8
- package/src/RichTextEditor.tsx +3 -1
- package/src/RichTextEditorNativeTypes.tsx +1 -1
- package/src/RichTextEditorNativeView.tsx +2 -8
- package/src/RichTextEditorSerialization.tsx +70 -24
- package/src/RichTextEditorTypes.tsx +1 -1
- package/src/YjsCollaboration.ts +143 -47
- package/src/addons.ts +12 -8
- package/src/atomInstances.ts +25 -5
- package/src/atomUpdates.ts +23 -6
- package/src/atoms.ts +85 -18
- package/src/attributeValidation.ts +97 -33
- package/src/contentExpression.ts +218 -63
- package/src/listMarkers.ts +1 -1
- package/src/schemaDefinition.ts +82 -20
- package/src/schemaDocument.ts +15 -1
- package/src/schemaDocumentConstruction.ts +80 -16
- package/src/schemaNormalization.ts +155 -41
- package/src/schemaPresets.ts +37 -31
- package/src/schemaResolution.ts +126 -27
- package/src/specs/PreparedProseViewerNativeComponent.ts +4 -2
- package/src/useEditorToolbarInteractions.tsx +85 -36
- package/src/useEditorToolbarItems.tsx +50 -22
- package/src/useEditorToolbarPresentation.tsx +81 -58
- package/src/useEditorToolbarState.tsx +8 -6
- package/src/useFocusPreservingFrames.ts +43 -15
- package/src/useNativeEditor.ts +111 -51
- package/src/useRichTextEditorCommands.tsx +127 -54
- package/src/useRichTextEditorEvents.tsx +144 -42
- package/src/useRichTextEditorMentions.tsx +118 -37
- package/src/useRichTextEditorPresentation.tsx +128 -114
- package/src/useRichTextEditorState.tsx +76 -40
- package/src/useRichTextEditorUpdates.tsx +207 -56
- package/src/useViewerAtoms.tsx +162 -66
package/dist/useViewerAtoms.js
CHANGED
|
@@ -15,8 +15,9 @@ function isRecord(value) {
|
|
|
15
15
|
}
|
|
16
16
|
function parsePositions(json, definitions, snapshot) {
|
|
17
17
|
const values = JSON.parse(json);
|
|
18
|
-
if (!Array.isArray(values))
|
|
18
|
+
if (!Array.isArray(values)) {
|
|
19
19
|
throw new Error('Atom positions must be an array.');
|
|
20
|
+
}
|
|
20
21
|
const seen = new Set();
|
|
21
22
|
const identities = new Set();
|
|
22
23
|
return values.map((value) => {
|
|
@@ -27,24 +28,27 @@ function parsePositions(json, definitions, snapshot) {
|
|
|
27
28
|
Number(value.docPos) < 0 ||
|
|
28
29
|
Number(value.docPos) > 0xffffffff ||
|
|
29
30
|
typeof value.attrsJson !== 'string' ||
|
|
30
|
-
!['x', 'y', 'width', 'height'].every(
|
|
31
|
+
!['x', 'y', 'width', 'height'].every(key => typeof value[key] === 'number' && Number.isFinite(value[key])) ||
|
|
31
32
|
Number(value.width) <= 0 ||
|
|
32
33
|
Number(value.height) < 0 ||
|
|
33
34
|
seen.has(Number(value.docPos))) {
|
|
34
35
|
throw new Error('Invalid prepared atom position.');
|
|
35
36
|
}
|
|
36
37
|
const attrs = JSON.parse(value.attrsJson);
|
|
37
|
-
if (!isRecord(attrs))
|
|
38
|
+
if (!isRecord(attrs)) {
|
|
38
39
|
throw new Error('Atom attributes must be a JSON object.');
|
|
40
|
+
}
|
|
39
41
|
const idAttribute = definitions.get(value.nodeType)?.idAttribute;
|
|
40
42
|
const atomId = idAttribute == null ? undefined : attrs[idAttribute];
|
|
41
|
-
if (idAttribute != null && (typeof atomId !== 'string' || atomId.length === 0))
|
|
43
|
+
if (idAttribute != null && (typeof atomId !== 'string' || atomId.length === 0)) {
|
|
42
44
|
throw new Error('Atom identity must be a non-empty string.');
|
|
45
|
+
}
|
|
43
46
|
const key = typeof atomId === 'string'
|
|
44
47
|
? JSON.stringify([value.nodeType, atomId])
|
|
45
|
-
: `${snapshot}:${value.docPos}:${value.nodeType}`;
|
|
46
|
-
if (identities.has(key))
|
|
48
|
+
: `${snapshot}:${String(value.docPos)}:${value.nodeType}`;
|
|
49
|
+
if (identities.has(key)) {
|
|
47
50
|
throw new Error('Duplicate atom identity.');
|
|
51
|
+
}
|
|
48
52
|
identities.add(key);
|
|
49
53
|
seen.add(Number(value.docPos));
|
|
50
54
|
return { ...value, attrs, key, atomId };
|
|
@@ -52,18 +56,20 @@ function parsePositions(json, definitions, snapshot) {
|
|
|
52
56
|
}
|
|
53
57
|
function useViewerAtoms({ atoms, identity, themeJson, readOnly, interactive = true, viewport, onUpdateAtomAttrs, onError, }) {
|
|
54
58
|
const [width, setWidth] = (0, react_1.useState)(null);
|
|
55
|
-
const snapshot = (0, react_1.useMemo)(() => String(++nextGeneration), [identity]);
|
|
59
|
+
const snapshot = (0, react_1.useMemo)(() => ({ identity, value: String(++nextGeneration) }), [identity]).value;
|
|
56
60
|
const serializedAtoms = (0, atoms_1.serializeEditorAtoms)(atoms);
|
|
57
|
-
const identifiers = JSON.stringify((atoms ?? []).map(
|
|
58
|
-
const generation = (0, react_1.useMemo)(() =>
|
|
59
|
-
|
|
61
|
+
const identifiers = JSON.stringify((atoms ?? []).map(atom => [atom.name, atom.idAttribute]));
|
|
62
|
+
const generation = (0, react_1.useMemo)(() => ({
|
|
63
|
+
identity, identifiers, serializedAtoms, width, value: String(++nextGeneration),
|
|
64
|
+
}), [identity, identifiers, serializedAtoms, width]).value;
|
|
65
|
+
const definitions = (0, react_1.useMemo)(() => new Map((atoms ?? []).map(atom => [atom.name, atom])), [atoms]);
|
|
60
66
|
const enabled = definitions.size > 0;
|
|
61
67
|
const [measurementState, setMeasurementState] = (0, react_1.useState)({
|
|
62
68
|
generation,
|
|
63
69
|
revision: 0,
|
|
64
70
|
values: {},
|
|
65
71
|
});
|
|
66
|
-
const measurements = measurementState.generation === generation ? measurementState.values : {};
|
|
72
|
+
const measurements = (0, react_1.useMemo)(() => measurementState.generation === generation ? measurementState.values : {}, [measurementState, generation]);
|
|
67
73
|
const revision = String(measurementState.generation === generation ? measurementState.revision : 0);
|
|
68
74
|
const [layout, setLayout] = (0, react_1.useState)(null);
|
|
69
75
|
const pendingLayout = layout?.generation !== generation;
|
|
@@ -72,8 +78,9 @@ function useViewerAtoms({ atoms, identity, themeJson, readOnly, interactive = tr
|
|
|
72
78
|
const mountedMeasurements = (0, react_1.useRef)(new Map());
|
|
73
79
|
(0, react_1.useLayoutEffect)(() => {
|
|
74
80
|
for (const [key, measured] of mountedMeasurements.current) {
|
|
75
|
-
if (!definitions.has(measured.nodeType))
|
|
81
|
+
if (!definitions.has(measured.nodeType)) {
|
|
76
82
|
mountedMeasurements.current.delete(key);
|
|
83
|
+
}
|
|
77
84
|
}
|
|
78
85
|
}, [definitions]);
|
|
79
86
|
const mounted = (0, react_1.useRef)(false);
|
|
@@ -119,7 +126,12 @@ function useViewerAtoms({ atoms, identity, themeJson, readOnly, interactive = tr
|
|
|
119
126
|
measurements,
|
|
120
127
|
},
|
|
121
128
|
})
|
|
122
|
-
: themeJson, [enabled,
|
|
129
|
+
: themeJson, [enabled,
|
|
130
|
+
themeJson,
|
|
131
|
+
serializedAtoms,
|
|
132
|
+
generation,
|
|
133
|
+
revision,
|
|
134
|
+
measurements]);
|
|
123
135
|
const onAtomLayout = (0, react_1.useCallback)((event) => {
|
|
124
136
|
const value = event.nativeEvent;
|
|
125
137
|
const latest = current.current;
|
|
@@ -128,8 +140,9 @@ function useViewerAtoms({ atoms, identity, themeJson, readOnly, interactive = tr
|
|
|
128
140
|
value.revision !== latest.revision ||
|
|
129
141
|
!Number.isFinite(value.layoutWidth) ||
|
|
130
142
|
value.layoutWidth <= 0 ||
|
|
131
|
-
(latest.width != null && Math.abs(latest.width - value.layoutWidth) > 1))
|
|
143
|
+
(latest.width != null && Math.abs(latest.width - value.layoutWidth) > 1)) {
|
|
132
144
|
return;
|
|
145
|
+
}
|
|
133
146
|
try {
|
|
134
147
|
const next = parsePositions(value.atomsJson, latest.definitions, latest.snapshot);
|
|
135
148
|
const retainedMeasurements = {};
|
|
@@ -143,18 +156,19 @@ function useViewerAtoms({ atoms, identity, themeJson, readOnly, interactive = tr
|
|
|
143
156
|
}
|
|
144
157
|
}
|
|
145
158
|
for (const key of mountedMeasurements.current.keys()) {
|
|
146
|
-
if (!retainedKeys.has(key))
|
|
159
|
+
if (!retainedKeys.has(key)) {
|
|
147
160
|
mountedMeasurements.current.delete(key);
|
|
161
|
+
}
|
|
148
162
|
}
|
|
149
163
|
// Unchanged mounted sizes do not emit another Yoga onLayout event.
|
|
150
|
-
setMeasurementState(
|
|
164
|
+
setMeasurementState(previous => previous.generation === value.generation
|
|
151
165
|
? previous
|
|
152
166
|
: {
|
|
153
167
|
generation: value.generation,
|
|
154
168
|
revision: Object.keys(retainedMeasurements).length > 0 ? 1 : 0,
|
|
155
169
|
values: retainedMeasurements,
|
|
156
170
|
});
|
|
157
|
-
setLayout(
|
|
171
|
+
setLayout(previous => previous?.generation === value.generation && previous.json === value.atomsJson
|
|
158
172
|
? previous
|
|
159
173
|
: { generation: value.generation, json: value.atomsJson, positions: next });
|
|
160
174
|
}
|
|
@@ -171,8 +185,9 @@ function useViewerAtoms({ atoms, identity, themeJson, readOnly, interactive = tr
|
|
|
171
185
|
}, []);
|
|
172
186
|
const onContainerLayout = (0, react_1.useCallback)((event) => {
|
|
173
187
|
const next = event.nativeEvent.layout.width;
|
|
174
|
-
if (Number.isFinite(next) && next > 0)
|
|
175
|
-
setWidth(
|
|
188
|
+
if (Number.isFinite(next) && next > 0) {
|
|
189
|
+
setWidth(previous => (previous === next ? previous : next));
|
|
190
|
+
}
|
|
176
191
|
}, []);
|
|
177
192
|
const updateQueues = (0, react_1.useRef)(new Map());
|
|
178
193
|
const acknowledged = (0, react_1.useRef)(new Map());
|
|
@@ -182,14 +197,16 @@ function useViewerAtoms({ atoms, identity, themeJson, readOnly, interactive = tr
|
|
|
182
197
|
const updateAttrs = (0, react_1.useCallback)((owner, atom, update) => {
|
|
183
198
|
const execute = async () => {
|
|
184
199
|
const latest = current.current;
|
|
185
|
-
if (!mounted.current)
|
|
200
|
+
if (!mounted.current) {
|
|
186
201
|
throw new atomInstances_1.AtomUpdateAttrsError('not-ready', 'The viewer is unmounted.');
|
|
202
|
+
}
|
|
187
203
|
if (owner !== latest.generation ||
|
|
188
|
-
!latest.positions.some(
|
|
204
|
+
!latest.positions.some(position => position.key === atom.key && position.attrsJson === atom.attrsJson)) {
|
|
189
205
|
throw new atomInstances_1.AtomUpdateAttrsError('stale-revision', 'The viewer content has changed.');
|
|
190
206
|
}
|
|
191
|
-
if (latest.readOnly || !latest.onUpdateAtomAttrs)
|
|
207
|
+
if (latest.readOnly || !latest.onUpdateAtomAttrs) {
|
|
192
208
|
throw new atomInstances_1.AtomUpdateAttrsError('not-applicable', 'Viewer updates require readOnly={false} and onUpdateAtomAttrs.');
|
|
209
|
+
}
|
|
193
210
|
const accepted = acknowledged.current.get(atom.key);
|
|
194
211
|
const attrs = accepted?.generation === owner ? accepted.attrs : atom.attrs;
|
|
195
212
|
const partial = (0, atomUpdates_1.resolveAtomAttrsUpdate)(attrs, update);
|
|
@@ -198,8 +215,9 @@ function useViewerAtoms({ atoms, identity, themeJson, readOnly, interactive = tr
|
|
|
198
215
|
(0, attributeValidation_1.validateAttributes)({ ...attrs, ...partial }, definition.attrs ?? {});
|
|
199
216
|
if (definition.idAttribute &&
|
|
200
217
|
Object.prototype.hasOwnProperty.call(partial, definition.idAttribute) &&
|
|
201
|
-
partial[definition.idAttribute] !== attrs[definition.idAttribute])
|
|
218
|
+
partial[definition.idAttribute] !== attrs[definition.idAttribute]) {
|
|
202
219
|
throw new Error('An atom identity cannot be changed.');
|
|
220
|
+
}
|
|
203
221
|
}
|
|
204
222
|
catch (error) {
|
|
205
223
|
throw new atomInstances_1.AtomUpdateAttrsError('not-applicable', error instanceof Error ? error.message : String(error));
|
|
@@ -211,18 +229,21 @@ function useViewerAtoms({ atoms, identity, themeJson, readOnly, interactive = tr
|
|
|
211
229
|
attrs,
|
|
212
230
|
partial,
|
|
213
231
|
});
|
|
214
|
-
if (mounted.current && current.current.generation === owner)
|
|
232
|
+
if (mounted.current && current.current.generation === owner) {
|
|
215
233
|
acknowledged.current.set(atom.key, {
|
|
216
234
|
generation: owner,
|
|
217
235
|
attrs: { ...attrs, ...partial },
|
|
218
236
|
});
|
|
237
|
+
}
|
|
219
238
|
};
|
|
220
239
|
const previous = updateQueues.current.get(atom.key);
|
|
221
|
-
const result = previous ? previous.catch(() => {
|
|
240
|
+
const result = previous ? previous.catch(() => {
|
|
241
|
+
}).then(execute) : execute();
|
|
222
242
|
updateQueues.current.set(atom.key, result);
|
|
223
243
|
const clean = () => {
|
|
224
|
-
if (updateQueues.current.get(atom.key) === result)
|
|
244
|
+
if (updateQueues.current.get(atom.key) === result) {
|
|
225
245
|
updateQueues.current.delete(atom.key);
|
|
246
|
+
}
|
|
226
247
|
};
|
|
227
248
|
void result.then(clean, clean);
|
|
228
249
|
return result;
|
|
@@ -238,22 +259,25 @@ function useViewerAtoms({ atoms, identity, themeJson, readOnly, interactive = tr
|
|
|
238
259
|
!Number.isFinite(measured.height) ||
|
|
239
260
|
measured.height < 0 ||
|
|
240
261
|
Math.abs(measured.width - atom.width) > 1 ||
|
|
241
|
-
!latest.renderedPositions.some(
|
|
262
|
+
!latest.renderedPositions.some(position => position.docPos === atom.docPos &&
|
|
242
263
|
position.nodeType === atom.nodeType &&
|
|
243
264
|
position.width === atom.width &&
|
|
244
|
-
position.attrsJson === atom.attrsJson))
|
|
265
|
+
position.attrsJson === atom.attrsJson)) {
|
|
245
266
|
return;
|
|
267
|
+
}
|
|
246
268
|
mountedMeasurements.current.set(atom.key, {
|
|
247
269
|
nodeType: atom.nodeType,
|
|
248
270
|
size: { width: atom.width, height: measured.height },
|
|
249
271
|
});
|
|
250
|
-
if (latest.pendingLayout)
|
|
272
|
+
if (latest.pendingLayout) {
|
|
251
273
|
return;
|
|
252
|
-
|
|
274
|
+
}
|
|
275
|
+
setMeasurementState(previous => {
|
|
253
276
|
const values = previous.generation === owner ? previous.values : {};
|
|
254
277
|
const existing = values[String(atom.docPos)];
|
|
255
|
-
if (existing?.width === atom.width && existing.height === measured.height)
|
|
278
|
+
if (existing?.width === atom.width && existing.height === measured.height) {
|
|
256
279
|
return previous;
|
|
280
|
+
}
|
|
257
281
|
return {
|
|
258
282
|
generation: owner,
|
|
259
283
|
revision: previous.generation === owner ? previous.revision + 1 : 1,
|
|
@@ -264,25 +288,28 @@ function useViewerAtoms({ atoms, identity, themeJson, readOnly, interactive = tr
|
|
|
264
288
|
};
|
|
265
289
|
});
|
|
266
290
|
}, []);
|
|
267
|
-
const children = renderedPositions.map(
|
|
291
|
+
const children = renderedPositions.map(atom => {
|
|
268
292
|
const Component = definitions.get(atom.nodeType)?.component;
|
|
269
|
-
if (!Component)
|
|
293
|
+
if (!Component) {
|
|
270
294
|
return null;
|
|
295
|
+
}
|
|
271
296
|
return ((0, jsx_runtime_1.jsx)(react_native_1.View, { collapsable: false, pointerEvents: !interactive || pendingLayout ? 'none' : 'box-none', accessibilityElementsHidden: pendingLayout, importantForAccessibility: pendingLayout ? 'no-hide-descendants' : 'auto', style: {
|
|
272
297
|
position: 'absolute',
|
|
273
298
|
left: atom.x,
|
|
274
299
|
top: atom.y,
|
|
275
300
|
width: atom.width,
|
|
276
301
|
opacity: pendingLayout ? 0 : 1,
|
|
277
|
-
}, onLayout:
|
|
302
|
+
}, onLayout: event => measure(generation, atom, Component, event), children: (0, jsx_runtime_1.jsx)(AtomHost_1.AtomHost, { component: Component, width: atom.width, estimatedHeight: atom.height, visible: (0, AtomHost_1.atomIsVisible)(atom.y, atom.height, viewport), onMeasure: event => measure(generation, atom, Component, event), atomProps: {
|
|
278
303
|
attrs: atom.attrs,
|
|
279
304
|
nodeType: atom.nodeType,
|
|
280
305
|
selected: false,
|
|
281
306
|
readOnly,
|
|
282
307
|
interactive: interactive && !pendingLayout,
|
|
283
308
|
isViewer: true,
|
|
284
|
-
updateAttrs:
|
|
309
|
+
updateAttrs: partial => updateAttrs(layout.generation, atom, partial),
|
|
285
310
|
} }) }, atom.key));
|
|
286
311
|
});
|
|
287
|
-
return {
|
|
312
|
+
return {
|
|
313
|
+
enabled, themeJson: configuredThemeJson, onAtomLayout, onContainerLayout, children,
|
|
314
|
+
};
|
|
288
315
|
}
|
|
@@ -8,32 +8,32 @@
|
|
|
8
8
|
<key>BinaryPath</key>
|
|
9
9
|
<string>libeditor_core.a</string>
|
|
10
10
|
<key>LibraryIdentifier</key>
|
|
11
|
-
<string>ios-
|
|
11
|
+
<string>ios-arm64_x86_64-simulator</string>
|
|
12
12
|
<key>LibraryPath</key>
|
|
13
13
|
<string>libeditor_core.a</string>
|
|
14
14
|
<key>SupportedArchitectures</key>
|
|
15
15
|
<array>
|
|
16
16
|
<string>arm64</string>
|
|
17
|
+
<string>x86_64</string>
|
|
17
18
|
</array>
|
|
18
19
|
<key>SupportedPlatform</key>
|
|
19
20
|
<string>ios</string>
|
|
21
|
+
<key>SupportedPlatformVariant</key>
|
|
22
|
+
<string>simulator</string>
|
|
20
23
|
</dict>
|
|
21
24
|
<dict>
|
|
22
25
|
<key>BinaryPath</key>
|
|
23
26
|
<string>libeditor_core.a</string>
|
|
24
27
|
<key>LibraryIdentifier</key>
|
|
25
|
-
<string>ios-
|
|
28
|
+
<string>ios-arm64</string>
|
|
26
29
|
<key>LibraryPath</key>
|
|
27
30
|
<string>libeditor_core.a</string>
|
|
28
31
|
<key>SupportedArchitectures</key>
|
|
29
32
|
<array>
|
|
30
33
|
<string>arm64</string>
|
|
31
|
-
<string>x86_64</string>
|
|
32
34
|
</array>
|
|
33
35
|
<key>SupportedPlatform</key>
|
|
34
36
|
<string>ios</string>
|
|
35
|
-
<key>SupportedPlatformVariant</key>
|
|
36
|
-
<string>simulator</string>
|
|
37
37
|
</dict>
|
|
38
38
|
</array>
|
|
39
39
|
<key>CFBundlePackageType</key>
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apollohg/react-native-rich-text-editor",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Native rich text editor with Rust core for React Native",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://github.com/apollohg/react-native-rich-text-editor",
|
|
@@ -104,21 +104,29 @@
|
|
|
104
104
|
},
|
|
105
105
|
"devDependencies": {
|
|
106
106
|
"@babel/runtime": "^7.29.7",
|
|
107
|
+
"@eslint/js": "^10.0.1",
|
|
107
108
|
"@expo/vector-icons": "^15.0.3",
|
|
108
109
|
"@react-native/jest-preset": "0.86.2",
|
|
110
|
+
"@stylistic/eslint-plugin": "^5.10.0",
|
|
109
111
|
"@testing-library/react-native": "^13.3.3",
|
|
110
112
|
"@types/jest": "29.5.14",
|
|
111
113
|
"@types/react": "^19.2.14",
|
|
112
114
|
"babel-jest": "^29.7.0",
|
|
113
115
|
"babel-preset-expo": "~57.0.8",
|
|
116
|
+
"eslint": "^10.10.0",
|
|
117
|
+
"eslint-plugin-react": "^7.37.5",
|
|
118
|
+
"eslint-plugin-react-hooks": "^7.1.1",
|
|
119
|
+
"eslint-plugin-react-native": "^5.0.0",
|
|
120
|
+
"eslint-plugin-slop": "^0.1.3",
|
|
114
121
|
"expo": "^57.0.9",
|
|
115
122
|
"expo-modules-core": "~57.0.8",
|
|
116
123
|
"jest": "^29.7.0",
|
|
117
|
-
"
|
|
124
|
+
"jiti": "^2.7.0",
|
|
118
125
|
"react": "19.2.3",
|
|
119
126
|
"react-native": "0.86.2",
|
|
120
127
|
"react-test-renderer": "19.2.3",
|
|
121
|
-
"typescript": "^5.9.3"
|
|
128
|
+
"typescript": "^5.9.3",
|
|
129
|
+
"typescript-eslint": "^8.70.0"
|
|
122
130
|
},
|
|
123
131
|
"files": [
|
|
124
132
|
"README.md",
|
package/react-native.config.js
CHANGED
|
@@ -7,7 +7,7 @@ module.exports = {
|
|
|
7
7
|
// metadata; no nested compatibility podspec exists.
|
|
8
8
|
ios: {},
|
|
9
9
|
android: {
|
|
10
|
-
componentDescriptors: ['PreparedProseViewerComponentDescriptor'],
|
|
10
|
+
componentDescriptors: [ 'PreparedProseViewerComponentDescriptor' ],
|
|
11
11
|
cmakeListsPath: '../android/src/main/jni/CMakeLists.txt',
|
|
12
12
|
packageImportPath: 'import com.apollohg.editor.viewer.PreparedProseViewerPackage;',
|
|
13
13
|
packageInstance: 'new PreparedProseViewerPackage()',
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/src/AtomHost.tsx
CHANGED
|
@@ -11,9 +11,12 @@ export interface AtomViewport {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export function atomIsVisible(y: number, height: number, viewport?: AtomViewport): boolean {
|
|
14
|
-
if (!viewport)
|
|
14
|
+
if (!viewport) {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
|
|
15
18
|
if (
|
|
16
|
-
![viewport.y, viewport.height, viewport.overscan ?? 200].every(Number.isFinite) ||
|
|
19
|
+
![ viewport.y, viewport.height, viewport.overscan ?? 200 ].every(Number.isFinite) ||
|
|
17
20
|
viewport.height < 0 ||
|
|
18
21
|
(viewport.overscan ?? 200) < 0
|
|
19
22
|
) {
|
|
@@ -21,7 +24,9 @@ export function atomIsVisible(y: number, height: number, viewport?: AtomViewport
|
|
|
21
24
|
'Atom viewport must have finite coordinates and non-negative height and overscan.'
|
|
22
25
|
);
|
|
23
26
|
}
|
|
27
|
+
|
|
24
28
|
const overscan = viewport.overscan ?? 200;
|
|
29
|
+
|
|
25
30
|
return y + height >= viewport.y - overscan && y <= viewport.y + viewport.height + overscan;
|
|
26
31
|
}
|
|
27
32
|
|
|
@@ -49,13 +54,17 @@ class AtomErrorBoundary extends React.Component<
|
|
|
49
54
|
}
|
|
50
55
|
|
|
51
56
|
render() {
|
|
52
|
-
if (!this.state.error)
|
|
57
|
+
if (!this.state.error) {
|
|
58
|
+
return this.props.children;
|
|
59
|
+
}
|
|
60
|
+
|
|
53
61
|
return (
|
|
54
|
-
<View accessibilityRole='alert' style={{ minHeight: 32, padding: 8 }}>
|
|
62
|
+
<View accessibilityRole={'alert'} style={{ minHeight: 32, padding: 8 }}>
|
|
55
63
|
<Text>Unable to display {this.props.nodeType}</Text>
|
|
56
64
|
<Pressable
|
|
57
|
-
accessibilityRole='button'
|
|
58
|
-
onPress={() => this.setState({ error: false })}
|
|
65
|
+
accessibilityRole={'button'}
|
|
66
|
+
onPress={() => this.setState({ error: false })}
|
|
67
|
+
>
|
|
59
68
|
<Text>Retry</Text>
|
|
60
69
|
</Pressable>
|
|
61
70
|
</View>
|
|
@@ -80,58 +89,77 @@ export function AtomHost({
|
|
|
80
89
|
onMeasure?: (event: import('react-native').LayoutChangeEvent) => void;
|
|
81
90
|
nativeID?: string;
|
|
82
91
|
}) {
|
|
83
|
-
const [measurement, setMeasurement] = useState({ width, height: estimatedHeight });
|
|
84
|
-
const [focused, setFocused] = useState(false);
|
|
85
|
-
const [active, setActive] = useState(false);
|
|
86
|
-
const [pending, setPending] = useState(0);
|
|
87
|
-
const [updateError, setUpdateError] = useState<Error | null>(null);
|
|
92
|
+
const [ measurement, setMeasurement ] = useState({ width, height: estimatedHeight });
|
|
93
|
+
const [ focused, setFocused ] = useState(false);
|
|
94
|
+
const [ active, setActive ] = useState(false);
|
|
95
|
+
const [ pending, setPending ] = useState(0);
|
|
96
|
+
const [ updateError, setUpdateError ] = useState<Error | null>(null);
|
|
88
97
|
const latestRequest = useRef(0);
|
|
89
98
|
const applyUpdate = atomProps.updateAttrs;
|
|
90
99
|
const mounted = useRef(true);
|
|
100
|
+
|
|
91
101
|
useEffect(() => {
|
|
92
102
|
mounted.current = true;
|
|
103
|
+
|
|
93
104
|
return () => {
|
|
94
105
|
mounted.current = false;
|
|
95
106
|
};
|
|
96
107
|
}, []);
|
|
108
|
+
|
|
97
109
|
const updateAttrs = useCallback(
|
|
98
|
-
async
|
|
99
|
-
if (!mounted.current)
|
|
110
|
+
async(update: AtomAttrsUpdate) => {
|
|
111
|
+
if (!mounted.current) {
|
|
100
112
|
throw new AtomUpdateAttrsError('not-ready', 'The atom is unmounted.');
|
|
113
|
+
}
|
|
114
|
+
|
|
101
115
|
const request = ++latestRequest.current;
|
|
102
|
-
setPending(
|
|
116
|
+
setPending(count => count + 1);
|
|
103
117
|
setUpdateError(null);
|
|
118
|
+
|
|
104
119
|
try {
|
|
105
120
|
await applyUpdate(update);
|
|
106
121
|
} catch (error) {
|
|
107
|
-
if (mounted.current && latestRequest.current === request)
|
|
122
|
+
if (mounted.current && latestRequest.current === request) {
|
|
108
123
|
setUpdateError(error instanceof Error ? error : new Error(String(error)));
|
|
124
|
+
}
|
|
125
|
+
|
|
109
126
|
throw error;
|
|
110
127
|
} finally {
|
|
111
|
-
if (mounted.current)
|
|
128
|
+
if (mounted.current) {
|
|
129
|
+
setPending(count => count - 1);
|
|
130
|
+
}
|
|
112
131
|
}
|
|
113
132
|
},
|
|
114
|
-
[applyUpdate]
|
|
133
|
+
[ applyUpdate ]
|
|
115
134
|
);
|
|
135
|
+
|
|
116
136
|
const editor = useMemo(() => {
|
|
117
|
-
if (!atomProps.editor)
|
|
118
|
-
|
|
119
|
-
|
|
137
|
+
if (!atomProps.editor) {
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const guard = (action: () => Promise<void>) => async() => {
|
|
142
|
+
if (!mounted.current) {
|
|
120
143
|
throw new AtomUpdateAttrsError('not-ready', 'The atom is unmounted.');
|
|
144
|
+
}
|
|
145
|
+
|
|
121
146
|
return action();
|
|
122
147
|
};
|
|
148
|
+
|
|
123
149
|
return {
|
|
124
150
|
select: guard(atomProps.editor.select),
|
|
125
151
|
delete: guard(atomProps.editor.delete),
|
|
126
152
|
focusBefore: guard(atomProps.editor.focusBefore),
|
|
127
153
|
focusAfter: guard(atomProps.editor.focusAfter),
|
|
128
154
|
};
|
|
129
|
-
}, [atomProps.editor]);
|
|
155
|
+
}, [ atomProps.editor ]);
|
|
156
|
+
|
|
130
157
|
const retainedHeight = measurement.width === width ? measurement.height : estimatedHeight;
|
|
131
158
|
const show = visible || focused || active || pending > 0 || atomProps.selected;
|
|
159
|
+
|
|
132
160
|
return (
|
|
133
161
|
<View
|
|
134
|
-
testID='atom-host'
|
|
162
|
+
testID={'atom-host'}
|
|
135
163
|
nativeID={nativeID}
|
|
136
164
|
collapsable={false}
|
|
137
165
|
pointerEvents={atomProps.interactive === false ? 'none' : 'box-none'}
|
|
@@ -139,23 +167,24 @@ export function AtomHost({
|
|
|
139
167
|
importantForAccessibility={!show ? 'no-hide-descendants' : 'auto'}
|
|
140
168
|
onFocus={() => setFocused(true)}
|
|
141
169
|
onBlur={() => setFocused(false)}
|
|
142
|
-
onLayout={
|
|
170
|
+
onLayout={event => {
|
|
143
171
|
onMeasure?.(event);
|
|
144
172
|
const size = event.nativeEvent.layout;
|
|
173
|
+
|
|
145
174
|
if (
|
|
146
175
|
show &&
|
|
147
176
|
Number.isFinite(size.height) &&
|
|
148
177
|
size.height >= 0 &&
|
|
149
178
|
Math.abs(size.width - width) <= 1
|
|
150
179
|
) {
|
|
151
|
-
setMeasurement(
|
|
180
|
+
setMeasurement(previous =>
|
|
152
181
|
previous.width === width && previous.height === size.height
|
|
153
182
|
? previous
|
|
154
|
-
: { width, height: size.height }
|
|
155
|
-
);
|
|
183
|
+
: { width, height: size.height });
|
|
156
184
|
}
|
|
157
185
|
}}
|
|
158
|
-
style={show ? undefined : { height: retainedHeight }}
|
|
186
|
+
style={show ? undefined : { height: retainedHeight }}
|
|
187
|
+
>
|
|
159
188
|
{show && (
|
|
160
189
|
<AtomErrorBoundary component={Component} nodeType={atomProps.nodeType}>
|
|
161
190
|
<Suspense
|
|
@@ -163,7 +192,8 @@ export function AtomHost({
|
|
|
163
192
|
<View style={{ height: retainedHeight }}>
|
|
164
193
|
<Text>Loading {atomProps.nodeType}…</Text>
|
|
165
194
|
</View>
|
|
166
|
-
}
|
|
195
|
+
}
|
|
196
|
+
>
|
|
167
197
|
<Component
|
|
168
198
|
{...atomProps}
|
|
169
199
|
editor={editor}
|
package/src/EditorAddon.ts
CHANGED
|
@@ -37,21 +37,31 @@ export type EditorAddonEntry = EditorAddon | false | null | undefined;
|
|
|
37
37
|
export type EditorAddons = readonly EditorAddonEntry[];
|
|
38
38
|
|
|
39
39
|
function copyConfiguration<T>(value: T, ancestors = new Set<object>()): T {
|
|
40
|
-
if (value === null || typeof value !== 'object')
|
|
41
|
-
|
|
40
|
+
if (value === null || typeof value !== 'object') {
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (ancestors.has(value)) {
|
|
45
|
+
throw new Error('Addon configuration must not contain cycles.');
|
|
46
|
+
}
|
|
47
|
+
|
|
42
48
|
if (!Array.isArray(value) && Object.getPrototypeOf(value) !== Object.prototype) {
|
|
43
49
|
throw new Error('Addon configuration must contain only plain objects and arrays.');
|
|
44
50
|
}
|
|
51
|
+
|
|
45
52
|
ancestors.add(value);
|
|
53
|
+
|
|
46
54
|
const copy = Array.isArray(value)
|
|
47
|
-
? value.map((entry) => copyConfiguration(entry, ancestors))
|
|
55
|
+
? value.map((entry: unknown) => copyConfiguration(entry, ancestors))
|
|
48
56
|
: Object.fromEntries(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
57
|
+
Object.entries(value).map(([ key, entry ]) => [
|
|
58
|
+
key,
|
|
59
|
+
copyConfiguration(entry, ancestors),
|
|
60
|
+
])
|
|
61
|
+
);
|
|
62
|
+
|
|
54
63
|
ancestors.delete(value);
|
|
64
|
+
|
|
55
65
|
return Object.freeze(copy) as T;
|
|
56
66
|
}
|
|
57
67
|
|
|
@@ -14,36 +14,65 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export function normalizeEditorAddons(addons?: EditorAddons): NormalizedEditorAddons {
|
|
17
|
-
if (addons === undefined)
|
|
18
|
-
|
|
17
|
+
if (addons === undefined) {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!Array.isArray(addons)) {
|
|
22
|
+
throw new Error('addons must be a flat readonly array.');
|
|
23
|
+
}
|
|
24
|
+
|
|
19
25
|
const normalized: NormalizedEditorAddons = {};
|
|
20
26
|
const ids = new Set<string>();
|
|
21
|
-
|
|
22
|
-
|
|
27
|
+
|
|
28
|
+
for (const [ index, addon ] of addons.entries()) {
|
|
29
|
+
if (addon === false || addon === null || addon === undefined) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
|
|
23
33
|
const path = `addons[${index}]`;
|
|
24
|
-
|
|
25
|
-
if (addon
|
|
34
|
+
|
|
35
|
+
if (!isRecord(addon)) {
|
|
36
|
+
throw new Error(`${path} must be an addon descriptor.`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (addon.version !== 1) {
|
|
40
|
+
throw new Error(`${path}.version must be 1.`);
|
|
41
|
+
}
|
|
42
|
+
|
|
26
43
|
if (addon.capability !== 'mentions' && addon.capability !== 'code-highlighting') {
|
|
27
44
|
throw new Error(`${path}.capability is unsupported.`);
|
|
28
45
|
}
|
|
29
|
-
|
|
30
|
-
if (
|
|
31
|
-
throw new Error(`${path}
|
|
46
|
+
|
|
47
|
+
if (addon.id !== addon.capability) {
|
|
48
|
+
throw new Error(`${path}.id must match its capability.`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (ids.has(addon.capability)) {
|
|
52
|
+
throw new Error(`${path}: duplicate addon id '${String(addon.id)}'.`);
|
|
53
|
+
}
|
|
54
|
+
|
|
32
55
|
ids.add(addon.capability);
|
|
33
|
-
|
|
56
|
+
|
|
57
|
+
if (!isRecord(addon.options)) {
|
|
58
|
+
throw new Error(`${path}.options must be an object.`);
|
|
59
|
+
}
|
|
60
|
+
|
|
34
61
|
if (addon.capability === 'mentions') {
|
|
35
62
|
normalized.mentions = addon.options;
|
|
36
63
|
} else {
|
|
37
|
-
for (const key of ['provider', 'theme'] as const) {
|
|
64
|
+
for (const key of [ 'provider', 'theme' ] as const) {
|
|
38
65
|
if (typeof addon.options[key] !== 'string' || !addon.options[key].trim()) {
|
|
39
66
|
throw new Error(`${path}.options.${key} must be a nonempty string.`);
|
|
40
67
|
}
|
|
41
68
|
}
|
|
69
|
+
|
|
42
70
|
normalized.codeHighlighting = {
|
|
43
71
|
provider: addon.options.provider as string,
|
|
44
72
|
theme: addon.options.theme as string,
|
|
45
73
|
};
|
|
46
74
|
}
|
|
47
75
|
}
|
|
76
|
+
|
|
48
77
|
return normalized;
|
|
49
78
|
}
|