@sigmela/router 0.3.7 → 0.5.0

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 (118) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/LICENSE +2 -1
  3. package/README.md +127 -471
  4. package/dist/Router.d.ts +190 -0
  5. package/dist/Router.d.ts.map +1 -0
  6. package/dist/components/Modal.d.ts +56 -0
  7. package/dist/components/Modal.d.ts.map +1 -0
  8. package/dist/components/OverlayContentScope.d.ts +8 -0
  9. package/dist/components/OverlayContentScope.d.ts.map +1 -0
  10. package/dist/components/RouterCloseButton.d.ts +9 -0
  11. package/dist/components/RouterCloseButton.d.ts.map +1 -0
  12. package/dist/components/Sheet.d.ts +56 -0
  13. package/dist/components/Sheet.d.ts.map +1 -0
  14. package/dist/components/SheetSurface.d.ts +27 -0
  15. package/dist/components/SheetSurface.d.ts.map +1 -0
  16. package/dist/components/index.d.ts +4 -0
  17. package/dist/components/index.d.ts.map +1 -0
  18. package/dist/components/pageScrollLock.d.ts +3 -0
  19. package/dist/components/pageScrollLock.d.ts.map +1 -0
  20. package/dist/components/sheetDragEngine.d.ts +27 -0
  21. package/dist/components/sheetDragEngine.d.ts.map +1 -0
  22. package/dist/index.d.ts +5 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +3696 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/navigationHistory.d.ts +17 -0
  27. package/dist/navigationHistory.d.ts.map +1 -0
  28. package/dist/router.css +975 -0
  29. package/dist/types.d.ts +3 -0
  30. package/dist/types.d.ts.map +1 -0
  31. package/dist/utils/id.d.ts +2 -0
  32. package/dist/utils/id.d.ts.map +1 -0
  33. package/package.json +73 -174
  34. package/lib/module/Drawer/Drawer.js +0 -250
  35. package/lib/module/Drawer/DrawerContext.js +0 -4
  36. package/lib/module/Drawer/DrawerIcon.web.js +0 -47
  37. package/lib/module/Drawer/RenderDrawer.native.js +0 -248
  38. package/lib/module/Drawer/RenderDrawer.web.js +0 -197
  39. package/lib/module/Drawer/useDrawer.js +0 -11
  40. package/lib/module/Navigation.js +0 -80
  41. package/lib/module/NavigationStack.js +0 -254
  42. package/lib/module/Router.js +0 -2171
  43. package/lib/module/RouterContext.js +0 -33
  44. package/lib/module/ScreenStack/ScreenStack.native.js +0 -3
  45. package/lib/module/ScreenStack/ScreenStack.web.js +0 -561
  46. package/lib/module/ScreenStack/ScreenStackContext.js +0 -21
  47. package/lib/module/ScreenStack/animationHelpers.js +0 -87
  48. package/lib/module/ScreenStack/index.js +0 -3
  49. package/lib/module/ScreenStackItem/ScreenStackItem.js +0 -70
  50. package/lib/module/ScreenStackItem/ScreenStackItem.types.js +0 -3
  51. package/lib/module/ScreenStackItem/ScreenStackItem.web.js +0 -171
  52. package/lib/module/ScreenStackItem/index.js +0 -3
  53. package/lib/module/ScreenStackSheetItem/ScreenStackSheetItem.native.js +0 -70
  54. package/lib/module/ScreenStackSheetItem/ScreenStackSheetItem.web.js +0 -5
  55. package/lib/module/ScreenStackSheetItem/index.js +0 -3
  56. package/lib/module/SplitView/RenderSplitView.native.js +0 -101
  57. package/lib/module/SplitView/RenderSplitView.web.js +0 -107
  58. package/lib/module/SplitView/SplitView.js +0 -97
  59. package/lib/module/SplitView/SplitViewContext.js +0 -4
  60. package/lib/module/SplitView/index.js +0 -5
  61. package/lib/module/SplitView/useSplitView.js +0 -11
  62. package/lib/module/StackRenderer.js +0 -42
  63. package/lib/module/TabBar/RenderTabBar.native.js +0 -418
  64. package/lib/module/TabBar/RenderTabBar.web.js +0 -218
  65. package/lib/module/TabBar/TabBar.js +0 -208
  66. package/lib/module/TabBar/TabBarContext.js +0 -4
  67. package/lib/module/TabBar/TabIcon.web.js +0 -47
  68. package/lib/module/TabBar/index.js +0 -6
  69. package/lib/module/TabBar/useTabBar.js +0 -11
  70. package/lib/module/TabBar/useTabBarHeight.js +0 -22
  71. package/lib/module/createController.js +0 -5
  72. package/lib/module/index.js +0 -15
  73. package/lib/module/navigationNode.js +0 -3
  74. package/lib/module/package.json +0 -1
  75. package/lib/module/styles.css +0 -1111
  76. package/lib/module/types.js +0 -13
  77. package/lib/typescript/package.json +0 -1
  78. package/lib/typescript/src/Drawer/Drawer.d.ts +0 -100
  79. package/lib/typescript/src/Drawer/DrawerContext.d.ts +0 -3
  80. package/lib/typescript/src/Drawer/DrawerIcon.web.d.ts +0 -7
  81. package/lib/typescript/src/Drawer/RenderDrawer.native.d.ts +0 -8
  82. package/lib/typescript/src/Drawer/RenderDrawer.web.d.ts +0 -8
  83. package/lib/typescript/src/Drawer/useDrawer.d.ts +0 -2
  84. package/lib/typescript/src/Navigation.d.ts +0 -17
  85. package/lib/typescript/src/NavigationStack.d.ts +0 -46
  86. package/lib/typescript/src/Router.d.ts +0 -232
  87. package/lib/typescript/src/RouterContext.d.ts +0 -18
  88. package/lib/typescript/src/ScreenStack/ScreenStack.native.d.ts +0 -2
  89. package/lib/typescript/src/ScreenStack/ScreenStack.web.d.ts +0 -9
  90. package/lib/typescript/src/ScreenStack/ScreenStackContext.d.ts +0 -31
  91. package/lib/typescript/src/ScreenStack/animationHelpers.d.ts +0 -6
  92. package/lib/typescript/src/ScreenStack/index.d.ts +0 -2
  93. package/lib/typescript/src/ScreenStackItem/ScreenStackItem.d.ts +0 -3
  94. package/lib/typescript/src/ScreenStackItem/ScreenStackItem.types.d.ts +0 -14
  95. package/lib/typescript/src/ScreenStackItem/ScreenStackItem.web.d.ts +0 -3
  96. package/lib/typescript/src/ScreenStackItem/index.d.ts +0 -3
  97. package/lib/typescript/src/ScreenStackSheetItem/ScreenStackSheetItem.native.d.ts +0 -11
  98. package/lib/typescript/src/ScreenStackSheetItem/ScreenStackSheetItem.web.d.ts +0 -2
  99. package/lib/typescript/src/ScreenStackSheetItem/index.d.ts +0 -2
  100. package/lib/typescript/src/SplitView/RenderSplitView.native.d.ts +0 -19
  101. package/lib/typescript/src/SplitView/RenderSplitView.web.d.ts +0 -8
  102. package/lib/typescript/src/SplitView/SplitView.d.ts +0 -33
  103. package/lib/typescript/src/SplitView/SplitViewContext.d.ts +0 -3
  104. package/lib/typescript/src/SplitView/index.d.ts +0 -5
  105. package/lib/typescript/src/SplitView/useSplitView.d.ts +0 -2
  106. package/lib/typescript/src/StackRenderer.d.ts +0 -8
  107. package/lib/typescript/src/TabBar/RenderTabBar.native.d.ts +0 -8
  108. package/lib/typescript/src/TabBar/RenderTabBar.web.d.ts +0 -8
  109. package/lib/typescript/src/TabBar/TabBar.d.ts +0 -112
  110. package/lib/typescript/src/TabBar/TabBarContext.d.ts +0 -3
  111. package/lib/typescript/src/TabBar/TabIcon.web.d.ts +0 -7
  112. package/lib/typescript/src/TabBar/index.d.ts +0 -5
  113. package/lib/typescript/src/TabBar/useTabBar.d.ts +0 -2
  114. package/lib/typescript/src/TabBar/useTabBarHeight.d.ts +0 -18
  115. package/lib/typescript/src/createController.d.ts +0 -15
  116. package/lib/typescript/src/index.d.ts +0 -21
  117. package/lib/typescript/src/navigationNode.d.ts +0 -41
  118. package/lib/typescript/src/types.d.ts +0 -140
