1 | import {
|
2 | getPathFromState,
|
3 | NavigationAction,
|
4 | NavigationContainerRefContext,
|
5 | NavigationHelpersContext,
|
6 | NavigatorScreenParams,
|
7 | ParamListBase,
|
8 | } from '@react-navigation/core';
|
9 | import type { NavigationState, PartialState } from '@react-navigation/routers';
|
10 | import * as React from 'react';
|
11 | import { GestureResponderEvent, Platform } from 'react-native';
|
12 |
|
13 | import LinkingContext from './LinkingContext';
|
14 | import useLinkTo, { To } from './useLinkTo';
|
15 |
|
16 | type Props<ParamList extends ReactNavigation.RootParamList> = {
|
17 | to: To<ParamList>;
|
18 | action?: NavigationAction;
|
19 | };
|
20 |
|
21 | const 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 |
|
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 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 | export 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 &&
|
73 |
|
74 | !(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) &&
|
75 |
|
76 | (e.button == null || e.button === 0) &&
|
77 |
|
78 | [undefined, null, '', 'self'].includes(e.currentTarget?.target)
|
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 |
|
112 | params: to.params,
|
113 |
|
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 | }
|