@tanstack/react-router 1.167.5 → 1.168.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.
- package/dist/cjs/Match.cjs +118 -52
- package/dist/cjs/Match.cjs.map +1 -1
- package/dist/cjs/Matches.cjs +20 -20
- package/dist/cjs/Matches.cjs.map +1 -1
- package/dist/cjs/Scripts.cjs +36 -32
- package/dist/cjs/Scripts.cjs.map +1 -1
- package/dist/cjs/Transitioner.cjs +10 -16
- package/dist/cjs/Transitioner.cjs.map +1 -1
- package/dist/cjs/headContentUtils.cjs +147 -59
- package/dist/cjs/headContentUtils.cjs.map +1 -1
- package/dist/cjs/index.cjs +1 -1
- package/dist/cjs/index.dev.cjs +1 -1
- package/dist/cjs/link.cjs +34 -29
- package/dist/cjs/link.cjs.map +1 -1
- package/dist/cjs/not-found.cjs +20 -2
- package/dist/cjs/not-found.cjs.map +1 -1
- package/dist/cjs/router.cjs +2 -1
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/routerStores.cjs +21 -0
- package/dist/cjs/routerStores.cjs.map +1 -0
- package/dist/cjs/routerStores.d.cts +7 -0
- package/dist/cjs/ssr/RouterClient.cjs +1 -1
- package/dist/cjs/ssr/RouterClient.cjs.map +1 -1
- package/dist/cjs/ssr/renderRouterToStream.cjs +2 -2
- package/dist/cjs/ssr/renderRouterToStream.cjs.map +1 -1
- package/dist/cjs/ssr/renderRouterToString.cjs +1 -1
- package/dist/cjs/ssr/renderRouterToString.cjs.map +1 -1
- package/dist/cjs/useCanGoBack.cjs +7 -2
- package/dist/cjs/useCanGoBack.cjs.map +1 -1
- package/dist/cjs/useLocation.cjs +21 -2
- package/dist/cjs/useLocation.cjs.map +1 -1
- package/dist/cjs/useMatch.cjs +29 -9
- package/dist/cjs/useMatch.cjs.map +1 -1
- package/dist/cjs/useRouterState.cjs +2 -2
- package/dist/cjs/useRouterState.cjs.map +1 -1
- package/dist/esm/Match.js +118 -52
- package/dist/esm/Match.js.map +1 -1
- package/dist/esm/Matches.js +21 -21
- package/dist/esm/Matches.js.map +1 -1
- package/dist/esm/Scripts.js +36 -32
- package/dist/esm/Scripts.js.map +1 -1
- package/dist/esm/Transitioner.js +10 -16
- package/dist/esm/Transitioner.js.map +1 -1
- package/dist/esm/headContentUtils.js +148 -60
- package/dist/esm/headContentUtils.js.map +1 -1
- package/dist/esm/index.dev.js +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/link.js +34 -29
- package/dist/esm/link.js.map +1 -1
- package/dist/esm/not-found.js +20 -2
- package/dist/esm/not-found.js.map +1 -1
- package/dist/esm/router.js +2 -1
- package/dist/esm/router.js.map +1 -1
- package/dist/esm/routerStores.d.ts +7 -0
- package/dist/esm/routerStores.js +20 -0
- package/dist/esm/routerStores.js.map +1 -0
- package/dist/esm/ssr/RouterClient.js +1 -1
- package/dist/esm/ssr/RouterClient.js.map +1 -1
- package/dist/esm/ssr/renderRouterToStream.js +2 -2
- package/dist/esm/ssr/renderRouterToStream.js.map +1 -1
- package/dist/esm/ssr/renderRouterToString.js +1 -1
- package/dist/esm/ssr/renderRouterToString.js.map +1 -1
- package/dist/esm/useCanGoBack.js +6 -2
- package/dist/esm/useCanGoBack.js.map +1 -1
- package/dist/esm/useLocation.js +20 -2
- package/dist/esm/useLocation.js.map +1 -1
- package/dist/esm/useMatch.js +29 -9
- package/dist/esm/useMatch.js.map +1 -1
- package/dist/esm/useRouterState.js +2 -2
- package/dist/esm/useRouterState.js.map +1 -1
- package/dist/llms/rules/api.d.ts +1 -1
- package/dist/llms/rules/api.js +3 -9
- package/package.json +3 -3
- package/src/Match.tsx +218 -78
- package/src/Matches.tsx +45 -25
- package/src/Scripts.tsx +72 -44
- package/src/Transitioner.tsx +24 -16
- package/src/headContentUtils.tsx +210 -27
- package/src/link.tsx +66 -71
- package/src/not-found.tsx +41 -4
- package/src/router.ts +2 -1
- package/src/routerStores.ts +26 -0
- package/src/ssr/RouterClient.tsx +1 -1
- package/src/ssr/renderRouterToStream.tsx +2 -2
- package/src/ssr/renderRouterToString.tsx +1 -1
- package/src/useCanGoBack.ts +14 -2
- package/src/useLocation.tsx +32 -5
- package/src/useMatch.tsx +61 -21
- package/src/useRouterState.tsx +4 -2
package/dist/esm/Transitioner.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useLayoutEffect, usePrevious } from "./utils.js";
|
|
2
2
|
import { useRouter } from "./useRouter.js";
|
|
3
|
-
import { useRouterState } from "./useRouterState.js";
|
|
4
3
|
import { getLocationChangeInfo, handleHashScroll, trimPathRight } from "@tanstack/router-core";
|
|
5
4
|
import * as React$1 from "react";
|
|
5
|
+
import { batch, useStore } from "@tanstack/react-store";
|
|
6
6
|
//#region src/Transitioner.tsx
|
|
7
7
|
function Transitioner() {
|
|
8
8
|
const router = useRouter();
|
|
@@ -11,13 +11,8 @@ function Transitioner() {
|
|
|
11
11
|
mounted: false
|
|
12
12
|
});
|
|
13
13
|
const [isTransitioning, setIsTransitioning] = React$1.useState(false);
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
isLoading: s.isLoading,
|
|
17
|
-
hasPendingMatches: s.matches.some((d) => d.status === "pending")
|
|
18
|
-
}),
|
|
19
|
-
structuralSharing: true
|
|
20
|
-
});
|
|
14
|
+
const isLoading = useStore(router.stores.isLoading, (value) => value);
|
|
15
|
+
const hasPendingMatches = useStore(router.stores.hasPendingMatches, (value) => value);
|
|
21
16
|
const previousIsLoading = usePrevious(isLoading);
|
|
22
17
|
const isAnyPending = isLoading || isTransitioning || hasPendingMatches;
|
|
23
18
|
const previousIsAnyPending = usePrevious(isAnyPending);
|
|
@@ -66,7 +61,7 @@ function Transitioner() {
|
|
|
66
61
|
useLayoutEffect(() => {
|
|
67
62
|
if (previousIsLoading && !isLoading) router.emit({
|
|
68
63
|
type: "onLoad",
|
|
69
|
-
...getLocationChangeInfo(router.state)
|
|
64
|
+
...getLocationChangeInfo(router.stores.location.state, router.stores.resolvedLocation.state)
|
|
70
65
|
});
|
|
71
66
|
}, [
|
|
72
67
|
previousIsLoading,
|
|
@@ -76,7 +71,7 @@ function Transitioner() {
|
|
|
76
71
|
useLayoutEffect(() => {
|
|
77
72
|
if (previousIsPagePending && !isPagePending) router.emit({
|
|
78
73
|
type: "onBeforeRouteMount",
|
|
79
|
-
...getLocationChangeInfo(router.state)
|
|
74
|
+
...getLocationChangeInfo(router.stores.location.state, router.stores.resolvedLocation.state)
|
|
80
75
|
});
|
|
81
76
|
}, [
|
|
82
77
|
isPagePending,
|
|
@@ -85,16 +80,15 @@ function Transitioner() {
|
|
|
85
80
|
]);
|
|
86
81
|
useLayoutEffect(() => {
|
|
87
82
|
if (previousIsAnyPending && !isAnyPending) {
|
|
88
|
-
const changeInfo = getLocationChangeInfo(router.state);
|
|
83
|
+
const changeInfo = getLocationChangeInfo(router.stores.location.state, router.stores.resolvedLocation.state);
|
|
89
84
|
router.emit({
|
|
90
85
|
type: "onResolved",
|
|
91
86
|
...changeInfo
|
|
92
87
|
});
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}));
|
|
88
|
+
batch(() => {
|
|
89
|
+
router.stores.status.setState(() => "idle");
|
|
90
|
+
router.stores.resolvedLocation.setState(() => router.stores.location.state);
|
|
91
|
+
});
|
|
98
92
|
if (changeInfo.hrefChanged) handleHashScroll(router);
|
|
99
93
|
}
|
|
100
94
|
}, [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Transitioner.js","names":[],"sources":["../../src/Transitioner.tsx"],"sourcesContent":["import * as React from 'react'\nimport {\n getLocationChangeInfo,\n handleHashScroll,\n trimPathRight,\n} from '@tanstack/router-core'\nimport { useLayoutEffect, usePrevious } from './utils'\nimport { useRouter } from './useRouter'\
|
|
1
|
+
{"version":3,"file":"Transitioner.js","names":[],"sources":["../../src/Transitioner.tsx"],"sourcesContent":["import * as React from 'react'\nimport { batch, useStore } from '@tanstack/react-store'\nimport {\n getLocationChangeInfo,\n handleHashScroll,\n trimPathRight,\n} from '@tanstack/router-core'\nimport { useLayoutEffect, usePrevious } from './utils'\nimport { useRouter } from './useRouter'\n\nexport function Transitioner() {\n const router = useRouter()\n const mountLoadForRouter = React.useRef({ router, mounted: false })\n\n const [isTransitioning, setIsTransitioning] = React.useState(false)\n // Track pending state changes\n const isLoading = useStore(router.stores.isLoading, (value) => value)\n const hasPendingMatches = useStore(\n router.stores.hasPendingMatches,\n (value) => value,\n )\n\n const previousIsLoading = usePrevious(isLoading)\n\n const isAnyPending = isLoading || isTransitioning || hasPendingMatches\n const previousIsAnyPending = usePrevious(isAnyPending)\n\n const isPagePending = isLoading || hasPendingMatches\n const previousIsPagePending = usePrevious(isPagePending)\n\n router.startTransition = (fn: () => void) => {\n setIsTransitioning(true)\n React.startTransition(() => {\n fn()\n setIsTransitioning(false)\n })\n }\n\n // Subscribe to location changes\n // and try to load the new location\n React.useEffect(() => {\n const unsub = router.history.subscribe(router.load)\n\n const nextLocation = router.buildLocation({\n to: router.latestLocation.pathname,\n search: true,\n params: true,\n hash: true,\n state: true,\n _includeValidateSearch: true,\n })\n\n // Check if the current URL matches the canonical form.\n // Compare publicHref (browser-facing URL) for consistency with\n // the server-side redirect check in router.beforeLoad.\n if (\n trimPathRight(router.latestLocation.publicHref) !==\n trimPathRight(nextLocation.publicHref)\n ) {\n router.commitLocation({ ...nextLocation, replace: true })\n }\n\n return () => {\n unsub()\n }\n }, [router, router.history])\n\n // Try to load the initial location\n useLayoutEffect(() => {\n if (\n // if we are hydrating from SSR, loading is triggered in ssr-client\n (typeof window !== 'undefined' && router.ssr) ||\n (mountLoadForRouter.current.router === router &&\n mountLoadForRouter.current.mounted)\n ) {\n return\n }\n mountLoadForRouter.current = { router, mounted: true }\n\n const tryLoad = async () => {\n try {\n await router.load()\n } catch (err) {\n console.error(err)\n }\n }\n\n tryLoad()\n }, [router])\n\n useLayoutEffect(() => {\n // The router was loading and now it's not\n if (previousIsLoading && !isLoading) {\n router.emit({\n type: 'onLoad', // When the new URL has committed, when the new matches have been loaded into state.matches\n ...getLocationChangeInfo(\n router.stores.location.state,\n router.stores.resolvedLocation.state,\n ),\n })\n }\n }, [previousIsLoading, router, isLoading])\n\n useLayoutEffect(() => {\n // emit onBeforeRouteMount\n if (previousIsPagePending && !isPagePending) {\n router.emit({\n type: 'onBeforeRouteMount',\n ...getLocationChangeInfo(\n router.stores.location.state,\n router.stores.resolvedLocation.state,\n ),\n })\n }\n }, [isPagePending, previousIsPagePending, router])\n\n useLayoutEffect(() => {\n if (previousIsAnyPending && !isAnyPending) {\n const changeInfo = getLocationChangeInfo(\n router.stores.location.state,\n router.stores.resolvedLocation.state,\n )\n router.emit({\n type: 'onResolved',\n ...changeInfo,\n })\n\n batch(() => {\n router.stores.status.setState(() => 'idle')\n router.stores.resolvedLocation.setState(\n () => router.stores.location.state,\n )\n })\n\n if (changeInfo.hrefChanged) {\n handleHashScroll(router)\n }\n }\n }, [isAnyPending, previousIsAnyPending, router])\n\n return null\n}\n"],"mappings":";;;;;;AAUA,SAAgB,eAAe;CAC7B,MAAM,SAAS,WAAW;CAC1B,MAAM,qBAAqB,QAAM,OAAO;EAAE;EAAQ,SAAS;EAAO,CAAC;CAEnE,MAAM,CAAC,iBAAiB,sBAAsB,QAAM,SAAS,MAAM;CAEnE,MAAM,YAAY,SAAS,OAAO,OAAO,YAAY,UAAU,MAAM;CACrE,MAAM,oBAAoB,SACxB,OAAO,OAAO,oBACb,UAAU,MACZ;CAED,MAAM,oBAAoB,YAAY,UAAU;CAEhD,MAAM,eAAe,aAAa,mBAAmB;CACrD,MAAM,uBAAuB,YAAY,aAAa;CAEtD,MAAM,gBAAgB,aAAa;CACnC,MAAM,wBAAwB,YAAY,cAAc;AAExD,QAAO,mBAAmB,OAAmB;AAC3C,qBAAmB,KAAK;AACxB,UAAM,sBAAsB;AAC1B,OAAI;AACJ,sBAAmB,MAAM;IACzB;;AAKJ,SAAM,gBAAgB;EACpB,MAAM,QAAQ,OAAO,QAAQ,UAAU,OAAO,KAAK;EAEnD,MAAM,eAAe,OAAO,cAAc;GACxC,IAAI,OAAO,eAAe;GAC1B,QAAQ;GACR,QAAQ;GACR,MAAM;GACN,OAAO;GACP,wBAAwB;GACzB,CAAC;AAKF,MACE,cAAc,OAAO,eAAe,WAAW,KAC/C,cAAc,aAAa,WAAW,CAEtC,QAAO,eAAe;GAAE,GAAG;GAAc,SAAS;GAAM,CAAC;AAG3D,eAAa;AACX,UAAO;;IAER,CAAC,QAAQ,OAAO,QAAQ,CAAC;AAG5B,uBAAsB;AACpB,MAEG,OAAO,WAAW,eAAe,OAAO,OACxC,mBAAmB,QAAQ,WAAW,UACrC,mBAAmB,QAAQ,QAE7B;AAEF,qBAAmB,UAAU;GAAE;GAAQ,SAAS;GAAM;EAEtD,MAAM,UAAU,YAAY;AAC1B,OAAI;AACF,UAAM,OAAO,MAAM;YACZ,KAAK;AACZ,YAAQ,MAAM,IAAI;;;AAItB,WAAS;IACR,CAAC,OAAO,CAAC;AAEZ,uBAAsB;AAEpB,MAAI,qBAAqB,CAAC,UACxB,QAAO,KAAK;GACV,MAAM;GACN,GAAG,sBACD,OAAO,OAAO,SAAS,OACvB,OAAO,OAAO,iBAAiB,MAChC;GACF,CAAC;IAEH;EAAC;EAAmB;EAAQ;EAAU,CAAC;AAE1C,uBAAsB;AAEpB,MAAI,yBAAyB,CAAC,cAC5B,QAAO,KAAK;GACV,MAAM;GACN,GAAG,sBACD,OAAO,OAAO,SAAS,OACvB,OAAO,OAAO,iBAAiB,MAChC;GACF,CAAC;IAEH;EAAC;EAAe;EAAuB;EAAO,CAAC;AAElD,uBAAsB;AACpB,MAAI,wBAAwB,CAAC,cAAc;GACzC,MAAM,aAAa,sBACjB,OAAO,OAAO,SAAS,OACvB,OAAO,OAAO,iBAAiB,MAChC;AACD,UAAO,KAAK;IACV,MAAM;IACN,GAAG;IACJ,CAAC;AAEF,eAAY;AACV,WAAO,OAAO,OAAO,eAAe,OAAO;AAC3C,WAAO,OAAO,iBAAiB,eACvB,OAAO,OAAO,SAAS,MAC9B;KACD;AAEF,OAAI,WAAW,YACb,kBAAiB,OAAO;;IAG3B;EAAC;EAAc;EAAsB;EAAO,CAAC;AAEhD,QAAO"}
|
|
@@ -1,8 +1,107 @@
|
|
|
1
1
|
import { useRouter } from "./useRouter.js";
|
|
2
|
-
import {
|
|
3
|
-
import { escapeHtml } from "@tanstack/router-core";
|
|
2
|
+
import { deepEqual, escapeHtml } from "@tanstack/router-core";
|
|
4
3
|
import * as React$1 from "react";
|
|
4
|
+
import { useStore } from "@tanstack/react-store";
|
|
5
|
+
import { isServer } from "@tanstack/router-core/isServer";
|
|
5
6
|
//#region src/headContentUtils.tsx
|
|
7
|
+
function buildTagsFromMatches(router, nonce, matches) {
|
|
8
|
+
const routeMeta = matches.map((match) => match.meta).filter(Boolean);
|
|
9
|
+
const resultMeta = [];
|
|
10
|
+
const metaByAttribute = {};
|
|
11
|
+
let title;
|
|
12
|
+
for (let i = routeMeta.length - 1; i >= 0; i--) {
|
|
13
|
+
const metas = routeMeta[i];
|
|
14
|
+
for (let j = metas.length - 1; j >= 0; j--) {
|
|
15
|
+
const m = metas[j];
|
|
16
|
+
if (!m) continue;
|
|
17
|
+
if (m.title) {
|
|
18
|
+
if (!title) title = {
|
|
19
|
+
tag: "title",
|
|
20
|
+
children: m.title
|
|
21
|
+
};
|
|
22
|
+
} else if ("script:ld+json" in m) try {
|
|
23
|
+
const json = JSON.stringify(m["script:ld+json"]);
|
|
24
|
+
resultMeta.push({
|
|
25
|
+
tag: "script",
|
|
26
|
+
attrs: { type: "application/ld+json" },
|
|
27
|
+
children: escapeHtml(json)
|
|
28
|
+
});
|
|
29
|
+
} catch {}
|
|
30
|
+
else {
|
|
31
|
+
const attribute = m.name ?? m.property;
|
|
32
|
+
if (attribute) if (metaByAttribute[attribute]) continue;
|
|
33
|
+
else metaByAttribute[attribute] = true;
|
|
34
|
+
resultMeta.push({
|
|
35
|
+
tag: "meta",
|
|
36
|
+
attrs: {
|
|
37
|
+
...m,
|
|
38
|
+
nonce
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (title) resultMeta.push(title);
|
|
45
|
+
if (nonce) resultMeta.push({
|
|
46
|
+
tag: "meta",
|
|
47
|
+
attrs: {
|
|
48
|
+
property: "csp-nonce",
|
|
49
|
+
content: nonce
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
resultMeta.reverse();
|
|
53
|
+
const constructedLinks = matches.map((match) => match.links).filter(Boolean).flat(1).map((link) => ({
|
|
54
|
+
tag: "link",
|
|
55
|
+
attrs: {
|
|
56
|
+
...link,
|
|
57
|
+
nonce
|
|
58
|
+
}
|
|
59
|
+
}));
|
|
60
|
+
const manifest = router.ssr?.manifest;
|
|
61
|
+
const assetLinks = matches.map((match) => manifest?.routes[match.routeId]?.assets ?? []).filter(Boolean).flat(1).filter((asset) => asset.tag === "link").map((asset) => ({
|
|
62
|
+
tag: "link",
|
|
63
|
+
attrs: {
|
|
64
|
+
...asset.attrs,
|
|
65
|
+
suppressHydrationWarning: true,
|
|
66
|
+
nonce
|
|
67
|
+
}
|
|
68
|
+
}));
|
|
69
|
+
const preloadLinks = [];
|
|
70
|
+
matches.map((match) => router.looseRoutesById[match.routeId]).forEach((route) => router.ssr?.manifest?.routes[route.id]?.preloads?.filter(Boolean).forEach((preload) => {
|
|
71
|
+
preloadLinks.push({
|
|
72
|
+
tag: "link",
|
|
73
|
+
attrs: {
|
|
74
|
+
rel: "modulepreload",
|
|
75
|
+
href: preload,
|
|
76
|
+
nonce
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}));
|
|
80
|
+
const styles = matches.map((match) => match.styles).flat(1).filter(Boolean).map(({ children, ...attrs }) => ({
|
|
81
|
+
tag: "style",
|
|
82
|
+
attrs: {
|
|
83
|
+
...attrs,
|
|
84
|
+
nonce
|
|
85
|
+
},
|
|
86
|
+
children
|
|
87
|
+
}));
|
|
88
|
+
const headScripts = matches.map((match) => match.headScripts).flat(1).filter(Boolean).map(({ children, ...script }) => ({
|
|
89
|
+
tag: "script",
|
|
90
|
+
attrs: {
|
|
91
|
+
...script,
|
|
92
|
+
nonce
|
|
93
|
+
},
|
|
94
|
+
children
|
|
95
|
+
}));
|
|
96
|
+
return uniqBy([
|
|
97
|
+
...resultMeta,
|
|
98
|
+
...preloadLinks,
|
|
99
|
+
...constructedLinks,
|
|
100
|
+
...assetLinks,
|
|
101
|
+
...styles,
|
|
102
|
+
...headScripts
|
|
103
|
+
], (d) => JSON.stringify(d));
|
|
104
|
+
}
|
|
6
105
|
/**
|
|
7
106
|
* Build the list of head/link/meta/script tags to render for active matches.
|
|
8
107
|
* Used internally by `HeadContent`.
|
|
@@ -10,9 +109,10 @@ import * as React$1 from "react";
|
|
|
10
109
|
var useTags = () => {
|
|
11
110
|
const router = useRouter();
|
|
12
111
|
const nonce = router.options.ssr?.nonce;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
112
|
+
if (isServer ?? router.isServer) return buildTagsFromMatches(router, nonce, router.stores.activeMatchesSnapshot.state);
|
|
113
|
+
const routeMeta = useStore(router.stores.activeMatchesSnapshot, (matches) => {
|
|
114
|
+
return matches.map((match) => match.meta).filter(Boolean);
|
|
115
|
+
}, deepEqual);
|
|
16
116
|
const meta = React$1.useMemo(() => {
|
|
17
117
|
const resultMeta = [];
|
|
18
118
|
const metaByAttribute = {};
|
|
@@ -60,67 +160,55 @@ var useTags = () => {
|
|
|
60
160
|
resultMeta.reverse();
|
|
61
161
|
return resultMeta;
|
|
62
162
|
}, [routeMeta, nonce]);
|
|
63
|
-
const links =
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
163
|
+
const links = useStore(router.stores.activeMatchesSnapshot, (matches) => {
|
|
164
|
+
const constructed = matches.map((match) => match.links).filter(Boolean).flat(1).map((link) => ({
|
|
165
|
+
tag: "link",
|
|
166
|
+
attrs: {
|
|
167
|
+
...link,
|
|
168
|
+
nonce
|
|
169
|
+
}
|
|
170
|
+
}));
|
|
171
|
+
const manifest = router.ssr?.manifest;
|
|
172
|
+
const assets = matches.map((match) => manifest?.routes[match.routeId]?.assets ?? []).filter(Boolean).flat(1).filter((asset) => asset.tag === "link").map((asset) => ({
|
|
173
|
+
tag: "link",
|
|
174
|
+
attrs: {
|
|
175
|
+
...asset.attrs,
|
|
176
|
+
suppressHydrationWarning: true,
|
|
177
|
+
nonce
|
|
178
|
+
}
|
|
179
|
+
}));
|
|
180
|
+
return [...constructed, ...assets];
|
|
181
|
+
}, deepEqual);
|
|
182
|
+
const preloadLinks = useStore(router.stores.activeMatchesSnapshot, (matches) => {
|
|
183
|
+
const preloadLinks = [];
|
|
184
|
+
matches.map((match) => router.looseRoutesById[match.routeId]).forEach((route) => router.ssr?.manifest?.routes[route.id]?.preloads?.filter(Boolean).forEach((preload) => {
|
|
185
|
+
preloadLinks.push({
|
|
74
186
|
tag: "link",
|
|
75
187
|
attrs: {
|
|
76
|
-
|
|
77
|
-
|
|
188
|
+
rel: "modulepreload",
|
|
189
|
+
href: preload,
|
|
78
190
|
nonce
|
|
79
191
|
}
|
|
80
|
-
})
|
|
81
|
-
|
|
192
|
+
});
|
|
193
|
+
}));
|
|
194
|
+
return preloadLinks;
|
|
195
|
+
}, deepEqual);
|
|
196
|
+
const styles = useStore(router.stores.activeMatchesSnapshot, (matches) => matches.map((match) => match.styles).flat(1).filter(Boolean).map(({ children, ...attrs }) => ({
|
|
197
|
+
tag: "style",
|
|
198
|
+
attrs: {
|
|
199
|
+
...attrs,
|
|
200
|
+
nonce
|
|
82
201
|
},
|
|
83
|
-
|
|
84
|
-
});
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
tag: "link",
|
|
91
|
-
attrs: {
|
|
92
|
-
rel: "modulepreload",
|
|
93
|
-
href: preload,
|
|
94
|
-
nonce
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
}));
|
|
98
|
-
return preloadLinks;
|
|
202
|
+
children
|
|
203
|
+
})), deepEqual);
|
|
204
|
+
const headScripts = useStore(router.stores.activeMatchesSnapshot, (matches) => matches.map((match) => match.headScripts).flat(1).filter(Boolean).map(({ children, ...script }) => ({
|
|
205
|
+
tag: "script",
|
|
206
|
+
attrs: {
|
|
207
|
+
...script,
|
|
208
|
+
nonce
|
|
99
209
|
},
|
|
100
|
-
|
|
101
|
-
});
|
|
102
|
-
const styles = useRouterState({
|
|
103
|
-
select: (state) => state.matches.map((match) => match.styles).flat(1).filter(Boolean).map(({ children, ...attrs }) => ({
|
|
104
|
-
tag: "style",
|
|
105
|
-
attrs: {
|
|
106
|
-
...attrs,
|
|
107
|
-
nonce
|
|
108
|
-
},
|
|
109
|
-
children
|
|
110
|
-
})),
|
|
111
|
-
structuralSharing: true
|
|
112
|
-
});
|
|
113
|
-
const headScripts = useRouterState({
|
|
114
|
-
select: (state) => state.matches.map((match) => match.headScripts).flat(1).filter(Boolean).map(({ children, ...script }) => ({
|
|
115
|
-
tag: "script",
|
|
116
|
-
attrs: {
|
|
117
|
-
...script,
|
|
118
|
-
nonce
|
|
119
|
-
},
|
|
120
|
-
children
|
|
121
|
-
})),
|
|
122
|
-
structuralSharing: true
|
|
123
|
-
});
|
|
210
|
+
children
|
|
211
|
+
})), deepEqual);
|
|
124
212
|
return uniqBy([
|
|
125
213
|
...meta,
|
|
126
214
|
...preloadLinks,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"headContentUtils.js","names":[],"sources":["../../src/headContentUtils.tsx"],"sourcesContent":["import * as React from 'react'\nimport { escapeHtml } from '@tanstack/router-core'\nimport { useRouter } from './useRouter'\nimport { useRouterState } from './useRouterState'\nimport type { RouterManagedTag } from '@tanstack/router-core'\n\n/**\n * Build the list of head/link/meta/script tags to render for active matches.\n * Used internally by `HeadContent`.\n */\nexport const useTags = () => {\n const router = useRouter()\n const nonce = router.options.ssr?.nonce\n const routeMeta = useRouterState({\n select: (state) => {\n return state.matches.map((match) => match.meta!).filter(Boolean)\n },\n })\n\n const meta: Array<RouterManagedTag> = React.useMemo(() => {\n const resultMeta: Array<RouterManagedTag> = []\n const metaByAttribute: Record<string, true> = {}\n let title: RouterManagedTag | undefined\n for (let i = routeMeta.length - 1; i >= 0; i--) {\n const metas = routeMeta[i]!\n for (let j = metas.length - 1; j >= 0; j--) {\n const m = metas[j]\n if (!m) continue\n\n if (m.title) {\n if (!title) {\n title = {\n tag: 'title',\n children: m.title,\n }\n }\n } else if ('script:ld+json' in m) {\n // Handle JSON-LD structured data\n // Content is HTML-escaped to prevent XSS when injected via dangerouslySetInnerHTML\n try {\n const json = JSON.stringify(m['script:ld+json'])\n resultMeta.push({\n tag: 'script',\n attrs: {\n type: 'application/ld+json',\n },\n children: escapeHtml(json),\n })\n } catch {\n // Skip invalid JSON-LD objects\n }\n } else {\n const attribute = m.name ?? m.property\n if (attribute) {\n if (metaByAttribute[attribute]) {\n continue\n } else {\n metaByAttribute[attribute] = true\n }\n }\n\n resultMeta.push({\n tag: 'meta',\n attrs: {\n ...m,\n nonce,\n },\n })\n }\n }\n }\n\n if (title) {\n resultMeta.push(title)\n }\n\n if (nonce) {\n resultMeta.push({\n tag: 'meta',\n attrs: {\n property: 'csp-nonce',\n content: nonce,\n },\n })\n }\n resultMeta.reverse()\n\n return resultMeta\n }, [routeMeta, nonce])\n\n const links = useRouterState({\n select: (state) => {\n const constructed = state.matches\n .map((match) => match.links!)\n .filter(Boolean)\n .flat(1)\n .map((link) => ({\n tag: 'link',\n attrs: {\n ...link,\n nonce,\n },\n })) satisfies Array<RouterManagedTag>\n\n const manifest = router.ssr?.manifest\n\n // These are the assets extracted from the ViteManifest\n // using the `startManifestPlugin`\n const assets = state.matches\n .map((match) => manifest?.routes[match.routeId]?.assets ?? [])\n .filter(Boolean)\n .flat(1)\n .filter((asset) => asset.tag === 'link')\n .map(\n (asset) =>\n ({\n tag: 'link',\n attrs: {\n ...asset.attrs,\n suppressHydrationWarning: true,\n nonce,\n },\n }) satisfies RouterManagedTag,\n )\n\n return [...constructed, ...assets]\n },\n structuralSharing: true as any,\n })\n\n const preloadLinks = useRouterState({\n select: (state) => {\n const preloadLinks: Array<RouterManagedTag> = []\n\n state.matches\n .map((match) => router.looseRoutesById[match.routeId]!)\n .forEach((route) =>\n router.ssr?.manifest?.routes[route.id]?.preloads\n ?.filter(Boolean)\n .forEach((preload) => {\n preloadLinks.push({\n tag: 'link',\n attrs: {\n rel: 'modulepreload',\n href: preload,\n nonce,\n },\n })\n }),\n )\n\n return preloadLinks\n },\n structuralSharing: true as any,\n })\n\n const styles = useRouterState({\n select: (state) =>\n (\n state.matches\n .map((match) => match.styles!)\n .flat(1)\n .filter(Boolean) as Array<RouterManagedTag>\n ).map(({ children, ...attrs }) => ({\n tag: 'style',\n attrs: {\n ...attrs,\n nonce,\n },\n children,\n })),\n structuralSharing: true as any,\n })\n\n const headScripts: Array<RouterManagedTag> = useRouterState({\n select: (state) =>\n (\n state.matches\n .map((match) => match.headScripts!)\n .flat(1)\n .filter(Boolean) as Array<RouterManagedTag>\n ).map(({ children, ...script }) => ({\n tag: 'script',\n attrs: {\n ...script,\n nonce,\n },\n children,\n })),\n structuralSharing: true as any,\n })\n\n return uniqBy(\n [\n ...meta,\n ...preloadLinks,\n ...links,\n ...styles,\n ...headScripts,\n ] as Array<RouterManagedTag>,\n (d) => {\n return JSON.stringify(d)\n },\n )\n}\n\nexport function uniqBy<T>(arr: Array<T>, fn: (item: T) => string) {\n const seen = new Set<string>()\n return arr.filter((item) => {\n const key = fn(item)\n if (seen.has(key)) {\n return false\n }\n seen.add(key)\n return true\n })\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"headContentUtils.js","names":[],"sources":["../../src/headContentUtils.tsx"],"sourcesContent":["import * as React from 'react'\nimport { useStore } from '@tanstack/react-store'\nimport { deepEqual, escapeHtml } from '@tanstack/router-core'\nimport { isServer } from '@tanstack/router-core/isServer'\nimport { useRouter } from './useRouter'\nimport type { RouterManagedTag } from '@tanstack/router-core'\n\nfunction buildTagsFromMatches(\n router: ReturnType<typeof useRouter>,\n nonce: string | undefined,\n matches: Array<any>,\n): Array<RouterManagedTag> {\n const routeMeta = matches.map((match) => match.meta!).filter(Boolean)\n\n const resultMeta: Array<RouterManagedTag> = []\n const metaByAttribute: Record<string, true> = {}\n let title: RouterManagedTag | undefined\n for (let i = routeMeta.length - 1; i >= 0; i--) {\n const metas = routeMeta[i]!\n for (let j = metas.length - 1; j >= 0; j--) {\n const m = metas[j]\n if (!m) continue\n\n if (m.title) {\n if (!title) {\n title = {\n tag: 'title',\n children: m.title,\n }\n }\n } else if ('script:ld+json' in m) {\n try {\n const json = JSON.stringify(m['script:ld+json'])\n resultMeta.push({\n tag: 'script',\n attrs: {\n type: 'application/ld+json',\n },\n children: escapeHtml(json),\n })\n } catch {\n // Skip invalid JSON-LD objects\n }\n } else {\n const attribute = m.name ?? m.property\n if (attribute) {\n if (metaByAttribute[attribute]) {\n continue\n } else {\n metaByAttribute[attribute] = true\n }\n }\n\n resultMeta.push({\n tag: 'meta',\n attrs: {\n ...m,\n nonce,\n },\n })\n }\n }\n }\n\n if (title) {\n resultMeta.push(title)\n }\n\n if (nonce) {\n resultMeta.push({\n tag: 'meta',\n attrs: {\n property: 'csp-nonce',\n content: nonce,\n },\n })\n }\n resultMeta.reverse()\n\n const constructedLinks = matches\n .map((match) => match.links!)\n .filter(Boolean)\n .flat(1)\n .map((link) => ({\n tag: 'link',\n attrs: {\n ...link,\n nonce,\n },\n })) satisfies Array<RouterManagedTag>\n\n const manifest = router.ssr?.manifest\n const assetLinks = matches\n .map((match) => manifest?.routes[match.routeId]?.assets ?? [])\n .filter(Boolean)\n .flat(1)\n .filter((asset) => asset.tag === 'link')\n .map(\n (asset) =>\n ({\n tag: 'link',\n attrs: {\n ...asset.attrs,\n suppressHydrationWarning: true,\n nonce,\n },\n }) satisfies RouterManagedTag,\n )\n\n const preloadLinks: Array<RouterManagedTag> = []\n matches\n .map((match) => router.looseRoutesById[match.routeId]!)\n .forEach((route) =>\n router.ssr?.manifest?.routes[route.id]?.preloads\n ?.filter(Boolean)\n .forEach((preload) => {\n preloadLinks.push({\n tag: 'link',\n attrs: {\n rel: 'modulepreload',\n href: preload,\n nonce,\n },\n })\n }),\n )\n\n const styles = (\n matches\n .map((match) => match.styles!)\n .flat(1)\n .filter(Boolean) as Array<RouterManagedTag>\n ).map(({ children, ...attrs }) => ({\n tag: 'style',\n attrs: {\n ...attrs,\n nonce,\n },\n children,\n }))\n\n const headScripts = (\n matches\n .map((match) => match.headScripts!)\n .flat(1)\n .filter(Boolean) as Array<RouterManagedTag>\n ).map(({ children, ...script }) => ({\n tag: 'script',\n attrs: {\n ...script,\n nonce,\n },\n children,\n }))\n\n return uniqBy(\n [\n ...resultMeta,\n ...preloadLinks,\n ...constructedLinks,\n ...assetLinks,\n ...styles,\n ...headScripts,\n ] as Array<RouterManagedTag>,\n (d) => JSON.stringify(d),\n )\n}\n\n/**\n * Build the list of head/link/meta/script tags to render for active matches.\n * Used internally by `HeadContent`.\n */\nexport const useTags = () => {\n const router = useRouter()\n const nonce = router.options.ssr?.nonce\n\n if (isServer ?? router.isServer) {\n return buildTagsFromMatches(\n router,\n nonce,\n router.stores.activeMatchesSnapshot.state,\n )\n }\n\n // eslint-disable-next-line react-hooks/rules-of-hooks -- condition is static\n const routeMeta = useStore(\n router.stores.activeMatchesSnapshot,\n (matches) => {\n return matches.map((match) => match.meta!).filter(Boolean)\n },\n deepEqual,\n )\n\n // eslint-disable-next-line react-hooks/rules-of-hooks -- condition is static\n const meta: Array<RouterManagedTag> = React.useMemo(() => {\n const resultMeta: Array<RouterManagedTag> = []\n const metaByAttribute: Record<string, true> = {}\n let title: RouterManagedTag | undefined\n for (let i = routeMeta.length - 1; i >= 0; i--) {\n const metas = routeMeta[i]!\n for (let j = metas.length - 1; j >= 0; j--) {\n const m = metas[j]\n if (!m) continue\n\n if (m.title) {\n if (!title) {\n title = {\n tag: 'title',\n children: m.title,\n }\n }\n } else if ('script:ld+json' in m) {\n // Handle JSON-LD structured data\n // Content is HTML-escaped to prevent XSS when injected via dangerouslySetInnerHTML\n try {\n const json = JSON.stringify(m['script:ld+json'])\n resultMeta.push({\n tag: 'script',\n attrs: {\n type: 'application/ld+json',\n },\n children: escapeHtml(json),\n })\n } catch {\n // Skip invalid JSON-LD objects\n }\n } else {\n const attribute = m.name ?? m.property\n if (attribute) {\n if (metaByAttribute[attribute]) {\n continue\n } else {\n metaByAttribute[attribute] = true\n }\n }\n\n resultMeta.push({\n tag: 'meta',\n attrs: {\n ...m,\n nonce,\n },\n })\n }\n }\n }\n\n if (title) {\n resultMeta.push(title)\n }\n\n if (nonce) {\n resultMeta.push({\n tag: 'meta',\n attrs: {\n property: 'csp-nonce',\n content: nonce,\n },\n })\n }\n resultMeta.reverse()\n\n return resultMeta\n }, [routeMeta, nonce])\n\n // eslint-disable-next-line react-hooks/rules-of-hooks -- condition is static\n const links = useStore(\n router.stores.activeMatchesSnapshot,\n (matches) => {\n const constructed = matches\n .map((match) => match.links!)\n .filter(Boolean)\n .flat(1)\n .map((link) => ({\n tag: 'link',\n attrs: {\n ...link,\n nonce,\n },\n })) satisfies Array<RouterManagedTag>\n\n const manifest = router.ssr?.manifest\n\n // These are the assets extracted from the ViteManifest\n // using the `startManifestPlugin`\n const assets = matches\n .map((match) => manifest?.routes[match.routeId]?.assets ?? [])\n .filter(Boolean)\n .flat(1)\n .filter((asset) => asset.tag === 'link')\n .map(\n (asset) =>\n ({\n tag: 'link',\n attrs: {\n ...asset.attrs,\n suppressHydrationWarning: true,\n nonce,\n },\n }) satisfies RouterManagedTag,\n )\n\n return [...constructed, ...assets]\n },\n deepEqual,\n )\n\n // eslint-disable-next-line react-hooks/rules-of-hooks -- condition is static\n const preloadLinks = useStore(\n router.stores.activeMatchesSnapshot,\n (matches) => {\n const preloadLinks: Array<RouterManagedTag> = []\n\n matches\n .map((match) => router.looseRoutesById[match.routeId]!)\n .forEach((route) =>\n router.ssr?.manifest?.routes[route.id]?.preloads\n ?.filter(Boolean)\n .forEach((preload) => {\n preloadLinks.push({\n tag: 'link',\n attrs: {\n rel: 'modulepreload',\n href: preload,\n nonce,\n },\n })\n }),\n )\n\n return preloadLinks\n },\n deepEqual,\n )\n\n // eslint-disable-next-line react-hooks/rules-of-hooks -- condition is static\n const styles = useStore(\n router.stores.activeMatchesSnapshot,\n (matches) =>\n (\n matches\n .map((match) => match.styles!)\n .flat(1)\n .filter(Boolean) as Array<RouterManagedTag>\n ).map(({ children, ...attrs }) => ({\n tag: 'style',\n attrs: {\n ...attrs,\n nonce,\n },\n children,\n })),\n deepEqual,\n )\n\n // eslint-disable-next-line react-hooks/rules-of-hooks -- condition is static\n const headScripts: Array<RouterManagedTag> = useStore(\n router.stores.activeMatchesSnapshot,\n (matches) =>\n (\n matches\n .map((match) => match.headScripts!)\n .flat(1)\n .filter(Boolean) as Array<RouterManagedTag>\n ).map(({ children, ...script }) => ({\n tag: 'script',\n attrs: {\n ...script,\n nonce,\n },\n children,\n })),\n deepEqual,\n )\n\n return uniqBy(\n [\n ...meta,\n ...preloadLinks,\n ...links,\n ...styles,\n ...headScripts,\n ] as Array<RouterManagedTag>,\n (d) => {\n return JSON.stringify(d)\n },\n )\n}\n\nexport function uniqBy<T>(arr: Array<T>, fn: (item: T) => string) {\n const seen = new Set<string>()\n return arr.filter((item) => {\n const key = fn(item)\n if (seen.has(key)) {\n return false\n }\n seen.add(key)\n return true\n })\n}\n"],"mappings":"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"}
|
package/dist/esm/index.dev.js
CHANGED
|
@@ -2,7 +2,6 @@ import { Await, useAwaited } from "./awaited.js";
|
|
|
2
2
|
import { CatchBoundary, ErrorComponent } from "./CatchBoundary.js";
|
|
3
3
|
import { ClientOnly, useHydrated } from "./ClientOnly.js";
|
|
4
4
|
import { useRouter } from "./useRouter.js";
|
|
5
|
-
import { useRouterState } from "./useRouterState.js";
|
|
6
5
|
import { useMatch } from "./useMatch.js";
|
|
7
6
|
import { useLoaderData } from "./useLoaderData.js";
|
|
8
7
|
import { useLoaderDeps } from "./useLoaderDeps.js";
|
|
@@ -22,6 +21,7 @@ import { Router, createRouter } from "./router.js";
|
|
|
22
21
|
import { RouterContextProvider, RouterProvider } from "./RouterProvider.js";
|
|
23
22
|
import { ScrollRestoration, useElementScrollRestoration } from "./ScrollRestoration.js";
|
|
24
23
|
import { Block, useBlocker } from "./useBlocker.js";
|
|
24
|
+
import { useRouterState } from "./useRouterState.js";
|
|
25
25
|
import { useLocation } from "./useLocation.js";
|
|
26
26
|
import { useCanGoBack } from "./useCanGoBack.js";
|
|
27
27
|
import { Asset } from "./Asset.js";
|
package/dist/esm/index.js
CHANGED
|
@@ -2,7 +2,6 @@ import { Await, useAwaited } from "./awaited.js";
|
|
|
2
2
|
import { CatchBoundary, ErrorComponent } from "./CatchBoundary.js";
|
|
3
3
|
import { ClientOnly, useHydrated } from "./ClientOnly.js";
|
|
4
4
|
import { useRouter } from "./useRouter.js";
|
|
5
|
-
import { useRouterState } from "./useRouterState.js";
|
|
6
5
|
import { useMatch } from "./useMatch.js";
|
|
7
6
|
import { useLoaderData } from "./useLoaderData.js";
|
|
8
7
|
import { useLoaderDeps } from "./useLoaderDeps.js";
|
|
@@ -22,6 +21,7 @@ import { Router, createRouter } from "./router.js";
|
|
|
22
21
|
import { RouterContextProvider, RouterProvider } from "./RouterProvider.js";
|
|
23
22
|
import { ScrollRestoration, useElementScrollRestoration } from "./ScrollRestoration.js";
|
|
24
23
|
import { Block, useBlocker } from "./useBlocker.js";
|
|
24
|
+
import { useRouterState } from "./useRouterState.js";
|
|
25
25
|
import { useLocation } from "./useLocation.js";
|
|
26
26
|
import { useCanGoBack } from "./useCanGoBack.js";
|
|
27
27
|
import { Asset } from "./Asset.js";
|
package/dist/esm/link.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { useForwardedRef, useIntersectionObserver } from "./utils.js";
|
|
2
2
|
import { useHydrated } from "./ClientOnly.js";
|
|
3
3
|
import { useRouter } from "./useRouter.js";
|
|
4
|
-
import { useRouterState } from "./useRouterState.js";
|
|
5
4
|
import { deepEqual, exactPathTest, functionalUpdate, isDangerousProtocol, preloadWarning, removeTrailingSlash } from "@tanstack/router-core";
|
|
6
5
|
import * as React$1 from "react";
|
|
7
6
|
import { jsx } from "react/jsx-runtime";
|
|
7
|
+
import { useStore } from "@tanstack/react-store";
|
|
8
8
|
import { isServer } from "@tanstack/router-core/isServer";
|
|
9
9
|
import { flushSync } from "react-dom";
|
|
10
10
|
//#region src/link.tsx
|
|
@@ -78,7 +78,7 @@ function useLinkProps(options, forwardedRef) {
|
|
|
78
78
|
})();
|
|
79
79
|
const isActive = (() => {
|
|
80
80
|
if (externalLink) return false;
|
|
81
|
-
const currentLocation = router.
|
|
81
|
+
const currentLocation = router.stores.location.state;
|
|
82
82
|
const exact = activeOptions?.exact ?? false;
|
|
83
83
|
if (exact) {
|
|
84
84
|
if (!exactPathTest(currentLocation.pathname, next.pathname, router.basepath)) return false;
|
|
@@ -154,27 +154,9 @@ function useLinkProps(options, forwardedRef) {
|
|
|
154
154
|
};
|
|
155
155
|
}
|
|
156
156
|
const isHydrated = useHydrated();
|
|
157
|
-
const
|
|
158
|
-
select: (s) => {
|
|
159
|
-
const leaf = s.matches[s.matches.length - 1];
|
|
160
|
-
return {
|
|
161
|
-
search: leaf?.search,
|
|
162
|
-
hash: s.location.hash,
|
|
163
|
-
path: leaf?.pathname
|
|
164
|
-
};
|
|
165
|
-
},
|
|
166
|
-
structuralSharing: true
|
|
167
|
-
});
|
|
168
|
-
const from = options.from;
|
|
169
|
-
const _options = React$1.useMemo(() => {
|
|
170
|
-
return {
|
|
171
|
-
...options,
|
|
172
|
-
from
|
|
173
|
-
};
|
|
174
|
-
}, [
|
|
157
|
+
const _options = React$1.useMemo(() => options, [
|
|
175
158
|
router,
|
|
176
|
-
|
|
177
|
-
from,
|
|
159
|
+
options.from,
|
|
178
160
|
options._fromLocation,
|
|
179
161
|
options.hash,
|
|
180
162
|
options.to,
|
|
@@ -184,7 +166,18 @@ function useLinkProps(options, forwardedRef) {
|
|
|
184
166
|
options.mask,
|
|
185
167
|
options.unsafeRelative
|
|
186
168
|
]);
|
|
187
|
-
const
|
|
169
|
+
const currentLocation = useStore(router.stores.location, (l) => l, (prev, next) => prev.href === next.href);
|
|
170
|
+
const next = React$1.useMemo(() => {
|
|
171
|
+
const opts = {
|
|
172
|
+
_fromLocation: currentLocation,
|
|
173
|
+
..._options
|
|
174
|
+
};
|
|
175
|
+
return router.buildLocation(opts);
|
|
176
|
+
}, [
|
|
177
|
+
router,
|
|
178
|
+
currentLocation,
|
|
179
|
+
_options
|
|
180
|
+
]);
|
|
188
181
|
const hrefOptionPublicHref = next.maskedLocation ? next.maskedLocation.publicHref : next.publicHref;
|
|
189
182
|
const hrefOptionExternal = next.maskedLocation ? next.maskedLocation.external : next.external;
|
|
190
183
|
const hrefOption = React$1.useMemo(() => getHrefOption(hrefOptionPublicHref, hrefOptionExternal, router.history, disabled), [
|
|
@@ -216,24 +209,36 @@ function useLinkProps(options, forwardedRef) {
|
|
|
216
209
|
hrefOption,
|
|
217
210
|
router.protocolAllowlist
|
|
218
211
|
]);
|
|
219
|
-
const isActive =
|
|
212
|
+
const isActive = React$1.useMemo(() => {
|
|
220
213
|
if (externalLink) return false;
|
|
221
214
|
if (activeOptions?.exact) {
|
|
222
|
-
if (!exactPathTest(
|
|
215
|
+
if (!exactPathTest(currentLocation.pathname, next.pathname, router.basepath)) return false;
|
|
223
216
|
} else {
|
|
224
|
-
const currentPathSplit = removeTrailingSlash(
|
|
217
|
+
const currentPathSplit = removeTrailingSlash(currentLocation.pathname, router.basepath);
|
|
225
218
|
const nextPathSplit = removeTrailingSlash(next.pathname, router.basepath);
|
|
226
219
|
if (!(currentPathSplit.startsWith(nextPathSplit) && (currentPathSplit.length === nextPathSplit.length || currentPathSplit[nextPathSplit.length] === "/"))) return false;
|
|
227
220
|
}
|
|
228
221
|
if (activeOptions?.includeSearch ?? true) {
|
|
229
|
-
if (!deepEqual(
|
|
222
|
+
if (!deepEqual(currentLocation.search, next.search, {
|
|
230
223
|
partial: !activeOptions?.exact,
|
|
231
224
|
ignoreUndefined: !activeOptions?.explicitUndefined
|
|
232
225
|
})) return false;
|
|
233
226
|
}
|
|
234
|
-
if (activeOptions?.includeHash) return isHydrated &&
|
|
227
|
+
if (activeOptions?.includeHash) return isHydrated && currentLocation.hash === next.hash;
|
|
235
228
|
return true;
|
|
236
|
-
}
|
|
229
|
+
}, [
|
|
230
|
+
activeOptions?.exact,
|
|
231
|
+
activeOptions?.explicitUndefined,
|
|
232
|
+
activeOptions?.includeHash,
|
|
233
|
+
activeOptions?.includeSearch,
|
|
234
|
+
currentLocation,
|
|
235
|
+
externalLink,
|
|
236
|
+
isHydrated,
|
|
237
|
+
next.hash,
|
|
238
|
+
next.pathname,
|
|
239
|
+
next.search,
|
|
240
|
+
router.basepath
|
|
241
|
+
]);
|
|
237
242
|
const resolvedActiveProps = isActive ? functionalUpdate(activeProps, {}) ?? STATIC_ACTIVE_OBJECT : STATIC_EMPTY_OBJECT;
|
|
238
243
|
const resolvedInactiveProps = isActive ? STATIC_EMPTY_OBJECT : functionalUpdate(inactiveProps, {}) ?? STATIC_EMPTY_OBJECT;
|
|
239
244
|
const resolvedClassName = [
|