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