@lodev09/react-native-true-sheet 4.0.0-beta.0 → 4.0.0-beta.1
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/LICENSE +1 -1
- package/README.md +42 -31
- package/RNTrueSheet.podspec +10 -2
- package/android/build.gradle +2 -1
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerView.kt +174 -49
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerViewManager.kt +7 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentView.kt +371 -18
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentViewManager.kt +14 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetFooterView.kt +150 -21
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetFooterViewManager.kt +14 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetHeaderView.kt +38 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetHeaderViewManager.kt +28 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetModule.kt +84 -24
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetPackage.kt +5 -4
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetPeekView.kt +72 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetPeekViewManager.kt +33 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetStateUpdater.kt +45 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +526 -231
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +1217 -603
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +136 -29
- package/android/src/main/java/com/lodev09/truesheet/core/RNScreensEventObserver.kt +63 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetBehavior.kt +36 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetView.kt +255 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetCoordinatorLayout.kt +303 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDetentCalculator.kt +277 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDimView.kt +162 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetGrabberView.kt +180 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetKeyboardObserver.kt +207 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetStackManager.kt +220 -0
- package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetDragEvents.kt +71 -0
- package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetFocusEvents.kt +65 -0
- package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetLifecycleEvents.kt +112 -0
- package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetStateEvents.kt +56 -0
- package/android/src/main/java/com/lodev09/truesheet/utils/KeyboardUtils.kt +84 -0
- package/android/src/main/java/com/lodev09/truesheet/utils/ScreenUtils.kt +93 -67
- package/android/src/main/java/com/lodev09/truesheet/utils/ViewUtils.kt +30 -0
- package/android/src/main/jni/CMakeLists.txt +63 -0
- package/android/src/main/jni/TrueSheetSpec.h +19 -0
- package/android/src/main/jni/TrueSheetStateUpdater.cpp +96 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewComponentDescriptor.h +24 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.cpp +43 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.h +28 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.cpp +11 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.h +37 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewComponentDescriptor.h +24 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.cpp +56 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.h +28 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewState.cpp +11 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewState.h +41 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetInsets.cpp +18 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetInsets.h +16 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewComponentDescriptor.h +28 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.cpp +61 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.h +45 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.cpp +23 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.h +52 -0
- package/ios/TrueSheetContainerView.h +81 -6
- package/ios/TrueSheetContainerView.mm +275 -21
- package/ios/TrueSheetContentView.h +38 -1
- package/ios/TrueSheetContentView.mm +413 -43
- package/ios/TrueSheetFooterView.h +27 -2
- package/ios/TrueSheetFooterView.mm +141 -35
- package/ios/{events/OnDidDismissEvent.h → TrueSheetHeaderView.h} +10 -7
- package/ios/TrueSheetHeaderView.mm +64 -0
- package/ios/TrueSheetModule.mm +92 -5
- package/ios/TrueSheetPeekView.h +32 -0
- package/ios/TrueSheetPeekView.mm +99 -0
- package/ios/TrueSheetView.h +7 -15
- package/ios/TrueSheetView.mm +633 -215
- package/ios/TrueSheetViewController.h +84 -16
- package/ios/TrueSheetViewController.mm +1136 -333
- package/ios/core/RNScreensEventObserver.h +46 -0
- package/ios/core/RNScreensEventObserver.mm +280 -0
- package/ios/core/TrueSheetBlurView.h +27 -0
- package/ios/core/TrueSheetBlurView.mm +82 -0
- package/ios/core/TrueSheetDetentCalculator.h +107 -0
- package/ios/core/TrueSheetDetentCalculator.mm +197 -0
- package/ios/core/TrueSheetGrabberView.h +72 -0
- package/ios/core/TrueSheetGrabberView.mm +178 -0
- package/ios/core/TrueSheetKeyboardObserver.h +39 -0
- package/ios/core/TrueSheetKeyboardObserver.mm +112 -0
- package/ios/events/TrueSheetDragEvents.h +39 -0
- package/ios/events/TrueSheetDragEvents.mm +62 -0
- package/ios/events/{OnPositionChangeEvent.h → TrueSheetFocusEvents.h} +8 -5
- package/ios/events/TrueSheetFocusEvents.mm +49 -0
- package/ios/events/TrueSheetLifecycleEvents.h +40 -0
- package/ios/events/TrueSheetLifecycleEvents.mm +71 -0
- package/ios/events/TrueSheetStateEvents.h +35 -0
- package/ios/events/TrueSheetStateEvents.mm +49 -0
- package/ios/tvos/TrueSheetStubs.mm +154 -0
- package/ios/{events/OnMountEvent.h → utils/BlurUtil.h} +5 -7
- package/ios/utils/BlurUtil.mm +69 -0
- package/ios/utils/GestureUtil.h +7 -0
- package/ios/utils/GestureUtil.mm +12 -0
- package/ios/utils/PlatformUtil.h +15 -0
- package/ios/utils/UIView+FirstResponder.h +15 -0
- package/ios/utils/UIView+FirstResponder.mm +26 -0
- package/ios/{events/OnWillDismissEvent.h → utils/UIView+ScrollEdgeInteraction.h} +6 -7
- package/ios/utils/UIView+ScrollEdgeInteraction.mm +62 -0
- package/ios/utils/WindowUtil.mm +17 -1
- package/lib/module/TrueSheet.js +224 -64
- package/lib/module/TrueSheet.js.map +1 -1
- package/lib/module/TrueSheet.web.js +1201 -0
- package/lib/module/TrueSheet.web.js.map +1 -0
- package/lib/module/TrueSheetPeek.js +15 -0
- package/lib/module/TrueSheetPeek.js.map +1 -0
- package/lib/module/TrueSheetPeek.web.js +55 -0
- package/lib/module/TrueSheetPeek.web.js.map +1 -0
- package/lib/module/TrueSheetProvider.js +28 -0
- package/lib/module/TrueSheetProvider.js.map +1 -0
- package/lib/module/TrueSheetProvider.web.js +157 -0
- package/lib/module/TrueSheetProvider.web.js.map +1 -0
- package/lib/module/fabric/TrueSheetContainerViewNativeComponent.ts +1 -1
- package/lib/module/fabric/TrueSheetContentViewNativeComponent.ts +5 -1
- package/lib/module/fabric/TrueSheetFooterViewNativeComponent.ts +10 -2
- package/lib/module/fabric/TrueSheetHeaderViewNativeComponent.ts +8 -0
- package/lib/module/fabric/TrueSheetPeekViewNativeComponent.ts +8 -0
- package/lib/module/fabric/TrueSheetViewNativeComponent.ts +105 -18
- package/lib/module/index.js +3 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/mocks/index.js +93 -0
- package/lib/module/mocks/index.js.map +1 -0
- package/lib/module/mocks/navigation.js +85 -0
- package/lib/module/mocks/navigation.js.map +1 -0
- package/lib/module/mocks/reanimated.js +91 -0
- package/lib/module/mocks/reanimated.js.map +1 -0
- package/lib/module/navigation/TrueSheetRouter.js +170 -0
- package/lib/module/navigation/TrueSheetRouter.js.map +1 -0
- package/lib/module/navigation/TrueSheetView.js +71 -0
- package/lib/module/navigation/TrueSheetView.js.map +1 -0
- package/lib/module/navigation/createTrueSheetNavigator.js +63 -0
- package/lib/module/navigation/createTrueSheetNavigator.js.map +1 -0
- package/lib/module/navigation/index.js +6 -0
- package/lib/module/navigation/index.js.map +1 -0
- package/lib/module/navigation/screen/ReanimatedTrueSheetScreen.js +56 -0
- package/lib/module/navigation/screen/ReanimatedTrueSheetScreen.js.map +1 -0
- package/lib/module/navigation/screen/TrueSheetScreen.js +49 -0
- package/lib/module/navigation/screen/TrueSheetScreen.js.map +1 -0
- package/lib/module/navigation/screen/index.js +5 -0
- package/lib/module/navigation/screen/index.js.map +1 -0
- package/lib/module/navigation/screen/types.js +4 -0
- package/lib/module/navigation/screen/types.js.map +1 -0
- package/lib/module/navigation/screen/useSheetScreenState.js +68 -0
- package/lib/module/navigation/screen/useSheetScreenState.js.map +1 -0
- package/lib/module/navigation/types.js +4 -0
- package/lib/module/navigation/types.js.map +1 -0
- package/lib/module/navigation/useTrueSheetNavigation.js +24 -0
- package/lib/module/navigation/useTrueSheetNavigation.js.map +1 -0
- package/lib/module/reanimated/ReanimatedTrueSheet.js +15 -9
- package/lib/module/reanimated/ReanimatedTrueSheet.js.map +1 -1
- package/lib/module/reanimated/ReanimatedTrueSheet.web.js +81 -0
- package/lib/module/reanimated/ReanimatedTrueSheet.web.js.map +1 -0
- package/lib/module/reanimated/ReanimatedTrueSheetProvider.js +4 -2
- package/lib/module/reanimated/ReanimatedTrueSheetProvider.js.map +1 -1
- package/lib/module/reanimated/index.js +2 -2
- package/lib/module/reanimated/index.js.map +1 -1
- package/lib/module/reanimated/useReanimatedPositionChangeHandler.web.js +20 -0
- package/lib/module/reanimated/useReanimatedPositionChangeHandler.web.js.map +1 -0
- package/lib/module/specs/NativeTrueSheetModule.js.map +1 -1
- package/lib/module/web/constants.js +16 -0
- package/lib/module/web/constants.js.map +1 -0
- package/lib/module/web/vaul/README.md +9 -0
- package/lib/module/web/vaul/browser.js +30 -0
- package/lib/module/web/vaul/browser.js.map +1 -0
- package/lib/module/web/vaul/constants.js +15 -0
- package/lib/module/web/vaul/constants.js.map +1 -0
- package/lib/module/web/vaul/context.js +59 -0
- package/lib/module/web/vaul/context.js.map +1 -0
- package/lib/module/web/vaul/helpers.js +93 -0
- package/lib/module/web/vaul/helpers.js.map +1 -0
- package/lib/module/web/vaul/index.js +1309 -0
- package/lib/module/web/vaul/index.js.map +1 -0
- package/lib/module/web/vaul/style.css +287 -0
- package/lib/module/web/vaul/style.css.d.js +2 -0
- package/lib/module/web/vaul/style.css.d.js.map +1 -0
- package/lib/module/web/vaul/types.js +2 -0
- package/lib/module/web/vaul/types.js.map +1 -0
- package/lib/module/web/vaul/use-composed-refs.js +34 -0
- package/lib/module/web/vaul/use-composed-refs.js.map +1 -0
- package/lib/module/web/vaul/use-controllable-state.js +54 -0
- package/lib/module/web/vaul/use-controllable-state.js.map +1 -0
- package/lib/module/web/vaul/use-position-fixed.js +123 -0
- package/lib/module/web/vaul/use-position-fixed.js.map +1 -0
- package/lib/module/web/vaul/use-prevent-scroll.js +258 -0
- package/lib/module/web/vaul/use-prevent-scroll.js.map +1 -0
- package/lib/module/web/vaul/use-scale-background.js +57 -0
- package/lib/module/web/vaul/use-scale-background.js.map +1 -0
- package/lib/module/web/vaul/use-snap-points.js +305 -0
- package/lib/module/web/vaul/use-snap-points.js.map +1 -0
- package/lib/typescript/src/TrueSheet.d.ts +51 -14
- package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheet.types.d.ts +463 -54
- package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheet.web.d.ts +5 -0
- package/lib/typescript/src/TrueSheet.web.d.ts.map +1 -0
- package/lib/typescript/src/TrueSheetPeek.d.ts +9 -0
- package/lib/typescript/src/TrueSheetPeek.d.ts.map +1 -0
- package/lib/typescript/src/TrueSheetPeek.web.d.ts +28 -0
- package/lib/typescript/src/TrueSheetPeek.web.d.ts.map +1 -0
- package/lib/typescript/src/TrueSheetProvider.d.ts +18 -0
- package/lib/typescript/src/TrueSheetProvider.d.ts.map +1 -0
- package/lib/typescript/src/TrueSheetProvider.web.d.ts +27 -0
- package/lib/typescript/src/TrueSheetProvider.web.d.ts.map +1 -0
- package/lib/typescript/src/fabric/TrueSheetContainerViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/fabric/TrueSheetContentViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/fabric/TrueSheetFooterViewNativeComponent.d.ts +2 -0
- package/lib/typescript/src/fabric/TrueSheetFooterViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/fabric/TrueSheetHeaderViewNativeComponent.d.ts +6 -0
- package/lib/typescript/src/fabric/TrueSheetHeaderViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/fabric/TrueSheetPeekViewNativeComponent.d.ts +6 -0
- package/lib/typescript/src/fabric/TrueSheetPeekViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +67 -16
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/mocks/index.d.ts +43 -0
- package/lib/typescript/src/mocks/index.d.ts.map +1 -0
- package/lib/typescript/src/mocks/navigation.d.ts +66 -0
- package/lib/typescript/src/mocks/navigation.d.ts.map +1 -0
- package/lib/typescript/src/mocks/reanimated.d.ts +48 -0
- package/lib/typescript/src/mocks/reanimated.d.ts.map +1 -0
- package/lib/typescript/src/navigation/TrueSheetRouter.d.ts +57 -0
- package/lib/typescript/src/navigation/TrueSheetRouter.d.ts.map +1 -0
- package/lib/typescript/src/navigation/TrueSheetView.d.ts +10 -0
- package/lib/typescript/src/navigation/TrueSheetView.d.ts.map +1 -0
- package/lib/typescript/src/navigation/createTrueSheetNavigator.d.ts +35 -0
- package/lib/typescript/src/navigation/createTrueSheetNavigator.d.ts.map +1 -0
- package/lib/typescript/src/navigation/index.d.ts +6 -0
- package/lib/typescript/src/navigation/index.d.ts.map +1 -0
- package/lib/typescript/src/navigation/screen/ReanimatedTrueSheetScreen.d.ts +3 -0
- package/lib/typescript/src/navigation/screen/ReanimatedTrueSheetScreen.d.ts.map +1 -0
- package/lib/typescript/src/navigation/screen/TrueSheetScreen.d.ts +3 -0
- package/lib/typescript/src/navigation/screen/TrueSheetScreen.d.ts.map +1 -0
- package/lib/typescript/src/navigation/screen/index.d.ts +4 -0
- package/lib/typescript/src/navigation/screen/index.d.ts.map +1 -0
- package/lib/typescript/src/navigation/screen/types.d.ts +15 -0
- package/lib/typescript/src/navigation/screen/types.d.ts.map +1 -0
- package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts +35 -0
- package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts.map +1 -0
- package/lib/typescript/src/navigation/types.d.ts +153 -0
- package/lib/typescript/src/navigation/types.d.ts.map +1 -0
- package/lib/typescript/src/navigation/useTrueSheetNavigation.d.ts +23 -0
- package/lib/typescript/src/navigation/useTrueSheetNavigation.d.ts.map +1 -0
- package/lib/typescript/src/reanimated/ReanimatedTrueSheet.d.ts.map +1 -1
- package/lib/typescript/src/reanimated/ReanimatedTrueSheet.web.d.ts +41 -0
- package/lib/typescript/src/reanimated/ReanimatedTrueSheet.web.d.ts.map +1 -0
- package/lib/typescript/src/reanimated/ReanimatedTrueSheetProvider.d.ts +8 -2
- package/lib/typescript/src/reanimated/ReanimatedTrueSheetProvider.d.ts.map +1 -1
- package/lib/typescript/src/reanimated/useReanimatedPositionChangeHandler.web.d.ts +15 -0
- package/lib/typescript/src/reanimated/useReanimatedPositionChangeHandler.web.d.ts.map +1 -0
- package/lib/typescript/src/specs/NativeTrueSheetModule.d.ts +25 -3
- package/lib/typescript/src/specs/NativeTrueSheetModule.d.ts.map +1 -1
- package/lib/typescript/src/web/constants.d.ts +14 -0
- package/lib/typescript/src/web/constants.d.ts.map +1 -0
- package/lib/typescript/src/web/vaul/browser.d.ts +8 -0
- package/lib/typescript/src/web/vaul/browser.d.ts.map +1 -0
- package/lib/typescript/src/web/vaul/constants.d.ts +13 -0
- package/lib/typescript/src/web/vaul/constants.d.ts.map +1 -0
- package/lib/typescript/src/web/vaul/context.d.ts +53 -0
- package/lib/typescript/src/web/vaul/context.d.ts.map +1 -0
- package/lib/typescript/src/web/vaul/helpers.d.ts +17 -0
- package/lib/typescript/src/web/vaul/helpers.d.ts.map +1 -0
- package/lib/typescript/src/web/vaul/index.d.ts +228 -0
- package/lib/typescript/src/web/vaul/index.d.ts.map +1 -0
- package/lib/typescript/src/web/vaul/types.d.ts +7 -0
- package/lib/typescript/src/web/vaul/types.d.ts.map +1 -0
- package/lib/typescript/src/web/vaul/use-composed-refs.d.ts +14 -0
- package/lib/typescript/src/web/vaul/use-composed-refs.d.ts.map +1 -0
- package/lib/typescript/src/web/vaul/use-controllable-state.d.ts +9 -0
- package/lib/typescript/src/web/vaul/use-controllable-state.d.ts.map +1 -0
- package/lib/typescript/src/web/vaul/use-position-fixed.d.ts +19 -0
- package/lib/typescript/src/web/vaul/use-position-fixed.d.ts.map +1 -0
- package/lib/typescript/src/web/vaul/use-prevent-scroll.d.ts +18 -0
- package/lib/typescript/src/web/vaul/use-prevent-scroll.d.ts.map +1 -0
- package/lib/typescript/src/web/vaul/use-scale-background.d.ts +2 -0
- package/lib/typescript/src/web/vaul/use-scale-background.d.ts.map +1 -0
- package/lib/typescript/src/web/vaul/use-snap-points.d.ts +38 -0
- package/lib/typescript/src/web/vaul/use-snap-points.d.ts.map +1 -0
- package/package.json +78 -34
- package/react-native.config.js +9 -2
- package/src/TrueSheet.tsx +276 -60
- package/src/TrueSheet.types.ts +525 -71
- package/src/TrueSheet.web.tsx +1366 -0
- package/src/TrueSheetPeek.tsx +11 -0
- package/src/TrueSheetPeek.web.tsx +63 -0
- package/src/TrueSheetProvider.tsx +33 -0
- package/src/TrueSheetProvider.web.tsx +220 -0
- package/src/fabric/TrueSheetContainerViewNativeComponent.ts +1 -1
- package/src/fabric/TrueSheetContentViewNativeComponent.ts +5 -1
- package/src/fabric/TrueSheetFooterViewNativeComponent.ts +10 -2
- package/src/fabric/TrueSheetHeaderViewNativeComponent.ts +8 -0
- package/src/fabric/TrueSheetPeekViewNativeComponent.ts +8 -0
- package/src/fabric/TrueSheetViewNativeComponent.ts +105 -18
- package/src/index.ts +2 -2
- package/src/mocks/index.ts +89 -0
- package/src/mocks/navigation.ts +72 -0
- package/src/mocks/reanimated.ts +101 -0
- package/src/navigation/TrueSheetRouter.ts +234 -0
- package/src/navigation/TrueSheetView.tsx +99 -0
- package/src/navigation/createTrueSheetNavigator.tsx +93 -0
- package/src/navigation/index.ts +14 -0
- package/src/navigation/screen/ReanimatedTrueSheetScreen.tsx +61 -0
- package/src/navigation/screen/TrueSheetScreen.tsx +54 -0
- package/src/navigation/screen/index.ts +3 -0
- package/src/navigation/screen/types.ts +21 -0
- package/src/navigation/screen/useSheetScreenState.ts +107 -0
- package/src/navigation/types.ts +219 -0
- package/src/navigation/useTrueSheetNavigation.ts +26 -0
- package/src/reanimated/ReanimatedTrueSheet.tsx +12 -7
- package/src/reanimated/ReanimatedTrueSheet.web.tsx +75 -0
- package/src/reanimated/ReanimatedTrueSheetProvider.tsx +12 -8
- package/src/reanimated/useReanimatedPositionChangeHandler.web.ts +31 -0
- package/src/specs/NativeTrueSheetModule.ts +28 -3
- package/src/web/constants.ts +13 -0
- package/src/web/vaul/README.md +9 -0
- package/src/web/vaul/browser.ts +38 -0
- package/src/web/vaul/constants.ts +20 -0
- package/src/web/vaul/context.ts +101 -0
- package/src/web/vaul/helpers.ts +124 -0
- package/src/web/vaul/index.tsx +1740 -0
- package/src/web/vaul/style.css +287 -0
- package/src/web/vaul/style.css.d.ts +1 -0
- package/src/web/vaul/types.ts +7 -0
- package/src/web/vaul/use-composed-refs.ts +35 -0
- package/src/web/vaul/use-controllable-state.ts +66 -0
- package/src/web/vaul/use-position-fixed.ts +147 -0
- package/src/web/vaul/use-prevent-scroll.ts +309 -0
- package/src/web/vaul/use-scale-background.ts +64 -0
- package/src/web/vaul/use-snap-points.ts +455 -0
- package/android/src/main/java/com/lodev09/truesheet/events/DetentChangeEvent.kt +0 -26
- package/android/src/main/java/com/lodev09/truesheet/events/DidDismissEvent.kt +0 -20
- package/android/src/main/java/com/lodev09/truesheet/events/DidPresentEvent.kt +0 -26
- package/android/src/main/java/com/lodev09/truesheet/events/DragBeginEvent.kt +0 -26
- package/android/src/main/java/com/lodev09/truesheet/events/DragChangeEvent.kt +0 -26
- package/android/src/main/java/com/lodev09/truesheet/events/DragEndEvent.kt +0 -26
- package/android/src/main/java/com/lodev09/truesheet/events/MountEvent.kt +0 -20
- package/android/src/main/java/com/lodev09/truesheet/events/PositionChangeEvent.kt +0 -32
- package/android/src/main/java/com/lodev09/truesheet/events/SizeChangeEvent.kt +0 -27
- package/android/src/main/java/com/lodev09/truesheet/events/WillDismissEvent.kt +0 -20
- package/android/src/main/java/com/lodev09/truesheet/events/WillPresentEvent.kt +0 -26
- package/android/src/main/res/values/styles.xml +0 -8
- package/ios/events/OnDetentChangeEvent.h +0 -28
- package/ios/events/OnDetentChangeEvent.mm +0 -30
- package/ios/events/OnDidDismissEvent.mm +0 -25
- package/ios/events/OnDidPresentEvent.h +0 -28
- package/ios/events/OnDidPresentEvent.mm +0 -30
- package/ios/events/OnDragBeginEvent.h +0 -28
- package/ios/events/OnDragBeginEvent.mm +0 -30
- package/ios/events/OnDragChangeEvent.h +0 -28
- package/ios/events/OnDragChangeEvent.mm +0 -30
- package/ios/events/OnDragEndEvent.h +0 -28
- package/ios/events/OnDragEndEvent.mm +0 -30
- package/ios/events/OnMountEvent.mm +0 -25
- package/ios/events/OnPositionChangeEvent.mm +0 -32
- package/ios/events/OnSizeChangeEvent.h +0 -28
- package/ios/events/OnSizeChangeEvent.mm +0 -30
- package/ios/events/OnWillDismissEvent.mm +0 -25
- package/ios/events/OnWillPresentEvent.h +0 -28
- package/ios/events/OnWillPresentEvent.mm +0 -30
- package/ios/utils/LayoutUtil.h +0 -44
- package/ios/utils/LayoutUtil.mm +0 -50
- package/lib/module/TrueSheetGrabber.js +0 -51
- package/lib/module/TrueSheetGrabber.js.map +0 -1
- package/lib/typescript/src/TrueSheetGrabber.d.ts +0 -39
- package/lib/typescript/src/TrueSheetGrabber.d.ts.map +0 -1
- package/src/TrueSheetGrabber.tsx +0 -82
- package/src/__mocks__/index.js +0 -67
|
@@ -7,200 +7,642 @@
|
|
|
7
7
|
//
|
|
8
8
|
|
|
9
9
|
#import "TrueSheetViewController.h"
|
|
10
|
+
#import "TrueSheetContainerView.h"
|
|
11
|
+
#import "TrueSheetContentView.h"
|
|
12
|
+
#import "core/TrueSheetBlurView.h"
|
|
13
|
+
#import "core/TrueSheetDetentCalculator.h"
|
|
14
|
+
#import "core/TrueSheetGrabberView.h"
|
|
15
|
+
#import "utils/BlurUtil.h"
|
|
16
|
+
#import "utils/GestureUtil.h"
|
|
17
|
+
#import "utils/PlatformUtil.h"
|
|
10
18
|
#import "utils/WindowUtil.h"
|
|
11
19
|
|
|
12
20
|
#import <React/RCTLog.h>
|
|
13
21
|
#import <React/RCTScrollViewComponentView.h>
|
|
14
|
-
#import
|
|
15
|
-
#import
|
|
22
|
+
#import <objc/runtime.h>
|
|
23
|
+
#import <react/renderer/components/TrueSheetSpec/Props.h>
|
|
24
|
+
|
|
25
|
+
using namespace facebook::react;
|
|
26
|
+
|
|
27
|
+
typedef struct {
|
|
28
|
+
CGFloat position;
|
|
29
|
+
CGFloat detent;
|
|
30
|
+
CGFloat index;
|
|
31
|
+
} TrueSheetPositionState;
|
|
32
|
+
|
|
33
|
+
static BOOL TrueSheetPositionStateEquals(TrueSheetPositionState a, TrueSheetPositionState b) {
|
|
34
|
+
return fabs(a.position - b.position) <= 0.01 && fabs(a.detent - b.detent) <= 0.01 && fabs(a.index - b.index) <= 0.01;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static char TrueSheetAccessibilityWindowOwnerKey;
|
|
38
|
+
static char TrueSheetAccessibilityWindowPreviousElementsKey;
|
|
16
39
|
|
|
17
40
|
@interface TrueSheetViewController ()
|
|
18
41
|
|
|
42
|
+
- (UIViewController *)accessibilityPresentingViewController;
|
|
43
|
+
- (void)setupTransitionTracker;
|
|
44
|
+
- (void)settleAtDetentIndex:(NSInteger)index debug:(NSString *)debug;
|
|
45
|
+
- (void)restoreWindowAccessibilityElements;
|
|
46
|
+
- (void)setSheetAccessibilityElementsHidden:(BOOL)hidden;
|
|
47
|
+
- (void)setAccessibilityContentElement:(UIView *)contentView;
|
|
48
|
+
- (void)endInteractiveDismissState;
|
|
49
|
+
- (void)animateInteractiveContainerToTransform:(CGAffineTransform)transform
|
|
50
|
+
duration:(NSTimeInterval)duration
|
|
51
|
+
allowUserInteraction:(BOOL)allowUserInteraction
|
|
52
|
+
completion:(void (^)(void))completion;
|
|
53
|
+
|
|
19
54
|
@end
|
|
20
55
|
|
|
21
56
|
@implementation TrueSheetViewController {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
CGFloat
|
|
57
|
+
TrueSheetPositionState _lastEmittedPositionState;
|
|
58
|
+
CGSize _lastReportedSize;
|
|
59
|
+
CGFloat _autoDetentHeight;
|
|
60
|
+
CGFloat _peekDetentHeight;
|
|
61
|
+
NSInteger _pendingDetentIndex;
|
|
62
|
+
|
|
63
|
+
// Present/dismiss tracking: a display link scoped to the transition
|
|
64
|
+
// coordinator — started alongside the transition, stopped in its completion.
|
|
65
|
+
CADisplayLink *_transitionLink;
|
|
26
66
|
BOOL _isTransitioning;
|
|
67
|
+
|
|
68
|
+
BOOL _pendingContentSizeChange;
|
|
69
|
+
BOOL _pendingDetentsChange;
|
|
27
70
|
BOOL _isDragging;
|
|
28
|
-
BOOL
|
|
29
|
-
|
|
30
|
-
|
|
71
|
+
BOOL _isWillDismissEmitted;
|
|
72
|
+
|
|
73
|
+
BOOL _isInteractiveDismiss;
|
|
74
|
+
CGFloat _interactiveStartPosition;
|
|
75
|
+
UIView *_interactiveContainerView;
|
|
76
|
+
NSUInteger _interactiveGeneration;
|
|
77
|
+
CADisplayLink *_interactivePositionLink;
|
|
78
|
+
|
|
79
|
+
__weak TrueSheetViewController *_parentSheetController;
|
|
80
|
+
|
|
81
|
+
UIView *_anchorView;
|
|
82
|
+
|
|
83
|
+
__weak UIWindow *_accessibilityWindow;
|
|
84
|
+
|
|
85
|
+
TrueSheetBlurView *_blurView;
|
|
86
|
+
TrueSheetGrabberView *_grabberView;
|
|
87
|
+
TrueSheetDetentCalculator *_detentCalculator;
|
|
31
88
|
}
|
|
32
89
|
|
|
90
|
+
#pragma mark - Initialization
|
|
91
|
+
|
|
33
92
|
- (instancetype)init {
|
|
34
93
|
if (self = [super initWithNibName:nil bundle:nil]) {
|
|
35
94
|
_detents = @[ @0.5, @1 ];
|
|
36
95
|
_contentHeight = @(0);
|
|
96
|
+
_headerHeight = @(0);
|
|
97
|
+
_footerHeight = @(0);
|
|
98
|
+
_peekContentHeight = @(0);
|
|
37
99
|
_grabber = YES;
|
|
100
|
+
_draggable = YES;
|
|
101
|
+
_scrollingExpandsSheet = YES;
|
|
102
|
+
_dismissible = YES;
|
|
38
103
|
_dimmed = YES;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
_isTransitioning = NO;
|
|
104
|
+
_dimmedDetentIndex = @(0);
|
|
105
|
+
_presentation = facebook::react::TrueSheetViewPresentation::Page;
|
|
106
|
+
_lastEmittedPositionState = (TrueSheetPositionState){0, 0, 0};
|
|
43
107
|
_isDragging = NO;
|
|
44
|
-
_isTrackingPositionFromLayout = NO;
|
|
45
|
-
_layoutTransitioning = NO;
|
|
46
108
|
_isPresented = NO;
|
|
109
|
+
_isWillDismissEmitted = NO;
|
|
110
|
+
_isTransitioning = NO;
|
|
111
|
+
_pendingContentSizeChange = NO;
|
|
112
|
+
_pendingDetentsChange = NO;
|
|
47
113
|
_activeDetentIndex = -1;
|
|
114
|
+
_pendingDetentIndex = -1;
|
|
48
115
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
_fakeTransitionView = [[UIView alloc] init];
|
|
54
|
-
_fakeTransitionView.hidden = YES;
|
|
55
|
-
_fakeTransitionView.userInteractionEnabled = NO;
|
|
56
|
-
|
|
57
|
-
// Get bottom safe area inset from the window's safe area
|
|
58
|
-
// The sheet's view has smaller insets, so we need the actual device insets
|
|
59
|
-
UIWindow *window = [WindowUtil keyWindow];
|
|
60
|
-
_bottomInset = window ? window.safeAreaInsets.bottom : 0;
|
|
116
|
+
_blurInteraction = YES;
|
|
117
|
+
_insetAdjustment = TrueSheetViewInsetAdjustment::Automatic;
|
|
118
|
+
_detentCalculator = [[TrueSheetDetentCalculator alloc] init];
|
|
119
|
+
_detentCalculator.delegate = self;
|
|
61
120
|
}
|
|
62
121
|
return self;
|
|
63
122
|
}
|
|
64
123
|
|
|
65
124
|
- (void)dealloc {
|
|
125
|
+
[self restoreWindowAccessibilityElements];
|
|
126
|
+
[_transitionLink invalidate];
|
|
127
|
+
_transitionLink = nil;
|
|
128
|
+
[_interactivePositionLink invalidate];
|
|
129
|
+
_interactivePositionLink = nil;
|
|
66
130
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
67
131
|
}
|
|
68
132
|
|
|
69
|
-
#pragma mark -
|
|
133
|
+
#pragma mark - Computed Properties
|
|
134
|
+
|
|
135
|
+
- (UISheetPresentationController *)sheet {
|
|
136
|
+
return self.sheetPresentationController;
|
|
137
|
+
}
|
|
70
138
|
|
|
71
139
|
- (BOOL)isTopmostPresentedController {
|
|
72
|
-
// Check if we're in the window hierarchy and visible
|
|
73
140
|
if (!self.isViewLoaded || self.view.window == nil) {
|
|
74
141
|
return NO;
|
|
75
142
|
}
|
|
76
|
-
|
|
77
|
-
// Check if another controller is presented on top of this sheet
|
|
78
143
|
return self.presentedViewController == nil;
|
|
79
144
|
}
|
|
80
145
|
|
|
81
|
-
|
|
82
|
-
|
|
146
|
+
// YES while a child controller is stacked on top (e.g. a nested sheet) —
|
|
147
|
+
// UIKit's push-back scaling mutates the frame while the sheet is backgrounded.
|
|
148
|
+
- (BOOL)isStackedBehindChild {
|
|
149
|
+
UIViewController *presented = self.presentedViewController;
|
|
150
|
+
return presented != nil && !presented.isBeingDismissed;
|
|
83
151
|
}
|
|
84
152
|
|
|
85
153
|
- (UIView *)presentedView {
|
|
86
|
-
return self.
|
|
154
|
+
return self.sheet.presentedView;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
- (CGFloat)currentPosition {
|
|
158
|
+
UIView *presentedView = self.presentedView;
|
|
159
|
+
return presentedView ? presentedView.frame.origin.y : 0.0;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// YES when any layer from the presented view up to the window has in-flight
|
|
163
|
+
// animations (detent snap, present/dismiss transition, container settle).
|
|
164
|
+
- (BOOL)isPresentedViewAnimating {
|
|
165
|
+
for (CALayer *layer = self.presentedView.layer; layer; layer = layer.superlayer) {
|
|
166
|
+
if (layer.animationKeys.count > 0) {
|
|
167
|
+
return YES;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return NO;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* The sheet's live on-screen position. At rest and during drags (direct frame
|
|
175
|
+
* sets) this is the model position; during animations it adds the
|
|
176
|
+
* presentation-vs-model delta measured in window space, so ancestor animations
|
|
177
|
+
* compose and persistent transforms (e.g. floating-sheet inset) cancel out.
|
|
178
|
+
*/
|
|
179
|
+
- (CGFloat)livePosition {
|
|
180
|
+
UIView *presentedView = self.presentedView;
|
|
181
|
+
UIWindow *window = presentedView.window;
|
|
182
|
+
UIView *containerView = self.sheet.containerView;
|
|
183
|
+
|
|
184
|
+
// Direct (non-animated) translation applied during an interactive nav dismiss.
|
|
185
|
+
CGFloat containerTy = containerView ? containerView.transform.ty : 0;
|
|
186
|
+
|
|
187
|
+
if (!presentedView || !window || !self.isPresentedViewAnimating) {
|
|
188
|
+
return self.currentPosition + containerTy;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
CALayer *modelLayer = presentedView.layer;
|
|
192
|
+
CALayer *presentationLayer = modelLayer.presentationLayer;
|
|
193
|
+
CALayer *rootModel = window.layer;
|
|
194
|
+
CALayer *rootPresentation = rootModel.presentationLayer;
|
|
195
|
+
if (!presentationLayer || !rootPresentation) {
|
|
196
|
+
return self.currentPosition + containerTy;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
CGFloat modelY = [modelLayer convertPoint:CGPointZero toLayer:rootModel].y;
|
|
200
|
+
CGFloat presentationY = [presentationLayer convertPoint:CGPointZero toLayer:rootPresentation].y;
|
|
201
|
+
return self.currentPosition + (presentationY - modelY) + containerTy;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
- (CGFloat)screenHeight {
|
|
205
|
+
UIWindow *window = self.view.window;
|
|
206
|
+
return window ? window.bounds.size.height : UIScreen.mainScreen.bounds.size.height;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
- (CGFloat)detentBottomAdjustmentForHeight:(CGFloat)height {
|
|
210
|
+
if (_insetAdjustment == TrueSheetViewInsetAdjustment::Automatic) {
|
|
211
|
+
return 0;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return [self bottomSafeAreaForHeight:height];
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
- (CGFloat)bottomSafeAreaForHeight:(CGFloat)height {
|
|
218
|
+
if (UIDevice.currentDevice.userInterfaceIdiom != UIUserInterfaceIdiomPhone) {
|
|
219
|
+
return 0;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// On iOS 26+, returns 0 for small detents (height <= 150)
|
|
223
|
+
// Floating sheets don't need adjustment
|
|
224
|
+
if (@available(iOS 26.0, *)) {
|
|
225
|
+
if (height <= 150) {
|
|
226
|
+
return 0;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
UIWindow *window = [WindowUtil keyWindow];
|
|
231
|
+
return window ? window.safeAreaInsets.bottom : 0;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Bottom inset excluded from the auto detent. A relative footer owns the
|
|
236
|
+
* sheet's bottom edge, so it absorbs the inset — UIKit lays custom detents out
|
|
237
|
+
* above the safe area, which would otherwise gap the content from the footer.
|
|
238
|
+
*/
|
|
239
|
+
- (CGFloat)autoDetentBottomInsetForHeight:(CGFloat)height {
|
|
240
|
+
if (_insetAdjustment != TrueSheetViewInsetAdjustment::Automatic) {
|
|
241
|
+
// 'none' already excludes the inset for every detent
|
|
242
|
+
return 0;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (_absoluteFooter || [self.footerHeight floatValue] <= 0) {
|
|
246
|
+
return 0;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return [self bottomSafeAreaForHeight:height];
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// The inset the footer absorbs as padding. Uses the auto detent height when
|
|
253
|
+
// available so it matches the auto detent's inset exclusion above.
|
|
254
|
+
- (CGFloat)footerBottomInset {
|
|
255
|
+
if (_insetAdjustment != TrueSheetViewInsetAdjustment::Automatic) {
|
|
256
|
+
return 0;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return [self bottomSafeAreaForHeight:_autoDetentHeight > 0 ? _autoDetentHeight : self.screenHeight];
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Bottom inset excluded from the peek detent. An absolute footer counts
|
|
264
|
+
* toward the peek height and absorbs the inset as padding, but UIKit already
|
|
265
|
+
* lays custom detents out above the safe area — subtract it so the inset
|
|
266
|
+
* isn't doubled.
|
|
267
|
+
*/
|
|
268
|
+
- (CGFloat)peekDetentBottomInsetForHeight:(CGFloat)height {
|
|
269
|
+
if (_insetAdjustment != TrueSheetViewInsetAdjustment::Automatic) {
|
|
270
|
+
return 0;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (!_absoluteFooter || [self.footerHeight floatValue] <= 0) {
|
|
274
|
+
return 0;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return [self bottomSafeAreaForHeight:height];
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
- (BOOL)isDesignCompatibilityMode {
|
|
281
|
+
if (@available(iOS 26.0, *)) {
|
|
282
|
+
NSNumber *value = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIDesignRequiresCompatibility"];
|
|
283
|
+
return value.boolValue;
|
|
284
|
+
}
|
|
285
|
+
return NO;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
- (NSInteger)currentDetentIndex {
|
|
289
|
+
UISheetPresentationController *sheet = self.sheet;
|
|
290
|
+
if (!sheet)
|
|
291
|
+
return -1;
|
|
292
|
+
|
|
293
|
+
UISheetPresentationControllerDetentIdentifier selectedIdentifier = sheet.selectedDetentIdentifier;
|
|
294
|
+
if (!selectedIdentifier)
|
|
295
|
+
return -1;
|
|
296
|
+
|
|
297
|
+
NSArray<UISheetPresentationControllerDetent *> *detents = sheet.detents;
|
|
298
|
+
for (NSInteger i = 0; i < detents.count; i++) {
|
|
299
|
+
if (@available(iOS 16.0, *)) {
|
|
300
|
+
if ([detents[i].identifier isEqualToString:selectedIdentifier]) {
|
|
301
|
+
return i;
|
|
302
|
+
}
|
|
303
|
+
} else {
|
|
304
|
+
if ([selectedIdentifier isEqualToString:UISheetPresentationControllerDetentIdentifierMedium]) {
|
|
305
|
+
return 0;
|
|
306
|
+
} else if ([selectedIdentifier isEqualToString:UISheetPresentationControllerDetentIdentifierLarge]) {
|
|
307
|
+
return detents.count - 1;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return -1;
|
|
87
313
|
}
|
|
88
314
|
|
|
315
|
+
#pragma mark - View Lifecycle
|
|
316
|
+
|
|
89
317
|
- (void)viewDidLoad {
|
|
90
318
|
[super viewDidLoad];
|
|
91
|
-
|
|
92
319
|
self.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
|
|
93
|
-
|
|
94
|
-
[
|
|
320
|
+
|
|
321
|
+
_blurView = [[TrueSheetBlurView alloc] init];
|
|
322
|
+
[_blurView addToView:self.view];
|
|
323
|
+
|
|
324
|
+
_grabberView = [[TrueSheetGrabberView alloc] init];
|
|
325
|
+
_grabberView.hidden = YES;
|
|
326
|
+
[_grabberView addToView:self.view];
|
|
95
327
|
}
|
|
96
328
|
|
|
97
329
|
- (void)viewWillAppear:(BOOL)animated {
|
|
98
330
|
[super viewWillAppear:animated];
|
|
99
331
|
|
|
100
|
-
|
|
332
|
+
_blurView.alpha = 1;
|
|
333
|
+
|
|
101
334
|
if (!_isPresented) {
|
|
102
|
-
|
|
103
|
-
|
|
335
|
+
UIViewController *presenter = self.presentingViewController;
|
|
336
|
+
if ([presenter isKindOfClass:[TrueSheetViewController class]]) {
|
|
337
|
+
_parentSheetController = (TrueSheetViewController *)presenter;
|
|
338
|
+
[_parentSheetController.delegate viewControllerWillBlur];
|
|
339
|
+
[_parentSheetController setAccessibilityContentElement:self.accessibilityContentView ?: self.view];
|
|
104
340
|
}
|
|
105
341
|
|
|
106
|
-
|
|
107
|
-
|
|
342
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
343
|
+
NSInteger index = self.currentDetentIndex;
|
|
344
|
+
CGFloat position = self.currentPosition;
|
|
345
|
+
CGFloat detent = [self detentValueForIndex:index];
|
|
346
|
+
|
|
347
|
+
[self.delegate viewControllerWillPresentAtIndex:index position:position detent:detent];
|
|
348
|
+
[self.delegate viewControllerWillFocus];
|
|
349
|
+
});
|
|
108
350
|
}
|
|
351
|
+
|
|
352
|
+
[self setupTransitionTracker];
|
|
109
353
|
}
|
|
110
354
|
|
|
111
355
|
- (void)viewDidAppear:(BOOL)animated {
|
|
112
356
|
[super viewDidAppear:animated];
|
|
357
|
+
[self setSheetAccessibilityElementsHidden:NO];
|
|
113
358
|
|
|
114
|
-
// Only trigger didPresent on the initial presentation, not on repositioning
|
|
115
359
|
if (!_isPresented) {
|
|
116
|
-
|
|
117
|
-
[self.delegate viewControllerDidPresent];
|
|
118
|
-
}
|
|
360
|
+
[_parentSheetController.delegate viewControllerDidBlur];
|
|
119
361
|
|
|
120
|
-
|
|
121
|
-
|
|
362
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
363
|
+
NSInteger index = [self currentDetentIndex];
|
|
364
|
+
CGFloat detent = [self detentValueForIndex:index];
|
|
365
|
+
[self.delegate viewControllerDidPresentAtIndex:index position:self.currentPosition detent:detent];
|
|
366
|
+
[self.delegate viewControllerDidFocus];
|
|
122
367
|
|
|
368
|
+
[self->_grabberView updateAccessibilityValueWithIndex:index detentCount:self->_detents.count];
|
|
369
|
+
[self settleAtDetentIndex:index debug:@"did present"];
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
[self setupGestureRecognizer];
|
|
123
373
|
_isPresented = YES;
|
|
124
374
|
}
|
|
375
|
+
|
|
376
|
+
[self setupAccessibilityContainer];
|
|
125
377
|
}
|
|
126
378
|
|
|
127
|
-
- (void)
|
|
128
|
-
|
|
379
|
+
- (void)setupAccessibilityContainer {
|
|
380
|
+
UIView *contentView = self.accessibilityContentView;
|
|
381
|
+
if (!contentView) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
[self setAccessibilityContentElement:contentView];
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
- (UIViewController *)accessibilityPresentingViewController {
|
|
389
|
+
UIViewController *presentingViewController = self.presentingViewController;
|
|
390
|
+
while ([presentingViewController isKindOfClass:[TrueSheetViewController class]] &&
|
|
391
|
+
presentingViewController.presentingViewController) {
|
|
392
|
+
presentingViewController = presentingViewController.presentingViewController;
|
|
393
|
+
}
|
|
394
|
+
return presentingViewController;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
- (void)restoreWindowAccessibilityElements {
|
|
398
|
+
UIWindow *window = _accessibilityWindow;
|
|
399
|
+
if (window) {
|
|
400
|
+
// The active sheet may temporarily own window accessibility traversal. Only
|
|
401
|
+
// restore the previous value when this controller still owns that override.
|
|
402
|
+
NSValue *ownerValue = objc_getAssociatedObject(window, &TrueSheetAccessibilityWindowOwnerKey);
|
|
403
|
+
if (ownerValue && ownerValue.nonretainedObjectValue == self) {
|
|
404
|
+
id previousElements = objc_getAssociatedObject(window, &TrueSheetAccessibilityWindowPreviousElementsKey);
|
|
405
|
+
window.accessibilityElements = previousElements == [NSNull null] ? nil : previousElements;
|
|
406
|
+
objc_setAssociatedObject(window, &TrueSheetAccessibilityWindowOwnerKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
407
|
+
objc_setAssociatedObject(
|
|
408
|
+
window, &TrueSheetAccessibilityWindowPreviousElementsKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
_accessibilityWindow = nil;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
- (void)setSheetAccessibilityElementsHidden:(BOOL)hidden {
|
|
416
|
+
self.view.accessibilityElementsHidden = hidden;
|
|
417
|
+
|
|
418
|
+
UIView *presentedView = self.presentationController.presentedView;
|
|
419
|
+
if (presentedView) {
|
|
420
|
+
presentedView.accessibilityElementsHidden = hidden;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
- (void)setAccessibilityContentElement:(UIView *)contentView {
|
|
425
|
+
BOOL hasAccessibilityFooterElements = [contentView isKindOfClass:[TrueSheetContainerView class]] &&
|
|
426
|
+
[(TrueSheetContainerView *)contentView hasAccessibilityFooterElements];
|
|
427
|
+
// Footer controls exposed as separate accessibility elements can be skipped
|
|
428
|
+
// by XCTest when the presented sheet is a hard modal accessibility boundary.
|
|
429
|
+
BOOL isAccessibilityModal = _dimmed && !hasAccessibilityFooterElements;
|
|
430
|
+
|
|
431
|
+
// At a hard modal boundary XCTest skips nested elements, so flatten the
|
|
432
|
+
// container's children into the array. Otherwise expose the container itself:
|
|
433
|
+
// its accessibilityElements getter recomputes live, so a footer/content subtree
|
|
434
|
+
// that remounts (e.g. swapping a footer button on a state change) stays
|
|
435
|
+
// discoverable instead of leaving this snapshot pointing at destroyed views.
|
|
436
|
+
NSArray *accessibilityElements;
|
|
437
|
+
if (isAccessibilityModal) {
|
|
438
|
+
NSArray *contentElements = contentView.accessibilityElements;
|
|
439
|
+
accessibilityElements = contentElements.count > 0 ? contentElements : @[ contentView ];
|
|
440
|
+
} else {
|
|
441
|
+
accessibilityElements = @[ contentView ];
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
self.view.isAccessibilityElement = NO;
|
|
445
|
+
self.view.accessibilityViewIsModal = YES;
|
|
446
|
+
self.view.accessibilityElements = accessibilityElements;
|
|
447
|
+
|
|
448
|
+
UIView *presentedView = self.presentationController.presentedView;
|
|
449
|
+
if (presentedView) {
|
|
450
|
+
presentedView.isAccessibilityElement = NO;
|
|
451
|
+
presentedView.accessibilityViewIsModal = isAccessibilityModal;
|
|
452
|
+
presentedView.accessibilityElements = accessibilityElements;
|
|
453
|
+
|
|
454
|
+
UIWindow *window = self.view.window;
|
|
455
|
+
UIViewController *presentingViewController = [self accessibilityPresentingViewController];
|
|
456
|
+
if (window && presentingViewController.view) {
|
|
457
|
+
if (!_accessibilityWindow) {
|
|
458
|
+
_accessibilityWindow = window;
|
|
459
|
+
id previousElements = objc_getAssociatedObject(window, &TrueSheetAccessibilityWindowPreviousElementsKey);
|
|
460
|
+
if (!previousElements) {
|
|
461
|
+
previousElements = window.accessibilityElements ?: [NSNull null];
|
|
462
|
+
objc_setAssociatedObject(window, &TrueSheetAccessibilityWindowPreviousElementsKey, previousElements,
|
|
463
|
+
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
objc_setAssociatedObject(window, &TrueSheetAccessibilityWindowOwnerKey, [NSValue valueWithNonretainedObject:self],
|
|
467
|
+
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
468
|
+
window.accessibilityElements =
|
|
469
|
+
isAccessibilityModal ? @[ presentedView ] : @[ presentingViewController.view, presentedView ];
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
[self restoreWindowAccessibilityElements];
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
#pragma mark - Presentation
|
|
478
|
+
|
|
479
|
+
- (void)presentViewController:(UIViewController *)viewControllerToPresent
|
|
480
|
+
animated:(BOOL)flag
|
|
481
|
+
completion:(void (^)(void))completion {
|
|
482
|
+
// A non-sheet controller (e.g. an action sheet, alert, or image picker) is about
|
|
483
|
+
// to present over us. Clear our window accessibility override so UIKit's default
|
|
484
|
+
// traversal surfaces it for XCTest and assistive technologies; we reclaim the
|
|
485
|
+
// override when it dismisses. Nested sheets claim their own override.
|
|
486
|
+
if (![viewControllerToPresent isKindOfClass:[TrueSheetViewController class]]) {
|
|
487
|
+
self.view.window.accessibilityElements = nil;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
[super presentViewController:viewControllerToPresent animated:flag completion:completion];
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
- (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion {
|
|
494
|
+
[super dismissViewControllerAnimated:flag
|
|
495
|
+
completion:^{
|
|
496
|
+
if (completion) {
|
|
497
|
+
completion();
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Reclaim the window accessibility override once nothing
|
|
501
|
+
// else is presented over us.
|
|
502
|
+
if (self.isPresented && self.presentedViewController == nil) {
|
|
503
|
+
[self setupAccessibilityContainer];
|
|
504
|
+
}
|
|
505
|
+
}];
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
- (void)emitWillDismissEvents {
|
|
509
|
+
if (self.isBeingDismissed && !_isWillDismissEmitted) {
|
|
510
|
+
_isWillDismissEmitted = YES;
|
|
129
511
|
|
|
130
|
-
|
|
512
|
+
[self.delegate viewControllerWillBlur];
|
|
131
513
|
[self.delegate viewControllerWillDismiss];
|
|
514
|
+
[_parentSheetController.delegate viewControllerWillFocus];
|
|
132
515
|
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
- (void)emitDidDismissEvents {
|
|
519
|
+
if (self.isBeingDismissed) {
|
|
520
|
+
[self restoreWindowAccessibilityElements];
|
|
521
|
+
_isPresented = NO;
|
|
522
|
+
_isWillDismissEmitted = NO;
|
|
523
|
+
|
|
524
|
+
[_anchorView removeFromSuperview];
|
|
525
|
+
_anchorView = nil;
|
|
133
526
|
|
|
134
|
-
|
|
135
|
-
|
|
527
|
+
[_parentSheetController.delegate viewControllerDidFocus];
|
|
528
|
+
[_parentSheetController setSheetAccessibilityElementsHidden:NO];
|
|
529
|
+
[_parentSheetController setupAccessibilityContainer];
|
|
530
|
+
_parentSheetController = nil;
|
|
531
|
+
|
|
532
|
+
[self.delegate viewControllerDidBlur];
|
|
533
|
+
[self.delegate viewControllerDidDismiss];
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
- (void)viewWillDisappear:(BOOL)animated {
|
|
538
|
+
[super viewWillDisappear:animated];
|
|
539
|
+
[self restoreWindowAccessibilityElements];
|
|
540
|
+
[self setSheetAccessibilityElementsHidden:YES];
|
|
541
|
+
|
|
542
|
+
// Dispatch to allow pan gesture to set _isDragging before checking;
|
|
543
|
+
// the transition tracker emits when the sheet is transitioning to dismiss
|
|
544
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
545
|
+
if (!self->_isDragging) {
|
|
546
|
+
[self emitWillDismissEvents];
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
[self setupTransitionTracker];
|
|
136
551
|
}
|
|
137
552
|
|
|
138
553
|
- (void)viewDidDisappear:(BOOL)animated {
|
|
139
554
|
[super viewDidDisappear:animated];
|
|
140
|
-
|
|
141
|
-
|
|
555
|
+
|
|
556
|
+
// Backstop: if the sheet is torn down mid-gesture (e.g. owning view deallocated),
|
|
557
|
+
// the observer can't reach us through its weak delegate, so end here to invalidate
|
|
558
|
+
// _interactivePositionLink — which otherwise retains this controller indefinitely.
|
|
559
|
+
if (_isInteractiveDismiss) {
|
|
560
|
+
[self endInteractiveDismissState];
|
|
142
561
|
}
|
|
143
562
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
563
|
+
// Dismissing with the keyboard up skips the hide notification — don't let
|
|
564
|
+
// the stale flag block learning on the next present.
|
|
565
|
+
_keyboardSheetGrown = NO;
|
|
566
|
+
|
|
567
|
+
[self emitDidDismissEvents];
|
|
147
568
|
}
|
|
148
569
|
|
|
149
|
-
- (void)
|
|
150
|
-
|
|
151
|
-
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
|
|
570
|
+
- (void)viewWillLayoutSubviews {
|
|
571
|
+
[super viewWillLayoutSubviews];
|
|
152
572
|
|
|
153
|
-
//
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
|
|
159
|
-
// After rotation completes
|
|
160
|
-
[self setupSheetDetents];
|
|
573
|
+
// Skip during transitions and nav swipes — their trackers own position then.
|
|
574
|
+
if (_isTransitioning || _isInteractiveDismiss) {
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
161
577
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
578
|
+
if (_pendingContentSizeChange || _pendingDetentsChange) {
|
|
579
|
+
_pendingContentSizeChange = NO;
|
|
580
|
+
_pendingDetentsChange = NO;
|
|
581
|
+
[self settleAtDetentIndex:self.currentDetentIndex debug:@"layout"];
|
|
582
|
+
} else if (!_isDragging) {
|
|
583
|
+
// Drags emit from the pan handler. A child on top moves this sheet two
|
|
584
|
+
// ways: an animated collapse/restore step (presenting/dismissing) — emit
|
|
585
|
+
// the target non-realtime so JS animates to it — or direct per-frame
|
|
586
|
+
// re-layouts while the child drags, which stay realtime.
|
|
587
|
+
BOOL realtime = self.presentedViewController == nil || !self.isPresentedViewAnimating;
|
|
588
|
+
|
|
589
|
+
// A layout pass at rest can nudge the frame by a sub-pixel amount
|
|
590
|
+
// (pixel-grid snapping after present), leaving the interpolated index
|
|
591
|
+
// slightly off the whole number. Re-learn when the frame is effectively
|
|
592
|
+
// at the current detent so the drift is absorbed instead of emitted.
|
|
593
|
+
// The tight threshold keeps real movement (e.g. scroll-driven expansion)
|
|
594
|
+
// from being absorbed.
|
|
595
|
+
if (self.presentedViewController == nil && !self.isPresentedViewAnimating) {
|
|
596
|
+
NSInteger index = self.currentDetentIndex;
|
|
597
|
+
CGFloat expectedHeight = [_detentCalculator resolvedHeightForIndex:index];
|
|
598
|
+
CGFloat actualHeight = self.screenHeight - self.currentPosition;
|
|
599
|
+
if (expectedHeight > 0 && fabs(actualHeight - expectedHeight) < 2) {
|
|
600
|
+
[self learnOffsetForDetentIndex:index];
|
|
165
601
|
}
|
|
166
|
-
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
[self emitChangePositionDelegateWithPosition:self.currentPosition realtime:realtime debug:@"layout"];
|
|
605
|
+
}
|
|
167
606
|
}
|
|
168
607
|
|
|
169
608
|
- (void)viewDidLayoutSubviews {
|
|
170
609
|
[super viewDidLayoutSubviews];
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
//
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
610
|
+
|
|
611
|
+
// Skip size reports while backgrounded behind a stacked child — the push-back
|
|
612
|
+
// scaling changes the frame and would needlessly resize content.
|
|
613
|
+
// _lastReportedSize stays stale so the restore pass re-reports any real change.
|
|
614
|
+
if (!self.isStackedBehindChild) {
|
|
615
|
+
// Report any size change (detent resize, keyboard, rotation) so Yoga
|
|
616
|
+
// relayouts the container synchronously with the sheet.
|
|
617
|
+
CGSize size = self.view.frame.size;
|
|
618
|
+
if (!CGSizeEqualToSize(_lastReportedSize, size)) {
|
|
619
|
+
_lastReportedSize = size;
|
|
620
|
+
[self.delegate viewControllerDidChangeSize:size];
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
if (_pendingDetentIndex >= 0) {
|
|
625
|
+
NSInteger pendingIndex = _pendingDetentIndex;
|
|
626
|
+
_pendingDetentIndex = -1;
|
|
627
|
+
|
|
628
|
+
// The presentedView frame isn't final until UIKit finishes the resize
|
|
629
|
+
// animation — no completion hook exists for it, hence the delay.
|
|
630
|
+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
631
|
+
CGFloat detent = [self detentValueForIndex:pendingIndex];
|
|
632
|
+
[self.delegate viewControllerDidChangeDetent:pendingIndex position:self.currentPosition detent:detent];
|
|
633
|
+
[self->_grabberView updateAccessibilityValueWithIndex:pendingIndex detentCount:self->_detents.count];
|
|
634
|
+
[self settleAtDetentIndex:pendingIndex debug:@"pending detent change"];
|
|
185
635
|
});
|
|
186
636
|
}
|
|
187
637
|
}
|
|
188
638
|
|
|
189
|
-
#pragma mark - Gesture Handling
|
|
639
|
+
#pragma mark - Position & Gesture Handling
|
|
190
640
|
|
|
191
|
-
/**
|
|
192
|
-
* Finds the TrueSheetContentView in the hierarchy.
|
|
193
|
-
*
|
|
194
|
-
* @param view The presentedView to start searching from
|
|
195
|
-
* @return The TrueSheetContentView found, or nil
|
|
196
|
-
*/
|
|
197
641
|
- (TrueSheetContentView *)findContentView:(UIView *)view {
|
|
198
|
-
// Check if this view itself is TrueSheetContentView
|
|
199
642
|
if ([view isKindOfClass:[TrueSheetContentView class]]) {
|
|
200
643
|
return (TrueSheetContentView *)view;
|
|
201
644
|
}
|
|
202
645
|
|
|
203
|
-
// Recursively search all subviews
|
|
204
646
|
for (UIView *subview in view.subviews) {
|
|
205
647
|
TrueSheetContentView *found = [self findContentView:subview];
|
|
206
648
|
if (found) {
|
|
@@ -216,44 +658,60 @@
|
|
|
216
658
|
if (!presentedView)
|
|
217
659
|
return;
|
|
218
660
|
|
|
219
|
-
|
|
661
|
+
if (!self.draggable) {
|
|
662
|
+
[GestureUtil setPanGesturesEnabled:NO forView:presentedView];
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
|
|
220
666
|
[GestureUtil attachPanGestureHandler:presentedView target:self selector:@selector(handlePanGesture:)];
|
|
221
667
|
|
|
222
|
-
// Find and attach to the first ScrollView's pan gesture in the view hierarchy
|
|
223
|
-
// This handles cases where the sheet content includes a ScrollView
|
|
224
668
|
TrueSheetContentView *contentView = [self findContentView:presentedView];
|
|
225
669
|
if (contentView) {
|
|
226
670
|
RCTScrollViewComponentView *scrollViewComponent = [contentView findScrollView];
|
|
227
|
-
if (scrollViewComponent) {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
[GestureUtil attachPanGestureHandler:scrollView target:self selector:@selector(handlePanGesture:)];
|
|
232
|
-
}
|
|
671
|
+
if (scrollViewComponent && scrollViewComponent.scrollView) {
|
|
672
|
+
[GestureUtil attachPanGestureHandler:scrollViewComponent.scrollView
|
|
673
|
+
target:self
|
|
674
|
+
selector:@selector(handlePanGesture:)];
|
|
233
675
|
}
|
|
234
676
|
}
|
|
235
677
|
}
|
|
236
678
|
|
|
679
|
+
- (void)setupDraggable {
|
|
680
|
+
UIView *presentedView = self.presentedView;
|
|
681
|
+
if (!presentedView)
|
|
682
|
+
return;
|
|
683
|
+
|
|
684
|
+
[GestureUtil setPanGesturesEnabled:self.draggable forView:presentedView];
|
|
685
|
+
}
|
|
686
|
+
|
|
237
687
|
- (void)handlePanGesture:(UIPanGestureRecognizer *)gesture {
|
|
238
|
-
NSInteger index =
|
|
688
|
+
NSInteger index = self.currentDetentIndex;
|
|
689
|
+
CGFloat detent = [self detentValueForIndex:index];
|
|
239
690
|
|
|
240
|
-
|
|
241
|
-
[self.delegate viewControllerDidDrag:gesture.state index:index position:self.currentPosition];
|
|
242
|
-
}
|
|
691
|
+
[self.delegate viewControllerDidDrag:gesture.state index:index position:self.currentPosition detent:detent];
|
|
243
692
|
|
|
244
693
|
switch (gesture.state) {
|
|
245
694
|
case UIGestureRecognizerStateBegan:
|
|
246
695
|
_isDragging = YES;
|
|
247
696
|
break;
|
|
248
697
|
case UIGestureRecognizerStateChanged:
|
|
249
|
-
|
|
250
|
-
[self emitChangePositionDelegateWithPosition:self.currentPosition transitioning:NO];
|
|
698
|
+
[self emitChangePositionDelegateWithPosition:self.currentPosition realtime:YES debug:@"drag change"];
|
|
251
699
|
break;
|
|
252
700
|
case UIGestureRecognizerStateEnded:
|
|
253
|
-
case UIGestureRecognizerStateCancelled:
|
|
701
|
+
case UIGestureRecognizerStateCancelled: {
|
|
702
|
+
if (!_isTransitioning) {
|
|
703
|
+
// Dispatch so UIKit picks the target detent first; the settle emit is
|
|
704
|
+
// non-realtime and JS animates alongside UIKit's snap spring.
|
|
705
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
706
|
+
NSInteger endIndex = self.currentDetentIndex;
|
|
707
|
+
[self->_grabberView updateAccessibilityValueWithIndex:endIndex detentCount:self->_detents.count];
|
|
708
|
+
[self settleAtDetentIndex:endIndex debug:@"drag end"];
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
|
|
254
712
|
_isDragging = NO;
|
|
255
713
|
break;
|
|
256
|
-
|
|
714
|
+
}
|
|
257
715
|
default:
|
|
258
716
|
break;
|
|
259
717
|
}
|
|
@@ -262,165 +720,337 @@
|
|
|
262
720
|
#pragma mark - Position Tracking
|
|
263
721
|
|
|
264
722
|
/**
|
|
265
|
-
*
|
|
266
|
-
*
|
|
723
|
+
* Tracks the sheet's live position through a present/dismiss transition. The
|
|
724
|
+
* link's lifetime is scoped to the transition coordinator: started alongside
|
|
725
|
+
* the transition, stopped in its completion — which fires for finished and
|
|
726
|
+
* cancelled (interactive) transitions alike.
|
|
267
727
|
*/
|
|
268
|
-
- (void)
|
|
269
|
-
|
|
270
|
-
_lastPosition = position;
|
|
728
|
+
- (void)setupTransitionTracker {
|
|
729
|
+
id<UIViewControllerTransitionCoordinator> coordinator = self.transitionCoordinator;
|
|
271
730
|
|
|
272
|
-
|
|
273
|
-
|
|
731
|
+
// A cancelled dismiss re-enters viewWillAppear with the same coordinator —
|
|
732
|
+
// the tracker from viewWillDisappear still owns it.
|
|
733
|
+
if (!coordinator || _isTransitioning) {
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
_isTransitioning = YES;
|
|
274
738
|
|
|
275
|
-
|
|
276
|
-
|
|
739
|
+
// Learn the resolver-vs-actual offset before emitting transition positions so
|
|
740
|
+
// the interpolated index lands exactly on the target detent. The presented
|
|
741
|
+
// frame is already final here (UIKit animates the layer, not the frame).
|
|
742
|
+
if (!self.isBeingDismissed) {
|
|
743
|
+
[self learnOffsetForDetentIndex:self.currentDetentIndex];
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
__weak __typeof(self) weakSelf = self;
|
|
747
|
+
[coordinator
|
|
748
|
+
animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
|
|
749
|
+
__strong __typeof(weakSelf) strongSelf = weakSelf;
|
|
750
|
+
if (!strongSelf)
|
|
751
|
+
return;
|
|
752
|
+
|
|
753
|
+
[strongSelf->_transitionLink invalidate];
|
|
754
|
+
strongSelf->_transitionLink = [CADisplayLink displayLinkWithTarget:strongSelf
|
|
755
|
+
selector:@selector(handleTransitionTick)];
|
|
756
|
+
// Track at the display's native refresh rate — the default caps at 60Hz
|
|
757
|
+
// on ProMotion, dropping every other frame of a 120Hz animation.
|
|
758
|
+
strongSelf->_transitionLink.preferredFrameRateRange = CAFrameRateRangeMake(60, 120, 120);
|
|
759
|
+
[strongSelf->_transitionLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
|
|
277
760
|
}
|
|
761
|
+
completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
|
|
762
|
+
__strong __typeof(weakSelf) strongSelf = weakSelf;
|
|
763
|
+
if (!strongSelf)
|
|
764
|
+
return;
|
|
765
|
+
|
|
766
|
+
[strongSelf->_transitionLink invalidate];
|
|
767
|
+
strongSelf->_transitionLink = nil;
|
|
768
|
+
strongSelf->_isTransitioning = NO;
|
|
769
|
+
|
|
770
|
+
// Settle after a present, cancelled dismiss, or detent-snap transition.
|
|
771
|
+
// Delayed because the presentedView frame isn't final until UIKit
|
|
772
|
+
// completes its layout pass after the transition animation — learning
|
|
773
|
+
// here absorbs any sub-pixel drift since the earlier learns.
|
|
774
|
+
if (strongSelf->_isPresented && !strongSelf.isBeingDismissed) {
|
|
775
|
+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
776
|
+
[strongSelf settleAtDetentIndex:strongSelf.currentDetentIndex debug:@"transition end"];
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
}];
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
- (void)handleTransitionTick {
|
|
783
|
+
// Interactive dismiss phase (finger down) — the pan handler emits.
|
|
784
|
+
if (!_isDragging) {
|
|
785
|
+
CGFloat position = self.livePosition;
|
|
786
|
+
|
|
787
|
+
if (self.isBeingDismissed) {
|
|
788
|
+
// Emit only once the dismiss is committed: on release UIKit moves the
|
|
789
|
+
// model frame off-screen, while a cancelled drag rewinds it back to the
|
|
790
|
+
// detent (isBeingDismissed stays YES during the rewind).
|
|
791
|
+
if (self.currentPosition >= self.screenHeight) {
|
|
792
|
+
[self emitWillDismissEvents];
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// Hide blur at the end of dismiss to prevent UIVisualEffectView
|
|
796
|
+
// from causing a flicker/flash at the bottom edge of the sheet.
|
|
797
|
+
if (self.screenHeight - position < 1) {
|
|
798
|
+
_blurView.alpha = 0;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
[self emitChangePositionDelegateWithPosition:position realtime:YES debug:@"transition"];
|
|
278
803
|
}
|
|
279
804
|
}
|
|
280
805
|
|
|
281
806
|
/**
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
* alongside the actual presented view. By observing the presentation layer of this
|
|
286
|
-
* fake view's frame, we can track smooth position changes during the native transition
|
|
287
|
-
* animation without manually animating in JavaScript.
|
|
288
|
-
*
|
|
289
|
-
* The display link fires at screen refresh rate, allowing us to emit position updates
|
|
290
|
-
* that match the native animation curve, providing smooth synchronized updates to JS.
|
|
807
|
+
* The sheet is at rest at a detent: learn the resolver-vs-actual offset and
|
|
808
|
+
* emit the settled position. Only valid when the presentedView frame is final
|
|
809
|
+
* — never mid-drag or mid-animation, where learning would store a bogus offset.
|
|
291
810
|
*/
|
|
292
|
-
- (void)
|
|
293
|
-
|
|
294
|
-
|
|
811
|
+
- (void)settleAtDetentIndex:(NSInteger)index debug:(NSString *)debug {
|
|
812
|
+
// Don't learn while a child sits on top — the deck transform skews the
|
|
813
|
+
// measured frame; the next unobstructed settle learns.
|
|
814
|
+
if (self.presentedViewController == nil) {
|
|
815
|
+
[self learnOffsetForDetentIndex:index];
|
|
816
|
+
}
|
|
295
817
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
UIView *presentedView = self.presentedView;
|
|
818
|
+
[self emitChangePositionDelegateWithPosition:self.currentPosition realtime:NO debug:debug];
|
|
819
|
+
}
|
|
299
820
|
|
|
300
|
-
|
|
301
|
-
return;
|
|
821
|
+
#pragma mark - Interactive Navigation Dismiss
|
|
302
822
|
|
|
303
|
-
|
|
823
|
+
- (void)beginInteractiveDismiss {
|
|
824
|
+
if (_isInteractiveDismiss) {
|
|
825
|
+
// A settle animation from a prior gesture is still running. Stop it and reset so
|
|
826
|
+
// this gesture tracks from a clean state; its stale completion is ignored via the
|
|
827
|
+
// generation check in animateInteractiveContainerToTransform.
|
|
828
|
+
[_interactiveContainerView.layer removeAllAnimations];
|
|
829
|
+
[self endInteractiveDismissState];
|
|
830
|
+
}
|
|
304
831
|
|
|
305
|
-
|
|
306
|
-
|
|
832
|
+
_interactiveGeneration += 1;
|
|
833
|
+
_isInteractiveDismiss = YES;
|
|
834
|
+
_interactiveStartPosition = self.currentPosition;
|
|
835
|
+
_interactiveContainerView = self.sheet.containerView;
|
|
307
836
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
frame.origin.y = presentedView.frame.origin.y;
|
|
315
|
-
}
|
|
837
|
+
// Emit position from the container's presentation layer for the whole gesture, so
|
|
838
|
+
// both the direct-set drag and the settle animation report a live, smooth position.
|
|
839
|
+
_interactivePositionLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(emitInteractivePosition)];
|
|
840
|
+
_interactivePositionLink.preferredFrameRateRange = CAFrameRateRangeMake(60, 120, 120);
|
|
841
|
+
[_interactivePositionLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
|
|
842
|
+
}
|
|
316
843
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
[[context containerView] addSubview:self->_fakeTransitionView];
|
|
323
|
-
|
|
324
|
-
// Animate fake view to the presented view's target position
|
|
325
|
-
// UIKit will animate this with the same timing curve as the sheet
|
|
326
|
-
CGRect finalFrame = presentedView.frame;
|
|
327
|
-
finalFrame.origin.y = presentedView.frame.origin.y;
|
|
328
|
-
self->_fakeTransitionView.frame = finalFrame;
|
|
329
|
-
|
|
330
|
-
// Set our last transition position so we can check if
|
|
331
|
-
// fake view's presentation layer has changed
|
|
332
|
-
// This value will not change if the sheet is being repositioned
|
|
333
|
-
// during a transition after a drag
|
|
334
|
-
self->_lastTransitionPosition = finalFrame.origin.y;
|
|
335
|
-
|
|
336
|
-
// Start display link to track position changes at screen refresh rate
|
|
337
|
-
// This fires at 60-120Hz and reads from the presentation layer
|
|
338
|
-
self->_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(trackTransitionPosition:)];
|
|
339
|
-
[self->_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
|
|
340
|
-
};
|
|
844
|
+
- (void)emitInteractivePosition {
|
|
845
|
+
CALayer *presentation = _interactiveContainerView.layer.presentationLayer;
|
|
846
|
+
CGFloat offset = presentation ? presentation.affineTransform.ty : 0;
|
|
847
|
+
[self emitChangePositionDelegateWithPosition:_interactiveStartPosition + offset realtime:YES debug:@"nav swipe"];
|
|
848
|
+
}
|
|
341
849
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
850
|
+
// Translate the presentation container, not the sheet's presentedView (whose transform
|
|
851
|
+
// UISheetPresentationController owns for the floating-sheet inset). Only runs for undimmed
|
|
852
|
+
// sheets — a dimmed sheet's dimming view intercepts the edge-swipe so the pop never starts.
|
|
853
|
+
- (void)updateInteractiveDismiss:(CGFloat)progress {
|
|
854
|
+
if (!_isInteractiveDismiss) {
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
CGFloat clamped = fmin(1, fmax(0, progress));
|
|
858
|
+
CGFloat dy = clamped * (self.screenHeight - _interactiveStartPosition);
|
|
859
|
+
_interactiveContainerView.transform = CGAffineTransformMakeTranslation(0, dy);
|
|
860
|
+
}
|
|
348
861
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
}];
|
|
862
|
+
- (void)cancelInteractiveDismissWithDuration:(NSTimeInterval)duration {
|
|
863
|
+
if (!_isInteractiveDismiss) {
|
|
864
|
+
return;
|
|
353
865
|
}
|
|
866
|
+
[self animateInteractiveContainerToTransform:CGAffineTransformIdentity
|
|
867
|
+
duration:duration
|
|
868
|
+
allowUserInteraction:YES
|
|
869
|
+
completion:nil];
|
|
354
870
|
}
|
|
355
871
|
|
|
356
|
-
- (void)
|
|
357
|
-
|
|
872
|
+
- (void)finishInteractiveDismissWithDuration:(NSTimeInterval)duration completion:(void (^)(void))completion {
|
|
873
|
+
if (!_isInteractiveDismiss) {
|
|
874
|
+
if (completion) {
|
|
875
|
+
completion();
|
|
876
|
+
}
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
// Leave the sheet translated off-screen; the caller tears it down non-animated
|
|
880
|
+
// from here so there is no second slide.
|
|
881
|
+
CGFloat dy = self.screenHeight - _interactiveStartPosition;
|
|
882
|
+
[self animateInteractiveContainerToTransform:CGAffineTransformMakeTranslation(0, dy)
|
|
883
|
+
duration:duration
|
|
884
|
+
allowUserInteraction:NO
|
|
885
|
+
completion:completion];
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
- (void)animateInteractiveContainerToTransform:(CGAffineTransform)transform
|
|
889
|
+
duration:(NSTimeInterval)duration
|
|
890
|
+
allowUserInteraction:(BOOL)allowUserInteraction
|
|
891
|
+
completion:(void (^)(void))completion {
|
|
892
|
+
UIView *container = _interactiveContainerView;
|
|
893
|
+
NSUInteger generation = _interactiveGeneration;
|
|
358
894
|
|
|
359
|
-
|
|
895
|
+
UIViewAnimationOptions options = UIViewAnimationOptionCurveEaseOut | UIViewAnimationOptionBeginFromCurrentState;
|
|
896
|
+
if (allowUserInteraction) {
|
|
897
|
+
options |= UIViewAnimationOptionAllowUserInteraction;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
[UIView animateWithDuration:fmax(duration, 0.2)
|
|
901
|
+
delay:0
|
|
902
|
+
options:options
|
|
903
|
+
animations:^{
|
|
904
|
+
container.transform = transform;
|
|
905
|
+
}
|
|
906
|
+
completion:^(BOOL finished) {
|
|
907
|
+
// A newer gesture superseded this settle and owns teardown now.
|
|
908
|
+
if (generation != self->_interactiveGeneration) {
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
[self endInteractiveDismissState];
|
|
912
|
+
if (completion) {
|
|
913
|
+
completion();
|
|
914
|
+
}
|
|
915
|
+
}];
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
- (void)endInteractiveDismissState {
|
|
919
|
+
_isInteractiveDismiss = NO;
|
|
920
|
+
_interactiveContainerView = nil;
|
|
921
|
+
_interactiveStartPosition = 0;
|
|
922
|
+
[_interactivePositionLink invalidate];
|
|
923
|
+
_interactivePositionLink = nil;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
- (void)emitChangePositionDelegateWithPosition:(CGFloat)position realtime:(BOOL)realtime debug:(NSString *)debug {
|
|
927
|
+
UIViewController *presented = self.presentedViewController;
|
|
928
|
+
if (presented) {
|
|
929
|
+
UIModalPresentationStyle style = presented.modalPresentationStyle;
|
|
930
|
+
if (style == UIModalPresentationFullScreen || style == UIModalPresentationOverFullScreen ||
|
|
931
|
+
style == UIModalPresentationCurrentContext || style == UIModalPresentationOverCurrentContext) {
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
TrueSheetPositionState state = {
|
|
937
|
+
.position = position,
|
|
938
|
+
.detent = [self interpolatedDetentForPosition:position],
|
|
939
|
+
.index = [self interpolatedIndexForPosition:position],
|
|
940
|
+
};
|
|
941
|
+
|
|
942
|
+
// Settle (non-realtime) emits are authoritative and bypass the dedupe — a
|
|
943
|
+
// learn right before them can correct the interpolated index by less than
|
|
944
|
+
// the tolerance, and the corrected value must still reach JS.
|
|
945
|
+
if (!realtime || !TrueSheetPositionStateEquals(_lastEmittedPositionState, state)) {
|
|
946
|
+
_lastEmittedPositionState = state;
|
|
947
|
+
|
|
948
|
+
[self.delegate viewControllerDidChangePosition:state.index
|
|
949
|
+
position:state.position
|
|
950
|
+
detent:state.detent
|
|
951
|
+
realtime:realtime];
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
- (void)learnOffsetForDetentIndex:(NSInteger)index {
|
|
956
|
+
if (_keyboardSheetGrown) {
|
|
360
957
|
return;
|
|
361
958
|
}
|
|
959
|
+
[_detentCalculator learnOffsetForDetentIndex:index];
|
|
960
|
+
}
|
|
362
961
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
CALayer *presentationLayer = _fakeTransitionView.layer.presentationLayer;
|
|
962
|
+
- (void)setKeyboardSheetGrown:(BOOL)keyboardSheetGrown {
|
|
963
|
+
BOOL wasGrown = _keyboardSheetGrown;
|
|
964
|
+
_keyboardSheetGrown = keyboardSheetGrown;
|
|
367
965
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
966
|
+
// Settles are skipped or measure a mid-animation frame while the keyboard
|
|
967
|
+
// has the sheet grown (e.g. a drag-end during the shrink-back) — re-settle
|
|
968
|
+
// at the resting detent once the keyboard is fully away.
|
|
969
|
+
if (wasGrown && !keyboardSheetGrown && self.isPresented && !_isDragging) {
|
|
970
|
+
[self settleAtDetentIndex:self.currentDetentIndex debug:@"keyboard settled"];
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
- (BOOL)findSegmentForPosition:(CGFloat)position outIndex:(NSInteger *)outIndex outProgress:(CGFloat *)outProgress {
|
|
975
|
+
return [_detentCalculator findSegmentForPosition:position outIndex:outIndex outProgress:outProgress];
|
|
976
|
+
}
|
|
371
977
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
// Let's just flag it as transitioning to let JS manually animate
|
|
376
|
-
transitioning = YES;
|
|
978
|
+
- (CGFloat)interpolatedIndexForPosition:(CGFloat)position {
|
|
979
|
+
return [_detentCalculator interpolatedIndexForPosition:position];
|
|
980
|
+
}
|
|
377
981
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
982
|
+
- (CGFloat)interpolatedDetentForPosition:(CGFloat)position {
|
|
983
|
+
return [_detentCalculator interpolatedDetentForPosition:position];
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
- (CGFloat)detentValueForIndex:(NSInteger)index {
|
|
987
|
+
return [_detentCalculator detentValueForIndex:index];
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
#pragma mark - Sheet Configuration
|
|
991
|
+
|
|
992
|
+
/**
|
|
993
|
+
* Applies a content/header/footer/peek size change to the already-built
|
|
994
|
+
* detents. Auto and peek detents resolve lazily from snapshots, so this only
|
|
995
|
+
* refreshes the snapshots and invalidates — never reassigns `sheet.detents`,
|
|
996
|
+
* which would force UIKit to re-layout the whole presentation stack (visibly
|
|
997
|
+
* perturbing a sheet behind this one).
|
|
998
|
+
*/
|
|
999
|
+
- (void)setupSheetDetentsForSizeChange {
|
|
1000
|
+
if (@available(iOS 16.0, *)) {
|
|
1001
|
+
CGFloat autoHeight = [_detentCalculator autoHeight];
|
|
1002
|
+
CGFloat peekHeight = [_detentCalculator peekHeight];
|
|
1003
|
+
|
|
1004
|
+
if (fabs(autoHeight - _autoDetentHeight) < 0.5 && fabs(peekHeight - _peekDetentHeight) < 0.5) {
|
|
1005
|
+
return;
|
|
384
1006
|
}
|
|
385
1007
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
1008
|
+
_autoDetentHeight = autoHeight;
|
|
1009
|
+
_peekDetentHeight = peekHeight;
|
|
1010
|
+
|
|
1011
|
+
[self.sheet animateChanges:^{
|
|
1012
|
+
self->_pendingContentSizeChange = YES;
|
|
1013
|
+
[self.sheet invalidateDetents];
|
|
1014
|
+
}];
|
|
389
1015
|
}
|
|
1016
|
+
// iOS 15: detents are fixed medium/large — size changes can't affect them.
|
|
390
1017
|
}
|
|
391
1018
|
|
|
392
|
-
|
|
1019
|
+
- (void)setupSheetDetentsForDetentsChange {
|
|
1020
|
+
_pendingDetentsChange = YES;
|
|
1021
|
+
[self setupSheetDetents];
|
|
1022
|
+
}
|
|
393
1023
|
|
|
394
1024
|
- (void)setupSheetDetents {
|
|
395
|
-
UISheetPresentationController *sheet = self.
|
|
396
|
-
if (!sheet)
|
|
1025
|
+
UISheetPresentationController *sheet = self.sheet;
|
|
1026
|
+
if (!sheet) {
|
|
1027
|
+
RCTLogError(@"TrueSheet: sheetPresentationController is nil in setupSheetDetents");
|
|
397
1028
|
return;
|
|
1029
|
+
}
|
|
398
1030
|
|
|
399
1031
|
NSMutableArray<UISheetPresentationControllerDetent *> *detents = [NSMutableArray array];
|
|
1032
|
+
[_detentCalculator clearResolvedHeights];
|
|
400
1033
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
CGFloat totalHeight = [self.contentHeight floatValue] - _bottomInset;
|
|
1034
|
+
_autoDetentHeight = [_detentCalculator autoHeight];
|
|
1035
|
+
_peekDetentHeight = [_detentCalculator peekHeight];
|
|
404
1036
|
|
|
405
1037
|
for (NSInteger index = 0; index < self.detents.count; index++) {
|
|
406
1038
|
id detent = self.detents[index];
|
|
407
|
-
UISheetPresentationControllerDetent *sheetDetent = [self detentForValue:detent
|
|
408
|
-
withHeight:totalHeight
|
|
409
|
-
atIndex:index];
|
|
1039
|
+
UISheetPresentationControllerDetent *sheetDetent = [self detentForValue:detent atIndex:index];
|
|
410
1040
|
[detents addObject:sheetDetent];
|
|
411
1041
|
}
|
|
412
1042
|
|
|
1043
|
+
[_detentCalculator setDetentCount:self.detents.count];
|
|
413
1044
|
sheet.detents = detents;
|
|
414
1045
|
|
|
415
|
-
|
|
416
|
-
if (self.dimmed && [self.dimmedIndex integerValue] == 0) {
|
|
1046
|
+
if (self.dimmed && [self.dimmedDetentIndex integerValue] == 0) {
|
|
417
1047
|
sheet.largestUndimmedDetentIdentifier = nil;
|
|
418
1048
|
} else {
|
|
419
1049
|
sheet.largestUndimmedDetentIdentifier = UISheetPresentationControllerDetentIdentifierLarge;
|
|
420
1050
|
|
|
421
1051
|
if (@available(iOS 16.0, *)) {
|
|
422
|
-
if (self.dimmed && self.
|
|
423
|
-
NSInteger dimmedIdx = [self.
|
|
1052
|
+
if (self.dimmed && self.dimmedDetentIndex) {
|
|
1053
|
+
NSInteger dimmedIdx = [self.dimmedDetentIndex integerValue];
|
|
424
1054
|
if (dimmedIdx > 0 && dimmedIdx - 1 < sheet.detents.count) {
|
|
425
1055
|
sheet.largestUndimmedDetentIdentifier = sheet.detents[dimmedIdx - 1].identifier;
|
|
426
1056
|
} else if (sheet.detents.lastObject) {
|
|
@@ -433,62 +1063,116 @@
|
|
|
433
1063
|
}
|
|
434
1064
|
}
|
|
435
1065
|
|
|
436
|
-
- (UISheetPresentationControllerDetent *)detentForValue:(id)detent
|
|
1066
|
+
- (UISheetPresentationControllerDetent *)detentForValue:(id)detent atIndex:(NSInteger)index {
|
|
437
1067
|
if (![detent isKindOfClass:[NSNumber class]]) {
|
|
438
1068
|
return [UISheetPresentationControllerDetent mediumDetent];
|
|
439
1069
|
}
|
|
440
1070
|
|
|
441
|
-
CGFloat value = [detent
|
|
1071
|
+
CGFloat value = [detent doubleValue];
|
|
442
1072
|
|
|
443
|
-
//
|
|
1073
|
+
// Auto/peek resolve from height snapshots — refreshed only in
|
|
1074
|
+
// setupSheetDetents and setupSheetDetentsForSizeChange — so size changes are
|
|
1075
|
+
// picked up on invalidation, while UIKit's spontaneous re-resolutions (e.g.
|
|
1076
|
+
// while a child sheet dismisses) read a stable value.
|
|
444
1077
|
if (value == -1) {
|
|
445
1078
|
if (@available(iOS 16.0, *)) {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
1079
|
+
return [self customDetentWithIdentifier:@"custom-auto"
|
|
1080
|
+
atIndex:index
|
|
1081
|
+
heightBlock:^CGFloat {
|
|
1082
|
+
CGFloat height = self->_autoDetentHeight;
|
|
1083
|
+
return height - [self autoDetentBottomInsetForHeight:height];
|
|
1084
|
+
}];
|
|
1085
|
+
} else {
|
|
1086
|
+
return [UISheetPresentationControllerDetent mediumDetent];
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
if (value == -2) {
|
|
1091
|
+
if (@available(iOS 16.0, *)) {
|
|
1092
|
+
return [self customDetentWithIdentifier:@"custom-peek"
|
|
1093
|
+
atIndex:index
|
|
1094
|
+
heightBlock:^CGFloat {
|
|
1095
|
+
CGFloat height = self->_peekDetentHeight;
|
|
1096
|
+
return height - [self peekDetentBottomInsetForHeight:height];
|
|
1097
|
+
}];
|
|
456
1098
|
} else {
|
|
457
1099
|
return [UISheetPresentationControllerDetent mediumDetent];
|
|
458
1100
|
}
|
|
459
1101
|
}
|
|
460
1102
|
|
|
461
|
-
// Handle fraction (0-1)
|
|
462
|
-
// Fraction should only be > 0 and <= 1
|
|
463
1103
|
if (value <= 0 || value > 1) {
|
|
464
1104
|
RCTLogError(@"TrueSheet: detent fraction (%f) must be between 0 and 1", value);
|
|
465
1105
|
return [UISheetPresentationControllerDetent mediumDetent];
|
|
466
1106
|
}
|
|
467
1107
|
|
|
468
|
-
NSString *detentId = [NSString stringWithFormat:@"custom-%f", value];
|
|
469
|
-
|
|
470
1108
|
if (@available(iOS 16.0, *)) {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
self.maxHeight ? MIN(maxDetentValue, [self.maxHeight floatValue]) : maxDetentValue;
|
|
477
|
-
CGFloat resolvedValue = MIN(value * maxDetentValue, maxValue);
|
|
478
|
-
return resolvedValue;
|
|
479
|
-
}];
|
|
1109
|
+
NSString *detentId = [NSString stringWithFormat:@"custom-%f", value];
|
|
1110
|
+
CGFloat sheetHeight = value * self.screenHeight;
|
|
1111
|
+
return [self customDetentWithIdentifier:detentId height:sheetHeight atIndex:index];
|
|
1112
|
+
} else if (value >= 0.5) {
|
|
1113
|
+
return [UISheetPresentationControllerDetent largeDetent];
|
|
480
1114
|
} else {
|
|
481
1115
|
return [UISheetPresentationControllerDetent mediumDetent];
|
|
482
1116
|
}
|
|
483
1117
|
}
|
|
484
1118
|
|
|
1119
|
+
- (UISheetPresentationControllerDetent *)customDetentWithIdentifier:(NSString *)identifier
|
|
1120
|
+
height:(CGFloat)height
|
|
1121
|
+
atIndex:(NSInteger)index API_AVAILABLE(ios(16.0)) {
|
|
1122
|
+
return [self customDetentWithIdentifier:identifier
|
|
1123
|
+
atIndex:index
|
|
1124
|
+
heightBlock:^CGFloat {
|
|
1125
|
+
return height;
|
|
1126
|
+
}];
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
- (UISheetPresentationControllerDetent *)customDetentWithIdentifier:(NSString *)identifier
|
|
1130
|
+
atIndex:(NSInteger)index
|
|
1131
|
+
heightBlock:(CGFloat (^)(void))heightBlock
|
|
1132
|
+
API_AVAILABLE(ios(16.0)) {
|
|
1133
|
+
return [UISheetPresentationControllerDetent
|
|
1134
|
+
customDetentWithIdentifier:identifier
|
|
1135
|
+
resolver:^CGFloat(id<UISheetPresentationControllerDetentResolutionContext> context) {
|
|
1136
|
+
CGFloat maxDetentValue = context.maximumDetentValue;
|
|
1137
|
+
self->_detentCalculator.maxDetentHeight = maxDetentValue;
|
|
1138
|
+
|
|
1139
|
+
CGFloat height = heightBlock();
|
|
1140
|
+
CGFloat bottomAdjustment = [self detentBottomAdjustmentForHeight:height];
|
|
1141
|
+
CGFloat maxValue = self.maxContentHeight
|
|
1142
|
+
? fmin(maxDetentValue, [self.maxContentHeight floatValue])
|
|
1143
|
+
: maxDetentValue;
|
|
1144
|
+
|
|
1145
|
+
CGFloat adjustedHeight = height - bottomAdjustment;
|
|
1146
|
+
|
|
1147
|
+
// Only the last detent may occupy the full maximum. Two detents
|
|
1148
|
+
// resolving to the same height (e.g. a capped auto and 1) make
|
|
1149
|
+
// UIKit skip the stack's deck animation when presenting a child
|
|
1150
|
+
// sheet and snap the sheet behind into place instead.
|
|
1151
|
+
NSInteger lastIndex = (NSInteger)self.detents.count - 1;
|
|
1152
|
+
if (adjustedHeight >= maxValue && index < lastIndex) {
|
|
1153
|
+
maxValue -= lastIndex - index;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
CGFloat resolved = fmin(adjustedHeight, maxValue);
|
|
1157
|
+
|
|
1158
|
+
NSMutableArray *heights = self->_detentCalculator.resolvedDetentHeights;
|
|
1159
|
+
if (heights && index >= 0 && index < (NSInteger)heights.count) {
|
|
1160
|
+
heights[index] = @(resolved);
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
return resolved;
|
|
1164
|
+
}];
|
|
1165
|
+
}
|
|
1166
|
+
|
|
485
1167
|
- (UISheetPresentationControllerDetentIdentifier)detentIdentifierForIndex:(NSInteger)index {
|
|
486
|
-
UISheetPresentationController *sheet = self.
|
|
487
|
-
if (!sheet)
|
|
1168
|
+
UISheetPresentationController *sheet = self.sheet;
|
|
1169
|
+
if (!sheet) {
|
|
1170
|
+
RCTLogError(@"TrueSheet: sheetPresentationController is nil in detentIdentifierForIndex");
|
|
488
1171
|
return UISheetPresentationControllerDetentIdentifierMedium;
|
|
1172
|
+
}
|
|
489
1173
|
|
|
490
1174
|
UISheetPresentationControllerDetentIdentifier identifier = UISheetPresentationControllerDetentIdentifierMedium;
|
|
491
|
-
if (index < sheet.detents.count) {
|
|
1175
|
+
if (index >= 0 && index < (NSInteger)sheet.detents.count) {
|
|
492
1176
|
UISheetPresentationControllerDetent *detent = sheet.detents[index];
|
|
493
1177
|
if (@available(iOS 16.0, *)) {
|
|
494
1178
|
identifier = detent.identifier;
|
|
@@ -503,17 +1187,15 @@
|
|
|
503
1187
|
}
|
|
504
1188
|
|
|
505
1189
|
- (void)applyActiveDetent {
|
|
506
|
-
|
|
507
|
-
|
|
1190
|
+
if (!self.sheet) {
|
|
1191
|
+
RCTLogError(@"TrueSheet: sheetPresentationController is nil in applyActiveDetent");
|
|
508
1192
|
return;
|
|
1193
|
+
}
|
|
509
1194
|
|
|
510
|
-
// Validate and clamp activeDetentIndex to detents bounds
|
|
511
1195
|
NSInteger detentCount = _detents.count;
|
|
512
|
-
if (detentCount == 0)
|
|
1196
|
+
if (detentCount == 0)
|
|
513
1197
|
return;
|
|
514
|
-
}
|
|
515
1198
|
|
|
516
|
-
// Clamp index to valid range
|
|
517
1199
|
NSInteger clampedIndex = _activeDetentIndex;
|
|
518
1200
|
if (clampedIndex < 0) {
|
|
519
1201
|
clampedIndex = 0;
|
|
@@ -521,14 +1203,13 @@
|
|
|
521
1203
|
clampedIndex = detentCount - 1;
|
|
522
1204
|
}
|
|
523
1205
|
|
|
524
|
-
// Update the stored index if it was clamped
|
|
525
1206
|
if (clampedIndex != _activeDetentIndex) {
|
|
526
1207
|
_activeDetentIndex = clampedIndex;
|
|
527
1208
|
}
|
|
528
1209
|
|
|
529
1210
|
UISheetPresentationControllerDetentIdentifier identifier = [self detentIdentifierForIndex:clampedIndex];
|
|
530
1211
|
if (identifier) {
|
|
531
|
-
sheet.selectedDetentIdentifier = identifier;
|
|
1212
|
+
self.sheet.selectedDetentIdentifier = identifier;
|
|
532
1213
|
}
|
|
533
1214
|
}
|
|
534
1215
|
|
|
@@ -537,113 +1218,235 @@
|
|
|
537
1218
|
[self applyActiveDetent];
|
|
538
1219
|
}
|
|
539
1220
|
|
|
540
|
-
- (NSInteger)
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
1221
|
+
- (void)resizeToDetentIndex:(NSInteger)index {
|
|
1222
|
+
if (index == _activeDetentIndex) {
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
544
1225
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
1226
|
+
_pendingDetentIndex = index;
|
|
1227
|
+
_activeDetentIndex = index;
|
|
1228
|
+
[self applyActiveDetent];
|
|
1229
|
+
}
|
|
548
1230
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
1231
|
+
- (void)setupBackground {
|
|
1232
|
+
auto effectiveBackgroundBlur = self.backgroundBlur;
|
|
1233
|
+
if (@available(iOS 26.0, *)) {
|
|
1234
|
+
// iOS 26+ has default liquid glass effect
|
|
1235
|
+
} else if (effectiveBackgroundBlur == TrueSheetViewBackgroundBlur::None && !self.backgroundColor) {
|
|
1236
|
+
effectiveBackgroundBlur = TrueSheetViewBackgroundBlur::SystemMaterial;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
BOOL hasBlur = effectiveBackgroundBlur != TrueSheetViewBackgroundBlur::None;
|
|
1240
|
+
|
|
1241
|
+
_blurView.backgroundBlur = hasBlur ? effectiveBackgroundBlur : TrueSheetViewBackgroundBlur::None;
|
|
1242
|
+
_blurView.blurIntensity = self.blurIntensity;
|
|
1243
|
+
_blurView.blurInteraction = self.blurInteraction;
|
|
1244
|
+
[_blurView applyBlurEffect];
|
|
1245
|
+
|
|
1246
|
+
#if RNTS_IPHONE_OS_VERSION_AVAILABLE(26_1) && !TARGET_OS_MACCATALYST
|
|
1247
|
+
if (@available(iOS 26.1, *)) {
|
|
1248
|
+
if (!self.isDesignCompatibilityMode) {
|
|
1249
|
+
if (self.backgroundColor) {
|
|
1250
|
+
self.sheet.backgroundEffect = [UIColorEffect effectWithColor:self.backgroundColor];
|
|
1251
|
+
} else if (hasBlur) {
|
|
1252
|
+
self.sheet.backgroundEffect = [UIColorEffect effectWithColor:[UIColor clearColor]];
|
|
1253
|
+
} else {
|
|
1254
|
+
self.sheet.backgroundEffect = nil;
|
|
563
1255
|
}
|
|
1256
|
+
return;
|
|
564
1257
|
}
|
|
565
1258
|
}
|
|
1259
|
+
#endif
|
|
566
1260
|
|
|
567
|
-
|
|
1261
|
+
self.view.backgroundColor = self.backgroundColor;
|
|
568
1262
|
}
|
|
569
1263
|
|
|
570
|
-
- (
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
1264
|
+
- (void)setupGrabber {
|
|
1265
|
+
BOOL showGrabber = self.grabber && self.draggable;
|
|
1266
|
+
|
|
1267
|
+
if (self.grabberOptions) {
|
|
1268
|
+
self.sheet.prefersGrabberVisible = NO;
|
|
1269
|
+
|
|
1270
|
+
GrabberOptions *options = self.grabberOptions;
|
|
1271
|
+
_grabberView.accessibilityOptions = self.accessibilityOptions;
|
|
1272
|
+
_grabberView.grabberWidth = options.width;
|
|
1273
|
+
_grabberView.grabberHeight = options.height;
|
|
1274
|
+
_grabberView.topMargin = options.topMargin;
|
|
1275
|
+
_grabberView.cornerRadius = options.cornerRadius;
|
|
1276
|
+
_grabberView.color = options.color;
|
|
1277
|
+
_grabberView.adaptive = @(options.adaptive);
|
|
1278
|
+
[_grabberView applyConfiguration];
|
|
1279
|
+
_grabberView.hidden = !showGrabber;
|
|
1280
|
+
|
|
1281
|
+
__weak __typeof(self) weakSelf = self;
|
|
1282
|
+
_grabberView.onTap = ^{
|
|
1283
|
+
[weakSelf handleGrabberTap];
|
|
1284
|
+
};
|
|
1285
|
+
_grabberView.onIncrement = ^{
|
|
1286
|
+
__strong __typeof(weakSelf) strongSelf = weakSelf;
|
|
1287
|
+
if (!strongSelf)
|
|
1288
|
+
return;
|
|
1289
|
+
NSInteger current = strongSelf.currentDetentIndex;
|
|
1290
|
+
NSInteger count = strongSelf->_detents.count;
|
|
1291
|
+
if (current >= 0 && current < count - 1) {
|
|
1292
|
+
[strongSelf.sheet animateChanges:^{
|
|
1293
|
+
[strongSelf resizeToDetentIndex:current + 1];
|
|
1294
|
+
}];
|
|
1295
|
+
}
|
|
1296
|
+
};
|
|
1297
|
+
_grabberView.onDecrement = ^{
|
|
1298
|
+
__strong __typeof(weakSelf) strongSelf = weakSelf;
|
|
1299
|
+
if (!strongSelf)
|
|
1300
|
+
return;
|
|
1301
|
+
NSInteger current = strongSelf.currentDetentIndex;
|
|
1302
|
+
if (current > 0) {
|
|
1303
|
+
[strongSelf.sheet animateChanges:^{
|
|
1304
|
+
[strongSelf resizeToDetentIndex:current - 1];
|
|
1305
|
+
}];
|
|
1306
|
+
} else if (strongSelf.dismissible) {
|
|
1307
|
+
[strongSelf.presentingViewController dismissViewControllerAnimated:YES completion:nil];
|
|
1308
|
+
}
|
|
1309
|
+
};
|
|
574
1310
|
|
|
575
|
-
|
|
1311
|
+
[self.view bringSubviewToFront:_grabberView];
|
|
1312
|
+
} else {
|
|
1313
|
+
self.sheet.prefersGrabberVisible = showGrabber;
|
|
1314
|
+
_grabberView.hidden = YES;
|
|
1315
|
+
_grabberView.onTap = nil;
|
|
1316
|
+
_grabberView.onIncrement = nil;
|
|
1317
|
+
_grabberView.onDecrement = nil;
|
|
1318
|
+
}
|
|
576
1319
|
}
|
|
577
1320
|
|
|
578
|
-
- (
|
|
579
|
-
|
|
1321
|
+
- (void)handleGrabberTap {
|
|
1322
|
+
NSInteger detentCount = _detents.count;
|
|
1323
|
+
if (detentCount == 0)
|
|
1324
|
+
return;
|
|
1325
|
+
|
|
1326
|
+
NSInteger currentIndex = self.currentDetentIndex;
|
|
1327
|
+
if (currentIndex < 0)
|
|
1328
|
+
return;
|
|
1329
|
+
|
|
1330
|
+
NSInteger nextIndex = (currentIndex + 1) % detentCount;
|
|
1331
|
+
if (nextIndex == 0 && detentCount == 1 && self.dismissible) {
|
|
1332
|
+
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
|
|
1333
|
+
} else {
|
|
1334
|
+
[self.sheet animateChanges:^{
|
|
1335
|
+
[self resizeToDetentIndex:nextIndex];
|
|
1336
|
+
}];
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
- (BOOL)isAnchored {
|
|
1341
|
+
return self.anchor == TrueSheetViewAnchor::Left || self.anchor == TrueSheetViewAnchor::Right;
|
|
580
1342
|
}
|
|
581
1343
|
|
|
582
|
-
- (
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
1344
|
+
- (void)setupAnchorViewInView:(UIView *)parentView {
|
|
1345
|
+
if (!parentView)
|
|
1346
|
+
return;
|
|
1347
|
+
|
|
1348
|
+
[_anchorView removeFromSuperview];
|
|
1349
|
+
_anchorView = nil;
|
|
586
1350
|
|
|
587
|
-
|
|
1351
|
+
if (!self.isAnchored) {
|
|
1352
|
+
self.sheetPresentationController.sourceView = nil;
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
_anchorView = [[UIView alloc] init];
|
|
1357
|
+
_anchorView.userInteractionEnabled = NO;
|
|
1358
|
+
_anchorView.translatesAutoresizingMaskIntoConstraints = NO;
|
|
1359
|
+
[parentView addSubview:_anchorView];
|
|
1360
|
+
|
|
1361
|
+
NSLayoutAnchor *horizontalAnchor =
|
|
1362
|
+
self.anchor == TrueSheetViewAnchor::Right ? parentView.trailingAnchor : parentView.leadingAnchor;
|
|
1363
|
+
|
|
1364
|
+
[NSLayoutConstraint activateConstraints:@[
|
|
1365
|
+
[_anchorView.bottomAnchor constraintEqualToAnchor:parentView.bottomAnchor],
|
|
1366
|
+
[horizontalAnchor constraintEqualToAnchor:_anchorView.leadingAnchor],
|
|
1367
|
+
]];
|
|
1368
|
+
|
|
1369
|
+
self.sheetPresentationController.sourceView = _anchorView;
|
|
588
1370
|
}
|
|
589
1371
|
|
|
590
|
-
- (void)
|
|
591
|
-
UISheetPresentationController *sheet = self.
|
|
592
|
-
if (!sheet)
|
|
1372
|
+
- (void)setupSheetSizing {
|
|
1373
|
+
UISheetPresentationController *sheet = self.sheet;
|
|
1374
|
+
if (!sheet)
|
|
593
1375
|
return;
|
|
1376
|
+
|
|
1377
|
+
// `presentation` is absolute on the form side: 'form' always renders a
|
|
1378
|
+
// centered form sheet and ignores `maxContentWidth`. For 'page' with a
|
|
1379
|
+
// custom `maxContentWidth`, prefersPageSizing has to flip to NO since
|
|
1380
|
+
// `widthFollowsPreferredContentSizeWhenEdgeAttached` only takes effect
|
|
1381
|
+
// when the sheet is edge-attached (Apple API constraint).
|
|
1382
|
+
BOOL formSheet = self.presentation == facebook::react::TrueSheetViewPresentation::Form;
|
|
1383
|
+
BOOL hasMaxWidth = self.maxContentWidth != nil && !formSheet;
|
|
1384
|
+
|
|
1385
|
+
if (@available(iOS 17.0, *)) {
|
|
1386
|
+
sheet.prefersPageSizing = !formSheet && !hasMaxWidth;
|
|
594
1387
|
}
|
|
595
1388
|
|
|
1389
|
+
sheet.widthFollowsPreferredContentSizeWhenEdgeAttached = hasMaxWidth;
|
|
1390
|
+
|
|
1391
|
+
if (hasMaxWidth) {
|
|
1392
|
+
CGFloat height = self.maxContentHeight ? [self.maxContentHeight floatValue] : self.screenHeight;
|
|
1393
|
+
self.preferredContentSize = CGSizeMake([self.maxContentWidth floatValue], height);
|
|
1394
|
+
} else {
|
|
1395
|
+
self.preferredContentSize = CGSizeZero;
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
- (void)setupSheetProps {
|
|
1400
|
+
UISheetPresentationController *sheet = self.sheet;
|
|
1401
|
+
if (!sheet)
|
|
1402
|
+
return;
|
|
1403
|
+
|
|
596
1404
|
sheet.delegate = self;
|
|
597
1405
|
sheet.prefersEdgeAttachedInCompactHeight = YES;
|
|
598
|
-
sheet.
|
|
599
|
-
|
|
1406
|
+
sheet.prefersScrollingExpandsWhenScrolledToEdge = self.draggable && self.scrollingExpandsSheet;
|
|
1407
|
+
|
|
600
1408
|
if (self.cornerRadius) {
|
|
601
1409
|
sheet.preferredCornerRadius = [self.cornerRadius floatValue];
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
// Setup blur effect if blurTint is provided
|
|
605
|
-
if (self.blurTint && self.blurTint.length > 0) {
|
|
606
|
-
UIBlurEffectStyle style = UIBlurEffectStyleLight;
|
|
607
|
-
|
|
608
|
-
if ([self.blurTint isEqualToString:@"dark"]) {
|
|
609
|
-
style = UIBlurEffectStyleDark;
|
|
610
|
-
} else if ([self.blurTint isEqualToString:@"light"]) {
|
|
611
|
-
style = UIBlurEffectStyleLight;
|
|
612
|
-
} else if ([self.blurTint isEqualToString:@"extraLight"]) {
|
|
613
|
-
style = UIBlurEffectStyleExtraLight;
|
|
614
|
-
} else if ([self.blurTint isEqualToString:@"regular"]) {
|
|
615
|
-
style = UIBlurEffectStyleRegular;
|
|
616
|
-
} else if ([self.blurTint isEqualToString:@"prominent"]) {
|
|
617
|
-
style = UIBlurEffectStyleProminent;
|
|
618
|
-
} else if ([self.blurTint isEqualToString:@"systemThinMaterial"]) {
|
|
619
|
-
style = UIBlurEffectStyleSystemThinMaterial;
|
|
620
|
-
} else if ([self.blurTint isEqualToString:@"systemMaterial"]) {
|
|
621
|
-
style = UIBlurEffectStyleSystemMaterial;
|
|
622
|
-
} else if ([self.blurTint isEqualToString:@"systemThickMaterial"]) {
|
|
623
|
-
style = UIBlurEffectStyleSystemThickMaterial;
|
|
624
|
-
} else if ([self.blurTint isEqualToString:@"systemChromeMaterial"]) {
|
|
625
|
-
style = UIBlurEffectStyleSystemChromeMaterial;
|
|
626
|
-
} else if ([self.blurTint isEqualToString:@"systemUltraThinMaterial"]) {
|
|
627
|
-
style = UIBlurEffectStyleSystemUltraThinMaterial;
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
_backgroundView.effect = [UIBlurEffect effectWithStyle:style];
|
|
631
|
-
_backgroundView.backgroundColor = nil;
|
|
632
1410
|
} else {
|
|
633
|
-
|
|
634
|
-
_backgroundView.effect = nil;
|
|
635
|
-
_backgroundView.backgroundColor = self.backgroundColor;
|
|
1411
|
+
sheet.preferredCornerRadius = UISheetPresentationControllerAutomaticDimension;
|
|
636
1412
|
}
|
|
1413
|
+
|
|
1414
|
+
[self setupBackground];
|
|
1415
|
+
[self setupGrabber];
|
|
637
1416
|
}
|
|
638
1417
|
|
|
639
1418
|
#pragma mark - UISheetPresentationControllerDelegate
|
|
640
1419
|
|
|
1420
|
+
- (BOOL)presentationControllerShouldDismiss:(UIPresentationController *)presentationController {
|
|
1421
|
+
return self.dismissible;
|
|
1422
|
+
}
|
|
1423
|
+
|
|
641
1424
|
- (void)sheetPresentationControllerDidChangeSelectedDetentIdentifier:
|
|
642
1425
|
(UISheetPresentationController *)sheetPresentationController {
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
1426
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
1427
|
+
NSInteger index = self.currentDetentIndex;
|
|
1428
|
+
if (index >= 0) {
|
|
1429
|
+
CGFloat detent = [self detentValueForIndex:index];
|
|
1430
|
+
[self.delegate viewControllerDidChangeDetent:index position:self.currentPosition detent:detent];
|
|
1431
|
+
}
|
|
1432
|
+
});
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
#pragma mark - RNSDismissibleModalProtocol
|
|
1436
|
+
|
|
1437
|
+
#if RNS_DISMISSIBLE_MODAL_PROTOCOL_AVAILABLE
|
|
1438
|
+
- (BOOL)isDismissible {
|
|
1439
|
+
return NO;
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
- (UIViewController *)newPresentingViewController {
|
|
1443
|
+
UIViewController *topmost = self;
|
|
1444
|
+
while (topmost.presentedViewController != nil && !topmost.presentedViewController.isBeingDismissed &&
|
|
1445
|
+
[topmost.presentedViewController isKindOfClass:[TrueSheetViewController class]]) {
|
|
1446
|
+
topmost = topmost.presentedViewController;
|
|
646
1447
|
}
|
|
1448
|
+
return topmost;
|
|
647
1449
|
}
|
|
1450
|
+
#endif
|
|
648
1451
|
|
|
649
1452
|
@end
|