@apollohg/react-native-prose-editor 0.5.20 → 0.5.21

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.
@@ -500,7 +500,17 @@ class NativeEditorModule : Module() {
500
500
  Prop("editorUpdateRevision") { view: NativeEditorExpoView, editorUpdateRevision: Int ->
501
501
  view.setPendingEditorUpdateRevision(editorUpdateRevision)
502
502
  }
503
+ Prop("editorResetUpdateJson") { view: NativeEditorExpoView, editorResetUpdateJson: String? ->
504
+ view.setPendingEditorResetUpdateJson(editorResetUpdateJson)
505
+ }
506
+ Prop("editorResetUpdateEditorId") { view: NativeEditorExpoView, editorResetUpdateEditorId: Int? ->
507
+ view.setPendingEditorResetUpdateEditorId(editorResetUpdateEditorId?.let { nativeULong(it)?.toLong() })
508
+ }
509
+ Prop("editorResetUpdateRevision") { view: NativeEditorExpoView, editorResetUpdateRevision: Int ->
510
+ view.setPendingEditorResetUpdateRevision(editorResetUpdateRevision)
511
+ }
503
512
  OnViewDidUpdateProps { view: NativeEditorExpoView ->
513
+ view.applyPendingEditorResetUpdateIfNeeded()
504
514
  view.applyPendingEditorUpdateIfNeeded()
505
515
  }
506
516
 
@@ -519,6 +529,10 @@ class NativeEditorModule : Module() {
519
529
  view.applyEditorUpdate(updateJson)
520
530
  }
521
531
 
532
+ AsyncFunction("applyEditorResetUpdate") { view: NativeEditorExpoView, updateJson: String ->
533
+ view.applyEditorResetUpdate(updateJson)
534
+ }
535
+
522
536
  }
523
537
 
