Skip to content

Commit f09f976

Browse files
committed
feat(tabs): add 'programmatic-native' actionOrigin variant
Adds a fourth value to the public `TabSelectedEvent.actionOrigin` union: `'user' | 'programmatic-js' | 'programmatic-native' | 'implicit'`. The new origin is reserved for downstream libraries that integrate directly against `TabsContainer` (Android) or `RNSTabBarController` (iOS) — this library itself does not produce it. Plumbed end-to-end: - Public TS contract + both Android/iOS codegen specs. - iOS: `RNSTabsActionOriginProgrammaticNative` enum case and the matching arm in the conversion helper that maps the Obj-C enum to the codegen C++ scoped enum. - Android: `TabsActionOrigin.PROGRAMMATIC_NATIVE` + `toString()` mapping. Android visibility widening to make the new origin reachable from outside the library (iOS already exposes the equivalent surface): - `TabsContainer` class is now public; constructor stays `internal` because it takes the still-`internal` `TabsContainerDelegate`. Downstream is expected to obtain the container reference via its own integration path (TabsHost.container stays `private`). - `setContainerOperation(TabsContainerOp)` is replaced with a public `setPendingNavigationStateUpdate(TabsNavStateUpdateRequest)` that takes the request directly and wraps in `TabSelectOp` internally. Naming mirrors `RNSTabBarController.setPendingNavigationStateUpdate:`. - `performContainerUpdateIfNeeded()` is now public — the flush trigger downstream calls after setting a pending update. - Internal sealed-class machinery (`TabsContainerOp`, `TabSelectOp`) stays internal. Also reformats iOS doc comments in `RNSTabsNavigationState.h` to per-case style and the `/**\n * ...\n */` form consistently.
1 parent 0adb315 commit f09f976

8 files changed

Lines changed: 64 additions & 26 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,30 @@
11
package com.swmansion.rnscreens.gamma.tabs.container
22

33
import com.swmansion.rnscreens.gamma.tabs.container.TabsActionOrigin.PROGRAMMATIC_JS
4+
import com.swmansion.rnscreens.gamma.tabs.container.TabsActionOrigin.PROGRAMMATIC_NATIVE
45
import com.swmansion.rnscreens.gamma.tabs.container.TabsActionOrigin.USER
56

67
/**
78
* Origin (actor) that requested a tab transition. Mirrors the public `actionOrigin` event field.
89
*
910
* - [USER] — direct native UI interaction (tab bar tap).
1011
* - [PROGRAMMATIC_JS] — JS-initiated request delivered via the `navStateRequest` prop.
12+
* - [PROGRAMMATIC_NATIVE] — request initiated from the native side by a downstream library
13+
* integrating directly against [TabsContainer] (not produced by this library itself).
1114
*
1215
* The `implicit` origin defined on the public TS API is iOS-only at the moment;
1316
* Android does not currently produce it.
1417
*/
1518
enum class TabsActionOrigin {
1619
USER,
1720
PROGRAMMATIC_JS,
21+
PROGRAMMATIC_NATIVE,
1822
;
1923

2024
override fun toString(): String =
2125
when (this) {
2226
USER -> "user"
2327
PROGRAMMATIC_JS -> "programmatic-js"
28+
PROGRAMMATIC_NATIVE -> "programmatic-native"
2429
}
2530
}

android/src/main/java/com/swmansion/rnscreens/gamma/tabs/container/TabsContainer.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import com.swmansion.rnscreens.utils.RNSLog
3737
import kotlin.properties.Delegates
3838

