@richie-router/react 0.0.1 → 0.1.1

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.
@@ -0,0 +1,849 @@
1
+ // packages/react/src/router.tsx
2
+ import React from "react";
3
+ import {
4
+ buildPath,
5
+ collectBranches,
6
+ collectRoutes,
7
+ createParsedLocation,
8
+ createRouteNode,
9
+ defaultParseSearch,
10
+ defaultStringifySearch,
11
+ isNotFound,
12
+ isRedirect,
13
+ matchRouteTree,
14
+ notFound,
15
+ redirect,
16
+ resolveHeadConfig
17
+ } from "@richie-router/core";
18
+ import {
19
+ createBrowserHistory,
20
+ createHashHistory,
21
+ createMemoryHistory
22
+ } from "./history.mjs";
23
+ import { jsxDEV } from "react/jsx-dev-runtime";
24
+ var RouterContext = React.createContext(null);
25
+ var RouterStateContext = React.createContext(null);
26
+ var OutletContext = React.createContext(null);
27
+ var MatchContext = React.createContext(null);
28
+ var MANAGED_HEAD_ATTRIBUTE = "data-richie-router-head";
29
+ var EMPTY_HEAD = { meta: [], links: [], scripts: [], styles: [] };
30
+ function isHeadTagReference(head) {
31
+ return typeof head === "string";
32
+ }
33
+ function routeHasRecord(value) {
34
+ return typeof value === "object" && value !== null;
35
+ }
36
+ function resolveParamsInput(input, previous) {
37
+ if (input === undefined) {
38
+ return previous;
39
+ }
40
+ return typeof input === "function" ? input(previous) : input;
41
+ }
42
+ function resolveSearchInput(input, previous) {
43
+ if (input === true) {
44
+ return previous;
45
+ }
46
+ if (input === undefined) {
47
+ return {};
48
+ }
49
+ return typeof input === "function" ? input(previous) : input;
50
+ }
51
+ function attachRouteApi(route) {
52
+ const api = route;
53
+ api.useParams = () => useRouteMatchByFullPath(route.fullPath).params;
54
+ api.useSearch = () => useRouteMatchByFullPath(route.fullPath).search;
55
+ api.useNavigate = () => useNavigate();
56
+ api.useMatch = () => useRouteMatchByFullPath(route.fullPath);
57
+ return api;
58
+ }
59
+ function createFileRoute(path) {
60
+ return function(options) {
61
+ const route = createRouteNode(path, options);
62
+ return attachRouteApi(route);
63
+ };
64
+ }
65
+ function createRootRoute(options) {
66
+ const route = createRouteNode("__root__", options, { isRoot: true });
67
+ return attachRouteApi(route);
68
+ }
69
+
70
+ class Router {
71
+ routeTree;
72
+ history;
73
+ options;
74
+ state;
75
+ routesByFullPath = new Map;
76
+ routesByTo = new Map;
77
+ listeners = new Set;
78
+ headCache = new Map;
79
+ parseSearch;
80
+ stringifySearch;
81
+ started = false;
82
+ unsubscribeHistory;
83
+ constructor(options) {
84
+ this.routeTree = options.routeTree;
85
+ this.options = options;
86
+ this.history = options.history ?? (typeof window === "undefined" ? createMemoryHistory() : createBrowserHistory());
87
+ this.parseSearch = options.parseSearch ?? defaultParseSearch;
88
+ this.stringifySearch = options.stringifySearch ?? defaultStringifySearch;
89
+ for (const route of collectRoutes(this.routeTree)) {
90
+ this.routesByFullPath.set(route.fullPath, route);
91
+ }
92
+ for (const branch of collectBranches(this.routeTree)) {
93
+ this.routesByTo.set(branch.leaf.to, branch.leaf);
94
+ }
95
+ const location = this.readLocation();
96
+ const initialHeadSnapshot = typeof window !== "undefined" ? window.__RICHIE_ROUTER_HEAD__ : undefined;
97
+ const initialHead = initialHeadSnapshot && initialHeadSnapshot.href === location.href ? initialHeadSnapshot.head : EMPTY_HEAD;
98
+ if (typeof window !== "undefined" && initialHeadSnapshot !== undefined) {
99
+ delete window.__RICHIE_ROUTER_HEAD__;
100
+ }
101
+ this.state = {
102
+ status: "loading",
103
+ location,
104
+ matches: this.buildMatches(location),
105
+ head: initialHead,
106
+ error: null
107
+ };
108
+ }
109
+ getSnapshot = () => this.state;
110
+ subscribe = (listener) => {
111
+ this.listeners.add(listener);
112
+ return () => {
113
+ this.listeners.delete(listener);
114
+ };
115
+ };
116
+ start() {
117
+ if (this.started) {
118
+ return;
119
+ }
120
+ this.started = true;
121
+ this.unsubscribeHistory = this.history.listen(() => {
122
+ this.handleHistoryChange();
123
+ });
124
+ if (this.state.status === "loading") {
125
+ this.load();
126
+ }
127
+ }
128
+ dispose() {
129
+ this.unsubscribeHistory?.();
130
+ this.unsubscribeHistory = undefined;
131
+ this.started = false;
132
+ }
133
+ async load(options) {
134
+ const nextLocation = this.readLocation();
135
+ await this.commitLocation(nextLocation, {
136
+ request: options?.request,
137
+ replace: true,
138
+ writeHistory: false
139
+ });
140
+ }
141
+ async navigate(options) {
142
+ const href = this.buildHref(options);
143
+ const location = createParsedLocation(href, options.state ?? null, this.parseSearch);
144
+ await this.commitLocation(location, {
145
+ replace: options.replace ?? false,
146
+ writeHistory: true,
147
+ resetScroll: options.resetScroll
148
+ });
149
+ }
150
+ async preloadRoute(options) {
151
+ const href = this.buildHref(options);
152
+ const location = createParsedLocation(href, options.state ?? null, this.parseSearch);
153
+ try {
154
+ await this.resolveLocation(location);
155
+ } catch {}
156
+ }
157
+ async invalidate() {
158
+ this.headCache.clear();
159
+ await this.load();
160
+ }
161
+ buildHref(options) {
162
+ const targetRoute = this.routesByTo.get(options.to) ?? null;
163
+ const fromMatch = options.from ? this.findMatchByTo(options.from) : null;
164
+ const previousParams = fromMatch?.params ?? {};
165
+ const previousSearch = fromMatch?.search ?? this.state.location.search;
166
+ const params = resolveParamsInput(options.params, previousParams);
167
+ const path = buildPath(options.to, params);
168
+ const searchValue = resolveSearchInput(options.search, previousSearch);
169
+ const search = this.stringifySearch(routeHasRecord(searchValue) ? searchValue : {});
170
+ const hash = options.hash ? `#${options.hash.replace(/^#/, "")}` : "";
171
+ const normalizedPath = this.applyTrailingSlash(path, targetRoute);
172
+ return `${normalizedPath}${search}${hash}`;
173
+ }
174
+ readLocation() {
175
+ const location = this.history.location;
176
+ return createParsedLocation(location.href, location.state, this.parseSearch);
177
+ }
178
+ applyTrailingSlash(pathname, route) {
179
+ const trailingSlash = this.options.trailingSlash ?? "preserve";
180
+ if (trailingSlash === "preserve") {
181
+ return pathname;
182
+ }
183
+ if (pathname === "/") {
184
+ return "/";
185
+ }
186
+ if (trailingSlash === "always") {
187
+ return pathname.endsWith("/") ? pathname : `${pathname}/`;
188
+ }
189
+ if (route && route.fullPath.endsWith("/") && route.to === pathname) {
190
+ return pathname;
191
+ }
192
+ return pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
193
+ }
194
+ notify() {
195
+ for (const listener of this.listeners) {
196
+ listener();
197
+ }
198
+ }
199
+ findMatchByTo(to) {
200
+ const route = this.routesByTo.get(to);
201
+ if (!route) {
202
+ return null;
203
+ }
204
+ return this.state.matches.find((match) => match.route.fullPath === route.fullPath) ?? null;
205
+ }
206
+ buildMatches(location) {
207
+ const matched = matchRouteTree(this.routeTree, location.pathname) ?? [];
208
+ const rawSearch = location.search;
209
+ let accumulatedSearch = { ...rawSearch };
210
+ return matched.map(({ route, params }) => {
211
+ const nextSearch = this.resolveSearch(route, rawSearch);
212
+ if (routeHasRecord(nextSearch)) {
213
+ accumulatedSearch = {
214
+ ...accumulatedSearch,
215
+ ...nextSearch
216
+ };
217
+ }
218
+ return {
219
+ id: route.fullPath,
220
+ pathname: location.pathname,
221
+ params,
222
+ route,
223
+ search: accumulatedSearch,
224
+ to: route.to
225
+ };
226
+ });
227
+ }
228
+ resolveSearch(route, rawSearch) {
229
+ const fromHeadTagSchema = route.searchSchema ? route.searchSchema.parse(rawSearch) : {};
230
+ const fromRoute = route.options.validateSearch ? route.options.validateSearch(rawSearch) : {};
231
+ if (routeHasRecord(fromHeadTagSchema) || routeHasRecord(fromRoute)) {
232
+ return {
233
+ ...routeHasRecord(fromHeadTagSchema) ? fromHeadTagSchema : {},
234
+ ...routeHasRecord(fromRoute) ? fromRoute : {}
235
+ };
236
+ }
237
+ return rawSearch;
238
+ }
239
+ async resolveLocation(location, options) {
240
+ const matched = matchRouteTree(this.routeTree, location.pathname);
241
+ if (!matched) {
242
+ throw notFound();
243
+ }
244
+ const rawSearch = location.search;
245
+ let accumulatedSearch = { ...rawSearch };
246
+ const matches = [];
247
+ for (const { route, params } of matched) {
248
+ const nextSearch = this.resolveSearch(route, rawSearch);
249
+ if (routeHasRecord(nextSearch)) {
250
+ accumulatedSearch = {
251
+ ...accumulatedSearch,
252
+ ...nextSearch
253
+ };
254
+ }
255
+ if (route.options.beforeLoad) {
256
+ await route.options.beforeLoad({
257
+ location,
258
+ params,
259
+ search: accumulatedSearch,
260
+ navigate: async (navigateOptions) => {
261
+ await this.navigate(navigateOptions);
262
+ },
263
+ cause: this.state.location.pathname === location.pathname ? "stay" : "enter"
264
+ });
265
+ }
266
+ matches.push({
267
+ id: route.fullPath,
268
+ pathname: location.pathname,
269
+ params,
270
+ route,
271
+ search: accumulatedSearch,
272
+ to: route.to
273
+ });
274
+ }
275
+ const head = await this.resolveLocationHead(matches, location, options?.request);
276
+ return { matches, head, error: null };
277
+ }
278
+ async resolveLocationHead(matches, location, request) {
279
+ const resolvedHeadByRoute = new Map;
280
+ for (const match of matches) {
281
+ const headOption = match.route.options.head;
282
+ if (!isHeadTagReference(headOption)) {
283
+ continue;
284
+ }
285
+ resolvedHeadByRoute.set(match.route.fullPath, await this.loadRouteHead(match.route, headOption, match.params, match.search, location, request));
286
+ }
287
+ return resolveHeadConfig(matches, resolvedHeadByRoute);
288
+ }
289
+ async loadRouteHead(route, headTagName, params, search, location, request) {
290
+ const cacheKey = JSON.stringify({
291
+ headTagName,
292
+ params,
293
+ search
294
+ });
295
+ const cached = this.headCache.get(cacheKey);
296
+ if (cached && cached.expiresAt > Date.now()) {
297
+ return cached.head;
298
+ }
299
+ const response = this.options.loadRouteHead !== undefined ? await this.options.loadRouteHead({
300
+ route,
301
+ headTagName,
302
+ params,
303
+ search,
304
+ location,
305
+ request
306
+ }) : await this.fetchRouteHead(route, headTagName, params, search);
307
+ this.headCache.set(cacheKey, {
308
+ head: response.head,
309
+ expiresAt: Date.now() + (response.staleTime ?? 0)
310
+ });
311
+ return response.head;
312
+ }
313
+ async fetchRouteHead(route, headTagName, params, search) {
314
+ const basePath = this.options.headBasePath ?? "/head-api";
315
+ const searchParams = new URLSearchParams({
316
+ routeId: route.fullPath,
317
+ params: JSON.stringify(params),
318
+ search: JSON.stringify(search)
319
+ });
320
+ const response = await fetch(`${basePath}/${encodeURIComponent(headTagName)}?${searchParams.toString()}`);
321
+ if (!response.ok) {
322
+ if (response.status === 404) {
323
+ throw notFound();
324
+ }
325
+ throw new Error(`Failed to resolve head tag "${headTagName}" for route "${route.fullPath}"`);
326
+ }
327
+ return await response.json();
328
+ }
329
+ async commitLocation(location, options) {
330
+ this.state = {
331
+ ...this.state,
332
+ status: "loading",
333
+ location
334
+ };
335
+ this.notify();
336
+ try {
337
+ const resolved = await this.resolveLocation(location, {
338
+ request: options.request
339
+ });
340
+ if (options.writeHistory) {
341
+ if (options.replace) {
342
+ this.history.replace(location.href, location.state);
343
+ } else {
344
+ this.history.push(location.href, location.state);
345
+ }
346
+ }
347
+ this.state = {
348
+ status: "idle",
349
+ location,
350
+ matches: resolved.matches,
351
+ head: resolved.head,
352
+ error: resolved.error
353
+ };
354
+ this.notify();
355
+ this.restoreScroll(options.resetScroll);
356
+ } catch (error) {
357
+ if (isRedirect(error)) {
358
+ await this.navigate({
359
+ ...error.options,
360
+ replace: error.options.replace ?? true
361
+ });
362
+ return;
363
+ }
364
+ const errorMatches = this.buildMatches(location);
365
+ if (options.writeHistory) {
366
+ if (options.replace) {
367
+ this.history.replace(location.href, location.state);
368
+ } else {
369
+ this.history.push(location.href, location.state);
370
+ }
371
+ }
372
+ this.state = {
373
+ status: "idle",
374
+ location,
375
+ matches: errorMatches,
376
+ head: resolveHeadConfig(errorMatches),
377
+ error
378
+ };
379
+ this.notify();
380
+ }
381
+ }
382
+ restoreScroll(resetScroll) {
383
+ if (typeof window === "undefined") {
384
+ return;
385
+ }
386
+ if (!this.options.scrollRestoration || resetScroll === false) {
387
+ return;
388
+ }
389
+ const selectors = this.options.scrollToTopSelectors ?? [];
390
+ if (selectors.length === 0) {
391
+ window.scrollTo({ top: 0, left: 0, behavior: "instant" });
392
+ return;
393
+ }
394
+ for (const selector of selectors) {
395
+ const element = document.querySelector(selector);
396
+ if (element instanceof HTMLElement) {
397
+ element.scrollTo({ top: 0, left: 0, behavior: "instant" });
398
+ }
399
+ }
400
+ }
401
+ async handleHistoryChange() {
402
+ const nextLocation = this.readLocation();
403
+ await this.commitLocation(nextLocation, {
404
+ replace: true,
405
+ writeHistory: false
406
+ });
407
+ }
408
+ }
409
+ function createRouter(options) {
410
+ return new Router(options);
411
+ }
412
+ function useRouterContext() {
413
+ const router = React.useContext(RouterContext);
414
+ if (!router) {
415
+ throw new Error("Richie Router hooks must be used inside <RouterProvider>.");
416
+ }
417
+ return router;
418
+ }
419
+ function useRouterStateContext() {
420
+ const state = React.useContext(RouterStateContext);
421
+ if (!state) {
422
+ throw new Error("Richie Router hooks must be used inside <RouterProvider>.");
423
+ }
424
+ return state;
425
+ }
426
+ function useRouteMatchByFullPath(fullPath) {
427
+ const state = useRouterStateContext();
428
+ const currentRenderedMatch = React.useContext(MatchContext);
429
+ return state.matches.find((match) => match.route.fullPath === fullPath) ?? (currentRenderedMatch?.route.fullPath === fullPath ? currentRenderedMatch : null) ?? (() => {
430
+ throw new Error(`No active match found for "${fullPath}".`);
431
+ })();
432
+ }
433
+ function resolveHookMatch(from) {
434
+ const router = useRouterContext();
435
+ const state = useRouterStateContext();
436
+ const currentRenderedMatch = React.useContext(MatchContext);
437
+ if (!from) {
438
+ return currentRenderedMatch ?? state.matches.at(-1) ?? (() => {
439
+ throw new Error("No active route match is available.");
440
+ })();
441
+ }
442
+ const targetRoute = router.routesByTo.get(from);
443
+ if (!targetRoute) {
444
+ throw new Error(`Unknown route path "${from}".`);
445
+ }
446
+ return state.matches.find((match) => match.route.fullPath === targetRoute.fullPath) ?? (() => {
447
+ throw new Error(`The route "${from}" is not part of the current match set.`);
448
+ })();
449
+ }
450
+ function createManagedHeadElements(head) {
451
+ if (typeof document === "undefined") {
452
+ return [];
453
+ }
454
+ const elements = [];
455
+ const managed = (element) => {
456
+ element.setAttribute(MANAGED_HEAD_ATTRIBUTE, "true");
457
+ return element;
458
+ };
459
+ for (const meta of head.meta ?? []) {
460
+ if ("title" in meta) {
461
+ const title = managed(document.createElement("title"));
462
+ title.textContent = meta.title;
463
+ elements.push(title);
464
+ continue;
465
+ }
466
+ const tag = managed(document.createElement("meta"));
467
+ if ("charset" in meta) {
468
+ tag.setAttribute("charset", meta.charset);
469
+ } else if ("name" in meta) {
470
+ tag.setAttribute("name", meta.name);
471
+ tag.setAttribute("content", meta.content);
472
+ } else if ("property" in meta) {
473
+ tag.setAttribute("property", meta.property);
474
+ tag.setAttribute("content", meta.content);
475
+ } else {
476
+ tag.setAttribute("http-equiv", meta.httpEquiv);
477
+ tag.setAttribute("content", meta.content);
478
+ }
479
+ elements.push(tag);
480
+ }
481
+ for (const link of head.links ?? []) {
482
+ const tag = managed(document.createElement("link"));
483
+ tag.setAttribute("rel", link.rel);
484
+ tag.setAttribute("href", link.href);
485
+ if (link.type)
486
+ tag.setAttribute("type", link.type);
487
+ if (link.media)
488
+ tag.setAttribute("media", link.media);
489
+ if (link.sizes)
490
+ tag.setAttribute("sizes", link.sizes);
491
+ if (link.crossorigin)
492
+ tag.setAttribute("crossorigin", link.crossorigin);
493
+ elements.push(tag);
494
+ }
495
+ for (const style of head.styles ?? []) {
496
+ const tag = managed(document.createElement("style"));
497
+ if (style.media)
498
+ tag.setAttribute("media", style.media);
499
+ tag.textContent = style.children;
500
+ elements.push(tag);
501
+ }
502
+ for (const script of head.scripts ?? []) {
503
+ const tag = managed(document.createElement("script"));
504
+ if (script.src)
505
+ tag.setAttribute("src", script.src);
506
+ if (script.type)
507
+ tag.setAttribute("type", script.type);
508
+ if (script.async)
509
+ tag.async = true;
510
+ if (script.defer)
511
+ tag.defer = true;
512
+ if (script.children)
513
+ tag.textContent = script.children;
514
+ elements.push(tag);
515
+ }
516
+ return elements;
517
+ }
518
+ function reconcileDocumentHead(head) {
519
+ if (typeof document === "undefined") {
520
+ return;
521
+ }
522
+ for (const element of Array.from(document.head.querySelectorAll(`[${MANAGED_HEAD_ATTRIBUTE}]`))) {
523
+ element.remove();
524
+ }
525
+ const elements = createManagedHeadElements(head);
526
+ for (const element of elements) {
527
+ document.head.appendChild(element);
528
+ }
529
+ }
530
+ function RenderMatches({ matches, index }) {
531
+ const match = matches[index];
532
+ if (!match) {
533
+ return null;
534
+ }
535
+ const Component = match.route.options.component;
536
+ const outlet = /* @__PURE__ */ jsxDEV(RenderMatches, {
537
+ matches,
538
+ index: index + 1
539
+ }, undefined, false, undefined, this);
540
+ return /* @__PURE__ */ jsxDEV(MatchContext.Provider, {
541
+ value: match,
542
+ children: /* @__PURE__ */ jsxDEV(OutletContext.Provider, {
543
+ value: outlet,
544
+ children: Component ? /* @__PURE__ */ jsxDEV(Component, {}, undefined, false, undefined, this) : outlet
545
+ }, undefined, false, undefined, this)
546
+ }, undefined, false, undefined, this);
547
+ }
548
+ function renderError(error, matches, router) {
549
+ const reversed = [...matches].reverse();
550
+ if (isNotFound(error)) {
551
+ const NotFoundComponent = reversed.find((match) => match.route.options.notFoundComponent)?.route.options.notFoundComponent ?? router.options.defaultNotFoundComponent;
552
+ if (NotFoundComponent) {
553
+ return React.createElement(NotFoundComponent);
554
+ }
555
+ return /* @__PURE__ */ jsxDEV("div", {
556
+ children: "Not Found"
557
+ }, undefined, false, undefined, this);
558
+ }
559
+ const ErrorComponent = reversed.find((match) => match.route.options.errorComponent)?.route.options.errorComponent ?? router.options.defaultErrorComponent;
560
+ if (ErrorComponent) {
561
+ return React.createElement(ErrorComponent, {
562
+ error,
563
+ reset: () => {
564
+ router.invalidate();
565
+ }
566
+ });
567
+ }
568
+ return /* @__PURE__ */ jsxDEV("pre", {
569
+ children: error instanceof Error ? error.message : "Unknown routing error"
570
+ }, undefined, false, undefined, this);
571
+ }
572
+ function RouterProvider({ router }) {
573
+ const snapshot = React.useSyncExternalStore(router.subscribe, router.getSnapshot, router.getSnapshot);
574
+ React.useEffect(() => {
575
+ router.start();
576
+ return () => {
577
+ router.dispose();
578
+ };
579
+ }, [router]);
580
+ React.useEffect(() => {
581
+ reconcileDocumentHead(snapshot.head);
582
+ }, [snapshot.head]);
583
+ const content = snapshot.error ? renderError(snapshot.error, snapshot.matches, router) : /* @__PURE__ */ jsxDEV(RenderMatches, {
584
+ matches: snapshot.matches,
585
+ index: 0
586
+ }, undefined, false, undefined, this);
587
+ return /* @__PURE__ */ jsxDEV(RouterContext.Provider, {
588
+ value: router,
589
+ children: /* @__PURE__ */ jsxDEV(RouterStateContext.Provider, {
590
+ value: snapshot,
591
+ children: content
592
+ }, undefined, false, undefined, this)
593
+ }, undefined, false, undefined, this);
594
+ }
595
+ function Outlet() {
596
+ return React.useContext(OutletContext);
597
+ }
598
+ function useRouter() {
599
+ return useRouterContext();
600
+ }
601
+ function useMatches() {
602
+ return useRouterStateContext().matches;
603
+ }
604
+ function useMatch(options) {
605
+ return resolveHookMatch(options?.from);
606
+ }
607
+ function useParams(options) {
608
+ return resolveHookMatch(options?.from).params;
609
+ }
610
+ function useSearch(options) {
611
+ return resolveHookMatch(options?.from).search;
612
+ }
613
+ function useNavigate() {
614
+ const router = useRouterContext();
615
+ return React.useCallback(async (options) => {
616
+ await router.navigate(options);
617
+ }, [router]);
618
+ }
619
+ function useLocation() {
620
+ return useRouterStateContext().location;
621
+ }
622
+ function useRouterState(options) {
623
+ const router = useRouterContext();
624
+ return React.useSyncExternalStore(router.subscribe, () => options.select(router.getSnapshot()), () => options.select(router.getSnapshot()));
625
+ }
626
+ function useBlocker(options) {
627
+ const current = useLocation();
628
+ const [next, setNext] = React.useState(null);
629
+ React.useEffect(() => {
630
+ if (!options.enableBeforeUnload || typeof window === "undefined") {
631
+ return;
632
+ }
633
+ const listener = (event) => {
634
+ if (!options.shouldBlockFn?.({ current, next })) {
635
+ return;
636
+ }
637
+ event.preventDefault();
638
+ event.returnValue = "";
639
+ };
640
+ window.addEventListener("beforeunload", listener);
641
+ return () => window.removeEventListener("beforeunload", listener);
642
+ }, [current, next, options]);
643
+ return {
644
+ status: next ? "blocked" : "idle",
645
+ next,
646
+ proceed() {
647
+ setNext(null);
648
+ },
649
+ reset() {
650
+ setNext(null);
651
+ }
652
+ };
653
+ }
654
+ function Block() {
655
+ return null;
656
+ }
657
+ function useElementScrollRestoration() {
658
+ return {
659
+ ref: () => {}
660
+ };
661
+ }
662
+ function useResolvedLink(props) {
663
+ const router = useRouterContext();
664
+ const href = router.buildHref(props);
665
+ const location = useLocation();
666
+ const pathOnly = href.split(/[?#]/u)[0] ?? href;
667
+ const isActive = pathOnly === location.pathname;
668
+ return { href, isActive, router };
669
+ }
670
+ var LinkComponent = React.forwardRef(function LinkInner(props, ref) {
671
+ const allProps = props;
672
+ const {
673
+ to,
674
+ from,
675
+ params,
676
+ search,
677
+ hash,
678
+ replace,
679
+ resetScroll,
680
+ state,
681
+ mask,
682
+ ignoreBlocker,
683
+ activeProps,
684
+ children,
685
+ onClick,
686
+ onMouseEnter,
687
+ onFocus,
688
+ preload,
689
+ ...anchorProps
690
+ } = allProps;
691
+ const navigation = {
692
+ to,
693
+ from,
694
+ params,
695
+ search,
696
+ hash,
697
+ replace,
698
+ resetScroll,
699
+ state,
700
+ mask,
701
+ ignoreBlocker
702
+ };
703
+ const { href, isActive, router } = useResolvedLink(navigation);
704
+ const preloadMode = preload ?? router.options.defaultPreload;
705
+ const preloadDelay = router.options.defaultPreloadDelay ?? 50;
706
+ const preloadTimeout = React.useRef(null);
707
+ React.useEffect(() => {
708
+ if (preloadMode !== "render") {
709
+ return;
710
+ }
711
+ router.preloadRoute(navigation);
712
+ }, [navigation, preloadMode, router]);
713
+ const schedulePreload = React.useCallback(() => {
714
+ if (preloadMode !== "intent") {
715
+ return;
716
+ }
717
+ preloadTimeout.current = window.setTimeout(() => {
718
+ router.preloadRoute(navigation);
719
+ }, preloadDelay);
720
+ }, [navigation, preloadDelay, preloadMode, router]);
721
+ const cancelPreload = React.useCallback(() => {
722
+ if (preloadTimeout.current !== null) {
723
+ window.clearTimeout(preloadTimeout.current);
724
+ preloadTimeout.current = null;
725
+ }
726
+ }, []);
727
+ const renderedChildren = typeof children === "function" ? children({ isActive }) : children;
728
+ return /* @__PURE__ */ jsxDEV("a", {
729
+ ...anchorProps,
730
+ ...isActive ? activeProps : undefined,
731
+ ref,
732
+ href,
733
+ onClick: (event) => {
734
+ onClick?.(event);
735
+ if (event.defaultPrevented || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0) {
736
+ return;
737
+ }
738
+ event.preventDefault();
739
+ router.navigate(navigation);
740
+ },
741
+ onMouseEnter: (event) => {
742
+ onMouseEnter?.(event);
743
+ if (typeof window !== "undefined") {
744
+ schedulePreload();
745
+ }
746
+ },
747
+ onMouseLeave: cancelPreload,
748
+ onFocus: (event) => {
749
+ onFocus?.(event);
750
+ if (typeof window !== "undefined") {
751
+ schedulePreload();
752
+ }
753
+ },
754
+ onBlur: cancelPreload,
755
+ children: renderedChildren
756
+ }, undefined, false, undefined, this);
757
+ });
758
+ var Link = LinkComponent;
759
+ function createLink(Component) {
760
+ return function CreatedLink(props) {
761
+ const allProps = props;
762
+ const {
763
+ to,
764
+ from,
765
+ params,
766
+ search,
767
+ hash,
768
+ replace,
769
+ resetScroll,
770
+ state,
771
+ mask,
772
+ ignoreBlocker,
773
+ activeProps,
774
+ children,
775
+ preload,
776
+ ...componentProps
777
+ } = allProps;
778
+ const navigation = {
779
+ to,
780
+ from,
781
+ params,
782
+ search,
783
+ hash,
784
+ replace,
785
+ resetScroll,
786
+ state,
787
+ mask,
788
+ ignoreBlocker
789
+ };
790
+ const { href, isActive, router } = useResolvedLink(navigation);
791
+ const renderedChildren = typeof children === "function" ? children({ isActive }) : children;
792
+ React.useEffect(() => {
793
+ if (preload !== "render") {
794
+ return;
795
+ }
796
+ router.preloadRoute(navigation);
797
+ }, [navigation, preload, router]);
798
+ return /* @__PURE__ */ jsxDEV(Component, {
799
+ ...componentProps,
800
+ ...isActive ? activeProps : undefined,
801
+ href,
802
+ children: renderedChildren
803
+ }, undefined, false, undefined, this);
804
+ };
805
+ }
806
+ function linkOptions(options) {
807
+ return options;
808
+ }
809
+ function getRouteApi(to) {
810
+ return {
811
+ useParams: () => useParams({ from: to }),
812
+ useSearch: () => useSearch({ from: to }),
813
+ useMatch: () => useMatch({ from: to })
814
+ };
815
+ }
816
+ function createRouteMask(mask) {
817
+ return mask;
818
+ }
819
+ export {
820
+ useSearch,
821
+ useRouterState,
822
+ useRouter,
823
+ useParams,
824
+ useNavigate,
825
+ useMatches,
826
+ useMatch,
827
+ useLocation,
828
+ useElementScrollRestoration,
829
+ useBlocker,
830
+ redirect,
831
+ notFound,
832
+ linkOptions,
833
+ isRedirect,
834
+ isNotFound,
835
+ getRouteApi,
836
+ createRouter,
837
+ createRouteMask,
838
+ createRootRoute,
839
+ createMemoryHistory,
840
+ createLink,
841
+ createHashHistory,
842
+ createFileRoute,
843
+ createBrowserHistory,
844
+ RouterProvider,
845
+ Router,
846
+ Outlet,
847
+ Link,
848
+ Block
849
+ };