@tanstack/react-router 1.31.21 → 1.31.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/Matches.cjs +9 -2
- package/dist/cjs/Matches.cjs.map +1 -1
- package/dist/cjs/RouterProvider.cjs +4 -120
- package/dist/cjs/RouterProvider.cjs.map +1 -1
- package/dist/cjs/Transitioner.cjs +110 -0
- package/dist/cjs/Transitioner.cjs.map +1 -0
- package/dist/cjs/Transitioner.d.cts +1 -0
- package/dist/cjs/awaited.cjs +7 -6
- package/dist/cjs/awaited.cjs.map +1 -1
- package/dist/cjs/utils.cjs +11 -0
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +1 -0
- package/dist/esm/Matches.js +11 -4
- package/dist/esm/Matches.js.map +1 -1
- package/dist/esm/RouterProvider.js +4 -103
- package/dist/esm/RouterProvider.js.map +1 -1
- package/dist/esm/Transitioner.d.ts +1 -0
- package/dist/esm/Transitioner.js +93 -0
- package/dist/esm/Transitioner.js.map +1 -0
- package/dist/esm/awaited.js +7 -6
- package/dist/esm/awaited.js.map +1 -1
- package/dist/esm/utils.d.ts +1 -0
- package/dist/esm/utils.js +11 -0
- package/dist/esm/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/Matches.tsx +17 -7
- package/src/RouterProvider.tsx +6 -149
- package/src/Transitioner.tsx +126 -0
- package/src/awaited.tsx +7 -6
- package/src/utils.ts +12 -0
package/dist/esm/utils.js
CHANGED
|
@@ -152,6 +152,16 @@ function removeLayoutSegments(routePath) {
|
|
|
152
152
|
const newSegments = segments.filter((segment) => !segment.startsWith("_"));
|
|
153
153
|
return newSegments.join("/");
|
|
154
154
|
}
|
|
155
|
+
function usePrevious(value) {
|
|
156
|
+
const ref = React.useRef(value);
|
|
157
|
+
if (ref.current !== value) {
|
|
158
|
+
const prevValue = ref.current;
|
|
159
|
+
ref.current = value;
|
|
160
|
+
return prevValue;
|
|
161
|
+
} else {
|
|
162
|
+
return ref.current;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
155
165
|
export {
|
|
156
166
|
createControlledPromise,
|
|
157
167
|
deepEqual,
|
|
@@ -167,6 +177,7 @@ export {
|
|
|
167
177
|
replaceEqualDeep,
|
|
168
178
|
shallow,
|
|
169
179
|
useLayoutEffect,
|
|
180
|
+
usePrevious,
|
|
170
181
|
useStableCallback
|
|
171
182
|
};
|
|
172
183
|
//# sourceMappingURL=utils.js.map
|
package/dist/esm/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sources":["../../src/utils.ts"],"sourcesContent":["import * as React from 'react'\n\nexport type NoInfer<T> = [T][T extends any ? 0 : never]\nexport type IsAny<TValue, TYesResult, TNoResult = TValue> = 1 extends 0 & TValue\n ? TYesResult\n : TNoResult\nexport type PickAsRequired<TValue, TKey extends keyof TValue> = Omit<\n TValue,\n TKey\n> &\n Required<Pick<TValue, TKey>>\n\nexport type PickRequired<T> = {\n [K in keyof T as undefined extends T[K] ? never : K]: T[K]\n}\n\n// from https://stackoverflow.com/a/76458160\nexport type WithoutEmpty<T> = T extends any ? ({} extends T ? never : T) : never\n\n// export type Expand<T> = T\nexport type Expand<T> = T extends object\n ? T extends infer O\n ? O extends Function\n ? O\n : { [K in keyof O]: O[K] }\n : never\n : T\n\nexport type UnionToIntersection<T> = (\n T extends any ? (k: T) => void : never\n) extends (k: infer I) => any\n ? I\n : never\n\nexport type DeepPartial<T> = T extends object\n ? {\n [P in keyof T]?: DeepPartial<T[P]>\n }\n : T\n\nexport type MakeDifferenceOptional<TLeft, TRight> = Omit<\n TRight,\n keyof TLeft\n> & {\n [K in keyof TLeft & keyof TRight]?: TRight[K]\n}\n\n// from https://stackoverflow.com/a/53955431\n// eslint-disable-next-line @typescript-eslint/naming-convention\nexport type IsUnion<T, U extends T = T> = (\n T extends any ? (U extends T ? false : true) : never\n) extends false\n ? false\n : true\n\nexport type Assign<TLeft, TRight> = keyof TLeft extends never\n ? TRight\n : keyof TRight extends never\n ? TLeft\n : Omit<TLeft, keyof TRight> & TRight\n\nexport type Timeout = ReturnType<typeof setTimeout>\n\nexport type Updater<TPrevious, TResult = TPrevious> =\n | TResult\n | ((prev?: TPrevious) => TResult)\n\nexport type NonNullableUpdater<TPrevious, TResult = TPrevious> =\n | TResult\n | ((prev: TPrevious) => TResult)\n\n// from https://github.com/type-challenges/type-challenges/issues/737\ntype LastInUnion<T> =\n UnionToIntersection<T extends unknown ? (x: T) => 0 : never> extends (\n x: infer L,\n ) => 0\n ? L\n : never\nexport type UnionToTuple<T, TLast = LastInUnion<T>> = [T] extends [never]\n ? []\n : [...UnionToTuple<Exclude<T, TLast>>, TLast]\n\n//\n\nexport function last<T>(arr: Array<T>) {\n return arr[arr.length - 1]\n}\n\nfunction isFunction(d: any): d is Function {\n return typeof d === 'function'\n}\n\nexport function functionalUpdate<TResult>(\n updater: Updater<TResult> | NonNullableUpdater<TResult>,\n previous: TResult,\n): TResult {\n if (isFunction(updater)) {\n return updater(previous)\n }\n\n return updater\n}\n\nexport function pick<TValue, TKey extends keyof TValue>(\n parent: TValue,\n keys: Array<TKey>,\n): Pick<TValue, TKey> {\n return keys.reduce((obj: any, key: TKey) => {\n obj[key] = parent[key]\n return obj\n }, {} as any)\n}\n\n/**\n * This function returns `prev` if `_next` is deeply equal.\n * If not, it will replace any deeply equal children of `b` with those of `a`.\n * This can be used for structural sharing between immutable JSON values for example.\n * Do not use this with signals\n */\nexport function replaceEqualDeep<T>(prev: any, _next: T): T {\n if (prev === _next) {\n return prev\n }\n\n const next = _next as any\n\n const array = isPlainArray(prev) && isPlainArray(next)\n\n if (array || (isPlainObject(prev) && isPlainObject(next))) {\n const prevItems = array ? prev : Object.keys(prev)\n const prevSize = prevItems.length\n const nextItems = array ? next : Object.keys(next)\n const nextSize = nextItems.length\n const copy: any = array ? [] : {}\n\n let equalItems = 0\n\n for (let i = 0; i < nextSize; i++) {\n const key = array ? i : nextItems[i]\n if (\n ((!array && prevItems.includes(key)) || array) &&\n prev[key] === undefined &&\n next[key] === undefined\n ) {\n copy[key] = undefined\n equalItems++\n } else {\n copy[key] = replaceEqualDeep(prev[key], next[key])\n if (copy[key] === prev[key] && prev[key] !== undefined) {\n equalItems++\n }\n }\n }\n\n return prevSize === nextSize && equalItems === prevSize ? prev : copy\n }\n\n return next\n}\n\n// Copied from: https://github.com/jonschlinkert/is-plain-object\nexport function isPlainObject(o: any) {\n if (!hasObjectPrototype(o)) {\n return false\n }\n\n // If has modified constructor\n const ctor = o.constructor\n if (typeof ctor === 'undefined') {\n return true\n }\n\n // If has modified prototype\n const prot = ctor.prototype\n if (!hasObjectPrototype(prot)) {\n return false\n }\n\n // If constructor does not have an Object-specific method\n if (!prot.hasOwnProperty('isPrototypeOf')) {\n return false\n }\n\n // Most likely a plain Object\n return true\n}\n\nfunction hasObjectPrototype(o: any) {\n return Object.prototype.toString.call(o) === '[object Object]'\n}\n\nexport function isPlainArray(value: unknown) {\n return Array.isArray(value) && value.length === Object.keys(value).length\n}\n\nexport function deepEqual(a: any, b: any, partial: boolean = false): boolean {\n if (a === b) {\n return true\n }\n\n if (typeof a !== typeof b) {\n return false\n }\n\n if (isPlainObject(a) && isPlainObject(b)) {\n const aKeys = Object.keys(a)\n const bKeys = Object.keys(b)\n\n if (!partial && aKeys.length !== bKeys.length) {\n return false\n }\n\n return !bKeys.some(\n (key) => !(key in a) || !deepEqual(a[key], b[key], partial),\n )\n }\n\n if (Array.isArray(a) && Array.isArray(b)) {\n return !a.some((item, index) => !deepEqual(item, b[index], partial))\n }\n\n return false\n}\n\nexport function useStableCallback<T extends (...args: Array<any>) => any>(\n fn: T,\n): T {\n const fnRef = React.useRef(fn)\n fnRef.current = fn\n\n const ref = React.useRef((...args: Array<any>) => fnRef.current(...args))\n return ref.current as T\n}\n\nexport function shallow<T>(objA: T, objB: T) {\n if (Object.is(objA, objB)) {\n return true\n }\n\n if (\n typeof objA !== 'object' ||\n objA === null ||\n typeof objB !== 'object' ||\n objB === null\n ) {\n return false\n }\n\n const keysA = Object.keys(objA)\n if (keysA.length !== Object.keys(objB).length) {\n return false\n }\n\n for (const item of keysA) {\n if (\n !Object.prototype.hasOwnProperty.call(objB, item) ||\n !Object.is(objA[item as keyof T], objB[item as keyof T])\n ) {\n return false\n }\n }\n return true\n}\n\nexport type StringLiteral<T> = T extends string\n ? string extends T\n ? string\n : T\n : never\n\nexport type StrictOrFrom<TFrom, TReturnIntersection extends boolean = false> =\n | {\n from: StringLiteral<TFrom> | TFrom\n strict?: true\n }\n | {\n from?: never\n strict: false\n experimental_returnIntersection?: TReturnIntersection\n }\n\nexport const useLayoutEffect =\n typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect\n\n/**\n *\n * @deprecated use `jsesc` instead\n */\nexport function escapeJSON(jsonString: string) {\n return jsonString\n .replace(/\\\\/g, '\\\\\\\\') // Escape backslashes\n .replace(/'/g, \"\\\\'\") // Escape single quotes\n .replace(/\"/g, '\\\\\"') // Escape double quotes\n}\n\nexport function removeTrailingSlash(value: string): string {\n if (value.endsWith('/') && value !== '/') {\n return value.slice(0, -1)\n }\n return value\n}\n\n// intended to only compare path name\n// see the usage in the isActive under useLinkProps\n// /sample/path1 = /sample/path1/\n// /sample/path1/some <> /sample/path1\nexport function exactPathTest(pathName1: string, pathName2: string): boolean {\n return removeTrailingSlash(pathName1) === removeTrailingSlash(pathName2)\n}\n\nexport type ControlledPromise<T> = Promise<T> & {\n resolve: (value: T) => void\n reject: (value: any) => void\n status: 'pending' | 'resolved' | 'rejected'\n}\n\nexport function createControlledPromise<T>(onResolve?: () => void) {\n let resolveLoadPromise!: () => void\n let rejectLoadPromise!: (value: any) => void\n\n const controlledPromise = new Promise<void>((resolve, reject) => {\n resolveLoadPromise = resolve\n rejectLoadPromise = reject\n }) as ControlledPromise<T>\n\n controlledPromise.status = 'pending'\n\n controlledPromise.resolve = () => {\n controlledPromise.status = 'resolved'\n resolveLoadPromise()\n onResolve?.()\n }\n\n controlledPromise.reject = (e) => {\n controlledPromise.status = 'rejected'\n rejectLoadPromise(e)\n }\n\n return controlledPromise\n}\n\n/**\n * Removes all segments from a given path that start with an underscore ('_').\n *\n * @param {string} routePath - The path from which to remove segments. Defaults to '/'.\n * @returns {string} The path with all underscore-prefixed segments removed.\n * @example\n * removeLayoutSegments('/workspace/_auth/foo') // '/workspace/foo'\n */\nexport function removeLayoutSegments(routePath: string): string {\n const segments = routePath.split('/')\n const newSegments = segments.filter((segment) => !segment.startsWith('_'))\n return newSegments.join('/')\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"utils.js","sources":["../../src/utils.ts"],"sourcesContent":["import * as React from 'react'\n\nexport type NoInfer<T> = [T][T extends any ? 0 : never]\nexport type IsAny<TValue, TYesResult, TNoResult = TValue> = 1 extends 0 & TValue\n ? TYesResult\n : TNoResult\nexport type PickAsRequired<TValue, TKey extends keyof TValue> = Omit<\n TValue,\n TKey\n> &\n Required<Pick<TValue, TKey>>\n\nexport type PickRequired<T> = {\n [K in keyof T as undefined extends T[K] ? never : K]: T[K]\n}\n\n// from https://stackoverflow.com/a/76458160\nexport type WithoutEmpty<T> = T extends any ? ({} extends T ? never : T) : never\n\n// export type Expand<T> = T\nexport type Expand<T> = T extends object\n ? T extends infer O\n ? O extends Function\n ? O\n : { [K in keyof O]: O[K] }\n : never\n : T\n\nexport type UnionToIntersection<T> = (\n T extends any ? (k: T) => void : never\n) extends (k: infer I) => any\n ? I\n : never\n\nexport type DeepPartial<T> = T extends object\n ? {\n [P in keyof T]?: DeepPartial<T[P]>\n }\n : T\n\nexport type MakeDifferenceOptional<TLeft, TRight> = Omit<\n TRight,\n keyof TLeft\n> & {\n [K in keyof TLeft & keyof TRight]?: TRight[K]\n}\n\n// from https://stackoverflow.com/a/53955431\n// eslint-disable-next-line @typescript-eslint/naming-convention\nexport type IsUnion<T, U extends T = T> = (\n T extends any ? (U extends T ? false : true) : never\n) extends false\n ? false\n : true\n\nexport type Assign<TLeft, TRight> = keyof TLeft extends never\n ? TRight\n : keyof TRight extends never\n ? TLeft\n : Omit<TLeft, keyof TRight> & TRight\n\nexport type Timeout = ReturnType<typeof setTimeout>\n\nexport type Updater<TPrevious, TResult = TPrevious> =\n | TResult\n | ((prev?: TPrevious) => TResult)\n\nexport type NonNullableUpdater<TPrevious, TResult = TPrevious> =\n | TResult\n | ((prev: TPrevious) => TResult)\n\n// from https://github.com/type-challenges/type-challenges/issues/737\ntype LastInUnion<T> =\n UnionToIntersection<T extends unknown ? (x: T) => 0 : never> extends (\n x: infer L,\n ) => 0\n ? L\n : never\nexport type UnionToTuple<T, TLast = LastInUnion<T>> = [T] extends [never]\n ? []\n : [...UnionToTuple<Exclude<T, TLast>>, TLast]\n\n//\n\nexport function last<T>(arr: Array<T>) {\n return arr[arr.length - 1]\n}\n\nfunction isFunction(d: any): d is Function {\n return typeof d === 'function'\n}\n\nexport function functionalUpdate<TResult>(\n updater: Updater<TResult> | NonNullableUpdater<TResult>,\n previous: TResult,\n): TResult {\n if (isFunction(updater)) {\n return updater(previous)\n }\n\n return updater\n}\n\nexport function pick<TValue, TKey extends keyof TValue>(\n parent: TValue,\n keys: Array<TKey>,\n): Pick<TValue, TKey> {\n return keys.reduce((obj: any, key: TKey) => {\n obj[key] = parent[key]\n return obj\n }, {} as any)\n}\n\n/**\n * This function returns `prev` if `_next` is deeply equal.\n * If not, it will replace any deeply equal children of `b` with those of `a`.\n * This can be used for structural sharing between immutable JSON values for example.\n * Do not use this with signals\n */\nexport function replaceEqualDeep<T>(prev: any, _next: T): T {\n if (prev === _next) {\n return prev\n }\n\n const next = _next as any\n\n const array = isPlainArray(prev) && isPlainArray(next)\n\n if (array || (isPlainObject(prev) && isPlainObject(next))) {\n const prevItems = array ? prev : Object.keys(prev)\n const prevSize = prevItems.length\n const nextItems = array ? next : Object.keys(next)\n const nextSize = nextItems.length\n const copy: any = array ? [] : {}\n\n let equalItems = 0\n\n for (let i = 0; i < nextSize; i++) {\n const key = array ? i : nextItems[i]\n if (\n ((!array && prevItems.includes(key)) || array) &&\n prev[key] === undefined &&\n next[key] === undefined\n ) {\n copy[key] = undefined\n equalItems++\n } else {\n copy[key] = replaceEqualDeep(prev[key], next[key])\n if (copy[key] === prev[key] && prev[key] !== undefined) {\n equalItems++\n }\n }\n }\n\n return prevSize === nextSize && equalItems === prevSize ? prev : copy\n }\n\n return next\n}\n\n// Copied from: https://github.com/jonschlinkert/is-plain-object\nexport function isPlainObject(o: any) {\n if (!hasObjectPrototype(o)) {\n return false\n }\n\n // If has modified constructor\n const ctor = o.constructor\n if (typeof ctor === 'undefined') {\n return true\n }\n\n // If has modified prototype\n const prot = ctor.prototype\n if (!hasObjectPrototype(prot)) {\n return false\n }\n\n // If constructor does not have an Object-specific method\n if (!prot.hasOwnProperty('isPrototypeOf')) {\n return false\n }\n\n // Most likely a plain Object\n return true\n}\n\nfunction hasObjectPrototype(o: any) {\n return Object.prototype.toString.call(o) === '[object Object]'\n}\n\nexport function isPlainArray(value: unknown) {\n return Array.isArray(value) && value.length === Object.keys(value).length\n}\n\nexport function deepEqual(a: any, b: any, partial: boolean = false): boolean {\n if (a === b) {\n return true\n }\n\n if (typeof a !== typeof b) {\n return false\n }\n\n if (isPlainObject(a) && isPlainObject(b)) {\n const aKeys = Object.keys(a)\n const bKeys = Object.keys(b)\n\n if (!partial && aKeys.length !== bKeys.length) {\n return false\n }\n\n return !bKeys.some(\n (key) => !(key in a) || !deepEqual(a[key], b[key], partial),\n )\n }\n\n if (Array.isArray(a) && Array.isArray(b)) {\n return !a.some((item, index) => !deepEqual(item, b[index], partial))\n }\n\n return false\n}\n\nexport function useStableCallback<T extends (...args: Array<any>) => any>(\n fn: T,\n): T {\n const fnRef = React.useRef(fn)\n fnRef.current = fn\n\n const ref = React.useRef((...args: Array<any>) => fnRef.current(...args))\n return ref.current as T\n}\n\nexport function shallow<T>(objA: T, objB: T) {\n if (Object.is(objA, objB)) {\n return true\n }\n\n if (\n typeof objA !== 'object' ||\n objA === null ||\n typeof objB !== 'object' ||\n objB === null\n ) {\n return false\n }\n\n const keysA = Object.keys(objA)\n if (keysA.length !== Object.keys(objB).length) {\n return false\n }\n\n for (const item of keysA) {\n if (\n !Object.prototype.hasOwnProperty.call(objB, item) ||\n !Object.is(objA[item as keyof T], objB[item as keyof T])\n ) {\n return false\n }\n }\n return true\n}\n\nexport type StringLiteral<T> = T extends string\n ? string extends T\n ? string\n : T\n : never\n\nexport type StrictOrFrom<TFrom, TReturnIntersection extends boolean = false> =\n | {\n from: StringLiteral<TFrom> | TFrom\n strict?: true\n }\n | {\n from?: never\n strict: false\n experimental_returnIntersection?: TReturnIntersection\n }\n\nexport const useLayoutEffect =\n typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect\n\n/**\n *\n * @deprecated use `jsesc` instead\n */\nexport function escapeJSON(jsonString: string) {\n return jsonString\n .replace(/\\\\/g, '\\\\\\\\') // Escape backslashes\n .replace(/'/g, \"\\\\'\") // Escape single quotes\n .replace(/\"/g, '\\\\\"') // Escape double quotes\n}\n\nexport function removeTrailingSlash(value: string): string {\n if (value.endsWith('/') && value !== '/') {\n return value.slice(0, -1)\n }\n return value\n}\n\n// intended to only compare path name\n// see the usage in the isActive under useLinkProps\n// /sample/path1 = /sample/path1/\n// /sample/path1/some <> /sample/path1\nexport function exactPathTest(pathName1: string, pathName2: string): boolean {\n return removeTrailingSlash(pathName1) === removeTrailingSlash(pathName2)\n}\n\nexport type ControlledPromise<T> = Promise<T> & {\n resolve: (value: T) => void\n reject: (value: any) => void\n status: 'pending' | 'resolved' | 'rejected'\n}\n\nexport function createControlledPromise<T>(onResolve?: () => void) {\n let resolveLoadPromise!: () => void\n let rejectLoadPromise!: (value: any) => void\n\n const controlledPromise = new Promise<void>((resolve, reject) => {\n resolveLoadPromise = resolve\n rejectLoadPromise = reject\n }) as ControlledPromise<T>\n\n controlledPromise.status = 'pending'\n\n controlledPromise.resolve = () => {\n controlledPromise.status = 'resolved'\n resolveLoadPromise()\n onResolve?.()\n }\n\n controlledPromise.reject = (e) => {\n controlledPromise.status = 'rejected'\n rejectLoadPromise(e)\n }\n\n return controlledPromise\n}\n\n/**\n * Removes all segments from a given path that start with an underscore ('_').\n *\n * @param {string} routePath - The path from which to remove segments. Defaults to '/'.\n * @returns {string} The path with all underscore-prefixed segments removed.\n * @example\n * removeLayoutSegments('/workspace/_auth/foo') // '/workspace/foo'\n */\nexport function removeLayoutSegments(routePath: string): string {\n const segments = routePath.split('/')\n const newSegments = segments.filter((segment) => !segment.startsWith('_'))\n return newSegments.join('/')\n}\n\nexport function usePrevious<T>(value: T): T {\n const ref = React.useRef<T>(value)\n\n if (ref.current !== value) {\n const prevValue = ref.current\n ref.current = value\n return prevValue\n } else {\n return ref.current\n }\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/Matches.tsx
CHANGED
|
@@ -8,6 +8,7 @@ import { createControlledPromise, pick } from './utils'
|
|
|
8
8
|
import { CatchNotFound, DefaultGlobalNotFound, isNotFound } from './not-found'
|
|
9
9
|
import { isRedirect } from './redirects'
|
|
10
10
|
import { type AnyRouter, type RegisteredRouter } from './router'
|
|
11
|
+
import { Transitioner } from './Transitioner'
|
|
11
12
|
import type { ResolveRelativePath, ToOptions } from './link'
|
|
12
13
|
import type { AnyRoute, ReactNode, StaticDataRouteOption } from './route'
|
|
13
14
|
import type {
|
|
@@ -99,6 +100,21 @@ export type AnyRouteMatch = RouteMatch<any, any, any, any, any, any, any>
|
|
|
99
100
|
export function Matches() {
|
|
100
101
|
const router = useRouter()
|
|
101
102
|
|
|
103
|
+
const inner = (
|
|
104
|
+
<>
|
|
105
|
+
<MatchesInner />
|
|
106
|
+
<Transitioner />
|
|
107
|
+
</>
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
return router.options.InnerWrap ? (
|
|
111
|
+
<router.options.InnerWrap>{inner}</router.options.InnerWrap>
|
|
112
|
+
) : (
|
|
113
|
+
inner
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function MatchesInner() {
|
|
102
118
|
const matchId = useRouterState({
|
|
103
119
|
select: (s) => {
|
|
104
120
|
return s.matches[0]?.id
|
|
@@ -109,7 +125,7 @@ export function Matches() {
|
|
|
109
125
|
select: (s) => s.resolvedLocation.state.key!,
|
|
110
126
|
})
|
|
111
127
|
|
|
112
|
-
|
|
128
|
+
return (
|
|
113
129
|
<matchContext.Provider value={matchId}>
|
|
114
130
|
<CatchBoundary
|
|
115
131
|
getResetKey={() => resetKey}
|
|
@@ -126,12 +142,6 @@ export function Matches() {
|
|
|
126
142
|
</CatchBoundary>
|
|
127
143
|
</matchContext.Provider>
|
|
128
144
|
)
|
|
129
|
-
|
|
130
|
-
return router.options.InnerWrap ? (
|
|
131
|
-
<router.options.InnerWrap>{inner}</router.options.InnerWrap>
|
|
132
|
-
) : (
|
|
133
|
-
inner
|
|
134
|
-
)
|
|
135
145
|
}
|
|
136
146
|
|
|
137
147
|
function SafeFragment(props: any) {
|
package/src/RouterProvider.tsx
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
|
1
2
|
import * as React from 'react'
|
|
2
|
-
import { flushSync } from 'react-dom'
|
|
3
3
|
import { Matches } from './Matches'
|
|
4
|
-
import { pick, useLayoutEffect } from './utils'
|
|
5
|
-
import { useRouter } from './useRouter'
|
|
6
|
-
import { useRouterState } from './useRouterState'
|
|
7
4
|
import { getRouterContext } from './routerContext'
|
|
5
|
+
import { Transitioner } from './Transitioner'
|
|
8
6
|
import type { NavigateOptions, ToOptions } from './link'
|
|
9
7
|
import type { ParsedLocation } from './location'
|
|
10
8
|
import type { AnyRoute } from './route'
|
|
@@ -87,17 +85,11 @@ export function RouterContextProvider<
|
|
|
87
85
|
|
|
88
86
|
const routerContext = getRouterContext()
|
|
89
87
|
|
|
90
|
-
const pendingElement = router.options.defaultPendingComponent ? (
|
|
91
|
-
<router.options.defaultPendingComponent />
|
|
92
|
-
) : null
|
|
93
|
-
|
|
94
88
|
const provider = (
|
|
95
|
-
<
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
</routerContext.Provider>
|
|
100
|
-
</React.Suspense>
|
|
89
|
+
<routerContext.Provider value={router}>
|
|
90
|
+
{children}
|
|
91
|
+
<Transitioner />
|
|
92
|
+
</routerContext.Provider>
|
|
101
93
|
)
|
|
102
94
|
|
|
103
95
|
if (router.options.Wrap) {
|
|
@@ -118,129 +110,6 @@ export function RouterProvider<
|
|
|
118
110
|
)
|
|
119
111
|
}
|
|
120
112
|
|
|
121
|
-
function Transitioner() {
|
|
122
|
-
const router = useRouter()
|
|
123
|
-
const mountLoadForRouter = React.useRef({ router, mounted: false })
|
|
124
|
-
const routerState = useRouterState({
|
|
125
|
-
select: (s) =>
|
|
126
|
-
pick(s, ['isLoading', 'location', 'resolvedLocation', 'isTransitioning']),
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
const [isTransitioning, startReactTransition_] = React.useTransition()
|
|
130
|
-
// Track pending state changes
|
|
131
|
-
const hasPendingMatches = useRouterState({
|
|
132
|
-
select: (s) => s.matches.some((d) => d.status === 'pending'),
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
const previousIsLoading = usePrevious(routerState.isLoading)
|
|
136
|
-
|
|
137
|
-
const isAnyPending =
|
|
138
|
-
routerState.isLoading || isTransitioning || hasPendingMatches
|
|
139
|
-
const previousIsAnyPending = usePrevious(isAnyPending)
|
|
140
|
-
|
|
141
|
-
router.startReactTransition = startReactTransition_
|
|
142
|
-
|
|
143
|
-
const tryLoad = async () => {
|
|
144
|
-
try {
|
|
145
|
-
await router.load()
|
|
146
|
-
} catch (err) {
|
|
147
|
-
console.error(err)
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Subscribe to location changes
|
|
152
|
-
// and try to load the new location
|
|
153
|
-
useLayoutEffect(() => {
|
|
154
|
-
const unsub = router.history.subscribe(router.load)
|
|
155
|
-
|
|
156
|
-
const nextLocation = router.buildLocation({
|
|
157
|
-
to: router.latestLocation.pathname,
|
|
158
|
-
search: true,
|
|
159
|
-
params: true,
|
|
160
|
-
hash: true,
|
|
161
|
-
state: true,
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
if (routerState.location.href !== nextLocation.href) {
|
|
165
|
-
router.commitLocation({ ...nextLocation, replace: true })
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
return () => {
|
|
169
|
-
unsub()
|
|
170
|
-
}
|
|
171
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
172
|
-
}, [router, router.history])
|
|
173
|
-
|
|
174
|
-
// Try to load the initial location
|
|
175
|
-
useLayoutEffect(() => {
|
|
176
|
-
if (
|
|
177
|
-
window.__TSR_DEHYDRATED__ ||
|
|
178
|
-
(mountLoadForRouter.current.router === router &&
|
|
179
|
-
mountLoadForRouter.current.mounted)
|
|
180
|
-
) {
|
|
181
|
-
return
|
|
182
|
-
}
|
|
183
|
-
mountLoadForRouter.current = { router, mounted: true }
|
|
184
|
-
tryLoad()
|
|
185
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
186
|
-
}, [router])
|
|
187
|
-
|
|
188
|
-
useLayoutEffect(() => {
|
|
189
|
-
// The router was loading and now it's not
|
|
190
|
-
if (previousIsLoading && !routerState.isLoading) {
|
|
191
|
-
const toLocation = router.state.location
|
|
192
|
-
const fromLocation = router.state.resolvedLocation
|
|
193
|
-
const pathChanged = fromLocation.href !== toLocation.href
|
|
194
|
-
|
|
195
|
-
router.emit({
|
|
196
|
-
type: 'onLoad',
|
|
197
|
-
fromLocation,
|
|
198
|
-
toLocation,
|
|
199
|
-
pathChanged,
|
|
200
|
-
})
|
|
201
|
-
|
|
202
|
-
// if (router.viewTransitionPromise) {
|
|
203
|
-
// console.log('resolving view transition promise')
|
|
204
|
-
// }
|
|
205
|
-
|
|
206
|
-
// router.viewTransitionPromise?.resolve(true)
|
|
207
|
-
}
|
|
208
|
-
}, [previousIsLoading, router, routerState.isLoading])
|
|
209
|
-
|
|
210
|
-
useLayoutEffect(() => {
|
|
211
|
-
// The router was pending and now it's not
|
|
212
|
-
if (previousIsAnyPending && !isAnyPending) {
|
|
213
|
-
const toLocation = router.state.location
|
|
214
|
-
const fromLocation = router.state.resolvedLocation
|
|
215
|
-
const pathChanged = fromLocation.href !== toLocation.href
|
|
216
|
-
|
|
217
|
-
router.emit({
|
|
218
|
-
type: 'onResolved',
|
|
219
|
-
fromLocation,
|
|
220
|
-
toLocation,
|
|
221
|
-
pathChanged,
|
|
222
|
-
})
|
|
223
|
-
|
|
224
|
-
router.__store.setState((s) => ({
|
|
225
|
-
...s,
|
|
226
|
-
status: 'idle',
|
|
227
|
-
resolvedLocation: s.location,
|
|
228
|
-
}))
|
|
229
|
-
|
|
230
|
-
if ((document as any).querySelector) {
|
|
231
|
-
if (router.state.location.hash !== '') {
|
|
232
|
-
const el = document.getElementById(router.state.location.hash)
|
|
233
|
-
if (el) {
|
|
234
|
-
el.scrollIntoView()
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
}, [isAnyPending, previousIsAnyPending, router])
|
|
240
|
-
|
|
241
|
-
return null
|
|
242
|
-
}
|
|
243
|
-
|
|
244
113
|
export function getRouteMatch<TRouteTree extends AnyRoute>(
|
|
245
114
|
state: RouterState<TRouteTree>,
|
|
246
115
|
id: string,
|
|
@@ -275,15 +144,3 @@ export type RouterProps<
|
|
|
275
144
|
>['context']
|
|
276
145
|
>
|
|
277
146
|
}
|
|
278
|
-
|
|
279
|
-
function usePrevious<T>(value: T): T {
|
|
280
|
-
const ref = React.useRef<T>(value)
|
|
281
|
-
|
|
282
|
-
if (ref.current !== value) {
|
|
283
|
-
const prevValue = ref.current
|
|
284
|
-
ref.current = value
|
|
285
|
-
return prevValue
|
|
286
|
-
} else {
|
|
287
|
-
return ref.current
|
|
288
|
-
}
|
|
289
|
-
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { pick, useLayoutEffect, usePrevious } from './utils'
|
|
3
|
+
import { useRouter } from './useRouter'
|
|
4
|
+
import { useRouterState } from './useRouterState'
|
|
5
|
+
|
|
6
|
+
export function Transitioner() {
|
|
7
|
+
const router = useRouter()
|
|
8
|
+
const mountLoadForRouter = React.useRef({ router, mounted: false })
|
|
9
|
+
const routerState = useRouterState({
|
|
10
|
+
select: (s) =>
|
|
11
|
+
pick(s, ['isLoading', 'location', 'resolvedLocation', 'isTransitioning']),
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
const [isTransitioning, startReactTransition_] = React.useTransition()
|
|
15
|
+
// Track pending state changes
|
|
16
|
+
const hasPendingMatches = useRouterState({
|
|
17
|
+
select: (s) => s.matches.some((d) => d.status === 'pending'),
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const previousIsLoading = usePrevious(routerState.isLoading)
|
|
21
|
+
|
|
22
|
+
const isAnyPending =
|
|
23
|
+
routerState.isLoading || isTransitioning || hasPendingMatches
|
|
24
|
+
const previousIsAnyPending = usePrevious(isAnyPending)
|
|
25
|
+
|
|
26
|
+
router.startReactTransition = startReactTransition_
|
|
27
|
+
|
|
28
|
+
const tryLoad = async () => {
|
|
29
|
+
try {
|
|
30
|
+
await router.load()
|
|
31
|
+
} catch (err) {
|
|
32
|
+
console.error(err)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Subscribe to location changes
|
|
37
|
+
// and try to load the new location
|
|
38
|
+
useLayoutEffect(() => {
|
|
39
|
+
const unsub = router.history.subscribe(router.load)
|
|
40
|
+
|
|
41
|
+
const nextLocation = router.buildLocation({
|
|
42
|
+
to: router.latestLocation.pathname,
|
|
43
|
+
search: true,
|
|
44
|
+
params: true,
|
|
45
|
+
hash: true,
|
|
46
|
+
state: true,
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
if (routerState.location.href !== nextLocation.href) {
|
|
50
|
+
router.commitLocation({ ...nextLocation, replace: true })
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return () => {
|
|
54
|
+
unsub()
|
|
55
|
+
}
|
|
56
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
57
|
+
}, [router, router.history])
|
|
58
|
+
|
|
59
|
+
// Try to load the initial location
|
|
60
|
+
useLayoutEffect(() => {
|
|
61
|
+
if (
|
|
62
|
+
window.__TSR_DEHYDRATED__ ||
|
|
63
|
+
(mountLoadForRouter.current.router === router &&
|
|
64
|
+
mountLoadForRouter.current.mounted)
|
|
65
|
+
) {
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
mountLoadForRouter.current = { router, mounted: true }
|
|
69
|
+
tryLoad()
|
|
70
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
71
|
+
}, [router])
|
|
72
|
+
|
|
73
|
+
useLayoutEffect(() => {
|
|
74
|
+
// The router was loading and now it's not
|
|
75
|
+
if (previousIsLoading && !routerState.isLoading) {
|
|
76
|
+
const toLocation = router.state.location
|
|
77
|
+
const fromLocation = router.state.resolvedLocation
|
|
78
|
+
const pathChanged = fromLocation.href !== toLocation.href
|
|
79
|
+
|
|
80
|
+
router.emit({
|
|
81
|
+
type: 'onLoad',
|
|
82
|
+
fromLocation,
|
|
83
|
+
toLocation,
|
|
84
|
+
pathChanged,
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
// if (router.viewTransitionPromise) {
|
|
88
|
+
// console.log('resolving view transition promise')
|
|
89
|
+
// }
|
|
90
|
+
// router.viewTransitionPromise?.resolve(true)
|
|
91
|
+
}
|
|
92
|
+
}, [previousIsLoading, router, routerState.isLoading])
|
|
93
|
+
|
|
94
|
+
useLayoutEffect(() => {
|
|
95
|
+
// The router was pending and now it's not
|
|
96
|
+
if (previousIsAnyPending && !isAnyPending) {
|
|
97
|
+
const toLocation = router.state.location
|
|
98
|
+
const fromLocation = router.state.resolvedLocation
|
|
99
|
+
const pathChanged = fromLocation.href !== toLocation.href
|
|
100
|
+
|
|
101
|
+
router.emit({
|
|
102
|
+
type: 'onResolved',
|
|
103
|
+
fromLocation,
|
|
104
|
+
toLocation,
|
|
105
|
+
pathChanged,
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
router.__store.setState((s) => ({
|
|
109
|
+
...s,
|
|
110
|
+
status: 'idle',
|
|
111
|
+
resolvedLocation: s.location,
|
|
112
|
+
}))
|
|
113
|
+
|
|
114
|
+
if ((document as any).querySelector) {
|
|
115
|
+
if (router.state.location.hash !== '') {
|
|
116
|
+
const el = document.getElementById(router.state.location.hash)
|
|
117
|
+
if (el) {
|
|
118
|
+
el.scrollIntoView()
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}, [isAnyPending, previousIsAnyPending, router])
|
|
124
|
+
|
|
125
|
+
return null
|
|
126
|
+
}
|
package/src/awaited.tsx
CHANGED
|
@@ -59,14 +59,15 @@ export function useAwaited<T>({ promise }: AwaitOptions<T>): [T] {
|
|
|
59
59
|
// If we are the originator of the promise,
|
|
60
60
|
// inject the state into the HTML stream
|
|
61
61
|
if (!isDehydratedDeferred(promise)) {
|
|
62
|
-
router.injectHtml(
|
|
63
|
-
|
|
62
|
+
router.injectHtml(
|
|
63
|
+
`<script class='tsr_deferred_data'>window.__TSR__DEFERRED__${state.uid} = ${JSON.stringify(router.options.transformer.stringify(state))}
|
|
64
64
|
if (window.__TSR__ROUTER__) {
|
|
65
|
-
let deferred = window.__TSR__ROUTER__.getDeferred('${state.uid}')
|
|
66
|
-
if (deferred) deferred.resolve(window.__TSR__DEFERRED__${state.uid})
|
|
65
|
+
let deferred = window.__TSR__ROUTER__.getDeferred('${state.uid}');
|
|
66
|
+
if (deferred) deferred.resolve(window.__TSR__DEFERRED__${state.uid});
|
|
67
67
|
}
|
|
68
|
-
document.querySelectorAll('.tsr_deferred_data').forEach((el) => el.parentElement.removeChild(el))
|
|
69
|
-
</script
|
|
68
|
+
document.querySelectorAll('.tsr_deferred_data').forEach((el) => el.parentElement.removeChild(el));
|
|
69
|
+
</script>`,
|
|
70
|
+
)
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
if (state.status === 'error') {
|
package/src/utils.ts
CHANGED
|
@@ -352,3 +352,15 @@ export function removeLayoutSegments(routePath: string): string {
|
|
|
352
352
|
const newSegments = segments.filter((segment) => !segment.startsWith('_'))
|
|
353
353
|
return newSegments.join('/')
|
|
354
354
|
}
|
|
355
|
+
|
|
356
|
+
export function usePrevious<T>(value: T): T {
|
|
357
|
+
const ref = React.useRef<T>(value)
|
|
358
|
+
|
|
359
|
+
if (ref.current !== value) {
|
|
360
|
+
const prevValue = ref.current
|
|
361
|
+
ref.current = value
|
|
362
|
+
return prevValue
|
|
363
|
+
} else {
|
|
364
|
+
return ref.current
|
|
365
|
+
}
|
|
366
|
+
}
|