UNPKG

3.77 kBTypeScriptView Raw
1import {
2 getPathFromState,
3 NavigationAction,
4 NavigationContainerRefContext,
5 NavigationHelpersContext,
6 NavigatorScreenParams,
7 ParamListBase,
8} from '@react-navigation/core';
9import type { NavigationState, PartialState } from '@react-navigation/routers';
10import * as React from 'react';
11import { GestureResponderEvent, Platform } from 'react-native';
12
13import LinkingContext from './LinkingContext';
14import useLinkTo, { To } from './useLinkTo';
15
16type Props<ParamList extends ReactNavigation.RootParamList> = {
17 to: To<ParamList>;
18 action?: NavigationAction;
19};
20
21const getStateFromParams = (
22 params: NavigatorScreenParams<ParamListBase, NavigationState> | undefined
23): PartialState<NavigationState> | NavigationState | undefined => {
24 if (params?.state) {
25 return params.state;
26 }
27
28 if (params?.screen) {
29 return {
30 routes: [
31 {
32 name: params.screen,
33 params: params.params,
34 // @ts-expect-error
35 state: params.screen
36 ? getStateFromParams(
37 params.params as
38 | NavigatorScreenParams<ParamListBase, NavigationState>
39 | undefined
40 )
41 : undefined,
42 },
43 ],
44 };
45 }
46
47 return undefined;
48};
49
50/**
51 * Hook to get props for an anchor tag so it can work with in page navigation.
52 *
53 * @param props.to Absolute path to screen (e.g. `/feeds/hot`).
54 * @param props.action Optional action to use for in-page navigation. By default, the path is parsed to an action based on linking config.
55 */
56export default function useLinkProps<
57 ParamList extends ReactNavigation.RootParamList
58>({ to, action }: Props<ParamList>) {
59 const root = React.useContext(NavigationContainerRefContext);
60 const navigation = React.useContext(NavigationHelpersContext);
61 const { options } = React.useContext(LinkingContext);
62 const linkTo = useLinkTo<ParamList>();
63
64 const onPress = (
65 e?: React.MouseEvent<HTMLAnchorElement, MouseEvent> | GestureResponderEvent
66 ) => {
67 let shouldHandle = false;
68
69 if (Platform.OS !== 'web' || !e) {
70 shouldHandle = e ? !e.defaultPrevented : true;
71 } else if (
72 !e.defaultPrevented && // onPress prevented default
73 // @ts-expect-error: these properties exist on web, but not in React Native
74 !(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) && // ignore clicks with modifier keys
75 // @ts-expect-error: these properties exist on web, but not in React Native
76 (e.button == null || e.button === 0) && // ignore everything but left clicks
77 // @ts-expect-error: these properties exist on web, but not in React Native
78 [undefined, null, '', 'self'].includes(e.currentTarget?.target) // let browser handle "target=_blank" etc.
79 ) {
80 e.preventDefault();
81 shouldHandle = true;
82 }
83
84 if (shouldHandle) {
85 if (action) {
86 if (navigation) {
87 navigation.dispatch(action);
88 } else if (root) {
89 root.dispatch(action);
90 } else {
91 throw new Error(
92 "Couldn't find a navigation object. Is your component inside NavigationContainer?"
93 );
94 }
95 } else {
96 linkTo(to);
97 }
98 }
99 };
100
101 const getPathFromStateHelper = options?.getPathFromState ?? getPathFromState;
102
103 const href =
104 typeof to === 'string'
105 ? to
106 : getPathFromStateHelper(
107 {
108 routes: [
109 {
110 name: to.screen,
111 // @ts-expect-error
112 params: to.params,
113 // @ts-expect-error
114 state: getStateFromParams(to.params),
115 },
116 ],
117 },
118 options?.config
119 );
120
121 return {
122 href,
123 accessibilityRole: 'link' as const,
124 onPress,
125 };
126}