@apollohg/react-native-prose-editor 0.5.17 → 0.5.19
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/android/src/main/java/com/apollohg/editor/EditorEditText.kt +950 -38
- package/android/src/main/java/com/apollohg/editor/EditorInputConnection.kt +408 -33
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoView.kt +104 -5
- package/dist/NativeRichTextEditor.js +37 -2
- package/ios/EditorAddons.swift +78 -6
- 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/NativeEditorExpoView.swift +127 -18
- package/ios/RenderBridge.swift +3 -1
- package/ios/RichTextEditorView.swift +211 -14
- 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
|
@@ -390,6 +390,11 @@ class NativeEditorExpoView(
|
|
|
390
390
|
val mentionQuery: String? = null
|
|
391
391
|
)
|
|
392
392
|
|
|
393
|
+
private data class PendingEditorUpdateEvent(
|
|
394
|
+
val editorId: Long,
|
|
395
|
+
val updateJSON: String
|
|
396
|
+
)
|
|
397
|
+
|
|
393
398
|
val richTextView: RichTextEditorView = RichTextEditorView(context)
|
|
394
399
|
private val keyboardToolbarView = EditorKeyboardToolbarView(context)
|
|
395
400
|
private val mainHandler = Handler(Looper.getMainLooper())
|
|
@@ -478,6 +483,9 @@ class NativeEditorExpoView(
|
|
|
478
483
|
private var pendingNativeActionRetryGeneration = 0
|
|
479
484
|
private var pendingNativeActionRetryAttempts = 0
|
|
480
485
|
private var lastReadyEditorId: Long? = null
|
|
486
|
+
private val pendingEditorUpdateEvents = java.util.ArrayDeque<PendingEditorUpdateEvent>()
|
|
487
|
+
private var pendingEditorUpdateDispatchGeneration = 0
|
|
488
|
+
private var pendingEditorUpdateDispatchScheduled = false
|
|
481
489
|
|
|
482
490
|
init {
|
|
483
491
|
addView(richTextView, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
|
|
@@ -1737,6 +1745,7 @@ class NativeEditorExpoView(
|
|
|
1737
1745
|
isApplyingJSUpdate = true
|
|
1738
1746
|
return try {
|
|
1739
1747
|
richTextView.editorEditText.applyUpdateJSON(updateJson)
|
|
1748
|
+
clearPendingEditorUpdateDispatchQueue("jsUpdate")
|
|
1740
1749
|
true
|
|
1741
1750
|
} catch (error: Throwable) {
|
|
1742
1751
|
Log.w(LOG_TAG, "Failed to apply JS editor update", error)
|
|
@@ -1812,27 +1821,114 @@ class NativeEditorExpoView(
|
|
|
1812
1821
|
}
|
|
1813
1822
|
|
|
1814
1823
|
override fun onEditorUpdate(updateJSON: String) {
|
|
1824
|
+
if (isApplyingJSUpdate) {
|
|
1825
|
+
dispatchEditorUpdate(
|
|
1826
|
+
PendingEditorUpdateEvent(
|
|
1827
|
+
editorId = richTextView.editorId,
|
|
1828
|
+
updateJSON = updateJSON
|
|
1829
|
+
),
|
|
1830
|
+
emitToJS = false
|
|
1831
|
+
)
|
|
1832
|
+
return
|
|
1833
|
+
}
|
|
1834
|
+
pendingEditorUpdateEvents.addLast(
|
|
1835
|
+
PendingEditorUpdateEvent(
|
|
1836
|
+
editorId = richTextView.editorId,
|
|
1837
|
+
updateJSON = updateJSON
|
|
1838
|
+
)
|
|
1839
|
+
)
|
|
1840
|
+
richTextView.editorEditText.recordImeTraceForTesting(
|
|
1841
|
+
"nativeViewEditorUpdateQueued",
|
|
1842
|
+
"queue=${pendingEditorUpdateEvents.size} jsonLength=${updateJSON.length}"
|
|
1843
|
+
)
|
|
1844
|
+
schedulePendingEditorUpdateDispatch()
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
internal fun pendingEditorUpdateEventCountForTesting(): Int =
|
|
1848
|
+
pendingEditorUpdateEvents.size
|
|
1849
|
+
|
|
1850
|
+
private fun schedulePendingEditorUpdateDispatch() {
|
|
1851
|
+
pendingEditorUpdateDispatchScheduled = true
|
|
1852
|
+
val generation = ++pendingEditorUpdateDispatchGeneration
|
|
1853
|
+
mainHandler.postDelayed({
|
|
1854
|
+
if (generation != pendingEditorUpdateDispatchGeneration) return@postDelayed
|
|
1855
|
+
pendingEditorUpdateDispatchScheduled = false
|
|
1856
|
+
drainPendingEditorUpdateEvents()
|
|
1857
|
+
}, EDITOR_UPDATE_EVENT_DEBOUNCE_MS)
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
private fun drainPendingEditorUpdateEvents() {
|
|
1861
|
+
if (pendingEditorUpdateEvents.isEmpty()) return
|
|
1862
|
+
val startedAt = System.nanoTime()
|
|
1863
|
+
var drainedCount = 0
|
|
1864
|
+
while (pendingEditorUpdateEvents.isNotEmpty()) {
|
|
1865
|
+
val event = pendingEditorUpdateEvents.removeFirst()
|
|
1866
|
+
if (event.editorId != richTextView.editorId) {
|
|
1867
|
+
richTextView.editorEditText.recordImeTraceForTesting(
|
|
1868
|
+
"nativeViewEditorUpdateSkipped",
|
|
1869
|
+
"reason=staleEditor queuedEditor=${event.editorId} currentEditor=${richTextView.editorId}"
|
|
1870
|
+
)
|
|
1871
|
+
continue
|
|
1872
|
+
}
|
|
1873
|
+
dispatchEditorUpdate(event, emitToJS = true)
|
|
1874
|
+
drainedCount += 1
|
|
1875
|
+
}
|
|
1876
|
+
richTextView.editorEditText.recordImeTraceForTesting(
|
|
1877
|
+
"nativeViewEditorUpdateDrained",
|
|
1878
|
+
"count=$drainedCount totalUs=${nanosToMicros(System.nanoTime() - startedAt)}"
|
|
1879
|
+
)
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
private fun clearPendingEditorUpdateDispatchQueue(reason: String) {
|
|
1883
|
+
if (pendingEditorUpdateEvents.isEmpty() && !pendingEditorUpdateDispatchScheduled) return
|
|
1884
|
+
val clearedCount = pendingEditorUpdateEvents.size
|
|
1885
|
+
pendingEditorUpdateEvents.clear()
|
|
1886
|
+
pendingEditorUpdateDispatchScheduled = false
|
|
1887
|
+
pendingEditorUpdateDispatchGeneration += 1
|
|
1888
|
+
richTextView.editorEditText.recordImeTraceForTesting(
|
|
1889
|
+
"nativeViewEditorUpdateQueueCleared",
|
|
1890
|
+
"reason=$reason count=$clearedCount"
|
|
1891
|
+
)
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
private fun dispatchEditorUpdate(event: PendingEditorUpdateEvent, emitToJS: Boolean) {
|
|
1895
|
+
val updateJSON = event.updateJSON
|
|
1896
|
+
val startedAt = System.nanoTime()
|
|
1815
1897
|
noteDocumentVersionFromUpdateJSON(updateJSON)
|
|
1898
|
+
val noteNanos = System.nanoTime() - startedAt
|
|
1899
|
+
val toolbarStartedAt = System.nanoTime()
|
|
1816
1900
|
NativeToolbarState.fromUpdateJson(updateJSON)?.let { state ->
|
|
1817
1901
|
toolbarState = state
|
|
1818
1902
|
keyboardToolbarView.applyState(state)
|
|
1819
1903
|
}
|
|
1904
|
+
val toolbarNanos = System.nanoTime() - toolbarStartedAt
|
|
1905
|
+
val mentionStartedAt = System.nanoTime()
|
|
1820
1906
|
refreshMentionQuery()
|
|
1907
|
+
val mentionNanos = System.nanoTime() - mentionStartedAt
|
|
1908
|
+
val retryStartedAt = System.nanoTime()
|
|
1821
1909
|
clearPendingNativeActionRetryIfScopeChanged()
|
|
1822
1910
|
schedulePendingPreflightWake()
|
|
1823
1911
|
richTextView.refreshRemoteSelections()
|
|
1912
|
+
val retryNanos = System.nanoTime() - retryStartedAt
|
|
1824
1913
|
if (heightBehavior == EditorHeightBehavior.AUTO_GROW) {
|
|
1825
1914
|
post {
|
|
1826
1915
|
requestLayout()
|
|
1827
1916
|
emitContentHeightIfNeeded(force = false)
|
|
1828
1917
|
}
|
|
1829
1918
|
}
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1919
|
+
val emitStartedAt = System.nanoTime()
|
|
1920
|
+
if (emitToJS) {
|
|
1921
|
+
val payload = mapOf<String, Any>(
|
|
1922
|
+
"updateJson" to updateJSON,
|
|
1923
|
+
"editorId" to event.editorId
|
|
1924
|
+
)
|
|
1925
|
+
onEditorUpdate(payload)
|
|
1926
|
+
}
|
|
1927
|
+
val totalNanos = System.nanoTime() - startedAt
|
|
1928
|
+
richTextView.editorEditText.recordImeTraceForTesting(
|
|
1929
|
+
"nativeViewEditorUpdateDispatch",
|
|
1930
|
+
"emitToJS=$emitToJS jsonLength=${updateJSON.length} noteUs=${nanosToMicros(noteNanos)} toolbarUs=${nanosToMicros(toolbarNanos)} mentionUs=${nanosToMicros(mentionNanos)} retryUs=${nanosToMicros(retryNanos)} emitUs=${nanosToMicros(System.nanoTime() - emitStartedAt)} totalUs=${nanosToMicros(totalNanos)}"
|
|
1834
1931
|
)
|
|
1835
|
-
onEditorUpdate(event)
|
|
1836
1932
|
}
|
|
1837
1933
|
|
|
1838
1934
|
private fun installOutsideTapBlurHandlerIfNeeded() {
|
|
@@ -2082,10 +2178,13 @@ class NativeEditorExpoView(
|
|
|
2082
2178
|
private const val TOOLBAR_FOCUS_PRESERVE_MS = 750L
|
|
2083
2179
|
private const val OUTSIDE_TAP_BLUR_DELAY_MS = 100L
|
|
2084
2180
|
private const val NATIVE_ACTION_RETRY_DELAY_MS = 16L
|
|
2181
|
+
private const val EDITOR_UPDATE_EVENT_DEBOUNCE_MS = 64L
|
|
2085
2182
|
private const val PENDING_UPDATE_RECOVERY_RETRY_DELAY_MS = 250L
|
|
2086
2183
|
private const val MAX_NATIVE_ACTION_RETRY_ATTEMPTS = 3
|
|
2087
2184
|
private const val MAX_PENDING_UPDATE_RETRY_ATTEMPTS = 5
|
|
2088
2185
|
private const val LOG_TAG = "NativeEditor"
|
|
2186
|
+
|
|
2187
|
+
private fun nanosToMicros(nanos: Long): Long = nanos / 1_000L
|
|
2089
2188
|
}
|
|
2090
2189
|
|
|
2091
2190
|
private fun resolveActivity(context: Context): Activity? {
|
|
@@ -567,6 +567,7 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
567
567
|
const pendingControlledSyncAfterNativeUpdateRef = (0, react_1.useRef)(false);
|
|
568
568
|
const pendingBlockedNativeCommandRetryRef = (0, react_1.useRef)(false);
|
|
569
569
|
const pendingNativeCommandRetryRef = (0, react_1.useRef)(null);
|
|
570
|
+
const pendingBridgeRecreationContentRef = (0, react_1.useRef)(null);
|
|
570
571
|
const blockedNativeCommandRetryTimerRef = (0, react_1.useRef)(null);
|
|
571
572
|
const [autoGrowHeight, setAutoGrowHeight] = (0, react_1.useState)(null);
|
|
572
573
|
// Toolbar state from EditorUpdate events
|
|
@@ -1039,6 +1040,10 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
1039
1040
|
}
|
|
1040
1041
|
}, [syncNativeUpdateFromBridge]);
|
|
1041
1042
|
(0, react_1.useEffect)(() => {
|
|
1043
|
+
const preservedUncontrolledContent = value == null && serializedValueJson == null
|
|
1044
|
+
? pendingBridgeRecreationContentRef.current
|
|
1045
|
+
: null;
|
|
1046
|
+
pendingBridgeRecreationContentRef.current = null;
|
|
1042
1047
|
const bridgeConfig = maxLength != null || serializedSchemaJson || allowBase64Images
|
|
1043
1048
|
? {
|
|
1044
1049
|
maxLength,
|
|
@@ -1056,18 +1061,48 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
1056
1061
|
else if (serializedValueJson != null) {
|
|
1057
1062
|
bridge.setJsonString(serializedValueJson);
|
|
1058
1063
|
}
|
|
1064
|
+
else if (preservedUncontrolledContent != null) {
|
|
1065
|
+
bridge.setJsonString(preservedUncontrolledContent.jsonString);
|
|
1066
|
+
}
|
|
1059
1067
|
else if (serializedInitialJson != null) {
|
|
1060
1068
|
bridge.setJsonString(serializedInitialJson);
|
|
1061
1069
|
}
|
|
1062
1070
|
else if (initialContent) {
|
|
1063
1071
|
bridge.setHtml(initialContent);
|
|
1064
1072
|
}
|
|
1073
|
+
if (preservedUncontrolledContent != null) {
|
|
1074
|
+
const preservedSelection = preservedUncontrolledContent.selection;
|
|
1075
|
+
if (preservedSelection.type === 'text') {
|
|
1076
|
+
const anchor = preservedSelection.anchor ?? 0;
|
|
1077
|
+
const head = preservedSelection.head ?? anchor;
|
|
1078
|
+
bridge.setSelection(anchor, head);
|
|
1079
|
+
}
|
|
1080
|
+
else if (preservedSelection.type === 'node' &&
|
|
1081
|
+
typeof preservedSelection.pos === 'number') {
|
|
1082
|
+
bridge.setSelection(preservedSelection.pos, preservedSelection.pos);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1065
1085
|
syncStateFromUpdate(bridge.getCurrentState());
|
|
1066
1086
|
setIsReady(true);
|
|
1067
1087
|
return () => {
|
|
1088
|
+
if (bridgeRef.current === bridge &&
|
|
1089
|
+
value == null &&
|
|
1090
|
+
serializedValueJson == null &&
|
|
1091
|
+
!bridge.isDestroyed) {
|
|
1092
|
+
try {
|
|
1093
|
+
pendingBridgeRecreationContentRef.current = {
|
|
1094
|
+
jsonString: bridge.getJsonString(),
|
|
1095
|
+
selection: selectionRef.current,
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
catch {
|
|
1099
|
+
pendingBridgeRecreationContentRef.current = null;
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1068
1102
|
bridge.destroy();
|
|
1069
|
-
bridgeRef.current
|
|
1070
|
-
|
|
1103
|
+
if (bridgeRef.current === bridge) {
|
|
1104
|
+
bridgeRef.current = null;
|
|
1105
|
+
}
|
|
1071
1106
|
pendingNativeUpdateInFlightRef.current = null;
|
|
1072
1107
|
pendingDetachedControlledSyncRef.current = false;
|
|
1073
1108
|
pendingControlledSyncAfterNativeUpdateRef.current = false;
|
package/ios/EditorAddons.swift
CHANGED
|
@@ -131,6 +131,9 @@ func resolveMentionQueryState(
|
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
final class MentionSuggestionChipButton: UIButton {
|
|
134
|
+
private static let horizontalContentInset: CGFloat = 8
|
|
135
|
+
private static let verticalContentInset: CGFloat = 8
|
|
136
|
+
|
|
134
137
|
private let titleLabelView = UILabel()
|
|
135
138
|
private let subtitleLabelView = UILabel()
|
|
136
139
|
private let stackView = UIStackView()
|
|
@@ -180,10 +183,10 @@ final class MentionSuggestionChipButton: UIButton {
|
|
|
180
183
|
addSubview(stackView)
|
|
181
184
|
|
|
182
185
|
NSLayoutConstraint.activate([
|
|
183
|
-
stackView.topAnchor.constraint(equalTo: topAnchor, constant:
|
|
184
|
-
stackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant:
|
|
185
|
-
stackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -
|
|
186
|
-
stackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -
|
|
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),
|
|
187
190
|
heightAnchor.constraint(greaterThanOrEqualToConstant: 40),
|
|
188
191
|
])
|
|
189
192
|
|
|
@@ -213,6 +216,13 @@ final class MentionSuggestionChipButton: UIButton {
|
|
|
213
216
|
}
|
|
214
217
|
}
|
|
215
218
|
|
|
219
|
+
override func tintColorDidChange() {
|
|
220
|
+
super.tintColorDidChange()
|
|
221
|
+
if toolbarAppearance == .native {
|
|
222
|
+
updateAppearance(highlighted: isHighlighted)
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
216
226
|
@objc private func handleTouchDown() {
|
|
217
227
|
updateAppearance(highlighted: true)
|
|
218
228
|
}
|
|
@@ -226,14 +236,46 @@ final class MentionSuggestionChipButton: UIButton {
|
|
|
226
236
|
layer.cornerRadius = 18
|
|
227
237
|
layer.borderColor = UIColor.clear.cgColor
|
|
228
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
|
|
229
265
|
backgroundColor = highlighted
|
|
230
266
|
? UIColor.white.withAlphaComponent(0.18)
|
|
231
267
|
: .clear
|
|
232
|
-
titleLabelView.textColor =
|
|
233
|
-
subtitleLabelView.textColor = .
|
|
268
|
+
titleLabelView.textColor = tintColor
|
|
269
|
+
subtitleLabelView.textColor = tintColor.withAlphaComponent(0.72)
|
|
234
270
|
return
|
|
235
271
|
}
|
|
236
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
|
+
}
|
|
237
279
|
backgroundColor = highlighted
|
|
238
280
|
? (theme?.optionHighlightedBackgroundColor ?? UIColor.systemBlue.withAlphaComponent(0.12))
|
|
239
281
|
: (theme?.backgroundColor ?? UIColor.secondarySystemBackground)
|
|
@@ -252,4 +294,34 @@ final class MentionSuggestionChipButton: UIButton {
|
|
|
252
294
|
func usesNativeAppearanceForTesting() -> Bool {
|
|
253
295
|
toolbarAppearance == .native
|
|
254
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
|
+
}
|
|
255
327
|
}
|
|
@@ -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
|
|
@@ -691,6 +691,8 @@ final class EditorAccessoryToolbarView: UIInputView {
|
|
|
691
691
|
private static let contentSpacing: CGFloat = 6
|
|
692
692
|
private static let defaultHorizontalInset: CGFloat = 0
|
|
693
693
|
private static let defaultKeyboardOffset: CGFloat = 0
|
|
694
|
+
private static let chromeTransitionDuration: TimeInterval = 0.18
|
|
695
|
+
private static let nativeDisabledButtonOpacity: CGFloat = 0.46
|
|
694
696
|
|
|
695
697
|
private struct ButtonBinding {
|
|
696
698
|
let item: NativeToolbarItem
|
|
@@ -727,6 +729,7 @@ final class EditorAccessoryToolbarView: UIInputView {
|
|
|
727
729
|
private var currentState = NativeToolbarState.empty
|
|
728
730
|
private var theme: EditorToolbarTheme?
|
|
729
731
|
private var mentionTheme: EditorMentionTheme?
|
|
732
|
+
private var didAnimateChromeTransition = false
|
|
730
733
|
fileprivate var onPressItem: ((NativeToolbarItem) -> Void)?
|
|
731
734
|
var onSelectMentionSuggestion: ((NativeMentionSuggestion) -> Void)?
|
|
732
735
|
var isShowingMentionSuggestions: Bool {
|
|
@@ -746,6 +749,16 @@ final class EditorAccessoryToolbarView: UIInputView {
|
|
|
746
749
|
var chromeBorderWidthForTesting: CGFloat {
|
|
747
750
|
chromeView.layer.borderWidth
|
|
748
751
|
}
|
|
752
|
+
var nativeChromeIsTransparentForTesting: Bool {
|
|
753
|
+
blurView.isHidden
|
|
754
|
+
&& glassTintView.isHidden
|
|
755
|
+
&& chromeView.layer.borderWidth == 0
|
|
756
|
+
&& chromeView.layer.shadowOpacity == 0
|
|
757
|
+
&& (chromeView.backgroundColor ?? .clear) == .clear
|
|
758
|
+
}
|
|
759
|
+
var didAnimateChromeTransitionForTesting: Bool {
|
|
760
|
+
didAnimateChromeTransition
|
|
761
|
+
}
|
|
749
762
|
var nativeToolbarVisibleWidthForTesting: CGFloat {
|
|
750
763
|
activeNativeToolbarScrollViewForTesting.bounds.width
|
|
751
764
|
}
|
|
@@ -849,15 +862,42 @@ final class EditorAccessoryToolbarView: UIInputView {
|
|
|
849
862
|
func apply(mentionTheme: EditorMentionTheme?) {
|
|
850
863
|
self.mentionTheme = mentionTheme
|
|
851
864
|
for button in mentionButtons {
|
|
852
|
-
button.apply(theme: mentionTheme)
|
|
865
|
+
button.apply(theme: mentionTheme, toolbarAppearance: resolvedAppearance)
|
|
853
866
|
}
|
|
854
867
|
}
|
|
855
868
|
|
|
856
869
|
func apply(theme: EditorToolbarTheme?) {
|
|
870
|
+
apply(theme: theme, animateChrome: false)
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
private func apply(theme: EditorToolbarTheme?, animateChrome: Bool) {
|
|
857
874
|
self.theme = theme
|
|
858
875
|
let usesNativeAppearance = resolvedAppearance == .native
|
|
876
|
+
let usesTransparentMentionChrome = self.usesTransparentMentionChrome
|
|
859
877
|
let hasFloatingGlassButtons = self.usesFloatingGlassButtons
|
|
860
878
|
let usesBarToolbar = usesNativeBarToolbar
|
|
879
|
+
let targetBlurHidden = usesTransparentMentionChrome || usesBarToolbar || !usesNativeAppearance
|
|
880
|
+
let targetBlurAlpha: CGFloat = usesNativeAppearance && !usesTransparentMentionChrome ? resolvedEffectAlpha : 0
|
|
881
|
+
let targetBlurEffect = usesNativeAppearance && !usesTransparentMentionChrome ? resolvedBlurEffect() : nil
|
|
882
|
+
let targetGlassHidden = usesTransparentMentionChrome || usesBarToolbar || !usesNativeAppearance
|
|
883
|
+
let targetGlassBackground = usesNativeAppearance && !usesTransparentMentionChrome
|
|
884
|
+
? UIColor.systemBackground.withAlphaComponent(resolvedGlassTintAlpha)
|
|
885
|
+
: .clear
|
|
886
|
+
let targetGlassAlpha: CGFloat = targetGlassHidden ? 0 : 1
|
|
887
|
+
let targetBorderColor = usesTransparentMentionChrome ? UIColor.clear : resolvedBorderColor
|
|
888
|
+
let targetBorderWidth: CGFloat = usesTransparentMentionChrome || usesBarToolbar
|
|
889
|
+
? 0
|
|
890
|
+
: (usesNativeAppearance
|
|
891
|
+
? (1 / UIScreen.main.scale)
|
|
892
|
+
: resolvedBorderWidth)
|
|
893
|
+
let targetClipsToBounds =
|
|
894
|
+
!usesTransparentMentionChrome
|
|
895
|
+
&& ((usesNativeAppearance && !hasFloatingGlassButtons && !usesBarToolbar) || resolvedBorderRadius > 0)
|
|
896
|
+
let targetShadowOpacity: Float =
|
|
897
|
+
usesNativeAppearance && !usesTransparentMentionChrome && !hasFloatingGlassButtons && !usesBarToolbar ? 0.08 : 0
|
|
898
|
+
let targetShadowRadius: CGFloat =
|
|
899
|
+
usesNativeAppearance && !usesTransparentMentionChrome && !hasFloatingGlassButtons && !usesBarToolbar ? 10 : 0
|
|
900
|
+
|
|
861
901
|
chromeView.backgroundColor = usesNativeAppearance
|
|
862
902
|
? .clear
|
|
863
903
|
: (theme?.backgroundColor ?? .systemBackground)
|
|
@@ -865,19 +905,6 @@ final class EditorAccessoryToolbarView: UIInputView {
|
|
|
865
905
|
? nil
|
|
866
906
|
: (theme?.buttonColor ?? tintColor)
|
|
867
907
|
chromeView.isOpaque = false
|
|
868
|
-
blurView.isHidden = usesBarToolbar || !usesNativeAppearance
|
|
869
|
-
blurView.effect = usesNativeAppearance ? resolvedBlurEffect() : nil
|
|
870
|
-
blurView.alpha = usesNativeAppearance ? resolvedEffectAlpha : 1
|
|
871
|
-
glassTintView.isHidden = usesBarToolbar || !usesNativeAppearance
|
|
872
|
-
glassTintView.backgroundColor = usesNativeAppearance
|
|
873
|
-
? UIColor.systemBackground.withAlphaComponent(resolvedGlassTintAlpha)
|
|
874
|
-
: .clear
|
|
875
|
-
chromeView.layer.borderColor = resolvedBorderColor.cgColor
|
|
876
|
-
chromeView.layer.borderWidth = usesBarToolbar
|
|
877
|
-
? 0
|
|
878
|
-
: (usesNativeAppearance
|
|
879
|
-
? (1 / UIScreen.main.scale)
|
|
880
|
-
: resolvedBorderWidth)
|
|
881
908
|
chromeView.layer.cornerRadius = resolvedBorderRadius
|
|
882
909
|
if #available(iOS 13.0, *) {
|
|
883
910
|
chromeView.layer.cornerCurve = .continuous
|
|
@@ -892,11 +919,68 @@ final class EditorAccessoryToolbarView: UIInputView {
|
|
|
892
919
|
glassTintView.cornerConfiguration = cornerConfig
|
|
893
920
|
}
|
|
894
921
|
#endif
|
|
895
|
-
chromeView.clipsToBounds = (usesNativeAppearance && !hasFloatingGlassButtons && !usesBarToolbar) || resolvedBorderRadius > 0
|
|
896
|
-
chromeView.layer.shadowOpacity = usesNativeAppearance && !hasFloatingGlassButtons && !usesBarToolbar ? 0.08 : 0
|
|
897
|
-
chromeView.layer.shadowRadius = usesNativeAppearance && !hasFloatingGlassButtons && !usesBarToolbar ? 10 : 0
|
|
898
922
|
chromeView.layer.shadowOffset = CGSize(width: 0, height: 2)
|
|
899
923
|
chromeView.layer.shadowColor = UIColor.black.cgColor
|
|
924
|
+
|
|
925
|
+
let applyChromeProperties = {
|
|
926
|
+
self.blurView.alpha = targetBlurAlpha
|
|
927
|
+
self.glassTintView.alpha = targetGlassAlpha
|
|
928
|
+
self.chromeView.layer.borderColor = targetBorderColor.cgColor
|
|
929
|
+
self.chromeView.layer.borderWidth = targetBorderWidth
|
|
930
|
+
self.chromeView.layer.shadowOpacity = targetShadowOpacity
|
|
931
|
+
self.chromeView.layer.shadowRadius = targetShadowRadius
|
|
932
|
+
}
|
|
933
|
+
let finishChromeProperties = {
|
|
934
|
+
self.blurView.isHidden = targetBlurHidden
|
|
935
|
+
self.blurView.effect = targetBlurEffect
|
|
936
|
+
self.blurView.alpha = targetBlurAlpha
|
|
937
|
+
self.glassTintView.isHidden = targetGlassHidden
|
|
938
|
+
self.glassTintView.backgroundColor = targetGlassHidden ? .clear : targetGlassBackground
|
|
939
|
+
self.glassTintView.alpha = targetGlassAlpha
|
|
940
|
+
self.chromeView.layer.borderColor = targetBorderColor.cgColor
|
|
941
|
+
self.chromeView.layer.borderWidth = targetBorderWidth
|
|
942
|
+
self.chromeView.layer.shadowOpacity = targetShadowOpacity
|
|
943
|
+
self.chromeView.layer.shadowRadius = targetShadowRadius
|
|
944
|
+
self.chromeView.clipsToBounds = targetClipsToBounds
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
let shouldAnimateChrome = animateChrome && UIView.areAnimationsEnabled && window != nil
|
|
948
|
+
didAnimateChromeTransition = shouldAnimateChrome
|
|
949
|
+
if shouldAnimateChrome {
|
|
950
|
+
let blurWasHidden = blurView.isHidden
|
|
951
|
+
let glassWasHidden = glassTintView.isHidden
|
|
952
|
+
if !targetBlurHidden {
|
|
953
|
+
blurView.effect = targetBlurEffect
|
|
954
|
+
}
|
|
955
|
+
if !targetBlurHidden || !blurWasHidden {
|
|
956
|
+
blurView.isHidden = false
|
|
957
|
+
}
|
|
958
|
+
if blurWasHidden && !targetBlurHidden {
|
|
959
|
+
blurView.alpha = 0
|
|
960
|
+
}
|
|
961
|
+
if !targetGlassHidden {
|
|
962
|
+
glassTintView.backgroundColor = targetGlassBackground
|
|
963
|
+
}
|
|
964
|
+
if !targetGlassHidden || !glassWasHidden {
|
|
965
|
+
glassTintView.isHidden = false
|
|
966
|
+
}
|
|
967
|
+
if glassWasHidden && !targetGlassHidden {
|
|
968
|
+
glassTintView.alpha = 0
|
|
969
|
+
}
|
|
970
|
+
chromeView.clipsToBounds = targetClipsToBounds
|
|
971
|
+
UIView.animate(
|
|
972
|
+
withDuration: Self.chromeTransitionDuration,
|
|
973
|
+
delay: 0,
|
|
974
|
+
options: [.beginFromCurrentState, .allowUserInteraction, .curveEaseOut],
|
|
975
|
+
animations: applyChromeProperties,
|
|
976
|
+
completion: { _ in
|
|
977
|
+
finishChromeProperties()
|
|
978
|
+
}
|
|
979
|
+
)
|
|
980
|
+
} else {
|
|
981
|
+
finishChromeProperties()
|
|
982
|
+
}
|
|
983
|
+
|
|
900
984
|
chromeLeadingConstraint?.constant = resolvedHorizontalInset
|
|
901
985
|
chromeTrailingConstraint?.constant = -resolvedHorizontalInset
|
|
902
986
|
chromeBottomConstraint?.constant = -resolvedKeyboardOffset
|
|
@@ -949,6 +1033,7 @@ final class EditorAccessoryToolbarView: UIInputView {
|
|
|
949
1033
|
mentionScrollView.isHidden = !hasSuggestions
|
|
950
1034
|
scrollView.isHidden = hasSuggestions
|
|
951
1035
|
mentionRowHeightConstraint?.constant = hasSuggestions ? Self.mentionRowHeight : 0
|
|
1036
|
+
apply(theme: theme, animateChrome: hadSuggestions != hasSuggestions)
|
|
952
1037
|
invalidateIntrinsicContentSize()
|
|
953
1038
|
setNeedsLayout()
|
|
954
1039
|
return hadSuggestions != hasSuggestions
|
|
@@ -988,6 +1073,9 @@ final class EditorAccessoryToolbarView: UIInputView {
|
|
|
988
1073
|
var firstButtonTintColorForTesting: UIColor? {
|
|
989
1074
|
buttonBindings.first?.button.tintColor
|
|
990
1075
|
}
|
|
1076
|
+
func firstButtonTitleColorForTesting(_ state: UIControl.State) -> UIColor? {
|
|
1077
|
+
buttonBindings.first?.button.titleColor(for: state)
|
|
1078
|
+
}
|
|
991
1079
|
var firstButtonTintAdjustmentModeForTesting: UIView.TintAdjustmentMode {
|
|
992
1080
|
buttonBindings.first?.button.tintAdjustmentMode ?? .automatic
|
|
993
1081
|
}
|
|
@@ -1552,7 +1640,10 @@ final class EditorAccessoryToolbarView: UIInputView {
|
|
|
1552
1640
|
#endif
|
|
1553
1641
|
|
|
1554
1642
|
if resolvedAppearance == .native {
|
|
1555
|
-
|
|
1643
|
+
let tintColor = enabled ? theme?.buttonColor : resolvedNativeDisabledButtonTintColor()
|
|
1644
|
+
button.tintColor = tintColor
|
|
1645
|
+
button.setTitleColor(tintColor, for: .normal)
|
|
1646
|
+
button.setTitleColor(resolvedNativeDisabledButtonTintColor(), for: .disabled)
|
|
1556
1647
|
button.tintAdjustmentMode = enabled ? .automatic : .normal
|
|
1557
1648
|
button.alpha = 1
|
|
1558
1649
|
button.backgroundColor = active
|
|
@@ -1602,10 +1693,28 @@ final class EditorAccessoryToolbarView: UIInputView {
|
|
|
1602
1693
|
theme?.resolvedButtonBorderRadius ?? 8
|
|
1603
1694
|
}
|
|
1604
1695
|
|
|
1696
|
+
private func resolvedNativeDisabledButtonTintColor() -> UIColor {
|
|
1697
|
+
theme?.buttonDisabledColor ?? resolvedNativeButtonTintColor.withAlphaComponent(Self.nativeDisabledButtonOpacity)
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
private var resolvedNativeButtonTintColor: UIColor {
|
|
1701
|
+
theme?.buttonColor ?? tintColor ?? .label
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1605
1704
|
private var usesFloatingGlassButtons: Bool {
|
|
1606
1705
|
return false
|
|
1607
1706
|
}
|
|
1608
1707
|
|
|
1708
|
+
private var usesTransparentMentionChrome: Bool {
|
|
1709
|
+
guard resolvedAppearance == .native, !mentionButtons.isEmpty else { return false }
|
|
1710
|
+
#if compiler(>=6.2)
|
|
1711
|
+
if #available(iOS 26.0, *) {
|
|
1712
|
+
return true
|
|
1713
|
+
}
|
|
1714
|
+
#endif
|
|
1715
|
+
return false
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1609
1718
|
private var usesNativeBarToolbar: Bool {
|
|
1610
1719
|
return false
|
|
1611
1720
|
}
|
package/ios/RenderBridge.swift
CHANGED
|
@@ -1104,10 +1104,12 @@ final class RenderBridge {
|
|
|
1104
1104
|
}
|
|
1105
1105
|
|
|
1106
1106
|
let tagged = NSMutableAttributedString(attributedString: attributedString)
|
|
1107
|
+
let firstComposedCharacterRange = (tagged.string as NSString)
|
|
1108
|
+
.rangeOfComposedCharacterSequence(at: 0)
|
|
1107
1109
|
tagged.addAttribute(
|
|
1108
1110
|
RenderBridgeAttributes.topLevelChildIndex,
|
|
1109
1111
|
value: NSNumber(value: topLevelChildIndex),
|
|
1110
|
-
range:
|
|
1112
|
+
range: firstComposedCharacterRange
|
|
1111
1113
|
)
|
|
1112
1114
|
return tagged
|
|
1113
1115
|
}
|