@onekeyfe/react-native-native-overlay 6.7.0-11 → 6.7.0-13
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.
|
@@ -125,7 +125,11 @@ class NativeOverlayView(
|
|
|
125
125
|
|
|
126
126
|
fun removeReactChild(child: View) {
|
|
127
127
|
if (contentChild !== child) return
|
|
128
|
-
(child.parent as? ViewGroup)?.
|
|
128
|
+
(child.parent as? ViewGroup)?.let { previous ->
|
|
129
|
+
previous.removeOnLayoutChangeListener(contentLayoutListener)
|
|
130
|
+
previous.removeView(child)
|
|
131
|
+
previous.endViewTransition(child)
|
|
132
|
+
}
|
|
129
133
|
contentChild = null
|
|
130
134
|
}
|
|
131
135
|
|
|
@@ -417,6 +421,8 @@ class NativeOverlayView(
|
|
|
417
421
|
(child.parent as? ViewGroup)?.let { previous ->
|
|
418
422
|
previous.removeOnLayoutChangeListener(contentLayoutListener)
|
|
419
423
|
previous.removeView(child)
|
|
424
|
+
// Android retains a transitioning child's parent until the transition ends.
|
|
425
|
+
previous.endViewTransition(child)
|
|
420
426
|
}
|
|
421
427
|
container.addView(child)
|
|
422
428
|
container.addOnLayoutChangeListener(contentLayoutListener)
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
package com.onekey.nativeoverlay
|
|
2
2
|
|
|
3
|
+
import android.animation.ValueAnimator
|
|
3
4
|
import android.graphics.Color
|
|
4
5
|
import android.os.Looper
|
|
5
6
|
import android.view.View
|
|
7
|
+
import android.view.ViewGroup
|
|
6
8
|
import androidx.activity.ComponentActivity
|
|
7
9
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
8
10
|
import com.facebook.react.bridge.BridgeReactContext
|
|
@@ -11,6 +13,7 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
|
11
13
|
import org.junit.Assert.assertEquals
|
|
12
14
|
import org.junit.Assert.assertFalse
|
|
13
15
|
import org.junit.Assert.assertNotSame
|
|
16
|
+
import org.junit.Assert.assertNull
|
|
14
17
|
import org.junit.Assert.assertSame
|
|
15
18
|
import org.junit.Assert.assertTrue
|
|
16
19
|
import org.junit.Test
|
|
@@ -67,10 +70,11 @@ class NativeOverlayMotionTest {
|
|
|
67
70
|
val reactContext = BridgeReactContext(activity)
|
|
68
71
|
reactContext.onHostResume(activity)
|
|
69
72
|
val context = ThemedReactContext(reactContext, activity, "OverlayMotionTest", 1)
|
|
73
|
+
val child = View(context)
|
|
70
74
|
val overlay = NativeOverlayView(context).apply {
|
|
71
75
|
presentation = "sheet"
|
|
72
76
|
sheetHeight = 200.0
|
|
73
|
-
addReactChild(
|
|
77
|
+
addReactChild(child)
|
|
74
78
|
}
|
|
75
79
|
activity.setContentView(overlay)
|
|
76
80
|
var dismissals = 0
|
|
@@ -80,13 +84,24 @@ class NativeOverlayMotionTest {
|
|
|
80
84
|
|
|
81
85
|
val entryField = NativeOverlayView::class.java.getDeclaredField("entry").apply { isAccessible = true }
|
|
82
86
|
val original = entryField.get(overlay) as NativeOverlayEntryRootView
|
|
87
|
+
Shadows.shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(350))
|
|
83
88
|
overlay.visible = false
|
|
84
89
|
overlay.commitConfiguration()
|
|
85
90
|
assertTrue(original.isClosing)
|
|
91
|
+
val animatorField = NativeOverlayView::class.java.getDeclaredField("contentAnimator").apply { isAccessible = true }
|
|
92
|
+
val exitAnimator = animatorField.get(overlay) as ValueAnimator
|
|
93
|
+
assertTrue(exitAnimator.isStarted)
|
|
94
|
+
assertEquals(292L, exitAnimator.duration)
|
|
95
|
+
exitAnimator.currentPlayTime = exitAnimator.duration / 3
|
|
96
|
+
val alphaBeforeReopen = original.animatedView.alpha
|
|
97
|
+
assertTrue(alphaBeforeReopen > 0f && alphaBeforeReopen < 1f)
|
|
98
|
+
assertTrue(original.isClosing)
|
|
86
99
|
|
|
87
100
|
overlay.visible = true
|
|
88
101
|
overlay.commitConfiguration()
|
|
89
102
|
assertSame(original, entryField.get(overlay))
|
|
103
|
+
assertSame(child, original.contentView.getChildAt(0))
|
|
104
|
+
assertEquals(alphaBeforeReopen, original.animatedView.alpha, 0.01f)
|
|
90
105
|
assertTrue(original.isShownForInput)
|
|
91
106
|
assertFalse(original.isClosing)
|
|
92
107
|
assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO, original.contentView.importantForAccessibility)
|
|
@@ -98,4 +113,125 @@ class NativeOverlayMotionTest {
|
|
|
98
113
|
Shadows.shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(350))
|
|
99
114
|
assertEquals(1, dismissals)
|
|
100
115
|
}
|
|
116
|
+
|
|
117
|
+
@Test
|
|
118
|
+
fun finishingExitMovesTransitioningContentBackToStagingView() {
|
|
119
|
+
val activity = Robolectric.buildActivity(ComponentActivity::class.java).setup().visible().get()
|
|
120
|
+
val reactContext = BridgeReactContext(activity)
|
|
121
|
+
reactContext.onHostResume(activity)
|
|
122
|
+
val context = ThemedReactContext(reactContext, activity, "OverlayMotionTest", 1)
|
|
123
|
+
val child = View(context)
|
|
124
|
+
val overlay = NativeOverlayView(context).apply {
|
|
125
|
+
addReactChild(child)
|
|
126
|
+
}
|
|
127
|
+
activity.setContentView(overlay)
|
|
128
|
+
var dismissals = 0
|
|
129
|
+
overlay.onDismissed = { dismissals++ }
|
|
130
|
+
|
|
131
|
+
overlay.visible = true
|
|
132
|
+
overlay.commitConfiguration()
|
|
133
|
+
val entryField = NativeOverlayView::class.java.getDeclaredField("entry").apply { isAccessible = true }
|
|
134
|
+
val entry = entryField.get(overlay) as NativeOverlayEntryRootView
|
|
135
|
+
Shadows.shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(350))
|
|
136
|
+
|
|
137
|
+
entry.contentView.startViewTransition(child)
|
|
138
|
+
overlay.visible = false
|
|
139
|
+
overlay.commitConfiguration()
|
|
140
|
+
Shadows.shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(350))
|
|
141
|
+
assertSame(overlay, child.parent)
|
|
142
|
+
assertEquals(0, entry.contentView.childCount)
|
|
143
|
+
assertEquals(1, overlay.childCount)
|
|
144
|
+
assertSame(child, overlay.getChildAt(0))
|
|
145
|
+
entry.contentView.endViewTransition(child)
|
|
146
|
+
assertSame(overlay, child.parent)
|
|
147
|
+
assertEquals(1, dismissals)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
@Test
|
|
151
|
+
fun presentingMovesTransitioningContentIntoEntry() {
|
|
152
|
+
val activity = Robolectric.buildActivity(ComponentActivity::class.java).setup().visible().get()
|
|
153
|
+
val reactContext = BridgeReactContext(activity)
|
|
154
|
+
reactContext.onHostResume(activity)
|
|
155
|
+
val context = ThemedReactContext(reactContext, activity, "OverlayMotionTest", 1)
|
|
156
|
+
val child = View(context)
|
|
157
|
+
val overlay = NativeOverlayView(context).apply {
|
|
158
|
+
addReactChild(child)
|
|
159
|
+
}
|
|
160
|
+
activity.setContentView(overlay)
|
|
161
|
+
|
|
162
|
+
overlay.startViewTransition(child)
|
|
163
|
+
overlay.visible = true
|
|
164
|
+
overlay.commitConfiguration()
|
|
165
|
+
|
|
166
|
+
val entryField = NativeOverlayView::class.java.getDeclaredField("entry").apply { isAccessible = true }
|
|
167
|
+
val entry = entryField.get(overlay) as NativeOverlayEntryRootView
|
|
168
|
+
assertSame(entry.contentView, child.parent)
|
|
169
|
+
assertEquals(0, overlay.childCount)
|
|
170
|
+
assertEquals(1, entry.contentView.childCount)
|
|
171
|
+
assertSame(child, entry.contentView.getChildAt(0))
|
|
172
|
+
overlay.endViewTransition(child)
|
|
173
|
+
assertSame(entry.contentView, child.parent)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
@Test
|
|
177
|
+
fun removingTransitioningStagedContentReleasesItsParentAndLayoutListener() {
|
|
178
|
+
val activity = Robolectric.buildActivity(ComponentActivity::class.java).setup().visible().get()
|
|
179
|
+
val reactContext = BridgeReactContext(activity)
|
|
180
|
+
reactContext.onHostResume(activity)
|
|
181
|
+
val context = ThemedReactContext(reactContext, activity, "OverlayMotionTest", 1)
|
|
182
|
+
val child = View(context)
|
|
183
|
+
val overlay = NativeOverlayView(context).apply { addReactChild(child) }
|
|
184
|
+
activity.setContentView(overlay)
|
|
185
|
+
|
|
186
|
+
overlay.startViewTransition(child)
|
|
187
|
+
overlay.removeReactChild(child)
|
|
188
|
+
|
|
189
|
+
assertNull(child.parent)
|
|
190
|
+
assertEquals(0, overlay.childCount)
|
|
191
|
+
assertEquals(0, overlay.getReactChildCount())
|
|
192
|
+
assertNoContentLayoutListener(overlay, overlay)
|
|
193
|
+
overlay.endViewTransition(child)
|
|
194
|
+
assertNull(child.parent)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
@Test
|
|
198
|
+
fun replacingTransitioningPresentedContentReleasesTheRemovedChild() {
|
|
199
|
+
val activity = Robolectric.buildActivity(ComponentActivity::class.java).setup().visible().get()
|
|
200
|
+
val reactContext = BridgeReactContext(activity)
|
|
201
|
+
reactContext.onHostResume(activity)
|
|
202
|
+
val context = ThemedReactContext(reactContext, activity, "OverlayMotionTest", 1)
|
|
203
|
+
val child = View(context)
|
|
204
|
+
val overlay = NativeOverlayView(context).apply { addReactChild(child) }
|
|
205
|
+
activity.setContentView(overlay)
|
|
206
|
+
overlay.visible = true
|
|
207
|
+
overlay.commitConfiguration()
|
|
208
|
+
val container = child.parent as ViewGroup
|
|
209
|
+
container.startViewTransition(child)
|
|
210
|
+
|
|
211
|
+
val replacement = View(context)
|
|
212
|
+
overlay.addReactChild(replacement)
|
|
213
|
+
|
|
214
|
+
assertNull(child.parent)
|
|
215
|
+
assertSame(replacement, overlay.getReactChildAt(0))
|
|
216
|
+
assertSame(container, replacement.parent)
|
|
217
|
+
assertEquals(1, container.childCount)
|
|
218
|
+
container.endViewTransition(child)
|
|
219
|
+
assertSame(container, replacement.parent)
|
|
220
|
+
|
|
221
|
+
overlay.removeReactChild(replacement)
|
|
222
|
+
assertNoContentLayoutListener(container, overlay)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private fun assertNoContentLayoutListener(container: View, overlay: NativeOverlayView) {
|
|
226
|
+
val listener = NativeOverlayView::class.java.getDeclaredField("contentLayoutListener").apply {
|
|
227
|
+
isAccessible = true
|
|
228
|
+
}.get(overlay)
|
|
229
|
+
val listenerInfo = View::class.java.getDeclaredField("mListenerInfo").apply {
|
|
230
|
+
isAccessible = true
|
|
231
|
+
}.get(container) ?: return
|
|
232
|
+
val layoutListeners = listenerInfo.javaClass.getDeclaredField("mOnLayoutChangeListeners").apply {
|
|
233
|
+
isAccessible = true
|
|
234
|
+
}.get(listenerInfo) as? Collection<*>
|
|
235
|
+
assertFalse(layoutListeners?.contains(listener) == true)
|
|
236
|
+
}
|
|
101
237
|
}
|
package/docs/SPEC.md
CHANGED
|
@@ -41,14 +41,15 @@ attribution in source comments.
|
|
|
41
41
|
|
|
42
42
|
### Release boundary
|
|
43
43
|
|
|
44
|
-
The `
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
The
|
|
44
|
+
The earlier `3.0.162-alpha.272` release remains immutable. This branch is
|
|
45
|
+
prepared at `6.7.0-9`: the repository's release validator requires all 43
|
|
46
|
+
publishable workspace manifests and exact internal pins to use the same
|
|
47
|
+
version. Publish only `@onekeyfe/react-native-native-overlay` through the
|
|
48
|
+
manual `package-publish` workflow with `npm_dist_tag=latest` and
|
|
49
|
+
`only_workspace=@onekeyfe/react-native-native-overlay`; verify that exact
|
|
50
|
+
registry tarball before the App pins it and refreshes its dependency and native
|
|
51
|
+
locks. The other workspace version changes are release metadata, not a request
|
|
52
|
+
to publish their packages in this run. Leave the source PR open for review.
|
|
52
53
|
|
|
53
54
|
## 5.1 Purpose, scope, and non-goals
|
|
54
55
|
|
|
@@ -231,6 +232,15 @@ request ─► queued ─► active ─► (enter anim) presented
|
|
|
231
232
|
- Content stays mounted until `finalize`. This replaces today's hardcoded
|
|
232
233
|
timers: the Dialog 300 ms portal destroy, the ColorPicker 300 ms, the
|
|
233
234
|
ActionList and Select 150 ms waits, and the DialogLoading 50 ms delays.
|
|
235
|
+
- On Android, moving the same React child between its hidden staging view and
|
|
236
|
+
the entry host must release an in-progress transition held by the previous
|
|
237
|
+
parent before the new parent adds it. Completing that previous transition
|
|
238
|
+
later must not detach the child from its new parent. Animated closing moves
|
|
239
|
+
the child back after exit completion; reopening during exit keeps the same
|
|
240
|
+
child and entry without reporting a completed dismissal. Fabric removal or
|
|
241
|
+
replacement of the React child must also release the old container's layout
|
|
242
|
+
listener and in-progress transition, leaving the removed child without a
|
|
243
|
+
parent; a later transition completion must not affect replacement content.
|
|
234
244
|
- A queued entry is promoted only after the previous entry in its lane is
|
|
235
245
|
finalized, so exit and enter never overlap. `replace` is the exception: the
|
|
236
246
|
new entry enters while the replaced one exits.
|
|
@@ -299,14 +309,6 @@ their existing values and does not publish a release.
|
|
|
299
309
|
|
|
300
310
|
## 5.6 Platform contract
|
|
301
311
|
|
|
302
|
-
### Mac Catalyst sheet background
|
|
303
|
-
|
|
304
|
-
Implemented; Catalyst build and runtime verification pending. Global sheets
|
|
305
|
-
retain their configured surface color on Mac Catalyst and MUST NOT call
|
|
306
|
-
`UISheetPresentationController.backgroundEffect` or the matching detent API,
|
|
307
|
-
which are unavailable there. iOS 26.1 and later retain their existing color
|
|
308
|
-
effect. Presentation, sizing, motion, and lifecycle contracts are unchanged.
|
|
309
|
-
|
|
310
312
|
### Web keyboard focus containment
|
|
311
313
|
|
|
312
314
|
The topmost active, unsuspended blocking Web entry MUST contain sequential
|
|
@@ -414,6 +416,11 @@ Automated (P0):
|
|
|
414
416
|
blockers, input readiness, canceled key-up, global/page precedence,
|
|
415
417
|
suspension and blocking-only accessibility isolation. It also checks live
|
|
416
418
|
host reuse, Activity isolation and rebuilding after a cleared weak value.
|
|
419
|
+
- `NativeOverlayMotionTest` exercises the production Android view's transition
|
|
420
|
+
reparenting in both directions, including the exit-completion staging path,
|
|
421
|
+
old-parent transition completion after the move, and reopen during exit.
|
|
422
|
+
It also covers Fabric removal of staged content and replacement of presented
|
|
423
|
+
content during a transition, including layout-listener cleanup.
|
|
417
424
|
- `src/__tests__/pageIsolation.web.test.ts` mounts a real React portal in a
|
|
418
425
|
DOM host and verifies late suspension, level ordering and restoration after
|
|
419
426
|
the blocking entry is removed.
|
|
@@ -183,13 +183,11 @@ final class NativeOverlaySheetController: UIViewController,
|
|
|
183
183
|
) { [weak self] context in
|
|
184
184
|
min(max(self?.style.height ?? 1, 1), context.maximumDetentValue)
|
|
185
185
|
}
|
|
186
|
-
#if !targetEnvironment(macCatalyst)
|
|
187
186
|
if #available(iOS 26.1, *) {
|
|
188
187
|
let effect = UIColorEffect(color: style.backgroundColor)
|
|
189
188
|
sheet.backgroundEffect = effect
|
|
190
189
|
detent.backgroundEffect = effect
|
|
191
190
|
}
|
|
192
|
-
#endif
|
|
193
191
|
sheet.detents = [detent]
|
|
194
192
|
sheet.selectedDetentIdentifier = detentIdentifier
|
|
195
193
|
sheet.largestUndimmedDetentIdentifier = detentIdentifier
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/react-native-native-overlay",
|
|
3
|
-
"version": "6.7.0-
|
|
3
|
+
"version": "6.7.0-13",
|
|
4
4
|
"description": "Native and Web overlay hosts with ordered levels, page scope and Fabric measurement",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|