@granite-js/react-native 0.0.1

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 (189) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/LICENSE +202 -0
  3. package/README.md +24 -0
  4. package/bin/cli.js +3 -0
  5. package/cli.d.ts +1 -0
  6. package/cli.js +4 -0
  7. package/config.d.ts +2 -0
  8. package/config.js +5 -0
  9. package/dist/app/App/index.android.d.ts +2 -0
  10. package/dist/app/App/index.ios.d.ts +6 -0
  11. package/dist/app/Granite.d.ts +60 -0
  12. package/dist/app/index.d.ts +2 -0
  13. package/dist/app/registerPage.d.ts +20 -0
  14. package/dist/async-bridges.d.ts +2 -0
  15. package/dist/constant-bridges.d.ts +1 -0
  16. package/dist/dev-entrypoint/index.d.ts +2 -0
  17. package/dist/event/abstract.d.ts +42 -0
  18. package/dist/event/index.d.ts +2 -0
  19. package/dist/event/useGraniteEvent.d.ts +14 -0
  20. package/dist/impression-area/ImpressionArea.d.ts +231 -0
  21. package/dist/impression-area/index.d.ts +1 -0
  22. package/dist/index.d.ts +17 -0
  23. package/dist/initial-props/InitialProps.d.ts +127 -0
  24. package/dist/initial-props/index.d.ts +1 -0
  25. package/dist/intersection-observer/IOContext.d.ts +10 -0
  26. package/dist/intersection-observer/IOFlatList.d.ts +55 -0
  27. package/dist/intersection-observer/IOManager.d.ts +24 -0
  28. package/dist/intersection-observer/IOScrollView.d.ts +59 -0
  29. package/dist/intersection-observer/InView.d.ts +107 -0
  30. package/dist/intersection-observer/IntersectionObserver.d.ts +67 -0
  31. package/dist/intersection-observer/index.d.ts +8 -0
  32. package/dist/intersection-observer/withIO.d.ts +20 -0
  33. package/dist/jest/index.d.ts +1 -0
  34. package/dist/jest/index.js +32 -0
  35. package/dist/keyboard/KeyboardAboveView.d.ts +40 -0
  36. package/dist/keyboard/index.d.ts +1 -0
  37. package/dist/keyboard/useKeyboardAnimatedHeight.d.ts +20 -0
  38. package/dist/native-event-emitter/eventEmitters/index.d.ts +2 -0
  39. package/dist/native-event-emitter/eventEmitters/types.d.ts +4 -0
  40. package/dist/native-event-emitter/eventEmitters/visibilityChanged.d.ts +10 -0
  41. package/dist/native-event-emitter/index.d.ts +1 -0
  42. package/dist/native-event-emitter/nativeEventEmitter.d.ts +15 -0
  43. package/dist/native-modules/core/GraniteCoreModule.d.ts +8 -0
  44. package/dist/native-modules/index.d.ts +3 -0
  45. package/dist/native-modules/natives/GraniteModule.d.ts +7 -0
  46. package/dist/native-modules/natives/closeView.d.ts +21 -0
  47. package/dist/native-modules/natives/getSchemeUri.d.ts +23 -0
  48. package/dist/native-modules/natives/index.d.ts +3 -0
  49. package/dist/native-modules/natives/openURL.d.ts +36 -0
  50. package/dist/react/index.d.ts +1 -0
  51. package/dist/react/useWaitForReturnNavigator.d.ts +39 -0
  52. package/dist/rn-polyfills/index.d.ts +1 -0
  53. package/dist/rn-polyfills/symbol-asynciterator/index.d.ts +9 -0
  54. package/dist/rn-polyfills/url/index.d.ts +1 -0
  55. package/dist/router/Router.d.ts +59 -0
  56. package/dist/router/components/BackButton.d.ts +7 -0
  57. package/dist/router/components/CanGoBackGuard.d.ts +6 -0
  58. package/dist/router/components/RouterBackButton.d.ts +9 -0
  59. package/dist/router/components/StackNavigator.d.ts +54 -0
  60. package/dist/router/constants.d.ts +2 -0
  61. package/dist/router/createRoute.d.ts +39 -0
  62. package/dist/router/createRoute.test-d.d.ts +9 -0
  63. package/dist/router/hooks/useInitialRouteName.d.ts +1 -0
  64. package/dist/router/hooks/useIsInitialScreen.d.ts +1 -0
  65. package/dist/router/hooks/useRouterControls.d.ts +11 -0
  66. package/dist/router/index.d.ts +3 -0
  67. package/dist/router/types/RequireContext.d.ts +7 -0
  68. package/dist/router/types/RouteScreen.d.ts +16 -0
  69. package/dist/router/types/Screen.d.ts +23 -0
  70. package/dist/router/types/index.d.ts +3 -0
  71. package/dist/router/types/screen-option.d.ts +4 -0
  72. package/dist/router/utils/createParentRouteScreenMap.d.ts +8 -0
  73. package/dist/router/utils/defaultParserParams.d.ts +9 -0
  74. package/dist/router/utils/index.d.ts +2 -0
  75. package/dist/router/utils/matchers.d.ts +2 -0
  76. package/dist/router/utils/mergeParentLayoutScreen.d.ts +18 -0
  77. package/dist/router/utils/path.d.ts +53 -0
  78. package/dist/router/utils/screen.d.ts +53 -0
  79. package/dist/scroll-view-inertial-background/ScrollViewInertialBackground.d.ts +49 -0
  80. package/dist/scroll-view-inertial-background/index.d.ts +1 -0
  81. package/dist/types/global.d.ts +18 -0
  82. package/dist/use-back-event/index.d.ts +1 -0
  83. package/dist/use-back-event/useBackEvent.d.ts +135 -0
  84. package/dist/utils/noop.d.ts +1 -0
  85. package/dist/utils/usePreservedCallback.d.ts +1 -0
  86. package/dist/visibility/VisibilityProvider.d.ts +27 -0
  87. package/dist/visibility/index.d.ts +6 -0
  88. package/dist/visibility/react-navigation/index.d.ts +2 -0
  89. package/dist/visibility/react-navigation/useIsFocusedSafely.d.ts +20 -0
  90. package/dist/visibility/react-navigation/useNavigationSafely.d.ts +19 -0
  91. package/dist/visibility/useIsAppForeground.d.ts +39 -0
  92. package/dist/visibility/useVisibility.d.ts +35 -0
  93. package/dist/visibility/useVisibilityChange.d.ts +51 -0
  94. package/dist/visibility/useVisibilityChanged.d.ts +41 -0
  95. package/dist/visibility/utils/usePrevious.d.ts +15 -0
  96. package/jest.d.ts +1 -0
  97. package/package.json +92 -0
  98. package/presets.d.ts +1 -0
  99. package/src/app/App/index.android.tsx +6 -0
  100. package/src/app/App/index.d.ts +6 -0
  101. package/src/app/App/index.ios.tsx +13 -0
  102. package/src/app/Granite.tsx +130 -0
  103. package/src/app/index.ts +2 -0
  104. package/src/app/registerPage.ts +29 -0
  105. package/src/async-bridges.ts +2 -0
  106. package/src/constant-bridges.ts +1 -0
  107. package/src/dev-entrypoint/index.tsx +21 -0
  108. package/src/event/abstract.ts +130 -0
  109. package/src/event/index.ts +2 -0
  110. package/src/event/useGraniteEvent.ts +34 -0
  111. package/src/impression-area/ImpressionArea.tsx +341 -0
  112. package/src/impression-area/index.ts +1 -0
  113. package/src/index.ts +19 -0
  114. package/src/initial-props/InitialProps.ts +144 -0
  115. package/src/initial-props/index.ts +1 -0
  116. package/src/intersection-observer/IOContext.ts +16 -0
  117. package/src/intersection-observer/IOFlatList.ts +72 -0
  118. package/src/intersection-observer/IOManager.ts +73 -0
  119. package/src/intersection-observer/IOScrollView.ts +69 -0
  120. package/src/intersection-observer/InView.tsx +205 -0
  121. package/src/intersection-observer/IntersectionObserver.ts +212 -0
  122. package/src/intersection-observer/index.ts +24 -0
  123. package/src/intersection-observer/withIO.tsx +151 -0
  124. package/src/jest/index.ts +1 -0
  125. package/src/keyboard/KeyboardAboveView.tsx +62 -0
  126. package/src/keyboard/index.ts +1 -0
  127. package/src/keyboard/useKeyboardAnimatedHeight.tsx +81 -0
  128. package/src/native-event-emitter/eventEmitters/index.ts +3 -0
  129. package/src/native-event-emitter/eventEmitters/types.ts +4 -0
  130. package/src/native-event-emitter/eventEmitters/visibilityChanged.ts +11 -0
  131. package/src/native-event-emitter/index.ts +1 -0
  132. package/src/native-event-emitter/nativeEventEmitter.ts +18 -0
  133. package/src/native-modules/core/GraniteCoreModule.ts +9 -0
  134. package/src/native-modules/index.ts +3 -0
  135. package/src/native-modules/natives/GraniteModule.ts +8 -0
  136. package/src/native-modules/natives/closeView.ts +25 -0
  137. package/src/native-modules/natives/getSchemeUri.ts +27 -0
  138. package/src/native-modules/natives/index.ts +3 -0
  139. package/src/native-modules/natives/openURL.ts +40 -0
  140. package/src/react/index.ts +1 -0
  141. package/src/react/useWaitForReturnNavigator.ts +75 -0
  142. package/src/rn-polyfills/index.ts +7 -0
  143. package/src/rn-polyfills/symbol-asynciterator/index.ts +15 -0
  144. package/src/rn-polyfills/url/index.ts +1 -0
  145. package/src/router/Router.tsx +164 -0
  146. package/src/router/components/BackButton.tsx +58 -0
  147. package/src/router/components/CanGoBackGuard.tsx +31 -0
  148. package/src/router/components/RouterBackButton.tsx +32 -0
  149. package/src/router/components/StackNavigator.tsx +12 -0
  150. package/src/router/constants.ts +3 -0
  151. package/src/router/createRoute.test-d.ts +52 -0
  152. package/src/router/createRoute.ts +161 -0
  153. package/src/router/hooks/useInitialRouteName.tsx +22 -0
  154. package/src/router/hooks/useIsInitialScreen.ts +7 -0
  155. package/src/router/hooks/useRouterControls.tsx +72 -0
  156. package/src/router/index.ts +3 -0
  157. package/src/router/types/RequireContext.ts +7 -0
  158. package/src/router/types/RouteScreen.ts +17 -0
  159. package/src/router/types/Screen.tsx +24 -0
  160. package/src/router/types/index.ts +3 -0
  161. package/src/router/types/screen-option.ts +23 -0
  162. package/src/router/utils/createParentRouteScreenMap.spec.ts +166 -0
  163. package/src/router/utils/createParentRouteScreenMap.ts +136 -0
  164. package/src/router/utils/defaultParserParams.spec.ts +46 -0
  165. package/src/router/utils/defaultParserParams.ts +19 -0
  166. package/src/router/utils/index.ts +2 -0
  167. package/src/router/utils/matchers.ts +5 -0
  168. package/src/router/utils/mergeParentLayoutScreen.spec.tsx +112 -0
  169. package/src/router/utils/mergeParentLayoutScreen.tsx +43 -0
  170. package/src/router/utils/path.spec.ts +135 -0
  171. package/src/router/utils/path.ts +105 -0
  172. package/src/router/utils/screen.tsx +111 -0
  173. package/src/scroll-view-inertial-background/ScrollViewInertialBackground.tsx +99 -0
  174. package/src/scroll-view-inertial-background/index.ts +1 -0
  175. package/src/types/global.ts +31 -0
  176. package/src/use-back-event/index.ts +1 -0
  177. package/src/use-back-event/useBackEvent.tsx +260 -0
  178. package/src/utils/noop.ts +1 -0
  179. package/src/utils/usePreservedCallback.ts +16 -0
  180. package/src/visibility/VisibilityProvider.tsx +36 -0
  181. package/src/visibility/index.ts +6 -0
  182. package/src/visibility/react-navigation/index.ts +2 -0
  183. package/src/visibility/react-navigation/useIsFocusedSafely.tsx +58 -0
  184. package/src/visibility/react-navigation/useNavigationSafely.tsx +30 -0
  185. package/src/visibility/useIsAppForeground.tsx +73 -0
  186. package/src/visibility/useVisibility.tsx +54 -0
  187. package/src/visibility/useVisibilityChange.ts +69 -0
  188. package/src/visibility/useVisibilityChanged.tsx +69 -0
  189. package/src/visibility/utils/usePrevious.tsx +24 -0
