@sigmela/router 0.0.17 → 0.1.2
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.
- package/README.md +861 -118
- package/lib/module/NavigationStack.js +6 -0
- package/lib/module/Router.js +27 -8
- package/lib/module/ScreenStackItem/ScreenStackItem.js +25 -14
- package/lib/module/ScreenStackSheetItem/ScreenStackSheetItem.native.js +70 -0
- package/lib/module/ScreenStackSheetItem/ScreenStackSheetItem.web.js +5 -0
- package/lib/module/ScreenStackSheetItem/index.js +3 -0
- package/lib/module/TabBar/TabBar.js +5 -3
- package/lib/typescript/src/NavigationStack.d.ts +1 -0
- package/lib/typescript/src/Router.d.ts +3 -0
- package/lib/typescript/src/RouterContext.d.ts +2 -3
- package/lib/typescript/src/ScreenStackSheetItem/ScreenStackSheetItem.native.d.ts +11 -0
- package/lib/typescript/src/ScreenStackSheetItem/ScreenStackSheetItem.web.d.ts +2 -0
- package/lib/typescript/src/ScreenStackSheetItem/index.d.ts +2 -0
- package/lib/typescript/src/TabBar/TabBar.d.ts +6 -1
- package/lib/typescript/src/types.d.ts +10 -1
- package/package.json +10 -4
|
@@ -47,6 +47,12 @@ export class NavigationStack {
|
|
|
47
47
|
stackPresentation: 'modal'
|
|
48
48
|
});
|
|
49
49
|
}
|
|
50
|
+
addSheet(path, mixedComponent, options) {
|
|
51
|
+
return this.addScreen(path, mixedComponent, {
|
|
52
|
+
...options,
|
|
53
|
+
stackPresentation: 'sheet'
|
|
54
|
+
});
|
|
55
|
+
}
|
|
50
56
|
getRoutes() {
|
|
51
57
|
return this.routes.slice();
|
|
52
58
|
}
|
package/lib/module/Router.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import { nanoid } from 'nanoid/non-secure';
|
|
4
|
+
import { Platform } from 'react-native';
|
|
4
5
|
import qs from 'query-string';
|
|
5
6
|
|
|
6
7
|
// Root transition option: allow string shorthand like 'fade'
|
|
@@ -22,6 +23,8 @@ export class Router {
|
|
|
22
23
|
history: [],
|
|
23
24
|
activeTabIndex: undefined
|
|
24
25
|
};
|
|
26
|
+
sheetDismissers = new Map();
|
|
27
|
+
|
|
25
28
|
// per-stack slices and listeners
|
|
26
29
|
stackSlices = new Map();
|
|
27
30
|
stackListeners = new Map();
|
|
@@ -75,12 +78,16 @@ export class Router {
|
|
|
75
78
|
dedupe: !!dedupe
|
|
76
79
|
});
|
|
77
80
|
};
|
|
81
|
+
registerSheetDismisser = (key, dismisser) => {
|
|
82
|
+
this.sheetDismissers.set(key, dismisser);
|
|
83
|
+
};
|
|
84
|
+
unregisterSheetDismisser = key => {
|
|
85
|
+
this.sheetDismissers.delete(key);
|
|
86
|
+
};
|
|
78
87
|
goBack = () => {
|
|
79
88
|
if (this.isWebEnv()) {
|
|
80
|
-
// Web: only go back within the current active stack.
|
|
81
89
|
const didPop = this.tryPopActiveStack();
|
|
82
90
|
if (didPop) {
|
|
83
|
-
// Keep URL in sync with the new visible route without adding history entries
|
|
84
91
|
const path = this.getVisibleRoute()?.path;
|
|
85
92
|
if (path) this.replaceUrl(path);
|
|
86
93
|
}
|
|
@@ -577,6 +584,9 @@ export class Router {
|
|
|
577
584
|
...(routeOptions ?? {}),
|
|
578
585
|
...(routerDefaults ?? {})
|
|
579
586
|
};
|
|
587
|
+
if (merged.stackPresentation === 'modal' && merged.convertModalToSheetForAndroid && Platform.OS === 'android') {
|
|
588
|
+
merged.stackPresentation = 'sheet';
|
|
589
|
+
}
|
|
580
590
|
return merged;
|
|
581
591
|
}
|
|
582
592
|
findStackById(stackId) {
|
|
@@ -715,13 +725,24 @@ export class Router {
|
|
|
715
725
|
// Attempts to pop exactly one screen within the active stack only.
|
|
716
726
|
// Returns true if a pop occurred; false otherwise.
|
|
717
727
|
tryPopActiveStack() {
|
|
728
|
+
const handlePop = item => {
|
|
729
|
+
if (item.options?.stackPresentation === 'sheet') {
|
|
730
|
+
const dismisser = this.sheetDismissers.get(item.key);
|
|
731
|
+
if (dismisser) {
|
|
732
|
+
this.unregisterSheetDismisser(item.key);
|
|
733
|
+
dismisser();
|
|
734
|
+
return true;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
this.applyHistoryChange('pop', item);
|
|
738
|
+
return true;
|
|
739
|
+
};
|
|
718
740
|
if (this.global) {
|
|
719
741
|
const gid = this.global.getId();
|
|
720
742
|
const gslice = this.getStackHistory(gid);
|
|
721
743
|
const gtop = gslice.length ? gslice[gslice.length - 1] : undefined;
|
|
722
744
|
if (gtop) {
|
|
723
|
-
|
|
724
|
-
return true;
|
|
745
|
+
return handlePop(gtop);
|
|
725
746
|
}
|
|
726
747
|
}
|
|
727
748
|
if (this.tabBar) {
|
|
@@ -736,8 +757,7 @@ export class Router {
|
|
|
736
757
|
if (slice.length > 1) {
|
|
737
758
|
const top = slice[slice.length - 1];
|
|
738
759
|
if (top) {
|
|
739
|
-
|
|
740
|
-
return true;
|
|
760
|
+
return handlePop(top);
|
|
741
761
|
}
|
|
742
762
|
}
|
|
743
763
|
return false;
|
|
@@ -748,8 +768,7 @@ export class Router {
|
|
|
748
768
|
if (slice.length > 1) {
|
|
749
769
|
const top = slice[slice.length - 1];
|
|
750
770
|
if (top) {
|
|
751
|
-
|
|
752
|
-
return true;
|
|
771
|
+
return handlePop(top);
|
|
753
772
|
}
|
|
754
773
|
}
|
|
755
774
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { ScreenStackItem as RNSScreenStackItem } from 'react-native-screens';
|
|
4
4
|
import { RouteLocalContext, useRouter } from "../RouterContext.js";
|
|
5
|
+
import { ScreenStackSheetItem } from "../ScreenStackSheetItem/index.js";
|
|
5
6
|
import { StyleSheet } from 'react-native';
|
|
6
7
|
import { memo } from 'react';
|
|
7
8
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
@@ -11,6 +12,18 @@ export const ScreenStackItem = /*#__PURE__*/memo(({
|
|
|
11
12
|
stackAnimation,
|
|
12
13
|
appearance
|
|
13
14
|
}) => {
|
|
15
|
+
const {
|
|
16
|
+
header,
|
|
17
|
+
stackPresentation,
|
|
18
|
+
...screenProps
|
|
19
|
+
} = item.options || {};
|
|
20
|
+
const route = {
|
|
21
|
+
presentation: stackPresentation ?? 'push',
|
|
22
|
+
params: item.params,
|
|
23
|
+
query: item.query,
|
|
24
|
+
pattern: item.pattern,
|
|
25
|
+
path: item.path
|
|
26
|
+
};
|
|
14
27
|
const router = useRouter();
|
|
15
28
|
const onDismissed = () => {
|
|
16
29
|
if (stackId) {
|
|
@@ -21,34 +34,32 @@ export const ScreenStackItem = /*#__PURE__*/memo(({
|
|
|
21
34
|
}
|
|
22
35
|
}
|
|
23
36
|
};
|
|
24
|
-
const value = {
|
|
25
|
-
presentation: item.options?.stackPresentation ?? 'push',
|
|
26
|
-
params: item.params,
|
|
27
|
-
query: item.query,
|
|
28
|
-
pattern: item.pattern,
|
|
29
|
-
path: item.path
|
|
30
|
-
};
|
|
31
|
-
const {
|
|
32
|
-
header,
|
|
33
|
-
...screenProps
|
|
34
|
-
} = item.options || {};
|
|
35
37
|
const headerConfig = {
|
|
36
38
|
...header,
|
|
37
39
|
hidden: !header?.title || header?.hidden,
|
|
38
40
|
backgroundColor: appearance?.header?.backgroundColor ?? 'transparent'
|
|
39
41
|
};
|
|
42
|
+
if (route.presentation === 'sheet') {
|
|
43
|
+
return /*#__PURE__*/_jsx(ScreenStackSheetItem, {
|
|
44
|
+
appearance: appearance?.sheet,
|
|
45
|
+
onDismissed: onDismissed,
|
|
46
|
+
route: route,
|
|
47
|
+
item: item
|
|
48
|
+
});
|
|
49
|
+
}
|
|
40
50
|
return /*#__PURE__*/_jsx(RNSScreenStackItem, {
|
|
51
|
+
...screenProps,
|
|
41
52
|
screenId: item.key,
|
|
42
53
|
onDismissed: onDismissed,
|
|
43
54
|
style: StyleSheet.absoluteFill,
|
|
44
55
|
contentStyle: appearance?.screen,
|
|
45
56
|
headerConfig: headerConfig,
|
|
46
|
-
|
|
57
|
+
stackPresentation: stackPresentation,
|
|
47
58
|
stackAnimation: stackAnimation ?? item.options?.stackAnimation,
|
|
48
59
|
children: /*#__PURE__*/_jsx(RouteLocalContext.Provider, {
|
|
49
|
-
value:
|
|
60
|
+
value: route,
|
|
50
61
|
children: /*#__PURE__*/_jsx(item.component, {
|
|
51
|
-
...
|
|
62
|
+
...item.passProps
|
|
52
63
|
})
|
|
53
64
|
})
|
|
54
65
|
}, item.key);
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { ScreenStackItem as RNSScreenStackItem } from 'react-native-screens';
|
|
4
|
+
import { NativeSheetView, Commands } from '@sigmela/native-sheet';
|
|
5
|
+
import { RouteLocalContext, useRouter } from "../RouterContext.js";
|
|
6
|
+
import { memo, useRef, useEffect } from 'react';
|
|
7
|
+
import { StyleSheet } from 'react-native';
|
|
8
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
9
|
+
export const ScreenStackSheetItem = /*#__PURE__*/memo(props => {
|
|
10
|
+
const {
|
|
11
|
+
item,
|
|
12
|
+
onDismissed,
|
|
13
|
+
route,
|
|
14
|
+
appearance
|
|
15
|
+
} = props;
|
|
16
|
+
const {
|
|
17
|
+
convertModalToSheetForAndroid
|
|
18
|
+
} = item.options || {};
|
|
19
|
+
const headerConfig = {
|
|
20
|
+
hidden: true
|
|
21
|
+
};
|
|
22
|
+
const ref = useRef(null);
|
|
23
|
+
const router = useRouter();
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (route.presentation === 'sheet') {
|
|
26
|
+
router.registerSheetDismisser(item.key, () => {
|
|
27
|
+
if (ref.current) {
|
|
28
|
+
Commands.dismiss(ref.current);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
34
|
+
}, []);
|
|
35
|
+
const handleSheetDismissed = () => {
|
|
36
|
+
router.unregisterSheetDismisser(item.key);
|
|
37
|
+
onDismissed();
|
|
38
|
+
};
|
|
39
|
+
return /*#__PURE__*/_jsx(RNSScreenStackItem, {
|
|
40
|
+
stackPresentation: "transparentModal",
|
|
41
|
+
headerConfig: headerConfig,
|
|
42
|
+
style: StyleSheet.absoluteFill,
|
|
43
|
+
contentStyle: styles.content,
|
|
44
|
+
screenId: item.key,
|
|
45
|
+
stackAnimation: "none",
|
|
46
|
+
onDismissed: onDismissed,
|
|
47
|
+
children: /*#__PURE__*/_jsx(NativeSheetView, {
|
|
48
|
+
containerBackgroundColor: appearance?.backgroundColor,
|
|
49
|
+
cornerRadius: appearance?.cornerRadius ?? 18,
|
|
50
|
+
onDismissed: handleSheetDismissed,
|
|
51
|
+
style: styles.flex,
|
|
52
|
+
ref: ref,
|
|
53
|
+
fullscreenTopInset: convertModalToSheetForAndroid ? appearance?.androidFullScreenTopInset ?? 40 : undefined,
|
|
54
|
+
children: /*#__PURE__*/_jsx(RouteLocalContext.Provider, {
|
|
55
|
+
value: route,
|
|
56
|
+
children: /*#__PURE__*/_jsx(item.component, {
|
|
57
|
+
...item.passProps
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
}, `${item.key}-sheet-item`);
|
|
62
|
+
});
|
|
63
|
+
const styles = StyleSheet.create({
|
|
64
|
+
flex: {
|
|
65
|
+
flex: 1
|
|
66
|
+
},
|
|
67
|
+
content: {
|
|
68
|
+
backgroundColor: 'transparent'
|
|
69
|
+
}
|
|
70
|
+
});
|
|
@@ -6,11 +6,13 @@ export class TabBar {
|
|
|
6
6
|
screens = {};
|
|
7
7
|
stacks = {};
|
|
8
8
|
listeners = new Set();
|
|
9
|
-
constructor(
|
|
9
|
+
constructor(options = {}) {
|
|
10
10
|
this.state = {
|
|
11
11
|
tabs: [],
|
|
12
|
-
index: 0,
|
|
13
|
-
config
|
|
12
|
+
index: options.initialIndex ?? 0,
|
|
13
|
+
config: {
|
|
14
|
+
component: options.component
|
|
15
|
+
}
|
|
14
16
|
};
|
|
15
17
|
}
|
|
16
18
|
addTab(tab) {
|
|
@@ -21,6 +21,7 @@ export declare class NavigationStack {
|
|
|
21
21
|
getId(): string;
|
|
22
22
|
addScreen(path: string, mixedComponent: MixedComponent, options?: ScreenOptions): NavigationStack;
|
|
23
23
|
addModal(path: string, mixedComponent: MixedComponent, options?: ScreenOptions): NavigationStack;
|
|
24
|
+
addSheet(path: string, mixedComponent: MixedComponent, options?: ScreenOptions): NavigationStack;
|
|
24
25
|
getRoutes(): BuiltRoute[];
|
|
25
26
|
getFirstRoute(): BuiltRoute | undefined;
|
|
26
27
|
getDefaultOptions(): ScreenOptions | undefined;
|
|
@@ -20,6 +20,7 @@ export declare class Router {
|
|
|
20
20
|
private readonly registry;
|
|
21
21
|
private state;
|
|
22
22
|
private readonly routerScreenOptions;
|
|
23
|
+
private sheetDismissers;
|
|
23
24
|
private stackSlices;
|
|
24
25
|
private stackListeners;
|
|
25
26
|
private activeTabListeners;
|
|
@@ -31,6 +32,8 @@ export declare class Router {
|
|
|
31
32
|
constructor(config: RouterConfig);
|
|
32
33
|
navigate: (path: string) => void;
|
|
33
34
|
replace: (path: string, dedupe?: boolean) => void;
|
|
35
|
+
registerSheetDismisser: (key: string, dismisser: () => void) => void;
|
|
36
|
+
unregisterSheetDismisser: (key: string) => void;
|
|
34
37
|
goBack: () => void;
|
|
35
38
|
onTabIndexChange: (index: number) => void;
|
|
36
39
|
setActiveTabIndex: (index: number) => void;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type { StackPresentationTypes } from 'react-native-screens';
|
|
2
1
|
import React from 'react';
|
|
2
|
+
import type { StackPresentationTypes } from './types';
|
|
3
3
|
import type { Router } from './Router';
|
|
4
4
|
export declare const RouterContext: React.Context<Router | null>;
|
|
5
|
-
type RouteLocalContextValue = {
|
|
5
|
+
export type RouteLocalContextValue = {
|
|
6
6
|
presentation: StackPresentationTypes;
|
|
7
7
|
params?: Record<string, unknown>;
|
|
8
8
|
query?: Record<string, unknown>;
|
|
@@ -15,5 +15,4 @@ export declare const useCurrentRoute: () => import("./types").VisibleRoute;
|
|
|
15
15
|
export declare function useParams<TParams extends Record<string, unknown> = Record<string, unknown>>(): TParams;
|
|
16
16
|
export declare function useQueryParams<TQuery extends Record<string, unknown> = Record<string, unknown>>(): TQuery;
|
|
17
17
|
export declare function useRoute(): RouteLocalContextValue;
|
|
18
|
-
export {};
|
|
19
18
|
//# sourceMappingURL=RouterContext.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { RouteLocalContextValue } from '../RouterContext';
|
|
2
|
+
import type { HistoryItem, SheetAppearance } from '../types';
|
|
3
|
+
interface ScreenStackSheetItemProps {
|
|
4
|
+
route: RouteLocalContextValue;
|
|
5
|
+
appearance?: SheetAppearance;
|
|
6
|
+
onDismissed: () => void;
|
|
7
|
+
item: HistoryItem;
|
|
8
|
+
}
|
|
9
|
+
export declare const ScreenStackSheetItem: import("react").NamedExoticComponent<ScreenStackSheetItemProps>;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=ScreenStackSheetItem.native.d.ts.map
|
|
@@ -26,12 +26,17 @@ type TabBarConfig = Omit<InternalTabItem, 'tabKey' | 'key'> & {
|
|
|
26
26
|
screen?: React.ComponentType<any>;
|
|
27
27
|
component?: ComponentType<TabBarProps>;
|
|
28
28
|
};
|
|
29
|
+
type TabBarOptions = {
|
|
30
|
+
component?: ComponentType<TabBarProps>;
|
|
31
|
+
config?: TabBarConfig;
|
|
32
|
+
initialIndex?: number;
|
|
33
|
+
};
|
|
29
34
|
export declare class TabBar {
|
|
30
35
|
screens: Record<string, React.ComponentType<any>>;
|
|
31
36
|
stacks: Record<string, NavigationStack>;
|
|
32
37
|
private listeners;
|
|
33
38
|
private state;
|
|
34
|
-
constructor(
|
|
39
|
+
constructor(options?: TabBarOptions);
|
|
35
40
|
addTab(tab: Omit<TabBarConfig, 'tabKey'> & {
|
|
36
41
|
key: string;
|
|
37
42
|
}): TabBar;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ColorValue, StyleProp, ViewStyle, TextStyle } from 'react-native';
|
|
2
2
|
import type { BottomTabsScreenProps, ScreenProps as RNSScreenProps, ScreenStackHeaderConfigProps, TabBarItemLabelVisibilityMode, TabBarMinimizeBehavior } from 'react-native-screens';
|
|
3
|
+
export type StackPresentationTypes = 'push' | 'modal' | 'transparentModal' | 'containedModal' | 'containedTransparentModal' | 'fullScreenModal' | 'formSheet' | 'pageSheet' | 'sheet';
|
|
3
4
|
export type TabItem = Omit<BottomTabsScreenProps, 'isFocused' | 'children'>;
|
|
4
5
|
export type NavigationState<Route extends TabItem> = {
|
|
5
6
|
index: number;
|
|
@@ -9,8 +10,10 @@ export type Scope = 'global' | 'tab' | 'root';
|
|
|
9
10
|
export type TabBarIcon = {
|
|
10
11
|
sfSymbolName?: string;
|
|
11
12
|
} | string;
|
|
12
|
-
export type ScreenOptions = Partial<RNSScreenProps
|
|
13
|
+
export type ScreenOptions = Partial<Omit<RNSScreenProps, 'stackPresentation'>> & {
|
|
13
14
|
header?: ScreenStackHeaderConfigProps;
|
|
15
|
+
stackPresentation?: StackPresentationTypes;
|
|
16
|
+
convertModalToSheetForAndroid?: boolean;
|
|
14
17
|
/**
|
|
15
18
|
* Tab bar icon source for this route (used on web renderer, optional on native).
|
|
16
19
|
*/
|
|
@@ -77,6 +80,11 @@ export type TabBarConfig = {
|
|
|
77
80
|
*/
|
|
78
81
|
tabBarMinimizeBehavior?: TabBarMinimizeBehavior;
|
|
79
82
|
};
|
|
83
|
+
export type SheetAppearance = {
|
|
84
|
+
androidFullScreenTopInset?: number;
|
|
85
|
+
backgroundColor?: ColorValue;
|
|
86
|
+
cornerRadius?: number;
|
|
87
|
+
};
|
|
80
88
|
export interface NavigationAppearance {
|
|
81
89
|
tabBar?: {
|
|
82
90
|
backgroundColor?: ColorValue;
|
|
@@ -99,5 +107,6 @@ export interface NavigationAppearance {
|
|
|
99
107
|
};
|
|
100
108
|
screen?: StyleProp<ViewStyle>;
|
|
101
109
|
header?: ScreenStackHeaderConfigProps;
|
|
110
|
+
sheet?: SheetAppearance;
|
|
102
111
|
}
|
|
103
112
|
//# sourceMappingURL=types.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sigmela/router",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "React Native Router",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
|
@@ -44,9 +44,13 @@
|
|
|
44
44
|
"release": "release-it --only-version"
|
|
45
45
|
},
|
|
46
46
|
"keywords": [
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
47
|
+
"React Native Router",
|
|
48
|
+
"react-native-navigation",
|
|
49
|
+
"react-native-router",
|
|
50
|
+
"react-native-navigation",
|
|
51
|
+
"react-native-screens",
|
|
52
|
+
"react-native-bottom-tabs",
|
|
53
|
+
"react-native-bottom-sheet"
|
|
50
54
|
],
|
|
51
55
|
"repository": {
|
|
52
56
|
"type": "git",
|
|
@@ -71,6 +75,7 @@
|
|
|
71
75
|
"@react-native/babel-preset": "0.81.1",
|
|
72
76
|
"@react-native/eslint-config": "^0.81.1",
|
|
73
77
|
"@release-it/conventional-changelog": "^10.0.1",
|
|
78
|
+
"@sigmela/native-sheet": "^0.0.1",
|
|
74
79
|
"@types/jest": "^29.5.14",
|
|
75
80
|
"@types/react": "^19.1.12",
|
|
76
81
|
"commitlint": "^19.8.1",
|
|
@@ -88,6 +93,7 @@
|
|
|
88
93
|
"typescript": "^5.9.2"
|
|
89
94
|
},
|
|
90
95
|
"peerDependencies": {
|
|
96
|
+
"@sigmela/native-sheet": ">=0.0.1",
|
|
91
97
|
"react": "*",
|
|
92
98
|
"react-native": "*",
|
|
93
99
|
"react-native-screens": ">=4.16.0"
|