1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 | import {focusWithoutScrolling, isMac, isWebKit} from './index';
|
14 | import {isFirefox, isIPad} from './platform';
|
15 | import {LinkDOMProps} from '@react-types/shared';
|
16 | import React, {createContext, ReactNode, useContext, useMemo} from 'react';
|
17 |
|
18 | interface Router {
|
19 | isNative: boolean,
|
20 | open: (target: Element, modifiers: Modifiers) => void
|
21 | }
|
22 |
|
23 | const RouterContext = createContext<Router>({
|
24 | isNative: true,
|
25 | open: openSyntheticLink
|
26 | });
|
27 |
|
28 | interface RouterProviderProps {
|
29 | navigate: (path: string) => void,
|
30 | children: ReactNode
|
31 | }
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 | export function RouterProvider(props: RouterProviderProps) {
|
38 | let {children, navigate} = props;
|
39 |
|
40 | let ctx = useMemo(() => ({
|
41 | isNative: false,
|
42 | open: (target: Element, modifiers: Modifiers) => {
|
43 | getSyntheticLink(target, link => {
|
44 | if (shouldClientNavigate(link, modifiers)) {
|
45 | navigate(link.pathname + link.search + link.hash);
|
46 | } else {
|
47 | openLink(link, modifiers);
|
48 | }
|
49 | });
|
50 | }
|
51 | }), [navigate]);
|
52 |
|
53 | return (
|
54 | <RouterContext.Provider value={ctx}>
|
55 | {children}
|
56 | </RouterContext.Provider>
|
57 | );
|
58 | }
|
59 |
|
60 | export function useRouter(): Router {
|
61 | return useContext(RouterContext);
|
62 | }
|
63 |
|
64 | interface Modifiers {
|
65 | metaKey?: boolean,
|
66 | ctrlKey?: boolean,
|
67 | altKey?: boolean,
|
68 | shiftKey?: boolean
|
69 | }
|
70 |
|
71 | export function shouldClientNavigate(link: HTMLAnchorElement, modifiers: Modifiers) {
|
72 |
|
73 | let target = link.getAttribute('target');
|
74 | return (
|
75 | (!target || target === '_self') &&
|
76 | link.origin === location.origin &&
|
77 | !link.hasAttribute('download') &&
|
78 | !modifiers.metaKey &&
|
79 | !modifiers.ctrlKey &&
|
80 | !modifiers.altKey &&
|
81 | !modifiers.shiftKey
|
82 | );
|
83 | }
|
84 |
|
85 | export function openLink(target: HTMLAnchorElement, modifiers: Modifiers, setOpening = true) {
|
86 | let {metaKey, ctrlKey, altKey, shiftKey} = modifiers;
|
87 |
|
88 |
|
89 |
|
90 |
|
91 |
|
92 | if (isFirefox() && window.event?.type?.startsWith('key') && target.target === '_blank') {
|
93 | if (isMac()) {
|
94 | metaKey = true;
|
95 | } else {
|
96 | ctrlKey = true;
|
97 | }
|
98 | }
|
99 |
|
100 |
|
101 |
|
102 | let event = isWebKit() && isMac() && !isIPad() && process.env.NODE_ENV !== 'test'
|
103 |
|
104 | ? new KeyboardEvent('keydown', {keyIdentifier: 'Enter', metaKey, ctrlKey, altKey, shiftKey})
|
105 | : new MouseEvent('click', {metaKey, ctrlKey, altKey, shiftKey, bubbles: true, cancelable: true});
|
106 | openLink.isOpening = setOpening;
|
107 | focusWithoutScrolling(target);
|
108 | target.dispatchEvent(event);
|
109 | openLink.isOpening = false;
|
110 | }
|
111 |
|
112 | openLink.isOpening = false;
|
113 |
|
114 | function getSyntheticLink(target: Element, open: (link: HTMLAnchorElement) => void) {
|
115 | if (target instanceof HTMLAnchorElement) {
|
116 | open(target);
|
117 | } else if (target.hasAttribute('data-href')) {
|
118 | let link = document.createElement('a');
|
119 | link.href = target.getAttribute('data-href');
|
120 | if (target.hasAttribute('data-target')) {
|
121 | link.target = target.getAttribute('data-target');
|
122 | }
|
123 | if (target.hasAttribute('data-rel')) {
|
124 | link.rel = target.getAttribute('data-rel');
|
125 | }
|
126 | if (target.hasAttribute('data-download')) {
|
127 | link.download = target.getAttribute('data-download');
|
128 | }
|
129 | if (target.hasAttribute('data-ping')) {
|
130 | link.ping = target.getAttribute('data-ping');
|
131 | }
|
132 | if (target.hasAttribute('data-referrer-policy')) {
|
133 | link.referrerPolicy = target.getAttribute('data-referrer-policy');
|
134 | }
|
135 | target.appendChild(link);
|
136 | open(link);
|
137 | target.removeChild(link);
|
138 | }
|
139 | }
|
140 |
|
141 | function openSyntheticLink(target: Element, modifiers: Modifiers) {
|
142 | getSyntheticLink(target, link => openLink(link, modifiers));
|
143 | }
|
144 |
|
145 | export function getSyntheticLinkProps(props: LinkDOMProps) {
|
146 | return {
|
147 | 'data-href': props.href,
|
148 | 'data-target': props.target,
|
149 | 'data-rel': props.rel,
|
150 | 'data-download': props.download,
|
151 | 'data-ping': props.ping,
|
152 | 'data-referrer-policy': props.referrerPolicy
|
153 | };
|
154 | }
|