Skip to content

Commit 5bc649e

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 f588899 commit 5bc649e

8 files changed

Lines changed: 69 additions & 32 deletions

File tree

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,22 @@ package com.swmansion.rnscreens.gamma.tabs.container
55
*
66
* - [USER] — direct native UI interaction (tab bar tap).
77
* - [PROGRAMMATIC_JS] — JS-initiated request delivered via the `navStateRequest` prop.
8+
* - [PROGRAMMATIC_NATIVE] — request initiated from the native side by a downstream library
9+
* integrating directly against [TabsContainer] (not produced by this library itself).
810
*
911
* The `implicit` origin defined on the public TS API is iOS-only at the moment;
1012
* Android does not currently produce it.
1113
*/
1214
enum class TabsActionOrigin {
1315
USER,
1416
PROGRAMMATIC_JS,
17+
PROGRAMMATIC_NATIVE,
1518
;
1619

1720
override fun toString(): String =
1821
when (this) {
1922
USER -> "user"
2023
PROGRAMMATIC_JS -> "programmatic-js"
24+
PROGRAMMATIC_NATIVE -> "programmatic-native"
2125
}
2226
}

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
default:

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: 9 additions & 7 deletions
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
/**
@@ -241,8 +243,8 @@ export interface TabsHostPropsBase {
241243
* @platform android, ios
242244
*/
243245
onTabSelected?:
244-
| ((event: NativeSyntheticEvent<TabSelectedEvent>) => void)
245-
| undefined;
246+
| ((event: NativeSyntheticEvent<TabSelectedEvent>) => void)
247+
| undefined;
246248

247249
/**
248250
* @summary
@@ -251,8 +253,8 @@ export interface TabsHostPropsBase {
251253
* @see {@link TabSelectionRejectedEvent}
252254
*/
253255
onTabSelectionRejected?:
254-
| ((event: NativeSyntheticEvent<TabSelectionRejectedEvent>) => void)
255-
| undefined;
256+
| ((event: NativeSyntheticEvent<TabSelectionRejectedEvent>) => void)
257+
| undefined;
256258

257259
/**
258260
* @summary
@@ -262,8 +264,8 @@ export interface TabsHostPropsBase {
262264
* @see {@link TabSelectionPreventedEvent}
263265
*/
264266
onTabSelectionPrevented?:
265-
| ((event: NativeSyntheticEvent<TabSelectionPreventedEvent>) => void)
266-
| undefined;
267+
| ((event: NativeSyntheticEvent<TabSelectionPreventedEvent>) => void)
268+
| undefined;
267269
}
268270

269271
export interface TabsHostProps extends TabsHostPropsBase {

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)