@idealyst/navigation 1.1.4 → 1.1.5
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/context/NavigatorContext.native.tsx +16 -4
- package/src/context/NavigatorContext.web.tsx +17 -2
- package/src/context/types.ts +10 -0
- package/src/hooks/index.native.ts +2 -1
- package/src/hooks/index.ts +2 -1
- package/src/hooks/index.web.ts +2 -1
- package/src/hooks/useNavigationState.native.ts +34 -0
- package/src/hooks/useNavigationState.web.ts +87 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@idealyst/navigation",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.5",
|
|
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.1.
|
|
47
|
-
"@idealyst/theme": "^1.1.
|
|
46
|
+
"@idealyst/components": "^1.1.5",
|
|
47
|
+
"@idealyst/theme": "^1.1.5",
|
|
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.1.
|
|
63
|
+
"@idealyst/components": "^1.1.5",
|
|
64
64
|
"@idealyst/datagrid": "^1.0.93",
|
|
65
65
|
"@idealyst/datepicker": "^1.0.93",
|
|
66
|
-
"@idealyst/theme": "^1.1.
|
|
66
|
+
"@idealyst/theme": "^1.1.5",
|
|
67
67
|
"@types/react": "^19.1.8",
|
|
68
68
|
"@types/react-dom": "^19.1.6",
|
|
69
69
|
"react": "^19.1.0",
|
|
@@ -198,17 +198,23 @@ const UnwrappedNavigatorProvider = ({ route }: NavigatorProviderProps) => {
|
|
|
198
198
|
return;
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
+
// Merge route params with navigation state (state values stored directly in params)
|
|
202
|
+
const navigationParams = {
|
|
203
|
+
...parsed.params,
|
|
204
|
+
...(params.state || {}),
|
|
205
|
+
};
|
|
206
|
+
|
|
201
207
|
if (params.replace) {
|
|
202
208
|
// Use CommonActions.reset to replace the current route
|
|
203
209
|
navigation.dispatch(
|
|
204
210
|
CommonActions.reset({
|
|
205
211
|
index: 0,
|
|
206
|
-
routes: [{ name: parsed.routeName, params:
|
|
212
|
+
routes: [{ name: parsed.routeName, params: navigationParams }],
|
|
207
213
|
})
|
|
208
214
|
);
|
|
209
215
|
} else {
|
|
210
216
|
// Navigate to the pattern route with extracted parameters
|
|
211
|
-
navigation.navigate(parsed.routeName as never,
|
|
217
|
+
navigation.navigate(parsed.routeName as never, navigationParams as never);
|
|
212
218
|
}
|
|
213
219
|
};
|
|
214
220
|
|
|
@@ -290,17 +296,23 @@ const DrawerNavigatorProvider = ({ navigation, route, children }: { navigation:
|
|
|
290
296
|
return;
|
|
291
297
|
}
|
|
292
298
|
|
|
299
|
+
// Merge route params with navigation state (state values stored directly in params)
|
|
300
|
+
const navigationParams = {
|
|
301
|
+
...parsed.params,
|
|
302
|
+
...(params.state || {}),
|
|
303
|
+
};
|
|
304
|
+
|
|
293
305
|
if (params.replace) {
|
|
294
306
|
// Use CommonActions.reset to replace the current route
|
|
295
307
|
navigation.dispatch(
|
|
296
308
|
CommonActions.reset({
|
|
297
309
|
index: 0,
|
|
298
|
-
routes: [{ name: parsed.routeName, params:
|
|
310
|
+
routes: [{ name: parsed.routeName, params: navigationParams }],
|
|
299
311
|
})
|
|
300
312
|
);
|
|
301
313
|
} else {
|
|
302
314
|
// Navigate to the pattern route with extracted parameters
|
|
303
|
-
navigation.navigate(parsed.routeName as never,
|
|
315
|
+
navigation.navigate(parsed.routeName as never, navigationParams as never);
|
|
304
316
|
}
|
|
305
317
|
};
|
|
306
318
|
|
|
@@ -238,8 +238,23 @@ export const NavigatorProvider = ({
|
|
|
238
238
|
// If no notFoundComponent configured, React Router will render nothing
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
-
//
|
|
242
|
-
|
|
241
|
+
// Build URL with query params if state is provided
|
|
242
|
+
let finalPath = path;
|
|
243
|
+
if (params.state && Object.keys(params.state).length > 0) {
|
|
244
|
+
const searchParams = new URLSearchParams();
|
|
245
|
+
for (const [key, value] of Object.entries(params.state)) {
|
|
246
|
+
if (value !== undefined && value !== null) {
|
|
247
|
+
searchParams.set(key, String(value));
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const queryString = searchParams.toString();
|
|
251
|
+
if (queryString) {
|
|
252
|
+
finalPath = `${path}?${queryString}`;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Use React Router's navigate function
|
|
257
|
+
reactRouterNavigate(finalPath, { replace: params.replace });
|
|
243
258
|
}
|
|
244
259
|
};
|
|
245
260
|
|
package/src/context/types.ts
CHANGED
|
@@ -11,6 +11,16 @@ export type NavigateParams = {
|
|
|
11
11
|
* On web, this uses history.replace(). On native, this resets the navigation state.
|
|
12
12
|
*/
|
|
13
13
|
replace?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Optional state data to pass to the destination screen.
|
|
16
|
+
* On web, this is passed as URL query parameters (e.g., ?autostart=true).
|
|
17
|
+
* On native, this is passed via route params.
|
|
18
|
+
* Use useNavigationState() hook to access this data in the destination screen.
|
|
19
|
+
*
|
|
20
|
+
* Note: Values are serialized to strings. Booleans and numbers are automatically
|
|
21
|
+
* parsed back when using useNavigationState().
|
|
22
|
+
*/
|
|
23
|
+
state?: Record<string, string | number | boolean>;
|
|
14
24
|
};
|
|
15
25
|
|
|
16
26
|
export type NavigatorProviderProps = {
|
package/src/hooks/index.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
// Cross-platform hook exports
|
|
2
|
-
export * from './useParams.web'; // This will be overridden by platform-specific exports
|
|
2
|
+
export * from './useParams.web'; // This will be overridden by platform-specific exports
|
|
3
|
+
export * from './useNavigationState.web'; // This will be overridden by platform-specific exports
|
package/src/hooks/index.web.ts
CHANGED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useRoute } from '@react-navigation/native';
|
|
2
|
+
|
|
3
|
+
export interface UseNavigationStateOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Keys to remove from the URL query parameters after reading.
|
|
6
|
+
* This option only affects web - it has no effect on native.
|
|
7
|
+
* Included here for API consistency across platforms.
|
|
8
|
+
*/
|
|
9
|
+
consume?: string[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Hook to access navigation state passed via the navigate() function.
|
|
14
|
+
* On native, state is passed via route params (merged with path params).
|
|
15
|
+
* Returns the state object passed during navigation, or an empty object if no state was passed.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* // Navigate with state
|
|
19
|
+
* navigate({ path: '/recording', state: { autostart: true } });
|
|
20
|
+
*
|
|
21
|
+
* // Access state in destination screen
|
|
22
|
+
* const { autostart } = useNavigationState<{ autostart?: boolean }>();
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* // The consume option is accepted but has no effect on native
|
|
26
|
+
* const { autostart } = useNavigationState<{ autostart?: boolean }>({ consume: ['autostart'] });
|
|
27
|
+
*/
|
|
28
|
+
export function useNavigationState<T extends Record<string, unknown> = Record<string, unknown>>(
|
|
29
|
+
_options?: UseNavigationStateOptions
|
|
30
|
+
): T {
|
|
31
|
+
const route = useRoute();
|
|
32
|
+
const params = route.params as Record<string, unknown> | undefined;
|
|
33
|
+
return (params as T) || ({} as T);
|
|
34
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
import { useSearchParams } from '../router';
|
|
3
|
+
|
|
4
|
+
export interface UseNavigationStateOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Keys to remove from the URL query parameters after reading.
|
|
7
|
+
* This is useful for one-time state like 'autostart' that shouldn't persist in the URL.
|
|
8
|
+
* Only affects web - has no effect on native.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* // URL: /recording?autostart=true
|
|
12
|
+
* const { autostart } = useNavigationState<{ autostart?: boolean }>({ consume: ['autostart'] });
|
|
13
|
+
* // autostart = true, URL becomes: /recording
|
|
14
|
+
*/
|
|
15
|
+
consume?: string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Hook to access navigation state passed via the navigate() function.
|
|
20
|
+
* On web, state is passed as URL query parameters.
|
|
21
|
+
* Returns the state object passed during navigation, or an empty object if no state was passed.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* // Navigate with state
|
|
25
|
+
* navigate({ path: '/recording', state: { autostart: true } });
|
|
26
|
+
*
|
|
27
|
+
* // Access state in destination screen
|
|
28
|
+
* const { autostart } = useNavigationState<{ autostart?: boolean }>();
|
|
29
|
+
* // URL will be: /recording?autostart=true
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* // Consume (remove) the parameter after reading
|
|
33
|
+
* const { autostart } = useNavigationState<{ autostart?: boolean }>({ consume: ['autostart'] });
|
|
34
|
+
* // autostart = true, URL becomes: /recording (param removed)
|
|
35
|
+
*/
|
|
36
|
+
function parseSearchParams(searchParams: URLSearchParams): Record<string, unknown> {
|
|
37
|
+
const state: Record<string, unknown> = {};
|
|
38
|
+
searchParams.forEach((value, key) => {
|
|
39
|
+
if (value === 'true') {
|
|
40
|
+
state[key] = true;
|
|
41
|
+
} else if (value === 'false') {
|
|
42
|
+
state[key] = false;
|
|
43
|
+
} else if (!isNaN(Number(value)) && value !== '') {
|
|
44
|
+
state[key] = Number(value);
|
|
45
|
+
} else {
|
|
46
|
+
state[key] = value;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
return state;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function useNavigationState<T extends Record<string, unknown> = Record<string, unknown>>(
|
|
53
|
+
options?: UseNavigationStateOptions
|
|
54
|
+
): T {
|
|
55
|
+
const [searchParams, setSearchParams] = useSearchParams();
|
|
56
|
+
const consumedStateRef = useRef<Record<string, unknown> | null>(null);
|
|
57
|
+
const hasConsumed = useRef(false);
|
|
58
|
+
|
|
59
|
+
// Capture consumed values before they're removed from URL
|
|
60
|
+
if (options?.consume && options.consume.length > 0 && !hasConsumed.current) {
|
|
61
|
+
const keysToConsume = options.consume.filter(key => searchParams.has(key));
|
|
62
|
+
if (keysToConsume.length > 0 && consumedStateRef.current === null) {
|
|
63
|
+
// Capture the full state including values we're about to consume
|
|
64
|
+
consumedStateRef.current = parseSearchParams(searchParams);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Remove consumed keys from URL after initial read
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (options?.consume && options.consume.length > 0 && !hasConsumed.current) {
|
|
71
|
+
const keysToRemove = options.consume.filter(key => searchParams.has(key));
|
|
72
|
+
if (keysToRemove.length > 0) {
|
|
73
|
+
hasConsumed.current = true;
|
|
74
|
+
const newParams = new URLSearchParams(searchParams);
|
|
75
|
+
keysToRemove.forEach(key => newParams.delete(key));
|
|
76
|
+
setSearchParams(newParams, { replace: true });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}, [options?.consume, searchParams, setSearchParams]);
|
|
80
|
+
|
|
81
|
+
// Return captured state if we consumed params, otherwise parse current URL
|
|
82
|
+
if (consumedStateRef.current !== null) {
|
|
83
|
+
return consumedStateRef.current as T;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return parseSearchParams(searchParams) as T;
|
|
87
|
+
}
|