@octanejs/tanstack-router 0.2.3 → 0.2.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@octanejs/tanstack-router",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "engines": {
@@ -58,13 +58,13 @@
58
58
  "isbot": "^5.1.22"
59
59
  },
60
60
  "peerDependencies": {
61
- "octane": "^0.10.0"
61
+ "octane": "^0.11.0"
62
62
  },
63
63
  "devDependencies": {
64
64
  "@tanstack/react-router": "1.170.41",
65
- "@tsrx/react": "^0.3.7",
65
+ "@tsrx/react": "^0.4.5",
66
66
  "esbuild": "^0.28.1",
67
- "octane": "0.10.0",
67
+ "octane": "0.11.0",
68
68
  "react": "19.2.7",
69
69
  "react-dom": "19.2.7",
70
70
  "vitest": "^4.1.10"
package/src/Asset.tsrx CHANGED
@@ -4,11 +4,10 @@ import type { RouterManagedTag } from '@tanstack/router-core';
4
4
  import { useRouter } from './context.ts';
5
5
  import { useHydrated } from './ClientOnly.tsrx';
6
6
 
7
- export type AssetProps =
8
- RouterManagedTag & {
9
- assetKey: string;
10
- target: 'head' | 'body';
11
- };
7
+ export type AssetProps = RouterManagedTag & {
8
+ assetKey: string;
9
+ target: 'head' | 'body';
10
+ };
12
11
 
13
12
  type ManagedAssetProps = AssetProps & { managedKey?: string };
14
13
 
@@ -40,8 +39,10 @@ function setAttributes(element: Element, attrs: RouterManagedTag['attrs']) {
40
39
  }
41
40
  for (const [name, value] of Object.entries(attrs)) {
42
41
  if (
43
- name === 'suppressHydrationWarning' || value === undefined || value === null ||
44
- value === false
42
+ name === 'suppressHydrationWarning' ||
43
+ value === undefined ||
44
+ value === null ||
45
+ value === false
45
46
  ) {
46
47
  continue;
47
48
  }
@@ -67,8 +68,7 @@ function claim(element: Element) {
67
68
  }
68
69
 
69
70
  function mountAsset(props: ManagedAssetProps) {
70
- const parent =
71
- props.target === 'head' ? document.head : document.body;
71
+ const parent = props.target === 'head' ? document.head : document.body;
72
72
  const managedKey = props.managedKey ?? props.assetKey;
73
73
  const existing = findManagedElement(parent, managedKey);
74
74
  if (existing) {
@@ -95,23 +95,19 @@ export function Asset(props: AssetProps) @{
95
95
  // every navigation, and an identity dependency would remove and re-add
96
96
  // unchanged assets such as the stylesheet.
97
97
  const attrsKey = JSON.stringify(props.attrs ?? null);
98
- useLayoutEffect(() => mountAsset(props), [
99
- props.assetKey,
100
- managedKey,
101
- props.tag,
102
- attrsKey,
103
- props.children,
104
- props.target,
105
- ]);
98
+ useLayoutEffect(
99
+ () => mountAsset(props),
100
+ [props.assetKey, managedKey, props.tag, attrsKey, props.children, props.target],
101
+ );
106
102
  }
107
103
 
108
104
  const visibleTag =
109
- (isServer ?? router.isServer) || props.target === 'body' && !hydrated ? props.tag : undefined;
105
+ (isServer ?? router.isServer) || (props.target === 'body' && !hydrated) ? props.tag : undefined;
110
106
 
111
107
  @switch (visibleTag) {
112
108
  @case 'title': {
113
109
  <title {...props.attrs} data-tsr-managed-key={managedKey}>
114
- {props.children ?? '' as string}
110
+ {props.children ?? ('' as string)}
115
111
  </title>
116
112
  }
117
113
  @case 'meta': {
package/src/Await.tsrx CHANGED
@@ -20,8 +20,6 @@ export function Await(props: {
20
20
  @try {
21
21
  <AwaitInner promise={props.promise} render={props.children} />
22
22
  } @pending {
23
- <>
24
- {props.fallback}
25
- </>
23
+ <>{props.fallback}</>
26
24
  }
27
25
  }
@@ -24,9 +24,7 @@ export function CatchBoundary(props: {
24
24
  onCatch?: (error: Error, errorInfo: ErrorInfo) => void;
25
25
  }) @{
26
26
  @try {
27
- <>
28
- {props.children}
29
- </>
27
+ <>{props.children}</>
30
28
  } @catch (error, reset) {
31
29
  <CatchErrorView
32
30
  error={error}
@@ -55,8 +53,7 @@ function CatchErrorView(props: {
55
53
 
56
54
  // Reset when the reset key rolls (navigation). Skip the mount render — the key
57
55
  // captured while catching is the baseline.
58
- const resetKey =
59
- props.getResetKey ? props.getResetKey() : undefined;
56
+ const resetKey = props.getResetKey ? props.getResetKey() : undefined;
60
57
  const keyRef = useRef(resetKey);
61
58
  useLayoutEffect(() => {
62
59
  if (keyRef.current !== resetKey) {
@@ -93,9 +90,7 @@ export function ErrorComponent(props: {
93
90
  <div style="height:.25rem"></div>
94
91
  @if (show) {
95
92
  <div>
96
- <pre
97
- style="font-size:.7em;border:1px solid red;border-radius:.25rem;padding:.3rem;color:red;overflow:auto"
98
- >
93
+ <pre style="font-size:.7em;border:1px solid red;border-radius:.25rem;padding:.3rem;color:red;overflow:auto">
99
94
  @if (props.error?.message) {
100
95
  <code>{props.error.message as string}</code>
101
96
  }
@@ -13,12 +13,15 @@ const subscribe = () => () => {};
13
13
 
14
14
  export function useHydrated(...args: Array<unknown>) {
15
15
  const [, slot] = splitSlot(args);
16
- return useSyncExternalStore(subscribe, () => true, () => false, subSlot(slot, 'hydrated'));
16
+ return useSyncExternalStore(
17
+ subscribe,
18
+ () => true,
19
+ () => false,
20
+ subSlot(slot, 'hydrated'),
21
+ );
17
22
  }
18
23
 
19
24
  export function ClientOnly(props: { children?: OctaneNode; fallback?: OctaneNode }) @{
20
25
  const hydrated = useHydrated();
21
- <>
22
- {hydrated ? props.children : props.fallback ?? null}
23
- </>
26
+ <>{hydrated ? props.children : (props.fallback ?? null)}</>
24
27
  }
package/src/Link.tsrx CHANGED
@@ -23,18 +23,20 @@ export function Link<
23
23
  TMaskFrom extends string = TFrom,
24
24
  TMaskTo extends string = '',
25
25
  >(props: LinkComponentProps<'a', TRouter, TFrom, TTo, TMaskFrom, TMaskTo>) @{
26
- const { _asChild: AsChild, children, ...rest } = props as
27
- typeof props & {
28
- _asChild?: any;
29
- };
26
+ const {
27
+ _asChild: AsChild,
28
+ children,
29
+ ...rest
30
+ } = props as typeof props & {
31
+ _asChild?: any;
32
+ };
30
33
  const linkProps = useLinkProps(
31
34
  rest as UseLinkPropsOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,
32
- ) as
33
- OctaneAnchorProps & {
34
- disabled?: boolean;
35
- 'data-status'?: string;
36
- 'data-transitioning'?: string;
37
- };
35
+ ) as OctaneAnchorProps & {
36
+ disabled?: boolean;
37
+ 'data-status'?: string;
38
+ 'data-transitioning'?: string;
39
+ };
38
40
  const { disabled: _disabled, ...aProps } = linkProps;
39
41
 
40
42
  const resolvedChildren =
@@ -48,6 +50,6 @@ export function Link<
48
50
  @if (AsChild) {
49
51
  <AsChild {...linkProps}>{resolvedChildren}</AsChild>
50
52
  } @else {
51
- <a {...aProps as Record<string, any>}>{resolvedChildren}</a>
53
+ <a {...(aProps as Record<string, any>)}>{resolvedChildren}</a>
52
54
  }
53
55
  }
package/src/Match.tsrx CHANGED
@@ -52,15 +52,16 @@ export function Match(props: { routeId: string }) @{
52
52
  const routeErrorComponent = route.options.errorComponent ?? router.options.defaultErrorComponent;
53
53
  const routeOnCatch = route.options.onCatch ?? router.options.defaultOnCatch;
54
54
  const routeNotFoundComponent = route.isRoot
55
- ? route.options.notFoundComponent ?? router.options.notFoundRoute?.options.component
55
+ ? (route.options.notFoundComponent ?? router.options.notFoundRoute?.options.component)
56
56
  : route.options.notFoundComponent;
57
57
  const resolvedNoSsr = matchState.ssr === false || matchState.ssr === 'data-only';
58
58
 
59
59
  // Boundary presence, per upstream MatchView. The root route only gets a
60
60
  // Suspense boundary when explicitly opted in via wrapInSuspense.
61
61
  const suspenseSignal =
62
- route.options.wrapInSuspense ?? PendingComponent ??
63
- (routeErrorComponent as ErrorRouteComponent | undefined)?.preload;
62
+ route.options.wrapInSuspense ??
63
+ PendingComponent ??
64
+ (routeErrorComponent as ErrorRouteComponent | undefined)?.preload;
64
65
  const SuspenseWrap =
65
66
  (!route.isRoot || route.options.wrapInSuspense || resolvedNoSsr) &&
66
67
  (suspenseSignal || resolvedNoSsr)
@@ -69,11 +70,10 @@ export function Match(props: { routeId: string }) @{
69
70
  const CatchWrap = routeErrorComponent ? CatchBoundary : SafeFragment;
70
71
  const NotFoundWrap = routeNotFoundComponent ? CatchNotFound : SafeFragment;
71
72
  const ShellComponent = route.isRoot
72
- ? (route.options as RootRouteOptions).shellComponent ?? SafeFragment
73
+ ? ((route.options as RootRouteOptions).shellComponent ?? SafeFragment)
73
74
  : SafeFragment;
74
75
 
75
- const pendingElement =
76
- PendingComponent ? createElement(PendingComponent, {}) : null;
76
+ const pendingElement = PendingComponent ? createElement(PendingComponent, {}) : null;
77
77
  const parentRouteId = route.parentRoute?.id;
78
78
 
79
79
  <ShellComponent>
@@ -95,8 +95,9 @@ export function Match(props: { routeId: string }) @{
95
95
  (error as any).routeId ??= routeId;
96
96
 
97
97
  if (
98
- !routeNotFoundComponent || error.routeId && error.routeId !== routeId ||
99
- !error.routeId && !route.isRoot
98
+ !routeNotFoundComponent ||
99
+ (error.routeId && error.routeId !== routeId) ||
100
+ (!error.routeId && !route.isRoot)
100
101
  ) {
101
102
  throw error;
102
103
  }
@@ -129,19 +130,18 @@ function MatchInner(props: { routeId: string }) {
129
130
 
130
131
  const Comp = route.options.component ?? router.options.defaultComponent;
131
132
  const remountFn = route.options.remountDeps ?? router.options.defaultRemountDeps;
132
- const remountDeps =
133
- remountFn
134
- ? remountFn({
135
- routeId,
136
- loaderDeps: match.loaderDeps,
137
- params: match._strictParams,
138
- search: match._strictSearch,
139
- })
140
- : undefined;
141
- const key =
142
- remountDeps ? JSON.stringify(remountDeps) : undefined;
143
- const out =
144
- Comp ? createElement(Comp, key !== undefined ? { key } : {}) : createElement(Outlet, {});
133
+ const remountDeps = remountFn
134
+ ? remountFn({
135
+ routeId,
136
+ loaderDeps: match.loaderDeps,
137
+ params: match._strictParams,
138
+ search: match._strictSearch,
139
+ })
140
+ : undefined;
141
+ const key = remountDeps ? JSON.stringify(remountDeps) : undefined;
142
+ const out = Comp
143
+ ? createElement(Comp, key !== undefined ? { key } : {})
144
+ : createElement(Outlet, {});
145
145
 
146
146
  if (match.status === 'pending') {
147
147
  // router-core owns the pending lifecycle (pendingMs, pendingMinMs, redirects
@@ -13,10 +13,13 @@ export function useMatchRoute() {
13
13
  useStore(router.stores.location, (l: any) => l.href as string);
14
14
  useStore(router.stores.resolvedLocation, (l: any) => l?.href as string | undefined);
15
15
  useStore(router.stores.status, (s: any) => s as string);
16
- return useCallback((opts: Record<string, any>) => {
17
- const { pending, caseSensitive, fuzzy, includeSearch, ...rest } = opts;
18
- return router.matchRoute(rest as any, { pending, caseSensitive, fuzzy, includeSearch });
19
- }, [router]);
16
+ return useCallback(
17
+ (opts: Record<string, any>) => {
18
+ const { pending, caseSensitive, fuzzy, includeSearch, ...rest } = opts;
19
+ return router.matchRoute(rest as any, { pending, caseSensitive, fuzzy, includeSearch });
20
+ },
21
+ [router],
22
+ );
20
23
  }
21
24
 
22
25
  // Props are permissive (`Record<string, any>` + optional render-prop children) —
@@ -31,7 +34,5 @@ export function MatchRoute(props: Record<string, any>) @{
31
34
  : params
32
35
  ? props.children
33
36
  : null;
34
- <>
35
- {out}
36
- </>
37
+ <>{out}</>
37
38
  }
package/src/Matches.tsrx CHANGED
@@ -24,8 +24,7 @@ export function Matches() @{
24
24
  const rootRoute = (router.routesById as any)[rootRouteId];
25
25
  const PendingComponent =
26
26
  rootRoute.options.pendingComponent ?? router.options.defaultPendingComponent;
27
- const pendingElement =
28
- PendingComponent ? createElement(PendingComponent, {}) : null;
27
+ const pendingElement = PendingComponent ? createElement(PendingComponent, {}) : null;
29
28
  const InnerWrap = router.options.InnerWrap ?? SafeFragment;
30
29
 
31
30
  // `createRouter({ scrollRestoration: true })` wires scroll save/restore here, so
@@ -49,7 +48,7 @@ function MatchesInner() @{
49
48
  // navigation transition is pending, render the matches router-core expects
50
49
  // (`acknowledgement[0]`) so the committed tree equals what it awaits, then
51
50
  // settle that render acknowledgement from a layout effect once it lands.
52
- const acknowledgement: Array<any> = (router as any)._rendered ??= [];
51
+ const acknowledgement: Array<any> = ((router as any)._rendered ??= []);
53
52
  const matches = useStore(router.stores.matches, (m: any) => acknowledgement[0] ?? m);
54
53
  const rootMatch = matches[0];
55
54
  const routeId = rootMatch?.routeId as string | undefined;
package/src/Outlet.tsrx CHANGED
@@ -39,10 +39,8 @@ export function Outlet() @{
39
39
  // whose fallback is the router's defaultPendingComponent (react-router's
40
40
  // Outlet does the same) — the outermost pending UI for the initial load.
41
41
  const DefaultPending = router.options.defaultPendingComponent;
42
- const RootSuspense =
43
- routeId === rootRouteId ? Suspense : SafeFragment;
44
- const pendingElement =
45
- DefaultPending ? createElement(DefaultPending, {}) : null;
42
+ const RootSuspense = routeId === rootRouteId ? Suspense : SafeFragment;
43
+ const pendingElement = DefaultPending ? createElement(DefaultPending, {}) : null;
46
44
 
47
45
  @if (notFound.global) {
48
46
  <RouteNotFound routeId={routeId} error={notFound.error} />
@@ -16,7 +16,7 @@ export function RouteNotFound(props: { routeId: string; error?: any }) @{
16
16
  const NotFound = route.options.notFoundComponent ?? router.options.defaultNotFoundComponent;
17
17
 
18
18
  @if (NotFound) {
19
- <NotFound {...props.error ?? {}} />
19
+ <NotFound {...(props.error ?? {})} />
20
20
  } @else {
21
21
  <p>Not Found</p>
22
22
  }
@@ -14,11 +14,10 @@ import { SafeFragment } from './SafeFragment.tsrx';
14
14
  // Upstream's `RouterProps` is `Omit<RouterOptions<…>, 'context'> & { router }` —
15
15
  // extra props reconfigure the instance via `router.update()`. The octane binding
16
16
  // keeps the router-typed member strict and the option passthrough permissive.
17
- export type RouterProps =
18
- {
19
- router: AnyRouter;
20
- context?: Record<string, any>;
21
- } & Record<string, any>;
17
+ export type RouterProps = {
18
+ router: AnyRouter;
19
+ context?: Record<string, any>;
20
+ } & Record<string, any>;
22
21
 
23
22
  export function RouterContextProvider(props: RouterProps & { children?: OctaneNode }) @{
24
23
  const { router, children, ...rest } = props;
@@ -9,7 +9,5 @@
9
9
  // it stands in for Suspense/CatchBoundary/CatchNotFound/shellComponent at dynamic
10
10
  // boundary slots, so it must accept any boundary's props and render only children.
11
11
  export function SafeFragment(props: any) @{
12
- <>
13
- {props.children}
14
- </>
12
+ <>{props.children}</>
15
13
  }
@@ -33,19 +33,18 @@ export function useTransitioner() {
33
33
  const router = useRouter() as any;
34
34
  const [, forceRender] = useState<object>();
35
35
 
36
- const acknowledgement: Array<any> = router._rendered ??= [];
36
+ const acknowledgement: Array<any> = (router._rendered ??= []);
37
37
 
38
- router.startTransition = (fn: () => void, expected: unknown) => new Promise<boolean>((
39
- resolve,
40
- ) => {
41
- // A newer transition supersedes an unrendered older one.
42
- settleOwner(acknowledgement, false);
43
- acknowledgement.push(expected, resolve);
44
- // Force `MatchesInner` to re-read: it renders `acknowledgement[0]` and
45
- // settles this promise from a layout effect once that commit lands.
46
- forceRender({});
47
- startTransition(fn);
48
- });
38
+ router.startTransition = (fn: () => void, expected: unknown) =>
39
+ new Promise<boolean>((resolve) => {
40
+ // A newer transition supersedes an unrendered older one.
41
+ settleOwner(acknowledgement, false);
42
+ acknowledgement.push(expected, resolve);
43
+ // Force `MatchesInner` to re-read: it renders `acknowledgement[0]` and
44
+ // settles this promise from a layout effect once that commit lands.
45
+ forceRender({});
46
+ startTransition(fn);
47
+ });
49
48
 
50
49
  useLayoutEffect(() => {
51
50
  const unsub = router.history.subscribe(() => router.load());
@@ -58,16 +57,14 @@ export function useTransitioner() {
58
57
  // the swapped-in router's load.
59
58
  router.updateLatestLocation?.();
60
59
  const location = router.latestLocation;
61
- const nextLocation = router.buildLocation(
62
- {
63
- to: location.pathname,
64
- search: true,
65
- params: true,
66
- hash: true,
67
- state: true,
68
- _includeValidateSearch: true,
69
- } as any,
70
- );
60
+ const nextLocation = router.buildLocation({
61
+ to: location.pathname,
62
+ search: true,
63
+ params: true,
64
+ hash: true,
65
+ state: true,
66
+ _includeValidateSearch: true,
67
+ } as any);
71
68
  if (trimPathRight(location.publicHref) !== trimPathRight(nextLocation.publicHref)) {
72
69
  router.commitLocation({ ...nextLocation, replace: true, ignoreBlocker: true });
73
70
  return unsub;
@@ -76,7 +73,7 @@ export function useTransitioner() {
76
73
  const resolvedLocation = router.stores.resolvedLocation.get();
77
74
  if (
78
75
  resolvedLocation?.href === location.href &&
79
- resolvedLocation.state.__TSR_key === location.state.__TSR_key
76
+ resolvedLocation.state.__TSR_key === location.state.__TSR_key
80
77
  ) {
81
78
  // Already resolved (SSR hydration): emit `onRendered` for scroll
82
79
  // restoration and devtools. The match tree has already committed by the
@@ -40,11 +40,15 @@ export function CatchNotFound(props: {
40
40
  throw error;
41
41
  }
42
42
  }}
43
- errorComponent={(p: ErrorComponentProps) => NotFoundFallback({
44
- error: p.error,
45
- render: props.fallback,
46
- })}
47
- >{props.children}</CatchBoundary>
43
+ errorComponent={(p: ErrorComponentProps) =>
44
+ NotFoundFallback({
45
+ error: p.error,
46
+ render: props.fallback,
47
+ })
48
+ }
49
+ >
50
+ {props.children}
51
+ </CatchBoundary>
48
52
  }
49
53
 
50
54
  export function DefaultGlobalNotFound() @{
@@ -5,9 +5,7 @@
5
5
  // itself before React hydrates. Octane hydration claims the server's arm, so the
6
6
  // client takes the arm the server took and ScriptOnce adopts, then drops, the
7
7
  // server's script.
8
- import {
9
- getScrollRestorationScriptForRouter,
10
- } from '@tanstack/router-core/scroll-restoration-script';
8
+ import { getScrollRestorationScriptForRouter } from '@tanstack/router-core/scroll-restoration-script';
11
9
  import type { AnyRouter } from '@tanstack/router-core';
12
10
  import { useRouter } from './context.ts';
13
11
  import { ScriptOnce } from './ScriptOnce.tsrx';
@@ -16,7 +16,7 @@ const ready = {
16
16
  function getHydrationPromise(router: AnyRouter) {
17
17
  let promise = hydrationPromises.get(router);
18
18
  if (!promise) {
19
- promise = router.stores.ids.get().length ? ready as PromiseLike<void> : hydrate(router);
19
+ promise = router.stores.ids.get().length ? (ready as PromiseLike<void>) : hydrate(router);
20
20
  hydrationPromises.set(router, promise);
21
21
  }
22
22
  return promise;
@@ -108,8 +108,12 @@ export function useBlocker(
108
108
  opts?: UseBlockerOpts | LegacyBlockerOpts | LegacyBlockerFn,
109
109
  condition?: boolean | any,
110
110
  ): BlockerResolver {
111
- const { shouldBlockFn, enableBeforeUnload = true, disabled = false, withResolver = false } =
112
- _resolveBlockerOpts(opts, condition);
111
+ const {
112
+ shouldBlockFn,
113
+ enableBeforeUnload = true,
114
+ disabled = false,
115
+ withResolver = false,
116
+ } = _resolveBlockerOpts(opts, condition);
113
117
 
114
118
  const router = useRouter();
115
119
  const { history } = router;
@@ -182,23 +186,19 @@ export function useBlocker(
182
186
 
183
187
  // Upstream's PromptProps: the blocker options plus optional children (a render
184
188
  // prop receiving the resolver, or plain renderables).
185
- export type PromptProps =
186
- (UseBlockerOpts | LegacyBlockerOpts) & {
187
- children?: OctaneNode | ((params: BlockerResolver) => OctaneNode);
188
- };
189
+ export type PromptProps = (UseBlockerOpts | LegacyBlockerOpts) & {
190
+ children?: OctaneNode | ((params: BlockerResolver) => OctaneNode);
191
+ };
189
192
 
190
193
  // Declarative blocker: registers useBlocker and renders children (optionally a
191
194
  // render prop receiving the resolver).
192
195
  export function Block(props: PromptProps) @{
193
196
  const { children, ...rest } = props;
194
197
  const resolver = useBlocker(rest);
195
- const out =
196
- children
197
- ? typeof children === 'function' && !isChildrenBlock(children)
198
- ? children(resolver)
199
- : children
200
- : null;
201
- <>
202
- {out}
203
- </>
198
+ const out = children
199
+ ? typeof children === 'function' && !isChildrenBlock(children)
200
+ ? children(resolver)
201
+ : children
202
+ : null;
203
+ <>{out}</>
204
204
  }