@sigmela/router 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +346 -0
  3. package/lib/module/Navigation.js +74 -0
  4. package/lib/module/NavigationStack.js +72 -0
  5. package/lib/module/Router.js +571 -0
  6. package/lib/module/RouterContext.js +33 -0
  7. package/lib/module/ScreenStackItem.js +61 -0
  8. package/lib/module/StackRenderer.js +29 -0
  9. package/lib/module/TabBar/RenderTabBar.js +122 -0
  10. package/lib/module/TabBar/TabBar.js +74 -0
  11. package/lib/module/TabBar/TabBarContext.js +4 -0
  12. package/lib/module/TabBar/useTabBar.js +11 -0
  13. package/lib/module/createController.js +5 -0
  14. package/lib/module/index.js +14 -0
  15. package/lib/module/package.json +1 -0
  16. package/lib/module/types.js +3 -0
  17. package/lib/typescript/package.json +1 -0
  18. package/lib/typescript/src/Navigation.d.ts +8 -0
  19. package/lib/typescript/src/NavigationStack.d.ts +30 -0
  20. package/lib/typescript/src/Router.d.ts +70 -0
  21. package/lib/typescript/src/RouterContext.d.ts +19 -0
  22. package/lib/typescript/src/ScreenStackItem.d.ts +12 -0
  23. package/lib/typescript/src/StackRenderer.d.ts +6 -0
  24. package/lib/typescript/src/TabBar/RenderTabBar.d.ts +8 -0
  25. package/lib/typescript/src/TabBar/TabBar.d.ts +43 -0
  26. package/lib/typescript/src/TabBar/TabBarContext.d.ts +3 -0
  27. package/lib/typescript/src/TabBar/useTabBar.d.ts +2 -0
  28. package/lib/typescript/src/createController.d.ts +14 -0
  29. package/lib/typescript/src/index.d.ts +15 -0
  30. package/lib/typescript/src/types.d.ts +244 -0
  31. package/package.json +166 -0
  32. package/src/Navigation.tsx +102 -0
  33. package/src/NavigationStack.ts +106 -0
  34. package/src/Router.ts +684 -0
  35. package/src/RouterContext.tsx +58 -0
  36. package/src/ScreenStackItem.tsx +64 -0
  37. package/src/StackRenderer.tsx +41 -0
  38. package/src/TabBar/RenderTabBar.tsx +154 -0
  39. package/src/TabBar/TabBar.ts +106 -0
  40. package/src/TabBar/TabBarContext.ts +4 -0
  41. package/src/TabBar/useTabBar.ts +10 -0
  42. package/src/createController.ts +27 -0
  43. package/src/index.ts +24 -0
  44. package/src/types.ts +272 -0
