@octanejs/remix-router 0.1.0
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/LICENSE +21 -0
- package/README.md +90 -0
- package/package.json +53 -0
- package/src/dom.ts +16 -0
- package/src/index.ts +267 -0
- package/src/internal.ts +37 -0
- package/src/lib/Await.tsrx +145 -0
- package/src/lib/Await.tsrx.d.ts +6 -0
- package/src/lib/DefaultErrorComponent.tsrx +47 -0
- package/src/lib/DefaultErrorComponent.tsrx.d.ts +2 -0
- package/src/lib/RenderErrorBoundary.tsrx +117 -0
- package/src/lib/RenderErrorBoundary.tsrx.d.ts +14 -0
- package/src/lib/actions.ts +90 -0
- package/src/lib/components/MemoryRouter.tsrx +42 -0
- package/src/lib/components/MemoryRouter.tsrx.d.ts +10 -0
- package/src/lib/components/Navigate.ts +60 -0
- package/src/lib/components/Router.tsrx +88 -0
- package/src/lib/components/Router.tsrx.d.ts +13 -0
- package/src/lib/components/RouterProvider.tsrx +320 -0
- package/src/lib/components/RouterProvider.tsrx.d.ts +21 -0
- package/src/lib/components/Routes.tsrx +53 -0
- package/src/lib/components/Routes.tsrx.d.ts +7 -0
- package/src/lib/components/routes-collector.ts +317 -0
- package/src/lib/components/utils.ts +171 -0
- package/src/lib/components/with-props.ts +72 -0
- package/src/lib/context.ts +129 -0
- package/src/lib/dom/Form.tsrx +76 -0
- package/src/lib/dom/Form.tsrx.d.ts +22 -0
- package/src/lib/dom/Link.tsrx +91 -0
- package/src/lib/dom/Link.tsrx.d.ts +22 -0
- package/src/lib/dom/NavLink.tsrx +118 -0
- package/src/lib/dom/NavLink.tsrx.d.ts +33 -0
- package/src/lib/dom/dom.ts +344 -0
- package/src/lib/dom/hooks.ts +93 -0
- package/src/lib/dom/lib.ts +822 -0
- package/src/lib/dom/routers.tsrx +104 -0
- package/src/lib/dom/routers.tsrx.d.ts +23 -0
- package/src/lib/dom/server.tsrx +350 -0
- package/src/lib/dom/server.tsrx.d.ts +25 -0
- package/src/lib/errors.ts +84 -0
- package/src/lib/framework-stubs.ts +103 -0
- package/src/lib/hooks.ts +1797 -0
- package/src/lib/href.ts +71 -0
- package/src/lib/react-types.ts +10 -0
- package/src/lib/router/history.ts +763 -0
- package/src/lib/router/instrumentation.ts +496 -0
- package/src/lib/router/links.ts +192 -0
- package/src/lib/router/router.ts +7165 -0
- package/src/lib/router/server-runtime-types.ts +12 -0
- package/src/lib/router/url.ts +8 -0
- package/src/lib/router/utils.ts +2179 -0
- package/src/lib/server-runtime/cookies.ts +240 -0
- package/src/lib/server-runtime/crypto.ts +55 -0
- package/src/lib/server-runtime/mode.ts +16 -0
- package/src/lib/server-runtime/sessions/cookieStorage.ts +54 -0
- package/src/lib/server-runtime/sessions/memoryStorage.ts +59 -0
- package/src/lib/server-runtime/sessions.ts +291 -0
- package/src/lib/server-runtime/warnings.ts +10 -0
- package/src/lib/types/future.ts +16 -0
- package/src/lib/types/params.ts +8 -0
- package/src/lib/types/register.ts +39 -0
- package/src/lib/types/route-module.ts +17 -0
- package/src/lib/types/utils.ts +37 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
// Vendored from react-router@7.18.1 packages/react-router/lib/dom/dom.ts — unmodified.
|
|
2
|
+
// Re-vendor with `node scripts/vendor-remix-router.mjs`; never hand-edit.
|
|
3
|
+
import { warning } from '../router/history';
|
|
4
|
+
import type { RelativeRoutingType } from '../router/router';
|
|
5
|
+
import type { FormEncType, HTMLFormMethod } from '../router/utils';
|
|
6
|
+
import { stripBasename } from '../router/utils';
|
|
7
|
+
|
|
8
|
+
export const defaultMethod: HTMLFormMethod = 'get';
|
|
9
|
+
const defaultEncType: FormEncType = 'application/x-www-form-urlencoded';
|
|
10
|
+
|
|
11
|
+
export function isHtmlElement(object: any): object is HTMLElement {
|
|
12
|
+
return typeof HTMLElement !== 'undefined' && object instanceof HTMLElement;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function isButtonElement(object: any): object is HTMLButtonElement {
|
|
16
|
+
return isHtmlElement(object) && object.tagName.toLowerCase() === 'button';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function isFormElement(object: any): object is HTMLFormElement {
|
|
20
|
+
return isHtmlElement(object) && object.tagName.toLowerCase() === 'form';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function isInputElement(object: any): object is HTMLInputElement {
|
|
24
|
+
return isHtmlElement(object) && object.tagName.toLowerCase() === 'input';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
type LimitedMouseEvent = Pick<MouseEvent, 'button' | 'metaKey' | 'altKey' | 'ctrlKey' | 'shiftKey'>;
|
|
28
|
+
|
|
29
|
+
function isModifiedEvent(event: LimitedMouseEvent) {
|
|
30
|
+
return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function shouldProcessLinkClick(event: LimitedMouseEvent, target?: string) {
|
|
34
|
+
return (
|
|
35
|
+
event.button === 0 && // Ignore everything but left clicks
|
|
36
|
+
(!target || target === '_self') && // Let browser handle "target=_blank" etc.
|
|
37
|
+
!isModifiedEvent(event) // Ignore clicks with modifier keys
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type ParamKeyValuePair = [string, string];
|
|
42
|
+
|
|
43
|
+
export type URLSearchParamsInit =
|
|
44
|
+
| string
|
|
45
|
+
| ParamKeyValuePair[]
|
|
46
|
+
| Record<string, string | string[]>
|
|
47
|
+
| URLSearchParams;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
Creates a URLSearchParams object using the given initializer.
|
|
51
|
+
|
|
52
|
+
This is identical to `new URLSearchParams(init)` except it also
|
|
53
|
+
supports arrays as values in the object form of the initializer
|
|
54
|
+
instead of just strings. This is convenient when you need multiple
|
|
55
|
+
values for a given key, but don't want to use an array initializer.
|
|
56
|
+
|
|
57
|
+
For example, instead of:
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
let searchParams = new URLSearchParams([
|
|
61
|
+
['sort', 'name'],
|
|
62
|
+
['sort', 'price']
|
|
63
|
+
]);
|
|
64
|
+
```
|
|
65
|
+
you can do:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
let searchParams = createSearchParams({
|
|
69
|
+
sort: ['name', 'price']
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
@category Utils
|
|
74
|
+
*/
|
|
75
|
+
export function createSearchParams(init: URLSearchParamsInit = ''): URLSearchParams {
|
|
76
|
+
return new URLSearchParams(
|
|
77
|
+
typeof init === 'string' || Array.isArray(init) || init instanceof URLSearchParams
|
|
78
|
+
? init
|
|
79
|
+
: Object.keys(init).reduce((memo, key) => {
|
|
80
|
+
let value = init[key];
|
|
81
|
+
return memo.concat(Array.isArray(value) ? value.map((v) => [key, v]) : [[key, value]]);
|
|
82
|
+
}, [] as ParamKeyValuePair[]),
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function getSearchParamsForLocation(
|
|
87
|
+
locationSearch: string,
|
|
88
|
+
defaultSearchParams: URLSearchParams | null,
|
|
89
|
+
) {
|
|
90
|
+
let searchParams = createSearchParams(locationSearch);
|
|
91
|
+
|
|
92
|
+
if (defaultSearchParams) {
|
|
93
|
+
// Use `defaultSearchParams.forEach(...)` here instead of iterating of
|
|
94
|
+
// `defaultSearchParams.keys()` to work-around a bug in Firefox related to
|
|
95
|
+
// web extensions. Relevant Bugzilla tickets:
|
|
96
|
+
// https://bugzilla.mozilla.org/show_bug.cgi?id=1414602
|
|
97
|
+
// https://bugzilla.mozilla.org/show_bug.cgi?id=1023984
|
|
98
|
+
defaultSearchParams.forEach((_, key) => {
|
|
99
|
+
if (!searchParams.has(key)) {
|
|
100
|
+
defaultSearchParams.getAll(key).forEach((value) => {
|
|
101
|
+
searchParams.append(key, value);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return searchParams;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Thanks https://github.com/sindresorhus/type-fest!
|
|
111
|
+
type JsonObject = { [Key in string]: JsonValue } & {
|
|
112
|
+
[Key in string]?: JsonValue | undefined;
|
|
113
|
+
};
|
|
114
|
+
type JsonArray = JsonValue[] | readonly JsonValue[];
|
|
115
|
+
type JsonPrimitive = string | number | boolean | null;
|
|
116
|
+
type JsonValue = JsonPrimitive | JsonObject | JsonArray;
|
|
117
|
+
|
|
118
|
+
export type SubmitTarget =
|
|
119
|
+
| HTMLFormElement
|
|
120
|
+
| HTMLButtonElement
|
|
121
|
+
| HTMLInputElement
|
|
122
|
+
| FormData
|
|
123
|
+
| URLSearchParams
|
|
124
|
+
| JsonValue
|
|
125
|
+
| null;
|
|
126
|
+
|
|
127
|
+
// One-time check for submitter support
|
|
128
|
+
let _formDataSupportsSubmitter: boolean | null = null;
|
|
129
|
+
|
|
130
|
+
function isFormDataSubmitterSupported() {
|
|
131
|
+
if (_formDataSupportsSubmitter === null) {
|
|
132
|
+
try {
|
|
133
|
+
new FormData(
|
|
134
|
+
document.createElement('form'),
|
|
135
|
+
// @ts-expect-error if FormData supports the submitter parameter, this will throw
|
|
136
|
+
0,
|
|
137
|
+
);
|
|
138
|
+
_formDataSupportsSubmitter = false;
|
|
139
|
+
} catch (
|
|
140
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
141
|
+
e
|
|
142
|
+
) {
|
|
143
|
+
_formDataSupportsSubmitter = true;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return _formDataSupportsSubmitter;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Submit options shared by both navigations and fetchers
|
|
151
|
+
*/
|
|
152
|
+
interface SharedSubmitOptions {
|
|
153
|
+
/**
|
|
154
|
+
* The HTTP method used to submit the form. Overrides `<form method>`.
|
|
155
|
+
* Defaults to "GET".
|
|
156
|
+
*/
|
|
157
|
+
method?: HTMLFormMethod;
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* The action URL path used to submit the form. Overrides `<form action>`.
|
|
161
|
+
* Defaults to the path of the current route.
|
|
162
|
+
*/
|
|
163
|
+
action?: string;
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* The encoding used to submit the form. Overrides `<form encType>`.
|
|
167
|
+
* Defaults to "application/x-www-form-urlencoded".
|
|
168
|
+
*/
|
|
169
|
+
encType?: FormEncType;
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Determines whether the form action is relative to the route hierarchy or
|
|
173
|
+
* the pathname. Use this if you want to opt out of navigating the route
|
|
174
|
+
* hierarchy and want to instead route based on /-delimited URL segments
|
|
175
|
+
*/
|
|
176
|
+
relative?: RelativeRoutingType;
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* In browser-based environments, prevent resetting scroll after this
|
|
180
|
+
* navigation when using the <ScrollRestoration> component
|
|
181
|
+
*/
|
|
182
|
+
preventScrollReset?: boolean;
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Enable flushSync for this submission's state updates
|
|
186
|
+
*/
|
|
187
|
+
flushSync?: boolean;
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Specify the default revalidation behavior after this submission
|
|
191
|
+
*
|
|
192
|
+
* If no `shouldRevalidate` functions are present on the active routes, then this
|
|
193
|
+
* value will be used directly. Otherwise it will be passed into `shouldRevalidate`
|
|
194
|
+
* so the route can make the final determination on revalidation. This can be
|
|
195
|
+
* useful when updating search params and you don't want to trigger a revalidation.
|
|
196
|
+
*
|
|
197
|
+
* By default (when not specified), loaders will revalidate according to the routers
|
|
198
|
+
* standard revalidation behavior.
|
|
199
|
+
*/
|
|
200
|
+
defaultShouldRevalidate?: boolean;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Submit options available to fetchers
|
|
205
|
+
*/
|
|
206
|
+
export interface FetcherSubmitOptions extends SharedSubmitOptions {}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Submit options available to navigations
|
|
210
|
+
*/
|
|
211
|
+
export interface SubmitOptions extends FetcherSubmitOptions {
|
|
212
|
+
/**
|
|
213
|
+
* Set `true` to replace the current entry in the browser's history stack
|
|
214
|
+
* instead of creating a new one (i.e. stay on "the same page"). Defaults
|
|
215
|
+
* to `false`.
|
|
216
|
+
*/
|
|
217
|
+
replace?: boolean;
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* State object to add to the history stack entry for this navigation
|
|
221
|
+
*/
|
|
222
|
+
state?: any;
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Indicate a specific fetcherKey to use when using navigate=false
|
|
226
|
+
*/
|
|
227
|
+
fetcherKey?: string;
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* navigate=false will use a fetcher instead of a navigation
|
|
231
|
+
*/
|
|
232
|
+
navigate?: boolean;
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Enable view transitions on this submission navigation
|
|
236
|
+
*/
|
|
237
|
+
viewTransition?: boolean;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const supportedFormEncTypes: Set<FormEncType> = new Set([
|
|
241
|
+
'application/x-www-form-urlencoded',
|
|
242
|
+
'multipart/form-data',
|
|
243
|
+
'text/plain',
|
|
244
|
+
]);
|
|
245
|
+
|
|
246
|
+
function getFormEncType(encType: string | null) {
|
|
247
|
+
if (encType != null && !supportedFormEncTypes.has(encType as FormEncType)) {
|
|
248
|
+
warning(
|
|
249
|
+
false,
|
|
250
|
+
`"${encType}" is not a valid \`encType\` for \`<Form>\`/\`<fetcher.Form>\` ` +
|
|
251
|
+
`and will default to "${defaultEncType}"`,
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
return encType;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export function getFormSubmissionInfo(
|
|
260
|
+
target: SubmitTarget,
|
|
261
|
+
basename: string,
|
|
262
|
+
): {
|
|
263
|
+
action: string | null;
|
|
264
|
+
method: string;
|
|
265
|
+
encType: string;
|
|
266
|
+
formData: FormData | undefined;
|
|
267
|
+
body: any;
|
|
268
|
+
} {
|
|
269
|
+
let method: string;
|
|
270
|
+
let action: string | null;
|
|
271
|
+
let encType: string;
|
|
272
|
+
let formData: FormData | undefined;
|
|
273
|
+
let body: any;
|
|
274
|
+
|
|
275
|
+
if (isFormElement(target)) {
|
|
276
|
+
// When grabbing the action from the element, it will have had the basename
|
|
277
|
+
// prefixed to ensure non-JS scenarios work, so strip it since we'll
|
|
278
|
+
// re-prefix in the router
|
|
279
|
+
let attr = target.getAttribute('action');
|
|
280
|
+
action = attr ? stripBasename(attr, basename) : null;
|
|
281
|
+
method = target.getAttribute('method') || defaultMethod;
|
|
282
|
+
encType = getFormEncType(target.getAttribute('enctype')) || defaultEncType;
|
|
283
|
+
|
|
284
|
+
formData = new FormData(target);
|
|
285
|
+
} else if (
|
|
286
|
+
isButtonElement(target) ||
|
|
287
|
+
(isInputElement(target) && (target.type === 'submit' || target.type === 'image'))
|
|
288
|
+
) {
|
|
289
|
+
let form = target.form;
|
|
290
|
+
|
|
291
|
+
if (form == null) {
|
|
292
|
+
throw new Error(`Cannot submit a <button> or <input type="submit"> without a <form>`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// <button>/<input type="submit"> may override attributes of <form>
|
|
296
|
+
|
|
297
|
+
// When grabbing the action from the element, it will have had the basename
|
|
298
|
+
// prefixed to ensure non-JS scenarios work, so strip it since we'll
|
|
299
|
+
// re-prefix in the router
|
|
300
|
+
let attr = target.getAttribute('formaction') || form.getAttribute('action');
|
|
301
|
+
action = attr ? stripBasename(attr, basename) : null;
|
|
302
|
+
|
|
303
|
+
method = target.getAttribute('formmethod') || form.getAttribute('method') || defaultMethod;
|
|
304
|
+
encType =
|
|
305
|
+
getFormEncType(target.getAttribute('formenctype')) ||
|
|
306
|
+
getFormEncType(form.getAttribute('enctype')) ||
|
|
307
|
+
defaultEncType;
|
|
308
|
+
|
|
309
|
+
// Build a FormData object populated from a form and submitter
|
|
310
|
+
formData = new FormData(form, target);
|
|
311
|
+
|
|
312
|
+
// If this browser doesn't support the `FormData(el, submitter)` format,
|
|
313
|
+
// then tack on the submitter value at the end. This is a lightweight
|
|
314
|
+
// solution that is not 100% spec compliant. For complete support in older
|
|
315
|
+
// browsers, consider using the `formdata-submitter-polyfill` package
|
|
316
|
+
if (!isFormDataSubmitterSupported()) {
|
|
317
|
+
let { name, type, value } = target;
|
|
318
|
+
if (type === 'image') {
|
|
319
|
+
let prefix = name ? `${name}.` : '';
|
|
320
|
+
formData.append(`${prefix}x`, '0');
|
|
321
|
+
formData.append(`${prefix}y`, '0');
|
|
322
|
+
} else if (name) {
|
|
323
|
+
formData.append(name, value);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
} else if (isHtmlElement(target)) {
|
|
327
|
+
throw new Error(
|
|
328
|
+
`Cannot submit element that is not <form>, <button>, or ` + `<input type="submit|image">`,
|
|
329
|
+
);
|
|
330
|
+
} else {
|
|
331
|
+
method = defaultMethod;
|
|
332
|
+
action = null;
|
|
333
|
+
encType = defaultEncType;
|
|
334
|
+
body = target;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Send body for <Form encType="text/plain" so we encode it into text
|
|
338
|
+
if (formData && encType === 'text/plain') {
|
|
339
|
+
body = formData;
|
|
340
|
+
formData = undefined;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return { action, method: method.toLowerCase(), encType, formData, body };
|
|
344
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// DOM hooks — transcribed from react-router@7.18.1 lib/dom/lib.tsx onto
|
|
2
|
+
// octane. This PR ships useLinkClickHandler (Link's engine); the remaining DOM
|
|
3
|
+
// hooks (useSearchParams, useSubmit, useFetcher, …) land in later phases per
|
|
4
|
+
// docs/remix-router-port-plan.md.
|
|
5
|
+
import { startTransition, useCallback } from 'octane';
|
|
6
|
+
import type { To } from '../router/history';
|
|
7
|
+
import { createPath } from '../router/history';
|
|
8
|
+
import type { RelativeRoutingType } from '../router/router';
|
|
9
|
+
import { shouldProcessLinkClick } from './dom';
|
|
10
|
+
import { useLocation, useNavigate, useResolvedPath } from '../hooks';
|
|
11
|
+
import { splitSlot, subSlot } from '../../internal';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Handles the click behavior for router `<Link>` components. This is useful if
|
|
15
|
+
* you need to create custom `<Link>` components with the same click behavior we
|
|
16
|
+
* use in our exported `<Link>`.
|
|
17
|
+
*/
|
|
18
|
+
export function useLinkClickHandler<E extends Element = HTMLAnchorElement>(
|
|
19
|
+
to: To,
|
|
20
|
+
...rest: unknown[]
|
|
21
|
+
): (event: MouseEvent & { currentTarget: E }) => void {
|
|
22
|
+
const [user, slot] = splitSlot(rest as any[]);
|
|
23
|
+
const {
|
|
24
|
+
target,
|
|
25
|
+
replace: replaceProp,
|
|
26
|
+
mask,
|
|
27
|
+
state,
|
|
28
|
+
preventScrollReset,
|
|
29
|
+
relative,
|
|
30
|
+
viewTransition,
|
|
31
|
+
defaultShouldRevalidate,
|
|
32
|
+
useTransitions,
|
|
33
|
+
} = (user[0] ?? {}) as {
|
|
34
|
+
target?: string;
|
|
35
|
+
replace?: boolean;
|
|
36
|
+
mask?: To;
|
|
37
|
+
state?: any;
|
|
38
|
+
preventScrollReset?: boolean;
|
|
39
|
+
relative?: RelativeRoutingType;
|
|
40
|
+
viewTransition?: boolean;
|
|
41
|
+
defaultShouldRevalidate?: boolean;
|
|
42
|
+
useTransitions?: boolean;
|
|
43
|
+
};
|
|
44
|
+
const navigate = useNavigate(subSlot(slot, 'ulch:nav')) as (to: To, opts?: any) => void;
|
|
45
|
+
const location = useLocation();
|
|
46
|
+
const path = useResolvedPath(to, { relative }, subSlot(slot, 'ulch:path')) as any;
|
|
47
|
+
|
|
48
|
+
return useCallback(
|
|
49
|
+
(event: MouseEvent) => {
|
|
50
|
+
if (shouldProcessLinkClick(event as any, target)) {
|
|
51
|
+
event.preventDefault();
|
|
52
|
+
|
|
53
|
+
// If the URL hasn't changed, a regular <a> will do a replace instead of
|
|
54
|
+
// a push, so do the same here unless the replace prop is explicitly set
|
|
55
|
+
const replace =
|
|
56
|
+
replaceProp !== undefined ? replaceProp : createPath(location) === createPath(path);
|
|
57
|
+
|
|
58
|
+
const doNavigate = () =>
|
|
59
|
+
navigate(to, {
|
|
60
|
+
replace,
|
|
61
|
+
mask,
|
|
62
|
+
state,
|
|
63
|
+
preventScrollReset,
|
|
64
|
+
relative,
|
|
65
|
+
viewTransition,
|
|
66
|
+
defaultShouldRevalidate,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
if (useTransitions) {
|
|
70
|
+
startTransition(() => doNavigate());
|
|
71
|
+
} else {
|
|
72
|
+
doNavigate();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
[
|
|
77
|
+
location,
|
|
78
|
+
navigate,
|
|
79
|
+
path,
|
|
80
|
+
replaceProp,
|
|
81
|
+
mask,
|
|
82
|
+
state,
|
|
83
|
+
target,
|
|
84
|
+
to,
|
|
85
|
+
preventScrollReset,
|
|
86
|
+
relative,
|
|
87
|
+
viewTransition,
|
|
88
|
+
defaultShouldRevalidate,
|
|
89
|
+
useTransitions,
|
|
90
|
+
],
|
|
91
|
+
subSlot(slot, 'ulch:cb'),
|
|
92
|
+
);
|
|
93
|
+
}
|