@apollohg/react-native-prose-editor 0.1.1 → 0.3.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.
Files changed (53) hide show
  1. package/README.md +12 -7
  2. package/android/build.gradle +7 -2
  3. package/android/src/main/java/com/apollohg/editor/EditorEditText.kt +289 -2
  4. package/android/src/main/java/com/apollohg/editor/EditorTheme.kt +51 -1
  5. package/android/src/main/java/com/apollohg/editor/ImageResizeOverlayView.kt +199 -0
  6. package/android/src/main/java/com/apollohg/editor/NativeEditorExpoView.kt +16 -3
  7. package/android/src/main/java/com/apollohg/editor/NativeEditorModule.kt +82 -1
  8. package/android/src/main/java/com/apollohg/editor/NativeToolbar.kt +403 -45
  9. package/android/src/main/java/com/apollohg/editor/RemoteSelectionOverlayView.kt +246 -0
  10. package/android/src/main/java/com/apollohg/editor/RenderBridge.kt +841 -155
  11. package/android/src/main/java/com/apollohg/editor/RichTextEditorView.kt +125 -8
  12. package/{src/EditorTheme.ts → dist/EditorTheme.d.ts} +12 -52
  13. package/dist/EditorTheme.js +29 -0
  14. package/dist/EditorToolbar.d.ts +129 -0
  15. package/dist/EditorToolbar.js +394 -0
  16. package/dist/NativeEditorBridge.d.ts +242 -0
  17. package/dist/NativeEditorBridge.js +647 -0
  18. package/dist/NativeRichTextEditor.d.ts +142 -0
  19. package/dist/NativeRichTextEditor.js +649 -0
  20. package/dist/YjsCollaboration.d.ts +83 -0
  21. package/dist/YjsCollaboration.js +585 -0
  22. package/dist/addons.d.ts +70 -0
  23. package/dist/addons.js +77 -0
  24. package/dist/index.d.ts +8 -0
  25. package/dist/index.js +26 -0
  26. package/dist/schemas.d.ts +35 -0
  27. package/{src/schemas.ts → dist/schemas.js} +62 -27
  28. package/dist/useNativeEditor.d.ts +40 -0
  29. package/dist/useNativeEditor.js +117 -0
  30. package/ios/EditorAddons.swift +26 -3
  31. package/ios/EditorCore.xcframework/Info.plist +5 -5
  32. package/ios/EditorCore.xcframework/ios-arm64/libeditor_core.a +0 -0
  33. package/ios/EditorCore.xcframework/ios-arm64_x86_64-simulator/libeditor_core.a +0 -0
  34. package/ios/EditorLayoutManager.swift +236 -0
  35. package/ios/EditorTheme.swift +51 -1
  36. package/ios/Generated_editor_core.swift +270 -2
  37. package/ios/NativeEditorExpoView.swift +612 -45
  38. package/ios/NativeEditorModule.swift +81 -0
  39. package/ios/PositionBridge.swift +22 -0
  40. package/ios/RenderBridge.swift +427 -39
  41. package/ios/RichTextEditorView.swift +1342 -18
  42. package/ios/editor_coreFFI/editor_coreFFI.h +209 -0
  43. package/package.json +80 -64
  44. package/rust/android/arm64-v8a/libeditor_core.so +0 -0
  45. package/rust/android/armeabi-v7a/libeditor_core.so +0 -0
  46. package/rust/android/x86_64/libeditor_core.so +0 -0
  47. package/rust/bindings/kotlin/uniffi/editor_core/editor_core.kt +404 -4
  48. package/src/EditorToolbar.tsx +0 -620
  49. package/src/NativeEditorBridge.ts +0 -607
  50. package/src/NativeRichTextEditor.tsx +0 -951
  51. package/src/addons.ts +0 -158
  52. package/src/index.ts +0 -63
  53. package/src/useNativeEditor.ts +0 -173
