@richie-router/react 0.1.2 → 0.1.4

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.
@@ -69,6 +69,7 @@ __export(exports_router, {
69
69
  useParams: () => useParams,
70
70
  useNavigate: () => useNavigate,
71
71
  useMatches: () => useMatches,
72
+ useMatchRoute: () => useMatchRoute,
72
73
  useMatch: () => useMatch,
73
74
  useLocation: () => useLocation,
74
75
  useElementScrollRestoration: () => useElementScrollRestoration,
@@ -97,16 +98,89 @@ module.exports = __toCommonJS(exports_router);
97
98
  var import_react = __toESM(require("react"));
98
99
  var import_core = require("@richie-router/core");
99
100
  var import_history = require("./history.cjs");
100
- var jsx_dev_runtime = require("react/jsx-dev-runtime");
101
+ var jsx_runtime = require("react/jsx-runtime");
101
102
  var RouterContext = import_react.default.createContext(null);
102
103
  var RouterStateContext = import_react.default.createContext(null);
103
104
  var OutletContext = import_react.default.createContext(null);
104
105
  var MatchContext = import_react.default.createContext(null);
105
106
  var MANAGED_HEAD_ATTRIBUTE = "data-richie-router-head";
106
- var EMPTY_HEAD = { meta: [], links: [], scripts: [], styles: [] };
107
+ var EMPTY_HEAD = [];
108
+ function ensureLeadingSlash(value) {
109
+ return value.startsWith("/") ? value : `/${value}`;
110
+ }
111
+ function normalizeBasePath(basePath) {
112
+ if (!basePath) {
113
+ return "";
114
+ }
115
+ const trimmed = basePath.trim();
116
+ if (trimmed === "" || trimmed === "/") {
117
+ return "";
118
+ }
119
+ const normalized = ensureLeadingSlash(trimmed).replace(/\/+$/u, "");
120
+ return normalized === "/" ? "" : normalized;
121
+ }
122
+ function parseHref(href) {
123
+ if (href.startsWith("http://") || href.startsWith("https://")) {
124
+ return new URL(href);
125
+ }
126
+ return new URL(ensureLeadingSlash(href), "http://richie-router.local");
127
+ }
128
+ function stripBasePathFromPathname(pathname, basePath) {
129
+ if (!basePath) {
130
+ return pathname;
131
+ }
132
+ if (pathname === basePath) {
133
+ return "/";
134
+ }
135
+ return pathname.startsWith(`${basePath}/`) ? pathname.slice(basePath.length) || "/" : pathname;
136
+ }
137
+ function stripBasePathFromHref(href, basePath) {
138
+ const normalizedBasePath = normalizeBasePath(basePath);
139
+ if (!normalizedBasePath) {
140
+ return href;
141
+ }
142
+ const url = parseHref(href);
143
+ return `${stripBasePathFromPathname(url.pathname, normalizedBasePath)}${url.search}${url.hash}`;
144
+ }
145
+ function prependBasePathToPathname(pathname, basePath) {
146
+ if (!basePath) {
147
+ return pathname;
148
+ }
149
+ return pathname === "/" ? basePath : `${basePath}${ensureLeadingSlash(pathname)}`;
150
+ }
151
+ function prependBasePathToHref(href, basePath) {
152
+ const normalizedBasePath = normalizeBasePath(basePath);
153
+ if (!normalizedBasePath) {
154
+ return href;
155
+ }
156
+ const url = parseHref(href);
157
+ return `${prependBasePathToPathname(url.pathname, normalizedBasePath)}${url.search}${url.hash}`;
158
+ }
107
159
  function routeHasRecord(value) {
108
160
  return typeof value === "object" && value !== null;
109
161
  }
162
+ function isDeepInclusiveMatch(expected, actual) {
163
+ if (Array.isArray(expected)) {
164
+ if (!Array.isArray(actual) || expected.length !== actual.length) {
165
+ return false;
166
+ }
167
+ return expected.every((value, index) => isDeepInclusiveMatch(value, actual[index]));
168
+ }
169
+ if (routeHasRecord(expected)) {
170
+ if (!routeHasRecord(actual)) {
171
+ return false;
172
+ }
173
+ return Object.entries(expected).every(([key, value]) => (key in actual) && isDeepInclusiveMatch(value, actual[key]));
174
+ }
175
+ return Object.is(expected, actual);
176
+ }
177
+ function routeHasInlineHead(route) {
178
+ const headOption = route.options.head;
179
+ return Boolean(headOption && typeof headOption !== "string");
180
+ }
181
+ function matchesHaveInlineHead(matches) {
182
+ return matches.some((match) => routeHasInlineHead(match.route));
183
+ }
110
184
  function resolveParamsInput(input, previous) {
111
185
  if (input === undefined) {
112
186
  return previous;
@@ -152,6 +226,8 @@ class Router {
152
226
  headCache = new Map;
153
227
  parseSearch;
154
228
  stringifySearch;
229
+ basePath;
230
+ initialHeadSnapshot;
155
231
  started = false;
156
232
  unsubscribeHistory;
157
233
  constructor(options) {
@@ -160,6 +236,7 @@ class Router {
160
236
  this.history = options.history ?? (typeof window === "undefined" ? import_history.createMemoryHistory() : import_history.createBrowserHistory());
161
237
  this.parseSearch = options.parseSearch ?? import_core.defaultParseSearch;
162
238
  this.stringifySearch = options.stringifySearch ?? import_core.defaultStringifySearch;
239
+ this.basePath = normalizeBasePath(options.basePath);
163
240
  for (const route of import_core.collectRoutes(this.routeTree)) {
164
241
  this.routesByFullPath.set(route.fullPath, route);
165
242
  }
@@ -167,15 +244,22 @@ class Router {
167
244
  this.routesByTo.set(branch.leaf.to, branch.leaf);
168
245
  }
169
246
  const location = this.readLocation();
247
+ const initialMatches = this.buildMatches(location);
248
+ const rawHistoryHref = this.history.location.href;
170
249
  const initialHeadSnapshot = typeof window !== "undefined" ? window.__RICHIE_ROUTER_HEAD__ : undefined;
171
- const initialHead = initialHeadSnapshot && initialHeadSnapshot.href === location.href ? initialHeadSnapshot.head : EMPTY_HEAD;
250
+ const hasMatchingInitialHeadSnapshot = Boolean(initialHeadSnapshot && (initialHeadSnapshot.href === location.href || initialHeadSnapshot.href === rawHistoryHref));
251
+ const initialHead = hasMatchingInitialHeadSnapshot && initialHeadSnapshot ? initialHeadSnapshot.head : EMPTY_HEAD;
252
+ if (hasMatchingInitialHeadSnapshot && this.options.loadRouteHead === undefined && initialHeadSnapshot?.routeHeads !== undefined) {
253
+ this.seedHeadCacheFromRouteHeads(initialMatches, initialHeadSnapshot.routeHeads);
254
+ }
172
255
  if (typeof window !== "undefined" && initialHeadSnapshot !== undefined) {
173
256
  delete window.__RICHIE_ROUTER_HEAD__;
174
257
  }
258
+ this.initialHeadSnapshot = hasMatchingInitialHeadSnapshot ? initialHeadSnapshot : undefined;
175
259
  this.state = {
176
260
  status: "loading",
177
261
  location,
178
- matches: this.buildMatches(location),
262
+ matches: initialMatches,
179
263
  head: initialHead,
180
264
  error: null
181
265
  };
@@ -206,15 +290,17 @@ class Router {
206
290
  }
207
291
  async load(options) {
208
292
  const nextLocation = this.readLocation();
293
+ const initialHeadSnapshot = this.initialHeadSnapshot?.href === nextLocation.href ? this.initialHeadSnapshot : undefined;
294
+ this.initialHeadSnapshot = undefined;
209
295
  await this.commitLocation(nextLocation, {
210
296
  request: options?.request,
211
297
  replace: true,
212
- writeHistory: false
298
+ writeHistory: false,
299
+ initialHeadSnapshot
213
300
  });
214
301
  }
215
302
  async navigate(options) {
216
- const href = this.buildHref(options);
217
- const location = import_core.createParsedLocation(href, options.state ?? null, this.parseSearch);
303
+ const location = this.buildLocation(options);
218
304
  await this.commitLocation(location, {
219
305
  replace: options.replace ?? false,
220
306
  writeHistory: true,
@@ -222,8 +308,7 @@ class Router {
222
308
  });
223
309
  }
224
310
  async preloadRoute(options) {
225
- const href = this.buildHref(options);
226
- const location = import_core.createParsedLocation(href, options.state ?? null, this.parseSearch);
311
+ const location = this.buildLocation(options);
227
312
  try {
228
313
  await this.resolveLocation(location);
229
314
  } catch {}
@@ -233,6 +318,12 @@ class Router {
233
318
  await this.load();
234
319
  }
235
320
  buildHref(options) {
321
+ return prependBasePathToHref(this.buildLocationHref(options), this.basePath);
322
+ }
323
+ buildLocation(options) {
324
+ return import_core.createParsedLocation(this.buildLocationHref(options), options.state ?? null, this.parseSearch);
325
+ }
326
+ buildLocationHref(options) {
236
327
  const targetRoute = this.routesByTo.get(options.to) ?? null;
237
328
  const fromMatch = options.from ? this.findMatchByTo(options.from) : null;
238
329
  const previousParams = fromMatch?.params ?? {};
@@ -247,7 +338,7 @@ class Router {
247
338
  }
248
339
  readLocation() {
249
340
  const location = this.history.location;
250
- return import_core.createParsedLocation(location.href, location.state, this.parseSearch);
341
+ return import_core.createParsedLocation(stripBasePathFromHref(location.href, this.basePath), location.state, this.parseSearch);
251
342
  }
252
343
  applyTrailingSlash(pathname, route) {
253
344
  const trailingSlash = this.options.trailingSlash ?? "preserve";
@@ -342,29 +433,102 @@ class Router {
342
433
  to: route.to
343
434
  });
344
435
  }
345
- const head = await this.resolveLocationHead(matches, location, options?.request);
436
+ const head = await this.resolveLocationHead(matches, location, options?.request, options?.initialHeadSnapshot);
346
437
  return { matches, head, error: null };
347
438
  }
348
- async resolveLocationHead(matches, location, request) {
439
+ async resolveLocationHead(matches, location, request, initialHeadSnapshot) {
349
440
  const resolvedHeadByRoute = new Map;
350
- for (const match of matches) {
351
- if (!match.route.serverHead) {
441
+ const serverMatches = matches.filter((match) => match.route.serverHead);
442
+ if (serverMatches.length === 0) {
443
+ return import_core.resolveHeadConfig(matches, resolvedHeadByRoute);
444
+ }
445
+ if (this.options.loadRouteHead !== undefined) {
446
+ for (const match of serverMatches) {
447
+ resolvedHeadByRoute.set(match.route.fullPath, await this.loadRouteHead(match.route, match.params, match.search, location, request));
448
+ }
449
+ return import_core.resolveHeadConfig(matches, resolvedHeadByRoute);
450
+ }
451
+ if (initialHeadSnapshot?.href === location.href && initialHeadSnapshot.routeHeads === undefined && !matchesHaveInlineHead(matches)) {
452
+ return initialHeadSnapshot.head;
453
+ }
454
+ let needsDocumentHeadFetch = false;
455
+ for (const match of serverMatches) {
456
+ const cachedHead = this.getCachedRouteHead(match.route.fullPath, match.params, match.search);
457
+ if (cachedHead) {
458
+ resolvedHeadByRoute.set(match.route.fullPath, cachedHead);
352
459
  continue;
353
460
  }
354
- resolvedHeadByRoute.set(match.route.fullPath, await this.loadRouteHead(match.route, match.params, match.search, location, request));
461
+ needsDocumentHeadFetch = true;
462
+ }
463
+ if (!needsDocumentHeadFetch) {
464
+ return import_core.resolveHeadConfig(matches, resolvedHeadByRoute);
465
+ }
466
+ const documentHead = await this.fetchDocumentHead(location);
467
+ if ((documentHead.routeHeads?.length ?? 0) === 0 && !matchesHaveInlineHead(matches)) {
468
+ return documentHead.head;
469
+ }
470
+ const routeHeadsById = this.cacheRouteHeadsFromDocument(matches, documentHead.routeHeads ?? []);
471
+ for (const match of serverMatches) {
472
+ const responseHead = routeHeadsById.get(match.route.fullPath);
473
+ if (responseHead) {
474
+ resolvedHeadByRoute.set(match.route.fullPath, responseHead);
475
+ continue;
476
+ }
477
+ const cachedHead = this.getCachedRouteHead(match.route.fullPath, match.params, match.search);
478
+ if (cachedHead) {
479
+ resolvedHeadByRoute.set(match.route.fullPath, cachedHead);
480
+ continue;
481
+ }
482
+ const response = await this.fetchRouteHead(match.route, match.params, match.search);
483
+ this.setRouteHeadCache(match.route.fullPath, match.params, match.search, response);
484
+ resolvedHeadByRoute.set(match.route.fullPath, response.head);
355
485
  }
356
486
  return import_core.resolveHeadConfig(matches, resolvedHeadByRoute);
357
487
  }
358
- async loadRouteHead(route, params, search, location, request) {
359
- const cacheKey = JSON.stringify({
360
- routeId: route.fullPath,
488
+ getRouteHeadCacheKey(routeId, params, search) {
489
+ return JSON.stringify({
490
+ routeId,
361
491
  params,
362
492
  search
363
493
  });
364
- const cached = this.headCache.get(cacheKey);
494
+ }
495
+ getCachedRouteHead(routeId, params, search) {
496
+ const cached = this.headCache.get(this.getRouteHeadCacheKey(routeId, params, search));
365
497
  if (cached && cached.expiresAt > Date.now()) {
366
498
  return cached.head;
367
499
  }
500
+ return null;
501
+ }
502
+ setRouteHeadCache(routeId, params, search, response) {
503
+ this.headCache.set(this.getRouteHeadCacheKey(routeId, params, search), {
504
+ head: response.head,
505
+ expiresAt: Date.now() + (response.staleTime ?? 0)
506
+ });
507
+ }
508
+ seedHeadCacheFromRouteHeads(matches, routeHeads) {
509
+ this.cacheRouteHeadsFromDocument(matches, routeHeads);
510
+ }
511
+ cacheRouteHeadsFromDocument(matches, routeHeads) {
512
+ const routeHeadsById = new Map(routeHeads.map((entry) => [entry.routeId, entry]));
513
+ const resolvedHeadByRoute = new Map;
514
+ for (const match of matches) {
515
+ if (!match.route.serverHead) {
516
+ continue;
517
+ }
518
+ const entry = routeHeadsById.get(match.route.fullPath);
519
+ if (!entry) {
520
+ continue;
521
+ }
522
+ this.setRouteHeadCache(match.route.fullPath, match.params, match.search, entry);
523
+ resolvedHeadByRoute.set(match.route.fullPath, entry.head);
524
+ }
525
+ return resolvedHeadByRoute;
526
+ }
527
+ async loadRouteHead(route, params, search, location, request) {
528
+ const cachedHead = this.getCachedRouteHead(route.fullPath, params, search);
529
+ if (cachedHead) {
530
+ return cachedHead;
531
+ }
368
532
  const response = this.options.loadRouteHead !== undefined ? await this.options.loadRouteHead({
369
533
  route,
370
534
  routeId: route.fullPath,
@@ -373,14 +537,11 @@ class Router {
373
537
  location,
374
538
  request
375
539
  }) : await this.fetchRouteHead(route, params, search);
376
- this.headCache.set(cacheKey, {
377
- head: response.head,
378
- expiresAt: Date.now() + (response.staleTime ?? 0)
379
- });
540
+ this.setRouteHeadCache(route.fullPath, params, search, response);
380
541
  return response.head;
381
542
  }
382
543
  async fetchRouteHead(route, params, search) {
383
- const basePath = this.options.headBasePath ?? "/head-api";
544
+ const basePath = this.options.headBasePath ?? prependBasePathToHref("/head-api", this.basePath);
384
545
  const searchParams = new URLSearchParams({
385
546
  routeId: route.fullPath,
386
547
  params: JSON.stringify(params),
@@ -395,6 +556,20 @@ class Router {
395
556
  }
396
557
  return await response.json();
397
558
  }
559
+ async fetchDocumentHead(location) {
560
+ const basePath = this.options.headBasePath ?? prependBasePathToHref("/head-api", this.basePath);
561
+ const searchParams = new URLSearchParams({
562
+ href: prependBasePathToHref(location.href, this.basePath)
563
+ });
564
+ const response = await fetch(`${basePath}?${searchParams.toString()}`);
565
+ if (!response.ok) {
566
+ if (response.status === 404) {
567
+ throw import_core.notFound();
568
+ }
569
+ throw new Error(`Failed to resolve server head for location "${location.href}"`);
570
+ }
571
+ return await response.json();
572
+ }
398
573
  async commitLocation(location, options) {
399
574
  this.state = {
400
575
  ...this.state,
@@ -404,13 +579,15 @@ class Router {
404
579
  this.notify();
405
580
  try {
406
581
  const resolved = await this.resolveLocation(location, {
407
- request: options.request
582
+ request: options.request,
583
+ initialHeadSnapshot: options.initialHeadSnapshot
408
584
  });
585
+ const historyHref = prependBasePathToHref(location.href, this.basePath);
409
586
  if (options.writeHistory) {
410
587
  if (options.replace) {
411
- this.history.replace(location.href, location.state);
588
+ this.history.replace(historyHref, location.state);
412
589
  } else {
413
- this.history.push(location.href, location.state);
590
+ this.history.push(historyHref, location.state);
414
591
  }
415
592
  }
416
593
  this.state = {
@@ -431,11 +608,12 @@ class Router {
431
608
  return;
432
609
  }
433
610
  const errorMatches = this.buildMatches(location);
611
+ const historyHref = prependBasePathToHref(location.href, this.basePath);
434
612
  if (options.writeHistory) {
435
613
  if (options.replace) {
436
- this.history.replace(location.href, location.state);
614
+ this.history.replace(historyHref, location.state);
437
615
  } else {
438
- this.history.push(location.href, location.state);
616
+ this.history.push(historyHref, location.state);
439
617
  }
440
618
  }
441
619
  this.state = {
@@ -525,61 +703,95 @@ function createManagedHeadElements(head) {
525
703
  element.setAttribute(MANAGED_HEAD_ATTRIBUTE, "true");
526
704
  return element;
527
705
  };
528
- for (const meta of head.meta ?? []) {
529
- if ("title" in meta) {
530
- const title = managed(document.createElement("title"));
531
- title.textContent = meta.title;
532
- elements.push(title);
706
+ const setAttributes = (element, attributes) => {
707
+ for (const [key, value] of Object.entries(attributes)) {
708
+ if (value === undefined || value === false) {
709
+ continue;
710
+ }
711
+ if (value === true) {
712
+ element.setAttribute(key, "");
713
+ continue;
714
+ }
715
+ element.setAttribute(key, value);
716
+ }
717
+ };
718
+ for (const element of head) {
719
+ if (element.tag === "title") {
533
720
  continue;
534
721
  }
535
- const tag = managed(document.createElement("meta"));
536
- if ("charset" in meta) {
537
- tag.setAttribute("charset", meta.charset);
538
- } else if ("name" in meta) {
539
- tag.setAttribute("name", meta.name);
540
- tag.setAttribute("content", meta.content);
541
- } else if ("property" in meta) {
542
- tag.setAttribute("property", meta.property);
543
- tag.setAttribute("content", meta.content);
544
- } else {
545
- tag.setAttribute("http-equiv", meta.httpEquiv);
546
- tag.setAttribute("content", meta.content);
722
+ if (element.tag === "meta") {
723
+ const tag2 = managed(document.createElement("meta"));
724
+ if ("charset" in element) {
725
+ tag2.setAttribute("charset", element.charset);
726
+ } else if ("name" in element) {
727
+ tag2.setAttribute("name", element.name);
728
+ tag2.setAttribute("content", element.content);
729
+ } else if ("property" in element) {
730
+ tag2.setAttribute("property", element.property);
731
+ tag2.setAttribute("content", element.content);
732
+ } else {
733
+ tag2.setAttribute("http-equiv", element.httpEquiv);
734
+ tag2.setAttribute("content", element.content);
735
+ }
736
+ elements.push(tag2);
737
+ continue;
738
+ }
739
+ if (element.tag === "link") {
740
+ const tag2 = managed(document.createElement("link"));
741
+ setAttributes(tag2, {
742
+ rel: element.rel,
743
+ href: element.href,
744
+ type: element.type,
745
+ media: element.media,
746
+ sizes: element.sizes,
747
+ crossorigin: element.crossorigin
748
+ });
749
+ elements.push(tag2);
750
+ continue;
751
+ }
752
+ if (element.tag === "style") {
753
+ const tag2 = managed(document.createElement("style"));
754
+ if (element.media) {
755
+ tag2.setAttribute("media", element.media);
756
+ }
757
+ tag2.textContent = element.children;
758
+ elements.push(tag2);
759
+ continue;
760
+ }
761
+ if (element.tag === "script") {
762
+ const tag2 = managed(document.createElement("script"));
763
+ if (element.src) {
764
+ tag2.setAttribute("src", element.src);
765
+ }
766
+ if (element.type) {
767
+ tag2.setAttribute("type", element.type);
768
+ }
769
+ if (element.async) {
770
+ tag2.async = true;
771
+ }
772
+ if (element.defer) {
773
+ tag2.defer = true;
774
+ }
775
+ if (element.children) {
776
+ tag2.textContent = element.children;
777
+ }
778
+ elements.push(tag2);
779
+ continue;
780
+ }
781
+ if (element.tag === "base") {
782
+ const tag2 = managed(document.createElement("base"));
783
+ tag2.setAttribute("href", element.href);
784
+ if (element.target) {
785
+ tag2.setAttribute("target", element.target);
786
+ }
787
+ elements.push(tag2);
788
+ continue;
789
+ }
790
+ const tag = managed(document.createElement(element.name));
791
+ setAttributes(tag, element.attrs ?? {});
792
+ if (element.children) {
793
+ tag.textContent = element.children;
547
794
  }
548
- elements.push(tag);
549
- }
550
- for (const link of head.links ?? []) {
551
- const tag = managed(document.createElement("link"));
552
- tag.setAttribute("rel", link.rel);
553
- tag.setAttribute("href", link.href);
554
- if (link.type)
555
- tag.setAttribute("type", link.type);
556
- if (link.media)
557
- tag.setAttribute("media", link.media);
558
- if (link.sizes)
559
- tag.setAttribute("sizes", link.sizes);
560
- if (link.crossorigin)
561
- tag.setAttribute("crossorigin", link.crossorigin);
562
- elements.push(tag);
563
- }
564
- for (const style of head.styles ?? []) {
565
- const tag = managed(document.createElement("style"));
566
- if (style.media)
567
- tag.setAttribute("media", style.media);
568
- tag.textContent = style.children;
569
- elements.push(tag);
570
- }
571
- for (const script of head.scripts ?? []) {
572
- const tag = managed(document.createElement("script"));
573
- if (script.src)
574
- tag.setAttribute("src", script.src);
575
- if (script.type)
576
- tag.setAttribute("type", script.type);
577
- if (script.async)
578
- tag.async = true;
579
- if (script.defer)
580
- tag.defer = true;
581
- if (script.children)
582
- tag.textContent = script.children;
583
795
  elements.push(tag);
584
796
  }
585
797
  return elements;
@@ -588,6 +800,10 @@ function reconcileDocumentHead(head) {
588
800
  if (typeof document === "undefined") {
589
801
  return;
590
802
  }
803
+ const title = [...head].reverse().find((element) => element.tag === "title");
804
+ if (title && title.tag === "title") {
805
+ document.title = title.children;
806
+ }
591
807
  for (const element of Array.from(document.head.querySelectorAll(`[${MANAGED_HEAD_ATTRIBUTE}]`))) {
592
808
  element.remove();
593
809
  }
@@ -602,17 +818,17 @@ function RenderMatches({ matches, index }) {
602
818
  return null;
603
819
  }
604
820
  const Component = match.route.options.component;
605
- const outlet = /* @__PURE__ */ jsx_dev_runtime.jsxDEV(RenderMatches, {
821
+ const outlet = /* @__PURE__ */ jsx_runtime.jsx(RenderMatches, {
606
822
  matches,
607
823
  index: index + 1
608
- }, undefined, false, undefined, this);
609
- return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(MatchContext.Provider, {
824
+ });
825
+ return /* @__PURE__ */ jsx_runtime.jsx(MatchContext.Provider, {
610
826
  value: match,
611
- children: /* @__PURE__ */ jsx_dev_runtime.jsxDEV(OutletContext.Provider, {
827
+ children: /* @__PURE__ */ jsx_runtime.jsx(OutletContext.Provider, {
612
828
  value: outlet,
613
- children: Component ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Component, {}, undefined, false, undefined, this) : outlet
614
- }, undefined, false, undefined, this)
615
- }, undefined, false, undefined, this);
829
+ children: Component ? /* @__PURE__ */ jsx_runtime.jsx(Component, {}) : outlet
830
+ })
831
+ });
616
832
  }
617
833
  function renderError(error, matches, router) {
618
834
  const reversed = [...matches].reverse();
@@ -621,9 +837,9 @@ function renderError(error, matches, router) {
621
837
  if (NotFoundComponent) {
622
838
  return import_react.default.createElement(NotFoundComponent);
623
839
  }
624
- return /* @__PURE__ */ jsx_dev_runtime.jsxDEV("div", {
840
+ return /* @__PURE__ */ jsx_runtime.jsx("div", {
625
841
  children: "Not Found"
626
- }, undefined, false, undefined, this);
842
+ });
627
843
  }
628
844
  const ErrorComponent = reversed.find((match) => match.route.options.errorComponent)?.route.options.errorComponent ?? router.options.defaultErrorComponent;
629
845
  if (ErrorComponent) {
@@ -634,9 +850,9 @@ function renderError(error, matches, router) {
634
850
  }
635
851
  });
636
852
  }
637
- return /* @__PURE__ */ jsx_dev_runtime.jsxDEV("pre", {
853
+ return /* @__PURE__ */ jsx_runtime.jsx("pre", {
638
854
  children: error instanceof Error ? error.message : "Unknown routing error"
639
- }, undefined, false, undefined, this);
855
+ });
640
856
  }
641
857
  function RouterProvider({ router }) {
642
858
  const snapshot = import_react.default.useSyncExternalStore(router.subscribe, router.getSnapshot, router.getSnapshot);
@@ -649,17 +865,17 @@ function RouterProvider({ router }) {
649
865
  import_react.default.useEffect(() => {
650
866
  reconcileDocumentHead(snapshot.head);
651
867
  }, [snapshot.head]);
652
- const content = snapshot.error ? renderError(snapshot.error, snapshot.matches, router) : /* @__PURE__ */ jsx_dev_runtime.jsxDEV(RenderMatches, {
868
+ const content = snapshot.error ? renderError(snapshot.error, snapshot.matches, router) : /* @__PURE__ */ jsx_runtime.jsx(RenderMatches, {
653
869
  matches: snapshot.matches,
654
870
  index: 0
655
- }, undefined, false, undefined, this);
656
- return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(RouterContext.Provider, {
871
+ });
872
+ return /* @__PURE__ */ jsx_runtime.jsx(RouterContext.Provider, {
657
873
  value: router,
658
- children: /* @__PURE__ */ jsx_dev_runtime.jsxDEV(RouterStateContext.Provider, {
874
+ children: /* @__PURE__ */ jsx_runtime.jsx(RouterStateContext.Provider, {
659
875
  value: snapshot,
660
876
  children: content
661
- }, undefined, false, undefined, this)
662
- }, undefined, false, undefined, this);
877
+ })
878
+ });
663
879
  }
664
880
  function Outlet() {
665
881
  return import_react.default.useContext(OutletContext);
@@ -685,6 +901,31 @@ function useNavigate() {
685
901
  await router.navigate(options);
686
902
  }, [router]);
687
903
  }
904
+ function useMatchRoute() {
905
+ const location = useLocation();
906
+ return import_react.default.useCallback((options) => {
907
+ const matched = import_core.matchPathname(options.to, location.pathname, {
908
+ partial: options.fuzzy === true
909
+ });
910
+ if (!matched) {
911
+ return false;
912
+ }
913
+ if (options.params) {
914
+ for (const [key, value] of Object.entries(options.params)) {
915
+ if (value !== undefined && matched.params[key] !== value) {
916
+ return false;
917
+ }
918
+ }
919
+ }
920
+ if (options.includeSearch) {
921
+ const expectedSearch = options.search ?? {};
922
+ if (!isDeepInclusiveMatch(expectedSearch, location.search)) {
923
+ return false;
924
+ }
925
+ }
926
+ return matched.params;
927
+ }, [location.pathname, location.search]);
928
+ }
688
929
  function useLocation() {
689
930
  return useRouterStateContext().location;
690
931
  }
@@ -728,12 +969,14 @@ function useElementScrollRestoration() {
728
969
  ref: () => {}
729
970
  };
730
971
  }
731
- function useResolvedLink(props) {
972
+ function useResolvedLink(props, activeOptions) {
732
973
  const router = useRouterContext();
733
974
  const href = router.buildHref(props);
734
975
  const location = useLocation();
735
- const pathOnly = href.split(/[?#]/u)[0] ?? href;
736
- const isActive = pathOnly === location.pathname;
976
+ const targetPathname = stripBasePathFromHref(href, router.options.basePath).split(/[?#]/u)[0] ?? href;
977
+ const isActive = import_core.matchPathname(targetPathname, location.pathname, {
978
+ partial: activeOptions?.exact !== true
979
+ }) !== null;
737
980
  return { href, isActive, router };
738
981
  }
739
982
  var LinkComponent = import_react.default.forwardRef(function LinkInner(props, ref) {
@@ -749,6 +992,7 @@ var LinkComponent = import_react.default.forwardRef(function LinkInner(props, re
749
992
  state,
750
993
  mask,
751
994
  ignoreBlocker,
995
+ activeOptions,
752
996
  activeProps,
753
997
  children,
754
998
  onClick,
@@ -769,7 +1013,7 @@ var LinkComponent = import_react.default.forwardRef(function LinkInner(props, re
769
1013
  mask,
770
1014
  ignoreBlocker
771
1015
  };
772
- const { href, isActive, router } = useResolvedLink(navigation);
1016
+ const { href, isActive, router } = useResolvedLink(navigation, activeOptions);
773
1017
  const preloadMode = preload ?? router.options.defaultPreload;
774
1018
  const preloadDelay = router.options.defaultPreloadDelay ?? 50;
775
1019
  const preloadTimeout = import_react.default.useRef(null);
@@ -794,7 +1038,7 @@ var LinkComponent = import_react.default.forwardRef(function LinkInner(props, re
794
1038
  }
795
1039
  }, []);
796
1040
  const renderedChildren = typeof children === "function" ? children({ isActive }) : children;
797
- return /* @__PURE__ */ jsx_dev_runtime.jsxDEV("a", {
1041
+ return /* @__PURE__ */ jsx_runtime.jsx("a", {
798
1042
  ...anchorProps,
799
1043
  ...isActive ? activeProps : undefined,
800
1044
  ref,
@@ -822,7 +1066,7 @@ var LinkComponent = import_react.default.forwardRef(function LinkInner(props, re
822
1066
  },
823
1067
  onBlur: cancelPreload,
824
1068
  children: renderedChildren
825
- }, undefined, false, undefined, this);
1069
+ });
826
1070
  });
827
1071
  var Link = LinkComponent;
828
1072
  function createLink(Component) {
@@ -839,6 +1083,7 @@ function createLink(Component) {
839
1083
  state,
840
1084
  mask,
841
1085
  ignoreBlocker,
1086
+ activeOptions,
842
1087
  activeProps,
843
1088
  children,
844
1089
  preload,
@@ -856,7 +1101,7 @@ function createLink(Component) {
856
1101
  mask,
857
1102
  ignoreBlocker
858
1103
  };
859
- const { href, isActive, router } = useResolvedLink(navigation);
1104
+ const { href, isActive, router } = useResolvedLink(navigation, activeOptions);
860
1105
  const renderedChildren = typeof children === "function" ? children({ isActive }) : children;
861
1106
  import_react.default.useEffect(() => {
862
1107
  if (preload !== "render") {
@@ -864,12 +1109,12 @@ function createLink(Component) {
864
1109
  }
865
1110
  router.preloadRoute(navigation);
866
1111
  }, [navigation, preload, router]);
867
- return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Component, {
1112
+ return /* @__PURE__ */ jsx_runtime.jsx(Component, {
868
1113
  ...componentProps,
869
1114
  ...isActive ? activeProps : undefined,
870
1115
  href,
871
1116
  children: renderedChildren
872
- }, undefined, false, undefined, this);
1117
+ });
873
1118
  };
874
1119
  }
875
1120
  function linkOptions(options) {