@granite-js/react-native 1.0.39 → 1.0.40

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,20 @@
1
1
  # @granite-js/react-native
2
2
 
3
+ ## 1.0.40
4
+
5
+ ### Patch Changes
6
+
7
+ - c7e7fc9: fix(react-native): unset iOS back press handler on cleanup instead of re-registering
8
+ - @granite-js/blur-view@1.0.40
9
+ - @granite-js/brownfield-module@1.0.40
10
+ - @granite-js/cli@1.0.40
11
+ - @granite-js/jest@1.0.40
12
+ - @granite-js/mpack@1.0.40
13
+ - @granite-js/native@1.0.40
14
+ - @granite-js/plugin-core@1.0.40
15
+ - @granite-js/style-utils@1.0.40
16
+ - @granite-js/video@1.0.40
17
+
3
18
  ## 1.0.39
4
19
 
5
20
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  import { ComponentType, type JSX, PropsWithChildren } from 'react';
2
2
  import type { InitialProps } from '../initial-props';
3
- import type { RouterProps, RequireContext } from '../router';
3
+ import type { RouterProps, RequireContext, SetIOSBackPressHandler } from '../router';
4
4
  export interface GraniteProps {
5
5
  /**
6
6
  * @description
@@ -34,10 +34,11 @@ export interface GraniteProps {
34
34
  /**
35
35
  * @description
36
36
  * The function to register a handler that runs when the iOS swipe back gesture is detected.
37
+ * Called with empty params (`{}`, no `handler`) when the handler should be unset — in
38
+ * that case the implementation must initialize the registered handler to `null`
39
+ * (e.g. call `unsetBackPressHandler()`), not keep an empty function registered.
37
40
  */
38
- setiOSBackPressHandler?: ({ handler }: {
39
- handler: () => void;
40
- }) => Promise<void> | void;
41
+ setiOSBackPressHandler?: SetIOSBackPressHandler;
41
42
  /**
42
43
  * @description
43
44
  * The function to provide the initial URL to router.
@@ -3,8 +3,15 @@ import type { BackEvent } from '../../use-back-event';
3
3
  type SetIosSwipeGestureEnabled = ({ isEnabled }: {
4
4
  isEnabled: boolean;
5
5
  }) => Promise<void> | void;
6
- type SetIOSBackPressHandler = ({ handler }: {
7
- handler: () => void;
6
+ /**
7
+ * Registers the iOS back press handler. Called with empty params (`{}`, no
8
+ * `handler`) to unset — mirroring `unsetBackPressHandler()`, which calls
9
+ * `setBackPressHandler` with empty params. In that case implementations should
10
+ * initialize the registered handler to `null`, not keep an empty function
11
+ * registered.
12
+ */
13
+ export type SetIOSBackPressHandler = ({ handler }: {
14
+ handler?: () => void;
8
15
  }) => Promise<void> | void;
9
16
  export declare function CanGoBackGuard({ children, canGoBack, hasBackEvent, onBack, isInitialScreen, setIosSwipeGestureEnabled, setiOSBackPressHandler, }: {
10
17
  canGoBack: boolean;
@@ -2,4 +2,5 @@ export * from './Router';
2
2
  export { getRouteScreens, getScreenPathMapConfig, defaultParserParams } from './utils';
3
3
  export * from './types';
4
4
  export * from './components/BackButton';
5
+ export type { SetIOSBackPressHandler } from './components/CanGoBackGuard';
5
6
  export * from './components/useRouterBackHandler';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@granite-js/react-native",
3
- "version": "1.0.39",
3
+ "version": "1.0.40",
4
4
  "description": "The Granite Framework",
5
5
  "bin": {
6
6
  "granite": "./bin/cli.js"
@@ -91,8 +91,8 @@
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.39",
95
- "@granite-js/vitest": "1.0.39",
94
+ "@granite-js/native": "1.0.40",
95
+ "@granite-js/vitest": "1.0.40",
96
96
  "@testing-library/dom": "^10.4.0",
97
97
  "@testing-library/react": "^16.1.0",
98
98
  "@types/babel__core": "^7",
@@ -114,21 +114,21 @@
114
114
  "zod": "^4.1.12"
115
115
  },
116
116
  "peerDependencies": {
117
- "@granite-js/native": "1.0.39",
117
+ "@granite-js/native": "1.0.40",
118
118
  "@types/react": "*",
119
119
  "brick-module": "*",
120
120
  "react": "*",
121
121
  "react-native": "*"
122
122
  },
123
123
  "dependencies": {
124
- "@granite-js/blur-view": "1.0.39",
125
- "@granite-js/brownfield-module": "1.0.39",
126
- "@granite-js/cli": "1.0.39",
127
- "@granite-js/jest": "1.0.39",
128
- "@granite-js/mpack": "1.0.39",
129
- "@granite-js/plugin-core": "1.0.39",
130
- "@granite-js/style-utils": "1.0.39",
131
- "@granite-js/video": "1.0.39",
124
+ "@granite-js/blur-view": "1.0.40",
125
+ "@granite-js/brownfield-module": "1.0.40",
126
+ "@granite-js/cli": "1.0.40",
127
+ "@granite-js/jest": "1.0.40",
128
+ "@granite-js/mpack": "1.0.40",
129
+ "@granite-js/plugin-core": "1.0.40",
130
+ "@granite-js/style-utils": "1.0.40",
131
+ "@granite-js/video": "1.0.40",
132
132
  "@standard-schema/spec": "^1.0.0",
133
133
  "es-toolkit": "^1.39.8",
134
134
  "react-native-url-polyfill": "3.0.0"
@@ -1,7 +1,7 @@
1
1
  import { SafeAreaProvider } from '@granite-js/native/react-native-safe-area-context';
2
2
  import type { ComponentType, PropsWithChildren } from 'react';
3
3
  import type { InitialProps } from '../initial-props';
4
- import { Router, type InternalRouterProps } from '../router';
4
+ import { Router, type InternalRouterProps, type SetIOSBackPressHandler } from '../router';
5
5
  import { BackEventProvider } from '../use-back-event';
6
6
  import { App } from './App';
7
7
  import type { GraniteProps } from './Granite';
@@ -16,7 +16,7 @@ interface AppRootProps extends GraniteProps {
16
16
  initialProps: InitialProps;
17
17
  initialScheme: string;
18
18
  setIosSwipeGestureEnabled?: ({ isEnabled }: { isEnabled: boolean }) => void;
19
- setiOSBackPressHandler?: ({ handler }: { handler: () => void }) => Promise<void> | void;
19
+ setiOSBackPressHandler?: SetIOSBackPressHandler;
20
20
  getInitialUrl: InternalRouterProps['getInitialUrl'];
21
21
  }
22
22
 
@@ -2,7 +2,7 @@ import { ComponentType, type JSX, PropsWithChildren } from 'react';
2
2
  import { AppRegistry } from 'react-native';
3
3
  import { ENTRY_BUNDLE_NAME } from '../constants';
4
4
  import type { InitialProps } from '../initial-props';
5
- import type { RouterProps, RequireContext } from '../router';
5
+ import type { RouterProps, RequireContext, SetIOSBackPressHandler } from '../router';
6
6
  import { AppRoot } from './AppRoot';
7
7
  import { HostAppRoot } from './HostAppRoot';
8
8
  import { getSchemeUri } from '../constant-bridges';
@@ -42,8 +42,11 @@ export interface GraniteProps {
42
42
  /**
43
43
  * @description
44
44
  * The function to register a handler that runs when the iOS swipe back gesture is detected.
45
+ * Called with empty params (`{}`, no `handler`) when the handler should be unset — in
46
+ * that case the implementation must initialize the registered handler to `null`
47
+ * (e.g. call `unsetBackPressHandler()`), not keep an empty function registered.
45
48
  */
46
- setiOSBackPressHandler?: ({ handler }: { handler: () => void }) => Promise<void> | void;
49
+ setiOSBackPressHandler?: SetIOSBackPressHandler;
47
50
 
48
51
  /**
49
52
  * @description
@@ -20,7 +20,7 @@ import {
20
20
  import { InitialProps } from '..';
21
21
  import { closeView } from '../native-modules';
22
22
  import { BackButton } from './components/BackButton';
23
- import { CanGoBackGuard } from './components/CanGoBackGuard';
23
+ import { CanGoBackGuard, type SetIOSBackPressHandler } from './components/CanGoBackGuard';
24
24
  import { StackNavigator } from './components/StackNavigator';
25
25
  import { useInternalRouterBackHandler } from './components/useRouterBackHandler';
26
26
  import { useRouterControls, type RouterControlsConfig } from './hooks/useRouterControls';
@@ -65,7 +65,7 @@ export interface InternalRouterProps {
65
65
  initialProps: InitialProps;
66
66
  initialScheme: string;
67
67
  setIosSwipeGestureEnabled?: ({ isEnabled }: { isEnabled: boolean }) => Promise<void> | void;
68
- setiOSBackPressHandler?: ({ handler }: { handler: () => void }) => Promise<void> | void;
68
+ setiOSBackPressHandler?: SetIOSBackPressHandler;
69
69
  getInitialUrl?: RouterControlsConfig['getInitialUrl'];
70
70
  }
71
71
 
@@ -73,7 +73,72 @@ describe('CanGoBackGuard', () => {
73
73
 
74
74
  unmount();
75
75
 
76
- expect(setiOSBackPressHandler).toHaveBeenLastCalledWith({ handler: expect.any(Function) });
76
+ expect(setiOSBackPressHandler).toHaveBeenLastCalledWith({});
77
+ });
78
+
79
+ it('unsets the iOS back press handler on unmount so the registered handler is initialized to null', () => {
80
+ mockPlatformOS('ios');
81
+
82
+ let registeredHandler: (() => void) | null = null;
83
+ // destructures params like legacy host implementations — must not crash on unset
84
+ const setiOSBackPressHandler = vi.fn(({ handler }: { handler?: () => void }) => {
85
+ registeredHandler = handler ?? null;
86
+ });
87
+
88
+ const { unmount } = render(
89
+ <CanGoBackGuard
90
+ canGoBack={true}
91
+ hasBackEvent={true}
92
+ isInitialScreen={true}
93
+ setiOSBackPressHandler={setiOSBackPressHandler}
94
+ >
95
+ <div />
96
+ </CanGoBackGuard>
97
+ );
98
+
99
+ expect(registeredHandler).toEqual(expect.any(Function));
100
+
101
+ unmount();
102
+
103
+ expect(setiOSBackPressHandler).toHaveBeenLastCalledWith({});
104
+ expect(registeredHandler).toBeNull();
105
+ });
106
+
107
+ it('unsets the iOS back press handler when back events no longer exist', () => {
108
+ mockPlatformOS('ios');
109
+
110
+ let registeredHandler: (() => void) | null = null;
111
+ // destructures params like legacy host implementations — must not crash on unset
112
+ const setiOSBackPressHandler = vi.fn(({ handler }: { handler?: () => void }) => {
113
+ registeredHandler = handler ?? null;
114
+ });
115
+
116
+ const { rerender } = render(
117
+ <CanGoBackGuard
118
+ canGoBack={true}
119
+ hasBackEvent={true}
120
+ isInitialScreen={true}
121
+ setiOSBackPressHandler={setiOSBackPressHandler}
122
+ >
123
+ <div />
124
+ </CanGoBackGuard>
125
+ );
126
+
127
+ expect(registeredHandler).toEqual(expect.any(Function));
128
+
129
+ rerender(
130
+ <CanGoBackGuard
131
+ canGoBack={true}
132
+ hasBackEvent={false}
133
+ isInitialScreen={true}
134
+ setiOSBackPressHandler={setiOSBackPressHandler}
135
+ >
136
+ <div />
137
+ </CanGoBackGuard>
138
+ );
139
+
140
+ expect(setiOSBackPressHandler).toHaveBeenLastCalledWith({});
141
+ expect(registeredHandler).toBeNull();
77
142
  });
78
143
 
79
144
  it('disables iOS swipe when the current state should block default back navigation', () => {
@@ -3,7 +3,15 @@ import { BackHandler, Platform } from 'react-native';
3
3
  import type { BackEvent } from '../../use-back-event';
4
4
 
5
5
  type SetIosSwipeGestureEnabled = ({ isEnabled }: { isEnabled: boolean }) => Promise<void> | void;
6
- type SetIOSBackPressHandler = ({ handler }: { handler: () => void }) => Promise<void> | void;
6
+
7
+ /**
8
+ * Registers the iOS back press handler. Called with empty params (`{}`, no
9
+ * `handler`) to unset — mirroring `unsetBackPressHandler()`, which calls
10
+ * `setBackPressHandler` with empty params. In that case implementations should
11
+ * initialize the registered handler to `null`, not keep an empty function
12
+ * registered.
13
+ */
14
+ export type SetIOSBackPressHandler = ({ handler }: { handler?: () => void }) => Promise<void> | void;
7
15
 
8
16
  export function CanGoBackGuard({
9
17
  children,
@@ -74,7 +82,7 @@ export function CanGoBackGuard({
74
82
  });
75
83
 
76
84
  return () => {
77
- setiOSBackPressHandler({ handler: () => {} });
85
+ setiOSBackPressHandler?.({});
78
86
  };
79
87
  }, [hasBackEvent, onBack, setiOSBackPressHandler]);
80
88
 
@@ -2,4 +2,5 @@ export * from './Router';
2
2
  export { getRouteScreens, getScreenPathMapConfig, defaultParserParams } from './utils';
3
3
  export * from './types';
4
4
  export * from './components/BackButton';
5
+ export type { SetIOSBackPressHandler } from './components/CanGoBackGuard';
5
6
  export * from './components/useRouterBackHandler';