@granite-js/react-native 1.0.28 → 1.0.30

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/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # @granite-js/react-native
2
2
 
3
+ ## 1.0.30
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [4b2e737]
8
+ - @granite-js/video@1.0.30
9
+ - @granite-js/native@1.0.30
10
+ - @granite-js/blur-view@1.0.30
11
+ - @granite-js/jest@1.0.30
12
+ - @granite-js/style-utils@1.0.30
13
+ - @granite-js/brownfield-module@1.0.30
14
+ - @granite-js/cli@1.0.30
15
+ - @granite-js/mpack@1.0.30
16
+ - @granite-js/plugin-core@1.0.30
17
+
18
+ ## 1.0.29
19
+
20
+ ### Patch Changes
21
+
22
+ - 1b504a1: feat(router): Route.useSetParams
23
+ - @granite-js/blur-view@1.0.29
24
+ - @granite-js/brownfield-module@1.0.29
25
+ - @granite-js/cli@1.0.29
26
+ - @granite-js/jest@1.0.29
27
+ - @granite-js/mpack@1.0.29
28
+ - @granite-js/native@1.0.29
29
+ - @granite-js/plugin-core@1.0.29
30
+ - @granite-js/style-utils@1.0.29
31
+ - @granite-js/video@1.0.29
32
+
3
33
  ## 1.0.28
4
34
 
5
35
  ### Patch Changes
@@ -18,10 +18,10 @@ export interface RegisterScreenInput {
18
18
  export interface RegisterScreen {
19
19
  }
20
20
  type RegisteredParamList = keyof RegisterScreenInput extends never ? ParamListBase : RegisterScreenInput;
21
- type NavigationParamsHelpers = Pick<NativeStackNavigationProp<RegisteredParamList, keyof RegisteredParamList>, 'replaceParams' | 'setParams'>;
22
- export type NavigationProps = Omit<NativeStackNavigationProp<ParamListBase>, keyof NavigationParamsHelpers> & NavigationParamsHelpers;
21
+ export type NavigationProps = NativeStackNavigationProp<RegisteredParamList>;
23
22
  export declare function useNavigation(): NavigationProps;
24
- export type RouteHooksOptions<TScreen extends keyof RegisterScreen> = {
23
+ type RouteHookScreen = keyof RegisterScreenInput | keyof RegisterScreen;
24
+ export type RouteHooksOptions<TScreen extends RouteHookScreen> = {
25
25
  from: TScreen;
26
26
  strict?: true;
27
27
  } | {
@@ -36,7 +36,7 @@ export declare const routeMap: Map<keyof RegisterScreenInput, {
36
36
  params: any;
37
37
  }) => NativeStackNavigationOptions);
38
38
  }>;
