feat: move Fabric state wrapper to host view (#230)

* feat: move state wrapper from container view to host view

* fix(ios): call updateStateIfNeeded on every state update

* fix(ios): handle device rotation and only track width changes

* feat(android): align state update flow with iOS

* chore: improve clean script with colors
This commit is contained in:
Jovanni Lo 2025-11-26 03:32:22 +08:00 committed by GitHub
parent 64cf0531ab
commit 248b64de25
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 308 additions and 169 deletions

135
AGENTS.md
View File

@ -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)
```

View File

@ -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)
*/

View File

@ -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<TrueSheetContainerView>()
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"
}

View File

@ -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.
*

View File

@ -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)

View File

@ -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<TrueSheetView> = delegate
/**

View File

@ -3,7 +3,7 @@
#include <ReactCommon/JavaTurboModule.h>
#include <ReactCommon/TurboModule.h>
#include <jsi/jsi.h>
#include <react/renderer/components/TrueSheetSpec/TrueSheetContainerViewComponentDescriptor.h>
#include <react/renderer/components/TrueSheetSpec/TrueSheetViewComponentDescriptor.h>
namespace facebook {
namespace react {

View File

@ -1,20 +1,20 @@
#pragma once
#include <react/renderer/components/TrueSheetSpec/TrueSheetContainerViewShadowNode.h>
#include <react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
namespace facebook::react {
/*
* Descriptor for <TrueSheetContainerView> component.
* Descriptor for <TrueSheetView> component.
*/
class TrueSheetContainerViewComponentDescriptor final
: public ConcreteComponentDescriptor<TrueSheetContainerViewShadowNode> {
class TrueSheetViewComponentDescriptor final
: public ConcreteComponentDescriptor<TrueSheetViewShadowNode> {
using ConcreteComponentDescriptor::ConcreteComponentDescriptor;
void adopt(ShadowNode &shadowNode) const override {
auto &concreteShadowNode =
static_cast<TrueSheetContainerViewShadowNode &>(shadowNode);
static_cast<TrueSheetViewShadowNode &>(shadowNode);
concreteShadowNode.adjustLayoutWithState();
ConcreteComponentDescriptor::adopt(shadowNode);

View File

@ -1,16 +1,16 @@
#include "TrueSheetContainerViewShadowNode.h"
#include "TrueSheetViewShadowNode.h"
#include <yoga/style/StyleSizeLength.h>
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

View File

@ -3,22 +3,22 @@
#include <jsi/jsi.h>
#include <react/renderer/components/TrueSheetSpec/EventEmitters.h>
#include <react/renderer/components/TrueSheetSpec/Props.h>
#include <react/renderer/components/TrueSheetSpec/TrueSheetContainerViewState.h>
#include <react/renderer/components/TrueSheetSpec/TrueSheetViewState.h>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
namespace facebook::react {
JSI_EXPORT extern const char TrueSheetContainerViewComponentName[];
JSI_EXPORT extern const char TrueSheetViewComponentName[];
/*
* `ShadowNode` for <TrueSheetContainerView> component.
* `ShadowNode` for <TrueSheetView> 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:

View File

@ -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

View File

@ -11,18 +11,18 @@
namespace facebook::react {
/*
* State for <TrueSheetContainerView> component.
* State for <TrueSheetView> component.
* Contains the container dimensions from native.
*/
class TrueSheetContainerViewState final {
class TrueSheetViewState final {
public:
using Shared = std::shared_ptr<const TrueSheetContainerViewState>;
using Shared = std::shared_ptr<const TrueSheetViewState>;
TrueSheetContainerViewState() = default;
TrueSheetViewState() = default;
#ifdef ANDROID
TrueSheetContainerViewState(
TrueSheetContainerViewState const &previousState,
TrueSheetViewState(
TrueSheetViewState const &previousState,
folly::dynamic data)
: containerWidth(static_cast<float>(data["containerWidth"].getDouble())),
containerHeight(static_cast<float>(data["containerHeight"].getDouble())) {}

View File

@ -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

View File

@ -9,18 +9,15 @@
#ifdef RCT_NEW_ARCH_ENABLED
#import "TrueSheetContainerView.h"
#import <react/renderer/components/TrueSheetSpec/ComponentDescriptors.h>
#import <react/renderer/components/TrueSheetSpec/EventEmitters.h>
#import <react/renderer/components/TrueSheetSpec/Props.h>
#import <react/renderer/components/TrueSheetSpec/RCTComponentViewHelpers.h>
#import <react/renderer/components/TrueSheetSpec/TrueSheetContainerViewComponentDescriptor.h>
#import <react/renderer/components/TrueSheetSpec/TrueSheetContainerViewShadowNode.h>
#import <react/renderer/components/TrueSheetSpec/TrueSheetContainerViewState.h>
#import "TrueSheetContentView.h"
#import "TrueSheetFooterView.h"
#import <React/RCTConversions.h>
#import <React/RCTLog.h>
#import <react/renderer/core/State.h>
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<float>(_lastContainerWidth);
return std::make_shared<TrueSheetContainerViewShadowNode::ConcreteState::Data const>(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<TrueSheetContainerViewShadowNode::ConcreteState const>(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 {

View File

@ -27,16 +27,19 @@
#import "utils/LayoutUtil.h"
#import "utils/WindowUtil.h"
#import <react/renderer/components/TrueSheetSpec/ComponentDescriptors.h>
#import <react/renderer/components/TrueSheetSpec/EventEmitters.h>
#import <react/renderer/components/TrueSheetSpec/Props.h>
#import <react/renderer/components/TrueSheetSpec/RCTComponentViewHelpers.h>
#import <react/renderer/components/TrueSheetSpec/TrueSheetViewComponentDescriptor.h>
#import <react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.h>
#import <react/renderer/components/TrueSheetSpec/TrueSheetViewState.h>
#import <React/RCTConversions.h>
#import <React/RCTFabricComponentsPlugins.h>
#import <React/RCTLog.h>
#import <React/RCTSurfaceTouchHandler.h>
#import <React/RCTUtils.h>
#import <react/renderer/core/State.h>
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<TrueSheetViewShadowNode::ConcreteState const>(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<float>(containerWidth);
return std::make_shared<TrueSheetViewShadowNode::ConcreteState::Data const>(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 {

View File

@ -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

View File

@ -164,6 +164,11 @@
completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
// After rotation completes
[self setupSheetDetents];
// Notify delegate of size change for state update
if ([self.delegate respondsToSelector:@selector(viewControllerDidChangeSize:)]) {
[self.delegate viewControllerDidChangeSize:size];
}
}];
}

View File

@ -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",

View File

@ -3,7 +3,7 @@ module.exports = {
platforms: {
ios: {},
android: {
componentDescriptors: ['TrueSheetContainerViewComponentDescriptor'],
componentDescriptors: ['TrueSheetViewComponentDescriptor'],
cmakeListsPath: '../android/src/main/jni/CMakeLists.txt',
},
},

View File

@ -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}"

View File

@ -391,7 +391,6 @@ export class TrueSheet extends PureComponent<TrueSheetProps, TrueSheetState> {
const styles = StyleSheet.create({
sheetView: {
position: 'absolute',
width: '100%',
zIndex: -9999,
},
footer: {

View File

@ -5,6 +5,4 @@ export interface NativeProps extends ViewProps {
// No props needed - container accesses props from parent TrueSheetView
}
export default codegenNativeComponent<NativeProps>('TrueSheetContainerView', {
interfaceOnly: true,
});
export default codegenNativeComponent<NativeProps>('TrueSheetContainerView');

View File

@ -56,4 +56,6 @@ export interface NativeProps extends ViewProps {
onPositionChange?: DirectEventHandler<PositionChangeEventPayload>;
}
export default codegenNativeComponent<NativeProps>('TrueSheetView');
export default codegenNativeComponent<NativeProps>('TrueSheetView', {
interfaceOnly: true,
});