@idealyst/navigation 1.0.97 → 1.0.98

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@idealyst/navigation",
3
- "version": "1.0.97",
3
+ "version": "1.0.98",
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.97",
47
- "@idealyst/theme": "^1.0.97",
46
+ "@idealyst/components": "^1.0.98",
47
+ "@idealyst/theme": "^1.0.98",
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.97",
63
+ "@idealyst/components": "^1.0.98",
64
64
  "@idealyst/datagrid": "^1.0.93",
65
65
  "@idealyst/datepicker": "^1.0.93",
66
- "@idealyst/theme": "^1.0.97",
66
+ "@idealyst/theme": "^1.0.98",
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, useNavigate } from '../router'
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 notFoundComponent that:
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: React.ComponentType<NotFoundComponentProps>
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
- // Handler returned NavigateParams - redirect
32
- navigate(redirectParams.path, { replace: redirectParams.replace ?? true })
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, navigate])
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,8 @@ 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 route for notFoundComponent if configured
103
- if (params.notFoundComponent) {
146
+ // Add catch-all route if notFoundComponent or onInvalidRoute is configured
147
+ if (params.notFoundComponent || params.onInvalidRoute) {
104
148
  childRoutes.push(
105
149
  <Route
106
150
  key="__notFound__"