@granite-js/react-native 0.0.0-dev-20250725013859

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 (217) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +24 -0
  3. package/bin/cli.js +3 -0
  4. package/cli.d.ts +1 -0
  5. package/cli.js +4 -0
  6. package/config.d.ts +2 -0
  7. package/config.js +5 -0
  8. package/dist/app/App/index.android.d.ts +2 -0
  9. package/dist/app/App/index.ios.d.ts +6 -0
  10. package/dist/app/AppRoot.d.ts +1 -0
  11. package/dist/app/Granite.d.ts +61 -0
  12. package/dist/app/HostAppRoot.d.ts +1 -0
  13. package/dist/app/index.d.ts +2 -0
  14. package/dist/async-bridges.d.ts +2 -0
  15. package/dist/blur/BlurView.d.ts +78 -0
  16. package/dist/blur/ReactNativeBlurModule.d.ts +6 -0
  17. package/dist/blur/constants.d.ts +1 -0
  18. package/dist/blur/index.d.ts +1 -0
  19. package/dist/constant-bridges.d.ts +1 -0
  20. package/dist/constants.d.ts +1 -0
  21. package/dist/dev-entrypoint/index.d.ts +2 -0
  22. package/dist/event/abstract.d.ts +42 -0
  23. package/dist/event/index.d.ts +2 -0
  24. package/dist/event/useGraniteEvent.d.ts +14 -0
  25. package/dist/impression-area/ImpressionArea.d.ts +231 -0
  26. package/dist/impression-area/index.d.ts +1 -0
  27. package/dist/index.d.ts +21 -0
  28. package/dist/initial-props/InitialProps.d.ts +92 -0
  29. package/dist/initial-props/index.d.ts +1 -0
  30. package/dist/intersection-observer/IOContext.d.ts +10 -0
  31. package/dist/intersection-observer/IOFlatList.d.ts +55 -0
  32. package/dist/intersection-observer/IOManager.d.ts +24 -0
  33. package/dist/intersection-observer/IOScrollView.d.ts +59 -0
  34. package/dist/intersection-observer/InView.d.ts +107 -0
  35. package/dist/intersection-observer/IntersectionObserver.d.ts +67 -0
  36. package/dist/intersection-observer/index.d.ts +8 -0
  37. package/dist/intersection-observer/withIO.d.ts +20 -0
  38. package/dist/jest/index.d.ts +1 -0
  39. package/dist/jest/index.js +32 -0
  40. package/dist/keyboard/KeyboardAboveView.d.ts +40 -0
  41. package/dist/keyboard/index.d.ts +2 -0
  42. package/dist/keyboard/useKeyboardAnimatedHeight.d.ts +20 -0
  43. package/dist/native-event-emitter/eventEmitters/index.d.ts +2 -0
  44. package/dist/native-event-emitter/eventEmitters/types.d.ts +4 -0
  45. package/dist/native-event-emitter/eventEmitters/visibilityChanged.d.ts +10 -0
  46. package/dist/native-event-emitter/index.d.ts +1 -0
  47. package/dist/native-event-emitter/nativeEventEmitter.d.ts +15 -0
  48. package/dist/native-modules/core/GraniteCoreModule.d.ts +8 -0
  49. package/dist/native-modules/index.d.ts +3 -0
  50. package/dist/native-modules/natives/GraniteModule.d.ts +7 -0
  51. package/dist/native-modules/natives/closeView.d.ts +21 -0
  52. package/dist/native-modules/natives/getSchemeUri.d.ts +23 -0
  53. package/dist/native-modules/natives/index.d.ts +3 -0
  54. package/dist/native-modules/natives/openURL.d.ts +36 -0
  55. package/dist/react/index.d.ts +1 -0
  56. package/dist/react/useWaitForReturnNavigator.d.ts +39 -0
  57. package/dist/rn-polyfills/index.d.ts +1 -0
  58. package/dist/rn-polyfills/symbol-asynciterator/index.d.ts +9 -0
  59. package/dist/rn-polyfills/url/index.d.ts +1 -0
  60. package/dist/router/Router.d.ts +59 -0
  61. package/dist/router/components/BackButton.d.ts +7 -0
  62. package/dist/router/components/CanGoBackGuard.d.ts +6 -0
  63. package/dist/router/components/RouterBackButton.d.ts +9 -0
  64. package/dist/router/components/StackNavigator.d.ts +54 -0
  65. package/dist/router/constants.d.ts +2 -0
  66. package/dist/router/createRoute.d.ts +39 -0
  67. package/dist/router/createRoute.test-d.d.ts +9 -0
  68. package/dist/router/hooks/useInitialRouteName.d.ts +1 -0
  69. package/dist/router/hooks/useIsInitialScreen.d.ts +1 -0
  70. package/dist/router/hooks/useRouterControls.d.ts +11 -0
  71. package/dist/router/index.d.ts +3 -0
  72. package/dist/router/types/RequireContext.d.ts +7 -0
  73. package/dist/router/types/RouteScreen.d.ts +16 -0
  74. package/dist/router/types/Screen.d.ts +23 -0
  75. package/dist/router/types/index.d.ts +3 -0
  76. package/dist/router/types/screen-option.d.ts +4 -0
  77. package/dist/router/utils/createParentRouteScreenMap.d.ts +8 -0
  78. package/dist/router/utils/defaultParserParams.d.ts +9 -0
  79. package/dist/router/utils/index.d.ts +2 -0
  80. package/dist/router/utils/matchers.d.ts +2 -0
  81. package/dist/router/utils/mergeParentLayoutScreen.d.ts +18 -0
  82. package/dist/router/utils/path.d.ts +53 -0
  83. package/dist/router/utils/screen.d.ts +37 -0
  84. package/dist/scroll-view-inertial-background/ScrollViewInertialBackground.d.ts +49 -0
  85. package/dist/scroll-view-inertial-background/index.d.ts +1 -0
  86. package/dist/status-bar/StatusBar.android.d.ts +3 -0
  87. package/dist/status-bar/StatusBar.ios.d.ts +3 -0
  88. package/dist/status-bar/index.d.ts +2 -0
  89. package/dist/status-bar/types.d.ts +20 -0
  90. package/dist/status-bar/utils.d.ts +3 -0
  91. package/dist/types/global.d.ts +14 -0
  92. package/dist/use-back-event/index.d.ts +1 -0
  93. package/dist/use-back-event/useBackEvent.d.ts +135 -0
  94. package/dist/utils/noop.d.ts +1 -0
  95. package/dist/utils/usePreservedCallback.d.ts +1 -0
  96. package/dist/video/Video.d.ts +67 -0
  97. package/dist/video/index.d.ts +1 -0
  98. package/dist/video/instance.d.ts +9 -0
  99. package/dist/visibility/VisibilityProvider.d.ts +27 -0
  100. package/dist/visibility/index.d.ts +6 -0
  101. package/dist/visibility/react-navigation/index.d.ts +2 -0
  102. package/dist/visibility/react-navigation/useIsFocusedSafely.d.ts +20 -0
  103. package/dist/visibility/react-navigation/useNavigationSafely.d.ts +19 -0
  104. package/dist/visibility/useIsAppForeground.d.ts +39 -0
  105. package/dist/visibility/useVisibility.d.ts +35 -0
  106. package/dist/visibility/useVisibilityChange.d.ts +51 -0
  107. package/dist/visibility/useVisibilityChanged.d.ts +41 -0
  108. package/dist/visibility/utils/usePrevious.d.ts +15 -0
  109. package/jest.d.ts +1 -0
  110. package/package.json +94 -0
  111. package/presets.d.ts +1 -0
  112. package/src/app/App/index.android.tsx +6 -0
  113. package/src/app/App/index.d.ts +6 -0
  114. package/src/app/App/index.ios.tsx +13 -0
  115. package/src/app/AppRoot.tsx +39 -0
  116. package/src/app/Granite.tsx +128 -0
  117. package/src/app/HostAppRoot.tsx +19 -0
  118. package/src/app/index.ts +2 -0
  119. package/src/async-bridges.ts +2 -0
  120. package/src/blur/BlurView.tsx +103 -0
  121. package/src/blur/ReactNativeBlurModule.ts +19 -0
  122. package/src/blur/constants.ts +3 -0
  123. package/src/blur/index.ts +1 -0
  124. package/src/constant-bridges.ts +1 -0
  125. package/src/constants.ts +1 -0
  126. package/src/dev-entrypoint/index.tsx +17 -0
  127. package/src/event/abstract.ts +130 -0
  128. package/src/event/index.ts +2 -0
  129. package/src/event/useGraniteEvent.ts +34 -0
  130. package/src/impression-area/ImpressionArea.tsx +341 -0
  131. package/src/impression-area/index.ts +1 -0
  132. package/src/index.ts +24 -0
  133. package/src/initial-props/InitialProps.ts +95 -0
  134. package/src/initial-props/index.ts +1 -0
  135. package/src/intersection-observer/IOContext.ts +16 -0
  136. package/src/intersection-observer/IOFlatList.ts +72 -0
  137. package/src/intersection-observer/IOManager.ts +73 -0
  138. package/src/intersection-observer/IOScrollView.ts +69 -0
  139. package/src/intersection-observer/InView.tsx +205 -0
  140. package/src/intersection-observer/IntersectionObserver.ts +212 -0
  141. package/src/intersection-observer/index.ts +24 -0
  142. package/src/intersection-observer/withIO.tsx +151 -0
  143. package/src/jest/index.ts +1 -0
  144. package/src/keyboard/KeyboardAboveView.tsx +62 -0
  145. package/src/keyboard/index.ts +2 -0
  146. package/src/keyboard/useKeyboardAnimatedHeight.tsx +81 -0
  147. package/src/native-event-emitter/eventEmitters/index.ts +3 -0
  148. package/src/native-event-emitter/eventEmitters/types.ts +4 -0
  149. package/src/native-event-emitter/eventEmitters/visibilityChanged.ts +11 -0
  150. package/src/native-event-emitter/index.ts +1 -0
  151. package/src/native-event-emitter/nativeEventEmitter.ts +18 -0
  152. package/src/native-modules/core/GraniteCoreModule.ts +9 -0
  153. package/src/native-modules/index.ts +3 -0
  154. package/src/native-modules/natives/GraniteModule.ts +8 -0
  155. package/src/native-modules/natives/closeView.ts +25 -0
  156. package/src/native-modules/natives/getSchemeUri.ts +27 -0
  157. package/src/native-modules/natives/index.ts +3 -0
  158. package/src/native-modules/natives/openURL.ts +40 -0
  159. package/src/react/index.ts +1 -0
  160. package/src/react/useWaitForReturnNavigator.ts +75 -0
  161. package/src/rn-polyfills/index.ts +7 -0
  162. package/src/rn-polyfills/symbol-asynciterator/index.ts +15 -0
  163. package/src/rn-polyfills/url/index.ts +1 -0
  164. package/src/router/Router.tsx +164 -0
  165. package/src/router/components/BackButton.tsx +58 -0
  166. package/src/router/components/CanGoBackGuard.tsx +31 -0
  167. package/src/router/components/RouterBackButton.tsx +32 -0
  168. package/src/router/components/StackNavigator.tsx +12 -0
  169. package/src/router/constants.ts +3 -0
  170. package/src/router/createRoute.test-d.ts +52 -0
  171. package/src/router/createRoute.ts +161 -0
  172. package/src/router/hooks/useInitialRouteName.tsx +22 -0
  173. package/src/router/hooks/useIsInitialScreen.ts +7 -0
  174. package/src/router/hooks/useRouterControls.tsx +72 -0
  175. package/src/router/index.ts +3 -0
  176. package/src/router/types/RequireContext.ts +7 -0
  177. package/src/router/types/RouteScreen.ts +17 -0
  178. package/src/router/types/Screen.tsx +24 -0
  179. package/src/router/types/index.ts +3 -0
  180. package/src/router/types/screen-option.ts +23 -0
  181. package/src/router/utils/createParentRouteScreenMap.spec.ts +166 -0
  182. package/src/router/utils/createParentRouteScreenMap.ts +136 -0
  183. package/src/router/utils/defaultParserParams.spec.ts +46 -0
  184. package/src/router/utils/defaultParserParams.ts +19 -0
  185. package/src/router/utils/index.ts +2 -0
  186. package/src/router/utils/matchers.ts +5 -0
  187. package/src/router/utils/mergeParentLayoutScreen.spec.tsx +112 -0
  188. package/src/router/utils/mergeParentLayoutScreen.tsx +43 -0
  189. package/src/router/utils/path.spec.ts +135 -0
  190. package/src/router/utils/path.ts +105 -0
  191. package/src/router/utils/screen.tsx +95 -0
  192. package/src/scroll-view-inertial-background/ScrollViewInertialBackground.tsx +99 -0
  193. package/src/scroll-view-inertial-background/index.ts +1 -0
  194. package/src/status-bar/StatusBar.android.tsx +36 -0
  195. package/src/status-bar/StatusBar.d.ts +4 -0
  196. package/src/status-bar/StatusBar.ios.tsx +34 -0
  197. package/src/status-bar/index.ts +2 -0
  198. package/src/status-bar/types.ts +21 -0
  199. package/src/status-bar/utils.ts +20 -0
  200. package/src/types/global.ts +21 -0
  201. package/src/use-back-event/index.ts +1 -0
  202. package/src/use-back-event/useBackEvent.tsx +260 -0
  203. package/src/utils/noop.ts +1 -0
  204. package/src/utils/usePreservedCallback.ts +16 -0
  205. package/src/video/Video.tsx +104 -0
  206. package/src/video/index.ts +1 -0
  207. package/src/video/instance.tsx +28 -0
  208. package/src/visibility/VisibilityProvider.tsx +36 -0
  209. package/src/visibility/index.ts +6 -0
  210. package/src/visibility/react-navigation/index.ts +2 -0
  211. package/src/visibility/react-navigation/useIsFocusedSafely.tsx +58 -0
  212. package/src/visibility/react-navigation/useNavigationSafely.tsx +30 -0
  213. package/src/visibility/useIsAppForeground.tsx +73 -0
  214. package/src/visibility/useVisibility.tsx +54 -0
  215. package/src/visibility/useVisibilityChange.ts +69 -0
  216. package/src/visibility/useVisibilityChanged.tsx +69 -0
  217. package/src/visibility/utils/usePrevious.tsx +24 -0
