`} component.
* It uses the {@link https://refine.dev/docs/api-reference/core/hooks/navigation/useNavigation#create `create`} method from {@link https://refine.dev/docs/api-reference/core/hooks/navigation/useNavigation `useNavigation`} under the hood.
- * It can be useful to redirect the app to the create page route of resource}.
+ * It can be useful when redirecting the app to the create page route of resource.
*
* @see {@link https://refine.dev/docs/api-reference/mui/components/buttons/create-button} for more details.
*/
diff --git a/packages/tanstack-router/.npmignore b/packages/tanstack-router/.npmignore
new file mode 100644
index 0000000000000..a29724dad73dc
--- /dev/null
+++ b/packages/tanstack-router/.npmignore
@@ -0,0 +1,10 @@
+node_modules
+.DS_Store
+test
+**/*.spec.ts
+**/*.spec.tsx
+**/*.test.ts
+**/*.test.tsx
+tsup.config.ts
+tsconfig.test.json
+tsconfig.declarations.json
diff --git a/packages/tanstack-router/CHANGELOG.md b/packages/tanstack-router/CHANGELOG.md
new file mode 100644
index 0000000000000..d18c74b3ca41b
--- /dev/null
+++ b/packages/tanstack-router/CHANGELOG.md
@@ -0,0 +1,3 @@
+# @refinedev/tanstack-router
+
+All notable changes to this package will be documented in this file.
diff --git a/packages/tanstack-router/LICENSE b/packages/tanstack-router/LICENSE
new file mode 100644
index 0000000000000..1028bea7bced4
--- /dev/null
+++ b/packages/tanstack-router/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 Refine Development Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/packages/tanstack-router/README.md b/packages/tanstack-router/README.md
new file mode 100644
index 0000000000000..88739716a3cf8
--- /dev/null
+++ b/packages/tanstack-router/README.md
@@ -0,0 +1,29 @@
+
+
+refine is an open-source, headless React framework for developers building enterprise web applications.
+
+It eliminates repetitive tasks in CRUD operations and provides industry-standard solutions for critical project components like **authentication**, **access control**, **routing**, **networking**, **state management**, and **i18n**.
+
+
+
+
+
+
+## About
+
+[refine](https://refine.dev/) is **headless by design**, offering unlimited styling and customization options. Moreover, refine ships with ready-made integrations for [Ant Design](https://ant.design/), [Material UI](https://mui.com/material-ui/getting-started/overview/), [Mantine](https://mantine.dev/), and [Chakra UI](https://chakra-ui.com/) for convenience.
+
+refine has connectors for 15+ backend services, including REST API, [GraphQL](https://graphql.org/), and popular services like [Airtable](https://www.airtable.com/), [Strapi](https://strapi.io/), [Supabase](https://supabase.com/), [Firebase](https://firebase.google.com/), and [Directus](https://directus.io/)
+
+[Refer to documentation for more info about refine→](https://refine.dev/docs/)
+[Step up to refine tutorials →](https://refine.dev/docs/tutorial/introduction/index/)
+
+## Documentation
+
+For more detailed information and usage, refer to the [refine TanStack Router documentation](https://refine.dev/docs/routing/integrations/tanstack-router/).
diff --git a/packages/tanstack-router/package.json b/packages/tanstack-router/package.json
new file mode 100644
index 0000000000000..dccad08da1975
--- /dev/null
+++ b/packages/tanstack-router/package.json
@@ -0,0 +1,78 @@
+{
+ "name": "@refinedev/tanstack-router",
+ "version": "0.0.0",
+ "private": false,
+ "description": "TanStack Router support for Refine, with full routing capabilities.",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/refinedev/refine.git",
+ "directory": "packages/tanstack-router"
+ },
+ "license": "MIT",
+ "author": "refine",
+ "sideEffects": false,
+ "exports": {
+ ".": {
+ "import": {
+ "types": "./dist/index.d.mts",
+ "default": "./dist/index.mjs"
+ },
+ "require": {
+ "types": "./dist/index.d.cts",
+ "default": "./dist/index.cjs"
+ }
+ }
+ },
+ "main": "dist/index.cjs",
+ "module": "dist/index.mjs",
+ "typesVersions": {
+ "*": {
+ ".": [
+ "dist/index.d.ts"
+ ]
+ }
+ },
+ "typings": "dist/index.d.ts",
+ "files": [
+ "dist",
+ "src"
+ ],
+ "scripts": {
+ "attw": "attw --pack .",
+ "build": "tsup && node ../shared/generate-declarations.js",
+ "dev": "tsup --watch",
+ "prepare": "pnpm build",
+ "publint": "publint --strict=true --level=suggestion",
+ "test": "vitest run",
+ "test:watch": "vitest",
+ "types": "node ../shared/generate-declarations.js"
+ },
+ "dependencies": {
+ "@tanstack/react-router": "^1.131.35"
+ },
+ "devDependencies": {
+ "@esbuild-plugins/node-resolve": "^0.1.4",
+ "@refinedev/core": "^5.0.10",
+ "@tanstack/react-router": "^1.131.35",
+ "@types/node": "^20",
+ "@vitest/ui": "^2.1.8",
+ "tslib": "^2.6.2",
+ "tsup": "^6.7.0",
+ "typescript": "^5.8.3",
+ "vitest": "^2.1.8"
+ },
+ "peerDependencies": {
+ "@refinedev/core": "^5.0.0",
+ "@tanstack/react-router": "^1.131.35",
+ "@types/react": "^18.0.0 || ^19.0.0",
+ "@types/react-dom": "^18.0.0 || ^19.0.0",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/packages/tanstack-router/src/bindings.tsx b/packages/tanstack-router/src/bindings.tsx
new file mode 100644
index 0000000000000..23dc6f903b2f5
--- /dev/null
+++ b/packages/tanstack-router/src/bindings.tsx
@@ -0,0 +1,140 @@
+import React, { type ComponentProps } from "react";
+import {
+ type GoConfig,
+ type ParseResponse,
+ type RouterProvider,
+ matchResourceFromRoute,
+ ResourceContext,
+} from "@refinedev/core";
+import { useCallback, useContext } from "react";
+import {
+ Link as TanStackLink,
+ useLocation,
+ useNavigate,
+ useParams,
+ useRouter,
+} from "@tanstack/react-router";
+import { convertToNumberIfPossible } from "./convert-to-number-if-possible";
+
+export const stringifyConfig = {
+ addQueryPrefix: true,
+ skipNulls: true,
+ arrayFormat: "indices" as const,
+ encode: false,
+ encodeValuesOnly: true,
+};
+
+export const routerProvider: RouterProvider = {
+ go: () => {
+ const {
+ pathname,
+ search: existingSearch,
+ hash: existingHash,
+ } = useLocation();
+ const navigate = useNavigate();
+ const router = useRouter();
+
+ const fn = useCallback(
+ ({
+ to,
+ type,
+ query,
+ hash,
+ options: { keepQuery, keepHash } = {},
+ }: GoConfig) => {
+ const urlQuery = {
+ ...(keepQuery ? existingSearch : {}),
+ ...query,
+ };
+
+ const normalizedHash = (hash ?? (keepHash ? existingHash : "") ?? "")
+ .replace(/^#/, "")
+ .trim();
+
+ const urlTo = to || pathname;
+ const hasUrlQuery = Object.keys(urlQuery).length > 0;
+ const hasUrlHash = normalizedHash.length > 0;
+
+ const fullPath = router.buildLocation({
+ to: urlTo as never,
+ search: hasUrlQuery ? (urlQuery as never) : undefined,
+ hash: hasUrlHash ? normalizedHash : undefined,
+ }).href;
+
+ if (type === "path") {
+ return fullPath;
+ }
+
+ void navigate({
+ to: urlTo as never,
+ search: hasUrlQuery ? (urlQuery as never) : undefined,
+ hash: hasUrlHash ? normalizedHash : undefined,
+ replace: type === "replace",
+ });
+
+ return;
+ },
+ [existingHash, existingSearch, navigate, pathname, router],
+ );
+
+ return fn;
+ },
+ back: () => {
+ const router = useRouter();
+
+ const fn = useCallback(() => {
+ router.history.back();
+ }, [router]);
+
+ return fn;
+ },
+ parse: () => {
+ const params = useParams({ strict: false }) ?? {};
+ const { pathname, search } = useLocation();
+ const { resources } = useContext(ResourceContext);
+
+ const { resource, action } = React.useMemo(() => {
+ return matchResourceFromRoute(pathname, resources);
+ }, [resources, pathname]);
+
+ const fn = useCallback(() => {
+ const combinedParams = {
+ ...params,
+ ...search,
+ };
+
+ const response: ParseResponse = {
+ ...(resource && { resource }),
+ ...(action && { action }),
+ ...(params?.id && { id: params.id }),
+ pathname,
+ params: {
+ ...combinedParams,
+ currentPage: convertToNumberIfPossible(combinedParams.currentPage) as
+ | number
+ | undefined,
+ pageSize: convertToNumberIfPossible(combinedParams.pageSize) as
+ | number
+ | undefined,
+ to: combinedParams.to as string | undefined,
+ },
+ };
+
+ return response;
+ }, [action, params, pathname, resource, search]);
+
+ return fn;
+ },
+ Link: React.forwardRef<
+ HTMLAnchorElement,
+ ComponentProps>
+ >(function RefineLink(props, ref) {
+ return (
+ )}
+ to={props.to as never}
+ ref={ref as never}
+ />
+ );
+ }),
+};
diff --git a/packages/tanstack-router/src/catch-all-navigate.test.tsx b/packages/tanstack-router/src/catch-all-navigate.test.tsx
new file mode 100644
index 0000000000000..c84a32a7bad89
--- /dev/null
+++ b/packages/tanstack-router/src/catch-all-navigate.test.tsx
@@ -0,0 +1,58 @@
+import React from "react";
+import { screen } from "@testing-library/react";
+import { vi } from "vitest";
+
+import { CatchAllNavigate } from "./catch-all-navigate";
+import { render, TestWrapper } from "./test/index";
+
+vi.mock("@tanstack/react-router", async () => {
+ const actual = await vi.importActual("@tanstack/react-router");
+
+ return {
+ ...actual,
+ Navigate: ({
+ to,
+ search,
+ }: {
+ to: string;
+ search?: Record;
+ }) => (
+
+ {to}
+
+ ),
+ };
+});
+
+describe("CatchAllNavigate", () => {
+ it("should not append the to query for the root path", () => {
+ render( , {
+ wrapper: TestWrapper({
+ routerInitialEntries: ["/"],
+ }),
+ });
+
+ expect(screen.getByTestId("navigate-to")).toHaveTextContent("/login");
+ expect(screen.getByTestId("navigate-to")).toHaveAttribute(
+ "data-search",
+ "",
+ );
+ });
+
+ it("should append the current path and search for non-root routes", () => {
+ render( , {
+ wrapper: TestWrapper({
+ routerInitialEntries: ["/posts?currentPage=2"],
+ }),
+ });
+
+ expect(screen.getByTestId("navigate-to")).toHaveTextContent("/login");
+ expect(screen.getByTestId("navigate-to")).toHaveAttribute(
+ "data-search",
+ JSON.stringify({ to: "/posts?currentPage=2" }),
+ );
+ });
+});
diff --git a/packages/tanstack-router/src/catch-all-navigate.tsx b/packages/tanstack-router/src/catch-all-navigate.tsx
new file mode 100644
index 0000000000000..97acfc9097bb8
--- /dev/null
+++ b/packages/tanstack-router/src/catch-all-navigate.tsx
@@ -0,0 +1,13 @@
+import React from "react";
+import { Navigate, useLocation } from "@tanstack/react-router";
+
+/**
+ * A component that will navigate to the given path with `to` query parameter included with the current location.
+ */
+export const CatchAllNavigate: React.FC<{ to: string }> = ({ to }) => {
+ const location = useLocation();
+ const queryValue = location.href.split("#")[0];
+ const search = queryValue.length > 1 ? { to: queryValue } : undefined;
+
+ return ;
+};
diff --git a/packages/tanstack-router/src/convert-to-number-if-possible.ts b/packages/tanstack-router/src/convert-to-number-if-possible.ts
new file mode 100644
index 0000000000000..612aac73aaffc
--- /dev/null
+++ b/packages/tanstack-router/src/convert-to-number-if-possible.ts
@@ -0,0 +1,16 @@
+export const convertToNumberIfPossible = (value: unknown) => {
+ if (typeof value === "undefined") {
+ return value;
+ }
+ if (typeof value === "number") {
+ return value;
+ }
+ if (typeof value !== "string") {
+ return value;
+ }
+ const num = Number(value);
+ if (`${num}` === value) {
+ return num;
+ }
+ return value;
+};
diff --git a/packages/tanstack-router/src/document-title-handler.test.tsx b/packages/tanstack-router/src/document-title-handler.test.tsx
new file mode 100644
index 0000000000000..2f7e01df531c5
--- /dev/null
+++ b/packages/tanstack-router/src/document-title-handler.test.tsx
@@ -0,0 +1,192 @@
+import React, { type ReactNode } from "react";
+
+import { DocumentTitleHandler } from "./document-title-handler";
+import { render, TestWrapper, type ITestWrapperProps } from "./test/index";
+import { mockRouterProvider } from "./test/dataMocks";
+
+const renderDocumentTitleHandler = (
+ children: ReactNode,
+ wrapperProps: ITestWrapperProps = {},
+) => {
+ return render(<>{children}>, {
+ wrapper: TestWrapper(wrapperProps),
+ });
+};
+
+describe(" ", () => {
+ it("should render default list title", async () => {
+ renderDocumentTitleHandler( , {
+ resources: [{ name: "posts", list: "/posts" }],
+ routerInitialEntries: ["/posts"],
+ routerProvider: mockRouterProvider({
+ action: "list",
+ resource: { name: "posts", list: "/posts" },
+ }),
+ });
+
+ expect(document.title).toBe("Posts | Refine");
+ });
+
+ it("should render default create title", async () => {
+ renderDocumentTitleHandler( , {
+ resources: [{ name: "posts", create: "/posts/create" }],
+ routerInitialEntries: ["/posts/create"],
+ routerProvider: mockRouterProvider({
+ action: "create",
+ resource: { name: "posts", create: "/posts/create" },
+ }),
+ });
+
+ expect(document.title).toBe("Create new Post | Refine");
+ });
+
+ it("should render default edit title", async () => {
+ renderDocumentTitleHandler( , {
+ resources: [{ name: "posts", edit: "/posts/edit/:id" }],
+ routerInitialEntries: ["/posts/edit/1"],
+ routerProvider: mockRouterProvider({
+ action: "edit",
+ resource: { name: "posts", edit: "/posts/edit/1" },
+ id: "1",
+ }),
+ });
+
+ expect(document.title).toBe("#1 Edit Post | Refine");
+ });
+
+ it("should render default show title", async () => {
+ renderDocumentTitleHandler( , {
+ resources: [{ name: "posts", show: "/posts/show/:id" }],
+ routerInitialEntries: ["/posts/show/1"],
+ routerProvider: mockRouterProvider({
+ action: "show",
+ resource: { name: "posts", show: "/posts/show/1" },
+ id: "1",
+ }),
+ });
+
+ expect(document.title).toBe("#1 Show Post | Refine");
+ });
+
+ it("should render default clone title", async () => {
+ renderDocumentTitleHandler( , {
+ resources: [{ name: "posts", clone: "/posts/clone/:id" }],
+ routerInitialEntries: ["/posts/clone/1"],
+ routerProvider: mockRouterProvider({
+ action: "clone",
+ resource: { name: "posts", clone: "/posts/clone/1" },
+ id: "1",
+ }),
+ });
+
+ expect(document.title).toBe("#1 Clone Post | Refine");
+ });
+
+ it("should render default title for unknown resource", async () => {
+ renderDocumentTitleHandler( , {
+ resources: [{ name: "posts" }],
+ routerInitialEntries: ["/unknown"],
+ routerProvider: mockRouterProvider({
+ action: "list",
+ resource: undefined,
+ }),
+ });
+
+ expect(document.title).toBe("Refine");
+ });
+
+ it("should render default title for unknown action", async () => {
+ renderDocumentTitleHandler( , {
+ resources: [{ name: "posts" }],
+ routerInitialEntries: ["/posts/unknown"],
+ routerProvider: mockRouterProvider({
+ action: undefined,
+ resource: {
+ name: "posts",
+ },
+ }),
+ });
+
+ expect(document.title).toBe("Refine");
+ });
+
+ it("should use identifier", async () => {
+ renderDocumentTitleHandler( , {
+ resources: [
+ { name: "posts", list: "/posts", identifier: "Awesome Posts" },
+ ],
+ routerInitialEntries: ["/posts"],
+ routerProvider: mockRouterProvider({
+ action: "list",
+ resource: {
+ name: "posts",
+ list: "/posts",
+ identifier: "Awesome Posts",
+ },
+ }),
+ });
+
+ expect(document.title).toBe("Awesome posts | Refine");
+ });
+
+ it("should render custom title", async () => {
+ renderDocumentTitleHandler(
+ {
+ return "Custom Title";
+ }}
+ />,
+ {
+ resources: [{ name: "posts", list: "/posts" }],
+ routerInitialEntries: ["/posts"],
+ routerProvider: mockRouterProvider({
+ action: "list",
+ resource: { name: "posts", list: "/posts" },
+ }),
+ },
+ );
+
+ expect(document.title).toBe("Custom Title");
+ });
+
+ it("should label be populated with friendly resource name on handler function", async () => {
+ renderDocumentTitleHandler(
+ {
+ const labelMeta = resource?.meta?.label;
+
+ expect(labelMeta).toBe("Posts");
+
+ return autoGeneratedTitle;
+ }}
+ />,
+ {
+ resources: [{ name: "posts", list: "/posts" }],
+ routerInitialEntries: ["/posts"],
+ routerProvider: mockRouterProvider({
+ action: "list",
+ resource: { name: "posts", list: "/posts" },
+ }),
+ },
+ );
+ });
+
+ it("should use label from resource if its provided", async () => {
+ renderDocumentTitleHandler( , {
+ resources: [
+ { name: "posts", list: "/posts", meta: { label: "Labeled Posts" } },
+ ],
+ routerInitialEntries: ["/posts"],
+ routerProvider: mockRouterProvider({
+ action: "list",
+ resource: {
+ name: "posts",
+ list: "/posts",
+ meta: { label: "Labeled Posts" },
+ },
+ }),
+ });
+
+ expect(document.title).toBe("Labeled Posts | Refine");
+ });
+});
diff --git a/packages/tanstack-router/src/document-title-handler.tsx b/packages/tanstack-router/src/document-title-handler.tsx
new file mode 100644
index 0000000000000..e73e64f408e3c
--- /dev/null
+++ b/packages/tanstack-router/src/document-title-handler.tsx
@@ -0,0 +1,79 @@
+import {
+ type Action,
+ type IResourceItem,
+ useParsed,
+ useTranslate,
+ generateDefaultDocumentTitle,
+ useUserFriendlyName,
+} from "@refinedev/core";
+import React, { useLayoutEffect } from "react";
+import { useLocation } from "@tanstack/react-router";
+
+type Props = {
+ handler?: (options: {
+ resource?: IResourceItem;
+ action?: Action;
+ params?: Record;
+ pathname?: string;
+ autoGeneratedTitle: string;
+ }) => string;
+};
+
+export function DocumentTitleHandler({ handler }: Props) {
+ const location = useLocation();
+ const { action, id, params, pathname, resource } = useParsed();
+ const translate = useTranslate();
+ const getUserFriendlyName = useUserFriendlyName();
+
+ const identifier = resource?.identifier ?? resource?.name;
+ const preferredLabel = resource?.meta?.label;
+ const resourceName =
+ preferredLabel ??
+ getUserFriendlyName(identifier, action === "list" ? "plural" : "singular");
+ const populatedLabel = translate(
+ `${resource?.name}.${resource?.name}`,
+ resourceName,
+ );
+
+ useLayoutEffect(() => {
+ const autoGeneratedTitle = generateDefaultDocumentTitle(
+ translate,
+ resource!,
+ action,
+ `${id}`,
+ resourceName,
+ getUserFriendlyName,
+ );
+ if (handler) {
+ document.title = handler({
+ action,
+ resource: {
+ ...(resource! ?? {}),
+ meta: {
+ ...resource?.meta,
+ label: populatedLabel,
+ },
+ },
+ params,
+ pathname,
+ autoGeneratedTitle,
+ });
+ } else {
+ document.title = autoGeneratedTitle;
+ }
+ }, [
+ action,
+ getUserFriendlyName,
+ handler,
+ id,
+ location.href,
+ params,
+ pathname,
+ populatedLabel,
+ resource,
+ resourceName,
+ translate,
+ ]);
+
+ return <>>;
+}
diff --git a/packages/tanstack-router/src/index.ts b/packages/tanstack-router/src/index.ts
new file mode 100644
index 0000000000000..2b3c1d00ffd5c
--- /dev/null
+++ b/packages/tanstack-router/src/index.ts
@@ -0,0 +1,6 @@
+export { routerProvider as default, stringifyConfig } from "./bindings.js";
+export { NavigateToResource } from "./navigate-to-resource.js";
+export { UnsavedChangesNotifier } from "./unsaved-changes-notifier.js";
+export { CatchAllNavigate } from "./catch-all-navigate.js";
+export { DocumentTitleHandler } from "./document-title-handler.js";
+export { useDocumentTitle } from "./use-document-title.js";
diff --git a/packages/tanstack-router/src/navigate-to-resource.test.tsx b/packages/tanstack-router/src/navigate-to-resource.test.tsx
new file mode 100644
index 0000000000000..4e2dff3ab3ef1
--- /dev/null
+++ b/packages/tanstack-router/src/navigate-to-resource.test.tsx
@@ -0,0 +1,205 @@
+import React from "react";
+import { vi } from "vitest";
+import { screen } from "@testing-library/react";
+
+import { NavigateToResource } from "./navigate-to-resource";
+import { render, TestWrapper, type ITestWrapperProps } from "./test/index";
+import { mockRouterProvider } from "./test/dataMocks";
+
+// Mock Navigate component to capture navigation
+vi.mock("@tanstack/react-router", async () => {
+ const actual = await vi.importActual("@tanstack/react-router");
+ return {
+ ...actual,
+ Navigate: ({ to }: { to: string }) => (
+ {to}
+ ),
+ };
+});
+
+const renderNavigateToResource = (
+ props: React.ComponentProps = {},
+ wrapperProps: ITestWrapperProps = {},
+) => {
+ return render( , {
+ wrapper: TestWrapper(wrapperProps),
+ });
+};
+
+describe("NavigateToResource", () => {
+ it("should navigate to the first resource with list action", async () => {
+ renderNavigateToResource(
+ {},
+ {
+ resources: [
+ { name: "posts", list: "/posts" },
+ { name: "categories", list: "/categories" },
+ ],
+ routerProvider: mockRouterProvider({
+ fns: {
+ go: () => {
+ return ({ to, type }) => {
+ if (type === "path") return to;
+ return undefined;
+ };
+ },
+ },
+ }),
+ },
+ );
+
+ expect(screen.getByTestId("navigate-to").textContent).toBe("/posts");
+ });
+
+ it("should navigate to the specified resource", async () => {
+ renderNavigateToResource(
+ { resource: "categories" },
+ {
+ resources: [
+ { name: "posts", list: "/posts" },
+ { name: "categories", list: "/categories" },
+ ],
+ routerProvider: mockRouterProvider({
+ fns: {
+ go: () => {
+ return ({ to, type }) => {
+ if (type === "path") return to;
+ return undefined;
+ };
+ },
+ },
+ }),
+ },
+ );
+
+ expect(screen.getByTestId("navigate-to").textContent).toBe("/categories");
+ });
+
+ it("should navigate to fallbackTo when no resource is found", async () => {
+ const consoleWarnSpy = vi
+ .spyOn(console, "warn")
+ .mockImplementation(() => {});
+
+ renderNavigateToResource(
+ { fallbackTo: "/dashboard" },
+ {
+ resources: [],
+ routerProvider: mockRouterProvider({
+ fns: {
+ go: () => {
+ return ({ to, type }) => {
+ if (type === "path") return to;
+ return undefined;
+ };
+ },
+ },
+ }),
+ },
+ );
+
+ expect(consoleWarnSpy).toHaveBeenCalledWith(
+ "No resource is found. navigation to /dashboard.",
+ );
+ expect(screen.getByTestId("navigate-to").textContent).toBe("/dashboard");
+
+ consoleWarnSpy.mockRestore();
+ });
+
+ it("should not navigate when no resource and no fallbackTo is provided", async () => {
+ const consoleWarnSpy = vi
+ .spyOn(console, "warn")
+ .mockImplementation(() => {});
+
+ renderNavigateToResource(
+ {},
+ {
+ resources: [],
+ routerProvider: mockRouterProvider({
+ fns: {
+ go: () => {
+ return ({ to, type }) => {
+ if (type === "path") return to;
+ return undefined;
+ };
+ },
+ },
+ }),
+ },
+ );
+
+ expect(consoleWarnSpy).toHaveBeenCalledWith(
+ 'No resource and "fallbackTo" is found. No navigation will be made.',
+ );
+ expect(screen.queryByTestId("navigate-to")).toBeNull();
+
+ consoleWarnSpy.mockRestore();
+ });
+
+ it("should pass meta to getToPath", async () => {
+ const meta = { foo: "bar" };
+
+ renderNavigateToResource(
+ { resource: "posts", meta },
+ {
+ resources: [{ name: "posts", list: "/posts" }],
+ routerProvider: mockRouterProvider({
+ fns: {
+ go: () => {
+ return ({ to, type }) => {
+ if (type === "path") return to;
+ return undefined;
+ };
+ },
+ },
+ }),
+ },
+ );
+
+ expect(screen.getByTestId("navigate-to").textContent).toBe("/posts");
+ });
+
+ it("should prefer specified resource over first resource with list", async () => {
+ renderNavigateToResource(
+ { resource: "categories" },
+ {
+ resources: [
+ { name: "posts", list: "/posts" },
+ { name: "categories", list: "/categories" },
+ ],
+ routerProvider: mockRouterProvider({
+ fns: {
+ go: () => {
+ return ({ to, type }) => {
+ if (type === "path") return to;
+ return undefined;
+ };
+ },
+ },
+ }),
+ },
+ );
+
+ expect(screen.getByTestId("navigate-to").textContent).toBe("/categories");
+ });
+
+ it("should return null when resource exists but path is not available", async () => {
+ renderNavigateToResource(
+ { resource: "posts" },
+ {
+ resources: [{ name: "posts" }],
+ routerProvider: mockRouterProvider({
+ fns: {
+ go: () => {
+ return ({ to, type }) => {
+ if (type === "path") return undefined;
+ return undefined;
+ };
+ },
+ },
+ }),
+ },
+ );
+
+ expect(screen.queryByTestId("navigate-to")).toBeNull();
+ });
+});
diff --git a/packages/tanstack-router/src/navigate-to-resource.tsx b/packages/tanstack-router/src/navigate-to-resource.tsx
new file mode 100644
index 0000000000000..dab980d582dbe
--- /dev/null
+++ b/packages/tanstack-router/src/navigate-to-resource.tsx
@@ -0,0 +1,46 @@
+import { useResourceParams, useGetToPath } from "@refinedev/core";
+import React, { type PropsWithChildren } from "react";
+import { Navigate } from "@tanstack/react-router";
+
+type NavigateToResourceProps = PropsWithChildren<{
+ resource?: string;
+ fallbackTo?: string;
+ meta?: Record;
+}>;
+
+export const NavigateToResource: React.FC = ({
+ resource: resourceProp,
+ fallbackTo,
+ meta,
+}) => {
+ const getToPath = useGetToPath();
+ const { resource, resources } = useResourceParams({
+ resource: resourceProp,
+ });
+
+ const toResource = resource || resources.find((r) => r.list);
+
+ if (toResource) {
+ const path = getToPath({
+ resource: toResource,
+ action: "list",
+ meta,
+ });
+
+ if (path) {
+ return ;
+ }
+
+ return null;
+ }
+
+ if (fallbackTo) {
+ console.warn(`No resource is found. navigation to ${fallbackTo}.`);
+ return ;
+ }
+
+ console.warn(
+ 'No resource and "fallbackTo" is found. No navigation will be made.',
+ );
+ return null;
+};
diff --git a/packages/tanstack-router/src/test/dataMocks.ts b/packages/tanstack-router/src/test/dataMocks.ts
new file mode 100644
index 0000000000000..8794b09d546d3
--- /dev/null
+++ b/packages/tanstack-router/src/test/dataMocks.ts
@@ -0,0 +1,110 @@
+import type {
+ ParsedParams,
+ IResourceItem,
+ Action,
+ RouterProvider,
+} from "@refinedev/core";
+
+export const posts = [
+ {
+ id: "1",
+ title:
+ "Necessitatibus necessitatibus id et cupiditate provident est qui amet.",
+ slug: "ut-ad-et",
+ content:
+ "Cupiditate labore quaerat cum incidunt vel et consequatur modi illo. Et maxime aut commodi occaecati omnis. Est voluptatem quibusdam aliquam. Esse tenetur omnis eaque. Consequatur necessitatibus illum ipsum aspernatur architecto qui. Ut temporibus qui nobis. Reiciendis est magnam ipsa quasi dolor ipsa error. Et eaque cumque est. Eos et odit corporis delectus aut corrupti tempora velit. Perferendis ratione voluptas corrupti id temporibus nam.",
+ categoryId: 1,
+ category: {
+ id: 1,
+ },
+ status: "active",
+ userId: 5,
+ tags: [16, 31, 45],
+ },
+ {
+ id: "2",
+ title: "Recusandae consectetur aut atque est.",
+ slug: "consequatur-molestiae-rerum",
+ content:
+ "Quia ut autem. Hic dolorum magni est quisquam. Modi est id et est. Est sapiente velit iure non voluptatem natus enim. Distinctio ipsa repellendus est. Sunt ipsam dignissimos vero error est cumque eaque. Consequatur voluptas suscipit optio incidunt doloremque quia harum harum. Totam voluptatibus aperiam quia. Est omnis deleniti et aut at fugit temporibus debitis modi. Magni aut vel quod magnam.",
+ category: {
+ id: 38,
+ },
+ status: "active",
+ userId: 36,
+ tags: [16, 30, 46],
+ },
+];
+
+const MockDataProvider = () => {
+ return {
+ create: () => Promise.resolve({ data: posts[0] }),
+ createMany: () => Promise.resolve({ data: posts }),
+ deleteOne: () => Promise.resolve({ data: posts[0] }),
+ deleteMany: () => Promise.resolve({ data: [] }),
+ getList: () => Promise.resolve({ data: posts, total: 2 }),
+ getMany: () => Promise.resolve({ data: [...posts] }),
+ getOne: () => Promise.resolve({ data: posts[0] }),
+ update: () => Promise.resolve({ data: posts[0] }),
+ updateMany: () => Promise.resolve({ data: [] }),
+ getApiUrl: () => "https://api.fake-rest.refine.dev",
+ custom: () => Promise.resolve({ data: [...posts] }),
+ };
+};
+
+export const MockJSONServer = MockDataProvider() as any;
+
+export const mockRouterProvider = ({
+ pathname,
+ params,
+ resource,
+ action,
+ id,
+ fns,
+}: {
+ pathname?: string;
+ params?: ParsedParams;
+ resource?: IResourceItem;
+ action?: Action;
+ id?: string;
+ fns?: Partial;
+} = {}): RouterProvider => {
+ const bindings: RouterProvider = {
+ go: () => {
+ return ({ type }) => {
+ if (type === "path") return "";
+ return undefined;
+ };
+ },
+ parse: () => {
+ return () => {
+ return {
+ params: {
+ ...params,
+ },
+ pathname,
+ resource: resource,
+ action: action,
+ id: id || undefined,
+ };
+ };
+ },
+ back: () => {
+ return () => undefined;
+ },
+ Link: () => null,
+ ...fns,
+ };
+
+ return bindings;
+};
+
+export const MockAccessControlProvider: any = {
+ can: () => Promise.resolve({ can: true }),
+};
+
+export const MockLiveProvider: any = {
+ subscribe: () => ({}),
+ unsubscribe: () => ({}),
+ publish: () => ({}),
+};
diff --git a/packages/tanstack-router/src/test/index.tsx b/packages/tanstack-router/src/test/index.tsx
new file mode 100644
index 0000000000000..9ea973d1a684f
--- /dev/null
+++ b/packages/tanstack-router/src/test/index.tsx
@@ -0,0 +1,124 @@
+import React, { type ReactNode } from "react";
+import {
+ Outlet,
+ RouterProvider as TanStackRouterProvider,
+ createMemoryHistory,
+ createRootRoute,
+ createRoute,
+ createRouter,
+} from "@tanstack/react-router";
+
+import {
+ type AccessControlProvider,
+ type AuthProvider,
+ type NotificationProvider,
+ Refine,
+ type I18nProvider,
+ type DataProvider,
+ type IResourceItem,
+ type RouterProvider,
+ type IRefineOptions,
+} from "@refinedev/core";
+
+import { MockJSONServer, mockRouterProvider } from "./dataMocks";
+
+export interface ITestWrapperProps {
+ dataProvider?: DataProvider;
+ routerProvider?: RouterProvider;
+ authProvider?: AuthProvider;
+ resources?: IResourceItem[];
+ notificationProvider?: NotificationProvider;
+ accessControlProvider?: AccessControlProvider;
+ i18nProvider?: I18nProvider;
+ routerInitialEntries?: string[];
+ options?: IRefineOptions;
+}
+
+export const TestWrapper: (
+ props: ITestWrapperProps,
+) => React.FC<{ children?: ReactNode }> = ({
+ dataProvider,
+ authProvider,
+ routerProvider,
+ resources,
+ notificationProvider,
+ accessControlProvider,
+ routerInitialEntries,
+ i18nProvider,
+ options,
+}) => {
+ return ({ children }): React.ReactElement => {
+ const rootRoute = createRootRoute({
+ component: () => ,
+ });
+
+ const routes = [
+ "/",
+ "/login",
+ "/posts",
+ "/posts/create",
+ "/posts/$id",
+ "/posts/$id/edit",
+ "/posts/show/$id",
+ "/posts/edit/$id",
+ "/posts/clone/$id",
+ "/categories",
+ "/categories/$id",
+ "/categories/show/$id",
+ ].map((path) =>
+ createRoute({
+ getParentRoute: () => rootRoute,
+ path,
+ component: () => null,
+ }),
+ );
+
+ const router = createRouter({
+ routeTree: rootRoute.addChildren(routes),
+ history: createMemoryHistory({
+ initialEntries: routerInitialEntries ?? ["/"],
+ }),
+ defaultPreload: false,
+ InnerWrap: ({ children: routerChildren }) => (
+
+ {children}
+ {routerChildren}
+
+ ),
+ });
+
+ return ;
+ };
+};
+
+export {
+ MockJSONServer,
+ MockAccessControlProvider,
+ MockLiveProvider,
+} from "./dataMocks";
+
+// re-export everything
+export * from "@testing-library/react";
diff --git a/packages/tanstack-router/src/test/vitest.setup.ts b/packages/tanstack-router/src/test/vitest.setup.ts
new file mode 100644
index 0000000000000..816c69dbcacb1
--- /dev/null
+++ b/packages/tanstack-router/src/test/vitest.setup.ts
@@ -0,0 +1,30 @@
+import "@testing-library/jest-dom/vitest";
+import "@testing-library/react";
+import { configure } from "@testing-library/dom";
+import { vi } from "vitest";
+
+vi.setConfig({ testTimeout: 20000 });
+
+configure({
+ asyncUtilTimeout: 10000,
+});
+
+/** Antd mocks */
+window.matchMedia = vi.fn().mockImplementation((query) => {
+ return {
+ matches: false,
+ media: query,
+ onchange: null,
+ addListener: vi.fn(),
+ removeListener: vi.fn(),
+ };
+});
+
+// TextEncoder/TextDecoder globals for Node.js environment
+if (typeof global !== "undefined") {
+ global.TextEncoder = TextEncoder;
+ global.TextDecoder = TextDecoder;
+}
+
+window.scroll = vi.fn();
+window.alert = vi.fn();
diff --git a/packages/tanstack-router/src/unsaved-changes-notifier.tsx b/packages/tanstack-router/src/unsaved-changes-notifier.tsx
new file mode 100644
index 0000000000000..474c57524ecf0
--- /dev/null
+++ b/packages/tanstack-router/src/unsaved-changes-notifier.tsx
@@ -0,0 +1,68 @@
+import React from "react";
+import { useTranslate, useWarnAboutChange } from "@refinedev/core";
+import { useBlocker, useLocation } from "@tanstack/react-router";
+
+type UnsavedChangesNotifierProps = {
+ translationKey?: string;
+ message?: string;
+};
+
+export const UnsavedChangesNotifier: React.FC = ({
+ translationKey = "warnWhenUnsavedChanges",
+ message = "Are you sure you want to leave? You have unsaved changes.",
+}) => {
+ const translate = useTranslate();
+ const location = useLocation();
+ const { warnWhen, setWarnWhen } = useWarnAboutChange();
+
+ React.useEffect(() => {
+ return () => setWarnWhen?.(false);
+ }, [location.pathname, setWarnWhen]);
+
+ const warnMessage = React.useMemo(() => {
+ return translate(translationKey, message);
+ }, [translationKey, message, translate]);
+
+ const serializedSearch = React.useMemo(() => {
+ return JSON.stringify(location.search);
+ }, [location.search]);
+
+ const blocker = useBlocker({
+ shouldBlockFn: ({ next }) => {
+ if (!warnWhen) {
+ return false;
+ }
+
+ return (
+ next.pathname !== location.pathname ||
+ JSON.stringify(next.search) !== serializedSearch
+ );
+ },
+ enableBeforeUnload: warnWhen,
+ withResolver: true,
+ }) as
+ | {
+ status: string;
+ proceed?: () => void;
+ reset?: () => void;
+ }
+ | undefined;
+
+ React.useEffect(() => {
+ if (blocker?.status !== "blocked") {
+ return;
+ }
+
+ const confirm = window.confirm(warnMessage);
+
+ if (confirm) {
+ setWarnWhen?.(false);
+ blocker.proceed?.();
+ return;
+ }
+
+ blocker.reset?.();
+ }, [blocker, blocker?.status, setWarnWhen, warnMessage]);
+
+ return null;
+};
diff --git a/packages/tanstack-router/src/use-document-title.ts b/packages/tanstack-router/src/use-document-title.ts
new file mode 100644
index 0000000000000..d17597d3e21fe
--- /dev/null
+++ b/packages/tanstack-router/src/use-document-title.ts
@@ -0,0 +1,26 @@
+import { useTranslate } from "@refinedev/core";
+import { useEffect } from "react";
+
+type Title = string | { i18nKey: string };
+
+export const useDocumentTitle = (title?: Title) => {
+ const translate = useTranslate();
+
+ useEffect(() => {
+ if (!title) return;
+
+ if (typeof title === "string") {
+ document.title = translate(title);
+ } else {
+ document.title = translate(title.i18nKey);
+ }
+ }, [title]);
+
+ return (title: Title) => {
+ if (typeof title === "string") {
+ document.title = translate(title);
+ } else {
+ document.title = translate(title.i18nKey);
+ }
+ };
+};
diff --git a/packages/tanstack-router/tsconfig.declarations.json b/packages/tanstack-router/tsconfig.declarations.json
new file mode 100644
index 0000000000000..f372d3ce688cb
--- /dev/null
+++ b/packages/tanstack-router/tsconfig.declarations.json
@@ -0,0 +1,21 @@
+{
+ "extends": "./tsconfig.json",
+ "exclude": [
+ "node_modules",
+ "dist",
+ "test",
+ "../test/**/*",
+ "**/*.spec.ts",
+ "**/*.test.ts",
+ "**/*.spec.tsx",
+ "**/*.test.tsx"
+ ],
+ "compilerOptions": {
+ "outDir": "dist",
+ "declarationDir": "dist",
+ "declaration": true,
+ "emitDeclarationOnly": true,
+ "noEmit": false,
+ "declarationMap": true
+ }
+}
diff --git a/packages/tanstack-router/tsconfig.json b/packages/tanstack-router/tsconfig.json
new file mode 100644
index 0000000000000..33c73363578dd
--- /dev/null
+++ b/packages/tanstack-router/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "include": ["src", "types"],
+ "extends": "../../tsconfig.build.json",
+ "compilerOptions": {
+ "rootDir": "src",
+ "baseUrl": ""
+ }
+}
diff --git a/packages/tanstack-router/tsup.config.ts b/packages/tanstack-router/tsup.config.ts
new file mode 100644
index 0000000000000..f468bacfccaf4
--- /dev/null
+++ b/packages/tanstack-router/tsup.config.ts
@@ -0,0 +1,30 @@
+import { defineConfig } from "tsup";
+import { NodeResolvePlugin } from "@esbuild-plugins/node-resolve";
+
+export default defineConfig((options) => ({
+ entry: {
+ index: "src/index.ts",
+ },
+ outDir: "dist",
+ splitting: false,
+ sourcemap: true,
+ clean: false,
+ minify: false,
+ format: ["cjs", "esm"],
+ outExtension: ({ format }) => ({ js: format === "cjs" ? ".cjs" : ".mjs" }),
+ platform: "browser",
+ esbuildPlugins: [
+ NodeResolvePlugin({
+ extensions: [".js", "ts", "tsx", "jsx"],
+ onResolved: (resolved) => {
+ if (resolved.includes("node_modules")) {
+ return {
+ external: true,
+ };
+ }
+ return resolved;
+ },
+ }),
+ ],
+ onSuccess: options.watch ? "pnpm types" : undefined,
+}));
diff --git a/packages/tanstack-router/vitest.config.mts b/packages/tanstack-router/vitest.config.mts
new file mode 100644
index 0000000000000..9d65ddd6b3118
--- /dev/null
+++ b/packages/tanstack-router/vitest.config.mts
@@ -0,0 +1,24 @@
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+ test: {
+ environment: "jsdom",
+ setupFiles: ["./src/test/vitest.setup.ts"],
+ coverage: {
+ provider: "v8",
+ reporter: ["text", "json", "html"],
+ reportsDirectory: "./coverage",
+ exclude: ["src/index.ts"],
+ },
+ globals: true,
+ },
+ resolve: {
+ alias: {
+ // Handle .js extension mapping
+ "^(..?/.+)\\.js?$": "$1",
+ },
+ },
+ esbuild: {
+ target: "node22",
+ },
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f71f8633d05b3..d2f643594f6a6 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -13401,6 +13401,52 @@ importers:
specifier: ^2.1.8
version: 2.1.9(@types/node@20.5.1)(@vitest/ui@2.1.9)(happy-dom@15.11.7)(jsdom@26.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(lightningcss@1.30.1)(msw@2.11.2(@types/node@20.5.1)(typescript@5.8.3))(sass@1.75.0)(terser@5.30.4)
+ packages/tanstack-router:
+ dependencies:
+ '@tanstack/react-router':
+ specifier: ^1.131.35
+ version: 1.131.35(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@types/react':
+ specifier: ^18.0.0 || ^19.0.0
+ version: 19.1.8
+ '@types/react-dom':
+ specifier: ^18.0.0 || ^19.0.0
+ version: 19.1.6(@types/react@19.1.8)
+ react:
+ specifier: ^18.0.0 || ^19.0.0
+ version: 19.1.0
+ react-dom:
+ specifier: ^18.0.0 || ^19.0.0
+ version: 19.1.0(react@19.1.0)
+ devDependencies:
+ '@esbuild-plugins/node-resolve':
+ specifier: ^0.1.4
+ version: 0.1.4(esbuild@0.21.5)
+ '@refinedev/core':
+ specifier: ^5.0.10
+ version: link:../core
+ '@tanstack/react-router':
+ specifier: ^1.131.35
+ version: 1.131.35(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@types/node':
+ specifier: ^20
+ version: 20.5.1
+ '@vitest/ui':
+ specifier: ^2.1.8
+ version: 2.1.9(vitest@2.1.9)
+ tslib:
+ specifier: ^2.6.2
+ version: 2.6.2
+ tsup:
+ specifier: ^6.7.0
+ version: 6.7.0(postcss@8.5.3)(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.8.3))(typescript@5.8.3)
+ typescript:
+ specifier: ^5.8.3
+ version: 5.8.3
+ vitest:
+ specifier: ^2.1.8
+ version: 2.1.9(@types/node@20.5.1)(@vitest/ui@2.1.9)(happy-dom@15.11.7)(jsdom@26.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(lightningcss@1.30.1)(msw@2.11.2(@types/node@20.5.1)(typescript@5.8.3))(sass@1.75.0)(terser@5.30.4)
+
packages/react-table:
dependencies:
'@tanstack/react-table':
@@ -20228,6 +20274,23 @@ packages:
peerDependencies:
react: ^18 || ^19
+ '@tanstack/history@1.131.2':
+ resolution: {integrity: sha512-cs1WKawpXIe+vSTeiZUuSBy8JFjEuDgdMKZFRLKwQysKo8y2q6Q1HvS74Yw+m5IhOW1nTZooa6rlgdfXcgFAaw==}
+ engines: {node: '>=12'}
+
+ '@tanstack/react-router@1.131.35':
+ resolution: {integrity: sha512-2mwHgwoSs4wih67jfl2TjcF4enYpLpY0TljE+Sl1njZ01CWLrrQgjQ6tEuVA24Pm5re4V01A3abKvDtN1miQ9Q==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ react: '>=18.0.0 || >=19.0.0'
+ react-dom: '>=18.0.0 || >=19.0.0'
+
+ '@tanstack/react-store@0.7.7':
+ resolution: {integrity: sha512-qqT0ufegFRDGSof9D/VqaZgjNgp4tRPHZIJq2+QIHkMUtHjaJ0lYrrXjeIUJvjnTbgPfSD1XgOMEt0lmANn6Zg==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
'@tanstack/react-table@8.16.0':
resolution: {integrity: sha512-rKRjnt8ostqN2fercRVOIH/dq7MAmOENCMvVlKx6P9Iokhh6woBGnIZEkqsY/vEJf1jN3TqLOb34xQGLVRuhAg==}
engines: {node: '>=12'}
@@ -20235,6 +20298,13 @@ packages:
react: '>=16.8'
react-dom: '>=16.8'
+ '@tanstack/router-core@1.131.35':
+ resolution: {integrity: sha512-wS+Tcczo3+63LbrRKQGrpUSa9yws0V/fg32KK/tOi0BDlloVM3KTED3UP2hMVyqaMgts6jK7n1b/cEGWOlLdAA==}
+ engines: {node: '>=12'}
+
+ '@tanstack/store@0.7.7':
+ resolution: {integrity: sha512-xa6pTan1bcaqYDS9BDpSiS63qa6EoDkPN9RsRaxHuDdVDNntzq3xNwR5YKTU/V3SkSyC9T4YVOPh2zRQN0nhIQ==}
+
'@tanstack/react-virtual@3.4.0':
resolution: {integrity: sha512-GZN4xn/Tg5w7gvYeVcMVCeL4pEyUhvg+Cp6KX2Z01C4FRNxIWMgIQ9ibgMarNQfo+gt0PVLcEER4A9sNv/jlow==}
peerDependencies:
@@ -22556,6 +22626,9 @@ packages:
cookie-signature@1.0.6:
resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}
+ cookie-es@1.2.3:
+ resolution: {integrity: sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==}
+
cookie-signature@1.2.1:
resolution: {integrity: sha512-78KWk9T26NhzXtuL26cIJ8/qNHANyJ/ZYrmEXFzUmhZdjpBv+DlWlOANRTGBt48YcyslsLrj0bMLFTmXvLRCOw==}
engines: {node: '>=6.6.0'}
@@ -25863,6 +25936,10 @@ packages:
isarray@2.0.5:
resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
+ isbot@5.1.38:
+ resolution: {integrity: sha512-Cus2702JamTNMEY4zTP+TShgq/3qzjvGcBC4XMOV45BLaxD4iUFENkqu7ZhFeSzwNsCSZLjnGlihDQznnpnEEA==}
+ engines: {node: '>=18'}
+
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
@@ -30742,6 +30819,14 @@ packages:
serialize-javascript@6.0.2:
resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==}
+ seroval@1.5.2:
+ resolution: {integrity: sha512-xcRN39BdsnO9Tf+VzsE7b3JyTJASItIV1FVFewJKCFcW4s4haIKS3e6vj8PGB9qBwC7tnuOywQMdv5N4qkzi7Q==}
+
+ seroval-plugins@1.5.2:
+ resolution: {integrity: sha512-qpY0Cl+fKYFn4GOf3cMiq6l72CpuVaawb6ILjubOQ+diJ54LfOWaSSPsaswN8DRPIPW4Yq+tE1k5aKd7ILyaFg==}
+ peerDependencies:
+ seroval: ^1.0
+
serve-index@1.9.1:
resolution: {integrity: sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==}
engines: {node: '>= 0.8.0'}
@@ -31637,6 +31722,9 @@ packages:
tiny-invariant@1.3.3:
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
+ tiny-warning@1.0.3:
+ resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==}
+
tinybench@2.9.0:
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
@@ -42675,12 +42763,44 @@ snapshots:
'@tanstack/query-core': 5.81.5
react: 19.1.0
+ '@tanstack/history@1.131.2': {}
+
+ '@tanstack/react-router@1.131.35(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@tanstack/history': 1.131.2
+ '@tanstack/react-store': 0.7.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@tanstack/router-core': 1.131.35
+ isbot: 5.1.38
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ tiny-invariant: 1.3.3
+ tiny-warning: 1.0.3
+
+ '@tanstack/react-store@0.7.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@tanstack/store': 0.7.7
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ use-sync-external-store: 1.6.0(react@19.1.0)
+
'@tanstack/react-table@8.16.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@tanstack/table-core': 8.16.0
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
+ '@tanstack/router-core@1.131.35':
+ dependencies:
+ '@tanstack/history': 1.131.2
+ '@tanstack/store': 0.7.7
+ cookie-es: 1.2.3
+ seroval: 1.5.2
+ seroval-plugins: 1.5.2(seroval@1.5.2)
+ tiny-invariant: 1.3.3
+ tiny-warning: 1.0.3
+
+ '@tanstack/store@0.7.7': {}
+
'@tanstack/react-virtual@3.4.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@tanstack/virtual-core': 3.4.0
@@ -45672,6 +45792,8 @@ snapshots:
cookie-signature@1.0.6: {}
+ cookie-es@1.2.3: {}
+
cookie-signature@1.2.1: {}
cookie@0.4.2: {}
@@ -49785,6 +49907,8 @@ snapshots:
isarray@2.0.5: {}
+ isbot@5.1.38: {}
+
isexe@2.0.0: {}
isexe@3.1.1: {}
@@ -56336,6 +56460,12 @@ snapshots:
dependencies:
randombytes: 2.1.0
+ seroval@1.5.2: {}
+
+ seroval-plugins@1.5.2(seroval@1.5.2):
+ dependencies:
+ seroval: 1.5.2
+
serve-index@1.9.1:
dependencies:
accepts: 1.3.8
@@ -57492,6 +57622,8 @@ snapshots:
tiny-invariant@1.3.3: {}
+ tiny-warning@1.0.3: {}
+
tinybench@2.9.0: {}
tinycolor2@1.6.0: {}