@apollohg/react-native-prose-editor 0.4.1 → 0.4.3
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 +3 -2
- package/dist/NativeEditorBridge.d.ts +2 -1
- package/dist/NativeEditorBridge.js +23 -7
- package/dist/NativeRichTextEditor.d.ts +1 -2
- package/dist/NativeRichTextEditor.js +8 -7
- package/dist/schemas.d.ts +2 -0
- package/dist/schemas.js +63 -0
- 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/ios/RichTextEditorView.swift +31 -0
- package/package.json +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_64/libeditor_core.so +0 -0
package/README.md
CHANGED
|
@@ -49,7 +49,6 @@ The minimum tested Expo version is SDK 54.
|
|
|
49
49
|
Required peer dependencies:
|
|
50
50
|
|
|
51
51
|
- `expo`
|
|
52
|
-
- `expo-modules-core`
|
|
53
52
|
- `react`
|
|
54
53
|
- `react-native`
|
|
55
54
|
- `@expo/vector-icons`
|
|
@@ -57,7 +56,7 @@ Required peer dependencies:
|
|
|
57
56
|
Install the package:
|
|
58
57
|
|
|
59
58
|
```sh
|
|
60
|
-
npm install @apollohg/react-native-prose-editor
|
|
59
|
+
npm install @apollohg/react-native-prose-editor@0.4.3
|
|
61
60
|
```
|
|
62
61
|
|
|
63
62
|
For local package development in this repo:
|
|
@@ -111,6 +110,8 @@ For setup and customization details, start with the [Documentation Index](./docs
|
|
|
111
110
|
|
|
112
111
|
For realtime collaboration, including the correct `useYjsCollaboration()` wiring, encoded-state persistence, remote cursors, and automatic reconnect behavior, see the [Collaboration Guide](./docs/modules/collaboration.md).
|
|
113
112
|
|
|
113
|
+
For whole-document JSON loads, `initialJSON`, controlled `valueJSON`, and `setContentJson()` will normalize an empty root document like `{ type: 'doc', content: [] }` to the active schema's empty text block so block-constrained schemas still load a valid empty document.
|
|
114
|
+
|
|
114
115
|
## Development
|
|
115
116
|
|
|
116
117
|
Common commands:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type SchemaDefinition } from './schemas';
|
|
2
2
|
export interface NativeEditorModule {
|
|
3
3
|
editorCreate(configJson: string): number;
|
|
4
4
|
editorDestroy(editorId: number): void;
|
|
@@ -150,6 +150,7 @@ export declare function parseCollaborationResultJson(json: string): Collaboratio
|
|
|
150
150
|
export declare function _resetNativeModuleCache(): void;
|
|
151
151
|
export declare class NativeEditorBridge {
|
|
152
152
|
private _editorId;
|
|
153
|
+
private _schema?;
|
|
153
154
|
private _destroyed;
|
|
154
155
|
private _lastSelection;
|
|
155
156
|
private _documentVersion;
|
|
@@ -8,6 +8,7 @@ exports.decodeCollaborationStateBase64 = decodeCollaborationStateBase64;
|
|
|
8
8
|
exports.parseCollaborationResultJson = parseCollaborationResultJson;
|
|
9
9
|
exports._resetNativeModuleCache = _resetNativeModuleCache;
|
|
10
10
|
const expo_modules_core_1 = require("expo-modules-core");
|
|
11
|
+
const schemas_1 = require("./schemas");
|
|
11
12
|
const ERR_DESTROYED = 'NativeEditorBridge: editor has been destroyed';
|
|
12
13
|
const ERR_NATIVE_RESPONSE = 'NativeEditorBridge: invalid JSON response from native module';
|
|
13
14
|
const ERR_INVALID_ENCODED_STATE = 'NativeEditorBridge: invalid encoded collaboration state';
|
|
@@ -233,6 +234,16 @@ function encodeCollaborationStateBase64(encodedState) {
|
|
|
233
234
|
function decodeCollaborationStateBase64(base64) {
|
|
234
235
|
return base64ToBytes(base64);
|
|
235
236
|
}
|
|
237
|
+
function normalizeDocumentJsonString(jsonString, schema) {
|
|
238
|
+
try {
|
|
239
|
+
const parsed = JSON.parse(jsonString);
|
|
240
|
+
const normalized = (0, schemas_1.normalizeDocumentJson)(parsed, schema);
|
|
241
|
+
return normalized === parsed ? jsonString : JSON.stringify(normalized);
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
return jsonString;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
236
247
|
function parseCollaborationResultJson(json) {
|
|
237
248
|
if (!json || json === '') {
|
|
238
249
|
return {
|
|
@@ -275,7 +286,7 @@ function _resetNativeModuleCache() {
|
|
|
275
286
|
_nativeModule = null;
|
|
276
287
|
}
|
|
277
288
|
class NativeEditorBridge {
|
|
278
|
-
constructor(editorId) {
|
|
289
|
+
constructor(editorId, schema) {
|
|
279
290
|
this._destroyed = false;
|
|
280
291
|
this._lastSelection = { type: 'text', anchor: 0, head: 0 };
|
|
281
292
|
this._documentVersion = 0;
|
|
@@ -283,10 +294,12 @@ class NativeEditorBridge {
|
|
|
283
294
|
this._cachedJsonString = null;
|
|
284
295
|
this._renderBlocksCache = null;
|
|
285
296
|
this._editorId = editorId;
|
|
297
|
+
this._schema = schema;
|
|
286
298
|
}
|
|
287
299
|
/** Create a new editor instance backed by the Rust engine. */
|
|
288
300
|
static create(config) {
|
|
289
301
|
const configObj = {};
|
|
302
|
+
let parsedSchema;
|
|
290
303
|
if (config?.maxLength != null)
|
|
291
304
|
configObj.maxLength = config.maxLength;
|
|
292
305
|
if (config?.allowBase64Images != null) {
|
|
@@ -294,14 +307,15 @@ class NativeEditorBridge {
|
|
|
294
307
|
}
|
|
295
308
|
if (config?.schemaJson != null) {
|
|
296
309
|
try {
|
|
297
|
-
|
|
310
|
+
parsedSchema = JSON.parse(config.schemaJson);
|
|
311
|
+
configObj.schema = parsedSchema;
|
|
298
312
|
}
|
|
299
313
|
catch {
|
|
300
314
|
// Fall back to the default schema when the provided JSON is invalid.
|
|
301
315
|
}
|
|
302
316
|
}
|
|
303
317
|
const id = getNativeModule().editorCreate(JSON.stringify(configObj));
|
|
304
|
-
return new NativeEditorBridge(id);
|
|
318
|
+
return new NativeEditorBridge(id, parsedSchema);
|
|
305
319
|
}
|
|
306
320
|
/** The underlying native editor ID. */
|
|
307
321
|
get editorId() {
|
|
@@ -339,14 +353,15 @@ class NativeEditorBridge {
|
|
|
339
353
|
}
|
|
340
354
|
/** Set content from ProseMirror JSON. Returns render elements. */
|
|
341
355
|
setJson(doc) {
|
|
342
|
-
return this.setJsonString(JSON.stringify(doc));
|
|
356
|
+
return this.setJsonString(JSON.stringify((0, schemas_1.normalizeDocumentJson)(doc, this._schema)));
|
|
343
357
|
}
|
|
344
358
|
/** Set content from a serialized ProseMirror JSON string. Returns render elements. */
|
|
345
359
|
setJsonString(jsonString) {
|
|
346
360
|
this.assertNotDestroyed();
|
|
347
361
|
this.invalidateContentCaches();
|
|
348
362
|
this._renderBlocksCache = null;
|
|
349
|
-
const
|
|
363
|
+
const normalizedJsonString = normalizeDocumentJsonString(jsonString, this._schema);
|
|
364
|
+
const json = getNativeModule().editorSetJson(this._editorId, normalizedJsonString);
|
|
350
365
|
return parseRenderElements(json);
|
|
351
366
|
}
|
|
352
367
|
/** Get content as raw ProseMirror JSON string. */
|
|
@@ -520,12 +535,13 @@ class NativeEditorBridge {
|
|
|
520
535
|
}
|
|
521
536
|
/** Replace entire document with JSON via transaction (preserves undo history). */
|
|
522
537
|
replaceJson(doc) {
|
|
523
|
-
return this.replaceJsonString(JSON.stringify(doc));
|
|
538
|
+
return this.replaceJsonString(JSON.stringify((0, schemas_1.normalizeDocumentJson)(doc, this._schema)));
|
|
524
539
|
}
|
|
525
540
|
/** Replace entire document with a serialized JSON transaction. */
|
|
526
541
|
replaceJsonString(jsonString) {
|
|
527
542
|
this.assertNotDestroyed();
|
|
528
|
-
const
|
|
543
|
+
const normalizedJsonString = normalizeDocumentJsonString(jsonString, this._schema);
|
|
544
|
+
const json = getNativeModule().editorReplaceJson(this._editorId, normalizedJsonString);
|
|
529
545
|
return this.parseAndNoteUpdate(json);
|
|
530
546
|
}
|
|
531
547
|
/** Undo the last operation. Returns update or null if nothing to undo. */
|
|
@@ -4,8 +4,7 @@ import { type ActiveState, type DocumentJSON, type HistoryState, type Selection
|
|
|
4
4
|
import { type EditorToolbarHeadingLevel, type EditorToolbarItem } from './EditorToolbar';
|
|
5
5
|
import { type EditorTheme } from './EditorTheme';
|
|
6
6
|
import { type EditorAddons } from './addons';
|
|
7
|
-
import { type SchemaDefinition } from './schemas';
|
|
8
|
-
import { type ImageNodeAttributes } from './schemas';
|
|
7
|
+
import { type ImageNodeAttributes, type SchemaDefinition } from './schemas';
|
|
9
8
|
export type NativeRichTextEditorHeightBehavior = 'fixed' | 'autoGrow';
|
|
10
9
|
export type NativeRichTextEditorToolbarPlacement = 'keyboard' | 'inline';
|
|
11
10
|
export interface RemoteSelectionDecoration {
|
|
@@ -10,7 +10,6 @@ const EditorToolbar_1 = require("./EditorToolbar");
|
|
|
10
10
|
const EditorTheme_1 = require("./EditorTheme");
|
|
11
11
|
const addons_1 = require("./addons");
|
|
12
12
|
const schemas_1 = require("./schemas");
|
|
13
|
-
const schemas_2 = require("./schemas");
|
|
14
13
|
const NativeEditorView = (0, expo_modules_core_1.requireNativeViewManager)('NativeEditor');
|
|
15
14
|
const DEV_NATIVE_VIEW_KEY = __DEV__
|
|
16
15
|
? `native-editor-dev:${Math.random().toString(36).slice(2)}`
|
|
@@ -35,7 +34,7 @@ function mapToolbarChildForNative(item, activeState, editable, onRequestLink, on
|
|
|
35
34
|
label: item.label,
|
|
36
35
|
icon: item.icon,
|
|
37
36
|
isActive: false,
|
|
38
|
-
isDisabled: !editable || !onRequestImage || !activeState.insertableNodes.includes(
|
|
37
|
+
isDisabled: !editable || !onRequestImage || !activeState.insertableNodes.includes(schemas_1.IMAGE_NODE_NAME),
|
|
39
38
|
};
|
|
40
39
|
}
|
|
41
40
|
return item;
|
|
@@ -188,9 +187,11 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
188
187
|
: undefined;
|
|
189
188
|
const mentionSuggestionsByKeyRef = (0, react_1.useRef)(new Map());
|
|
190
189
|
mentionSuggestionsByKeyRef.current = new Map((addons?.mentions?.suggestions ?? []).map((suggestion) => [suggestion.key, suggestion]));
|
|
191
|
-
const
|
|
192
|
-
const
|
|
193
|
-
const
|
|
190
|
+
const bridgeSchema = addons?.mentions != null ? (0, addons_1.withMentionsSchema)(schema ?? schemas_1.tiptapSchema) : schema;
|
|
191
|
+
const documentSchema = bridgeSchema ?? schemas_1.tiptapSchema;
|
|
192
|
+
const serializedSchemaJson = useSerializedValue(bridgeSchema, (nextSchema) => stringifyCachedJson(nextSchema));
|
|
193
|
+
const serializedInitialJson = useSerializedValue(initialJSON, (doc) => stringifyCachedJson((0, schemas_1.normalizeDocumentJson)(doc, documentSchema)));
|
|
194
|
+
const serializedValueJson = useSerializedValue(valueJSON, (doc) => stringifyCachedJson((0, schemas_1.normalizeDocumentJson)(doc, documentSchema)), valueJSONRevision);
|
|
194
195
|
const themeJson = useSerializedValue(theme, EditorTheme_1.serializeEditorTheme);
|
|
195
196
|
const addonsJson = useSerializedValue(addons, addons_1.serializeEditorAddons);
|
|
196
197
|
const remoteSelectionsJson = useSerializedValue(remoteSelections, (selections) => serializeRemoteSelections(selections));
|
|
@@ -493,7 +494,7 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
493
494
|
if (selection) {
|
|
494
495
|
restoreSelection(selection);
|
|
495
496
|
}
|
|
496
|
-
return (bridgeRef.current?.insertContentJson((0,
|
|
497
|
+
return (bridgeRef.current?.insertContentJson((0, schemas_1.buildImageFragmentJson)({
|
|
497
498
|
src: trimmedSrc,
|
|
498
499
|
...(attrs ?? {}),
|
|
499
500
|
})) ?? null);
|
|
@@ -668,7 +669,7 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
668
669
|
return null;
|
|
669
670
|
const isLinkActive = activeState.marks.link === true;
|
|
670
671
|
const allowsLink = activeState.allowedMarks.includes('link');
|
|
671
|
-
const canInsertImage = activeState.insertableNodes.includes(
|
|
672
|
+
const canInsertImage = activeState.insertableNodes.includes(schemas_1.IMAGE_NODE_NAME);
|
|
672
673
|
const canRequestLink = typeof onRequestLink === 'function';
|
|
673
674
|
const canRequestImage = typeof onRequestImage === 'function';
|
|
674
675
|
const cachedToolbarItems = toolbarItemsSerializationCacheRef.current;
|
package/dist/schemas.d.ts
CHANGED
|
@@ -32,4 +32,6 @@ export declare function imageNodeSpec(name?: string): NodeSpec;
|
|
|
32
32
|
export declare function withImagesSchema(schema: SchemaDefinition): SchemaDefinition;
|
|
33
33
|
export declare function buildImageFragmentJson(attrs: ImageNodeAttributes): DocumentJSON;
|
|
34
34
|
export declare const tiptapSchema: SchemaDefinition;
|
|
35
|
+
export declare function defaultEmptyDocument(schema?: SchemaDefinition): DocumentJSON;
|
|
36
|
+
export declare function normalizeDocumentJson(doc: DocumentJSON, schema?: SchemaDefinition): DocumentJSON;
|
|
35
37
|
export declare const prosemirrorSchema: SchemaDefinition;
|
package/dist/schemas.js
CHANGED
|
@@ -4,6 +4,8 @@ exports.prosemirrorSchema = exports.tiptapSchema = exports.IMAGE_NODE_NAME = voi
|
|
|
4
4
|
exports.imageNodeSpec = imageNodeSpec;
|
|
5
5
|
exports.withImagesSchema = withImagesSchema;
|
|
6
6
|
exports.buildImageFragmentJson = buildImageFragmentJson;
|
|
7
|
+
exports.defaultEmptyDocument = defaultEmptyDocument;
|
|
8
|
+
exports.normalizeDocumentJson = normalizeDocumentJson;
|
|
7
9
|
exports.IMAGE_NODE_NAME = 'image';
|
|
8
10
|
const HEADING_LEVELS = [1, 2, 3, 4, 5, 6];
|
|
9
11
|
function imageNodeSpec(name = exports.IMAGE_NODE_NAME) {
|
|
@@ -129,6 +131,67 @@ exports.tiptapSchema = {
|
|
|
129
131
|
],
|
|
130
132
|
marks: MARKS,
|
|
131
133
|
};
|
|
134
|
+
function acceptingGroupsForChildCount(content, existingChildCount) {
|
|
135
|
+
const tokens = content
|
|
136
|
+
.trim()
|
|
137
|
+
.split(/\s+/)
|
|
138
|
+
.filter(Boolean)
|
|
139
|
+
.map((token) => {
|
|
140
|
+
const quantifier = token[token.length - 1];
|
|
141
|
+
if (quantifier === '+' || quantifier === '*' || quantifier === '?') {
|
|
142
|
+
return {
|
|
143
|
+
group: token.slice(0, -1),
|
|
144
|
+
min: quantifier === '+' ? 1 : 0,
|
|
145
|
+
max: quantifier === '?' ? 1 : null,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
group: token,
|
|
150
|
+
min: 1,
|
|
151
|
+
max: 1,
|
|
152
|
+
};
|
|
153
|
+
});
|
|
154
|
+
let remaining = existingChildCount;
|
|
155
|
+
const acceptingGroups = [];
|
|
156
|
+
for (const token of tokens) {
|
|
157
|
+
if (remaining >= token.min) {
|
|
158
|
+
const consumed = token.max == null ? remaining : Math.min(remaining, token.max);
|
|
159
|
+
remaining = Math.max(0, remaining - consumed);
|
|
160
|
+
const atMax = token.max != null && consumed >= token.max;
|
|
161
|
+
if (!atMax) {
|
|
162
|
+
acceptingGroups.push(token.group);
|
|
163
|
+
}
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
acceptingGroups.push(token.group);
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
return acceptingGroups;
|
|
170
|
+
}
|
|
171
|
+
function defaultEmptyDocument(schema = exports.tiptapSchema) {
|
|
172
|
+
const docNode = schema.nodes.find((node) => node.role === 'doc' || node.name === 'doc');
|
|
173
|
+
const acceptingGroups = docNode == null ? [] : acceptingGroupsForChildCount(docNode.content ?? '', 0);
|
|
174
|
+
const matchingTextBlocks = schema.nodes.filter((node) => node.role === 'textBlock' &&
|
|
175
|
+
acceptingGroups.some((group) => node.name === group || node.group === group));
|
|
176
|
+
const preferredTextBlock = matchingTextBlocks.find((node) => node.htmlTag === 'p' || node.name === 'paragraph') ??
|
|
177
|
+
matchingTextBlocks[0] ??
|
|
178
|
+
schema.nodes.find((node) => node.htmlTag === 'p' || node.name === 'paragraph') ??
|
|
179
|
+
schema.nodes.find((node) => node.role === 'textBlock');
|
|
180
|
+
return {
|
|
181
|
+
type: 'doc',
|
|
182
|
+
content: [{ type: preferredTextBlock?.name ?? 'paragraph' }],
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function normalizeDocumentJson(doc, schema = exports.tiptapSchema) {
|
|
186
|
+
const root = doc;
|
|
187
|
+
if (root?.type !== 'doc') {
|
|
188
|
+
return doc;
|
|
189
|
+
}
|
|
190
|
+
if (Array.isArray(root.content) && root.content.length > 0) {
|
|
191
|
+
return doc;
|
|
192
|
+
}
|
|
193
|
+
return defaultEmptyDocument(schema);
|
|
194
|
+
}
|
|
132
195
|
exports.prosemirrorSchema = {
|
|
133
196
|
nodes: [
|
|
134
197
|
{
|
|
@@ -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
|
|
@@ -1153,6 +1153,15 @@ final class EditorTextView: UITextView, UITextViewDelegate, UIGestureRecognizerD
|
|
|
1153
1153
|
}
|
|
1154
1154
|
}
|
|
1155
1155
|
|
|
1156
|
+
override func becomeFirstResponder() -> Bool {
|
|
1157
|
+
let didBecomeFirstResponder = super.becomeFirstResponder()
|
|
1158
|
+
if didBecomeFirstResponder {
|
|
1159
|
+
_ = normalizeSelectionForEmptyBlockAutocapitalizationIfNeeded()
|
|
1160
|
+
refreshTypingAttributesForSelection()
|
|
1161
|
+
}
|
|
1162
|
+
return didBecomeFirstResponder
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1156
1165
|
private func isRenderedContentEmpty() -> Bool {
|
|
1157
1166
|
let renderedText = textStorage.string
|
|
1158
1167
|
guard !renderedText.isEmpty else { return true }
|
|
@@ -1169,6 +1178,23 @@ final class EditorTextView: UITextView, UITextViewDelegate, UIGestureRecognizerD
|
|
|
1169
1178
|
return true
|
|
1170
1179
|
}
|
|
1171
1180
|
|
|
1181
|
+
@discardableResult
|
|
1182
|
+
private func normalizeSelectionForEmptyBlockAutocapitalizationIfNeeded() -> Bool {
|
|
1183
|
+
guard textStorage.length == 1 else { return false }
|
|
1184
|
+
guard textStorage.string.unicodeScalars.elementsEqual([Self.emptyBlockPlaceholderScalar]) else {
|
|
1185
|
+
return false
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
let currentRange = selectedRange
|
|
1189
|
+
guard currentRange.location != NSNotFound, currentRange.length == 0 else { return false }
|
|
1190
|
+
guard currentRange.location == textStorage.length else { return false }
|
|
1191
|
+
|
|
1192
|
+
let adjustedRange = NSRange(location: 0, length: 0)
|
|
1193
|
+
guard currentRange != adjustedRange else { return false }
|
|
1194
|
+
selectedRange = adjustedRange
|
|
1195
|
+
return true
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1172
1198
|
private func refreshPlaceholderVisibility() {
|
|
1173
1199
|
placeholderLabel.isHidden = placeholder.isEmpty || !isRenderedContentEmpty()
|
|
1174
1200
|
}
|
|
@@ -1947,6 +1973,9 @@ final class EditorTextView: UITextView, UITextViewDelegate, UIGestureRecognizerD
|
|
|
1947
1973
|
func textViewDidChangeSelection(_ textView: UITextView) {
|
|
1948
1974
|
guard textView === self else { return }
|
|
1949
1975
|
guard !isApplyingRustState else { return }
|
|
1976
|
+
if normalizeSelectionForEmptyBlockAutocapitalizationIfNeeded() {
|
|
1977
|
+
return
|
|
1978
|
+
}
|
|
1950
1979
|
refreshNativeSelectionChromeVisibility()
|
|
1951
1980
|
onSelectionOrContentMayChange?()
|
|
1952
1981
|
scheduleSelectionSync()
|
|
@@ -2099,7 +2128,9 @@ final class EditorTextView: UITextView, UITextViewDelegate, UIGestureRecognizerD
|
|
|
2099
2128
|
}
|
|
2100
2129
|
|
|
2101
2130
|
func refreshSelectionVisualState() {
|
|
2131
|
+
_ = normalizeSelectionForEmptyBlockAutocapitalizationIfNeeded()
|
|
2102
2132
|
refreshNativeSelectionChromeVisibility()
|
|
2133
|
+
refreshTypingAttributesForSelection()
|
|
2103
2134
|
onSelectionOrContentMayChange?()
|
|
2104
2135
|
}
|
|
2105
2136
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apollohg/react-native-prose-editor",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
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-prose-editor",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|