@lodev09/react-native-true-sheet 4.0.0-beta.29 → 4.0.0-beta.30
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/lodev09/truesheet/TrueSheetView.kt +18 -8
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +5 -2
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetCoordinatorLayout.kt +38 -6
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetOverlayRootView.kt +4 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetStackManager.kt +2 -2
- package/ios/TrueSheetContainerView.mm +12 -14
- package/ios/TrueSheetContentView.mm +6 -0
- package/ios/TrueSheetFooterView.mm +40 -2
- package/ios/TrueSheetOverlayView.h +10 -0
- package/ios/TrueSheetOverlayView.mm +14 -0
- package/ios/TrueSheetViewController.mm +20 -1
- package/ios/core/TrueSheetKeyboardObserver.h +7 -0
- package/ios/core/TrueSheetKeyboardObserver.mm +7 -0
- package/lib/module/navigation/createTrueSheetRouter.js +47 -3
- package/lib/module/navigation/createTrueSheetRouter.js.map +1 -1
- package/lib/module/navigation/expo-router/extendTrueSheetRouter.js +31 -3
- package/lib/module/navigation/expo-router/extendTrueSheetRouter.js.map +1 -1
- package/lib/module/navigation/expo-router/index.js +9 -6
- package/lib/module/navigation/expo-router/index.js.map +1 -1
- package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts +10 -0
- package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts.map +1 -1
- package/lib/typescript/src/navigation/expo-router/extendTrueSheetRouter.d.ts +6 -2
- package/lib/typescript/src/navigation/expo-router/extendTrueSheetRouter.d.ts.map +1 -1
- package/lib/typescript/src/navigation/expo-router/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/navigation/createTrueSheetRouter.ts +72 -4
- package/src/navigation/expo-router/extendTrueSheetRouter.ts +33 -5
- package/src/navigation/expo-router/index.ts +14 -9
|
@@ -448,6 +448,17 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
448
448
|
viewController.createSheet()
|
|
449
449
|
setupScrollable()
|
|
450
450
|
|
|
451
|
+
// Attach before any keyboard wait so the touches meanwhile don't reach the views below
|
|
452
|
+
rootContainerView = findRootContainerView()
|
|
453
|
+
rootContainerView?.let { container ->
|
|
454
|
+
viewController.coordinatorLayout?.let {
|
|
455
|
+
it.blocksTouches = true
|
|
456
|
+
container.addView(it)
|
|
457
|
+
// Touches skip a view without a frame — don't wait for the next layout pass
|
|
458
|
+
it.layout(0, 0, container.width, container.height)
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
451
462
|
// Dismiss keyboard if focused view is within a sheet or if target detent will be dimmed
|
|
452
463
|
val parentSheet = TrueSheetStackManager.getTopmostSheet()
|
|
453
464
|
val isFocusedViewWithinSheet = parentSheet?.viewController?.isFocusedViewWithinSheet() == true
|
|
@@ -481,10 +492,6 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
481
492
|
return
|
|
482
493
|
}
|
|
483
494
|
|
|
484
|
-
// Attach coordinator to the root container
|
|
485
|
-
rootContainerView = findRootContainerView()
|
|
486
|
-
viewController.coordinatorLayout?.let { rootContainerView?.addView(it) }
|
|
487
|
-
|
|
488
495
|
// Register with observer to track sheet stack hierarchy
|
|
489
496
|
viewController.parentSheetView = TrueSheetStackManager.registerSheet(this)
|
|
490
497
|
|
|
@@ -492,6 +499,11 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
492
499
|
viewController.present(detentIndex, animated)
|
|
493
500
|
}
|
|
494
501
|
|
|
502
|
+
private fun detachCoordinator() {
|
|
503
|
+
viewController.coordinatorLayout?.let { rootContainerView?.removeView(it) }
|
|
504
|
+
rootContainerView = null
|
|
505
|
+
}
|
|
506
|
+
|
|
495
507
|
@UiThread
|
|
496
508
|
fun handleBackPress() {
|
|
497
509
|
viewController.handleBackPress()
|
|
@@ -505,6 +517,7 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
505
517
|
val callback = pendingPresentCallback ?: return false
|
|
506
518
|
pendingPresentCallback = null
|
|
507
519
|
pendingPresentParent = null
|
|
520
|
+
detachCoordinator()
|
|
508
521
|
if (restoreFocus) {
|
|
509
522
|
viewController.restoreFocusedView()
|
|
510
523
|
} else {
|
|
@@ -725,10 +738,7 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
725
738
|
// A slide-back cut short by this sheet's own dismissal never emits; don't carry it over
|
|
726
739
|
isDidFocusPending = false
|
|
727
740
|
|
|
728
|
-
|
|
729
|
-
viewController.coordinatorLayout?.let { rootContainerView?.removeView(it) }
|
|
730
|
-
rootContainerView = null
|
|
731
|
-
|
|
741
|
+
detachCoordinator()
|
|
732
742
|
cleanupScreenEventObserver()
|
|
733
743
|
|
|
734
744
|
val surfaceId = UIManagerHelper.getSurfaceId(this)
|
|
@@ -1017,6 +1017,8 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1017
1017
|
}
|
|
1018
1018
|
|
|
1019
1019
|
private fun finishPresent() {
|
|
1020
|
+
coordinatorLayout?.blocksTouches = false
|
|
1021
|
+
|
|
1020
1022
|
// Restore isHideable to actual value after present animation
|
|
1021
1023
|
behavior?.isHideable = dismissible
|
|
1022
1024
|
|
|
@@ -1550,8 +1552,9 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1550
1552
|
delegate?.viewControllerWillDismiss()
|
|
1551
1553
|
parentSheetView?.viewControllerWillFocus()
|
|
1552
1554
|
|
|
1553
|
-
// Dismissed mid-present: finishPresent() won't run.
|
|
1554
|
-
// before didDismiss so JS clears isPresenting and unmounts the lazy native view
|
|
1555
|
+
// Dismissed mid-present: finishPresent() won't run. Stop blocking touches, and settle the
|
|
1556
|
+
// pending present well before didDismiss so JS clears isPresenting and unmounts the lazy native view
|
|
1557
|
+
coordinatorLayout?.blocksTouches = false
|
|
1555
1558
|
presentPromise?.invoke()
|
|
1556
1559
|
presentPromise = null
|
|
1557
1560
|
}
|
|
@@ -80,19 +80,29 @@ class TrueSheetCoordinatorLayout(context: Context) :
|
|
|
80
80
|
// Pull-down tracking. BottomSheetBehavior gives no callback when a downward drag
|
|
81
81
|
// is clamped by `isHideable = false` — the sheet stays put (or the scrollable
|
|
82
82
|
// overscrolls) and no state change fires. Accumulate the downward finger travel
|
|
83
|
-
// neither the sheet nor
|
|
84
|
-
// controller can surface a blocked dismiss attempt.
|
|
83
|
+
// neither the sheet nor a scroller under the finger followed and report it on
|
|
84
|
+
// release so the controller can surface a blocked dismiss attempt. Like iOS, a
|
|
85
|
+
// pull that began on a scroller that could scroll up stays a scroll, even past
|
|
86
|
+
// its top.
|
|
85
87
|
private var pullTracking = false
|
|
86
88
|
private var pullStartX = 0f
|
|
87
89
|
private var pullStartY = 0f
|
|
88
90
|
private var pullLastY = 0f
|
|
89
91
|
private var pullOverdrag = 0f
|
|
90
92
|
private var pullKeyboardVisible = false
|
|
93
|
+
private var pullScrollers: List<View> = emptyList()
|
|
94
|
+
private var pullBeganScrolled = false
|
|
91
95
|
|
|
92
96
|
// A touch that began inside the sheet is still down
|
|
93
97
|
val isTouchingSheet: Boolean
|
|
94
98
|
get() = pullTracking
|
|
95
99
|
|
|
100
|
+
// Set while the sheet presents, from present() (keyboard wait included) until the
|
|
101
|
+
// present finishes or a dismiss starts. A stream that begins meanwhile is swallowed
|
|
102
|
+
// whole so nothing below takes it, like the iOS presentation transition.
|
|
103
|
+
var blocksTouches = false
|
|
104
|
+
private var streamBlocked = false
|
|
105
|
+
|
|
96
106
|
init {
|
|
97
107
|
layoutParams = LayoutParams(
|
|
98
108
|
LayoutParams.MATCH_PARENT,
|
|
@@ -123,6 +133,14 @@ class TrueSheetCoordinatorLayout(context: Context) :
|
|
|
123
133
|
get() = PointerEvents.BOX_NONE
|
|
124
134
|
|
|
125
135
|
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
|
|
136
|
+
if (ev.actionMasked == MotionEvent.ACTION_DOWN) streamBlocked = blocksTouches
|
|
137
|
+
if (streamBlocked) {
|
|
138
|
+
if (ev.actionMasked == MotionEvent.ACTION_UP || ev.actionMasked == MotionEvent.ACTION_CANCEL) {
|
|
139
|
+
streamBlocked = false
|
|
140
|
+
}
|
|
141
|
+
return true
|
|
142
|
+
}
|
|
143
|
+
|
|
126
144
|
when (ev.actionMasked) {
|
|
127
145
|
MotionEvent.ACTION_DOWN -> {
|
|
128
146
|
val sheet = delegate?.findSheetView()
|
|
@@ -140,9 +158,14 @@ class TrueSheetCoordinatorLayout(context: Context) :
|
|
|
140
158
|
val handled = super.dispatchTouchEvent(ev)
|
|
141
159
|
val step = ev.rawY - pullLastY
|
|
142
160
|
pullLastY = ev.rawY
|
|
143
|
-
// Finger moved down but the sheet stayed put and
|
|
144
|
-
//
|
|
145
|
-
if (
|
|
161
|
+
// Finger moved down but the sheet stayed put and nothing under the finger
|
|
162
|
+
// scrolled — the pull was clamped
|
|
163
|
+
if (!pullBeganScrolled &&
|
|
164
|
+
step > 0 &&
|
|
165
|
+
sheet != null &&
|
|
166
|
+
sheet.top == topBefore &&
|
|
167
|
+
pullScrollers.none { it.canScrollVertically(-1) }
|
|
168
|
+
) {
|
|
146
169
|
pullOverdrag += step
|
|
147
170
|
}
|
|
148
171
|
return handled
|
|
@@ -205,18 +228,27 @@ class TrueSheetCoordinatorLayout(context: Context) :
|
|
|
205
228
|
/**
|
|
206
229
|
* Resolves the stream's touch target (respecting zIndex and pointerEvents) and
|
|
207
230
|
* decides whether the sheet may drag from it — inside the sheet, outside the detected
|
|
208
|
-
* scrollable, and nothing along the target's chain scrolls vertically.
|
|
231
|
+
* scrollable, and nothing along the target's chain scrolls vertically. Also collects
|
|
232
|
+
* the chain's vertical scrollers for pull-down tracking.
|
|
209
233
|
*/
|
|
210
234
|
private fun updateStreamTargetFlags(ev: MotionEvent) {
|
|
211
235
|
streamSheetDraggable = false
|
|
212
236
|
streamDraggableEditText = false
|
|
213
237
|
streamScrollsIndependently = false
|
|
238
|
+
pullScrollers = emptyList()
|
|
239
|
+
pullBeganScrolled = false
|
|
214
240
|
|
|
215
241
|
val sheet = delegate?.findSheetView() ?: return
|
|
216
242
|
val target = TouchTargetHelper.findTargetPathAndCoordinatesForTouch(ev.x, ev.y, this, FloatArray(2))
|
|
217
243
|
.firstNotNullOfOrNull { it.getView() } ?: return
|
|
218
244
|
if (target !== sheet && !target.isDescendantOf(sheet)) return
|
|
219
245
|
|
|
246
|
+
pullScrollers = generateSequence(target) { it.parent as? View }
|
|
247
|
+
.takeWhile { it !== this }
|
|
248
|
+
.filter { it.canScrollVertically(1) || it.canScrollVertically(-1) }
|
|
249
|
+
.toList()
|
|
250
|
+
pullBeganScrolled = pullScrollers.any { it.canScrollVertically(-1) }
|
|
251
|
+
|
|
220
252
|
// A detected scrollable that can scroll (or hosts pull-to-refresh) owns its
|
|
221
253
|
// touches — nested scrolling drags the sheet. When it can't scroll (e.g.
|
|
222
254
|
// the sheet is fully expanded and the content fits) it eats the gesture
|
|
@@ -44,6 +44,10 @@ class TrueSheetOverlayRootView(private val reactContext: ThemedReactContext) :
|
|
|
44
44
|
init {
|
|
45
45
|
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
|
46
46
|
|
|
47
|
+
// React children draw outside their bounds (shadows, overflow)
|
|
48
|
+
clipChildren = false
|
|
49
|
+
clipToPadding = false
|
|
50
|
+
|
|
47
51
|
// Draws and receives touches above the sheet coordinators regardless of add order
|
|
48
52
|
translationZ = OVERLAY_Z
|
|
49
53
|
}
|
|
@@ -161,7 +161,7 @@ object TrueSheetStackManager {
|
|
|
161
161
|
return
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
// The dimmed sheet
|
|
164
|
+
// The dimmed sheet, any sheets above it, and overlays (rendered above every sheet) stay accessible
|
|
165
165
|
val accessibleCoordinators = presentedSheetStack
|
|
166
166
|
.subList(topDimmedIndex, presentedSheetStack.size)
|
|
167
167
|
.mapNotNull { it.viewController.coordinatorLayout }
|
|
@@ -169,7 +169,7 @@ object TrueSheetStackManager {
|
|
|
169
169
|
|
|
170
170
|
for (i in 0 until root.childCount) {
|
|
171
171
|
val child = root.getChildAt(i)
|
|
172
|
-
if (child in accessibleCoordinators) {
|
|
172
|
+
if (child in accessibleCoordinators || child is TrueSheetOverlayRootView) {
|
|
173
173
|
savedBackgroundImportance.remove(child)?.let { child.importantForAccessibility = it }
|
|
174
174
|
continue
|
|
175
175
|
}
|
|
@@ -248,22 +248,29 @@ using namespace facebook::react;
|
|
|
248
248
|
_contentView.footerView = _footerView;
|
|
249
249
|
[_footerView setBottomInset:_footerBottomInset];
|
|
250
250
|
[self footerViewDidChangeSize:_footerView.frame.size];
|
|
251
|
+
|
|
252
|
+
// The observer is set up on present — a footer mounted after that (e.g. a
|
|
253
|
+
// conditional one) joins it here and rises above a keyboard that's already open
|
|
254
|
+
_footerView.keyboardObserver = _keyboardObserver;
|
|
255
|
+
[self updateFooterKeyboardOffset];
|
|
251
256
|
}
|
|
252
257
|
}
|
|
253
258
|
|
|
254
259
|
- (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index {
|
|
255
|
-
if (
|
|
260
|
+
if (childComponentView == _contentView) {
|
|
261
|
+
_contentView.keyboardObserver = nil;
|
|
256
262
|
_contentView.delegate = nil;
|
|
257
263
|
_contentView = nil;
|
|
258
264
|
}
|
|
259
265
|
|
|
260
|
-
if (
|
|
266
|
+
if (childComponentView == _headerView) {
|
|
261
267
|
_headerView.delegate = nil;
|
|
262
268
|
_headerView = nil;
|
|
263
269
|
[self headerViewDidChangeSize:CGSizeZero];
|
|
264
270
|
}
|
|
265
271
|
|
|
266
|
-
if (
|
|
272
|
+
if (childComponentView == _footerView) {
|
|
273
|
+
_footerView.keyboardObserver = nil;
|
|
267
274
|
_footerView.delegate = nil;
|
|
268
275
|
_footerView = nil;
|
|
269
276
|
_contentView.footerView = nil;
|
|
@@ -364,17 +371,8 @@ using namespace facebook::react;
|
|
|
364
371
|
|
|
365
372
|
_keyboardObserver = [[TrueSheetKeyboardObserver alloc] init];
|
|
366
373
|
_keyboardObserver.viewController = (TrueSheetViewController *)viewController;
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
_contentView.keyboardObserver = _keyboardObserver;
|
|
370
|
-
[_keyboardObserver addDelegate:_contentView];
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
if (_footerView) {
|
|
374
|
-
_footerView.keyboardObserver = _keyboardObserver;
|
|
375
|
-
[_keyboardObserver addDelegate:_footerView];
|
|
376
|
-
}
|
|
377
|
-
|
|
374
|
+
_contentView.keyboardObserver = _keyboardObserver;
|
|
375
|
+
_footerView.keyboardObserver = _keyboardObserver;
|
|
378
376
|
[_keyboardObserver start];
|
|
379
377
|
}
|
|
380
378
|
|
|
@@ -388,6 +388,12 @@ using namespace facebook::react;
|
|
|
388
388
|
|
|
389
389
|
#pragma mark - TrueSheetKeyboardObserverDelegate
|
|
390
390
|
|
|
391
|
+
- (void)setKeyboardObserver:(TrueSheetKeyboardObserver *)keyboardObserver {
|
|
392
|
+
[_keyboardObserver removeDelegate:self];
|
|
393
|
+
_keyboardObserver = keyboardObserver;
|
|
394
|
+
[keyboardObserver addDelegate:self];
|
|
395
|
+
}
|
|
396
|
+
|
|
391
397
|
- (void)keyboardWillShow:(CGFloat)height duration:(NSTimeInterval)duration curve:(UIViewAnimationOptions)curve {
|
|
392
398
|
if (!_detectedScrollView) {
|
|
393
399
|
return;
|
|
@@ -156,7 +156,19 @@ using namespace facebook::react;
|
|
|
156
156
|
|
|
157
157
|
- (void)updateLayoutMetrics:(const facebook::react::LayoutMetrics &)layoutMetrics
|
|
158
158
|
oldLayoutMetrics:(const facebook::react::LayoutMetrics &)oldLayoutMetrics {
|
|
159
|
-
|
|
159
|
+
// UIKit resizes the sheet for the keyboard inside its animation, and the new
|
|
160
|
+
// size usually relays out the footer in the same pass. When the keyboard
|
|
161
|
+
// changes during a React commit (e.g. its input unmounts), that layout can
|
|
162
|
+
// only mount after the commit, outside the animation — the footer, pinned to
|
|
163
|
+
// the container's bottom edge, would snap by the keyboard's height while the
|
|
164
|
+
// sheet is still animating. Carry it on the rest of the keyboard's curve.
|
|
165
|
+
if (layoutMetrics.frame.origin.y != oldLayoutMetrics.frame.origin.y) {
|
|
166
|
+
[self animateAlongKeyboard:^{
|
|
167
|
+
[super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics];
|
|
168
|
+
}];
|
|
169
|
+
} else {
|
|
170
|
+
[super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics];
|
|
171
|
+
}
|
|
160
172
|
|
|
161
173
|
// State lands before layout metrics within the same mount transaction, so
|
|
162
174
|
// this reflects the inset the shadow node baked into this frame — seeded
|
|
@@ -197,6 +209,28 @@ using namespace facebook::react;
|
|
|
197
209
|
|
|
198
210
|
#pragma mark - TrueSheetKeyboardObserverDelegate
|
|
199
211
|
|
|
212
|
+
- (void)setKeyboardObserver:(TrueSheetKeyboardObserver *)keyboardObserver {
|
|
213
|
+
[_keyboardObserver removeDelegate:self];
|
|
214
|
+
_keyboardObserver = keyboardObserver;
|
|
215
|
+
[keyboardObserver addDelegate:self];
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
- (void)animateAlongKeyboard:(void (^)(void))changes {
|
|
219
|
+
NSTimeInterval remaining = self.keyboardObserver.remainingAnimationDuration;
|
|
220
|
+
if (remaining > 0 && [UIView inheritedAnimationDuration] == 0) {
|
|
221
|
+
[UIView animateWithDuration:remaining
|
|
222
|
+
delay:0
|
|
223
|
+
options:self.keyboardObserver.animationCurve | UIViewAnimationOptionBeginFromCurrentState
|
|
224
|
+
animations:^{
|
|
225
|
+
changes();
|
|
226
|
+
[self syncEdgeEffect];
|
|
227
|
+
}
|
|
228
|
+
completion:nil];
|
|
229
|
+
} else {
|
|
230
|
+
changes();
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
200
234
|
// Only an absolute footer floats above the keyboard — a relative footer stays
|
|
201
235
|
// in the layout flow behind it, and an absolute one opts out via
|
|
202
236
|
// footerOptions.avoidKeyboard.
|
|
@@ -291,7 +325,11 @@ using namespace facebook::react;
|
|
|
291
325
|
|
|
292
326
|
CGFloat slide = [self keyboardSlideForHeight:height];
|
|
293
327
|
[self setKeyboardOffset:slide];
|
|
294
|
-
|
|
328
|
+
// A footer mounted or relaid out mid-keyboard animation follows the keyboard
|
|
329
|
+
// instead of snapping ahead of it
|
|
330
|
+
[self animateAlongKeyboard:^{
|
|
331
|
+
self.transform = CGAffineTransformMakeTranslation(0, -slide);
|
|
332
|
+
}];
|
|
295
333
|
}
|
|
296
334
|
|
|
297
335
|
@end
|
|
@@ -13,6 +13,11 @@
|
|
|
13
13
|
|
|
14
14
|
NS_ASSUME_NONNULL_BEGIN
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Posted when an overlay container joins or leaves a window.
|
|
18
|
+
*/
|
|
19
|
+
extern NSNotificationName const TrueSheetOverlayWindowDidChangeNotification;
|
|
20
|
+
|
|
16
21
|
/**
|
|
17
22
|
* Hidden host that renders its children in a container attached directly to
|
|
18
23
|
* the window, above every presented sheet.
|
|
@@ -25,6 +30,11 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
25
30
|
*/
|
|
26
31
|
+ (void)bringOverlaysToFrontInWindow:(nullable UIWindow *)window;
|
|
27
32
|
|
|
33
|
+
/**
|
|
34
|
+
* The overlay containers attached to the window, front to back.
|
|
35
|
+
*/
|
|
36
|
+
+ (NSArray<UIView *> *)overlayContainersInWindow:(nullable UIWindow *)window;
|
|
37
|
+
|
|
28
38
|
@end
|
|
29
39
|
|
|
30
40
|
NS_ASSUME_NONNULL_END
|
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
|
|
22
22
|
using namespace facebook::react;
|
|
23
23
|
|
|
24
|
+
NSNotificationName const TrueSheetOverlayWindowDidChangeNotification = @"TrueSheetOverlayWindowDidChangeNotification";
|
|
25
|
+
|
|
24
26
|
static NSHashTable<TrueSheetOverlayView *> *gOverlayViews;
|
|
25
27
|
|
|
26
28
|
@interface TrueSheetOverlayView () <TrueSheetOverlayContainerViewDelegate>
|
|
@@ -53,6 +55,16 @@ static NSHashTable<TrueSheetOverlayView *> *gOverlayViews;
|
|
|
53
55
|
}
|
|
54
56
|
}
|
|
55
57
|
|
|
58
|
+
+ (NSArray<UIView *> *)overlayContainersInWindow:(nullable UIWindow *)window {
|
|
59
|
+
NSMutableArray<UIView *> *containers = [NSMutableArray array];
|
|
60
|
+
for (UIView *subview in window.subviews.reverseObjectEnumerator) {
|
|
61
|
+
if ([subview isKindOfClass:[TrueSheetOverlayContainerView class]]) {
|
|
62
|
+
[containers addObject:subview];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return containers;
|
|
66
|
+
}
|
|
67
|
+
|
|
56
68
|
- (instancetype)initWithFrame:(CGRect)frame {
|
|
57
69
|
if (self = [super initWithFrame:frame]) {
|
|
58
70
|
static const auto defaultProps = std::make_shared<const TrueSheetOverlayViewProps>();
|
|
@@ -100,6 +112,8 @@ static NSHashTable<TrueSheetOverlayView *> *gOverlayViews;
|
|
|
100
112
|
[_containerView removeFromSuperview];
|
|
101
113
|
[_containerController removeFromParentViewController];
|
|
102
114
|
}
|
|
115
|
+
|
|
116
|
+
[[NSNotificationCenter defaultCenter] postNotificationName:TrueSheetOverlayWindowDidChangeNotification object:nil];
|
|
103
117
|
}
|
|
104
118
|
|
|
105
119
|
#pragma mark - State
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
#import "TrueSheetViewController.h"
|
|
10
10
|
#import "TrueSheetContainerView.h"
|
|
11
11
|
#import "TrueSheetContentView.h"
|
|
12
|
+
#import "TrueSheetOverlayView.h"
|
|
12
13
|
#import "core/TrueSheetBlurView.h"
|
|
13
14
|
#import "core/TrueSheetDetentCalculator.h"
|
|
14
15
|
#import "core/TrueSheetDimView.h"
|
|
@@ -172,6 +173,11 @@ static char TrueSheetAccessibilityWindowPreviousElementsKey;
|
|
|
172
173
|
_hingeOffsets = [NSMutableDictionary dictionary];
|
|
173
174
|
_detentCalculator = [[TrueSheetDetentCalculator alloc] init];
|
|
174
175
|
_detentCalculator.delegate = self;
|
|
176
|
+
|
|
177
|
+
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
178
|
+
selector:@selector(overlayWindowDidChange)
|
|
179
|
+
name:TrueSheetOverlayWindowDidChangeNotification
|
|
180
|
+
object:nil];
|
|
175
181
|
}
|
|
176
182
|
return self;
|
|
177
183
|
}
|
|
@@ -686,6 +692,15 @@ static BOOL TrueSheetIsPhoneIdiom(void) {
|
|
|
686
692
|
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil);
|
|
687
693
|
}
|
|
688
694
|
|
|
695
|
+
- (void)overlayWindowDidChange {
|
|
696
|
+
if (!self.canRefreshAccessibilityContainer) {
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
[self setupAccessibilityContainer];
|
|
701
|
+
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil);
|
|
702
|
+
}
|
|
703
|
+
|
|
689
704
|
- (UIViewController *)accessibilityPresentingViewController {
|
|
690
705
|
UIViewController *presentingViewController = self.presentingViewController;
|
|
691
706
|
while ([presentingViewController isKindOfClass:[TrueSheetViewController class]] &&
|
|
@@ -807,8 +822,12 @@ static BOOL TrueSheetIsPhoneIdiom(void) {
|
|
|
807
822
|
}
|
|
808
823
|
objc_setAssociatedObject(window, &TrueSheetAccessibilityWindowOwnerKey, [NSValue valueWithNonretainedObject:self],
|
|
809
824
|
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
810
|
-
|
|
825
|
+
NSArray *sheetElements =
|
|
811
826
|
isAccessibilityModal ? @[ presentedView ] : @[ presentingViewController.view, presentedView ];
|
|
827
|
+
// Overlays render above every sheet. Listed first, since accessibility
|
|
828
|
+
// hit testing takes the first element containing the point.
|
|
829
|
+
window.accessibilityElements =
|
|
830
|
+
[[TrueSheetOverlayView overlayContainersInWindow:window] arrayByAddingObjectsFromArray:sheetElements];
|
|
812
831
|
return;
|
|
813
832
|
}
|
|
814
833
|
}
|
|
@@ -26,6 +26,13 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
26
26
|
@property (nonatomic, weak, nullable) TrueSheetViewController *viewController;
|
|
27
27
|
@property (nonatomic, readonly) CGFloat currentHeight;
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* What's left of the keyboard's last show/hide animation, so a change landing
|
|
31
|
+
* outside it (e.g. after a React commit) can finish on the same curve.
|
|
32
|
+
*/
|
|
33
|
+
@property (nonatomic, readonly) NSTimeInterval remainingAnimationDuration;
|
|
34
|
+
@property (nonatomic, readonly) UIViewAnimationOptions animationCurve;
|
|
35
|
+
|
|
29
36
|
- (void)addDelegate:(id<TrueSheetKeyboardObserverDelegate>)delegate;
|
|
30
37
|
- (void)removeDelegate:(id<TrueSheetKeyboardObserverDelegate>)delegate;
|
|
31
38
|
|
|
@@ -22,12 +22,17 @@ static const NSTimeInterval kUnanimatedKeyboardDuration = 0.25;
|
|
|
22
22
|
NSHashTable<id<TrueSheetKeyboardObserverDelegate>> *_delegates;
|
|
23
23
|
CGFloat _currentHeight;
|
|
24
24
|
CGFloat _pendingMinimizedKeyboardOverlap;
|
|
25
|
+
CFTimeInterval _animationEndTime;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
- (CGFloat)currentHeight {
|
|
28
29
|
return _currentHeight;
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
- (NSTimeInterval)remainingAnimationDuration {
|
|
33
|
+
return MAX(0, _animationEndTime - CACurrentMediaTime());
|
|
34
|
+
}
|
|
35
|
+
|
|
31
36
|
- (instancetype)init {
|
|
32
37
|
if (self = [super init]) {
|
|
33
38
|
_delegates = [NSHashTable weakObjectsHashTable];
|
|
@@ -145,6 +150,8 @@ static const NSTimeInterval kUnanimatedKeyboardDuration = 0.25;
|
|
|
145
150
|
}
|
|
146
151
|
|
|
147
152
|
_currentHeight = keyboardHeight;
|
|
153
|
+
_animationEndTime = CACurrentMediaTime() + duration;
|
|
154
|
+
_animationCurve = curve;
|
|
148
155
|
|
|
149
156
|
// UIKit also moves the sheet as the minimized keyboard comes and goes, by
|
|
150
157
|
// whatever the detents couldn't absorb — settle again once it's done.
|
|
@@ -72,10 +72,11 @@ export const getTrueSheetStateForAction = (state, action) => {
|
|
|
72
72
|
const closingIndex = targetIndex + 1;
|
|
73
73
|
|
|
74
74
|
// Mark only the bottom-most route to pop as closing
|
|
75
|
-
// The sheet's dismiss() will handle dismissing sheets above it first
|
|
75
|
+
// The sheet's dismiss() will handle dismissing sheets above it first.
|
|
76
|
+
// The index stays on the top sheet until REMOVE: Expo Router 58 treats the routes
|
|
77
|
+
// after `index` as preloaded.
|
|
76
78
|
return {
|
|
77
79
|
...state,
|
|
78
|
-
index: closingIndex,
|
|
79
80
|
routes: state.routes.map((route, i) => {
|
|
80
81
|
if (i === closingIndex) return {
|
|
81
82
|
...route,
|
|
@@ -133,6 +134,47 @@ export const getTrueSheetStateForAction = (state, action) => {
|
|
|
133
134
|
return undefined;
|
|
134
135
|
}
|
|
135
136
|
};
|
|
137
|
+
const BASE_ROUTE_ACTIONS = new Set(['NAVIGATE', 'NAVIGATE_DEPRECATED', 'PUSH', 'REPLACE', 'POP_TO']);
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Reduces an action that targets the base route by name. React Navigation 7 adds another base
|
|
141
|
+
* route for a `PUSH`, a `REPLACE` or a `NAVIGATE` without `pop`, which presents the base screen
|
|
142
|
+
* as a sheet, and this router's `POP_TO` drops the params and nested state. This goes back to it
|
|
143
|
+
* instead: `getPoppedState` reduces the action as a `NAVIGATE` with `pop` (a `PUSH` ignores
|
|
144
|
+
* `pop`) to update the base route (params, nested state), then the sheets above it are dismissed.
|
|
145
|
+
* Returns `undefined` for other actions, a `REPLACE` with no sheet (it swaps the base route), or
|
|
146
|
+
* when the base router doesn't land on the base route.
|
|
147
|
+
*/
|
|
148
|
+
export const getStateForNavigateToBase = (state, action, getPoppedState) => {
|
|
149
|
+
const {
|
|
150
|
+
type,
|
|
151
|
+
payload
|
|
152
|
+
} = action;
|
|
153
|
+
const baseRoute = state.routes[0];
|
|
154
|
+
const hasSheets = state.routes.length > 1;
|
|
155
|
+
if (!BASE_ROUTE_ACTIONS.has(type) || payload?.name !== baseRoute.name || type === 'REPLACE' && !hasSheets) {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
const popped = getPoppedState({
|
|
159
|
+
...action,
|
|
160
|
+
type: 'NAVIGATE',
|
|
161
|
+
payload: {
|
|
162
|
+
...payload,
|
|
163
|
+
pop: true
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
const base = popped?.routes[popped.index];
|
|
167
|
+
if (base?.key !== baseRoute.key) {
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
if (!hasSheets) {
|
|
171
|
+
return popped;
|
|
172
|
+
}
|
|
173
|
+
return getTrueSheetStateForAction({
|
|
174
|
+
...state,
|
|
175
|
+
routes: [base, ...state.routes.slice(1)]
|
|
176
|
+
}, TrueSheetActions.pop(state.routes.length - 1));
|
|
177
|
+
};
|
|
136
178
|
export const createTrueSheetRouter = stackRouter => routerOptions => {
|
|
137
179
|
const baseRouter = stackRouter(routerOptions);
|
|
138
180
|
return {
|
|
@@ -150,7 +192,9 @@ export const createTrueSheetRouter = stackRouter => routerOptions => {
|
|
|
150
192
|
};
|
|
151
193
|
},
|
|
152
194
|
getStateForAction(state, action, options) {
|
|
153
|
-
const result =
|
|
195
|
+
const result = getStateForNavigateToBase(state, action,
|
|
196
|
+
// NAVIGATE never returns a partial state
|
|
197
|
+
popAction => baseRouter.getStateForAction(state, popAction, options)) ?? getTrueSheetStateForAction(state, action);
|
|
154
198
|
return result === undefined ? baseRouter.getStateForAction(state, action, options) : result;
|
|
155
199
|
},
|
|
156
200
|
getRehydratedState(partialState, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["TrueSheetActions","uid","Math","random","toString","slice","ensureBaseRoute","state","baseRouteName","routeParamList","hasBaseRoute","routes","some","r","name","baseRoute","key","params","index","length","getResizeRouteIndex","action","target","source","findIndex","getTrueSheetStateForAction","type","routeIndex","map","route","i","resizeIndex","resizeKey","pop","count","payload","maxPopCount","actualCount","min","targetIndex","closingIndex","closing","dismissing","popCount","targetName","routeKey","removed","isPopped","filter","undefined","createTrueSheetRouter","stackRouter","routerOptions","baseRouter","getInitialState","options","initialRouteName","routeNames","stateWithBaseRoute","stale","getStateForAction","result","getRehydratedState","partialState","routeGetIdList","actionCreators"],"sourceRoot":"../../../src","sources":["navigation/createTrueSheetRouter.ts"],"mappings":";;AAAA,SAASA,gBAAgB,QAAkC,cAAW;
|
|
1
|
+
{"version":3,"names":["TrueSheetActions","uid","Math","random","toString","slice","ensureBaseRoute","state","baseRouteName","routeParamList","hasBaseRoute","routes","some","r","name","baseRoute","key","params","index","length","getResizeRouteIndex","action","target","source","findIndex","getTrueSheetStateForAction","type","routeIndex","map","route","i","resizeIndex","resizeKey","pop","count","payload","maxPopCount","actualCount","min","targetIndex","closingIndex","closing","dismissing","popCount","targetName","routeKey","removed","isPopped","filter","undefined","BASE_ROUTE_ACTIONS","Set","getStateForNavigateToBase","getPoppedState","hasSheets","has","popped","base","createTrueSheetRouter","stackRouter","routerOptions","baseRouter","getInitialState","options","initialRouteName","routeNames","stateWithBaseRoute","stale","getStateForAction","result","popAction","getRehydratedState","partialState","routeGetIdList","actionCreators"],"sourceRoot":"../../../src","sources":["navigation/createTrueSheetRouter.ts"],"mappings":";;AAAA,SAASA,gBAAgB,QAAkC,cAAW;AAQtE,MAAMC,GAAG,GAAGA,CAAA,KAAMC,IAAI,CAACC,MAAM,CAAC,CAAC,CAACC,QAAQ,CAAC,EAAE,CAAC,CAACC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;AAEzD,MAAMC,eAAe,GAAGA,CACtBC,KAAQ,EACRC,aAAiC,EACjCC,cAA8D,KACf;EAC/C,IAAI,CAACD,aAAa,EAAE;IAClB,OAAOD,KAAK;EACd;EAEA,MAAMG,YAAY,GAAGH,KAAK,CAACI,MAAM,CAACC,IAAI,CAAEC,CAAC,IAAKA,CAAC,CAACC,IAAI,KAAKN,aAAa,CAAC;EAEvE,IAAI,CAACE,YAAY,EAAE;IACjB,MAAMK,SAAS,GAAG;MAChBC,GAAG,EAAE,GAAGR,aAAa,IAAIP,GAAG,CAAC,CAAC,EAAE;MAChCa,IAAI,EAAEN,aAAa;MACnBS,MAAM,EAAER,cAAc,GAAGD,aAAa;IACxC,CAAC;IAED,OAAO;MACL,GAAGD,KAAK;MACRW,KAAK,EAAEX,KAAK,CAACI,MAAM,CAACQ,MAAM;MAC1BR,MAAM,EAAE,CAACI,SAAS,EAAE,GAAGR,KAAK,CAACI,MAAM;IACrC,CAAC;EACH;EAEA,OAAOJ,KAAK;AACd,CAAC;AAYD;AACA;AACA;AACA,OAAO,MAAMa,mBAAmB,GAAGA,CACjCb,KAAiB,EACjBc,MAA4C,KAE5CA,MAAM,CAACC,MAAM,KAAKf,KAAK,CAACS,GAAG,IAAIK,MAAM,CAACE,MAAM,GACxChB,KAAK,CAACI,MAAM,CAACa,SAAS,CAAEX,CAAC,IAAKA,CAAC,CAACG,GAAG,KAAKK,MAAM,CAACE,MAAM,CAAC,GACtDhB,KAAK,CAACW,KAAK;;AAEjB;AACA;AACA;AACA;AACA,OAAO,MAAMO,0BAA0B,GAAGA,CACxClB,KAAY,EACZc,MAA2B,KACE;EAC7B,QAAQA,MAAM,CAACK,IAAI;IACjB,KAAK,QAAQ;MAAE;QACb,MAAMC,UAAU,GAAGP,mBAAmB,CAACb,KAAK,EAAEc,MAAM,CAAC;QAErD,OAAO;UACL,GAAGd,KAAK;UACRI,MAAM,EAAEJ,KAAK,CAACI,MAAM,CAACiB,GAAG,CAAC,CAACC,KAAK,EAAEC,CAAC,KAChCA,CAAC,KAAKH,UAAU,GACZ;YACE,GAAGE,KAAK;YACRE,WAAW,EAAEV,MAAM,CAACH,KAAK;YACzBc,SAAS,EAAE,CAACH,KAAK,CAACG,SAAS,IAAI,CAAC,IAAI;UACtC,CAAC,GACDH,KACN;QACF,CAAC;MACH;IAEA,KAAK,SAAS;IACd,KAAK,SAAS;MAAE;QACd,OAAOJ,0BAA0B,CAAClB,KAAK,EAAEP,gBAAgB,CAACiC,GAAG,CAAC,CAAC,CAAC,CAAC;MACnE;IAEA,KAAK,KAAK;MAAE;QACV;QACA,IAAI1B,KAAK,CAACI,MAAM,CAACQ,MAAM,IAAI,CAAC,EAAE;UAC5B,OAAO,IAAI;QACb;QAEA,MAAMe,KAAK,GACT,SAAS,IAAIb,MAAM,IAAI,OAAOA,MAAM,CAACc,OAAO,EAAED,KAAK,KAAK,QAAQ,GAAGb,MAAM,CAACc,OAAO,CAACD,KAAK,GAAG,CAAC;;QAE7F;QACA,MAAME,WAAW,GAAG7B,KAAK,CAACI,MAAM,CAACQ,MAAM,GAAG,CAAC;QAC3C,MAAMkB,WAAW,GAAGnC,IAAI,CAACoC,GAAG,CAACJ,KAAK,EAAEE,WAAW,CAAC;;QAEhD;QACA,IAAIC,WAAW,IAAI,CAAC,EAAE;UACpB,OAAO,IAAI;QACb;;QAEA;QACA;QACA,MAAME,WAAW,GAAGhC,KAAK,CAACI,MAAM,CAACQ,MAAM,GAAG,CAAC,GAAGkB,WAAW;QACzD,MAAMG,YAAY,GAAGD,WAAW,GAAG,CAAC;;QAEpC;QACA;QACA;QACA;QACA,OAAO;UACL,GAAGhC,KAAK;UACRI,MAAM,EAAEJ,KAAK,CAACI,MAAM,CAACiB,GAAG,CAAC,CAACC,KAAK,EAAEC,CAAC,KAAK;YACrC,IAAIA,CAAC,KAAKU,YAAY,EAAE,OAAO;cAAE,GAAGX,KAAK;cAAEY,OAAO,EAAE;YAAK,CAAC;YAC1D,IAAIX,CAAC,GAAGU,YAAY,EAAE,OAAO;cAAE,GAAGX,KAAK;cAAEa,UAAU,EAAE;YAAK,CAAC;YAC3D,OAAOb,KAAK;UACd,CAAC;QACH,CAAC;MACH;IAEA,KAAK,YAAY;MAAE;QACjB,MAAMc,QAAQ,GAAGpC,KAAK,CAACI,MAAM,CAACQ,MAAM,GAAG,CAAC;QACxC,OAAOM,0BAA0B,CAAClB,KAAK,EAAEP,gBAAgB,CAACiC,GAAG,CAACU,QAAQ,CAAC,CAAC;MAC1E;IAEA,KAAK,QAAQ;MAAE;QACb,MAAMC,UAAU,GACd,SAAS,IAAIvB,MAAM,IAAI,OAAOA,MAAM,CAACc,OAAO,EAAErB,IAAI,KAAK,QAAQ,GAC3DO,MAAM,CAACc,OAAO,CAACrB,IAAI,GACnB,IAAI;QAEV,IAAI,CAAC8B,UAAU,EAAE;UACf,OAAO,IAAI;QACb;QAEA,MAAML,WAAW,GAAGhC,KAAK,CAACI,MAAM,CAACa,SAAS,CAAEX,CAAC,IAAKA,CAAC,CAACC,IAAI,KAAK8B,UAAU,CAAC;;QAExE;QACA,IAAIL,WAAW,KAAK,CAAC,CAAC,IAAIA,WAAW,IAAIhC,KAAK,CAACW,KAAK,EAAE;UACpD,OAAO,IAAI;QACb;QAEA,MAAMyB,QAAQ,GAAGpC,KAAK,CAACI,MAAM,CAACQ,MAAM,GAAG,CAAC,GAAGoB,WAAW;QACtD,OAAOd,0BAA0B,CAAClB,KAAK,EAAEP,gBAAgB,CAACiC,GAAG,CAACU,QAAQ,CAAC,CAAC;MAC1E;IAEA,KAAK,QAAQ;MAAE;QACb;QACA,MAAME,QAAQ,GAAGxB,MAAM,CAACE,MAAM;QAC9B,MAAMI,UAAU,GAAGkB,QAAQ,GACvBtC,KAAK,CAACI,MAAM,CAACa,SAAS,CAAEX,CAAC,IAAKA,CAAC,CAACG,GAAG,KAAK6B,QAAQ,CAAC,GACjDtC,KAAK,CAACI,MAAM,CAACa,SAAS,CAAEX,CAAC,IAAKA,CAAC,CAAC4B,OAAO,CAAC;QAE5C,IAAId,UAAU,KAAK,CAAC,CAAC,EAAE;UACrB,OAAOpB,KAAK;QACd;;QAEA;QACA;QACA,MAAMuC,OAAO,GAAGvC,KAAK,CAACI,MAAM,CAACgB,UAAU,CAAE;QACzC,MAAMoB,QAAQ,GAAGD,OAAO,CAACL,OAAO,IAAIK,OAAO,CAACJ,UAAU;QACtD,MAAM/B,MAAM,GAAGJ,KAAK,CAACI,MAAM,CAACqC,MAAM,CAChC,CAACnB,KAAK,EAAEC,CAAC,KAAKA,CAAC,GAAGH,UAAU,IAAKG,CAAC,GAAGH,UAAU,IAAIoB,QAAQ,IAAI,CAAClB,KAAK,CAACa,UACxE,CAAC;QAED,OAAO;UACL,GAAGnC,KAAK;UACRW,KAAK,EAAEhB,IAAI,CAACoC,GAAG,CAAC/B,KAAK,CAACW,KAAK,EAAEP,MAAM,CAACQ,MAAM,GAAG,CAAC,CAAC;UAC/CR;QACF,CAAC;MACH;IAEA;MACE,OAAOsC,SAAS;EACpB;AACF,CAAC;AAED,MAAMC,kBAAkB,GAAG,IAAIC,GAAG,CAAC,CACjC,UAAU,EACV,qBAAqB,EACrB,MAAM,EACN,SAAS,EACT,QAAQ,CACT,CAAC;;AAEF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMC,yBAAyB,GAAGA,CACvC7C,KAAY,EACZc,MAA2B,EAC3BgC,cAAyE,KACnD;EACtB,MAAM;IAAE3B,IAAI;IAAES;EAAQ,CAAC,GAAGd,MAAuD;EACjF,MAAMN,SAAS,GAAGR,KAAK,CAACI,MAAM,CAAC,CAAC,CAAE;EAClC,MAAM2C,SAAS,GAAG/C,KAAK,CAACI,MAAM,CAACQ,MAAM,GAAG,CAAC;EAEzC,IACE,CAAC+B,kBAAkB,CAACK,GAAG,CAAC7B,IAAI,CAAC,IAC7BS,OAAO,EAAErB,IAAI,KAAKC,SAAS,CAACD,IAAI,IAC/BY,IAAI,KAAK,SAAS,IAAI,CAAC4B,SAAU,EAClC;IACA,OAAOL,SAAS;EAClB;EAEA,MAAMO,MAAM,GAAGH,cAAc,CAAC;IAC5B,GAAGhC,MAAM;IACTK,IAAI,EAAE,UAAU;IAChBS,OAAO,EAAE;MAAE,GAAGA,OAAO;MAAEF,GAAG,EAAE;IAAK;EACnC,CAAmC,CAAC;EACpC,MAAMwB,IAAI,GAAGD,MAAM,EAAE7C,MAAM,CAAC6C,MAAM,CAACtC,KAAK,CAAC;EAEzC,IAAIuC,IAAI,EAAEzC,GAAG,KAAKD,SAAS,CAACC,GAAG,EAAE;IAC/B,OAAOiC,SAAS;EAClB;EAEA,IAAI,CAACK,SAAS,EAAE;IACd,OAAOE,MAAM;EACf;EAEA,OAAO/B,0BAA0B,CAC/B;IAAE,GAAGlB,KAAK;IAAEI,MAAM,EAAE,CAAC8C,IAAI,EAAE,GAAGlD,KAAK,CAACI,MAAM,CAACN,KAAK,CAAC,CAAC,CAAC;EAAE,CAAC,EACtDL,gBAAgB,CAACiC,GAAG,CAAC1B,KAAK,CAACI,MAAM,CAACQ,MAAM,GAAG,CAAC,CAC9C,CAAC;AACH,CAAC;AAED,OAAO,MAAMuC,qBAAqB,GAC/BC,WAAmC,IACnCC,aAAqC,IAAsB;EAC1D,MAAMC,UAAU,GAAGF,WAAW,CAACC,aAAa,CAAC;EAE7C,OAAO;IACL,GAAGC,UAAU;IACbnC,IAAI,EAAE,YAAY;IAElBoC,eAAeA,CAACC,OAAO,EAAE;MACvB,MAAMxD,KAAK,GAAGsD,UAAU,CAACC,eAAe,CAACC,OAAO,CAAC;MACjD,MAAMvD,aAAa,GAAGoD,aAAa,CAACI,gBAAgB,IAAID,OAAO,CAACE,UAAU,CAAC,CAAC,CAAC;MAC7E,MAAMC,kBAAkB,GAAG5D,eAAe,CAACC,KAAK,EAAEC,aAAa,EAAEuD,OAAO,CAACtD,cAAc,CAAC;MAExF,OAAO;QACL,GAAGyD,kBAAkB;QACrBC,KAAK,EAAE,KAAK;QACZzC,IAAI,EAAE,YAAY;QAClBV,GAAG,EAAE,cAAcf,GAAG,CAAC,CAAC;MAC1B,CAAC;IACH,CAAC;IAEDmE,iBAAiBA,CAAC7D,KAAK,EAAEc,MAAM,EAAE0C,OAAO,EAAE;MACxC,MAAMM,MAAM,GACVjB,yBAAyB,CACvB7C,KAAK,EACLc,MAAM;MACN;MACCiD,SAAS,IACRT,UAAU,CAACO,iBAAiB,CAAC7D,KAAK,EAAE+D,SAAS,EAAEP,OAAO,CAC1D,CAAC,IAAItC,0BAA0B,CAAClB,KAAK,EAAEc,MAAM,CAAC;MAEhD,OAAOgD,MAAM,KAAKpB,SAAS,GAAGY,UAAU,CAACO,iBAAiB,CAAC7D,KAAK,EAAEc,MAAM,EAAE0C,OAAO,CAAC,GAAGM,MAAM;IAC7F,CAAC;IAEDE,kBAAkBA,CAACC,YAAY,EAAE;MAAEP,UAAU;MAAExD,cAAc;MAAEgE;IAAe,CAAC,EAAE;MAC/E,IAAID,YAAY,CAACL,KAAK,KAAK,KAAK,EAAE;QAChC,OAAOK,YAAY;MACrB;MAEA,MAAMjE,KAAK,GAAGsD,UAAU,CAACU,kBAAkB,CAACC,YAAY,EAAE;QACxDP,UAAU;QACVxD,cAAc;QACdgE;MACF,CAAC,CAAC;MAEF,MAAMjE,aAAa,GAAGoD,aAAa,CAACI,gBAAgB,IAAIC,UAAU,CAAC,CAAC,CAAC;MACrE,MAAMC,kBAAkB,GAAG5D,eAAe,CAACC,KAAK,EAAEC,aAAa,EAAEC,cAAc,CAAC;MAEhF,OAAO;QACL,GAAGyD,kBAAkB;QACrBxC,IAAI,EAAE,YAAY;QAClBV,GAAG,EAAE,cAAcf,GAAG,CAAC,CAAC;MAC1B,CAAC;IACH,CAAC;IAEDyE,cAAc,EAAE1E;EAClB,CAAC;AACH,CAAC","ignoreList":[]}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import { TrueSheetActions } from "../actions.js";
|
|
4
|
-
import { getResizeRouteIndex, getTrueSheetStateForAction } from "../createTrueSheetRouter.js";
|
|
4
|
+
import { getResizeRouteIndex, getStateForNavigateToBase, getTrueSheetStateForAction } from "../createTrueSheetRouter.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* `RouterActionHandling.Unhandled` from Expo Router 58+, which deprecates `null`.
|
|
8
|
+
*/
|
|
5
9
|
|
|
6
10
|
/**
|
|
7
11
|
* `extendRouter` from Expo Router 58+. Declared locally because the
|
|
@@ -13,7 +17,7 @@ import { getResizeRouteIndex, getTrueSheetStateForAction } from "../createTrueSh
|
|
|
13
17
|
* (no `getInitialState`/`getRehydratedState`) and `getStateForAction` returns
|
|
14
18
|
* `{ state, affectedRouteKey }`.
|
|
15
19
|
*/
|
|
16
|
-
export const extendTrueSheetRouter = (extendRouter, stackRouter) => extendRouter(stackRouter, ({
|
|
20
|
+
export const extendTrueSheetRouter = (extendRouter, stackRouter, unhandled) => extendRouter(stackRouter, ({
|
|
17
21
|
baseRouter,
|
|
18
22
|
options
|
|
19
23
|
}) => {
|
|
@@ -22,12 +26,36 @@ export const extendTrueSheetRouter = (extendRouter, stackRouter) => extendRouter
|
|
|
22
26
|
}
|
|
23
27
|
return {
|
|
24
28
|
getStateForAction(state, action, config) {
|
|
29
|
+
const {
|
|
30
|
+
type,
|
|
31
|
+
payload
|
|
32
|
+
} = action;
|
|
33
|
+
|
|
34
|
+
// Expo Router keeps preloaded routes in `routes`, and the navigator presents every
|
|
35
|
+
// route there as a sheet. A parent navigator's route bubbles up to it, as in a `Stack`.
|
|
36
|
+
if (type === 'PRELOAD') {
|
|
37
|
+
return payload?.name && state.routeNames.includes(payload.name) ? {
|
|
38
|
+
state
|
|
39
|
+
} : unhandled;
|
|
40
|
+
}
|
|
41
|
+
const navigated = getStateForNavigateToBase(state, action, popAction => {
|
|
42
|
+
const popped = baseRouter.getStateForAction(state, popAction, config);
|
|
43
|
+
return popped === unhandled ? undefined : popped.state;
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Expo Router resolves the destination's nested state in the affected route
|
|
47
|
+
if (navigated) {
|
|
48
|
+
return {
|
|
49
|
+
state: navigated,
|
|
50
|
+
affectedRouteKey: navigated.routes[0]?.key
|
|
51
|
+
};
|
|
52
|
+
}
|
|
25
53
|
const result = getTrueSheetStateForAction(state, action);
|
|
26
54
|
if (result === undefined) {
|
|
27
55
|
return baseRouter.getStateForAction(state, action, config);
|
|
28
56
|
}
|
|
29
57
|
if (result === null) {
|
|
30
|
-
return
|
|
58
|
+
return unhandled;
|
|
31
59
|
}
|
|
32
60
|
const affectedIndex = action.type === 'RESIZE' ? getResizeRouteIndex(state, action) : result.index;
|
|
33
61
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["TrueSheetActions","getResizeRouteIndex","getTrueSheetStateForAction","extendTrueSheetRouter","extendRouter","stackRouter","baseRouter","options","__DEV__","initialRouteName","undefined","console","warn","getStateForAction","state","action","config","
|
|
1
|
+
{"version":3,"names":["TrueSheetActions","getResizeRouteIndex","getStateForNavigateToBase","getTrueSheetStateForAction","extendTrueSheetRouter","extendRouter","stackRouter","unhandled","baseRouter","options","__DEV__","initialRouteName","undefined","console","warn","getStateForAction","state","action","config","type","payload","name","routeNames","includes","navigated","popAction","popped","affectedRouteKey","routes","key","result","affectedIndex","index","actionCreators"],"sourceRoot":"../../../../src","sources":["navigation/expo-router/extendTrueSheetRouter.ts"],"mappings":";;AAAA,SAASA,gBAAgB,QAAkC,eAAY;AACvE,SACEC,mBAAmB,EACnBC,yBAAyB,EACzBC,0BAA0B,QACrB,6BAA0B;;AAKjC;AACA;AACA;;AAcA;AACA;AACA;AACA;;AAOA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMC,qBAAqB,GAAGA,CACnCC,YAA0B,EAC1BC,WAA+B,EAC/BC,SAAoB,KAEpBF,YAAY,CACVC,WAAW,EACX,CAAC;EAAEE,UAAU;EAAEC;AAAQ,CAAC,KAAK;EAC3B,IAAIC,OAAO,IAAID,OAAO,CAACE,gBAAgB,KAAKC,SAAS,EAAE;IACrDC,OAAO,CAACC,IAAI,CACV,wQACF,CAAC;EACH;EAEA,OAAO;IACLC,iBAAiBA,CACfC,KAA2B,EAC3BC,MAA2B,EAC3BC,MAAc,EACY;MAC1B,MAAM;QAAEC,IAAI;QAAEC;MAAQ,CAAC,GAAGH,MAAuD;;MAEjF;MACA;MACA,IAAIE,IAAI,KAAK,SAAS,EAAE;QACtB,OAAOC,OAAO,EAAEC,IAAI,IAAIL,KAAK,CAACM,UAAU,CAACC,QAAQ,CAACH,OAAO,CAACC,IAAI,CAAC,GAAG;UAAEL;QAAM,CAAC,GAAGT,SAAS;MACzF;MAEA,MAAMiB,SAAS,GAAGtB,yBAAyB,CAACc,KAAK,EAAEC,MAAM,EAAGQ,SAAS,IAAK;QACxE,MAAMC,MAAM,GAAGlB,UAAU,CAACO,iBAAiB,CAACC,KAAK,EAAES,SAAS,EAAEP,MAAM,CAAC;QACrE,OAAOQ,MAAM,KAAKnB,SAAS,GAAGK,SAAS,GAAGc,MAAM,CAACV,KAAK;MACxD,CAAC,CAAC;;MAEF;MACA,IAAIQ,SAAS,EAAE;QACb,OAAO;UAAER,KAAK,EAAEQ,SAAS;UAAEG,gBAAgB,EAAEH,SAAS,CAACI,MAAM,CAAC,CAAC,CAAC,EAAEC;QAAI,CAAC;MACzE;MAEA,MAAMC,MAAM,GAAG3B,0BAA0B,CAACa,KAAK,EAAEC,MAAM,CAAC;MAExD,IAAIa,MAAM,KAAKlB,SAAS,EAAE;QACxB,OAAOJ,UAAU,CAACO,iBAAiB,CAACC,KAAK,EAAEC,MAAM,EAAEC,MAAM,CAAC;MAC5D;MAEA,IAAIY,MAAM,KAAK,IAAI,EAAE;QACnB,OAAOvB,SAAS;MAClB;MAEA,MAAMwB,aAAa,GACjBd,MAAM,CAACE,IAAI,KAAK,QAAQ,GAAGlB,mBAAmB,CAACe,KAAK,EAAEC,MAAM,CAAC,GAAGa,MAAM,CAACE,KAAK;MAE9E,OAAO;QAAEhB,KAAK,EAAEc,MAAM;QAAEH,gBAAgB,EAAEG,MAAM,CAACF,MAAM,CAACG,aAAa,CAAC,EAAEF;MAAI,CAAC;IAC/E,CAAC;IACDI,cAAc,EAAEjC;EAClB,CAAC;AACH,CAAC,EACD;EAAEmB,IAAI,EAAE;AAAa,CACvB,CAAC","ignoreList":[]}
|
|
@@ -6,12 +6,15 @@ import { createTrueSheetRouter } from "../createTrueSheetRouter.js";
|
|
|
6
6
|
import { trueSheetNavigator } from "../navigator.js";
|
|
7
7
|
import { extendTrueSheetRouter } from "./extendTrueSheetRouter.js";
|
|
8
8
|
// Expo Router 58+ replaced the React Navigation router contract and ships `extendRouter`
|
|
9
|
-
// for it. Before that, the vendored StackRouter is structurally
|
|
10
|
-
// @react-navigation's. Pinned to this entry's own router types:
|
|
11
|
-
// shared and typed against React Navigation, which resolves to
|
|
12
|
-
// install expo-router.
|
|
13
|
-
const
|
|
14
|
-
|
|
9
|
+
// and `RouterActionHandling` for it. Before that, the vendored StackRouter is structurally
|
|
10
|
+
// identical to @react-navigation's. Pinned to this entry's own router types:
|
|
11
|
+
// `createTrueSheetRouter` is shared and typed against React Navigation, which resolves to
|
|
12
|
+
// `any` in apps that only install expo-router.
|
|
13
|
+
const {
|
|
14
|
+
extendRouter,
|
|
15
|
+
RouterActionHandling
|
|
16
|
+
} = ExpoRouter;
|
|
17
|
+
const sheetRouter = extendRouter ? extendTrueSheetRouter(extendRouter, StackRouter, RouterActionHandling.Unhandled) : createTrueSheetRouter(StackRouter);
|
|
15
18
|
|
|
16
19
|
// `routes`/`dispatch` are injected by `createProps`, so they are declared as
|
|
17
20
|
// CreateProps (last generic) and excluded from the public navigator props
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["ExpoRouter","StackRouter","unstable_integrateWithRouter","useNavigation","createTrueSheetRouter","trueSheetNavigator","extendTrueSheetRouter","extendRouter","sheetRouter","SheetNavigator","createProps","state","dispatch","routes","Sheet","useTrueSheetNavigation","TrueSheetActions"],"sourceRoot":"../../../../src","sources":["navigation/expo-router/index.ts"],"mappings":";;AAAA,OAAO,KAAKA,UAAU,MAAM,aAAa;AACzC,SAASC,WAAW,EAAEC,4BAA4B,EAAEC,aAAa,QAAQ,aAAa;AAEtF,SAASC,qBAAqB,QAAQ,6BAA0B;AAChE,SAASC,kBAAkB,QAAQ,iBAAc;AAEjD,SAASC,qBAAqB,
|
|
1
|
+
{"version":3,"names":["ExpoRouter","StackRouter","unstable_integrateWithRouter","useNavigation","createTrueSheetRouter","trueSheetNavigator","extendTrueSheetRouter","extendRouter","RouterActionHandling","sheetRouter","Unhandled","SheetNavigator","createProps","state","dispatch","routes","Sheet","useTrueSheetNavigation","TrueSheetActions"],"sourceRoot":"../../../../src","sources":["navigation/expo-router/index.ts"],"mappings":";;AAAA,OAAO,KAAKA,UAAU,MAAM,aAAa;AACzC,SAASC,WAAW,EAAEC,4BAA4B,EAAEC,aAAa,QAAQ,aAAa;AAEtF,SAASC,qBAAqB,QAAQ,6BAA0B;AAChE,SAASC,kBAAkB,QAAQ,iBAAc;AAEjD,SAASC,qBAAqB,QAA2C,4BAAyB;AAalG;AACA;AACA;AACA;AACA;AACA,MAAM;EAAEC,YAAY;EAAEC;AAAqB,CAAC,GAAGR,UAG9C;AAED,MAAMS,WAAmC,GAAGF,YAAY,GACpDD,qBAAqB,CACnBC,YAAY,EACZN,WAAW,EACXO,oBAAoB,CAAEE,SACxB,CAAC,GACDN,qBAAqB,CAACH,WAAiC,CAAC;;AAE5D;AACA;AACA,MAAMU,cAAc,GAAGT,4BAA4B,CAOjDG,kBAAkB,EAAEI,WAAW,EAAE;EACjCG,WAAW,EAAEA,CAAC;IAAEC,KAAK;IAAEC;EAAS,CAAC,MAAM;IACrCC,MAAM,EAAEF,KAAK,CAACE,MAAM;IACpBD;EACF,CAAC;AACH,CAAC,CAAC;;AAEF;AACA;;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAME,KAAK,GAAGL,cAGpB;;AAED;AACA;AACA;AACA;AACA,OAAO,MAAMM,sBAAsB,GAAGA,CAAA,KAEHd,aAAa,CAA6B,CAAC;AAE9E,SAASe,gBAAgB,QAAkC,eAAY","ignoreList":[]}
|
|
@@ -25,6 +25,16 @@ export declare const getResizeRouteIndex: (state: SheetState, action: {
|
|
|
25
25
|
* Returns `undefined` for actions the base `StackRouter` should handle.
|
|
26
26
|
*/
|
|
27
27
|
export declare const getTrueSheetStateForAction: <State extends SheetState>(state: State, action: TrueSheetActionType) => State | null | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* Reduces an action that targets the base route by name. React Navigation 7 adds another base
|
|
30
|
+
* route for a `PUSH`, a `REPLACE` or a `NAVIGATE` without `pop`, which presents the base screen
|
|
31
|
+
* as a sheet, and this router's `POP_TO` drops the params and nested state. This goes back to it
|
|
32
|
+
* instead: `getPoppedState` reduces the action as a `NAVIGATE` with `pop` (a `PUSH` ignores
|
|
33
|
+
* `pop`) to update the base route (params, nested state), then the sheets above it are dismissed.
|
|
34
|
+
* Returns `undefined` for other actions, a `REPLACE` with no sheet (it swaps the base route), or
|
|
35
|
+
* when the base router doesn't land on the base route.
|
|
36
|
+
*/
|
|
37
|
+
export declare const getStateForNavigateToBase: <State extends SheetState>(state: State, action: TrueSheetActionType, getPoppedState: (action: TrueSheetActionType) => State | null | undefined) => State | undefined;
|
|
28
38
|
export declare const createTrueSheetRouter: (stackRouter: TrueSheetRouterFactory) => (routerOptions: TrueSheetRouterOptions) => TrueSheetRouter;
|
|
29
39
|
export {};
|
|
30
40
|
//# sourceMappingURL=createTrueSheetRouter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createTrueSheetRouter.d.ts","sourceRoot":"","sources":["../../../../src/navigation/createTrueSheetRouter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,mBAAmB,EAAE,MAAM,WAAW,CAAC;AACvE,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"createTrueSheetRouter.d.ts","sourceRoot":"","sources":["../../../../src/navigation/createTrueSheetRouter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,mBAAmB,EAAE,MAAM,WAAW,CAAC;AACvE,OAAO,KAAK,EACV,eAAe,EACf,sBAAsB,EACtB,sBAAsB,EAEvB,MAAM,SAAS,CAAC;AAgCjB,KAAK,UAAU,GAAG;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AACF,KAAK,UAAU,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,UAAU,EAAE,CAAA;CAAE,CAAC;AAEvE;;GAEG;AACH,eAAO,MAAM,mBAAmB,GAC9B,OAAO,UAAU,EACjB,QAAQ;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,KAC3C,MAGc,CAAC;AAElB;;;GAGG;AACH,eAAO,MAAM,0BAA0B,GAAI,KAAK,SAAS,UAAU,EACjE,OAAO,KAAK,EACZ,QAAQ,mBAAmB,KAC1B,KAAK,GAAG,IAAI,GAAG,SAoHjB,CAAC;AAUF;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,GAAI,KAAK,SAAS,UAAU,EAChE,OAAO,KAAK,EACZ,QAAQ,mBAAmB,EAC3B,gBAAgB,CAAC,MAAM,EAAE,mBAAmB,KAAK,KAAK,GAAG,IAAI,GAAG,SAAS,KACxE,KAAK,GAAG,SAgCV,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAC/B,aAAa,sBAAsB,MACnC,eAAe,sBAAsB,KAAG,eAwDxC,CAAC"}
|
|
@@ -4,9 +4,13 @@ type ActionResult = {
|
|
|
4
4
|
state: TrueSheetRouterState;
|
|
5
5
|
affectedRouteKey?: string;
|
|
6
6
|
};
|
|
7
|
+
/**
|
|
8
|
+
* `RouterActionHandling.Unhandled` from Expo Router 58+, which deprecates `null`.
|
|
9
|
+
*/
|
|
10
|
+
export type Unhandled = 'UNHANDLED';
|
|
7
11
|
type RouterExtension = (context: {
|
|
8
12
|
baseRouter: {
|
|
9
|
-
getStateForAction(state: TrueSheetRouterState, action: TrueSheetActionType, options: object): ActionResult |
|
|
13
|
+
getStateForAction(state: TrueSheetRouterState, action: TrueSheetActionType, options: object): ActionResult | Unhandled;
|
|
10
14
|
};
|
|
11
15
|
options: {
|
|
12
16
|
initialRouteName?: string;
|
|
@@ -24,6 +28,6 @@ export type ExtendRouter = (base: StackRouterFactory, extension: RouterExtension
|
|
|
24
28
|
* (no `getInitialState`/`getRehydratedState`) and `getStateForAction` returns
|
|
25
29
|
* `{ state, affectedRouteKey }`.
|
|
26
30
|
*/
|
|
27
|
-
export declare const extendTrueSheetRouter: (extendRouter: ExtendRouter, stackRouter: StackRouterFactory) => TrueSheetRouterFactory;
|
|
31
|
+
export declare const extendTrueSheetRouter: (extendRouter: ExtendRouter, stackRouter: StackRouterFactory, unhandled: Unhandled) => TrueSheetRouterFactory;
|
|
28
32
|
export {};
|
|
29
33
|
//# sourceMappingURL=extendTrueSheetRouter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extendTrueSheetRouter.d.ts","sourceRoot":"","sources":["../../../../../src/navigation/expo-router/extendTrueSheetRouter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,mBAAmB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"extendTrueSheetRouter.d.ts","sourceRoot":"","sources":["../../../../../src/navigation/expo-router/extendTrueSheetRouter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAMxE,OAAO,KAAK,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAEhG,KAAK,YAAY,GAAG;IAAE,KAAK,EAAE,oBAAoB,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/E;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,WAAW,CAAC;AAEpC,KAAK,eAAe,GAAG,CAAC,OAAO,EAAE;IAC/B,UAAU,EAAE;QACV,iBAAiB,CACf,KAAK,EAAE,oBAAoB,EAC3B,MAAM,EAAE,mBAAmB,EAC3B,OAAO,EAAE,MAAM,GACd,YAAY,GAAG,SAAS,CAAC;KAC7B,CAAC;IACF,OAAO,EAAE;QAAE,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACxC,KAAK,MAAM,CAAC;AAEb;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,CACzB,IAAI,EAAE,kBAAkB,EACxB,SAAS,EAAE,eAAe,EAC1B,OAAO,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,KACtB,sBAAsB,CAAC;AAE5B;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,GAChC,cAAc,YAAY,EAC1B,aAAa,kBAAkB,EAC/B,WAAW,SAAS,KACnB,sBAqDA,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/navigation/expo-router/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,UAAU,MAAM,aAAa,CAAC;AAK1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD,OAAO,KAAK,EAEV,0BAA0B,EAC1B,uBAAuB,EACvB,wBAAwB,EAExB,uBAAuB,EAGvB,yBAAyB,EAC1B,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/navigation/expo-router/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,UAAU,MAAM,aAAa,CAAC;AAK1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD,OAAO,KAAK,EAEV,0BAA0B,EAC1B,uBAAuB,EACvB,wBAAwB,EAExB,uBAAuB,EAGvB,yBAAyB,EAC1B,MAAM,SAAS,CAAC;AAsBjB,QAAA,MAAM,cAAc;;;;;CAYlB,CAAC;AAIH,KAAK,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,cAAc,CAAC,EAAE,iBAAiB,CAAC,GACpF,IAAI,CAAC,uBAAuB,EAAE,iBAAiB,CAAC,CAAC;AAEnD;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,KAAK,EAAqB,KAAK,CAAC,aAAa,CAAC,UAAU,CAAC,GAAG;IACvE,MAAM,EAAE,OAAO,cAAc,CAAC,MAAM,CAAC;IACrC,SAAS,EAAE,OAAO,cAAc,CAAC,SAAS,CAAC;CAC5C,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,sBAAsB,GACjC,CAAC,SAAS,aAAa,GAAG,aAAa,OACpC,uBAAuB,CAAC,CAAC,CAAgD,CAAC;AAE/E,OAAO,EAAE,gBAAgB,EAAE,KAAK,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAExE,YAAY,EAAE,sBAAsB,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;AAEhG,YAAY,EACV,2BAA2B,EAC3B,0BAA0B,EAC1B,0BAA0B,EAC1B,uBAAuB,EACvB,wBAAwB,EACxB,oBAAoB,GACrB,MAAM,SAAS,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { TrueSheetActions, type TrueSheetActionType } from './actions';
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
TrueSheetRouter,
|
|
4
|
+
TrueSheetRouterFactory,
|
|
5
|
+
TrueSheetRouterOptions,
|
|
6
|
+
TrueSheetRouterState,
|
|
7
|
+
} from './types';
|
|
3
8
|
|
|
4
9
|
const uid = () => Math.random().toString(36).slice(2, 10);
|
|
5
10
|
|
|
@@ -107,10 +112,11 @@ export const getTrueSheetStateForAction = <State extends SheetState>(
|
|
|
107
112
|
const closingIndex = targetIndex + 1;
|
|
108
113
|
|
|
109
114
|
// Mark only the bottom-most route to pop as closing
|
|
110
|
-
// The sheet's dismiss() will handle dismissing sheets above it first
|
|
115
|
+
// The sheet's dismiss() will handle dismissing sheets above it first.
|
|
116
|
+
// The index stays on the top sheet until REMOVE: Expo Router 58 treats the routes
|
|
117
|
+
// after `index` as preloaded.
|
|
111
118
|
return {
|
|
112
119
|
...state,
|
|
113
|
-
index: closingIndex,
|
|
114
120
|
routes: state.routes.map((route, i) => {
|
|
115
121
|
if (i === closingIndex) return { ...route, closing: true };
|
|
116
122
|
if (i > closingIndex) return { ...route, dismissing: true };
|
|
@@ -176,6 +182,61 @@ export const getTrueSheetStateForAction = <State extends SheetState>(
|
|
|
176
182
|
}
|
|
177
183
|
};
|
|
178
184
|
|
|
185
|
+
const BASE_ROUTE_ACTIONS = new Set([
|
|
186
|
+
'NAVIGATE',
|
|
187
|
+
'NAVIGATE_DEPRECATED',
|
|
188
|
+
'PUSH',
|
|
189
|
+
'REPLACE',
|
|
190
|
+
'POP_TO',
|
|
191
|
+
]);
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Reduces an action that targets the base route by name. React Navigation 7 adds another base
|
|
195
|
+
* route for a `PUSH`, a `REPLACE` or a `NAVIGATE` without `pop`, which presents the base screen
|
|
196
|
+
* as a sheet, and this router's `POP_TO` drops the params and nested state. This goes back to it
|
|
197
|
+
* instead: `getPoppedState` reduces the action as a `NAVIGATE` with `pop` (a `PUSH` ignores
|
|
198
|
+
* `pop`) to update the base route (params, nested state), then the sheets above it are dismissed.
|
|
199
|
+
* Returns `undefined` for other actions, a `REPLACE` with no sheet (it swaps the base route), or
|
|
200
|
+
* when the base router doesn't land on the base route.
|
|
201
|
+
*/
|
|
202
|
+
export const getStateForNavigateToBase = <State extends SheetState>(
|
|
203
|
+
state: State,
|
|
204
|
+
action: TrueSheetActionType,
|
|
205
|
+
getPoppedState: (action: TrueSheetActionType) => State | null | undefined
|
|
206
|
+
): State | undefined => {
|
|
207
|
+
const { type, payload } = action as { type: string; payload?: { name?: string } };
|
|
208
|
+
const baseRoute = state.routes[0]!;
|
|
209
|
+
const hasSheets = state.routes.length > 1;
|
|
210
|
+
|
|
211
|
+
if (
|
|
212
|
+
!BASE_ROUTE_ACTIONS.has(type) ||
|
|
213
|
+
payload?.name !== baseRoute.name ||
|
|
214
|
+
(type === 'REPLACE' && !hasSheets)
|
|
215
|
+
) {
|
|
216
|
+
return undefined;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const popped = getPoppedState({
|
|
220
|
+
...action,
|
|
221
|
+
type: 'NAVIGATE',
|
|
222
|
+
payload: { ...payload, pop: true },
|
|
223
|
+
} as unknown as TrueSheetActionType);
|
|
224
|
+
const base = popped?.routes[popped.index];
|
|
225
|
+
|
|
226
|
+
if (base?.key !== baseRoute.key) {
|
|
227
|
+
return undefined;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (!hasSheets) {
|
|
231
|
+
return popped!;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return getTrueSheetStateForAction(
|
|
235
|
+
{ ...state, routes: [base, ...state.routes.slice(1)] },
|
|
236
|
+
TrueSheetActions.pop(state.routes.length - 1)
|
|
237
|
+
)!;
|
|
238
|
+
};
|
|
239
|
+
|
|
179
240
|
export const createTrueSheetRouter =
|
|
180
241
|
(stackRouter: TrueSheetRouterFactory) =>
|
|
181
242
|
(routerOptions: TrueSheetRouterOptions): TrueSheetRouter => {
|
|
@@ -199,7 +260,14 @@ export const createTrueSheetRouter =
|
|
|
199
260
|
},
|
|
200
261
|
|
|
201
262
|
getStateForAction(state, action, options) {
|
|
202
|
-
const result =
|
|
263
|
+
const result =
|
|
264
|
+
getStateForNavigateToBase(
|
|
265
|
+
state,
|
|
266
|
+
action,
|
|
267
|
+
// NAVIGATE never returns a partial state
|
|
268
|
+
(popAction) =>
|
|
269
|
+
baseRouter.getStateForAction(state, popAction, options) as TrueSheetRouterState | null
|
|
270
|
+
) ?? getTrueSheetStateForAction(state, action);
|
|
203
271
|
|
|
204
272
|
return result === undefined ? baseRouter.getStateForAction(state, action, options) : result;
|
|
205
273
|
},
|
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
import { TrueSheetActions, type TrueSheetActionType } from '../actions';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
getResizeRouteIndex,
|
|
4
|
+
getStateForNavigateToBase,
|
|
5
|
+
getTrueSheetStateForAction,
|
|
6
|
+
} from '../createTrueSheetRouter';
|
|
3
7
|
import type { StackRouterFactory, TrueSheetRouterFactory, TrueSheetRouterState } from './types';
|
|
4
8
|
|
|
5
9
|
type ActionResult = { state: TrueSheetRouterState; affectedRouteKey?: string };
|
|
6
10
|
|
|
11
|
+
/**
|
|
12
|
+
* `RouterActionHandling.Unhandled` from Expo Router 58+, which deprecates `null`.
|
|
13
|
+
*/
|
|
14
|
+
export type Unhandled = 'UNHANDLED';
|
|
15
|
+
|
|
7
16
|
type RouterExtension = (context: {
|
|
8
17
|
baseRouter: {
|
|
9
18
|
getStateForAction(
|
|
10
19
|
state: TrueSheetRouterState,
|
|
11
20
|
action: TrueSheetActionType,
|
|
12
21
|
options: object
|
|
13
|
-
): ActionResult |
|
|
22
|
+
): ActionResult | Unhandled;
|
|
14
23
|
};
|
|
15
24
|
options: { initialRouteName?: string };
|
|
16
25
|
}) => object;
|
|
@@ -32,7 +41,8 @@ export type ExtendRouter = (
|
|
|
32
41
|
*/
|
|
33
42
|
export const extendTrueSheetRouter = (
|
|
34
43
|
extendRouter: ExtendRouter,
|
|
35
|
-
stackRouter: StackRouterFactory
|
|
44
|
+
stackRouter: StackRouterFactory,
|
|
45
|
+
unhandled: Unhandled
|
|
36
46
|
): TrueSheetRouterFactory =>
|
|
37
47
|
extendRouter(
|
|
38
48
|
stackRouter,
|
|
@@ -48,7 +58,25 @@ export const extendTrueSheetRouter = (
|
|
|
48
58
|
state: TrueSheetRouterState,
|
|
49
59
|
action: TrueSheetActionType,
|
|
50
60
|
config: object
|
|
51
|
-
): ActionResult |
|
|
61
|
+
): ActionResult | Unhandled {
|
|
62
|
+
const { type, payload } = action as { type: string; payload?: { name?: string } };
|
|
63
|
+
|
|
64
|
+
// Expo Router keeps preloaded routes in `routes`, and the navigator presents every
|
|
65
|
+
// route there as a sheet. A parent navigator's route bubbles up to it, as in a `Stack`.
|
|
66
|
+
if (type === 'PRELOAD') {
|
|
67
|
+
return payload?.name && state.routeNames.includes(payload.name) ? { state } : unhandled;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const navigated = getStateForNavigateToBase(state, action, (popAction) => {
|
|
71
|
+
const popped = baseRouter.getStateForAction(state, popAction, config);
|
|
72
|
+
return popped === unhandled ? undefined : popped.state;
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Expo Router resolves the destination's nested state in the affected route
|
|
76
|
+
if (navigated) {
|
|
77
|
+
return { state: navigated, affectedRouteKey: navigated.routes[0]?.key };
|
|
78
|
+
}
|
|
79
|
+
|
|
52
80
|
const result = getTrueSheetStateForAction(state, action);
|
|
53
81
|
|
|
54
82
|
if (result === undefined) {
|
|
@@ -56,7 +84,7 @@ export const extendTrueSheetRouter = (
|
|
|
56
84
|
}
|
|
57
85
|
|
|
58
86
|
if (result === null) {
|
|
59
|
-
return
|
|
87
|
+
return unhandled;
|
|
60
88
|
}
|
|
61
89
|
|
|
62
90
|
const affectedIndex =
|
|
@@ -4,7 +4,7 @@ import { StackRouter, unstable_integrateWithRouter, useNavigation } from 'expo-r
|
|
|
4
4
|
import { createTrueSheetRouter } from '../createTrueSheetRouter';
|
|
5
5
|
import { trueSheetNavigator } from '../navigator';
|
|
6
6
|
import type { ParamListBase } from './base-types';
|
|
7
|
-
import { extendTrueSheetRouter, type ExtendRouter } from './extendTrueSheetRouter';
|
|
7
|
+
import { extendTrueSheetRouter, type ExtendRouter, type Unhandled } from './extendTrueSheetRouter';
|
|
8
8
|
import type {
|
|
9
9
|
StackRouterFactory,
|
|
10
10
|
TrueSheetNavigationOptions,
|
|
@@ -18,16 +18,21 @@ import type {
|
|
|
18
18
|
} from './types';
|
|
19
19
|
|
|
20
20
|
// Expo Router 58+ replaced the React Navigation router contract and ships `extendRouter`
|
|
21
|
-
// for it. Before that, the vendored StackRouter is structurally
|
|
22
|
-
// @react-navigation's. Pinned to this entry's own router types:
|
|
23
|
-
// shared and typed against React Navigation, which resolves to
|
|
24
|
-
// install expo-router.
|
|
25
|
-
const extendRouter =
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
// and `RouterActionHandling` for it. Before that, the vendored StackRouter is structurally
|
|
22
|
+
// identical to @react-navigation's. Pinned to this entry's own router types:
|
|
23
|
+
// `createTrueSheetRouter` is shared and typed against React Navigation, which resolves to
|
|
24
|
+
// `any` in apps that only install expo-router.
|
|
25
|
+
const { extendRouter, RouterActionHandling } = ExpoRouter as {
|
|
26
|
+
extendRouter?: ExtendRouter;
|
|
27
|
+
RouterActionHandling?: { Unhandled: Unhandled };
|
|
28
|
+
};
|
|
28
29
|
|
|
29
30
|
const sheetRouter: TrueSheetRouterFactory = extendRouter
|
|
30
|
-
? extendTrueSheetRouter(
|
|
31
|
+
? extendTrueSheetRouter(
|
|
32
|
+
extendRouter,
|
|
33
|
+
StackRouter as StackRouterFactory,
|
|
34
|
+
RouterActionHandling!.Unhandled
|
|
35
|
+
)
|
|
31
36
|
: createTrueSheetRouter(StackRouter as StackRouterFactory);
|
|
32
37
|
|
|
33
38
|
// `routes`/`dispatch` are injected by `createProps`, so they are declared as
|