Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,22 @@ package com.swmansion.rnscreens.gamma.tabs.container
*
* - [USER] — direct native UI interaction (tab bar tap).
* - [PROGRAMMATIC_JS] — JS-initiated request delivered via the `navStateRequest` prop.
* - [PROGRAMMATIC_NATIVE] — request initiated from the native side by a downstream library
* integrating directly against [TabsContainer] (not produced by this library itself).
*
* The `implicit` origin defined on the public TS API is iOS-only at the moment;
* Android does not currently produce it.
*/
enum class TabsActionOrigin {
USER,
PROGRAMMATIC_JS,
PROGRAMMATIC_NATIVE,
;

override fun toString(): String =
when (this) {
USER -> "user"
PROGRAMMATIC_JS -> "programmatic-js"
PROGRAMMATIC_NATIVE -> "programmatic-native"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import com.swmansion.rnscreens.utils.RNSLog
import kotlin.properties.Delegates

@SuppressLint("ViewConstructor") // Created only by us. Should never be restored.
internal class TabsContainer(
class TabsContainer internal constructor(
private val context: Context,
private val delegate: TabsContainerDelegate,
) : FrameLayout(context),
Comment thread
kkafar marked this conversation as resolved.
Expand Down Expand Up @@ -196,8 +196,8 @@ internal class TabsContainer(
return insets
}

internal fun setContainerOperation(op: TabsContainerOp) {
pendingOperation = op
fun setPendingNavigationStateUpdate(request: TabsNavStateUpdateRequest) {
pendingOperation = TabSelectOp(request)
invalidationFlags.isSelectedTabInvalidated = true
}

Expand All @@ -224,7 +224,7 @@ internal class TabsContainer(
invalidationFlags.invalidateAll()
}

internal fun performContainerUpdateIfNeeded() {
fun performContainerUpdateIfNeeded() {
if (invalidationFlags.any() && isAttachedToWindow) {
performContainerUpdate()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.UIManagerHelper
import com.swmansion.rnscreens.gamma.common.colorscheme.ColorScheme
import com.swmansion.rnscreens.gamma.helpers.getFabricUIManagerNotNull
import com.swmansion.rnscreens.gamma.tabs.container.TabSelectOp
import com.swmansion.rnscreens.gamma.tabs.container.TabsActionOrigin
import com.swmansion.rnscreens.gamma.tabs.container.TabsContainer
import com.swmansion.rnscreens.gamma.tabs.container.TabsContainerDelegate
Expand Down Expand Up @@ -113,7 +112,7 @@ class TabsHost(

internal fun updateJSNavStateRequest(navStateRequest: TabsNavStateUpdateRequest) {
jsNavStateRequest = navStateRequest
container.setContainerOperation(TabSelectOp(navStateRequest.copy()))
container.setPendingNavigationStateUpdate(navStateRequest.copy())
}

private val layoutCallback =
Expand Down
2 changes: 2 additions & 0 deletions ios/conversion/RNSConversions-Tabs.mm
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,8 @@ UITabBarControllerMode UITabBarControllerModeFromRNSTabBarControllerMode(RNSTabB
return User;
case RNSTabsActionOriginProgrammaticJs:
return ProgrammaticJs;
case RNSTabsActionOriginProgrammaticNative:
return ProgrammaticNative;
case RNSTabsActionOriginImplicit:
return Implicit;
default:
Expand Down
64 changes: 47 additions & 17 deletions ios/tabs/host/RNSTabsNavigationState.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,40 @@ NS_ASSUME_NONNULL_BEGIN

/**
* Origin (actor) that requested a tab transition. Mirrors the public `actionOrigin` event field.
*
* - [User] direct native UI interaction (tab bar tap, drag-and-drop).
* - [ProgrammaticJs] JS-initiated request delivered via the `navStateRequest` prop.
* - [Implicit] platform side effect not attributable to an explicit actor — UIKit changed the selection
* as a side effect of another operation (e.g. More navigation controller disappearing during a
* horizontal size class transition on iPad).
*/
typedef NS_ENUM(NSInteger, RNSTabsActionOrigin) {
/**
* Direct native UI interaction (tab bar tap, drag-and-drop).
*/
RNSTabsActionOriginUser = 0,
/**
* JS-initiated request delivered via the `navStateRequest` prop.
*/
RNSTabsActionOriginProgrammaticJs,
/**
* Request initiated from the native side by some actor, e.g. a downstream
* library integrating directly against `RNSTabBarController`.
*/
RNSTabsActionOriginProgrammaticNative,
/**
* Platform side effect not attributable to an explicit actor — UIKit changed the selection
* as a side effect of another operation (e.g. More navigation controller disappearing during a
* horizontal size class transition on iPad).
*/
RNSTabsActionOriginImplicit,
Comment thread
kkafar marked this conversation as resolved.
};

/** Reason why a navigation state update was rejected by the container. */
/**
* Reason why a navigation state update was rejected by the container.
*/
typedef NS_ENUM(NSInteger, RNSTabsNavigationStateRejectionReason) {
/** The update's provenance is based on a stale state. */
/**
* The update's provenance is based on a stale state.
*/
RNSTabsNavigationStateRejectionReasonStale = 0,
/** The requested tab is already selected. */
/**
* The requested tab is already selected.
*/
RNSTabsNavigationStateRejectionReasonRepeated,
};

Expand All @@ -36,11 +52,15 @@ typedef NS_ENUM(NSInteger, RNSTabsNavigationStateRejectionReason) {
*/
@interface RNSTabsNavigationState : NSObject

/** Screen key of the currently selected tab. */
/**
* Screen key of the currently selected tab.
*/
@property (nonatomic, strong, readonly, nonnull) NSString *selectedScreenKey;

/** Monotonically increasing number describing the generation of this state instance.
* Used for stale update detection. */
/**
* Monotonically increasing number describing the generation of this state instance.
* Used for stale update detection.
*/
@property (nonatomic, readonly) int provenance;

- (instancetype)initWithSelectedScreenKey:(nonnull NSString *)selectedScreenKey provenance:(int)provenance;
Expand Down Expand Up @@ -76,16 +96,26 @@ typedef NS_ENUM(NSInteger, RNSTabsNavigationStateRejectionReason) {

@end

/** Bundles a navigation state change together with metadata about the selection context. */
/**
* Bundles a navigation state change together with metadata about the selection context.
*/
@interface RNSTabsNavigationStateUpdateContext : NSObject

/** The navigation state after the change. */
/**
* The navigation state after the change.
*/
@property (nonatomic, readonly, strong, nonnull) RNSTabsNavigationState *navState;
/** Whether the same tab that was already selected has been selected again. */
/**
* Whether the same tab that was already selected has been selected again.
*/
@property (nonatomic, readonly) BOOL isRepeated;
/** Whether a special effect (e.g. scroll-to-top) was triggered by the selection. */
/**
* Whether a special effect (e.g. scroll-to-top) was triggered by the selection.
*/
@property (nonatomic, readonly) BOOL hasTriggeredSpecialEffect;
/** Origin (actor) that requested this transition. */
/**
* Origin (actor) that requested this transition.
*/
@property (nonatomic, readonly) RNSTabsActionOrigin actionOrigin;

- (instancetype)initWithNavState:(nonnull RNSTabsNavigationState *)navState
Expand Down
16 changes: 9 additions & 7 deletions src/components/tabs/host/TabsHost.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,13 @@ export type TabSelectedEvent = {
* @description
* - `user` — direct native UI interaction (e.g. tab bar tap, iOS tab drag-and-drop).
* - `programmatic-js` — JS-initiated request delivered via the `navStateRequest` prop.
* - `programmatic-native` — request initiated from the native side by an actor
* integrating directly against the native container.
* - `implicit` — platform side effect not attributable to an explicit actor
* (e.g. UIKit reshuffling the selection during a horizontal size-class transition on iPad).
* Currently only emitted on iOS.
*/
actionOrigin: 'user' | 'programmatic-js' | 'implicit';
actionOrigin: 'user' | 'programmatic-js' | 'programmatic-native' | 'implicit';
};

/**
Expand Down Expand Up @@ -241,8 +243,8 @@ export interface TabsHostPropsBase {
* @platform android, ios
*/
onTabSelected?:
| ((event: NativeSyntheticEvent<TabSelectedEvent>) => void)
| undefined;
| ((event: NativeSyntheticEvent<TabSelectedEvent>) => void)
| undefined;

/**
* @summary
Expand All @@ -251,8 +253,8 @@ export interface TabsHostPropsBase {
* @see {@link TabSelectionRejectedEvent}
*/
onTabSelectionRejected?:
| ((event: NativeSyntheticEvent<TabSelectionRejectedEvent>) => void)
| undefined;
| ((event: NativeSyntheticEvent<TabSelectionRejectedEvent>) => void)
| undefined;

/**
* @summary
Expand All @@ -262,8 +264,8 @@ export interface TabsHostPropsBase {
* @see {@link TabSelectionPreventedEvent}
*/
onTabSelectionPrevented?:
| ((event: NativeSyntheticEvent<TabSelectionPreventedEvent>) => void)
| undefined;
| ((event: NativeSyntheticEvent<TabSelectionPreventedEvent>) => void)
| undefined;
}

export interface TabsHostProps extends TabsHostPropsBase {
Expand Down
2 changes: 1 addition & 1 deletion src/fabric/tabs/TabsHostAndroidNativeComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type TabSelectedEvent = {
provenance: CT.Int32;
isRepeated: boolean;
hasTriggeredSpecialEffect: boolean;
actionOrigin: 'user' | 'programmatic-js' | 'implicit';
actionOrigin: 'user' | 'programmatic-js' | 'programmatic-native' | 'implicit';
};

type NavigationStateRequest = {
Expand Down
2 changes: 1 addition & 1 deletion src/fabric/tabs/TabsHostIOSNativeComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type TabSelectedEvent = Readonly<{
provenance: CT.Int32;
isRepeated: boolean;
hasTriggeredSpecialEffect: boolean;
actionOrigin: 'user' | 'programmatic-js' | 'implicit';
actionOrigin: 'user' | 'programmatic-js' | 'programmatic-native' | 'implicit';
}>;

type NavigationStateRequest = Readonly<{
Expand Down
Loading