@tanstack/router-core
Advanced tools
@@ -21,3 +21,2 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); | ||
| const require_searchMiddleware = require("./searchMiddleware.cjs"); | ||
| const require_hash_scroll = require("./hash-scroll.cjs"); | ||
| const require_transformer = require("./ssr/serializer/transformer.cjs"); | ||
@@ -65,3 +64,2 @@ const require_RawStream = require("./ssr/serializer/RawStream.cjs"); | ||
| exports.getStylesheetHref = require_manifest.getStylesheetHref; | ||
| exports.handleHashScroll = require_hash_scroll.handleHashScroll; | ||
| exports.hasKeys = require_utils.hasKeys; | ||
@@ -94,3 +92,2 @@ exports.interpolatePath = require_path.interpolatePath; | ||
| exports.rootRouteId = require_root.rootRouteId; | ||
| exports.scrollRestorationCache = require_scroll_restoration.scrollRestorationCache; | ||
| exports.setupScrollRestoration = require_scroll_restoration.setupScrollRestoration; | ||
@@ -97,0 +94,0 @@ exports.storageKey = require_scroll_restoration.storageKey; |
@@ -43,4 +43,3 @@ export * from './global.cjs'; | ||
| export { isNotFound, notFound } from './not-found.cjs'; | ||
| export { defaultGetScrollRestorationKey, getElementScrollRestorationEntry, storageKey, scrollRestorationCache, setupScrollRestoration, } from './scroll-restoration.cjs'; | ||
| export { handleHashScroll } from './hash-scroll.cjs'; | ||
| export { defaultGetScrollRestorationKey, getElementScrollRestorationEntry, storageKey, setupScrollRestoration, } from './scroll-restoration.cjs'; | ||
| export type { ScrollRestorationOptions, ScrollRestorationEntry, } from './scroll-restoration.cjs'; | ||
@@ -47,0 +46,0 @@ export type { ValidateFromPath, ValidateToPath, ValidateSearch, ValidateParams, InferFrom, InferTo, InferMaskTo, InferMaskFrom, ValidateNavigateOptions, ValidateNavigateOptionsArray, ValidateRedirectOptions, ValidateRedirectOptionsArray, ValidateId, InferStrict, InferShouldThrow, InferSelected, ValidateUseSearchResult, ValidateUseParamsResult, } from './typePrimitives.cjs'; |
| const require_path = require("./path.cjs"); | ||
| //#region src/rewrite.ts | ||
| /** Compose multiple rewrite pairs into a single in/out rewrite. */ | ||
| /** Compose multiple rewrite pairs into a single in/out rewrite. */ | ||
| function composeRewrites(rewrites) { | ||
@@ -18,9 +17,7 @@ return { | ||
| /** Create a rewrite pair that strips/adds a basepath on input/output. */ | ||
| /** Create a rewrite pair that strips/adds a basepath on input/output. */ | ||
| function rewriteBasepath(opts) { | ||
| const trimmedBasepath = require_path.trimPath(opts.basepath); | ||
| const normalizedBasepath = `/${trimmedBasepath}`; | ||
| const normalizedBasepathWithSlash = `${normalizedBasepath}/`; | ||
| const checkBasepath = opts.caseSensitive ? normalizedBasepath : normalizedBasepath.toLowerCase(); | ||
| const checkBasepathWithSlash = opts.caseSensitive ? normalizedBasepathWithSlash : normalizedBasepathWithSlash.toLowerCase(); | ||
| const checkBasepathWithSlash = `${checkBasepath}/`; | ||
| return { | ||
@@ -44,3 +41,2 @@ input: ({ url }) => { | ||
| /** Execute a location input rewrite if provided. */ | ||
| /** Execute a location input rewrite if provided. */ | ||
| function executeRewriteInput(rewrite, url) { | ||
@@ -55,3 +51,2 @@ const res = rewrite?.input?.({ url }); | ||
| /** Execute a location output rewrite if provided. */ | ||
| /** Execute a location output rewrite if provided. */ | ||
| function executeRewriteOutput(rewrite, url) { | ||
@@ -58,0 +53,0 @@ const res = rewrite?.output?.({ url }); |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"rewrite.cjs","names":[],"sources":["../../src/rewrite.ts"],"sourcesContent":["import { joinPaths, trimPath } from './path'\nimport type { LocationRewrite } from './router'\n\n/** Compose multiple rewrite pairs into a single in/out rewrite. */\n/** Compose multiple rewrite pairs into a single in/out rewrite. */\nexport function composeRewrites(rewrites: Array<LocationRewrite>) {\n return {\n input: ({ url }) => {\n for (const rewrite of rewrites) {\n url = executeRewriteInput(rewrite, url)\n }\n return url\n },\n output: ({ url }) => {\n for (let i = rewrites.length - 1; i >= 0; i--) {\n url = executeRewriteOutput(rewrites[i], url)\n }\n return url\n },\n } satisfies LocationRewrite\n}\n\n/** Create a rewrite pair that strips/adds a basepath on input/output. */\n/** Create a rewrite pair that strips/adds a basepath on input/output. */\nexport function rewriteBasepath(opts: {\n basepath: string\n caseSensitive?: boolean\n}) {\n const trimmedBasepath = trimPath(opts.basepath)\n const normalizedBasepath = `/${trimmedBasepath}`\n const normalizedBasepathWithSlash = `${normalizedBasepath}/`\n const checkBasepath = opts.caseSensitive\n ? normalizedBasepath\n : normalizedBasepath.toLowerCase()\n const checkBasepathWithSlash = opts.caseSensitive\n ? normalizedBasepathWithSlash\n : normalizedBasepathWithSlash.toLowerCase()\n\n return {\n input: ({ url }) => {\n const pathname = opts.caseSensitive\n ? url.pathname\n : url.pathname.toLowerCase()\n\n // Handle exact basepath match (e.g., /my-app -> /)\n if (pathname === checkBasepath) {\n url.pathname = '/'\n } else if (pathname.startsWith(checkBasepathWithSlash)) {\n // Handle basepath with trailing content (e.g., /my-app/users -> /users)\n url.pathname = url.pathname.slice(normalizedBasepath.length)\n }\n return url\n },\n output: ({ url }) => {\n url.pathname = joinPaths(['/', trimmedBasepath, url.pathname])\n return url\n },\n } satisfies LocationRewrite\n}\n\n/** Execute a location input rewrite if provided. */\n/** Execute a location input rewrite if provided. */\nexport function executeRewriteInput(\n rewrite: LocationRewrite | undefined,\n url: URL,\n): URL {\n const res = rewrite?.input?.({ url })\n if (res) {\n if (typeof res === 'string') {\n return new URL(res)\n } else if (res instanceof URL) {\n return res\n }\n }\n return url\n}\n\n/** Execute a location output rewrite if provided. */\n/** Execute a location output rewrite if provided. */\nexport function executeRewriteOutput(\n rewrite: LocationRewrite | undefined,\n url: URL,\n): URL {\n const res = rewrite?.output?.({ url })\n if (res) {\n if (typeof res === 'string') {\n return new URL(res)\n } else if (res instanceof URL) {\n return res\n }\n }\n return url\n}\n"],"mappings":";;;;AAKA,SAAgB,gBAAgB,UAAkC;AAChE,QAAO;EACL,QAAQ,EAAE,UAAU;AAClB,QAAK,MAAM,WAAW,SACpB,OAAM,oBAAoB,SAAS,IAAI;AAEzC,UAAO;;EAET,SAAS,EAAE,UAAU;AACnB,QAAK,IAAI,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,IACxC,OAAM,qBAAqB,SAAS,IAAI,IAAI;AAE9C,UAAO;;EAEV;;;;AAKH,SAAgB,gBAAgB,MAG7B;CACD,MAAM,kBAAkB,aAAA,SAAS,KAAK,SAAS;CAC/C,MAAM,qBAAqB,IAAI;CAC/B,MAAM,8BAA8B,GAAG,mBAAmB;CAC1D,MAAM,gBAAgB,KAAK,gBACvB,qBACA,mBAAmB,aAAa;CACpC,MAAM,yBAAyB,KAAK,gBAChC,8BACA,4BAA4B,aAAa;AAE7C,QAAO;EACL,QAAQ,EAAE,UAAU;GAClB,MAAM,WAAW,KAAK,gBAClB,IAAI,WACJ,IAAI,SAAS,aAAa;AAG9B,OAAI,aAAa,cACf,KAAI,WAAW;YACN,SAAS,WAAW,uBAAuB,CAEpD,KAAI,WAAW,IAAI,SAAS,MAAM,mBAAmB,OAAO;AAE9D,UAAO;;EAET,SAAS,EAAE,UAAU;AACnB,OAAI,WAAW,aAAA,UAAU;IAAC;IAAK;IAAiB,IAAI;IAAS,CAAC;AAC9D,UAAO;;EAEV;;;;AAKH,SAAgB,oBACd,SACA,KACK;CACL,MAAM,MAAM,SAAS,QAAQ,EAAE,KAAK,CAAC;AACrC,KAAI;MACE,OAAO,QAAQ,SACjB,QAAO,IAAI,IAAI,IAAI;WACV,eAAe,IACxB,QAAO;;AAGX,QAAO;;;;AAKT,SAAgB,qBACd,SACA,KACK;CACL,MAAM,MAAM,SAAS,SAAS,EAAE,KAAK,CAAC;AACtC,KAAI;MACE,OAAO,QAAQ,SACjB,QAAO,IAAI,IAAI,IAAI;WACV,eAAe,IACxB,QAAO;;AAGX,QAAO"} | ||
| {"version":3,"file":"rewrite.cjs","names":[],"sources":["../../src/rewrite.ts"],"sourcesContent":["import { joinPaths, trimPath } from './path'\nimport type { LocationRewrite } from './router'\n\n/** Compose multiple rewrite pairs into a single in/out rewrite. */\nexport function composeRewrites(rewrites: Array<LocationRewrite>) {\n return {\n input: ({ url }) => {\n for (const rewrite of rewrites) {\n url = executeRewriteInput(rewrite, url)\n }\n return url\n },\n output: ({ url }) => {\n for (let i = rewrites.length - 1; i >= 0; i--) {\n url = executeRewriteOutput(rewrites[i], url)\n }\n return url\n },\n } satisfies LocationRewrite\n}\n\n/** Create a rewrite pair that strips/adds a basepath on input/output. */\nexport function rewriteBasepath(opts: {\n basepath: string\n caseSensitive?: boolean\n}) {\n const trimmedBasepath = trimPath(opts.basepath)\n const normalizedBasepath = `/${trimmedBasepath}`\n const checkBasepath = opts.caseSensitive\n ? normalizedBasepath\n : normalizedBasepath.toLowerCase()\n const checkBasepathWithSlash = `${checkBasepath}/`\n\n return {\n input: ({ url }) => {\n const pathname = opts.caseSensitive\n ? url.pathname\n : url.pathname.toLowerCase()\n\n // Handle exact basepath match (e.g., /my-app -> /)\n if (pathname === checkBasepath) {\n url.pathname = '/'\n } else if (pathname.startsWith(checkBasepathWithSlash)) {\n // Handle basepath with trailing content (e.g., /my-app/users -> /users)\n url.pathname = url.pathname.slice(normalizedBasepath.length)\n }\n return url\n },\n output: ({ url }) => {\n url.pathname = joinPaths(['/', trimmedBasepath, url.pathname])\n return url\n },\n } satisfies LocationRewrite\n}\n\n/** Execute a location input rewrite if provided. */\nexport function executeRewriteInput(\n rewrite: LocationRewrite | undefined,\n url: URL,\n): URL {\n const res = rewrite?.input?.({ url })\n if (res) {\n if (typeof res === 'string') {\n return new URL(res)\n } else if (res instanceof URL) {\n return res\n }\n }\n return url\n}\n\n/** Execute a location output rewrite if provided. */\nexport function executeRewriteOutput(\n rewrite: LocationRewrite | undefined,\n url: URL,\n): URL {\n const res = rewrite?.output?.({ url })\n if (res) {\n if (typeof res === 'string') {\n return new URL(res)\n } else if (res instanceof URL) {\n return res\n }\n }\n return url\n}\n"],"mappings":";;;AAIA,SAAgB,gBAAgB,UAAkC;AAChE,QAAO;EACL,QAAQ,EAAE,UAAU;AAClB,QAAK,MAAM,WAAW,SACpB,OAAM,oBAAoB,SAAS,IAAI;AAEzC,UAAO;;EAET,SAAS,EAAE,UAAU;AACnB,QAAK,IAAI,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,IACxC,OAAM,qBAAqB,SAAS,IAAI,IAAI;AAE9C,UAAO;;EAEV;;;AAIH,SAAgB,gBAAgB,MAG7B;CACD,MAAM,kBAAkB,aAAA,SAAS,KAAK,SAAS;CAC/C,MAAM,qBAAqB,IAAI;CAC/B,MAAM,gBAAgB,KAAK,gBACvB,qBACA,mBAAmB,aAAa;CACpC,MAAM,yBAAyB,GAAG,cAAc;AAEhD,QAAO;EACL,QAAQ,EAAE,UAAU;GAClB,MAAM,WAAW,KAAK,gBAClB,IAAI,WACJ,IAAI,SAAS,aAAa;AAG9B,OAAI,aAAa,cACf,KAAI,WAAW;YACN,SAAS,WAAW,uBAAuB,CAEpD,KAAI,WAAW,IAAI,SAAS,MAAM,mBAAmB,OAAO;AAE9D,UAAO;;EAET,SAAS,EAAE,UAAU;AACnB,OAAI,WAAW,aAAA,UAAU;IAAC;IAAK;IAAiB,IAAI;IAAS,CAAC;AAC9D,UAAO;;EAEV;;;AAIH,SAAgB,oBACd,SACA,KACK;CACL,MAAM,MAAM,SAAS,QAAQ,EAAE,KAAK,CAAC;AACrC,KAAI;MACE,OAAO,QAAQ,SACjB,QAAO,IAAI,IAAI,IAAI;WACV,eAAe,IACxB,QAAO;;AAGX,QAAO;;;AAIT,SAAgB,qBACd,SACA,KACK;CACL,MAAM,MAAM,SAAS,SAAS,EAAE,KAAK,CAAC;AACtC,KAAI;MACE,OAAO,QAAQ,SACjB,QAAO,IAAI,IAAI,IAAI;WACV,eAAe,IACxB,QAAO;;AAGX,QAAO"} |
| import { LocationRewrite } from './router.cjs'; | ||
| /** Compose multiple rewrite pairs into a single in/out rewrite. */ | ||
| /** Compose multiple rewrite pairs into a single in/out rewrite. */ | ||
| export declare function composeRewrites(rewrites: Array<LocationRewrite>): { | ||
@@ -13,3 +12,2 @@ input: ({ url }: { | ||
| /** Create a rewrite pair that strips/adds a basepath on input/output. */ | ||
| /** Create a rewrite pair that strips/adds a basepath on input/output. */ | ||
| export declare function rewriteBasepath(opts: { | ||
@@ -27,6 +25,4 @@ basepath: string; | ||
| /** Execute a location input rewrite if provided. */ | ||
| /** Execute a location input rewrite if provided. */ | ||
| export declare function executeRewriteInput(rewrite: LocationRewrite | undefined, url: URL): URL; | ||
| /** Execute a location output rewrite if provided. */ | ||
| /** Execute a location output rewrite if provided. */ | ||
| export declare function executeRewriteOutput(rewrite: LocationRewrite | undefined, url: URL): URL; |
@@ -52,2 +52,3 @@ const require_utils = require("./utils.cjs"); | ||
| } | ||
| const locationHistoryActions = /* @__PURE__ */ new WeakMap(); | ||
| /** | ||
@@ -378,2 +379,3 @@ * Core, framework-agnostic router engine that powers TanStack Router. | ||
| this.commitLocation = async ({ viewTransition, ignoreBlocker, ...next }) => { | ||
| let historyAction; | ||
| const isSameState = () => { | ||
@@ -427,6 +429,7 @@ const ignoredProps = [ | ||
| this.shouldViewTransition = viewTransition; | ||
| this.history[next.replace ? "replace" : "push"](nextHistory.publicHref, nextHistory.state, { ignoreBlocker }); | ||
| historyAction = next.replace ? "REPLACE" : "PUSH"; | ||
| this.history[historyAction === "REPLACE" ? "replace" : "push"](nextHistory.publicHref, nextHistory.state, { ignoreBlocker }); | ||
| } | ||
| this.resetNextScroll = next.resetScroll ?? true; | ||
| if (!this.history.subscribers.size) this.load(); | ||
| if (!this.history.subscribers.size) this.load(historyAction ? { action: { type: historyAction } } : void 0); | ||
| return this.commitLocationPromise; | ||
@@ -536,2 +539,3 @@ }; | ||
| this.load = async (opts) => { | ||
| const historyAction = opts?.action?.type; | ||
| let redirect; | ||
@@ -545,2 +549,4 @@ let notFound; | ||
| this.beforeLoad(); | ||
| if (historyAction) locationHistoryActions.set(this.latestLocation, historyAction); | ||
| else locationHistoryActions.delete(this.latestLocation); | ||
| const next = this.latestLocation; | ||
@@ -1188,4 +1194,5 @@ const locationChangeInfo = getLocationChangeInfo(next, this.stores.resolvedLocation.get()); | ||
| exports.lazyFn = lazyFn; | ||
| exports.locationHistoryActions = locationHistoryActions; | ||
| exports.trailingSlashOptions = trailingSlashOptions; | ||
| //# sourceMappingURL=router.cjs.map |
@@ -6,3 +6,3 @@ import { loadRouteChunk } from './load-matches.cjs'; | ||
| import { AnyRedirect, ResolvedRedirect } from './redirect.cjs'; | ||
| import { HistoryLocation, HistoryState, ParsedHistoryState, RouterHistory } from '@tanstack/history'; | ||
| import { HistoryAction, HistoryLocation, HistoryState, ParsedHistoryState, RouterHistory } from '@tanstack/history'; | ||
| import { Awaitable, Constrain, ControlledPromise, NoInfer, NonNullableUpdater, PickAsRequired, Updater } from './utils.cjs'; | ||
@@ -487,2 +487,5 @@ import { ParsedLocation } from './location.cjs'; | ||
| sync?: boolean; | ||
| action?: { | ||
| type: HistoryAction; | ||
| }; | ||
| }) => Promise<void>; | ||
@@ -573,2 +576,3 @@ export type CommitLocationFn = ({ viewTransition, ignoreBlocker, ...next }: ParsedLocation & CommitLocationOptions) => Promise<void>; | ||
| }; | ||
| export declare const locationHistoryActions: WeakMap<ParsedLocation<{}>, HistoryAction>; | ||
| export type CreateRouterFn = <TRouteTree extends AnyRoute, TTrailingSlashOption extends TrailingSlashOption = 'never', TDefaultStructuralSharingOption extends boolean = false, TRouterHistory extends RouterHistory = RouterHistory, TDehydrated extends Record<string, any> = Record<string, any>>(options: undefined extends number ? 'strictNullChecks must be enabled in tsconfig.json' : RouterConstructorOptions<TRouteTree, TTrailingSlashOption, TDefaultStructuralSharingOption, TRouterHistory, TDehydrated>) => RouterCore<TRouteTree, TTrailingSlashOption, TDefaultStructuralSharingOption, TRouterHistory, TDehydrated>; | ||
@@ -575,0 +579,0 @@ declare global { |
| //#region src/scroll-restoration-inline.ts?script-string | ||
| var scroll_restoration_inline_default = "function(t){let s;try{s=JSON.parse(sessionStorage.getItem(t.storageKey)||\"{}\")}catch(e){console.error(e);return}const c=t.key||window.history.state?.__TSR_key,r=c?s[c]:void 0;if(t.shouldScrollRestoration&&r&&typeof r==\"object\"&&Object.keys(r).length>0){for(const e in r){const o=r[e];if(!o||typeof o!=\"object\")continue;const l=o.scrollX,i=o.scrollY;if(!(!Number.isFinite(l)||!Number.isFinite(i))){if(e===\"window\")window.scrollTo({top:i,left:l,behavior:t.behavior});else if(e){let n;try{n=document.querySelector(e)}catch{continue}n&&(n.scrollLeft=l,n.scrollTop=i)}}}return}const a=window.location.hash.split(\"#\",2)[1];if(a){const e=window.history.state?.__hashScrollIntoViewOptions??!0;if(e){const o=document.getElementById(a);o&&o.scrollIntoView(e)}return}window.scrollTo({top:0,left:0,behavior:t.behavior})}"; | ||
| var scroll_restoration_inline_default = "function(i){let l;try{l=JSON.parse(sessionStorage.getItem(i.storageKey)||\"{}\")}catch(e){console.error(e);return}const c=i.key||window.history.state?.__TSR_key,o=c?l[c]:void 0;let f=!1;if(o&&typeof o==\"object\")for(const e in o){const t=o[e];if(!t||typeof t!=\"object\")continue;const r=t.scrollX,s=t.scrollY;if(!(!Number.isFinite(r)||!Number.isFinite(s))){if(e===\"window\")window.scrollTo({top:s,left:r}),f=!0;else if(e){let n;try{n=document.querySelector(e)}catch{continue}n&&(n.scrollLeft=r,n.scrollTop=s)}}}if(f)return;const w=window.location.hash.split(\"#\",2)[1];if(w){const e=window.history.state?.__hashScrollIntoViewOptions??!0;if(e){const t=document.getElementById(w);t&&t.scrollIntoView(e)}return}window.scrollTo({top:0,left:0})}"; | ||
| //#endregion | ||
@@ -4,0 +4,0 @@ exports.default = scroll_restoration_inline_default; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"scroll-restoration-inline.cjs","names":[],"sources":["../../src/scroll-restoration-inline.ts?script-string"],"sourcesContent":["export default function (options: {\n storageKey: string\n key?: string\n behavior?: ScrollToOptions['behavior']\n shouldScrollRestoration?: boolean\n}) {\n let byKey\n\n try {\n byKey = JSON.parse(sessionStorage.getItem(options.storageKey) || '{}')\n } catch (error) {\n console.error(error)\n return\n }\n\n const resolvedKey = options.key || window.history.state?.__TSR_key\n const elementEntries = resolvedKey ? byKey[resolvedKey] : undefined\n\n if (\n options.shouldScrollRestoration &&\n elementEntries &&\n typeof elementEntries === 'object' &&\n Object.keys(elementEntries).length > 0\n ) {\n for (const elementSelector in elementEntries) {\n const entry = elementEntries[elementSelector]\n\n if (!entry || typeof entry !== 'object') {\n continue\n }\n\n const scrollX = entry.scrollX\n const scrollY = entry.scrollY\n\n if (!Number.isFinite(scrollX) || !Number.isFinite(scrollY)) {\n continue\n }\n\n if (elementSelector === 'window') {\n window.scrollTo({\n top: scrollY,\n left: scrollX,\n behavior: options.behavior,\n })\n } else if (elementSelector) {\n let element\n\n try {\n element = document.querySelector(elementSelector)\n } catch {\n continue\n }\n\n if (element) {\n element.scrollLeft = scrollX\n element.scrollTop = scrollY\n }\n }\n }\n\n return\n }\n\n const hash = window.location.hash.split('#', 2)[1]\n\n if (hash) {\n const hashScrollIntoViewOptions =\n window.history.state?.__hashScrollIntoViewOptions ?? true\n\n if (hashScrollIntoViewOptions) {\n const el = document.getElementById(hash)\n if (el) {\n el.scrollIntoView(hashScrollIntoViewOptions)\n }\n }\n\n return\n }\n\n window.scrollTo({ top: 0, left: 0, behavior: options.behavior })\n}\n"],"mappings":";AAAA,IAAA,oCAAe"} | ||
| {"version":3,"file":"scroll-restoration-inline.cjs","names":[],"sources":["../../src/scroll-restoration-inline.ts?script-string"],"sourcesContent":["export default function (options: { storageKey: string; key?: string }) {\n let byKey\n\n try {\n byKey = JSON.parse(sessionStorage.getItem(options.storageKey) || '{}')\n } catch (error) {\n console.error(error)\n return\n }\n\n const resolvedKey = options.key || window.history.state?.__TSR_key\n const elementEntries = resolvedKey ? byKey[resolvedKey] : undefined\n let windowRestored = false\n\n if (elementEntries && typeof elementEntries === 'object') {\n for (const elementSelector in elementEntries) {\n const entry = elementEntries[elementSelector]\n\n if (!entry || typeof entry !== 'object') {\n continue\n }\n\n const scrollX = entry.scrollX\n const scrollY = entry.scrollY\n\n if (!Number.isFinite(scrollX) || !Number.isFinite(scrollY)) {\n continue\n }\n\n if (elementSelector === 'window') {\n window.scrollTo({\n top: scrollY,\n left: scrollX,\n })\n windowRestored = true\n } else if (elementSelector) {\n let element\n\n try {\n element = document.querySelector(elementSelector)\n } catch {\n continue\n }\n\n if (element) {\n element.scrollLeft = scrollX\n element.scrollTop = scrollY\n }\n }\n }\n }\n\n if (windowRestored) return\n\n const hash = window.location.hash.split('#', 2)[1]\n\n if (hash) {\n const hashScrollIntoViewOptions =\n window.history.state?.__hashScrollIntoViewOptions ?? true\n\n if (hashScrollIntoViewOptions) {\n const el = document.getElementById(hash)\n if (el) {\n el.scrollIntoView(hashScrollIntoViewOptions)\n }\n }\n\n return\n }\n\n window.scrollTo({ top: 0, left: 0 })\n}\n"],"mappings":";AAAA,IAAA,oCAAe"} |
| export default function (options: { | ||
| storageKey: string; | ||
| key?: string; | ||
| behavior?: ScrollToOptions['behavior']; | ||
| shouldScrollRestoration?: boolean; | ||
| }): void; |
@@ -6,8 +6,5 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); | ||
| //#region src/scroll-restoration-script/server.ts | ||
| const defaultInlineScrollRestorationScript = `(${require_scroll_restoration_inline.default})(${require_utils.escapeHtml(JSON.stringify({ | ||
| storageKey: require_scroll_restoration.storageKey, | ||
| shouldScrollRestoration: true | ||
| }))})`; | ||
| const defaultInlineScrollRestorationScript = `(${require_scroll_restoration_inline.default})(${require_utils.escapeHtml(JSON.stringify({ storageKey: require_scroll_restoration.storageKey }))})`; | ||
| function getScrollRestorationScript(options) { | ||
| if (options.storageKey === "tsr-scroll-restoration-v1_3" && options.shouldScrollRestoration === true && options.key === void 0 && options.behavior === void 0) return defaultInlineScrollRestorationScript; | ||
| if (options.storageKey === "tsr-scroll-restoration-v1_3" && options.key === void 0) return defaultInlineScrollRestorationScript; | ||
| return `(${require_scroll_restoration_inline.default})(${require_utils.escapeHtml(JSON.stringify(options))})`; | ||
@@ -24,3 +21,2 @@ } | ||
| storageKey: require_scroll_restoration.storageKey, | ||
| shouldScrollRestoration: true, | ||
| key: userKey | ||
@@ -27,0 +23,0 @@ }); |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"server.cjs","names":[],"sources":["../../../src/scroll-restoration-script/server.ts"],"sourcesContent":["import minifiedScrollRestorationScript from '../scroll-restoration-inline?script-string'\nimport {\n defaultGetScrollRestorationKey,\n storageKey,\n} from '../scroll-restoration'\nimport { escapeHtml } from '../utils'\nimport type { AnyRouter } from '../router'\n\ntype InlineScrollRestorationScriptOptions = {\n storageKey: string\n key?: string\n behavior?: ScrollToOptions['behavior']\n shouldScrollRestoration?: boolean\n}\n\nconst defaultInlineScrollRestorationScript = `(${minifiedScrollRestorationScript})(${escapeHtml(\n JSON.stringify({\n storageKey,\n shouldScrollRestoration: true,\n } satisfies InlineScrollRestorationScriptOptions),\n)})`\n\nfunction getScrollRestorationScript(\n options: InlineScrollRestorationScriptOptions,\n) {\n if (\n options.storageKey === storageKey &&\n options.shouldScrollRestoration === true &&\n options.key === undefined &&\n options.behavior === undefined\n ) {\n return defaultInlineScrollRestorationScript\n }\n\n return `(${minifiedScrollRestorationScript})(${escapeHtml(JSON.stringify(options))})`\n}\n\nexport function getScrollRestorationScriptForRouter(router: AnyRouter) {\n if (\n typeof router.options.scrollRestoration === 'function' &&\n !router.options.scrollRestoration({ location: router.latestLocation })\n ) {\n return null\n }\n\n const getKey = router.options.getScrollRestorationKey\n if (!getKey) {\n return defaultInlineScrollRestorationScript\n }\n\n const location = router.latestLocation\n const userKey = getKey(location)\n const defaultKey = defaultGetScrollRestorationKey(location)\n\n if (userKey === defaultKey) {\n return defaultInlineScrollRestorationScript\n }\n\n return getScrollRestorationScript({\n storageKey,\n shouldScrollRestoration: true,\n key: userKey,\n })\n}\n"],"mappings":";;;;;AAeA,MAAM,uCAAuC,IAAI,kCAAA,QAAgC,IAAI,cAAA,WACnF,KAAK,UAAU;CACb,YAAA,2BAAA;CACA,yBAAyB;CAC1B,CAAgD,CAClD,CAAC;AAEF,SAAS,2BACP,SACA;AACA,KACE,QAAQ,eAAA,iCACR,QAAQ,4BAA4B,QACpC,QAAQ,QAAQ,KAAA,KAChB,QAAQ,aAAa,KAAA,EAErB,QAAO;AAGT,QAAO,IAAI,kCAAA,QAAgC,IAAI,cAAA,WAAW,KAAK,UAAU,QAAQ,CAAC,CAAC;;AAGrF,SAAgB,oCAAoC,QAAmB;AACrE,KACE,OAAO,OAAO,QAAQ,sBAAsB,cAC5C,CAAC,OAAO,QAAQ,kBAAkB,EAAE,UAAU,OAAO,gBAAgB,CAAC,CAEtE,QAAO;CAGT,MAAM,SAAS,OAAO,QAAQ;AAC9B,KAAI,CAAC,OACH,QAAO;CAGT,MAAM,WAAW,OAAO;CACxB,MAAM,UAAU,OAAO,SAAS;AAGhC,KAAI,YAFe,2BAAA,+BAA+B,SAAS,CAGzD,QAAO;AAGT,QAAO,2BAA2B;EAChC,YAAA,2BAAA;EACA,yBAAyB;EACzB,KAAK;EACN,CAAC"} | ||
| {"version":3,"file":"server.cjs","names":[],"sources":["../../../src/scroll-restoration-script/server.ts"],"sourcesContent":["import minifiedScrollRestorationScript from '../scroll-restoration-inline?script-string'\nimport {\n defaultGetScrollRestorationKey,\n storageKey,\n} from '../scroll-restoration'\nimport { escapeHtml } from '../utils'\nimport type { AnyRouter } from '../router'\n\ntype InlineScrollRestorationScriptOptions = {\n storageKey: string\n key?: string\n}\n\nconst defaultInlineScrollRestorationScript = `(${minifiedScrollRestorationScript})(${escapeHtml(\n JSON.stringify({\n storageKey,\n } satisfies InlineScrollRestorationScriptOptions),\n)})`\n\nfunction getScrollRestorationScript(\n options: InlineScrollRestorationScriptOptions,\n) {\n if (options.storageKey === storageKey && options.key === undefined) {\n return defaultInlineScrollRestorationScript\n }\n\n return `(${minifiedScrollRestorationScript})(${escapeHtml(JSON.stringify(options))})`\n}\n\nexport function getScrollRestorationScriptForRouter(router: AnyRouter) {\n if (\n typeof router.options.scrollRestoration === 'function' &&\n !router.options.scrollRestoration({ location: router.latestLocation })\n ) {\n return null\n }\n\n const getKey = router.options.getScrollRestorationKey\n if (!getKey) {\n return defaultInlineScrollRestorationScript\n }\n\n const location = router.latestLocation\n const userKey = getKey(location)\n const defaultKey = defaultGetScrollRestorationKey(location)\n\n if (userKey === defaultKey) {\n return defaultInlineScrollRestorationScript\n }\n\n return getScrollRestorationScript({\n storageKey,\n key: userKey,\n })\n}\n"],"mappings":";;;;;AAaA,MAAM,uCAAuC,IAAI,kCAAA,QAAgC,IAAI,cAAA,WACnF,KAAK,UAAU,EACb,YAAA,2BAAA,YACD,CAAgD,CAClD,CAAC;AAEF,SAAS,2BACP,SACA;AACA,KAAI,QAAQ,eAAA,iCAA6B,QAAQ,QAAQ,KAAA,EACvD,QAAO;AAGT,QAAO,IAAI,kCAAA,QAAgC,IAAI,cAAA,WAAW,KAAK,UAAU,QAAQ,CAAC,CAAC;;AAGrF,SAAgB,oCAAoC,QAAmB;AACrE,KACE,OAAO,OAAO,QAAQ,sBAAsB,cAC5C,CAAC,OAAO,QAAQ,kBAAkB,EAAE,UAAU,OAAO,gBAAgB,CAAC,CAEtE,QAAO;CAGT,MAAM,SAAS,OAAO,QAAQ;AAC9B,KAAI,CAAC,OACH,QAAO;CAGT,MAAM,WAAW,OAAO;CACxB,MAAM,UAAU,OAAO,SAAS;AAGhC,KAAI,YAFe,2BAAA,+BAA+B,SAAS,CAGzD,QAAO;AAGT,QAAO,2BAA2B;EAChC,YAAA,2BAAA;EACA,KAAK;EACN,CAAC"} |
+113
-107
@@ -1,2 +0,2 @@ | ||
| const require_utils = require("./utils.cjs"); | ||
| const require_router = require("./router.cjs"); | ||
| let _tanstack_router_core_isServer = require("@tanstack/router-core/isServer"); | ||
@@ -6,3 +6,3 @@ //#region src/scroll-restoration.ts | ||
| try { | ||
| return typeof window !== "undefined" && typeof window.sessionStorage === "object" ? window.sessionStorage : void 0; | ||
| return sessionStorage; | ||
| } catch { | ||
@@ -13,28 +13,19 @@ return; | ||
| const storageKey = "tsr-scroll-restoration-v1_3"; | ||
| const safeSessionStorage = getSafeSessionStorage(); | ||
| function createScrollRestorationCache() { | ||
| const safeSessionStorage = getSafeSessionStorage(); | ||
| if (!safeSessionStorage) return null; | ||
| let state = {}; | ||
| try { | ||
| const parsed = JSON.parse(safeSessionStorage.getItem("tsr-scroll-restoration-v1_3") || "{}"); | ||
| if (require_utils.isPlainObject(parsed)) state = parsed; | ||
| } catch {} | ||
| const persist = () => { | ||
| try { | ||
| safeSessionStorage.setItem(storageKey, JSON.stringify(state)); | ||
| } catch { | ||
| if (process.env.NODE_ENV !== "production") console.warn("[ts-router] Could not persist scroll restoration state to sessionStorage."); | ||
| } | ||
| }; | ||
| return { | ||
| get state() { | ||
| return state; | ||
| }, | ||
| set: (updater) => { | ||
| state = require_utils.functionalUpdate(updater, state) || state; | ||
| }, | ||
| persist | ||
| }; | ||
| return JSON.parse(safeSessionStorage?.getItem("tsr-scroll-restoration-v1_3") || "{}"); | ||
| } catch { | ||
| return {}; | ||
| } | ||
| } | ||
| const scrollRestorationCache = createScrollRestorationCache(); | ||
| function persistScrollRestorationCache() { | ||
| try { | ||
| safeSessionStorage?.setItem(storageKey, JSON.stringify(scrollRestorationCache)); | ||
| } catch { | ||
| if (process.env.NODE_ENV !== "production") console.warn("[ts-router] Could not persist scroll restoration state to sessionStorage."); | ||
| } | ||
| } | ||
| const scrollRestorationCache = /* @__PURE__ */ createScrollRestorationCache(); | ||
| const scrollRestorationIdAttribute = "data-scroll-restoration-id"; | ||
| /** | ||
@@ -49,26 +40,45 @@ * The default `getKey` function for `useScrollRestoration`. | ||
| }; | ||
| function getCssSelector(el) { | ||
| const path = []; | ||
| function getScrollRestorationSelector(element) { | ||
| const attrId = element.getAttribute(scrollRestorationIdAttribute); | ||
| if (attrId) return `[${scrollRestorationIdAttribute}="${attrId}"]`; | ||
| let selector = ""; | ||
| let el = element; | ||
| let parent; | ||
| while (parent = el.parentNode) { | ||
| path.push(`${el.tagName}:nth-child(${Array.prototype.indexOf.call(parent.children, el) + 1})`); | ||
| let index = 1; | ||
| let sibling = el; | ||
| while (sibling = sibling.previousElementSibling) index++; | ||
| const part = `${el.localName}:nth-child(${index})`; | ||
| selector = selector ? `${part} > ${selector}` : part; | ||
| el = parent; | ||
| } | ||
| return `${path.reverse().join(" > ")}`.toLowerCase(); | ||
| return selector; | ||
| } | ||
| function getElementScrollRestorationEntry(router, options) { | ||
| const restoreKey = (options.getKey || defaultGetScrollRestorationKey)(router.latestLocation); | ||
| if (options.id) return scrollRestorationCache?.state[restoreKey]?.[`[${scrollRestorationIdAttribute}="${options.id}"]`]; | ||
| const entries = scrollRestorationCache[(options.getKey || defaultGetScrollRestorationKey)(router.latestLocation)]; | ||
| if (!entries) return; | ||
| if (options.id) return entries[`[${scrollRestorationIdAttribute}="${options.id}"]`]; | ||
| const element = options.getElement?.(); | ||
| if (!element) return; | ||
| return scrollRestorationCache?.state[restoreKey]?.[element instanceof Window ? windowScrollTarget : getCssSelector(element)]; | ||
| return entries[element === window ? windowScrollTarget : getScrollRestorationSelector(element)]; | ||
| } | ||
| let ignoreScroll = false; | ||
| const windowScrollTarget = "window"; | ||
| const scrollRestorationIdAttribute = "data-scroll-restoration-id"; | ||
| function getElement(selector) { | ||
| try { | ||
| return typeof selector === "function" ? selector() : document.querySelector(selector); | ||
| } catch {} | ||
| } | ||
| function getScrollToTopElements(scrollToTopSelectors) { | ||
| const elements = []; | ||
| for (const selector of scrollToTopSelectors) { | ||
| if (selector === windowScrollTarget) continue; | ||
| const element = getElement(selector); | ||
| if (element) elements.push(element); | ||
| } | ||
| return elements; | ||
| } | ||
| function setupScrollRestoration(router, force) { | ||
| if (!scrollRestorationCache && !(_tanstack_router_core_isServer.isServer ?? router.isServer)) return; | ||
| const cache = scrollRestorationCache; | ||
| if (force ?? router.options.scrollRestoration ?? false) router.isScrollRestoring = true; | ||
| if ((_tanstack_router_core_isServer.isServer ?? router.isServer) || router.isScrollRestorationSetup || !cache) return; | ||
| if (force ?? router.options.scrollRestoration) router.isScrollRestoring = true; | ||
| if ((_tanstack_router_core_isServer.isServer ?? router.isServer) || router.isScrollRestorationSetup) return; | ||
| router.isScrollRestorationSetup = true; | ||
@@ -78,61 +88,76 @@ ignoreScroll = false; | ||
| const trackedScrollEntries = /* @__PURE__ */ new Map(); | ||
| window.history.scrollRestoration = "manual"; | ||
| const setTrackedScrollEntry = (target, scrollX, scrollY) => { | ||
| const entry = trackedScrollEntries.get(target) || {}; | ||
| entry.scrollX = scrollX; | ||
| entry.scrollY = scrollY; | ||
| trackedScrollEntries.set(target, entry); | ||
| }; | ||
| history.scrollRestoration = "manual"; | ||
| const onScroll = (event) => { | ||
| if (ignoreScroll || !router.isScrollRestoring) return; | ||
| if (event.target === document || event.target === window) trackedScrollEntries.set(windowScrollTarget, { | ||
| scrollX: window.scrollX || 0, | ||
| scrollY: window.scrollY || 0 | ||
| }); | ||
| if (event.target === document) setTrackedScrollEntry(windowScrollTarget, scrollX, scrollY); | ||
| else { | ||
| const target = event.target; | ||
| trackedScrollEntries.set(target, { | ||
| scrollX: target.scrollLeft || 0, | ||
| scrollY: target.scrollTop || 0 | ||
| }); | ||
| setTrackedScrollEntry(target, target.scrollLeft, target.scrollTop); | ||
| } | ||
| }; | ||
| const snapshotCurrentScrollTargets = (restoreKey) => { | ||
| if (!router.isScrollRestoring || !restoreKey || trackedScrollEntries.size === 0 || !cache) return; | ||
| const keyEntry = cache.state[restoreKey] ||= {}; | ||
| for (const [target, position] of trackedScrollEntries) { | ||
| let selector; | ||
| if (target === windowScrollTarget) selector = windowScrollTarget; | ||
| else if (target.isConnected) { | ||
| const attrId = target.getAttribute(scrollRestorationIdAttribute); | ||
| selector = attrId ? `[${scrollRestorationIdAttribute}="${attrId}"]` : getCssSelector(target); | ||
| } | ||
| if (!selector) continue; | ||
| keyEntry[selector] = position; | ||
| } | ||
| if (!router.isScrollRestoring) return; | ||
| const keyEntry = scrollRestorationCache[restoreKey] ||= {}; | ||
| for (const [target, position] of trackedScrollEntries) if (target === windowScrollTarget) keyEntry[windowScrollTarget] = position; | ||
| else if (target.isConnected) keyEntry[getScrollRestorationSelector(target)] = position; | ||
| }; | ||
| document.addEventListener("scroll", onScroll, true); | ||
| router.subscribe("onBeforeLoad", (event) => { | ||
| snapshotCurrentScrollTargets(event.fromLocation ? getKey(event.fromLocation) : void 0); | ||
| if (event.fromLocation) snapshotCurrentScrollTargets(getKey(event.fromLocation)); | ||
| trackedScrollEntries.clear(); | ||
| }); | ||
| window.addEventListener("pagehide", () => { | ||
| addEventListener("pagehide", () => { | ||
| snapshotCurrentScrollTargets(getKey(router.stores.resolvedLocation.get() ?? router.stores.location.get())); | ||
| cache.persist(); | ||
| persistScrollRestorationCache(); | ||
| }); | ||
| router.subscribe("onRendered", (event) => { | ||
| const cacheKey = getKey(event.toLocation); | ||
| const behavior = router.options.scrollRestorationBehavior; | ||
| const scrollToTopSelectors = router.options.scrollToTopSelectors; | ||
| const shouldResetScroll = router.resetNextScroll; | ||
| let scrollToTopElements; | ||
| trackedScrollEntries.clear(); | ||
| if (!router.resetNextScroll) { | ||
| router.resetNextScroll = true; | ||
| return; | ||
| if (!shouldResetScroll) router.resetNextScroll = true; | ||
| if (typeof router.options.scrollRestoration === "function" && !router.options.scrollRestoration({ location: router.latestLocation })) return; | ||
| const cacheKey = getKey(event.toLocation); | ||
| const fromCacheKey = event.fromLocation && getKey(event.fromLocation); | ||
| if (router.isScrollRestoring && fromCacheKey && fromCacheKey !== cacheKey) { | ||
| const fromElementEntries = scrollRestorationCache[fromCacheKey]; | ||
| if (fromElementEntries) { | ||
| let toElementEntries = scrollRestorationCache[cacheKey]; | ||
| for (const elementSelector in fromElementEntries) { | ||
| if (elementSelector === windowScrollTarget) { | ||
| if (shouldResetScroll) continue; | ||
| } else { | ||
| const element = getElement(elementSelector); | ||
| if (!element) continue; | ||
| if (shouldResetScroll && scrollToTopSelectors) { | ||
| scrollToTopElements ??= getScrollToTopElements(scrollToTopSelectors); | ||
| if (scrollToTopElements.includes(element)) continue; | ||
| } | ||
| } | ||
| if (!toElementEntries) toElementEntries = scrollRestorationCache[cacheKey] = {}; | ||
| toElementEntries[elementSelector] ??= fromElementEntries[elementSelector]; | ||
| } | ||
| } | ||
| } | ||
| if (typeof router.options.scrollRestoration === "function" && !router.options.scrollRestoration({ location: router.latestLocation })) return; | ||
| if (!shouldResetScroll) return; | ||
| ignoreScroll = true; | ||
| try { | ||
| const elementEntries = router.isScrollRestoring ? cache.state[cacheKey] : void 0; | ||
| let restored = false; | ||
| const hash = event.toLocation.hash; | ||
| const hashScrollIntoViewOptions = event.toLocation.state.__hashScrollIntoViewOptions ?? true; | ||
| const action = require_router.locationHistoryActions.get(event.toLocation); | ||
| const skipWindowRestore = hash && hashScrollIntoViewOptions && (action === "PUSH" || action === "REPLACE"); | ||
| const elementEntries = router.isScrollRestoring ? scrollRestorationCache[cacheKey] : void 0; | ||
| let windowRestored = false; | ||
| if (elementEntries) for (const elementSelector in elementEntries) { | ||
| const entry = elementEntries[elementSelector]; | ||
| if (!require_utils.isPlainObject(entry)) continue; | ||
| const { scrollX, scrollY } = entry; | ||
| if (!Number.isFinite(scrollX) || !Number.isFinite(scrollY)) continue; | ||
| const { scrollX, scrollY } = elementEntries[elementSelector]; | ||
| if (elementSelector === windowScrollTarget) { | ||
| window.scrollTo({ | ||
| if (skipWindowRestore) continue; | ||
| scrollTo({ | ||
| top: scrollY, | ||
@@ -142,37 +167,23 @@ left: scrollX, | ||
| }); | ||
| restored = true; | ||
| } else if (elementSelector) { | ||
| let element; | ||
| try { | ||
| element = document.querySelector(elementSelector); | ||
| } catch { | ||
| continue; | ||
| } | ||
| windowRestored = true; | ||
| } else { | ||
| const element = getElement(elementSelector); | ||
| if (element) { | ||
| element.scrollLeft = scrollX; | ||
| element.scrollTop = scrollY; | ||
| restored = true; | ||
| } | ||
| } | ||
| } | ||
| if (!restored) { | ||
| const hash = router.history.location.hash.slice(1); | ||
| if (hash) { | ||
| const hashScrollIntoViewOptions = window.history.state?.__hashScrollIntoViewOptions ?? true; | ||
| if (hashScrollIntoViewOptions) { | ||
| const el = document.getElementById(hash); | ||
| if (el) el.scrollIntoView(hashScrollIntoViewOptions); | ||
| } | ||
| } else { | ||
| const scrollOptions = { | ||
| top: 0, | ||
| left: 0, | ||
| behavior | ||
| }; | ||
| window.scrollTo(scrollOptions); | ||
| if (scrollToTopSelectors) for (const selector of scrollToTopSelectors) { | ||
| if (selector === windowScrollTarget) continue; | ||
| const element = typeof selector === "function" ? selector() : document.querySelector(selector); | ||
| if (element) element.scrollTo(scrollOptions); | ||
| } | ||
| if (!windowRestored) if (hash) { | ||
| if (hashScrollIntoViewOptions) document.getElementById(hash)?.scrollIntoView(hashScrollIntoViewOptions); | ||
| } else { | ||
| const scrollOptions = { | ||
| top: 0, | ||
| left: 0, | ||
| behavior | ||
| }; | ||
| scrollTo(scrollOptions); | ||
| if (scrollToTopSelectors) { | ||
| scrollToTopElements ??= getScrollToTopElements(scrollToTopSelectors); | ||
| for (const element of scrollToTopElements) element.scrollTo(scrollOptions); | ||
| } | ||
@@ -183,6 +194,2 @@ } | ||
| } | ||
| if (router.isScrollRestoring) cache.set((state) => { | ||
| state[cacheKey] ||= {}; | ||
| return state; | ||
| }); | ||
| }); | ||
@@ -193,3 +200,2 @@ } | ||
| exports.getElementScrollRestorationEntry = getElementScrollRestorationEntry; | ||
| exports.scrollRestorationCache = scrollRestorationCache; | ||
| exports.setupScrollRestoration = setupScrollRestoration; | ||
@@ -196,0 +202,0 @@ exports.storageKey = storageKey; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"scroll-restoration.cjs","names":[],"sources":["../../src/scroll-restoration.ts"],"sourcesContent":["import { isServer } from '@tanstack/router-core/isServer'\nimport { functionalUpdate, isPlainObject } from './utils'\nimport type { AnyRouter } from './router'\nimport type { ParsedLocation } from './location'\nimport type { NonNullableUpdater } from './utils'\n\nexport type ScrollRestorationEntry = { scrollX: number; scrollY: number }\n\ntype ScrollRestorationByElement = Record<string, ScrollRestorationEntry>\n\ntype ScrollRestorationByKey = Record<string, ScrollRestorationByElement>\n\ntype ScrollRestorationCache = {\n readonly state: ScrollRestorationByKey\n set: (updater: NonNullableUpdater<ScrollRestorationByKey>) => void\n persist: () => void\n}\n\nexport type ScrollRestorationOptions = {\n getKey?: (location: ParsedLocation) => string\n scrollBehavior?: ScrollToOptions['behavior']\n}\n\nfunction getSafeSessionStorage() {\n try {\n return typeof window !== 'undefined' &&\n typeof window.sessionStorage === 'object'\n ? window.sessionStorage\n : undefined\n } catch {\n // silent\n return undefined\n }\n}\n\n// SessionStorage key used to store scroll positions across navigations.\nexport const storageKey = 'tsr-scroll-restoration-v1_3'\n\nfunction createScrollRestorationCache(): ScrollRestorationCache | null {\n const safeSessionStorage = getSafeSessionStorage()\n if (!safeSessionStorage) {\n return null\n }\n\n let state: ScrollRestorationByKey = {}\n\n try {\n const parsed = JSON.parse(safeSessionStorage.getItem(storageKey) || '{}')\n if (isPlainObject(parsed)) {\n state = parsed as ScrollRestorationByKey\n }\n } catch {\n // ignore invalid session storage payloads\n }\n\n const persist = () => {\n try {\n safeSessionStorage.setItem(storageKey, JSON.stringify(state))\n } catch {\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n '[ts-router] Could not persist scroll restoration state to sessionStorage.',\n )\n }\n }\n }\n\n return {\n get state() {\n return state\n },\n set: (updater) => {\n state = functionalUpdate(updater, state) || state\n },\n persist,\n }\n}\n\nexport const scrollRestorationCache = createScrollRestorationCache()\n\n/**\n * The default `getKey` function for `useScrollRestoration`.\n * It returns the `key` from the location state or the `href` of the location.\n *\n * The `location.href` is used as a fallback to support the use case where the location state is not available like the initial render.\n */\nexport const defaultGetScrollRestorationKey = (location: ParsedLocation) => {\n return location.state.__TSR_key! || location.href\n}\n\nfunction getCssSelector(el: any): string {\n const path = []\n let parent: HTMLElement\n while ((parent = el.parentNode)) {\n path.push(\n `${el.tagName}:nth-child(${Array.prototype.indexOf.call(parent.children, el) + 1})`,\n )\n el = parent\n }\n return `${path.reverse().join(' > ')}`.toLowerCase()\n}\n\nexport function getElementScrollRestorationEntry(\n router: AnyRouter,\n options: (\n | {\n id: string\n getElement?: () => Window | Element | undefined | null\n }\n | {\n id?: string\n getElement: () => Window | Element | undefined | null\n }\n ) & {\n getKey?: (location: ParsedLocation) => string\n },\n): ScrollRestorationEntry | undefined {\n const getKey = options.getKey || defaultGetScrollRestorationKey\n const restoreKey = getKey(router.latestLocation)\n\n if (options.id) {\n return scrollRestorationCache?.state[restoreKey]?.[\n `[${scrollRestorationIdAttribute}=\"${options.id}\"]`\n ]\n }\n\n const element = options.getElement?.()\n if (!element) {\n return\n }\n\n return scrollRestorationCache?.state[restoreKey]?.[\n element instanceof Window ? windowScrollTarget : getCssSelector(element)\n ]\n}\n\nlet ignoreScroll = false\nconst windowScrollTarget = 'window'\nconst scrollRestorationIdAttribute = 'data-scroll-restoration-id'\ntype ScrollTarget = typeof windowScrollTarget | Element\n\nexport function setupScrollRestoration(router: AnyRouter, force?: boolean) {\n if (!scrollRestorationCache && !(isServer ?? router.isServer)) {\n return\n }\n\n const cache = scrollRestorationCache\n\n const shouldScrollRestoration =\n force ?? router.options.scrollRestoration ?? false\n\n if (shouldScrollRestoration) {\n router.isScrollRestoring = true\n }\n\n if (\n (isServer ?? router.isServer) ||\n router.isScrollRestorationSetup ||\n !cache\n ) {\n return\n }\n\n router.isScrollRestorationSetup = true\n ignoreScroll = false\n\n const getKey =\n router.options.getScrollRestorationKey || defaultGetScrollRestorationKey\n const trackedScrollEntries = new Map<ScrollTarget, ScrollRestorationEntry>()\n\n window.history.scrollRestoration = 'manual'\n\n const onScroll = (event: Event) => {\n if (ignoreScroll || !router.isScrollRestoring) {\n return\n }\n\n if (event.target === document || event.target === window) {\n trackedScrollEntries.set(windowScrollTarget, {\n scrollX: window.scrollX || 0,\n scrollY: window.scrollY || 0,\n })\n } else {\n const target = event.target as Element\n trackedScrollEntries.set(target, {\n scrollX: target.scrollLeft || 0,\n scrollY: target.scrollTop || 0,\n })\n }\n }\n\n // Snapshot the current page's tracked scroll targets before navigation or unload.\n const snapshotCurrentScrollTargets = (restoreKey?: string) => {\n if (\n !router.isScrollRestoring ||\n !restoreKey ||\n trackedScrollEntries.size === 0 ||\n !cache\n ) {\n return\n }\n\n const keyEntry = (cache.state[restoreKey] ||=\n {} as ScrollRestorationByElement)\n\n for (const [target, position] of trackedScrollEntries) {\n let selector: string | undefined\n\n if (target === windowScrollTarget) {\n selector = windowScrollTarget\n } else if (target.isConnected) {\n const attrId = target.getAttribute(scrollRestorationIdAttribute)\n selector = attrId\n ? `[${scrollRestorationIdAttribute}=\"${attrId}\"]`\n : getCssSelector(target)\n }\n\n if (!selector) {\n continue\n }\n\n keyEntry[selector] = position\n }\n }\n\n document.addEventListener('scroll', onScroll, true)\n router.subscribe('onBeforeLoad', (event) => {\n snapshotCurrentScrollTargets(\n event.fromLocation ? getKey(event.fromLocation) : undefined,\n )\n trackedScrollEntries.clear()\n })\n window.addEventListener('pagehide', () => {\n snapshotCurrentScrollTargets(\n getKey(\n router.stores.resolvedLocation.get() ?? router.stores.location.get(),\n ),\n )\n cache.persist()\n })\n\n // Restore destination scroll after the new route has rendered.\n router.subscribe('onRendered', (event) => {\n const cacheKey = getKey(event.toLocation)\n const behavior = router.options.scrollRestorationBehavior\n const scrollToTopSelectors = router.options.scrollToTopSelectors\n trackedScrollEntries.clear()\n\n if (!router.resetNextScroll) {\n router.resetNextScroll = true\n return\n }\n\n if (\n typeof router.options.scrollRestoration === 'function' &&\n !router.options.scrollRestoration({ location: router.latestLocation })\n ) {\n return\n }\n\n ignoreScroll = true\n\n try {\n const elementEntries = router.isScrollRestoring\n ? cache.state[cacheKey]\n : undefined\n let restored = false\n\n if (elementEntries) {\n for (const elementSelector in elementEntries) {\n const entry = elementEntries[elementSelector]\n\n if (!isPlainObject(entry)) {\n continue\n }\n\n const { scrollX, scrollY } = entry as {\n scrollX?: unknown\n scrollY?: unknown\n }\n\n if (!Number.isFinite(scrollX) || !Number.isFinite(scrollY)) {\n continue\n }\n\n if (elementSelector === windowScrollTarget) {\n window.scrollTo({\n top: scrollY as number,\n left: scrollX as number,\n behavior,\n })\n restored = true\n } else if (elementSelector) {\n let element\n\n try {\n element = document.querySelector(elementSelector)\n } catch {\n continue\n }\n\n if (element) {\n element.scrollLeft = scrollX as number\n element.scrollTop = scrollY as number\n restored = true\n }\n }\n }\n }\n\n if (!restored) {\n const hash = router.history.location.hash.slice(1)\n\n if (hash) {\n const hashScrollIntoViewOptions =\n window.history.state?.__hashScrollIntoViewOptions ?? true\n\n if (hashScrollIntoViewOptions) {\n const el = document.getElementById(hash)\n if (el) {\n el.scrollIntoView(hashScrollIntoViewOptions)\n }\n }\n } else {\n const scrollOptions = {\n top: 0,\n left: 0,\n behavior,\n }\n\n window.scrollTo(scrollOptions)\n if (scrollToTopSelectors) {\n for (const selector of scrollToTopSelectors) {\n if (selector === windowScrollTarget) continue\n const element =\n typeof selector === 'function'\n ? selector()\n : document.querySelector(selector)\n if (element) {\n element.scrollTo(scrollOptions)\n }\n }\n }\n }\n }\n } finally {\n ignoreScroll = false\n }\n\n if (router.isScrollRestoring) {\n cache.set((state) => {\n state[cacheKey] ||= {} as ScrollRestorationByElement\n return state\n })\n }\n })\n}\n"],"mappings":"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"} | ||
| {"version":3,"file":"scroll-restoration.cjs","names":[],"sources":["../../src/scroll-restoration.ts"],"sourcesContent":["import { isServer } from '@tanstack/router-core/isServer'\nimport { locationHistoryActions } from './router'\nimport type { AnyRouter } from './router'\nimport type { ParsedLocation } from './location'\n\nexport type ScrollRestorationEntry = { scrollX: number; scrollY: number }\n\ntype ScrollRestorationByElement = Record<string, ScrollRestorationEntry>\n\ntype ScrollRestorationByKey = Record<string, ScrollRestorationByElement>\n\nexport type ScrollRestorationOptions = {\n getKey?: (location: ParsedLocation) => string\n scrollBehavior?: ScrollToOptions['behavior']\n}\n\nfunction getSafeSessionStorage() {\n try {\n // Accessing sessionStorage itself can throw SecurityError in locked-down\n // contexts, e.g. sandboxed/opaque origins or blocked storage policies.\n return sessionStorage\n } catch {\n return\n }\n}\n\n// SessionStorage key used to store scroll positions across navigations.\nexport const storageKey = 'tsr-scroll-restoration-v1_3'\nconst safeSessionStorage = getSafeSessionStorage()\n\nfunction createScrollRestorationCache() {\n try {\n return JSON.parse(\n safeSessionStorage?.getItem(storageKey) || '{}',\n ) as ScrollRestorationByKey\n } catch {\n // ignore invalid session storage payloads\n return {}\n }\n}\n\nfunction persistScrollRestorationCache() {\n try {\n safeSessionStorage?.setItem(\n storageKey,\n JSON.stringify(scrollRestorationCache),\n )\n } catch {\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n '[ts-router] Could not persist scroll restoration state to sessionStorage.',\n )\n }\n }\n}\n\nconst scrollRestorationCache = /* @__PURE__ */ createScrollRestorationCache()\nconst scrollRestorationIdAttribute = 'data-scroll-restoration-id'\n\n/**\n * The default `getKey` function for `useScrollRestoration`.\n * It returns the `key` from the location state or the `href` of the location.\n *\n * The `location.href` is used as a fallback to support the use case where the location state is not available like the initial render.\n */\nexport const defaultGetScrollRestorationKey = (location: ParsedLocation) => {\n return location.state.__TSR_key! || location.href\n}\n\nfunction getScrollRestorationSelector(element: Element): string {\n const attrId = element.getAttribute(scrollRestorationIdAttribute)\n if (attrId) {\n return `[${scrollRestorationIdAttribute}=\"${attrId}\"]`\n }\n\n let selector = ''\n let el: any = element\n let parent: HTMLElement\n\n while ((parent = el.parentNode)) {\n let index = 1\n let sibling = el\n while ((sibling = sibling.previousElementSibling)) {\n index++\n }\n\n const part = `${el.localName}:nth-child(${index})`\n selector = selector ? `${part} > ${selector}` : part\n el = parent\n }\n\n return selector\n}\n\nexport function getElementScrollRestorationEntry(\n router: AnyRouter,\n options: (\n | {\n id: string\n getElement?: () => Window | Element | undefined | null\n }\n | {\n id?: string\n getElement: () => Window | Element | undefined | null\n }\n ) & {\n getKey?: (location: ParsedLocation) => string\n },\n): ScrollRestorationEntry | undefined {\n const getKey = options.getKey || defaultGetScrollRestorationKey\n const restoreKey = getKey(router.latestLocation)\n const entries = scrollRestorationCache[restoreKey]\n\n if (!entries) {\n return\n }\n\n if (options.id) {\n return entries[`[${scrollRestorationIdAttribute}=\"${options.id}\"]`]\n }\n\n const element = options.getElement?.()\n if (!element) {\n return\n }\n\n return entries[\n element === window\n ? windowScrollTarget\n : getScrollRestorationSelector(element as Element)\n ]\n}\n\nlet ignoreScroll = false\nconst windowScrollTarget = 'window'\ntype ScrollTarget = typeof windowScrollTarget | Element\n\nfunction getElement(selector: string | (() => Element | null | undefined)) {\n try {\n return typeof selector === 'function'\n ? selector()\n : document.querySelector(selector)\n } catch {}\n return\n}\n\nfunction getScrollToTopElements(\n scrollToTopSelectors: NonNullable<\n AnyRouter['options']['scrollToTopSelectors']\n >,\n): Array<Element> {\n const elements: Array<Element> = []\n\n for (const selector of scrollToTopSelectors) {\n if (selector === windowScrollTarget) {\n continue\n }\n\n const element = getElement(selector)\n if (element) {\n elements.push(element)\n }\n }\n\n return elements\n}\n\nexport function setupScrollRestoration(router: AnyRouter, force?: boolean) {\n // Keep hash/top scrolling active even when sessionStorage is unavailable.\n\n if (force ?? router.options.scrollRestoration) {\n router.isScrollRestoring = true\n }\n\n if ((isServer ?? router.isServer) || router.isScrollRestorationSetup) {\n return\n }\n\n router.isScrollRestorationSetup = true\n ignoreScroll = false\n\n const getKey =\n router.options.getScrollRestorationKey || defaultGetScrollRestorationKey\n const trackedScrollEntries = new Map<ScrollTarget, ScrollRestorationEntry>()\n const setTrackedScrollEntry = (\n target: ScrollTarget,\n scrollX: number,\n scrollY: number,\n ) => {\n const entry =\n trackedScrollEntries.get(target) || ({} as ScrollRestorationEntry)\n entry.scrollX = scrollX\n entry.scrollY = scrollY\n trackedScrollEntries.set(target, entry)\n }\n\n history.scrollRestoration = 'manual'\n\n const onScroll = (event: Event) => {\n if (ignoreScroll || !router.isScrollRestoring) {\n return\n }\n\n if (event.target === document) {\n setTrackedScrollEntry(windowScrollTarget, scrollX, scrollY)\n } else {\n const target = event.target as Element\n setTrackedScrollEntry(target, target.scrollLeft, target.scrollTop)\n }\n }\n\n // Snapshot the current page's tracked scroll targets before navigation or unload.\n const snapshotCurrentScrollTargets = (restoreKey: string) => {\n if (!router.isScrollRestoring) {\n return\n }\n\n const keyEntry = (scrollRestorationCache[restoreKey] ||=\n {} as ScrollRestorationByElement)\n\n for (const [target, position] of trackedScrollEntries) {\n if (target === windowScrollTarget) {\n keyEntry[windowScrollTarget] = position\n } else if (target.isConnected) {\n keyEntry[getScrollRestorationSelector(target)] = position\n }\n }\n }\n\n document.addEventListener('scroll', onScroll, true)\n router.subscribe('onBeforeLoad', (event) => {\n if (event.fromLocation) {\n snapshotCurrentScrollTargets(getKey(event.fromLocation))\n }\n trackedScrollEntries.clear()\n })\n addEventListener('pagehide', () => {\n snapshotCurrentScrollTargets(\n getKey(\n router.stores.resolvedLocation.get() ?? router.stores.location.get(),\n ),\n )\n persistScrollRestorationCache()\n })\n\n // Restore destination scroll after the new route has rendered.\n router.subscribe('onRendered', (event) => {\n const behavior = router.options.scrollRestorationBehavior\n const scrollToTopSelectors = router.options.scrollToTopSelectors\n const shouldResetScroll = router.resetNextScroll\n let scrollToTopElements: Array<Element> | undefined\n trackedScrollEntries.clear()\n\n if (!shouldResetScroll) {\n router.resetNextScroll = true\n }\n\n if (\n typeof router.options.scrollRestoration === 'function' &&\n !router.options.scrollRestoration({ location: router.latestLocation })\n ) {\n return\n }\n\n const cacheKey = getKey(event.toLocation)\n const fromCacheKey = event.fromLocation && getKey(event.fromLocation)\n\n if (router.isScrollRestoring && fromCacheKey && fromCacheKey !== cacheKey) {\n const fromElementEntries = scrollRestorationCache[fromCacheKey]\n\n if (fromElementEntries) {\n let toElementEntries = scrollRestorationCache[cacheKey]\n\n for (const elementSelector in fromElementEntries) {\n if (elementSelector === windowScrollTarget) {\n if (shouldResetScroll) {\n continue\n }\n } else {\n const element = getElement(elementSelector)\n if (!element) {\n continue\n }\n\n if (shouldResetScroll && scrollToTopSelectors) {\n scrollToTopElements ??=\n getScrollToTopElements(scrollToTopSelectors)\n if (scrollToTopElements.includes(element)) {\n continue\n }\n }\n }\n\n if (!toElementEntries) {\n toElementEntries = scrollRestorationCache[cacheKey] =\n {} as ScrollRestorationByElement\n }\n\n toElementEntries[elementSelector] ??=\n fromElementEntries[elementSelector]!\n }\n }\n }\n\n if (!shouldResetScroll) {\n return\n }\n\n ignoreScroll = true\n\n try {\n const hash = event.toLocation.hash\n const hashScrollIntoViewOptions =\n event.toLocation.state.__hashScrollIntoViewOptions ?? true\n const action = locationHistoryActions.get(event.toLocation)\n const skipWindowRestore =\n hash &&\n hashScrollIntoViewOptions &&\n (action === 'PUSH' || action === 'REPLACE')\n\n const elementEntries = router.isScrollRestoring\n ? scrollRestorationCache[cacheKey]\n : undefined\n let windowRestored = false\n\n if (elementEntries) {\n for (const elementSelector in elementEntries) {\n const { scrollX, scrollY } = elementEntries[elementSelector]!\n\n if (elementSelector === windowScrollTarget) {\n if (skipWindowRestore) {\n continue\n }\n\n scrollTo({\n top: scrollY,\n left: scrollX,\n behavior,\n })\n windowRestored = true\n } else {\n const element = getElement(elementSelector)\n if (element) {\n element.scrollLeft = scrollX\n element.scrollTop = scrollY\n }\n }\n }\n }\n\n if (!windowRestored) {\n if (hash) {\n if (hashScrollIntoViewOptions) {\n document\n .getElementById(hash)\n ?.scrollIntoView(hashScrollIntoViewOptions)\n }\n } else {\n const scrollOptions = {\n top: 0,\n left: 0,\n behavior,\n }\n\n scrollTo(scrollOptions)\n if (scrollToTopSelectors) {\n scrollToTopElements ??= getScrollToTopElements(scrollToTopSelectors)\n for (const element of scrollToTopElements) {\n element.scrollTo(scrollOptions)\n }\n }\n }\n }\n } finally {\n ignoreScroll = false\n }\n })\n}\n"],"mappings":"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"} |
| import { AnyRouter } from './router.cjs'; | ||
| import { ParsedLocation } from './location.cjs'; | ||
| import { NonNullableUpdater } from './utils.cjs'; | ||
| export type ScrollRestorationEntry = { | ||
@@ -8,9 +7,2 @@ scrollX: number; | ||
| }; | ||
| type ScrollRestorationByElement = Record<string, ScrollRestorationEntry>; | ||
| type ScrollRestorationByKey = Record<string, ScrollRestorationByElement>; | ||
| type ScrollRestorationCache = { | ||
| readonly state: ScrollRestorationByKey; | ||
| set: (updater: NonNullableUpdater<ScrollRestorationByKey>) => void; | ||
| persist: () => void; | ||
| }; | ||
| export type ScrollRestorationOptions = { | ||
@@ -21,3 +13,2 @@ getKey?: (location: ParsedLocation) => string; | ||
| export declare const storageKey = "tsr-scroll-restoration-v1_3"; | ||
| export declare const scrollRestorationCache: ScrollRestorationCache | null; | ||
| /** | ||
@@ -40,2 +31,1 @@ * The default `getKey` function for `useScrollRestoration`. | ||
| export declare function setupScrollRestoration(router: AnyRouter, force?: boolean): void; | ||
| export {}; |
@@ -43,4 +43,3 @@ export * from './global.js'; | ||
| export { isNotFound, notFound } from './not-found.js'; | ||
| export { defaultGetScrollRestorationKey, getElementScrollRestorationEntry, storageKey, scrollRestorationCache, setupScrollRestoration, } from './scroll-restoration.js'; | ||
| export { handleHashScroll } from './hash-scroll.js'; | ||
| export { defaultGetScrollRestorationKey, getElementScrollRestorationEntry, storageKey, setupScrollRestoration, } from './scroll-restoration.js'; | ||
| export type { ScrollRestorationOptions, ScrollRestorationEntry, } from './scroll-restoration.js'; | ||
@@ -47,0 +46,0 @@ export type { ValidateFromPath, ValidateToPath, ValidateSearch, ValidateParams, InferFrom, InferTo, InferMaskTo, InferMaskFrom, ValidateNavigateOptions, ValidateNavigateOptionsArray, ValidateRedirectOptions, ValidateRedirectOptionsArray, ValidateId, InferStrict, InferShouldThrow, InferSelected, ValidateUseSearchResult, ValidateUseParamsResult, } from './typePrimitives.js'; |
@@ -5,3 +5,3 @@ import { DEFAULT_PROTOCOL_ALLOWLIST, buildDevStylesUrl, createControlledPromise, deepEqual, escapeHtml, functionalUpdate, hasKeys, isDangerousProtocol, isModuleNotFoundError, isPlainArray, isPlainObject, replaceEqualDeep } from "./utils.js"; | ||
| import { isNotFound, notFound } from "./not-found.js"; | ||
| import { defaultGetScrollRestorationKey, getElementScrollRestorationEntry, scrollRestorationCache, setupScrollRestoration, storageKey } from "./scroll-restoration.js"; | ||
| import { defaultGetScrollRestorationKey, getElementScrollRestorationEntry, setupScrollRestoration, storageKey } from "./scroll-restoration.js"; | ||
| import { decode, encode } from "./qss.js"; | ||
@@ -21,6 +21,5 @@ import { defaultParseSearch, defaultStringifySearch, parseSearchWith, stringifySearchWith } from "./searchParams.js"; | ||
| import { retainSearchParams, stripSearchParams } from "./searchMiddleware.js"; | ||
| import { handleHashScroll } from "./hash-scroll.js"; | ||
| import { createSerializationAdapter, makeSerovalPlugin, makeSsrSerovalPlugin } from "./ssr/serializer/transformer.js"; | ||
| import { RawStream, createRawStreamDeserializePlugin, createRawStreamRPCPlugin } from "./ssr/serializer/RawStream.js"; | ||
| import { defaultSerovalPlugins } from "./ssr/serializer/seroval-plugins.js"; | ||
| export { BaseRootRoute, BaseRoute, BaseRouteApi, DEFAULT_PROTOCOL_ALLOWLIST, PathParamError, RawStream, RouterCore, SearchParamError, TSR_DEFERRED_PROMISE, buildDevStylesUrl, cleanPath, composeRewrites, createControlledPromise, createInlineCssStyleAsset, createNonReactiveMutableStore, createNonReactiveReadonlyStore, createRawStreamDeserializePlugin, createRawStreamRPCPlugin, createRouterConfig, createSerializationAdapter, decode, deepEqual, defaultGetScrollRestorationKey, defaultParseSearch, defaultSerializeError, defaultSerovalPlugins, defaultStringifySearch, defer, encode, escapeHtml, exactPathTest, executeRewriteInput, functionalUpdate, getAssetCrossOrigin, getElementScrollRestorationEntry, getInitialRouterState, getLocationChangeInfo, getMatchedRoutes, getStylesheetHref, handleHashScroll, hasKeys, interpolatePath, invariant, isDangerousProtocol, isInlinableStylesheet, isMatch, isModuleNotFoundError, isNotFound, isPlainArray, isPlainObject, isRedirect, isResolvedRedirect, joinPaths, lazyFn, makeSerovalPlugin, makeSsrSerovalPlugin, notFound, parseRedirect, parseSearchWith, preloadWarning, redirect, removeTrailingSlash, replaceEqualDeep, resolveManifestAssetLink, resolvePath, retainSearchParams, rootRouteId, scrollRestorationCache, setupScrollRestoration, storageKey, stringifySearchWith, stripSearchParams, trailingSlashOptions, trimPath, trimPathLeft, trimPathRight }; | ||
| export { BaseRootRoute, BaseRoute, BaseRouteApi, DEFAULT_PROTOCOL_ALLOWLIST, PathParamError, RawStream, RouterCore, SearchParamError, TSR_DEFERRED_PROMISE, buildDevStylesUrl, cleanPath, composeRewrites, createControlledPromise, createInlineCssStyleAsset, createNonReactiveMutableStore, createNonReactiveReadonlyStore, createRawStreamDeserializePlugin, createRawStreamRPCPlugin, createRouterConfig, createSerializationAdapter, decode, deepEqual, defaultGetScrollRestorationKey, defaultParseSearch, defaultSerializeError, defaultSerovalPlugins, defaultStringifySearch, defer, encode, escapeHtml, exactPathTest, executeRewriteInput, functionalUpdate, getAssetCrossOrigin, getElementScrollRestorationEntry, getInitialRouterState, getLocationChangeInfo, getMatchedRoutes, getStylesheetHref, hasKeys, interpolatePath, invariant, isDangerousProtocol, isInlinableStylesheet, isMatch, isModuleNotFoundError, isNotFound, isPlainArray, isPlainObject, isRedirect, isResolvedRedirect, joinPaths, lazyFn, makeSerovalPlugin, makeSsrSerovalPlugin, notFound, parseRedirect, parseSearchWith, preloadWarning, redirect, removeTrailingSlash, replaceEqualDeep, resolveManifestAssetLink, resolvePath, retainSearchParams, rootRouteId, setupScrollRestoration, storageKey, stringifySearchWith, stripSearchParams, trailingSlashOptions, trimPath, trimPathLeft, trimPathRight }; |
| import { LocationRewrite } from './router.js'; | ||
| /** Compose multiple rewrite pairs into a single in/out rewrite. */ | ||
| /** Compose multiple rewrite pairs into a single in/out rewrite. */ | ||
| export declare function composeRewrites(rewrites: Array<LocationRewrite>): { | ||
@@ -13,3 +12,2 @@ input: ({ url }: { | ||
| /** Create a rewrite pair that strips/adds a basepath on input/output. */ | ||
| /** Create a rewrite pair that strips/adds a basepath on input/output. */ | ||
| export declare function rewriteBasepath(opts: { | ||
@@ -27,6 +25,4 @@ basepath: string; | ||
| /** Execute a location input rewrite if provided. */ | ||
| /** Execute a location input rewrite if provided. */ | ||
| export declare function executeRewriteInput(rewrite: LocationRewrite | undefined, url: URL): URL; | ||
| /** Execute a location output rewrite if provided. */ | ||
| /** Execute a location output rewrite if provided. */ | ||
| export declare function executeRewriteOutput(rewrite: LocationRewrite | undefined, url: URL): URL; |
| import { joinPaths, trimPath } from "./path.js"; | ||
| //#region src/rewrite.ts | ||
| /** Compose multiple rewrite pairs into a single in/out rewrite. */ | ||
| /** Compose multiple rewrite pairs into a single in/out rewrite. */ | ||
| function composeRewrites(rewrites) { | ||
@@ -18,9 +17,7 @@ return { | ||
| /** Create a rewrite pair that strips/adds a basepath on input/output. */ | ||
| /** Create a rewrite pair that strips/adds a basepath on input/output. */ | ||
| function rewriteBasepath(opts) { | ||
| const trimmedBasepath = trimPath(opts.basepath); | ||
| const normalizedBasepath = `/${trimmedBasepath}`; | ||
| const normalizedBasepathWithSlash = `${normalizedBasepath}/`; | ||
| const checkBasepath = opts.caseSensitive ? normalizedBasepath : normalizedBasepath.toLowerCase(); | ||
| const checkBasepathWithSlash = opts.caseSensitive ? normalizedBasepathWithSlash : normalizedBasepathWithSlash.toLowerCase(); | ||
| const checkBasepathWithSlash = `${checkBasepath}/`; | ||
| return { | ||
@@ -44,3 +41,2 @@ input: ({ url }) => { | ||
| /** Execute a location input rewrite if provided. */ | ||
| /** Execute a location input rewrite if provided. */ | ||
| function executeRewriteInput(rewrite, url) { | ||
@@ -55,3 +51,2 @@ const res = rewrite?.input?.({ url }); | ||
| /** Execute a location output rewrite if provided. */ | ||
| /** Execute a location output rewrite if provided. */ | ||
| function executeRewriteOutput(rewrite, url) { | ||
@@ -58,0 +53,0 @@ const res = rewrite?.output?.({ url }); |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"rewrite.js","names":[],"sources":["../../src/rewrite.ts"],"sourcesContent":["import { joinPaths, trimPath } from './path'\nimport type { LocationRewrite } from './router'\n\n/** Compose multiple rewrite pairs into a single in/out rewrite. */\n/** Compose multiple rewrite pairs into a single in/out rewrite. */\nexport function composeRewrites(rewrites: Array<LocationRewrite>) {\n return {\n input: ({ url }) => {\n for (const rewrite of rewrites) {\n url = executeRewriteInput(rewrite, url)\n }\n return url\n },\n output: ({ url }) => {\n for (let i = rewrites.length - 1; i >= 0; i--) {\n url = executeRewriteOutput(rewrites[i], url)\n }\n return url\n },\n } satisfies LocationRewrite\n}\n\n/** Create a rewrite pair that strips/adds a basepath on input/output. */\n/** Create a rewrite pair that strips/adds a basepath on input/output. */\nexport function rewriteBasepath(opts: {\n basepath: string\n caseSensitive?: boolean\n}) {\n const trimmedBasepath = trimPath(opts.basepath)\n const normalizedBasepath = `/${trimmedBasepath}`\n const normalizedBasepathWithSlash = `${normalizedBasepath}/`\n const checkBasepath = opts.caseSensitive\n ? normalizedBasepath\n : normalizedBasepath.toLowerCase()\n const checkBasepathWithSlash = opts.caseSensitive\n ? normalizedBasepathWithSlash\n : normalizedBasepathWithSlash.toLowerCase()\n\n return {\n input: ({ url }) => {\n const pathname = opts.caseSensitive\n ? url.pathname\n : url.pathname.toLowerCase()\n\n // Handle exact basepath match (e.g., /my-app -> /)\n if (pathname === checkBasepath) {\n url.pathname = '/'\n } else if (pathname.startsWith(checkBasepathWithSlash)) {\n // Handle basepath with trailing content (e.g., /my-app/users -> /users)\n url.pathname = url.pathname.slice(normalizedBasepath.length)\n }\n return url\n },\n output: ({ url }) => {\n url.pathname = joinPaths(['/', trimmedBasepath, url.pathname])\n return url\n },\n } satisfies LocationRewrite\n}\n\n/** Execute a location input rewrite if provided. */\n/** Execute a location input rewrite if provided. */\nexport function executeRewriteInput(\n rewrite: LocationRewrite | undefined,\n url: URL,\n): URL {\n const res = rewrite?.input?.({ url })\n if (res) {\n if (typeof res === 'string') {\n return new URL(res)\n } else if (res instanceof URL) {\n return res\n }\n }\n return url\n}\n\n/** Execute a location output rewrite if provided. */\n/** Execute a location output rewrite if provided. */\nexport function executeRewriteOutput(\n rewrite: LocationRewrite | undefined,\n url: URL,\n): URL {\n const res = rewrite?.output?.({ url })\n if (res) {\n if (typeof res === 'string') {\n return new URL(res)\n } else if (res instanceof URL) {\n return res\n }\n }\n return url\n}\n"],"mappings":";;;;AAKA,SAAgB,gBAAgB,UAAkC;AAChE,QAAO;EACL,QAAQ,EAAE,UAAU;AAClB,QAAK,MAAM,WAAW,SACpB,OAAM,oBAAoB,SAAS,IAAI;AAEzC,UAAO;;EAET,SAAS,EAAE,UAAU;AACnB,QAAK,IAAI,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,IACxC,OAAM,qBAAqB,SAAS,IAAI,IAAI;AAE9C,UAAO;;EAEV;;;;AAKH,SAAgB,gBAAgB,MAG7B;CACD,MAAM,kBAAkB,SAAS,KAAK,SAAS;CAC/C,MAAM,qBAAqB,IAAI;CAC/B,MAAM,8BAA8B,GAAG,mBAAmB;CAC1D,MAAM,gBAAgB,KAAK,gBACvB,qBACA,mBAAmB,aAAa;CACpC,MAAM,yBAAyB,KAAK,gBAChC,8BACA,4BAA4B,aAAa;AAE7C,QAAO;EACL,QAAQ,EAAE,UAAU;GAClB,MAAM,WAAW,KAAK,gBAClB,IAAI,WACJ,IAAI,SAAS,aAAa;AAG9B,OAAI,aAAa,cACf,KAAI,WAAW;YACN,SAAS,WAAW,uBAAuB,CAEpD,KAAI,WAAW,IAAI,SAAS,MAAM,mBAAmB,OAAO;AAE9D,UAAO;;EAET,SAAS,EAAE,UAAU;AACnB,OAAI,WAAW,UAAU;IAAC;IAAK;IAAiB,IAAI;IAAS,CAAC;AAC9D,UAAO;;EAEV;;;;AAKH,SAAgB,oBACd,SACA,KACK;CACL,MAAM,MAAM,SAAS,QAAQ,EAAE,KAAK,CAAC;AACrC,KAAI;MACE,OAAO,QAAQ,SACjB,QAAO,IAAI,IAAI,IAAI;WACV,eAAe,IACxB,QAAO;;AAGX,QAAO;;;;AAKT,SAAgB,qBACd,SACA,KACK;CACL,MAAM,MAAM,SAAS,SAAS,EAAE,KAAK,CAAC;AACtC,KAAI;MACE,OAAO,QAAQ,SACjB,QAAO,IAAI,IAAI,IAAI;WACV,eAAe,IACxB,QAAO;;AAGX,QAAO"} | ||
| {"version":3,"file":"rewrite.js","names":[],"sources":["../../src/rewrite.ts"],"sourcesContent":["import { joinPaths, trimPath } from './path'\nimport type { LocationRewrite } from './router'\n\n/** Compose multiple rewrite pairs into a single in/out rewrite. */\nexport function composeRewrites(rewrites: Array<LocationRewrite>) {\n return {\n input: ({ url }) => {\n for (const rewrite of rewrites) {\n url = executeRewriteInput(rewrite, url)\n }\n return url\n },\n output: ({ url }) => {\n for (let i = rewrites.length - 1; i >= 0; i--) {\n url = executeRewriteOutput(rewrites[i], url)\n }\n return url\n },\n } satisfies LocationRewrite\n}\n\n/** Create a rewrite pair that strips/adds a basepath on input/output. */\nexport function rewriteBasepath(opts: {\n basepath: string\n caseSensitive?: boolean\n}) {\n const trimmedBasepath = trimPath(opts.basepath)\n const normalizedBasepath = `/${trimmedBasepath}`\n const checkBasepath = opts.caseSensitive\n ? normalizedBasepath\n : normalizedBasepath.toLowerCase()\n const checkBasepathWithSlash = `${checkBasepath}/`\n\n return {\n input: ({ url }) => {\n const pathname = opts.caseSensitive\n ? url.pathname\n : url.pathname.toLowerCase()\n\n // Handle exact basepath match (e.g., /my-app -> /)\n if (pathname === checkBasepath) {\n url.pathname = '/'\n } else if (pathname.startsWith(checkBasepathWithSlash)) {\n // Handle basepath with trailing content (e.g., /my-app/users -> /users)\n url.pathname = url.pathname.slice(normalizedBasepath.length)\n }\n return url\n },\n output: ({ url }) => {\n url.pathname = joinPaths(['/', trimmedBasepath, url.pathname])\n return url\n },\n } satisfies LocationRewrite\n}\n\n/** Execute a location input rewrite if provided. */\nexport function executeRewriteInput(\n rewrite: LocationRewrite | undefined,\n url: URL,\n): URL {\n const res = rewrite?.input?.({ url })\n if (res) {\n if (typeof res === 'string') {\n return new URL(res)\n } else if (res instanceof URL) {\n return res\n }\n }\n return url\n}\n\n/** Execute a location output rewrite if provided. */\nexport function executeRewriteOutput(\n rewrite: LocationRewrite | undefined,\n url: URL,\n): URL {\n const res = rewrite?.output?.({ url })\n if (res) {\n if (typeof res === 'string') {\n return new URL(res)\n } else if (res instanceof URL) {\n return res\n }\n }\n return url\n}\n"],"mappings":";;;AAIA,SAAgB,gBAAgB,UAAkC;AAChE,QAAO;EACL,QAAQ,EAAE,UAAU;AAClB,QAAK,MAAM,WAAW,SACpB,OAAM,oBAAoB,SAAS,IAAI;AAEzC,UAAO;;EAET,SAAS,EAAE,UAAU;AACnB,QAAK,IAAI,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,IACxC,OAAM,qBAAqB,SAAS,IAAI,IAAI;AAE9C,UAAO;;EAEV;;;AAIH,SAAgB,gBAAgB,MAG7B;CACD,MAAM,kBAAkB,SAAS,KAAK,SAAS;CAC/C,MAAM,qBAAqB,IAAI;CAC/B,MAAM,gBAAgB,KAAK,gBACvB,qBACA,mBAAmB,aAAa;CACpC,MAAM,yBAAyB,GAAG,cAAc;AAEhD,QAAO;EACL,QAAQ,EAAE,UAAU;GAClB,MAAM,WAAW,KAAK,gBAClB,IAAI,WACJ,IAAI,SAAS,aAAa;AAG9B,OAAI,aAAa,cACf,KAAI,WAAW;YACN,SAAS,WAAW,uBAAuB,CAEpD,KAAI,WAAW,IAAI,SAAS,MAAM,mBAAmB,OAAO;AAE9D,UAAO;;EAET,SAAS,EAAE,UAAU;AACnB,OAAI,WAAW,UAAU;IAAC;IAAK;IAAiB,IAAI;IAAS,CAAC;AAC9D,UAAO;;EAEV;;;AAIH,SAAgB,oBACd,SACA,KACK;CACL,MAAM,MAAM,SAAS,QAAQ,EAAE,KAAK,CAAC;AACrC,KAAI;MACE,OAAO,QAAQ,SACjB,QAAO,IAAI,IAAI,IAAI;WACV,eAAe,IACxB,QAAO;;AAGX,QAAO;;;AAIT,SAAgB,qBACd,SACA,KACK;CACL,MAAM,MAAM,SAAS,SAAS,EAAE,KAAK,CAAC;AACtC,KAAI;MACE,OAAO,QAAQ,SACjB,QAAO,IAAI,IAAI,IAAI;WACV,eAAe,IACxB,QAAO;;AAGX,QAAO"} |
@@ -6,3 +6,3 @@ import { loadRouteChunk } from './load-matches.js'; | ||
| import { AnyRedirect, ResolvedRedirect } from './redirect.js'; | ||
| import { HistoryLocation, HistoryState, ParsedHistoryState, RouterHistory } from '@tanstack/history'; | ||
| import { HistoryAction, HistoryLocation, HistoryState, ParsedHistoryState, RouterHistory } from '@tanstack/history'; | ||
| import { Awaitable, Constrain, ControlledPromise, NoInfer, NonNullableUpdater, PickAsRequired, Updater } from './utils.js'; | ||
@@ -487,2 +487,5 @@ import { ParsedLocation } from './location.js'; | ||
| sync?: boolean; | ||
| action?: { | ||
| type: HistoryAction; | ||
| }; | ||
| }) => Promise<void>; | ||
@@ -573,2 +576,3 @@ export type CommitLocationFn = ({ viewTransition, ignoreBlocker, ...next }: ParsedLocation & CommitLocationOptions) => Promise<void>; | ||
| }; | ||
| export declare const locationHistoryActions: WeakMap<ParsedLocation<{}>, HistoryAction>; | ||
| export type CreateRouterFn = <TRouteTree extends AnyRoute, TTrailingSlashOption extends TrailingSlashOption = 'never', TDefaultStructuralSharingOption extends boolean = false, TRouterHistory extends RouterHistory = RouterHistory, TDehydrated extends Record<string, any> = Record<string, any>>(options: undefined extends number ? 'strictNullChecks must be enabled in tsconfig.json' : RouterConstructorOptions<TRouteTree, TTrailingSlashOption, TDefaultStructuralSharingOption, TRouterHistory, TDehydrated>) => RouterCore<TRouteTree, TTrailingSlashOption, TDefaultStructuralSharingOption, TRouterHistory, TDehydrated>; | ||
@@ -575,0 +579,0 @@ declare global { |
@@ -52,2 +52,3 @@ import { DEFAULT_PROTOCOL_ALLOWLIST, createControlledPromise, decodePath, deepEqual, encodePathLikeUrl, findLast, functionalUpdate, hasKeys, isDangerousProtocol, last, nullReplaceEqualDeep, replaceEqualDeep } from "./utils.js"; | ||
| } | ||
| const locationHistoryActions = /* @__PURE__ */ new WeakMap(); | ||
| /** | ||
@@ -378,2 +379,3 @@ * Core, framework-agnostic router engine that powers TanStack Router. | ||
| this.commitLocation = async ({ viewTransition, ignoreBlocker, ...next }) => { | ||
| let historyAction; | ||
| const isSameState = () => { | ||
@@ -427,6 +429,7 @@ const ignoredProps = [ | ||
| this.shouldViewTransition = viewTransition; | ||
| this.history[next.replace ? "replace" : "push"](nextHistory.publicHref, nextHistory.state, { ignoreBlocker }); | ||
| historyAction = next.replace ? "REPLACE" : "PUSH"; | ||
| this.history[historyAction === "REPLACE" ? "replace" : "push"](nextHistory.publicHref, nextHistory.state, { ignoreBlocker }); | ||
| } | ||
| this.resetNextScroll = next.resetScroll ?? true; | ||
| if (!this.history.subscribers.size) this.load(); | ||
| if (!this.history.subscribers.size) this.load(historyAction ? { action: { type: historyAction } } : void 0); | ||
| return this.commitLocationPromise; | ||
@@ -536,2 +539,3 @@ }; | ||
| this.load = async (opts) => { | ||
| const historyAction = opts?.action?.type; | ||
| let redirect; | ||
@@ -545,2 +549,4 @@ let notFound; | ||
| this.beforeLoad(); | ||
| if (historyAction) locationHistoryActions.set(this.latestLocation, historyAction); | ||
| else locationHistoryActions.delete(this.latestLocation); | ||
| const next = this.latestLocation; | ||
@@ -1180,4 +1186,4 @@ const locationChangeInfo = getLocationChangeInfo(next, this.stores.resolvedLocation.get()); | ||
| //#endregion | ||
| export { PathParamError, RouterCore, SearchParamError, defaultSerializeError, getInitialRouterState, getLocationChangeInfo, getMatchedRoutes, lazyFn, trailingSlashOptions }; | ||
| export { PathParamError, RouterCore, SearchParamError, defaultSerializeError, getInitialRouterState, getLocationChangeInfo, getMatchedRoutes, lazyFn, locationHistoryActions, trailingSlashOptions }; | ||
| //# sourceMappingURL=router.js.map |
| export default function (options: { | ||
| storageKey: string; | ||
| key?: string; | ||
| behavior?: ScrollToOptions['behavior']; | ||
| shouldScrollRestoration?: boolean; | ||
| }): void; |
| //#region src/scroll-restoration-inline.ts?script-string | ||
| var scroll_restoration_inline_default = "function(t){let s;try{s=JSON.parse(sessionStorage.getItem(t.storageKey)||\"{}\")}catch(e){console.error(e);return}const c=t.key||window.history.state?.__TSR_key,r=c?s[c]:void 0;if(t.shouldScrollRestoration&&r&&typeof r==\"object\"&&Object.keys(r).length>0){for(const e in r){const o=r[e];if(!o||typeof o!=\"object\")continue;const l=o.scrollX,i=o.scrollY;if(!(!Number.isFinite(l)||!Number.isFinite(i))){if(e===\"window\")window.scrollTo({top:i,left:l,behavior:t.behavior});else if(e){let n;try{n=document.querySelector(e)}catch{continue}n&&(n.scrollLeft=l,n.scrollTop=i)}}}return}const a=window.location.hash.split(\"#\",2)[1];if(a){const e=window.history.state?.__hashScrollIntoViewOptions??!0;if(e){const o=document.getElementById(a);o&&o.scrollIntoView(e)}return}window.scrollTo({top:0,left:0,behavior:t.behavior})}"; | ||
| var scroll_restoration_inline_default = "function(i){let l;try{l=JSON.parse(sessionStorage.getItem(i.storageKey)||\"{}\")}catch(e){console.error(e);return}const c=i.key||window.history.state?.__TSR_key,o=c?l[c]:void 0;let f=!1;if(o&&typeof o==\"object\")for(const e in o){const t=o[e];if(!t||typeof t!=\"object\")continue;const r=t.scrollX,s=t.scrollY;if(!(!Number.isFinite(r)||!Number.isFinite(s))){if(e===\"window\")window.scrollTo({top:s,left:r}),f=!0;else if(e){let n;try{n=document.querySelector(e)}catch{continue}n&&(n.scrollLeft=r,n.scrollTop=s)}}}if(f)return;const w=window.location.hash.split(\"#\",2)[1];if(w){const e=window.history.state?.__hashScrollIntoViewOptions??!0;if(e){const t=document.getElementById(w);t&&t.scrollIntoView(e)}return}window.scrollTo({top:0,left:0})}"; | ||
| //#endregion | ||
@@ -4,0 +4,0 @@ export { scroll_restoration_inline_default as default }; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"scroll-restoration-inline.js","names":[],"sources":["../../src/scroll-restoration-inline.ts?script-string"],"sourcesContent":["export default function (options: {\n storageKey: string\n key?: string\n behavior?: ScrollToOptions['behavior']\n shouldScrollRestoration?: boolean\n}) {\n let byKey\n\n try {\n byKey = JSON.parse(sessionStorage.getItem(options.storageKey) || '{}')\n } catch (error) {\n console.error(error)\n return\n }\n\n const resolvedKey = options.key || window.history.state?.__TSR_key\n const elementEntries = resolvedKey ? byKey[resolvedKey] : undefined\n\n if (\n options.shouldScrollRestoration &&\n elementEntries &&\n typeof elementEntries === 'object' &&\n Object.keys(elementEntries).length > 0\n ) {\n for (const elementSelector in elementEntries) {\n const entry = elementEntries[elementSelector]\n\n if (!entry || typeof entry !== 'object') {\n continue\n }\n\n const scrollX = entry.scrollX\n const scrollY = entry.scrollY\n\n if (!Number.isFinite(scrollX) || !Number.isFinite(scrollY)) {\n continue\n }\n\n if (elementSelector === 'window') {\n window.scrollTo({\n top: scrollY,\n left: scrollX,\n behavior: options.behavior,\n })\n } else if (elementSelector) {\n let element\n\n try {\n element = document.querySelector(elementSelector)\n } catch {\n continue\n }\n\n if (element) {\n element.scrollLeft = scrollX\n element.scrollTop = scrollY\n }\n }\n }\n\n return\n }\n\n const hash = window.location.hash.split('#', 2)[1]\n\n if (hash) {\n const hashScrollIntoViewOptions =\n window.history.state?.__hashScrollIntoViewOptions ?? true\n\n if (hashScrollIntoViewOptions) {\n const el = document.getElementById(hash)\n if (el) {\n el.scrollIntoView(hashScrollIntoViewOptions)\n }\n }\n\n return\n }\n\n window.scrollTo({ top: 0, left: 0, behavior: options.behavior })\n}\n"],"mappings":";AAAA,IAAA,oCAAe"} | ||
| {"version":3,"file":"scroll-restoration-inline.js","names":[],"sources":["../../src/scroll-restoration-inline.ts?script-string"],"sourcesContent":["export default function (options: { storageKey: string; key?: string }) {\n let byKey\n\n try {\n byKey = JSON.parse(sessionStorage.getItem(options.storageKey) || '{}')\n } catch (error) {\n console.error(error)\n return\n }\n\n const resolvedKey = options.key || window.history.state?.__TSR_key\n const elementEntries = resolvedKey ? byKey[resolvedKey] : undefined\n let windowRestored = false\n\n if (elementEntries && typeof elementEntries === 'object') {\n for (const elementSelector in elementEntries) {\n const entry = elementEntries[elementSelector]\n\n if (!entry || typeof entry !== 'object') {\n continue\n }\n\n const scrollX = entry.scrollX\n const scrollY = entry.scrollY\n\n if (!Number.isFinite(scrollX) || !Number.isFinite(scrollY)) {\n continue\n }\n\n if (elementSelector === 'window') {\n window.scrollTo({\n top: scrollY,\n left: scrollX,\n })\n windowRestored = true\n } else if (elementSelector) {\n let element\n\n try {\n element = document.querySelector(elementSelector)\n } catch {\n continue\n }\n\n if (element) {\n element.scrollLeft = scrollX\n element.scrollTop = scrollY\n }\n }\n }\n }\n\n if (windowRestored) return\n\n const hash = window.location.hash.split('#', 2)[1]\n\n if (hash) {\n const hashScrollIntoViewOptions =\n window.history.state?.__hashScrollIntoViewOptions ?? true\n\n if (hashScrollIntoViewOptions) {\n const el = document.getElementById(hash)\n if (el) {\n el.scrollIntoView(hashScrollIntoViewOptions)\n }\n }\n\n return\n }\n\n window.scrollTo({ top: 0, left: 0 })\n}\n"],"mappings":";AAAA,IAAA,oCAAe"} |
@@ -5,8 +5,5 @@ import { escapeHtml } from "../utils.js"; | ||
| //#region src/scroll-restoration-script/server.ts | ||
| const defaultInlineScrollRestorationScript = `(${scroll_restoration_inline_default})(${escapeHtml(JSON.stringify({ | ||
| storageKey, | ||
| shouldScrollRestoration: true | ||
| }))})`; | ||
| const defaultInlineScrollRestorationScript = `(${scroll_restoration_inline_default})(${escapeHtml(JSON.stringify({ storageKey }))})`; | ||
| function getScrollRestorationScript(options) { | ||
| if (options.storageKey === "tsr-scroll-restoration-v1_3" && options.shouldScrollRestoration === true && options.key === void 0 && options.behavior === void 0) return defaultInlineScrollRestorationScript; | ||
| if (options.storageKey === "tsr-scroll-restoration-v1_3" && options.key === void 0) return defaultInlineScrollRestorationScript; | ||
| return `(${scroll_restoration_inline_default})(${escapeHtml(JSON.stringify(options))})`; | ||
@@ -23,3 +20,2 @@ } | ||
| storageKey, | ||
| shouldScrollRestoration: true, | ||
| key: userKey | ||
@@ -26,0 +22,0 @@ }); |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"server.js","names":[],"sources":["../../../src/scroll-restoration-script/server.ts"],"sourcesContent":["import minifiedScrollRestorationScript from '../scroll-restoration-inline?script-string'\nimport {\n defaultGetScrollRestorationKey,\n storageKey,\n} from '../scroll-restoration'\nimport { escapeHtml } from '../utils'\nimport type { AnyRouter } from '../router'\n\ntype InlineScrollRestorationScriptOptions = {\n storageKey: string\n key?: string\n behavior?: ScrollToOptions['behavior']\n shouldScrollRestoration?: boolean\n}\n\nconst defaultInlineScrollRestorationScript = `(${minifiedScrollRestorationScript})(${escapeHtml(\n JSON.stringify({\n storageKey,\n shouldScrollRestoration: true,\n } satisfies InlineScrollRestorationScriptOptions),\n)})`\n\nfunction getScrollRestorationScript(\n options: InlineScrollRestorationScriptOptions,\n) {\n if (\n options.storageKey === storageKey &&\n options.shouldScrollRestoration === true &&\n options.key === undefined &&\n options.behavior === undefined\n ) {\n return defaultInlineScrollRestorationScript\n }\n\n return `(${minifiedScrollRestorationScript})(${escapeHtml(JSON.stringify(options))})`\n}\n\nexport function getScrollRestorationScriptForRouter(router: AnyRouter) {\n if (\n typeof router.options.scrollRestoration === 'function' &&\n !router.options.scrollRestoration({ location: router.latestLocation })\n ) {\n return null\n }\n\n const getKey = router.options.getScrollRestorationKey\n if (!getKey) {\n return defaultInlineScrollRestorationScript\n }\n\n const location = router.latestLocation\n const userKey = getKey(location)\n const defaultKey = defaultGetScrollRestorationKey(location)\n\n if (userKey === defaultKey) {\n return defaultInlineScrollRestorationScript\n }\n\n return getScrollRestorationScript({\n storageKey,\n shouldScrollRestoration: true,\n key: userKey,\n })\n}\n"],"mappings":";;;;AAeA,MAAM,uCAAuC,IAAI,kCAAgC,IAAI,WACnF,KAAK,UAAU;CACb;CACA,yBAAyB;CAC1B,CAAgD,CAClD,CAAC;AAEF,SAAS,2BACP,SACA;AACA,KACE,QAAQ,eAAA,iCACR,QAAQ,4BAA4B,QACpC,QAAQ,QAAQ,KAAA,KAChB,QAAQ,aAAa,KAAA,EAErB,QAAO;AAGT,QAAO,IAAI,kCAAgC,IAAI,WAAW,KAAK,UAAU,QAAQ,CAAC,CAAC;;AAGrF,SAAgB,oCAAoC,QAAmB;AACrE,KACE,OAAO,OAAO,QAAQ,sBAAsB,cAC5C,CAAC,OAAO,QAAQ,kBAAkB,EAAE,UAAU,OAAO,gBAAgB,CAAC,CAEtE,QAAO;CAGT,MAAM,SAAS,OAAO,QAAQ;AAC9B,KAAI,CAAC,OACH,QAAO;CAGT,MAAM,WAAW,OAAO;CACxB,MAAM,UAAU,OAAO,SAAS;AAGhC,KAAI,YAFe,+BAA+B,SAAS,CAGzD,QAAO;AAGT,QAAO,2BAA2B;EAChC;EACA,yBAAyB;EACzB,KAAK;EACN,CAAC"} | ||
| {"version":3,"file":"server.js","names":[],"sources":["../../../src/scroll-restoration-script/server.ts"],"sourcesContent":["import minifiedScrollRestorationScript from '../scroll-restoration-inline?script-string'\nimport {\n defaultGetScrollRestorationKey,\n storageKey,\n} from '../scroll-restoration'\nimport { escapeHtml } from '../utils'\nimport type { AnyRouter } from '../router'\n\ntype InlineScrollRestorationScriptOptions = {\n storageKey: string\n key?: string\n}\n\nconst defaultInlineScrollRestorationScript = `(${minifiedScrollRestorationScript})(${escapeHtml(\n JSON.stringify({\n storageKey,\n } satisfies InlineScrollRestorationScriptOptions),\n)})`\n\nfunction getScrollRestorationScript(\n options: InlineScrollRestorationScriptOptions,\n) {\n if (options.storageKey === storageKey && options.key === undefined) {\n return defaultInlineScrollRestorationScript\n }\n\n return `(${minifiedScrollRestorationScript})(${escapeHtml(JSON.stringify(options))})`\n}\n\nexport function getScrollRestorationScriptForRouter(router: AnyRouter) {\n if (\n typeof router.options.scrollRestoration === 'function' &&\n !router.options.scrollRestoration({ location: router.latestLocation })\n ) {\n return null\n }\n\n const getKey = router.options.getScrollRestorationKey\n if (!getKey) {\n return defaultInlineScrollRestorationScript\n }\n\n const location = router.latestLocation\n const userKey = getKey(location)\n const defaultKey = defaultGetScrollRestorationKey(location)\n\n if (userKey === defaultKey) {\n return defaultInlineScrollRestorationScript\n }\n\n return getScrollRestorationScript({\n storageKey,\n key: userKey,\n })\n}\n"],"mappings":";;;;AAaA,MAAM,uCAAuC,IAAI,kCAAgC,IAAI,WACnF,KAAK,UAAU,EACb,YACD,CAAgD,CAClD,CAAC;AAEF,SAAS,2BACP,SACA;AACA,KAAI,QAAQ,eAAA,iCAA6B,QAAQ,QAAQ,KAAA,EACvD,QAAO;AAGT,QAAO,IAAI,kCAAgC,IAAI,WAAW,KAAK,UAAU,QAAQ,CAAC,CAAC;;AAGrF,SAAgB,oCAAoC,QAAmB;AACrE,KACE,OAAO,OAAO,QAAQ,sBAAsB,cAC5C,CAAC,OAAO,QAAQ,kBAAkB,EAAE,UAAU,OAAO,gBAAgB,CAAC,CAEtE,QAAO;CAGT,MAAM,SAAS,OAAO,QAAQ;AAC9B,KAAI,CAAC,OACH,QAAO;CAGT,MAAM,WAAW,OAAO;CACxB,MAAM,UAAU,OAAO,SAAS;AAGhC,KAAI,YAFe,+BAA+B,SAAS,CAGzD,QAAO;AAGT,QAAO,2BAA2B;EAChC;EACA,KAAK;EACN,CAAC"} |
| import { AnyRouter } from './router.js'; | ||
| import { ParsedLocation } from './location.js'; | ||
| import { NonNullableUpdater } from './utils.js'; | ||
| export type ScrollRestorationEntry = { | ||
@@ -8,9 +7,2 @@ scrollX: number; | ||
| }; | ||
| type ScrollRestorationByElement = Record<string, ScrollRestorationEntry>; | ||
| type ScrollRestorationByKey = Record<string, ScrollRestorationByElement>; | ||
| type ScrollRestorationCache = { | ||
| readonly state: ScrollRestorationByKey; | ||
| set: (updater: NonNullableUpdater<ScrollRestorationByKey>) => void; | ||
| persist: () => void; | ||
| }; | ||
| export type ScrollRestorationOptions = { | ||
@@ -21,3 +13,2 @@ getKey?: (location: ParsedLocation) => string; | ||
| export declare const storageKey = "tsr-scroll-restoration-v1_3"; | ||
| export declare const scrollRestorationCache: ScrollRestorationCache | null; | ||
| /** | ||
@@ -40,2 +31,1 @@ * The default `getKey` function for `useScrollRestoration`. | ||
| export declare function setupScrollRestoration(router: AnyRouter, force?: boolean): void; | ||
| export {}; |
+114
-107
@@ -1,2 +0,2 @@ | ||
| import { functionalUpdate, isPlainObject } from "./utils.js"; | ||
| import { locationHistoryActions } from "./router.js"; | ||
| import { isServer } from "@tanstack/router-core/isServer"; | ||
@@ -6,3 +6,3 @@ //#region src/scroll-restoration.ts | ||
| try { | ||
| return typeof window !== "undefined" && typeof window.sessionStorage === "object" ? window.sessionStorage : void 0; | ||
| return sessionStorage; | ||
| } catch { | ||
@@ -13,28 +13,19 @@ return; | ||
| const storageKey = "tsr-scroll-restoration-v1_3"; | ||
| const safeSessionStorage = getSafeSessionStorage(); | ||
| function createScrollRestorationCache() { | ||
| const safeSessionStorage = getSafeSessionStorage(); | ||
| if (!safeSessionStorage) return null; | ||
| let state = {}; | ||
| try { | ||
| const parsed = JSON.parse(safeSessionStorage.getItem("tsr-scroll-restoration-v1_3") || "{}"); | ||
| if (isPlainObject(parsed)) state = parsed; | ||
| } catch {} | ||
| const persist = () => { | ||
| try { | ||
| safeSessionStorage.setItem(storageKey, JSON.stringify(state)); | ||
| } catch { | ||
| if (process.env.NODE_ENV !== "production") console.warn("[ts-router] Could not persist scroll restoration state to sessionStorage."); | ||
| } | ||
| }; | ||
| return { | ||
| get state() { | ||
| return state; | ||
| }, | ||
| set: (updater) => { | ||
| state = functionalUpdate(updater, state) || state; | ||
| }, | ||
| persist | ||
| }; | ||
| return JSON.parse(safeSessionStorage?.getItem("tsr-scroll-restoration-v1_3") || "{}"); | ||
| } catch { | ||
| return {}; | ||
| } | ||
| } | ||
| const scrollRestorationCache = createScrollRestorationCache(); | ||
| function persistScrollRestorationCache() { | ||
| try { | ||
| safeSessionStorage?.setItem(storageKey, JSON.stringify(scrollRestorationCache)); | ||
| } catch { | ||
| if (process.env.NODE_ENV !== "production") console.warn("[ts-router] Could not persist scroll restoration state to sessionStorage."); | ||
| } | ||
| } | ||
| const scrollRestorationCache = /* @__PURE__ */ createScrollRestorationCache(); | ||
| const scrollRestorationIdAttribute = "data-scroll-restoration-id"; | ||
| /** | ||
@@ -49,26 +40,45 @@ * The default `getKey` function for `useScrollRestoration`. | ||
| }; | ||
| function getCssSelector(el) { | ||
| const path = []; | ||
| function getScrollRestorationSelector(element) { | ||
| const attrId = element.getAttribute(scrollRestorationIdAttribute); | ||
| if (attrId) return `[${scrollRestorationIdAttribute}="${attrId}"]`; | ||
| let selector = ""; | ||
| let el = element; | ||
| let parent; | ||
| while (parent = el.parentNode) { | ||
| path.push(`${el.tagName}:nth-child(${Array.prototype.indexOf.call(parent.children, el) + 1})`); | ||
| let index = 1; | ||
| let sibling = el; | ||
| while (sibling = sibling.previousElementSibling) index++; | ||
| const part = `${el.localName}:nth-child(${index})`; | ||
| selector = selector ? `${part} > ${selector}` : part; | ||
| el = parent; | ||
| } | ||
| return `${path.reverse().join(" > ")}`.toLowerCase(); | ||
| return selector; | ||
| } | ||
| function getElementScrollRestorationEntry(router, options) { | ||
| const restoreKey = (options.getKey || defaultGetScrollRestorationKey)(router.latestLocation); | ||
| if (options.id) return scrollRestorationCache?.state[restoreKey]?.[`[${scrollRestorationIdAttribute}="${options.id}"]`]; | ||
| const entries = scrollRestorationCache[(options.getKey || defaultGetScrollRestorationKey)(router.latestLocation)]; | ||
| if (!entries) return; | ||
| if (options.id) return entries[`[${scrollRestorationIdAttribute}="${options.id}"]`]; | ||
| const element = options.getElement?.(); | ||
| if (!element) return; | ||
| return scrollRestorationCache?.state[restoreKey]?.[element instanceof Window ? windowScrollTarget : getCssSelector(element)]; | ||
| return entries[element === window ? windowScrollTarget : getScrollRestorationSelector(element)]; | ||
| } | ||
| let ignoreScroll = false; | ||
| const windowScrollTarget = "window"; | ||
| const scrollRestorationIdAttribute = "data-scroll-restoration-id"; | ||
| function getElement(selector) { | ||
| try { | ||
| return typeof selector === "function" ? selector() : document.querySelector(selector); | ||
| } catch {} | ||
| } | ||
| function getScrollToTopElements(scrollToTopSelectors) { | ||
| const elements = []; | ||
| for (const selector of scrollToTopSelectors) { | ||
| if (selector === windowScrollTarget) continue; | ||
| const element = getElement(selector); | ||
| if (element) elements.push(element); | ||
| } | ||
| return elements; | ||
| } | ||
| function setupScrollRestoration(router, force) { | ||
| if (!scrollRestorationCache && !(isServer ?? router.isServer)) return; | ||
| const cache = scrollRestorationCache; | ||
| if (force ?? router.options.scrollRestoration ?? false) router.isScrollRestoring = true; | ||
| if ((isServer ?? router.isServer) || router.isScrollRestorationSetup || !cache) return; | ||
| if (force ?? router.options.scrollRestoration) router.isScrollRestoring = true; | ||
| if ((isServer ?? router.isServer) || router.isScrollRestorationSetup) return; | ||
| router.isScrollRestorationSetup = true; | ||
@@ -78,61 +88,76 @@ ignoreScroll = false; | ||
| const trackedScrollEntries = /* @__PURE__ */ new Map(); | ||
| window.history.scrollRestoration = "manual"; | ||
| const setTrackedScrollEntry = (target, scrollX, scrollY) => { | ||
| const entry = trackedScrollEntries.get(target) || {}; | ||
| entry.scrollX = scrollX; | ||
| entry.scrollY = scrollY; | ||
| trackedScrollEntries.set(target, entry); | ||
| }; | ||
| history.scrollRestoration = "manual"; | ||
| const onScroll = (event) => { | ||
| if (ignoreScroll || !router.isScrollRestoring) return; | ||
| if (event.target === document || event.target === window) trackedScrollEntries.set(windowScrollTarget, { | ||
| scrollX: window.scrollX || 0, | ||
| scrollY: window.scrollY || 0 | ||
| }); | ||
| if (event.target === document) setTrackedScrollEntry(windowScrollTarget, scrollX, scrollY); | ||
| else { | ||
| const target = event.target; | ||
| trackedScrollEntries.set(target, { | ||
| scrollX: target.scrollLeft || 0, | ||
| scrollY: target.scrollTop || 0 | ||
| }); | ||
| setTrackedScrollEntry(target, target.scrollLeft, target.scrollTop); | ||
| } | ||
| }; | ||
| const snapshotCurrentScrollTargets = (restoreKey) => { | ||
| if (!router.isScrollRestoring || !restoreKey || trackedScrollEntries.size === 0 || !cache) return; | ||
| const keyEntry = cache.state[restoreKey] ||= {}; | ||
| for (const [target, position] of trackedScrollEntries) { | ||
| let selector; | ||
| if (target === windowScrollTarget) selector = windowScrollTarget; | ||
| else if (target.isConnected) { | ||
| const attrId = target.getAttribute(scrollRestorationIdAttribute); | ||
| selector = attrId ? `[${scrollRestorationIdAttribute}="${attrId}"]` : getCssSelector(target); | ||
| } | ||
| if (!selector) continue; | ||
| keyEntry[selector] = position; | ||
| } | ||
| if (!router.isScrollRestoring) return; | ||
| const keyEntry = scrollRestorationCache[restoreKey] ||= {}; | ||
| for (const [target, position] of trackedScrollEntries) if (target === windowScrollTarget) keyEntry[windowScrollTarget] = position; | ||
| else if (target.isConnected) keyEntry[getScrollRestorationSelector(target)] = position; | ||
| }; | ||
| document.addEventListener("scroll", onScroll, true); | ||
| router.subscribe("onBeforeLoad", (event) => { | ||
| snapshotCurrentScrollTargets(event.fromLocation ? getKey(event.fromLocation) : void 0); | ||
| if (event.fromLocation) snapshotCurrentScrollTargets(getKey(event.fromLocation)); | ||
| trackedScrollEntries.clear(); | ||
| }); | ||
| window.addEventListener("pagehide", () => { | ||
| addEventListener("pagehide", () => { | ||
| snapshotCurrentScrollTargets(getKey(router.stores.resolvedLocation.get() ?? router.stores.location.get())); | ||
| cache.persist(); | ||
| persistScrollRestorationCache(); | ||
| }); | ||
| router.subscribe("onRendered", (event) => { | ||
| const cacheKey = getKey(event.toLocation); | ||
| const behavior = router.options.scrollRestorationBehavior; | ||
| const scrollToTopSelectors = router.options.scrollToTopSelectors; | ||
| const shouldResetScroll = router.resetNextScroll; | ||
| let scrollToTopElements; | ||
| trackedScrollEntries.clear(); | ||
| if (!router.resetNextScroll) { | ||
| router.resetNextScroll = true; | ||
| return; | ||
| if (!shouldResetScroll) router.resetNextScroll = true; | ||
| if (typeof router.options.scrollRestoration === "function" && !router.options.scrollRestoration({ location: router.latestLocation })) return; | ||
| const cacheKey = getKey(event.toLocation); | ||
| const fromCacheKey = event.fromLocation && getKey(event.fromLocation); | ||
| if (router.isScrollRestoring && fromCacheKey && fromCacheKey !== cacheKey) { | ||
| const fromElementEntries = scrollRestorationCache[fromCacheKey]; | ||
| if (fromElementEntries) { | ||
| let toElementEntries = scrollRestorationCache[cacheKey]; | ||
| for (const elementSelector in fromElementEntries) { | ||
| if (elementSelector === windowScrollTarget) { | ||
| if (shouldResetScroll) continue; | ||
| } else { | ||
| const element = getElement(elementSelector); | ||
| if (!element) continue; | ||
| if (shouldResetScroll && scrollToTopSelectors) { | ||
| scrollToTopElements ??= getScrollToTopElements(scrollToTopSelectors); | ||
| if (scrollToTopElements.includes(element)) continue; | ||
| } | ||
| } | ||
| if (!toElementEntries) toElementEntries = scrollRestorationCache[cacheKey] = {}; | ||
| toElementEntries[elementSelector] ??= fromElementEntries[elementSelector]; | ||
| } | ||
| } | ||
| } | ||
| if (typeof router.options.scrollRestoration === "function" && !router.options.scrollRestoration({ location: router.latestLocation })) return; | ||
| if (!shouldResetScroll) return; | ||
| ignoreScroll = true; | ||
| try { | ||
| const elementEntries = router.isScrollRestoring ? cache.state[cacheKey] : void 0; | ||
| let restored = false; | ||
| const hash = event.toLocation.hash; | ||
| const hashScrollIntoViewOptions = event.toLocation.state.__hashScrollIntoViewOptions ?? true; | ||
| const action = locationHistoryActions.get(event.toLocation); | ||
| const skipWindowRestore = hash && hashScrollIntoViewOptions && (action === "PUSH" || action === "REPLACE"); | ||
| const elementEntries = router.isScrollRestoring ? scrollRestorationCache[cacheKey] : void 0; | ||
| let windowRestored = false; | ||
| if (elementEntries) for (const elementSelector in elementEntries) { | ||
| const entry = elementEntries[elementSelector]; | ||
| if (!isPlainObject(entry)) continue; | ||
| const { scrollX, scrollY } = entry; | ||
| if (!Number.isFinite(scrollX) || !Number.isFinite(scrollY)) continue; | ||
| const { scrollX, scrollY } = elementEntries[elementSelector]; | ||
| if (elementSelector === windowScrollTarget) { | ||
| window.scrollTo({ | ||
| if (skipWindowRestore) continue; | ||
| scrollTo({ | ||
| top: scrollY, | ||
@@ -142,37 +167,23 @@ left: scrollX, | ||
| }); | ||
| restored = true; | ||
| } else if (elementSelector) { | ||
| let element; | ||
| try { | ||
| element = document.querySelector(elementSelector); | ||
| } catch { | ||
| continue; | ||
| } | ||
| windowRestored = true; | ||
| } else { | ||
| const element = getElement(elementSelector); | ||
| if (element) { | ||
| element.scrollLeft = scrollX; | ||
| element.scrollTop = scrollY; | ||
| restored = true; | ||
| } | ||
| } | ||
| } | ||
| if (!restored) { | ||
| const hash = router.history.location.hash.slice(1); | ||
| if (hash) { | ||
| const hashScrollIntoViewOptions = window.history.state?.__hashScrollIntoViewOptions ?? true; | ||
| if (hashScrollIntoViewOptions) { | ||
| const el = document.getElementById(hash); | ||
| if (el) el.scrollIntoView(hashScrollIntoViewOptions); | ||
| } | ||
| } else { | ||
| const scrollOptions = { | ||
| top: 0, | ||
| left: 0, | ||
| behavior | ||
| }; | ||
| window.scrollTo(scrollOptions); | ||
| if (scrollToTopSelectors) for (const selector of scrollToTopSelectors) { | ||
| if (selector === windowScrollTarget) continue; | ||
| const element = typeof selector === "function" ? selector() : document.querySelector(selector); | ||
| if (element) element.scrollTo(scrollOptions); | ||
| } | ||
| if (!windowRestored) if (hash) { | ||
| if (hashScrollIntoViewOptions) document.getElementById(hash)?.scrollIntoView(hashScrollIntoViewOptions); | ||
| } else { | ||
| const scrollOptions = { | ||
| top: 0, | ||
| left: 0, | ||
| behavior | ||
| }; | ||
| scrollTo(scrollOptions); | ||
| if (scrollToTopSelectors) { | ||
| scrollToTopElements ??= getScrollToTopElements(scrollToTopSelectors); | ||
| for (const element of scrollToTopElements) element.scrollTo(scrollOptions); | ||
| } | ||
@@ -183,11 +194,7 @@ } | ||
| } | ||
| if (router.isScrollRestoring) cache.set((state) => { | ||
| state[cacheKey] ||= {}; | ||
| return state; | ||
| }); | ||
| }); | ||
| } | ||
| //#endregion | ||
| export { defaultGetScrollRestorationKey, getElementScrollRestorationEntry, scrollRestorationCache, setupScrollRestoration, storageKey }; | ||
| export { defaultGetScrollRestorationKey, getElementScrollRestorationEntry, setupScrollRestoration, storageKey }; | ||
| //# sourceMappingURL=scroll-restoration.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"scroll-restoration.js","names":[],"sources":["../../src/scroll-restoration.ts"],"sourcesContent":["import { isServer } from '@tanstack/router-core/isServer'\nimport { functionalUpdate, isPlainObject } from './utils'\nimport type { AnyRouter } from './router'\nimport type { ParsedLocation } from './location'\nimport type { NonNullableUpdater } from './utils'\n\nexport type ScrollRestorationEntry = { scrollX: number; scrollY: number }\n\ntype ScrollRestorationByElement = Record<string, ScrollRestorationEntry>\n\ntype ScrollRestorationByKey = Record<string, ScrollRestorationByElement>\n\ntype ScrollRestorationCache = {\n readonly state: ScrollRestorationByKey\n set: (updater: NonNullableUpdater<ScrollRestorationByKey>) => void\n persist: () => void\n}\n\nexport type ScrollRestorationOptions = {\n getKey?: (location: ParsedLocation) => string\n scrollBehavior?: ScrollToOptions['behavior']\n}\n\nfunction getSafeSessionStorage() {\n try {\n return typeof window !== 'undefined' &&\n typeof window.sessionStorage === 'object'\n ? window.sessionStorage\n : undefined\n } catch {\n // silent\n return undefined\n }\n}\n\n// SessionStorage key used to store scroll positions across navigations.\nexport const storageKey = 'tsr-scroll-restoration-v1_3'\n\nfunction createScrollRestorationCache(): ScrollRestorationCache | null {\n const safeSessionStorage = getSafeSessionStorage()\n if (!safeSessionStorage) {\n return null\n }\n\n let state: ScrollRestorationByKey = {}\n\n try {\n const parsed = JSON.parse(safeSessionStorage.getItem(storageKey) || '{}')\n if (isPlainObject(parsed)) {\n state = parsed as ScrollRestorationByKey\n }\n } catch {\n // ignore invalid session storage payloads\n }\n\n const persist = () => {\n try {\n safeSessionStorage.setItem(storageKey, JSON.stringify(state))\n } catch {\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n '[ts-router] Could not persist scroll restoration state to sessionStorage.',\n )\n }\n }\n }\n\n return {\n get state() {\n return state\n },\n set: (updater) => {\n state = functionalUpdate(updater, state) || state\n },\n persist,\n }\n}\n\nexport const scrollRestorationCache = createScrollRestorationCache()\n\n/**\n * The default `getKey` function for `useScrollRestoration`.\n * It returns the `key` from the location state or the `href` of the location.\n *\n * The `location.href` is used as a fallback to support the use case where the location state is not available like the initial render.\n */\nexport const defaultGetScrollRestorationKey = (location: ParsedLocation) => {\n return location.state.__TSR_key! || location.href\n}\n\nfunction getCssSelector(el: any): string {\n const path = []\n let parent: HTMLElement\n while ((parent = el.parentNode)) {\n path.push(\n `${el.tagName}:nth-child(${Array.prototype.indexOf.call(parent.children, el) + 1})`,\n )\n el = parent\n }\n return `${path.reverse().join(' > ')}`.toLowerCase()\n}\n\nexport function getElementScrollRestorationEntry(\n router: AnyRouter,\n options: (\n | {\n id: string\n getElement?: () => Window | Element | undefined | null\n }\n | {\n id?: string\n getElement: () => Window | Element | undefined | null\n }\n ) & {\n getKey?: (location: ParsedLocation) => string\n },\n): ScrollRestorationEntry | undefined {\n const getKey = options.getKey || defaultGetScrollRestorationKey\n const restoreKey = getKey(router.latestLocation)\n\n if (options.id) {\n return scrollRestorationCache?.state[restoreKey]?.[\n `[${scrollRestorationIdAttribute}=\"${options.id}\"]`\n ]\n }\n\n const element = options.getElement?.()\n if (!element) {\n return\n }\n\n return scrollRestorationCache?.state[restoreKey]?.[\n element instanceof Window ? windowScrollTarget : getCssSelector(element)\n ]\n}\n\nlet ignoreScroll = false\nconst windowScrollTarget = 'window'\nconst scrollRestorationIdAttribute = 'data-scroll-restoration-id'\ntype ScrollTarget = typeof windowScrollTarget | Element\n\nexport function setupScrollRestoration(router: AnyRouter, force?: boolean) {\n if (!scrollRestorationCache && !(isServer ?? router.isServer)) {\n return\n }\n\n const cache = scrollRestorationCache\n\n const shouldScrollRestoration =\n force ?? router.options.scrollRestoration ?? false\n\n if (shouldScrollRestoration) {\n router.isScrollRestoring = true\n }\n\n if (\n (isServer ?? router.isServer) ||\n router.isScrollRestorationSetup ||\n !cache\n ) {\n return\n }\n\n router.isScrollRestorationSetup = true\n ignoreScroll = false\n\n const getKey =\n router.options.getScrollRestorationKey || defaultGetScrollRestorationKey\n const trackedScrollEntries = new Map<ScrollTarget, ScrollRestorationEntry>()\n\n window.history.scrollRestoration = 'manual'\n\n const onScroll = (event: Event) => {\n if (ignoreScroll || !router.isScrollRestoring) {\n return\n }\n\n if (event.target === document || event.target === window) {\n trackedScrollEntries.set(windowScrollTarget, {\n scrollX: window.scrollX || 0,\n scrollY: window.scrollY || 0,\n })\n } else {\n const target = event.target as Element\n trackedScrollEntries.set(target, {\n scrollX: target.scrollLeft || 0,\n scrollY: target.scrollTop || 0,\n })\n }\n }\n\n // Snapshot the current page's tracked scroll targets before navigation or unload.\n const snapshotCurrentScrollTargets = (restoreKey?: string) => {\n if (\n !router.isScrollRestoring ||\n !restoreKey ||\n trackedScrollEntries.size === 0 ||\n !cache\n ) {\n return\n }\n\n const keyEntry = (cache.state[restoreKey] ||=\n {} as ScrollRestorationByElement)\n\n for (const [target, position] of trackedScrollEntries) {\n let selector: string | undefined\n\n if (target === windowScrollTarget) {\n selector = windowScrollTarget\n } else if (target.isConnected) {\n const attrId = target.getAttribute(scrollRestorationIdAttribute)\n selector = attrId\n ? `[${scrollRestorationIdAttribute}=\"${attrId}\"]`\n : getCssSelector(target)\n }\n\n if (!selector) {\n continue\n }\n\n keyEntry[selector] = position\n }\n }\n\n document.addEventListener('scroll', onScroll, true)\n router.subscribe('onBeforeLoad', (event) => {\n snapshotCurrentScrollTargets(\n event.fromLocation ? getKey(event.fromLocation) : undefined,\n )\n trackedScrollEntries.clear()\n })\n window.addEventListener('pagehide', () => {\n snapshotCurrentScrollTargets(\n getKey(\n router.stores.resolvedLocation.get() ?? router.stores.location.get(),\n ),\n )\n cache.persist()\n })\n\n // Restore destination scroll after the new route has rendered.\n router.subscribe('onRendered', (event) => {\n const cacheKey = getKey(event.toLocation)\n const behavior = router.options.scrollRestorationBehavior\n const scrollToTopSelectors = router.options.scrollToTopSelectors\n trackedScrollEntries.clear()\n\n if (!router.resetNextScroll) {\n router.resetNextScroll = true\n return\n }\n\n if (\n typeof router.options.scrollRestoration === 'function' &&\n !router.options.scrollRestoration({ location: router.latestLocation })\n ) {\n return\n }\n\n ignoreScroll = true\n\n try {\n const elementEntries = router.isScrollRestoring\n ? cache.state[cacheKey]\n : undefined\n let restored = false\n\n if (elementEntries) {\n for (const elementSelector in elementEntries) {\n const entry = elementEntries[elementSelector]\n\n if (!isPlainObject(entry)) {\n continue\n }\n\n const { scrollX, scrollY } = entry as {\n scrollX?: unknown\n scrollY?: unknown\n }\n\n if (!Number.isFinite(scrollX) || !Number.isFinite(scrollY)) {\n continue\n }\n\n if (elementSelector === windowScrollTarget) {\n window.scrollTo({\n top: scrollY as number,\n left: scrollX as number,\n behavior,\n })\n restored = true\n } else if (elementSelector) {\n let element\n\n try {\n element = document.querySelector(elementSelector)\n } catch {\n continue\n }\n\n if (element) {\n element.scrollLeft = scrollX as number\n element.scrollTop = scrollY as number\n restored = true\n }\n }\n }\n }\n\n if (!restored) {\n const hash = router.history.location.hash.slice(1)\n\n if (hash) {\n const hashScrollIntoViewOptions =\n window.history.state?.__hashScrollIntoViewOptions ?? true\n\n if (hashScrollIntoViewOptions) {\n const el = document.getElementById(hash)\n if (el) {\n el.scrollIntoView(hashScrollIntoViewOptions)\n }\n }\n } else {\n const scrollOptions = {\n top: 0,\n left: 0,\n behavior,\n }\n\n window.scrollTo(scrollOptions)\n if (scrollToTopSelectors) {\n for (const selector of scrollToTopSelectors) {\n if (selector === windowScrollTarget) continue\n const element =\n typeof selector === 'function'\n ? selector()\n : document.querySelector(selector)\n if (element) {\n element.scrollTo(scrollOptions)\n }\n }\n }\n }\n }\n } finally {\n ignoreScroll = false\n }\n\n if (router.isScrollRestoring) {\n cache.set((state) => {\n state[cacheKey] ||= {} as ScrollRestorationByElement\n return state\n })\n }\n })\n}\n"],"mappings":"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"} | ||
| {"version":3,"file":"scroll-restoration.js","names":[],"sources":["../../src/scroll-restoration.ts"],"sourcesContent":["import { isServer } from '@tanstack/router-core/isServer'\nimport { locationHistoryActions } from './router'\nimport type { AnyRouter } from './router'\nimport type { ParsedLocation } from './location'\n\nexport type ScrollRestorationEntry = { scrollX: number; scrollY: number }\n\ntype ScrollRestorationByElement = Record<string, ScrollRestorationEntry>\n\ntype ScrollRestorationByKey = Record<string, ScrollRestorationByElement>\n\nexport type ScrollRestorationOptions = {\n getKey?: (location: ParsedLocation) => string\n scrollBehavior?: ScrollToOptions['behavior']\n}\n\nfunction getSafeSessionStorage() {\n try {\n // Accessing sessionStorage itself can throw SecurityError in locked-down\n // contexts, e.g. sandboxed/opaque origins or blocked storage policies.\n return sessionStorage\n } catch {\n return\n }\n}\n\n// SessionStorage key used to store scroll positions across navigations.\nexport const storageKey = 'tsr-scroll-restoration-v1_3'\nconst safeSessionStorage = getSafeSessionStorage()\n\nfunction createScrollRestorationCache() {\n try {\n return JSON.parse(\n safeSessionStorage?.getItem(storageKey) || '{}',\n ) as ScrollRestorationByKey\n } catch {\n // ignore invalid session storage payloads\n return {}\n }\n}\n\nfunction persistScrollRestorationCache() {\n try {\n safeSessionStorage?.setItem(\n storageKey,\n JSON.stringify(scrollRestorationCache),\n )\n } catch {\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n '[ts-router] Could not persist scroll restoration state to sessionStorage.',\n )\n }\n }\n}\n\nconst scrollRestorationCache = /* @__PURE__ */ createScrollRestorationCache()\nconst scrollRestorationIdAttribute = 'data-scroll-restoration-id'\n\n/**\n * The default `getKey` function for `useScrollRestoration`.\n * It returns the `key` from the location state or the `href` of the location.\n *\n * The `location.href` is used as a fallback to support the use case where the location state is not available like the initial render.\n */\nexport const defaultGetScrollRestorationKey = (location: ParsedLocation) => {\n return location.state.__TSR_key! || location.href\n}\n\nfunction getScrollRestorationSelector(element: Element): string {\n const attrId = element.getAttribute(scrollRestorationIdAttribute)\n if (attrId) {\n return `[${scrollRestorationIdAttribute}=\"${attrId}\"]`\n }\n\n let selector = ''\n let el: any = element\n let parent: HTMLElement\n\n while ((parent = el.parentNode)) {\n let index = 1\n let sibling = el\n while ((sibling = sibling.previousElementSibling)) {\n index++\n }\n\n const part = `${el.localName}:nth-child(${index})`\n selector = selector ? `${part} > ${selector}` : part\n el = parent\n }\n\n return selector\n}\n\nexport function getElementScrollRestorationEntry(\n router: AnyRouter,\n options: (\n | {\n id: string\n getElement?: () => Window | Element | undefined | null\n }\n | {\n id?: string\n getElement: () => Window | Element | undefined | null\n }\n ) & {\n getKey?: (location: ParsedLocation) => string\n },\n): ScrollRestorationEntry | undefined {\n const getKey = options.getKey || defaultGetScrollRestorationKey\n const restoreKey = getKey(router.latestLocation)\n const entries = scrollRestorationCache[restoreKey]\n\n if (!entries) {\n return\n }\n\n if (options.id) {\n return entries[`[${scrollRestorationIdAttribute}=\"${options.id}\"]`]\n }\n\n const element = options.getElement?.()\n if (!element) {\n return\n }\n\n return entries[\n element === window\n ? windowScrollTarget\n : getScrollRestorationSelector(element as Element)\n ]\n}\n\nlet ignoreScroll = false\nconst windowScrollTarget = 'window'\ntype ScrollTarget = typeof windowScrollTarget | Element\n\nfunction getElement(selector: string | (() => Element | null | undefined)) {\n try {\n return typeof selector === 'function'\n ? selector()\n : document.querySelector(selector)\n } catch {}\n return\n}\n\nfunction getScrollToTopElements(\n scrollToTopSelectors: NonNullable<\n AnyRouter['options']['scrollToTopSelectors']\n >,\n): Array<Element> {\n const elements: Array<Element> = []\n\n for (const selector of scrollToTopSelectors) {\n if (selector === windowScrollTarget) {\n continue\n }\n\n const element = getElement(selector)\n if (element) {\n elements.push(element)\n }\n }\n\n return elements\n}\n\nexport function setupScrollRestoration(router: AnyRouter, force?: boolean) {\n // Keep hash/top scrolling active even when sessionStorage is unavailable.\n\n if (force ?? router.options.scrollRestoration) {\n router.isScrollRestoring = true\n }\n\n if ((isServer ?? router.isServer) || router.isScrollRestorationSetup) {\n return\n }\n\n router.isScrollRestorationSetup = true\n ignoreScroll = false\n\n const getKey =\n router.options.getScrollRestorationKey || defaultGetScrollRestorationKey\n const trackedScrollEntries = new Map<ScrollTarget, ScrollRestorationEntry>()\n const setTrackedScrollEntry = (\n target: ScrollTarget,\n scrollX: number,\n scrollY: number,\n ) => {\n const entry =\n trackedScrollEntries.get(target) || ({} as ScrollRestorationEntry)\n entry.scrollX = scrollX\n entry.scrollY = scrollY\n trackedScrollEntries.set(target, entry)\n }\n\n history.scrollRestoration = 'manual'\n\n const onScroll = (event: Event) => {\n if (ignoreScroll || !router.isScrollRestoring) {\n return\n }\n\n if (event.target === document) {\n setTrackedScrollEntry(windowScrollTarget, scrollX, scrollY)\n } else {\n const target = event.target as Element\n setTrackedScrollEntry(target, target.scrollLeft, target.scrollTop)\n }\n }\n\n // Snapshot the current page's tracked scroll targets before navigation or unload.\n const snapshotCurrentScrollTargets = (restoreKey: string) => {\n if (!router.isScrollRestoring) {\n return\n }\n\n const keyEntry = (scrollRestorationCache[restoreKey] ||=\n {} as ScrollRestorationByElement)\n\n for (const [target, position] of trackedScrollEntries) {\n if (target === windowScrollTarget) {\n keyEntry[windowScrollTarget] = position\n } else if (target.isConnected) {\n keyEntry[getScrollRestorationSelector(target)] = position\n }\n }\n }\n\n document.addEventListener('scroll', onScroll, true)\n router.subscribe('onBeforeLoad', (event) => {\n if (event.fromLocation) {\n snapshotCurrentScrollTargets(getKey(event.fromLocation))\n }\n trackedScrollEntries.clear()\n })\n addEventListener('pagehide', () => {\n snapshotCurrentScrollTargets(\n getKey(\n router.stores.resolvedLocation.get() ?? router.stores.location.get(),\n ),\n )\n persistScrollRestorationCache()\n })\n\n // Restore destination scroll after the new route has rendered.\n router.subscribe('onRendered', (event) => {\n const behavior = router.options.scrollRestorationBehavior\n const scrollToTopSelectors = router.options.scrollToTopSelectors\n const shouldResetScroll = router.resetNextScroll\n let scrollToTopElements: Array<Element> | undefined\n trackedScrollEntries.clear()\n\n if (!shouldResetScroll) {\n router.resetNextScroll = true\n }\n\n if (\n typeof router.options.scrollRestoration === 'function' &&\n !router.options.scrollRestoration({ location: router.latestLocation })\n ) {\n return\n }\n\n const cacheKey = getKey(event.toLocation)\n const fromCacheKey = event.fromLocation && getKey(event.fromLocation)\n\n if (router.isScrollRestoring && fromCacheKey && fromCacheKey !== cacheKey) {\n const fromElementEntries = scrollRestorationCache[fromCacheKey]\n\n if (fromElementEntries) {\n let toElementEntries = scrollRestorationCache[cacheKey]\n\n for (const elementSelector in fromElementEntries) {\n if (elementSelector === windowScrollTarget) {\n if (shouldResetScroll) {\n continue\n }\n } else {\n const element = getElement(elementSelector)\n if (!element) {\n continue\n }\n\n if (shouldResetScroll && scrollToTopSelectors) {\n scrollToTopElements ??=\n getScrollToTopElements(scrollToTopSelectors)\n if (scrollToTopElements.includes(element)) {\n continue\n }\n }\n }\n\n if (!toElementEntries) {\n toElementEntries = scrollRestorationCache[cacheKey] =\n {} as ScrollRestorationByElement\n }\n\n toElementEntries[elementSelector] ??=\n fromElementEntries[elementSelector]!\n }\n }\n }\n\n if (!shouldResetScroll) {\n return\n }\n\n ignoreScroll = true\n\n try {\n const hash = event.toLocation.hash\n const hashScrollIntoViewOptions =\n event.toLocation.state.__hashScrollIntoViewOptions ?? true\n const action = locationHistoryActions.get(event.toLocation)\n const skipWindowRestore =\n hash &&\n hashScrollIntoViewOptions &&\n (action === 'PUSH' || action === 'REPLACE')\n\n const elementEntries = router.isScrollRestoring\n ? scrollRestorationCache[cacheKey]\n : undefined\n let windowRestored = false\n\n if (elementEntries) {\n for (const elementSelector in elementEntries) {\n const { scrollX, scrollY } = elementEntries[elementSelector]!\n\n if (elementSelector === windowScrollTarget) {\n if (skipWindowRestore) {\n continue\n }\n\n scrollTo({\n top: scrollY,\n left: scrollX,\n behavior,\n })\n windowRestored = true\n } else {\n const element = getElement(elementSelector)\n if (element) {\n element.scrollLeft = scrollX\n element.scrollTop = scrollY\n }\n }\n }\n }\n\n if (!windowRestored) {\n if (hash) {\n if (hashScrollIntoViewOptions) {\n document\n .getElementById(hash)\n ?.scrollIntoView(hashScrollIntoViewOptions)\n }\n } else {\n const scrollOptions = {\n top: 0,\n left: 0,\n behavior,\n }\n\n scrollTo(scrollOptions)\n if (scrollToTopSelectors) {\n scrollToTopElements ??= getScrollToTopElements(scrollToTopSelectors)\n for (const element of scrollToTopElements) {\n element.scrollTo(scrollOptions)\n }\n }\n }\n }\n } finally {\n ignoreScroll = false\n }\n })\n}\n"],"mappings":"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"} |
+1
-1
| { | ||
| "name": "@tanstack/router-core", | ||
| "version": "1.171.3", | ||
| "version": "1.171.4", | ||
| "description": "Modern and scalable routing for React applications", | ||
@@ -5,0 +5,0 @@ "author": "Tanner Linsley", |
+0
-3
@@ -410,8 +410,5 @@ export * from './global' | ||
| storageKey, | ||
| scrollRestorationCache, | ||
| setupScrollRestoration, | ||
| } from './scroll-restoration' | ||
| export { handleHashScroll } from './hash-scroll' | ||
| export type { | ||
@@ -418,0 +415,0 @@ ScrollRestorationOptions, |
+1
-8
@@ -5,3 +5,2 @@ import { joinPaths, trimPath } from './path' | ||
| /** Compose multiple rewrite pairs into a single in/out rewrite. */ | ||
| /** Compose multiple rewrite pairs into a single in/out rewrite. */ | ||
| export function composeRewrites(rewrites: Array<LocationRewrite>) { | ||
@@ -25,3 +24,2 @@ return { | ||
| /** Create a rewrite pair that strips/adds a basepath on input/output. */ | ||
| /** Create a rewrite pair that strips/adds a basepath on input/output. */ | ||
| export function rewriteBasepath(opts: { | ||
@@ -33,9 +31,6 @@ basepath: string | ||
| const normalizedBasepath = `/${trimmedBasepath}` | ||
| const normalizedBasepathWithSlash = `${normalizedBasepath}/` | ||
| const checkBasepath = opts.caseSensitive | ||
| ? normalizedBasepath | ||
| : normalizedBasepath.toLowerCase() | ||
| const checkBasepathWithSlash = opts.caseSensitive | ||
| ? normalizedBasepathWithSlash | ||
| : normalizedBasepathWithSlash.toLowerCase() | ||
| const checkBasepathWithSlash = `${checkBasepath}/` | ||
@@ -65,3 +60,2 @@ return { | ||
| /** Execute a location input rewrite if provided. */ | ||
| /** Execute a location input rewrite if provided. */ | ||
| export function executeRewriteInput( | ||
@@ -83,3 +77,2 @@ rewrite: LocationRewrite | undefined, | ||
| /** Execute a location output rewrite if provided. */ | ||
| /** Execute a location output rewrite if provided. */ | ||
| export function executeRewriteOutput( | ||
@@ -86,0 +79,0 @@ rewrite: LocationRewrite | undefined, |
@@ -1,7 +0,2 @@ | ||
| export default function (options: { | ||
| storageKey: string | ||
| key?: string | ||
| behavior?: ScrollToOptions['behavior'] | ||
| shouldScrollRestoration?: boolean | ||
| }) { | ||
| export default function (options: { storageKey: string; key?: string }) { | ||
| let byKey | ||
@@ -18,9 +13,5 @@ | ||
| const elementEntries = resolvedKey ? byKey[resolvedKey] : undefined | ||
| let windowRestored = false | ||
| if ( | ||
| options.shouldScrollRestoration && | ||
| elementEntries && | ||
| typeof elementEntries === 'object' && | ||
| Object.keys(elementEntries).length > 0 | ||
| ) { | ||
| if (elementEntries && typeof elementEntries === 'object') { | ||
| for (const elementSelector in elementEntries) { | ||
@@ -44,4 +35,4 @@ const entry = elementEntries[elementSelector] | ||
| left: scrollX, | ||
| behavior: options.behavior, | ||
| }) | ||
| windowRestored = true | ||
| } else if (elementSelector) { | ||
@@ -62,6 +53,6 @@ let element | ||
| } | ||
| return | ||
| } | ||
| if (windowRestored) return | ||
| const hash = window.location.hash.split('#', 2)[1] | ||
@@ -83,3 +74,3 @@ | ||
| window.scrollTo({ top: 0, left: 0, behavior: options.behavior }) | ||
| window.scrollTo({ top: 0, left: 0 }) | ||
| } |
@@ -12,4 +12,2 @@ import minifiedScrollRestorationScript from '../scroll-restoration-inline?script-string' | ||
| key?: string | ||
| behavior?: ScrollToOptions['behavior'] | ||
| shouldScrollRestoration?: boolean | ||
| } | ||
@@ -20,3 +18,2 @@ | ||
| storageKey, | ||
| shouldScrollRestoration: true, | ||
| } satisfies InlineScrollRestorationScriptOptions), | ||
@@ -28,8 +25,3 @@ )})` | ||
| ) { | ||
| if ( | ||
| options.storageKey === storageKey && | ||
| options.shouldScrollRestoration === true && | ||
| options.key === undefined && | ||
| options.behavior === undefined | ||
| ) { | ||
| if (options.storageKey === storageKey && options.key === undefined) { | ||
| return defaultInlineScrollRestorationScript | ||
@@ -64,5 +56,4 @@ } | ||
| storageKey, | ||
| shouldScrollRestoration: true, | ||
| key: userKey, | ||
| }) | ||
| } |
+188
-167
| import { isServer } from '@tanstack/router-core/isServer' | ||
| import { functionalUpdate, isPlainObject } from './utils' | ||
| import { locationHistoryActions } from './router' | ||
| import type { AnyRouter } from './router' | ||
| import type { ParsedLocation } from './location' | ||
| import type { NonNullableUpdater } from './utils' | ||
@@ -13,8 +12,2 @@ export type ScrollRestorationEntry = { scrollX: number; scrollY: number } | ||
| type ScrollRestorationCache = { | ||
| readonly state: ScrollRestorationByKey | ||
| set: (updater: NonNullableUpdater<ScrollRestorationByKey>) => void | ||
| persist: () => void | ||
| } | ||
| export type ScrollRestorationOptions = { | ||
@@ -27,9 +20,7 @@ getKey?: (location: ParsedLocation) => string | ||
| try { | ||
| return typeof window !== 'undefined' && | ||
| typeof window.sessionStorage === 'object' | ||
| ? window.sessionStorage | ||
| : undefined | ||
| // Accessing sessionStorage itself can throw SecurityError in locked-down | ||
| // contexts, e.g. sandboxed/opaque origins or blocked storage policies. | ||
| return sessionStorage | ||
| } catch { | ||
| // silent | ||
| return undefined | ||
| return | ||
| } | ||
@@ -40,44 +31,32 @@ } | ||
| export const storageKey = 'tsr-scroll-restoration-v1_3' | ||
| const safeSessionStorage = getSafeSessionStorage() | ||
| function createScrollRestorationCache(): ScrollRestorationCache | null { | ||
| const safeSessionStorage = getSafeSessionStorage() | ||
| if (!safeSessionStorage) { | ||
| return null | ||
| } | ||
| let state: ScrollRestorationByKey = {} | ||
| function createScrollRestorationCache() { | ||
| try { | ||
| const parsed = JSON.parse(safeSessionStorage.getItem(storageKey) || '{}') | ||
| if (isPlainObject(parsed)) { | ||
| state = parsed as ScrollRestorationByKey | ||
| } | ||
| return JSON.parse( | ||
| safeSessionStorage?.getItem(storageKey) || '{}', | ||
| ) as ScrollRestorationByKey | ||
| } catch { | ||
| // ignore invalid session storage payloads | ||
| return {} | ||
| } | ||
| } | ||
| const persist = () => { | ||
| try { | ||
| safeSessionStorage.setItem(storageKey, JSON.stringify(state)) | ||
| } catch { | ||
| if (process.env.NODE_ENV !== 'production') { | ||
| console.warn( | ||
| '[ts-router] Could not persist scroll restoration state to sessionStorage.', | ||
| ) | ||
| } | ||
| function persistScrollRestorationCache() { | ||
| try { | ||
| safeSessionStorage?.setItem( | ||
| storageKey, | ||
| JSON.stringify(scrollRestorationCache), | ||
| ) | ||
| } catch { | ||
| if (process.env.NODE_ENV !== 'production') { | ||
| console.warn( | ||
| '[ts-router] Could not persist scroll restoration state to sessionStorage.', | ||
| ) | ||
| } | ||
| } | ||
| return { | ||
| get state() { | ||
| return state | ||
| }, | ||
| set: (updater) => { | ||
| state = functionalUpdate(updater, state) || state | ||
| }, | ||
| persist, | ||
| } | ||
| } | ||
| export const scrollRestorationCache = createScrollRestorationCache() | ||
| const scrollRestorationCache = /* @__PURE__ */ createScrollRestorationCache() | ||
| const scrollRestorationIdAttribute = 'data-scroll-restoration-id' | ||
@@ -94,12 +73,25 @@ /** | ||
| function getCssSelector(el: any): string { | ||
| const path = [] | ||
| function getScrollRestorationSelector(element: Element): string { | ||
| const attrId = element.getAttribute(scrollRestorationIdAttribute) | ||
| if (attrId) { | ||
| return `[${scrollRestorationIdAttribute}="${attrId}"]` | ||
| } | ||
| let selector = '' | ||
| let el: any = element | ||
| let parent: HTMLElement | ||
| while ((parent = el.parentNode)) { | ||
| path.push( | ||
| `${el.tagName}:nth-child(${Array.prototype.indexOf.call(parent.children, el) + 1})`, | ||
| ) | ||
| let index = 1 | ||
| let sibling = el | ||
| while ((sibling = sibling.previousElementSibling)) { | ||
| index++ | ||
| } | ||
| const part = `${el.localName}:nth-child(${index})` | ||
| selector = selector ? `${part} > ${selector}` : part | ||
| el = parent | ||
| } | ||
| return `${path.reverse().join(' > ')}`.toLowerCase() | ||
| return selector | ||
| } | ||
@@ -124,7 +116,10 @@ | ||
| const restoreKey = getKey(router.latestLocation) | ||
| const entries = scrollRestorationCache[restoreKey] | ||
| if (!entries) { | ||
| return | ||
| } | ||
| if (options.id) { | ||
| return scrollRestorationCache?.state[restoreKey]?.[ | ||
| `[${scrollRestorationIdAttribute}="${options.id}"]` | ||
| ] | ||
| return entries[`[${scrollRestorationIdAttribute}="${options.id}"]`] | ||
| } | ||
@@ -137,4 +132,6 @@ | ||
| return scrollRestorationCache?.state[restoreKey]?.[ | ||
| element instanceof Window ? windowScrollTarget : getCssSelector(element) | ||
| return entries[ | ||
| element === window | ||
| ? windowScrollTarget | ||
| : getScrollRestorationSelector(element as Element) | ||
| ] | ||
@@ -145,24 +142,42 @@ } | ||
| const windowScrollTarget = 'window' | ||
| const scrollRestorationIdAttribute = 'data-scroll-restoration-id' | ||
| type ScrollTarget = typeof windowScrollTarget | Element | ||
| export function setupScrollRestoration(router: AnyRouter, force?: boolean) { | ||
| if (!scrollRestorationCache && !(isServer ?? router.isServer)) { | ||
| return | ||
| function getElement(selector: string | (() => Element | null | undefined)) { | ||
| try { | ||
| return typeof selector === 'function' | ||
| ? selector() | ||
| : document.querySelector(selector) | ||
| } catch {} | ||
| return | ||
| } | ||
| function getScrollToTopElements( | ||
| scrollToTopSelectors: NonNullable< | ||
| AnyRouter['options']['scrollToTopSelectors'] | ||
| >, | ||
| ): Array<Element> { | ||
| const elements: Array<Element> = [] | ||
| for (const selector of scrollToTopSelectors) { | ||
| if (selector === windowScrollTarget) { | ||
| continue | ||
| } | ||
| const element = getElement(selector) | ||
| if (element) { | ||
| elements.push(element) | ||
| } | ||
| } | ||
| const cache = scrollRestorationCache | ||
| return elements | ||
| } | ||
| const shouldScrollRestoration = | ||
| force ?? router.options.scrollRestoration ?? false | ||
| export function setupScrollRestoration(router: AnyRouter, force?: boolean) { | ||
| // Keep hash/top scrolling active even when sessionStorage is unavailable. | ||
| if (shouldScrollRestoration) { | ||
| if (force ?? router.options.scrollRestoration) { | ||
| router.isScrollRestoring = true | ||
| } | ||
| if ( | ||
| (isServer ?? router.isServer) || | ||
| router.isScrollRestorationSetup || | ||
| !cache | ||
| ) { | ||
| if ((isServer ?? router.isServer) || router.isScrollRestorationSetup) { | ||
| return | ||
@@ -177,4 +192,15 @@ } | ||
| const trackedScrollEntries = new Map<ScrollTarget, ScrollRestorationEntry>() | ||
| const setTrackedScrollEntry = ( | ||
| target: ScrollTarget, | ||
| scrollX: number, | ||
| scrollY: number, | ||
| ) => { | ||
| const entry = | ||
| trackedScrollEntries.get(target) || ({} as ScrollRestorationEntry) | ||
| entry.scrollX = scrollX | ||
| entry.scrollY = scrollY | ||
| trackedScrollEntries.set(target, entry) | ||
| } | ||
| window.history.scrollRestoration = 'manual' | ||
| history.scrollRestoration = 'manual' | ||
@@ -186,13 +212,7 @@ const onScroll = (event: Event) => { | ||
| if (event.target === document || event.target === window) { | ||
| trackedScrollEntries.set(windowScrollTarget, { | ||
| scrollX: window.scrollX || 0, | ||
| scrollY: window.scrollY || 0, | ||
| }) | ||
| if (event.target === document) { | ||
| setTrackedScrollEntry(windowScrollTarget, scrollX, scrollY) | ||
| } else { | ||
| const target = event.target as Element | ||
| trackedScrollEntries.set(target, { | ||
| scrollX: target.scrollLeft || 0, | ||
| scrollY: target.scrollTop || 0, | ||
| }) | ||
| setTrackedScrollEntry(target, target.scrollLeft, target.scrollTop) | ||
| } | ||
@@ -202,32 +222,16 @@ } | ||
| // Snapshot the current page's tracked scroll targets before navigation or unload. | ||
| const snapshotCurrentScrollTargets = (restoreKey?: string) => { | ||
| if ( | ||
| !router.isScrollRestoring || | ||
| !restoreKey || | ||
| trackedScrollEntries.size === 0 || | ||
| !cache | ||
| ) { | ||
| const snapshotCurrentScrollTargets = (restoreKey: string) => { | ||
| if (!router.isScrollRestoring) { | ||
| return | ||
| } | ||
| const keyEntry = (cache.state[restoreKey] ||= | ||
| const keyEntry = (scrollRestorationCache[restoreKey] ||= | ||
| {} as ScrollRestorationByElement) | ||
| for (const [target, position] of trackedScrollEntries) { | ||
| let selector: string | undefined | ||
| if (target === windowScrollTarget) { | ||
| selector = windowScrollTarget | ||
| keyEntry[windowScrollTarget] = position | ||
| } else if (target.isConnected) { | ||
| const attrId = target.getAttribute(scrollRestorationIdAttribute) | ||
| selector = attrId | ||
| ? `[${scrollRestorationIdAttribute}="${attrId}"]` | ||
| : getCssSelector(target) | ||
| keyEntry[getScrollRestorationSelector(target)] = position | ||
| } | ||
| if (!selector) { | ||
| continue | ||
| } | ||
| keyEntry[selector] = position | ||
| } | ||
@@ -238,8 +242,8 @@ } | ||
| router.subscribe('onBeforeLoad', (event) => { | ||
| snapshotCurrentScrollTargets( | ||
| event.fromLocation ? getKey(event.fromLocation) : undefined, | ||
| ) | ||
| if (event.fromLocation) { | ||
| snapshotCurrentScrollTargets(getKey(event.fromLocation)) | ||
| } | ||
| trackedScrollEntries.clear() | ||
| }) | ||
| window.addEventListener('pagehide', () => { | ||
| addEventListener('pagehide', () => { | ||
| snapshotCurrentScrollTargets( | ||
@@ -250,3 +254,3 @@ getKey( | ||
| ) | ||
| cache.persist() | ||
| persistScrollRestorationCache() | ||
| }) | ||
@@ -256,10 +260,10 @@ | ||
| router.subscribe('onRendered', (event) => { | ||
| const cacheKey = getKey(event.toLocation) | ||
| const behavior = router.options.scrollRestorationBehavior | ||
| const scrollToTopSelectors = router.options.scrollToTopSelectors | ||
| const shouldResetScroll = router.resetNextScroll | ||
| let scrollToTopElements: Array<Element> | undefined | ||
| trackedScrollEntries.clear() | ||
| if (!router.resetNextScroll) { | ||
| if (!shouldResetScroll) { | ||
| router.resetNextScroll = true | ||
| return | ||
| } | ||
@@ -274,47 +278,83 @@ | ||
| const cacheKey = getKey(event.toLocation) | ||
| const fromCacheKey = event.fromLocation && getKey(event.fromLocation) | ||
| if (router.isScrollRestoring && fromCacheKey && fromCacheKey !== cacheKey) { | ||
| const fromElementEntries = scrollRestorationCache[fromCacheKey] | ||
| if (fromElementEntries) { | ||
| let toElementEntries = scrollRestorationCache[cacheKey] | ||
| for (const elementSelector in fromElementEntries) { | ||
| if (elementSelector === windowScrollTarget) { | ||
| if (shouldResetScroll) { | ||
| continue | ||
| } | ||
| } else { | ||
| const element = getElement(elementSelector) | ||
| if (!element) { | ||
| continue | ||
| } | ||
| if (shouldResetScroll && scrollToTopSelectors) { | ||
| scrollToTopElements ??= | ||
| getScrollToTopElements(scrollToTopSelectors) | ||
| if (scrollToTopElements.includes(element)) { | ||
| continue | ||
| } | ||
| } | ||
| } | ||
| if (!toElementEntries) { | ||
| toElementEntries = scrollRestorationCache[cacheKey] = | ||
| {} as ScrollRestorationByElement | ||
| } | ||
| toElementEntries[elementSelector] ??= | ||
| fromElementEntries[elementSelector]! | ||
| } | ||
| } | ||
| } | ||
| if (!shouldResetScroll) { | ||
| return | ||
| } | ||
| ignoreScroll = true | ||
| try { | ||
| const hash = event.toLocation.hash | ||
| const hashScrollIntoViewOptions = | ||
| event.toLocation.state.__hashScrollIntoViewOptions ?? true | ||
| const action = locationHistoryActions.get(event.toLocation) | ||
| const skipWindowRestore = | ||
| hash && | ||
| hashScrollIntoViewOptions && | ||
| (action === 'PUSH' || action === 'REPLACE') | ||
| const elementEntries = router.isScrollRestoring | ||
| ? cache.state[cacheKey] | ||
| ? scrollRestorationCache[cacheKey] | ||
| : undefined | ||
| let restored = false | ||
| let windowRestored = false | ||
| if (elementEntries) { | ||
| for (const elementSelector in elementEntries) { | ||
| const entry = elementEntries[elementSelector] | ||
| const { scrollX, scrollY } = elementEntries[elementSelector]! | ||
| if (!isPlainObject(entry)) { | ||
| continue | ||
| } | ||
| const { scrollX, scrollY } = entry as { | ||
| scrollX?: unknown | ||
| scrollY?: unknown | ||
| } | ||
| if (!Number.isFinite(scrollX) || !Number.isFinite(scrollY)) { | ||
| continue | ||
| } | ||
| if (elementSelector === windowScrollTarget) { | ||
| window.scrollTo({ | ||
| top: scrollY as number, | ||
| left: scrollX as number, | ||
| behavior, | ||
| }) | ||
| restored = true | ||
| } else if (elementSelector) { | ||
| let element | ||
| try { | ||
| element = document.querySelector(elementSelector) | ||
| } catch { | ||
| if (skipWindowRestore) { | ||
| continue | ||
| } | ||
| scrollTo({ | ||
| top: scrollY, | ||
| left: scrollX, | ||
| behavior, | ||
| }) | ||
| windowRestored = true | ||
| } else { | ||
| const element = getElement(elementSelector) | ||
| if (element) { | ||
| element.scrollLeft = scrollX as number | ||
| element.scrollTop = scrollY as number | ||
| restored = true | ||
| element.scrollLeft = scrollX | ||
| element.scrollTop = scrollY | ||
| } | ||
@@ -325,14 +365,8 @@ } | ||
| if (!restored) { | ||
| const hash = router.history.location.hash.slice(1) | ||
| if (!windowRestored) { | ||
| if (hash) { | ||
| const hashScrollIntoViewOptions = | ||
| window.history.state?.__hashScrollIntoViewOptions ?? true | ||
| if (hashScrollIntoViewOptions) { | ||
| const el = document.getElementById(hash) | ||
| if (el) { | ||
| el.scrollIntoView(hashScrollIntoViewOptions) | ||
| } | ||
| document | ||
| .getElementById(hash) | ||
| ?.scrollIntoView(hashScrollIntoViewOptions) | ||
| } | ||
@@ -346,13 +380,7 @@ } else { | ||
| window.scrollTo(scrollOptions) | ||
| scrollTo(scrollOptions) | ||
| if (scrollToTopSelectors) { | ||
| for (const selector of scrollToTopSelectors) { | ||
| if (selector === windowScrollTarget) continue | ||
| const element = | ||
| typeof selector === 'function' | ||
| ? selector() | ||
| : document.querySelector(selector) | ||
| if (element) { | ||
| element.scrollTo(scrollOptions) | ||
| } | ||
| scrollToTopElements ??= getScrollToTopElements(scrollToTopSelectors) | ||
| for (const element of scrollToTopElements) { | ||
| element.scrollTo(scrollOptions) | ||
| } | ||
@@ -365,10 +393,3 @@ } | ||
| } | ||
| if (router.isScrollRestoring) { | ||
| cache.set((state) => { | ||
| state[cacheKey] ||= {} as ScrollRestorationByElement | ||
| return state | ||
| }) | ||
| } | ||
| }) | ||
| } |
| //#region src/hash-scroll.ts | ||
| /** | ||
| * @private | ||
| * Handles hash-based scrolling after navigation completes. | ||
| * To be used in framework-specific <Transitioner> components during the onResolved event. | ||
| */ | ||
| function handleHashScroll(router) { | ||
| if (typeof document !== "undefined" && document.querySelector) { | ||
| const location = router.stores.location.get(); | ||
| const hashScrollIntoViewOptions = location.state.__hashScrollIntoViewOptions ?? true; | ||
| if (hashScrollIntoViewOptions && location.hash !== "") { | ||
| const el = document.getElementById(location.hash); | ||
| if (el) el.scrollIntoView(hashScrollIntoViewOptions); | ||
| } | ||
| } | ||
| } | ||
| //#endregion | ||
| exports.handleHashScroll = handleHashScroll; | ||
| //# sourceMappingURL=hash-scroll.cjs.map |
| {"version":3,"file":"hash-scroll.cjs","names":[],"sources":["../../src/hash-scroll.ts"],"sourcesContent":["import type { AnyRouter } from './router'\n\n/**\n * @private\n * Handles hash-based scrolling after navigation completes.\n * To be used in framework-specific <Transitioner> components during the onResolved event.\n */\nexport function handleHashScroll(router: AnyRouter) {\n if (typeof document !== 'undefined' && (document as any).querySelector) {\n const location = router.stores.location.get()\n const hashScrollIntoViewOptions =\n location.state.__hashScrollIntoViewOptions ?? true\n\n if (hashScrollIntoViewOptions && location.hash !== '') {\n const el = document.getElementById(location.hash)\n if (el) {\n el.scrollIntoView(hashScrollIntoViewOptions)\n }\n }\n }\n}\n"],"mappings":";;;;;;AAOA,SAAgB,iBAAiB,QAAmB;AAClD,KAAI,OAAO,aAAa,eAAgB,SAAiB,eAAe;EACtE,MAAM,WAAW,OAAO,OAAO,SAAS,KAAK;EAC7C,MAAM,4BACJ,SAAS,MAAM,+BAA+B;AAEhD,MAAI,6BAA6B,SAAS,SAAS,IAAI;GACrD,MAAM,KAAK,SAAS,eAAe,SAAS,KAAK;AACjD,OAAI,GACF,IAAG,eAAe,0BAA0B"} |
| import { AnyRouter } from './router.cjs'; | ||
| /** | ||
| * @private | ||
| * Handles hash-based scrolling after navigation completes. | ||
| * To be used in framework-specific <Transitioner> components during the onResolved event. | ||
| */ | ||
| export declare function handleHashScroll(router: AnyRouter): void; |
| import { AnyRouter } from './router.js'; | ||
| /** | ||
| * @private | ||
| * Handles hash-based scrolling after navigation completes. | ||
| * To be used in framework-specific <Transitioner> components during the onResolved event. | ||
| */ | ||
| export declare function handleHashScroll(router: AnyRouter): void; |
| //#region src/hash-scroll.ts | ||
| /** | ||
| * @private | ||
| * Handles hash-based scrolling after navigation completes. | ||
| * To be used in framework-specific <Transitioner> components during the onResolved event. | ||
| */ | ||
| function handleHashScroll(router) { | ||
| if (typeof document !== "undefined" && document.querySelector) { | ||
| const location = router.stores.location.get(); | ||
| const hashScrollIntoViewOptions = location.state.__hashScrollIntoViewOptions ?? true; | ||
| if (hashScrollIntoViewOptions && location.hash !== "") { | ||
| const el = document.getElementById(location.hash); | ||
| if (el) el.scrollIntoView(hashScrollIntoViewOptions); | ||
| } | ||
| } | ||
| } | ||
| //#endregion | ||
| export { handleHashScroll }; | ||
| //# sourceMappingURL=hash-scroll.js.map |
| {"version":3,"file":"hash-scroll.js","names":[],"sources":["../../src/hash-scroll.ts"],"sourcesContent":["import type { AnyRouter } from './router'\n\n/**\n * @private\n * Handles hash-based scrolling after navigation completes.\n * To be used in framework-specific <Transitioner> components during the onResolved event.\n */\nexport function handleHashScroll(router: AnyRouter) {\n if (typeof document !== 'undefined' && (document as any).querySelector) {\n const location = router.stores.location.get()\n const hashScrollIntoViewOptions =\n location.state.__hashScrollIntoViewOptions ?? true\n\n if (hashScrollIntoViewOptions && location.hash !== '') {\n const el = document.getElementById(location.hash)\n if (el) {\n el.scrollIntoView(hashScrollIntoViewOptions)\n }\n }\n }\n}\n"],"mappings":";;;;;;AAOA,SAAgB,iBAAiB,QAAmB;AAClD,KAAI,OAAO,aAAa,eAAgB,SAAiB,eAAe;EACtE,MAAM,WAAW,OAAO,OAAO,SAAS,KAAK;EAC7C,MAAM,4BACJ,SAAS,MAAM,+BAA+B;AAEhD,MAAI,6BAA6B,SAAS,SAAS,IAAI;GACrD,MAAM,KAAK,SAAS,eAAe,SAAS,KAAK;AACjD,OAAI,GACF,IAAG,eAAe,0BAA0B"} |
| import type { AnyRouter } from './router' | ||
| /** | ||
| * @private | ||
| * Handles hash-based scrolling after navigation completes. | ||
| * To be used in framework-specific <Transitioner> components during the onResolved event. | ||
| */ | ||
| export function handleHashScroll(router: AnyRouter) { | ||
| if (typeof document !== 'undefined' && (document as any).querySelector) { | ||
| const location = router.stores.location.get() | ||
| const hashScrollIntoViewOptions = | ||
| location.state.__hashScrollIntoViewOptions ?? true | ||
| if (hashScrollIntoViewOptions && location.hash !== '') { | ||
| const el = document.getElementById(location.hash) | ||
| if (el) { | ||
| el.scrollIntoView(hashScrollIntoViewOptions) | ||
| } | ||
| } | ||
| } | ||
| } |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
2495305
-0.12%372
-1.85%28675
-0.21%