@openeditor/react-native-prose-editor 0.0.2
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/LICENSE +160 -0
- package/README.md +180 -0
- package/android/build.gradle +69 -0
- package/android/consumer-rules.pro +18 -0
- package/android/src/androidTest/AndroidManifest.xml +8 -0
- package/android/src/androidTest/java/com/apollohg/editor/MeasureHeightInstrumentedTest.kt +53 -0
- package/android/src/androidTest/java/com/apollohg/editor/NativeDeviceCollaborationInitialSyncTest.kt +241 -0
- package/android/src/androidTest/java/com/apollohg/editor/NativeDeviceImeRegressionTest.kt +338 -0
- package/android/src/androidTest/java/com/apollohg/editor/NativeDeviceOutsideTapTest.kt +789 -0
- package/android/src/androidTest/java/com/apollohg/editor/NativeDevicePerformanceTest.kt +350 -0
- package/android/src/androidTest/java/com/apollohg/editor/NativeEditorOutsideTapActivity.kt +5 -0
- package/android/src/main/assets/editor-icons/MaterialIcons.json +2236 -0
- package/android/src/main/assets/editor-icons/MaterialIcons.ttf +0 -0
- package/android/src/main/java/com/apollohg/editor/CaretGeometry.kt +50 -0
- package/android/src/main/java/com/apollohg/editor/EditorAddons.kt +135 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditText.kt +4527 -0
- package/android/src/main/java/com/apollohg/editor/EditorHeightBehavior.kt +14 -0
- package/android/src/main/java/com/apollohg/editor/EditorInputConnection.kt +999 -0
- package/android/src/main/java/com/apollohg/editor/EditorTheme.kt +474 -0
- package/android/src/main/java/com/apollohg/editor/ImageResizeOverlayView.kt +199 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoView.kt +3137 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorModule.kt +561 -0
- package/android/src/main/java/com/apollohg/editor/NativeProseViewerExpoView.kt +315 -0
- package/android/src/main/java/com/apollohg/editor/NativeToolbar.kt +1413 -0
- package/android/src/main/java/com/apollohg/editor/PositionBridge.kt +133 -0
- package/android/src/main/java/com/apollohg/editor/RemoteSelectionOverlayView.kt +315 -0
- package/android/src/main/java/com/apollohg/editor/RenderBridge.kt +2172 -0
- package/android/src/main/java/com/apollohg/editor/RichTextEditorView.kt +399 -0
- package/android/src/sharedTest/java/com/apollohg/editor/NativePerformanceSupport.kt +482 -0
- package/android/src/test/java/com/apollohg/editor/CaretGeometryTest.kt +137 -0
- package/android/src/test/java/com/apollohg/editor/EditorEditTextHardwareKeyTest.kt +321 -0
- package/android/src/test/java/com/apollohg/editor/EditorInputConnectionTest.kt +4202 -0
- package/android/src/test/java/com/apollohg/editor/NativeEditorExpoViewTest.kt +2581 -0
- package/android/src/test/java/com/apollohg/editor/NativeEditorModuleTest.kt +27 -0
- package/android/src/test/java/com/apollohg/editor/NativePerformanceTest.kt +197 -0
- package/android/src/test/java/com/apollohg/editor/NativeProseViewerExpoViewTest.kt +84 -0
- package/android/src/test/java/com/apollohg/editor/NativeToolbarTest.kt +544 -0
- package/android/src/test/java/com/apollohg/editor/PositionBridgeTest.kt +461 -0
- package/android/src/test/java/com/apollohg/editor/RenderBridgeTest.kt +2054 -0
- package/android/src/test/java/com/apollohg/editor/RichTextEditorViewTest.kt +1367 -0
- package/app.plugin.js +62 -0
- package/dist/EditorTheme.d.ts +113 -0
- package/dist/EditorTheme.js +29 -0
- package/dist/EditorToolbar.d.ts +191 -0
- package/dist/EditorToolbar.js +1095 -0
- package/dist/NativeEditorBridge.d.ts +325 -0
- package/dist/NativeEditorBridge.js +933 -0
- package/dist/NativeProseViewer.d.ts +53 -0
- package/dist/NativeProseViewer.js +480 -0
- package/dist/NativeRichTextEditor.d.ts +183 -0
- package/dist/NativeRichTextEditor.js +2574 -0
- package/dist/YjsCollaboration.d.ts +90 -0
- package/dist/YjsCollaboration.js +743 -0
- package/dist/addons.d.ts +100 -0
- package/dist/addons.js +82 -0
- package/dist/heightCache.d.ts +6 -0
- package/dist/heightCache.js +45 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +30 -0
- package/dist/schemas.d.ts +37 -0
- package/dist/schemas.js +263 -0
- package/dist/useNativeEditor.d.ts +42 -0
- package/dist/useNativeEditor.js +124 -0
- package/expo-module.config.json +9 -0
- package/ios/EditorAddons.swift +327 -0
- package/ios/EditorCore.xcframework/Info.plist +44 -0
- 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/EditorLayoutManager.swift +774 -0
- package/ios/EditorTheme.swift +515 -0
- package/ios/Generated_editor_core.swift +1513 -0
- package/ios/NativeEditorExpoView.swift +3593 -0
- package/ios/NativeEditorModule.swift +624 -0
- package/ios/NativeProseViewerExpoView.swift +276 -0
- package/ios/PositionBridge.swift +558 -0
- package/ios/ReactNativeProseEditor.podspec +49 -0
- package/ios/RenderBridge.swift +1708 -0
- package/ios/RichTextEditorView.swift +6150 -0
- package/ios/Tests/NativePerformanceTests.swift +687 -0
- package/ios/Tests/PositionBridgeTests.swift +706 -0
- package/ios/Tests/RenderBridgeTests.swift +2236 -0
- package/ios/Tests/RichTextEditorViewTests.swift +6548 -0
- package/ios/editor_coreFFI/editor_coreFFI.h +1289 -0
- package/ios/editor_coreFFI/module.modulemap +7 -0
- package/ios/editor_coreFFI.h +904 -0
- package/ios/editor_coreFFI.modulemap +7 -0
- package/package.json +74 -0
- 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/rust/bindings/kotlin/uniffi/editor_core/editor_core.kt +2563 -0
|
@@ -0,0 +1,124 @@
|
|
|
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 toggleHeading = (0, react_1.useCallback)((level) => {
|
|
86
|
+
if (!bridgeRef.current || bridgeRef.current.isDestroyed)
|
|
87
|
+
return;
|
|
88
|
+
const update = bridgeRef.current.toggleHeading(level);
|
|
89
|
+
applyUpdate(update);
|
|
90
|
+
}, [applyUpdate]);
|
|
91
|
+
const insertText = (0, react_1.useCallback)((pos, text) => {
|
|
92
|
+
if (!bridgeRef.current || bridgeRef.current.isDestroyed)
|
|
93
|
+
return;
|
|
94
|
+
const update = bridgeRef.current.insertText(pos, text);
|
|
95
|
+
applyUpdate(update);
|
|
96
|
+
}, [applyUpdate]);
|
|
97
|
+
const deleteRange = (0, react_1.useCallback)((from, to) => {
|
|
98
|
+
if (!bridgeRef.current || bridgeRef.current.isDestroyed)
|
|
99
|
+
return;
|
|
100
|
+
const update = bridgeRef.current.deleteRange(from, to);
|
|
101
|
+
applyUpdate(update);
|
|
102
|
+
}, [applyUpdate]);
|
|
103
|
+
const getHtml = (0, react_1.useCallback)(() => {
|
|
104
|
+
if (!bridgeRef.current || bridgeRef.current.isDestroyed)
|
|
105
|
+
return '';
|
|
106
|
+
return bridgeRef.current.getHtml();
|
|
107
|
+
}, []);
|
|
108
|
+
return {
|
|
109
|
+
bridge: bridgeRef.current,
|
|
110
|
+
isReady,
|
|
111
|
+
selection,
|
|
112
|
+
activeState,
|
|
113
|
+
historyState,
|
|
114
|
+
renderElements,
|
|
115
|
+
toggleMark,
|
|
116
|
+
undo,
|
|
117
|
+
redo,
|
|
118
|
+
toggleBlockquote,
|
|
119
|
+
toggleHeading,
|
|
120
|
+
insertText,
|
|
121
|
+
deleteRange,
|
|
122
|
+
getHtml,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
|
|
3
|
+
struct NativeMentionSuggestion {
|
|
4
|
+
let key: String
|
|
5
|
+
let title: String
|
|
6
|
+
let subtitle: String?
|
|
7
|
+
let label: String
|
|
8
|
+
let attrs: [String: Any]
|
|
9
|
+
|
|
10
|
+
init?(dictionary: [String: Any]) {
|
|
11
|
+
guard let key = dictionary["key"] as? String,
|
|
12
|
+
let title = dictionary["title"] as? String,
|
|
13
|
+
let label = dictionary["label"] as? String
|
|
14
|
+
else {
|
|
15
|
+
return nil
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
self.key = key
|
|
19
|
+
self.title = title
|
|
20
|
+
self.subtitle = dictionary["subtitle"] as? String
|
|
21
|
+
self.label = label
|
|
22
|
+
self.attrs = dictionary["attrs"] as? [String: Any] ?? [:]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
struct NativeMentionsAddonConfig {
|
|
27
|
+
let trigger: String
|
|
28
|
+
let suggestions: [NativeMentionSuggestion]
|
|
29
|
+
let theme: EditorMentionTheme?
|
|
30
|
+
let resolveSelectionAttrs: Bool
|
|
31
|
+
let resolveTheme: Bool
|
|
32
|
+
|
|
33
|
+
init?(dictionary: [String: Any]) {
|
|
34
|
+
let trigger = (dictionary["trigger"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
35
|
+
self.trigger = (trigger?.isEmpty == false ? trigger : "@") ?? "@"
|
|
36
|
+
self.suggestions = ((dictionary["suggestions"] as? [[String: Any]]) ?? []).compactMap(NativeMentionSuggestion.init(dictionary:))
|
|
37
|
+
self.resolveSelectionAttrs = dictionary["resolveSelectionAttrs"] as? Bool ?? false
|
|
38
|
+
self.resolveTheme = dictionary["resolveTheme"] as? Bool ?? false
|
|
39
|
+
if let theme = dictionary["theme"] as? [String: Any] {
|
|
40
|
+
self.theme = EditorMentionTheme(dictionary: theme)
|
|
41
|
+
} else {
|
|
42
|
+
self.theme = nil
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
struct NativeEditorAddons {
|
|
48
|
+
let mentions: NativeMentionsAddonConfig?
|
|
49
|
+
|
|
50
|
+
static func from(json: String?) -> NativeEditorAddons {
|
|
51
|
+
guard let json,
|
|
52
|
+
let data = json.data(using: .utf8),
|
|
53
|
+
let raw = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
|
|
54
|
+
else {
|
|
55
|
+
return NativeEditorAddons(mentions: nil)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return NativeEditorAddons(
|
|
59
|
+
mentions: (raw["mentions"] as? [String: Any]).flatMap(NativeMentionsAddonConfig.init(dictionary:))
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
struct MentionQueryState: Equatable {
|
|
65
|
+
let query: String
|
|
66
|
+
let trigger: String
|
|
67
|
+
let anchor: UInt32
|
|
68
|
+
let head: UInt32
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
func isMentionIdentifierScalar(_ scalar: Unicode.Scalar) -> Bool {
|
|
72
|
+
CharacterSet.alphanumerics.contains(scalar) || scalar == "_" || scalar == "-"
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
func resolveMentionQueryState(
|
|
76
|
+
in text: String,
|
|
77
|
+
cursorScalar: UInt32,
|
|
78
|
+
trigger: String,
|
|
79
|
+
isCaretInsideMention: Bool
|
|
80
|
+
) -> MentionQueryState? {
|
|
81
|
+
guard !isCaretInsideMention else { return nil }
|
|
82
|
+
|
|
83
|
+
let scalars = Array(text.unicodeScalars)
|
|
84
|
+
let scalarCount = UInt32(scalars.count)
|
|
85
|
+
guard cursorScalar <= scalarCount else { return nil }
|
|
86
|
+
|
|
87
|
+
let triggerScalars = Array(trigger.unicodeScalars)
|
|
88
|
+
guard triggerScalars.count == 1, let triggerScalar = triggerScalars.first else {
|
|
89
|
+
return nil
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
var start = Int(cursorScalar)
|
|
93
|
+
while start > 0 {
|
|
94
|
+
let previous = scalars[start - 1]
|
|
95
|
+
if previous.properties.isWhitespace
|
|
96
|
+
|| previous == "\n"
|
|
97
|
+
|| previous == "\u{FFFC}"
|
|
98
|
+
|| (!isMentionIdentifierScalar(previous) && previous != triggerScalar)
|
|
99
|
+
{
|
|
100
|
+
break
|
|
101
|
+
}
|
|
102
|
+
start -= 1
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
guard start < scalars.count, scalars[start] == triggerScalar else {
|
|
106
|
+
return nil
|
|
107
|
+
}
|
|
108
|
+
if start > 0 {
|
|
109
|
+
let previous = scalars[start - 1]
|
|
110
|
+
if isMentionIdentifierScalar(previous) {
|
|
111
|
+
return nil
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let queryStart = start + 1
|
|
116
|
+
let cursor = Int(cursorScalar)
|
|
117
|
+
guard queryStart <= cursor else { return nil }
|
|
118
|
+
let query = String(String.UnicodeScalarView(scalars[queryStart..<cursor]))
|
|
119
|
+
if query.unicodeScalars.contains(where: {
|
|
120
|
+
$0.properties.isWhitespace || $0 == "\n" || $0 == "\u{FFFC}"
|
|
121
|
+
}) {
|
|
122
|
+
return nil
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return MentionQueryState(
|
|
126
|
+
query: query,
|
|
127
|
+
trigger: trigger,
|
|
128
|
+
anchor: UInt32(start),
|
|
129
|
+
head: cursorScalar
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
final class MentionSuggestionChipButton: UIButton {
|
|
134
|
+
private static let horizontalContentInset: CGFloat = 8
|
|
135
|
+
private static let verticalContentInset: CGFloat = 8
|
|
136
|
+
|
|
137
|
+
private let titleLabelView = UILabel()
|
|
138
|
+
private let subtitleLabelView = UILabel()
|
|
139
|
+
private let stackView = UIStackView()
|
|
140
|
+
private var theme: EditorMentionTheme?
|
|
141
|
+
private var toolbarAppearance: EditorToolbarAppearance = .custom
|
|
142
|
+
|
|
143
|
+
let suggestion: NativeMentionSuggestion
|
|
144
|
+
|
|
145
|
+
init(
|
|
146
|
+
suggestion: NativeMentionSuggestion,
|
|
147
|
+
theme: EditorMentionTheme?,
|
|
148
|
+
toolbarAppearance: EditorToolbarAppearance = .custom
|
|
149
|
+
) {
|
|
150
|
+
self.suggestion = suggestion
|
|
151
|
+
self.theme = theme
|
|
152
|
+
self.toolbarAppearance = toolbarAppearance
|
|
153
|
+
super.init(frame: .zero)
|
|
154
|
+
translatesAutoresizingMaskIntoConstraints = false
|
|
155
|
+
layer.cornerRadius = 12
|
|
156
|
+
clipsToBounds = true
|
|
157
|
+
if #available(iOS 15.0, *) {
|
|
158
|
+
var configuration = UIButton.Configuration.plain()
|
|
159
|
+
configuration.contentInsets = .zero
|
|
160
|
+
self.configuration = configuration
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
titleLabelView.translatesAutoresizingMaskIntoConstraints = false
|
|
164
|
+
titleLabelView.isUserInteractionEnabled = false
|
|
165
|
+
titleLabelView.font = .systemFont(ofSize: 14, weight: .semibold)
|
|
166
|
+
titleLabelView.text = suggestion.label
|
|
167
|
+
titleLabelView.numberOfLines = 1
|
|
168
|
+
|
|
169
|
+
subtitleLabelView.translatesAutoresizingMaskIntoConstraints = false
|
|
170
|
+
subtitleLabelView.isUserInteractionEnabled = false
|
|
171
|
+
subtitleLabelView.font = .systemFont(ofSize: 12)
|
|
172
|
+
subtitleLabelView.text = suggestion.subtitle
|
|
173
|
+
subtitleLabelView.numberOfLines = 1
|
|
174
|
+
subtitleLabelView.isHidden = suggestion.subtitle == nil
|
|
175
|
+
|
|
176
|
+
stackView.translatesAutoresizingMaskIntoConstraints = false
|
|
177
|
+
stackView.isUserInteractionEnabled = false
|
|
178
|
+
stackView.axis = .vertical
|
|
179
|
+
stackView.alignment = .fill
|
|
180
|
+
stackView.spacing = 1
|
|
181
|
+
stackView.addArrangedSubview(titleLabelView)
|
|
182
|
+
stackView.addArrangedSubview(subtitleLabelView)
|
|
183
|
+
addSubview(stackView)
|
|
184
|
+
|
|
185
|
+
NSLayoutConstraint.activate([
|
|
186
|
+
stackView.topAnchor.constraint(equalTo: topAnchor, constant: Self.verticalContentInset),
|
|
187
|
+
stackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: Self.horizontalContentInset),
|
|
188
|
+
stackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -Self.horizontalContentInset),
|
|
189
|
+
stackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -Self.verticalContentInset),
|
|
190
|
+
heightAnchor.constraint(greaterThanOrEqualToConstant: 40),
|
|
191
|
+
])
|
|
192
|
+
|
|
193
|
+
addTarget(self, action: #selector(handleTouchDown), for: [.touchDown, .touchDragEnter])
|
|
194
|
+
addTarget(self, action: #selector(handleTouchUp), for: [.touchCancel, .touchDragExit, .touchUpInside, .touchUpOutside])
|
|
195
|
+
apply(theme: theme, toolbarAppearance: toolbarAppearance)
|
|
196
|
+
updateAppearance(highlighted: false)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
required init?(coder: NSCoder) {
|
|
200
|
+
return nil
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
func apply(theme: EditorMentionTheme?, toolbarAppearance: EditorToolbarAppearance = .custom) {
|
|
204
|
+
self.theme = theme
|
|
205
|
+
self.toolbarAppearance = toolbarAppearance
|
|
206
|
+
layer.cornerRadius = theme?.borderRadius ?? 12
|
|
207
|
+
layer.borderColor = (theme?.borderColor ?? UIColor.clear).cgColor
|
|
208
|
+
layer.borderWidth = theme?.borderWidth ?? 0
|
|
209
|
+
subtitleLabelView.isHidden = suggestion.subtitle == nil
|
|
210
|
+
updateAppearance(highlighted: isHighlighted)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
override var isHighlighted: Bool {
|
|
214
|
+
didSet {
|
|
215
|
+
updateAppearance(highlighted: isHighlighted)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
override func tintColorDidChange() {
|
|
220
|
+
super.tintColorDidChange()
|
|
221
|
+
if toolbarAppearance == .native {
|
|
222
|
+
updateAppearance(highlighted: isHighlighted)
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
@objc private func handleTouchDown() {
|
|
227
|
+
updateAppearance(highlighted: true)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
@objc private func handleTouchUp() {
|
|
231
|
+
updateAppearance(highlighted: false)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private func updateAppearance(highlighted: Bool) {
|
|
235
|
+
if toolbarAppearance == .native {
|
|
236
|
+
layer.cornerRadius = 18
|
|
237
|
+
layer.borderColor = UIColor.clear.cgColor
|
|
238
|
+
layer.borderWidth = 0
|
|
239
|
+
#if compiler(>=6.2)
|
|
240
|
+
if #available(iOS 26.0, *) {
|
|
241
|
+
stackView.isHidden = true
|
|
242
|
+
backgroundColor = .clear
|
|
243
|
+
var configuration = highlighted
|
|
244
|
+
? UIButton.Configuration.prominentGlass()
|
|
245
|
+
: UIButton.Configuration.glass()
|
|
246
|
+
configuration.cornerStyle = .capsule
|
|
247
|
+
configuration.contentInsets = NSDirectionalEdgeInsets(
|
|
248
|
+
top: Self.verticalContentInset,
|
|
249
|
+
leading: Self.horizontalContentInset,
|
|
250
|
+
bottom: Self.verticalContentInset,
|
|
251
|
+
trailing: Self.horizontalContentInset
|
|
252
|
+
)
|
|
253
|
+
configuration.title = suggestion.label
|
|
254
|
+
configuration.subtitle = suggestion.subtitle
|
|
255
|
+
configuration.titleTextAttributesTransformer = UIConfigurationTextAttributesTransformer { incoming in
|
|
256
|
+
var outgoing = incoming
|
|
257
|
+
outgoing.font = .systemFont(ofSize: 14, weight: .semibold)
|
|
258
|
+
return outgoing
|
|
259
|
+
}
|
|
260
|
+
self.configuration = configuration
|
|
261
|
+
return
|
|
262
|
+
}
|
|
263
|
+
#endif
|
|
264
|
+
stackView.isHidden = false
|
|
265
|
+
backgroundColor = highlighted
|
|
266
|
+
? UIColor.white.withAlphaComponent(0.18)
|
|
267
|
+
: .clear
|
|
268
|
+
titleLabelView.textColor = tintColor
|
|
269
|
+
subtitleLabelView.textColor = tintColor.withAlphaComponent(0.72)
|
|
270
|
+
return
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
stackView.isHidden = false
|
|
274
|
+
if #available(iOS 15.0, *) {
|
|
275
|
+
var configuration = UIButton.Configuration.plain()
|
|
276
|
+
configuration.contentInsets = .zero
|
|
277
|
+
self.configuration = configuration
|
|
278
|
+
}
|
|
279
|
+
backgroundColor = highlighted
|
|
280
|
+
? (theme?.optionHighlightedBackgroundColor ?? UIColor.systemBlue.withAlphaComponent(0.12))
|
|
281
|
+
: (theme?.backgroundColor ?? UIColor.secondarySystemBackground)
|
|
282
|
+
titleLabelView.textColor = highlighted
|
|
283
|
+
? (theme?.optionHighlightedTextColor ?? theme?.optionTextColor ?? .label)
|
|
284
|
+
: (theme?.optionTextColor ?? theme?.textColor ?? .label)
|
|
285
|
+
subtitleLabelView.textColor = theme?.optionSecondaryTextColor ?? .secondaryLabel
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
func contentViewsAllowTouchPassthroughForTesting() -> Bool {
|
|
289
|
+
!stackView.isUserInteractionEnabled
|
|
290
|
+
&& !titleLabelView.isUserInteractionEnabled
|
|
291
|
+
&& !subtitleLabelView.isUserInteractionEnabled
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
func usesNativeAppearanceForTesting() -> Bool {
|
|
295
|
+
toolbarAppearance == .native
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
func titleTextColorForTesting() -> UIColor? {
|
|
299
|
+
titleLabelView.textColor
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
func subtitleTextColorForTesting() -> UIColor? {
|
|
303
|
+
subtitleLabelView.textColor
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
func usesNativeGlassTextRenderingForTesting() -> Bool {
|
|
307
|
+
#if compiler(>=6.2)
|
|
308
|
+
if #available(iOS 26.0, *) {
|
|
309
|
+
return toolbarAppearance == .native
|
|
310
|
+
&& stackView.isHidden
|
|
311
|
+
&& configuration?.title == suggestion.label
|
|
312
|
+
}
|
|
313
|
+
#endif
|
|
314
|
+
return false
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
func usesNativeGlassSemiboldTitleForTesting() -> Bool {
|
|
318
|
+
#if compiler(>=6.2)
|
|
319
|
+
if #available(iOS 26.0, *) {
|
|
320
|
+
return toolbarAppearance == .native
|
|
321
|
+
&& stackView.isHidden
|
|
322
|
+
&& configuration?.titleTextAttributesTransformer != nil
|
|
323
|
+
}
|
|
324
|
+
#endif
|
|
325
|
+
return false
|
|
326
|
+
}
|
|
327
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>AvailableLibraries</key>
|
|
6
|
+
<array>
|
|
7
|
+
<dict>
|
|
8
|
+
<key>BinaryPath</key>
|
|
9
|
+
<string>libeditor_core.a</string>
|
|
10
|
+
<key>LibraryIdentifier</key>
|
|
11
|
+
<string>ios-arm64</string>
|
|
12
|
+
<key>LibraryPath</key>
|
|
13
|
+
<string>libeditor_core.a</string>
|
|
14
|
+
<key>SupportedArchitectures</key>
|
|
15
|
+
<array>
|
|
16
|
+
<string>arm64</string>
|
|
17
|
+
</array>
|
|
18
|
+
<key>SupportedPlatform</key>
|
|
19
|
+
<string>ios</string>
|
|
20
|
+
</dict>
|
|
21
|
+
<dict>
|
|
22
|
+
<key>BinaryPath</key>
|
|
23
|
+
<string>libeditor_core.a</string>
|
|
24
|
+
<key>LibraryIdentifier</key>
|
|
25
|
+
<string>ios-arm64_x86_64-simulator</string>
|
|
26
|
+
<key>LibraryPath</key>
|
|
27
|
+
<string>libeditor_core.a</string>
|
|
28
|
+
<key>SupportedArchitectures</key>
|
|
29
|
+
<array>
|
|
30
|
+
<string>arm64</string>
|
|
31
|
+
<string>x86_64</string>
|
|
32
|
+
</array>
|
|
33
|
+
<key>SupportedPlatform</key>
|
|
34
|
+
<string>ios</string>
|
|
35
|
+
<key>SupportedPlatformVariant</key>
|
|
36
|
+
<string>simulator</string>
|
|
37
|
+
</dict>
|
|
38
|
+
</array>
|
|
39
|
+
<key>CFBundlePackageType</key>
|
|
40
|
+
<string>XFWK</string>
|
|
41
|
+
<key>XCFrameworkFormatVersion</key>
|
|
42
|
+
<string>1.0</string>
|
|
43
|
+
</dict>
|
|
44
|
+
</plist>
|
|
Binary file
|