@granite-js/react-native 0.1.11 → 0.1.12

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.
Files changed (49) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/cli.d.ts +1 -1
  3. package/config.d.ts +1 -2
  4. package/dist/app/AppRoot.d.ts +1 -1
  5. package/dist/app/Granite.d.ts +6 -1
  6. package/dist/cli.d.mts +3 -0
  7. package/dist/cli.d.ts +1 -0
  8. package/dist/cli.js +30 -0
  9. package/dist/cli.mjs +5 -0
  10. package/dist/config.d.mts +1 -0
  11. package/dist/config.d.ts +1 -0
  12. package/dist/config.js +30 -0
  13. package/dist/config.mjs +5 -0
  14. package/dist/index.d.ts +1 -0
  15. package/dist/jest.d.mts +1 -0
  16. package/dist/{jest/index.js → jest.js} +4 -4
  17. package/dist/jest.mjs +6 -0
  18. package/dist/router/Router.d.ts +1 -1
  19. package/dist/router/components/BackButton.d.ts +41 -0
  20. package/dist/router/components/useRouterBackHandler.d.ts +57 -0
  21. package/dist/router/hooks/useInitialRouteName.d.ts +4 -1
  22. package/dist/router/hooks/useRouterControls.d.ts +2 -1
  23. package/dist/router/index.d.ts +2 -0
  24. package/dist/router/types/screen-option.d.ts +1 -1
  25. package/dist/types/global.d.ts +1 -6
  26. package/dist/utils/getSchemePrefix.d.ts +5 -0
  27. package/jest.d.ts +1 -1
  28. package/package.json +35 -17
  29. package/src/app/AppRoot.tsx +13 -8
  30. package/src/app/Granite.tsx +9 -1
  31. package/src/cli.ts +1 -0
  32. package/src/config.ts +1 -0
  33. package/src/index.ts +2 -0
  34. package/src/router/Router.tsx +20 -9
  35. package/src/router/components/BackButton.tsx +42 -3
  36. package/src/router/components/useRouterBackHandler.tsx +91 -0
  37. package/src/router/hooks/useInitialRouteName.tsx +4 -10
  38. package/src/router/hooks/useRouterControls.tsx +10 -7
  39. package/src/router/index.ts +2 -0
  40. package/src/router/types/screen-option.ts +1 -1
  41. package/src/types/global.ts +1 -3
  42. package/src/utils/getSchemePrefix.spec.ts +44 -0
  43. package/src/utils/getSchemePrefix.ts +5 -0
  44. package/cli.js +0 -4
  45. package/config.js +0 -5
  46. package/dist/router/components/RouterBackButton.d.ts +0 -9
  47. package/src/router/components/RouterBackButton.tsx +0 -32
  48. /package/dist/{jest/index.d.ts → jest.d.ts} +0 -0
  49. /package/src/{jest/index.ts → jest.ts} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @granite-js/react-native
2
2
 
3
+ ## 0.1.12
4
+
5
+ ### Patch Changes
6
+
7
+ - bc69cc7: Add useRouterBackHandler Hook, Add BackButton Component.
8
+ - d1e6585: fix module resolutions
9
+ - 1e99fe1: support initialScheme, support host
10
+ - Updated dependencies [d1e6585]
11
+ - Updated dependencies [1e99fe1]
12
+ - @granite-js/cli@0.1.12
13
+ - @granite-js/image@0.1.12
14
+ - @granite-js/jest@0.1.12
15
+ - @granite-js/lottie@0.1.12
16
+ - @granite-js/mpack@0.1.12
17
+ - @granite-js/native@0.1.12
18
+ - @granite-js/plugin-core@0.1.12
19
+ - @granite-js/style-utils@0.1.12
20
+
3
21
  ## 0.1.11
4
22
 
5
23
  ### Patch Changes
package/cli.d.ts CHANGED
@@ -1 +1 @@
1
- export { initialize } from '@granite-js/cli';
1
+ export * from './dist/cli';
package/config.d.ts CHANGED
@@ -1,2 +1 @@
1
- export * from '@granite-js/plugin-core';
2
- export { Config as MpackConfig } from '@granite-js/mpack';
1
+ export * from './dist/config';
@@ -1 +1 @@
1
- export declare function AppRoot({ appName, context, container: Container, initialProps, router }: AppRootProps): import("react/jsx-runtime").JSX.Element;
1
+ export declare function AppRoot({ appName, context, container: Container, initialProps, initialScheme, router }: AppRootProps): import("react/jsx-runtime").JSX.Element;
@@ -19,6 +19,11 @@ export interface GraniteProps {
19
19
  * Configuration object to be passed to the router.
20
20
  */
21
21
  router?: RouterProps;
22
+ /**
23
+ * @description
24
+ * The initial scheme of the app.
25
+ */
26
+ initialScheme?: string;
22
27
  }
23
28
  /**
24
29
  * @public
@@ -55,7 +60,7 @@ export interface GraniteProps {
55
60
  * ```
56
61
  */
57
62
  export declare const Granite: {
58
- registerApp(AppContainer: ComponentType<PropsWithChildren<InitialProps>>, { appName, context, router }: GraniteProps): (initialProps: InitialProps) => JSX.Element;
63
+ registerApp(AppContainer: ComponentType<PropsWithChildren<InitialProps>>, { appName, context, router, initialScheme }: GraniteProps): (initialProps: InitialProps) => JSX.Element;
59
64
  registerHostApp(AppContainer: ComponentType<PropsWithChildren<InitialProps>>, { appName }: Pick<GraniteProps, "appName">): (initialProps: InitialProps) => JSX.Element;
60
65
  readonly appName: string;
61
66
  };