@@ -0,0 +1,70 @@
1
+ import type { EditorMentionTheme } from './EditorTheme';
2
+ import type { DocumentJSON } from './NativeEditorBridge';
3
+ import type { SchemaDefinition, NodeSpec } from './schemas';
4
+ export interface MentionSuggestion {
5
+ key: string;
6
+ title: string;
7
+ subtitle?: string;
8
+ label?: string;
9
+ attrs?: Record<string, unknown>;
10
+ }
11
+ export interface MentionQueryChangeEvent {
12
+ query: string;
13
+ trigger: string;
14
+ range: {
15
+ anchor: number;
16
+ head: number;
17
+ };
18
+ isActive: boolean;
19
+ }
20
+ export interface MentionSelectEvent {
21
+ trigger: string;
22
+ suggestion: MentionSuggestion;
23
+ attrs: Record<string, unknown>;
24
+ }
25
+ export interface MentionsAddonConfig {
26
+ trigger?: string;
27
+ suggestions?: readonly MentionSuggestion[];
28
+ theme?: EditorMentionTheme;
29
+ onQueryChange?: (event: MentionQueryChangeEvent) => void;
30
+ onSelect?: (event: MentionSelectEvent) => void;
31
+ }
32
+ export interface EditorAddons {
33
+ mentions?: MentionsAddonConfig;
34
+ }
35
+ export interface SerializedMentionSuggestion {
36
+ key: string;
37
+ title: string;
38
+ subtitle?: string;
39
+ label: string;
40
+ attrs: Record<string, unknown>;
41
+ }
42
+ export interface SerializedMentionsAddonConfig {
43
+ trigger: string;
44
+ theme?: EditorMentionTheme;
45
+ suggestions: SerializedMentionSuggestion[];
46
+ }
47
+ export interface SerializedEditorAddons {
48
+ mentions?: SerializedMentionsAddonConfig;
49
+ }
50
+ export type EditorAddonEvent = {
51
+ type: 'mentionsQueryChange';
52
+ query: string;
53
+ trigger: string;
54
+ range: {
55
+ anchor: number;
56
+ head: number;
57
+ };
58
+ isActive: boolean;
59
+ } | {
60
+ type: 'mentionsSelect';
61
+ trigger: string;
62
+ suggestionKey: string;
63
+ attrs: Record<string, unknown>;
64
+ };
65
+ export declare const MENTION_NODE_NAME = "mention";
66
+ export declare function mentionNodeSpec(): NodeSpec;
67
+ export declare function withMentionsSchema(schema: SchemaDefinition): SchemaDefinition;
68
+ export declare function normalizeEditorAddons(addons?: EditorAddons): SerializedEditorAddons | undefined;
69
+ export declare function serializeEditorAddons(addons?: EditorAddons): string | undefined;
70
+ export declare function buildMentionFragmentJson(attrs: Record<string, unknown>): DocumentJSON;
package/dist/addons.js ADDED
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MENTION_NODE_NAME = void 0;
4
+ exports.mentionNodeSpec = mentionNodeSpec;
5
+ exports.withMentionsSchema = withMentionsSchema;
6
+ exports.normalizeEditorAddons = normalizeEditorAddons;
7
+ exports.serializeEditorAddons = serializeEditorAddons;
8
+ exports.buildMentionFragmentJson = buildMentionFragmentJson;
9
+ exports.MENTION_NODE_NAME = 'mention';
10
+ const DEFAULT_MENTION_TRIGGER = '@';
11
+ function mentionNodeSpec() {
12
+ return {
13
+ name: exports.MENTION_NODE_NAME,
14
+ content: '',
15
+ group: 'inline',
16
+ role: 'inline',
17
+ isVoid: true,
18
+ attrs: {
19
+ label: { default: null },
20
+ },
21
+ };
22
+ }
23
+ function withMentionsSchema(schema) {
24
+ const hasMentionNode = schema.nodes.some((node) => node.name === exports.MENTION_NODE_NAME);
25
+ if (hasMentionNode) {
26
+ return schema;
27
+ }
28
+ return {
29
+ ...schema,
30
+ nodes: [...schema.nodes, mentionNodeSpec()],
31
+ };
32
+ }
33
+ function normalizeEditorAddons(addons) {
34
+ if (!addons?.mentions) {
35
+ return undefined;
36
+ }
37
+ const trigger = addons.mentions.trigger?.trim() || DEFAULT_MENTION_TRIGGER;
38
+ const suggestions = (addons.mentions.suggestions ?? []).map((suggestion) => {
39
+ const label = suggestion.label?.trim() || `${trigger}${suggestion.title}`;
40
+ const attrs = {
41
+ label,
42
+ ...(suggestion.attrs ?? {}),
43
+ };
44
+ return {
45
+ key: suggestion.key,
46
+ title: suggestion.title,
47
+ subtitle: suggestion.subtitle,
48
+ label,
49
+ attrs,
50
+ };
51
+ });
52
+ return {
53
+ mentions: {
54
+ trigger,
55
+ theme: addons.mentions.theme,
56
+ suggestions,
57
+ },
58
+ };
59
+ }
60
+ function serializeEditorAddons(addons) {
61
+ const normalized = normalizeEditorAddons(addons);
62
+ if (!normalized?.mentions) {
63
+ return undefined;
64
+ }
65
+ return JSON.stringify(normalized);
66
+ }
67
+ function buildMentionFragmentJson(attrs) {
68
+ return {
69
+ type: 'doc',
70
+ content: [
71
+ {
72
+ type: exports.MENTION_NODE_NAME,
73
+ attrs,
74
+ },
75
+ ],
76
+ };
77
+ }
@@ -0,0 +1,8 @@
1
+ export { NativeRichTextEditor, type NativeRichTextEditorProps, type NativeRichTextEditorRef, type NativeRichTextEditorHeightBehavior, type NativeRichTextEditorToolbarPlacement, type RemoteSelectionDecoration, type LinkRequestContext, type ImageRequestContext, } from './NativeRichTextEditor';
2
+ export { EditorToolbar, DEFAULT_EDITOR_TOOLBAR_ITEMS, type EditorToolbarProps, type EditorToolbarItem, type EditorToolbarIcon, type EditorToolbarDefaultIconId, type EditorToolbarSFSymbolIcon, type EditorToolbarMaterialIcon, type EditorToolbarCommand, type EditorToolbarListType, } from './EditorToolbar';
3
+ export type { EditorContentInsets, EditorTheme, EditorTextStyle, EditorHeadingTheme, EditorListTheme, EditorHorizontalRuleTheme, EditorMentionTheme, EditorToolbarTheme, EditorToolbarAppearance, EditorFontStyle, EditorFontWeight, } from './EditorTheme';
4
+ export { MENTION_NODE_NAME, mentionNodeSpec, withMentionsSchema, buildMentionFragmentJson, type EditorAddons, type MentionsAddonConfig, type MentionSuggestion, type MentionQueryChangeEvent, type MentionSelectEvent, type EditorAddonEvent, } from './addons';
5
+ export { tiptapSchema, prosemirrorSchema, IMAGE_NODE_NAME, imageNodeSpec, withImagesSchema, buildImageFragmentJson, type SchemaDefinition, type NodeSpec, type MarkSpec, type AttrSpec, type ImageNodeAttributes, } from './schemas';
6
+ export { createYjsCollaborationController, useYjsCollaboration, type YjsCollaborationOptions, type YjsCollaborationState, type YjsTransportStatus, type LocalAwarenessState, type LocalAwarenessUser, type UseYjsCollaborationResult, type YjsCollaborationController, } from './YjsCollaboration';
7
+ export type { Selection, ActiveState, HistoryState, EditorUpdate, DocumentJSON, CollaborationPeer, EncodedCollaborationStateInput, } from './NativeEditorBridge';
8
+ export { encodeCollaborationStateBase64, decodeCollaborationStateBase64, } from './NativeEditorBridge';
package/dist/index.js ADDED
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.decodeCollaborationStateBase64 = exports.encodeCollaborationStateBase64 = exports.useYjsCollaboration = exports.createYjsCollaborationController = exports.buildImageFragmentJson = exports.withImagesSchema = exports.imageNodeSpec = exports.IMAGE_NODE_NAME = exports.prosemirrorSchema = exports.tiptapSchema = exports.buildMentionFragmentJson = exports.withMentionsSchema = exports.mentionNodeSpec = exports.MENTION_NODE_NAME = exports.DEFAULT_EDITOR_TOOLBAR_ITEMS = exports.EditorToolbar = exports.NativeRichTextEditor = void 0;
4
+ var NativeRichTextEditor_1 = require("./NativeRichTextEditor");
5
+ Object.defineProperty(exports, "NativeRichTextEditor", { enumerable: true, get: function () { return NativeRichTextEditor_1.NativeRichTextEditor; } });
6
+ var EditorToolbar_1 = require("./EditorToolbar");
7
+ Object.defineProperty(exports, "EditorToolbar", { enumerable: true, get: function () { return EditorToolbar_1.EditorToolbar; } });
8
+ Object.defineProperty(exports, "DEFAULT_EDITOR_TOOLBAR_ITEMS", { enumerable: true, get: function () { return EditorToolbar_1.DEFAULT_EDITOR_TOOLBAR_ITEMS; } });
9
+ var addons_1 = require("./addons");
10
+ Object.defineProperty(exports, "MENTION_NODE_NAME", { enumerable: true, get: function () { return addons_1.MENTION_NODE_NAME; } });
11
+ Object.defineProperty(exports, "mentionNodeSpec", { enumerable: true, get: function () { return addons_1.mentionNodeSpec; } });
12
+ Object.defineProperty(exports, "withMentionsSchema", { enumerable: true, get: function () { return addons_1.withMentionsSchema; } });
13
+ Object.defineProperty(exports, "buildMentionFragmentJson", { enumerable: true, get: function () { return addons_1.buildMentionFragmentJson; } });
14
+ var schemas_1 = require("./schemas");
15
+ Object.defineProperty(exports, "tiptapSchema", { enumerable: true, get: function () { return schemas_1.tiptapSchema; } });
16
+ Object.defineProperty(exports, "prosemirrorSchema", { enumerable: true, get: function () { return schemas_1.prosemirrorSchema; } });
17
+ Object.defineProperty(exports, "IMAGE_NODE_NAME", { enumerable: true, get: function () { return schemas_1.IMAGE_NODE_NAME; } });
18
+ Object.defineProperty(exports, "imageNodeSpec", { enumerable: true, get: function () { return schemas_1.imageNodeSpec; } });
19
+ Object.defineProperty(exports, "withImagesSchema", { enumerable: true, get: function () { return schemas_1.withImagesSchema; } });
20
+ Object.defineProperty(exports, "buildImageFragmentJson", { enumerable: true, get: function () { return schemas_1.buildImageFragmentJson; } });
21
+ var YjsCollaboration_1 = require("./YjsCollaboration");
22
+ Object.defineProperty(exports, "createYjsCollaborationController", { enumerable: true, get: function () { return YjsCollaboration_1.createYjsCollaborationController; } });
23
+ Object.defineProperty(exports, "useYjsCollaboration", { enumerable: true, get: function () { return YjsCollaboration_1.useYjsCollaboration; } });
24
+ var NativeEditorBridge_1 = require("./NativeEditorBridge");
25
+ Object.defineProperty(exports, "encodeCollaborationStateBase64", { enumerable: true, get: function () { return NativeEditorBridge_1.encodeCollaborationStateBase64; } });
26
+ Object.defineProperty(exports, "decodeCollaborationStateBase64", { enumerable: true, get: function () { return NativeEditorBridge_1.decodeCollaborationStateBase64; } });
@@ -0,0 +1,35 @@
1
+ import type { DocumentJSON } from './NativeEditorBridge';
2
+ export interface AttrSpec {
3
+ default?: unknown;
4
+ }
5
+ export interface NodeSpec {
6
+ name: string;
7
+ content: string;
8
+ group?: string;
9
+ attrs?: Record<string, AttrSpec>;
10
+ role: string;
11
+ htmlTag?: string;
12
+ isVoid?: boolean;
13
+ }
14
+ export interface MarkSpec {
15
+ name: string;
16
+ attrs?: Record<string, AttrSpec>;
17
+ excludes?: string;
18
+ }
19
+ export interface SchemaDefinition {
20
+ nodes: NodeSpec[];
21
+ marks: MarkSpec[];
22
+ }
23
+ export interface ImageNodeAttributes {
24
+ src: string;
25
+ alt?: string | null;
26
+ title?: string | null;
27
+ width?: number | null;
28
+ height?: number | null;
29
+ }
30
+ export declare const IMAGE_NODE_NAME = "image";
31
+ export declare function imageNodeSpec(name?: string): NodeSpec;
32
+ export declare function withImagesSchema(schema: SchemaDefinition): SchemaDefinition;
33
+ export declare function buildImageFragmentJson(attrs: ImageNodeAttributes): DocumentJSON;
34
+ export declare const tiptapSchema: SchemaDefinition;
35
+ export declare const prosemirrorSchema: SchemaDefinition;
@@ -1,36 +1,56 @@
1
- export interface AttrSpec {
2
- default?: unknown;
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.prosemirrorSchema = exports.tiptapSchema = exports.IMAGE_NODE_NAME = void 0;
4
+ exports.imageNodeSpec = imageNodeSpec;
5
+ exports.withImagesSchema = withImagesSchema;
6
+ exports.buildImageFragmentJson = buildImageFragmentJson;
7
+ exports.IMAGE_NODE_NAME = 'image';
8
+ function imageNodeSpec(name = exports.IMAGE_NODE_NAME) {
9
+ return {
10
+ name,
11
+ content: '',
12
+ group: 'block',
13
+ attrs: {
14
+ src: {},
15
+ alt: { default: null },
16
+ title: { default: null },
17
+ width: { default: null },
18
+ height: { default: null },
19
+ },
20
+ role: 'block',
21
+ htmlTag: 'img',
22
+ isVoid: true,
23
+ };
3
24
  }
4
-
5
- export interface NodeSpec {
6
- name: string;
7
- content: string;
8
- group?: string;
9
- attrs?: Record<string, AttrSpec>;
10
- role: string;
11
- htmlTag?: string;
12
- isVoid?: boolean;
25
+ function withImagesSchema(schema) {
26
+ const hasImageNode = schema.nodes.some((node) => node.name === exports.IMAGE_NODE_NAME);
27
+ if (hasImageNode) {
28
+ return schema;
29
+ }
30
+ return {
31
+ ...schema,
32
+ nodes: [...schema.nodes, imageNodeSpec()],
33
+ };
13
34
  }
14
-
15
- export interface MarkSpec {
16
- name: string;
17
- attrs?: Record<string, AttrSpec>;
18
- excludes?: string;
35
+ function buildImageFragmentJson(attrs) {
36
+ return {
37
+ type: 'doc',
38
+ content: [
39
+ {
40
+ type: exports.IMAGE_NODE_NAME,
41
+ attrs,
42
+ },
43
+ ],
44
+ };
19
45
  }
20
-
21
- export interface SchemaDefinition {
22
- nodes: NodeSpec[];
23
- marks: MarkSpec[];
24
- }
25
-
26
- const MARKS: MarkSpec[] = [
46
+ const MARKS = [
27
47
  { name: 'bold' },
28
48
  { name: 'italic' },
29
49
  { name: 'underline' },
30
50
  { name: 'strike' },
51
+ { name: 'link', attrs: { href: {} } },
31
52
  ];
32
-
33
- export const tiptapSchema: SchemaDefinition = {
53
+ exports.tiptapSchema = {
34
54
  nodes: [
35
55
  {
36
56
  name: 'doc',
@@ -44,6 +64,13 @@ export const tiptapSchema: SchemaDefinition = {
44
64
  role: 'textBlock',
45
65
  htmlTag: 'p',
46
66
  },
67
+ {
68
+ name: 'blockquote',
69
+ content: 'block+',
70
+ group: 'block',
71
+ role: 'block',
72
+ htmlTag: 'blockquote',
73
+ },
47
74
  {
48
75
  name: 'bulletList',
49
76
  content: 'listItem+',
@@ -81,6 +108,7 @@ export const tiptapSchema: SchemaDefinition = {
81
108
  htmlTag: 'hr',
82
109
  isVoid: true,
83
110
  },
111
+ imageNodeSpec(),
84
112
  {
85
113
  name: 'text',
86
114
  content: '',
@@ -90,8 +118,7 @@ export const tiptapSchema: SchemaDefinition = {
90
118
  ],
91
119
  marks: MARKS,
92
120
  };
93
-
94
- export const prosemirrorSchema: SchemaDefinition = {
121
+ exports.prosemirrorSchema = {
95
122
  nodes: [
96
123
  {
97
124
  name: 'doc',
@@ -105,6 +132,13 @@ export const prosemirrorSchema: SchemaDefinition = {
105
132
  role: 'textBlock',
106
133
  htmlTag: 'p',
107
134
  },
135
+ {
136
+ name: 'blockquote',
137
+ content: 'block+',
138
+ group: 'block',
139
+ role: 'block',
140
+ htmlTag: 'blockquote',
141
+ },
108
142
  {
109
143
  name: 'bullet_list',
110
144
  content: 'list_item+',
@@ -142,6 +176,7 @@ export const prosemirrorSchema: SchemaDefinition = {
142
176
  htmlTag: 'hr',
143
177
  isVoid: true,
144
178
  },
179
+ imageNodeSpec('image'),
145
180
  {
146
181
  name: 'text',
147
182
  content: '',
@@ -0,0 +1,40 @@
1
+ import { NativeEditorBridge, type ActiveState, type HistoryState, type RenderElement, type Selection } from './NativeEditorBridge';
2
+ export interface UseNativeEditorOptions {
3
+ /** Maximum character length. Omit for no limit. */
4
+ maxLength?: number;
5
+ /** Initial HTML content to load after creation. */
6
+ initialHtml?: string;
7
+ /** Called when content changes after an editing operation. */
8
+ onChange?: (html: string) => void;
9
+ /** Called when selection changes. */
10
+ onSelectionChange?: (selection: Selection) => void;
11
+ }
12
+ export interface UseNativeEditorReturn {
13
+ /** The underlying bridge instance, or null before creation. */
14
+ bridge: NativeEditorBridge | null;
15
+ /** Whether the editor has been created and is ready. */
16
+ isReady: boolean;
17
+ /** Current selection state. */
18
+ selection: Selection;
19
+ /** Currently active marks/nodes at the selection. */
20
+ activeState: ActiveState;
21
+ /** Current undo/redo availability. */
22
+ historyState: HistoryState;
23
+ /** Current render elements. */
24
+ renderElements: RenderElement[];
25
+ /** Toggle a mark (e.g. 'bold', 'italic', 'underline'). */
26
+ toggleMark: (markType: string) => void;
27
+ /** Undo the last operation. */
28
+ undo: () => void;
29
+ /** Redo the last undone operation. */
30
+ redo: () => void;
31
+ /** Toggle blockquote wrapping around the current block selection. */
32
+ toggleBlockquote: () => void;
33
+ /** Insert text at a position. */
34
+ insertText: (pos: number, text: string) => void;
35
+ /** Delete a range [from, to). */
36
+ deleteRange: (from: number, to: number) => void;
37
+ /** Get the current HTML content. */
38
+ getHtml: () => string;
39
+ }
40
+ export declare function useNativeEditor(options?: UseNativeEditorOptions): UseNativeEditorReturn;
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useNativeEditor = useNativeEditor;
4
+ const react_1 = require("react");
5
+ const NativeEditorBridge_1 = require("./NativeEditorBridge");
6
+ const DEFAULT_SELECTION = { type: 'text', anchor: 0, head: 0 };
7
+ const DEFAULT_ACTIVE_STATE = {
8
+ marks: {},
9
+ markAttrs: {},
10
+ nodes: {},
11
+ commands: {},
12
+ allowedMarks: [],
13
+ insertableNodes: [],
14
+ };
15
+ const DEFAULT_HISTORY_STATE = { canUndo: false, canRedo: false };
16
+ function useNativeEditor(options = {}) {
17
+ const { maxLength, initialHtml, onChange, onSelectionChange } = options;
18
+ const bridgeRef = (0, react_1.useRef)(null);
19
+ const onChangeRef = (0, react_1.useRef)(onChange);
20
+ onChangeRef.current = onChange;
21
+ const onSelectionChangeRef = (0, react_1.useRef)(onSelectionChange);
22
+ onSelectionChangeRef.current = onSelectionChange;
23
+ const [isReady, setIsReady] = (0, react_1.useState)(false);
24
+ const [selection, setSelection] = (0, react_1.useState)(DEFAULT_SELECTION);
25
+ const [activeState, setActiveState] = (0, react_1.useState)(DEFAULT_ACTIVE_STATE);
26
+ const [historyState, setHistoryState] = (0, react_1.useState)(DEFAULT_HISTORY_STATE);
27
+ const [renderElements, setRenderElements] = (0, react_1.useState)([]);
28
+ const syncStateFromUpdate = (0, react_1.useCallback)((update) => {
29
+ if (!update)
30
+ return;
31
+ setRenderElements(update.renderElements);
32
+ setSelection(update.selection);
33
+ setActiveState(update.activeState);
34
+ setHistoryState(update.historyState);
35
+ }, []);
36
+ const applyUpdate = (0, react_1.useCallback)((update) => {
37
+ if (!update)
38
+ return;
39
+ syncStateFromUpdate(update);
40
+ onSelectionChangeRef.current?.(update.selection);
41
+ // Fetch current HTML and notify onChange
42
+ if (onChangeRef.current && bridgeRef.current && !bridgeRef.current.isDestroyed) {
43
+ const html = bridgeRef.current.getHtml();
44
+ onChangeRef.current(html);
45
+ }
46
+ }, [syncStateFromUpdate]);
47
+ (0, react_1.useEffect)(() => {
48
+ const bridge = NativeEditorBridge_1.NativeEditorBridge.create(maxLength != null ? { maxLength } : undefined);
49
+ bridgeRef.current = bridge;
50
+ if (initialHtml) {
51
+ bridge.setHtml(initialHtml);
52
+ }
53
+ syncStateFromUpdate(bridge.getCurrentState());
54
+ setIsReady(true);
55
+ return () => {
56
+ bridge.destroy();
57
+ bridgeRef.current = null;
58
+ setIsReady(false);
59
+ };
60
+ }, [maxLength, initialHtml, syncStateFromUpdate]);
61
+ const toggleMark = (0, react_1.useCallback)((markType) => {
62
+ if (!bridgeRef.current || bridgeRef.current.isDestroyed)
63
+ return;
64
+ const update = bridgeRef.current.toggleMark(markType);
65
+ applyUpdate(update);
66
+ }, [applyUpdate]);
67
+ const undo = (0, react_1.useCallback)(() => {
68
+ if (!bridgeRef.current || bridgeRef.current.isDestroyed)
69
+ return;
70
+ const update = bridgeRef.current.undo();
71
+ applyUpdate(update);
72
+ }, [applyUpdate]);
73
+ const redo = (0, react_1.useCallback)(() => {
74
+ if (!bridgeRef.current || bridgeRef.current.isDestroyed)
75
+ return;
76
+ const update = bridgeRef.current.redo();
77
+ applyUpdate(update);
78
+ }, [applyUpdate]);
79
+ const toggleBlockquote = (0, react_1.useCallback)(() => {
80
+ if (!bridgeRef.current || bridgeRef.current.isDestroyed)
81
+ return;
82
+ const update = bridgeRef.current.toggleBlockquote();
83
+ applyUpdate(update);
84
+ }, [applyUpdate]);
85
+ const insertText = (0, react_1.useCallback)((pos, text) => {
86
+ if (!bridgeRef.current || bridgeRef.current.isDestroyed)
87
+ return;
88
+ const update = bridgeRef.current.insertText(pos, text);
89
+ applyUpdate(update);
90
+ }, [applyUpdate]);
91
+ const deleteRange = (0, react_1.useCallback)((from, to) => {
92
+ if (!bridgeRef.current || bridgeRef.current.isDestroyed)
93
+ return;
94
+ const update = bridgeRef.current.deleteRange(from, to);
95
+ applyUpdate(update);
96
+ }, [applyUpdate]);
97
+ const getHtml = (0, react_1.useCallback)(() => {
98
+ if (!bridgeRef.current || bridgeRef.current.isDestroyed)
99
+ return '';
100
+ return bridgeRef.current.getHtml();
101
+ }, []);
102
+ return {
103
+ bridge: bridgeRef.current,
104
+ isReady,
105
+ selection,
106
+ activeState,
107
+ historyState,
108
+ renderElements,
109
+ toggleMark,
110
+ undo,
111
+ redo,
112
+ toggleBlockquote,
113
+ insertText,
114
+ deleteRange,
115
+ getHtml,
116
+ };
117
+ }
@@ -131,12 +131,18 @@ final class MentionSuggestionChipButton: UIButton {
131
131
  private let subtitleLabelView = UILabel()
132
132
  private let stackView = UIStackView()
133
133
  private var theme: EditorMentionTheme?
134
+ private var toolbarAppearance: EditorToolbarAppearance = .custom
134
135
 
135
136
  let suggestion: NativeMentionSuggestion
136
137
 
137
- init(suggestion: NativeMentionSuggestion, theme: EditorMentionTheme?) {
138
+ init(
139
+ suggestion: NativeMentionSuggestion,
140
+ theme: EditorMentionTheme?,
141
+ toolbarAppearance: EditorToolbarAppearance = .custom
142
+ ) {
138
143
  self.suggestion = suggestion
139
144
  self.theme = theme
145
+ self.toolbarAppearance = toolbarAppearance
140
146
  super.init(frame: .zero)
141
147
  translatesAutoresizingMaskIntoConstraints = false
142
148
  layer.cornerRadius = 12
@@ -179,7 +185,7 @@ final class MentionSuggestionChipButton: UIButton {
179
185
 
180
186
  addTarget(self, action: #selector(handleTouchDown), for: [.touchDown, .touchDragEnter])
181
187
  addTarget(self, action: #selector(handleTouchUp), for: [.touchCancel, .touchDragExit, .touchUpInside, .touchUpOutside])
182
- apply(theme: theme)
188
+ apply(theme: theme, toolbarAppearance: toolbarAppearance)
183
189
  updateAppearance(highlighted: false)
184
190
  }
185
191
 
@@ -187,8 +193,9 @@ final class MentionSuggestionChipButton: UIButton {
187
193
  return nil
188
194
  }
189
195
 
190
- func apply(theme: EditorMentionTheme?) {
196
+ func apply(theme: EditorMentionTheme?, toolbarAppearance: EditorToolbarAppearance = .custom) {
191
197
  self.theme = theme
198
+ self.toolbarAppearance = toolbarAppearance
192
199
  layer.cornerRadius = theme?.borderRadius ?? 12
193
200
  layer.borderColor = (theme?.borderColor ?? UIColor.clear).cgColor
194
201
  layer.borderWidth = theme?.borderWidth ?? 0
@@ -211,6 +218,18 @@ final class MentionSuggestionChipButton: UIButton {
211
218
  }
212
219
 
213
220
  private func updateAppearance(highlighted: Bool) {
221
+ if toolbarAppearance == .native {
222
+ layer.cornerRadius = 18
223
+ layer.borderColor = UIColor.clear.cgColor
224
+ layer.borderWidth = 0
225
+ backgroundColor = highlighted
226
+ ? UIColor.white.withAlphaComponent(0.18)
227
+ : .clear
228
+ titleLabelView.textColor = .label
229
+ subtitleLabelView.textColor = .secondaryLabel
230
+ return
231
+ }
232
+
214
233
  backgroundColor = highlighted
215
234
  ? (theme?.optionHighlightedBackgroundColor ?? UIColor.systemBlue.withAlphaComponent(0.12))
216
235
  : (theme?.backgroundColor ?? UIColor.secondarySystemBackground)
@@ -225,4 +244,8 @@ final class MentionSuggestionChipButton: UIButton {
225
244
  && !titleLabelView.isUserInteractionEnabled
226
245
  && !subtitleLabelView.isUserInteractionEnabled
227
246
  }
247
+
248
+ func usesNativeAppearanceForTesting() -> Bool {
249
+ toolbarAppearance == .native
250
+ }
228
251
  }
@@ -8,32 +8,32 @@
8
8
  <key>BinaryPath</key>
9
9
  <string>libeditor_core.a</string>
10
10
  <key>LibraryIdentifier</key>
11
- <string>ios-arm64_x86_64-simulator</string>
11
+ <string>ios-arm64</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>
18
17
  </array>
19
18
  <key>SupportedPlatform</key>
20
19
  <string>ios</string>
21
- <key>SupportedPlatformVariant</key>
22
- <string>simulator</string>
23
20
  </dict>
24
21
  <dict>
25
22
  <key>BinaryPath</key>
26
23
  <string>libeditor_core.a</string>
27
24
  <key>LibraryIdentifier</key>
28
- <string>ios-arm64</string>
25
+ <string>ios-arm64_x86_64-simulator</string>
29
26
  <key>LibraryPath</key>
30
27
  <string>libeditor_core.a</string>
31
28
  <key>SupportedArchitectures</key>
32
29
  <array>
33
30
  <string>arm64</string>
31
+ <string>x86_64</string>
34
32
  </array>
35
33
  <key>SupportedPlatform</key>
36
34
  <string>ios</string>
35
+ <key>SupportedPlatformVariant</key>
36
+ <string>simulator</string>
37
37
  </dict>
38
38
  </array>
39
39
  <key>CFBundlePackageType</key>