524
538
  View(NativeProseViewerExpoView::class) {
package/app.plugin.js ADDED
@@ -0,0 +1,62 @@
1
+ const path = require('path');
2
+ const { createRequire } = require('module');
3
+
4
+ const pkg = require('./package.json');
5
+
6
+ const PACKAGING_EXCLUDES_PROPERTY = 'android.packagingOptions.excludes';
7
+ const LEGACY_JNA_ABI_EXCLUDES = [
8
+ '**/armeabi/libjnidispatch.so',
9
+ '**/mips/libjnidispatch.so',
10
+ '**/mips64/libjnidispatch.so',
11
+ ];
12
+
13
+ function requireExpoConfigPlugins() {
14
+ try {
15
+ return require('expo/config-plugins');
16
+ } catch (error) {
17
+ if (error.code !== 'MODULE_NOT_FOUND') {
18
+ throw error;
19
+ }
20
+
21
+ const appRequire = createRequire(path.join(process.cwd(), 'package.json'));
22
+ return appRequire('expo/config-plugins');
23
+ }
24
+ }
25
+
26
+ function mergeCommaList(value, additions) {
27
+ const values = (value || '')
28
+ .split(',')
29
+ .map((item) => item.trim())
30
+ .filter(Boolean);
31
+
32
+ return Array.from(new Set([...values, ...additions])).join(',');
33
+ }
34
+
35
+ function withReactNativeProseEditor(config) {
36
+ const { withGradleProperties } = requireExpoConfigPlugins();
37
+
38
+ return withGradleProperties(config, (config) => {
39
+ const existing = config.modResults.find(
40
+ (property) => property.type === 'property' && property.key === PACKAGING_EXCLUDES_PROPERTY
41
+ );
42
+ const value = mergeCommaList(existing && existing.value, LEGACY_JNA_ABI_EXCLUDES);
43
+
44
+ if (existing) {
45
+ existing.value = value;
46
+ } else {
47
+ config.modResults.push({
48
+ type: 'property',
49
+ key: PACKAGING_EXCLUDES_PROPERTY,
50
+ value,
51
+ });
52
+ }
53
+
54
+ return config;
55
+ });
56
+ }
57
+
58
+ module.exports = requireExpoConfigPlugins().createRunOncePlugin(
59
+ withReactNativeProseEditor,
60
+ pkg.name,
61
+ pkg.version
62
+ );
@@ -107,9 +107,10 @@ interface EditorToolbarMentionState {
107
107
  onSelectSuggestion: (suggestion: MentionSuggestion) => void;
108
108
  }
109
109
  export declare function isEditorToolbarFocusPreservationActive(): boolean;
110
- export declare function useEditorToolbarFrames(): readonly EditorToolbarFrame[];
110
+ export declare function setActiveEditorToolbarFrameOwnerForEditor(ownerId: number, isActive: boolean): void;
111
+ export declare function useEditorToolbarFrames(ownerId: number): readonly EditorToolbarFrame[];
111
112
  export declare function setEditorToolbarMentionState(ownerId: number, state: Omit<EditorToolbarMentionState, 'ownerId'> | null): void;
112
- export declare function _setEditorToolbarFrameForTests(id: number, frame: EditorToolbarFrame | null): void;
113
+ export declare function _setEditorToolbarFrameForTests(id: number, frame: EditorToolbarFrame | null, ownerId?: number | null): void;
113
114
  export declare function _resetEditorToolbarFrameRegistryForTests(): void;
114
115
  export declare function _beginEditorToolbarInteractionForTests(): void;
115
116
  export declare function _endEditorToolbarInteractionForTests(): void;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DEFAULT_EDITOR_TOOLBAR_ITEMS = void 0;
4
4
  exports.isEditorToolbarFocusPreservationActive = isEditorToolbarFocusPreservationActive;
5
+ exports.setActiveEditorToolbarFrameOwnerForEditor = setActiveEditorToolbarFrameOwnerForEditor;
5
6
  exports.useEditorToolbarFrames = useEditorToolbarFrames;
6
7
  exports.setEditorToolbarMentionState = setEditorToolbarMentionState;
7
8
  exports._setEditorToolbarFrameForTests = _setEditorToolbarFrameForTests;
@@ -19,6 +20,7 @@ const editorToolbarMentionStateListeners = new Set();
19
20
  let nextEditorToolbarRegistrationId = 1;
20
21
  let activeEditorToolbarInteractions = 0;
21
22
  let editorToolbarFocusPreserveUntil = 0;
23
+ let activeEditorToolbarFrameOwnerId = null;
22
24
  let editorToolbarMentionState = null;
23
25
  const EDITOR_TOOLBAR_FOCUS_PRESERVE_MS = 750;
24
26
  function areToolbarFramesEqual(left, right) {
@@ -27,14 +29,23 @@ function areToolbarFramesEqual(left, right) {
27
29
  left?.width === right?.width &&
28
30
  left?.height === right?.height);
29
31
  }
32
+ function areToolbarFrameListsEqual(left, right) {
33
+ if (left.length !== right.length) {
34
+ return false;
35
+ }
36
+ return left.every((frame, index) => areToolbarFramesEqual(frame, right[index]));
37
+ }
30
38
  function notifyEditorToolbarFrameListeners() {
31
39
  editorToolbarFrameListeners.forEach((listener) => listener());
32
40
  }
33
41
  function notifyEditorToolbarMentionStateListeners() {
34
42
  editorToolbarMentionStateListeners.forEach((listener) => listener());
35
43
  }
36
- function getEditorToolbarFramesSnapshot() {
37
- return Array.from(editorToolbarFrames.values());
44
+ function getEditorToolbarFramesSnapshot(ownerId) {
45
+ return Array.from(editorToolbarFrames.values())
46
+ .filter((registration) => registration.ownerId === ownerId ||
47
+ (registration.ownerId == null && activeEditorToolbarFrameOwnerId === ownerId))
48
+ .map((registration) => registration.frame);
38
49
  }
39
50
  function subscribeEditorToolbarMentionState(listener) {
40
51
  editorToolbarMentionStateListeners.add(listener);
@@ -48,18 +59,19 @@ function getEditorToolbarMentionStateSnapshot() {
48
59
  function useEditorToolbarMentionState() {
49
60
  return (0, react_1.useSyncExternalStore)(subscribeEditorToolbarMentionState, getEditorToolbarMentionStateSnapshot, getEditorToolbarMentionStateSnapshot);
50
61
  }
51
- function registerEditorToolbarFrame(id, frame) {
62
+ function registerEditorToolbarFrame(id, frame, ownerId) {
52
63
  if (frame == null || frame.width <= 0 || frame.height <= 0) {
53
64
  if (editorToolbarFrames.delete(id)) {
54
65
  notifyEditorToolbarFrameListeners();
55
66
  }
56
67
  return;
57
68
  }
58
- const currentFrame = editorToolbarFrames.get(id);
59
- if (areToolbarFramesEqual(currentFrame, frame)) {
69
+ const currentRegistration = editorToolbarFrames.get(id);
70
+ if (currentRegistration?.ownerId === ownerId &&
71
+ areToolbarFramesEqual(currentRegistration.frame, frame)) {
60
72
  return;
61
73
  }
62
- editorToolbarFrames.set(id, frame);
74
+ editorToolbarFrames.set(id, { ownerId, frame });
63
75
  notifyEditorToolbarFrameListeners();
64
76
  }
65
77
  function unregisterEditorToolbarFrame(id) {
@@ -81,16 +93,31 @@ function endEditorToolbarInteraction() {
81
93
  function isEditorToolbarFocusPreservationActive() {
82
94
  return activeEditorToolbarInteractions > 0 || Date.now() <= editorToolbarFocusPreserveUntil;
83
95
  }
84
- function useEditorToolbarFrames() {
85
- const [frames, setFrames] = (0, react_1.useState)(getEditorToolbarFramesSnapshot);
96
+ function setActiveEditorToolbarFrameOwnerForEditor(ownerId, isActive) {
97
+ const nextOwnerId = isActive
98
+ ? ownerId
99
+ : activeEditorToolbarFrameOwnerId === ownerId
100
+ ? null
101
+ : activeEditorToolbarFrameOwnerId;
102
+ if (activeEditorToolbarFrameOwnerId === nextOwnerId) {
103
+ return;
104
+ }
105
+ activeEditorToolbarFrameOwnerId = nextOwnerId;
106
+ notifyEditorToolbarFrameListeners();
107
+ }
108
+ function useEditorToolbarFrames(ownerId) {
109
+ const [frames, setFrames] = (0, react_1.useState)(() => getEditorToolbarFramesSnapshot(ownerId));
86
110
  (0, react_1.useEffect)(() => {
87
- const listener = () => setFrames(getEditorToolbarFramesSnapshot());
111
+ const listener = () => {
112
+ const nextFrames = getEditorToolbarFramesSnapshot(ownerId);
113
+ setFrames((currentFrames) => areToolbarFrameListsEqual(currentFrames, nextFrames) ? currentFrames : nextFrames);
114
+ };
88
115
  editorToolbarFrameListeners.add(listener);
89
116
  listener();
90
117
  return () => {
91
118
  editorToolbarFrameListeners.delete(listener);
92
119
  };
93
- }, []);
120
+ }, [ownerId]);
94
121
  return frames;
95
122
  }
96
123
  function setEditorToolbarMentionState(ownerId, state) {
@@ -108,14 +135,15 @@ function setEditorToolbarMentionState(ownerId, state) {
108
135
  };
109
136
  notifyEditorToolbarMentionStateListeners();
110
137
  }
111
- function _setEditorToolbarFrameForTests(id, frame) {
112
- registerEditorToolbarFrame(id, frame);
138
+ function _setEditorToolbarFrameForTests(id, frame, ownerId = null) {
139
+ registerEditorToolbarFrame(id, frame, ownerId);
113
140
  }
114
141
  function _resetEditorToolbarFrameRegistryForTests() {
115
142
  editorToolbarFrames.clear();
116
143
  editorToolbarMentionState = null;
117
144
  activeEditorToolbarInteractions = 0;
118
145
  editorToolbarFocusPreserveUntil = 0;
146
+ activeEditorToolbarFrameOwnerId = null;
119
147
  notifyEditorToolbarFrameListeners();
120
148
  notifyEditorToolbarMentionStateListeners();
121
149
  }
@@ -518,7 +546,7 @@ function EditorToolbar({ activeState, historyState, onToggleBold, onToggleItalic
518
546
  return;
519
547
  }
520
548
  toolbar.measureInWindow((x, y, width, height) => {
521
- registerEditorToolbarFrame(registrationId, { x, y, width, height });
549
+ registerEditorToolbarFrame(registrationId, { x, y, width, height }, null);
522
550
  });
523
551
  }, [preserveEditorFocus]);
524
552
  const cancelScheduledFramePublishes = (0, react_1.useCallback)(() => {
@@ -7,6 +7,7 @@ import { type EditorAddons } from './addons';
7
7
  import { type ImageNodeAttributes, type SchemaDefinition } from './schemas';
8
8
  export type NativeRichTextEditorHeightBehavior = 'fixed' | 'autoGrow';
9
9
  export type NativeRichTextEditorToolbarPlacement = 'keyboard' | 'inline';
10
+ export type NativeRichTextEditorValueJSONUpdateMode = 'replace' | 'reset';
10
11
  export type NativeRichTextEditorAutoCapitalize = 'none' | 'sentences' | 'words' | 'characters';
11
12
  export type NativeRichTextEditorKeyboardType = 'default' | 'email-address' | 'numeric' | 'phone-pad' | 'ascii-capable' | 'numbers-and-punctuation' | 'url' | 'number-pad' | 'name-phone-pad' | 'decimal-pad' | 'twitter' | 'web-search' | 'visible-password' | 'ascii-capable-number-pad';
12
13
  export interface RemoteSelectionDecoration {
@@ -41,6 +42,12 @@ export interface NativeRichTextEditorProps {
41
42
  valueJSON?: DocumentJSON;
42
43
  /** Optional stable revision hint for `valueJSON` to avoid reserializing equal docs on rerender. */
43
44
  valueJSONRevision?: string | number;
45
+ /** Controls how external `valueJSON` changes are applied. Defaults to preserving undo history. */
46
+ valueJSONUpdateMode?: NativeRichTextEditorValueJSONUpdateMode;
47
+ /** When using reset-mode `valueJSON`, preserve the current local text selection after applying the reset. */
48
+ preserveSelectionOnValueJSONReset?: boolean;
49
+ /** Preferred selection to restore when applying reset-mode `valueJSON`; falls back to the live selection. */
50
+ selectionOnValueJSONReset?: Selection;
44
51
  /** Schema definition. Defaults to tiptapSchema if not provided. */
45
52
  schema?: SchemaDefinition;
46
53
  /** Placeholder text shown when editor is empty. */
@@ -137,6 +144,8 @@ export interface NativeRichTextEditorRef {
137
144
  setContent(html: string): void;
138
145
  /** Replace entire document with JSON (preserves undo history). */
139
146
  setContentJson(doc: DocumentJSON): void;
147
+ /** Clear the document to the active schema's empty text block. */
148
+ clearContent(): void;
140
149
  /** Get the current HTML content. */
141
150
  getContent(): string;
142
151
  /** Get the current content as ProseMirror JSON. */