@@ -0,0 +1,244 @@
1
+ import type { ColorValue, StyleProp, ViewStyle, TextStyle } from 'react-native';
2
+ import type { BottomTabsScreenAppearance, BottomTabsScreenProps, ScreenProps as RNSScreenProps, ScreenStackHeaderConfigProps, TabBarItemLabelVisibilityMode, TabBarMinimizeBehavior } from 'react-native-screens';
3
+ export type TabItem = Omit<BottomTabsScreenProps, 'isFocused' | 'children'>;
4
+ export type NavigationState<Route extends TabItem> = {
5
+ index: number;
6
+ routes: Route[];
7
+ };
8
+ export type Scope = 'global' | 'tab' | 'root';
9
+ export type ScreenOptions = Partial<RNSScreenProps> & {
10
+ header?: ScreenStackHeaderConfigProps;
11
+ };
12
+ export type HistoryItem = {
13
+ key: string;
14
+ scope: Scope;
15
+ routeId: string;
16
+ component: React.ComponentType<any>;
17
+ options?: ScreenOptions;
18
+ params?: Record<string, unknown>;
19
+ query?: Record<string, unknown>;
20
+ passProps?: any;
21
+ tabIndex?: number;
22
+ stackId?: string;
23
+ pattern?: string;
24
+ path?: string;
25
+ };
26
+ export type VisibleRoute = {
27
+ routeId: string;
28
+ stackId?: string;
29
+ tabIndex?: number;
30
+ scope: Scope;
31
+ path?: string;
32
+ params?: Record<string, unknown>;
33
+ query?: Record<string, unknown>;
34
+ } | null;
35
+ export type CompiledRoute = {
36
+ routeId: string;
37
+ scope: Scope;
38
+ path: string;
39
+ match: (path: string) => false | {
40
+ params: Record<string, any>;
41
+ };
42
+ component: React.ComponentType<any>;
43
+ controller?: import('./createController').Controller<any, any>;
44
+ options?: ScreenOptions;
45
+ tabIndex?: number;
46
+ stackId?: string;
47
+ };
48
+ export type TabBarConfig = {
49
+ /**
50
+ * @summary Specifies the minimize behavior for the tab bar.
51
+ *
52
+ * Available starting from iOS 26.
53
+ *
54
+ * The following values are currently supported:
55
+ *
56
+ * - `automatic` - resolves to the system default minimize behavior
57
+ * - `never` - the tab bar does not minimize
58
+ * - `onScrollDown` - the tab bar minimizes when scrolling down and
59
+ * expands when scrolling back up
60
+ * - `onScrollUp` - the tab bar minimizes when scrolling up and expands
61
+ * when scrolling back down
62
+ *
63
+ * The supported values correspond to the official UIKit documentation:
64
+ * @see {@link https://developer.apple.com/documentation/uikit/uitabbarcontroller/minimizebehavior|UITabBarController.MinimizeBehavior}
65
+ *
66
+ * @default Defaults to `automatic`.
67
+ *
68
+ * @platform ios
69
+ * @supported iOS 26 or higher
70
+ */
71
+ tabBarMinimizeBehavior?: TabBarMinimizeBehavior;
72
+ };
73
+ export interface NavigationAppearance {
74
+ tabBar?: {
75
+ /**
76
+ * @summary Specifies the standard tab bar appearance.
77
+ *
78
+ * Allows to customize the appearance depending on the tab bar item layout (stacked,
79
+ * inline, compact inline) and state (normal, selected, focused, disabled).
80
+ *
81
+ * @platform ios
82
+ */
83
+ standardAppearance?: BottomTabsScreenAppearance;
84
+ /**
85
+ * @summary Specifies the tab bar appearace when edge of scrollable content aligns
86
+ * with the edge of the tab bar.
87
+ *
88
+ * Allows to customize the appearance depending on the tab bar item layout (stacked,
89
+ * inline, compact inline) and state (normal, selected, focused, disabled).
90
+ *
91
+ * If this property is `undefined`, UIKit uses `standardAppearance`, modified to
92
+ * have a transparent background.
93
+ *
94
+ * @platform ios
95
+ */
96
+ scrollEdgeAppearance?: BottomTabsScreenAppearance;
97
+ /**
98
+ * @summary Specifies the background color for the entire tab bar.
99
+ *
100
+ * @platform android
101
+ */
102
+ backgroundColor?: ColorValue;
103
+ tabBarItemStyle?: {
104
+ /**
105
+ * @summary Specifies the font family used for the title of each tab bar item.
106
+ *
107
+ * @platform android
108
+ */
109
+ titleFontFamily?: TextStyle['fontFamily'];
110
+ /**
111
+ * @summary Specifies the font size used for the title of each tab bar item.
112
+ *
113
+ * The size is represented in scale-independent pixels (sp).
114
+ *
115
+ * @platform android
116
+ */
117
+ titleFontSize?: TextStyle['fontSize'];
118
+ /**
119
+ * @summary Specifies the font size used for the title of each tab bar item in active state.
120
+ *
121
+ * The size is represented in scale-independent pixels (sp).
122
+ *
123
+ * @platform android
124
+ */
125
+ titleFontSizeActive?: TextStyle['fontSize'];
126
+ /**
127
+ * @summary Specifies the font weight used for the title of each tab bar item.
128
+ *
129
+ * @platform android
130
+ */
131
+ titleFontWeight?: TextStyle['fontWeight'];
132
+ /**
133
+ * @summary Specifies the font style used for the title of each tab bar item.
134
+ *
135
+ * @platform android
136
+ */
137
+ titleFontStyle?: TextStyle['fontStyle'];
138
+ /**
139
+ * @summary Specifies the font color used for the title of each tab bar item.
140
+ *
141
+ * @platform android
142
+ */
143
+ titleFontColor?: TextStyle['color'];
144
+ /**
145
+ * @summary Specifies the font color used for the title of each tab bar item in active state.
146
+ *
147
+ * If not provided, `tabBarItemTitleFontColor` is used.
148
+ *
149
+ * @platform android
150
+ */
151
+ titleFontColorActive?: TextStyle['color'];
152
+ /**
153
+ * @summary Specifies the icon color for each tab bar item.
154
+ *
155
+ * @platform android
156
+ */
157
+ iconColor?: ColorValue;
158
+ /**
159
+ * @summary Specifies the icon color for each tab bar item in active state.
160
+ *
161
+ * If not provided, `tabBarItemIconColor` is used.
162
+ *
163
+ * @platform android
164
+ */
165
+ iconColorActive?: ColorValue;
166
+ /**
167
+ * @summary Specifies the background color of the active indicator.
168
+ *
169
+ * @platform android
170
+ */
171
+ activeIndicatorColor?: ColorValue;
172
+ /**
173
+ * @summary Specifies if the active indicator should be used.
174
+ *
175
+ * @default true
176
+ *
177
+ * @platform android
178
+ */
179
+ activeIndicatorEnabled?: boolean;
180
+ /**
181
+ * @summary Specifies the color of each tab bar item's ripple effect.
182
+ *
183
+ * @platform android
184
+ */
185
+ rippleColor?: ColorValue;
186
+ /**
187
+ * @summary Specifies the label visibility mode.
188
+ *
189
+ * The label visibility mode defines when the labels of each item bar should be displayed.
190
+ *
191
+ * The following values are available:
192
+ * - `auto` - the label behaves as in “labeled” mode when there are 3 items or less, or as in “selected” mode when there are 4 items or more
193
+ * - `selected` - the label is only shown on the selected navigation item
194
+ * - `labeled` - the label is shown on all navigation items
195
+ * - `unlabeled` - the label is hidden for all navigation items
196
+ *
197
+ * The supported values correspond to the official Material Components documentation:
198
+ * @see {@link https://github.com/material-components/material-components-android/blob/master/docs/components/BottomNavigation.md#making-navigation-bar-accessible|Material Components documentation}
199
+ *
200
+ * @default auto
201
+ * @platform android
202
+ */
203
+ labelVisibilityMode?: TabBarItemLabelVisibilityMode;
204
+ };
205
+ /**
206
+ * @summary Specifies the color used for selected tab's text and icon color.
207
+ *
208
+ * Starting from iOS 26, it also impacts glow of Liquid Glass tab
209
+ * selection view.
210
+ *
211
+ * `tabBarItemTitleFontColor` and `tabBarItemIconColor` defined on
212
+ * BottomTabsScreen component override this color.
213
+ *
214
+ * @platform ios
215
+ */
216
+ tintColor?: ColorValue;
217
+ /**
218
+ * @summary Experimental prop for changing container control.
219
+ *
220
+ * If set to true, tab screen changes need to be handled by JS using
221
+ * onNativeFocusChange callback (controlled/programatically-driven).
222
+ *
223
+ * If set to false, tab screen change will not be prevented by the
224
+ * native side (managed/natively-driven).
225
+ *
226
+ * On iOS, some features are not fully implemented for managed tabs
227
+ * (e.g. overrideScrollViewContentInsetAdjustmentBehavior).
228
+ *
229
+ * On Android, only controlled tabs are currently supported.
230
+ *
231
+ * @default Defaults to `false`.
232
+ *
233
+ * @platform android, ios
234
+ */
235
+ experimentalControlNavigationStateInJS?: boolean;
236
+ };
237
+ screenStyle?: StyleProp<ViewStyle>;
238
+ /**
239
+ * Global header appearance applied to all screens with visible headers.
240
+ * Per-screen header options override these.
241
+ */
242
+ header?: ScreenStackHeaderConfigProps;
243
+ }
244
+ //# sourceMappingURL=types.d.ts.map
package/package.json ADDED
@@ -0,0 +1,166 @@
1
+ {
2
+ "name": "@sigmela/router",
3
+ "version": "0.0.11",
4
+ "description": "React Native Router",
5
+ "main": "./lib/module/index.js",
6
+ "types": "./lib/typescript/src/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "source": "./src/index.ts",
10
+ "types": "./lib/typescript/src/index.d.ts",
11
+ "default": "./lib/module/index.js"
12
+ },
13
+ "./package.json": "./package.json"
14
+ },
15
+ "files": [
16
+ "src",
17
+ "lib",
18
+ "android",
19
+ "ios",
20
+ "cpp",
21
+ "*.podspec",
22
+ "react-native.config.js",
23
+ "!ios/build",
24
+ "!android/build",
25
+ "!android/gradle",
26
+ "!android/gradlew",
27
+ "!android/gradlew.bat",
28
+ "!android/local.properties",
29
+ "!**/__tests__",
30
+ "!**/__fixtures__",
31
+ "!**/__mocks__",
32
+ "!**/.*"
33
+ ],
34
+ "scripts": {
35
+ "example": "yarn workspace @sigmela/router-example",
36
+ "test": "jest",
37
+ "typecheck": "tsc",
38
+ "lint": "eslint \"**/*.{js,ts,tsx}\"",
39
+ "clean": "del-cli lib",
40
+ "prepare": "bob build && del-cli \"lib/**/*.map\"",
41
+ "release": "release-it --only-version"
42
+ },
43
+ "keywords": [
44
+ "react-native",
45
+ "ios",
46
+ "android"
47
+ ],
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "git+https://github.com/sigmela/router.git"
51
+ },
52
+ "author": "https://github.com/bogoslavskiy <help@sigmela.com> (https://github.com/bogoslavskiy)",
53
+ "license": "MIT",
54
+ "bugs": {
55
+ "url": "https://github.com/sigmela/router/issues"
56
+ },
57
+ "homepage": "https://github.com/sigmela/router#readme",
58
+ "publishConfig": {
59
+ "access": "public",
60
+ "registry": "https://registry.npmjs.org/"
61
+ },
62
+ "devDependencies": {
63
+ "@commitlint/config-conventional": "^19.8.1",
64
+ "@eslint/compat": "^1.3.2",
65
+ "@eslint/eslintrc": "^3.3.1",
66
+ "@eslint/js": "^9.35.0",
67
+ "@evilmartians/lefthook": "^1.12.3",
68
+ "@react-native/babel-preset": "0.81.1",
69
+ "@react-native/eslint-config": "^0.81.1",
70
+ "@release-it/conventional-changelog": "^10.0.1",
71
+ "@types/jest": "^29.5.14",
72
+ "@types/react": "^19.1.12",
73
+ "commitlint": "^19.8.1",
74
+ "del-cli": "^6.0.0",
75
+ "eslint": "^9.35.0",
76
+ "eslint-config-prettier": "^10.1.8",
77
+ "eslint-plugin-prettier": "^5.5.4",
78
+ "jest": "^29.7.0",
79
+ "prettier": "^3.6.2",
80
+ "react": "19.1.0",
81
+ "react-native": "0.81.4",
82
+ "react-native-builder-bob": "^0.40.13",
83
+ "react-native-screens": "^4.16.0",
84
+ "release-it": "^19.0.4",
85
+ "typescript": "^5.9.2"
86
+ },
87
+ "peerDependencies": {
88
+ "react": "*",
89
+ "react-native": "*",
90
+ "react-native-screens": ">=4.16.0"
91
+ },
92
+ "workspaces": [
93
+ "example"
94
+ ],
95
+ "packageManager": "yarn@3.6.1",
96
+ "jest": {
97
+ "preset": "react-native",
98
+ "modulePathIgnorePatterns": [
99
+ "<rootDir>/example/node_modules",
100
+ "<rootDir>/lib/"
101
+ ],
102
+ "transformIgnorePatterns": [
103
+ "node_modules/(?!((jest-)?react-native|@react-native|react-native-screens|nanoid|query-string|decode-uri-component|split-on-first|filter-obj)/)"
104
+ ]
105
+ },
106
+ "commitlint": {
107
+ "extends": [
108
+ "@commitlint/config-conventional"
109
+ ]
110
+ },
111
+ "release-it": {
112
+ "git": {
113
+ "commitMessage": "chore: release ${version}",
114
+ "tagName": "v${version}"
115
+ },
116
+ "npm": {
117
+ "publish": true
118
+ },
119
+ "github": {
120
+ "release": true
121
+ },
122
+ "plugins": {
123
+ "@release-it/conventional-changelog": {
124
+ "preset": {
125
+ "name": "angular"
126
+ }
127
+ }
128
+ }
129
+ },
130
+ "prettier": {
131
+ "quoteProps": "consistent",
132
+ "singleQuote": true,
133
+ "tabWidth": 2,
134
+ "trailingComma": "es5",
135
+ "useTabs": false
136
+ },
137
+ "react-native-builder-bob": {
138
+ "source": "src",
139
+ "output": "lib",
140
+ "targets": [
141
+ [
142
+ "module",
143
+ {
144
+ "esm": true,
145
+ "sourceMaps": false
146
+ }
147
+ ],
148
+ [
149
+ "typescript",
150
+ {
151
+ "project": "tsconfig.build.json"
152
+ }
153
+ ]
154
+ ]
155
+ },
156
+ "create-react-native-library": {
157
+ "languages": "js",
158
+ "type": "library",
159
+ "version": "0.54.3"
160
+ },
161
+ "dependencies": {
162
+ "nanoid": "^5.1.6",
163
+ "path-to-regexp": "^8.3.0",
164
+ "query-string": "^9.3.1"
165
+ }
166
+ }
@@ -0,0 +1,102 @@
1
+ import {
2
+ ScreenStack,
3
+ ScreenStackItem as RNNScreenStackItem,
4
+ } from 'react-native-screens';
5
+ import {
6
+ memo,
7
+ useCallback,
8
+ useEffect,
9
+ useState,
10
+ useSyncExternalStore,
11
+ } from 'react';
12
+ import { RenderTabBar } from './TabBar/RenderTabBar';
13
+ import { ScreenStackItem } from './ScreenStackItem';
14
+ import { RouterContext } from './RouterContext';
15
+ import { StyleSheet } from 'react-native';
16
+ import { Router } from './Router';
17
+ import type { NavigationAppearance } from './types';
18
+
19
+ export interface NavigationProps {
20
+ router: Router;
21
+ appearance?: NavigationAppearance;
22
+ }
23
+
24
+ const EMPTY_HISTORY: any[] = [];
25
+
26
+ function useStackHistory(router: Router, stackId?: string) {
27
+ const subscribe = useCallback(
28
+ (cb: () => void) =>
29
+ stackId ? router.subscribeStack(stackId, cb) : () => {},
30
+ [router, stackId]
31
+ );
32
+ const get = useCallback(
33
+ () => (stackId ? router.getStackHistory(stackId) : EMPTY_HISTORY),
34
+ [router, stackId]
35
+ );
36
+ return useSyncExternalStore(subscribe, get, get);
37
+ }
38
+
39
+ export const Navigation = memo<NavigationProps>(({ router, appearance }) => {
40
+ const [root, setRoot] = useState(() => ({
41
+ hasTabBar: router.hasTabBar(),
42
+ rootId: router.getRootStackId(),
43
+ }));
44
+
45
+ useEffect(() => {
46
+ return router.subscribeRoot(() => {
47
+ setRoot({
48
+ hasTabBar: router.hasTabBar(),
49
+ rootId: router.getRootStackId(),
50
+ });
51
+ });
52
+ }, [router]);
53
+
54
+ const { hasTabBar, rootId } = root;
55
+ const rootTransition = router.getRootTransition();
56
+ const globalId = router.getGlobalStackId();
57
+ const rootItems = useStackHistory(router, rootId);
58
+ const globalItems = useStackHistory(router, globalId);
59
+
60
+ return (
61
+ <RouterContext.Provider value={router}>
62
+ <ScreenStack style={styles.flex}>
63
+ {hasTabBar && (
64
+ <RNNScreenStackItem
65
+ screenId="root-tabbar"
66
+ headerConfig={{ hidden: true }}
67
+ style={styles.flex}
68
+ stackAnimation={rootTransition}
69
+ >
70
+ <RenderTabBar
71
+ tabBar={router.tabBar!}
72
+ appearance={appearance?.tabBar}
73
+ />
74
+ </RNNScreenStackItem>
75
+ )}
76
+ {rootItems.map((item) => (
77
+ <ScreenStackItem
78
+ key={item.key}
79
+ stackId={rootId}
80
+ item={item}
81
+ stackAnimation={rootTransition}
82
+ screenStyle={appearance?.screenStyle}
83
+ headerAppearance={appearance?.header}
84
+ />
85
+ ))}
86
+ {globalItems.map((item) => (
87
+ <ScreenStackItem
88
+ key={item.key}
89
+ stackId={globalId}
90
+ item={item}
91
+ screenStyle={appearance?.screenStyle}
92
+ headerAppearance={appearance?.header}
93
+ />
94
+ ))}
95
+ </ScreenStack>
96
+ </RouterContext.Provider>
97
+ );
98
+ });
99
+
100
+ const styles = StyleSheet.create({
101
+ flex: { flex: 1 },
102
+ });
@@ -0,0 +1,106 @@
1
+ import type { ScreenOptions } from './types';
2
+ import { nanoid } from 'nanoid/non-secure';
3
+ import { match } from 'path-to-regexp';
4
+ import {
5
+ type ComponentWithController,
6
+ type MixedComponent,
7
+ } from './createController';
8
+
9
+ type BuiltRoute = {
10
+ routeId: string;
11
+ path: string;
12
+ match: (path: string) => false | { params: Record<string, any> };
13
+ component: React.ComponentType<any>;
14
+ controller?: ComponentWithController['controller'];
15
+ options?: ScreenOptions; // per-screen options only (no stack defaults merged)
16
+ };
17
+
18
+ export class NavigationStack {
19
+ private readonly stackId: string;
20
+ private readonly routes: BuiltRoute[] = [];
21
+ private readonly defaultOptions: ScreenOptions | undefined;
22
+
23
+ // Overloads
24
+ constructor();
25
+ constructor(id: string);
26
+ constructor(defaultOptions: ScreenOptions);
27
+ constructor(id: string, defaultOptions: ScreenOptions);
28
+ constructor(
29
+ idOrOptions?: string | ScreenOptions,
30
+ maybeOptions?: ScreenOptions
31
+ ) {
32
+ if (typeof idOrOptions === 'string') {
33
+ this.stackId = idOrOptions ?? `stack-${nanoid()}`;
34
+ this.defaultOptions = maybeOptions;
35
+ } else {
36
+ this.stackId = `stack-${nanoid()}`;
37
+ this.defaultOptions = idOrOptions;
38
+ }
39
+ }
40
+
41
+ public getId(): string {
42
+ return this.stackId;
43
+ }
44
+
45
+ public addScreen(
46
+ path: string,
47
+ mixedComponent: MixedComponent,
48
+ options?: ScreenOptions
49
+ ): NavigationStack {
50
+ const { component, controller } = this.extractComponent(mixedComponent);
51
+ const routeId = `${this.stackId}-route-${this.routes.length}`;
52
+ const matcher = match(path);
53
+
54
+ this.routes.push({
55
+ routeId,
56
+ path,
57
+ match: (p: string) => {
58
+ const result = matcher(p);
59
+ return result ? { params: (result as any).params ?? {} } : false;
60
+ },
61
+ component,
62
+ controller,
63
+ options,
64
+ });
65
+
66
+ return this;
67
+ }
68
+
69
+ public addModal(
70
+ path: string,
71
+ mixedComponent: MixedComponent,
72
+ options?: ScreenOptions
73
+ ): NavigationStack {
74
+ return this.addScreen(path, mixedComponent, {
75
+ ...options,
76
+ stackPresentation: 'modal',
77
+ });
78
+ }
79
+
80
+ public getRoutes(): BuiltRoute[] {
81
+ return this.routes.slice();
82
+ }
83
+
84
+ public getFirstRoute(): BuiltRoute | undefined {
85
+ return this.routes[0];
86
+ }
87
+
88
+ public getDefaultOptions(): ScreenOptions | undefined {
89
+ return this.defaultOptions;
90
+ }
91
+
92
+ private extractComponent(component: MixedComponent) {
93
+ const componentWithController = component as ComponentWithController;
94
+ if (componentWithController?.component) {
95
+ return {
96
+ controller: componentWithController.controller,
97
+ component: componentWithController.component,
98
+ };
99
+ }
100
+
101
+ return {
102
+ component: component as React.ComponentType<any>,
103
+ controller: undefined,
104
+ };
105
+ }
106
+ }