@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,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,104 @@
1
+ import type { default as VideoRef } from '@granite-js/native/react-native-video';
2
+ import { ComponentProps, forwardRef, useMemo, useState } from 'react';
3
+ import { Animated, Platform } from 'react-native';
4
+ import * as instance from './instance';
5
+ import { useVisibility } from '../visibility';
6
+
7
+ const AnimatedRNVideo = Animated.createAnimatedComponent(instance.Component);
8
+
9
+ type VideoProps = ComponentProps<typeof AnimatedRNVideo>;
10
+
11
+ /**
12
+ * @public
13
+ * @name Video
14
+ * @category UI
15
+ * @description
16
+ * The Video component implements audio focus control logic to prevent the sandbox app from stopping music playing in other apps. It automatically plays or pauses based on the app's state. For example, when the app transitions to the background, the video automatically pauses.
17
+ *
18
+ * ::: warning
19
+ * The Video component uses [react-native-video version (6.0.0-alpha.6)](https://github.com/TheWidlarzGroup/react-native-video/tree/v6.0.0-alpha.6). Therefore, some types or features may not be compatible with the latest version.
20
+ * :::
21
+ *
22
+ * @property {boolean} [isAvailable] Value to check if the `Video` component can be used. You can check this value to determine if the user can render the video or if video functionality is unavailable due to environmental constraints (e.g., network connection issues, unsupported devices). If this value is `false`, you should handle it by not rendering the video or providing alternative content.
23
+ *
24
+ * @param {VideoProperties} [props] Properties provided by [`react-native-video`](https://github.com/TheWidlarzGroup/react-native-video/tree/v6.0.0-alpha.6).
25
+ * @param {string} [props.source.uri] Source of the video to play. Can be set to a file path or URL.
26
+ * @param {boolean} [props.muted=false] Controls the mute state of the video. If `true`, the video's audio is muted; if `false`, the audio plays. Default is `false`.
27
+ * @param {boolean} [props.paused=false] Property to control video playback. If `true`, the video is paused; if `false`, the video plays. Default is `false`, and it autoplays.
28
+ * @param {OnAudioFocusChanged} [props.onAudioFocusChanged] Callback function called when audio focus changes. Must be implemented when `muted` is `false`. For more details, see [OnAudioFocusChanged](/reference/react-native/Types/OnAudioFocusChanged.html).
29
+ * @param {Ref<VideoRef>} ref Reference object to access the video instance. Through this ref, you can access various methods of the video instance.
30
+ *
31
+ * @returns {JSX.Element} Returns a JSX element that renders the video. Uses `Animated` to provide smooth animation effects during video playback.
32
+ *
33
+ * @see [react-native-video] https://github.com/react-native-video/react-native-video
34
+ * For detailed properties of the video component, please refer to the official documentation.
35
+ * @see [react-native-video-6.0.0] https://github.com/TheWidlarzGroup/react-native-video/releases/tag/v6.0.0
36
+ * This is the source code of the version currently installed in the sandbox app.
37
+ *
38
+ * @example
39
+ *
40
+ * ### Video Autoplay Example
41
+ *
42
+ * ```tsx
43
+ * import { useRef } from 'react';
44
+ * import { View } from 'react-native';
45
+ * import { Video } from '@granite-js/react-native';
46
+ *
47
+ * export function VideoExample() {
48
+ * const videoRef = useRef(null);
49
+ *
50
+ * return (
51
+ * <View>
52
+ * <Video
53
+ * ref={videoRef}
54
+ * source={{ uri: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4' }}
55
+ * muted={true}
56
+ * paused={false}
57
+ * resizeMode="cover"
58
+ * style={{ width: 300, height: 200, borderWidth: 1 }}
59
+ * />
60
+ * </View>
61
+ * );
62
+ * }
63
+ * ```
64
+ */
65
+ const Video = forwardRef<VideoRef, instance.VideoNativeProps>((props, ref) => {
66
+ const [isFocused, setIsFocused] = useState(props.muted || props.paused);
67
+ const visible = useVisibility();
68
+
69
+ // If focus state is not managed directly by the service through onAudioFocusChanged, control the paused value based on internal state.
70
+ const paused = useMemo(
71
+ () => !visible || props.paused || (!props.onAudioFocusChanged && !isFocused),
72
+ [props.onAudioFocusChanged, props.paused, visible, isFocused]
73
+ );
74
+
75
+ const disableFocus = props.muted || props.paused;
76
+ const mixWithOthers = props.muted ? 'mix' : undefined;
77
+
78
+ return (
79
+ <AnimatedRNVideo
80
+ ref={ref as any}
81
+ progressUpdateInterval={16}
82
+ disableFocus={Platform.OS === 'ios' ? false : disableFocus}
83
+ playWhenInactive
84
+ onAudioFocusChanged={(event: any) => {
85
+ setIsFocused(event.hasAudioFocus);
86
+ props.onAudioFocusChanged?.(event);
87
+ }}
88
+ {...props}
89
+ // Internal state is used to control the component's states.
90
+ paused={paused}
91
+ mixWithOthers={mixWithOthers}
92
+ />
93
+ );
94
+ });
95
+
96
+ Object.defineProperty(Video, 'isAvailable', {
97
+ value: instance.isAvailable,
98
+ enumerable: true,
99
+ writable: false,
100
+ configurable: false,
101
+ });
102
+
103
+ export { Video };
104
+ export type { VideoRef, VideoProps };
@@ -0,0 +1 @@
1
+ export * from './Video';
@@ -0,0 +1,28 @@
1
+ import type { VideoProperties } from '@granite-js/native/react-native-video';
2
+ import { Component as ReactComponent, forwardRef, type ComponentType, type ForwardedRef } from 'react';
3
+ import { View } from 'react-native';
4
+
5
+ export type VideoNativeProps = Omit<VideoProperties, 'onAudioFocusChanged'> & {
6
+ onAudioFocusChanged?: (event: { hasAudioFocus: boolean }) => void;
7
+ };
8
+
9
+ class FallbackComponent extends ReactComponent<VideoNativeProps & { innerRef: ForwardedRef<View> }> {
10
+ render() {
11
+ return <View ref={this.props.innerRef} />;
12
+ }
13
+ }
14
+
15
+ const ForwardedComponent = forwardRef<View, VideoNativeProps>((props: VideoNativeProps, ref) => (
16
+ <FallbackComponent {...props} innerRef={ref} />
17
+ ));
18
+
19
+ function getVideoComponent(): ComponentType<VideoNativeProps> {
20
+ try {
21
+ return require('@granite-js/native/react-native-video')?.default;
22
+ } catch {
23
+ return ForwardedComponent;
24
+ }
25
+ }
26
+
27
+ export const Component = getVideoComponent();
28
+ export const isAvailable = Component !== ForwardedComponent;
@@ -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
+ }
@@ -0,0 +1,73 @@
1
+ import { createContext, ReactElement, ReactNode, useContext, useEffect, useState } from 'react';
2
+ import { AppState, AppStateStatus } from 'react-native';
3
+
4
+ interface Props {
5
+ children: ReactNode;
6
+ }
7
+
8
+ const AppStateContext = createContext<AppStateStatus | undefined>(undefined);
9
+
10
+ /**
11
+ * @name AppStateProvider
12
+ * @description
13
+ * A Provider that manages the `AppState` of React Native screens. This Provider manages the app's state (active, background, etc.) and passes it to child components that subscribe to it.
14
+ * @link https://reactnative.dev/docs/appstate
15
+ * @param {Props} children - Child components to be wrapped by `AppStateProvider`. These components can detect and respond to changes in `AppState`.
16
+ * @returns {ReactElement} - A React Provider component wrapped with `AppStateProvider`.
17
+ * @example
18
+ * ```tsx
19
+ * export function App() {
20
+ * return (
21
+ * <AppStateProvider>
22
+ * <MyApp />
23
+ * </AppStateProvider>
24
+ * );
25
+ * }
26
+ * ```
27
+ */
28
+ export function AppStateProvider({ children }: Props): ReactElement {
29
+ const [appState, setAppState] = useState<AppStateStatus>(AppState.currentState);
30
+
31
+ const handleAppStateChange = (status: AppStateStatus) => {
32
+ setAppState(status);
33
+ };
34
+
35
+ useEffect(() => {
36
+ const subscription = AppState.addEventListener('change', handleAppStateChange);
37
+
38
+ return () => {
39
+ subscription.remove();
40
+ };
41
+ }, []);
42
+
43
+ return <AppStateContext.Provider value={appState}>{children}</AppStateContext.Provider>;
44
+ }
45
+
46
+ /**
47
+ * @category Hooks
48
+ * @name useIsAppForeground
49
+ * @description
50
+ * Returns whether the React Native app is in the foreground state.
51
+ *
52
+ * @see https://reactnative.dev/docs/0.72/appstate#app-states
53
+ * @returns {boolean} - Returns whether the app is in the foreground state.
54
+ * @throws {Error} Throws an error when the managed AppState is `null`.
55
+ * @example
56
+ * ```typescript
57
+ * const isForeground = useIsAppForeground();
58
+ * ```
59
+ */
60
+ export const useIsAppForeground = (): boolean => {
61
+ const appState = useContext(AppStateContext);
62
+
63
+ if (appState == null) {
64
+ throw new Error('useIsAppForeground must be used within a AppStateProvider');
65
+ }
66
+
67
+ /**
68
+ * In iOS, the 'inactive' state is also considered as foreground.
69
+ * 'inactive' is a state that only exists in iOS, for example, when the control center or notification panel is open.
70
+ * @see https://reactnative.dev/docs/0.72/appstate#app-states
71
+ */
72
+ return appState === 'active' || appState === 'inactive';
73
+ };
@@ -0,0 +1,54 @@
1
+ import { Platform } from 'react-native';
2
+ import { useIsFocusedSafely } from './react-navigation/useIsFocusedSafely';
3
+ import { useIsAppForeground } from './useIsAppForeground';
4
+ import { useVisibilityChanged } from './useVisibilityChanged';
5
+
6
+ /**
7
+ * @public
8
+ * @category Screen Control
9
+ * @name useVisibility
10
+ * @description
11
+ * Returns whether the screen is visible to the user.
12
+ * Returns `true` if the app's screen is currently visible to the user, and `false` if it's not. However, the screen visibility state does not change when opening and closing the system share modal ([share](/reference/react-native/Share/share)).
13
+ *
14
+ * Usage examples:
15
+ * - Returns `false` when switching to another app or pressing the home button.
16
+ * - Returns `true` when returning to the granite app or when the screen becomes visible.
17
+ * - Returns `false` when navigating to another service within the granite app.
18
+ *
19
+ * @returns {boolean} - Whether the current screen is visible to the user.
20
+ * @example
21
+ *
22
+ * ### Example of checking screen visibility
23
+ *
24
+ * ```tsx
25
+ * import { useEffect } from 'react';
26
+ * import { Text } from 'react-native';
27
+ * import { useVisibility } from '@granite-js/react-native';
28
+ *
29
+ * export function UseVisibilityExample() {
30
+ * const visibility = useVisibility();
31
+ *
32
+ * useEffect(() => {
33
+ * console.log({ visibility });
34
+ * }, [visibility]);
35
+ *
36
+ * return <Text>Logs are created when leaving and returning to the home screen.</Text>;
37
+ * }
38
+ * ```
39
+ */
40
+ export function useVisibility(): boolean {
41
+ const visible = useVisibilityChanged();
42
+ const isForeground = useIsAppForeground();
43
+ const isReactNavigationFocused = useIsFocusedSafely();
44
+
45
+ /**
46
+ * We don't use AppState in Android. (AppState uses `onHostPause` and `onHostResume`.)
47
+ * From Android version 5.172.0, `visibilityChanged` uses `onHostStop` and `onHostStart`.
48
+ * - For example, in Android, the `visible` state is maintained even when the share modal appears (share app bridge call).
49
+ */
50
+ const androidCondition = visible && isReactNavigationFocused;
51
+ const iOSCondition = visible && isForeground && isReactNavigationFocused;
52
+
53
+ return Platform.OS === 'android' ? androidCondition : iOSCondition;
54
+ }