@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,122 @@
1
+ "use strict";
2
+
3
+ import { useCallback, useSyncExternalStore, memo, useEffect } from 'react';
4
+ import { Platform } from 'react-native';
5
+ import { StackRenderer } from "../StackRenderer.js";
6
+ import { TabBarContext } from "./TabBarContext.js";
7
+ import { useRouter } from "../RouterContext.js";
8
+ import { BottomTabsScreen, BottomTabs } from 'react-native-screens';
9
+ import { jsx as _jsx } from "react/jsx-runtime";
10
+ // Helpers outside render to avoid re-creation
11
+ const isImageSource = value => {
12
+ if (value == null) return false;
13
+ const valueType = typeof value;
14
+ if (valueType === 'number') return true;
15
+ if (Array.isArray(value)) return value.length > 0;
16
+ if (valueType === 'object') {
17
+ if ('uri' in value || 'width' in value || 'height' in value) return true;
18
+ if ('sfSymbolName' in value || 'imageSource' in value || 'templateSource' in value) return false;
19
+ }
20
+ return false;
21
+ };
22
+ const buildIOSIcon = value => {
23
+ if (!value) return undefined;
24
+ if (typeof value === 'object' && ('sfSymbolName' in value || 'imageSource' in value || 'templateSource' in value)) {
25
+ return value;
26
+ }
27
+ return {
28
+ templateSource: value
29
+ };
30
+ };
31
+ export const RenderTabBar = /*#__PURE__*/memo(({
32
+ tabBar,
33
+ appearance
34
+ }) => {
35
+ const router = useRouter();
36
+ const subscribe = useCallback(cb => tabBar.subscribe(cb), [tabBar]);
37
+ const snapshot = useSyncExternalStore(subscribe, tabBar.getState, tabBar.getState);
38
+ const {
39
+ tabs,
40
+ index
41
+ } = snapshot;
42
+ const {
43
+ standardAppearance,
44
+ scrollEdgeAppearance,
45
+ tabBarItemStyle,
46
+ tintColor,
47
+ backgroundColor
48
+ } = appearance ?? {};
49
+ useEffect(() => {
50
+ router.ensureTabSeed(index);
51
+ }, [index, router]);
52
+ const onNativeFocusChange = useCallback(event => {
53
+ const tabKey = event.nativeEvent.tabKey;
54
+ const tabIndex = tabs.findIndex(route => route.tabKey === tabKey);
55
+ router.onTabIndexChange(tabIndex);
56
+ }, [tabs, router]);
57
+ return /*#__PURE__*/_jsx(TabBarContext.Provider, {
58
+ value: tabBar,
59
+ children: /*#__PURE__*/_jsx(BottomTabs, {
60
+ onNativeFocusChange: onNativeFocusChange,
61
+ tabBarBackgroundColor: backgroundColor,
62
+ tabBarTintColor: tintColor,
63
+ tabBarItemTitleFontFamily: tabBarItemStyle?.titleFontFamily,
64
+ tabBarItemTitleFontSize: tabBarItemStyle?.titleFontSize,
65
+ tabBarItemTitleFontSizeActive: tabBarItemStyle?.titleFontSizeActive,
66
+ tabBarItemTitleFontWeight: tabBarItemStyle?.titleFontWeight,
67
+ tabBarItemTitleFontStyle: tabBarItemStyle?.titleFontStyle,
68
+ tabBarItemTitleFontColor: tabBarItemStyle?.titleFontColor,
69
+ tabBarItemTitleFontColorActive: tabBarItemStyle?.titleFontColorActive,
70
+ tabBarItemIconColor: tabBarItemStyle?.iconColor,
71
+ tabBarItemIconColorActive: tabBarItemStyle?.iconColorActive,
72
+ tabBarItemActiveIndicatorColor: tabBarItemStyle?.activeIndicatorColor,
73
+ tabBarItemActiveIndicatorEnabled: tabBarItemStyle?.activeIndicatorEnabled,
74
+ tabBarItemRippleColor: tabBarItemStyle?.rippleColor,
75
+ tabBarItemLabelVisibilityMode: tabBarItemStyle?.labelVisibilityMode
76
+ // tabBarMinimizeBehavior={}
77
+ ,
78
+ children: tabs.map(tab => {
79
+ const isFocused = tab.tabKey === tabs[index]?.tabKey;
80
+ const stack = tabBar.stacks[tab.tabKey];
81
+ const Screen = tabBar.screens[tab.tabKey];
82
+
83
+ // Map unified icon to platform-specific props expected by RNS BottomTabsScreen
84
+ const {
85
+ icon,
86
+ selectedIcon,
87
+ ...restTab
88
+ } = tab;
89
+ let mappedTabProps = restTab;
90
+ if (icon || selectedIcon) {
91
+ if (Platform.OS === 'android') {
92
+ mappedTabProps = {
93
+ ...restTab,
94
+ ...(isImageSource(icon) ? {
95
+ iconResource: icon
96
+ } : null)
97
+ };
98
+ } else {
99
+ mappedTabProps = {
100
+ ...restTab,
101
+ ...(icon ? {
102
+ icon: buildIOSIcon(icon)
103
+ } : null),
104
+ ...(selectedIcon ? {
105
+ selectedIcon: buildIOSIcon(selectedIcon)
106
+ } : null)
107
+ };
108
+ }
109
+ }
110
+ return /*#__PURE__*/_jsx(BottomTabsScreen, {
111
+ scrollEdgeAppearance: scrollEdgeAppearance,
112
+ standardAppearance: standardAppearance,
113
+ isFocused: isFocused,
114
+ ...mappedTabProps,
115
+ children: stack ? /*#__PURE__*/_jsx(StackRenderer, {
116
+ stack: stack
117
+ }) : Screen ? /*#__PURE__*/_jsx(Screen, {}) : null
118
+ }, tab.tabKey);
119
+ })
120
+ })
121
+ });
122
+ });
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+
3
+ // Internal representation used by TabBar to support unified icon source while keeping original android props
4
+
5
+ export class TabBar {
6
+ screens = {};
7
+ stacks = {};
8
+ listeners = new Set();
9
+ constructor(config = {}) {
10
+ this.state = {
11
+ tabs: [],
12
+ index: 0,
13
+ config
14
+ };
15
+ }
16
+ addTab(tab) {
17
+ const {
18
+ key,
19
+ ...rest
20
+ } = tab;
21
+ const nextIndex = this.state.tabs.length;
22
+ const tabKey = key ?? `tab-${nextIndex}`;
23
+ this.state.tabs.push({
24
+ tabKey,
25
+ ...rest
26
+ });
27
+ if (tab.stack) {
28
+ this.stacks[tabKey] = tab.stack;
29
+ } else if (tab.screen) {
30
+ this.screens[tabKey] = tab.screen;
31
+ }
32
+ return this;
33
+ }
34
+ setBadge(tabIndex, badge) {
35
+ this.setState({
36
+ tabs: this.state.tabs.map((route, index) => index === tabIndex ? {
37
+ ...route,
38
+ badgeValue: badge ?? undefined
39
+ } : route)
40
+ });
41
+ }
42
+ setTabBarConfig(config) {
43
+ this.setState({
44
+ config: {
45
+ ...this.state.config,
46
+ ...config
47
+ }
48
+ });
49
+ }
50
+ onIndexChange(index) {
51
+ this.setState({
52
+ index
53
+ });
54
+ }
55
+ getState = () => {
56
+ return this.state;
57
+ };
58
+ setState(state) {
59
+ this.state = {
60
+ ...this.state,
61
+ ...state
62
+ };
63
+ this.notifyListeners();
64
+ }
65
+ notifyListeners() {
66
+ this.listeners.forEach(listener => listener());
67
+ }
68
+ subscribe(listener) {
69
+ this.listeners.add(listener);
70
+ return () => {
71
+ this.listeners.delete(listener);
72
+ };
73
+ }
74
+ }
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+
3
+ import { createContext } from 'react';
4
+ export const TabBarContext = /*#__PURE__*/createContext(null);
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+
3
+ import { useContext } from 'react';
4
+ import { TabBarContext } from "./TabBarContext.js";
5
+ export const useTabBar = () => {
6
+ const tabBar = useContext(TabBarContext);
7
+ if (!tabBar) {
8
+ throw new Error('useTabBar must be used within a TabBarProvider');
9
+ }
10
+ return tabBar;
11
+ };
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ export function createController(controller) {
4
+ return controller;
5
+ }
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+
3
+ export { useTabBar } from "./TabBar/useTabBar.js";
4
+ export { TabBar } from "./TabBar/TabBar.js";
5
+ /**
6
+ * Navigation System
7
+ */
8
+
9
+ export { Router } from "./Router.js";
10
+ export { Navigation } from "./Navigation.js";
11
+ export { createController } from "./createController.js";
12
+ export { NavigationStack } from "./NavigationStack.js";
13
+ export { StackRenderer } from "./StackRenderer.js";
14
+ export { useRouter, useCurrentRoute, useParams, useQueryParams, useRoute } from "./RouterContext.js";
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+
3
+ export {};
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -0,0 +1,8 @@
1
+ import { Router } from './Router';
2
+ import type { NavigationAppearance } from './types';
3
+ export interface NavigationProps {
4
+ router: Router;
5
+ appearance?: NavigationAppearance;
6
+ }
7
+ export declare const Navigation: import("react").NamedExoticComponent<NavigationProps>;
8
+ //# sourceMappingURL=Navigation.d.ts.map
@@ -0,0 +1,30 @@
1
+ import type { ScreenOptions } from './types';
2
+ import { type ComponentWithController, type MixedComponent } from './createController';
3
+ type BuiltRoute = {
4
+ routeId: string;
5
+ path: string;
6
+ match: (path: string) => false | {
7
+ params: Record<string, any>;
8
+ };
9
+ component: React.ComponentType<any>;
10
+ controller?: ComponentWithController['controller'];
11
+ options?: ScreenOptions;
12
+ };
13
+ export declare class NavigationStack {
14
+ private readonly stackId;
15
+ private readonly routes;
16
+ private readonly defaultOptions;
17
+ constructor();
18
+ constructor(id: string);
19
+ constructor(defaultOptions: ScreenOptions);
20
+ constructor(id: string, defaultOptions: ScreenOptions);
21
+ getId(): string;
22
+ addScreen(path: string, mixedComponent: MixedComponent, options?: ScreenOptions): NavigationStack;
23
+ addModal(path: string, mixedComponent: MixedComponent, options?: ScreenOptions): NavigationStack;
24
+ getRoutes(): BuiltRoute[];
25
+ getFirstRoute(): BuiltRoute | undefined;
26
+ getDefaultOptions(): ScreenOptions | undefined;
27
+ private extractComponent;
28
+ }
29
+ export {};
30
+ //# sourceMappingURL=NavigationStack.d.ts.map
@@ -0,0 +1,70 @@
1
+ import { NavigationStack } from './NavigationStack';
2
+ import { TabBar } from './TabBar/TabBar';
3
+ import type { HistoryItem, ScreenOptions, VisibleRoute } from './types';
4
+ type Listener = () => void;
5
+ export interface RouterConfig {
6
+ root: TabBar | NavigationStack;
7
+ global?: NavigationStack;
8
+ screenOptions?: ScreenOptions;
9
+ }
10
+ export type RootTransition = ScreenOptions['stackAnimation'];
11
+ type RouterState = {
12
+ history: HistoryItem[];
13
+ activeTabIndex?: number;
14
+ };
15
+ export declare class Router {
16
+ tabBar: TabBar | null;
17
+ root: NavigationStack | TabBar | null;
18
+ global: NavigationStack | null;
19
+ private readonly listeners;
20
+ private readonly registry;
21
+ private state;
22
+ private readonly routerScreenOptions;
23
+ private stackSlices;
24
+ private stackListeners;
25
+ private activeTabListeners;
26
+ private stackById;
27
+ private routeById;
28
+ private visibleRoute;
29
+ private rootListeners;
30
+ private rootTransition?;
31
+ constructor(config: RouterConfig);
32
+ navigate: (path: string) => void;
33
+ replace: (path: string) => void;
34
+ goBack: () => void;
35
+ onTabIndexChange: (index: number) => void;
36
+ setActiveTabIndex: (index: number) => void;
37
+ ensureTabSeed: (index: number) => void;
38
+ getState: () => RouterState;
39
+ subscribe(listener: Listener): () => void;
40
+ getStackHistory: (stackId: string) => HistoryItem[];
41
+ subscribeStack: (stackId: string, cb: Listener) => (() => void);
42
+ getActiveTabIndex: () => number;
43
+ subscribeActiveTab: (cb: Listener) => (() => void);
44
+ getRootStackId(): string | undefined;
45
+ getGlobalStackId(): string | undefined;
46
+ hasTabBar(): boolean;
47
+ subscribeRoot(listener: Listener): () => void;
48
+ private emitRootChange;
49
+ getRootTransition(): RootTransition | undefined;
50
+ setRoot(nextRoot: TabBar | NavigationStack, options?: {
51
+ transition?: RootTransition;
52
+ }): void;
53
+ getVisibleRoute: () => VisibleRoute;
54
+ private recomputeVisibleRoute;
55
+ private performNavigation;
56
+ private createHistoryItem;
57
+ private buildRegistry;
58
+ private seedInitialHistory;
59
+ private matchRoute;
60
+ private generateKey;
61
+ private parsePath;
62
+ private applyHistoryChange;
63
+ private setState;
64
+ private emit;
65
+ private getTopForTarget;
66
+ private mergeOptions;
67
+ private findStackById;
68
+ }
69
+ export {};
70
+ //# sourceMappingURL=Router.d.ts.map
@@ -0,0 +1,19 @@
1
+ import type { StackPresentationTypes } from 'react-native-screens';
2
+ import React from 'react';
3
+ import type { Router } from './Router';
4
+ export declare const RouterContext: React.Context<Router | null>;
5
+ type RouteLocalContextValue = {
6
+ presentation: StackPresentationTypes;
7
+ params?: Record<string, unknown>;
8
+ query?: Record<string, unknown>;
9
+ pattern?: string;
10
+ path?: string;
11
+ };
12
+ export declare const RouteLocalContext: React.Context<RouteLocalContextValue | null>;
13
+ export declare const useRouter: () => Router;
14
+ export declare const useCurrentRoute: () => import("./types").VisibleRoute;
15
+ export declare function useParams<TParams extends Record<string, unknown> = Record<string, unknown>>(): TParams;
16
+ export declare function useQueryParams<TQuery extends Record<string, unknown> = Record<string, unknown>>(): TQuery;
17
+ export declare function useRoute(): RouteLocalContextValue;
18
+ export {};
19
+ //# sourceMappingURL=RouterContext.d.ts.map
@@ -0,0 +1,12 @@
1
+ import type { HistoryItem, ScreenOptions, NavigationAppearance } from './types';
2
+ import { type StyleProp, type ViewStyle } from 'react-native';
3
+ interface ScreenStackItemProps {
4
+ item: HistoryItem;
5
+ stackId?: string;
6
+ stackAnimation?: ScreenOptions['stackAnimation'];
7
+ screenStyle?: StyleProp<ViewStyle>;
8
+ headerAppearance?: NavigationAppearance['header'];
9
+ }
10
+ export declare const ScreenStackItem: import("react").NamedExoticComponent<ScreenStackItemProps>;
11
+ export {};
12
+ //# sourceMappingURL=ScreenStackItem.d.ts.map
@@ -0,0 +1,6 @@
1
+ import { NavigationStack } from './NavigationStack';
2
+ export interface StackRendererProps {
3
+ stack: NavigationStack;
4
+ }
5
+ export declare const StackRenderer: import("react").NamedExoticComponent<StackRendererProps>;
6
+ //# sourceMappingURL=StackRenderer.d.ts.map
@@ -0,0 +1,8 @@
1
+ import type { NavigationAppearance } from '../types';
2
+ import type { TabBar } from './TabBar';
3
+ export interface RenderTabBarProps {
4
+ tabBar: TabBar;
5
+ appearance?: NavigationAppearance['tabBar'];
6
+ }
7
+ export declare const RenderTabBar: import("react").NamedExoticComponent<RenderTabBarProps>;
8
+ //# sourceMappingURL=RenderTabBar.d.ts.map
@@ -0,0 +1,43 @@
1
+ import React from 'react';
2
+ import { NavigationStack } from '../NavigationStack';
3
+ import type { TabItem } from '../types';
4
+ import type { ImageSourcePropType } from 'react-native';
5
+ type IOSIconShape = {
6
+ sfSymbolName: string;
7
+ } | {
8
+ imageSource: ImageSourcePropType;
9
+ } | {
10
+ templateSource: ImageSourcePropType;
11
+ };
12
+ type ExtendedIcon = ImageSourcePropType | IOSIconShape;
13
+ type InternalTabItem = Omit<TabItem, 'icon' | 'selectedIcon'> & {
14
+ icon?: ExtendedIcon;
15
+ selectedIcon?: ExtendedIcon;
16
+ };
17
+ type TabBarConfig = Omit<InternalTabItem, 'tabKey' | 'key'> & {
18
+ stack?: NavigationStack;
19
+ screen?: React.ComponentType<any>;
20
+ };
21
+ export declare class TabBar {
22
+ screens: Record<string, React.ComponentType<any>>;
23
+ stacks: Record<string, NavigationStack>;
24
+ private listeners;
25
+ private state;
26
+ constructor(config?: TabBarConfig);
27
+ addTab(tab: Omit<TabBarConfig, 'tabKey'> & {
28
+ key: string;
29
+ }): TabBar;
30
+ setBadge(tabIndex: number, badge: string | null): void;
31
+ setTabBarConfig(config: Partial<TabBarConfig>): void;
32
+ onIndexChange(index: number): void;
33
+ getState: () => {
34
+ tabs: InternalTabItem[];
35
+ config: TabBarConfig;
36
+ index: number;
37
+ };
38
+ private setState;
39
+ private notifyListeners;
40
+ subscribe(listener: () => void): () => void;
41
+ }
42
+ export {};
43
+ //# sourceMappingURL=TabBar.d.ts.map
@@ -0,0 +1,3 @@
1
+ import { TabBar } from './TabBar';
2
+ export declare const TabBarContext: import("react").Context<TabBar | null>;
3
+ //# sourceMappingURL=TabBarContext.d.ts.map
@@ -0,0 +1,2 @@
1
+ export declare const useTabBar: () => import("./TabBar").TabBar;
2
+ //# sourceMappingURL=useTabBar.d.ts.map
@@ -0,0 +1,14 @@
1
+ type ControllerPresenter<P = any> = (passProps?: P) => void;
2
+ type ControllerInput<TParams, TQuery> = {
3
+ params: TParams;
4
+ query: TQuery;
5
+ };
6
+ export type Controller<TParams = Record<string, unknown>, TQuery = Record<string, unknown>> = (input: ControllerInput<TParams, TQuery>, present: ControllerPresenter) => void;
7
+ export type ComponentWithController = {
8
+ controller?: Controller<any, any>;
9
+ component: React.ComponentType<any>;
10
+ };
11
+ export type MixedComponent = ComponentWithController | React.ComponentType<any>;
12
+ export declare function createController<TParams, TQuery = Record<string, unknown>>(controller: Controller<TParams, TQuery>): Controller<TParams, TQuery>;
13
+ export {};
14
+ //# sourceMappingURL=createController.d.ts.map
@@ -0,0 +1,15 @@
1
+ export { useTabBar } from './TabBar/useTabBar';
2
+ export { TabBar } from './TabBar/TabBar';
3
+ export type { NavigationProps } from './Navigation';
4
+ export type { NavigationAppearance } from './types';
5
+ /**
6
+ * Navigation System
7
+ */
8
+ export { Router } from './Router';
9
+ export { Navigation } from './Navigation';
10
+ export { createController } from './createController';
11
+ export { NavigationStack } from './NavigationStack';
12
+ export type { HistoryItem } from './types';
13
+ export { StackRenderer } from './StackRenderer';
14
+ export { useRouter, useCurrentRoute, useParams, useQueryParams, useRoute, } from './RouterContext';
15
+ //# sourceMappingURL=index.d.ts.map