@lodev09/react-native-true-sheet 4.0.0-beta.1 → 4.0.0-beta.11
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 +25 -4
- package/android/build.gradle +22 -1
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerView.kt +24 -7
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentView.kt +211 -182
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentViewManager.kt +5 -1
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetFooterView.kt +14 -19
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayView.kt +106 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayViewManager.kt +45 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetPackage.kt +2 -1
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetStateUpdater.kt +18 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +82 -39
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +275 -88
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +26 -1
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetCoordinatorLayout.kt +75 -3
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDetentCalculator.kt +56 -34
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetOverlayRootView.kt +137 -0
- package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetLifecycleEvents.kt +15 -0
- package/android/src/main/java/com/lodev09/truesheet/utils/KeyboardUtils.kt +1 -1
- package/android/src/main/java/com/lodev09/truesheet/utils/TouchEventDeduper.kt +31 -0
- package/android/src/main/java/com/lodev09/truesheet/utils/ViewUtils.kt +19 -7
- package/android/src/main/jni/TrueSheetSpec.h +1 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewComponentDescriptor.h +0 -8
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.cpp +52 -29
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.h +6 -1
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.cpp +1 -1
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.h +6 -4
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.cpp +7 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetLayoutUtils.h +46 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewComponentDescriptor.h +21 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.cpp +17 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.h +36 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.cpp +4 -35
- package/ios/TrueSheetContainerView.h +14 -7
- package/ios/TrueSheetContainerView.mm +10 -7
- package/ios/TrueSheetContentView.h +31 -15
- package/ios/TrueSheetContentView.mm +148 -188
- package/ios/TrueSheetFooterView.h +6 -0
- package/ios/TrueSheetFooterView.mm +48 -2
- package/ios/TrueSheetModule.h +2 -7
- package/ios/TrueSheetModule.mm +7 -16
- package/ios/TrueSheetOverlayView.h +32 -0
- package/ios/TrueSheetOverlayView.mm +161 -0
- package/ios/TrueSheetView.mm +171 -29
- package/ios/TrueSheetViewController.h +10 -0
- package/ios/TrueSheetViewController.mm +256 -74
- package/ios/core/TrueSheetGrabberView.mm +13 -3
- package/ios/core/TrueSheetOverlayContainerView.h +31 -0
- package/ios/core/TrueSheetOverlayContainerView.mm +35 -0
- package/ios/events/TrueSheetLifecycleEvents.h +2 -0
- package/ios/events/TrueSheetLifecycleEvents.mm +8 -0
- package/ios/tvos/TrueSheetStubs.mm +12 -0
- package/ios/utils/UIView+ScrollEdgeInteraction.h +1 -0
- package/ios/utils/UIView+ScrollEdgeInteraction.mm +24 -2
- package/lib/module/TrueSheet.js +149 -34
- package/lib/module/TrueSheet.js.map +1 -1
- package/lib/module/TrueSheet.web.js +122 -53
- package/lib/module/TrueSheet.web.js.map +1 -1
- package/lib/module/TrueSheetOverlay.js +30 -0
- package/lib/module/TrueSheetOverlay.js.map +1 -0
- package/lib/module/TrueSheetOverlay.web.js +35 -0
- package/lib/module/TrueSheetOverlay.web.js.map +1 -0
- package/lib/module/TrueSheetPeek.web.js +3 -3
- package/lib/module/TrueSheetPeek.web.js.map +1 -1
- package/lib/module/fabric/TrueSheetContentViewNativeComponent.ts +1 -1
- package/lib/module/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
- package/lib/module/fabric/TrueSheetViewNativeComponent.ts +5 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/mocks/index.js +10 -0
- package/lib/module/mocks/index.js.map +1 -1
- package/lib/module/mocks/navigation.js +31 -21
- package/lib/module/mocks/navigation.js.map +1 -1
- package/lib/module/mocks/navigationExpoRouter.js +21 -0
- package/lib/module/mocks/navigationExpoRouter.js.map +1 -0
- package/lib/module/mocks/reanimated.js +1 -1
- package/lib/module/mocks/reanimated.js.map +1 -1
- package/lib/module/navigation/{TrueSheetView.js → TrueSheetNavigatorContent.js} +14 -12
- package/lib/module/navigation/TrueSheetNavigatorContent.js.map +1 -0
- package/lib/module/navigation/actions.js +50 -0
- package/lib/module/navigation/actions.js.map +1 -0
- package/lib/module/navigation/base-types.js +2 -0
- package/lib/module/navigation/base-types.js.map +1 -0
- package/lib/module/navigation/{TrueSheetRouter.js → createTrueSheetRouter.js} +11 -24
- package/lib/module/navigation/createTrueSheetRouter.js.map +1 -0
- package/lib/module/navigation/expo-router/base-types.js +2 -0
- package/lib/module/navigation/expo-router/base-types.js.map +1 -0
- package/lib/module/navigation/expo-router/index.js +50 -0
- package/lib/module/navigation/expo-router/index.js.map +1 -0
- package/lib/module/navigation/expo-router/types.js +4 -0
- package/lib/module/navigation/expo-router/types.js.map +1 -0
- package/lib/module/navigation/index.js +43 -2
- package/lib/module/navigation/index.js.map +1 -1
- package/lib/module/navigation/navigator.js +10 -0
- package/lib/module/navigation/navigator.js.map +1 -0
- package/lib/module/navigation/screen/ReanimatedTrueSheetScreen.js +2 -2
- package/lib/module/navigation/screen/ReanimatedTrueSheetScreen.js.map +1 -1
- package/lib/module/navigation/screen/TrueSheetScreen.js +2 -2
- package/lib/module/navigation/screen/TrueSheetScreen.js.map +1 -1
- package/lib/module/navigation/screen/useSheetScreenState.js +5 -4
- package/lib/module/navigation/screen/useSheetScreenState.js.map +1 -1
- package/lib/module/navigation/useTrueSheetNavigation.js +1 -1
- package/lib/module/navigation/useTrueSheetNavigation.js.map +1 -1
- package/lib/module/web/dom.js +10 -0
- package/lib/module/web/dom.js.map +1 -0
- package/lib/module/web/vaul/constants.js +4 -0
- package/lib/module/web/vaul/constants.js.map +1 -1
- package/lib/module/web/vaul/context.js.map +1 -1
- package/lib/module/web/vaul/helpers.js +10 -4
- package/lib/module/web/vaul/helpers.js.map +1 -1
- package/lib/module/web/vaul/index.js +35 -2
- package/lib/module/web/vaul/index.js.map +1 -1
- package/lib/typescript/src/TrueSheet.d.ts +6 -0
- package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheet.types.d.ts +72 -1
- package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheet.web.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheetOverlay.d.ts +8 -0
- package/lib/typescript/src/TrueSheetOverlay.d.ts.map +1 -0
- package/lib/typescript/src/TrueSheetOverlay.web.d.ts +8 -0
- package/lib/typescript/src/TrueSheetOverlay.web.d.ts.map +1 -0
- package/lib/typescript/src/TrueSheetPeek.web.d.ts.map +1 -1
- package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts +6 -0
- package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +4 -0
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/mocks/index.d.ts +4 -0
- package/lib/typescript/src/mocks/index.d.ts.map +1 -1
- package/lib/typescript/src/mocks/navigation.d.ts +5 -1
- package/lib/typescript/src/mocks/navigation.d.ts.map +1 -1
- package/lib/typescript/src/mocks/navigationExpoRouter.d.ts +22 -0
- package/lib/typescript/src/mocks/navigationExpoRouter.d.ts.map +1 -0
- package/lib/typescript/src/mocks/reanimated.d.ts.map +1 -1
- package/lib/typescript/src/navigation/TrueSheetNavigatorContent.d.ts +5 -0
- package/lib/typescript/src/navigation/TrueSheetNavigatorContent.d.ts.map +1 -0
- package/lib/typescript/src/navigation/actions.d.ts +71 -0
- package/lib/typescript/src/navigation/actions.d.ts.map +1 -0
- package/lib/typescript/src/navigation/base-types.d.ts +9 -0
- package/lib/typescript/src/navigation/base-types.d.ts.map +1 -0
- package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts +3 -0
- package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts.map +1 -0
- package/lib/typescript/src/navigation/expo-router/base-types.d.ts +8 -0
- package/lib/typescript/src/navigation/expo-router/base-types.d.ts.map +1 -0
- package/lib/typescript/src/navigation/expo-router/index.d.ts +37 -0
- package/lib/typescript/src/navigation/expo-router/index.d.ts.map +1 -0
- package/lib/typescript/src/navigation/expo-router/types.d.ts +44 -0
- package/lib/typescript/src/navigation/expo-router/types.d.ts.map +1 -0
- package/lib/typescript/src/navigation/index.d.ts +42 -3
- package/lib/typescript/src/navigation/index.d.ts.map +1 -1
- package/lib/typescript/src/navigation/navigator.d.ts +7 -0
- package/lib/typescript/src/navigation/navigator.d.ts.map +1 -0
- package/lib/typescript/src/navigation/screen/ReanimatedTrueSheetScreen.d.ts +1 -1
- package/lib/typescript/src/navigation/screen/ReanimatedTrueSheetScreen.d.ts.map +1 -1
- package/lib/typescript/src/navigation/screen/TrueSheetScreen.d.ts +1 -1
- package/lib/typescript/src/navigation/screen/TrueSheetScreen.d.ts.map +1 -1
- package/lib/typescript/src/navigation/screen/types.d.ts +3 -4
- package/lib/typescript/src/navigation/screen/types.d.ts.map +1 -1
- package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts +5 -6
- package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts.map +1 -1
- package/lib/typescript/src/navigation/types.d.ts +65 -5
- package/lib/typescript/src/navigation/types.d.ts.map +1 -1
- package/lib/typescript/src/navigation/useTrueSheetNavigation.d.ts +1 -1
- package/lib/typescript/src/navigation/useTrueSheetNavigation.d.ts.map +1 -1
- package/lib/typescript/src/web/dom.d.ts +7 -0
- package/lib/typescript/src/web/dom.d.ts.map +1 -0
- package/lib/typescript/src/web/vaul/constants.d.ts +1 -0
- package/lib/typescript/src/web/vaul/constants.d.ts.map +1 -1
- package/lib/typescript/src/web/vaul/context.d.ts +1 -0
- package/lib/typescript/src/web/vaul/context.d.ts.map +1 -1
- package/lib/typescript/src/web/vaul/helpers.d.ts.map +1 -1
- package/lib/typescript/src/web/vaul/index.d.ts +7 -1
- package/lib/typescript/src/web/vaul/index.d.ts.map +1 -1
- package/package.json +32 -9
- package/react-native.config.js +1 -0
- package/src/TrueSheet.tsx +184 -46
- package/src/TrueSheet.types.ts +77 -1
- package/src/TrueSheet.web.tsx +135 -64
- package/src/TrueSheetOverlay.tsx +24 -0
- package/src/TrueSheetOverlay.web.tsx +33 -0
- package/src/TrueSheetPeek.web.tsx +4 -3
- package/src/fabric/TrueSheetContentViewNativeComponent.ts +1 -1
- package/src/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
- package/src/fabric/TrueSheetViewNativeComponent.ts +5 -0
- package/src/index.ts +1 -0
- package/src/mocks/index.ts +7 -0
- package/src/mocks/navigation.ts +32 -25
- package/src/mocks/navigationExpoRouter.ts +41 -0
- package/src/mocks/reanimated.ts +8 -9
- package/src/navigation/{TrueSheetView.tsx → TrueSheetNavigatorContent.tsx} +24 -17
- package/src/navigation/actions.ts +78 -0
- package/src/navigation/base-types.ts +20 -0
- package/src/navigation/createTrueSheetRouter.ts +194 -0
- package/src/navigation/expo-router/base-types.ts +18 -0
- package/src/navigation/expo-router/index.ts +90 -0
- package/src/navigation/expo-router/types.ts +116 -0
- package/src/navigation/index.ts +68 -2
- package/src/navigation/navigator.ts +18 -0
- package/src/navigation/screen/ReanimatedTrueSheetScreen.tsx +2 -2
- package/src/navigation/screen/TrueSheetScreen.tsx +2 -2
- package/src/navigation/screen/types.ts +4 -6
- package/src/navigation/screen/useSheetScreenState.ts +10 -15
- package/src/navigation/types.ts +99 -16
- package/src/navigation/useTrueSheetNavigation.ts +1 -1
- package/src/web/dom.ts +9 -0
- package/src/web/vaul/constants.ts +4 -0
- package/src/web/vaul/context.ts +1 -0
- package/src/web/vaul/helpers.ts +13 -6
- package/src/web/vaul/index.tsx +46 -1
- package/lib/module/navigation/TrueSheetRouter.js.map +0 -1
- package/lib/module/navigation/TrueSheetView.js.map +0 -1
- package/lib/module/navigation/createTrueSheetNavigator.js +0 -63
- package/lib/module/navigation/createTrueSheetNavigator.js.map +0 -1
- package/lib/typescript/src/navigation/TrueSheetRouter.d.ts +0 -57
- package/lib/typescript/src/navigation/TrueSheetRouter.d.ts.map +0 -1
- package/lib/typescript/src/navigation/TrueSheetView.d.ts +0 -10
- package/lib/typescript/src/navigation/TrueSheetView.d.ts.map +0 -1
- package/lib/typescript/src/navigation/createTrueSheetNavigator.d.ts +0 -35
- package/lib/typescript/src/navigation/createTrueSheetNavigator.d.ts.map +0 -1
- package/src/navigation/TrueSheetRouter.ts +0 -234
- package/src/navigation/createTrueSheetNavigator.tsx +0 -93
package/src/TrueSheet.types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ComponentType, ReactElement } from 'react';
|
|
1
|
+
import type { Component, ComponentType, ReactElement, RefObject } from 'react';
|
|
2
2
|
import type {
|
|
3
3
|
ColorValue,
|
|
4
4
|
NativeSyntheticEvent,
|
|
@@ -36,6 +36,7 @@ export type WillPresentEvent = NativeSyntheticEvent<DetentInfoEventPayload>;
|
|
|
36
36
|
export type DidPresentEvent = NativeSyntheticEvent<DetentInfoEventPayload>;
|
|
37
37
|
export type WillDismissEvent = NativeSyntheticEvent<null>;
|
|
38
38
|
export type DidDismissEvent = NativeSyntheticEvent<null>;
|
|
39
|
+
export type DismissAttemptEvent = NativeSyntheticEvent<null>;
|
|
39
40
|
export type DragBeginEvent = NativeSyntheticEvent<DetentInfoEventPayload>;
|
|
40
41
|
export type DragChangeEvent = NativeSyntheticEvent<DetentInfoEventPayload>;
|
|
41
42
|
export type DragEndEvent = NativeSyntheticEvent<DetentInfoEventPayload>;
|
|
@@ -165,6 +166,28 @@ export type ScrollEdgeEffect = 'automatic' | 'hard' | 'soft' | 'hidden';
|
|
|
165
166
|
* Options for scrollable behavior.
|
|
166
167
|
*/
|
|
167
168
|
export interface ScrollableOptions {
|
|
169
|
+
/**
|
|
170
|
+
* Applies the bottom safe-area inset to the scroll content automatically
|
|
171
|
+
* while the content can scroll, so the last item clears the home indicator
|
|
172
|
+
* (iOS) and navigation bar (Android).
|
|
173
|
+
*
|
|
174
|
+
* - **iOS**: Sets the scroll view's native
|
|
175
|
+
* [`contentInsetAdjustmentBehavior`](https://developer.apple.com/documentation/uikit/uiscrollview/contentinsetadjustmentbehavior)
|
|
176
|
+
* to `automatic` while it's plugged into the sheet — no need to set React
|
|
177
|
+
* Native's iOS-only prop yourself.
|
|
178
|
+
* - **Android**: Pads the scroll content with the bottom window inset,
|
|
179
|
+
* mirroring iOS's `automatic` behavior — parity React Native's iOS-only
|
|
180
|
+
* prop can't provide.
|
|
181
|
+
* - **Web**: Lifts the content above the safe area
|
|
182
|
+
* (`env(safe-area-inset-bottom)`).
|
|
183
|
+
*
|
|
184
|
+
* Only applies while the sheet's `insetAdjustment` is `'automatic'`. When
|
|
185
|
+
* the sheet has a relative footer, the footer absorbs the inset instead.
|
|
186
|
+
*
|
|
187
|
+
* @default true
|
|
188
|
+
*/
|
|
189
|
+
contentInsetAdjustmentBehavior?: boolean;
|
|
190
|
+
|
|
168
191
|
/**
|
|
169
192
|
* Extra offset when scrolling to the focused input when keyboard appears.
|
|
170
193
|
*
|
|
@@ -172,6 +195,18 @@ export interface ScrollableOptions {
|
|
|
172
195
|
*/
|
|
173
196
|
keyboardScrollOffset?: number;
|
|
174
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Adjusts the bottom inset applied to the scrollable when the keyboard is shown.
|
|
200
|
+
* Positive values increase the inset; negative values reduce it.
|
|
201
|
+
* Pass `-insets.bottom` to cancel out safe-area padding already included in
|
|
202
|
+
* the content's `paddingBottom`.
|
|
203
|
+
* Does not affect the auto scroll of the focused input — it always targets
|
|
204
|
+
* the keyboard edge.
|
|
205
|
+
*
|
|
206
|
+
* @default 0
|
|
207
|
+
*/
|
|
208
|
+
keyboardOffset?: number;
|
|
209
|
+
|
|
175
210
|
/**
|
|
176
211
|
* Whether scrolling the content expands the sheet to the next detent.
|
|
177
212
|
* When `false`, only the grabber can expand the sheet.
|
|
@@ -397,6 +432,21 @@ export interface TrueSheetProps extends ViewProps {
|
|
|
397
432
|
*/
|
|
398
433
|
initialDetentAnimated?: boolean;
|
|
399
434
|
|
|
435
|
+
/**
|
|
436
|
+
* Specify whether the sheet content should mount lazily, on first presentation.
|
|
437
|
+
* Set to `false` to mount the content before presentation without presenting the sheet.
|
|
438
|
+
* Use this when content is not ready on the first render, then call `present()`
|
|
439
|
+
* after your readiness signal so auto detents measure the settled content.
|
|
440
|
+
*
|
|
441
|
+
* Content that requires window attachment to measure, such as SwiftUI-hosted views,
|
|
442
|
+
* still only measures during presentation.
|
|
443
|
+
*
|
|
444
|
+
* @platform android
|
|
445
|
+
* @platform ios
|
|
446
|
+
* @default true
|
|
447
|
+
*/
|
|
448
|
+
lazy?: boolean;
|
|
449
|
+
|
|
400
450
|
/**
|
|
401
451
|
* The detent index that the sheet should start to dim the background.
|
|
402
452
|
* This is ignored if `dimmed` is set to `false`.
|
|
@@ -407,6 +457,7 @@ export interface TrueSheetProps extends ViewProps {
|
|
|
407
457
|
|
|
408
458
|
/**
|
|
409
459
|
* Prevents interactive dismissal of the Sheet.
|
|
460
|
+
* Blocked attempts fire `onDismissAttempt`, so you can confirm before dismissing programmatically.
|
|
410
461
|
*
|
|
411
462
|
* @default true
|
|
412
463
|
*/
|
|
@@ -568,8 +619,26 @@ export interface TrueSheetProps extends ViewProps {
|
|
|
568
619
|
*/
|
|
569
620
|
footerStyle?: StyleProp<ViewStyle>;
|
|
570
621
|
|
|
622
|
+
/**
|
|
623
|
+
* A ref to the scrollable component (e.g. `ScrollView`, `FlatList`)
|
|
624
|
+
* rendered within the sheet content.
|
|
625
|
+
* Required for scrollable handling — nested scrolling, keyboard insets,
|
|
626
|
+
* and `auto` detent sizing are wired to this scrollable.
|
|
627
|
+
*
|
|
628
|
+
* Example:
|
|
629
|
+
* ```tsx
|
|
630
|
+
* const scrollableRef = useRef<ScrollView>(null)
|
|
631
|
+
*
|
|
632
|
+
* <TrueSheet scrollableRef={scrollableRef}>
|
|
633
|
+
* <ScrollView ref={scrollableRef} />
|
|
634
|
+
* </TrueSheet>
|
|
635
|
+
* ```
|
|
636
|
+
*/
|
|
637
|
+
scrollableRef?: RefObject<Component<unknown> | null>;
|
|
638
|
+
|
|
571
639
|
/**
|
|
572
640
|
* Options for scrollable behavior.
|
|
641
|
+
* Applies to the scrollable provided via `scrollableRef`.
|
|
573
642
|
*/
|
|
574
643
|
scrollableOptions?: ScrollableOptions;
|
|
575
644
|
|
|
@@ -621,6 +690,13 @@ export interface TrueSheetProps extends ViewProps {
|
|
|
621
690
|
*/
|
|
622
691
|
onDidDismiss?: (event: DidDismissEvent) => void;
|
|
623
692
|
|
|
693
|
+
/**
|
|
694
|
+
* Called when the user tries to dismiss the sheet while `dismissible` is `false`,
|
|
695
|
+
* e.g. by dragging down or pressing back/escape. Tapping the dim does not count.
|
|
696
|
+
* The sheet stays presented. Confirm with the user, then call `dismiss()` to proceed.
|
|
697
|
+
*/
|
|
698
|
+
onDismissAttempt?: (event: DismissAttemptEvent) => void;
|
|
699
|
+
|
|
624
700
|
/**
|
|
625
701
|
* Called when the detent of the sheet has changed.
|
|
626
702
|
* Either by dragging or programatically.
|
package/src/TrueSheet.web.tsx
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
useRef,
|
|
11
11
|
useState,
|
|
12
12
|
} from 'react';
|
|
13
|
-
import type {
|
|
13
|
+
import type { DimensionValue, LayoutChangeEvent } from 'react-native';
|
|
14
14
|
import { StyleSheet, useColorScheme, useWindowDimensions, View } from 'react-native';
|
|
15
15
|
|
|
16
16
|
import type {
|
|
@@ -31,6 +31,7 @@ import type {
|
|
|
31
31
|
TrueSheetStaticMethods,
|
|
32
32
|
WillBlurEvent,
|
|
33
33
|
WillDismissEvent,
|
|
34
|
+
DismissAttemptEvent,
|
|
34
35
|
WillFocusEvent,
|
|
35
36
|
WillPresentEvent,
|
|
36
37
|
} from './TrueSheet.types';
|
|
@@ -51,18 +52,19 @@ import {
|
|
|
51
52
|
DEFAULT_GRABBER_WIDTH,
|
|
52
53
|
DEFAULT_MAX_WIDTH,
|
|
53
54
|
} from './web/constants';
|
|
55
|
+
import { getDOMElement } from './web/dom';
|
|
54
56
|
import { Drawer } from './web/vaul';
|
|
55
|
-
import { DEFAULT_PEEK_HEIGHT, TRANSITIONS } from './web/vaul/constants';
|
|
56
|
-
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
return null;
|
|
57
|
+
import { DEFAULT_PEEK_HEIGHT, DRAG_CLASS, TRANSITIONS } from './web/vaul/constants';
|
|
58
|
+
|
|
59
|
+
// Resolves the user's `scrollableRef` to its scrollable DOM element — RN-web
|
|
60
|
+
// ScrollView refs expose the scroll node directly (with helpers attached);
|
|
61
|
+
// list refs (e.g. FlatList) expose it via getScrollableNode(). The node's
|
|
62
|
+
// first child is the content container.
|
|
63
|
+
const getScrollableElement = (scrollable: unknown): HTMLElement | null => {
|
|
64
|
+
if (!scrollable) return null;
|
|
65
|
+
if (scrollable instanceof HTMLElement) return scrollable;
|
|
66
|
+
const node = (scrollable as { getScrollableNode?: () => unknown }).getScrollableNode?.();
|
|
67
|
+
return node instanceof HTMLElement ? node : null;
|
|
66
68
|
};
|
|
67
69
|
|
|
68
70
|
const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props, ref) => {
|
|
@@ -78,6 +80,8 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
78
80
|
maxContentWidth,
|
|
79
81
|
anchor = 'center',
|
|
80
82
|
anchorOffset = DEFAULT_ANCHOR_OFFSET,
|
|
83
|
+
scrollableRef,
|
|
84
|
+
scrollableOptions,
|
|
81
85
|
grabber = true,
|
|
82
86
|
grabberOptions,
|
|
83
87
|
accessibilityOptions,
|
|
@@ -102,6 +106,7 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
102
106
|
onDidPresent,
|
|
103
107
|
onWillDismiss,
|
|
104
108
|
onDidDismiss,
|
|
109
|
+
onDismissAttempt,
|
|
105
110
|
onDetentChange,
|
|
106
111
|
onDragBegin,
|
|
107
112
|
onDragChange,
|
|
@@ -195,19 +200,34 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
195
200
|
e.preventDefault();
|
|
196
201
|
return;
|
|
197
202
|
}
|
|
203
|
+
if (!(target instanceof Element)) return;
|
|
204
|
+
const wrapper = drawerContentRef.current?.closest('[data-vaul-detached-wrapper]');
|
|
205
|
+
if (!wrapper) return;
|
|
198
206
|
// The footer is rendered via vaul's `detachedSiblings` as a sibling of
|
|
199
207
|
// Drawer.Content inside [data-vaul-detached-wrapper], so Radix treats
|
|
200
208
|
// clicks on it as "outside" the content. Don't dismiss for clicks that
|
|
201
209
|
// landed inside the wrapper.
|
|
202
|
-
if (target
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
210
|
+
if (wrapper.contains(target)) {
|
|
211
|
+
e.preventDefault();
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
// Every sheet runs Radix non-modal and portals its overlay + wrapper as
|
|
215
|
+
// siblings into the shared container, so a press on a sheet stacked above
|
|
216
|
+
// this one (its content or its dim overlay) is also delivered here as
|
|
217
|
+
// "outside". Sheets present in document order, so anything after our
|
|
218
|
+
// wrapper belongs to a descendant. Radix may dispatch on the deferred
|
|
219
|
+
// click (touch), after a dismissing descendant has left the DOM — treat
|
|
220
|
+
// disconnected as above too.
|
|
221
|
+
/* eslint-disable no-bitwise */
|
|
222
|
+
const above = Node.DOCUMENT_POSITION_FOLLOWING | Node.DOCUMENT_POSITION_DISCONNECTED;
|
|
223
|
+
if (wrapper.compareDocumentPosition(target) & above) {
|
|
224
|
+
e.preventDefault();
|
|
207
225
|
}
|
|
226
|
+
/* eslint-enable no-bitwise */
|
|
208
227
|
};
|
|
209
228
|
|
|
210
229
|
const dismissAboveRef = useRef<(animated?: boolean) => Promise<void>>(async () => {});
|
|
230
|
+
const dismissDescendantsRef = useRef<() => void>(() => {});
|
|
211
231
|
|
|
212
232
|
const methods = useMemo<TrueSheetMethods>(
|
|
213
233
|
() => ({
|
|
@@ -274,6 +294,17 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
274
294
|
const peekElRef = useRef<View>(null);
|
|
275
295
|
const peekContext = useMemo(() => ({ contentRef, peekRef: peekElRef, setPeekContentHeight }), []);
|
|
276
296
|
|
|
297
|
+
// The peek's own onLayout only fires when *it* resizes. Content added above
|
|
298
|
+
// it moves it without resizing it, so re-measure its offset whenever the
|
|
299
|
+
// content view's layout changes.
|
|
300
|
+
const handleContentLayout = useCallback(() => {
|
|
301
|
+
const peekEl = getDOMElement(peekElRef.current);
|
|
302
|
+
const contentEl = getDOMElement(contentRef.current);
|
|
303
|
+
if (peekEl && contentEl) {
|
|
304
|
+
setPeekContentHeight(measurePeekContentHeight(peekEl, contentEl));
|
|
305
|
+
}
|
|
306
|
+
}, []);
|
|
307
|
+
|
|
277
308
|
// An absolute (floating) header overlaps the content, so it contributes no
|
|
278
309
|
// height to the 'auto' detent measurement.
|
|
279
310
|
const absoluteHeader = headerOptions?.position === 'absolute';
|
|
@@ -302,8 +333,9 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
302
333
|
return [
|
|
303
334
|
footerStyle,
|
|
304
335
|
{
|
|
305
|
-
|
|
306
|
-
|
|
336
|
+
// RN types don't model CSS calc(), but RN-web passes it through
|
|
337
|
+
paddingBottom: `calc(${baseCss} + env(safe-area-inset-bottom, 0px))` as DimensionValue,
|
|
338
|
+
},
|
|
307
339
|
];
|
|
308
340
|
}, [footerOwnsInset, footerStyle]);
|
|
309
341
|
|
|
@@ -321,23 +353,19 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
321
353
|
// `naturalHeight`.
|
|
322
354
|
const [hasBoundedScrollable, setHasBoundedScrollable] = useState(false);
|
|
323
355
|
const [scrollableAutoHeight, setScrollableAutoHeight] = useState(0);
|
|
324
|
-
const
|
|
356
|
+
const detectedScrollerRef = useRef<HTMLElement | null>(null);
|
|
325
357
|
|
|
326
|
-
// Natural content height (header + content + footer, with a
|
|
358
|
+
// Natural content height (header + content + footer, with a detected
|
|
327
359
|
// scrollable's viewport replaced by its content size) — the height the
|
|
328
360
|
// content wants regardless of the sheet's bounds.
|
|
329
361
|
const measureNaturalHeight = useCallback(() => {
|
|
330
|
-
const contentEl = contentRef.current
|
|
362
|
+
const contentEl = getDOMElement(contentRef.current);
|
|
331
363
|
if (!contentEl || !contentEl.isConnected) return 0;
|
|
332
|
-
const headerEl = absoluteHeaderRef.current
|
|
333
|
-
|
|
334
|
-
: (headerElRef.current as unknown as HTMLElement | null);
|
|
335
|
-
const footerEl = absoluteFooterRef.current
|
|
336
|
-
? null
|
|
337
|
-
: (footerElRef.current as unknown as HTMLElement | null);
|
|
364
|
+
const headerEl = absoluteHeaderRef.current ? null : getDOMElement(headerElRef.current);
|
|
365
|
+
const footerEl = absoluteFooterRef.current ? null : getDOMElement(footerElRef.current);
|
|
338
366
|
let height =
|
|
339
367
|
(headerEl?.offsetHeight ?? 0) + (footerEl?.offsetHeight ?? 0) + contentEl.offsetHeight;
|
|
340
|
-
const scroller =
|
|
368
|
+
const scroller = detectedScrollerRef.current;
|
|
341
369
|
const scrollContent = scroller?.firstElementChild;
|
|
342
370
|
if (scroller?.isConnected && scrollContent instanceof HTMLElement) {
|
|
343
371
|
height += scrollContent.offsetHeight - scroller.clientHeight;
|
|
@@ -371,20 +399,18 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
371
399
|
scrollContent: Element | null;
|
|
372
400
|
} | null = null;
|
|
373
401
|
|
|
374
|
-
// (Re)
|
|
402
|
+
// (Re)resolve the plugged scrollable and observe the nodes whose size
|
|
375
403
|
// feeds the natural height — content growth inside a bounded scroller is
|
|
376
404
|
// invisible to the sheet's own layout (mirrors native's contentSize
|
|
377
405
|
// observation). Resolves the content node live: the layout-branch swap
|
|
378
406
|
// (natural flow ↔ sized) remounts it.
|
|
379
407
|
const observe = () => {
|
|
380
|
-
const contentEl = contentRef.current
|
|
381
|
-
const headerEl = headerElRef.current
|
|
408
|
+
const contentEl = getDOMElement(contentRef.current);
|
|
409
|
+
const headerEl = getDOMElement(headerElRef.current);
|
|
382
410
|
|
|
383
411
|
// Skip mutations that don't change the observed topology — e.g. a
|
|
384
|
-
// virtualized list mounting rows inside the
|
|
385
|
-
//
|
|
386
|
-
// resolves computed styles) on every scroll frame. Size changes are
|
|
387
|
-
// already covered by the ResizeObserver.
|
|
412
|
+
// virtualized list mounting rows inside the plugged scroller. Size
|
|
413
|
+
// changes are already covered by the ResizeObserver.
|
|
388
414
|
if (
|
|
389
415
|
observed &&
|
|
390
416
|
observed.contentEl === contentEl &&
|
|
@@ -396,9 +422,10 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
396
422
|
return;
|
|
397
423
|
}
|
|
398
424
|
|
|
399
|
-
const
|
|
400
|
-
contentEl && contentEl.isConnected ?
|
|
401
|
-
|
|
425
|
+
const resolved =
|
|
426
|
+
contentEl && contentEl.isConnected ? getScrollableElement(scrollableRef?.current) : null;
|
|
427
|
+
const scroller = resolved?.isConnected ? resolved : null;
|
|
428
|
+
detectedScrollerRef.current = scroller;
|
|
402
429
|
setHasBoundedScrollable(scroller != null);
|
|
403
430
|
observed = scroller
|
|
404
431
|
? { contentEl, headerEl, scroller, scrollContent: scroller.firstElementChild }
|
|
@@ -431,7 +458,7 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
431
458
|
mutationObserver?.disconnect();
|
|
432
459
|
resizeObserver?.disconnect();
|
|
433
460
|
};
|
|
434
|
-
}, [isOpen, hasAutoDetent, measureNaturalHeight]);
|
|
461
|
+
}, [isOpen, hasAutoDetent, measureNaturalHeight, scrollableRef]);
|
|
435
462
|
|
|
436
463
|
// Below the last detent a vertical touch pan moves the sheet, not the
|
|
437
464
|
// content — `[data-vaul-scroll-locked]` disables vertical touch panning on
|
|
@@ -549,13 +576,11 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
549
576
|
// measure either: they fire while the portal subtree is still detached.
|
|
550
577
|
// Geometry only runs while the drawer is mounted, so the elements are
|
|
551
578
|
// measurable here; fall back to the state value when they're absent.
|
|
552
|
-
const headerEl = headerElRef.current
|
|
579
|
+
const headerEl = getDOMElement(headerElRef.current);
|
|
553
580
|
// A relative footer is pushed off-screen at the peek detent — excluded
|
|
554
|
-
const footerEl = absoluteFooterRef.current
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
const peekEl = peekElRef.current as unknown as HTMLElement | null;
|
|
558
|
-
const contentEl = contentRef.current as unknown as HTMLElement | null;
|
|
581
|
+
const footerEl = absoluteFooterRef.current ? getDOMElement(footerElRef.current) : null;
|
|
582
|
+
const peekEl = getDOMElement(peekElRef.current);
|
|
583
|
+
const contentEl = getDOMElement(contentRef.current);
|
|
559
584
|
const livePeekContentHeight =
|
|
560
585
|
peekEl && contentEl ? measurePeekContentHeight(peekEl, contentEl) : inputs.peekContentHeight;
|
|
561
586
|
const livePeekHeight =
|
|
@@ -730,6 +755,9 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
730
755
|
|
|
731
756
|
const present = isOpen;
|
|
732
757
|
if (!present) {
|
|
758
|
+
// Mirror native: dismissing a sheet takes every sheet stacked above it
|
|
759
|
+
// (iOS dismisses from the presenter; Android runs dismissStack first).
|
|
760
|
+
dismissDescendantsRef.current();
|
|
733
761
|
// Pair willBlur with willDismiss on dismiss — mirrors native iOS
|
|
734
762
|
// emitWillDismissEvents (blur fires before dismiss). willPresent is
|
|
735
763
|
// deferred to `start()` below (needs the mounted drawer's geometry).
|
|
@@ -847,6 +875,13 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
847
875
|
isFormSheet
|
|
848
876
|
);
|
|
849
877
|
dismissAboveRef.current = dismissAbove;
|
|
878
|
+
// Captured per render: by the time the dismiss effect runs, this sheet has
|
|
879
|
+
// already been popped from the stack, so `dismissAbove` can't find it.
|
|
880
|
+
dismissDescendantsRef.current = () => {
|
|
881
|
+
for (let i = descendants.length - 1; i >= 0; i--) {
|
|
882
|
+
descendants[i]!.ref.current?.dismiss();
|
|
883
|
+
}
|
|
884
|
+
};
|
|
850
885
|
|
|
851
886
|
// Mirror Android: translate this sheet down to match the deepest descendant's
|
|
852
887
|
// top so the whole stack visually aligns. Cascades because every ancestor
|
|
@@ -892,17 +927,23 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
892
927
|
return;
|
|
893
928
|
}
|
|
894
929
|
|
|
895
|
-
// Track
|
|
896
|
-
//
|
|
897
|
-
//
|
|
898
|
-
// grandchild
|
|
930
|
+
// Track the immediate child's *actual* top: its cascade transform when it
|
|
931
|
+
// was itself pushed behind a grandchild, else its own snap point. Mirrors
|
|
932
|
+
// Android's addTranslation propagating extra translation up the stack —
|
|
933
|
+
// a grandchild opening pushes this sheet only as far as our child moved.
|
|
934
|
+
const readTranslateY = (el: HTMLElement) => {
|
|
935
|
+
const match = el.style.transform.match(/translate3d\([^,]*,\s*(-?\d*\.?\d+)px/);
|
|
936
|
+
return match ? Number.parseFloat(match[1]!) : null;
|
|
937
|
+
};
|
|
899
938
|
const computeTargetY = () => {
|
|
900
939
|
const parentSnap =
|
|
901
940
|
Number.parseFloat(parent.style.getPropertyValue('--snap-point-height')) || 0;
|
|
902
941
|
const node = descendants[0]?.nodeRef.current;
|
|
903
942
|
if (!node) return parentSnap;
|
|
904
|
-
const
|
|
905
|
-
|
|
943
|
+
const childTop =
|
|
944
|
+
readTranslateY(node) ??
|
|
945
|
+
(Number.parseFloat(node.style.getPropertyValue('--snap-point-height')) || 0);
|
|
946
|
+
return Math.max(parentSnap, childTop);
|
|
906
947
|
};
|
|
907
948
|
|
|
908
949
|
// When a form-sheet parent has a form-sheet descendant, clip the parent to
|
|
@@ -938,18 +979,24 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
938
979
|
|
|
939
980
|
const apply = () => {
|
|
940
981
|
applyFormClip();
|
|
941
|
-
// Mirror
|
|
942
|
-
//
|
|
943
|
-
//
|
|
944
|
-
|
|
945
|
-
|
|
982
|
+
// Mirror iPad/Android (`isExpanded`): a parent at full height (detent 1)
|
|
983
|
+
// stays put and the child simply overlays it. Full height is the sheet's
|
|
984
|
+
// own ceiling — the viewport for a page sheet, the capped content-fit
|
|
985
|
+
// height for a form sheet — so compare against where detent 1 lands.
|
|
986
|
+
const parentSnap =
|
|
987
|
+
Number.parseFloat(parent.style.getPropertyValue('--snap-point-height')) || 0;
|
|
988
|
+
const { effectiveH, ceiling } = computeDetentGeometry();
|
|
989
|
+
if (parentSnap <= effectiveH - ceiling + 0.5) {
|
|
946
990
|
parent.style.transition = transition;
|
|
947
991
|
parent.style.transform = '';
|
|
948
992
|
return;
|
|
949
993
|
}
|
|
994
|
+
// Mirror Android: stay put while the child is being dragged (its live
|
|
995
|
+
// transform would drag us along during drag-to-dismiss). The release
|
|
996
|
+
// snap rewrites the child's style and re-applies.
|
|
997
|
+
if (descendants[0]?.nodeRef.current?.classList.contains(DRAG_CLASS)) return;
|
|
950
998
|
const targetY = computeTargetY();
|
|
951
|
-
const
|
|
952
|
-
const currentY = match ? Number.parseFloat(match[1]!) : 0;
|
|
999
|
+
const currentY = readTranslateY(parent) ?? 0;
|
|
953
1000
|
if (Math.abs(currentY - targetY) < 0.5) return;
|
|
954
1001
|
parent.style.transition = transition;
|
|
955
1002
|
parent.style.transform = `translate3d(0, ${targetY}px, 0)`;
|
|
@@ -961,12 +1008,15 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
961
1008
|
// inline style changes.
|
|
962
1009
|
const observer = new MutationObserver(apply);
|
|
963
1010
|
observer.observe(parent, { attributes: true, attributeFilter: ['style'] });
|
|
1011
|
+
// The child's own cascade lands in a later frame — follow it when it moves.
|
|
1012
|
+
const childNode = descendants[0]?.nodeRef.current;
|
|
1013
|
+
if (childNode) observer.observe(childNode, { attributes: true, attributeFilter: ['style'] });
|
|
964
1014
|
|
|
965
1015
|
return () => {
|
|
966
1016
|
cancelAnimationFrame(raf);
|
|
967
1017
|
observer.disconnect();
|
|
968
1018
|
};
|
|
969
|
-
}, [descendants, activeSnapPoint, cornerRadius, isFormSheet, isOpen]);
|
|
1019
|
+
}, [descendants, activeSnapPoint, cornerRadius, isFormSheet, isOpen, computeDetentGeometry]);
|
|
970
1020
|
|
|
971
1021
|
// Focus/blur events fire when a descendant sheet appears on top of this one
|
|
972
1022
|
// (blur) or when all descendants are dismissed (focus). will-events fire
|
|
@@ -977,12 +1027,17 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
977
1027
|
const onDidBlurRef = useRef(onDidBlur);
|
|
978
1028
|
const onWillFocusRef = useRef(onWillFocus);
|
|
979
1029
|
const onDidFocusRef = useRef(onDidFocus);
|
|
1030
|
+
const onDismissAttemptRef = useRef(onDismissAttempt);
|
|
980
1031
|
useEffect(() => {
|
|
981
1032
|
onWillBlurRef.current = onWillBlur;
|
|
982
1033
|
onDidBlurRef.current = onDidBlur;
|
|
983
1034
|
onWillFocusRef.current = onWillFocus;
|
|
984
1035
|
onDidFocusRef.current = onDidFocus;
|
|
1036
|
+
onDismissAttemptRef.current = onDismissAttempt;
|
|
985
1037
|
});
|
|
1038
|
+
const handleDismissAttempt = useCallback(() => {
|
|
1039
|
+
onDismissAttemptRef.current?.({ nativeEvent: null } as DismissAttemptEvent);
|
|
1040
|
+
}, []);
|
|
986
1041
|
|
|
987
1042
|
const prevDescendantCountRef = useRef(0);
|
|
988
1043
|
useEffect(() => {
|
|
@@ -1073,12 +1128,25 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
1073
1128
|
// Lift content above iOS home indicator / bottom safe area when enabled.
|
|
1074
1129
|
// A relative footer owns the inset instead (see resolvedFooterStyle); an
|
|
1075
1130
|
// absolute footer floats over the content, so the content keeps its lift.
|
|
1131
|
+
// A plugged scrollable keeps the lift too (contentInsetAdjustmentBehavior,
|
|
1132
|
+
// default on) — disable it to scroll edge-to-edge behind the indicator
|
|
1133
|
+
// with user-applied content padding.
|
|
1076
1134
|
paddingBottom:
|
|
1077
|
-
insetAdjustment === 'automatic' &&
|
|
1135
|
+
insetAdjustment === 'automatic' &&
|
|
1136
|
+
!(footerOwnsInset && !absoluteFooter) &&
|
|
1137
|
+
(!scrollableRef || (scrollableOptions?.contentInsetAdjustmentBehavior ?? true))
|
|
1078
1138
|
? 'env(safe-area-inset-bottom, 0px)'
|
|
1079
1139
|
: 0,
|
|
1080
1140
|
}),
|
|
1081
|
-
[
|
|
1141
|
+
[
|
|
1142
|
+
backgroundColor,
|
|
1143
|
+
effectiveCornerRadius,
|
|
1144
|
+
insetAdjustment,
|
|
1145
|
+
footerOwnsInset,
|
|
1146
|
+
absoluteFooter,
|
|
1147
|
+
scrollableRef,
|
|
1148
|
+
scrollableOptions,
|
|
1149
|
+
]
|
|
1082
1150
|
);
|
|
1083
1151
|
|
|
1084
1152
|
const defaultGrabberColor =
|
|
@@ -1195,6 +1263,7 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
1195
1263
|
onDrag={handleDrag}
|
|
1196
1264
|
onRelease={handleRelease}
|
|
1197
1265
|
dismissible={dismissible}
|
|
1266
|
+
onDismissAttempt={handleDismissAttempt}
|
|
1198
1267
|
draggable={draggable}
|
|
1199
1268
|
repositionInputs={false}
|
|
1200
1269
|
modal={dimmed}
|
|
@@ -1258,10 +1327,12 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
1258
1327
|
{/* Content lays out naturally like native — fill only for auto
|
|
1259
1328
|
detents with a plugged scrollable, where natural layout is
|
|
1260
1329
|
circular (sheet height derives from the scroll content size).
|
|
1261
|
-
|
|
1330
|
+
Unlike native, CSS won't cap the content to the container
|
|
1331
|
+
(no fit-content flex basis), so the fill is still needed here. */}
|
|
1262
1332
|
<View
|
|
1263
1333
|
ref={contentRef}
|
|
1264
1334
|
style={hasAutoDetent && hasBoundedScrollable ? [contentFillStyle, style] : style}
|
|
1335
|
+
onLayout={handleContentLayout}
|
|
1265
1336
|
>
|
|
1266
1337
|
{children}
|
|
1267
1338
|
</View>
|
|
@@ -1284,7 +1355,7 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
1284
1355
|
{isValidElement(header) ? header : createElement(header)}
|
|
1285
1356
|
</View>
|
|
1286
1357
|
)}
|
|
1287
|
-
<View ref={contentRef} style={style}>
|
|
1358
|
+
<View ref={contentRef} style={style} onLayout={handleContentLayout}>
|
|
1288
1359
|
{children}
|
|
1289
1360
|
</View>
|
|
1290
1361
|
{footer && !absoluteFooter && (
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { StyleSheet, type ViewProps } from 'react-native';
|
|
2
|
+
|
|
3
|
+
import TrueSheetOverlayViewNativeComponent from './fabric/TrueSheetOverlayViewNativeComponent';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Renders its children in a native layer above every presented sheet — for
|
|
7
|
+
* toasts, dialogs, and other content a sheet must not cover.
|
|
8
|
+
* Fills the window; touches that miss its children pass through to the views beneath.
|
|
9
|
+
*/
|
|
10
|
+
export const TrueSheetOverlay = ({ style, ...rest }: ViewProps) => (
|
|
11
|
+
<TrueSheetOverlayViewNativeComponent {...rest} style={[styles.overlay, style]} />
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
const styles = StyleSheet.create({
|
|
15
|
+
// Sized to the window from native — absolute so it takes no layout space.
|
|
16
|
+
// Children render in a native container, so the host itself must never
|
|
17
|
+
// catch touches (Android forces it visible on layout).
|
|
18
|
+
overlay: {
|
|
19
|
+
position: 'absolute',
|
|
20
|
+
top: 0,
|
|
21
|
+
left: 0,
|
|
22
|
+
pointerEvents: 'none',
|
|
23
|
+
},
|
|
24
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
import { useLayoutEffect, useState } from 'react';
|
|
3
|
+
import { createPortal } from 'react-dom';
|
|
4
|
+
import { StyleSheet, View, type ViewProps } from 'react-native';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Renders its children in a layer above every presented sheet — for
|
|
8
|
+
* toasts, dialogs, and other content a sheet must not cover.
|
|
9
|
+
* Fills the window; touches that miss its children pass through to the views beneath.
|
|
10
|
+
*/
|
|
11
|
+
export const TrueSheetOverlay = ({ style, ...rest }: ViewProps) => {
|
|
12
|
+
// Portal target appended to the body so it stacks above the sheets' portal
|
|
13
|
+
const [container] = useState(() =>
|
|
14
|
+
typeof document !== 'undefined' ? document.createElement('div') : null
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
useLayoutEffect(() => {
|
|
18
|
+
if (!container) return;
|
|
19
|
+
|
|
20
|
+
container.style.cssText = 'position:fixed;inset:0;pointer-events:none';
|
|
21
|
+
document.body.appendChild(container);
|
|
22
|
+
return () => {
|
|
23
|
+
container.remove();
|
|
24
|
+
};
|
|
25
|
+
}, [container]);
|
|
26
|
+
|
|
27
|
+
if (!container) return null;
|
|
28
|
+
|
|
29
|
+
return createPortal(
|
|
30
|
+
<View pointerEvents="box-none" {...rest} style={[StyleSheet.absoluteFill, style]} />,
|
|
31
|
+
container
|
|
32
|
+
);
|
|
33
|
+
};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createContext, type RefObject, useContext, useEffect, useRef } from 'react';
|
|
2
2
|
import { type LayoutChangeEvent, View, type ViewProps } from 'react-native';
|
|
3
3
|
|
|
4
|
+
import { getDOMElement } from './web/dom';
|
|
5
|
+
|
|
4
6
|
/**
|
|
5
7
|
* Reports the measured peek content height to the owning TrueSheet.
|
|
6
8
|
* @internal
|
|
@@ -44,9 +46,8 @@ export const TrueSheetPeek = ({ onLayout, ...rest }: ViewProps) => {
|
|
|
44
46
|
|
|
45
47
|
const handleLayout = (event: LayoutChangeEvent) => {
|
|
46
48
|
if (context) {
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
const contentElement = context.contentRef.current as unknown as HTMLElement | null;
|
|
49
|
+
const peekElement = getDOMElement(viewRef.current);
|
|
50
|
+
const contentElement = getDOMElement(context.contentRef.current);
|
|
50
51
|
|
|
51
52
|
const bottom =
|
|
52
53
|
peekElement && contentElement
|
|
@@ -6,7 +6,7 @@ export interface NativeProps extends ViewProps {
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
// interfaceOnly: shadow node/state/descriptor are custom (common/cpp) so the
|
|
9
|
-
// content can be bounded to the container when a ScrollView is
|
|
9
|
+
// content can be bounded to the container when a ScrollView is detected
|
|
10
10
|
export default codegenNativeComponent<NativeProps>('TrueSheetContentView', {
|
|
11
11
|
interfaceOnly: true,
|
|
12
12
|
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ViewProps } from 'react-native';
|
|
2
|
+
import { codegenNativeComponent } from 'react-native';
|
|
3
|
+
|
|
4
|
+
export interface NativeProps extends ViewProps {
|
|
5
|
+
// No props needed - sized to the window from native
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// interfaceOnly: shadow node/state/descriptor are custom (common/cpp) so the
|
|
9
|
+
// overlay is sized to the window
|
|
10
|
+
export default codegenNativeComponent<NativeProps>('TrueSheetOverlayView', {
|
|
11
|
+
interfaceOnly: true,
|
|
12
|
+
});
|