package/dist/cli.d.mts ADDED
@@ -0,0 +1,3 @@
1
+ export { initialize } from '@granite-js/cli';
2
+ import '@granite-js/plugin-core';
3
+ import '@granite-js/jest';
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ export { initialize } from '@granite-js/cli';
package/dist/cli.js ADDED
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/cli.ts
21
+ var cli_exports = {};
22
+ __export(cli_exports, {
23
+ initialize: () => import_cli.initialize
24
+ });
25
+ module.exports = __toCommonJS(cli_exports);
26
+ var import_cli = require("@granite-js/cli");
27
+ // Annotate the CommonJS export names for ESM import in node:
28
+ 0 && (module.exports = {
29
+ initialize
30
+ });
package/dist/cli.mjs ADDED
@@ -0,0 +1,5 @@
1
+ // src/cli.ts
2
+ import { initialize } from "@granite-js/cli";
3
+ export {
4
+ initialize
5
+ };
@@ -0,0 +1 @@
1
+ export { defineConfig } from '@granite-js/plugin-core';
@@ -0,0 +1 @@
1
+ export { defineConfig } from '@granite-js/plugin-core';
package/dist/config.js ADDED
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/config.ts
21
+ var config_exports = {};
22
+ __export(config_exports, {
23
+ defineConfig: () => import_plugin_core.defineConfig
24
+ });
25
+ module.exports = __toCommonJS(config_exports);
26
+ var import_plugin_core = require("@granite-js/plugin-core");
27
+ // Annotate the CommonJS export names for ESM import in node:
28
+ 0 && (module.exports = {
29
+ defineConfig
30
+ });
@@ -0,0 +1,5 @@
1
+ // src/config.ts
2
+ import { defineConfig } from "@granite-js/plugin-core";
3
+ export {
4
+ defineConfig
5
+ };
package/dist/index.d.ts CHANGED
@@ -17,5 +17,6 @@ export * from './event';
17
17
  export * from './video';
18
18
  export * from './status-bar';
19
19
  export * from './blur';
20
+ export { BackButton, useRouterBackHandler } from './router';
20
21
  export type { InitialProps, ColorPreference } from './initial-props';
21
22
  export type { GraniteProps } from './app';
@@ -0,0 +1 @@
1
+ export { config, setup } from '@granite-js/jest';
@@ -17,13 +17,13 @@ var __copyProps = (to, from, except, desc) => {
17
17
  };
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
 
