@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,62 @@
1
+ import { ComponentProps, ReactElement } from 'react';
2
+ import { Animated, View } from 'react-native';
3
+ import { useKeyboardAnimatedHeight } from './useKeyboardAnimatedHeight';
4
+
5
+ /**
6
+ * @public
7
+ * @category UI
8
+ * @name KeyboardAboveView
9
+ * @kind function
10
+ * @description
11
+ * A component that automatically lifts child components above the keyboard when it appears on the screen.
12
+ * It's useful when you want to keep elements like a "Send" button fixed above the keyboard during text input.
13
+ *
14
+ * @param {StyleProp<ViewStyle>} [props.style] - Additional styles can be applied. For example, you can set background color or size.
15
+ * @param {ReactNode} [props.children] - Components to be displayed above the keyboard when it appears. For example, you can include buttons, text input fields, etc.
16
+ * @returns {ReactElement} - Returns an [`Animated.View`](https://reactnative.dev/docs/animated#createanimatedcomponent) that is adjusted above the keyboard when it appears.
17
+ * @example
18
+ *
19
+ * ### Lifting elements above the keyboard
20
+ *
21
+ * ```tsx
22
+ * import { ScrollView, TextInput, View, Text } from 'react-native';
23
+ * import { KeyboardAboveView } from '@granite-js/react-native';
24
+ *
25
+ * export function KeyboardAboveViewExample() {
26
+ * return (
27
+ * <>
28
+ * <ScrollView>
29
+ * <TextInput placeholder="placeholder" />
30
+ * </ScrollView>
31
+ *
32
+ * <KeyboardAboveView>
33
+ * <View style={{ width: '100%', height: 50, backgroundColor: 'yellow' }}>
34
+ * <Text>Above the keyboard</Text>
35
+ * </View>
36
+ * </KeyboardAboveView>
37
+ * </>
38
+ * );
39
+ * }
40
+ * ```
41
+ */
42
+ export function KeyboardAboveView({ style, children, ...props }: ComponentProps<typeof View>): ReactElement {
43
+ const keyboardHeight = useKeyboardAnimatedHeight();
44
+
45
+ return (
46
+ <Animated.View
47
+ style={[
48
+ style,
49
+ {
50
+ transform: [
51
+ {
52
+ translateY: Animated.subtract(0, keyboardHeight),
53
+ },
54
+ ],
55
+ },
56
+ ]}
57
+ {...props}
58
+ >
59
+ {children}
60
+ </Animated.View>
61
+ );
62
+ }
@@ -0,0 +1 @@
1
+ export { KeyboardAboveView } from './KeyboardAboveView';
@@ -0,0 +1,81 @@
1
+ import { useEffect, useRef } from 'react';
2
+ import { Animated, Keyboard, Platform } from 'react-native';
3
+
4
+ function getInitialKeyboardHeight() {
5
+ if (Platform.OS !== 'ios') {
6
+ return 0;
7
+ }
8
+
9
+ /**
10
+ * Branch handling for React Native 0.68.0 version where `metrics()` does not exist
11
+ */
12
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
13
+ // @ts-ignore
14
+ if (typeof Keyboard?.metrics === 'function') {
15
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
16
+ // @ts-ignore
17
+ return Keyboard.metrics()?.height ?? 0;
18
+ } else {
19
+ return 0;
20
+ }
21
+ }
22
+
23
+ /**
24
+ * @category Hooks
25
+ * @name useKeyboardAnimatedHeight
26
+ * @description
27
+ * A Hook that returns an animatable value (`Animated.Value`) representing the keyboard height changes when the keyboard appears or disappears. You can smoothly animate UI elements according to the keyboard height as it rises or falls.
28
+ *
29
+ * This Hook is primarily used on iOS. On Android, it does not detect keyboard height changes and always returns an `Animated.Value` with an initial value of `0`. In other words, animations are not applied in the Android environment.
30
+ *
31
+ * @returns {Animated.Value} - An animation value representing the keyboard height.
32
+ * @example
33
+ * ```typescript
34
+ * const keyboardHeight = useKeyboardAnimatedHeight();
35
+ *
36
+ * <Animated.View style={{ marginBottom: keyboardHeight }}>
37
+ * {children}
38
+ * </Animated.View>
39
+ * ```
40
+ */
41
+ export function useKeyboardAnimatedHeight(): Animated.Value {
42
+ const keyboardHeight = useRef(new Animated.Value(getInitialKeyboardHeight())).current;
43
+
44
+ useEffect(() => {
45
+ if (Platform.OS === 'ios') {
46
+ const willShowSubscription = Keyboard.addListener('keyboardWillShow', (event) => {
47
+ const height = event.endCoordinates.height;
48
+
49
+ Animated.spring(keyboardHeight, {
50
+ toValue: height,
51
+ useNativeDriver: true,
52
+ ...spring.quick,
53
+ }).start();
54
+ });
55
+
56
+ const willHideSubscription = Keyboard.addListener('keyboardWillHide', () => {
57
+ Animated.spring(keyboardHeight, {
58
+ toValue: 0,
59
+ useNativeDriver: true,
60
+ ...spring.quick,
61
+ }).start();
62
+ });
63
+ return () => {
64
+ willShowSubscription.remove();
65
+ willHideSubscription.remove();
66
+ };
67
+ } else {
68
+ return;
69
+ }
70
+ }, [keyboardHeight]);
71
+
72
+ return keyboardHeight;
73
+ }
74
+
75
+ const spring = {
76
+ quick: {
77
+ stiffness: 800,
78
+ damping: 55,
79
+ mass: 1,
80
+ },
81
+ };
@@ -0,0 +1,3 @@
1
+ import { VisibilityChangedEventEmitter } from './visibilityChanged';
2
+
3
+ export type EventEmitters = VisibilityChangedEventEmitter;
@@ -0,0 +1,4 @@
1
+ export interface EventEmitterSchema<K extends string, P extends unknown[]> {
2
+ name: K;
3
+ params: P;
4
+ }
@@ -0,0 +1,11 @@
1
+ import { EventEmitterSchema } from './types';
2
+
3
+ /**
4
+ * @name VisibilityChangedEventEmitter
5
+ * @kind typedef
6
+ * @platform iOS
7
+ * @description
8
+ * Emits an event when the visibility state of the app changes.
9
+ * Only available on iOS.
10
+ */
11
+ export type VisibilityChangedEventEmitter = EventEmitterSchema<'visibilityChanged', [boolean]>;
@@ -0,0 +1 @@
1
+ export * from './nativeEventEmitter';
@@ -0,0 +1,18 @@
1
+ import { EmitterSubscription, NativeEventEmitter } from 'react-native';
2
+ import { EventEmitters } from './eventEmitters';
3
+ import { GraniteCoreModule } from '../native-modules';
4
+ import { EventEmitterSchema } from './eventEmitters/types';
5
+
6
+ type MapOf<T> = T extends EventEmitterSchema<infer K, any> ? { [key in K]: T } : never;
7
+ type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
8
+ type EventEmittersMap = UnionToIntersection<MapOf<EventEmitters>>;
9
+ type EventKeys = keyof EventEmittersMap;
10
+ type ParamOf<K extends EventKeys> = EventEmittersMap[K]['params'];
11
+ interface EventEmitter {
12
+ addListener<Event extends EventKeys>(
13
+ event: Event,
14
+ callback: (...params: ParamOf<Event>) => void
15
+ ): EmitterSubscription;
16
+ }
17
+
18
+ export const nativeEventEmitter = new NativeEventEmitter(GraniteCoreModule) as unknown as EventEmitter;
@@ -0,0 +1,9 @@
1
+ import { TurboModule, TurboModuleRegistry } from 'react-native';
2
+
3
+ interface GraniteCoreModule extends TurboModule {
4
+ addListener: (eventType: string) => void;
5
+ removeListeners: (count: number) => void;
6
+ importLazy: () => Promise<void>;
7
+ }
8
+
9
+ export const GraniteCoreModule = TurboModuleRegistry.getEnforcing<GraniteCoreModule>('GraniteCoreModule');
@@ -0,0 +1,3 @@
1
+ /** Bridges API */
2
+ export * from './natives';
3
+ export { GraniteCoreModule } from './core/GraniteCoreModule';
@@ -0,0 +1,8 @@
1
+ import { TurboModule, TurboModuleRegistry } from 'react-native';
2
+
3
+ interface GraniteModuleSpec extends TurboModule {
4
+ closeView: () => void;
5
+ schemeUri: string;
6
+ }
7
+
8
+ export const GraniteModule = TurboModuleRegistry.getEnforcing<GraniteModuleSpec>('GraniteModule');
@@ -0,0 +1,25 @@
1
+ import { GraniteModule } from './GraniteModule';
2
+
3
+ /**
4
+ * @public
5
+ * @category Screen Control
6
+ * @kind function
7
+ * @name closeView
8
+ * @description Function that closes the current screen. It can be used when you want to exit a service by pressing a "Close" button.
9
+ * @returns {Promise<void>}
10
+ *
11
+ * @example
12
+ * ### Close screen with close button
13
+ *
14
+ * ```tsx
15
+ * import { Button } from 'react-native';
16
+ * import { closeView } from '@granite-js/react-native';
17
+ *
18
+ * function CloseButton() {
19
+ * return <Button title="Close" onPress={closeView} />;
20
+ * }
21
+ * ```
22
+ */
23
+ export async function closeView() {
24
+ return GraniteModule.closeView();
25
+ }
@@ -0,0 +1,27 @@
1
+ import { GraniteModule } from './GraniteModule';
2
+
3
+ /**
4
+ * @public
5
+ * @name getSchemeUri
6
+ * @category Environment Check
7
+ * @kind function
8
+ * @description Returns the scheme value when first entering the screen. URI changes due to page navigation are not reflected.
9
+ * @returns {string} Returns the scheme value when first entering the screen.
10
+ *
11
+ * @example
12
+ * ### Get initial scheme value
13
+ *
14
+ * ```tsx
15
+ * import { getSchemeUri } from '@granite-js/react-native';
16
+ * import { Text } from 'react-native';
17
+ *
18
+ * function MyPage() {
19
+ * const schemeUri = getSchemeUri();
20
+ *
21
+ * return <Text>Initial scheme value: {schemeUri}</Text>
22
+ * }
23
+ * ```
24
+ */
25
+ export function getSchemeUri() {
26
+ return GraniteModule.schemeUri;
27
+ }
@@ -0,0 +1,3 @@
1
+ export * from './closeView';
2
+ export * from './getSchemeUri';
3
+ export * from './openURL';
@@ -0,0 +1,40 @@
1
+ import { Linking } from 'react-native';
2
+
3
+ /**
4
+ * @public
5
+ * @kind function
6
+ * @category Screen Navigation
7
+ *
8
+ * @name openURL
9
+ * @signature
10
+ * ```typescript
11
+ * function openURL(url: string): Promise<any>;
12
+ * ```
13
+ *
14
+ * @description
15
+ * Opens the specified URL in the device's default browser or related app.
16
+ * This function uses the [`Linking.openURL`](https://reactnative.dev/docs/0.72/linking#openurl) method from `react-native` to open the URL.
17
+ *
18
+ * @param {string} url URL address to open
19
+ * @returns {Promise<any>} Promise that resolves when the URL is successfully opened
20
+ *
21
+ * @example
22
+ *
23
+ * ### Open external URL
24
+ *
25
+ * ```tsx
26
+ * import { openURL } from '@granite-js/react-native';
27
+ * import { Button } from 'react-native';
28
+ *
29
+ * function Page() {
30
+ * const handlePress = () => {
31
+ * openURL('https://google.com');
32
+ * };
33
+ *
34
+ * return <Button title="Open Google Website" onPress={handlePress} />;
35
+ * }
36
+ * ```
37
+ */
38
+ export function openURL(url: string): Promise<any> {
39
+ return Linking.openURL(url);
40
+ }
@@ -0,0 +1 @@
1
+ export { useWaitForReturnNavigator } from './useWaitForReturnNavigator';
@@ -0,0 +1,75 @@
1
+ import { useNavigation } from '@granite-js/native/@react-navigation/native';
2
+ import { NativeStackNavigationProp } from '@granite-js/native/@react-navigation/native-stack';
3
+ import { useCallback, useRef } from 'react';
4
+ import { useVisibilityChange, type VisibilityState } from '../visibility';
5
+
6
+ /**
7
+ * @public
8
+ * @category Screen Control
9
+ * @name useWaitForReturnNavigator
10
+ * @description
11
+ * A Hook that helps execute the next code synchronously when returning from a screen transition.
12
+ * Screen navigation uses [@react-navigation/native `useNavigation`'s `navigate`](https://reactnavigation.org/docs/6.x/navigation-prop#navigate).
13
+ *
14
+ * For example, it can be used when you want to log that a user has navigated to another screen and returned.
15
+ *
16
+ * @example
17
+ * ### Example of code execution when returning from screen navigation
18
+ *
19
+ * When the **"Navigate"** button is pressed, it navigates to another screen, and logs are created when returning.
20
+ *
21
+ * ```tsx
22
+ * import { Button } from 'react-native';
23
+ * import { useWaitForReturnNavigator } from '@granite-js/react-native';
24
+ *
25
+ * export function UseWaitForReturnNavigator() {
26
+ * const navigate = useWaitForReturnNavigator();
27
+ *
28
+ * return (
29
+ * <>
30
+ * <Button
31
+ * title="Navigate"
32
+ * onPress={async () => {
33
+ * console.log(1);
34
+ * await navigate('/examples/use-visibility');
35
+ * // This code executes when returning to the screen
36
+ * console.log(2);
37
+ * }}
38
+ * />
39
+ * </>
40
+ * );
41
+ * }
42
+ * ```
43
+ */
44
+
45
+ export function useWaitForReturnNavigator<T extends Record<string, object | undefined>>() {
46
+ const callbacks = useRef<Array<() => void>>([]).current;
47
+ const navigation = useNavigation<NativeStackNavigationProp<T>>();
48
+
49
+ const startNavigating = useCallback(
50
+ <RouteName extends keyof T>(route: RouteName, params?: T[RouteName]): Promise<void> => {
51
+ return new Promise<void>((resolve) => {
52
+ callbacks.push(resolve);
53
+ navigation.navigate(route as any, params as any);
54
+ });
55
+ },
56
+ [callbacks, navigation]
57
+ );
58
+
59
+ const handleVisibilityChange = useCallback(
60
+ (state: VisibilityState) => {
61
+ if (state === 'visible' && callbacks.length > 0) {
62
+ for (const callback of callbacks) {
63
+ callback();
64
+ }
65
+
66
+ callbacks.splice(0, callbacks.length);
67
+ }
68
+ },
69
+ [callbacks]
70
+ );
71
+
72
+ useVisibilityChange(handleVisibilityChange);
73
+
74
+ return startNavigating;
75
+ }
@@ -0,0 +1,7 @@
1
+ import { setup as setupSymbolAsyncIterator } from './symbol-asynciterator';
2
+ import { setup as setupURLPolyfill } from './url';
3
+
4
+ export function setup() {
5
+ setupSymbolAsyncIterator();
6
+ setupURLPolyfill();
7
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Polyfill for @swc/helpers build compatibility
3
+ *
4
+ * @see https://github.com/swc-project/swc/blob/v1.4.15/packages/helpers/esm/_async_iterator.js#L3
5
+ *
6
+ * - babel: No runtime issues after build as there is a fallback for `Symbol.asyncIterator`
7
+ * - swc: No fallback for `Symbol.asyncIterator`, so it needs to be defined in advance
8
+ */
9
+ export function setup() {
10
+ if (typeof Symbol !== 'undefined' && !Symbol.asyncIterator) {
11
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
12
+ // @ts-ignore
13
+ Symbol.asyncIterator = Symbol.for('@@asyncIterator');
14
+ }
15
+ }
@@ -0,0 +1 @@
1
+ export { setupURLPolyfill as setup } from 'react-native-url-polyfill';
@@ -0,0 +1,164 @@
1
+ import {
2
+ createNavigationContainerRef,
3
+ NavigationContainer,
4
+ NavigationContainerRefWithCurrent,
5
+ ParamListBase,
6
+ RouteProp,
7
+ } from '@granite-js/native/@react-navigation/native';
8
+ import { NativeStackNavigationOptions } from '@granite-js/native/@react-navigation/native-stack';
9
+ import { ComponentProps, ComponentType, Fragment, PropsWithChildren, ReactElement, useCallback, useMemo } from 'react';
10
+ import { InitialProps } from '..';
11
+ import { CanGoBackGuard } from './components/CanGoBackGuard';
12
+ import { RouterBackButton, RouterBackButtonProps } from './components/RouterBackButton';
13
+ import { StackNavigator } from './components/StackNavigator';
14
+ import { useInitialRouteName } from './hooks/useInitialRouteName';
15
+ import { useRouterControls } from './hooks/useRouterControls';
16
+ import { RequireContext } from './types';
17
+ import { BASE_STACK_NAVIGATOR_STYLE } from './types/screen-option';
18
+
19
+ /**
20
+ * @internal
21
+ */
22
+ export interface InternalRouterProps {
23
+ /**
24
+ * @name context
25
+ * @description
26
+ * Object containing screen information used for file-based routing.
27
+ */
28
+ context: RequireContext;
29
+ /**
30
+ * @name prefix
31
+ * @description
32
+ * Prefix to use when the scheme is executed. For example, to enter 'scheme://my-service/intro', you need to set 'scheme://my-service' as the prefix.
33
+ */
34
+ prefix: string;
35
+ /**
36
+ * @name canGoBack
37
+ * @description
38
+ * Whether navigation back is possible. Default is true, and when set to true, you can use the back gesture or back button from @react-navigation/native.
39
+ * @default true
40
+ */
41
+ canGoBack?: boolean;
42
+ /**
43
+ * @name onBack
44
+ * @description
45
+ * Callback function called when navigating back.
46
+ */
47
+ onBack?: () => void;
48
+ /**
49
+ * @name container
50
+ * @description
51
+ * Container component that wraps the Navigator from @react-navigation/native.
52
+ */
53
+ container: ComponentType<PropsWithChildren<InitialProps>>;
54
+ initialProps: InitialProps;
55
+ }
56
+
57
+ export type RouterProps = StackNavigatorProps & NavigationContainerProps;
58
+
59
+ interface StackNavigatorProps {
60
+ /**
61
+ * @name navigationContainerRef
62
+ * @description
63
+ * You can create and pass a NavigationContainerRef from @react-navigation/native externally. This allows external control of the router.
64
+ */
65
+ navigationContainerRef?: NavigationContainerRefWithCurrent<any>;
66
+ /**
67
+ * @name defaultScreenOption
68
+ * @description
69
+ * Default options for screens. You can set options to be applied commonly to screens, such as title or headerStyle.
70
+ */
71
+ defaultScreenOption?:
72
+ | NativeStackNavigationOptions
73
+ | ((props: { route: RouteProp<ParamListBase>; navigation: any }) => NativeStackNavigationOptions);
74
+ /**
75
+ * @name screenContainer
76
+ * @description
77
+ * Container component that wraps each Screen component.
78
+ */
79
+ screenContainer?: ComponentType<PropsWithChildren<any>>;
80
+ }
81
+
82
+ type NavigationContainerProps = Pick<
83
+ ComponentProps<typeof NavigationContainer>,
84
+ 'ref' | 'documentTitle' | 'fallback' | 'onReady' | 'onUnhandledAction' | 'onStateChange'
85
+ >;
86
+
87
+ /**
88
+ * @category Components
89
+ * @kind function
90
+ * @name Router
91
+ * @description
92
+ * Router component for page navigation in React Native environment.
93
+ * Automatically assigns appropriate paths to screens based on file naming rules in pages/*.
94
+ * Using this component, you can manage screens in a way similar to Next.js's file-based routing.
95
+ *
96
+ * @param {string} prefix Prefix to use when the scheme is executed. For example, to enter 'scheme://my-service/intro', you need to set 'scheme://my-service' as the prefix.
97
+ * @param {RequireContext} context Object containing information about screens for file-based routing.
98
+ * @param {NavigationContainerRefWithCurrent<any>} [navigationContainerRef] You can create and pass a NavigationContainerRef from @react-navigation/native externally. This allows external control of the router.
99
+ * @param {NativeStackNavigationOptions | ((props: { route: RouteProp<ParamListBase>; navigation: any }) => NativeStackNavigationOptions)} [defaultScreenOption] Default options for screens. You can set options to be applied commonly to screens, such as title or headerStyle.
100
+ * @param {boolean} [canGoBack=true] Whether navigation back is possible. Default is true, and when set to true, you can use the back gesture or back button from @react-navigation/native.
101
+ * @param {() => void} [onBack] Callback function called when the user presses the back button or uses the back gesture. For example, you can set it to log when the user presses the back button.
102
+ * @param {ComponentType<{ children: ReactNode }>} [container=Fragment] Container component that wraps the Navigator from @react-navigation/native.
103
+ * @param {NavigationContainerProps} [navigationContainerProps] - You can set props to be passed to NavigationContainer from @react-navigation/native.
104
+ *
105
+ * @returns {ReactElement} - Returns the router component.
106
+ * @example
107
+ * ```tsx
108
+ * import { Router } from '@granite-js/react-native';
109
+ * import { context } from '../require.context';
110
+ *
111
+ * function App() {
112
+ * return <Router context={context} prefix={'scheme://testbench'} />;
113
+ * }
114
+ * ```
115
+ */
116
+ export function Router({
117
+ // Internal props
118
+ prefix,
119
+ context,
120
+ canGoBack = true,
121
+ onBack,
122
+ container: Container = Fragment,
123
+ initialProps,
124
+ // Public props (NavigationContainer)
125
+ navigationContainerRef,
126
+ defaultScreenOption,
127
+ screenContainer,
128
+ // Public props (StackNavigator)
129
+ ...navigationContainerProps
130
+ }: InternalRouterProps & RouterProps): ReactElement {
131
+ const initialRouteName = useInitialRouteName(prefix);
132
+ const { Screens, linkingOptions } = useRouterControls({ prefix, context, screenContainer });
133
+
134
+ const ref = useMemo(() => navigationContainerRef ?? createNavigationContainerRef<any>(), [navigationContainerRef]);
135
+
136
+ const headerLeft = useCallback(
137
+ (backButtonProps: Omit<RouterBackButtonProps, 'navigationContainerRef'>) => (
138
+ <RouterBackButton {...backButtonProps} onBack={onBack} canGoBack={canGoBack} navigationContainerRef={ref} />
139
+ ),
140
+ [onBack, canGoBack, ref]
141
+ );
142
+
143
+ const screenOptions = useCallback(
144
+ (screenProps: any) => ({
145
+ ...BASE_STACK_NAVIGATOR_STYLE,
146
+ gestureEnabled: canGoBack,
147
+ headerLeft,
148
+ ...(typeof defaultScreenOption === 'function' ? defaultScreenOption(screenProps) : defaultScreenOption),
149
+ }),
150
+ [canGoBack, defaultScreenOption, headerLeft]
151
+ );
152
+
153
+ return (
154
+ <NavigationContainer ref={ref} {...navigationContainerProps} linking={linkingOptions}>
155
+ <CanGoBackGuard canGoBack={canGoBack} onBack={onBack}>
156
+ <Container {...initialProps}>
157
+ <StackNavigator.Navigator initialRouteName={initialRouteName} screenOptions={screenOptions}>
158
+ {Screens}
159
+ </StackNavigator.Navigator>
160
+ </Container>
161
+ </CanGoBackGuard>
162
+ </NavigationContainer>
163
+ );
164
+ }
@@ -0,0 +1,58 @@
1
+ import { SvgXml } from '@granite-js/native/react-native-svg';
2
+ import { Platform, TouchableOpacity, TouchableOpacityProps, View } from 'react-native';
3
+
4
+ interface BackButtonProps extends TouchableOpacityProps {
5
+ tintColor?: string;
6
+ onPress?: () => void;
7
+ }
8
+
9
+ const DEFAULT_COLOR = '#191f28'; // grey900
10
+ function BackButton({ tintColor, onPress }: BackButtonProps) {
11
+ return <NavbarBackButton onPress={onPress} color={tintColor ?? DEFAULT_COLOR} />;
12
+ }
13
+
14
+ function NavbarBackButton({ onPress, color }: { onPress?: () => void; color?: string }) {
15
+ return (
16
+ <TouchableOpacity
17
+ hitSlop={{ top: 10, right: 10, bottom: 10, left: 10 }}
18
+ style={{ width: 24, height: 24 }}
19
+ onPress={onPress}
20
+ accessible={true}
21
+ accessibilityLabel="Go back"
22
+ accessibilityRole="button"
23
+ >
24
+ <View style={{ width: 24, height: 24 }}>
25
+ <SvgXml
26
+ width={24}
27
+ height={24}
28
+ xml={BACK_BUTTON_XML.replace(/fill="#[0-9a-fA-F]{6}"/g, `fill="${color}"`)}
29
+ style={{
30
+ marginLeft: BACK_BUTTON_MARGIN,
31
+ }}
32
+ />
33
+ </View>
34
+ </TouchableOpacity>
35
+ );
36
+ }
37
+
38
+ const ANDROID_BACK_BUTTON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="line-icon">
39
+ <path fill="#B0B8C1" fill-rule="evenodd" d="M20.966 10.8H6.93l5.451-5.451a1.2 1.2 0 10-1.697-1.697l-7.5 7.5c-.003.002-.004.006-.007.009a1.2 1.2 0 00-.252 1.298c.06.143.145.27.252.38l.007.01 7.5 7.5c.235.234.542.35.848.35a1.2 1.2 0 00.849-2.048L6.931 13.2h14.036a1.2 1.2 0 100-2.4"/>
40
+ </svg>`;
41
+
42
+ const IOS_BACK_BUTTON = `<svg enable-background="new 0 0 24 24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
43
+ <path d="m20.8 20.7c-.3 0-.6-.1-.8-.4l-7.5-7.5c-.5-.5-.5-1.2 0-1.7l7.5-7.5c.5-.5 1.2-.5 1.7 0s.5 1.2 0 1.7l-6.8 6.7 6.7 6.7c.5.5.5 1.2 0 1.7-.2.2-.5.3-.8.3z" fill="#b0b8c1"/>
44
+ </svg>`;
45
+
46
+ const BACK_BUTTON_XML = Platform.select<string>({
47
+ android: ANDROID_BACK_BUTTON,
48
+ ios: IOS_BACK_BUTTON,
49
+ default: IOS_BACK_BUTTON,
50
+ });
51
+
52
+ const BACK_BUTTON_MARGIN = Platform.select<number>({
53
+ android: -4,
54
+ ios: -12,
55
+ default: -12,
56
+ });
57
+
58
+ export { BackButton };