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