20
- // src/jest/index.ts
21
- var index_exports = {};
22
- __export(index_exports, {
20
+ // src/jest.ts
21
+ var jest_exports = {};
22
+ __export(jest_exports, {
23
23
  config: () => import_jest.config,
24
24
  setup: () => import_jest.setup
25
25
  });
26
- module.exports = __toCommonJS(index_exports);
26
+ module.exports = __toCommonJS(jest_exports);
27
27
  var import_jest = require("@granite-js/jest");
28
28
  // Annotate the CommonJS export names for ESM import in node:
29
29
  0 && (module.exports = {
package/dist/jest.mjs ADDED
@@ -0,0 +1,6 @@
1
+ // src/jest.ts
2
+ import { setup, config } from "@granite-js/jest";
3
+ export {
4
+ config,
5
+ setup
6
+ };
@@ -55,5 +55,5 @@ type NavigationContainerProps = Pick<ComponentProps<typeof NavigationContainer>,
55
55
  * }
56
56
  * ```
57
57
  */
58
- export declare function Router({ prefix, context, canGoBack, onBack, container: Container, initialProps, navigationContainerRef, defaultScreenOption, screenContainer, ...navigationContainerProps }: InternalRouterProps & RouterProps): ReactElement;
58
+ export declare function Router({ prefix, context, container: Container, initialProps, initialScheme, navigationContainerRef, defaultScreenOption, screenContainer, ...navigationContainerProps }: InternalRouterProps & RouterProps): ReactElement;
59
59
  export {};
@@ -3,5 +3,46 @@ interface BackButtonProps extends TouchableOpacityProps {
3
3
  tintColor?: string;
4
4
  onPress?: () => void;
5
5
  }
6
+ /**
7
+ * @public
8
+ * @category UI
9
+ * @name BackButton
10
+ * @description
11
+ * A back button component. This component is primarily used to implement functionality for returning to the previous screen in navigation headers or custom screen tops. If no `onPress` handler is set, it won't perform any action.
12
+ *
13
+ * @param {string} [props.tintColor] - The color of the button icon. You can use CSS color strings.
14
+ * @param {() => void} [props.onPress] - A function to execute when the button is pressed. For example, you can add a function to return to the previous screen.
15
+ *
16
+ * @returns {ReactElement} Returns a back button component.
17
+ * @example
18
+ *
19
+ * ### Example of directly passing a handler to a back button and setting a function to close the view
20
+ *
21
+ * ```tsx
22
+ * import { createNavigationContainerRef, useNavigation } from '@granite-js/native/@react-navigation/native';
23
+ * import { BackButton, useRouterBackHandler } from '@granite-js/react-native';
24
+ * import { useEffect } from 'react';
25
+ *
26
+ * const navigationContainerRef = createNavigationContainerRef();
27
+ *
28
+ * function MyBackButton() {
29
+ * const navigation = useNavigation();
30
+ *
31
+ * const { handler } = useRouterBackHandler({
32
+ * navigationContainerRef,
33
+ * onClose: () => {
34
+ * // close the view
35
+ * },
36
+ * });
37
+ *
38
+ * useEffect(() => {
39
+ * navigation.setOptions({
40
+ * headerLeft: () => <BackButton onPress={handler} />,
41
+ * });
42
+ * }, [handler, navigation]);
43
+ *
44
+ * return <></>;
45
+ * }
46
+ */
6
47
  declare function BackButton({ tintColor, onPress }: BackButtonProps): import("react/jsx-runtime").JSX.Element;
7
48
  export { BackButton };
@@ -0,0 +1,57 @@
1
+ import { NavigationContainerRefWithCurrent } from '@granite-js/native/@react-navigation/native';
2
+ /**
3
+ * @public
4
+ * @name useRouterBackHandler
5
+ * @category Screen Control
6
+ * @description
7
+ * A hook that provides a handler for handling back navigation actions. This can be used when you need to close a view directly when the back button is pressed in modals or independent screens where there's no navigation stack. This hook uses `NavigationContainerRef` from `@react-navigation/native` to navigate to the previous screen if there's a remaining navigation stack, or executes the user-defined `onClose` function if the stack is empty.
8
+ *
9
+ * @param {NavigationContainerRefWithCurrent<any>} navigationContainerRef - A `NavigationContainerRef` that can reference the current navigation state. Used when executing back navigation actions.
10
+ * @param {() => void} [onClose] - A function to execute when the navigation stack is empty. For example, you can pass a function that closes a React Native View.
11
+ *
12
+ * @returns {{ handler: () => void }} A handler function that can be used in back buttons or similar components.
13
+ *
14
+ * @example
15
+ *
16
+ * ### Example of directly passing a handler to a back button and setting a function to close the view
17
+ *
18
+ * ```tsx
19
+ * import { createNavigationContainerRef, useNavigation } from '@granite-js/native/@react-navigation/native';
20
+ * import { BackButton, useRouterBackHandler } from '@granite-js/react-native';
21
+ * import { useEffect } from 'react';
22
+ *
23
+ * const navigationContainerRef = createNavigationContainerRef();
24
+ *
25
+ * function MyBackButton() {
26
+ * const navigation = useNavigation();
27
+ *
28
+ * const { handler } = useRouterBackHandler({
29
+ * navigationContainerRef,
30
+ * onClose: () => {
31
+ * // close the view
32
+ * },
33
+ * });
34
+ *
35
+ * useEffect(() => {
36
+ * navigation.setOptions({
37
+ * headerLeft: () => <BackButton onPress={handler} />,
38
+ * });
39
+ * }, [handler, navigation]);
40
+ *
41
+ * return <></>;
42
+ * }
43
+ */
44
+ export declare function useRouterBackHandler({ navigationContainerRef, onClose, }: {
45
+ navigationContainerRef: NavigationContainerRefWithCurrent<any>;
46
+ onClose?: () => void;
47
+ }): {
48
+ handler: () => void;
49
+ };
50
+ export declare function useInternalRouterBackHandler({ navigationContainerRef, onClose, }: {
51
+ navigationContainerRef: NavigationContainerRefWithCurrent<any>;
52
+ onClose?: () => void;
53
+ }): {
54
+ handler: () => void;
55
+ canGoBack: boolean;
56
+ onBack: () => void;
57
+ };
@@ -1 +1,4 @@
1
- export declare function useInitialRouteName(prefix: string): string | undefined;
1
+ export declare function useInitialRouteName({ prefix, initialScheme }: {
2
+ prefix: string;
3
+ initialScheme: string;
4
+ }): string | undefined;
@@ -2,10 +2,11 @@ import { type ComponentType, type PropsWithChildren } from 'react';
2
2
  import { RequireContext } from '../types/RequireContext';
3
3
  export interface RouterControlsConfig {
4
4
  prefix: string;
5
+ initialScheme: string;
5
6
  context: RequireContext;
6
7
  screenContainer?: ComponentType<PropsWithChildren<any>>;
7
8
  }
8
- export declare function useRouterControls({ prefix, context, screenContainer: ScreenContainer }: RouterControlsConfig): {
9
+ export declare function useRouterControls({ prefix, context, screenContainer: ScreenContainer, initialScheme, }: RouterControlsConfig): {
9
10
  Screens: import("react/jsx-runtime").JSX.Element[];
10
11
  linkingOptions: import("@react-navigation/native").LinkingOptions<{}>;
11
12
  };
@@ -1,3 +1,5 @@
1
1
  export * from './Router';
2
2
  export { getRouteScreens, getScreenPathMapConfig, defaultParserParams } from './utils';
3
3
  export * from './types';
4
+ export * from './components/BackButton';
5
+ export * from './components/useRouterBackHandler';
@@ -1,4 +1,4 @@
1
1
  import { NativeStackNavigationOptions } from '@granite-js/native/@react-navigation/native-stack';
2
2
  export declare const DEFAULT_BACKGROUND_COLOR = "#ffffff";
3
- export declare const DEFAULT_HEADER_TINT_COLOR = "#333d4b";
3
+ export declare const DEFAULT_HEADER_TINT_COLOR = "#191f28";
4
4
  export declare const BASE_STACK_NAVIGATOR_STYLE: NativeStackNavigationOptions;
@@ -2,13 +2,8 @@ export interface GraniteGlobal {
2
2
  app: {
3
3
  name: string;
4
4
  scheme: string;
5
+ host: string;
5
6
  };
6
7
  }
7
8
  declare global {
8
- var window: {
9
- __granite: GraniteGlobal;
10
- };
11
- var global: {
12
- __granite: GraniteGlobal;
13
- };
14
9
  }
@@ -0,0 +1,5 @@
1
+ export declare function getSchemePrefix({ scheme, appName, host }: {
2
+ scheme: string;
3
+ appName: string;
4
+ host: string;
5
+ }): string;
package/jest.d.ts CHANGED
@@ -1 +1 @@
1
- export * from './dist/jest';
1
+ export * from './dist/jest';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@granite-js/react-native",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "description": "The Granite Framework",
5
5
  "bin": {
6
6
  "granite": "./bin/cli.js"
@@ -27,17 +27,35 @@
27
27
  "types": "./src/constant-bridges.ts",
28
28
  "default": "./src/constant-bridges.ts"
29
29
  },
30
- "./jest": {
31
- "types": "./dist/jest/index.d.ts",
32
- "default": "./dist/jest/index.js"
30
+ "./config": {
31
+ "import": {
32
+ "types": "./dist/config.d.mts",
33
+ "default": "./dist/config.mjs"
34
+ },
35
+ "require": {
36
+ "types": "./dist/config.d.ts",
37
+ "default": "./dist/config.js"
38
+ }
33
39
  },
34
40
  "./cli": {
35
- "types": "./cli.d.ts",
36
- "default": "./cli.js"
41
+ "import": {
42
+ "types": "./dist/cli.d.mts",
43
+ "default": "./dist/cli.mjs"
44
+ },
45
+ "require": {
46
+ "types": "./dist/cli.d.ts",
47
+ "default": "./dist/cli.js"
48
+ }
37
49
  },
38
- "./config": {
39
- "types": "./config.d.ts",
40
- "default": "./config.js"
50
+ "./jest": {
51
+ "import": {
52
+ "types": "./dist/jest.d.mts",
53
+ "default": "./dist/jest.mjs"
54
+ },
55
+ "require": {
56
+ "types": "./dist/jest.d.ts",
57
+ "default": "./dist/jest.js"
58
+ }
41
59
  },
42
60
  "./types": {
43
61
  "types": "./dist/types/global.d.ts"
@@ -56,7 +74,7 @@
56
74
  "@babel/core": "^7.24.9",
57
75
  "@babel/preset-env": "^7.24.8",
58
76
  "@babel/preset-typescript": "^7.24.7",
59
- "@granite-js/native": "0.1.11",
77
+ "@granite-js/native": "0.1.12",
60
78
  "@testing-library/dom": "^10.4.0",
61
79
  "@testing-library/react": "^16.1.0",
62
80
  "@types/babel__core": "^7",
@@ -82,13 +100,13 @@
82
100
  "react-native": "*"
83
101
  },
84
102
  "dependencies": {
85
- "@granite-js/cli": "0.1.11",
86
- "@granite-js/image": "0.1.11",
87
- "@granite-js/jest": "0.1.11",
88
- "@granite-js/lottie": "0.1.11",
89
- "@granite-js/mpack": "0.1.11",
90
- "@granite-js/plugin-core": "0.1.11",
91
- "@granite-js/style-utils": "0.1.11",
103
+ "@granite-js/cli": "0.1.12",
104
+ "@granite-js/image": "0.1.12",
105
+ "@granite-js/jest": "0.1.12",
106
+ "@granite-js/lottie": "0.1.12",
107
+ "@granite-js/mpack": "0.1.12",
108
+ "@granite-js/plugin-core": "0.1.12",
109
+ "@granite-js/style-utils": "0.1.12",
92
110
  "es-toolkit": "^1.39.8",
93
111
  "react-native-url-polyfill": "1.3.0"
94
112
  }
@@ -1,10 +1,11 @@
1
1
  import { SafeAreaProvider } from '@granite-js/native/react-native-safe-area-context';
2
2
  import type { ComponentType, PropsWithChildren } from 'react';
3
- import type { GraniteProps } from './Granite';
4
3
  import type { InitialProps } from '../initial-props';
4
+ import { Router } from '../router';
5
5
  import { BackEventProvider, useBackEventState } from '../use-back-event';
6
6
  import { App } from './App';
7
- import { Router } from '../router';
7
+ import type { GraniteProps } from './Granite';
8
+ import { getSchemePrefix } from '../utils/getSchemePrefix';
8
9
 
9
10
  /**
10
11
  * @internal
@@ -12,12 +13,17 @@ import { Router } from '../router';
12
13
  interface AppRootProps extends GraniteProps {
13
14
  container: ComponentType<PropsWithChildren<InitialProps>>;
14
15
  initialProps: InitialProps;
16
+ initialScheme: string;
15
17
  }
16
18
 
17
- export function AppRoot({ appName, context, container: Container, initialProps, router }: AppRootProps) {
19
+ export function AppRoot({ appName, context, container: Container, initialProps, initialScheme, router }: AppRootProps) {
18
20
  const backEventState = useBackEventState();
19
- const scheme = global.__granite.app.scheme;
20
- const baseScheme = `${scheme}://${appName}`;
21
+
22
+ const prefix = getSchemePrefix({
23
+ appName,
24
+ scheme: global.__granite.app.scheme,
25
+ host: global.__granite.app.host,
26
+ });
21
27
 
22
28
  return (
23
29
  <App {...initialProps}>
@@ -26,10 +32,9 @@ export function AppRoot({ appName, context, container: Container, initialProps,
26
32
  <Router
27
33
  context={context}
28
34
  initialProps={initialProps}
35
+ initialScheme={initialScheme}
29
36
  container={Container}
30
- canGoBack={!backEventState.hasBackEvent}
31
- onBack={backEventState.onBack}
32
- prefix={baseScheme}
37
+ prefix={prefix}
33
38
  {...router}
34
39
  />
35
40
  </BackEventProvider>
@@ -5,6 +5,7 @@ import type { InitialProps } from '../initial-props';
5
5
  import type { RouterProps, RequireContext } from '../router';
6
6
  import { AppRoot } from './AppRoot';
7
7
  import { HostAppRoot } from './HostAppRoot';
8
+ import { getSchemeUri } from '../constant-bridges';
8
9
 
9
10
  export interface GraniteProps {
10
11
  /**
@@ -24,6 +25,12 @@ export interface GraniteProps {
24
25
  * Configuration object to be passed to the router.
25
26
  */
26
27
  router?: RouterProps;
28
+
29
+ /**
30
+ * @description
31
+ * The initial scheme of the app.
32
+ */
33
+ initialScheme?: string;
27
34
  }
28
35
 
29
36
  const createApp = () => {
@@ -40,7 +47,7 @@ const createApp = () => {
40
47
  return {
41
48
  registerApp(
42
49
  AppContainer: ComponentType<PropsWithChildren<InitialProps>>,
43
- { appName, context, router }: GraniteProps
50
+ { appName, context, router, initialScheme }: GraniteProps
44
51
  ): (initialProps: InitialProps) => JSX.Element {
45
52
  if (appName === ENTRY_BUNDLE_NAME) {
46
53
  throw new Error(`Reserved app name 'shared' cannot be used`);
@@ -51,6 +58,7 @@ const createApp = () => {
51
58
  <AppRoot
52
59
  container={AppContainer}
53
60
  initialProps={initialProps}
61
+ initialScheme={initialScheme ?? getSchemeUri()}
54
62
  appName={appName}
55
63
  context={context}
56
64
  router={router}
package/src/cli.ts ADDED
@@ -0,0 +1 @@
1
+ export { initialize } from '@granite-js/cli';
package/src/config.ts ADDED
@@ -0,0 +1 @@
1
+ export { defineConfig } from '@granite-js/plugin-core';
package/src/index.ts CHANGED
@@ -20,5 +20,7 @@ export * from './video';
20
20
  export * from './status-bar';
21
21
  export * from './blur';
22
22
 
23
+ export { BackButton, useRouterBackHandler } from './router';
24
+
23
25
  export type { InitialProps, ColorPreference } from './initial-props';
24
26
  export type { GraniteProps } from './app';
@@ -1,3 +1,4 @@
1
+ import { HeaderBackButtonProps } from '@granite-js/native/@react-navigation/elements';
1
2
  import {
2
3
  createNavigationContainerRef,
3
4
  NavigationContainer,
@@ -8,9 +9,11 @@ import {
8
9
  import { NativeStackNavigationOptions } from '@granite-js/native/@react-navigation/native-stack';
9
10
  import { ComponentProps, ComponentType, Fragment, PropsWithChildren, ReactElement, useCallback, useMemo } from 'react';
10
11
  import { InitialProps } from '..';
12
+ import { closeView } from '../native-modules';
13
+ import { BackButton } from './components/BackButton';
11
14
  import { CanGoBackGuard } from './components/CanGoBackGuard';
12
- import { RouterBackButton, RouterBackButtonProps } from './components/RouterBackButton';
13
15
  import { StackNavigator } from './components/StackNavigator';
16
+ import { useInternalRouterBackHandler } from './components/useRouterBackHandler';
14
17
  import { useInitialRouteName } from './hooks/useInitialRouteName';
15
18
  import { useRouterControls } from './hooks/useRouterControls';
16
19
  import { RequireContext } from './types';
@@ -52,6 +55,7 @@ export interface InternalRouterProps {
52
55
  */
53
56
  container: ComponentType<PropsWithChildren<InitialProps>>;
54
57
  initialProps: InitialProps;
58
+ initialScheme: string;
55
59
  }
56
60
 
57
61
  export type RouterProps = StackNavigatorProps & NavigationContainerProps;
@@ -117,10 +121,9 @@ export function Router({
117
121
  // Internal props
118
122
  prefix,
119
123
  context,
120
- canGoBack = true,
121
- onBack,
122
124
  container: Container = Fragment,
123
125
  initialProps,
126
+ initialScheme,
124
127
  // Public props (NavigationContainer)
125
128
  navigationContainerRef,
126
129
  defaultScreenOption,
@@ -128,16 +131,24 @@ export function Router({
128
131
  // Public props (StackNavigator)
129
132
  ...navigationContainerProps
130
133
  }: InternalRouterProps & RouterProps): ReactElement {
131
- const initialRouteName = useInitialRouteName(prefix);
132
- const { Screens, linkingOptions } = useRouterControls({ prefix, context, screenContainer });
134
+ const initialRouteName = useInitialRouteName({ prefix, initialScheme });
135
+ const { Screens, linkingOptions } = useRouterControls({
136
+ prefix,
137
+ context,
138
+ screenContainer,
139
+ initialScheme,
140
+ });
133
141
 
134
142
  const ref = useMemo(() => navigationContainerRef ?? createNavigationContainerRef<any>(), [navigationContainerRef]);
135
143
 
144
+ const { handler, canGoBack, onBack } = useInternalRouterBackHandler({
145
+ navigationContainerRef: ref,
146
+ onClose: closeView,
147
+ });
148
+
136
149
  const headerLeft = useCallback(
137
- (backButtonProps: Omit<RouterBackButtonProps, 'navigationContainerRef'>) => (
138
- <RouterBackButton {...backButtonProps} onBack={onBack} canGoBack={canGoBack} navigationContainerRef={ref} />
139
- ),
140
- [onBack, canGoBack, ref]
150
+ (backButtonProps: HeaderBackButtonProps) => <BackButton {...backButtonProps} onPress={handler} />,
151
+ [handler]
141
152
  );
142
153
 
143
154
  const screenOptions = useCallback(
@@ -5,10 +5,49 @@ interface BackButtonProps extends TouchableOpacityProps {
5
5
  tintColor?: string;
6
6
  onPress?: () => void;
7
7
  }
8
-
9
- const DEFAULT_COLOR = '#191f28'; // grey900
8
+ /**
9
+ * @public
10
+ * @category UI
11
+ * @name BackButton
12
+ * @description
13
+ * A back button component. This component is primarily used to implement functionality for returning to the previous screen in navigation headers or custom screen tops. If no `onPress` handler is set, it won't perform any action.
14
+ *
15
+ * @param {string} [props.tintColor] - The color of the button icon. You can use CSS color strings.
16
+ * @param {() => void} [props.onPress] - A function to execute when the button is pressed. For example, you can add a function to return to the previous screen.
17
+ *
18
+ * @returns {ReactElement} Returns a back button component.
19
+ * @example
20
+ *
21
+ * ### Example of directly passing a handler to a back button and setting a function to close the view
22
+ *
23
+ * ```tsx
24
+ * import { createNavigationContainerRef, useNavigation } from '@granite-js/native/@react-navigation/native';
25
+ * import { BackButton, useRouterBackHandler } from '@granite-js/react-native';
26
+ * import { useEffect } from 'react';
27
+ *
28
+ * const navigationContainerRef = createNavigationContainerRef();
29
+ *
30
+ * function MyBackButton() {
31
+ * const navigation = useNavigation();
32
+ *
33
+ * const { handler } = useRouterBackHandler({
34
+ * navigationContainerRef,
35
+ * onClose: () => {
36
+ * // close the view
37
+ * },
38
+ * });
39
+ *
40
+ * useEffect(() => {
41
+ * navigation.setOptions({
42
+ * headerLeft: () => <BackButton onPress={handler} />,
43
+ * });
44
+ * }, [handler, navigation]);
45
+ *
46
+ * return <></>;
47
+ * }
48
+ */
10
49
  function BackButton({ tintColor, onPress }: BackButtonProps) {
11
- return <NavbarBackButton onPress={onPress} color={tintColor ?? DEFAULT_COLOR} />;
50
+ return <NavbarBackButton onPress={onPress} color={tintColor} />;
12
51
  }
13
52
 
14
53
  function NavbarBackButton({ onPress, color }: { onPress?: () => void; color?: string }) {
@@ -0,0 +1,91 @@
1
+ import { NavigationContainerRefWithCurrent } from '@granite-js/native/@react-navigation/native';
2
+ import { useCallback, useMemo } from 'react';
3
+ import { useBackEventState } from '../../use-back-event';
4
+
5
+ /**
6
+ * @public
7
+ * @name useRouterBackHandler
8
+ * @category Screen Control
9
+ * @description
10
+ * A hook that provides a handler for handling back navigation actions. This can be used when you need to close a view directly when the back button is pressed in modals or independent screens where there's no navigation stack. This hook uses `NavigationContainerRef` from `@react-navigation/native` to navigate to the previous screen if there's a remaining navigation stack, or executes the user-defined `onClose` function if the stack is empty.
11
+ *
12
+ * @param {NavigationContainerRefWithCurrent<any>} navigationContainerRef - A `NavigationContainerRef` that can reference the current navigation state. Used when executing back navigation actions.
13
+ * @param {() => void} [onClose] - A function to execute when the navigation stack is empty. For example, you can pass a function that closes a React Native View.
14
+ *
15
+ * @returns {{ handler: () => void }} A handler function that can be used in back buttons or similar components.
16
+ *
17
+ * @example
18
+ *
19
+ * ### Example of directly passing a handler to a back button and setting a function to close the view
20
+ *
21
+ * ```tsx
22
+ * import { createNavigationContainerRef, useNavigation } from '@granite-js/native/@react-navigation/native';
23
+ * import { BackButton, useRouterBackHandler } from '@granite-js/react-native';
24
+ * import { useEffect } from 'react';
25
+ *
26
+ * const navigationContainerRef = createNavigationContainerRef();
27
+ *
28
+ * function MyBackButton() {
29
+ * const navigation = useNavigation();
30
+ *
31
+ * const { handler } = useRouterBackHandler({
32
+ * navigationContainerRef,
33
+ * onClose: () => {
34
+ * // close the view
35
+ * },
36
+ * });
37
+ *
38
+ * useEffect(() => {
39
+ * navigation.setOptions({
40
+ * headerLeft: () => <BackButton onPress={handler} />,
41
+ * });
42
+ * }, [handler, navigation]);
43
+ *
44
+ * return <></>;
45
+ * }
46
+ */
47
+ export function useRouterBackHandler({
48
+ navigationContainerRef,
49
+ onClose,
50
+ }: {
51
+ navigationContainerRef: NavigationContainerRefWithCurrent<any>;
52
+ onClose?: () => void;
53
+ }) {
54
+ const { handler } = useInternalRouterBackHandler({ navigationContainerRef, onClose });
55
+
56
+ return { handler };
57
+ }
58
+
59
+ export function useInternalRouterBackHandler({
60
+ navigationContainerRef,
61
+ onClose,
62
+ }: {
63
+ navigationContainerRef: NavigationContainerRefWithCurrent<any>;
64
+ onClose?: () => void;
65
+ }) {
66
+ const { onBack, hasBackEvent } = useBackEventState();
67
+ const canGoBack = !hasBackEvent;
68
+
69
+ const handler = useCallback(() => {
70
+ onBack?.();
71
+
72
+ if (!canGoBack) {
73
+ return;
74
+ }
75
+
76
+ if (navigationContainerRef.canGoBack()) {
77
+ navigationContainerRef.goBack();
78
+ } else {
79
+ onClose?.();
80
+ }
81
+ }, [canGoBack, onClose, navigationContainerRef, onBack]);
82
+
83
+ return useMemo(
84
+ () => ({
85
+ handler,
86
+ canGoBack,
87
+ onBack,
88
+ }),
89
+ [canGoBack, handler, onBack]
90
+ );
91
+ }
@@ -1,21 +1,15 @@
1
1
  import { Platform } from 'react-native';
2
- import { getSchemeUri } from '../../native-modules';
3
2
 
4
- export function useInitialRouteName(prefix: string) {
5
- const initialScheme = getInitialScheme();
6
- const pathname = initialScheme?.slice(prefix.length).split('?')[0];
3
+ export function useInitialRouteName({ prefix, initialScheme }: { prefix: string; initialScheme: string }) {
4
+ const pathname = removeTrailingSlash(initialScheme).slice(prefix.length).split('?')[0];
7
5
  const shouldUseIndex = initialScheme == null || pathname?.length === 0;
8
6
 
9
7
  return shouldUseIndex ? '/' : pathname;
10
8
  }
11
- function getInitialScheme() {
12
- const scheme = getSchemeUri();
13
9
 
14
- /**
15
- * Removes trailing '/' on Android.
16
- */
10
+ function removeTrailingSlash(scheme: string) {
17
11
  if (Platform.OS === 'android') {
18
- return scheme?.replaceAll(/\/+$/g, '');
12
+ return scheme.replaceAll(/\/+$/g, '');
19
13
  }
20
14
 
21
15
  return scheme;
@@ -1,6 +1,5 @@
1
1
  import { NavigationContainer } from '@granite-js/native/@react-navigation/native';
2
2
  import { useMemo, type ComponentProps, type ComponentType, type PropsWithChildren } from 'react';
3
- import { getSchemeUri } from '../../native-modules';
4
3
  import { StackNavigator } from '../components/StackNavigator';
5
4
  import { RESERVED_KEYWORDS } from '../constants';
6
5
  import { RequireContext } from '../types/RequireContext';
@@ -13,11 +12,17 @@ type NavigationContainerProps = ComponentProps<typeof NavigationContainer>;
13
12
 
14
13
  export interface RouterControlsConfig {
15
14
  prefix: string;
15
+ initialScheme: string;
16
16
  context: RequireContext;
17
17
  screenContainer?: ComponentType<PropsWithChildren<any>>;
18
18
  }
19
19
 
20
- export function useRouterControls({ prefix, context, screenContainer: ScreenContainer }: RouterControlsConfig) {
20
+ export function useRouterControls({
21
+ prefix,
22
+ context,
23
+ screenContainer: ScreenContainer,
24
+ initialScheme,
25
+ }: RouterControlsConfig) {
21
26
  const routeScreens = useMemo(() => getRouteScreens(context), [context]);
22
27
 
23
28
  const registerScreens = useMemo(() => {
@@ -56,17 +61,15 @@ export function useRouterControls({ prefix, context, screenContainer: ScreenCont
56
61
  screens: getScreenPathMapConfig(registerScreens),
57
62
  },
58
63
  async getInitialURL() {
59
- const initialURL = getSchemeUri();
60
-
61
- if (initialURL == null) {
64
+ if (initialScheme == null) {
62
65
  return;
63
66
  }
64
67
 
65
68
  /** @NOTE Korean paths need to be decoded. */
66
- return decodeURI(initialURL);
69
+ return decodeURI(initialScheme);
67
70
  },
68
71
  };
69
- }, [prefix, registerScreens]);
72
+ }, [initialScheme, prefix, registerScreens]);
70
73
 
71
74
  return { Screens, linkingOptions };
72
75
  }
@@ -1,3 +1,5 @@
1
1
  export * from './Router';
2
2
  export { getRouteScreens, getScreenPathMapConfig, defaultParserParams } from './utils';
3
3
  export * from './types';
4
+ export * from './components/BackButton';
5
+ export * from './components/useRouterBackHandler';
@@ -2,7 +2,7 @@ import { NativeStackNavigationOptions } from '@granite-js/native/@react-navigati
2
2
  import { Platform } from 'react-native';
3
3
 
4
4
  export const DEFAULT_BACKGROUND_COLOR = '#ffffff';
5
- export const DEFAULT_HEADER_TINT_COLOR = '#333d4b';
5
+ export const DEFAULT_HEADER_TINT_COLOR = '#191f28';
6
6
  export const BASE_STACK_NAVIGATOR_STYLE: NativeStackNavigationOptions = {
7
7
  contentStyle: {
8
8
  backgroundColor: DEFAULT_BACKGROUND_COLOR,
@@ -6,6 +6,7 @@ export interface GraniteGlobal {
6
6
  app: {
7
7
  name: string;
8
8
  scheme: string;
9
+ host: string;
9
10
  };
10
11
  }
11
12
 
@@ -15,7 +16,4 @@ declare global {
15
16
 
16
17
  // @internal
17
18
  var Page: ComponentType<any>;
18
-
19
- var window: { __granite: GraniteGlobal };
20
- var global: { __granite: GraniteGlobal };
21
19
  }
@@ -0,0 +1,44 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { getSchemePrefix } from './getSchemePrefix';
3
+
4
+ describe('getSchemePrefix', () => {
5
+ it('should return correct URL when host is empty', () => {
6
+ const result = getSchemePrefix({
7
+ scheme: 'myapp',
8
+ appName: 'testapp',
9
+ host: '',
10
+ });
11
+
12
+ expect(result).toBe('myapp://testapp');
13
+ });
14
+
15
+ it('should return correct URL when host is provided', () => {
16
+ const result = getSchemePrefix({
17
+ scheme: 'myapp',
18
+ appName: 'testapp',
19
+ host: 'super',
20
+ });
21
+
22
+ expect(result).toBe('myapp://super/testapp');
23
+ });
24
+
25
+ it('should handle different schemes', () => {
26
+ const result = getSchemePrefix({
27
+ scheme: 'https',
28
+ appName: 'webapp',
29
+ host: 'myhost',
30
+ });
31
+
32
+ expect(result).toBe('https://myhost/webapp');
33
+ });
34
+
35
+ it('should handle different app names', () => {
36
+ const result = getSchemePrefix({
37
+ scheme: 'myapp',
38
+ appName: 'production-app',
39
+ host: 'myhost',
40
+ });
41
+
42
+ expect(result).toBe('myapp://myhost/production-app');
43
+ });
44
+ });
@@ -0,0 +1,5 @@
1
+ export function getSchemePrefix({ scheme, appName, host }: { scheme: string; appName: string; host: string }) {
2
+ const base = host.length > 0 ? `${scheme}://${host}/` : `${scheme}://`;
3
+
4
+ return new URL(`${base}${appName}`).toString();
5
+ }
package/cli.js DELETED
@@ -1,4 +0,0 @@
1
- const { initialize } = require('@granite-js/cli');
2
- module.exports = {
3
- initialize,
4
- };
package/config.js DELETED
@@ -1,5 +0,0 @@
1
- const { defineConfig } = require('@granite-js/plugin-core');
2
-
3
- module.exports = {
4
- defineConfig,
5
- };
@@ -1,9 +0,0 @@
1
- import { NavigationContainerRefWithCurrent } from '@granite-js/native/@react-navigation/native';
2
- export type RouterBackButtonProps = {
3
- onPress?: () => void;
4
- tintColor?: string;
5
- canGoBack?: boolean;
6
- onBack?: () => void;
7
- navigationContainerRef: NavigationContainerRefWithCurrent<any>;
8
- };
9
- export declare function RouterBackButton({ tintColor, canGoBack, onBack, navigationContainerRef }: RouterBackButtonProps): import("react/jsx-runtime").JSX.Element;
@@ -1,32 +0,0 @@
1
- import { NavigationContainerRefWithCurrent } from '@granite-js/native/@react-navigation/native';
2
- import { BackButton } from './BackButton';
3
- import { closeView } from '../../native-modules';
4
-
5
- export type RouterBackButtonProps = {
6
- onPress?: () => void;
7
- tintColor?: string;
8
- canGoBack?: boolean;
9
- onBack?: () => void;
10
- navigationContainerRef: NavigationContainerRefWithCurrent<any>;
11
- };
12
-
13
- export function RouterBackButton({ tintColor, canGoBack, onBack, navigationContainerRef }: RouterBackButtonProps) {
14
- return (
15
- <BackButton
16
- tintColor={tintColor}
17
- onPress={() => {
18
- onBack?.();
19
-
20
- if (!canGoBack) {
21
- return;
22
- }
23
-
24
- if (navigationContainerRef.canGoBack()) {
25
- navigationContainerRef.goBack();
26
- } else {
27
- closeView();
28
- }
29
- }}
30
- />
31
- );
32
- }
File without changes
File without changes