@onekeyfe/react-native-pager-view 6.7.0-15 → 6.7.0-17
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/README.md +5 -0
- package/android/src/androidTest/AndroidManifest.xml +7 -0
- package/android/src/androidTest/java/com/reactnativepagerview/CollapsiblePagerTabReservationTest.kt +247 -0
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerHost.kt +76 -21
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerNativeHeaders.kt +72 -7
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerViewManager.kt +27 -0
- package/android/src/main/java/com/reactnativepagerview/NativeScrollerView.kt +50 -6
- package/docs/SPEC.md +146 -1
- package/ios/RNCCollapsiblePagerViewComponentView.mm +355 -40
- package/ios/RNCNativeScrollerComponentView.h +2 -0
- package/ios/RNCNativeScrollerComponentView.mm +18 -0
- package/ios/tests/RNCCollapsiblePagerFirstFrameTests.mm +186 -0
- package/ios/tests/RNCCollapsiblePagerHitTestTests.mm +178 -0
- package/ios/tests/RNCCollapsiblePagerPressCancellationTests.mm +74 -9
- package/lib/module/CollapsiblePagerView.js +20 -4
- package/lib/module/CollapsiblePagerView.web.js +11 -2
- package/lib/module/CollapsiblePagerViewNativeComponent.ts +13 -0
- package/lib/typescript/src/CollapsiblePagerView.d.ts +6 -1
- package/lib/typescript/src/CollapsiblePagerView.web.d.ts +2 -0
- package/lib/typescript/src/CollapsiblePagerViewNativeComponent.d.ts +5 -0
- package/package.json +2 -2
- package/src/CollapsiblePagerView.tsx +68 -4
- package/src/CollapsiblePagerView.web.tsx +19 -2
- package/src/CollapsiblePagerViewNativeComponent.ts +13 -0
|
@@ -239,6 +239,18 @@ class CollapsiblePagerViewManager : ViewGroupManager<CollapsiblePagerHost>(),
|
|
|
239
239
|
view.applyNativeHeaderStyle()
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
+
@ReactProp(name = "nativeTabBarVariant")
|
|
243
|
+
override fun setNativeTabBarVariant(view: CollapsiblePagerHost?, value: String?) {
|
|
244
|
+
view ?: return
|
|
245
|
+
view.nativeTabBarVariant = if (value == "segmented") "segmented" else "underline"
|
|
246
|
+
view.applyNativeHeaderStyle()
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
@ReactProp(name = "nativeTabBarTrailingReservedWidth", defaultDouble = 0.0)
|
|
250
|
+
override fun setNativeTabBarTrailingReservedWidth(view: CollapsiblePagerHost?, value: Double) {
|
|
251
|
+
view?.nativeTabBarTrailingReservedWidth = value
|
|
252
|
+
}
|
|
253
|
+
|
|
242
254
|
@ReactProp(name = "nativeTabBarContentPaddingHorizontal")
|
|
243
255
|
override fun setNativeTabBarContentPaddingHorizontal(
|
|
244
256
|
view: CollapsiblePagerHost?,
|
|
@@ -249,6 +261,13 @@ class CollapsiblePagerViewManager : ViewGroupManager<CollapsiblePagerHost>(),
|
|
|
249
261
|
view.applyNativeHeaderStyle()
|
|
250
262
|
}
|
|
251
263
|
|
|
264
|
+
@ReactProp(name = "nativeTabBarContentPaddingVertical")
|
|
265
|
+
override fun setNativeTabBarContentPaddingVertical(view: CollapsiblePagerHost?, value: Double) {
|
|
266
|
+
view ?: return
|
|
267
|
+
view.nativeTabBarContentPaddingVertical = if (value.isFinite()) value.coerceAtLeast(0.0) else 0.0
|
|
268
|
+
view.applyNativeHeaderStyle()
|
|
269
|
+
}
|
|
270
|
+
|
|
252
271
|
@ReactProp(name = "nativeTabBarItemSpacing")
|
|
253
272
|
override fun setNativeTabBarItemSpacing(view: CollapsiblePagerHost?, value: Double) {
|
|
254
273
|
view ?: return
|
|
@@ -343,6 +362,14 @@ class CollapsiblePagerViewManager : ViewGroupManager<CollapsiblePagerHost>(),
|
|
|
343
362
|
view?.scrollToTop(target, animated)
|
|
344
363
|
}
|
|
345
364
|
|
|
365
|
+
override fun scrollHeaderBy(view: CollapsiblePagerHost?, delta: Double) {
|
|
366
|
+
view?.scrollHeaderBy(delta)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
override fun scrollHeaderToOffset(view: CollapsiblePagerHost?, offset: Double) {
|
|
370
|
+
view?.scrollHeaderToOffset(offset)
|
|
371
|
+
}
|
|
372
|
+
|
|
346
373
|
override fun setPage(view: CollapsiblePagerHost?, selectedPage: Int) {
|
|
347
374
|
if (view != null && selectedPage in 0 until view.adapter.itemCount) {
|
|
348
375
|
view.beginPageTransition(selectedPage)
|
|
@@ -37,26 +37,50 @@ class NativeScrollerView(context: Context) : FrameLayout(context) {
|
|
|
37
37
|
setMeasuredDimension(MeasureSpec.getSize(widthSpec), MeasureSpec.getSize(heightSpec))
|
|
38
38
|
}
|
|
39
39
|
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) = Unit
|
|
40
|
+
|
|
41
|
+
override fun startViewTransition(view: View) {
|
|
42
|
+
super.startViewTransition(view)
|
|
43
|
+
if (view === reactChild) transitioningReactChild = view
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
override fun endViewTransition(view: View) {
|
|
47
|
+
super.endViewTransition(view)
|
|
48
|
+
if (transitioningReactChild === view) transitioningReactChild = null
|
|
49
|
+
if (retainedReactChild === view) {
|
|
50
|
+
clearRetainedGeometry()
|
|
51
|
+
if (isAttachedToWindow) scheduleLayout()
|
|
52
|
+
}
|
|
53
|
+
}
|
|
40
54
|
}
|
|
41
55
|
private val contentFrame = object : FrameLayout(context) {
|
|
42
56
|
override fun onMeasure(widthSpec: Int, heightSpec: Int) {
|
|
43
57
|
setMeasuredDimension(
|
|
44
|
-
max(MeasureSpec.getSize(widthSpec),
|
|
45
|
-
|
|
58
|
+
max(MeasureSpec.getSize(widthSpec), effectiveContentWidthPx + insetLeft + insetRight),
|
|
59
|
+
effectiveContentHeightPx + contentTop + insetBottom,
|
|
46
60
|
)
|
|
47
61
|
}
|
|
48
62
|
|
|
49
63
|
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
|
50
64
|
// Only the native origin layer moves. Fabric owns its React child's layout/transform.
|
|
65
|
+
val contentWidth = effectiveContentWidthPx
|
|
66
|
+
val contentHeight = effectiveContentHeightPx
|
|
51
67
|
contentOriginFrame.measure(
|
|
52
|
-
MeasureSpec.makeMeasureSpec(
|
|
53
|
-
MeasureSpec.makeMeasureSpec(
|
|
68
|
+
MeasureSpec.makeMeasureSpec(contentWidth, MeasureSpec.EXACTLY),
|
|
69
|
+
MeasureSpec.makeMeasureSpec(contentHeight, MeasureSpec.EXACTLY),
|
|
54
70
|
)
|
|
55
|
-
contentOriginFrame.layout(insetLeft, contentTop, insetLeft +
|
|
71
|
+
contentOriginFrame.layout(insetLeft, contentTop, insetLeft + contentWidth, contentTop + contentHeight)
|
|
56
72
|
}
|
|
57
73
|
}
|
|
58
74
|
private val pagerInsets = WeakHashMap<View, Int>()
|
|
59
75
|
private var reactChild: View? = null
|
|
76
|
+
private var transitioningReactChild: View? = null
|
|
77
|
+
private var retainedReactChild: View? = null
|
|
78
|
+
private var retainedContentWidthPx = 0
|
|
79
|
+
private var retainedContentHeightPx = 0
|
|
80
|
+
private val effectiveContentWidthPx: Int
|
|
81
|
+
get() = if (reactChild == null && retainedReactChild != null) retainedContentWidthPx else contentWidthPx
|
|
82
|
+
private val effectiveContentHeightPx: Int
|
|
83
|
+
get() = if (reactChild == null && retainedReactChild != null) retainedContentHeightPx else contentHeightPx
|
|
60
84
|
private var fabricState: StateWrapper? = null
|
|
61
85
|
private var lastOriginX: Int? = null
|
|
62
86
|
private var lastOriginY: Int? = null
|
|
@@ -172,6 +196,8 @@ class NativeScrollerView(context: Context) : FrameLayout(context) {
|
|
|
172
196
|
internal fun addReactChild(child: View, index: Int) {
|
|
173
197
|
require(index == 0 && reactChild == null) { "NativeScroller requires one React content view" }
|
|
174
198
|
viewport.stopMotion()
|
|
199
|
+
clearRetainedGeometry()
|
|
200
|
+
transitioningReactChild = null
|
|
175
201
|
reactChild = child
|
|
176
202
|
contentOriginFrame.addView(child)
|
|
177
203
|
scheduleLayout()
|
|
@@ -180,13 +206,29 @@ class NativeScrollerView(context: Context) : FrameLayout(context) {
|
|
|
180
206
|
internal fun removeReactChild() {
|
|
181
207
|
viewport.stopMotion()
|
|
182
208
|
pendingRestore.cancel()
|
|
183
|
-
reactChild?.let
|
|
209
|
+
reactChild?.let { child ->
|
|
210
|
+
clearRetainedGeometry()
|
|
211
|
+
// Fabric removes the child before its disappearing view finishes drawing.
|
|
212
|
+
if (transitioningReactChild === child &&
|
|
213
|
+
contentOriginFrame.width > 0 && contentOriginFrame.height > 0) {
|
|
214
|
+
retainedReactChild = child
|
|
215
|
+
retainedContentWidthPx = contentOriginFrame.width
|
|
216
|
+
retainedContentHeightPx = contentOriginFrame.height
|
|
217
|
+
}
|
|
218
|
+
contentOriginFrame.removeView(child)
|
|
219
|
+
}
|
|
184
220
|
reactChild = null
|
|
185
221
|
contentWidthPx = 0
|
|
186
222
|
contentHeightPx = 0
|
|
187
223
|
scheduleLayout()
|
|
188
224
|
}
|
|
189
225
|
|
|
226
|
+
private fun clearRetainedGeometry() {
|
|
227
|
+
retainedReactChild = null
|
|
228
|
+
retainedContentWidthPx = 0
|
|
229
|
+
retainedContentHeightPx = 0
|
|
230
|
+
}
|
|
231
|
+
|
|
190
232
|
internal fun reactChildCount() = if (reactChild == null) 0 else 1
|
|
191
233
|
internal fun reactChildAt(index: Int): View {
|
|
192
234
|
require(index == 0)
|
|
@@ -381,6 +423,8 @@ class NativeScrollerView(context: Context) : FrameLayout(context) {
|
|
|
381
423
|
lastContentViewportHeight = null
|
|
382
424
|
viewport.stopMotion()
|
|
383
425
|
viewport.setTag(R.id.onekey_native_scroll_coordinator_offset_listener, null)
|
|
426
|
+
transitioningReactChild = null
|
|
427
|
+
clearRetainedGeometry()
|
|
384
428
|
super.onDetachedFromWindow()
|
|
385
429
|
}
|
|
386
430
|
|
package/docs/SPEC.md
CHANGED
|
@@ -74,7 +74,7 @@ the shared host before their old scroll-edge interaction is released.
|
|
|
74
74
|
The measured overlay height is excluded from the collapsible header distance:
|
|
75
75
|
`header` remains collapsible and `stickyHeader` pins immediately below the
|
|
76
76
|
overlay. Selection, cancellation, reuse and disposal rebind or release the
|
|
77
|
-
interaction and restore any prior scroll-edge style. Native scroll/refresh
|
|
77
|
+
interaction and restore any prior scroll-edge style. The existing owner-scoped native sticky-height callback also reaches NativeList hosts, so their React container slots clip below the same fixed viewport region and release it when the owner detaches. Native scroll/refresh
|
|
78
78
|
coordination continues on the main thread without per-frame JavaScript work.
|
|
79
79
|
Refresh retains the pager-owned `overlay + header + sticky` top inset but offsets
|
|
80
80
|
the indicator by `header + sticky`, so its spinner begins below the fixed overlay.
|
|
@@ -171,6 +171,24 @@ intent. Real vertical consumption, a newer valid command, or owner detachment
|
|
|
171
171
|
releases the target. Existing NativeList and NativeScroller public coordinates
|
|
172
172
|
are unchanged. The same page-top/sticky-top destinations are available without smooth-header mode.
|
|
173
173
|
|
|
174
|
+
## Current-page partial header command
|
|
175
|
+
|
|
176
|
+
`scrollHeaderBy(delta: number)` moves the current page's shared collapsible
|
|
177
|
+
header from its displayed offset C to `clamp(C + delta, 0, D)`.
|
|
178
|
+
`scrollHeaderToOffset(offset: number)` moves it to `clamp(offset, 0, D)`
|
|
179
|
+
regardless of intervening native keyboard or user scrolling. D is the latest
|
|
180
|
+
measured collapsible height. Inputs use logical points on iOS and Android and
|
|
181
|
+
CSS pixels on Web. Non-finite values are ignored. The header and current body move immediately
|
|
182
|
+
so a focused input becomes visible before the next keyboard frame. A valid command
|
|
183
|
+
clears only the current page's body reading position B; it never changes
|
|
184
|
+
another page's memory or adds permanent content/insets. It requires the same
|
|
185
|
+
stable attached current owner as the page-top commands and is a safe no-op
|
|
186
|
+
during horizontal transitions, before mount, or after disposal. A newer
|
|
187
|
+
command or real user vertical consumption takes ownership from an older one.
|
|
188
|
+
Dynamic header measurements clamp any in-flight target to the latest D.
|
|
189
|
+
This command allows a focused input inside the shared header to move above a
|
|
190
|
+
keyboard without replacing the pager's sole vertical scroll owner.
|
|
191
|
+
|
|
174
192
|
## Independent NativeScroller migration
|
|
175
193
|
|
|
176
194
|
Status: implemented in source. Both native builds pass. Android focused emulator
|
|
@@ -333,6 +351,32 @@ warnings were observed. Keyboard, multi-touch, physical-device performance and
|
|
|
333
351
|
the full lifecycle/interaction matrix remain unverified. These are local source
|
|
334
352
|
builds; the currently installed npm version does not yet contain this migration.
|
|
335
353
|
|
|
354
|
+
## Android removal-transition geometry retention
|
|
355
|
+
|
|
356
|
+
Status: implemented locally; unreleased. On the Android UI thread, during a
|
|
357
|
+
screen removal transition,
|
|
358
|
+
NativeList and CollapsiblePager layout entry points respect the RecyclerView
|
|
359
|
+
suppression state established by the screen-transition owner. The owner restores
|
|
360
|
+
the original state when its last transition ownership ends. This adds no public
|
|
361
|
+
API and does not change Web or iOS behavior.
|
|
362
|
+
|
|
363
|
+
If Fabric removes the current NativeScroller React child while its origin-frame
|
|
364
|
+
transition remains active, Android retains the already displayed native extent
|
|
365
|
+
for drawing. The current child and raw content dimensions still clear normally;
|
|
366
|
+
only native content measurement and layout use the retained effective extent.
|
|
367
|
+
The matching transition end or detach clears the private child and extent state
|
|
368
|
+
idempotently. Adding a new child cancels prior retention, and a late end for the
|
|
369
|
+
old child cannot alter the new content. Ordinary removal, size events and scroll
|
|
370
|
+
commands keep their existing semantics.
|
|
371
|
+
|
|
372
|
+
Local arm64 DevShell acceptance covered Base, CXO and Overview exit content,
|
|
373
|
+
normal scrolling, RecyclerView ownership and suppression cleanup for the
|
|
374
|
+
observed CXO exit, and NativeScroller raw-zero content dimensions with a
|
|
375
|
+
nonzero displayed origin followed by detach cleanup. Nested-owner and cancel
|
|
376
|
+
paths, Overview rebound/cancel, natural resize and new-child runtime cases,
|
|
377
|
+
and the full performance matrix remain unverified. SDK publication and
|
|
378
|
+
consumer version alignment are separate steps.
|
|
379
|
+
|
|
336
380
|
## iOS vertical-scroll press cancellation
|
|
337
381
|
|
|
338
382
|
Status: implemented in source. A source-identical `node_modules` build was
|
|
@@ -452,6 +496,40 @@ The attached UIKit regression intentionally constrains the real pager pan to two
|
|
|
452
496
|
required touches to prove the no-pan path; it is distinct from natural Home OS
|
|
453
497
|
input, coalesced-event delivery, and physical-device acceptance.
|
|
454
498
|
|
|
499
|
+
## Native segmented tab bar
|
|
500
|
+
|
|
501
|
+
Status: implemented locally. Focused production-class iOS XCTest and Android
|
|
502
|
+
instrumentation checks pass; consuming App interaction acceptance is separate.
|
|
503
|
+
`nativeTabBar.style.variant` accepts `"underline"` (the default) or
|
|
504
|
+
`"segmented"` on iOS and Android. The optional
|
|
505
|
+
`nativeTabBar.style.contentPaddingVertical` is a nonnegative logical inset,
|
|
506
|
+
defaulting to zero. Omitting both fields preserves the existing content-width,
|
|
507
|
+
horizontally scrollable underline bar. Web ignores `nativeTabBar` and retains
|
|
508
|
+
its caller-rendered sticky header.
|
|
509
|
+
|
|
510
|
+
In segmented mode, the existing `height` is the entire tab slot. The track
|
|
511
|
+
fills the space inside `contentPaddingHorizontal` and
|
|
512
|
+
`contentPaddingVertical`, uses `backgroundColor`, and has a fully rounded
|
|
513
|
+
outline. Its visible items divide the track into equal-width segments in their
|
|
514
|
+
current order, including after item insertion, removal, relabeling, or resize.
|
|
515
|
+
The thumb uses `indicatorColor`, is inset two logical points from its segment,
|
|
516
|
+
and is fully rounded. Segment titles use the existing font and active/inactive
|
|
517
|
+
text colors, remain single-line with truncation, and retain their existing
|
|
518
|
+
accessibility labels, test IDs, and native press event keys. The segmented bar
|
|
519
|
+
does not scroll horizontally; `itemSpacing`, `indicatorHeight`, and
|
|
520
|
+
`indicatorBottom` apply only to underline mode. The pager's native fractional
|
|
521
|
+
page progress moves the thumb continuously between equal-width segments and
|
|
522
|
+
interpolates text emphasis without per-frame JavaScript updates. It settles at
|
|
523
|
+
the actual selected page after a completed or cancelled gesture. RTL reverses
|
|
524
|
+
physical segment order without changing logical page keys or indices.
|
|
525
|
+
|
|
526
|
+
Switching variants or removing the native tab bar clears the previous visual
|
|
527
|
+
state. The existing native press command, page callbacks, retention, vertical
|
|
528
|
+
header geometry, and Web behavior remain unchanged. Acceptance requires
|
|
529
|
+
focused native geometry/progress/press checks on iOS and Android, including
|
|
530
|
+
two or three segments, dynamic labels/items, RTL, narrow widths, and variant
|
|
531
|
+
switching. Type forwarding alone does not prove rendered behavior.
|
|
532
|
+
|
|
455
533
|
## Native tab-press animation default
|
|
456
534
|
|
|
457
535
|
Behavior change: previously an omitted `nativeTabPressAnimationEnabled` animated
|
|
@@ -472,6 +550,28 @@ Status: implemented; focused native-wrapper tests cover both directions,
|
|
|
472
550
|
explicit overrides and in-flight/superseded command targets. Home applies this policy to its JS tab bar, with iOS and
|
|
473
551
|
Android simulator recordings verifying distant jumps and adjacent transitions.
|
|
474
552
|
|
|
553
|
+
## Native tab trailing control reservation
|
|
554
|
+
|
|
555
|
+
Status: implemented locally, unreleased. The focused Android host test passed
|
|
556
|
+
(1/1); iOS hit tests had 8 passes and 1 existing skip. iOS companion-app UI
|
|
557
|
+
acceptance remains unverified.
|
|
558
|
+
`nativeTabBar.style.trailingReservedWidth` is an optional logical trailing
|
|
559
|
+
reservation for a sibling React control in the full-width sticky header. It
|
|
560
|
+
defaults to zero. JavaScript normalizes non-finite or negative input to zero;
|
|
561
|
+
native also clamps the reservation to the actual tab viewport width. On Android
|
|
562
|
+
and iOS, only the native tab viewport shrinks from `W` to `W - R`: its frame
|
|
563
|
+
begins at zero in LTR and at `R` in RTL. The sticky React header, its height,
|
|
564
|
+
and page content keep their original full-width geometry. Touches in the
|
|
565
|
+
reserved region reach the sibling control, while native tab hit testing and
|
|
566
|
+
last-tab visibility use the reduced viewport. Changing the reservation updates
|
|
567
|
+
that viewport; removing it or recycling the pager restores the default zero.
|
|
568
|
+
Web does not render the native tab and ignores this style field. For a positive
|
|
569
|
+
reservation, the box-none sticky React control draws above the native tab so
|
|
570
|
+
its inward fade and toolbar hit area remain visible; zero preserves the old
|
|
571
|
+
native-tab foreground order. Acceptance requires focused native layout and hit
|
|
572
|
+
testing for zero, nonzero, reset, and RTL reservations; type forwarding alone
|
|
573
|
+
is insufficient.
|
|
574
|
+
|
|
475
575
|
## Android refresh foreground coordination
|
|
476
576
|
|
|
477
577
|
Status: implemented; Android Home simulator placement verified on 2026-09-28.
|
|
@@ -817,6 +917,51 @@ fallback debounce, including replacement-state restoration. Other collection
|
|
|
817
917
|
fallbacks keep that safety delay. Caller insets and retained logical offsets
|
|
818
918
|
remain governed by the existing restoration policy.
|
|
819
919
|
|
|
920
|
+
When the active scroll owner is removed before its replacement becomes usable,
|
|
921
|
+
the pager MUST retain bounded recovery (at most 12 retries per recovery round,
|
|
922
|
+
spaced 100 ms apart). Clearing the completed mount's reattachment flag MUST NOT
|
|
923
|
+
cancel that recovery. A replacement that becomes usable during that round,
|
|
924
|
+
without another Fabric insertion, MUST receive the observer, shared headers,
|
|
925
|
+
insets and retained collapse state. Page selection changes and recycling
|
|
926
|
+
invalidate stale retry callbacks. Repeated mount attempts MUST NOT reset the
|
|
927
|
+
retry budget. Once all 12 retries have been scheduled, an Insert whose parent is
|
|
928
|
+
in the current page subtree MUST reopen recovery if that page retains a released
|
|
929
|
+
scroll owner, including when the final retry callback is still pending. Reopening
|
|
930
|
+
invalidates the old callback before clearing its scheduled state. The Insert
|
|
931
|
+
MUST be recorded during a horizontal drag or transition, while observer and shared
|
|
932
|
+
header attachment remain deferred until selection settles. Cancelling the drag
|
|
933
|
+
on the same page MUST retain the reopened recovery round. Empty transactions,
|
|
934
|
+
removals and inserts outside the current page MUST NOT reopen the budget. Owned
|
|
935
|
+
`NativeScroller` and `NativeList` hosts additionally emit push readiness
|
|
936
|
+
notifications when their scroll views attach and obtain positive bounds so late
|
|
937
|
+
replacements recover immediately even without another Fabric insertion or after
|
|
938
|
+
the retry budget expires; static pages with neither an unready scroll host nor a
|
|
939
|
+
released scroll owner do not schedule retry timers.
|
|
940
|
+
|
|
941
|
+
Status of transition-time and pending-final-retry recovery: implemented and
|
|
942
|
+
UIKit regression verified on iOS 26.5 (2026-10-27). Both new regressions fail
|
|
943
|
+
before the fix and pass afterward. Cancellation settles on the source page
|
|
944
|
+
before the replacement becomes usable; the pending-final-retry case makes the
|
|
945
|
+
replacement usable after the old timer's deadline. Observer and shared header
|
|
946
|
+
ownership stay detached during the drag, then recover with the retained collapse.
|
|
947
|
+
The full Pager suite executes 85 tests, with one iOS-27-only skip and no failures.
|
|
948
|
+
|
|
949
|
+
Status of exhausted-budget recovery: implemented and UIKit regression verified
|
|
950
|
+
on iOS 26.5 (2026-10-27). The regression exhausts the initial round, checks that
|
|
951
|
+
empty transactions, removals and offscreen inserts do not reopen it, then inserts
|
|
952
|
+
a current-page replacement whose scroller appears after did-mount. It fails
|
|
953
|
+
before the fix and passes afterward, including preservation of an active round
|
|
954
|
+
and restoration of the observer, shared headers and retained collapse. The full
|
|
955
|
+
Pager suite executes 83 tests, with one iOS-27-only skip and no failures.
|
|
956
|
+
|
|
957
|
+
Status of replacement recovery: implemented and runtime verified on iOS 26.5
|
|
958
|
+
(2026-10-23). The late-replacement UIKit regression fails before the fix and
|
|
959
|
+
passes afterward; the full Pager suite executes 82 tests, with one iOS-27-only
|
|
960
|
+
skip and no failures. In the consuming app based on x at b0789571d8 with SDK
|
|
961
|
+
6.7.0-9 and this local native change, two empty Favorites-to-Wallet-add-to-Market
|
|
962
|
+
cycles and a Trending/Favorites round trip keep the first row aligned with the
|
|
963
|
+
sticky header. The previously observed 212-point gap is absent.
|
|
964
|
+
|
|
820
965
|
This is a UI-thread, main-runtime native coordination change with no new props,
|
|
821
966
|
events, background-runtime work, persistence or retained strong view cache.
|
|
822
967
|
Android already prepares discovered scroll children before draw and is unchanged;
|