@@ -0,0 +1,135 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import {
3
+ excludeDynamicNamePattern,
4
+ excludeFileExtension,
5
+ excludeRelativePath,
6
+ getFileNameFromPath,
7
+ getRoutePath,
8
+ } from './path';
9
+
10
+ describe('excludeFileExtension', () => {
11
+ it('"./index.ts" is changed to "./index"', () => {
12
+ expect(excludeFileExtension('./index.ts')).toEqual('./index');
13
+ });
14
+
15
+ it('"./index.js" is changed to "./index"', () => {
16
+ expect(excludeFileExtension('./index.js')).toEqual('./index');
17
+ });
18
+
19
+ it('"./list/detail.tsx" is changed to "./list/detail"', () => {
20
+ expect(excludeFileExtension('./list/detail.tsx')).toEqual('./list/detail');
21
+ });
22
+
23
+ it('"./list/detail.jsx" is changed to "./list/detail"', () => {
24
+ expect(excludeFileExtension('./list/detail.jsx')).toEqual('./list/detail');
25
+ });
26
+
27
+ it('"./detail.foo" remains as "./detail.foo"', () => {
28
+ expect(excludeFileExtension('./detail.foo')).toEqual('./detail.foo');
29
+ });
30
+ });
31
+
32
+ describe('excludeRelativePath', () => {
33
+ it('"./index.tsx" is changed to "index.tsx"', () => {
34
+ expect(excludeRelativePath('./index.tsx')).toEqual('index.tsx');
35
+ });
36
+
37
+ it('"./../index.tsx" is changed to "index.tsx"', () => {
38
+ expect(excludeRelativePath('./../index.tsx')).toEqual('index.tsx');
39
+ });
40
+
41
+ it('"../../index.tsx" is changed to "index.tsx"', () => {
42
+ expect(excludeRelativePath('../../index.tsx')).toEqual('index.tsx');
43
+ });
44
+ });
45
+
46
+ describe('excludeDynamicNamePattern', () => {
47
+ it('"[id]" is changed to "id"', () => {
48
+ expect(excludeDynamicNamePattern('[id]')).toEqual('id');
49
+ });
50
+
51
+ it('"[id]/[name]" is changed to "id/name"', () => {
52
+ expect(excludeDynamicNamePattern('[id]/[name]')).toEqual('id/name');
53
+ });
54
+ });
55
+
56
+ describe('getRoutePath', () => {
57
+ describe('posix', () => {
58
+ it('"/index.tsx" is converted to "/"', () => {
59
+ expect(getRoutePath('/index.tsx')).toEqual('/');
60
+ });
61
+
62
+ it('"/index.ts" is converted to "/"', () => {
63
+ expect(getRoutePath('/index.tsx')).toEqual('/');
64
+ });
65
+
66
+ it('"/list/index.tsx" is converted to "/list"', () => {
67
+ expect(getRoutePath('/list/index.tsx')).toEqual('/list');
68
+ });
69
+
70
+ it('"/list/detail.tsx" is converted to "/list"', () => {
71
+ expect(getRoutePath('/list/detail.tsx')).toEqual('/list/detail');
72
+ });
73
+
74
+ it('"/list/[id].tsx" is converted to "/:id"', () => {
75
+ expect(getRoutePath('/list/[id].tsx')).toEqual('/list/:id');
76
+ });
77
+
78
+ it('"/list/[id].js" is converted to "/:id"', () => {
79
+ expect(getRoutePath('/list/[id].js')).toEqual('/list/:id');
80
+ });
81
+ });
82
+
83
+ describe('window', () => {
84
+ it('"\\index.tsx" is converted to "/"', () => {
85
+ expect(getRoutePath('\\index.tsx')).toEqual('/');
86
+ });
87
+
88
+ it('"\\index.ts" is converted to "/"', () => {
89
+ expect(getRoutePath('\\index.tsx')).toEqual('/');
90
+ });
91
+
92
+ it('"\\list\\index.tsx" is converted to "/list"', () => {
93
+ expect(getRoutePath('\\list\\index.tsx')).toEqual('/list');
94
+ });
95
+
96
+ it('"\\list\\detail.tsx" is converted to "/list"', () => {
97
+ expect(getRoutePath('\\list\\detail.tsx')).toEqual('/list/detail');
98
+ });
99
+
100
+ it('"\\list\\[id].tsx" is converted to "/:id"', () => {
101
+ expect(getRoutePath('\\list\\[id].tsx')).toEqual('/list/:id');
102
+ });
103
+
104
+ it('"\\list\\[id].js" is converted to "/:id"', () => {
105
+ expect(getRoutePath('\\list\\[id].js')).toEqual('/list/:id');
106
+ });
107
+ });
108
+ });
109
+
110
+ describe('getFileNameFromPath', () => {
111
+ it('extracts filename from file path', () => {
112
+ const result = getFileNameFromPath('/path/to/file.txt');
113
+ expect(result).toBe('file.txt');
114
+ });
115
+
116
+ it('returns the filename when path is just a filename', () => {
117
+ const result = getFileNameFromPath('file.txt');
118
+ expect(result).toBe('file.txt');
119
+ });
120
+
121
+ it('returns empty string when path is empty', () => {
122
+ const result = getFileNameFromPath('');
123
+ expect(result).toBe('');
124
+ });
125
+
126
+ it('returns empty string when path ends with slash', () => {
127
+ const result = getFileNameFromPath('/path/to/directory/');
128
+ expect(result).toBe('');
129
+ });
130
+
131
+ it('extracts filename without extension using withExtension option', () => {
132
+ const result = getFileNameFromPath('/path/to/file.txt', { withExtension: false });
133
+ expect(result).toBe('file');
134
+ });
135
+ });
@@ -0,0 +1,105 @@
1
+ import { matchDynamicName } from './matchers';
2
+
3
+ /**
4
+ * Converts Windows-style paths to Posix-based paths.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * toPosixBasedPath(".\\index.tsx") // "./index.tsx"
9
+ * toPosixBasedPath("..\\..\\index.tsx") // "../../index.tsx"
10
+ * ```
11
+ */
12
+ function toPosixBasedPath(filePath: string): string {
13
+ return filePath.replace(/\\/g, '/');
14
+ }
15
+
16
+ /**
17
+ * @name excludeFileExtension
18
+ * @description Removes file extension.
19
+ */
20
+ export function excludeFileExtension(name: string): string {
21
+ return name.replace(/\.(tsx|jsx|ts|js)$/g, '');
22
+ }
23
+
24
+ /**
25
+ * @name excludeRelativePath
26
+ * @description Removes relative path from path.
27
+ * @example
28
+ * ```ts
29
+ * excludeRelativePath("./index.tsx") // "index.tsx"
30
+ * excludeRelativePath("./list/detail.tsx") // "list/detail.tsx"
31
+ * excludeRelativePath("../../index.tsx") // "index.tsx"
32
+ * ```
33
+ */
34
+ export function excludeRelativePath(filePath: string): string {
35
+ return filePath.replace(/^(?:\.\.?\/)+/g, '');
36
+ }
37
+
38
+ /**
39
+ * @name excludeDynamicNamePattern
40
+ * @description Removes `[` `]` from dynamic route pattern.
41
+ * @example
42
+ * ```ts
43
+ * excludeDynamicNamePattern("[id]") // "id"
44
+ * excludeDynamicNamePattern("[id]/[name]") // "id/name"
45
+ * ```
46
+ */
47
+ export function excludeDynamicNamePattern(filePath: string): string {
48
+ return filePath.replace(/\[|\]/g, '');
49
+ }
50
+
51
+ /**
52
+ * @name getRoutePath
53
+ * @description Converts to route path.
54
+ * @example
55
+ * ```ts
56
+ * getRoutePath('./index.tsx') // "/"
57
+ * getRoutePath('./list/index.tsx') // "/list"
58
+ * getRoutePath('./list/detail.tsx') // "/list/detail"
59
+ * getRoutePath('./list/[id].js') // "/list/:id"
60
+ * ```
61
+ */
62
+ export function getRoutePath(filePath: string): string {
63
+ const posixBasedPath = toPosixBasedPath(filePath);
64
+ const normalPath = excludeRelativePath(excludeFileExtension(posixBasedPath));
65
+
66
+ const routePath = normalPath
67
+ .split('/')
68
+ .map((segment) => {
69
+ if (segment === 'index') {
70
+ return '';
71
+ }
72
+
73
+ if (matchDynamicName(segment)) {
74
+ return `:${excludeDynamicNamePattern(segment)}`;
75
+ }
76
+
77
+ return segment;
78
+ })
79
+ .filter((segment) => segment.length > 0)
80
+ .join('/');
81
+
82
+ return '/' + routePath;
83
+ }
84
+
85
+ /**
86
+ * @name getFileNameFromPath
87
+ * @description Extracts filename from file path.
88
+ * @example
89
+ * ```ts
90
+ * getFileNameFromPath('/path/to/file.txt') // "file.txt"
91
+ * getFileNameFromPath('file.txt') // "file.txt"
92
+ * getFileNameFromPath('') // ""
93
+ * getFileNameFromPath('/path/to/directory/') // ""
94
+ * getFileNameFromPath('/path/to/file.txt', { withExtension: false }) // "file"
95
+ * ```
96
+ */
97
+ export function getFileNameFromPath(
98
+ filePath: string,
99
+ options: { withExtension?: boolean } = {
100
+ withExtension: true,
101
+ }
102
+ ): string {
103
+ const fileName = filePath.split('/').pop() || '';
104
+ return options.withExtension ? fileName : fileName.replace(/\.[^.]+$/g, '');
105
+ }
@@ -0,0 +1,95 @@
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
+ export function getScreenPathMapConfig(routeScreens: RouteScreen[]) {
53
+ const screensConfig: ScreenPath = {};
54
+
55
+ routeScreens.forEach((routeScreen) => {
56
+ const routePath = routeScreen.path;
57
+
58
+ if (screensConfig[routePath] != null) {
59
+ throw new Error(`${routePath} is already registered. Please check for duplicate paths.`);
60
+ }
61
+
62
+ screensConfig[routePath] = {
63
+ path: routePath,
64
+ };
65
+ });
66
+
67
+ // @see https://reactnavigation.org/docs/configuring-links/#matching-exact-paths
68
+ // This is a mapping for the root path ('/') for deep link handling.
69
+ // Example: To handle URLs like scheme://{service_name}?name=John,
70
+ // map the root path to an empty string to correctly extract query parameters.
71
+ screensConfig['/'] = {
72
+ path: '',
73
+ };
74
+
75
+ // https://reactnavigation.org/docs/configuring-links/#handling-unmatched-routes-or-404
76
+ screensConfig['/_404'] = {
77
+ path: '*',
78
+ };
79
+
80
+ return screensConfig;
81
+ }
82
+
83
+ /**
84
+ * @name ScreenPath
85
+ * @description
86
+ * Type representing screen paths.
87
+ *
88
+ * @typedef {Record<string, { path?: string }>} ScreenPath
89
+ */
90
+ export type ScreenPath = Record<
91
+ string,
92
+ {
93
+ path?: string;
94
+ }
95
+ >;
@@ -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,36 @@
1
+ import { useLayoutEffect } from 'react';
2
+ import { StatusBar as BaseStatusBar, useColorScheme } from 'react-native';
3
+ import type { StatusBarProps } from './types';
4
+ import { toStatusBarContentStyle } from './utils';
5
+
6
+ export function StatusBar({ style, animated, hidden, backgroundColor, translucent = true }: StatusBarProps) {
7
+ const colorScheme = useColorScheme() ?? 'light';
8
+ const translucentWithNoBackgroundColor = translucent && backgroundColor == null;
9
+
10
+ return (
11
+ <BaseStatusBar
12
+ translucent={translucent}
13
+ barStyle={toStatusBarContentStyle(style, colorScheme)}
14
+ backgroundColor={translucentWithNoBackgroundColor ? 'transparent' : backgroundColor}
15
+ animated={animated}
16
+ hidden={hidden}
17
+ />
18
+ );
19
+ }
20
+
21
+ export function useStatusBar({ style, animated, hidden, backgroundColor, translucent = true }: StatusBarProps) {
22
+ const colorScheme = useColorScheme() ?? 'light';
23
+ const translucentWithNoBackgroundColor = translucent && backgroundColor == null;
24
+
25
+ useLayoutEffect(() => {
26
+ BaseStatusBar.setTranslucent(translucent);
27
+ BaseStatusBar.setBarStyle(toStatusBarContentStyle(style, colorScheme));
28
+ BaseStatusBar.setBackgroundColor(
29
+ translucentWithNoBackgroundColor ? 'transparent' : (backgroundColor ?? 'transparent')
30
+ );
31
+
32
+ if (hidden !== undefined) {
33
+ BaseStatusBar.setHidden(hidden);
34
+ }
35
+ }, [colorScheme, translucentWithNoBackgroundColor, style, animated, hidden, backgroundColor, translucent]);
36
+ }
@@ -0,0 +1,4 @@
1
+ import type { StatusBarProps } from './types';
2
+
3
+ export declare function StatusBar(props: StatusBarProps): JSX.Element;
4
+ export declare function useStatusBar(props: StatusBarProps): void;
@@ -0,0 +1,34 @@
1
+ import { useLayoutEffect } from 'react';
2
+ import { useColorScheme } from 'react-native';
3
+ import type { StatusBarProps } from './types';
4
+ import { toStatusBarContentStyle } from './utils';
5
+ import { useNavigation } from '../router/createRoute';
6
+
7
+ export function StatusBar(props: StatusBarProps) {
8
+ useStatusBar(props);
9
+
10
+ return null;
11
+ }
12
+
13
+ export function useStatusBar({ style, hidden }: StatusBarProps) {
14
+ const navigation = useNavigation();
15
+ const colorScheme = useColorScheme() ?? 'light';
16
+
17
+ useLayoutEffect(() => {
18
+ let statusBarStyle: 'light' | 'dark';
19
+
20
+ switch (toStatusBarContentStyle(style, colorScheme)) {
21
+ case 'light-content':
22
+ statusBarStyle = 'light';
23
+ break;
24
+ case 'dark-content':
25
+ statusBarStyle = 'dark';
26
+ break;
27
+ }
28
+
29
+ navigation.setOptions({
30
+ statusBarStyle,
31
+ statusBarHidden: hidden,
32
+ });
33
+ }, [navigation, colorScheme, style, hidden]);
34
+ }
@@ -0,0 +1,2 @@
1
+ export { StatusBar, useStatusBar } from './StatusBar';
2
+ export type { StatusBarProps, StatusBarStyle } from './types';
@@ -0,0 +1,21 @@
1
+ export type StatusBarStyle = 'light' | 'dark' | 'auto' | 'inverted';
2
+
3
+ export type StatusBarProps = {
4
+ /**
5
+ * @default 'auto'
6
+ */
7
+ style?: StatusBarStyle;
8
+ hidden?: boolean;
9
+ /**
10
+ * @platform android
11
+ */
12
+ animated?: boolean;
13
+ /**
14
+ * @platform android
15
+ */
16
+ backgroundColor?: string;
17
+ /**
18
+ * @platform android
19
+ */
20
+ translucent?: boolean;
21
+ };
@@ -0,0 +1,20 @@
1
+ import type { StatusBarStyle } from './types';
2
+ import type { ColorPreference } from '../initial-props';
3
+
4
+ export function toStatusBarContentStyle(
5
+ statusBarStyle: StatusBarStyle = 'auto',
6
+ colorPreference: ColorPreference = 'light'
7
+ ): 'light-content' | 'dark-content' {
8
+ const resolvedStyle = (() => {
9
+ switch (statusBarStyle) {
10
+ case 'auto':
11
+ return colorPreference === 'light' ? 'dark' : 'light';
12
+ case 'inverted':
13
+ return colorPreference === 'light' ? 'light' : 'dark';
14
+ default:
15
+ return statusBarStyle;
16
+ }
17
+ })();
18
+
19
+ return resolvedStyle === 'light' ? 'light-content' : 'dark-content';
20
+ }
@@ -0,0 +1,21 @@
1
+ /* eslint-disable @typescript-eslint/naming-convention */
2
+ /* eslint-disable no-var */
3
+ import type { ComponentType } from 'react';
4
+
5
+ export interface GraniteGlobal {
6
+ app: {
7
+ name: string;
8
+ scheme: string;
9
+ };
10
+ }
11
+
12
+ declare global {
13
+ // @internal
14
+ var __granite: GraniteGlobal;
15
+
16
+ // @internal
17
+ var Page: ComponentType<any>;
18
+
19
+ var window: { __granite: GraniteGlobal };
20
+ var global: { __granite: GraniteGlobal };
21
+ }
@@ -0,0 +1 @@
1
+ export * from './useBackEvent';