diff --git a/AGENTS.md b/AGENTS.md index 3947ca3..7f0bd12 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,3 +1,5 @@ +# Agent Instructions + ## Get Started - See README to know more about this project. @@ -6,5 +8,136 @@ ## Rules 1. I will do builds and UI test myself. -2. YOU MUST NOT commit changes youself until I explicitly tell you to. +2. YOU MUST NOT commit changes yourself until I explicitly tell you to. 3. YOU MUST NOT create summary documents unless you are told to. + +## Project Overview + +This is a React Native Fabric (New Architecture) bottom sheet library. It provides native bottom sheet functionality for both iOS and Android. + +### Key Technologies + +- **React Native New Architecture (Fabric)** - No bridge, direct C++ communication +- **Codegen** - Auto-generates native interfaces from TypeScript specs +- **C++ Shared Code** - State and shadow nodes shared between iOS and Android + +## Project Structure + +``` +src/ +├── fabric/ # Native component specs (codegen input) +│ ├── TrueSheetViewNativeComponent.ts # Host view spec (has interfaceOnly: true) +│ ├── TrueSheetContainerViewNativeComponent.ts +│ ├── TrueSheetContentViewNativeComponent.ts +│ └── TrueSheetFooterViewNativeComponent.ts +├── TrueSheet.tsx # Main React component +└── TrueSheet.types.ts # TypeScript types + +ios/ +├── TrueSheetView.mm/.h # Host view (Fabric component) +├── TrueSheetContainerView.mm/.h +├── TrueSheetContentView.mm/.h +├── TrueSheetFooterView.mm/.h +├── TrueSheetViewController.mm/.h # UIViewController for sheet presentation +└── TrueSheetModule.mm/.h # TurboModule for imperative methods + +android/src/main/java/com/lodev09/truesheet/ +├── TrueSheetView.kt # Host view +├── TrueSheetViewManager.kt # View manager +├── TrueSheetContainerView.kt +├── TrueSheetViewController.kt # Dialog/BottomSheet controller +└── TrueSheetModule.kt # TurboModule + +common/cpp/react/renderer/components/TrueSheetSpec/ +├── TrueSheetViewState.h/.cpp # Shared state (containerWidth) +├── TrueSheetViewShadowNode.h/.cpp # Custom shadow node with adjustLayoutWithState() +└── TrueSheetViewComponentDescriptor.h # Custom descriptor that calls adjustLayoutWithState() +``` + +## Architecture + +### View Hierarchy + +``` +TrueSheetView (host view - hidden, manages state) +└── TrueSheetContainerView (fills controller's view) + ├── TrueSheetContentView (sheet content) + └── TrueSheetFooterView (sticky footer) +``` + +### Fabric State Management + +The host view (`TrueSheetView`) uses Fabric state to pass native dimensions to Yoga for layout: + +1. **State files** (`TrueSheetViewState.h/.cpp`) - Hold `containerWidth` from native +2. **Shadow node** (`TrueSheetViewShadowNode`) - `adjustLayoutWithState()` updates Yoga dimensions +3. **Component descriptor** - Calls `adjustLayoutWithState()` on adopt +4. **Native view** - Calls `updateState()` when dimensions change (e.g., rotation) + +### iOS State Update Flow + +``` +TrueSheetView.mm: + updateState:oldState: → _state = new state, call updateStateIfNeeded + updateStateIfNeeded → check if width changed, push to Yoga via _state->updateState() + +TrueSheetViewController.mm: + viewWillTransitionToSize: → notify delegate of size change (rotation) +``` + +### Android State Update Flow + +``` +TrueSheetView.kt: + setStateWrapper() → store wrapper, call updateState if dimensions available + updateState() → stateWrapper.updateState() with WritableNativeMap + +TrueSheetViewManager.kt: + updateState() → view.setStateWrapper(stateWrapper) +``` + +## Key Concepts + +### interfaceOnly: true + +In `TrueSheetViewNativeComponent.ts`, `interfaceOnly: true` means: +- Codegen generates interfaces but not the component descriptor +- We provide custom C++ files (state, shadow node, descriptor) in `common/` + +### Container View + +The container view is a simple pass-through without custom state. It uses codegen-generated descriptor from `ComponentDescriptors.h`. + +### iOS: Width-only tracking + +iOS only tracks width changes for state updates since height is determined by content. See `_lastContainerWidth` in `TrueSheetView.mm`. + +### Android: Width and Height + +Android tracks both dimensions in state since the dialog size matters for layout. + +## Common Tasks + +### Adding a new prop + +1. Add to `TrueSheetViewNativeComponent.ts` +2. Run codegen (build the app) +3. Implement in `TrueSheetView.mm` (iOS) and `TrueSheetViewManager.kt` (Android) + +### Adding a new event + +1. Add `DirectEventHandler` to native component spec +2. Create event class in `ios/events/` and `android/.../events/` +3. Emit from native view + +### Modifying state/shadow node + +1. Update `TrueSheetViewState.h/.cpp` +2. Update `TrueSheetViewShadowNode.cpp` if layout logic changes +3. Update native views to push new state values + +## Commands + +```sh +yarn tidy # Format code (run before git commits) +``` diff --git a/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerView.kt b/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerView.kt index 68f57ab..d4084af 100644 --- a/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerView.kt +++ b/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerView.kt @@ -3,9 +3,6 @@ package com.lodev09.truesheet import android.annotation.SuppressLint import android.view.View import androidx.core.view.isNotEmpty -import com.facebook.react.bridge.WritableNativeMap -import com.facebook.react.uimanager.PixelUtil.pxToDp -import com.facebook.react.uimanager.StateWrapper import com.facebook.react.uimanager.ThemedReactContext import com.facebook.react.views.view.ReactViewGroup @@ -29,53 +26,6 @@ class TrueSheetContainerView(private val reactContext: ThemedReactContext) : var delegate: TrueSheetContainerViewDelegate? = null - private var stateWrapper: StateWrapper? = null - - // Pending dimensions to update when stateWrapper becomes available - private var pendingWidth: Int = 0 - private var pendingHeight: Int = 0 - - fun setStateWrapper(wrapper: StateWrapper?) { - stateWrapper = wrapper - - if (wrapper == null) return - - // Get dimensions from parent controller and update state if we haven't yet - val controller = parent as? TrueSheetViewController - if (controller != null && pendingWidth == 0) { - val w = controller.width - val h = controller.height - if (w > 0 && h > 0) { - updateState(w, h) - } - } - } - - /** - * Update state with container dimensions. - * Called by the controller when the dialog size changes. - */ - fun updateState(width: Int, height: Int) { - // Skip if dimensions haven't changed - if (width == pendingWidth && height == pendingHeight && stateWrapper != null) { - return - } - - // Store dimensions - pendingWidth = width - pendingHeight = height - - val sw = stateWrapper ?: return - - val realWidth = width.toFloat().pxToDp() - val realHeight = height.toFloat().pxToDp() - - val newStateData = WritableNativeMap() - newStateData.putDouble("containerWidth", realWidth.toDouble()) - newStateData.putDouble("containerHeight", realHeight.toDouble()) - sw.updateState(newStateData) - } - /** * Reference to content view (first child) */ diff --git a/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerViewManager.kt b/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerViewManager.kt index e60fd6e..e8d8f0a 100644 --- a/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerViewManager.kt +++ b/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerViewManager.kt @@ -1,8 +1,6 @@ package com.lodev09.truesheet import com.facebook.react.module.annotations.ReactModule -import com.facebook.react.uimanager.ReactStylesDiffMap -import com.facebook.react.uimanager.StateWrapper import com.facebook.react.uimanager.ThemedReactContext import com.facebook.react.uimanager.ViewGroupManager @@ -17,11 +15,6 @@ class TrueSheetContainerViewManager : ViewGroupManager() override fun createViewInstance(reactContext: ThemedReactContext): TrueSheetContainerView = TrueSheetContainerView(reactContext) - override fun updateState(view: TrueSheetContainerView, props: ReactStylesDiffMap?, stateWrapper: StateWrapper?): Any? { - view.setStateWrapper(stateWrapper) - return null - } - companion object { const val REACT_CLASS = "TrueSheetContainerView" } diff --git a/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt b/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt index 48a9223..0f26abd 100644 --- a/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +++ b/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt @@ -6,6 +6,9 @@ import android.view.ViewStructure import android.view.accessibility.AccessibilityEvent import androidx.annotation.UiThread import com.facebook.react.bridge.LifecycleEventListener +import com.facebook.react.bridge.WritableNativeMap +import com.facebook.react.uimanager.PixelUtil.pxToDp +import com.facebook.react.uimanager.StateWrapper import com.facebook.react.uimanager.ThemedReactContext import com.facebook.react.uimanager.UIManagerHelper import com.facebook.react.uimanager.events.EventDispatcher @@ -48,6 +51,12 @@ class TrueSheetView(private val reactContext: ThemedReactContext) : var initialDetentIndex: Int = -1 var initialDetentAnimated: Boolean = true + var stateWrapper: StateWrapper? = null + + // Track last dimensions to avoid unnecessary state updates + private var lastContainerWidth: Int = 0 + private var lastContainerHeight: Int = 0 + /** * Tracks if initial presentation has been handled */ @@ -85,10 +94,6 @@ class TrueSheetView(private val reactContext: ThemedReactContext) : TrueSheetModule.registerView(this, id) } - override fun onAttachedToWindow() { - super.onAttachedToWindow() - } - override fun onDetachedFromWindow() { super.onDetachedFromWindow() onDropInstance() @@ -270,6 +275,10 @@ class TrueSheetView(private val reactContext: ThemedReactContext) : ) } + override fun viewControllerDidChangeSize(width: Int, height: Int) { + updateState(width, height) + } + // ==================== Property Setters (forward to controller) ==================== fun setMaxHeight(height: Int) { @@ -325,6 +334,33 @@ class TrueSheetView(private val reactContext: ThemedReactContext) : viewController.edgeToEdgeFullScreen = edgeToEdgeFullScreen } + // ==================== State Management ==================== + + /** + * Update state with container dimensions. + * Called when the dialog size changes. + */ + fun updateState(width: Int, height: Int) { + // Skip if dimensions haven't changed + if (width == lastContainerWidth && height == lastContainerHeight) { + return + } + + // Store new dimensions + lastContainerWidth = width + lastContainerHeight = height + + val sw = stateWrapper ?: return + + val realWidth = width.toFloat().pxToDp() + val realHeight = height.toFloat().pxToDp() + + val newStateData = WritableNativeMap() + newStateData.putDouble("containerWidth", realWidth.toDouble()) + newStateData.putDouble("containerHeight", realHeight.toDouble()) + sw.updateState(newStateData) + } + /** * Presents the sheet at the given detent index. * diff --git a/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt b/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt index a4a452f..d54e0cb 100644 --- a/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +++ b/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt @@ -41,6 +41,7 @@ interface TrueSheetViewControllerDelegate { fun viewControllerDidDragChange(index: Int, position: Float) fun viewControllerDidDragEnd(index: Int, position: Float) fun viewControllerDidChangePosition(index: Int, position: Float, transitioning: Boolean) + fun viewControllerDidChangeSize(width: Int, height: Int) } /** @@ -65,9 +66,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) : private val jSTouchDispatcher = JSTouchDispatcher(this) private var jSPointerDispatcher: JSPointerDispatcher? = null - private var viewWidth = 0 - private var viewHeight = 0 - /** * Delegate for handling view controller events */ @@ -798,16 +796,13 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) : override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) - viewWidth = w - viewHeight = h - - // Update container state with new dimensions - containerView?.updateState(viewWidth, viewHeight) // Only proceed if size actually changed if (w == oldw && h == oldh) return - // Update screen height based on new dimensions + // Notify delegate about size change so host view can update state + delegate?.viewControllerDidChangeSize(w, h) + val oldMaxScreenHeight = maxScreenHeight maxScreenHeight = ScreenUtils.getScreenHeight(reactContext, edgeToEdgeEnabled) diff --git a/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt b/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt index da8f797..1bd9146 100644 --- a/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +++ b/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt @@ -4,6 +4,8 @@ import android.view.WindowManager import com.facebook.react.bridge.ReadableArray import com.facebook.react.module.annotations.ReactModule import com.facebook.react.uimanager.PixelUtil.dpToPx +import com.facebook.react.uimanager.ReactStylesDiffMap +import com.facebook.react.uimanager.StateWrapper import com.facebook.react.uimanager.ThemedReactContext import com.facebook.react.uimanager.UIManagerHelper import com.facebook.react.uimanager.ViewGroupManager @@ -43,6 +45,11 @@ class TrueSheetViewManager : view.eventDispatcher = dispatcher } + override fun updateState(view: TrueSheetView, props: ReactStylesDiffMap?, stateWrapper: StateWrapper?): Any? { + view.stateWrapper = stateWrapper + return null + } + override fun getDelegate(): ViewManagerDelegate = delegate /** diff --git a/android/src/main/jni/TrueSheetSpec.h b/android/src/main/jni/TrueSheetSpec.h index 7db55a8..9569f3d 100644 --- a/android/src/main/jni/TrueSheetSpec.h +++ b/android/src/main/jni/TrueSheetSpec.h @@ -3,7 +3,7 @@ #include #include #include -#include +#include namespace facebook { namespace react { diff --git a/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContainerViewComponentDescriptor.h b/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewComponentDescriptor.h similarity index 54% rename from common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContainerViewComponentDescriptor.h rename to common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewComponentDescriptor.h index d3db910..065f95a 100644 --- a/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContainerViewComponentDescriptor.h +++ b/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewComponentDescriptor.h @@ -1,20 +1,20 @@ #pragma once -#include +#include #include namespace facebook::react { /* - * Descriptor for component. + * Descriptor for component. */ -class TrueSheetContainerViewComponentDescriptor final - : public ConcreteComponentDescriptor { +class TrueSheetViewComponentDescriptor final + : public ConcreteComponentDescriptor { using ConcreteComponentDescriptor::ConcreteComponentDescriptor; void adopt(ShadowNode &shadowNode) const override { auto &concreteShadowNode = - static_cast(shadowNode); + static_cast(shadowNode); concreteShadowNode.adjustLayoutWithState(); ConcreteComponentDescriptor::adopt(shadowNode); diff --git a/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContainerViewShadowNode.cpp b/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.cpp similarity index 82% rename from common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContainerViewShadowNode.cpp rename to common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.cpp index 6e852ab..4689200 100644 --- a/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContainerViewShadowNode.cpp +++ b/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.cpp @@ -1,16 +1,16 @@ -#include "TrueSheetContainerViewShadowNode.h" +#include "TrueSheetViewShadowNode.h" #include namespace facebook::react { -extern const char TrueSheetContainerViewComponentName[] = "TrueSheetContainerView"; +extern const char TrueSheetViewComponentName[] = "TrueSheetView"; -void TrueSheetContainerViewShadowNode::adjustLayoutWithState() { +void TrueSheetViewShadowNode::adjustLayoutWithState() { ensureUnsealed(); auto state = std::static_pointer_cast< - const TrueSheetContainerViewShadowNode::ConcreteState>(getState()); + const TrueSheetViewShadowNode::ConcreteState>(getState()); auto stateData = state->getData(); // If container dimensions are set from native, override Yoga's dimensions diff --git a/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContainerViewShadowNode.h b/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.h similarity index 50% rename from common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContainerViewShadowNode.h rename to common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.h index d3b9a63..775e588 100644 --- a/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContainerViewShadowNode.h +++ b/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.h @@ -3,22 +3,22 @@ #include #include #include -#include +#include #include namespace facebook::react { -JSI_EXPORT extern const char TrueSheetContainerViewComponentName[]; +JSI_EXPORT extern const char TrueSheetViewComponentName[]; /* - * `ShadowNode` for component. + * `ShadowNode` for component. */ -class JSI_EXPORT TrueSheetContainerViewShadowNode final +class JSI_EXPORT TrueSheetViewShadowNode final : public ConcreteViewShadowNode< - TrueSheetContainerViewComponentName, - TrueSheetContainerViewProps, - TrueSheetContainerViewEventEmitter, - TrueSheetContainerViewState> { + TrueSheetViewComponentName, + TrueSheetViewProps, + TrueSheetViewEventEmitter, + TrueSheetViewState> { using ConcreteViewShadowNode::ConcreteViewShadowNode; public: diff --git a/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContainerViewState.cpp b/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.cpp similarity index 64% rename from common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContainerViewState.cpp rename to common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.cpp index ab3e0ee..8b62113 100644 --- a/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContainerViewState.cpp +++ b/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.cpp @@ -1,9 +1,9 @@ -#include "TrueSheetContainerViewState.h" +#include "TrueSheetViewState.h" namespace facebook::react { #ifdef ANDROID -folly::dynamic TrueSheetContainerViewState::getDynamic() const { +folly::dynamic TrueSheetViewState::getDynamic() const { return folly::dynamic::object("containerWidth", containerWidth)("containerHeight", containerHeight); } #endif diff --git a/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContainerViewState.h b/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.h similarity index 71% rename from common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContainerViewState.h rename to common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.h index be8b327..d96b692 100644 --- a/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContainerViewState.h +++ b/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.h @@ -11,18 +11,18 @@ namespace facebook::react { /* - * State for component. + * State for component. * Contains the container dimensions from native. */ -class TrueSheetContainerViewState final { +class TrueSheetViewState final { public: - using Shared = std::shared_ptr; + using Shared = std::shared_ptr; - TrueSheetContainerViewState() = default; + TrueSheetViewState() = default; #ifdef ANDROID - TrueSheetContainerViewState( - TrueSheetContainerViewState const &previousState, + TrueSheetViewState( + TrueSheetViewState const &previousState, folly::dynamic data) : containerWidth(static_cast(data["containerWidth"].getDouble())), containerHeight(static_cast(data["containerHeight"].getDouble())) {} diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 4f3d0c8..c749281 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -2646,7 +2646,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - RNTrueSheet (3.0.0-beta.5): + - RNTrueSheet (3.0.0-beta.6): - boost - DoubleConversion - fast_float @@ -3095,7 +3095,7 @@ SPEC CHECKSUMS: RNGestureHandler: e1cf8ef3f11045536eed6bd4f132b003ef5f9a5f RNReanimated: ac06da53579693ab451941ef89f5a55afeab0dd9 RNScreens: d821082c6dd1cb397cc0c98b026eeafaa68be479 - RNTrueSheet: 1666302304e0a1bc6075fac193dfb88bccae3099 + RNTrueSheet: 2ca9ea2c12271135b80eaa506794b9fa82ca0381 RNWorklets: ab618bf7d1c7fd2cb793b9f0f39c3e29274b3ebf SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 Yoga: 689c8e04277f3ad631e60fe2a08e41d411daf8eb diff --git a/ios/TrueSheetContainerView.mm b/ios/TrueSheetContainerView.mm index 2d0bd38..f1fd705 100644 --- a/ios/TrueSheetContainerView.mm +++ b/ios/TrueSheetContainerView.mm @@ -9,18 +9,15 @@ #ifdef RCT_NEW_ARCH_ENABLED #import "TrueSheetContainerView.h" +#import #import #import #import -#import -#import -#import #import "TrueSheetContentView.h" #import "TrueSheetFooterView.h" #import #import -#import using namespace facebook::react; @@ -30,8 +27,6 @@ using namespace facebook::react; @implementation TrueSheetContainerView { TrueSheetContentView *_contentView; TrueSheetFooterView *_footerView; - TrueSheetContainerViewShadowNode::ConcreteState::Shared _state; - CGFloat _lastContainerWidth; } + (ComponentDescriptorProvider)componentDescriptorProvider { @@ -47,7 +42,6 @@ using namespace facebook::react; _contentView = nil; _footerView = nil; - _lastContainerWidth = 0; } return self; } @@ -61,37 +55,9 @@ using namespace facebook::react; if (!CGRectEqualToRect(self.frame, parentBounds)) { self.frame = parentBounds; } - - // Update state with container width so Yoga can use it for children layout - [self updateStateIfNeeded]; } } -- (void)updateStateIfNeeded { - if (!self.superview) { - return; - } - - CGFloat containerWidth = self.superview.bounds.size.width; - if (containerWidth > 0 && fabs(containerWidth - _lastContainerWidth) > 0.5) { - _lastContainerWidth = containerWidth; - [self updateState]; - } -} - -- (void)updateState { - if (!_state) { - return; - } - - _state->updateState([=](TrueSheetContainerViewShadowNode::ConcreteState::Data const &oldData) - -> TrueSheetContainerViewShadowNode::ConcreteState::SharedData { - auto newData = oldData; - newData.containerWidth = static_cast(_lastContainerWidth); - return std::make_shared(newData); - }); -} - - (CGFloat)contentHeight { return _contentView ? _contentView.frame.size.height : 0; } @@ -153,19 +119,6 @@ using namespace facebook::react; [super updateProps:props oldProps:oldProps]; } -- (void)updateState:(const State::Shared &)state oldState:(const State::Shared &)oldState { - _state = std::static_pointer_cast(state); - - // Reset last width when state is updated to ensure we push the correct width - // This handles re-presentation of the sheet where state is recreated - _lastContainerWidth = 0; -} - -- (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask { - [super finalizeUpdates:updateMask]; - [self updateStateIfNeeded]; -} - #pragma mark - TrueSheetContentViewDelegate - (void)contentViewDidChangeSize:(CGSize)newSize { diff --git a/ios/TrueSheetView.mm b/ios/TrueSheetView.mm index f637445..3fe819e 100644 --- a/ios/TrueSheetView.mm +++ b/ios/TrueSheetView.mm @@ -27,16 +27,19 @@ #import "utils/LayoutUtil.h" #import "utils/WindowUtil.h" -#import #import #import #import +#import +#import +#import #import #import #import #import #import +#import using namespace facebook::react; @@ -47,6 +50,8 @@ using namespace facebook::react; TrueSheetContainerView *_containerView; TrueSheetViewController *_controller; RCTSurfaceTouchHandler *_touchHandler; + TrueSheetViewShadowNode::ConcreteState::Shared _state; + CGFloat _lastContainerWidth; NSInteger _initialDetentIndex; BOOL _fitScrollView; BOOL _initialDetentAnimated; @@ -66,6 +71,7 @@ using namespace facebook::react; _containerView = nil; + _lastContainerWidth = 0; _initialDetentIndex = -1; _initialDetentAnimated = YES; _fitScrollView = NO; @@ -238,6 +244,35 @@ using namespace facebook::react; _fitScrollView = newProps.fitScrollView; } +- (void)updateState:(const State::Shared &)state oldState:(const State::Shared &)oldState { + _state = std::static_pointer_cast(state); + [self updateStateIfNeeded]; +} + +- (void)updateStateIfNeeded { + if (!_state) { + return; + } + + CGFloat containerWidth = _controller.view.bounds.size.width; + + if (containerWidth <= 0) { + return; + } + + BOOL widthChanged = fabs(containerWidth - _lastContainerWidth) > 0.5; + + if (widthChanged) { + _lastContainerWidth = containerWidth; + _state->updateState([=](TrueSheetViewShadowNode::ConcreteState::Data const &oldData) + -> TrueSheetViewShadowNode::ConcreteState::SharedData { + auto newData = oldData; + newData.containerWidth = static_cast(containerWidth); + return std::make_shared(newData); + }); + } +} + - (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask { [super finalizeUpdates:updateMask]; @@ -292,10 +327,6 @@ using namespace facebook::react; // Ensure container is above background view [_controller.view bringSubviewToFront:_containerView]; - // Force layout pass immediately so container gets correct width on mount - // This pushes the width to Yoga before the sheet is presented - [_controller.view layoutIfNeeded]; - // Get initial content height from container CGFloat contentHeight = [_containerView contentHeight]; if (contentHeight > 0) { @@ -421,6 +452,10 @@ using namespace facebook::react; [OnPositionChangeEvent emit:_eventEmitter index:index position:position transitioning:transitioning]; } +- (void)viewControllerDidChangeSize:(CGSize)size { + [self updateStateIfNeeded]; +} + #pragma mark - Private Helpers - (UIViewController *)findPresentingViewController { diff --git a/ios/TrueSheetViewController.h b/ios/TrueSheetViewController.h index 73b372f..f2f8fd5 100644 --- a/ios/TrueSheetViewController.h +++ b/ios/TrueSheetViewController.h @@ -26,6 +26,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)viewControllerDidChangeDetent:(NSInteger)index position:(CGFloat)position; - (void)viewControllerDidDrag:(UIGestureRecognizerState)state index:(NSInteger)index position:(CGFloat)position; - (void)viewControllerDidChangePosition:(NSInteger)index position:(CGFloat)position transitioning:(BOOL)transitioning; +- (void)viewControllerDidChangeSize:(CGSize)size; @end diff --git a/ios/TrueSheetViewController.mm b/ios/TrueSheetViewController.mm index 48a6e6a..4a35f2e 100644 --- a/ios/TrueSheetViewController.mm +++ b/ios/TrueSheetViewController.mm @@ -164,6 +164,11 @@ completion:^(id context) { // After rotation completes [self setupSheetDetents]; + + // Notify delegate of size change for state update + if ([self.delegate respondsToSelector:@selector(viewControllerDidChangeSize:)]) { + [self.delegate viewControllerDidChangeSize:size]; + } }]; } diff --git a/package.json b/package.json index 6f89529..191738d 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "types": "./lib/typescript/src/index.d.ts", "exports": { ".": { - "source": "./src/index.tsx", + "source": "./src/index.ts", "types": "./lib/typescript/src/index.d.ts", "default": "./lib/module/index.js" }, @@ -38,7 +38,7 @@ "typecheck": "tsc", "lint": "eslint --fix \"**/*.{ts,tsx}\"", "format": "prettier --write \"**/*.{ts,tsx}\"", - "tidy": "yarn typecheck && yarn lint && yarn format && scripts/objc-lint.sh && scripts/ktlint.sh", + "tidy": "yarn typecheck && yarn lint && yarn format && scripts/objclint.sh && scripts/ktlint.sh", "clean": "scripts/clean.sh", "prepare": "bob build", "release": "yarn tidy && release-it --only-version", diff --git a/react-native.config.js b/react-native.config.js index eded423..e090603 100644 --- a/react-native.config.js +++ b/react-native.config.js @@ -3,7 +3,7 @@ module.exports = { platforms: { ios: {}, android: { - componentDescriptors: ['TrueSheetContainerViewComponentDescriptor'], + componentDescriptors: ['TrueSheetViewComponentDescriptor'], cmakeListsPath: '../android/src/main/jni/CMakeLists.txt', }, }, diff --git a/scripts/clean.sh b/scripts/clean.sh index 50ef198..84b2b22 100755 --- a/scripts/clean.sh +++ b/scripts/clean.sh @@ -1,16 +1,48 @@ #!/bin/bash -echo "[Installing dependencies]" -yarn +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +BOLD='\033[1m' +NC='\033[0m' # No Color -echo "[Cleaning android]" +step() { + echo -e "\n${BLUE}▶${NC} ${BOLD}$1${NC}" +} + +success() { + echo -e "${GREEN}✓${NC} $1" +} + +step "Installing dependencies" +yarn && success "Dependencies installed" + +step "Cleaning Android" cd example/android -./gradlew clean +./gradlew clean && success "Android cleaned" cd ../.. -echo "[Removing temp directories]" -del-cli android/build example/android/build example/android/app/build example/ios/build +step "Cleaning up Watchman" +watchman watch-del ./ ; watchman watch-project ./ +rm -rf $TMPDIR/metro-* +success "Watchman cache cleared" -echo "[Installing pods]" -npx pod-install example -bob build +step "Cleaning up Simulator cache" +# fixes "Unable to boot Simulator" error +rm -rf ~/Library/Developer/CoreSimulator/Caches +success "Simulator cache cleared" + +step "Removing temp directories" +del-cli android/build example/android/build example/android/app/build example/ios/build +success "Temp directories removed" + +step "Installing pods" +npx pod-install example && success "Pods installed" + +step "Building with bob" +bob build && success "Build complete" + +echo -e "\n${GREEN}${BOLD}All done!${NC}" diff --git a/scripts/objc-lint.sh b/scripts/objclint.sh similarity index 100% rename from scripts/objc-lint.sh rename to scripts/objclint.sh diff --git a/src/TrueSheet.tsx b/src/TrueSheet.tsx index eb16bc3..9155078 100644 --- a/src/TrueSheet.tsx +++ b/src/TrueSheet.tsx @@ -391,7 +391,6 @@ export class TrueSheet extends PureComponent { const styles = StyleSheet.create({ sheetView: { position: 'absolute', - width: '100%', zIndex: -9999, }, footer: { diff --git a/src/fabric/TrueSheetContainerViewNativeComponent.ts b/src/fabric/TrueSheetContainerViewNativeComponent.ts index 0c07dbe..5756756 100644 --- a/src/fabric/TrueSheetContainerViewNativeComponent.ts +++ b/src/fabric/TrueSheetContainerViewNativeComponent.ts @@ -5,6 +5,4 @@ export interface NativeProps extends ViewProps { // No props needed - container accesses props from parent TrueSheetView } -export default codegenNativeComponent('TrueSheetContainerView', { - interfaceOnly: true, -}); +export default codegenNativeComponent('TrueSheetContainerView'); diff --git a/src/fabric/TrueSheetViewNativeComponent.ts b/src/fabric/TrueSheetViewNativeComponent.ts index ceb3ad3..3688f69 100644 --- a/src/fabric/TrueSheetViewNativeComponent.ts +++ b/src/fabric/TrueSheetViewNativeComponent.ts @@ -56,4 +56,6 @@ export interface NativeProps extends ViewProps { onPositionChange?: DirectEventHandler; } -export default codegenNativeComponent('TrueSheetView'); +export default codegenNativeComponent('TrueSheetView', { + interfaceOnly: true, +});