39
- export declare function useMatchOptions<TScreen extends keyof RegisterScreen>(options: RouteHooksOptions<TScreen>): {
39
+ export declare function useMatchOptions<TScreen extends RouteHookScreen>(options: RouteHooksOptions<TScreen>): {
40
40
  parserParams: ((params: Record<string, unknown>) => Record<string, unknown>) | typeof defaultParserParams;
41
41
  validateParams?: StandardSchemaV1<any, any> | ((params: Readonly<object | undefined>) => any) | undefined;
42
42
  } | null;
@@ -47,20 +47,33 @@ export declare function useParams<TScreen extends keyof RegisterScreen>(options:
47
47
  export declare function useParams(options: {
48
48
  strict: false;
49
49
  }): Readonly<object | undefined>;
50
- export declare function createRoute<TSchema extends StandardSchemaV1<any, any>>(path: keyof RegisterScreenInput, options: Omit<RouteOptions<any>, 'validateParams'> & {
51
- validateParams: TSchema;
52
- }): {
50
+ type SetParamsFunction<TParams> = (params: TParams extends undefined ? undefined : Partial<TParams>) => void;
51
+ type ReplaceParamsFunction<TParams> = (params: TParams extends undefined ? undefined : TParams) => void;
52
+ type CreatedRoute<TInput, TOutput> = {
53
53
  _path: keyof RegisterScreenInput;
54
54
  useNavigation: typeof useNavigation;
55
- useParams: () => InferOutput<TSchema>;
56
- _inputType: InferInput<TSchema>;
57
- _outputType: InferOutput<TSchema>;
58
- };
59
- export declare function createRoute<T extends Readonly<object | undefined>>(path: keyof RegisterScreenInput, options: RouteOptions<T>): {
60
- _path: keyof RegisterScreenInput;
61
- useNavigation: typeof useNavigation;
62
- useParams: () => T;
63
- _inputType: T;
64
- _outputType: T;
55
+ useParams: () => TOutput;
56
+ useSetParams: () => SetParamsFunction<TInput>;
57
+ useReplaceParams: () => ReplaceParamsFunction<TInput>;
58
+ _inputType: TInput;
59
+ _outputType: TOutput;
65
60
  };
61
+ export declare function useSetParams<TScreen extends keyof RegisterScreenInput>(options: {
62
+ from: TScreen;
63
+ strict?: true;
64
+ }): SetParamsFunction<RegisterScreenInput[TScreen]>;
65
+ export declare function useSetParams(options: {
66
+ strict: false;
67
+ }): SetParamsFunction<Readonly<object | undefined>>;
68
+ export declare function useReplaceParams<TScreen extends keyof RegisterScreenInput>(options: {
69
+ from: TScreen;
70
+ strict?: true;
71
+ }): ReplaceParamsFunction<RegisterScreenInput[TScreen]>;
72
+ export declare function useReplaceParams(options: {
73
+ strict: false;
74
+ }): ReplaceParamsFunction<Readonly<object | undefined>>;
75
+ export declare function createRoute<TSchema extends StandardSchemaV1<any, any>>(path: keyof RegisterScreenInput, options: Omit<RouteOptions<any>, 'validateParams'> & {
76
+ validateParams: TSchema;
77
+ }): CreatedRoute<InferInput<TSchema>, InferOutput<TSchema>>;
78
+ export declare function createRoute<T extends Readonly<object | undefined>>(path: keyof RegisterScreenInput, options: RouteOptions<T>): CreatedRoute<T, T>;
66
79
  export {};
@@ -14,6 +14,9 @@ declare module './createRoute' {
14
14
  '/test-with-defaults': {
15
15
  animation?: boolean;
16
16
  };
17
+ '/test-optional': {
18
+ uri?: string;
19
+ };
17
20
  }
18
21
  interface RegisterScreen {
19
22
  '/test': {
@@ -30,6 +33,9 @@ declare module './createRoute' {
30
33
  '/test-with-defaults': {
31
34
  animation: boolean;
32
35
  };
36
+ '/test-optional': {
37
+ uri?: string;
38
+ };
33
39
  }
34
40
  }
35
41
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@granite-js/react-native",
3
- "version": "1.0.28",
3
+ "version": "1.0.30",
4
4
  "description": "The Granite Framework",
5
5
  "bin": {
6
6
  "granite": "./bin/cli.js"
@@ -91,7 +91,7 @@
91
91
  "@babel/core": "7.28.5",
92
92
  "@babel/preset-env": "7.28.5",
93
93
  "@babel/preset-typescript": "7.28.5",
94
- "@granite-js/native": "1.0.28",
94
+ "@granite-js/native": "1.0.30",
95
95
  "@testing-library/dom": "^10.4.0",
96
96
  "@testing-library/react": "^16.1.0",
97
97
  "@types/babel__core": "^7",
@@ -113,21 +113,21 @@
113
113
  "zod": "^4.1.12"
114
114
  },
115
115
  "peerDependencies": {
116
- "@granite-js/native": "1.0.28",
116
+ "@granite-js/native": "1.0.30",
117
117
  "@types/react": "*",
118
118
  "brick-module": "*",
119
119
  "react": "*",
120
120
  "react-native": "*"
121
121
  },
122
122
  "dependencies": {
123
- "@granite-js/blur-view": "1.0.28",
124
- "@granite-js/brownfield-module": "1.0.28",
125
- "@granite-js/cli": "1.0.28",
126
- "@granite-js/jest": "1.0.28",
127
- "@granite-js/mpack": "1.0.28",
128
- "@granite-js/plugin-core": "1.0.28",
129
- "@granite-js/style-utils": "1.0.28",
130
- "@granite-js/video": "1.0.28",
123
+ "@granite-js/blur-view": "1.0.30",
124
+ "@granite-js/brownfield-module": "1.0.30",
125
+ "@granite-js/cli": "1.0.30",
126
+ "@granite-js/jest": "1.0.30",
127
+ "@granite-js/mpack": "1.0.30",
128
+ "@granite-js/plugin-core": "1.0.30",
129
+ "@granite-js/style-utils": "1.0.30",
130
+ "@granite-js/video": "1.0.30",
131
131
  "@standard-schema/spec": "^1.0.0",
132
132
  "es-toolkit": "^1.39.8",
133
133
  "react-native-url-polyfill": "3.0.0"
@@ -1,6 +1,12 @@
1
1
  import { assertType, describe, it } from 'vitest';
2
2
  import { z } from 'zod';
3
- import { createRoute, useNavigation, useParams } from './createRoute';
3
+ import {
4
+ createRoute,
5
+ useNavigation,
6
+ useParams,
7
+ useReplaceParams,
8
+ useSetParams,
9
+ } from './createRoute';
4
10
 
5
11
  declare module './createRoute' {
6
12
  interface RegisterScreenInput {
@@ -18,6 +24,9 @@ declare module './createRoute' {
18
24
  '/test-with-defaults': {
19
25
  animation?: boolean;
20
26
  };
27
+ '/test-optional': {
28
+ uri?: string;
29
+ };
21
30
  }
22
31
 
23
32
  interface RegisterScreen {
@@ -35,6 +44,9 @@ declare module './createRoute' {
35
44
  '/test-with-defaults': {
36
45
  animation: boolean;
37
46
  };
47
+ '/test-optional': {
48
+ uri?: string;
49
+ };
38
50
  }
39
51
  }
40
52
 
@@ -78,6 +90,52 @@ describe('createRoute', () => {
78
90
  assertType(useParams({ from: '/test', strict: false }));
79
91
  });
80
92
 
93
+ it('useSetParams and useReplaceParams', () => {
94
+ const setRouteParams = Route.useSetParams();
95
+
96
+ assertType<(params: Partial<{ id: string; name: string }>) => void>(setRouteParams);
97
+ setRouteParams({ id: 'test-id' });
98
+ setRouteParams({ name: 'test-name' });
99
+
100
+ // @ts-expect-error Type error should occur when param type is invalid
101
+ setRouteParams({ id: 123 });
102
+
103
+ // @ts-expect-error Type error should occur when param key is invalid
104
+ setRouteParams({ count: 123 });
105
+
106
+ const setParams = useSetParams({ from: '/test' });
107
+ setParams({ id: 'test-id' });
108
+
109
+ // @ts-expect-error Type error should occur when path is not registered
110
+ assertType(useSetParams({ from: '/abcdefg' }));
111
+
112
+ // @ts-expect-error Type error should occur since 'from' and 'strict: false' are conflicting options
113
+ assertType(useSetParams({ from: '/test', strict: false }));
114
+
115
+ const replaceRouteParams = Route.useReplaceParams();
116
+
117
+ assertType<(params: { id: string; name: string }) => void>(replaceRouteParams);
118
+ replaceRouteParams({ id: 'test-id', name: 'test-name' });
119
+
120
+ // @ts-expect-error Type error should occur when required params are missing
121
+ replaceRouteParams({ id: 'test-id' });
122
+
123
+ // @ts-expect-error Type error should occur when param type is invalid
124
+ replaceRouteParams({ id: 123, name: 'test-name' });
125
+
126
+ // @ts-expect-error Type error should occur when param key is invalid
127
+ replaceRouteParams({ id: 'test-id', name: 'test-name', count: 123 });
128
+
129
+ const replaceParams = useReplaceParams({ from: '/test' });
130
+ replaceParams({ id: 'test-id', name: 'test-name' });
131
+
132
+ // @ts-expect-error Type error should occur when path is not registered
133
+ assertType(useReplaceParams({ from: '/abcdefg' }));
134
+
135
+ // @ts-expect-error Type error should occur since 'from' and 'strict: false' are conflicting options
136
+ assertType(useReplaceParams({ from: '/test', strict: false }));
137
+ });
138
+
81
139
  it('should infer _inputType and _outputType without undefined for function pattern', () => {
82
140
  type InputType = (typeof Route)['_inputType'];
83
141
  type OutputType = (typeof Route)['_outputType'];
@@ -165,10 +223,48 @@ describe('createRoute with StandardSchema', () => {
165
223
 
166
224
  // Should accept exact input type
167
225
  navigation.navigate('/test-schema', { id: 'test', count: 123 });
226
+
227
+ // @ts-expect-error Type error should occur when a required params object is missing
228
+ navigation.navigate('/test-schema');
229
+
230
+ // @ts-expect-error Type error should occur when navigate param type is invalid
231
+ navigation.navigate('/test-schema', { id: 'test', count: 'invalid' });
232
+
233
+ // @ts-expect-error Type error should occur when navigating to an unregistered path
234
+ navigation.navigate('/abcdefg');
235
+
236
+ navigation.navigate('/test-optional', {});
237
+ navigation.navigate('/test-optional', { uri: 'test-uri' });
238
+
239
+ // @ts-expect-error Type error should occur when navigate params include an unknown key
240
+ navigation.navigate('/test-optional', { name: '123' });
241
+
242
+ navigation.popTo('/test-optional', { uri: 'test-uri' });
243
+
244
+ // @ts-expect-error popTo should already reject unknown param keys
245
+ navigation.popTo('/test-optional', { name: '123' });
246
+
247
+ navigation.push('/test-optional', { uri: 'test-uri' });
248
+
249
+ // @ts-expect-error push should reject unknown param keys
250
+ navigation.push('/test-optional', { name: '123' });
251
+
252
+ navigation.replace('/test-optional', { uri: 'test-uri' });
253
+
254
+ // @ts-expect-error replace should reject unknown param keys
255
+ navigation.replace('/test-optional', { name: '123' });
256
+
257
+ // @ts-expect-error push should require params for required route params
258
+ navigation.push('/test-schema');
259
+
260
+ // @ts-expect-error replace should require params for required route params
261
+ navigation.replace('/test-schema');
262
+
263
+ // @ts-expect-error popTo should require params for required route params
264
+ navigation.popTo('/test-schema');
168
265
  });
169
266
 
170
267
  it('should infer output type from transformation', () => {
171
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
172
268
  const RouteWithTransform = createRoute('/test-transform', {
173
269
  component: () => null,
174
270
  validateParams: z.object({
@@ -199,6 +295,24 @@ describe('createRoute with StandardSchema', () => {
199
295
  const navigation = useNavigation();
200
296
  navigation.navigate('/test-transform', { id: 'test-id' });
201
297
 
298
+ const setParams = RouteWithTransform.useSetParams();
299
+ setParams({ id: 'test-id' });
300
+
301
+ // @ts-expect-error setParams should accept input type, not output type
302
+ setParams({ id: 123 });
303
+
304
+ const setParamsFromHook = useSetParams({ from: '/test-transform' });
305
+ setParamsFromHook({ id: 'test-id' });
306
+
307
+ // @ts-expect-error useSetParams should accept input type, not output type
308
+ setParamsFromHook({ id: 123 });
309
+
310
+ const replaceParams = RouteWithTransform.useReplaceParams();
311
+ replaceParams({ id: 'test-id' });
312
+
313
+ // @ts-expect-error useReplaceParams should accept input type, not output type
314
+ replaceParams({ id: 123 });
315
+
202
316
  // Verify that input and output types are different
203
317
  type InputIsString = InputType extends { id: string } ? true : false;
204
318
  type OutputIsNumber = OutputType extends { id: number } ? true : false;
@@ -241,5 +355,13 @@ describe('createRoute with StandardSchema', () => {
241
355
 
242
356
  // Should accept without animation parameter (default will be used)
243
357
  navigation.navigate('/test-with-defaults', {});
358
+
359
+ const setParams = RouteWithDefaults.useSetParams();
360
+ setParams({});
361
+ setParams({ animation: false });
362
+
363
+ const replaceParams = RouteWithDefaults.useReplaceParams();
364
+ replaceParams({});
365
+ replaceParams({ animation: false });
244
366
  });
245
367
  });
@@ -8,7 +8,7 @@ import {
8
8
  NativeStackNavigationProp,
9
9
  } from '@granite-js/native/@react-navigation/native-stack';
10
10
  import type { StandardSchemaV1 } from '@standard-schema/spec';
11
- import { useMemo } from 'react';
11
+ import { useCallback, useMemo } from 'react';
12
12
  import { RESERVED_PATHS } from './constants';
13
13
  import type { ErrorComponent } from './types';
14
14
  import { defaultParserParams } from './utils/defaultParserParams';
@@ -31,20 +31,18 @@ export interface RegisterScreen {}
31
31
 
32
32
  type RegisteredParamList = keyof RegisterScreenInput extends never ? ParamListBase : RegisterScreenInput;
33
33
 
34
- type NavigationParamsHelpers = Pick<
34
+ export type NavigationProps = NativeStackNavigationProp<
35
35
  // @ts-expect-error - override type
36
- NativeStackNavigationProp<RegisteredParamList, keyof RegisteredParamList>,
37
- 'replaceParams' | 'setParams'
36
+ RegisteredParamList
38
37
  >;
39
38
 
40
- export type NavigationProps = Omit<NativeStackNavigationProp<ParamListBase>, keyof NavigationParamsHelpers> &
41
- NavigationParamsHelpers;
42
-
43
39
  export function useNavigation() {
44
40
  return useNavigationNative<NavigationProps>();
45
41
  }
46
42
 
47
- export type RouteHooksOptions<TScreen extends keyof RegisterScreen> =
43
+ type RouteHookScreen = keyof RegisterScreenInput | keyof RegisterScreen;
44
+
45
+ export type RouteHooksOptions<TScreen extends RouteHookScreen> =
48
46
  | {
49
47
  from: TScreen;
50
48
  strict?: true;
@@ -64,7 +62,7 @@ export const routeMap = new Map<
64
62
  }
65
63
  >();
66
64
 
67
- export function useMatchOptions<TScreen extends keyof RegisterScreen>(options: RouteHooksOptions<TScreen>) {
65
+ export function useMatchOptions<TScreen extends RouteHookScreen>(options: RouteHooksOptions<TScreen>) {
68
66
  const route = useRoute();
69
67
  const from = 'from' in options ? options.from : (route.name as TScreen);
70
68
  const strict = 'from' in options ? true : options.strict;
@@ -78,11 +76,11 @@ export function useMatchOptions<TScreen extends keyof RegisterScreen>(options: R
78
76
  return null;
79
77
  }
80
78
 
81
- if (!(routeMap.has(from) || RESERVED_PATHS.includes(from))) {
79
+ if (!(routeMap.has(from as keyof RegisterScreenInput) || RESERVED_PATHS.includes(from as string))) {
82
80
  throw new Error(`Route '${from}' is not registered`);
83
81
  }
84
82
 
85
- const routeOptions = routeMap.get(from);
83
+ const routeOptions = routeMap.get(from as keyof RegisterScreenInput);
86
84
  return {
87
85
  ...routeOptions?.options,
88
86
  parserParams: routeOptions?.options.parserParams ?? defaultParserParams,
@@ -174,34 +172,69 @@ export function useParams<TScreen extends keyof RegisterScreen>(
174
172
  return params;
175
173
  }
176
174
 
175
+ type SetParamsFunction<TParams> = (params: TParams extends undefined ? undefined : Partial<TParams>) => void;
176
+ type ReplaceParamsFunction<TParams> = (params: TParams extends undefined ? undefined : TParams) => void;
177
+
178
+ type CreatedRoute<TInput, TOutput> = {
179
+ _path: keyof RegisterScreenInput;
180
+ useNavigation: typeof useNavigation;
181
+ useParams: () => TOutput;
182
+ useSetParams: () => SetParamsFunction<TInput>;
183
+ useReplaceParams: () => ReplaceParamsFunction<TInput>;
184
+ _inputType: TInput;
185
+ _outputType: TOutput;
186
+ };
187
+
188
+ export function useSetParams<TScreen extends keyof RegisterScreenInput>(options: {
189
+ from: TScreen;
190
+ strict?: true;
191
+ }): SetParamsFunction<RegisterScreenInput[TScreen]>;
192
+ export function useSetParams(options: { strict: false }): SetParamsFunction<Readonly<object | undefined>>;
193
+
194
+ export function useSetParams<TScreen extends keyof RegisterScreenInput>(
195
+ options: RouteHooksOptions<TScreen>
196
+ ): SetParamsFunction<RegisterScreenInput[TScreen]> | SetParamsFunction<Readonly<object | undefined>> {
197
+ useMatchOptions(options);
198
+ const navigation = useNavigationNative<NativeStackNavigationProp<ParamListBase>>();
199
+
200
+ return useCallback((params: never) => navigation.setParams(params), [navigation]) as
201
+ | SetParamsFunction<RegisterScreenInput[TScreen]>
202
+ | SetParamsFunction<Readonly<object | undefined>>;
203
+ }
204
+
205
+ export function useReplaceParams<TScreen extends keyof RegisterScreenInput>(options: {
206
+ from: TScreen;
207
+ strict?: true;
208
+ }): ReplaceParamsFunction<RegisterScreenInput[TScreen]>;
209
+ export function useReplaceParams(options: { strict: false }): ReplaceParamsFunction<Readonly<object | undefined>>;
210
+
211
+ export function useReplaceParams<TScreen extends keyof RegisterScreenInput>(
212
+ options: RouteHooksOptions<TScreen>
213
+ ): ReplaceParamsFunction<RegisterScreenInput[TScreen]> | ReplaceParamsFunction<Readonly<object | undefined>> {
214
+ useMatchOptions(options);
215
+ const navigation = useNavigationNative<NativeStackNavigationProp<ParamListBase>>();
216
+
217
+ return useCallback((params: never) => navigation.replaceParams(params), [navigation]) as
218
+ | ReplaceParamsFunction<RegisterScreenInput[TScreen]>
219
+ | ReplaceParamsFunction<Readonly<object | undefined>>;
220
+ }
221
+
177
222
  // Overload 1: StandardSchema pattern
178
223
  export function createRoute<TSchema extends StandardSchemaV1<any, any>>(
179
224
  path: keyof RegisterScreenInput,
180
225
  options: Omit<RouteOptions<any>, 'validateParams'> & {
181
226
  validateParams: TSchema;
182
227
  }
183
- ): {
184
- _path: keyof RegisterScreenInput;
185
- useNavigation: typeof useNavigation;
186
- useParams: () => InferOutput<TSchema>;
187
- _inputType: InferInput<TSchema>;
188
- _outputType: InferOutput<TSchema>;
189
- };
228
+ ): CreatedRoute<InferInput<TSchema>, InferOutput<TSchema>>;
190
229
 
191
230
  // Overload 2: Function pattern
192
231
  export function createRoute<T extends Readonly<object | undefined>>(
193
232
  path: keyof RegisterScreenInput,
194
233
  options: RouteOptions<T>
195
- ): {
196
- _path: keyof RegisterScreenInput;
197
- useNavigation: typeof useNavigation;
198
- useParams: () => T;
199
- _inputType: T;
200
- _outputType: T;
201
- };
234
+ ): CreatedRoute<T, T>;
202
235
 
203
236
  // Implementation
204
- export function createRoute(path: keyof RegisterScreenInput, options: RouteOptions<any>) {
237
+ export function createRoute(path: keyof RegisterScreenInput, options: RouteOptions<any>): CreatedRoute<any, any> {
205
238
  const { component, screenOptions, errorComponent, ...restOptions } = options;
206
239
  routeMap.set(path, {
207
240
  options: restOptions,
@@ -215,6 +248,8 @@ export function createRoute(path: keyof RegisterScreenInput, options: RouteOptio
215
248
  _path,
216
249
  useNavigation,
217
250
  useParams: () => useParams({ from: _path as keyof RegisterScreen, strict: true }),
251
+ useSetParams: () => useSetParams({ from: _path, strict: true }),
252
+ useReplaceParams: () => useReplaceParams({ from: _path, strict: true }),
218
253
 
219
254
  // These properties are only used for type inference in the generated router files
220
255
  // The actual values are never accessed at runtime