@richie-router/react 0.1.3 → 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.
- package/dist/cjs/router.cjs +72 -27
- package/dist/cjs/router.test.cjs +161 -0
- package/dist/esm/router.mjs +73 -27
- package/dist/esm/router.test.mjs +134 -0
- package/dist/types/router.d.ts +19 -2
- package/package.json +1 -1
package/dist/cjs/router.cjs
CHANGED
|
@@ -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,7 +98,7 @@ 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
|
|
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);
|
|
@@ -158,6 +159,21 @@ function prependBasePathToHref(href, basePath) {
|
|
|
158
159
|
function routeHasRecord(value) {
|
|
159
160
|
return typeof value === "object" && value !== null;
|
|
160
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
|
+
}
|
|
161
177
|
function routeHasInlineHead(route) {
|
|
162
178
|
const headOption = route.options.head;
|
|
163
179
|
return Boolean(headOption && typeof headOption !== "string");
|
|
@@ -802,17 +818,17 @@ function RenderMatches({ matches, index }) {
|
|
|
802
818
|
return null;
|
|
803
819
|
}
|
|
804
820
|
const Component = match.route.options.component;
|
|
805
|
-
const outlet = /* @__PURE__ */
|
|
821
|
+
const outlet = /* @__PURE__ */ jsx_runtime.jsx(RenderMatches, {
|
|
806
822
|
matches,
|
|
807
823
|
index: index + 1
|
|
808
|
-
}
|
|
809
|
-
return /* @__PURE__ */
|
|
824
|
+
});
|
|
825
|
+
return /* @__PURE__ */ jsx_runtime.jsx(MatchContext.Provider, {
|
|
810
826
|
value: match,
|
|
811
|
-
children: /* @__PURE__ */
|
|
827
|
+
children: /* @__PURE__ */ jsx_runtime.jsx(OutletContext.Provider, {
|
|
812
828
|
value: outlet,
|
|
813
|
-
children: Component ? /* @__PURE__ */
|
|
814
|
-
}
|
|
815
|
-
}
|
|
829
|
+
children: Component ? /* @__PURE__ */ jsx_runtime.jsx(Component, {}) : outlet
|
|
830
|
+
})
|
|
831
|
+
});
|
|
816
832
|
}
|
|
817
833
|
function renderError(error, matches, router) {
|
|
818
834
|
const reversed = [...matches].reverse();
|
|
@@ -821,9 +837,9 @@ function renderError(error, matches, router) {
|
|
|
821
837
|
if (NotFoundComponent) {
|
|
822
838
|
return import_react.default.createElement(NotFoundComponent);
|
|
823
839
|
}
|
|
824
|
-
return /* @__PURE__ */
|
|
840
|
+
return /* @__PURE__ */ jsx_runtime.jsx("div", {
|
|
825
841
|
children: "Not Found"
|
|
826
|
-
}
|
|
842
|
+
});
|
|
827
843
|
}
|
|
828
844
|
const ErrorComponent = reversed.find((match) => match.route.options.errorComponent)?.route.options.errorComponent ?? router.options.defaultErrorComponent;
|
|
829
845
|
if (ErrorComponent) {
|
|
@@ -834,9 +850,9 @@ function renderError(error, matches, router) {
|
|
|
834
850
|
}
|
|
835
851
|
});
|
|
836
852
|
}
|
|
837
|
-
return /* @__PURE__ */
|
|
853
|
+
return /* @__PURE__ */ jsx_runtime.jsx("pre", {
|
|
838
854
|
children: error instanceof Error ? error.message : "Unknown routing error"
|
|
839
|
-
}
|
|
855
|
+
});
|
|
840
856
|
}
|
|
841
857
|
function RouterProvider({ router }) {
|
|
842
858
|
const snapshot = import_react.default.useSyncExternalStore(router.subscribe, router.getSnapshot, router.getSnapshot);
|
|
@@ -849,17 +865,17 @@ function RouterProvider({ router }) {
|
|
|
849
865
|
import_react.default.useEffect(() => {
|
|
850
866
|
reconcileDocumentHead(snapshot.head);
|
|
851
867
|
}, [snapshot.head]);
|
|
852
|
-
const content = snapshot.error ? renderError(snapshot.error, snapshot.matches, router) : /* @__PURE__ */
|
|
868
|
+
const content = snapshot.error ? renderError(snapshot.error, snapshot.matches, router) : /* @__PURE__ */ jsx_runtime.jsx(RenderMatches, {
|
|
853
869
|
matches: snapshot.matches,
|
|
854
870
|
index: 0
|
|
855
|
-
}
|
|
856
|
-
return /* @__PURE__ */
|
|
871
|
+
});
|
|
872
|
+
return /* @__PURE__ */ jsx_runtime.jsx(RouterContext.Provider, {
|
|
857
873
|
value: router,
|
|
858
|
-
children: /* @__PURE__ */
|
|
874
|
+
children: /* @__PURE__ */ jsx_runtime.jsx(RouterStateContext.Provider, {
|
|
859
875
|
value: snapshot,
|
|
860
876
|
children: content
|
|
861
|
-
}
|
|
862
|
-
}
|
|
877
|
+
})
|
|
878
|
+
});
|
|
863
879
|
}
|
|
864
880
|
function Outlet() {
|
|
865
881
|
return import_react.default.useContext(OutletContext);
|
|
@@ -885,6 +901,31 @@ function useNavigate() {
|
|
|
885
901
|
await router.navigate(options);
|
|
886
902
|
}, [router]);
|
|
887
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
|
+
}
|
|
888
929
|
function useLocation() {
|
|
889
930
|
return useRouterStateContext().location;
|
|
890
931
|
}
|
|
@@ -928,12 +969,14 @@ function useElementScrollRestoration() {
|
|
|
928
969
|
ref: () => {}
|
|
929
970
|
};
|
|
930
971
|
}
|
|
931
|
-
function useResolvedLink(props) {
|
|
972
|
+
function useResolvedLink(props, activeOptions) {
|
|
932
973
|
const router = useRouterContext();
|
|
933
974
|
const href = router.buildHref(props);
|
|
934
975
|
const location = useLocation();
|
|
935
|
-
const
|
|
936
|
-
const isActive =
|
|
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;
|
|
937
980
|
return { href, isActive, router };
|
|
938
981
|
}
|
|
939
982
|
var LinkComponent = import_react.default.forwardRef(function LinkInner(props, ref) {
|
|
@@ -949,6 +992,7 @@ var LinkComponent = import_react.default.forwardRef(function LinkInner(props, re
|
|
|
949
992
|
state,
|
|
950
993
|
mask,
|
|
951
994
|
ignoreBlocker,
|
|
995
|
+
activeOptions,
|
|
952
996
|
activeProps,
|
|
953
997
|
children,
|
|
954
998
|
onClick,
|
|
@@ -969,7 +1013,7 @@ var LinkComponent = import_react.default.forwardRef(function LinkInner(props, re
|
|
|
969
1013
|
mask,
|
|
970
1014
|
ignoreBlocker
|
|
971
1015
|
};
|
|
972
|
-
const { href, isActive, router } = useResolvedLink(navigation);
|
|
1016
|
+
const { href, isActive, router } = useResolvedLink(navigation, activeOptions);
|
|
973
1017
|
const preloadMode = preload ?? router.options.defaultPreload;
|
|
974
1018
|
const preloadDelay = router.options.defaultPreloadDelay ?? 50;
|
|
975
1019
|
const preloadTimeout = import_react.default.useRef(null);
|
|
@@ -994,7 +1038,7 @@ var LinkComponent = import_react.default.forwardRef(function LinkInner(props, re
|
|
|
994
1038
|
}
|
|
995
1039
|
}, []);
|
|
996
1040
|
const renderedChildren = typeof children === "function" ? children({ isActive }) : children;
|
|
997
|
-
return /* @__PURE__ */
|
|
1041
|
+
return /* @__PURE__ */ jsx_runtime.jsx("a", {
|
|
998
1042
|
...anchorProps,
|
|
999
1043
|
...isActive ? activeProps : undefined,
|
|
1000
1044
|
ref,
|
|
@@ -1022,7 +1066,7 @@ var LinkComponent = import_react.default.forwardRef(function LinkInner(props, re
|
|
|
1022
1066
|
},
|
|
1023
1067
|
onBlur: cancelPreload,
|
|
1024
1068
|
children: renderedChildren
|
|
1025
|
-
}
|
|
1069
|
+
});
|
|
1026
1070
|
});
|
|
1027
1071
|
var Link = LinkComponent;
|
|
1028
1072
|
function createLink(Component) {
|
|
@@ -1039,6 +1083,7 @@ function createLink(Component) {
|
|
|
1039
1083
|
state,
|
|
1040
1084
|
mask,
|
|
1041
1085
|
ignoreBlocker,
|
|
1086
|
+
activeOptions,
|
|
1042
1087
|
activeProps,
|
|
1043
1088
|
children,
|
|
1044
1089
|
preload,
|
|
@@ -1056,7 +1101,7 @@ function createLink(Component) {
|
|
|
1056
1101
|
mask,
|
|
1057
1102
|
ignoreBlocker
|
|
1058
1103
|
};
|
|
1059
|
-
const { href, isActive, router } = useResolvedLink(navigation);
|
|
1104
|
+
const { href, isActive, router } = useResolvedLink(navigation, activeOptions);
|
|
1060
1105
|
const renderedChildren = typeof children === "function" ? children({ isActive }) : children;
|
|
1061
1106
|
import_react.default.useEffect(() => {
|
|
1062
1107
|
if (preload !== "render") {
|
|
@@ -1064,12 +1109,12 @@ function createLink(Component) {
|
|
|
1064
1109
|
}
|
|
1065
1110
|
router.preloadRoute(navigation);
|
|
1066
1111
|
}, [navigation, preload, router]);
|
|
1067
|
-
return /* @__PURE__ */
|
|
1112
|
+
return /* @__PURE__ */ jsx_runtime.jsx(Component, {
|
|
1068
1113
|
...componentProps,
|
|
1069
1114
|
...isActive ? activeProps : undefined,
|
|
1070
1115
|
href,
|
|
1071
1116
|
children: renderedChildren
|
|
1072
|
-
}
|
|
1117
|
+
});
|
|
1073
1118
|
};
|
|
1074
1119
|
}
|
|
1075
1120
|
function linkOptions(options) {
|
package/dist/cjs/router.test.cjs
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
function __accessProp(key) {
|
|
7
|
+
return this[key];
|
|
8
|
+
}
|
|
9
|
+
var __toESMCache_node;
|
|
10
|
+
var __toESMCache_esm;
|
|
11
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
12
|
+
var canCache = mod != null && typeof mod === "object";
|
|
13
|
+
if (canCache) {
|
|
14
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
15
|
+
var cached = cache.get(mod);
|
|
16
|
+
if (cached)
|
|
17
|
+
return cached;
|
|
18
|
+
}
|
|
19
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
20
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
21
|
+
for (let key of __getOwnPropNames(mod))
|
|
22
|
+
if (!__hasOwnProp.call(to, key))
|
|
23
|
+
__defProp(to, key, {
|
|
24
|
+
get: __accessProp.bind(mod, key),
|
|
25
|
+
enumerable: true
|
|
26
|
+
});
|
|
27
|
+
if (canCache)
|
|
28
|
+
cache.set(mod, to);
|
|
29
|
+
return to;
|
|
30
|
+
};
|
|
31
|
+
|
|
1
32
|
// packages/react/src/router.test.ts
|
|
2
33
|
var import_bun_test = require("bun:test");
|
|
34
|
+
var import_react = __toESM(require("react"));
|
|
35
|
+
var import_server = require("react-dom/server");
|
|
3
36
|
var import_router = require("./router.cjs");
|
|
4
37
|
function createTestRouteTree(options) {
|
|
5
38
|
const rootRoute = import_router.createRootRoute({
|
|
@@ -75,7 +108,67 @@ function createNestedServerHeadTree() {
|
|
|
75
108
|
posts: postsRoute
|
|
76
109
|
});
|
|
77
110
|
}
|
|
111
|
+
function createLinkTestRouteTree(component) {
|
|
112
|
+
const rootRoute = import_router.createRootRoute({
|
|
113
|
+
component
|
|
114
|
+
});
|
|
115
|
+
const postRoute = import_router.createFileRoute("/post")({
|
|
116
|
+
component: () => null
|
|
117
|
+
});
|
|
118
|
+
const postsRoute = import_router.createFileRoute("/posts")({
|
|
119
|
+
component: () => null
|
|
120
|
+
});
|
|
121
|
+
const postDetailRoute = import_router.createFileRoute("/posts/$postId")({
|
|
122
|
+
component: () => null
|
|
123
|
+
});
|
|
124
|
+
postsRoute._addFileChildren({
|
|
125
|
+
detail: postDetailRoute
|
|
126
|
+
});
|
|
127
|
+
return rootRoute._addFileChildren({
|
|
128
|
+
post: postRoute,
|
|
129
|
+
posts: postsRoute
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
function renderLinkMarkup(initialEntry, component) {
|
|
133
|
+
const history = import_router.createMemoryHistory({
|
|
134
|
+
initialEntries: [initialEntry]
|
|
135
|
+
});
|
|
136
|
+
const router = import_router.createRouter({
|
|
137
|
+
routeTree: createLinkTestRouteTree(component),
|
|
138
|
+
history
|
|
139
|
+
});
|
|
140
|
+
return import_server.renderToStaticMarkup(import_react.default.createElement(import_router.RouterProvider, { router }));
|
|
141
|
+
}
|
|
78
142
|
import_bun_test.describe("createRouter basePath", () => {
|
|
143
|
+
import_bun_test.test('treats "/" as the root basePath', () => {
|
|
144
|
+
const history = import_router.createMemoryHistory({
|
|
145
|
+
initialEntries: ["/about?tab=team#bio"]
|
|
146
|
+
});
|
|
147
|
+
const router = import_router.createRouter({
|
|
148
|
+
routeTree: createTestRouteTree(),
|
|
149
|
+
history,
|
|
150
|
+
basePath: "/"
|
|
151
|
+
});
|
|
152
|
+
import_bun_test.expect(router.state.location.pathname).toBe("/about");
|
|
153
|
+
import_bun_test.expect(router.state.location.href).toBe("/about?tab=team#bio");
|
|
154
|
+
import_bun_test.expect(router.buildHref({ to: "/about" })).toBe("/about");
|
|
155
|
+
});
|
|
156
|
+
import_bun_test.test("normalizes a trailing slash in the basePath", async () => {
|
|
157
|
+
const history = import_router.createMemoryHistory({
|
|
158
|
+
initialEntries: ["/project/about?tab=team#bio"]
|
|
159
|
+
});
|
|
160
|
+
const router = import_router.createRouter({
|
|
161
|
+
routeTree: createTestRouteTree(),
|
|
162
|
+
history,
|
|
163
|
+
basePath: "/project/"
|
|
164
|
+
});
|
|
165
|
+
import_bun_test.expect(router.state.location.pathname).toBe("/about");
|
|
166
|
+
import_bun_test.expect(router.buildHref({ to: "/about" })).toBe("/project/about");
|
|
167
|
+
await router.navigate({
|
|
168
|
+
to: "/about"
|
|
169
|
+
});
|
|
170
|
+
import_bun_test.expect(history.location.href).toBe("/project/about");
|
|
171
|
+
});
|
|
79
172
|
import_bun_test.test("strips the basePath from the current history location", () => {
|
|
80
173
|
const history = import_router.createMemoryHistory({
|
|
81
174
|
initialEntries: ["/project/about?tab=team#bio"]
|
|
@@ -411,3 +504,71 @@ import_bun_test.describe("createRouter basePath", () => {
|
|
|
411
504
|
}
|
|
412
505
|
});
|
|
413
506
|
});
|
|
507
|
+
import_bun_test.describe("Link active state", () => {
|
|
508
|
+
import_bun_test.test("keeps parent links active on child routes by default", () => {
|
|
509
|
+
const TestLink = import_router.Link;
|
|
510
|
+
const markup = renderLinkMarkup("/posts/alpha", () => import_react.default.createElement(TestLink, { to: "/posts", activeProps: { className: "active" } }, "Posts"));
|
|
511
|
+
import_bun_test.expect(markup).toContain('class="active"');
|
|
512
|
+
});
|
|
513
|
+
import_bun_test.test("supports exact-only active matching", () => {
|
|
514
|
+
const TestLink = import_router.Link;
|
|
515
|
+
const markup = renderLinkMarkup("/posts/alpha", () => import_react.default.createElement(TestLink, {
|
|
516
|
+
to: "/posts",
|
|
517
|
+
activeOptions: { exact: true },
|
|
518
|
+
activeProps: { className: "active" }
|
|
519
|
+
}, "Posts"));
|
|
520
|
+
import_bun_test.expect(markup).not.toContain('class="active"');
|
|
521
|
+
});
|
|
522
|
+
import_bun_test.test("matches path segments instead of raw string prefixes", () => {
|
|
523
|
+
const TestLink = import_router.Link;
|
|
524
|
+
const markup = renderLinkMarkup("/posts/alpha", () => import_react.default.createElement(TestLink, { to: "/post", activeProps: { className: "active" } }, "Post"));
|
|
525
|
+
import_bun_test.expect(markup).not.toContain('class="active"');
|
|
526
|
+
});
|
|
527
|
+
import_bun_test.test("applies activeOptions in custom links created with createLink", () => {
|
|
528
|
+
const AppLink = import_router.createLink((props) => import_react.default.createElement("a", props));
|
|
529
|
+
const markup = renderLinkMarkup("/posts/alpha", () => import_react.default.createElement(AppLink, {
|
|
530
|
+
to: "/posts",
|
|
531
|
+
activeOptions: { exact: true },
|
|
532
|
+
activeProps: { className: "active" }
|
|
533
|
+
}, "Posts"));
|
|
534
|
+
import_bun_test.expect(markup).not.toContain('class="active"');
|
|
535
|
+
});
|
|
536
|
+
});
|
|
537
|
+
import_bun_test.describe("useMatchRoute", () => {
|
|
538
|
+
import_bun_test.test("returns matched params for exact matches", () => {
|
|
539
|
+
const markup = renderLinkMarkup("/posts/alpha", () => {
|
|
540
|
+
const matchRoute = import_router.useMatchRoute();
|
|
541
|
+
const match = matchRoute({ to: "/posts/$postId" });
|
|
542
|
+
return import_react.default.createElement("pre", null, match === false ? "false" : JSON.stringify(match));
|
|
543
|
+
});
|
|
544
|
+
import_bun_test.expect(markup).toContain("{"postId":"alpha"}");
|
|
545
|
+
});
|
|
546
|
+
import_bun_test.test("supports fuzzy parent matching", () => {
|
|
547
|
+
const markup = renderLinkMarkup("/posts/alpha", () => {
|
|
548
|
+
const matchRoute = import_router.useMatchRoute();
|
|
549
|
+
const match = matchRoute({ to: "/posts", fuzzy: true });
|
|
550
|
+
return import_react.default.createElement("pre", null, match === false ? "false" : JSON.stringify(match));
|
|
551
|
+
});
|
|
552
|
+
import_bun_test.expect(markup).toContain("{}");
|
|
553
|
+
});
|
|
554
|
+
import_bun_test.test("supports partial param filters", () => {
|
|
555
|
+
const markup = renderLinkMarkup("/posts/alpha", () => {
|
|
556
|
+
const matchRoute = import_router.useMatchRoute();
|
|
557
|
+
const match = matchRoute({ to: "/posts/$postId", params: { postId: "beta" } });
|
|
558
|
+
return import_react.default.createElement("pre", null, match === false ? "false" : JSON.stringify(match));
|
|
559
|
+
});
|
|
560
|
+
import_bun_test.expect(markup).toContain("false");
|
|
561
|
+
});
|
|
562
|
+
import_bun_test.test("can include search params in the match", () => {
|
|
563
|
+
const markup = renderLinkMarkup("/posts/alpha?tab=details&count=2", () => {
|
|
564
|
+
const matchRoute = import_router.useMatchRoute();
|
|
565
|
+
const match = matchRoute({
|
|
566
|
+
to: "/posts/$postId",
|
|
567
|
+
includeSearch: true,
|
|
568
|
+
search: { tab: "details" }
|
|
569
|
+
});
|
|
570
|
+
return import_react.default.createElement("pre", null, match === false ? "false" : JSON.stringify(match));
|
|
571
|
+
});
|
|
572
|
+
import_bun_test.expect(markup).toContain("{"postId":"alpha"}");
|
|
573
|
+
});
|
|
574
|
+
});
|
package/dist/esm/router.mjs
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
defaultStringifySearch,
|
|
11
11
|
isNotFound,
|
|
12
12
|
isRedirect,
|
|
13
|
+
matchPathname,
|
|
13
14
|
matchRouteTree,
|
|
14
15
|
notFound,
|
|
15
16
|
redirect,
|
|
@@ -20,7 +21,7 @@ import {
|
|
|
20
21
|
createHashHistory,
|
|
21
22
|
createMemoryHistory
|
|
22
23
|
} from "./history.mjs";
|
|
23
|
-
import {
|
|
24
|
+
import { jsx } from "react/jsx-runtime";
|
|
24
25
|
var RouterContext = React.createContext(null);
|
|
25
26
|
var RouterStateContext = React.createContext(null);
|
|
26
27
|
var OutletContext = React.createContext(null);
|
|
@@ -81,6 +82,21 @@ function prependBasePathToHref(href, basePath) {
|
|
|
81
82
|
function routeHasRecord(value) {
|
|
82
83
|
return typeof value === "object" && value !== null;
|
|
83
84
|
}
|
|
85
|
+
function isDeepInclusiveMatch(expected, actual) {
|
|
86
|
+
if (Array.isArray(expected)) {
|
|
87
|
+
if (!Array.isArray(actual) || expected.length !== actual.length) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
return expected.every((value, index) => isDeepInclusiveMatch(value, actual[index]));
|
|
91
|
+
}
|
|
92
|
+
if (routeHasRecord(expected)) {
|
|
93
|
+
if (!routeHasRecord(actual)) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
return Object.entries(expected).every(([key, value]) => (key in actual) && isDeepInclusiveMatch(value, actual[key]));
|
|
97
|
+
}
|
|
98
|
+
return Object.is(expected, actual);
|
|
99
|
+
}
|
|
84
100
|
function routeHasInlineHead(route) {
|
|
85
101
|
const headOption = route.options.head;
|
|
86
102
|
return Boolean(headOption && typeof headOption !== "string");
|
|
@@ -725,17 +741,17 @@ function RenderMatches({ matches, index }) {
|
|
|
725
741
|
return null;
|
|
726
742
|
}
|
|
727
743
|
const Component = match.route.options.component;
|
|
728
|
-
const outlet = /* @__PURE__ */
|
|
744
|
+
const outlet = /* @__PURE__ */ jsx(RenderMatches, {
|
|
729
745
|
matches,
|
|
730
746
|
index: index + 1
|
|
731
|
-
}
|
|
732
|
-
return /* @__PURE__ */
|
|
747
|
+
});
|
|
748
|
+
return /* @__PURE__ */ jsx(MatchContext.Provider, {
|
|
733
749
|
value: match,
|
|
734
|
-
children: /* @__PURE__ */
|
|
750
|
+
children: /* @__PURE__ */ jsx(OutletContext.Provider, {
|
|
735
751
|
value: outlet,
|
|
736
|
-
children: Component ? /* @__PURE__ */
|
|
737
|
-
}
|
|
738
|
-
}
|
|
752
|
+
children: Component ? /* @__PURE__ */ jsx(Component, {}) : outlet
|
|
753
|
+
})
|
|
754
|
+
});
|
|
739
755
|
}
|
|
740
756
|
function renderError(error, matches, router) {
|
|
741
757
|
const reversed = [...matches].reverse();
|
|
@@ -744,9 +760,9 @@ function renderError(error, matches, router) {
|
|
|
744
760
|
if (NotFoundComponent) {
|
|
745
761
|
return React.createElement(NotFoundComponent);
|
|
746
762
|
}
|
|
747
|
-
return /* @__PURE__ */
|
|
763
|
+
return /* @__PURE__ */ jsx("div", {
|
|
748
764
|
children: "Not Found"
|
|
749
|
-
}
|
|
765
|
+
});
|
|
750
766
|
}
|
|
751
767
|
const ErrorComponent = reversed.find((match) => match.route.options.errorComponent)?.route.options.errorComponent ?? router.options.defaultErrorComponent;
|
|
752
768
|
if (ErrorComponent) {
|
|
@@ -757,9 +773,9 @@ function renderError(error, matches, router) {
|
|
|
757
773
|
}
|
|
758
774
|
});
|
|
759
775
|
}
|
|
760
|
-
return /* @__PURE__ */
|
|
776
|
+
return /* @__PURE__ */ jsx("pre", {
|
|
761
777
|
children: error instanceof Error ? error.message : "Unknown routing error"
|
|
762
|
-
}
|
|
778
|
+
});
|
|
763
779
|
}
|
|
764
780
|
function RouterProvider({ router }) {
|
|
765
781
|
const snapshot = React.useSyncExternalStore(router.subscribe, router.getSnapshot, router.getSnapshot);
|
|
@@ -772,17 +788,17 @@ function RouterProvider({ router }) {
|
|
|
772
788
|
React.useEffect(() => {
|
|
773
789
|
reconcileDocumentHead(snapshot.head);
|
|
774
790
|
}, [snapshot.head]);
|
|
775
|
-
const content = snapshot.error ? renderError(snapshot.error, snapshot.matches, router) : /* @__PURE__ */
|
|
791
|
+
const content = snapshot.error ? renderError(snapshot.error, snapshot.matches, router) : /* @__PURE__ */ jsx(RenderMatches, {
|
|
776
792
|
matches: snapshot.matches,
|
|
777
793
|
index: 0
|
|
778
|
-
}
|
|
779
|
-
return /* @__PURE__ */
|
|
794
|
+
});
|
|
795
|
+
return /* @__PURE__ */ jsx(RouterContext.Provider, {
|
|
780
796
|
value: router,
|
|
781
|
-
children: /* @__PURE__ */
|
|
797
|
+
children: /* @__PURE__ */ jsx(RouterStateContext.Provider, {
|
|
782
798
|
value: snapshot,
|
|
783
799
|
children: content
|
|
784
|
-
}
|
|
785
|
-
}
|
|
800
|
+
})
|
|
801
|
+
});
|
|
786
802
|
}
|
|
787
803
|
function Outlet() {
|
|
788
804
|
return React.useContext(OutletContext);
|
|
@@ -808,6 +824,31 @@ function useNavigate() {
|
|
|
808
824
|
await router.navigate(options);
|
|
809
825
|
}, [router]);
|
|
810
826
|
}
|
|
827
|
+
function useMatchRoute() {
|
|
828
|
+
const location = useLocation();
|
|
829
|
+
return React.useCallback((options) => {
|
|
830
|
+
const matched = matchPathname(options.to, location.pathname, {
|
|
831
|
+
partial: options.fuzzy === true
|
|
832
|
+
});
|
|
833
|
+
if (!matched) {
|
|
834
|
+
return false;
|
|
835
|
+
}
|
|
836
|
+
if (options.params) {
|
|
837
|
+
for (const [key, value] of Object.entries(options.params)) {
|
|
838
|
+
if (value !== undefined && matched.params[key] !== value) {
|
|
839
|
+
return false;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
if (options.includeSearch) {
|
|
844
|
+
const expectedSearch = options.search ?? {};
|
|
845
|
+
if (!isDeepInclusiveMatch(expectedSearch, location.search)) {
|
|
846
|
+
return false;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
return matched.params;
|
|
850
|
+
}, [location.pathname, location.search]);
|
|
851
|
+
}
|
|
811
852
|
function useLocation() {
|
|
812
853
|
return useRouterStateContext().location;
|
|
813
854
|
}
|
|
@@ -851,12 +892,14 @@ function useElementScrollRestoration() {
|
|
|
851
892
|
ref: () => {}
|
|
852
893
|
};
|
|
853
894
|
}
|
|
854
|
-
function useResolvedLink(props) {
|
|
895
|
+
function useResolvedLink(props, activeOptions) {
|
|
855
896
|
const router = useRouterContext();
|
|
856
897
|
const href = router.buildHref(props);
|
|
857
898
|
const location = useLocation();
|
|
858
|
-
const
|
|
859
|
-
const isActive =
|
|
899
|
+
const targetPathname = stripBasePathFromHref(href, router.options.basePath).split(/[?#]/u)[0] ?? href;
|
|
900
|
+
const isActive = matchPathname(targetPathname, location.pathname, {
|
|
901
|
+
partial: activeOptions?.exact !== true
|
|
902
|
+
}) !== null;
|
|
860
903
|
return { href, isActive, router };
|
|
861
904
|
}
|
|
862
905
|
var LinkComponent = React.forwardRef(function LinkInner(props, ref) {
|
|
@@ -872,6 +915,7 @@ var LinkComponent = React.forwardRef(function LinkInner(props, ref) {
|
|
|
872
915
|
state,
|
|
873
916
|
mask,
|
|
874
917
|
ignoreBlocker,
|
|
918
|
+
activeOptions,
|
|
875
919
|
activeProps,
|
|
876
920
|
children,
|
|
877
921
|
onClick,
|
|
@@ -892,7 +936,7 @@ var LinkComponent = React.forwardRef(function LinkInner(props, ref) {
|
|
|
892
936
|
mask,
|
|
893
937
|
ignoreBlocker
|
|
894
938
|
};
|
|
895
|
-
const { href, isActive, router } = useResolvedLink(navigation);
|
|
939
|
+
const { href, isActive, router } = useResolvedLink(navigation, activeOptions);
|
|
896
940
|
const preloadMode = preload ?? router.options.defaultPreload;
|
|
897
941
|
const preloadDelay = router.options.defaultPreloadDelay ?? 50;
|
|
898
942
|
const preloadTimeout = React.useRef(null);
|
|
@@ -917,7 +961,7 @@ var LinkComponent = React.forwardRef(function LinkInner(props, ref) {
|
|
|
917
961
|
}
|
|
918
962
|
}, []);
|
|
919
963
|
const renderedChildren = typeof children === "function" ? children({ isActive }) : children;
|
|
920
|
-
return /* @__PURE__ */
|
|
964
|
+
return /* @__PURE__ */ jsx("a", {
|
|
921
965
|
...anchorProps,
|
|
922
966
|
...isActive ? activeProps : undefined,
|
|
923
967
|
ref,
|
|
@@ -945,7 +989,7 @@ var LinkComponent = React.forwardRef(function LinkInner(props, ref) {
|
|
|
945
989
|
},
|
|
946
990
|
onBlur: cancelPreload,
|
|
947
991
|
children: renderedChildren
|
|
948
|
-
}
|
|
992
|
+
});
|
|
949
993
|
});
|
|
950
994
|
var Link = LinkComponent;
|
|
951
995
|
function createLink(Component) {
|
|
@@ -962,6 +1006,7 @@ function createLink(Component) {
|
|
|
962
1006
|
state,
|
|
963
1007
|
mask,
|
|
964
1008
|
ignoreBlocker,
|
|
1009
|
+
activeOptions,
|
|
965
1010
|
activeProps,
|
|
966
1011
|
children,
|
|
967
1012
|
preload,
|
|
@@ -979,7 +1024,7 @@ function createLink(Component) {
|
|
|
979
1024
|
mask,
|
|
980
1025
|
ignoreBlocker
|
|
981
1026
|
};
|
|
982
|
-
const { href, isActive, router } = useResolvedLink(navigation);
|
|
1027
|
+
const { href, isActive, router } = useResolvedLink(navigation, activeOptions);
|
|
983
1028
|
const renderedChildren = typeof children === "function" ? children({ isActive }) : children;
|
|
984
1029
|
React.useEffect(() => {
|
|
985
1030
|
if (preload !== "render") {
|
|
@@ -987,12 +1032,12 @@ function createLink(Component) {
|
|
|
987
1032
|
}
|
|
988
1033
|
router.preloadRoute(navigation);
|
|
989
1034
|
}, [navigation, preload, router]);
|
|
990
|
-
return /* @__PURE__ */
|
|
1035
|
+
return /* @__PURE__ */ jsx(Component, {
|
|
991
1036
|
...componentProps,
|
|
992
1037
|
...isActive ? activeProps : undefined,
|
|
993
1038
|
href,
|
|
994
1039
|
children: renderedChildren
|
|
995
|
-
}
|
|
1040
|
+
});
|
|
996
1041
|
};
|
|
997
1042
|
}
|
|
998
1043
|
function linkOptions(options) {
|
|
@@ -1015,6 +1060,7 @@ export {
|
|
|
1015
1060
|
useParams,
|
|
1016
1061
|
useNavigate,
|
|
1017
1062
|
useMatches,
|
|
1063
|
+
useMatchRoute,
|
|
1018
1064
|
useMatch,
|
|
1019
1065
|
useLocation,
|
|
1020
1066
|
useElementScrollRestoration,
|
package/dist/esm/router.test.mjs
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
// packages/react/src/router.test.ts
|
|
2
2
|
import { describe, expect, test } from "bun:test";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { renderToStaticMarkup } from "react-dom/server";
|
|
3
5
|
import {
|
|
6
|
+
Link,
|
|
7
|
+
useMatchRoute,
|
|
8
|
+
RouterProvider,
|
|
9
|
+
createLink,
|
|
4
10
|
createFileRoute,
|
|
5
11
|
createMemoryHistory,
|
|
6
12
|
createRootRoute,
|
|
@@ -80,7 +86,67 @@ function createNestedServerHeadTree() {
|
|
|
80
86
|
posts: postsRoute
|
|
81
87
|
});
|
|
82
88
|
}
|
|
89
|
+
function createLinkTestRouteTree(component) {
|
|
90
|
+
const rootRoute = createRootRoute({
|
|
91
|
+
component
|
|
92
|
+
});
|
|
93
|
+
const postRoute = createFileRoute("/post")({
|
|
94
|
+
component: () => null
|
|
95
|
+
});
|
|
96
|
+
const postsRoute = createFileRoute("/posts")({
|
|
97
|
+
component: () => null
|
|
98
|
+
});
|
|
99
|
+
const postDetailRoute = createFileRoute("/posts/$postId")({
|
|
100
|
+
component: () => null
|
|
101
|
+
});
|
|
102
|
+
postsRoute._addFileChildren({
|
|
103
|
+
detail: postDetailRoute
|
|
104
|
+
});
|
|
105
|
+
return rootRoute._addFileChildren({
|
|
106
|
+
post: postRoute,
|
|
107
|
+
posts: postsRoute
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function renderLinkMarkup(initialEntry, component) {
|
|
111
|
+
const history = createMemoryHistory({
|
|
112
|
+
initialEntries: [initialEntry]
|
|
113
|
+
});
|
|
114
|
+
const router = createRouter({
|
|
115
|
+
routeTree: createLinkTestRouteTree(component),
|
|
116
|
+
history
|
|
117
|
+
});
|
|
118
|
+
return renderToStaticMarkup(React.createElement(RouterProvider, { router }));
|
|
119
|
+
}
|
|
83
120
|
describe("createRouter basePath", () => {
|
|
121
|
+
test('treats "/" as the root basePath', () => {
|
|
122
|
+
const history = createMemoryHistory({
|
|
123
|
+
initialEntries: ["/about?tab=team#bio"]
|
|
124
|
+
});
|
|
125
|
+
const router = createRouter({
|
|
126
|
+
routeTree: createTestRouteTree(),
|
|
127
|
+
history,
|
|
128
|
+
basePath: "/"
|
|
129
|
+
});
|
|
130
|
+
expect(router.state.location.pathname).toBe("/about");
|
|
131
|
+
expect(router.state.location.href).toBe("/about?tab=team#bio");
|
|
132
|
+
expect(router.buildHref({ to: "/about" })).toBe("/about");
|
|
133
|
+
});
|
|
134
|
+
test("normalizes a trailing slash in the basePath", async () => {
|
|
135
|
+
const history = createMemoryHistory({
|
|
136
|
+
initialEntries: ["/project/about?tab=team#bio"]
|
|
137
|
+
});
|
|
138
|
+
const router = createRouter({
|
|
139
|
+
routeTree: createTestRouteTree(),
|
|
140
|
+
history,
|
|
141
|
+
basePath: "/project/"
|
|
142
|
+
});
|
|
143
|
+
expect(router.state.location.pathname).toBe("/about");
|
|
144
|
+
expect(router.buildHref({ to: "/about" })).toBe("/project/about");
|
|
145
|
+
await router.navigate({
|
|
146
|
+
to: "/about"
|
|
147
|
+
});
|
|
148
|
+
expect(history.location.href).toBe("/project/about");
|
|
149
|
+
});
|
|
84
150
|
test("strips the basePath from the current history location", () => {
|
|
85
151
|
const history = createMemoryHistory({
|
|
86
152
|
initialEntries: ["/project/about?tab=team#bio"]
|
|
@@ -416,3 +482,71 @@ describe("createRouter basePath", () => {
|
|
|
416
482
|
}
|
|
417
483
|
});
|
|
418
484
|
});
|
|
485
|
+
describe("Link active state", () => {
|
|
486
|
+
test("keeps parent links active on child routes by default", () => {
|
|
487
|
+
const TestLink = Link;
|
|
488
|
+
const markup = renderLinkMarkup("/posts/alpha", () => React.createElement(TestLink, { to: "/posts", activeProps: { className: "active" } }, "Posts"));
|
|
489
|
+
expect(markup).toContain('class="active"');
|
|
490
|
+
});
|
|
491
|
+
test("supports exact-only active matching", () => {
|
|
492
|
+
const TestLink = Link;
|
|
493
|
+
const markup = renderLinkMarkup("/posts/alpha", () => React.createElement(TestLink, {
|
|
494
|
+
to: "/posts",
|
|
495
|
+
activeOptions: { exact: true },
|
|
496
|
+
activeProps: { className: "active" }
|
|
497
|
+
}, "Posts"));
|
|
498
|
+
expect(markup).not.toContain('class="active"');
|
|
499
|
+
});
|
|
500
|
+
test("matches path segments instead of raw string prefixes", () => {
|
|
501
|
+
const TestLink = Link;
|
|
502
|
+
const markup = renderLinkMarkup("/posts/alpha", () => React.createElement(TestLink, { to: "/post", activeProps: { className: "active" } }, "Post"));
|
|
503
|
+
expect(markup).not.toContain('class="active"');
|
|
504
|
+
});
|
|
505
|
+
test("applies activeOptions in custom links created with createLink", () => {
|
|
506
|
+
const AppLink = createLink((props) => React.createElement("a", props));
|
|
507
|
+
const markup = renderLinkMarkup("/posts/alpha", () => React.createElement(AppLink, {
|
|
508
|
+
to: "/posts",
|
|
509
|
+
activeOptions: { exact: true },
|
|
510
|
+
activeProps: { className: "active" }
|
|
511
|
+
}, "Posts"));
|
|
512
|
+
expect(markup).not.toContain('class="active"');
|
|
513
|
+
});
|
|
514
|
+
});
|
|
515
|
+
describe("useMatchRoute", () => {
|
|
516
|
+
test("returns matched params for exact matches", () => {
|
|
517
|
+
const markup = renderLinkMarkup("/posts/alpha", () => {
|
|
518
|
+
const matchRoute = useMatchRoute();
|
|
519
|
+
const match = matchRoute({ to: "/posts/$postId" });
|
|
520
|
+
return React.createElement("pre", null, match === false ? "false" : JSON.stringify(match));
|
|
521
|
+
});
|
|
522
|
+
expect(markup).toContain("{"postId":"alpha"}");
|
|
523
|
+
});
|
|
524
|
+
test("supports fuzzy parent matching", () => {
|
|
525
|
+
const markup = renderLinkMarkup("/posts/alpha", () => {
|
|
526
|
+
const matchRoute = useMatchRoute();
|
|
527
|
+
const match = matchRoute({ to: "/posts", fuzzy: true });
|
|
528
|
+
return React.createElement("pre", null, match === false ? "false" : JSON.stringify(match));
|
|
529
|
+
});
|
|
530
|
+
expect(markup).toContain("{}");
|
|
531
|
+
});
|
|
532
|
+
test("supports partial param filters", () => {
|
|
533
|
+
const markup = renderLinkMarkup("/posts/alpha", () => {
|
|
534
|
+
const matchRoute = useMatchRoute();
|
|
535
|
+
const match = matchRoute({ to: "/posts/$postId", params: { postId: "beta" } });
|
|
536
|
+
return React.createElement("pre", null, match === false ? "false" : JSON.stringify(match));
|
|
537
|
+
});
|
|
538
|
+
expect(markup).toContain("false");
|
|
539
|
+
});
|
|
540
|
+
test("can include search params in the match", () => {
|
|
541
|
+
const markup = renderLinkMarkup("/posts/alpha?tab=details&count=2", () => {
|
|
542
|
+
const matchRoute = useMatchRoute();
|
|
543
|
+
const match = matchRoute({
|
|
544
|
+
to: "/posts/$postId",
|
|
545
|
+
includeSearch: true,
|
|
546
|
+
search: { tab: "details" }
|
|
547
|
+
});
|
|
548
|
+
return React.createElement("pre", null, match === false ? "false" : JSON.stringify(match));
|
|
549
|
+
});
|
|
550
|
+
expect(markup).toContain("{"postId":"alpha"}");
|
|
551
|
+
});
|
|
552
|
+
});
|
package/dist/types/router.d.ts
CHANGED
|
@@ -67,6 +67,8 @@ type ParamsInput<TParams> = TParams | ((previous: TParams) => TParams);
|
|
|
67
67
|
type SearchInput<TSearch> = TSearch | ((previous: TSearch) => TSearch) | true;
|
|
68
68
|
type ParamsOption<TParams> = keyof TParams extends never ? {
|
|
69
69
|
params?: never;
|
|
70
|
+
} : string extends keyof TParams ? {
|
|
71
|
+
params?: ParamsInput<TParams>;
|
|
70
72
|
} : {
|
|
71
73
|
params: ParamsInput<TParams>;
|
|
72
74
|
};
|
|
@@ -107,8 +109,22 @@ export type NavigateOptions<TTo extends RoutePaths = RoutePaths> = {
|
|
|
107
109
|
search?: SearchInput<SearchForTo<TTo>>;
|
|
108
110
|
} & NavigateBaseOptions & ParamsOption<ParamsForTo<TTo>>;
|
|
109
111
|
export type NavigateFn = <TTo extends RoutePaths>(options: NavigateOptions<TTo>) => Promise<void>;
|
|
112
|
+
export interface MatchRouteOptions {
|
|
113
|
+
fuzzy?: boolean;
|
|
114
|
+
includeSearch?: boolean;
|
|
115
|
+
}
|
|
116
|
+
export type UseMatchRouteOptions<TTo extends RoutePaths = RoutePaths> = {
|
|
117
|
+
to: TTo;
|
|
118
|
+
params?: Partial<ParamsForTo<TTo>>;
|
|
119
|
+
search?: Record<string, unknown>;
|
|
120
|
+
} & MatchRouteOptions;
|
|
121
|
+
export type MatchRouteFn = <TTo extends RoutePaths>(options: UseMatchRouteOptions<TTo>) => false | ParamsForTo<TTo>;
|
|
122
|
+
export interface ActiveOptions {
|
|
123
|
+
exact?: boolean;
|
|
124
|
+
}
|
|
110
125
|
export type LinkOwnProps<TTo extends RoutePaths> = NavigateOptions<TTo> & {
|
|
111
126
|
preload?: 'intent' | 'render' | false;
|
|
127
|
+
activeOptions?: ActiveOptions;
|
|
112
128
|
activeProps?: React.AnchorHTMLAttributes<HTMLAnchorElement>;
|
|
113
129
|
children?: React.ReactNode | ((ctx: {
|
|
114
130
|
isActive: boolean;
|
|
@@ -217,8 +233,8 @@ export declare class Router<TRouteTree extends AnyRoute> {
|
|
|
217
233
|
private handleHistoryChange;
|
|
218
234
|
}
|
|
219
235
|
export declare function createRouter<TRouteTree extends AnyRoute>(options: RouterOptions<TRouteTree>): Router<TRouteTree>;
|
|
220
|
-
export declare function RouterProvider({ router }: {
|
|
221
|
-
router:
|
|
236
|
+
export declare function RouterProvider<TRouteTree extends AnyRoute>({ router }: {
|
|
237
|
+
router: Router<TRouteTree>;
|
|
222
238
|
}): React.ReactElement;
|
|
223
239
|
export declare function Outlet(): React.ReactNode;
|
|
224
240
|
export declare function useRouter(): RegisteredRouter;
|
|
@@ -233,6 +249,7 @@ export declare function useSearch<TFrom extends RoutePaths>(options?: {
|
|
|
233
249
|
from?: TFrom;
|
|
234
250
|
}): SearchOfRoute<RouteById<TFrom>>;
|
|
235
251
|
export declare function useNavigate(): NavigateFn;
|
|
252
|
+
export declare function useMatchRoute(): MatchRouteFn;
|
|
236
253
|
export declare function useLocation(): ParsedLocation;
|
|
237
254
|
export declare function useRouterState<TSelection>(options: {
|
|
238
255
|
select: Selector<TSelection>;
|