@idealyst/navigation 1.0.97 → 1.0.99
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/package.json +5 -5
- package/src/routing/router.web.tsx +81 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@idealyst/navigation",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.99",
|
|
4
4
|
"description": "Cross-platform navigation library for React and React Native",
|
|
5
5
|
"readme": "README.md",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
"publish:npm": "npm publish"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
|
-
"@idealyst/components": "^1.0.
|
|
47
|
-
"@idealyst/theme": "^1.0.
|
|
46
|
+
"@idealyst/components": "^1.0.99",
|
|
47
|
+
"@idealyst/theme": "^1.0.99",
|
|
48
48
|
"@react-navigation/bottom-tabs": ">=7.0.0",
|
|
49
49
|
"@react-navigation/drawer": ">=7.0.0",
|
|
50
50
|
"@react-navigation/native": ">=7.0.0",
|
|
@@ -60,10 +60,10 @@
|
|
|
60
60
|
"react-router-dom": ">=6.0.0"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
|
-
"@idealyst/components": "^1.0.
|
|
63
|
+
"@idealyst/components": "^1.0.99",
|
|
64
64
|
"@idealyst/datagrid": "^1.0.93",
|
|
65
65
|
"@idealyst/datepicker": "^1.0.93",
|
|
66
|
-
"@idealyst/theme": "^1.0.
|
|
66
|
+
"@idealyst/theme": "^1.0.99",
|
|
67
67
|
"@types/react": "^19.1.8",
|
|
68
68
|
"@types/react-dom": "^19.1.6",
|
|
69
69
|
"react": "^19.1.0",
|
|
@@ -1,47 +1,91 @@
|
|
|
1
|
-
import React, { useEffect, useState } from 'react'
|
|
2
|
-
import { Routes, Route, useLocation, useParams
|
|
1
|
+
import React, { useEffect, useState, useRef } from 'react'
|
|
2
|
+
import { Routes, Route, useLocation, useParams } from '../router'
|
|
3
3
|
import { DefaultStackLayout } from '../layouts/DefaultStackLayout'
|
|
4
4
|
import { DefaultTabLayout } from '../layouts/DefaultTabLayout'
|
|
5
5
|
import { NavigatorParam, RouteParam, NotFoundComponentProps } from './types'
|
|
6
6
|
import { NavigateParams } from '../context/types'
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* Wrapper component for
|
|
9
|
+
* Wrapper component for catch-all routes that:
|
|
10
10
|
* 1. Checks onInvalidRoute handler first
|
|
11
|
-
* 2. If handler returns NavigateParams, redirects
|
|
12
|
-
* 3. If handler returns undefined or no handler, renders notFoundComponent
|
|
11
|
+
* 2. If handler returns NavigateParams, redirects (using absolute path)
|
|
12
|
+
* 3. If handler returns undefined or no handler, renders notFoundComponent (if provided)
|
|
13
13
|
*/
|
|
14
14
|
const NotFoundWrapper = ({
|
|
15
15
|
component: Component,
|
|
16
16
|
onInvalidRoute
|
|
17
17
|
}: {
|
|
18
|
-
component
|
|
18
|
+
component?: React.ComponentType<NotFoundComponentProps>
|
|
19
19
|
onInvalidRoute?: (path: string) => NavigateParams | undefined
|
|
20
20
|
}) => {
|
|
21
21
|
const location = useLocation()
|
|
22
22
|
const params = useParams()
|
|
23
|
-
const navigate = useNavigate()
|
|
24
23
|
const [shouldRender, setShouldRender] = useState(false)
|
|
24
|
+
const redirectingRef = useRef(false)
|
|
25
|
+
const lastPathRef = useRef(location.pathname)
|
|
25
26
|
|
|
26
27
|
useEffect(() => {
|
|
28
|
+
// Reset state if path changed (navigated to a different invalid route)
|
|
29
|
+
if (lastPathRef.current !== location.pathname) {
|
|
30
|
+
lastPathRef.current = location.pathname
|
|
31
|
+
redirectingRef.current = false
|
|
32
|
+
setShouldRender(false)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Prevent multiple redirects
|
|
36
|
+
if (redirectingRef.current) {
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
27
40
|
// Check if handler wants to redirect
|
|
28
41
|
if (onInvalidRoute) {
|
|
29
42
|
const redirectParams = onInvalidRoute(location.pathname)
|
|
30
43
|
if (redirectParams) {
|
|
31
|
-
//
|
|
32
|
-
|
|
44
|
+
// Mark as redirecting to prevent loops
|
|
45
|
+
redirectingRef.current = true
|
|
46
|
+
|
|
47
|
+
// Handler returned NavigateParams - redirect using absolute path
|
|
48
|
+
// Ensure path starts with / for absolute navigation
|
|
49
|
+
let targetPath = redirectParams.path
|
|
50
|
+
if (!targetPath.startsWith('/')) {
|
|
51
|
+
targetPath = `/${targetPath}`
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Substitute any vars in the path
|
|
55
|
+
if (redirectParams.vars) {
|
|
56
|
+
Object.entries(redirectParams.vars).forEach(([key, value]) => {
|
|
57
|
+
targetPath = targetPath.replace(`:${key}`, value)
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Use window.history for truly absolute navigation
|
|
62
|
+
// React Router's navigate can have issues in catch-all contexts
|
|
63
|
+
const replaceMode = redirectParams.replace ?? true
|
|
64
|
+
if (replaceMode) {
|
|
65
|
+
window.history.replaceState(null, '', targetPath)
|
|
66
|
+
} else {
|
|
67
|
+
window.history.pushState(null, '', targetPath)
|
|
68
|
+
}
|
|
69
|
+
// Trigger React Router to sync with the new URL
|
|
70
|
+
window.dispatchEvent(new PopStateEvent('popstate'))
|
|
33
71
|
return
|
|
34
72
|
}
|
|
35
73
|
}
|
|
36
|
-
// No redirect - show the 404 component
|
|
74
|
+
// No redirect - show the 404 component (if provided)
|
|
37
75
|
setShouldRender(true)
|
|
38
|
-
}, [location.pathname, onInvalidRoute
|
|
76
|
+
}, [location.pathname, onInvalidRoute])
|
|
39
77
|
|
|
40
78
|
// Don't render until we've checked the handler
|
|
41
79
|
if (!shouldRender) {
|
|
42
80
|
return null
|
|
43
81
|
}
|
|
44
82
|
|
|
83
|
+
// If no component provided, render nothing (handler-only mode)
|
|
84
|
+
if (!Component) {
|
|
85
|
+
console.warn(`[Navigation] Invalid route "${location.pathname}" - no notFoundComponent configured and onInvalidRoute returned undefined`)
|
|
86
|
+
return null
|
|
87
|
+
}
|
|
88
|
+
|
|
45
89
|
return <Component path={location.pathname} params={params as Record<string, string>} />
|
|
46
90
|
}
|
|
47
91
|
|
|
@@ -99,8 +143,32 @@ const buildRoute = (params: RouteParam, index: number, isNested = false) => {
|
|
|
99
143
|
// Build child routes including catch-all for 404
|
|
100
144
|
const childRoutes = params.routes.map((child, childIndex) => buildRoute(child, childIndex, true));
|
|
101
145
|
|
|
102
|
-
// Add catch-all
|
|
103
|
-
if (params.notFoundComponent) {
|
|
146
|
+
// Add catch-all and index routes if notFoundComponent or onInvalidRoute is configured
|
|
147
|
+
if (params.notFoundComponent || params.onInvalidRoute) {
|
|
148
|
+
// Check if any route handles the index (empty path or "/" for this navigator)
|
|
149
|
+
const hasIndexRoute = params.routes.some(route => {
|
|
150
|
+
const childPath = route.path.startsWith('/') ? route.path.slice(1) : route.path;
|
|
151
|
+
return childPath === '' || childPath === '/';
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// If no index route defined, add one for the 404 handler
|
|
155
|
+
// This handles the case where user visits the navigator's root path directly
|
|
156
|
+
if (!hasIndexRoute) {
|
|
157
|
+
childRoutes.push(
|
|
158
|
+
<Route
|
|
159
|
+
key="__notFoundIndex__"
|
|
160
|
+
index
|
|
161
|
+
element={
|
|
162
|
+
<NotFoundWrapper
|
|
163
|
+
component={params.notFoundComponent}
|
|
164
|
+
onInvalidRoute={params.onInvalidRoute}
|
|
165
|
+
/>
|
|
166
|
+
}
|
|
167
|
+
/>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Add catch-all for non-index invalid routes
|
|
104
172
|
childRoutes.push(
|
|
105
173
|
<Route
|
|
106
174
|
key="__notFound__"
|