react-native-controlled-input 0.18.0 → 0.20.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.
|
@@ -5,7 +5,6 @@ import android.util.AttributeSet
|
|
|
5
5
|
import android.util.Log
|
|
6
6
|
import android.util.TypedValue
|
|
7
7
|
import android.view.View
|
|
8
|
-
import android.view.ViewTreeObserver
|
|
9
8
|
import android.view.inputmethod.InputMethodManager
|
|
10
9
|
import android.widget.EditText
|
|
11
10
|
import android.widget.LinearLayout
|
|
@@ -27,6 +26,7 @@ import androidx.savedstate.SavedStateRegistryOwner
|
|
|
27
26
|
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
|
|
28
27
|
import com.facebook.react.bridge.ReactContext
|
|
29
28
|
import com.facebook.react.uimanager.UIManagerHelper
|
|
29
|
+
import com.facebook.react.uimanager.events.Event
|
|
30
30
|
import kotlinx.coroutines.flow.MutableStateFlow
|
|
31
31
|
|
|
32
32
|
/**
|
|
@@ -67,176 +67,184 @@ class ControlledInputView : LinearLayout, LifecycleOwner {
|
|
|
67
67
|
private var windowLifecycleBound = false
|
|
68
68
|
|
|
69
69
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
70
|
+
* Hidden [EditText] used only as KBC's [FocusedInputObserver.lastFocusedInput]: [syncUpLayout]
|
|
71
|
+
* reads [EditText]-scoped geometry. It is NOT focused and does not participate in the focus
|
|
72
|
+
* chain — we push state via reflection + synthetic selection events instead.
|
|
73
73
|
*/
|
|
74
|
-
private
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
* Layout height is 0 so LinearLayout ignores it visually. onLayout() forces its bounds to
|
|
85
|
-
* match ControlledInputView so keyboard-controller reads the correct width/height/position.
|
|
86
|
-
*/
|
|
87
|
-
private val focusProxy: EditText by lazy {
|
|
88
|
-
EditText(context).also { proxy ->
|
|
89
|
-
proxy.layoutParams = LayoutParams(0, 0)
|
|
90
|
-
// Must stay VISIBLE — Android's canTakeFocus() returns false for INVISIBLE/GONE views,
|
|
91
|
-
// causing requestFocus() to silently fail. Use alpha=0 to hide it visually instead.
|
|
92
|
-
proxy.alpha = 0f
|
|
93
|
-
proxy.isFocusableInTouchMode = true
|
|
94
|
-
proxy.showSoftInputOnFocus = false
|
|
95
|
-
proxy.isClickable = false
|
|
96
|
-
proxy.isCursorVisible = false
|
|
97
|
-
proxy.isLongClickable = false
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
private fun requestFocusProxy() {
|
|
102
|
-
val fontSize = viewModel.inputStyle.value?.fontSize?.toFloat()
|
|
103
|
-
Log.d(TAG, "──── requestFocusProxy ────")
|
|
104
|
-
Log.d(TAG, " fontSize=$fontSize")
|
|
105
|
-
Log.d(TAG, " proxy before: isFocused=${focusProxy.isFocused} size=${focusProxy.width}x${focusProxy.height}")
|
|
106
|
-
|
|
107
|
-
// Sync proxy ID with ControlledInputView's React Native tag BEFORE requestFocus(),
|
|
108
|
-
// so KeyboardAnimationCallback.focusListener sets viewTagFocused = this.id (not -1).
|
|
109
|
-
// Without this, KeyboardAwareScrollView JS sees e.target=-1 → focusWasChanged=false
|
|
110
|
-
// → layout.value never updated → maybeScroll() always returns 0 → no scroll.
|
|
111
|
-
focusProxy.id = this.id
|
|
112
|
-
Log.d(TAG, " proxy.id set to ${focusProxy.id} (= ControlledInputView RN tag)")
|
|
113
|
-
|
|
114
|
-
// Sync textSize so KeyboardControllerSelectionWatcher computes the correct
|
|
115
|
-
// cursor Y via android.text.Layout.getLineBottom() — used as `customHeight`
|
|
116
|
-
// in KeyboardAwareScrollView to determine how far to scroll.
|
|
117
|
-
fontSize?.let {
|
|
118
|
-
focusProxy.setTextSize(TypedValue.COMPLEX_UNIT_SP, it)
|
|
119
|
-
Log.d(TAG, " proxy textSize set to ${focusProxy.textSize}px (${it}sp)")
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
focusProxy.requestFocus()
|
|
123
|
-
Log.d(TAG, " proxy after requestFocus: isFocused=${focusProxy.isFocused} hasFocus=${focusProxy.hasFocus()}")
|
|
124
|
-
|
|
125
|
-
if (focusProxy.isFocused) {
|
|
126
|
-
// Proxy stole Android focus from AndroidComposeView → Compose will fire onBlur.
|
|
127
|
-
// Mark restoration mode so onBlur knows to recover (not dispatch BlurEvent to JS).
|
|
128
|
-
isRestoringComposeFocus = true
|
|
129
|
-
Log.d(TAG, " isRestoringComposeFocus=true (proxy has Android focus)")
|
|
74
|
+
private val kbcLayoutHost: EditText by lazy {
|
|
75
|
+
EditText(context).also { v ->
|
|
76
|
+
v.layoutParams = LayoutParams(0, 0)
|
|
77
|
+
v.alpha = 0f
|
|
78
|
+
v.isFocusable = false
|
|
79
|
+
v.isFocusableInTouchMode = false
|
|
80
|
+
v.showSoftInputOnFocus = false
|
|
81
|
+
v.isClickable = false
|
|
82
|
+
v.isCursorVisible = false
|
|
83
|
+
v.isLongClickable = false
|
|
130
84
|
}
|
|
131
|
-
|
|
132
|
-
// setSelection triggers a selection change (lastSelectionStart starts at -1),
|
|
133
|
-
// guaranteeing the watcher fires on the next pre-draw frame even if focus
|
|
134
|
-
// was just transferred.
|
|
135
|
-
focusProxy.setSelection(0)
|
|
136
|
-
Log.d(TAG, " proxy selectionStart=${focusProxy.selectionStart} layout=${focusProxy.layout != null}")
|
|
137
|
-
|
|
138
|
-
// Log absolute screen position — this is what keyboard-controller reads for scroll calculation
|
|
139
|
-
val loc = IntArray(2)
|
|
140
|
-
focusProxy.getLocationOnScreen(loc)
|
|
141
|
-
Log.d(TAG, " proxy screenLocation x=${loc[0]} y=${loc[1]} → absoluteY for KBC=${loc[1]}px")
|
|
142
|
-
Log.d(TAG, "──────────────────────────")
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
private fun clearFocusProxy() {
|
|
146
|
-
Log.d(TAG, "clearFocusProxy: proxy.isFocused=${focusProxy.isFocused} hasFocus=${focusProxy.hasFocus()}")
|
|
147
|
-
focusProxy.clearFocus()
|
|
148
|
-
Log.d(TAG, "clearFocusProxy: after clearFocus isFocused=${focusProxy.isFocused}")
|
|
149
85
|
}
|
|
150
86
|
|
|
151
87
|
/**
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
* its ViewTreeObserver listener before us → it fires first). We post restoreKbcTracking() to
|
|
155
|
-
* run after all synchronous focus-change handlers complete.
|
|
156
|
-
*/
|
|
157
|
-
private val proxyFocusLostListener =
|
|
158
|
-
ViewTreeObserver.OnGlobalFocusChangeListener { oldFocus, newFocus ->
|
|
159
|
-
// Fire for ANY proxy focus loss while we're in the restoration dance.
|
|
160
|
-
// newFocus can be null (proxy→null→AndroidComposeView, two steps on first focus)
|
|
161
|
-
// or AndroidComposeView directly (proxy→AndroidComposeView, subsequent focuses).
|
|
162
|
-
// Both cases need restoreKbcTracking(); KBC's null→AndroidComposeView transition does
|
|
163
|
-
// NOT clear lastFocusedInput, so restoring it once is enough for both paths.
|
|
164
|
-
if (oldFocus == focusProxy && isRestoringComposeFocus) {
|
|
165
|
-
Log.d(
|
|
166
|
-
TAG,
|
|
167
|
-
"proxyFocusLostListener: proxy → ${newFocus?.javaClass?.simpleName ?: "null"}, posting restoreKbcTracking()",
|
|
168
|
-
)
|
|
169
|
-
post { restoreKbcTracking() }
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Uses reflection to restore KBC's lastFocusedInput = focusProxy and call syncUpLayout(),
|
|
175
|
-
* so that KeyboardAwareScrollView receives the correct absoluteY / height to scroll to.
|
|
176
|
-
*
|
|
177
|
-
* Chain: EdgeToEdgeViewRegistry.get() → .callback → .layoutObserver → .lastFocusedInput / .syncUpLayout()
|
|
88
|
+
* EdgeToEdgeViewRegistry → KeyboardAnimationCallback + FocusedInputObserver.
|
|
89
|
+
* Null if react-native-keyboard-controller is missing or not initialized.
|
|
178
90
|
*/
|
|
179
|
-
private fun
|
|
180
|
-
Log.d(TAG, "restoreKbcTracking: starting reflection chain")
|
|
91
|
+
private fun resolveKbcCallbackAndObserver(): Pair<Any, Any>? {
|
|
181
92
|
try {
|
|
182
|
-
// 1. EdgeToEdgeViewRegistry is a Kotlin object — access via INSTANCE field
|
|
183
93
|
val registryClass =
|
|
184
94
|
Class.forName("com.reactnativekeyboardcontroller.views.EdgeToEdgeViewRegistry")
|
|
185
95
|
val registryInstance = registryClass.getField("INSTANCE").get(null)
|
|
186
96
|
val edgeToEdgeView =
|
|
187
97
|
registryClass.getDeclaredMethod("get").invoke(registryInstance)
|
|
188
98
|
?: run {
|
|
189
|
-
Log.w(TAG, "
|
|
190
|
-
return
|
|
99
|
+
Log.w(TAG, "resolveKbcCallbackAndObserver: EdgeToEdgeViewRegistry.get() == null")
|
|
100
|
+
return null
|
|
191
101
|
}
|
|
192
102
|
|
|
193
|
-
// 2. callback: KeyboardAnimationCallback (internal var — find by type)
|
|
194
103
|
val callbackField =
|
|
195
104
|
edgeToEdgeView.javaClass.declaredFields.firstOrNull {
|
|
196
105
|
it.type.simpleName == "KeyboardAnimationCallback"
|
|
197
106
|
}
|
|
198
107
|
?: run {
|
|
199
|
-
Log.w(TAG, "
|
|
200
|
-
return
|
|
108
|
+
Log.w(TAG, "resolveKbcCallbackAndObserver: KeyboardAnimationCallback field not found")
|
|
109
|
+
return null
|
|
201
110
|
}
|
|
202
111
|
callbackField.isAccessible = true
|
|
203
112
|
val callback =
|
|
204
113
|
callbackField.get(edgeToEdgeView)
|
|
205
114
|
?: run {
|
|
206
|
-
Log.w(TAG, "
|
|
207
|
-
return
|
|
115
|
+
Log.w(TAG, "resolveKbcCallbackAndObserver: callback == null")
|
|
116
|
+
return null
|
|
208
117
|
}
|
|
209
118
|
|
|
210
|
-
// 3. layoutObserver: FocusedInputObserver (internal var — find by type)
|
|
211
119
|
val observerField =
|
|
212
120
|
callback.javaClass.declaredFields.firstOrNull {
|
|
213
121
|
it.type.simpleName == "FocusedInputObserver"
|
|
214
122
|
}
|
|
215
123
|
?: run {
|
|
216
|
-
Log.w(TAG, "
|
|
217
|
-
return
|
|
124
|
+
Log.w(TAG, "resolveKbcCallbackAndObserver: FocusedInputObserver field not found")
|
|
125
|
+
return null
|
|
218
126
|
}
|
|
219
127
|
observerField.isAccessible = true
|
|
220
128
|
val observer =
|
|
221
129
|
observerField.get(callback)
|
|
222
130
|
?: run {
|
|
223
|
-
Log.w(TAG, "
|
|
131
|
+
Log.w(TAG, "resolveKbcCallbackAndObserver: layoutObserver == null")
|
|
132
|
+
return null
|
|
133
|
+
}
|
|
134
|
+
return Pair(callback, observer)
|
|
135
|
+
} catch (_: ClassNotFoundException) {
|
|
136
|
+
Log.d(TAG, "resolveKbcCallbackAndObserver: keyboard-controller not on classpath")
|
|
137
|
+
return null
|
|
138
|
+
} catch (e: Exception) {
|
|
139
|
+
Log.w(TAG, "resolveKbcCallbackAndObserver: ${e.javaClass.simpleName}: ${e.message}")
|
|
140
|
+
return null
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private fun setKbcViewTagFocused(callback: Any) {
|
|
145
|
+
try {
|
|
146
|
+
val f = callback.javaClass.getDeclaredField("viewTagFocused")
|
|
147
|
+
f.isAccessible = true
|
|
148
|
+
f.setInt(callback, id)
|
|
149
|
+
Log.d(TAG, "setKbcViewTagFocused: viewTagFocused=$id")
|
|
150
|
+
} catch (e: Exception) {
|
|
151
|
+
Log.w(TAG, "setKbcViewTagFocused: ${e.javaClass.simpleName}: ${e.message}")
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private fun setKbcFocusedInputHolder() {
|
|
156
|
+
try {
|
|
157
|
+
val holderClass =
|
|
158
|
+
Class.forName("com.reactnativekeyboardcontroller.traversal.FocusedInputHolder")
|
|
159
|
+
val instance = holderClass.getField("INSTANCE").get(null)
|
|
160
|
+
holderClass
|
|
161
|
+
.getMethod("set", EditText::class.java)
|
|
162
|
+
.invoke(instance, kbcLayoutHost)
|
|
163
|
+
} catch (e: Exception) {
|
|
164
|
+
Log.w(TAG, "setKbcFocusedInputHolder: ${e.javaClass.simpleName}: ${e.message}")
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Approximate one line height in dp for JS customHeight. */
|
|
169
|
+
private fun approximateSelectionEndYDp(): Double {
|
|
170
|
+
viewModel.inputStyle.value?.fontSize?.toDouble()?.takeIf { it > 0 }?.let { return it }
|
|
171
|
+
val dm = resources.displayMetrics
|
|
172
|
+
return (kbcLayoutHost.textSize / dm.density).toDouble().coerceAtLeast(12.0)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private fun dispatchSyntheticKbcSelectionEvent(observer: Any) {
|
|
176
|
+
val reactContext = context as? ReactContext ?: return
|
|
177
|
+
try {
|
|
178
|
+
val epField = observer.javaClass.getDeclaredField("eventPropagationView")
|
|
179
|
+
epField.isAccessible = true
|
|
180
|
+
val propagationId = (epField.get(observer) as View).id
|
|
181
|
+
|
|
182
|
+
val surfaceId = UIManagerHelper.getSurfaceId(this)
|
|
183
|
+
val targetId = id
|
|
184
|
+
val endY = approximateSelectionEndYDp()
|
|
185
|
+
|
|
186
|
+
val dataClz =
|
|
187
|
+
Class.forName("com.reactnativekeyboardcontroller.events.FocusedInputSelectionChangedEventData")
|
|
188
|
+
val dataCtor =
|
|
189
|
+
dataClz.declaredConstructors.singleOrNull { it.parameterTypes.size == 7 }
|
|
190
|
+
?: run {
|
|
191
|
+
Log.w(TAG, "dispatchSyntheticKbcSelectionEvent: no 7-arg data ctor")
|
|
224
192
|
return
|
|
225
193
|
}
|
|
194
|
+
dataCtor.isAccessible = true
|
|
195
|
+
val data =
|
|
196
|
+
dataCtor.newInstance(targetId, 0.0, 0.0, 0.0, endY, 0, 0)
|
|
197
|
+
|
|
198
|
+
val eventClz =
|
|
199
|
+
Class.forName("com.reactnativekeyboardcontroller.events.FocusedInputSelectionChangedEvent")
|
|
200
|
+
val eventCtor =
|
|
201
|
+
eventClz.getConstructor(
|
|
202
|
+
Int::class.javaPrimitiveType,
|
|
203
|
+
Int::class.javaPrimitiveType,
|
|
204
|
+
dataClz,
|
|
205
|
+
)
|
|
206
|
+
val event = eventCtor.newInstance(surfaceId, propagationId, data) as Event<*>
|
|
207
|
+
|
|
208
|
+
UIManagerHelper.getEventDispatcherForReactTag(reactContext, propagationId)
|
|
209
|
+
?.dispatchEvent(event)
|
|
210
|
+
Log.d(
|
|
211
|
+
TAG,
|
|
212
|
+
"dispatchSyntheticKbcSelectionEvent: propagationId=$propagationId target=$targetId endY(dp)=$endY",
|
|
213
|
+
)
|
|
214
|
+
} catch (e: Exception) {
|
|
215
|
+
Log.w(TAG, "dispatchSyntheticKbcSelectionEvent: ${e.javaClass.simpleName}: ${e.message}")
|
|
216
|
+
}
|
|
217
|
+
}
|
|
226
218
|
|
|
227
|
-
|
|
219
|
+
/**
|
|
220
|
+
* Pushes ControlledInput state into KBC without stealing Compose focus:
|
|
221
|
+
* viewTagFocused, lastFocusedInput, FocusedInputHolder, syncUpLayout, synthetic selection.
|
|
222
|
+
*/
|
|
223
|
+
private fun syncKeyboardControllerFocusedInput() {
|
|
224
|
+
Log.d(TAG, "syncKeyboardControllerFocusedInput: id=$id")
|
|
225
|
+
kbcLayoutHost.id = id
|
|
226
|
+
viewModel.inputStyle.value?.fontSize?.toFloat()?.let {
|
|
227
|
+
kbcLayoutHost.setTextSize(TypedValue.COMPLEX_UNIT_SP, it)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
val (callback, observer) = resolveKbcCallbackAndObserver() ?: return
|
|
231
|
+
|
|
232
|
+
setKbcViewTagFocused(callback)
|
|
233
|
+
setKbcFocusedInputHolder()
|
|
234
|
+
|
|
235
|
+
try {
|
|
228
236
|
val lastFocusedField = observer.javaClass.getDeclaredField("lastFocusedInput")
|
|
229
237
|
lastFocusedField.isAccessible = true
|
|
230
|
-
lastFocusedField.set(observer,
|
|
231
|
-
Log.d(TAG, "restoreKbcTracking: lastFocusedInput set to focusProxy")
|
|
238
|
+
lastFocusedField.set(observer, kbcLayoutHost)
|
|
232
239
|
|
|
233
|
-
// 5. Call syncUpLayout() — public fun, dispatches FocusedInputLayoutChangedEvent to JS
|
|
234
240
|
val syncMethod = observer.javaClass.getDeclaredMethod("syncUpLayout")
|
|
235
241
|
syncMethod.isAccessible = true
|
|
236
242
|
syncMethod.invoke(observer)
|
|
237
|
-
Log.d(TAG, "
|
|
243
|
+
Log.d(TAG, "syncKeyboardControllerFocusedInput: syncUpLayout() ok")
|
|
244
|
+
|
|
245
|
+
dispatchSyntheticKbcSelectionEvent(observer)
|
|
238
246
|
} catch (e: Exception) {
|
|
239
|
-
Log.w(TAG, "
|
|
247
|
+
Log.w(TAG, "syncKeyboardControllerFocusedInput: ${e.javaClass.simpleName}: ${e.message}")
|
|
240
248
|
}
|
|
241
249
|
}
|
|
242
250
|
|
|
@@ -244,19 +252,15 @@ class ControlledInputView : LinearLayout, LifecycleOwner {
|
|
|
244
252
|
|
|
245
253
|
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
|
|
246
254
|
super.onLayout(changed, l, t, r, b)
|
|
247
|
-
|
|
248
|
-
// the correct width/height/absolutePosition when the proxy is focused.
|
|
249
|
-
focusProxy.layout(0, 0, width, height)
|
|
255
|
+
kbcLayoutHost.layout(0, 0, width, height)
|
|
250
256
|
if (changed) {
|
|
251
257
|
val loc = IntArray(2)
|
|
252
258
|
getLocationOnScreen(loc)
|
|
253
259
|
Log.d(TAG, "onLayout: view=${width}x${height} screenX=${loc[0]} screenY=${loc[1]}")
|
|
254
|
-
Log.d(TAG, "onLayout: proxy=${focusProxy.width}x${focusProxy.height} (should match view)")
|
|
255
260
|
}
|
|
256
261
|
}
|
|
257
262
|
|
|
258
263
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
259
|
-
// Do not measure ComposeView until attached to a window.
|
|
260
264
|
if (shouldUseAndroidLayout && !isAttachedToWindow) {
|
|
261
265
|
setMeasuredDimension(
|
|
262
266
|
MeasureSpec.getSize(widthMeasureSpec).coerceAtLeast(0),
|
|
@@ -267,7 +271,6 @@ class ControlledInputView : LinearLayout, LifecycleOwner {
|
|
|
267
271
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
|
268
272
|
}
|
|
269
273
|
|
|
270
|
-
// Fabric/Yoga often won't drive Android layout for native children.
|
|
271
274
|
override fun requestLayout() {
|
|
272
275
|
super.requestLayout()
|
|
273
276
|
if (shouldUseAndroidLayout) {
|
|
@@ -287,14 +290,11 @@ class ControlledInputView : LinearLayout, LifecycleOwner {
|
|
|
287
290
|
override fun onAttachedToWindow() {
|
|
288
291
|
super.onAttachedToWindow()
|
|
289
292
|
Log.d(TAG, "onAttachedToWindow: id=$id")
|
|
290
|
-
viewTreeObserver.addOnGlobalFocusChangeListener(proxyFocusLostListener)
|
|
291
293
|
bindComposeToWindowLifecycle()
|
|
292
294
|
}
|
|
293
295
|
|
|
294
296
|
override fun onDetachedFromWindow() {
|
|
295
297
|
Log.d(TAG, "onDetachedFromWindow: id=$id")
|
|
296
|
-
viewTreeObserver.removeOnGlobalFocusChangeListener(proxyFocusLostListener)
|
|
297
|
-
isRestoringComposeFocus = false
|
|
298
298
|
if (usesLocalFallbackLifecycle) {
|
|
299
299
|
lifecycleRegistry.currentState = Lifecycle.State.CREATED
|
|
300
300
|
}
|
|
@@ -340,12 +340,10 @@ class ControlledInputView : LinearLayout, LifecycleOwner {
|
|
|
340
340
|
|
|
341
341
|
fun blur() {
|
|
342
342
|
Log.d(TAG, "blur() called from JS ref")
|
|
343
|
-
isRestoringComposeFocus = false
|
|
344
343
|
blurSignal.value = blurSignal.value + 1
|
|
345
344
|
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
|
346
345
|
imm.hideSoftInputFromWindow(windowToken, 0)
|
|
347
346
|
clearFocus()
|
|
348
|
-
clearFocusProxy()
|
|
349
347
|
}
|
|
350
348
|
|
|
351
349
|
fun focus() {
|
|
@@ -365,7 +363,7 @@ class ControlledInputView : LinearLayout, LifecycleOwner {
|
|
|
365
363
|
|
|
366
364
|
viewModel = JetpackComposeViewModel()
|
|
367
365
|
|
|
368
|
-
addView(
|
|
366
|
+
addView(kbcLayoutHost)
|
|
369
367
|
|
|
370
368
|
composeView = ComposeView(context).also { cv ->
|
|
371
369
|
cv.layoutParams = LayoutParams(
|
|
@@ -417,47 +415,21 @@ class ControlledInputView : LinearLayout, LifecycleOwner {
|
|
|
417
415
|
)
|
|
418
416
|
},
|
|
419
417
|
onFocus = {
|
|
420
|
-
Log.d(TAG, "
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
isRestoringComposeFocus = false
|
|
428
|
-
} else {
|
|
429
|
-
val surfaceId = UIManagerHelper.getSurfaceId(context)
|
|
430
|
-
val viewId = this@ControlledInputView.id
|
|
431
|
-
UIManagerHelper
|
|
432
|
-
.getEventDispatcherForReactTag(context as ReactContext, viewId)
|
|
433
|
-
?.dispatchEvent(FocusEvent(surfaceId, viewId))
|
|
434
|
-
requestFocusProxy()
|
|
435
|
-
}
|
|
418
|
+
Log.d(TAG, "Compose onFocus id=$id")
|
|
419
|
+
val surfaceId = UIManagerHelper.getSurfaceId(context)
|
|
420
|
+
val viewId = this@ControlledInputView.id
|
|
421
|
+
UIManagerHelper
|
|
422
|
+
.getEventDispatcherForReactTag(context as ReactContext, viewId)
|
|
423
|
+
?.dispatchEvent(FocusEvent(surfaceId, viewId))
|
|
424
|
+
post { syncKeyboardControllerFocusedInput() }
|
|
436
425
|
},
|
|
437
426
|
onBlur = {
|
|
438
|
-
Log.d(TAG, "
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
post {
|
|
445
|
-
// Give AndroidComposeView Android focus back (needed for showSoftInput to work)
|
|
446
|
-
composeView.requestFocus()
|
|
447
|
-
// Trigger BasicTextField to re-gain Compose focus → shows keyboard → fires onFocus
|
|
448
|
-
focusSignal.value = focusSignal.value + 1
|
|
449
|
-
}
|
|
450
|
-
} else {
|
|
451
|
-
// Real blur (user dismissed keyboard / tapped elsewhere)
|
|
452
|
-
Log.d(TAG, " → real blur: dispatching BlurEvent")
|
|
453
|
-
isRestoringComposeFocus = false
|
|
454
|
-
val surfaceId = UIManagerHelper.getSurfaceId(context)
|
|
455
|
-
val viewId = this@ControlledInputView.id
|
|
456
|
-
UIManagerHelper
|
|
457
|
-
.getEventDispatcherForReactTag(context as ReactContext, viewId)
|
|
458
|
-
?.dispatchEvent(BlurEvent(surfaceId, viewId))
|
|
459
|
-
clearFocusProxy()
|
|
460
|
-
}
|
|
427
|
+
Log.d(TAG, "Compose onBlur id=$id")
|
|
428
|
+
val surfaceId = UIManagerHelper.getSurfaceId(context)
|
|
429
|
+
val viewId = this@ControlledInputView.id
|
|
430
|
+
UIManagerHelper
|
|
431
|
+
.getEventDispatcherForReactTag(context as ReactContext, viewId)
|
|
432
|
+
?.dispatchEvent(BlurEvent(surfaceId, viewId))
|
|
461
433
|
},
|
|
462
434
|
focusRequester = focusRequester
|
|
463
435
|
)
|
package/package.json
CHANGED