3939
@SuppressLint("ViewConstructor") // Created only by us. Should never be restored.
40-
internal class TabsContainer(
40+
class TabsContainer internal constructor(
4141
private val context: Context,
4242
private val delegate: TabsContainerDelegate,
4343
) : FrameLayout(context),
@@ -196,8 +196,8 @@ internal class TabsContainer(
196196
return insets
197197
}
198198

199-
internal fun setContainerOperation(op: TabsContainerOp) {
200-
pendingOperation = op
199+
fun setPendingNavigationStateUpdate(request: TabsNavStateUpdateRequest) {
200+
pendingOperation = TabSelectOp(request)
201201
invalidationFlags.isSelectedTabInvalidated = true
202202
}
203203

@@ -224,7 +224,7 @@ internal class TabsContainer(
224224
invalidationFlags.invalidateAll()
225225
}
226226

227-
internal fun performContainerUpdateIfNeeded() {
227+
fun performContainerUpdateIfNeeded() {
228228
if (invalidationFlags.any() && isAttachedToWindow) {
229229
performContainerUpdate()
230230
}

android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/TabsHost.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import com.facebook.react.uimanager.ThemedReactContext
1212
import com.facebook.react.uimanager.UIManagerHelper
1313
import com.swmansion.rnscreens.gamma.common.colorscheme.ColorScheme
1414
import com.swmansion.rnscreens.gamma.helpers.getFabricUIManagerNotNull
15-
import com.swmansion.rnscreens.gamma.tabs.container.TabSelectOp
1615
import com.swmansion.rnscreens.gamma.tabs.container.TabsActionOrigin
1716
import com.swmansion.rnscreens.gamma.tabs.container.TabsContainer
1817
import com.swmansion.rnscreens.gamma.tabs.container.TabsContainerDelegate
@@ -113,7 +112,7 @@ class TabsHost(
113112

114113
internal fun updateJSNavStateRequest(navStateRequest: TabsNavStateUpdateRequest) {
115114
jsNavStateRequest = navStateRequest
116-
container.setContainerOperation(TabSelectOp(navStateRequest.copy()))
115+
container.setPendingNavigationStateUpdate(navStateRequest.copy())
117116
}
118117

119118
private val layoutCallback =

ios/conversion/RNSConversions-Tabs.mm

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,8 @@ UITabBarControllerMode UITabBarControllerModeFromRNSTabBarControllerMode(RNSTabB
242242
return User;
243243
case RNSTabsActionOriginProgrammaticJs:
244244
return ProgrammaticJs;
245+
case RNSTabsActionOriginProgrammaticNative:
246+
return ProgrammaticNative;
245247
case RNSTabsActionOriginImplicit:
246248
return Implicit;
247249
}

ios/tabs/host/RNSTabsNavigationState.h

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,40 @@ NS_ASSUME_NONNULL_BEGIN
66

77
/**
88
* Origin (actor) that requested a tab transition. Mirrors the public `actionOrigin` event field.
9-
*
10-
* - [User] direct native UI interaction (tab bar tap, drag-and-drop).
11-
* - [ProgrammaticJs] JS-initiated request delivered via the `navStateRequest` prop.
12-
* - [Implicit] platform side effect not attributable to an explicit actor — UIKit changed the selection
13-
* as a side effect of another operation (e.g. More navigation controller disappearing during a
14-
* horizontal size class transition on iPad).
159
*/
1610
typedef NS_ENUM(NSInteger, RNSTabsActionOrigin) {
11+
/**
12+
* Direct native UI interaction (tab bar tap, drag-and-drop).
13+
*/
1714
RNSTabsActionOriginUser = 0,
15+
/**
16+
* JS-initiated request delivered via the `navStateRequest` prop.
17+
*/
1818
RNSTabsActionOriginProgrammaticJs,
19+
/**
20+
* Request initiated from the native side by some actor, e.g. a downstream
21+
* library integrating directly against `RNSTabBarController`.
22+
*/
23+
RNSTabsActionOriginProgrammaticNative,
24+
/**
25+
* Platform side effect not attributable to an explicit actor — UIKit changed the selection
26+
* as a side effect of another operation (e.g. More navigation controller disappearing during a
27+
* horizontal size class transition on iPad).
28+
*/
1929
RNSTabsActionOriginImplicit,
2030
};
2131

22-
/** Reason why a navigation state update was rejected by the container. */
32+
/**
33+
* Reason why a navigation state update was rejected by the container.
34+
*/
2335
typedef NS_ENUM(NSInteger, RNSTabsNavigationStateRejectionReason) {
24-
/** The update's provenance is based on a stale state. */
36+
/**
37+
* The update's provenance is based on a stale state.
38+
*/
2539
RNSTabsNavigationStateRejectionReasonStale = 0,
26-
/** The requested tab is already selected. */
40+
/**
41+
* The requested tab is already selected.
42+
*/
2743
RNSTabsNavigationStateRejectionReasonRepeated,
2844
};
2945

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

39-
/** Screen key of the currently selected tab. */
55+
/**
56+
* Screen key of the currently selected tab.
57+
*/
4058
@property (nonatomic, strong, readonly, nonnull) NSString *selectedScreenKey;
4159

42-
/** Monotonically increasing number describing the generation of this state instance.
43-
* Used for stale update detection. */
60+
/**
61+
* Monotonically increasing number describing the generation of this state instance.
62+
* Used for stale update detection.
63+
*/
4464
@property (nonatomic, readonly) int provenance;
4565

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

7797
@end
7898

79-
/** Bundles a navigation state change together with metadata about the selection context. */
99+
/**
100+
* Bundles a navigation state change together with metadata about the selection context.
101+
*/
80102
@interface RNSTabsNavigationStateUpdateContext : NSObject
81103

82-
/** The navigation state after the change. */
104+
/**
105+
* The navigation state after the change.
106+
*/
83107
@property (nonatomic, readonly, strong, nonnull) RNSTabsNavigationState *navState;
84-
/** Whether the same tab that was already selected has been selected again. */
108+
/**
109+
* Whether the same tab that was already selected has been selected again.
110+
*/
85111
@property (nonatomic, readonly) BOOL isRepeated;
86-
/** Whether a special effect (e.g. scroll-to-top) was triggered by the selection. */
112+
/**
113+
* Whether a special effect (e.g. scroll-to-top) was triggered by the selection.
114+
*/
87115
@property (nonatomic, readonly) BOOL hasTriggeredSpecialEffect;
88-
/** Origin (actor) that requested this transition. */
116+
/**
117+
* Origin (actor) that requested this transition.
118+
*/
89119
@property (nonatomic, readonly) RNSTabsActionOrigin actionOrigin;
90120

91121
- (instancetype)initWithNavState:(nonnull RNSTabsNavigationState *)navState

src/components/tabs/host/TabsHost.types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,13 @@ export type TabSelectedEvent = {
6767
* @description
6868
* - `'user'` — direct native UI interaction (e.g. tab bar tap, iOS tab drag-and-drop).
6969
* - `'programmatic-js'` — JS-initiated request delivered via the `navStateRequest` prop.
70+
* - `'programmatic-native'` — request initiated from the native side by an actor
71+
* integrating directly against the native container.
7072
* - `'implicit'` — platform side effect not attributable to an explicit actor
7173
* (e.g. UIKit reshuffling the selection during a horizontal size-class transition on iPad).
7274
* Currently only emitted on iOS.
7375
*/
74-
actionOrigin: 'user' | 'programmatic-js' | 'implicit';
76+
actionOrigin: 'user' | 'programmatic-js' | 'programmatic-native' | 'implicit';
7577
};
7678

7779
/**

src/fabric/tabs/TabsHostAndroidNativeComponent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ type TabSelectedEvent = {
1010
provenance: CT.Int32;
1111
isRepeated: boolean;
1212
hasTriggeredSpecialEffect: boolean;
13-
actionOrigin: 'user' | 'programmatic-js' | 'implicit';
13+
actionOrigin: 'user' | 'programmatic-js' | 'programmatic-native' | 'implicit';
1414
};
1515

1616
type NavigationStateRequest = {

src/fabric/tabs/TabsHostIOSNativeComponent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ type TabSelectedEvent = Readonly<{
1010
provenance: CT.Int32;
1111
isRepeated: boolean;
1212
hasTriggeredSpecialEffect: boolean;
13-
actionOrigin: 'user' | 'programmatic-js' | 'implicit';
13+
actionOrigin: 'user' | 'programmatic-js' | 'programmatic-native' | 'implicit';
1414
}>;
1515

1616
type NavigationStateRequest = Readonly<{

0 commit comments

Comments
 (0)