@@ -1,87 +0,0 @@
1
- "use strict";
2
-
3
- import { isModalLikePresentation } from "../types.js";
4
- export function getPresentationTypeClass(presentation) {
5
- switch (presentation) {
6
- case 'push':
7
- return 'push';
8
- case 'modal':
9
- return 'modal';
10
- case 'modalRight':
11
- return 'modal-right';
12
- case 'transparentModal':
13
- return 'transparent-modal';
14
- case 'containedModal':
15
- return 'contained-modal';
16
- case 'containedTransparentModal':
17
- return 'contained-transparent-modal';
18
- case 'fullScreenModal':
19
- return 'fullscreen-modal';
20
- case 'formSheet':
21
- return 'formsheet';
22
- case 'pageSheet':
23
- return 'pagesheet';
24
- case 'sheet':
25
- return 'sheet';
26
- default:
27
- return 'push';
28
- }
29
- }
30
- export function getAnimationTypeForPresentation(presentation, isEntering, direction) {
31
- const suffix = isEntering ? 'enter' : 'exit';
32
- const presentationClass = getPresentationTypeClass(presentation);
33
- if (presentation === 'push') {
34
- return direction === 'forward' ? `push-${suffix}` : `pop-${suffix}`;
35
- }
36
- return `${presentationClass}-${suffix}`;
37
- }
38
- export function computeAnimationType(_key, isInStack, isTop, direction, presentation, isInitialPhase, animated = true, isBulkRemoval = false) {
39
- if (!animated) {
40
- return 'no-animate';
41
- }
42
- if (isInitialPhase) {
43
- return 'none';
44
- }
45
-
46
- // When multiple screens are removed at once (bulk removal), don't animate them
47
- if (isBulkRemoval && !isInStack) {
48
- return 'no-animate';
49
- }
50
- const isEntering = isInStack && isTop;
51
- const isModalLike = isModalLikePresentation(presentation);
52
- if (isModalLike) {
53
- if (!isInStack) {
54
- return getAnimationTypeForPresentation(presentation, false, direction);
55
- }
56
- if (isEntering) {
57
- return getAnimationTypeForPresentation(presentation, true, direction);
58
- }
59
-
60
- // Modal-like screen that's NOT top (background) - animate like push
61
- // This happens when navigating inside a modal stack
62
- if (direction === 'forward') {
63
- return 'push-background';
64
- } else {
65
- return 'pop-background';
66
- }
67
- }
68
- if (!isInStack) {
69
- if (direction === 'forward') {
70
- return 'push-exit';
71
- } else {
72
- return 'pop-exit';
73
- }
74
- }
75
- if (isTop) {
76
- if (direction === 'forward') {
77
- return 'push-enter';
78
- } else {
79
- return 'pop-enter';
80
- }
81
- }
82
- if (direction === 'forward') {
83
- return 'push-background';
84
- } else {
85
- return 'pop-background';
86
- }
87
- }
@@ -1,3 +0,0 @@
1
- "use strict";
2
-
3
- export { ScreenStack } from './ScreenStack';
@@ -1,70 +0,0 @@
1
- "use strict";
2
-
3
- import { ScreenStackItem as RNSScreenStackItem } from 'react-native-screens';
4
- import { RouteLocalContext, useRouter } from "../RouterContext.js";
5
- import { ScreenStackSheetItem } from "../ScreenStackSheetItem/index.js";
6
- import { StyleSheet } from 'react-native';
7
- import { memo, useMemo, useCallback } from 'react';
8
- import { jsx as _jsx } from "react/jsx-runtime";
9
- export const ScreenStackItem = /*#__PURE__*/memo(({
10
- item,
11
- stackId,
12
- stackAnimation,
13
- appearance
14
- }) => {
15
- const {
16
- header,
17
- stackPresentation,
18
- ...screenProps
19
- } = item.options || {};
20
-
21
- // On native, modalRight behaves as regular modal
22
- const nativePresentation = stackPresentation === 'modalRight' ? 'modal' : stackPresentation;
23
- const route = useMemo(() => ({
24
- presentation: stackPresentation ?? 'push',
25
- params: item.params,
26
- query: item.query,
27
- pattern: item.pattern,
28
- path: item.path
29
- }), [stackPresentation, item.params, item.query, item.pattern, item.path]);
30
- const router = useRouter();
31
- const onDismissed = useCallback(() => {
32
- if (stackId) {
33
- const history = router.getStackHistory(stackId);
34
- const topKey = history.length ? history[history.length - 1]?.key : null;
35
- if (topKey && topKey === item.key) {
36
- router.goBack();
37
- }
38
- }
39
- }, [stackId, router, item.key]);
40
- const headerConfig = useMemo(() => ({
41
- ...header,
42
- hidden: !header?.title || header?.hidden,
43
- backgroundColor: appearance?.header?.backgroundColor ?? 'transparent'
44
- }), [header, appearance?.header?.backgroundColor]);
45
- if (route.presentation === 'sheet') {
46
- return /*#__PURE__*/_jsx(ScreenStackSheetItem, {
47
- appearance: appearance?.sheet,
48
- onDismissed: onDismissed,
49
- route: route,
50
- item: item
51
- });
52
- }
53
- return /*#__PURE__*/_jsx(RNSScreenStackItem, {
54
- ...screenProps,
55
- screenId: item.key,
56
- onDismissed: onDismissed,
57
- style: StyleSheet.absoluteFill,
58
- contentStyle: appearance?.screen,
59
- headerConfig: headerConfig,
60
- stackPresentation: nativePresentation,
61
- stackAnimation: stackAnimation ?? item.options?.stackAnimation,
62
- children: /*#__PURE__*/_jsx(RouteLocalContext.Provider, {
63
- value: route,
64
- children: /*#__PURE__*/_jsx(item.component, {
65
- ...item.passProps,
66
- appearance: appearance
67
- })
68
- })
69
- }, item.key);
70
- });
@@ -1,3 +0,0 @@
1
- "use strict";
2
-
3
- export {};
@@ -1,171 +0,0 @@
1
- "use strict";
2
-
3
- import { RouteLocalContext, useRouter } from "../RouterContext.js";
4
- import { isModalLikePresentation } from "../types.js";
5
- import { memo, useMemo, useCallback, useEffect, useRef } from 'react';
6
- import { StyleSheet, View } from 'react-native';
7
- import { useScreenStackItemsContext } from "../ScreenStack/ScreenStackContext.js";
8
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
9
- export const ScreenStackItem = /*#__PURE__*/memo(({
10
- item,
11
- appearance,
12
- style
13
- }) => {
14
- const itemsContext = useScreenStackItemsContext();
15
- const router = useRouter();
16
- const containerRef = useRef(null);
17
- const debugEnabled = router.isDebugEnabled();
18
- const devLog = useCallback((msg, data) => {
19
- if (!debugEnabled) return;
20
- console.log(msg, data !== undefined ? JSON.stringify(data) : '');
21
- }, [debugEnabled]);
22
- const key = item.key;
23
- const itemState = itemsContext.items[key];
24
- const presentationType = itemState?.presentationType;
25
- const animationType = itemState?.animationType;
26
- const phase = itemState?.phase;
27
- const transitionStatus = itemState?.transitionStatus;
28
- const zIndex = itemState?.zIndex ?? 0;
29
- const presentation = item.options?.stackPresentation ?? 'push';
30
- const isModalLike = isModalLikePresentation(presentation);
31
- const className = useMemo(() => {
32
- const classes = ['screen-stack-item'];
33
- if (presentationType) {
34
- classes.push(presentationType);
35
- }
36
- if (animationType) {
37
- classes.push(animationType);
38
- }
39
- if (transitionStatus) {
40
- classes.push(`transition-${transitionStatus}`);
41
- }
42
- if (phase) {
43
- classes.push(`phase-${phase}`);
44
- }
45
- devLog('[ScreenStackItem] className', {
46
- key: item.key,
47
- path: item.path,
48
- presentationType,
49
- animationType,
50
- phase,
51
- transitionStatus,
52
- className: classes.join(' ')
53
- });
54
- return classes.join(' ');
55
- }, [presentationType, animationType, transitionStatus, phase, devLog, item.key, item.path]);
56
- const mergedStyle = useMemo(() => ({
57
- flex: 1,
58
- ...style,
59
- zIndex
60
- }), [style, zIndex]);
61
- const modalContainerStyle = useMemo(() => {
62
- if (!isModalLike || !item.options?.maxWidth) {
63
- return undefined;
64
- }
65
- return {
66
- maxWidth: `${item.options.maxWidth}px`
67
- };
68
- }, [isModalLike, item.options?.maxWidth]);
69
- const routeValue = useMemo(() => ({
70
- presentation,
71
- params: item.params,
72
- query: item.query,
73
- pattern: item.pattern,
74
- path: item.path
75
- }), [presentation, item.params, item.query, item.pattern, item.path]);
76
- const handleDismiss = useCallback(() => router.dismiss(), [router]);
77
- const handleDismissKeyDown = useCallback(e => {
78
- if (e.key === 'Enter' || e.key === ' ') {
79
- e.preventDefault?.();
80
- router.dismiss();
81
- }
82
- }, [router]);
83
-
84
- // Escape key handler for modals — only dismiss if this is the topmost modal
85
- useEffect(() => {
86
- if (!isModalLike) return;
87
- const handler = e => {
88
- if (e.key !== 'Escape') return;
89
- const allModals = document.querySelectorAll('[aria-modal="true"]');
90
- if (allModals.length > 0) {
91
- const last = allModals[allModals.length - 1] ?? null;
92
- if (!containerRef.current?.contains(last)) return;
93
- }
94
- router.dismiss();
95
- };
96
- document.addEventListener('keydown', handler);
97
- return () => document.removeEventListener('keydown', handler);
98
- }, [isModalLike, router]);
99
-
100
- // Focus trap for modals — re-queries focusable elements on each Tab
101
- useEffect(() => {
102
- if (!isModalLike || !containerRef.current) return;
103
- const container = containerRef.current;
104
- const FOCUSABLE = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
105
- const focusable = container.querySelectorAll(FOCUSABLE);
106
- focusable[0]?.focus();
107
- const trap = e => {
108
- if (e.key !== 'Tab') return;
109
- const els = container.querySelectorAll(FOCUSABLE);
110
- if (!els.length) return;
111
- const first = els[0];
112
- const last = els[els.length - 1];
113
- if (e.shiftKey && document.activeElement === first) {
114
- e.preventDefault();
115
- last?.focus();
116
- } else if (!e.shiftKey && document.activeElement === last) {
117
- e.preventDefault();
118
- first?.focus();
119
- }
120
- };
121
- container.addEventListener('keydown', trap);
122
- return () => container.removeEventListener('keydown', trap);
123
- }, [isModalLike]);
124
- if (!itemState) {
125
- return null;
126
- }
127
- return /*#__PURE__*/_jsxs("div", {
128
- ref: containerRef,
129
- style: mergedStyle,
130
- className: className,
131
- children: [isModalLike && /*#__PURE__*/_jsx("div", {
132
- className: "stack-modal-overlay",
133
- onClick: handleDismiss,
134
- onKeyDown: handleDismissKeyDown,
135
- role: "button",
136
- "aria-label": "Close modal",
137
- tabIndex: 0
138
- }), /*#__PURE__*/_jsx("div", {
139
- className: isModalLike ? 'stack-modal-container' : 'stack-screen-container',
140
- style: modalContainerStyle,
141
- ...(isModalLike ? {
142
- 'role': 'dialog',
143
- 'aria-modal': true,
144
- 'aria-label': item.pattern || 'Dialog'
145
- } : undefined),
146
- children: appearance?.screen ? /*#__PURE__*/_jsx(View, {
147
- style: [appearance?.screen, styles.flex],
148
- children: /*#__PURE__*/_jsx(RouteLocalContext.Provider, {
149
- value: routeValue,
150
- children: /*#__PURE__*/_jsx(item.component, {
151
- ...(item.passProps || {}),
152
- appearance: appearance
153
- })
154
- })
155
- }) : /*#__PURE__*/_jsx(RouteLocalContext.Provider, {
156
- value: routeValue,
157
- children: /*#__PURE__*/_jsx(item.component, {
158
- ...(item.passProps || {}),
159
- appearance: appearance
160
- })
161
- })
162
- })]
163
- });
164
- }, (prevProps, nextProps) => {
165
- return prevProps.item.key === nextProps.item.key && prevProps.item === nextProps.item && prevProps.appearance === nextProps.appearance && prevProps.style === nextProps.style;
166
- });
167
- const styles = StyleSheet.create({
168
- flex: {
169
- flex: 1
170
- }
171
- });
@@ -1,3 +0,0 @@
1
- "use strict";
2
-
3
- export { ScreenStackItem } from './ScreenStackItem';
@@ -1,70 +0,0 @@
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, useCallback } 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
- return () => router.unregisterSheetDismisser(item.key);
32
- }
33
- return undefined;
34
- }, [route.presentation, router, item.key]);
35
- const handleSheetDismissed = useCallback(() => {
36
- router.unregisterSheetDismisser(item.key);
37
- onDismissed();
38
- }, [router, item.key, onDismissed]);
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
- });
@@ -1,5 +0,0 @@
1
- "use strict";
2
-
3
- export const ScreenStackSheetItem = () => {
4
- return null;
5
- };
@@ -1,3 +0,0 @@
1
- "use strict";
2
-
3
- export { ScreenStackSheetItem } from './ScreenStackSheetItem';
@@ -1,101 +0,0 @@
1
- "use strict";
2
-
3
- import { ScreenStackItem } from "../ScreenStackItem/index.js";
4
- import { ScreenStack } from "../ScreenStack/index.js";
5
- import { SplitViewContext } from "./SplitViewContext.js";
6
- import { useRouter } from "../RouterContext.js";
7
- import { memo, useMemo, useRef, useState, useEffect, startTransition } from 'react';
8
- import { StyleSheet } from 'react-native';
9
- import { jsx as _jsx } from "react/jsx-runtime";
10
- const EMPTY_HISTORY = [];
11
- /**
12
- * On native, SplitView renders primary and secondary screens in a SINGLE
13
- * ScreenStack to get native push/pop animations.
14
- *
15
- * The combined history is: [...primaryHistory, ...secondaryHistory]
16
- * This way, navigating from primary to secondary is a native push.
17
- *
18
- * History starts empty and is populated via useEffect + startTransition,
19
- * following the pattern from react-native-screens' BottomTabsContainer.
20
- * This lets the parent Screen attach before child ScreenStackItems mount.
21
- */
22
- export const RenderSplitView = /*#__PURE__*/memo(({
23
- splitView,
24
- appearance
25
- }) => {
26
- const router = useRouter();
27
- const primaryId = splitView.primary.getId();
28
- const secondaryId = splitView.secondary.getId();
29
-
30
- // Start with empty history — useEffect populates via startTransition.
31
- const hydratedRef = useRef(false);
32
- const [primaryHistory, setPrimaryHistory] = useState(EMPTY_HISTORY);
33
- const [secondaryHistory, setSecondaryHistory] = useState(EMPTY_HISTORY);
34
- useEffect(() => {
35
- hydratedRef.current = true;
36
- const updatePrimary = () => {
37
- startTransition(() => {
38
- setPrimaryHistory(router.getStackHistory(primaryId));
39
- });
40
- };
41
- const updateSecondary = () => {
42
- startTransition(() => {
43
- setSecondaryHistory(router.getStackHistory(secondaryId));
44
- });
45
- };
46
- const unsub1 = router.subscribeStack(primaryId, updatePrimary);
47
- const unsub2 = router.subscribeStack(secondaryId, updateSecondary);
48
-
49
- // Sync in case the store changed between useState init and useEffect
50
- updatePrimary();
51
- updateSecondary();
52
- return () => {
53
- unsub1();
54
- unsub2();
55
- };
56
- }, [router, primaryId, secondaryId]);
57
-
58
- // Fallback: if primary is empty, seed with first route.
59
- // Skip the seed during the initial empty mount (before useEffect
60
- // hydrates history) so no ScreenStackItems mount before the parent Fragment attaches.
61
- const primaryHistoryToRender = useMemo(() => {
62
- if (primaryHistory.length > 0) {
63
- return primaryHistory;
64
- }
65
- if (!hydratedRef.current) return [];
66
- const first = splitView.primary.getFirstRoute();
67
- if (!first) return [];
68
- const activePath = router.getActiveRoute()?.path;
69
- return [{
70
- key: `splitview-seed-${primaryId}`,
71
- routeId: first.routeId,
72
- component: first.component,
73
- options: first.options,
74
- stackId: primaryId,
75
- pattern: first.path,
76
- path: activePath ?? first.path
77
- }];
78
- }, [primaryHistory, splitView.primary, primaryId, router]);
79
-
80
- // Combine histories: primary screens first, then secondary screens on top
81
- // This gives native push animation when navigating from primary to secondary
82
- const combinedHistory = useMemo(() => {
83
- return [...primaryHistoryToRender, ...secondaryHistory];
84
- }, [primaryHistoryToRender, secondaryHistory]);
85
- return /*#__PURE__*/_jsx(SplitViewContext.Provider, {
86
- value: splitView,
87
- children: /*#__PURE__*/_jsx(ScreenStack, {
88
- style: styles.container,
89
- children: combinedHistory.map(item => /*#__PURE__*/_jsx(ScreenStackItem, {
90
- appearance: appearance,
91
- stackId: item.stackId,
92
- item: item
93
- }, `splitview-${item.key}`))
94
- })
95
- });
96
- });
97
- const styles = StyleSheet.create({
98
- container: {
99
- flex: 1
100
- }
101
- });
@@ -1,107 +0,0 @@
1
- "use strict";
2
-
3
- import { StackRenderer } from "../StackRenderer.js";
4
- import { SplitViewContext } from "./SplitViewContext.js";
5
- import { useRouter } from "../RouterContext.js";
6
- import { ScreenStackConfigContext } from "../ScreenStack/ScreenStackContext.js";
7
- import { memo, useCallback, useEffect, useMemo, useState, useSyncExternalStore } from 'react';
8
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
9
- const StackSliceRenderer = /*#__PURE__*/memo(({
10
- stack,
11
- appearance,
12
- fallbackToFirstRoute
13
- }) => {
14
- const router = useRouter();
15
- const stackId = stack.getId();
16
- const subscribe = useCallback(cb => router.subscribeStack(stackId, cb), [router, stackId]);
17
- const get = useCallback(() => router.getStackHistory(stackId), [router, stackId]);
18
- const history = useSyncExternalStore(subscribe, get, get);
19
- let historyToRender = history;
20
-
21
- // When a container route (like /mail -> SplitView) is active, the child stack can be empty,
22
- // yet we still want to render its root screen. We keep Router history intact and provide a
23
- // renderer-only fallback item.
24
- if (fallbackToFirstRoute && historyToRender.length === 0) {
25
- const first = stack.getFirstRoute();
26
- if (first) {
27
- const activePath = router.getActiveRoute()?.path;
28
- historyToRender = [{
29
- key: `splitview-seed-${stackId}`,
30
- routeId: first.routeId,
31
- component: first.component,
32
- options: first.options,
33
- stackId,
34
- pattern: first.path,
35
- path: activePath ?? first.path
36
- }];
37
- }
38
- }
39
- return /*#__PURE__*/_jsx(StackRenderer, {
40
- appearance: appearance,
41
- stackId: stackId,
42
- history: historyToRender
43
- });
44
- });
45
- StackSliceRenderer.displayName = 'SplitViewStackSliceRenderer';
46
- export const RenderSplitView = /*#__PURE__*/memo(({
47
- splitView,
48
- appearance
49
- }) => {
50
- const [isWide, setIsWide] = useState(() => {
51
- if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {
52
- return false;
53
- }
54
- return window.matchMedia(`(min-width: ${splitView.minWidth}px)`).matches;
55
- });
56
- useEffect(() => {
57
- if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {
58
- return;
59
- }
60
- const mq = window.matchMedia(`(min-width: ${splitView.minWidth}px)`);
61
- const onChange = ev => setIsWide(ev.matches);
62
-
63
- // Sync immediately.
64
- setIsWide(mq.matches);
65
- if (typeof mq.addEventListener === 'function') {
66
- mq.addEventListener('change', onChange);
67
- return () => mq.removeEventListener('change', onChange);
68
- }
69
-
70
- // Safari < 14 fallback (deprecated, but still needed).
71
- const mqAny = mq;
72
- mqAny.addListener?.(onChange);
73
- return () => mqAny.removeListener?.(onChange);
74
- }, [splitView.minWidth]);
75
- const containerStyle = useMemo(() => {
76
- return {
77
- '--split-view-primary-max-width': `${splitView.primaryMaxWidth}px`
78
- };
79
- }, [splitView.primaryMaxWidth]);
80
- const secondaryConfig = useMemo(() => ({
81
- animateFirstScreenAfterEmpty: !isWide
82
- }), [isWide]);
83
- return /*#__PURE__*/_jsx(SplitViewContext.Provider, {
84
- value: splitView,
85
- children: /*#__PURE__*/_jsxs("div", {
86
- className: "split-view-container",
87
- style: containerStyle,
88
- children: [/*#__PURE__*/_jsx("div", {
89
- className: "split-view-primary",
90
- children: /*#__PURE__*/_jsx(StackSliceRenderer, {
91
- appearance: appearance,
92
- stack: splitView.primary,
93
- fallbackToFirstRoute: true
94
- })
95
- }), /*#__PURE__*/_jsx("div", {
96
- className: "split-view-secondary",
97
- children: /*#__PURE__*/_jsx(ScreenStackConfigContext.Provider, {
98
- value: secondaryConfig,
99
- children: /*#__PURE__*/_jsx(StackSliceRenderer, {
100
- appearance: appearance,
101
- stack: splitView.secondary
102
- })
103
- })
104
- })]
105
- })
106
- });
107
- });