@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.
Files changed (91) hide show
  1. package/LICENSE +160 -0
  2. package/README.md +180 -0
  3. package/android/build.gradle +69 -0
  4. package/android/consumer-rules.pro +18 -0
  5. package/android/src/androidTest/AndroidManifest.xml +8 -0
  6. package/android/src/androidTest/java/com/apollohg/editor/MeasureHeightInstrumentedTest.kt +53 -0
  7. package/android/src/androidTest/java/com/apollohg/editor/NativeDeviceCollaborationInitialSyncTest.kt +241 -0
  8. package/android/src/androidTest/java/com/apollohg/editor/NativeDeviceImeRegressionTest.kt +338 -0
  9. package/android/src/androidTest/java/com/apollohg/editor/NativeDeviceOutsideTapTest.kt +789 -0
  10. package/android/src/androidTest/java/com/apollohg/editor/NativeDevicePerformanceTest.kt +350 -0
  11. package/android/src/androidTest/java/com/apollohg/editor/NativeEditorOutsideTapActivity.kt +5 -0
  12. package/android/src/main/assets/editor-icons/MaterialIcons.json +2236 -0
  13. package/android/src/main/assets/editor-icons/MaterialIcons.ttf +0 -0
  14. package/android/src/main/java/com/apollohg/editor/CaretGeometry.kt +50 -0
  15. package/android/src/main/java/com/apollohg/editor/EditorAddons.kt +135 -0
  16. package/android/src/main/java/com/apollohg/editor/EditorEditText.kt +4527 -0
  17. package/android/src/main/java/com/apollohg/editor/EditorHeightBehavior.kt +14 -0
  18. package/android/src/main/java/com/apollohg/editor/EditorInputConnection.kt +999 -0
  19. package/android/src/main/java/com/apollohg/editor/EditorTheme.kt +474 -0
  20. package/android/src/main/java/com/apollohg/editor/ImageResizeOverlayView.kt +199 -0
  21. package/android/src/main/java/com/apollohg/editor/NativeEditorExpoView.kt +3137 -0
  22. package/android/src/main/java/com/apollohg/editor/NativeEditorModule.kt +561 -0
  23. package/android/src/main/java/com/apollohg/editor/NativeProseViewerExpoView.kt +315 -0
  24. package/android/src/main/java/com/apollohg/editor/NativeToolbar.kt +1413 -0
  25. package/android/src/main/java/com/apollohg/editor/PositionBridge.kt +133 -0
  26. package/android/src/main/java/com/apollohg/editor/RemoteSelectionOverlayView.kt +315 -0
  27. package/android/src/main/java/com/apollohg/editor/RenderBridge.kt +2172 -0
  28. package/android/src/main/java/com/apollohg/editor/RichTextEditorView.kt +399 -0
  29. package/android/src/sharedTest/java/com/apollohg/editor/NativePerformanceSupport.kt +482 -0
  30. package/android/src/test/java/com/apollohg/editor/CaretGeometryTest.kt +137 -0
  31. package/android/src/test/java/com/apollohg/editor/EditorEditTextHardwareKeyTest.kt +321 -0
  32. package/android/src/test/java/com/apollohg/editor/EditorInputConnectionTest.kt +4202 -0
  33. package/android/src/test/java/com/apollohg/editor/NativeEditorExpoViewTest.kt +2581 -0
  34. package/android/src/test/java/com/apollohg/editor/NativeEditorModuleTest.kt +27 -0
  35. package/android/src/test/java/com/apollohg/editor/NativePerformanceTest.kt +197 -0
  36. package/android/src/test/java/com/apollohg/editor/NativeProseViewerExpoViewTest.kt +84 -0
  37. package/android/src/test/java/com/apollohg/editor/NativeToolbarTest.kt +544 -0
  38. package/android/src/test/java/com/apollohg/editor/PositionBridgeTest.kt +461 -0
  39. package/android/src/test/java/com/apollohg/editor/RenderBridgeTest.kt +2054 -0
  40. package/android/src/test/java/com/apollohg/editor/RichTextEditorViewTest.kt +1367 -0
  41. package/app.plugin.js +62 -0
  42. package/dist/EditorTheme.d.ts +113 -0
  43. package/dist/EditorTheme.js +29 -0
  44. package/dist/EditorToolbar.d.ts +191 -0
  45. package/dist/EditorToolbar.js +1095 -0
  46. package/dist/NativeEditorBridge.d.ts +325 -0
  47. package/dist/NativeEditorBridge.js +933 -0
  48. package/dist/NativeProseViewer.d.ts +53 -0
  49. package/dist/NativeProseViewer.js +480 -0
  50. package/dist/NativeRichTextEditor.d.ts +183 -0
  51. package/dist/NativeRichTextEditor.js +2574 -0
  52. package/dist/YjsCollaboration.d.ts +90 -0
  53. package/dist/YjsCollaboration.js +743 -0
  54. package/dist/addons.d.ts +100 -0
  55. package/dist/addons.js +82 -0
  56. package/dist/heightCache.d.ts +6 -0
  57. package/dist/heightCache.js +45 -0
  58. package/dist/index.d.ts +10 -0
  59. package/dist/index.js +30 -0
  60. package/dist/schemas.d.ts +37 -0
  61. package/dist/schemas.js +263 -0
  62. package/dist/useNativeEditor.d.ts +42 -0
  63. package/dist/useNativeEditor.js +124 -0
  64. package/expo-module.config.json +9 -0
  65. package/ios/EditorAddons.swift +327 -0
  66. package/ios/EditorCore.xcframework/Info.plist +44 -0
  67. package/ios/EditorCore.xcframework/ios-arm64/libeditor_core.a +0 -0
  68. package/ios/EditorCore.xcframework/ios-arm64_x86_64-simulator/libeditor_core.a +0 -0
  69. package/ios/EditorLayoutManager.swift +774 -0
  70. package/ios/EditorTheme.swift +515 -0
  71. package/ios/Generated_editor_core.swift +1513 -0
  72. package/ios/NativeEditorExpoView.swift +3593 -0
  73. package/ios/NativeEditorModule.swift +624 -0
  74. package/ios/NativeProseViewerExpoView.swift +276 -0
  75. package/ios/PositionBridge.swift +558 -0
  76. package/ios/ReactNativeProseEditor.podspec +49 -0
  77. package/ios/RenderBridge.swift +1708 -0
  78. package/ios/RichTextEditorView.swift +6150 -0
  79. package/ios/Tests/NativePerformanceTests.swift +687 -0
  80. package/ios/Tests/PositionBridgeTests.swift +706 -0
  81. package/ios/Tests/RenderBridgeTests.swift +2236 -0
  82. package/ios/Tests/RichTextEditorViewTests.swift +6548 -0
  83. package/ios/editor_coreFFI/editor_coreFFI.h +1289 -0
  84. package/ios/editor_coreFFI/module.modulemap +7 -0
  85. package/ios/editor_coreFFI.h +904 -0
  86. package/ios/editor_coreFFI.modulemap +7 -0
  87. package/package.json +74 -0
  88. package/rust/android/arm64-v8a/libeditor_core.so +0 -0
  89. package/rust/android/armeabi-v7a/libeditor_core.so +0 -0
  90. package/rust/android/x86_64/libeditor_core.so +0 -0
  91. 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,9 @@
1
+ {
2
+ "platforms": ["ios", "android"],
3
+ "ios": {
4
+ "modules": ["NativeEditorModule"]
5
+ },
6
+ "android": {
7
+ "modules": ["com.openeditor.editor.NativeEditorModule"]
8
+ }
9
+ }
@@ -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>