@storybook/tanstack-react 10.5.0 → 10.5.2

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,6 +1,6 @@
1
1
  import {
2
2
  normalizeFileRoutePath
3
- } from "./chunk-XKNIZ3MM.js";
3
+ } from "./chunk-LTDGLEVR.js";
4
4
  import {
5
5
  __export
6
6
  } from "./chunk-4BE7D4DS.js";
@@ -28,7 +28,12 @@ import {
28
28
  } from "@tanstack/react-router";
29
29
 
30
30
  // src/routing/duplicate-tree.ts
31
- import { createRoute, RootRoute, createRootRouteWithContext } from "@tanstack/react-router";
31
+ import {
32
+ createRoute,
33
+ RootRoute,
34
+ createRootRouteWithContext,
35
+ joinPaths
36
+ } from "@tanstack/react-router";
32
37
  var MAX_PARENT_WALK = 50;
33
38
  function findRootRoute(route) {
34
39
  let current = route;
@@ -50,9 +55,9 @@ function initSourceTree(route, counter) {
50
55
  initSourceTree(child, counter);
51
56
  }
52
57
  function cloneChild(oldRoute, parent, overrides, byId) {
53
- let options = oldRoute.options ?? {}, { id: _id, getParentRoute: _g, ...rest } = options, override = getOverrideFor(overrides, oldRoute.id), cloned = createRoute({
54
- ...rest,
55
- ...override,
58
+ let options = oldRoute.options ?? {}, { id: originalId, getParentRoute: _g, ...rest } = options, override = getOverrideFor(overrides, oldRoute.id), merged = { ...rest, ...override }, cloned = createRoute({
59
+ ...!merged.path && originalId != null ? { id: originalId } : {},
60
+ ...merged,
56
61
  getParentRoute: () => parent
57
62
  });
58
63
  byId.set(oldRoute.id, cloned);
@@ -81,6 +86,16 @@ function duplicateRouteTree(rootRoute, { overrides } = {}) {
81
86
  }
82
87
  return { root: newRoot, byId };
83
88
  }
89
+ function mountPathFor(route) {
90
+ let segments = [], current = route;
91
+ for (let i = 0; i < MAX_PARENT_WALK && current; i += 1) {
92
+ let routePath = current.options?.path;
93
+ routePath && routePath !== "/" && segments.unshift(routePath);
94
+ let getParent = current.options?.getParentRoute;
95
+ current = typeof getParent == "function" ? getParent() : void 0;
96
+ }
97
+ return joinPaths(["/", ...segments]);
98
+ }
84
99
  function resolveStoryLeaf(tree, { path, boundRouteId }) {
85
100
  let { root, byId } = tree;
86
101
  if (path) {
@@ -134,7 +149,7 @@ function createStoryRouter({
134
149
  context,
135
150
  routerContext
136
151
  }) {
137
- let routerParameters = context.parameters.tanstack?.router ?? {}, { tree, leaf } = resolveTree(Story, context), routeTree = tree.root, inferredPath = routerParameters?.path || leaf.fullPath || (leaf.id ? normalizeFileRoutePath(leaf.id) : void 0) || routeTree.children?.[0]?.fullPath || "/", resolvedPath = interpolatePath({
152
+ let routerParameters = context.parameters.tanstack?.router ?? {}, { tree, leaf } = resolveTree(Story, context), routeTree = tree.root, inferredPath = routerParameters?.path || leaf.fullPath || (leaf.id ? normalizeFileRoutePath(leaf.id) : void 0) || mountPathFor(leaf), resolvedPath = interpolatePath({
138
153
  path: inferredPath,
139
154
  params: routerParameters?.params ?? {}
140
155
  }).interpolatedPath, search = routerParameters?.query ? defaultStringifySearch(routerParameters.query) : "";
@@ -154,6 +169,24 @@ function createStoryRouter({
154
169
  context: routerContext
155
170
  });
156
171
  }
172
+ function ensureMatchableLeaf(tree, leaf) {
173
+ if (!(leaf !== tree.root && !leaf.options?.path && leaf.options?.id != null))
174
+ return leaf;
175
+ let existingIndexChild = (leaf.children ?? []).find(
176
+ (child) => child.options?.path === "/"
177
+ );
178
+ if (existingIndexChild)
179
+ return existingIndexChild;
180
+ let syntheticLeaf = createRoute2({
181
+ path: "/",
182
+ component: () => null,
183
+ getParentRoute: () => leaf
184
+ });
185
+ return leaf.addChildren([
186
+ ...leaf.children ?? [],
187
+ syntheticLeaf
188
+ ]), syntheticLeaf;
189
+ }
157
190
  function injectStoryComponent(leaf, Story, overrides, leafId) {
158
191
  let userOverride = overrides?.[leafId];
159
192
  userOverride && "component" in userOverride && userOverride.component !== void 0 || leaf.update({ component: () => React.createElement(Story, null) });
@@ -165,7 +198,9 @@ function resolveTree(Story, context) {
165
198
  path: routerParameters.path,
166
199
  boundRouteId: resolvedRoute && resolvedRoute !== rootRoute ? resolvedRoute.id : void 0
167
200
  });
168
- return injectStoryComponent(leaf, Story, routeOverrides, leaf.id), { tree, leaf };
201
+ injectStoryComponent(leaf, Story, routeOverrides, leaf.id);
202
+ let renderLeaf = ensureMatchableLeaf(tree, leaf);
203
+ return { tree, leaf: renderLeaf };
169
204
  }
170
205
  if (isRoute(routerParameterRoute)) {
171
206
  let syntheticRoot2 = createRootRoute(
@@ -173,7 +208,9 @@ function resolveTree(Story, context) {
173
208
  );
174
209
  routerParameterRoute.update({ getParentRoute: () => syntheticRoot2 }), syntheticRoot2.addChildren([routerParameterRoute]);
175
210
  let tree = duplicateRouteTree(syntheticRoot2, { overrides: routeOverrides }), leaf = tree.byId.get(routerParameterRoute.id) ?? tree.root;
176
- return injectStoryComponent(leaf, Story, routeOverrides, leaf.id), { tree, leaf };
211
+ injectStoryComponent(leaf, Story, routeOverrides, leaf.id);
212
+ let renderLeaf = ensureMatchableLeaf(tree, leaf);
213
+ return { tree, leaf: renderLeaf };
177
214
  }
178
215
  let plainOptions = routerParameterRoute ?? {}, {
179
216
  path: plainRoutePath,
@@ -13,7 +13,15 @@ function removeUnderscores(s) {
13
13
  function normalizeFileRoutePath(path) {
14
14
  return removeGroups(removeUnderscores(removeLayoutSegments(path)) ?? "") || "/";
15
15
  }
16
+ function isPathlessSegment(segment) {
17
+ return segment.startsWith("_") || segment.startsWith("(") && segment.endsWith(")");
18
+ }
19
+ function isPathlessFileRouteId(id) {
20
+ let segments = id.split("/").filter(Boolean), lastSegment = segments[segments.length - 1];
21
+ return lastSegment != null && isPathlessSegment(lastSegment);
22
+ }
16
23
 
17
24
  export {
18
- normalizeFileRoutePath
25
+ normalizeFileRoutePath,
26
+ isPathlessFileRouteId
19
27
  };
@@ -1,6 +1,7 @@
1
1
  import {
2
+ isPathlessFileRouteId,
2
3
  normalizeFileRoutePath
3
- } from "../_browser-chunks/chunk-XKNIZ3MM.js";
4
+ } from "../_browser-chunks/chunk-LTDGLEVR.js";
4
5
  import {
5
6
  onNavigate
6
7
  } from "../_browser-chunks/chunk-4CKCBIXI.js";
@@ -51,17 +52,20 @@ var useNavigate = fn(_useNavigate).mockName("@tanstack/react-router::useNavigate
51
52
  );
52
53
  };
53
54
  function createFileRoute(path) {
54
- let normalizedPath = normalizeFileRoutePath(path);
55
- return (options) => createRoute({
56
- path: normalizedPath,
57
- ...options,
58
- isRoot: !1
59
- }).update({
60
- id: path,
61
- path: normalizedPath,
62
- fullPath: normalizedPath
63
- // any because tanstack router does that
64
- });
55
+ return (options) => {
56
+ let pathless = isPathlessFileRouteId(path) && options?.path == null, routePath = options?.path ?? normalizeFileRoutePath(path);
57
+ return createRoute({
58
+ ...pathless ? { id: path } : { path: routePath },
59
+ ...options,
60
+ isRoot: !1
61
+ }).update({
62
+ // routeTree.gen re-updates these later; set them here so route files
63
+ // imported without the generated tree still carry their identity
64
+ id: path,
65
+ ...pathless ? {} : { path: routePath, fullPath: routePath }
66
+ // any because tanstack router does that
67
+ });
68
+ };
65
69
  }
66
70
  export {
67
71
  Link,
@@ -108,6 +108,10 @@ declare const Link: ({ to, children, ...props }: {
108
108
  declare const Navigate: ({ to }: {
109
109
  to: string;
110
110
  }) => null;
111
+ declare const Hydrate: ({ children }: {
112
+ children?: React.ReactNode;
113
+ [key: string]: unknown;
114
+ }) => string | number | boolean | React.ReactElement<any, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null;
111
115
  declare const notFound: () => never;
112
116
  declare const createStart: () => {};
113
117
  declare const clearCookieStore: () => void;
@@ -117,4 +121,4 @@ declare const _default: {
117
121
  fetch: () => Promise<Response>;
118
122
  };
119
123
 
120
- export { HEADERS, Link, Navigate, StartServer, VIRTUAL_MODULES, attachRouterServerSsrUtils, clearCookieStore, clearResponseHeaders, clearSession, createRequestHandler, createServerFn, createStart, createStartHandler, _default as default, defaultRenderHandler, defaultStreamHandler, defineHandlerCallback, deleteCookie, fetchHandler as fetch, getCookie, getCookies, getRequest, getRequestHeader, getRequestHeaders, getRequestHost, getRequestIP, getRequestProtocol, getRequestUrl, getResponse, getResponseHeader, getResponseHeaders, getResponseStatus, getSession, getValidatedQuery, notFound, removeResponseHeader, requestHandler, sealSession, setCookie, setResponseHeader, setResponseHeaders, setResponseStatus, transformPipeableStreamWithRouter, transformReadableStreamWithRouter, unsealSession, updateSession, useServerFn, useSession };
124
+ export { HEADERS, Hydrate, Link, Navigate, StartServer, VIRTUAL_MODULES, attachRouterServerSsrUtils, clearCookieStore, clearResponseHeaders, clearSession, createRequestHandler, createServerFn, createStart, createStartHandler, _default as default, defaultRenderHandler, defaultStreamHandler, defineHandlerCallback, deleteCookie, fetchHandler as fetch, getCookie, getCookies, getRequest, getRequestHeader, getRequestHeaders, getRequestHost, getRequestIP, getRequestProtocol, getRequestUrl, getResponse, getResponseHeader, getResponseHeaders, getResponseStatus, getSession, getValidatedQuery, notFound, removeResponseHeader, requestHandler, sealSession, setCookie, setResponseHeader, setResponseHeaders, setResponseStatus, transformPipeableStreamWithRouter, transformReadableStreamWithRouter, unsealSession, updateSession, useServerFn, useSession };
@@ -262,7 +262,7 @@ function useServerFn(serverFn) {
262
262
  }
263
263
  function createMockServerFnBuilder() {
264
264
  let builder = () => createMockServerFnBuilder(), _storedOptions;
265
- return builder.options = (opts) => (_storedOptions = opts, builder), builder.middleware = () => createMockServerFnBuilder(), builder.inputValidator = () => createMockServerFnBuilder(), builder.handler = (handlerFn) => {
265
+ return builder.options = (opts) => (_storedOptions = opts, builder), builder.middleware = () => createMockServerFnBuilder(), builder.inputValidator = () => createMockServerFnBuilder(), builder.validator = () => createMockServerFnBuilder(), builder.handler = (handlerFn) => {
266
266
  let mock = fn().mockName("@tanstack/start-client-core::createServerFn.handler()");
267
267
  return handlerFn && mock.mockImplementation(async (opts) => handlerFn(opts)), mock;
268
268
  }, builder._getOptions = () => _storedOptions, builder;
@@ -276,7 +276,7 @@ var createServerFn = (options) => {
276
276
  ...props
277
277
  }) => React.createElement("a", { href: to, ...props }, children), Navigate = ({ to }) => (React.useEffect(() => {
278
278
  onNavigate({ to });
279
- }, [to]), null), notFound = () => {
279
+ }, [to]), null), Hydrate = ({ children }) => children ?? null, notFound = () => {
280
280
  throw new Error("Not found");
281
281
  }, createStart = () => ({}), clientCookieStore = /* @__PURE__ */ new Map(), clearCookieStore = () => {
282
282
  clientCookieStore.clear();
@@ -284,6 +284,7 @@ var createServerFn = (options) => {
284
284
  var start_default = { fetch: fetchHandler };
285
285
  export {
286
286
  HEADERS,
287
+ Hydrate,
287
288
  Link,
288
289
  Navigate,
289
290
  StartServer,
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  preview_exports
3
- } from "./_browser-chunks/chunk-WQHJSC2D.js";
4
- import "./_browser-chunks/chunk-XKNIZ3MM.js";
3
+ } from "./_browser-chunks/chunk-4W5ZAMIP.js";
4
+ import "./_browser-chunks/chunk-LTDGLEVR.js";
5
5
  import "./_browser-chunks/chunk-4BE7D4DS.js";
6
6
 
7
7
  // src/index.ts
@@ -1,10 +1,10 @@
1
- import CJS_COMPAT_NODE_URL_0p8wnllw6x9 from 'node:url';
2
- import CJS_COMPAT_NODE_PATH_0p8wnllw6x9 from 'node:path';
3
- import CJS_COMPAT_NODE_MODULE_0p8wnllw6x9 from "node:module";
1
+ import CJS_COMPAT_NODE_URL_zl58m7drvbc from 'node:url';
2
+ import CJS_COMPAT_NODE_PATH_zl58m7drvbc from 'node:path';
3
+ import CJS_COMPAT_NODE_MODULE_zl58m7drvbc from "node:module";
4
4
 
5
- var __filename = CJS_COMPAT_NODE_URL_0p8wnllw6x9.fileURLToPath(import.meta.url);
6
- var __dirname = CJS_COMPAT_NODE_PATH_0p8wnllw6x9.dirname(__filename);
7
- var require = CJS_COMPAT_NODE_MODULE_0p8wnllw6x9.createRequire(import.meta.url);
5
+ var __filename = CJS_COMPAT_NODE_URL_zl58m7drvbc.fileURLToPath(import.meta.url);
6
+ var __dirname = CJS_COMPAT_NODE_PATH_zl58m7drvbc.dirname(__filename);
7
+ var require = CJS_COMPAT_NODE_MODULE_zl58m7drvbc.createRequire(import.meta.url);
8
8
 
9
9
  // ------------------------------------------------------------
10
10
  // end of CJS compatibility banner, injected by Storybook's esbuild configuration
package/dist/preset.js CHANGED
@@ -1,10 +1,10 @@
1
- import CJS_COMPAT_NODE_URL_0p8wnllw6x9 from 'node:url';
2
- import CJS_COMPAT_NODE_PATH_0p8wnllw6x9 from 'node:path';
3
- import CJS_COMPAT_NODE_MODULE_0p8wnllw6x9 from "node:module";
1
+ import CJS_COMPAT_NODE_URL_zl58m7drvbc from 'node:url';
2
+ import CJS_COMPAT_NODE_PATH_zl58m7drvbc from 'node:path';
3
+ import CJS_COMPAT_NODE_MODULE_zl58m7drvbc from "node:module";
4
4
 
5
- var __filename = CJS_COMPAT_NODE_URL_0p8wnllw6x9.fileURLToPath(import.meta.url);
6
- var __dirname = CJS_COMPAT_NODE_PATH_0p8wnllw6x9.dirname(__filename);
7
- var require = CJS_COMPAT_NODE_MODULE_0p8wnllw6x9.createRequire(import.meta.url);
5
+ var __filename = CJS_COMPAT_NODE_URL_zl58m7drvbc.fileURLToPath(import.meta.url);
6
+ var __dirname = CJS_COMPAT_NODE_PATH_zl58m7drvbc.dirname(__filename);
7
+ var require = CJS_COMPAT_NODE_MODULE_zl58m7drvbc.createRequire(import.meta.url);
8
8
 
9
9
  // ------------------------------------------------------------
10
10
  // end of CJS compatibility banner, injected by Storybook's esbuild configuration
@@ -31,7 +31,7 @@ var dirname = function(p) {
31
31
  import { viteFinal as reactViteFinal } from "@storybook/react-vite/preset";
32
32
 
33
33
  // src/plugins/server-code-elimination.ts
34
- import { transformSync, types as t } from "storybook/internal/babel";
34
+ import { types as t, transformSync } from "storybook/internal/babel";
35
35
  var SERVER_FN_RE = /\bcreateServerFn\b/, MIDDLEWARE_RE = /\bcreateMiddleware\b/, ISOMORPHIC_FN_RE = /\bcreateIsomorphicFn\b/, SERVER_ONLY_FN_RE = /\bcreateServerOnlyFn\b/, CLIENT_ONLY_FN_RE = /\bcreateClientOnlyFn\b/, ROUTE_FACTORY_RE = /\b(createFileRoute|createRootRoute|createRootRouteWithContext|createRoute)\b/, ROUTE_FACTORIES = /* @__PURE__ */ new Set([
36
36
  "createFileRoute",
37
37
  "createRootRoute",
@@ -215,12 +215,21 @@ function stripServerOption(options) {
215
215
  return (t.isIdentifier(key) ? key.name : void 0) !== "server";
216
216
  }), options.properties.length !== initialLength;
217
217
  }
218
+ function getJsxRootIdentifier(name) {
219
+ for (; t.isJSXMemberExpression(name); )
220
+ name = name.object;
221
+ return t.isJSXIdentifier(name) ? name : null;
222
+ }
218
223
  function collectReferencedIdentifiers(programPath) {
219
224
  let referenced = /* @__PURE__ */ new Set();
220
225
  return programPath.traverse({
221
226
  enter(path2) {
222
227
  let { node } = path2;
223
228
  !t.isIdentifier(node) || path2.isBindingIdentifier() || path2.findParent((p) => p.isImportDeclaration()) || referenced.add(node.name);
229
+ },
230
+ JSXOpeningElement(path2) {
231
+ let root = getJsxRootIdentifier(path2.node.name);
232
+ root && referenced.add(root.name);
224
233
  }
225
234
  }), referenced;
226
235
  }
package/dist/preview.js CHANGED
@@ -3,8 +3,8 @@ import {
3
3
  loaders,
4
4
  optimizeDeps,
5
5
  parameters
6
- } from "./_browser-chunks/chunk-WQHJSC2D.js";
7
- import "./_browser-chunks/chunk-XKNIZ3MM.js";
6
+ } from "./_browser-chunks/chunk-4W5ZAMIP.js";
7
+ import "./_browser-chunks/chunk-LTDGLEVR.js";
8
8
  import "./_browser-chunks/chunk-4BE7D4DS.js";
9
9
  export {
10
10
  applyDecorators,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@storybook/tanstack-react",
3
- "version": "10.5.0",
3
+ "version": "10.5.2",
4
4
  "description": "Storybook for TanStack (React, Vite): Router and Start ready Storybook framework",
5
5
  "keywords": [
6
6
  "storybook",
@@ -75,9 +75,9 @@
75
75
  "!src/**/*"
76
76
  ],
77
77
  "dependencies": {
78
- "@storybook/builder-vite": "10.5.0",
79
- "@storybook/react": "10.5.0",
80
- "@storybook/react-vite": "10.5.0"
78
+ "@storybook/builder-vite": "10.5.2",
79
+ "@storybook/react": "10.5.2",
80
+ "@storybook/react-vite": "10.5.2"
81
81
  },
82
82
  "devDependencies": {
83
83
  "@tanstack/react-router": "^1.168.10",
@@ -95,7 +95,7 @@
95
95
  "@tanstack/start-client-core": "^1.167.9",
96
96
  "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
97
97
  "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
98
- "storybook": "^10.5.0",
98
+ "storybook": "^10.5.2",
99
99
  "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
100
100
  },
101
101
  "peerDependenciesMeta": {
@@ -0,0 +1,150 @@
1
+ import React from 'react';
2
+
3
+ import type { Meta, StoryObj } from '@storybook/tanstack-react';
4
+
5
+ import { Outlet, createRootRoute, createRoute } from '@tanstack/react-router';
6
+ import { expect, fn, within } from 'storybook/test';
7
+
8
+ // Regression coverage for pathless layout routes (`_authed`-style underscore
9
+ // files and `(group)` directories). The layout must render and its
10
+ // `beforeLoad` must run, while contributing nothing to the URL: the story
11
+ // mounts at `/dashboard`, not `/_authed/dashboard`.
12
+
13
+ function LeafContent() {
14
+ return <p data-testid="pathless-leaf">leaf rendered under a pathless layout</p>;
15
+ }
16
+
17
+ const onBeforeLoad = fn();
18
+
19
+ const RootRoute = createRootRoute({
20
+ component: () => (
21
+ <div data-testid="pathless-root">
22
+ <h1>root layout</h1>
23
+ <Outlet />
24
+ </div>
25
+ ),
26
+ });
27
+
28
+ const PathlessLayout = createRoute({
29
+ id: 'authed',
30
+ getParentRoute: () => RootRoute,
31
+ beforeLoad: onBeforeLoad,
32
+ component: () => (
33
+ <section data-testid="pathless-layout">
34
+ <Outlet />
35
+ </section>
36
+ ),
37
+ });
38
+
39
+ const DashboardRoute = createRoute({
40
+ path: '/dashboard',
41
+ getParentRoute: () => PathlessLayout,
42
+ component: LeafContent,
43
+ });
44
+
45
+ PathlessLayout.addChildren([DashboardRoute]);
46
+
47
+ // Regression coverage for a pathless layout nested under a pathful ancestor
48
+ // (e.g. `/products/_authed`): the layout's mount URL must be inferred from
49
+ // its pathful ancestor, not just '/'.
50
+
51
+ const onNestedBeforeLoad = fn();
52
+
53
+ function ProductsLayout() {
54
+ return (
55
+ <div data-testid="products-layout">
56
+ <Outlet />
57
+ </div>
58
+ );
59
+ }
60
+
61
+ const ProductsRoute = createRoute({
62
+ path: '/products',
63
+ getParentRoute: () => RootRoute,
64
+ component: ProductsLayout,
65
+ });
66
+
67
+ const NestedPathlessLayout = createRoute({
68
+ id: 'nested-authed',
69
+ getParentRoute: () => ProductsRoute,
70
+ beforeLoad: onNestedBeforeLoad,
71
+ component: () => (
72
+ <section data-testid="nested-pathless-layout">
73
+ <Outlet />
74
+ </section>
75
+ ),
76
+ });
77
+
78
+ const NestedSettingsRoute = createRoute({
79
+ path: '/settings',
80
+ getParentRoute: () => NestedPathlessLayout,
81
+ component: LeafContent,
82
+ });
83
+
84
+ NestedPathlessLayout.addChildren([NestedSettingsRoute]);
85
+ ProductsRoute.addChildren([NestedPathlessLayout]);
86
+
87
+ // A single `addChildren` call per parent: it replaces (not appends to) the
88
+ // children array, so `RootRoute`'s two top-level branches must be registered
89
+ // together.
90
+ RootRoute.addChildren([PathlessLayout, ProductsRoute]);
91
+
92
+ const meta = {
93
+ component: LeafContent,
94
+ parameters: { layout: 'fullscreen' },
95
+ } satisfies Meta<typeof LeafContent>;
96
+
97
+ export default meta;
98
+
99
+ type Story = StoryObj<typeof meta>;
100
+
101
+ /** The leaf mounts at the layout-free URL, with every ancestor layout rendered. */
102
+ export const RendersLeafThroughPathlessLayout: Story = {
103
+ parameters: {
104
+ tanstack: { router: { route: DashboardRoute, path: '/dashboard' } },
105
+ },
106
+ play: async ({ canvasElement }) => {
107
+ const canvas = within(canvasElement);
108
+ await expect(canvas.getByTestId('pathless-root')).toBeInTheDocument();
109
+ await expect(canvas.getByTestId('pathless-layout')).toBeInTheDocument();
110
+ await expect(canvas.getByTestId('pathless-leaf')).toBeInTheDocument();
111
+ await expect(onBeforeLoad).toHaveBeenCalled();
112
+ },
113
+ };
114
+
115
+ /** Binding the layout route itself (no path) mounts the story at the layout's position. */
116
+ export const BoundDirectlyToPathlessLayout: Story = {
117
+ parameters: {
118
+ tanstack: { router: { route: PathlessLayout } },
119
+ },
120
+ play: async ({ canvasElement }) => {
121
+ const canvas = within(canvasElement);
122
+ // injectStoryComponent replaces the layout's own `component` with the
123
+ // story's render (LeafContent), so `pathless-layout` itself is gone —
124
+ // assert the root layout plus the leaf content that now renders in its place.
125
+ await expect(canvas.getByTestId('pathless-root')).toBeInTheDocument();
126
+ await expect(canvas.getByTestId('pathless-leaf')).toBeInTheDocument();
127
+ },
128
+ };
129
+
130
+ /**
131
+ * A pathless layout nested under a pathful ancestor (`/products`). Binding
132
+ * the story directly to the nested layout must mount it at `/products`
133
+ * (inferred from the ancestor's path), with the root and the `/products`
134
+ * ancestor both rendered around the story's own content.
135
+ */
136
+ export const BoundDirectlyToNestedPathlessLayout: Story = {
137
+ parameters: {
138
+ tanstack: { router: { route: NestedPathlessLayout } },
139
+ },
140
+ play: async ({ canvasElement }) => {
141
+ const canvas = within(canvasElement);
142
+ await expect(canvas.getByTestId('pathless-root')).toBeInTheDocument();
143
+ await expect(canvas.getByTestId('products-layout')).toBeInTheDocument();
144
+ // injectStoryComponent replaces the nested layout's own `component`, so
145
+ // `nested-pathless-layout` itself is gone — assert the leaf content that
146
+ // now renders in its place instead.
147
+ await expect(canvas.getByTestId('pathless-leaf')).toBeInTheDocument();
148
+ await expect(onNestedBeforeLoad).toHaveBeenCalled();
149
+ },
150
+ };