@tanstack/react-router 1.89.1 → 1.90.0

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.
@@ -1,23 +1,68 @@
1
- import { BlockerFn } from '@tanstack/history';
2
- import { ReactNode } from './route.js';
3
- type BlockerResolver = {
4
- status: 'idle' | 'blocked';
1
+ import { HistoryAction } from '@tanstack/history';
2
+ import { AnyRoute } from './route.js';
3
+ import { ParseRoute } from './routeInfo.js';
4
+ import { AnyRouter, RegisteredRouter } from './router.js';
5
+ import * as React from 'react';
6
+ interface ShouldBlockFnLocation<out TRouteId, out TFullPath, out TAllParams, out TFullSearchSchema> {
7
+ routeId: TRouteId;
8
+ fullPath: TFullPath;
9
+ pathname: string;
10
+ params: TAllParams;
11
+ search: TFullSearchSchema;
12
+ }
13
+ type MakeShouldBlockFnLocationUnion<TRouter extends AnyRouter = RegisteredRouter, TRoute extends AnyRoute = ParseRoute<TRouter['routeTree']>> = TRoute extends any ? ShouldBlockFnLocation<TRoute['id'], TRoute['fullPath'], TRoute['types']['allParams'], TRoute['types']['fullSearchSchema']> : never;
14
+ type BlockerResolver<TRouter extends AnyRouter = RegisteredRouter> = {
15
+ status: 'blocked';
16
+ current: MakeShouldBlockFnLocationUnion<TRouter>;
17
+ next: MakeShouldBlockFnLocationUnion<TRouter>;
18
+ action: HistoryAction;
5
19
  proceed: () => void;
6
20
  reset: () => void;
21
+ } | {
22
+ status: 'idle';
23
+ current: undefined;
24
+ next: undefined;
25
+ action: undefined;
26
+ proceed: undefined;
27
+ reset: undefined;
7
28
  };
8
- type BlockerOpts = {
9
- blockerFn?: BlockerFn;
29
+ type ShouldBlockFnArgs<TRouter extends AnyRouter = RegisteredRouter> = {
30
+ current: MakeShouldBlockFnLocationUnion<TRouter>;
31
+ next: MakeShouldBlockFnLocationUnion<TRouter>;
32
+ action: HistoryAction;
33
+ };
34
+ export type ShouldBlockFn<TRouter extends AnyRouter = RegisteredRouter> = (args: ShouldBlockFnArgs<TRouter>) => boolean | Promise<boolean>;
35
+ export type UseBlockerOpts<TRouter extends AnyRouter = RegisteredRouter, TWithResolver extends boolean = boolean> = {
36
+ shouldBlockFn: ShouldBlockFn<TRouter>;
37
+ enableBeforeUnload?: boolean | (() => boolean);
38
+ disabled?: boolean;
39
+ withResolver?: TWithResolver;
40
+ };
41
+ type LegacyBlockerFn = () => Promise<any> | any;
42
+ type LegacyBlockerOpts = {
43
+ blockerFn?: LegacyBlockerFn;
10
44
  condition?: boolean | any;
11
45
  };
12
- export declare function useBlocker(blockerFnOrOpts?: BlockerOpts): BlockerResolver;
46
+ export declare function useBlocker<TRouter extends AnyRouter = RegisteredRouter, TWithResolver extends boolean = false>(opts: UseBlockerOpts<TRouter, TWithResolver>): TWithResolver extends true ? BlockerResolver<TRouter> : void;
47
+ /**
48
+ * @deprecated Use the shouldBlockFn property instead
49
+ */
50
+ export declare function useBlocker(blockerFnOrOpts?: LegacyBlockerOpts): BlockerResolver;
13
51
  /**
14
- * @deprecated Use the BlockerOpts object syntax instead
52
+ * @deprecated Use the UseBlockerOpts object syntax instead
15
53
  */
16
- export declare function useBlocker(blockerFn?: BlockerFn, condition?: boolean | any): BlockerResolver;
17
- export declare function Block({ blockerFn, condition, children }: PromptProps): any;
18
- export type PromptProps = {
19
- blockerFn?: BlockerFn;
54
+ export declare function useBlocker(blockerFn?: LegacyBlockerFn, condition?: boolean | any): BlockerResolver;
55
+ export declare function Block<TRouter extends AnyRouter = RegisteredRouter, TWithResolver extends boolean = boolean>(opts: PromptProps<TRouter, TWithResolver>): React.ReactNode;
56
+ /**
57
+ * @deprecated Use the UseBlockerOpts property instead
58
+ */
59
+ export declare function Block(opts: LegacyPromptProps): React.ReactNode;
60
+ type LegacyPromptProps = {
61
+ blockerFn?: LegacyBlockerFn;
20
62
  condition?: boolean | any;
21
- children?: ReactNode | (({ proceed, reset }: BlockerResolver) => ReactNode);
63
+ children?: React.ReactNode | ((params: BlockerResolver) => React.ReactNode);
64
+ };
65
+ type PromptProps<TRouter extends AnyRouter = RegisteredRouter, TWithResolver extends boolean = boolean, TParams = TWithResolver extends true ? BlockerResolver<TRouter> : void> = UseBlockerOpts<TRouter, TWithResolver> & {
66
+ children?: React.ReactNode | ((params: TParams) => React.ReactNode);
22
67
  };
23
68
  export {};
@@ -1,46 +1,134 @@
1
1
  import * as React from "react";
2
2
  import { useRouter } from "./useRouter.js";
3
- function useBlocker(blockerFnOrOpts, condition) {
4
- const { blockerFn, blockerCondition } = blockerFnOrOpts ? typeof blockerFnOrOpts === "function" ? { blockerFn: blockerFnOrOpts, blockerCondition: condition ?? true } : {
5
- blockerFn: blockerFnOrOpts.blockerFn,
6
- blockerCondition: blockerFnOrOpts.condition ?? true
7
- } : { blockerFn: void 0, blockerCondition: condition ?? true };
8
- const { history } = useRouter();
3
+ function _resolveBlockerOpts(opts, condition) {
4
+ if (opts === void 0) {
5
+ return {
6
+ shouldBlockFn: () => true,
7
+ withResolver: false
8
+ };
9
+ }
10
+ if ("shouldBlockFn" in opts) {
11
+ return opts;
12
+ }
13
+ if (typeof opts === "function") {
14
+ const shouldBlock2 = Boolean(condition ?? true);
15
+ const _customBlockerFn2 = async () => {
16
+ if (shouldBlock2) return await opts();
17
+ return false;
18
+ };
19
+ return {
20
+ shouldBlockFn: _customBlockerFn2,
21
+ enableBeforeUnload: shouldBlock2,
22
+ withResolver: false
23
+ };
24
+ }
25
+ const shouldBlock = Boolean(opts.condition ?? true);
26
+ const fn = opts.blockerFn;
27
+ const _customBlockerFn = async () => {
28
+ if (shouldBlock && fn !== void 0) {
29
+ return await fn();
30
+ }
31
+ return shouldBlock;
32
+ };
33
+ return {
34
+ shouldBlockFn: _customBlockerFn,
35
+ enableBeforeUnload: shouldBlock,
36
+ withResolver: fn === void 0
37
+ };
38
+ }
39
+ function useBlocker(opts, condition) {
40
+ const {
41
+ shouldBlockFn,
42
+ enableBeforeUnload = true,
43
+ disabled = false,
44
+ withResolver = false
45
+ } = _resolveBlockerOpts(opts, condition);
46
+ const router = useRouter();
47
+ const { history } = router;
9
48
  const [resolver, setResolver] = React.useState({
10
49
  status: "idle",
11
- proceed: () => {
12
- },
13
- reset: () => {
14
- }
50
+ current: void 0,
51
+ next: void 0,
52
+ action: void 0,
53
+ proceed: void 0,
54
+ reset: void 0
15
55
  });
16
56
  React.useEffect(() => {
17
- const blockerFnComposed = async () => {
18
- if (blockerFn) {
19
- return await blockerFn();
57
+ const blockerFnComposed = async (blockerFnArgs) => {
58
+ function getLocation(location) {
59
+ const parsedLocation = router.parseLocation(void 0, location);
60
+ const matchedRoutes = router.getMatchedRoutes(parsedLocation);
61
+ if (matchedRoutes.foundRoute === void 0) {
62
+ throw new Error(`No route found for location ${location.href}`);
63
+ }
64
+ return {
65
+ routeId: matchedRoutes.foundRoute.id,
66
+ fullPath: matchedRoutes.foundRoute.fullPath,
67
+ pathname: parsedLocation.pathname,
68
+ params: matchedRoutes.routeParams,
69
+ search: parsedLocation.search
70
+ };
71
+ }
72
+ const current = getLocation(blockerFnArgs.currentLocation);
73
+ const next = getLocation(blockerFnArgs.nextLocation);
74
+ const shouldBlock = await shouldBlockFn({
75
+ action: blockerFnArgs.action,
76
+ current,
77
+ next
78
+ });
79
+ if (!withResolver) {
80
+ return shouldBlock;
81
+ }
82
+ if (!shouldBlock) {
83
+ return false;
20
84
  }
21
85
  const promise = new Promise((resolve) => {
22
86
  setResolver({
23
87
  status: "blocked",
24
- proceed: () => resolve(true),
25
- reset: () => resolve(false)
88
+ current,
89
+ next,
90
+ action: blockerFnArgs.action,
91
+ proceed: () => resolve(false),
92
+ reset: () => resolve(true)
26
93
  });
27
94
  });
28
95
  const canNavigateAsync = await promise;
29
96
  setResolver({
30
97
  status: "idle",
31
- proceed: () => {
32
- },
33
- reset: () => {
34
- }
98
+ current: void 0,
99
+ next: void 0,
100
+ action: void 0,
101
+ proceed: void 0,
102
+ reset: void 0
35
103
  });
36
104
  return canNavigateAsync;
37
105
  };
38
- return !blockerCondition ? void 0 : history.block(blockerFnComposed);
39
- }, [blockerFn, blockerCondition, history]);
106
+ return disabled ? void 0 : history.block({ blockerFn: blockerFnComposed, enableBeforeUnload });
107
+ }, [shouldBlockFn, enableBeforeUnload, disabled, withResolver, history]);
40
108
  return resolver;
41
109
  }
42
- function Block({ blockerFn, condition, children }) {
43
- const resolver = useBlocker({ blockerFn, condition });
110
+ const _resolvePromptBlockerArgs = (props) => {
111
+ if ("shouldBlockFn" in props) {
112
+ return { ...props };
113
+ }
114
+ const shouldBlock = Boolean(props.condition ?? true);
115
+ const fn = props.blockerFn;
116
+ const _customBlockerFn = async () => {
117
+ if (shouldBlock && fn !== void 0) {
118
+ return await fn();
119
+ }
120
+ return shouldBlock;
121
+ };
122
+ return {
123
+ shouldBlockFn: _customBlockerFn,
124
+ enableBeforeUnload: shouldBlock,
125
+ withResolver: fn === void 0
126
+ };
127
+ };
128
+ function Block(opts) {
129
+ const { children, ...rest } = opts;
130
+ const args = _resolvePromptBlockerArgs(rest);
131
+ const resolver = useBlocker(args);
44
132
  return children ? typeof children === "function" ? children(resolver) : children : null;
45
133
  }
46
134
  export {
@@ -1 +1 @@
1
- {"version":3,"file":"useBlocker.js","sources":["../../src/useBlocker.tsx"],"sourcesContent":["import * as React from 'react'\nimport { useRouter } from './useRouter'\nimport type { BlockerFn } from '@tanstack/history'\nimport type { ReactNode } from './route'\n\ntype BlockerResolver = {\n status: 'idle' | 'blocked'\n proceed: () => void\n reset: () => void\n}\n\ntype BlockerOpts = {\n blockerFn?: BlockerFn\n condition?: boolean | any\n}\n\nexport function useBlocker(blockerFnOrOpts?: BlockerOpts): BlockerResolver\n\n/**\n * @deprecated Use the BlockerOpts object syntax instead\n */\nexport function useBlocker(\n blockerFn?: BlockerFn,\n condition?: boolean | any,\n): BlockerResolver\n\nexport function useBlocker(\n blockerFnOrOpts?: BlockerFn | BlockerOpts,\n condition?: boolean | any,\n): BlockerResolver {\n const { blockerFn, blockerCondition } = blockerFnOrOpts\n ? typeof blockerFnOrOpts === 'function'\n ? { blockerFn: blockerFnOrOpts, blockerCondition: condition ?? true }\n : {\n blockerFn: blockerFnOrOpts.blockerFn,\n blockerCondition: blockerFnOrOpts.condition ?? true,\n }\n : { blockerFn: undefined, blockerCondition: condition ?? true }\n const { history } = useRouter()\n\n const [resolver, setResolver] = React.useState<BlockerResolver>({\n status: 'idle',\n proceed: () => {},\n reset: () => {},\n })\n\n React.useEffect(() => {\n const blockerFnComposed = async () => {\n // If a function is provided, it takes precedence over the promise blocker\n if (blockerFn) {\n return await blockerFn()\n }\n\n const promise = new Promise<boolean>((resolve) => {\n setResolver({\n status: 'blocked',\n proceed: () => resolve(true),\n reset: () => resolve(false),\n })\n })\n\n const canNavigateAsync = await promise\n\n setResolver({\n status: 'idle',\n proceed: () => {},\n reset: () => {},\n })\n\n return canNavigateAsync\n }\n\n return !blockerCondition ? undefined : history.block(blockerFnComposed)\n }, [blockerFn, blockerCondition, history])\n\n return resolver\n}\n\nexport function Block({ blockerFn, condition, children }: PromptProps) {\n const resolver = useBlocker({ blockerFn, condition })\n return children\n ? typeof children === 'function'\n ? children(resolver)\n : children\n : null\n}\n\nexport type PromptProps = {\n blockerFn?: BlockerFn\n condition?: boolean | any\n children?: ReactNode | (({ proceed, reset }: BlockerResolver) => ReactNode)\n}\n"],"names":[],"mappings":";;AA0BgB,SAAA,WACd,iBACA,WACiB;AACjB,QAAM,EAAE,WAAW,qBAAqB,kBACpC,OAAO,oBAAoB,aACzB,EAAE,WAAW,iBAAiB,kBAAkB,aAAa,SAC7D;AAAA,IACE,WAAW,gBAAgB;AAAA,IAC3B,kBAAkB,gBAAgB,aAAa;AAAA,EAAA,IAEnD,EAAE,WAAW,QAAW,kBAAkB,aAAa,KAAK;AAC1D,QAAA,EAAE,QAAQ,IAAI,UAAU;AAE9B,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAA0B;AAAA,IAC9D,QAAQ;AAAA,IACR,SAAS,MAAM;AAAA,IAAC;AAAA,IAChB,OAAO,MAAM;AAAA,IAAA;AAAA,EAAC,CACf;AAED,QAAM,UAAU,MAAM;AACpB,UAAM,oBAAoB,YAAY;AAEpC,UAAI,WAAW;AACb,eAAO,MAAM,UAAU;AAAA,MAAA;AAGzB,YAAM,UAAU,IAAI,QAAiB,CAAC,YAAY;AACpC,oBAAA;AAAA,UACV,QAAQ;AAAA,UACR,SAAS,MAAM,QAAQ,IAAI;AAAA,UAC3B,OAAO,MAAM,QAAQ,KAAK;AAAA,QAAA,CAC3B;AAAA,MAAA,CACF;AAED,YAAM,mBAAmB,MAAM;AAEnB,kBAAA;AAAA,QACV,QAAQ;AAAA,QACR,SAAS,MAAM;AAAA,QAAC;AAAA,QAChB,OAAO,MAAM;AAAA,QAAA;AAAA,MAAC,CACf;AAEM,aAAA;AAAA,IACT;AAEA,WAAO,CAAC,mBAAmB,SAAY,QAAQ,MAAM,iBAAiB;AAAA,EACrE,GAAA,CAAC,WAAW,kBAAkB,OAAO,CAAC;AAElC,SAAA;AACT;AAEO,SAAS,MAAM,EAAE,WAAW,WAAW,YAAyB;AACrE,QAAM,WAAW,WAAW,EAAE,WAAW,WAAW;AACpD,SAAO,WACH,OAAO,aAAa,aAClB,SAAS,QAAQ,IACjB,WACF;AACN;"}
1
+ {"version":3,"file":"useBlocker.js","sources":["../../src/useBlocker.tsx"],"sourcesContent":["import * as React from 'react'\nimport { useRouter } from './useRouter'\nimport type {\n BlockerFnArgs,\n HistoryAction,\n HistoryLocation,\n} from '@tanstack/history'\nimport type { AnyRoute } from './route'\nimport type { ParseRoute } from './routeInfo'\nimport type { AnyRouter, RegisteredRouter } from './router'\n\ninterface ShouldBlockFnLocation<\n out TRouteId,\n out TFullPath,\n out TAllParams,\n out TFullSearchSchema,\n> {\n routeId: TRouteId\n fullPath: TFullPath\n pathname: string\n params: TAllParams\n search: TFullSearchSchema\n}\n\ntype AnyShouldBlockFnLocation = ShouldBlockFnLocation<any, any, any, any>\ntype MakeShouldBlockFnLocationUnion<\n TRouter extends AnyRouter = RegisteredRouter,\n TRoute extends AnyRoute = ParseRoute<TRouter['routeTree']>,\n> = TRoute extends any\n ? ShouldBlockFnLocation<\n TRoute['id'],\n TRoute['fullPath'],\n TRoute['types']['allParams'],\n TRoute['types']['fullSearchSchema']\n >\n : never\n\ntype BlockerResolver<TRouter extends AnyRouter = RegisteredRouter> =\n | {\n status: 'blocked'\n current: MakeShouldBlockFnLocationUnion<TRouter>\n next: MakeShouldBlockFnLocationUnion<TRouter>\n action: HistoryAction\n proceed: () => void\n reset: () => void\n }\n | {\n status: 'idle'\n current: undefined\n next: undefined\n action: undefined\n proceed: undefined\n reset: undefined\n }\n\ntype ShouldBlockFnArgs<TRouter extends AnyRouter = RegisteredRouter> = {\n current: MakeShouldBlockFnLocationUnion<TRouter>\n next: MakeShouldBlockFnLocationUnion<TRouter>\n action: HistoryAction\n}\n\nexport type ShouldBlockFn<TRouter extends AnyRouter = RegisteredRouter> = (\n args: ShouldBlockFnArgs<TRouter>,\n) => boolean | Promise<boolean>\nexport type UseBlockerOpts<\n TRouter extends AnyRouter = RegisteredRouter,\n TWithResolver extends boolean = boolean,\n> = {\n shouldBlockFn: ShouldBlockFn<TRouter>\n enableBeforeUnload?: boolean | (() => boolean)\n disabled?: boolean\n withResolver?: TWithResolver\n}\n\ntype LegacyBlockerFn = () => Promise<any> | any\ntype LegacyBlockerOpts = {\n blockerFn?: LegacyBlockerFn\n condition?: boolean | any\n}\n\nfunction _resolveBlockerOpts(\n opts?: UseBlockerOpts | LegacyBlockerOpts | LegacyBlockerFn,\n condition?: boolean | any,\n): UseBlockerOpts {\n if (opts === undefined) {\n return {\n shouldBlockFn: () => true,\n withResolver: false,\n }\n }\n\n if ('shouldBlockFn' in opts) {\n return opts\n }\n\n if (typeof opts === 'function') {\n const shouldBlock = Boolean(condition ?? true)\n\n const _customBlockerFn = async () => {\n if (shouldBlock) return await opts()\n return false\n }\n\n return {\n shouldBlockFn: _customBlockerFn,\n enableBeforeUnload: shouldBlock,\n withResolver: false,\n }\n }\n\n const shouldBlock = Boolean(opts.condition ?? true)\n const fn = opts.blockerFn\n\n const _customBlockerFn = async () => {\n if (shouldBlock && fn !== undefined) {\n return await fn()\n }\n return shouldBlock\n }\n\n return {\n shouldBlockFn: _customBlockerFn,\n enableBeforeUnload: shouldBlock,\n withResolver: fn === undefined,\n }\n}\n\nexport function useBlocker<\n TRouter extends AnyRouter = RegisteredRouter,\n TWithResolver extends boolean = false,\n>(\n opts: UseBlockerOpts<TRouter, TWithResolver>,\n): TWithResolver extends true ? BlockerResolver<TRouter> : void\n\n/**\n * @deprecated Use the shouldBlockFn property instead\n */\nexport function useBlocker(blockerFnOrOpts?: LegacyBlockerOpts): BlockerResolver\n\n/**\n * @deprecated Use the UseBlockerOpts object syntax instead\n */\nexport function useBlocker(\n blockerFn?: LegacyBlockerFn,\n condition?: boolean | any,\n): BlockerResolver\n\nexport function useBlocker(\n opts?: UseBlockerOpts | LegacyBlockerOpts | LegacyBlockerFn,\n condition?: boolean | any,\n): BlockerResolver | void {\n const {\n shouldBlockFn,\n enableBeforeUnload = true,\n disabled = false,\n withResolver = false,\n } = _resolveBlockerOpts(opts, condition)\n\n const router = useRouter()\n const { history } = router\n\n const [resolver, setResolver] = React.useState<BlockerResolver>({\n status: 'idle',\n current: undefined,\n next: undefined,\n action: undefined,\n proceed: undefined,\n reset: undefined,\n })\n\n React.useEffect(() => {\n const blockerFnComposed = async (blockerFnArgs: BlockerFnArgs) => {\n function getLocation(\n location: HistoryLocation,\n ): AnyShouldBlockFnLocation {\n const parsedLocation = router.parseLocation(undefined, location)\n const matchedRoutes = router.getMatchedRoutes(parsedLocation)\n if (matchedRoutes.foundRoute === undefined) {\n throw new Error(`No route found for location ${location.href}`)\n }\n return {\n routeId: matchedRoutes.foundRoute.id,\n fullPath: matchedRoutes.foundRoute.fullPath,\n pathname: parsedLocation.pathname,\n params: matchedRoutes.routeParams,\n search: parsedLocation.search,\n }\n }\n\n const current = getLocation(blockerFnArgs.currentLocation)\n const next = getLocation(blockerFnArgs.nextLocation)\n\n const shouldBlock = await shouldBlockFn({\n action: blockerFnArgs.action,\n current,\n next,\n })\n if (!withResolver) {\n return shouldBlock\n }\n\n if (!shouldBlock) {\n return false\n }\n\n const promise = new Promise<boolean>((resolve) => {\n setResolver({\n status: 'blocked',\n current,\n next,\n action: blockerFnArgs.action,\n proceed: () => resolve(false),\n reset: () => resolve(true),\n })\n })\n\n const canNavigateAsync = await promise\n setResolver({\n status: 'idle',\n current: undefined,\n next: undefined,\n action: undefined,\n proceed: undefined,\n reset: undefined,\n })\n\n return canNavigateAsync\n }\n\n return disabled\n ? undefined\n : history.block({ blockerFn: blockerFnComposed, enableBeforeUnload })\n }, [shouldBlockFn, enableBeforeUnload, disabled, withResolver, history])\n\n return resolver\n}\n\nconst _resolvePromptBlockerArgs = (\n props: PromptProps | LegacyPromptProps,\n): UseBlockerOpts => {\n if ('shouldBlockFn' in props) {\n return { ...props }\n }\n\n const shouldBlock = Boolean(props.condition ?? true)\n const fn = props.blockerFn\n\n const _customBlockerFn = async () => {\n if (shouldBlock && fn !== undefined) {\n return await fn()\n }\n return shouldBlock\n }\n\n return {\n shouldBlockFn: _customBlockerFn,\n enableBeforeUnload: shouldBlock,\n withResolver: fn === undefined,\n }\n}\n\nexport function Block<\n TRouter extends AnyRouter = RegisteredRouter,\n TWithResolver extends boolean = boolean,\n>(opts: PromptProps<TRouter, TWithResolver>): React.ReactNode\n\n/**\n * @deprecated Use the UseBlockerOpts property instead\n */\nexport function Block(opts: LegacyPromptProps): React.ReactNode\n\nexport function Block(opts: PromptProps | LegacyPromptProps): React.ReactNode {\n const { children, ...rest } = opts\n const args = _resolvePromptBlockerArgs(rest)\n\n const resolver = useBlocker(args)\n return children\n ? typeof children === 'function'\n ? children(resolver as any)\n : children\n : null\n}\n\ntype LegacyPromptProps = {\n blockerFn?: LegacyBlockerFn\n condition?: boolean | any\n children?: React.ReactNode | ((params: BlockerResolver) => React.ReactNode)\n}\n\ntype PromptProps<\n TRouter extends AnyRouter = RegisteredRouter,\n TWithResolver extends boolean = boolean,\n TParams = TWithResolver extends true ? BlockerResolver<TRouter> : void,\n> = UseBlockerOpts<TRouter, TWithResolver> & {\n children?: React.ReactNode | ((params: TParams) => React.ReactNode)\n}\n"],"names":["shouldBlock","_customBlockerFn"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/react-router",
3
- "version": "1.89.1",
3
+ "version": "1.90.0",
4
4
  "description": "Modern and scalable routing for React applications",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -53,7 +53,7 @@
53
53
  "jsesc": "^3.0.2",
54
54
  "tiny-invariant": "^1.3.3",
55
55
  "tiny-warning": "^1.0.3",
56
- "@tanstack/history": "1.87.6"
56
+ "@tanstack/history": "1.90.0"
57
57
  },
58
58
  "devDependencies": {
59
59
  "@testing-library/jest-dom": "^6.6.3",
package/src/index.tsx CHANGED
@@ -301,6 +301,7 @@ export type {
301
301
  DefaultTransformerStringify,
302
302
  } from './transformer'
303
303
 
304
+ export type { UseBlockerOpts, ShouldBlockFn } from './useBlocker'
304
305
  export { useBlocker, Block } from './useBlocker'
305
306
 
306
307
  export { useNavigate, Navigate } from './useNavigate'
package/src/link.tsx CHANGED
@@ -1012,7 +1012,7 @@ export type CreateLinkProps = LinkProps<
1012
1012
  >
1013
1013
 
1014
1014
  export type LinkComponent<TComp> = <
1015
- TRouter extends RegisteredRouter = RegisteredRouter,
1015
+ TRouter extends AnyRouter = RegisteredRouter,
1016
1016
  TFrom extends string = string,
1017
1017
  TTo extends string | undefined = undefined,
1018
1018
  TMaskFrom extends string = TFrom,
package/src/router.ts CHANGED
@@ -1058,6 +1058,7 @@ export class Router<
1058
1058
 
1059
1059
  parseLocation = (
1060
1060
  previousLocation?: ParsedLocation<FullSearchSchema<TRouteTree>>,
1061
+ locationToParse?: HistoryLocation,
1061
1062
  ): ParsedLocation<FullSearchSchema<TRouteTree>> => {
1062
1063
  const parse = ({
1063
1064
  pathname,
@@ -1078,7 +1079,7 @@ export class Router<
1078
1079
  }
1079
1080
  }
1080
1081
 
1081
- const location = parse(this.history.location)
1082
+ const location = parse(locationToParse ?? this.history.location)
1082
1083
 
1083
1084
  const { __tempLocation, __tempKey } = location.state
1084
1085
 
@@ -1,92 +1,296 @@
1
1
  import * as React from 'react'
2
2
  import { useRouter } from './useRouter'
3
- import type { BlockerFn } from '@tanstack/history'
4
- import type { ReactNode } from './route'
3
+ import type {
4
+ BlockerFnArgs,
5
+ HistoryAction,
6
+ HistoryLocation,
7
+ } from '@tanstack/history'
8
+ import type { AnyRoute } from './route'
9
+ import type { ParseRoute } from './routeInfo'
10
+ import type { AnyRouter, RegisteredRouter } from './router'
5
11
 
6
- type BlockerResolver = {
7
- status: 'idle' | 'blocked'
8
- proceed: () => void
9
- reset: () => void
12
+ interface ShouldBlockFnLocation<
13
+ out TRouteId,
14
+ out TFullPath,
15
+ out TAllParams,
16
+ out TFullSearchSchema,
17
+ > {
18
+ routeId: TRouteId
19
+ fullPath: TFullPath
20
+ pathname: string
21
+ params: TAllParams
22
+ search: TFullSearchSchema
10
23
  }
11
24
 
12
- type BlockerOpts = {
13
- blockerFn?: BlockerFn
25
+ type AnyShouldBlockFnLocation = ShouldBlockFnLocation<any, any, any, any>
26
+ type MakeShouldBlockFnLocationUnion<
27
+ TRouter extends AnyRouter = RegisteredRouter,
28
+ TRoute extends AnyRoute = ParseRoute<TRouter['routeTree']>,
29
+ > = TRoute extends any
30
+ ? ShouldBlockFnLocation<
31
+ TRoute['id'],
32
+ TRoute['fullPath'],
33
+ TRoute['types']['allParams'],
34
+ TRoute['types']['fullSearchSchema']
35
+ >
36
+ : never
37
+
38
+ type BlockerResolver<TRouter extends AnyRouter = RegisteredRouter> =
39
+ | {
40
+ status: 'blocked'
41
+ current: MakeShouldBlockFnLocationUnion<TRouter>
42
+ next: MakeShouldBlockFnLocationUnion<TRouter>
43
+ action: HistoryAction
44
+ proceed: () => void
45
+ reset: () => void
46
+ }
47
+ | {
48
+ status: 'idle'
49
+ current: undefined
50
+ next: undefined
51
+ action: undefined
52
+ proceed: undefined
53
+ reset: undefined
54
+ }
55
+
56
+ type ShouldBlockFnArgs<TRouter extends AnyRouter = RegisteredRouter> = {
57
+ current: MakeShouldBlockFnLocationUnion<TRouter>
58
+ next: MakeShouldBlockFnLocationUnion<TRouter>
59
+ action: HistoryAction
60
+ }
61
+
62
+ export type ShouldBlockFn<TRouter extends AnyRouter = RegisteredRouter> = (
63
+ args: ShouldBlockFnArgs<TRouter>,
64
+ ) => boolean | Promise<boolean>
65
+ export type UseBlockerOpts<
66
+ TRouter extends AnyRouter = RegisteredRouter,
67
+ TWithResolver extends boolean = boolean,
68
+ > = {
69
+ shouldBlockFn: ShouldBlockFn<TRouter>
70
+ enableBeforeUnload?: boolean | (() => boolean)
71
+ disabled?: boolean
72
+ withResolver?: TWithResolver
73
+ }
74
+
75
+ type LegacyBlockerFn = () => Promise<any> | any
76
+ type LegacyBlockerOpts = {
77
+ blockerFn?: LegacyBlockerFn
14
78
  condition?: boolean | any
15
79
  }
16
80
 
17
- export function useBlocker(blockerFnOrOpts?: BlockerOpts): BlockerResolver
81
+ function _resolveBlockerOpts(
82
+ opts?: UseBlockerOpts | LegacyBlockerOpts | LegacyBlockerFn,
83
+ condition?: boolean | any,
84
+ ): UseBlockerOpts {
85
+ if (opts === undefined) {
86
+ return {
87
+ shouldBlockFn: () => true,
88
+ withResolver: false,
89
+ }
90
+ }
91
+
92
+ if ('shouldBlockFn' in opts) {
93
+ return opts
94
+ }
95
+
96
+ if (typeof opts === 'function') {
97
+ const shouldBlock = Boolean(condition ?? true)
98
+
99
+ const _customBlockerFn = async () => {
100
+ if (shouldBlock) return await opts()
101
+ return false
102
+ }
103
+
104
+ return {
105
+ shouldBlockFn: _customBlockerFn,
106
+ enableBeforeUnload: shouldBlock,
107
+ withResolver: false,
108
+ }
109
+ }
110
+
111
+ const shouldBlock = Boolean(opts.condition ?? true)
112
+ const fn = opts.blockerFn
113
+
114
+ const _customBlockerFn = async () => {
115
+ if (shouldBlock && fn !== undefined) {
116
+ return await fn()
117
+ }
118
+ return shouldBlock
119
+ }
120
+
121
+ return {
122
+ shouldBlockFn: _customBlockerFn,
123
+ enableBeforeUnload: shouldBlock,
124
+ withResolver: fn === undefined,
125
+ }
126
+ }
127
+
128
+ export function useBlocker<
129
+ TRouter extends AnyRouter = RegisteredRouter,
130
+ TWithResolver extends boolean = false,
131
+ >(
132
+ opts: UseBlockerOpts<TRouter, TWithResolver>,
133
+ ): TWithResolver extends true ? BlockerResolver<TRouter> : void
134
+
135
+ /**
136
+ * @deprecated Use the shouldBlockFn property instead
137
+ */
138
+ export function useBlocker(blockerFnOrOpts?: LegacyBlockerOpts): BlockerResolver
18
139
 
19
140
  /**
20
- * @deprecated Use the BlockerOpts object syntax instead
141
+ * @deprecated Use the UseBlockerOpts object syntax instead
21
142
  */
22
143
  export function useBlocker(
23
- blockerFn?: BlockerFn,
144
+ blockerFn?: LegacyBlockerFn,
24
145
  condition?: boolean | any,
25
146
  ): BlockerResolver
26
147
 
27
148
  export function useBlocker(
28
- blockerFnOrOpts?: BlockerFn | BlockerOpts,
149
+ opts?: UseBlockerOpts | LegacyBlockerOpts | LegacyBlockerFn,
29
150
  condition?: boolean | any,
30
- ): BlockerResolver {
31
- const { blockerFn, blockerCondition } = blockerFnOrOpts
32
- ? typeof blockerFnOrOpts === 'function'
33
- ? { blockerFn: blockerFnOrOpts, blockerCondition: condition ?? true }
34
- : {
35
- blockerFn: blockerFnOrOpts.blockerFn,
36
- blockerCondition: blockerFnOrOpts.condition ?? true,
37
- }
38
- : { blockerFn: undefined, blockerCondition: condition ?? true }
39
- const { history } = useRouter()
151
+ ): BlockerResolver | void {
152
+ const {
153
+ shouldBlockFn,
154
+ enableBeforeUnload = true,
155
+ disabled = false,
156
+ withResolver = false,
157
+ } = _resolveBlockerOpts(opts, condition)
158
+
159
+ const router = useRouter()
160
+ const { history } = router
40
161
 
41
162
  const [resolver, setResolver] = React.useState<BlockerResolver>({
42
163
  status: 'idle',
43
- proceed: () => {},
44
- reset: () => {},
164
+ current: undefined,
165
+ next: undefined,
166
+ action: undefined,
167
+ proceed: undefined,
168
+ reset: undefined,
45
169
  })
46
170
 
47
171
  React.useEffect(() => {
48
- const blockerFnComposed = async () => {
49
- // If a function is provided, it takes precedence over the promise blocker
50
- if (blockerFn) {
51
- return await blockerFn()
172
+ const blockerFnComposed = async (blockerFnArgs: BlockerFnArgs) => {
173
+ function getLocation(
174
+ location: HistoryLocation,
175
+ ): AnyShouldBlockFnLocation {
176
+ const parsedLocation = router.parseLocation(undefined, location)
177
+ const matchedRoutes = router.getMatchedRoutes(parsedLocation)
178
+ if (matchedRoutes.foundRoute === undefined) {
179
+ throw new Error(`No route found for location ${location.href}`)
180
+ }
181
+ return {
182
+ routeId: matchedRoutes.foundRoute.id,
183
+ fullPath: matchedRoutes.foundRoute.fullPath,
184
+ pathname: parsedLocation.pathname,
185
+ params: matchedRoutes.routeParams,
186
+ search: parsedLocation.search,
187
+ }
188
+ }
189
+
190
+ const current = getLocation(blockerFnArgs.currentLocation)
191
+ const next = getLocation(blockerFnArgs.nextLocation)
192
+
193
+ const shouldBlock = await shouldBlockFn({
194
+ action: blockerFnArgs.action,
195
+ current,
196
+ next,
197
+ })
198
+ if (!withResolver) {
199
+ return shouldBlock
200
+ }
201
+
202
+ if (!shouldBlock) {
203
+ return false
52
204
  }
53
205
 
54
206
  const promise = new Promise<boolean>((resolve) => {
55
207
  setResolver({
56
208
  status: 'blocked',
57
- proceed: () => resolve(true),
58
- reset: () => resolve(false),
209
+ current,
210
+ next,
211
+ action: blockerFnArgs.action,
212
+ proceed: () => resolve(false),
213
+ reset: () => resolve(true),
59
214
  })
60
215
  })
61
216
 
62
217
  const canNavigateAsync = await promise
63
-
64
218
  setResolver({
65
219
  status: 'idle',
66
- proceed: () => {},
67
- reset: () => {},
220
+ current: undefined,
221
+ next: undefined,
222
+ action: undefined,
223
+ proceed: undefined,
224
+ reset: undefined,
68
225
  })
69
226
 
70
227
  return canNavigateAsync
71
228
  }
72
229
 
73
- return !blockerCondition ? undefined : history.block(blockerFnComposed)
74
- }, [blockerFn, blockerCondition, history])
230
+ return disabled
231
+ ? undefined
232
+ : history.block({ blockerFn: blockerFnComposed, enableBeforeUnload })
233
+ }, [shouldBlockFn, enableBeforeUnload, disabled, withResolver, history])
75
234
 
76
235
  return resolver
77
236
  }
78
237
 
79
- export function Block({ blockerFn, condition, children }: PromptProps) {
80
- const resolver = useBlocker({ blockerFn, condition })
238
+ const _resolvePromptBlockerArgs = (
239
+ props: PromptProps | LegacyPromptProps,
240
+ ): UseBlockerOpts => {
241
+ if ('shouldBlockFn' in props) {
242
+ return { ...props }
243
+ }
244
+
245
+ const shouldBlock = Boolean(props.condition ?? true)
246
+ const fn = props.blockerFn
247
+
248
+ const _customBlockerFn = async () => {
249
+ if (shouldBlock && fn !== undefined) {
250
+ return await fn()
251
+ }
252
+ return shouldBlock
253
+ }
254
+
255
+ return {
256
+ shouldBlockFn: _customBlockerFn,
257
+ enableBeforeUnload: shouldBlock,
258
+ withResolver: fn === undefined,
259
+ }
260
+ }
261
+
262
+ export function Block<
263
+ TRouter extends AnyRouter = RegisteredRouter,
264
+ TWithResolver extends boolean = boolean,
265
+ >(opts: PromptProps<TRouter, TWithResolver>): React.ReactNode
266
+
267
+ /**
268
+ * @deprecated Use the UseBlockerOpts property instead
269
+ */
270
+ export function Block(opts: LegacyPromptProps): React.ReactNode
271
+
272
+ export function Block(opts: PromptProps | LegacyPromptProps): React.ReactNode {
273
+ const { children, ...rest } = opts
274
+ const args = _resolvePromptBlockerArgs(rest)
275
+
276
+ const resolver = useBlocker(args)
81
277
  return children
82
278
  ? typeof children === 'function'
83
- ? children(resolver)
279
+ ? children(resolver as any)
84
280
  : children
85
281
  : null
86
282
  }
87
283
 
88
- export type PromptProps = {
89
- blockerFn?: BlockerFn
284
+ type LegacyPromptProps = {
285
+ blockerFn?: LegacyBlockerFn
90
286
  condition?: boolean | any
91
- children?: ReactNode | (({ proceed, reset }: BlockerResolver) => ReactNode)
287
+ children?: React.ReactNode | ((params: BlockerResolver) => React.ReactNode)
288
+ }
289
+
290
+ type PromptProps<
291
+ TRouter extends AnyRouter = RegisteredRouter,
292
+ TWithResolver extends boolean = boolean,
293
+ TParams = TWithResolver extends true ? BlockerResolver<TRouter> : void,
294
+ > = UseBlockerOpts<TRouter, TWithResolver> & {
295
+ children?: React.ReactNode | ((params: TParams) => React.ReactNode)
92
296
  }