@storybook/tanstack-react 10.5.7 → 10.5.9

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.
@@ -9,6 +9,7 @@ import {
9
9
  var preview_exports = {};
10
10
  __export(preview_exports, {
11
11
  applyDecorators: () => applyDecorators,
12
+ beforeEach: () => beforeEach,
12
13
  loaders: () => loaders,
13
14
  optimizeDeps: () => optimizeDeps,
14
15
  parameters: () => parameters
@@ -16,16 +17,16 @@ __export(preview_exports, {
16
17
  import { applyDecorators as reactApplyDecorators } from "@storybook/react/entry-preview-docs";
17
18
 
18
19
  // src/routing/decorator.tsx
19
- import React from "react";
20
20
  import {
21
21
  createMemoryHistory,
22
22
  createRootRoute,
23
23
  createRoute as createRoute2,
24
24
  createRouter,
25
- RouterProvider,
25
+ defaultStringifySearch,
26
26
  interpolatePath,
27
- defaultStringifySearch
27
+ RouterProvider
28
28
  } from "@tanstack/react-router";
29
+ import React from "react";
29
30
 
30
31
  // src/routing/duplicate-tree.ts
31
32
  import {
@@ -54,9 +55,12 @@ function initSourceTree(route, counter) {
54
55
  for (let child of children)
55
56
  initSourceTree(child, counter);
56
57
  }
58
+ function layoutIdFor(id) {
59
+ return id.length > 1 && id.endsWith("/") ? id.slice(0, -1) : id;
60
+ }
57
61
  function cloneChild(oldRoute, parent, overrides, byId) {
58
62
  let options = oldRoute.options ?? {}, { id: originalId, getParentRoute: _g, ...rest } = options, override = getOverrideFor(overrides, oldRoute.id), { id: overrideId, ...overrideRest } = override, merged = { ...rest, ...overrideRest }, explicitId = "id" in override ? overrideId : originalId, cloned = createRoute({
59
- ...!merged.path && explicitId != null ? { id: explicitId } : {},
63
+ ...!merged.path && explicitId != null ? { id: layoutIdFor(explicitId) } : {},
60
64
  ...merged,
61
65
  getParentRoute: () => parent
62
66
  });
@@ -133,17 +137,15 @@ var StoryContext = React.createContext({ Story: () => null }), StoryFromContext
133
137
  return React.createElement(Story, null);
134
138
  }, tanstackRouteDecorator = (Story, context) => React.createElement(TanStackRouterStory, { Story, context });
135
139
  function TanStackRouterStory({ Story, context }) {
136
- let routerContext = context.parameters.tanstack?.router?.useRouterContext?.({
140
+ let router = context.tanstackRouter, routerContext = context.parameters.tanstack?.router?.useRouterContext?.({
137
141
  storyContext: context
138
- }), router = React.useMemo(
139
- () => createStoryRouter({ Story: StoryFromContext, context, routerContext }),
140
- // eslint-disable-next-line react-hooks/exhaustive-deps
141
- [context.id]
142
- ), providerContext = React.useMemo(
143
- () => ({
144
- ...context.parameters.tanstack?.router?.context,
145
- ...routerContext
146
- }),
142
+ });
143
+ if (!router)
144
+ throw new Error(
145
+ "No story router found on the story context: the `routerBeforeEach` hook of @storybook/tanstack-react did not run before rendering. Note that portable stories are not supported by this framework."
146
+ );
147
+ let providerContext = React.useMemo(
148
+ () => routerContext,
147
149
  // eslint-disable-next-line react-hooks/exhaustive-deps
148
150
  [context.id, routerContext]
149
151
  );
@@ -242,6 +244,30 @@ function resolveTree(Story, context) {
242
244
  };
243
245
  }
244
246
 
247
+ // src/routing/before-each.ts
248
+ var storyRouters = /* @__PURE__ */ new Map(), routerBeforeEach = async (context) => {
249
+ let cached = storyRouters.get(context.id);
250
+ if (cached) {
251
+ context.tanstackRouter = cached;
252
+ return;
253
+ }
254
+ let storyContext = context, parameterContext = (context.parameters.tanstack?.router ?? {}).context, routerContext = typeof parameterContext == "function" ? parameterContext({ storyContext }) : parameterContext, router = createStoryRouter({
255
+ Story: StoryFromContext,
256
+ context: storyContext,
257
+ routerContext
258
+ }), load = router.load();
259
+ if (context.abortSignal && !context.abortSignal.aborted ? (load.catch(() => {
260
+ }), await Promise.race([
261
+ load,
262
+ new Promise(
263
+ (resolve) => context.abortSignal.addEventListener("abort", () => resolve(), { once: !0 })
264
+ )
265
+ ])) : await load, !context.abortSignal?.aborted)
266
+ return storyRouters.set(context.id, router), context.tanstackRouter = router, () => {
267
+ storyRouters.delete(context.id);
268
+ };
269
+ };
270
+
245
271
  // src/routing/loader.ts
246
272
  import { RootRoute as RootRoute3 } from "@tanstack/react-router";
247
273
  function getComponentFromRoute(route) {
@@ -262,7 +288,7 @@ var routeComponentLoader = (context) => {
262
288
  };
263
289
 
264
290
  // src/preview.tsx
265
- var loaders = [routeComponentLoader], applyDecorators = (storyFn, allDecorators) => (
291
+ var loaders = [routeComponentLoader], beforeEach = [routerBeforeEach], applyDecorators = (storyFn, allDecorators) => (
266
292
  // reorder decorators so `jsxDecorator` is innermost, and `tanstackRouteDecorator` is just outside it
267
293
  // There is an issue if `tanstackRouteDecorator` is innermost. All stories crashes due to a bug with the jsxDecorator.
268
294
  reactApplyDecorators(storyFn, [
@@ -279,6 +305,7 @@ var loaders = [routeComponentLoader], applyDecorators = (storyFn, allDecorators)
279
305
 
280
306
  export {
281
307
  loaders,
308
+ beforeEach,
282
309
  applyDecorators,
283
310
  parameters,
284
311
  optimizeDeps,
@@ -86,10 +86,11 @@ interface RouterParameters<TRoute = undefined, Path extends (TRoute extends AnyR
86
86
  * ```
87
87
  */
88
88
  routeOverrides?: RouteTreeOverrides;
89
- context?: Record<string, unknown>;
90
- /**
91
- *
92
- */
89
+ /** Object or factory; the factory runs before initial router load outside React, so loader and beforeLoad can read its values. */
90
+ context?: Record<string, unknown> | ((options: {
91
+ storyContext: Parameters<Decorator>[1];
92
+ }) => Record<string, unknown>);
93
+ /** React hook render context reaches components, not the initial loader or beforeLoad; use the context factory for loader-visible values. */
93
94
  useRouterContext?: ({
94
95
  storyContext
95
96
  }: {
@@ -334,7 +334,6 @@ declare const Link: ({
334
334
  children?: React.ReactNode;
335
335
  [key: string]: unknown;
336
336
  }) => React.DetailedReactHTMLElement<{
337
- href: string;
338
337
  onClick: (e: React.MouseEvent) => void;
339
338
  }, HTMLElement>;
340
339
  /**
@@ -38,15 +38,14 @@ var useNavigate = fn(_useNavigate).mockName("@tanstack/react-router::useNavigate
38
38
  children,
39
39
  ...props
40
40
  }) => {
41
- let location = useLocation();
41
+ let location = useLocation(), { onClick: _navigate, ...linkProps } = _useLinkProps({ to, ...props });
42
42
  return React.createElement(
43
43
  "a",
44
44
  {
45
- href: to,
45
+ ...linkProps,
46
46
  onClick: (e) => {
47
47
  e.preventDefault(), onNavigate({ to, from: location.href });
48
- },
49
- ...props
48
+ }
50
49
  },
51
50
  children
52
51
  );
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as TanStackPreviewOptions, c as IsRoute, d as StoryRouteOptions, i as TanStackParameters, l as RouterParameters, n as FrameworkOptions, o as TanStackTypes, r as StorybookConfig, s as CreateStoryRouteOptions, t as DefaultStoryPath, u as StoryRouteFileOptions } from "./chunk-CT6Edisg.js";
1
+ import { a as TanStackPreviewOptions, c as IsRoute, d as StoryRouteOptions, i as TanStackParameters, l as RouterParameters, n as FrameworkOptions, o as TanStackTypes, r as StorybookConfig, s as CreateStoryRouteOptions, t as DefaultStoryPath, u as StoryRouteFileOptions } from "./chunk-B0pqNVvJ.js";
2
2
  import { ComponentType } from "react";
3
3
  import { AddonTypes, InferTypes, PreviewAddon } from "storybook/internal/csf";
4
4
  import { Args, ArgsStoryFn, ComponentAnnotations, DecoratorFunction, Parameters, ProjectAnnotations, Renderer, StoryAnnotations } from "storybook/internal/types";
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  preview_exports
3
- } from "./_browser-chunks/chunk-6UULZFFJ.js";
3
+ } from "./_browser-chunks/chunk-AHXPC7UR.js";
4
4
  import "./_browser-chunks/chunk-LTDGLEVR.js";
5
5
  import "./_browser-chunks/chunk-4BE7D4DS.js";
6
6
 
@@ -1,4 +1,4 @@
1
- import { r as StorybookConfig } from "../chunk-CT6Edisg.js";
1
+ import { r as StorybookConfig } from "../chunk-B0pqNVvJ.js";
2
2
 
3
3
  //#region code/frameworks/tanstack-react/.dts-emit/code/frameworks/tanstack-react/src/node/index.d.ts
4
4
  declare function defineMain(config: StorybookConfig): StorybookConfig;
@@ -1,10 +1,10 @@
1
- import CJS_COMPAT_NODE_URL_4wc9cb9s2m3 from 'node:url';
2
- import CJS_COMPAT_NODE_PATH_4wc9cb9s2m3 from 'node:path';
3
- import CJS_COMPAT_NODE_MODULE_4wc9cb9s2m3 from "node:module";
1
+ import CJS_COMPAT_NODE_URL_14lcqg6ogjz from 'node:url';
2
+ import CJS_COMPAT_NODE_PATH_14lcqg6ogjz from 'node:path';
3
+ import CJS_COMPAT_NODE_MODULE_14lcqg6ogjz from "node:module";
4
4
 
5
- var __filename = CJS_COMPAT_NODE_URL_4wc9cb9s2m3.fileURLToPath(import.meta.url);
6
- var __dirname = CJS_COMPAT_NODE_PATH_4wc9cb9s2m3.dirname(__filename);
7
- var require = CJS_COMPAT_NODE_MODULE_4wc9cb9s2m3.createRequire(import.meta.url);
5
+ var __filename = CJS_COMPAT_NODE_URL_14lcqg6ogjz.fileURLToPath(import.meta.url);
6
+ var __dirname = CJS_COMPAT_NODE_PATH_14lcqg6ogjz.dirname(__filename);
7
+ var require = CJS_COMPAT_NODE_MODULE_14lcqg6ogjz.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_4wc9cb9s2m3 from 'node:url';
2
- import CJS_COMPAT_NODE_PATH_4wc9cb9s2m3 from 'node:path';
3
- import CJS_COMPAT_NODE_MODULE_4wc9cb9s2m3 from "node:module";
1
+ import CJS_COMPAT_NODE_URL_14lcqg6ogjz from 'node:url';
2
+ import CJS_COMPAT_NODE_PATH_14lcqg6ogjz from 'node:path';
3
+ import CJS_COMPAT_NODE_MODULE_14lcqg6ogjz from "node:module";
4
4
 
5
- var __filename = CJS_COMPAT_NODE_URL_4wc9cb9s2m3.fileURLToPath(import.meta.url);
6
- var __dirname = CJS_COMPAT_NODE_PATH_4wc9cb9s2m3.dirname(__filename);
7
- var require = CJS_COMPAT_NODE_MODULE_4wc9cb9s2m3.createRequire(import.meta.url);
5
+ var __filename = CJS_COMPAT_NODE_URL_14lcqg6ogjz.fileURLToPath(import.meta.url);
6
+ var __dirname = CJS_COMPAT_NODE_PATH_14lcqg6ogjz.dirname(__filename);
7
+ var require = CJS_COMPAT_NODE_MODULE_14lcqg6ogjz.createRequire(import.meta.url);
8
8
 
9
9
  // ------------------------------------------------------------
10
10
  // end of CJS compatibility banner, injected by Storybook's esbuild configuration
@@ -30,6 +30,17 @@ var dirname = function(p) {
30
30
  // src/preset.ts
31
31
  import { viteFinal as reactViteFinal } from "@storybook/react-vite/preset";
32
32
 
33
+ // src/plugins/incompatible-plugins.ts
34
+ var matchesPluginName = (p, matches) => {
35
+ if (Array.isArray(p))
36
+ return p.some((entry) => matchesPluginName(entry, matches));
37
+ let pluginRecord = p;
38
+ return typeof p == "object" && p !== null && "name" in pluginRecord && typeof pluginRecord.name == "string" && matches(pluginRecord.name);
39
+ }, isTanStackStartPlugin = (p) => matchesPluginName(p, (name) => name.startsWith("tanstack-start") || name.includes("rsc:")), isCloudflareVitePlugin = (p) => matchesPluginName(
40
+ p,
41
+ (name) => name === "vite-plugin-cloudflare" || name.startsWith("vite-plugin-cloudflare:")
42
+ );
43
+
33
44
  // src/plugins/server-code-elimination.ts
34
45
  import { types as t, transformSync } from "storybook/internal/babel";
35
46
  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([
@@ -413,17 +424,13 @@ var core = async (config, options) => {
413
424
  "@tanstack/react-router > @tanstack/react-store",
414
425
  "use-sync-external-store/shim/with-selector"
415
426
  ], viteFinal = async (config, options) => {
416
- let reactConfig = await reactViteFinal(config, options), isTanStackStartPlugin = (p) => {
417
- if (Array.isArray(p))
418
- return p.some(isTanStackStartPlugin);
419
- let pluginRecord = p;
420
- return typeof p == "object" && p !== null && "name" in pluginRecord && typeof pluginRecord.name == "string" && (pluginRecord.name.startsWith("tanstack-start") || pluginRecord.name.includes("rsc:"));
421
- }, startMockPath = fileURLToPath(import.meta.resolve("./export-mocks/start.js")), startStorageContextMockPath = fileURLToPath(
427
+ let reactConfig = await reactViteFinal(config, options), startMockPath = fileURLToPath(import.meta.resolve("./export-mocks/start.js")), startStorageContextMockPath = fileURLToPath(
422
428
  import.meta.resolve("./export-mocks/start-storage-context.js")
423
429
  ), routerMockPath = fileURLToPath(
424
430
  import.meta.resolve("@storybook/tanstack-react/react-router")
425
431
  ), plugins = [
426
- ...(reactConfig.plugins ?? []).filter((p) => !isTanStackStartPlugin(p)),
432
+ // Drop user plugins that are incompatible with Storybook — see ./plugins/incompatible-plugins.ts
433
+ ...(reactConfig.plugins ?? []).filter((p) => !isTanStackStartPlugin(p) && !isCloudflareVitePlugin(p)),
427
434
  serverCodeEliminationPlugin({ excludeFiles: [dirname(startMockPath)] }),
428
435
  serverOnlyStubPlugin(),
429
436
  moduleInterceptionPlugin({ startMockPath, startStorageContextMockPath, routerMockPath })
package/dist/preview.d.ts CHANGED
@@ -1,11 +1,12 @@
1
- import { i as TanStackParameters } from "./chunk-CT6Edisg.js";
2
- import { DecoratorFunction, LoaderFunction, Renderer } from "storybook/internal/types";
1
+ import { i as TanStackParameters } from "./chunk-B0pqNVvJ.js";
2
+ import { BeforeEach, DecoratorFunction, LoaderFunction, Renderer } from "storybook/internal/types";
3
3
  import { applyDecorators as applyDecorators$1 } from "@storybook/react/entry-preview-docs";
4
4
 
5
5
  //#region code/frameworks/tanstack-react/.dts-emit/code/frameworks/tanstack-react/src/preview.d.ts
6
6
  declare const loaders: LoaderFunction<Renderer>[];
7
+ declare const beforeEach: BeforeEach<Renderer>[];
7
8
  declare const applyDecorators: (storyFn: Parameters<typeof applyDecorators$1>[0], allDecorators: DecoratorFunction[]) => any;
8
9
  declare const parameters: TanStackParameters;
9
10
  declare const optimizeDeps: string[];
10
11
  //#endregion
11
- export { applyDecorators, loaders, optimizeDeps, parameters };
12
+ export { applyDecorators, beforeEach, loaders, optimizeDeps, parameters };
package/dist/preview.js CHANGED
@@ -1,13 +1,15 @@
1
1
  import {
2
2
  applyDecorators,
3
+ beforeEach,
3
4
  loaders,
4
5
  optimizeDeps,
5
6
  parameters
6
- } from "./_browser-chunks/chunk-6UULZFFJ.js";
7
+ } from "./_browser-chunks/chunk-AHXPC7UR.js";
7
8
  import "./_browser-chunks/chunk-LTDGLEVR.js";
8
9
  import "./_browser-chunks/chunk-4BE7D4DS.js";
9
10
  export {
10
11
  applyDecorators,
12
+ beforeEach,
11
13
  loaders,
12
14
  optimizeDeps,
13
15
  parameters
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@storybook/tanstack-react",
3
- "version": "10.5.7",
3
+ "version": "10.5.9",
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.7",
79
- "@storybook/react": "10.5.7",
80
- "@storybook/react-vite": "10.5.7"
78
+ "@storybook/builder-vite": "10.5.9",
79
+ "@storybook/react": "10.5.9",
80
+ "@storybook/react-vite": "10.5.9"
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.7",
98
+ "storybook": "^10.5.9",
99
99
  "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
100
100
  },
101
101
  "peerDependenciesMeta": {
@@ -0,0 +1,45 @@
1
+ import React from 'react';
2
+
3
+ import type { Meta, StoryObj } from '@storybook/tanstack-react';
4
+
5
+ import { expect, within } from 'storybook/test';
6
+
7
+ const loaderCalls: unknown[] = [];
8
+
9
+ function LoaderContextViewer() {
10
+ return <p data-testid="loader-context">{String(loaderCalls[0] ?? 'missing')}</p>;
11
+ }
12
+
13
+ const meta = {
14
+ component: LoaderContextViewer,
15
+ parameters: {
16
+ tanstack: {
17
+ router: {
18
+ context: () => {
19
+ loaderCalls.length = 0;
20
+ return { injected: 'from-factory' };
21
+ },
22
+ route: {
23
+ loader: ({ context }: { context: unknown }) => {
24
+ loaderCalls.push((context as any)?.injected ?? null);
25
+ },
26
+ },
27
+ },
28
+ },
29
+ },
30
+ } satisfies Meta<typeof LoaderContextViewer>;
31
+
32
+ export default meta;
33
+
34
+ type Story = StoryObj<typeof meta>;
35
+
36
+ /** Factory router context is visible to the route loader before render. */
37
+ export const FactoryValuesReachLoader: Story = {
38
+ play: async ({ canvasElement }) => {
39
+ const canvas = within(canvasElement);
40
+ await expect(canvas.getByTestId('loader-context')).toHaveTextContent('from-factory');
41
+
42
+ await expect(loaderCalls.length).toBeGreaterThan(0);
43
+ await expect(loaderCalls.every((call) => call === 'from-factory')).toBe(true);
44
+ },
45
+ };
@@ -0,0 +1,45 @@
1
+ import React from 'react';
2
+
3
+ import type { Meta, StoryObj } from '@storybook/tanstack-react';
4
+
5
+ import { useRouter } from '@tanstack/react-router';
6
+ import { expect, within } from 'storybook/test';
7
+
8
+ const InjectedContext = React.createContext('missing-provider');
9
+
10
+ function RouterContextViewer() {
11
+ const router = useRouter();
12
+ const injected = (router.options.context as { injected?: string } | undefined)?.injected;
13
+ return <p data-testid="injected-router-context">{injected ?? 'missing-context'}</p>;
14
+ }
15
+
16
+ const meta = {
17
+ component: RouterContextViewer,
18
+ decorators: [
19
+ (Story) => (
20
+ <InjectedContext.Provider value="from-react-hook">
21
+ <Story />
22
+ </InjectedContext.Provider>
23
+ ),
24
+ ],
25
+ parameters: {
26
+ tanstack: {
27
+ router: {
28
+ useRouterContext: () => ({ injected: React.useContext(InjectedContext) }),
29
+ },
30
+ },
31
+ },
32
+ } satisfies Meta<typeof RouterContextViewer>;
33
+
34
+ export default meta;
35
+
36
+ type Story = StoryObj<typeof meta>;
37
+
38
+ /** Hook-derived values read from a decorator's React provider must reach the story's router. */
39
+ export const HookValuesReachRouter: Story = {
40
+ play: async ({ canvasElement }) => {
41
+ await expect(within(canvasElement).getByTestId('injected-router-context')).toHaveTextContent(
42
+ 'from-react-hook'
43
+ );
44
+ },
45
+ };