@idealyst/navigation 1.3.13 → 1.3.14

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.3.13",
3
+ "version": "1.3.14",
4
4
  "description": "Cross-platform navigation library for React and React Native",
5
5
  "readme": "README.md",
6
6
  "main": "src/index.ts",
@@ -45,9 +45,9 @@
45
45
  },
46
46
  "peerDependencies": {
47
47
  "@idealyst/camera": "^1.2.30",
48
- "@idealyst/components": "^1.3.13",
48
+ "@idealyst/components": "^1.3.14",
49
49
  "@idealyst/microphone": "^1.2.30",
50
- "@idealyst/theme": "^1.3.13",
50
+ "@idealyst/theme": "^1.3.14",
51
51
  "@react-navigation/bottom-tabs": ">=7.0.0",
52
52
  "@react-navigation/drawer": ">=7.0.0",
53
53
  "@react-navigation/native": ">=7.0.0",
@@ -75,13 +75,13 @@
75
75
  "@idealyst/blur": "^1.2.40",
76
76
  "@idealyst/camera": "^1.2.30",
77
77
  "@idealyst/charts": "^1.2.80",
78
- "@idealyst/components": "^1.3.13",
78
+ "@idealyst/components": "^1.3.14",
79
79
  "@idealyst/datagrid": "^1.2.30",
80
80
  "@idealyst/datepicker": "^1.2.30",
81
81
  "@idealyst/lottie": "^1.2.38",
82
- "@idealyst/markdown": "^1.3.13",
82
+ "@idealyst/markdown": "^1.3.14",
83
83
  "@idealyst/microphone": "^1.2.30",
84
- "@idealyst/theme": "^1.3.13",
84
+ "@idealyst/theme": "^1.3.14",
85
85
  "@types/react": "^19.1.8",
86
86
  "@types/react-dom": "^19.1.6",
87
87
  "react": "^19.1.0",
@@ -1,4 +1,5 @@
1
- import { useRoute } from '@react-navigation/native';
1
+ import { useCallback } from 'react';
2
+ import { useRoute, useNavigation } from '@react-navigation/native';
2
3
 
3
4
  export interface UseNavigationStateOptions {
4
5
  /**
@@ -10,25 +11,35 @@ export interface UseNavigationStateOptions {
10
11
  }
11
12
 
12
13
  /**
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.
14
+ * Hook to access and update navigation state.
15
+ * On native, state is stored as route params via React Navigation.
16
+ * Returns a tuple of [state, setState] for reading and updating params in-place.
16
17
  *
17
18
  * @example
18
19
  * // Navigate with state
19
- * navigate({ path: '/recording', state: { autostart: true } });
20
+ * navigate({ path: '/search', state: { q: 'hello', sort: 'asc' } });
20
21
  *
21
- * // Access state in destination screen
22
- * const { autostart } = useNavigationState<{ autostart?: boolean }>();
22
+ * // Read and update state in destination screen
23
+ * const [state, setState] = useNavigationState<{ q: string; sort: string }>();
24
+ * // state.q === 'hello', state.sort === 'asc'
25
+ *
26
+ * // Update params in-place (no navigation)
27
+ * setState({ sort: 'desc' });
23
28
  *
24
29
  * @example
25
30
  * // The consume option is accepted but has no effect on native
26
- * const { autostart } = useNavigationState<{ autostart?: boolean }>({ consume: ['autostart'] });
31
+ * const [state] = useNavigationState<{ autostart?: boolean }>({ consume: ['autostart'] });
27
32
  */
28
33
  export function useNavigationState<T extends Record<string, unknown> = Record<string, unknown>>(
29
34
  _options?: UseNavigationStateOptions
30
- ): T {
35
+ ): [T, (updates: Partial<T>) => void] {
31
36
  const route = useRoute();
32
- const params = route.params as Record<string, unknown> | undefined;
33
- return (params as T) || ({} as T);
37
+ const navigation = useNavigation();
38
+ const params = (route.params as T) || ({} as T);
39
+
40
+ const setState = useCallback((updates: Partial<T>) => {
41
+ navigation.setParams(updates as any);
42
+ }, [navigation]);
43
+
44
+ return [params, setState];
34
45
  }
@@ -1,4 +1,4 @@
1
- import { useEffect, useRef } from 'react';
1
+ import { useCallback, useEffect, useRef } from 'react';
2
2
  import { useSearchParams } from '../router';
3
3
 
4
4
  export interface UseNavigationStateOptions {
@@ -16,22 +16,25 @@ export interface UseNavigationStateOptions {
16
16
  }
17
17
 
18
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.
19
+ * Hook to access and update navigation state.
20
+ * On web, state is stored as URL query parameters.
21
+ * Returns a tuple of [state, setState] for reading and updating params in-place.
22
22
  *
23
23
  * @example
24
24
  * // Navigate with state
25
- * navigate({ path: '/recording', state: { autostart: true } });
25
+ * navigate({ path: '/search', state: { q: 'hello', sort: 'asc' } });
26
26
  *
27
- * // Access state in destination screen
28
- * const { autostart } = useNavigationState<{ autostart?: boolean }>();
29
- * // URL will be: /recording?autostart=true
27
+ * // Read and update state in destination screen
28
+ * const [state, setState] = useNavigationState<{ q: string; sort: string }>();
29
+ * // state.q === 'hello', state.sort === 'asc'
30
+ *
31
+ * // Update params in-place (no navigation, replaces history entry)
32
+ * setState({ sort: 'desc' });
30
33
  *
31
34
  * @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
+ * // Consume (remove) a parameter after reading
36
+ * const [state] = useNavigationState<{ autostart?: boolean }>({ consume: ['autostart'] });
37
+ * // state.autostart = true, URL becomes: /recording (param removed)
35
38
  */
36
39
  function parseSearchParams(searchParams: URLSearchParams): Record<string, unknown> {
37
40
  const state: Record<string, unknown> = {};
@@ -51,7 +54,7 @@ function parseSearchParams(searchParams: URLSearchParams): Record<string, unknow
51
54
 
52
55
  export function useNavigationState<T extends Record<string, unknown> = Record<string, unknown>>(
53
56
  options?: UseNavigationStateOptions
54
- ): T {
57
+ ): [T, (updates: Partial<T>) => void] {
55
58
  const [searchParams, setSearchParams] = useSearchParams();
56
59
  const consumedStateRef = useRef<Record<string, unknown> | null>(null);
57
60
  const hasConsumed = useRef(false);
@@ -60,7 +63,6 @@ export function useNavigationState<T extends Record<string, unknown> = Record<st
60
63
  if (options?.consume && options.consume.length > 0 && !hasConsumed.current) {
61
64
  const keysToConsume = options.consume.filter(key => searchParams.has(key));
62
65
  if (keysToConsume.length > 0 && consumedStateRef.current === null) {
63
- // Capture the full state including values we're about to consume
64
66
  consumedStateRef.current = parseSearchParams(searchParams);
65
67
  }
66
68
  }
@@ -78,10 +80,24 @@ export function useNavigationState<T extends Record<string, unknown> = Record<st
78
80
  }
79
81
  }, [options?.consume, searchParams, setSearchParams]);
80
82
 
83
+ const setState = useCallback((updates: Partial<T>) => {
84
+ setSearchParams((prev) => {
85
+ const newParams = new URLSearchParams(prev);
86
+ for (const [key, value] of Object.entries(updates)) {
87
+ if (value === undefined || value === null) {
88
+ newParams.delete(key);
89
+ } else {
90
+ newParams.set(key, String(value));
91
+ }
92
+ }
93
+ return newParams;
94
+ }, { replace: true });
95
+ }, [setSearchParams]);
96
+
81
97
  // Return captured state if we consumed params, otherwise parse current URL
82
- if (consumedStateRef.current !== null) {
83
- return consumedStateRef.current as T;
84
- }
98
+ const state = consumedStateRef.current !== null
99
+ ? consumedStateRef.current as T
100
+ : parseSearchParams(searchParams) as T;
85
101
 
86
- return parseSearchParams(searchParams) as T;
102
+ return [state, setState];
87
103
  }