@@ -3,10 +3,15 @@ package com.swmansion.reanimated.pseudoSelectors
33import android.view.MotionEvent
44import android.view.View
55import android.view.ViewParent
6+ import com.facebook.react.bridge.UIManager
7+ import com.facebook.react.bridge.UIManagerListener
68import com.facebook.react.bridge.UiThreadUtil
9+ import com.facebook.react.common.annotations.UnstableReactNativeAPI
710import com.facebook.react.fabric.FabricUIManager
11+ import com.facebook.react.uimanager.IllegalViewOperationException
812import com.swmansion.reanimated.nativeProxy.PseudoSelectorCallback
913
14+ @OptIn(UnstableReactNativeAPI ::class )
1015class PseudoSelectorManager (
1116 private val fabricUIManager : FabricUIManager ,
1217) {
@@ -25,24 +30,88 @@ class PseudoSelectorManager(
2530 // Reused by updateHoverStates to avoid allocating on every hover event (UI thread only).
2631 private val hoverLocationBuffer = IntArray (2 )
2732
33+ private val pendingAttaches = LinkedHashMap <String , PendingAttach >()
34+ private var mountListenerRegistered = false
35+
36+ private data class PendingAttach (
37+ val tag : Int ,
38+ val selector : Int ,
39+ val callback : PseudoSelectorCallback ,
40+ )
41+
42+ private val mountListener =
43+ object : UIManagerListener {
44+ override fun didMountItems (uiManager : UIManager ) = flushPendingAttaches()
45+
46+ override fun willMountItems (uiManager : UIManager ) = Unit
47+
48+ override fun willDispatchViewUpdates (uiManager : UIManager ) = Unit
49+
50+ override fun didDispatchMountItems (uiManager : UIManager ) = Unit
51+
52+ override fun didScheduleMountItems (uiManager : UIManager ) = Unit
53+ }
54+
2855 fun attach (
2956 tag : Int ,
3057 selector : Int ,
3158 callback : PseudoSelectorCallback ,
3259 ) {
3360 UiThreadUtil .runOnUiThread {
34- val view = fabricUIManager.resolveView(tag) ? : return @runOnUiThread
35- val key = " $tag :$selector "
36- when (selector) {
37- 0 -> attachFocusWithin(view, key, callback)
38- 1 -> attachFocus(view, key, callback)
39- 2 -> attachHover(view, key, callback)
40- 3 -> attachActive(view, key, callback)
41- 4 -> attachActiveDeepest(view, key, callback)
61+ val view = tryResolveView(tag)
62+ if (view != null ) {
63+ attachToView(view, tag, selector, callback)
64+ } else {
65+ pendingAttaches[" $tag :$selector " ] = PendingAttach (tag, selector, callback)
66+ ensureMountListener()
4267 }
4368 }
4469 }
4570
71+ private fun attachToView (
72+ view : View ,
73+ tag : Int ,
74+ selector : Int ,
75+ callback : PseudoSelectorCallback ,
76+ ) {
77+ val key = " $tag :$selector "
78+ when (selector) {
79+ 0 -> attachFocusWithin(view, key, callback)
80+ 1 -> attachFocus(view, key, callback)
81+ 2 -> attachHover(view, key, callback)
82+ 3 -> attachActive(view, key, callback)
83+ 4 -> attachActiveDeepest(view, key, callback)
84+ }
85+ }
86+
87+ private fun tryResolveView (tag : Int ): View ? =
88+ try {
89+ fabricUIManager.resolveView(tag)
90+ } catch (e: IllegalViewOperationException ) {
91+ null
92+ }
93+
94+ private fun ensureMountListener () {
95+ if (mountListenerRegistered) {
96+ return
97+ }
98+ mountListenerRegistered = true
99+ fabricUIManager.addUIManagerEventListener(mountListener)
100+ }
101+
102+ private fun flushPendingAttaches () {
103+ if (pendingAttaches.isEmpty()) {
104+ return
105+ }
106+ val iterator = pendingAttaches.values.iterator()
107+ while (iterator.hasNext()) {
108+ val pending = iterator.next()
109+ val view = tryResolveView(pending.tag) ? : continue
110+ iterator.remove()
111+ attachToView(view, pending.tag, pending.selector, pending.callback)
112+ }
113+ }
114+
46115 private fun attachFocusWithin (
47116 view : View ,
48117 key : String ,
@@ -178,7 +247,11 @@ class PseudoSelectorManager(
178247 tag : Int ,
179248 selector : Int ,
180249 ) {
181- UiThreadUtil .runOnUiThread { detachActions.remove(" $tag :$selector " )?.run () }
250+ UiThreadUtil .runOnUiThread {
251+ val key = " $tag :$selector "
252+ pendingAttaches.remove(key)
253+ detachActions.remove(key)?.run ()
254+ }
182255 }
183256
184257 /* *
0 commit comments