@@ -0,0 +1,111 @@
1
+ import { getRoutePath } from './path';
2
+ import { routeMap } from '../createRoute';
3
+ import { RequireContext, RouteScreen } from '../types';
4
+
5
+ /**
6
+ * @kind function
7
+ * @name getRouteScreens
8
+ * @description
9
+ * Gets screens from the pages folder.
10
+ *
11
+ * @param {RequireContext} context - Object containing information about screens in Router
12
+ * @returns {RouteScreen[]} screens - Returns a list of screens that can be navigated to.
13
+ *
14
+ * @example
15
+ * ```tsx
16
+ * import { getRouteScreens } from '@granite-js/react-native';
17
+ *
18
+ * const context = require.context('../pages');
19
+ * const screens = getRouteScreens(context);
20
+ * ```
21
+ */
22
+ export function getRouteScreens(context: RequireContext): RouteScreen[] {
23
+ const screens = context.keys().map((key) => {
24
+ const path = getRoutePath(key);
25
+
26
+ /**
27
+ * Keep export default option for backward compatibility.
28
+ * If migrated to type-safe, only export Route will be needed.
29
+ */
30
+ const component = context(key)?.default ?? routeMap.get(context(key)?.Route?._path)?.component;
31
+
32
+ if (component == null) {
33
+ throw new Error(`Page component not found in ${key}.`);
34
+ }
35
+
36
+ return {
37
+ path,
38
+ component,
39
+ };
40
+ });
41
+
42
+ return screens;
43
+ }
44
+
45
+ /**
46
+ * @kind function
47
+ * @name getScreenPathMapConfig
48
+ * @description Maps paths of screens.
49
+ *
50
+ * @param {RouteScreen[]} routeScreens - List of screens that can be navigated to
51
+ *
52
+ * @example
53
+ * ```tsx
54
+ * import { registerPage, getRouteScreens, getScreenPathMapConfig, Router } from '@granite-js/react-native';
55
+ * import { context } from '../require.context';
56
+ *
57
+ * function App() {
58
+ * return <Router context={context} prefix={'scheme://minibench'} />;
59
+ * }
60
+ *
61
+ * export default registerPage(
62
+ * App,
63
+ * 'minibench',
64
+ * getScreenPathMapConfig(getRouteScreens(context))
65
+ * );
66
+ * ```
67
+ */
68
+ export function getScreenPathMapConfig(routeScreens: RouteScreen[]) {
69
+ const screensConfig: ScreenPath = {};
70
+
71
+ routeScreens.forEach((routeScreen) => {
72
+ const routePath = routeScreen.path;
73
+
74
+ if (screensConfig[routePath] != null) {
75
+ throw new Error(`${routePath} is already registered. Please check for duplicate paths.`);
76
+ }
77
+
78
+ screensConfig[routePath] = {
79
+ path: routePath,
80
+ };
81
+ });
82
+
83
+ // @see https://reactnavigation.org/docs/configuring-links/#matching-exact-paths
84
+ // This is a mapping for the root path ('/') for deep link handling.
85
+ // Example: To handle URLs like scheme://{service_name}?name=John,
86
+ // map the root path to an empty string to correctly extract query parameters.
87
+ screensConfig['/'] = {
88
+ path: '',
89
+ };
90
+
91
+ // https://reactnavigation.org/docs/configuring-links/#handling-unmatched-routes-or-404
92
+ screensConfig['/_404'] = {
93
+ path: '*',
94
+ };
95
+
96
+ return screensConfig;
97
+ }
98
+
99
+ /**
100
+ * @name ScreenPath
101
+ * @description
102
+ * Type representing screen paths.
103
+ *
104
+ * @typedef {Record<string, { path?: string }>} ScreenPath
105
+ */
106
+ export type ScreenPath = Record<
107
+ string,
108
+ {
109
+ path?: string;
110
+ }
111
+ >;
@@ -0,0 +1,99 @@
1
+ import type { ComponentProps } from 'react';
2
+ import { useWindowDimensions, View } from 'react-native';
3
+
4
+ interface ScrollViewInertialBackgroundProps {
5
+ topColor?: string;
6
+ bottomColor?: string;
7
+ spacer?: number;
8
+ }
9
+
10
+ /**
11
+ * @public
12
+ * @category UI
13
+ * @name ScrollViewInertialBackground
14
+ * @description
15
+ * Adds background colors to the top and bottom spaces of iOS `ScrollView` content to provide a natural visual effect when scrolling.
16
+ * In iOS, when scrolling reaches the end, a slight bouncing effect occurs, known as the [Bounce effect](https://medium.com/@wcandillon/ios-bounce-list-effect-with-react-native-5102e3a83999). Setting background colors in the spaces above and below the content can provide a more consistent user experience.
17
+ *
18
+ * @param {object} [props] - `props` object passed to the component.
19
+ * @param {string} [props.topColor] - Background color to apply to the top area of the scroll. Default is `adaptive.background` which is automatically applied based on the system theme.
20
+ * @param {string} [props.bottomColor] - Background color to apply to the bottom area of the scroll. Default is `adaptive.background` which is automatically applied based on the system theme.
21
+ * @param {number} [props.spacer] - Specifies the size of the space between the top and bottom areas where the background color is applied. Default uses the screen height obtained from [`useWindowDimensions`](https://reactnative.dev/docs/next/usewindowdimensions).
22
+ *
23
+ * @example
24
+ *
25
+ * ### Adding background colors to the top and bottom of a scroll view
26
+ *
27
+ * Adds red background color to the top and blue to the bottom of the scroll view. The background color is applied to areas outside the scroll.
28
+ *
29
+ * ```tsx
30
+ * import { ScrollView, View, Text } from 'react-native';
31
+ * import { ScrollViewInertialBackground } from '@granite-js/react-native';
32
+ *
33
+ * const dummies = Array.from({ length: 20 }, (_, i) => i);
34
+ *
35
+ * export function InertialBackgroundExample() {
36
+ * return (
37
+ * <ScrollView>
38
+ * <ScrollViewInertialBackground topColor="red" bottomColor="blue" />
39
+ * {dummies.map((i) => (
40
+ * <View
41
+ * key={`dummy-${i}`}
42
+ * style={{ width: '100%', height: 100, borderBottomColor: 'black', borderBottomWidth: 1 }}
43
+ * >
44
+ * <Text>Try scrolling.</Text>
45
+ * </View>
46
+ * ))}
47
+ * </ScrollView>
48
+ * );
49
+ * }
50
+ * ```
51
+ */
52
+ export function ScrollViewInertialBackground({
53
+ topColor,
54
+ bottomColor,
55
+ spacer: _spacer,
56
+ }: ScrollViewInertialBackgroundProps) {
57
+ const windowHeight = useWindowDimensions().height;
58
+
59
+ const spacer = _spacer ?? windowHeight;
60
+ const topBackgroundColor = topColor ?? DEFAULT_BACKGROUND_COLOR;
61
+ const bottomBackgroundColor = bottomColor ?? DEFAULT_BACKGROUND_COLOR;
62
+
63
+ return (
64
+ <>
65
+ <HiddenView
66
+ style={{
67
+ height: spacer,
68
+ top: -spacer,
69
+ backgroundColor: topBackgroundColor,
70
+ }}
71
+ />
72
+ <HiddenView
73
+ style={{
74
+ height: spacer,
75
+ /** Reduces by the CTA GradientHeight area. */
76
+ bottom: -spacer + GRADIENT_HEIGHT,
77
+ backgroundColor: bottomBackgroundColor,
78
+ }}
79
+ />
80
+ </>
81
+ );
82
+ }
83
+
84
+ function HiddenView({ style, pointerEvents = 'none', ...props }: ComponentProps<typeof View>) {
85
+ return (
86
+ <View
87
+ pointerEvents={pointerEvents}
88
+ style={[{ position: 'absolute', width: '100%', zIndex: -1, left: 0, right: 0 }, style]}
89
+ {...props}
90
+ />
91
+ );
92
+ }
93
+
94
+ /**
95
+ * FIXME:
96
+ * Gradient value for BottomCTA. Set as a fixed value to avoid peerDeps.
97
+ */
98
+ const GRADIENT_HEIGHT = 37;
99
+ const DEFAULT_BACKGROUND_COLOR = '#ffffff';
@@ -0,0 +1 @@
1
+ export * from './ScrollViewInertialBackground';
@@ -0,0 +1,31 @@
1
+ /* eslint-disable @typescript-eslint/naming-convention */
2
+ /* eslint-disable no-var */
3
+ import type { ComponentType } from 'react';
4
+
5
+ export interface GraniteGlobal {
6
+ shared: {
7
+ buildNumber: string;
8
+ };
9
+ app: {
10
+ name: string;
11
+ scheme: string;
12
+ buildNumber: string;
13
+ };
14
+ }
15
+
16
+ declare global {
17
+ // @internal
18
+ var __SPLIT_CHUNK_ENABLED__: boolean;
19
+
20
+ // @internal
21
+ var __granite_require__: (id: string) => any;
22
+
23
+ // @internal
24
+ var __granite: GraniteGlobal;
25
+
26
+ // @internal
27
+ var Page: ComponentType<any>;
28
+
29
+ var window: { __granite: GraniteGlobal };
30
+ var global: { __granite: GraniteGlobal };
31
+ }
@@ -0,0 +1 @@
1
+ export * from './useBackEvent';
@@ -0,0 +1,260 @@
1
+ import { createContext, ReactNode, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
2
+ import { useVisibility } from '../visibility';
3
+
4
+ export interface BackEventControls {
5
+ addEventListener: (...handlers: Array<() => void>) => void;
6
+ removeEventListener: (...handlers: Array<() => void>) => void;
7
+ }
8
+
9
+ interface PrivateBackEventControls extends BackEventControls {
10
+ handlersRef: Set<() => void>;
11
+ hasBackEvent: boolean;
12
+ onBack: () => void;
13
+ }
14
+
15
+ const BackEventContext = createContext<PrivateBackEventControls | null>(null);
16
+
17
+ /**
18
+ * @component
19
+ * @name BackEventProvider
20
+ * @description
21
+ * A component that provides a `Context` for handling back events within the app. Using this component, child components can subscribe to and manage back events. Placing `BackEventProvider` at the top of the tree allows all back events to be handled centrally.
22
+ *
23
+ * @param {ReactNode} children Child components that will control back events.
24
+ * @param {BackEventControls} backEvent Control object for handling back events. You can register back events using the `addEventListener` method and remove registered back events using the `removeEventListener` method.
25
+ * @returns {JSX.Element} A component that can handle back events.
26
+ *
27
+ * By adding `BackEventProvider` as shown below, child components can handle back events.
28
+ *
29
+ * @example
30
+ * ```jsx
31
+ * const backEventControls = {
32
+ * addEventListener: (handler) => { ... },
33
+ * removeEventListener: (handler) => { ... },
34
+ * };
35
+ *
36
+ * <BackEventProvider backEvent={backEventControls}>
37
+ * <App />
38
+ * </BackEventProvider>
39
+ * ```
40
+ */
41
+ export function BackEventProvider({
42
+ children,
43
+ backEvent,
44
+ }: {
45
+ children: ReactNode;
46
+ backEvent: PrivateBackEventControls;
47
+ }) {
48
+ return <BackEventContext.Provider value={backEvent}>{children}</BackEventContext.Provider>;
49
+ }
50
+
51
+ /**
52
+ * @name useBackEventState
53
+ * @description
54
+ * A hook that returns the current back event state and control methods managed by `BackEventProvider`. Using this hook, you can add or remove back event handlers and check the current state.
55
+ *
56
+ * @returns {{ handlers: Array<() => void>, backEvent: BackEventControls, routerProps: { canGoBack: boolean, onBack: () => void } }} Returns an array of back event handlers, the event control method `backEvent`, and `routerProps` that can be used when handling back functionality in the router.
57
+ *
58
+ * Using this Hook, you can add or remove back event handlers and integrate with the router.
59
+ *
60
+ * @example
61
+ * ```javascript
62
+ * const { backEvent, routerProps } = useBackEventState();
63
+ * // Handle back events in the router through routerProps
64
+ * backEvent.addEventListener(() => {
65
+ * console.log('Back event triggered');
66
+ * });
67
+ *
68
+ * return (
69
+ * <BackEventProvider backEvent={backEvent}>
70
+ * <GraniteRouter {...routerProps} context={context} prefix={'scheme://testbench'} />
71
+ * </BackEventProvider>
72
+ * );
73
+ *
74
+ * ```
75
+ */
76
+ export function useBackEventState() {
77
+ const handlersRef = useRef<Set<() => void>>(new Set()).current;
78
+ const [hasBackEvent, setHasBackEvent] = useState(false);
79
+
80
+ const addEventListener = useCallback(
81
+ (...handlers: Array<() => void>) => {
82
+ for (const handler of handlers) {
83
+ handlersRef.add(handler);
84
+ }
85
+
86
+ if (handlersRef.size > 0) {
87
+ setHasBackEvent(true);
88
+ }
89
+ },
90
+ [handlersRef]
91
+ );
92
+
93
+ const removeEventListener = useCallback(
94
+ (...handlers: Array<() => void>) => {
95
+ for (const handler of handlers) {
96
+ handlersRef.delete(handler);
97
+ }
98
+
99
+ if (handlersRef.size === 0) {
100
+ setHasBackEvent(false);
101
+ }
102
+ },
103
+ [handlersRef]
104
+ );
105
+
106
+ const backEvent = useMemo((): PrivateBackEventControls => {
107
+ return {
108
+ addEventListener,
109
+ removeEventListener,
110
+ handlersRef,
111
+ hasBackEvent,
112
+ onBack: () => {
113
+ handlersRef.forEach((handler) => handler());
114
+ },
115
+ };
116
+ }, [addEventListener, handlersRef, hasBackEvent, removeEventListener]);
117
+
118
+ return backEvent;
119
+ }
120
+
121
+ /**
122
+ * @public
123
+ * @category Screen Control
124
+ * @name useBackEvent
125
+ * @description
126
+ * A Hook that returns a controller object for registering and removing back events. Using this Hook, you can handle back events only when a specific component is active.
127
+ * Use `addEventListener` to register back events and `removeEventListener` to remove them.
128
+ * Registered back events are only active when the user is viewing the screen. The condition for viewing the screen is determined using [useVisibility](/en/reference/react-native/Screen%20Control/useVisibility).
129
+ *
130
+ * Using this Hook, you can define logic to handle back events in specific components.
131
+ *
132
+ * @returns {BackEventControls} An object that can control back events. This object includes the `addEventListener` method for registering events and the `removeEventListener` method for removing them.
133
+ *
134
+ * @throws {Error} Throws an error if this hook is not used within a `BackEventProvider`.
135
+ *
136
+ * @example
137
+ *
138
+ * ### Example of Registering and Removing Back Events
139
+ *
140
+ * - **When the "Add BackEvent" button is pressed, a back event is registered.** After that, pressing the back button shows an alert with "back" and prevents actual navigation.
141
+ * - **When the "Remove BackEvent" button is pressed, the registered event is removed.** After that, pressing the back button navigates back normally as per default behavior.
142
+ *
143
+ * ```tsx
144
+ * import { useEffect, useState } from 'react';
145
+ * import { Alert, Button, View } from 'react-native';
146
+ * import { useBackEvent } from '@granite-js/react-native';
147
+ *
148
+ * export function UseBackEventExample() {
149
+ * const backEvent = useBackEvent();
150
+ *
151
+ * const [handler, setHandler] = useState<{ callback: () => void } | undefined>(undefined);
152
+ *
153
+ * useEffect(() => {
154
+ * const callback = handler?.callback;
155
+ *
156
+ * if (callback != null) {
157
+ * backEvent.addEventListener(callback);
158
+ *
159
+ * return () => {
160
+ * backEvent.removeEventListener(callback);
161
+ * };
162
+ * }
163
+ *
164
+ * return;
165
+ * }, [backEvent, handler]);
166
+ *
167
+ * return (
168
+ * <View>
169
+ * <Button
170
+ * title="Add BackEvent"
171
+ * onPress={() => {
172
+ * setHandler({ callback: () => Alert.alert('back') });
173
+ * }}
174
+ * />
175
+ * <Button
176
+ * title="Remove BackEvent"
177
+ * onPress={() => {
178
+ * setHandler(undefined);
179
+ * }}
180
+ * />
181
+ * </View>
182
+ * );
183
+ * }
184
+ * ```
185
+ */
186
+ export function useBackEvent() {
187
+ const context = useContext(BackEventContext);
188
+ const handlersRef = useRef<Set<() => void>>(new Set()).current;
189
+
190
+ const isVisible = useVisibility();
191
+
192
+ if (context == null) {
193
+ throw new Error('useBackEvent must be used within a BackEventProvider');
194
+ }
195
+
196
+ const contextAddEventListener = context.addEventListener;
197
+ const contextRemoveEventListener = context.removeEventListener;
198
+
199
+ const addEventListener = useCallback(
200
+ (...handlers: Array<() => void>) => {
201
+ for (const handler of handlers) {
202
+ handlersRef.add(handler);
203
+ contextAddEventListener(handler);
204
+ }
205
+ },
206
+ [contextAddEventListener, handlersRef]
207
+ );
208
+
209
+ const removeEventListener = useCallback(
210
+ (...handlers: Array<() => void>) => {
211
+ for (const handler of handlers) {
212
+ handlersRef.delete(handler);
213
+ contextRemoveEventListener(handler);
214
+ }
215
+ },
216
+ [contextRemoveEventListener, handlersRef]
217
+ );
218
+
219
+ /**
220
+ * Events must be removed when navigating to another page.
221
+ * If events are not removed, interference will occur.
222
+ */
223
+ useEffect(() => {
224
+ if (!isVisible) {
225
+ return;
226
+ }
227
+
228
+ /** Re-register handlers stored locally. */
229
+ for (const handler of handlersRef) {
230
+ contextAddEventListener(handler);
231
+ }
232
+
233
+ return () => {
234
+ for (const handler of handlersRef) {
235
+ contextRemoveEventListener(handler);
236
+ }
237
+ };
238
+ }, [contextAddEventListener, contextRemoveEventListener, handlersRef, isVisible]);
239
+
240
+ const backEvent = useMemo((): BackEventControls => {
241
+ return {
242
+ addEventListener,
243
+ removeEventListener,
244
+ };
245
+ }, [addEventListener, removeEventListener]);
246
+
247
+ return backEvent;
248
+ }
249
+
250
+ export function useBackEventContext() {
251
+ const context = useContext(BackEventContext);
252
+ if (context == null) {
253
+ throw new Error('useBackEvent must be used within a BackEventProvider');
254
+ }
255
+
256
+ return {
257
+ onGoBack: context.onBack,
258
+ hasBackEvent: context.hasBackEvent,
259
+ };
260
+ }
@@ -0,0 +1 @@
1
+ export const noop = () => {};
@@ -0,0 +1,16 @@
1
+ import { useCallback, useEffect, useRef } from 'react';
2
+
3
+ export function usePreservedCallback<Callback extends (...args: any[]) => any>(callback: Callback) {
4
+ const callbackRef = useRef<Callback>(callback);
5
+
6
+ useEffect(() => {
7
+ callbackRef.current = callback;
8
+ }, [callback]);
9
+
10
+ return useCallback(
11
+ (...args: any[]) => {
12
+ return callbackRef.current(...args);
13
+ },
14
+ [callbackRef]
15
+ ) as Callback;
16
+ }
@@ -0,0 +1,36 @@
1
+ import { ReactElement, ReactNode } from 'react';
2
+ import { AppStateProvider } from './useIsAppForeground';
3
+ import { VisibilityChangedProvider } from './useVisibilityChanged';
4
+
5
+ interface Props {
6
+ isVisible: boolean;
7
+ children: ReactNode;
8
+ }
9
+
10
+ /**
11
+ * @name VisibilityProvider
12
+ * @description
13
+ * A Provider that manages whether a ReactNative view is currently in the foreground state.
14
+ * @param {boolean} isVisible - Whether the app is in the foreground state.
15
+ * @param {ReactNode | undefined} children - Child components that observe `AppState`.
16
+ * @returns {ReactElement} - A React Provider component wrapped with `VisibilityChangedProvider`.
17
+ * @example
18
+ * ```typescript
19
+ *
20
+ * function App() {
21
+ * return (
22
+ * <VisibilityProvider isVisible={true}>
23
+ * <MyApp />
24
+ * </VisibilityProvider>
25
+ * );
26
+ * }
27
+ *
28
+ * ```
29
+ */
30
+ export function VisibilityProvider({ isVisible, children }: Props): ReactElement {
31
+ return (
32
+ <VisibilityChangedProvider isVisible={isVisible}>
33
+ <AppStateProvider>{children}</AppStateProvider>
34
+ </VisibilityChangedProvider>
35
+ );
36
+ }
@@ -0,0 +1,6 @@
1
+ export * from './react-navigation';
2
+ export * from './useIsAppForeground';
3
+ export * from './useVisibility';
4
+ export * from './useVisibilityChanged';
5
+ export * from './useVisibilityChange';
6
+ export * from './VisibilityProvider';
@@ -0,0 +1,2 @@
1
+ export * from './useIsFocusedSafely';
2
+ export * from './useNavigationSafely';
@@ -0,0 +1,58 @@
1
+ import { useDebugValue, useEffect, useState } from 'react';
2
+ import { useNavigationSafely } from './useNavigationSafely';
3
+
4
+ /**
5
+ * @name useIsFocusedSafely
6
+ * @category Hooks
7
+ * @kind function
8
+ * @link https://github.com/react-navigation/react-navigation/blob/%40react-navigation/native%406.1.18/packages/core/src/useIsFocused.tsx
9
+ * @description
10
+ * Returns whether the current screen is in focus.
11
+ *
12
+ * A Hook that safely uses `useIsFocused` provided by `@react-navigation/native`.
13
+ * This Hook is based on `useIsFocused` from `@react-navigation/native`, but modified to not throw errors when `navigation` or `root` objects are `null` or `undefined`.
14
+ * It ensures that users don't see errors even when the code is used in environments where `@react-navigation/native` is not used.
15
+ *
16
+ * @returns {boolean} - Returns the focus state of the current screen.
17
+ * @example
18
+ * ```typescript
19
+ * const isFocused = useIsFocusedSafely();
20
+ * console.log(isFocused); // true or false
21
+ * ```
22
+ */
23
+ export function useIsFocusedSafely(): boolean {
24
+ const navigation = useNavigationSafely();
25
+ const isNavigationFocused = () => navigation?.isFocused() ?? true;
26
+
27
+ const [isFocused, setIsFocused] = useState(isNavigationFocused());
28
+
29
+ const valueToReturn = isNavigationFocused();
30
+
31
+ if (isFocused !== valueToReturn) {
32
+ // If the value has changed since the last render, we need to update it.
33
+ // This could happen if we missed an update from the event listeners during re-render.
34
+ // React will process this update immediately, so the old subscription value won't be committed.
35
+ // It is still nice to avoid returning a mismatched value though, so let's override the return value.
36
+ // This is the same logic as in https://github.com/facebook/react/tree/master/packages/use-subscription
37
+ setIsFocused(valueToReturn);
38
+ }
39
+
40
+ useEffect(() => {
41
+ if (navigation == null) {
42
+ return;
43
+ }
44
+
45
+ const unsubscribeFocus = navigation.addListener('focus', () => setIsFocused(true));
46
+
47
+ const unsubscribeBlur = navigation.addListener('blur', () => setIsFocused(false));
48
+
49
+ return () => {
50
+ unsubscribeFocus();
51
+ unsubscribeBlur();
52
+ };
53
+ }, [navigation]);
54
+
55
+ useDebugValue(valueToReturn);
56
+
57
+ return valueToReturn;
58
+ }
@@ -0,0 +1,30 @@
1
+ import {
2
+ NavigationContainerRefContext,
3
+ NavigationContext,
4
+ NavigationProp,
5
+ } from '@granite-js/native/@react-navigation/native';
6
+ import { useContext } from 'react';
7
+
8
+ /**
9
+ * @name useNavigationSafely
10
+ * @category Hooks
11
+ * @kind function
12
+ * @link https://github.com/react-navigation/react-navigation/blob/d1cd940d9a3fb46bec0eb6bf4f8023789fc93b28/packages/core/src/useNavigation.tsx#L13
13
+ * @description
14
+ * A Hook that safely uses [`useNavigation`](https://reactnavigation.org/docs/use-navigation/) from `@react-navigation/native` for managing navigation between screens.
15
+ * This Hook is based on `useNavigation` provided by `@react-navigation/native`, but operates more safely by not throwing errors when `navigation` or `root` objects are `null` or `undefined`.
16
+ * It also ensures the code runs safely in environments where `@react-navigation/native` is not used, preventing users from experiencing errors.
17
+ *
18
+ * @returns {NavigationProp<ReactNavigation.RootParamList> | undefined} - The screen is in a visible state.
19
+ * @example
20
+ * ```typescript
21
+ * const navigation = useNavigationSafely();
22
+ * const isNavigationFocused = () => navigation?.isFocused() ?? true;
23
+ * ```
24
+ */
25
+ export function useNavigationSafely(): NavigationProp<ReactNavigation.RootParamList> | undefined {
26
+ const root = useContext(NavigationContainerRefContext);
27
+ const navigation = useContext(NavigationContext);
28
+
29
+ return (navigation ?? root ?? undefined) as NavigationProp<ReactNavigation.RootParamList> | undefined;
30
+ }