@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.
@@ -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
  }
@@ -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
- this.applyHistoryChange('pop', gtop);
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
- this.applyHistoryChange('pop', top);
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
- this.applyHistoryChange('pop', top);
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
- ...screenProps,
57
+ stackPresentation: stackPresentation,
47
58
  stackAnimation: stackAnimation ?? item.options?.stackAnimation,
48
59
  children: /*#__PURE__*/_jsx(RouteLocalContext.Provider, {
49
- value: value,
60
+ value: route,
50
61
  children: /*#__PURE__*/_jsx(item.component, {
51
- ...(item.passProps || {})
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
+ });
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ export const ScreenStackSheetItem = () => {
4
+ return null; // TODO: Implement
5
+ };
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+
3
+ export { ScreenStackSheetItem } from './ScreenStackSheetItem';
@@ -6,11 +6,13 @@ export class TabBar {
6
6
  screens = {};
7
7
  stacks = {};
8
8
  listeners = new Set();
9
- constructor(config = {}) {
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
@@ -0,0 +1,2 @@
1
+ export declare const ScreenStackSheetItem: () => null;
2
+ //# sourceMappingURL=ScreenStackSheetItem.web.d.ts.map
@@ -0,0 +1,2 @@
1
+ export { ScreenStackSheetItem } from './ScreenStackSheetItem';
2
+ //# sourceMappingURL=index.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(config?: TabBarConfig);
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.0.17",
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
- "react-native",
48
- "ios",
49
- "android"
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"